From d042487dc118e494db2e2c1382310255c90ff544 Mon Sep 17 00:00:00 2001 From: Roshan Kumar Date: Tue, 28 Jul 2026 10:56:08 +0530 Subject: [PATCH 0001/1417] xfrm: iptfs: fix stack OOB read in iptfs_skb_reset_frag_walk() iptfs_skb_reset_frag_walk() advances to the fragment containing @offset with an unbounded loop: while (offset >= walk->past + walk->frags[walk->fragi].len) walk->past += walk->frags[walk->fragi++].len; walk->fragi is advanced and walk->frags[walk->fragi] is dereferenced without ever checking fragi against walk->nr_frags. When the requested offset is at or beyond the total length spanned by the walk's fragments, fragi runs past nr_frags and off the end of the fixed-size on-stack frags[MAX_SKB_FRAGS + 1] array, reading out-of-bounds stack memory. The two callers behave differently: iptfs_skb_add_frags() already guards against this with if (!walk->nr_frags || offset >= walk->total + walk->initial_offset) return len; but iptfs_skb_can_add_frags() has no such guard and calls iptfs_skb_reset_frag_walk() unconditionally, so it performs the out-of-range walk. Its own "fragi < walk->nr_frags" bound check runs only afterwards, too late to prevent the read. This is reachable from the receive path: a crafted IP-TFS (AGGFRAG) payload delivered to an IPTFS SA drives iptfs_reassem_cont() -> iptfs_skb_can_add_frags() with an offset past the fragment total, e.g.: BUG: KASAN: stack-out-of-bounds in iptfs_skb_reset_frag_walk+0x235/0x250 Read of size 4 at addr ffff888008ad7210 by task repro/345 iptfs_skb_reset_frag_walk+0x235/0x250 net/xfrm/xfrm_iptfs.c:392 iptfs_skb_can_add_frags+0x155/0x310 net/xfrm/xfrm_iptfs.c:420 iptfs_reassem_cont+0xcf8/0x1140 net/xfrm/xfrm_iptfs.c:902 iptfs_input_ordered+0x552/0x670 net/xfrm/xfrm_iptfs.c:1280 iptfs_input+0x3d6/0xde0 net/xfrm/xfrm_iptfs.c:1741 xfrm_input+0x282f/0x6140 net/xfrm/xfrm_input.c:700 xfrm4_esp_rcv+0x93/0x120 net/ipv4/xfrm4_protocol.c:104 ip_rcv+0x278/0x2d0 net/ipv4/ip_input.c:612 Give iptfs_skb_can_add_frags() the same up-front guard that iptfs_skb_add_frags() already has, so the walk is never entered with an out-of-range offset. When it triggers, the caller falls back to the existing linearize-and-copy path, which is safe. Fixes: 5f2b6a909574 ("xfrm: iptfs: add skb-fragment sharing code") Reported-by: Roshan Kumar Signed-off-by: Roshan Kumar Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_iptfs.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c index 597aedeac26eba..2ce15c472cc4db 100644 --- a/net/xfrm/xfrm_iptfs.c +++ b/net/xfrm/xfrm_iptfs.c @@ -416,6 +416,14 @@ static bool iptfs_skb_can_add_frags(const struct sk_buff *skb, if (skb_has_frag_list(skb) || skb->pp_recycle != walk->pp_recycle) return false; + /* Reject an @offset that is at or beyond the end of the walk's data + * before calling iptfs_skb_reset_frag_walk(), whose fragment-advance + * loop is otherwise unbounded and would index past walk->frags[]. + * This mirrors the guard already present in iptfs_skb_add_frags(). + */ + if (!walk->nr_frags || offset >= walk->total + walk->initial_offset) + return false; + /* Make offset relative to current frag after setting that */ offset = iptfs_skb_reset_frag_walk(walk, offset); From 89fefad9f971bc637fb22373078144f2563c4be9 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Thu, 30 Jul 2026 18:35:43 +0800 Subject: [PATCH 0002/1417] xfrm: serialize state GC with device state flush The deferred-device pass in xfrm_dev_state_flush() finds states under xfrm_state_dev_gc_lock, but drops the lock before calling xfrm_dev_state_free() because the driver callback may sleep. The device GC list does not hold an xfrm_state reference, so the state GC worker can destroy the same state concurrently. The race can proceed as follows: CPU 0 CPU 1 find x on the device GC list drop xfrm_state_dev_gc_lock read x->xso.dev xfrm_state_gc_destroy(x) xfrm_dev_state_free(x) xfrm_state_free(x) continue xfrm_dev_state_free(x) Both paths can invoke the driver callback and drop the device reference. CPU 0 can also access the xfrm_state after CPU 1 has freed it. KASAN reported: BUG: KASAN: slab-use-after-free in xfrm_dev_state_free+0x24c/0x2a0 Read of size 8 at addr ffff88810bbaa960 by task poc/102 Call Trace: xfrm_dev_state_free+0x24c/0x2a0 xfrm_dev_state_flush+0x353/0x400 xfrm_dev_event+0x26d/0x3a0 notifier_call_chain+0xc0/0x280 __dev_notify_flags+0x169/0x250 netif_change_flags+0xe7/0x160 dev_change_flags+0x96/0x220 devinet_ioctl+0x7f4/0x1880 Allocated by task 87: xfrm_state_alloc+0x1e/0x5c0 xfrm_add_sa+0xe7f/0x5820 xfrm_user_rcv_msg+0x4f3/0x940 Freed by task 57: kmem_cache_free+0xcb/0x3d0 xfrm_state_gc_task+0x4a8/0x650 process_one_work+0x63a/0x1070 Serialize xfrm_state destruction against the deferred-device pass with a mutex. Keep xfrm_state_dev_gc_lock limited to list operations and retain the existing callback and device-reference release ordering. Fixes: 07b87f9eea0c ("xfrm: Fix unregister netdevice hang on hardware offload.") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_state.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 36a4f6793edef2..de097bba803b70 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -226,6 +226,7 @@ static struct xfrm_state_afinfo __rcu *xfrm_state_afinfo[NPROTO]; static DEFINE_SPINLOCK(xfrm_state_gc_lock); static DEFINE_SPINLOCK(xfrm_state_dev_gc_lock); +static DEFINE_MUTEX(xfrm_state_gc_mutex); int __xfrm_state_delete(struct xfrm_state *x); @@ -632,8 +633,10 @@ static void xfrm_state_gc_task(struct work_struct *work) synchronize_rcu(); + mutex_lock(&xfrm_state_gc_mutex); hlist_for_each_entry_safe(x, tmp, &gc_list, gclist) xfrm_state_gc_destroy(x); + mutex_unlock(&xfrm_state_gc_mutex); } static enum hrtimer_restart xfrm_timer_handler(struct hrtimer *me) @@ -1000,6 +1003,7 @@ int xfrm_dev_state_flush(struct net *net, struct net_device *dev, bool task_vali out: spin_unlock_bh(&net->xfrm.xfrm_state_lock); + mutex_lock(&xfrm_state_gc_mutex); spin_lock_bh(&xfrm_state_dev_gc_lock); restart_gc: hlist_for_each_entry_safe(x, tmp, &xfrm_state_dev_gc_list, dev_gclist) { @@ -1014,6 +1018,7 @@ int xfrm_dev_state_flush(struct net *net, struct net_device *dev, bool task_vali } spin_unlock_bh(&xfrm_state_dev_gc_lock); + mutex_unlock(&xfrm_state_gc_mutex); xfrm_flush_gc(); From 42d100f5232f39b8ea7b00a7c2482325c7f032a4 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Fri, 31 Jul 2026 10:06:20 +0000 Subject: [PATCH 0003/1417] xfrm: add missing RCU read lock in xfrm_send_migrate_state() xfrm_nlmsg_multicast() requires the RCU read lock to be held because it safely dereferences the net->xfrm.nlsk pointer using rcu_dereference(). When it is called from xfrm_send_migrate_state(), the RCU read lock is not held, which triggers a suspicious RCU usage warning: WARNING: suspicious RCU usage net/xfrm/xfrm_user.c:1630 suspicious rcu_dereference_check() usage! Call Trace: lockdep_rcu_suspicious+0x13f/0x1d0 kernel/locking/lockdep.c:6876 xfrm_nlmsg_multicast+0x1d8/0x1f0 net/xfrm/xfrm_user.c:1630 xfrm_send_migrate_state+0x870/0xae0 net/xfrm/xfrm_user.c:3340 xfrm_do_migrate_state+0x1749/0x1e90 net/xfrm/xfrm_user.c:3507 xfrm_user_rcv_msg+0x7a8/0xf30 net/xfrm/xfrm_user.c:3907 Fix this by wrapping the xfrm_nlmsg_multicast() call in xfrm_send_migrate_state() with rcu_read_lock() and rcu_read_unlock(). Fixes: a9d155ea9b44 ("xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration") Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+c0e99a1aa85a286d7a3b@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c0e99a1aa85a286d7a3b Link: https://syzkaller.appspot.com/ai_job?id=8977f559-3a7e-4bb5-b4d6-1196956260b6 Signed-off-by: Aleksandr Nogikh Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_user.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index 6266a92cf30208..980dbeb5a57de6 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -3337,7 +3337,11 @@ static int xfrm_send_migrate_state(struct net *net, return err; } - return xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE); + rcu_read_lock(); + err = xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE); + rcu_read_unlock(); + + return err; } static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh, From dc33262be1fe43d0eb0b84fb58c6ed42e2f64a8c Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Mon, 3 Aug 2026 12:01:54 +0800 Subject: [PATCH 0004/1417] xfrm: iptfs: fix runt reassembly panic from short inner tot_len When the start of an inner packet is split across two outer packets such that fewer than 4 bytes land at the end of the first one, __input_process_payload() saves those bytes as a runt and skips the iplen/iphlen validation performed for in-place packets. When the continuation packet arrives, iptfs_reassem_cont() only requires the declared inner length to be >= sizeof(ra_runt) (6) before allocating the reassembly skb with that attacker-controlled length. However, __iptfs_iphlen() always returns the fixed minimum IP header size (20 for IPv4, 40 for IPv6), so for an inner IPv4 tot_len in [6, 19] the header-completion copy writes past the declared packet length, and the subsequent "ipremain -= copylen" underflows to ~4GB, leaving the payload copy length bounded only by blkoff (up to 64KB). At runtime the skb_put() tailroom check turns this into skb_over_panic(), i.e. an unprivileged kernel panic (DoS), reachable locally via userns+netns IPTFS SAs and remotely against IPTFS VPN gateways when the decrypted outer skb is linear (e.g. AF_PACKET taps, tun/tap delivery). Align the runt path with the normal path by requiring the declared inner length to cover at least the IP header size. This also subsumes the previous >= sizeof(ra_runt) check, since the minimum IP header is always larger than the runt buffer. This issue was found by the autokbug dynamic kernel fuzzer at Tencent Yunding Lab. Fixes: 075694765446 ("xfrm: iptfs: handle received fragmented inner packets") Reported-by: Henry Martin Signed-off-by: Henry Martin Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_iptfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c index 2ce15c472cc4db..6920940a35b49d 100644 --- a/net/xfrm/xfrm_iptfs.c +++ b/net/xfrm/xfrm_iptfs.c @@ -828,8 +828,8 @@ static u32 iptfs_reassem_cont(struct xfrm_iptfs_data *xtfs, u64 seq, * allocate an in progress skb */ ipremain = __iptfs_iplen(xtfs->ra_runt); - if (ipremain < sizeof(xtfs->ra_runt)) { - /* length has to be at least runtsize large */ + if (ipremain < __iptfs_iphlen(xtfs->ra_runt)) { + /* length has to be at least the IP header size */ XFRM_INC_STATS(xs_net(xtfs->x), LINUX_MIB_XFRMINIPTFSERROR); goto abandon; From 6973a21ee73c5567f883813c8ef414774b45892f Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Mon, 3 Aug 2026 21:28:58 +0800 Subject: [PATCH 0005/1417] ipv6: xfrm: use full sockets in local error paths xfrm6_local_rxpmtu() and xfrm6_local_error() dereference skb->sk as if it always pointed at a full IPv6 socket. That is not guaranteed. TCP SYN-ACK skbs can be owned by a TCP_NEW_SYN_RECV request_sock while the output path itself is driven by the full listener. If rerouting selects an IPv6 XFRM tunnel route with a lower MTU, the local PMTU/error handling path can reach these callbacks with that mini-socket still attached to the skb. The callbacks then miscast the request socket as a full inet/IPv6 socket and can read beyond the request_sock allocation when they access inet_sock or ipv6_pinfo state. Resolve the owner with skb_to_full_sk() in both callbacks and bail out when no full socket is attached. This matches the surrounding XFRM IPv6 PMTU/error logic, which already reasons about full sockets with skb_to_full_sk(). Fixes: dd767856a36e ("xfrm6: Don't call icmpv6_send on local error") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Steffen Klassert --- net/ipv6/xfrm6_output.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net/ipv6/xfrm6_output.c b/net/ipv6/xfrm6_output.c index 512bdaf136997a..44b221a09a0c87 100644 --- a/net/ipv6/xfrm6_output.c +++ b/net/ipv6/xfrm6_output.c @@ -19,7 +19,10 @@ void xfrm6_local_rxpmtu(struct sk_buff *skb, u32 mtu) { struct flowi6 fl6; - struct sock *sk = skb->sk; + struct sock *sk = skb_to_full_sk(skb); + + if (!sk) + return; fl6.flowi6_oif = sk->sk_bound_dev_if; fl6.daddr = ipv6_hdr(skb)->daddr; @@ -31,7 +34,10 @@ void xfrm6_local_error(struct sk_buff *skb, u32 mtu) { struct flowi6 fl6; const struct ipv6hdr *hdr; - struct sock *sk = skb->sk; + struct sock *sk = skb_to_full_sk(skb); + + if (!sk) + return; hdr = skb->encapsulation ? inner_ipv6_hdr(skb) : ipv6_hdr(skb); fl6.fl6_dport = inet_sk(sk)->inet_dport; From d1ebd9081879fd9ae9c8fb7e8928f19cc88ae320 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Tue, 4 Aug 2026 06:10:37 +0000 Subject: [PATCH 0006/1417] xfrm: fix compat ALLOCSPI request use-after-free xfrm_state_netlink() builds the ALLOCSPI response with dump_one_state(), which already calls alloc_compat() with the response skb and header. xfrm_alloc_userspi() then calls alloc_compat() again, but passes the original request skb and its header. For a compat request, the translator therefore interprets the 228-byte compat xfrm_userspi_info as the 232-byte native layout and reads four bytes past the declared payload. It also publishes the translated child through the request's frag_list. A multicast clone of the request shares skb_shared_info and can observe that child. xfrm_user_rcv_msg() frees it after the request handler returns, racing a compat receiver which may still be copying from it and resulting in a use-after-free. Remove the redundant conversion. The response keeps its correct compat translation from dump_one_state(), and no child is attached to the inbound request. Fixes: 5f3eea6b7e8f ("xfrm/compat: Attach xfrm dumps to 64=>32 bit translator") Assisted-by: Codex:gpt-5.6-sol Codex:gpt-5.5-cyber Signed-off-by: Kyle Zeng Co-developed-by: David Lee Signed-off-by: David Lee Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_user.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index 980dbeb5a57de6..a2587c7e796b4e 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -1877,7 +1877,6 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh, struct net *net = sock_net(skb->sk); struct xfrm_state *x; struct xfrm_userspi_info *p; - struct xfrm_translator *xtr; struct sk_buff *resp_skb; xfrm_address_t *daddr; int family; @@ -1943,17 +1942,6 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh, goto out; } - xtr = xfrm_get_translator(); - if (xtr) { - err = xtr->alloc_compat(skb, nlmsg_hdr(skb)); - - xfrm_put_translator(xtr); - if (err) { - kfree_skb(resp_skb); - goto out; - } - } - err = nlmsg_unicast(xfrm_net_nlsk(net, skb), resp_skb, NETLINK_CB(skb).portid); out: From d2f5082f9e84653fa1a9e8aebaaff23e688f5e19 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 7 Aug 2026 17:15:33 +0000 Subject: [PATCH 0007/1417] xfrm: add missing rcu_read_lock(), skb_dst_force() and dev_hold() for xfrm_trans_reinject() syzbot reported a suspicious RCU usage warning in ip6_pkt_drop(): WARNING: suspicious RCU usage in ip6_pkt_drop include/net/addrconf.h:389 suspicious rcu_dereference_check() usage! Call Trace: __in6_dev_get_safely include/net/addrconf.h:389 [inline] ip6_pkt_drop+0x596/0x610 net/ipv6/route.c:4620 ip6_pkt_discard+0x1c/0x30 net/ipv6/route.c:4651 xfrm_trans_reinject+0x324/0x630 net/xfrm/xfrm_input.c:806 process_one_work kernel/workqueue.c:3322 [inline] process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486 When commit 4f4920669d21 ("xfrm: Reinject transport-mode packets through workqueue") converted xfrm_trans_reinject from a tasklet to a workqueue, the reinjection loop ceased running in softirq context. Workqueue workers run in process context where local_bh_disable() does not enter an RCU read-side critical section under CONFIG_PREEMPT_RCU. Because finish callbacks (such as ip6_rcv_finish) expect to run under an RCU read lock (performing route lookups, l3mdev lookups, and accessing RCU-protected data structures), invoking them in workqueue context without rcu_read_lock() triggers RCU lockdep warnings. Furthermore, packets queued to the workqueue via xfrm_trans_queue_net() may carry non-refcounted (noref) dst entries (e.g. from ip_route_input_noref). Additionally, on netdevice unregistration, dst_dev_put() replaces dst->dev with blackhole_netdev, so dst entries do not keep skb->dev alive while queued in the workqueue. Fix these issues by: 1. Calling skb_dst_force(skb) in xfrm_trans_queue_net() while still in the caller's RCU section to ensure dst is reference-counted before queuing. 2. Holding a reference on skb->dev via dev_hold()/dev_put() across workqueue deferral so skb->dev remains valid during finish() callback processing. 3. Acquiring rcu_read_lock() around the finish callback invocation loop in xfrm_trans_reinject(). Fixes: 4f4920669d21 ("xfrm: Reinject transport-mode packets through workqueue") Reported-by: syzbot Signed-off-by: Eric Dumazet Cc: Steffen Klassert Cc: Liu Jian Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_input.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c index eecab337bd0a79..8f6109eada7eaa 100644 --- a/net/xfrm/xfrm_input.c +++ b/net/xfrm/xfrm_input.c @@ -800,12 +800,17 @@ static void xfrm_trans_reinject(struct work_struct *work) spin_unlock_bh(&trans->queue_lock); local_bh_disable(); + rcu_read_lock(); while ((skb = __skb_dequeue(&queue))) { struct net *net = XFRM_TRANS_SKB_CB(skb)->net; + struct net_device *dev = skb->dev; XFRM_TRANS_SKB_CB(skb)->finish(net, NULL, skb); + if (dev) + dev_put(dev); put_net(net); } + rcu_read_unlock(); local_bh_enable(); } @@ -821,12 +826,18 @@ int xfrm_trans_queue_net(struct net *net, struct sk_buff *skb, if (skb_queue_len(&trans->queue) >= READ_ONCE(net_hotdata.max_backlog)) return -ENOBUFS; + if (skb_dst(skb) && !skb_dst_force(skb)) + return -EHOSTUNREACH; + BUILD_BUG_ON(sizeof(struct xfrm_trans_cb) > sizeof(skb->cb)); hold_net = maybe_get_net(net); if (!hold_net) return -ENODEV; + if (skb->dev) + dev_hold(skb->dev); + XFRM_TRANS_SKB_CB(skb)->finish = finish; XFRM_TRANS_SKB_CB(skb)->net = hold_net; spin_lock_bh(&trans->queue_lock); From 2afb8dc1f4390f164db8352f8e685e126e9db566 Mon Sep 17 00:00:00 2001 From: Siwei Zhang Date: Thu, 30 Jul 2026 19:40:08 +0800 Subject: [PATCH 0008/1417] xfrm: use hlist_del_init_rcu for state_cache and state_cache_input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 14acf9652e56 ("xfrm: defensively unhash xfrm_state lists in __xfrm_state_delete") converted bydst/bysrc/byseq/byspi from hlist_del_rcu() to hlist_del_init_rcu() so that a second __xfrm_state_delete() on the same object becomes a no-op rather than a write through LIST_POISON pprev. It missed state_cache and state_cache_input, which kept hlist_del_rcu(): - hlist_del_rcu() leaves pprev = LIST_POISON2 (non-NULL), so hlist_unhashed() returns false. - hlist_del_init_rcu() leaves pprev = NULL, so hlist_unhashed() returns true. A second __xfrm_state_delete() therefore enters __hlist_del() on the already-deleted state_cache/state_cache_input nodes and does WRITE_ONCE(*pprev, next) through LIST_POISON2 — a write use-after-free once the slab is reused. The corruption can in turn cause a subsequent hlist_for_each_entry_rcu traversal to follow a dangling next pointer, producing the read use-after-free reported in xfrm_input_state_lookup(). Switch state_cache and state_cache_input to hlist_del_init_rcu() to match the other four lists, closing the write use-after-free and, with it, the read use-after-free it spawns. Assisted-by: CodeBuddy:GLM-5.2 Fixes: 0045e3d80613 ("xfrm: Cache used outbound xfrm states at the policy.") Fixes: 81a331a0e72d ("xfrm: Add an inbound percpu state cache.") Cc: stable@vger.kernel.org Signed-off-by: Siwei Zhang Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_state.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index de097bba803b70..e45aa1ed5b9659 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -826,9 +826,9 @@ int __xfrm_state_delete(struct xfrm_state *x) if (!hlist_unhashed(&x->byseq)) hlist_del_init_rcu(&x->byseq); if (!hlist_unhashed(&x->state_cache)) - hlist_del_rcu(&x->state_cache); + hlist_del_init_rcu(&x->state_cache); if (!hlist_unhashed(&x->state_cache_input)) - hlist_del_rcu(&x->state_cache_input); + hlist_del_init_rcu(&x->state_cache_input); if (!hlist_unhashed(&x->byspi)) hlist_del_init_rcu(&x->byspi); From f89416eb3db151170a6f3c6dfc5239d26cdce4d2 Mon Sep 17 00:00:00 2001 From: Maher Azzouzi Date: Mon, 17 Aug 2026 14:37:52 +0100 Subject: [PATCH 0009/1417] esp: downgrade zerocopy managed frags before mutating skb frags On the out-of-place output path (esp->inplace == false) ESP rewrites the skb frag array: esp_output_head() appends a trailer frag and esp_output_tail() replaces the frags with a destination page, both referenced with get_page(). When the skb carries zerocopy managed frags (SKBFL_MANAGED_FRAG_REFS) the payload frags are owned by the ubuf and must not be referenced or unreferenced individually, but ESP mutates the frag array without ever downgrading the skb. This breaks the managed-frag invariant two ways: - esp_ssg_unref() walks the source scatterlist and drops a page reference for every frag, including the ubuf-owned payload frags, pushing their refcount below the GUP pin bias while the pages are still pinned, i.e. a use-after-free of the zerocopy pages; - esp_output_tail() installs its destination page as frag 0 with get_page() but leaves SKBFL_MANAGED_FRAG_REFS set, so skb_release_data() takes the skip_unref branch and never drops that reference, leaking the x->xfrag page at packet rate. Fix this the way every other frag-mutating site does (__ip_append_data(), __ip6_append_data(), tcp_sendmsg_locked()) and call skb_zcopy_downgrade_managed() before ESP touches the frag array: it takes a real reference on each existing frag and clears SKBFL_MANAGED_FRAG_REFS, so the per-frag unref in esp_ssg_unref() and the frag release in skb_release_data() are both balanced and no mixed-ownership frag array is left behind. Fixes: 753f1ca4e1e5 ("net: introduce managed frags infrastructure") Signed-off-by: Maher Azzouzi Signed-off-by: Steffen Klassert --- net/ipv4/esp4.c | 6 ++++++ net/ipv6/esp6.c | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c index dfc81ee969ae03..faa48f5b973951 100644 --- a/net/ipv4/esp4.c +++ b/net/ipv4/esp4.c @@ -441,6 +441,12 @@ int esp_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info * esp->inplace = false; + /* Take real page refs and clear SKBFL_MANAGED_FRAG_REFS before + * we mutate the frag array, so the per-frag unref stays balanced + * for zerocopy managed frags (see __ip_append_data()). + */ + skb_zcopy_downgrade_managed(skb); + allocsize = ALIGN(tailen, L1_CACHE_BYTES); spin_lock_bh(&x->lock); diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c index 296b57926abb98..a3a3857eed98a9 100644 --- a/net/ipv6/esp6.c +++ b/net/ipv6/esp6.c @@ -470,6 +470,12 @@ int esp6_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info esp->inplace = false; + /* Take real page refs and clear SKBFL_MANAGED_FRAG_REFS before + * we mutate the frag array, so the per-frag unref stays balanced + * for zerocopy managed frags (see __ip_append_data()). + */ + skb_zcopy_downgrade_managed(skb); + allocsize = ALIGN(tailen, L1_CACHE_BYTES); spin_lock_bh(&x->lock); From 21efadc62272cabee9bec27777ae75d84a9ca8a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Tue, 18 Aug 2026 18:00:02 +0200 Subject: [PATCH 0010/1417] Input: adp5588-keys - cache GPIO state before registering the gpiochip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So as not to clobber any pre-programmed GPIO state in the execution of its gpiochip ops, the driver caches things during probe time. However, since those ops can be called both during and immediately after the call to devm_gpiochip_add_data(), it is imperative that things are cached before that. That's not the case right now, so reorder the two steps to prevent any clobbering. In the concrete example which motivated this change, a bootloader was preconfiguring an important GPIO output to HIGH before booting the kernel. Linux would then inadvertently set that output to LOW while configuring a GPIO hog on a discrete GPIO line within the same 8-bit bank (because the cached value was 0=LOW). Fixes: ba9f507a1bea ("Input: adp5588-keys - export unused GPIO pins") Signed-off-by: Alvin Šipraga Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260818-adp5588-gpio-cache-v1-1-650a2674fc0d@analog.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/adp5588-keys.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/input/keyboard/adp5588-keys.c b/drivers/input/keyboard/adp5588-keys.c index 40371f5bd9bac0..4f0ddff5babae6 100644 --- a/drivers/input/keyboard/adp5588-keys.c +++ b/drivers/input/keyboard/adp5588-keys.c @@ -446,12 +446,6 @@ static int adp5588_gpio_add(struct adp5588_kpad *kpad) mutex_init(&kpad->gpio_lock); - error = devm_gpiochip_add_data(dev, &kpad->gc, kpad); - if (error) { - dev_err(dev, "gpiochip_add failed: %d\n", error); - return error; - } - for (i = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++) { kpad->dat_out[i] = adp5588_read(kpad->client, GPIO_DAT_OUT1 + i); @@ -459,6 +453,12 @@ static int adp5588_gpio_add(struct adp5588_kpad *kpad) kpad->pull_dis[i] = adp5588_read(kpad->client, GPIO_PULL1 + i); } + error = devm_gpiochip_add_data(dev, &kpad->gc, kpad); + if (error) { + dev_err(dev, "gpiochip_add failed: %d\n", error); + return error; + } + return 0; } From 4d7fa28e151a6a6f24098d9e60f558f8ea61ddaa Mon Sep 17 00:00:00 2001 From: Roman Vivchar Date: Fri, 14 Aug 2026 16:13:52 +0300 Subject: [PATCH 0011/1417] dt-bindings: input: mediatek,mt6779-keypad: add mt6572 Add a compatible string for the mt6572 keypad, that is compatible with mt6779. Signed-off-by: Roman Vivchar Reviewed-by: Mattijs Korpershoek Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260814-6572-dt-keypad-v1-1-2ceff2373211@protonmail.com Signed-off-by: Dmitry Torokhov --- .../devicetree/bindings/input/mediatek,mt6779-keypad.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml b/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml index 914dd3283df330..20fa41f691ed61 100644 --- a/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml +++ b/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml @@ -26,6 +26,7 @@ properties: - const: mediatek,mt6779-keypad - items: - enum: + - mediatek,mt6572-keypad - mediatek,mt6873-keypad - mediatek,mt8183-keypad - mediatek,mt8365-keypad From 16b10f64c63f78220c3b4035f1ed6cd3bdcb0b02 Mon Sep 17 00:00:00 2001 From: Michail Tatas Date: Thu, 13 Aug 2026 20:36:50 +0300 Subject: [PATCH 0012/1417] gpiolib: Put fwnode reference on failure We get a reference to the fwnode handle which we pass to gpio_shared_make_ref. In case it fails we do not put the reference. Fix by putting the reference in the failure case Fixes: 49416483a953 ("gpio: shared: allow sharing a reset-gpios pin between reset-gpio and gpiolib") Cc: stable@vger.kernel.org Signed-off-by: Michail Tatas Link: https://patch.msgid.link/an4Asr4tx3D2QvLD@michalis-linux Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-shared.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c index 495bd3d0ddf02c..5f9623e40b0fbe 100644 --- a/drivers/gpio/gpiolib-shared.c +++ b/drivers/gpio/gpiolib-shared.c @@ -261,10 +261,13 @@ static int gpio_shared_of_traverse(struct device_node *curr) con_id[con_id_len - suffix_len] = '\0'; } - ref = gpio_shared_make_ref(fwnode_handle_get(of_fwnode_handle(curr)), - con_id, args.args[1]); - if (!ref) + struct fwnode_handle *curr_fwnode = + fwnode_handle_get(of_fwnode_handle(curr)); + ref = gpio_shared_make_ref(curr_fwnode, con_id, args.args[1]); + if (!ref) { + fwnode_handle_put(curr_fwnode); return -ENOMEM; + } if (!list_empty(&entry->refs)) pr_debug("GPIO %u at %s is shared by multiple firmware nodes\n", From aefbda23eeba234c3ff0f135dc5be6e294bd25a6 Mon Sep 17 00:00:00 2001 From: Alexei Turtanov <9alexei9@gmail.com> Date: Fri, 28 Aug 2026 14:22:39 +0300 Subject: [PATCH 0013/1417] Input: atkbd - skip deactivate for Xiaomi Redmi Book Pro 16 2026 The internal keyboard of the Xiaomi Redmi Book Pro 16 2026 (board TM2425) does not work: atkbd_probe() succeeds and every command is ACKed, but no scancodes ever arrive afterwards. Testing on the hardware through serio_raw shows that ATKBD_CMD_RESET_DIS (0xF5) is the culprit. After 0xF5 the embedded controller keeps ACKing commands but stops delivering scancodes, and neither ATKBD_CMD_ENABLE (0xF4) nor ATKBD_CMD_RESET_BAT (0xFF) bring them back. Only re-enabling the keyboard interface at the controller level (i8042 command 0xAE, or rewriting the command byte as i8042_port_close() does) revives it. Running the init sequence without 0xF5 (0xED 0x00, 0xF3 0x00, 0xF4) keeps the keyboard working. 'i8042.dumbkbd=1' also works around this, but then the driver never writes to the keyboard and the LEDs cannot be controlled. Use the existing atkbd_deactivate_fixup quirk instead, as done for the sibling TM2424 by commit 3a046db33bb9 ("Input: atkbd - skip deactivate for Xiaomi Book Pro 14's internal keyboard"). Tested on v7.2: keyboard, Caps Lock LED and s2idle suspend/resume all work. DMI: XIAOMI REDMI Book Pro 16 2026/TM2425, BIOS RMAPT6B0P0909 05/22/2026 Fixes: 9cf6e24c9fbf ("Input: atkbd - do not skip atkbd_deactivate() when skipping ATKBD_CMD_GETID") Cc: stable@vger.kernel.org Signed-off-by: Alexei Turtanov <9alexei9@gmail.com> Link: https://patch.msgid.link/20260828112239.18081-1-9alexei9@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/atkbd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index 5736f4bc5a50af..2627434099fffa 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -1946,6 +1946,14 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = { }, .callback = atkbd_deactivate_fixup, }, + { + /* Xiaomi Redmi Book Pro 16 2026 (TM2425) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "XIAOMI"), + DMI_MATCH(DMI_PRODUCT_NAME, "REDMI Book Pro 16 2026"), + }, + .callback = atkbd_deactivate_fixup, + }, { } }; From 51cfe54f815ae175c7d1126b983d4d7c89715004 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 4 Aug 2026 22:08:54 -0700 Subject: [PATCH 0014/1417] Input: rmi_smbus - fix out-of-bounds read in rmi_smb_write_block() When chunking writes into SMBus blocks in rmi_smb_write_block(), the loop calculates block_len using the original total length (len) instead of the remaining length (cur_len). If len is greater than 32 bytes (SMB_MAX_COUNT), block_len remains 32 for every iteration, even on the final partial chunk where fewer than 32 bytes remain. This causes smb_block_write() to read 32 bytes from the advanced data buffer pointer, reading past the end of the input buffer. Fix this by calculating block_len using cur_len and advancing the buffer and address pointers by block_len. Fixes: 82264d0cf7ae ("Input: synaptics-rmi4 - add SMBus support") Cc: stable@vger.kernel.org Reported-by: sashiko-bot@kernel.org Assisted-by: LLM Link: https://patch.msgid.link/anLFSMKSoKyyZ272@google.com Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_smbus.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/input/rmi4/rmi_smbus.c b/drivers/input/rmi4/rmi_smbus.c index 3160714a514a55..8b61c238229768 100644 --- a/drivers/input/rmi4/rmi_smbus.c +++ b/drivers/input/rmi4/rmi_smbus.c @@ -140,7 +140,7 @@ static int rmi_smb_write_block(struct rmi_transport_dev *xport, u16 rmiaddr, u8 commandcode; struct rmi_smb_xport *rmi_smb = container_of(xport, struct rmi_smb_xport, xport); - int cur_len = (int)len; + size_t cur_len = len; mutex_lock(&rmi_smb->page_mutex); @@ -148,7 +148,7 @@ static int rmi_smb_write_block(struct rmi_transport_dev *xport, u16 rmiaddr, /* * break into 32 bytes chunks to write get command code */ - int block_len = min_t(int, len, SMB_MAX_COUNT); + int block_len = min_t(size_t, cur_len, SMB_MAX_COUNT); retval = rmi_smb_get_command_code(xport, rmiaddr, block_len, false, &commandcode); @@ -161,9 +161,9 @@ static int rmi_smb_write_block(struct rmi_transport_dev *xport, u16 rmiaddr, goto exit; /* prepare to write next block of bytes */ - cur_len -= SMB_MAX_COUNT; - databuff += SMB_MAX_COUNT; - rmiaddr += SMB_MAX_COUNT; + cur_len -= block_len; + databuff += block_len; + rmiaddr += block_len; } exit: mutex_unlock(&rmi_smb->page_mutex); From fe10579b6dc3f0dac61e51e1797cacbba5039ac2 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 5 Aug 2026 22:44:23 -0700 Subject: [PATCH 0015/1417] Input: synaptics-rmi4 - fix GPF in suspend and resume when unbound Transport drivers (such as rmi_i2c and rmi_spi) invoke rmi_driver_suspend() and rmi_driver_resume() on their child rmi_dev device during system power management events. However, transport drivers are fully registered and operational even if the physical RMI driver failed to bind or probe the rmi_dev device. When rmi_driver_suspend() or rmi_driver_resume() is called on an unbound rmi_dev, dev_get_drvdata() returns NULL. Calling rmi_disable_irq() or rmi_enable_irq() without driver data attached causes a NULL pointer dereference and General Protection Fault when attempting to lock data->enabled_mutex. Fix this by checking if driver data is attached to rmi_dev in rmi_driver_suspend() and rmi_driver_resume(), exiting early if no driver data is present. Fixes: 2b6a321da9a2 ("Input: synaptics-rmi4 - add support for Synaptics RMI4 devices") Reported-by: syzbot+09103639e39c989e3ed3@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=09103639e39c989e3ed3 Cc: stable@vger.kernel.org Assisted-by: LLM Link: https://patch.msgid.link/anQe8UiyUR4x0flD@google.com Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_driver.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c index 5d49a9021c7d05..a349dfd17519c5 100644 --- a/drivers/input/rmi4/rmi_driver.c +++ b/drivers/input/rmi4/rmi_driver.c @@ -991,6 +991,15 @@ int rmi_driver_suspend(struct rmi_device *rmi_dev, bool enable_wake) { int retval; + /* + * Transport driver will try to suspend RMI device even if physical + * driver did not bind to the RMI device, because transport device + * (I2C, SPI) is fully registered and operational. Exit early if + * there is no driver data attached to the RMI device. + */ + if (!dev_get_drvdata(&rmi_dev->dev)) + return 0; + retval = rmi_suspend_functions(rmi_dev); if (retval) dev_warn(&rmi_dev->dev, "Failed to suspend functions: %d\n", @@ -1005,6 +1014,10 @@ int rmi_driver_resume(struct rmi_device *rmi_dev, bool clear_wake) { int retval; + /* Skip if not fully bound to RMI driver */ + if (!dev_get_drvdata(&rmi_dev->dev)) + return 0; + rmi_enable_irq(rmi_dev, clear_wake); retval = rmi_resume_functions(rmi_dev); From 882de800bf583dd15969836088789edbf035a944 Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sun, 23 Aug 2026 12:53:34 +0100 Subject: [PATCH 0016/1417] arm64: dts: amlogic: t7: use the real UART pclk uart_a listed the 24MHz crystal for all three of its clocks because the T7 clock controller driver did not exist when these boards were added. That leaves the real UART bus clock without a user, so the kernel turns it off when it disables unused clocks at the end of boot, and the board hangs. Update the board DTS files to point uart_a's pclk to CLKID_SYS_UART_A instead of the dummy crystal clock. Fixes: 4fef056588f5 ("arm64: dts: amlogic-t7-a311d2-khadas-vim4: add initial device-tree") Fixes: 6f048cc7a635 ("arm64: dts: add board AN400") Signed-off-by: Lucas Tanure Assisted-by: Claude:claude-fable-5 Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260823115335.102219-2-tanure@linux.com Signed-off-by: Neil Armstrong --- arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts | 2 +- arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts index cab2ee9ea0d31b..ca7536f772ff16 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts @@ -33,7 +33,7 @@ }; &uart_a { - clocks = <&xtal>, <&xtal>, <&xtal>; + clocks = <&xtal>, <&clkc_periphs CLKID_SYS_UART_A>, <&xtal>; clock-names = "xtal", "pclk", "baud"; status = "okay"; }; diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index c41525a34b721d..75d81ad6830dfb 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -266,6 +266,6 @@ &uart_a { status = "okay"; - clocks = <&xtal>, <&xtal>, <&xtal>; + clocks = <&xtal>, <&clkc_periphs CLKID_SYS_UART_A>, <&xtal>; clock-names = "xtal", "pclk", "baud"; }; From c793a084ab3088744516aef437b4239d0d98623f Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sun, 23 Aug 2026 12:53:35 +0100 Subject: [PATCH 0017/1417] arm64: dts: amlogic: t7: khadas-vim4: allow the SD card to be power cycled SD cards start at 3.3V and switch to 1.8V to reach UHS-I speeds. Some cards refuse that switch, and the SD specification says the only way to recover is to power the card off and start again. SD_3V3 is marked regulator-always-on, so the supply never goes off, the card stays stuck half way through the switch, and the MMC core retries forever: mmc1: error -95 whilst initialising SD card Drop regulator-always-on. regulator-boot-on still turns the supply on at boot, and a card that refuses the switch now falls back to high speed instead of failing to initialise. Fixes: 8c45bf9ae4ef ("arm64: dts: amlogic: t7: khadas-vim4: Add power regulators") Signed-off-by: Lucas Tanure Assisted-by: Claude:claude-fable-5 Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260823115335.102219-3-tanure@linux.com Signed-off-by: Neil Armstrong --- arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index 75d81ad6830dfb..c94afaaf8826e4 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -71,7 +71,6 @@ vin-supply = <&vddao_3v3>; gpio = <&gpio GPIOD_11 GPIO_ACTIVE_LOW>; regulator-boot-on; - regulator-always-on; }; sdio_pwrseq: sdio-pwrseq { From ae7be5c58a819259e5eb98a3abfa60842478f2ae Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 29 Aug 2026 10:47:56 +0100 Subject: [PATCH 0018/1417] arm64: dts: amlogic: t7: fix the pin groups of two PWM outputs Two of the PWM outputs can each appear on more than one pin, but the description named a single group that does not exist, so anything using it refused to start. Name the real groups instead, one entry per pin, the same way the other multi-pin PWM output is already described. Fixes: 2a2a7b9701a7 ("arm64: dts: amlogic: t7: Add PWM pinctrl nodes") Assisted-by: Claude:claude-opus-5 Signed-off-by: Lucas Tanure Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260829094758.23248-3-tanure@linux.com Signed-off-by: Neil Armstrong --- arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 32 ++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi index c3dc479b137dee..8c4bd683cce0f9 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi @@ -458,9 +458,25 @@ }; }; - pwm_ao_g_pins: pwm-ao-g { + pwm_ao_g_d11_pins: pwm-ao-g-d11 { mux { - groups = "pwm_ao_g"; + groups = "pwm_ao_g_d11"; + function = "pwm_ao_g"; + bias-disable; + }; + }; + + pwm_ao_g_d7_pins: pwm-ao-g-d7 { + mux { + groups = "pwm_ao_g_d7"; + function = "pwm_ao_g"; + bias-disable; + }; + }; + + pwm_ao_g_e_pins: pwm-ao-g-e { + mux { + groups = "pwm_ao_g_e"; function = "pwm_ao_g"; bias-disable; }; @@ -474,9 +490,17 @@ }; }; - pwm_ao_h_pins: pwm-ao-h { + pwm_ao_h_d5_pins: pwm-ao-h-d5 { + mux { + groups = "pwm_ao_h_d5"; + function = "pwm_ao_h"; + bias-disable; + }; + }; + + pwm_ao_h_d10_pins: pwm-ao-h-d10 { mux { - groups = "pwm_ao_h"; + groups = "pwm_ao_h_d10"; function = "pwm_ao_h"; bias-disable; }; From 1e5a53bd16ac501e68463e84023d1bff543cc696 Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 29 Aug 2026 10:47:57 +0100 Subject: [PATCH 0019/1417] arm64: dts: amlogic: t7: khadas-vim4: add the PWM-driven supplies The board powers its two CPU clusters, the GPU, the NPU, the memory and the always-on domain from regulators steered by PWM outputs. None of them were described, so Linux treated those outputs as unused and switched them off part way through boot. The supplies then drifted away from the levels the bootloader had set, which showed up as random hangs and memory corruption. Describe each supply so it has an owner and is left alone. The voltage ranges are read off the feedback networks on the board schematic. VDDNPU is deliberately not the range in Amlogic's own device tree: this board fits a different feedback resistor, which puts it about 40mV higher at both ends. Fixes: 8c45bf9ae4ef ("arm64: dts: amlogic: t7: khadas-vim4: Add power regulators") Assisted-by: Claude:claude-opus-5 Signed-off-by: Lucas Tanure Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260829094758.23248-4-tanure@linux.com Signed-off-by: Neil Armstrong --- .../amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 110 +++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index c94afaaf8826e4..77abfd555ec5b3 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -104,6 +104,66 @@ enable-active-high; }; + vdd_ddr: regulator-vddddr { + /* + * SY8003ADFC Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDDDR"; + regulator-min-microvolt = <690000>; + regulator-max-microvolt = <890000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_gh 0 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + + vdd_ee: regulator-vddee { + /* + * MP8756GD Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDEE"; + regulator-min-microvolt = <700000>; + regulator-max-microvolt = <922000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_ab 0 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + + vdd_gpu: regulator-vddgpu { + /* + * SY8003ADFC Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDGPU"; + regulator-min-microvolt = <700000>; + regulator-max-microvolt = <922000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_ef 0 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + + vdd_npu: regulator-vddnpu { + /* + * SY8003ADFC Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDNPU"; + regulator-min-microvolt = <733000>; + regulator-max-microvolt = <933000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_ef 1 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + vddao_1v8: regulator-vddao-1v8 { compatible = "regulator-fixed"; regulator-name = "VDDAO_1V8"; @@ -122,6 +182,36 @@ regulator-always-on; }; + vddcpu_a: regulator-vddcpu-a { + /* + * MP8756GD Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDCPU_A"; + regulator-min-microvolt = <689000>; + regulator-max-microvolt = <1049000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_cd 1 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + + vddcpu_b: regulator-vddcpu-b { + /* + * MP8756GD Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDCPU_B"; + regulator-min-microvolt = <689000>; + regulator-max-microvolt = <1049000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_ab 1 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + vddio_1v8: regulator-vddio-1v8 { compatible = "regulator-fixed"; regulator-name = "VDDIO_1V8"; @@ -172,9 +262,27 @@ pinctrl-names = "default"; }; +&pwm_ao_ab { + status = "okay"; + pinctrl-0 = <&pwm_ao_a_pins>, <&pwm_ao_b_pins>; + pinctrl-names = "default"; +}; + &pwm_ao_cd { status = "okay"; - pinctrl-0 = <&pwm_ao_c_d_pins>; + pinctrl-0 = <&pwm_ao_c_d_pins>, <&pwm_ao_d_pins>; + pinctrl-names = "default"; +}; + +&pwm_ao_ef { + status = "okay"; + pinctrl-0 = <&pwm_ao_e_pins>, <&pwm_ao_f_pins>; + pinctrl-names = "default"; +}; + +&pwm_ao_gh { + status = "okay"; + pinctrl-0 = <&pwm_ao_g_e_pins>; pinctrl-names = "default"; }; From 406292fd75f95aa3010fec95b5beb5a8b7e3ba3a Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 29 Aug 2026 10:47:58 +0100 Subject: [PATCH 0020/1417] arm64: dts: amlogic: t7: fix the pin groups of the vsync PWM The vsync PWM output can appear on either of two pins, but the description named a single group that does not exist, so anything using it would refuse to start. Name the real groups instead, one entry per pin. No board describes this output yet, so nothing changes today. Fixes: 2a2a7b9701a7 ("arm64: dts: amlogic: t7: Add PWM pinctrl nodes") Assisted-by: Claude:claude-opus-5 Signed-off-by: Lucas Tanure Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260829094758.23248-5-tanure@linux.com Signed-off-by: Neil Armstrong --- arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi index 8c4bd683cce0f9..719e111bc3dd56 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi @@ -546,9 +546,17 @@ }; }; - pwm_vs_pins: pwm-vs { + pwm_vs_y_pins: pwm-vs-y { mux { - groups = "pwm_vs"; + groups = "pwm_vs_y"; + function = "pwm_vs"; + bias-disable; + }; + }; + + pwm_vs_h_pins: pwm-vs-h { + mux { + groups = "pwm_vs_h"; function = "pwm_vs"; bias-disable; }; From cfc1e9a543e3589ba200795b6e7fd8ef4314efdf Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 22:22:44 +0800 Subject: [PATCH 0021/1417] ARM: socfpga: select the PL310 erratum 753970 workaround ARCH_INTEL_SOCFPGA selects CACHE_L2X0 and several PL310 erratum workarounds. The 753970 workaround is still conditioned on PL310, but that Kconfig symbol no longer exists, so this one selection is always disabled. Select PL310_ERRATA_753970 directly, consistently with the other PL310 workarounds required by the platform. Fixes: fbc125afdc50 ("ARM: socfpga: Turn on ARM errata for L2 cache") Signed-off-by: Pengpeng Hou Signed-off-by: Dinh Nguyen --- arch/arm/mach-socfpga/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/mach-socfpga/Kconfig b/arch/arm/mach-socfpga/Kconfig index eb72c240c2486d..528c5c1368c37e 100644 --- a/arch/arm/mach-socfpga/Kconfig +++ b/arch/arm/mach-socfpga/Kconfig @@ -16,7 +16,7 @@ menuconfig ARCH_INTEL_SOCFPGA select ARM_ERRATA_775420 select PL310_ERRATA_588369 select PL310_ERRATA_727915 - select PL310_ERRATA_753970 if PL310 + select PL310_ERRATA_753970 select PL310_ERRATA_769419 select RESET_CONTROLLER From 34b8b2d78b6276dc2dc4ebc06625a39956f266e4 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Sat, 1 Aug 2026 09:17:31 +0800 Subject: [PATCH 0022/1417] remoteproc: qcom: q6v5_pas: Don't enable handover IRQ on attach qcom_pas_attach() unmasks the handover IRQ and marks handover_issued even though this driver instance never runs qcom_q6v5_prepare() for the boot it is attaching to. This was believed necessary to flush a stale interrupt latched at the interrupt controller while masked, but the handover IRQ is a Qualcomm SMP2P soft IRQ, not a real edge-latched hardware interrupt. The Linux SMP2P driver updates its cached value unconditionally on every notification and only delivers the nested IRQ for bits currently enabled in its own software bitmap, so a transition that happens while masked is simply dropped, never replayed on a later unmask. Since there is nothing to flush, and this driver instance never takes the proxy power-domain/clock/regulator votes that the handover callback would tear down, there is no need to unmask the IRQ in attach() at all. Drop the enable_irq()/disable_irq() pair; setting handover_issued = true is sufficient to keep the flag consistent for the eventual qcom_q6v5_unprepare()/qcom_q6v5_prepare() cycle. It fixes the following unbalanced runtime PM usage and IRQ enable warnings seen on Nord ADSP (probed as attached), after commit bb7c5d6f5b41 ("remoteproc: qcom: q6v5: Make handover IRQ one-shot") comes in place. root@iq10-rrd:~# cat /sys/class/remoteproc/remoteproc0/state attached root@iq10-rrd:~# echo stop > /sys/class/remoteproc/remoteproc0/state [ 40.004874] genpd genpd:0:4c00000.remoteproc: Runtime PM usage count underflow! [ 40.012409] genpd genpd:1:4c00000.remoteproc: Runtime PM usage count underflow! [ 40.050074] remoteproc remoteproc0: stopped remote processor adsp root@iq10-rrd:~# echo start > /sys/class/remoteproc/remoteproc0/state [ 44.350298] remoteproc remoteproc0: powering up adsp [ 44.375769] remoteproc remoteproc0: Booting fw image qcom/nord/adsp.mbn, size 8241816 [ 44.389850] PDM: no support for the platform, userspace daemon might be required. [ 44.397864] ------------[ cut here ]------------ [ 44.402633] Unbalanced enable for IRQ 363 [ 44.406779] WARNING: kernel/irq/manage.c:775 at __enable_irq+0x4c/0x7c, CPU#9: sh/791 ... Suggested-by: Stephan Gerhold Signed-off-by: Shawn Guo Reviewed-by: Abel Vesa Link: https://lore.kernel.org/r/20260801011731.1084591-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_pas.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index ca8e61254c449d..8cff0684fe2f1b 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -524,7 +524,6 @@ static int qcom_pas_attach(struct rproc *rproc) int ret; pas->q6v5.handover_issued = true; - enable_irq(pas->q6v5.handover_irq); pas->q6v5.running = true; ret = irq_get_irqchip_state(pas->q6v5.fatal_irq, @@ -570,7 +569,6 @@ static int qcom_pas_attach(struct rproc *rproc) pas->rproc->state = RPROC_OFFLINE; ret = -EINVAL; disable_running: - disable_irq(pas->q6v5.handover_irq); pas->q6v5.running = false; return ret; From b853857293584dd1f52183f70a2bdeca692a1e71 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Fri, 21 Aug 2026 01:15:40 -0700 Subject: [PATCH 0023/1417] remoteproc: qcom_q6v5_mss: Don't require PAS for memory protection Commit f3b1357673dd ("remoteproc: qcom_q6v5_mss: Switch to generic PAS TZ APIs") changed the probe-time gate for need_mem_protection platforms from qcom_scm_is_available() to qcom_pas_is_available(). Memory protection in this driver is implemented with qcom_scm_assign_mem(), which is a TZ service distinct from PAS. The only PAS call in the driver is qcom_pas_mem_setup(), and it is already guarded by need_pas_mem_setup. No descriptor sets both flags: sc7180, sc7280, sdm660, sdm845, msm8996 and msm8998 set need_mem_protection only, while msm8937, msm8940 and msm8953 set need_pas_mem_setup only. On TrustZone firmware that does not implement PAS - for example SC7180 Chromebooks, where call-availability queries report every QCOM_SCM_SVC_PIL command as unavailable - the modem consequently never probes: platform 4080000.remoteproc: deferred probe pending: (reason unknown) On those machines the modem is also what loads the WLAN firmware, so ath10k never receives QMI and wifi does not come up either. Gate memory protection on SCM availability as it was before, and require PAS only where a PAS call is actually issued. Keeping the SCM check matters: qcom_scm_assign_mem() passes __scm->mempool to qcom_tzmem_alloc() without testing __scm, so dropping the gate entirely would allow a NULL dereference when qcom_scm has not yet probed. Fixes: f3b1357673dd ("remoteproc: qcom_q6v5_mss: Switch to generic PAS TZ APIs") Link: https://lore.kernel.org/r/20260808034716.58888-1-phollinsky@holtechnik.com Signed-off-by: Paul Hollinsky Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260821081540.89137-1-phollinsky@holtechnik.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_mss.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/remoteproc/qcom_q6v5_mss.c b/drivers/remoteproc/qcom_q6v5_mss.c index bef198b9ee6367..2f71ed2feff696 100644 --- a/drivers/remoteproc/qcom_q6v5_mss.c +++ b/drivers/remoteproc/qcom_q6v5_mss.c @@ -2079,7 +2079,16 @@ static int q6v5_probe(struct platform_device *pdev) if (!desc) return -EINVAL; - if (desc->need_mem_protection && !qcom_pas_is_available()) + /* + * Memory protection is done through qcom_scm_assign_mem(), which needs + * SCM but not PAS. Only the memory setup path issues PAS calls, so + * requiring PAS for every need_mem_protection platform prevents the + * modem from probing at all on TZ firmware that offers no PAS. + */ + if (desc->need_mem_protection && !qcom_scm_is_available()) + return -EPROBE_DEFER; + + if (desc->need_pas_mem_setup && !qcom_pas_is_available()) return -EPROBE_DEFER; mba_image = desc->hexagon_mba_image; From 0d8e2195bce6f08c1c53c5ef4d7347fe46418101 Mon Sep 17 00:00:00 2001 From: Mostafa Saleh Date: Thu, 27 Aug 2026 20:30:55 +0000 Subject: [PATCH 0024/1417] remoteproc: qcom_q6v5_adsp: Fix iommu_unmap() usage During adsp_map_carveout, the IOVA is computed by combining the physical address and the SID: iova = adsp->mem_phys | (sid << 32); However, adsp_unmap_carveout() uses the physical address and not the IOVA in iommu_unmap(), causing the unmap to fail or leak mappings because the address doesn't match the original IOVA. Cache the constructed IOVA within the qcom_adsp device struct during mapping and use it during unmapping. Fixes: f22eedff28af ("remoteproc: qcom: Add support for memory sandbox") Signed-off-by: Mostafa Saleh Link: https://lore.kernel.org/r/20260827203055.640116-1-smostafa@google.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_adsp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_adsp.c b/drivers/remoteproc/qcom_q6v5_adsp.c index 39654206781d22..2e5fb5954fa9e4 100644 --- a/drivers/remoteproc/qcom_q6v5_adsp.c +++ b/drivers/remoteproc/qcom_q6v5_adsp.c @@ -104,6 +104,7 @@ struct qcom_adsp { struct completion stop_done; phys_addr_t mem_phys; + unsigned long iova; phys_addr_t mem_reloc; void __iomem *mem_region; size_t mem_size; @@ -333,7 +334,7 @@ static void adsp_unmap_carveout(struct rproc *rproc) struct qcom_adsp *adsp = rproc->priv; if (adsp->has_iommu) - iommu_unmap(rproc->domain, adsp->mem_phys, adsp->mem_size); + iommu_unmap(rproc->domain, adsp->iova, adsp->mem_size); } static int adsp_map_carveout(struct rproc *rproc) @@ -341,7 +342,6 @@ static int adsp_map_carveout(struct rproc *rproc) struct qcom_adsp *adsp = rproc->priv; struct of_phandle_args args; long long sid; - unsigned long iova; int ret; if (!adsp->has_iommu) @@ -358,9 +358,9 @@ static int adsp_map_carveout(struct rproc *rproc) of_node_put(args.np); /* Add SID configuration for ADSP Firmware to SMMU */ - iova = adsp->mem_phys | (sid << 32); + adsp->iova = adsp->mem_phys | (sid << 32); - ret = iommu_map(rproc->domain, iova, adsp->mem_phys, + ret = iommu_map(rproc->domain, adsp->iova, adsp->mem_phys, adsp->mem_size, IOMMU_READ | IOMMU_WRITE, GFP_KERNEL); if (ret) { From 9db31edf92dde1f3c98008bd5f88e859d14324cc Mon Sep 17 00:00:00 2001 From: Vignesh Viswanathan Date: Wed, 19 Aug 2026 11:00:30 +0530 Subject: [PATCH 0025/1417] remoteproc: qcom_q6v5_pas: Fix error masking in qcom_pas_stop() In qcom_pas_stop function, return value of qcom_pas_shutdown for pas_id is overwritten by the return value of qcom_pas_shutdown for dtb_pas_id. This causes errors seen on qcom_pas_shutdown failures for pas_id to be masked to the caller. This might lead to issues where the memory regions locked by PAS, as part of qcom_pas_auth_and_reset, are not released for access by linux and rproc_coredump flow will end up accessing the locked memory, leading to an access violation. Fix this by using a separate variable for the dtb_pas_id shutdown call and only overriding the main return value if the pas_id shutdown succeeded but dtb_pas_id shutdown failed. Fixes: 29814986b82e ("remoteproc: qcom_q6v5_pas: add support for dtb co-firmware loading") Signed-off-by: Vignesh Viswanathan Reviewed-by: Konrad Dybcio Reviewed-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260819-rproc_dtb_fix-v1-1-cb6be7f93bc9@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_pas.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index 8cff0684fe2f1b..a005546c265d0e 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -405,6 +405,7 @@ static int qcom_pas_stop(struct rproc *rproc) { struct qcom_pas *pas = rproc->priv; int handover; + int dtb_ret; int ret; ret = qcom_q6v5_request_stop(&pas->q6v5, pas->sysmon); @@ -419,9 +420,12 @@ static int qcom_pas_stop(struct rproc *rproc) dev_err(pas->dev, "failed to shutdown: %d\n", ret); if (pas->dtb_pas_id) { - ret = qcom_pas_shutdown(pas->dtb_pas_id); - if (ret) - dev_err(pas->dev, "failed to shutdown dtb: %d\n", ret); + dtb_ret = qcom_pas_shutdown(pas->dtb_pas_id); + if (dtb_ret) + dev_err(pas->dev, "failed to shutdown dtb: %d\n", dtb_ret); + + if (!ret && dtb_ret) + ret = dtb_ret; qcom_pas_unmap_carveout(rproc, pas->dtb_mem_phys, pas->dtb_mem_size); } From 93d88ac4a448f200d18d1d71de0074b8e716bbee Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Tue, 25 Aug 2026 10:35:56 +0800 Subject: [PATCH 0026/1417] sched_ext: Pass the initial cpu.idle state in scx_cgroup_init_args scx_cgroup_init_args carries the initial weight and bandwidth control parameters of a cgroup to ops.cgroup_init(), but not its cpu.idle state. A cgroup that was already configured idle before the scheduler was loaded (or before it was onlined under it) is presented as non-idle, and the BPF scheduler only learns about it if cpu.idle is written again later. Add the sched_idle state to scx_cgroup_init_args and fill it in all four places that build the args: scx_tg_online() for cgroups onlined under the scheduler, scx_cgroup_init() for cgroups that already exist when the scheduler is loaded, and the sub-scheduler handover paths scx_cgroup_claim_subtree() and scx_cgroup_return_subtree(). Verified in a VM with a probe scheduler printing the init args: a cgroup configured cpu.idle=1 before loading shows sched_idle=1 in ops.cgroup_init(), the default shows 0, and later cpu.idle writes still come through ops.cgroup_set_idle(). The sub-scheduler paths are compile-tested only. Fixes: 347ed2d566da ("sched/ext: Implement cgroup_set_idle() callback") Signed-off-by: Tao Cui Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 4 +++- kernel/sched/ext/internal.h | 3 +++ kernel/sched/ext/sub.c | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 713aa26b28286b..901fb0f8b9766e 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -4766,7 +4766,8 @@ int scx_tg_online(struct task_group *tg) { .weight = tg->scx.weight, .bw_period_us = tg->scx.bw_period_us, .bw_quota_us = tg->scx.bw_quota_us, - .bw_burst_us = tg->scx.bw_burst_us }; + .bw_burst_us = tg->scx.bw_burst_us, + .sched_idle = tg->scx.idle }; ret = SCX_CALL_OP_RET(sch, cgroup_init, NULL, tg->css.cgroup, &args); @@ -5187,6 +5188,7 @@ static int scx_cgroup_init(struct scx_sched *sch) .bw_period_us = tg->scx.bw_period_us, .bw_quota_us = tg->scx.bw_quota_us, .bw_burst_us = tg->scx.bw_burst_us, + .sched_idle = tg->scx.idle, }; ret = SCX_CALL_OP_RET(sch, cgroup_init, NULL, css->cgroup, &args); diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 0967b99a494811..076a351bb3f287 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -259,6 +259,9 @@ struct scx_cgroup_init_args { u64 bw_period_us; u64 bw_quota_us; u64 bw_burst_us; + + /* whether the cgroup is configured SCHED_IDLE via cpu.idle */ + bool sched_idle; }; enum scx_cpu_preempt_reason { diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index 0554448835bd08..385302d199143e 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -1361,6 +1361,7 @@ static s32 scx_cgroup_claim_subtree(struct scx_sched *sch) .bw_period_us = tg->scx.bw_period_us, .bw_quota_us = tg->scx.bw_quota_us, .bw_burst_us = tg->scx.bw_burst_us, + .sched_idle = tg->scx.idle, }; if (tg->scx.sched != parent || @@ -1464,6 +1465,7 @@ static void scx_cgroup_return_subtree(struct scx_sched *sch) .bw_period_us = tg->scx.bw_period_us, .bw_quota_us = tg->scx.bw_quota_us, .bw_burst_us = tg->scx.bw_burst_us, + .sched_idle = tg->scx.idle, }; /* the first pass must have transferred everything */ From 5f8cb07d7a8573753c5d31dc76b51cab8e5e9460 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 28 Aug 2026 10:31:29 +0800 Subject: [PATCH 0027/1417] arm64: dts: socfpga: change access permission from 755 to 644 These files have an incorrect access permission of 755 instead of 644. Change them to the correct access permission of 644. Fixes: 4bc04eb90b7c ("arm64: dts: socfpga: stratix10: Add emmc support") Cc: # v7.1+ Signed-off-by: Dinh Nguyen --- arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dtsi | 0 arch/arm64/boot/dts/altera/socfpga_stratix10_socdk_emmc.dts | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dtsi mode change 100755 => 100644 arch/arm64/boot/dts/altera/socfpga_stratix10_socdk_emmc.dts diff --git a/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dtsi b/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dtsi old mode 100755 new mode 100644 diff --git a/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk_emmc.dts b/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk_emmc.dts old mode 100755 new mode 100644 From 32cd87f54dd1070020e664ccb0312a9f0fea79b4 Mon Sep 17 00:00:00 2001 From: Guoqing Jiang Date: Thu, 27 Aug 2026 20:55:53 +0800 Subject: [PATCH 0028/1417] RDMA/siw: Clear association under lock if siw_qp_modify fails in siw_accept We need to clear cep before release state_lock as siw_qp_llp_close and siw_qp_modify->siw_qp_llp_close did. Otherwise if siw_qp_modify() fails in siw_accept(), the QP's state_lock is released before the error path cleanup. A concurrent ibv_modify_qp() transitioning the QP to ERROR can race in this window: siw_accept() ibv_modify_qp(ERROR) ---------------------- ---------------------- siw_qp_modify() fails up_write(&qp->state_lock) down_write(&qp->state_lock) nextstate_from_idle(): if (qp->cep) siw_cep_put(qp->cep) <- frees cep qp->cep = NULL goto error cep->qp = NULL <- UAF Clear qp->cep and drop the association reference taken by siw_cep_get(), all under the write lock held from the initial down_write(&qp->state_lock). Thread B therefore sees qp->cep == NULL, skips its own put, and cannot free the cep before siw_accept() is done with it. Fixes: 6c52fdc244b5 ("rdma/siw: connection management") Reported-by: Shuangpeng Bai Link: https://lore.kernel.org/linux-rdma/d6fbe475-a5c2-f975-99b0-a0bd6b6d10e8@linux.dev/T/#m5876c1ff2de8686a9a1173b8f1aa0ff5363a785c Signed-off-by: Guoqing Jiang Link: https://patch.msgid.link/20260827125553.12831-1-guoqing.jiang@linux.dev Acked-by: Bernard Metzler Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/siw/siw_cm.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/sw/siw/siw_cm.c b/drivers/infiniband/sw/siw/siw_cm.c index 0245b25e727189..ed49818793ddeb 100644 --- a/drivers/infiniband/sw/siw/siw_cm.c +++ b/drivers/infiniband/sw/siw/siw_cm.c @@ -1719,9 +1719,12 @@ int siw_accept(struct iw_cm_id *id, struct iw_cm_conn_param *params) SIW_QP_ATTR_STATE | SIW_QP_ATTR_LLP_HANDLE | SIW_QP_ATTR_ORD | SIW_QP_ATTR_IRD | SIW_QP_ATTR_MPA); + if (rv) { + qp->cep = NULL; + siw_cep_put(cep); + goto error_unlock; + } up_write(&qp->state_lock); - if (rv) - goto error; siw_dbg_cep(cep, "[QP %u]: send mpa reply, %d byte pdata\n", qp_id(qp), params->private_data_len); From ae36a5b609ae79f4de966328b78d2584be9719a4 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Thu, 27 Aug 2026 19:18:07 +0200 Subject: [PATCH 0029/1417] RDMA/rxe: validate access flags before swapping the MR's PD rxe_rereg_user_mr() reassigns mr->ibmr.pd first and only then validates the IB_MR_REREG_ACCESS argument: if (flags & IB_MR_REREG_PD) { rxe_put(old_pd); rxe_get(pd); mr->ibmr.pd = ibpd; } if (flags & IB_MR_REREG_ACCESS) { if (access & ~RXE_ACCESS_SUPPORTED_MR) return ERR_PTR(-EOPNOTSUPP); mr->access = access; } Both flags pass the entry check because RXE_MR_REREG_SUPPORTED is IB_MR_REREG_PD | IB_MR_REREG_ACCESS, so a caller can reach the access check with mr->ibmr.pd already reassigned. mr->ibmr.pd is owned by the core, which adjusts pd->usecnt only on the success path: ib_uverbs_rereg_mr() jumps to put_new_uobj on a driver error without undoing the reassignment, so mr->pd == new_pd while the usecnts still charge the MR to orig_pd. ib_dereg_mr_user() then decrements new_pd, whose count can reach zero while a memory window still references it; uverbs_free_pd() frees the PD on that count alone and rxe_mw_cleanup() writes to freed memory: BUG: KASAN: slab-use-after-free in __rxe_put+0x31/0xa0 Write of size 4 at addr ffff8881301dd690 by task rxe_poc/591 __rxe_put+0x31/0xa0 rxe_mw_cleanup+0x42/0x200 __rxe_cleanup+0x115/0x370 rxe_dealloc_mw+0x4c/0x80 Allocated by task 591: ib_uverbs_alloc_pd+0x258/0x540 Freed by task 591: ib_dealloc_pd_user+0x174/0x210 uverbs_free_pd+0x8d/0xc0 ib_uverbs_dealloc_pd+0x18e/0x1d0 Validate the access flags before mutating any state so the callback either applies every requested change or none. Fixes: 544c7f62cf32 ("RDMA/rxe: Implement rereg_user_mr") Signed-off-by: Norbert Szetei Link: https://patch.msgid.link/46E1D5C0-24BE-4D01-BDB3-634FE09B22C5@doyensec.com Reviewed-by: Zhu Yanjun Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/rxe/rxe_verbs.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_verbs.c b/drivers/infiniband/sw/rxe/rxe_verbs.c index 96c7716057fe99..3864284522ebc1 100644 --- a/drivers/infiniband/sw/rxe/rxe_verbs.c +++ b/drivers/infiniband/sw/rxe/rxe_verbs.c @@ -1331,19 +1331,20 @@ static struct ib_mr *rxe_rereg_user_mr(struct ib_mr *ibmr, int flags, if (err) return ERR_PTR(err); + if ((flags & IB_MR_REREG_ACCESS) && + (access & ~RXE_ACCESS_SUPPORTED_MR)) { + rxe_err_mr(mr, "access = %#x not supported\n", access); + return ERR_PTR(-EOPNOTSUPP); + } + if (flags & IB_MR_REREG_PD) { rxe_put(old_pd); rxe_get(pd); mr->ibmr.pd = ibpd; } - if (flags & IB_MR_REREG_ACCESS) { - if (access & ~RXE_ACCESS_SUPPORTED_MR) { - rxe_err_mr(mr, "access = %#x not supported\n", access); - return ERR_PTR(-EOPNOTSUPP); - } + if (flags & IB_MR_REREG_ACCESS) mr->access = access; - } return NULL; } From d10e2a08799e858d3e71ea4169bcd018f216d444 Mon Sep 17 00:00:00 2001 From: Gang Yan Date: Fri, 14 Aug 2026 17:37:40 +0800 Subject: [PATCH 0030/1417] RDMA/rxe: Fix integer overflow in mr_check_range() leading to OOB access mr_check_range() validates that [iova, iova+length) falls within the registered MR range using wraparound-prone arithmetic: if (iova < mr->ibmr.iova || iova + length > mr->ibmr.iova + mr->ibmr.length) A remote peer can craft an RDMA-Write/Read RETH so that iova + length wraps to 0 (e.g. iova=0xfffffffffffffff8, length=8), bypassing the check. rxe_mr_iova_to_index() then computes a huge index (int idx, only guarded by WARN_ON) and rxe_mr_copy_xarray() dereferences mr->page_info[huge], causing an out-of-bounds read/write and a kernel oops that is triggerable by an unauthenticated remote peer. Rewrite the check in overflow-safe form; the first two clauses guarantee that the subsequent subtractions do not underflow: if (iova < mr->ibmr.iova || length > mr->ibmr.length || iova - mr->ibmr.iova > mr->ibmr.length - length) With the fix, mr_check_range() returns -EINVAL for the crafted iova and the responder reports REMOTE_ACCESS_ERROR instead of triggering the OOB. Fixes: 8700e3e7c485 ("Soft RoCE driver") Signed-off-by: Gang Yan Link: https://patch.msgid.link/20260814093740.292954-1-gang.yan@linux.dev Reviewed-by: Zhu Yanjun Reviewed-by: Shukai Ni Tested-by: Shukai Ni Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/rxe/rxe_mr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/sw/rxe/rxe_mr.c b/drivers/infiniband/sw/rxe/rxe_mr.c index 875eceb55fdfa2..71d9ea47728909 100644 --- a/drivers/infiniband/sw/rxe/rxe_mr.c +++ b/drivers/infiniband/sw/rxe/rxe_mr.c @@ -33,7 +33,8 @@ int mr_check_range(struct rxe_mr *mr, u64 iova, size_t length) case IB_MR_TYPE_USER: case IB_MR_TYPE_MEM_REG: if (iova < mr->ibmr.iova || - iova + length > mr->ibmr.iova + mr->ibmr.length) { + length > mr->ibmr.length || + iova - mr->ibmr.iova > mr->ibmr.length - length) { rxe_dbg_mr(mr, "iova/length out of range\n"); return -EINVAL; } From 9fa903b24b1f46b4ff5443bcd4aca23e5c57f9c1 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft Security FORGE Labs)" Date: Wed, 26 Aug 2026 16:17:45 -0400 Subject: [PATCH 0031/1417] xfrm: hold net_device reference under RCU in bundle creation xfrm_bundle_create() and xfrm_create_dummy_bundle() read dst->dev into a local pointer without taking a device reference, then pass it to xfrm_fill_dst(). A concurrent RTM_DELLINK replaces dst->dev via dst_dev_put() and frees the old net_device, causing a use-after-free when xfrm6_fill_dst() later dereferences the stale dev pointer. BUG: KASAN: slab-use-after-free in xfrm6_fill_dst+0x82c/0x860 (net/ipv6/xfrm6_policy.c:86 netdev_hold()) Read of size 8 at addr ffff8880142fe588 by task exploit/153 Call Trace: xfrm6_fill_dst+0x82c/0x860 xfrm_resolve_and_create_bundle+0x21d4/0x2bd0 xfrm_lookup_with_ifid+0x485/0x1640 ip6_dst_lookup_flow+0x19b/0x1e0 udpv6_sendmsg+0x1443/0x2dd0 Fix this by reading dst->dev via dst_dev_rcu() and keeping the RCU read-side critical section active until xfrm_fill_dst() has taken the required device references. Fixes: 25ee3286dcbc ("[IPSEC]: Merge common code into xfrm_bundle_create") Fixes: a0073fe18e71 ("xfrm: Add a state resolution packet queue") Suggested-by: Steffen Klassert Reported-by: Xiang Mei (Microsoft) Link: https://lore.kernel.org/all/20260820200245.44312-1-blbllhy@gmail.com/ Cc: AutonomousCodeSecurity@microsoft.com Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Cen Zhang (Microsoft Security FORGE Labs) Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_policy.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c index 932a313b9460a5..513c9f2283347c 100644 --- a/net/xfrm/xfrm_policy.c +++ b/net/xfrm/xfrm_policy.c @@ -2770,9 +2770,12 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy, xdst0->path = dst; err = -ENODEV; - dev = dst->dev; - if (!dev) + rcu_read_lock(); + dev = dst_dev_rcu(dst); + if (!dev) { + rcu_read_unlock(); goto free_dst; + } xfrm_init_path(xdst0, dst, nfheader_len); xfrm_init_pmtu(bundle, nx); @@ -2780,8 +2783,10 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy, for (xdst_prev = xdst0; xdst_prev != (struct xfrm_dst *)dst; xdst_prev = (struct xfrm_dst *) xfrm_dst_child(&xdst_prev->u.dst)) { err = xfrm_fill_dst(xdst_prev, dev, fl); - if (err) + if (err) { + rcu_read_unlock(); goto free_dst; + } xdst_prev->u.dst.header_len = header_len; xdst_prev->u.dst.trailer_len = trailer_len; @@ -2789,6 +2794,7 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy, trailer_len -= xdst_prev->u.dst.xfrm->props.trailer_len; } + rcu_read_unlock(); return &xdst0->u.dst; put_states: @@ -3058,11 +3064,15 @@ static struct xfrm_dst *xfrm_create_dummy_bundle(struct net *net, xfrm_init_path((struct xfrm_dst *)dst1, dst, 0); err = -ENODEV; - dev = dst->dev; - if (!dev) + rcu_read_lock(); + dev = dst_dev_rcu(dst); + if (!dev) { + rcu_read_unlock(); goto free_dst; + } err = xfrm_fill_dst(xdst, dev, fl); + rcu_read_unlock(); if (err) goto free_dst; From 3cf5cdecd99c9c186a5ea518d93bbf3045b6e3aa Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 29 Aug 2026 17:24:24 +0800 Subject: [PATCH 0032/1417] xfrm: save input state data before secpath resets xfrm_input() stores the current xfrm_state in the skb secpath while it continues receive-side processing. Some input paths can reset that secpath before xfrm_input() has finished dereferencing the state. Receive callback users such as VTI and XFRM interfaces can reset the secpath. The VTI receive path does so before checking whether the packet crosses network namespaces, while the XFRM interface path does so only for cross-network-namespace packets. The XFRM_MAX_DEPTH error path can also reset the secpath before the final drop callback reports the current state's protocol. If secpath_reset() drops the last state reference while the state is concurrently deleted, xfrm_input() can still dereference the freed state when selecting transport_finish() or reporting the drop callback protocol. Save the state protocol on the stack while the state is still valid, and use the already saved address family for transport_finish(). A larval XFRM_STATE_ACQ state has no type, so retain nexthdr as its protocol. This preserves the existing drop-path fallback while avoiding the post-reset state dereferences without adding an extra state reference to every received packet. Fixes: df3893c176e9 ("vti: Update the ipv4 side to use it's own receive hook.") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_input.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c index 8f6109eada7eaa..5ed87d51392a52 100644 --- a/net/xfrm/xfrm_input.c +++ b/net/xfrm/xfrm_input.c @@ -474,6 +474,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) struct xfrm_state *x = NULL; xfrm_address_t *daddr; u32 mark = skb->mark; + u8 xfrm_proto = nexthdr; unsigned int family = AF_UNSPEC; int decaps = 0; int async = 0; @@ -485,6 +486,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) if (encap_type < 0 || (xo && (xo->flags & XFRM_GRO || encap_type == 0 || encap_type == UDP_ENCAP_ESPINUDP))) { x = xfrm_input_state(skb); + xfrm_proto = x->type ? x->type->proto : nexthdr; if (unlikely(x->km.state != XFRM_STATE_VALID)) { if (x->km.state == XFRM_STATE_ACQ) @@ -592,11 +594,13 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) x = xfrm_input_state_lookup(net, mark, daddr, spi, nexthdr, family); if (x == NULL) { + xfrm_proto = nexthdr; secpath_reset(skb); XFRM_INC_STATS(net, LINUX_MIB_XFRMINNOSTATES); xfrm_audit_state_notfound(skb, family, spi, seq); goto drop; } + xfrm_proto = x->type ? x->type->proto : nexthdr; if (unlikely(x->dir && x->dir != XFRM_SA_DIR_IN)) { secpath_reset(skb); @@ -604,6 +608,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) xfrm_audit_state_notfound(skb, family, spi, seq); xfrm_state_put(x); x = NULL; + xfrm_proto = nexthdr; goto drop; } @@ -728,7 +733,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) } while (!err); rcu_read_lock(); - err = xfrm_rcv_cb(skb, family, x->type->proto, 0); + err = xfrm_rcv_cb(skb, family, xfrm_proto, 0); if (err) { rcu_read_unlock(); goto drop; @@ -753,7 +758,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) xfrm_gro = xo->flags & XFRM_GRO; err = -EAFNOSUPPORT; - afinfo = xfrm_state_afinfo_get_rcu(x->props.family); + afinfo = xfrm_state_afinfo_get_rcu(family); if (likely(afinfo)) err = afinfo->transport_finish(skb, xfrm_gro || async); if (xfrm_gro) { @@ -776,7 +781,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) drop: if (async) dev_put(dev); - xfrm_rcv_cb(skb, family, x && x->type ? x->type->proto : nexthdr, -1); + xfrm_rcv_cb(skb, family, xfrm_proto, -1); kfree_skb(skb); return 0; } From 96f01b53c2d05e003b040892256de54a586e8529 Mon Sep 17 00:00:00 2001 From: Wyatt Feng Date: Sat, 29 Aug 2026 23:44:32 +0800 Subject: [PATCH 0033/1417] net: xfrm: reject unrepresentable espintcp transport headers ESP-in-TCP can hand xfrm packets whose transport header offset no longer fits after the stream parser trims the TCP envelope. The plain transport header reset truncates that offset and triggers the skb warning path. Use the careful transport-header helper and drop the skb through the existing XFRM error path when the offset cannot be represented. Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: Codex:GPT-5.4 Signed-off-by: Wyatt Feng Signed-off-by: Ren Wei Signed-off-by: Steffen Klassert --- net/xfrm/espintcp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/xfrm/espintcp.c b/net/xfrm/espintcp.c index 674aedc5af5a2c..3e72b9f067b9be 100644 --- a/net/xfrm/espintcp.c +++ b/net/xfrm/espintcp.c @@ -30,7 +30,11 @@ static void handle_esp(struct sk_buff *skb, struct sock *sk) { struct tcp_skb_cb *tcp_cb = (struct tcp_skb_cb *)skb->cb; - skb_reset_transport_header(skb); + if (!skb_reset_transport_header_careful(skb)) { + XFRM_INC_STATS(sock_net(sk), LINUX_MIB_XFRMINERROR); + kfree_skb(skb); + return; + } /* restore IP CB, we need at least IP6CB->nhoff */ memmove(skb->cb, &tcp_cb->header, sizeof(tcp_cb->header)); From 65320b642025c53063372bd34a376bec7bf15598 Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 08:50:00 +0800 Subject: [PATCH 0034/1417] firmware: arm_scpi: fix device_node leak in scpi_dev_domain_id of_parse_phandle_with_args() takes a reference on clkspec.np that must be released with of_node_put(). scpi_dev_domain_id() returned clkspec.args[0] without dropping that reference, so every domain lookup leaked a device node. Paths such as scpi_dvfs_info() / cpufreq init call this per CPU, so the leak accumulates over time. Save the domain id, of_node_put(clkspec.np), then return the saved value. Signed-off-by: Xixin Liu Link: https://patch.msgid.link/84fdd490495b.v2.1785200642.git.liuxixin@kylinos.cn Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index 2acad5fa5a2867..43d6d9bbc7a97d 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -661,12 +661,15 @@ static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) static int scpi_dev_domain_id(struct device *dev) { struct of_phandle_args clkspec; + int domain; if (of_parse_phandle_with_args(dev->of_node, "clocks", "#clock-cells", 0, &clkspec)) return -EINVAL; - return clkspec.args[0]; + domain = clkspec.args[0]; + of_node_put(clkspec.np); + return domain; } static struct scpi_dvfs_info *scpi_dvfs_info(struct device *dev) From 32471d84a487c7fd74532bc96be56f8028cf4a3f Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 08:50:00 +0800 Subject: [PATCH 0035/1417] firmware: arm_scpi: reject DVFS OPP count above MAX_DVFS_OPPS scpi_dvfs_get_info() already rejected a zero opp_count, but still trusted any larger value from the SCP firmware. The shared-memory reply only holds MAX_DVFS_OPPS entries in buf.opps[]; a bigger count over-reads that array and then sizes the allocated OPP table incorrectly (garbage OPPs / OOB). The missing upper bound dates back to the original SCPI DVFS support. Reject zero and out-of-range counts in one check and return -EINVAL. Fixes: 8cb7cf56c9fe ("firmware: add support for ARM System Control and Power Interface(SCPI) protocol") Signed-off-by: Xixin Liu Link: https://patch.msgid.link/022802f0b38f.v2.1785200642.git.liuxixin@kylinos.cn Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index 43d6d9bbc7a97d..68a730d2203771 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -631,8 +631,8 @@ static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) if (ret) return ERR_PTR(ret); - if (!buf.opp_count) - return ERR_PTR(-ENOENT); + if (!buf.opp_count || buf.opp_count > MAX_DVFS_OPPS) + return ERR_PTR(-EINVAL); info = kmalloc_obj(*info); if (!info) From 70f4b78d560e592cbf3325b162424737d032fc1d Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 08:50:00 +0800 Subject: [PATCH 0036/1417] clk: scpi: bound-check DVFS index in scpi_dvfs_recalc_rate dvfs_get_idx() may return an out-of-range index if the SCP firmware is buggy or returns a stale value. Only negative indexes were rejected, so a large index walked past info->opps and could treat garbage as a clock rate (KASAN OOB / wrong frequency to consumers). The missing upper bound dates back to the original SCPI clock driver. Treat indexes >= opp count as invalid and return 0, same as idx < 0. Fixes: cd52c2a4b5c4 ("clk: add support for clocks provided by SCP(System Control Processor)") Signed-off-by: Xixin Liu Link: https://patch.msgid.link/04f9ab766e07.v2.1785200642.git.liuxixin@kylinos.cn Signed-off-by: Sudeep Holla --- drivers/clk/clk-scpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index 2328d2abf6d880..fa4349c8178aef 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -73,7 +73,7 @@ static unsigned long scpi_dvfs_recalc_rate(struct clk_hw *hw, int idx = clk->scpi_ops->dvfs_get_idx(clk->id); const struct scpi_opp *opp; - if (idx < 0) + if (idx < 0 || idx >= clk->info->count) return 0; opp = clk->info->opps + idx; From ab06cf8152dace327cd873188e4a036c4e0b5944 Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 08:50:00 +0800 Subject: [PATCH 0037/1417] clk: scpi: register scpi-cpufreq once and clear on failure scpi_clocks_probe() walks clock children and, for each DVFS provider, calls platform_device_register_simple("scpi-cpufreq", -1, ...). Two related bugs: Since all DVFS providers register the fixed scpi-cpufreq device using PLATFORM_DEVID_NONE, a second registration fails with -EEXIST and overwrites the pointer to the successfully registered device. The first device can then no longer be unregistered. Register the virtual device only once. If registration fails, reset the pointer to NULL so a subsequent DVFS provider can retry and the global pointer only represents a successfully registered device. Fixes: 9490f01e2471 ("clk: scpi: add support for cpufreq virtual device") Fixes: 67bcc2c5f1da ("clk: scpi: don't add cpufreq device if the scpi dvfs node is disabled") Signed-off-by: Xixin Liu Link: https://patch.msgid.link/fd1b9199a9c3.v2.1785200642.git.liuxixin@kylinos.cn (sudeep.holla: reworded the commit message to improve readability) Signed-off-by: Sudeep Holla --- drivers/clk/clk-scpi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index fa4349c8178aef..f8182175b48353 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -272,10 +272,14 @@ static int scpi_clocks_probe(struct platform_device *pdev) if (match->data != &scpi_dvfs_ops) continue; /* Add the virtual cpufreq device if it's DVFS clock provider */ + if (cpufreq_dev) + continue; cpufreq_dev = platform_device_register_simple("scpi-cpufreq", -1, NULL, 0); - if (IS_ERR(cpufreq_dev)) + if (IS_ERR(cpufreq_dev)) { pr_warn("unable to register cpufreq device"); + cpufreq_dev = NULL; + } } return 0; } From 86d923a882f48b047b079a293abb6ebca3eaf23f Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 09:27:31 +0800 Subject: [PATCH 0038/1417] clk: scpi: use PLATFORM_DEVID_NONE for scpi-cpufreq Replace the magic -1 passed to platform_device_register_simple() with PLATFORM_DEVID_NONE. This is a readability cleanup only and does not change behavior. Signed-off-by: Xixin Liu Link: https://patch.msgid.link/fb51cfbfbb41.v2.1785200642.git.liuxixin@kylinos.cn Signed-off-by: Sudeep Holla --- drivers/clk/clk-scpi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index f8182175b48353..b2d412ca1d2222 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -275,7 +275,8 @@ static int scpi_clocks_probe(struct platform_device *pdev) if (cpufreq_dev) continue; cpufreq_dev = platform_device_register_simple("scpi-cpufreq", - -1, NULL, 0); + PLATFORM_DEVID_NONE, + NULL, 0); if (IS_ERR(cpufreq_dev)) { pr_warn("unable to register cpufreq device"); cpufreq_dev = NULL; From 769001ce838d907ecaa95f1d0a4e8fc86f761f9f Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 26 Jul 2026 19:15:30 +0800 Subject: [PATCH 0039/1417] RDMA/rxe: Restore HMM_PFN_WRITE check in ODP write paths Commit 0b261d7c1cd3 ("RDMA/rxe: Break endless pagefault loop for RO pages") dropped the access permission test from rxe_check_pagefault() and left only HMM_PFN_VALID. A page faulted in read-only, for example a page-cache folio behind a PROT_READ file mapping, then satisfies the check and ODP write operations (RDMA WRITE, RDMA READ response, SEND payload, atomics) modify it through kmap without ever breaking CoW. An unprivileged user can register an ODP MR over such a mapping and have incoming RDMA traffic overwrite the page cache of a file it only holds O_RDONLY, including /etc/passwd or setuid binaries. This is the same primitive class as Dirty COW and CVE-2022-2590. mlx5 has the missing invariant: its ODP path sets the device write bit only for pfns that carry HMM_PFN_WRITE. Restore it in rxe by requiring HMM_PFN_WRITE in rxe_check_pagefault() for every operation except RXE_PAGEFAULT_RDONLY. A write to a non-writable VMA now fails the one fault attempt with -EPERM from hmm_vma_fault() instead of re-faulting forever. For a writable VMA the fault breaks CoW and the write lands in the private page. Keep pmem flushes on the read-only check. arch_wb_cache_pmem() never modifies memory, and the FLUSH access bits do not make the umem writable, so classifying flushes as writes would make every flush against a flush-only MR fail. Fixes: 0b261d7c1cd3 ("RDMA/rxe: Break endless pagefault loop for RO pages") Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20260726111533.1037819-1-bestswngs@gmail.com Reviewed-by: Zhu Yanjun Tested-by: Hongqiang Luo Tested-by: Xinyu Ma Tested-by: Zhanbo Ye Reported-by: Weiming Shi Reported-by: Shaomin Chen Reported-by: Rui Ding Reported-by: Miao Zhao Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/rxe/rxe_odp.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_odp.c b/drivers/infiniband/sw/rxe/rxe_odp.c index e870efa7a0a3c2..ab21b620e94ca4 100644 --- a/drivers/infiniband/sw/rxe/rxe_odp.c +++ b/drivers/infiniband/sw/rxe/rxe_odp.c @@ -120,19 +120,23 @@ int rxe_odp_mr_init_user(struct rxe_dev *rxe, u64 start, u64 length, } static inline bool rxe_check_pagefault(struct ib_umem_odp *umem_odp, u64 iova, - int length) + int length, bool write) { bool need_fault = false; + u64 access = HMM_PFN_VALID; u64 addr; int idx; + if (write) + access |= HMM_PFN_WRITE; + addr = iova & (~(BIT(umem_odp->page_shift) - 1)); /* Skim through all pages that are to be accessed. */ while (addr < iova + length) { idx = (addr - ib_umem_start(umem_odp)) >> umem_odp->page_shift; - if (!(umem_odp->map.pfn_list[idx] & HMM_PFN_VALID)) { + if ((umem_odp->map.pfn_list[idx] & access) != access) { need_fault = true; break; } @@ -155,6 +159,7 @@ static unsigned long rxe_odp_iova_to_page_offset(struct ib_umem_odp *umem_odp, u static int rxe_odp_map_range_and_lock(struct rxe_mr *mr, u64 iova, int length, u32 flags) { struct ib_umem_odp *umem_odp = to_ib_umem_odp(mr->umem); + bool write = !(flags & RXE_PAGEFAULT_RDONLY); bool need_fault; int err; @@ -163,7 +168,7 @@ static int rxe_odp_map_range_and_lock(struct rxe_mr *mr, u64 iova, int length, u mutex_lock(&umem_odp->umem_mutex); - need_fault = rxe_check_pagefault(umem_odp, iova, length); + need_fault = rxe_check_pagefault(umem_odp, iova, length, write); if (need_fault) { mutex_unlock(&umem_odp->umem_mutex); @@ -173,7 +178,7 @@ static int rxe_odp_map_range_and_lock(struct rxe_mr *mr, u64 iova, int length, u if (err < 0) return err; - need_fault = rxe_check_pagefault(umem_odp, iova, length); + need_fault = rxe_check_pagefault(umem_odp, iova, length, write); if (need_fault) { mutex_unlock(&umem_odp->umem_mutex); return -EFAULT; @@ -335,8 +340,9 @@ int rxe_odp_flush_pmem_iova(struct rxe_mr *mr, u64 iova, int err; u8 *va; + /* A flush never modifies memory; read-only access suffices. */ err = rxe_odp_map_range_and_lock(mr, iova, length, - RXE_PAGEFAULT_DEFAULT); + RXE_PAGEFAULT_RDONLY); if (err) return err; From 1caceeb2d74bbe88223aea55eb8626b4c5f076fd Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:27:28 -0400 Subject: [PATCH 0040/1417] RDMA/rxe: insert mcg into mcg_tree only after rxe_mcast_add() succeeds rxe_get_mcg() publishes a newly allocated multicast group in rxe->mcg_tree before programming the backing Ethernet multicast address with rxe_mcast_add(), which runs outside mcg_lock. A local userspace RDMA client reaches this path with ATTACH_MCAST on a UD QP; if rxe_mcast_add() then returns an error (for example -ENODEV when the backing netdev has been removed, or a propagated dev_mc_add() error), the unwind frees the published group without removing it from the tree. A later lookup of the same MGID dereferences the freed struct rxe_mcg from __rxe_lookup_mcg(). Fix this by keeping the new mcg private until rxe_mcast_add() succeeds. Split the tree publication into __rxe_publish_mcg(), call rxe_mcast_add() before taking the tree reference, and free the still-private mcg on failure. Because the group is never visible in mcg_tree until the multicast address is programmed, no concurrent caller can look it up or attach a QP to a group that is about to be torn down, so the error path needs no conditional unwind. If another caller publishes the same MGID while the address is being programmed, the post-add re-check under mcg_lock finds the winner; this caller then drops its private object and balances its own rxe_mcast_add() with rxe_mcast_del() before returning the winner. Reproduced by forcing the rxe_mcast_add() error return under KASAN: without the change the next attach to the same MGID reports a slab-use-after-free in __rxe_lookup_mcg(); with it the forced failure returns cleanly. A no-injection attach/detach regression, including a two-QP shared join/leave and re-attach, stays KASAN- and leak-clean. Fixes: a926a903b7dc ("RDMA/rxe: Do not call dev_mc_add/del() under a spinlock") Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/20260617022728.2770116-1-michael.bommarito@gmail.com Reviewed-by: Zhu Yanjun Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/rxe/rxe_mcast.c | 50 +++++++++++++++++++-------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_mcast.c b/drivers/infiniband/sw/rxe/rxe_mcast.c index acd03bd87794e2..5ca9211ab33bc6 100644 --- a/drivers/infiniband/sw/rxe/rxe_mcast.c +++ b/drivers/infiniband/sw/rxe/rxe_mcast.c @@ -175,7 +175,9 @@ struct rxe_mcg *rxe_lookup_mcg(struct rxe_dev *rxe, union ib_gid *mgid) * @mgid: multicast address as a gid * @mcg: new mcg object * - * Context: caller should hold rxe->mcg lock + * Initializes the mcg fields. The mcg is private and not yet visible in + * mcg_tree, so this may run without rxe->mcg_lock; __rxe_publish_mcg() + * makes it visible under the lock once it is ready. */ static void __rxe_init_mcg(struct rxe_dev *rxe, union ib_gid *mgid, struct rxe_mcg *mcg) @@ -184,13 +186,22 @@ static void __rxe_init_mcg(struct rxe_dev *rxe, union ib_gid *mgid, memcpy(&mcg->mgid, mgid, sizeof(mcg->mgid)); INIT_LIST_HEAD(&mcg->qp_list); mcg->rxe = rxe; +} - /* caller holds a ref on mcg but that will be - * dropped when mcg goes out of scope. We need to take a ref - * on the pointer that will be saved in the red-black tree - * by __rxe_insert_mcg and used to lookup mcg from mgid later. - * Inserting mcg makes it visible to outside so this should - * be done last after the object is ready. +/** + * __rxe_publish_mcg - make a fully initialized mcg visible in mcg_tree + * @mcg: the mcg object + * + * Context: caller must hold rxe->mcg_lock and a reference on mcg + */ +static void __rxe_publish_mcg(struct rxe_mcg *mcg) +{ + /* caller holds a ref on mcg but that will be dropped when mcg goes + * out of scope. We need to take a ref on the pointer that will be + * saved in the red-black tree by __rxe_insert_mcg and used to lookup + * mcg from mgid later. Inserting mcg makes it visible to outside so + * this is done last after the object is ready and the multicast + * address has been programmed. */ kref_get(&mcg->ref_cnt); __rxe_insert_mcg(mcg); @@ -228,26 +239,37 @@ static struct rxe_mcg *rxe_get_mcg(struct rxe_dev *rxe, union ib_gid *mgid) err = -ENOMEM; goto err_dec; } + __rxe_init_mcg(rxe, mgid, mcg); + + /* program the multicast address while mcg is still private, before + * it is inserted into mcg_tree. dev_mc_add() may sleep so this must + * run outside mcg_lock. On failure mcg was never published, so a + * plain free is correct and the tree is untouched. + */ + err = rxe_mcast_add(rxe, mgid); + if (err) { + kfree(mcg); + goto err_dec; + } spin_lock_bh(&rxe->mcg_lock); - /* re-check to see if someone else just added it */ + /* re-check to see if someone else just added it while we were adding + * the multicast address; if so use theirs and drop ours + */ tmp = __rxe_lookup_mcg(rxe, mgid); if (tmp) { spin_unlock_bh(&rxe->mcg_lock); + rxe_mcast_del(rxe, mgid); atomic_dec(&rxe->mcg_num); kfree(mcg); return tmp; } - __rxe_init_mcg(rxe, mgid, mcg); + __rxe_publish_mcg(mcg); spin_unlock_bh(&rxe->mcg_lock); - /* add mcast address outside of lock */ - err = rxe_mcast_add(rxe, mgid); - if (!err) - return mcg; + return mcg; - kfree(mcg); err_dec: atomic_dec(&rxe->mcg_num); return ERR_PTR(err); From 975396b9e5a4028e649f4b9a6a5ca5dfb76a824b Mon Sep 17 00:00:00 2001 From: Shuhei Takeshita Date: Sun, 9 Aug 2026 12:27:42 +0900 Subject: [PATCH 0041/1417] IB/hfi1: Resolve the credit-return buffer through the send context's node hfi1_file_mmap()'s PIO_CRED case derives this context's credit-return page offset, and the DMA handle for it, from dd->cr_base[uctxt->numa_id]. uctxt->numa_id is the node of whichever CPU the process happened to be running on, but the entry itself lives in the credit-return allocation of the send context's own node: sc->hw_free = &sc->dd->cr_base[sc->node].va[gc].cr[index]; and user send contexts are allocated with sc_alloc(dd, SC_USER, ..., dd->node), the HFI-local node. On a multi-socket host with the process running off that node the two allocations differ, so the subtraction produces an offset into an unrelated buffer and the DMA handle belongs to the wrong allocation. Use the send context's own node for all three references. The continuation lines are reindented at the same time; they mixed spaces and tabs. Fixes: 7724105686e7 ("IB/hfi1: add driver files") Cc: stable@vger.kernel.org Signed-off-by: Shuhei Takeshita Link: https://patch.msgid.link/20260809032743.2671579-2-jyohuku.alterego@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/hfi1/file_ops.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/hw/hfi1/file_ops.c b/drivers/infiniband/hw/hfi1/file_ops.c index dc548e6802e247..72c21d7455fd61 100644 --- a/drivers/infiniband/hw/hfi1/file_ops.c +++ b/drivers/infiniband/hw/hfi1/file_ops.c @@ -382,10 +382,10 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) * of enabled contexts > 64 and 128 respectively). */ cr_page_offset = ((u64)uctxt->sc->hw_free - - (u64)dd->cr_base[uctxt->numa_id].va) & - PAGE_MASK; - memvirt = dd->cr_base[uctxt->numa_id].va + cr_page_offset; - memdma = dd->cr_base[uctxt->numa_id].dma + cr_page_offset; + (u64)dd->cr_base[uctxt->sc->node].va) & + PAGE_MASK; + memvirt = dd->cr_base[uctxt->sc->node].va + cr_page_offset; + memdma = dd->cr_base[uctxt->sc->node].dma + cr_page_offset; memlen = PAGE_SIZE; flags &= ~VM_MAYWRITE; flags |= VM_DONTCOPY | VM_DONTEXPAND; From 62f0f34fbd2b2d5653d33d3b9d42fdcabb1c0101 Mon Sep 17 00:00:00 2001 From: Shuhei Takeshita Date: Sun, 9 Aug 2026 12:27:43 +0900 Subject: [PATCH 0042/1417] IB/hfi1: Fix the PIO_CRED credit-return mmap hfi1_file_mmap()'s PIO_CRED case must hand user space the single credit-return page that holds this context's entry. That page is the second or third page of the per-node credit-return allocation once the hardware send context index reaches 64 or 128, so the failure below is intermittent: when the entry lands on the first page the offset is zero and everything works. Two things are wrong. First, cr_page_offset is a byte offset but .va is a struct credit_return *, so adding it is pointer arithmetic and scales the offset by sizeof(struct credit_return) == 64. memvirt then lands 256 KiB or 512 KiB past a 10240-byte allocation. With an IOMMU translating, that address is inside the vmalloc range but in no vm_area, so dma_mmap_coherent() -> iommu_dma_mmap() finds no pages, vmalloc_to_pfn() returns page_to_pfn(NULL), and remap_pfn_range() installs a frame above MAXPHYADDR. The first user read then takes: psm2_ep_open_pr: Corrupted page table at address 7a14d007e000 PGD 800000013886a067 P4D 800000013886a067 PUD 13886b067 PMD 13886c067 PTE 800049168e911235 Oops: Bad pagetable: 000d [#1] SMP PTI Second, and still wrong once the arithmetic is corrected, dma_mmap_coherent() describes a whole coherent buffer and selects the page within it with vma->vm_pgoff. Offsetting cpu_addr has no effect: for a vmap'd allocation iommu_dma_mmap() uses cpu_addr only to locate the vm_area and then maps pages[vm_pgoff], which hfi1_file_mmap() has just set to 0. User space therefore always receives the first credit-return page, every credit read is for the wrong context, and send PIO stalls forever. Use the DMA API as intended: pass the base of the allocation with its full length and select the page with vm_pgoff. A separate length is needed because memlen must keep describing the VMA for the existing size check. The dma-direct path stays correct as well, since dma_direct_mmap() adds the same vm_pgoff to the base pfn. Tested on a Dell T7610 (Xeon E5-2650 v2, Intel IOMMU in DMA-FQ mode) against a Threadripper PRO 3995WX peer, both Omni-Path 100. Before this change psm2_ep_open() Oopses the kernel; with only the arithmetic corrected psm2_ep_open() succeeds but any transfer that uses send PIO hangs, PSM2_SDMA=2 (send PIO disabled) completing normally while PSM2_SDMA=0 (send PIO only) hangs every time. With this change send PIO, send DMA and the default mixed mode all work. Fixes: 1ec82317a1da ("IB/hfi1: Use dma_mmap_coherent for matching buffers") Cc: stable@vger.kernel.org Signed-off-by: Shuhei Takeshita Link: https://patch.msgid.link/20260809032743.2671579-3-jyohuku.alterego@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/hfi1/file_ops.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/hw/hfi1/file_ops.c b/drivers/infiniband/hw/hfi1/file_ops.c index 72c21d7455fd61..1a36f995c4f6db 100644 --- a/drivers/infiniband/hw/hfi1/file_ops.c +++ b/drivers/infiniband/hw/hfi1/file_ops.c @@ -326,6 +326,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) void *memvirt = NULL; dma_addr_t memdma = 0; u8 subctxt, mapio = 0, vmf = 0, type; + size_t memdmalen = 0; ssize_t memlen = 0; int ret = 0; u16 ctxt; @@ -371,7 +372,9 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) mapio = 1; break; case PIO_CRED: { + struct credit_return_base *cr = &dd->cr_base[uctxt->sc->node]; u64 cr_page_offset; + if (flags & VM_WRITE) { ret = -EPERM; goto done; @@ -381,11 +384,18 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) * second or third page allocated for credit returns (if number * of enabled contexts > 64 and 128 respectively). */ - cr_page_offset = ((u64)uctxt->sc->hw_free - - (u64)dd->cr_base[uctxt->sc->node].va) & + cr_page_offset = ((u64)uctxt->sc->hw_free - (u64)cr->va) & PAGE_MASK; - memvirt = dd->cr_base[uctxt->sc->node].va + cr_page_offset; - memdma = dd->cr_base[uctxt->sc->node].dma + cr_page_offset; + /* + * dma_mmap_coherent() describes the whole coherent buffer and + * selects the page within it with vma->vm_pgoff, so pass the + * base of the allocation and its length and let vm_pgoff pick + * the page. + */ + vma->vm_pgoff = cr_page_offset >> PAGE_SHIFT; + memvirt = cr->va; + memdma = cr->dma; + memdmalen = TXE_NUM_CONTEXTS * sizeof(struct credit_return); memlen = PAGE_SIZE; flags &= ~VM_MAYWRITE; flags |= VM_DONTCOPY | VM_DONTEXPAND; @@ -567,7 +577,8 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) ret = 0; } else if (memdma) { ret = dma_mmap_coherent(&dd->pcidev->dev, vma, - memvirt, memdma, memlen); + memvirt, memdma, + memdmalen ? memdmalen : memlen); } else if (mapio) { ret = io_remap_pfn_range(vma, vma->vm_start, PFN_DOWN(memaddr), From 2be77295316c2dff0a33c0dc3a65abdab4ccf796 Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Tue, 11 Aug 2026 19:25:57 +0300 Subject: [PATCH 0043/1417] RDMA/mlx5: Remove warn on missing representor in query_port_speed The representor ib_device's phys_port_cnt is set to the total vport count when the uplink vport rep loads. Individual port[i].rep entries are populated only as each VF/SF vport rep registers. A NULL .rep for a given port index is therefore expected while VF reps are still loading or haven't been enabled yet. Tools like ibstat and ibv_devinfo iterate over all ports of all RDMA devices. Some ports may not have an eswitch representor, causing repeated dmesg warnings when these tools run without a device argument. This causes dmesg to be flooded with this message on every ibstat invocation. Remove the warning and return -ENODEV when no representor exists for the queried port. Fixes: aaecff5e13cd ("RDMA/mlx5: Implement query_port_speed callback") Signed-off-by: Or Har-Toov Reviewed-by: Shay Drory Signed-off-by: Edward Srouji Link: https://patch.msgid.link/20260811-remove-warn-on-miss-rep-v1-1-eccf399bc6af@nvidia.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/mlx5/main.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/hw/mlx5/main.c b/drivers/infiniband/hw/mlx5/main.c index 373ee1f42d4ad1..a457647edacdee 100644 --- a/drivers/infiniband/hw/mlx5/main.c +++ b/drivers/infiniband/hw/mlx5/main.c @@ -1683,11 +1683,8 @@ static int mlx5_ib_query_port_speed_rep(struct mlx5_ib_dev *dev, u32 port_num, struct mlx5_core_dev *mdev; u16 op_mod; - if (!dev->port[port_num - 1].rep) { - mlx5_ib_warn(dev, "Representor doesn't exist for port %u\n", - port_num); - return -EINVAL; - } + if (!dev->port[port_num - 1].rep) + return -ENODEV; rep = dev->port[port_num - 1].rep; mdev = mlx5_eswitch_get_core_dev(rep->esw); From d215f014b3526a9898d87bfb4b866287f0864cc9 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:32 +0200 Subject: [PATCH 0044/1417] KVM: s390: Fix dirty marking in adapter_indicators_set*() When the indicator and/or summary bits are set in the guest, the accessed page was only marked dirty in KVM if the access was performed using the slow path; accesses through the new kvm_arch_set_irq_inatomic fast inject path would not mark the page as dirty. Fix by adding/moving the missing calls to mark_page_dirty(). Note that for the inatomic path set_page_dirty{,_lock}() is not needed as the page stays pinned; the unpin path correctly marks it as dirty. Opportunistically reorder the local variables to be in reverse Christmas tree order and refactor to use guard(). Fixes: 1e95e3bc6b05 ("KVM: s390: Enable adapter_indicators_set to use mapped pages") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-2-imbrenda@linux.ibm.com> --- arch/s390/kvm/s390/interrupt.c | 75 +++++++++++++++++----------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/arch/s390/kvm/s390/interrupt.c b/arch/s390/kvm/s390/interrupt.c index 0381ae98170354..49b4e233e79155 100644 --- a/arch/s390/kvm/s390/interrupt.c +++ b/arch/s390/kvm/s390/interrupt.c @@ -2984,61 +2984,58 @@ static int adapter_indicators_set(struct kvm *kvm, struct s390_io_adapter *adapter, struct kvm_s390_adapter_int *adapter_int) { - unsigned long bit; - int summary_set, idx; struct s390_map_info *ind_info, *summary_info; - void *map; struct page *ind_page, *summary_page; - unsigned long flags; + unsigned long bit; + int summary_set; + void *map; ind_page = NULL; - spin_lock_irqsave(&adapter->maps_lock, flags); - ind_info = get_map_info(adapter, adapter_int->ind_addr); + scoped_guard(spinlock_irqsave, &adapter->maps_lock) { + ind_info = get_map_info(adapter, adapter_int->ind_addr); + if (ind_info) { + map = page_address(ind_info->page); + bit = get_ind_bit(ind_info->addr, adapter_int->ind_offset, adapter->swap); + set_bit(bit, map); + } + } if (!ind_info) { - spin_unlock_irqrestore(&adapter->maps_lock, flags); ind_page = pin_map_page(kvm, adapter_int->ind_addr, 0); if (!ind_page) return -1; - idx = srcu_read_lock(&kvm->srcu); map = page_address(ind_page); bit = get_ind_bit(adapter_int->ind_addr, adapter_int->ind_offset, adapter->swap); set_bit(bit, map); - mark_page_dirty(kvm, adapter_int->ind_gaddr >> PAGE_SHIFT); set_page_dirty_lock(ind_page); - srcu_read_unlock(&kvm->srcu, idx); unpin_user_page(ind_page); - } else { - map = page_address(ind_info->page); - bit = get_ind_bit(ind_info->addr, adapter_int->ind_offset, adapter->swap); - set_bit(bit, map); - spin_unlock_irqrestore(&adapter->maps_lock, flags); } + scoped_guard(srcu, &kvm->srcu) + mark_page_dirty(kvm, gpa_to_gfn(adapter_int->ind_gaddr)); - spin_lock_irqsave(&adapter->maps_lock, flags); - summary_info = get_map_info(adapter, adapter_int->summary_addr); + scoped_guard(spinlock_irqsave, &adapter->maps_lock) { + summary_info = get_map_info(adapter, adapter_int->summary_addr); + if (summary_info) { + map = page_address(summary_info->page); + bit = get_ind_bit(summary_info->addr, adapter_int->summary_offset, + adapter->swap); + summary_set = test_and_set_bit(bit, map); + } + } if (!summary_info) { - spin_unlock_irqrestore(&adapter->maps_lock, flags); summary_page = pin_map_page(kvm, adapter_int->summary_addr, 0); if (!summary_page) return -1; - idx = srcu_read_lock(&kvm->srcu); map = page_address(summary_page); bit = get_ind_bit(adapter_int->summary_addr, adapter_int->summary_offset, adapter->swap); summary_set = test_and_set_bit(bit, map); - mark_page_dirty(kvm, adapter_int->summary_gaddr >> PAGE_SHIFT); set_page_dirty_lock(summary_page); - srcu_read_unlock(&kvm->srcu, idx); unpin_user_page(summary_page); - } else { - map = page_address(summary_info->page); - bit = get_ind_bit(summary_info->addr, adapter_int->summary_offset, - adapter->swap); - summary_set = test_and_set_bit(bit, map); - spin_unlock_irqrestore(&adapter->maps_lock, flags); } + scoped_guard(srcu, &kvm->srcu) + mark_page_dirty(kvm, gpa_to_gfn(adapter_int->summary_gaddr)); return summary_set ? 0 : 1; } @@ -3048,26 +3045,29 @@ static int adapter_indicators_set_fast(struct kvm *kvm, struct kvm_s390_adapter_int *adapter_int, int setbit) { + struct s390_map_info *ind_info, *summary_info; unsigned long bit; int summary_set; - struct s390_map_info *ind_info, *summary_info; void *map; - spin_lock(&adapter->maps_lock); + guard(srcu)(&kvm->srcu); + guard(spinlock)(&adapter->maps_lock); + ind_info = get_map_info(adapter, adapter_int->ind_addr); - if (!ind_info) { - spin_unlock(&adapter->maps_lock); + if (!ind_info) return -EWOULDBLOCK; - } + map = page_address(ind_info->page); bit = get_ind_bit(ind_info->addr, adapter_int->ind_offset, adapter->swap); - if (setbit) + if (setbit) { set_bit(bit, map); + mark_page_dirty(kvm, gpa_to_gfn(adapter_int->ind_gaddr)); + } + summary_info = get_map_info(adapter, adapter_int->summary_addr); - if (!summary_info) { - spin_unlock(&adapter->maps_lock); + if (!summary_info) return -EWOULDBLOCK; - } + map = page_address(summary_info->page); bit = get_ind_bit(summary_info->addr, adapter_int->summary_offset, adapter->swap); @@ -3077,7 +3077,8 @@ static int adapter_indicators_set_fast(struct kvm *kvm, summary_set = test_and_set_bit(bit, map); else summary_set = test_and_clear_bit(bit, map); - spin_unlock(&adapter->maps_lock); + mark_page_dirty(kvm, gpa_to_gfn(adapter_int->summary_gaddr)); + return summary_set ? 0 : 1; } From ae12d2f9c119639a142d92c4a37c30277e8576da Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:33 +0200 Subject: [PATCH 0045/1417] KVM: s390: Fix compile warning for kvm_s390_update_cmma_dirty() The parameter "old" should be marked as const, to prevent compile-time warnings. Fixes: d487a24041c2 ("KVM: s390: Prepare gmap for a second KVM implementation") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-3-imbrenda@linux.ibm.com> --- arch/s390/kvm/s390/s390.c | 2 +- arch/s390/kvm/s390/s390.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c index b0839e887221ec..8f7e09d7d049ab 100644 --- a/arch/s390/kvm/s390/s390.c +++ b/arch/s390/kvm/s390/s390.c @@ -5766,7 +5766,7 @@ static long cmma_d_count_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_ return 0; } -void kvm_s390_update_cmma_dirty(struct kvm *kvm, struct kvm_memory_slot *old) +void kvm_s390_update_cmma_dirty(struct kvm *kvm, const struct kvm_memory_slot *old) { const struct dat_walk_ops ops = { .pte_entry = cmma_d_count_pte, }; diff --git a/arch/s390/kvm/s390/s390.h b/arch/s390/kvm/s390/s390.h index d284a263ba7042..aa0d1d062f8dce 100644 --- a/arch/s390/kvm/s390/s390.h +++ b/arch/s390/kvm/s390/s390.h @@ -472,7 +472,7 @@ int __kvm_s390_mprotect_many(struct gmap *gmap, gpa_t gpa, u8 npages, unsigned i unsigned long bits); bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu); -void kvm_s390_update_cmma_dirty(struct kvm *kvm, struct kvm_memory_slot *old); +void kvm_s390_update_cmma_dirty(struct kvm *kvm, const struct kvm_memory_slot *old); int kvm_s390_vm_stop_migration(struct kvm *kvm); From faff4c8ff3dbec6d71b89dd281a0d17c4478fa44 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:34 +0200 Subject: [PATCH 0046/1417] KVM: s390: Fix _gaccess_shadow_fault() In some circumstances, it is possible that the page of nested guest memory that is being shadowed is not present at all in the parent guest gmap. dat_entry_walk() will not find any leaf entry and return with -ENOENT, which will erroneously be propagated all the way to userspace. Fix by manually calling gmap_link() on the memory of the nested guest that is being shadowed if the mapping was not already present. Fixes: e38c884df921 ("KVM: s390: Switch to new gmap") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-4-imbrenda@linux.ibm.com> --- arch/s390/kvm/s390/gaccess.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/arch/s390/kvm/s390/gaccess.c b/arch/s390/kvm/s390/gaccess.c index e5c064f263df8d..405345ccc4f1bb 100644 --- a/arch/s390/kvm/s390/gaccess.c +++ b/arch/s390/kvm/s390/gaccess.c @@ -1589,12 +1589,25 @@ static inline int ___gaccess_shadow_fault(struct kvm_vcpu *vcpu, struct gmap *sg parent = READ_ONCE(sg->parent); if (!parent) return -EAGAIN; +retry: scoped_guard(spinlock, &parent->children_lock) { if (READ_ONCE(sg->parent) != parent) return -EAGAIN; sg->invalidated = false; rc = _gaccess_do_shadow(vcpu->arch.mc, sg, saddr, walk); } + if (rc == -ENOENT) { + struct kvm_memory_slot *slot; + struct guest_fault *entries; + + entries = get_entries(walk); + slot = kvm_vcpu_gfn_to_memslot(vcpu, entries[LEVEL_MEM].gfn); + if (!slot) + return PGM_ADDRESSING; + rc = gmap_link(vcpu->arch.mc, parent, entries + LEVEL_MEM, slot); + if (!rc) + goto retry; + } if (!rc) kvm_s390_release_faultin_array(vcpu->kvm, walk->raw_entries, false); return rc; From 00c0ae5e438615bde828a3b9f33912960f4e6511 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:35 +0200 Subject: [PATCH 0047/1417] KVM: s390: Refactor dat_set_slot() Refactor dat_set_slot(), _dat_slot_pte(), _dat_slot_crste(). Now they only take a struct kvm_s390_mmu_cache as priv. For dat_delete_slot(), mc is NULL, as no allocations should take place. This is needed as a prerequisite to move gmap DAT table setup from kvm_arch_commit_memory_region() to kvm_arch_prepare_memory_region(). Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-5-imbrenda@linux.ibm.com> --- arch/s390/kvm/gmap/dat.c | 29 +++++++++-------------------- arch/s390/kvm/gmap/dat.h | 10 ++++------ arch/s390/kvm/gmap/kvm_mmu.c | 4 ++-- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/arch/s390/kvm/gmap/dat.c b/arch/s390/kvm/gmap/dat.c index 24547e39fab244..dcedd5479d828d 100644 --- a/arch/s390/kvm/gmap/dat.c +++ b/arch/s390/kvm/gmap/dat.c @@ -846,19 +846,12 @@ long dat_reset_skeys(union asce asce, gfn_t start) } #endif /* KVM_S390_MANAGES_S390_GUEST */ -struct slot_priv { - unsigned long token; - struct kvm_s390_mmu_cache *mc; -}; - static long _dat_slot_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_walk *walk) { - struct slot_priv *p = walk->priv; - union crste dummy = { .val = p->token }; union pte new_pte, pte = READ_ONCE(*ptep); union pgste pgste; - new_pte = _PTE_TOK(dummy.tok.type, dummy.tok.par); + new_pte = walk->priv ? _PTE_EMPTY : _PTE_TOK(_DAT_TOKEN_PIC, PGM_ADDRESSING); /* Table entry already in the desired state. */ if (pte.val == new_pte.val) @@ -875,10 +868,9 @@ static long _dat_slot_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_wal static long _dat_slot_crste(union crste *crstep, gfn_t gfn, gfn_t next, struct dat_walk *walk) { union crste new_crste, crste = READ_ONCE(*crstep); - struct slot_priv *p = walk->priv; + struct kvm_s390_mmu_cache *mc = walk->priv; - new_crste.val = p->token; - new_crste.h.tt = crste.h.tt; + new_crste = mc ? _CRSTE_EMPTY(crste.h.tt) : _CRSTE_HOLE(crste.h.tt); /* Table entry already in the desired state. */ if (crste.val == new_crste.val) @@ -902,7 +894,10 @@ static long _dat_slot_crste(union crste *crstep, gfn_t gfn, gfn_t next, struct d if (!crste.h.fc && !crste.h.i) return 0; /* Split (install a lower level table), and handle things there. */ - return dat_split_crste(p->mc, crstep, gfn, walk->asce, false); + if (mc) + return dat_split_crste(mc, crstep, gfn, walk->asce, false); + /* A large page should never cross memslots boundaries */ + return -EINVAL; } static const struct dat_walk_ops dat_slot_ops = { @@ -910,16 +905,10 @@ static const struct dat_walk_ops dat_slot_ops = { .crste_ops = { _dat_slot_crste, _dat_slot_crste, _dat_slot_crste, _dat_slot_crste, }, }; -int dat_set_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, gfn_t end, - u16 type, u16 param) +int dat_set_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, gfn_t end) { - struct slot_priv priv = { - .token = _CRSTE_TOK(0, type, param).val, - .mc = mc, - }; - return _dat_walk_gfn_range(start, end, asce, &dat_slot_ops, - DAT_WALK_IGN_HOLES | DAT_WALK_ANY, &priv); + DAT_WALK_IGN_HOLES | DAT_WALK_ANY, mc); } static void pgste_set_unlock_multiple(union pte *first, int n, union pgste *pgstes) diff --git a/arch/s390/kvm/gmap/dat.h b/arch/s390/kvm/gmap/dat.h index e452c141b84138..90389d47ba4e04 100644 --- a/arch/s390/kvm/gmap/dat.h +++ b/arch/s390/kvm/gmap/dat.h @@ -547,8 +547,7 @@ long dat_reset_skeys(union asce asce, gfn_t start); unsigned long dat_get_ptval(struct page_table *table, struct ptval_param param); void dat_set_ptval(struct page_table *table, struct ptval_param param, unsigned long val); -int dat_set_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, gfn_t end, - u16 type, u16 param); +int dat_set_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, gfn_t end); #if KVM_S390_MANAGES_S390_GUEST int dat_set_prefix_notif_bit(union asce asce, gfn_t gfn); @@ -973,16 +972,15 @@ static inline int get_level(union crste *crstep, union pte *ptep) return ptep ? TABLE_TYPE_PAGE_TABLE : crstep->h.tt; } -static inline int dat_delete_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, - unsigned long npages) +static inline int dat_delete_slot(union asce asce, gfn_t start, unsigned long npages) { - return dat_set_slot(mc, asce, start, start + npages, _DAT_TOKEN_PIC, PGM_ADDRESSING); + return dat_set_slot(NULL, asce, start, start + npages); } static inline int dat_create_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, unsigned long npages) { - return dat_set_slot(mc, asce, start, start + npages, _DAT_TOKEN_NONE, 0); + return dat_set_slot(mc, asce, start, start + npages); } static inline bool crste_is_ucas(union crste crste) diff --git a/arch/s390/kvm/gmap/kvm_mmu.c b/arch/s390/kvm/gmap/kvm_mmu.c index b08b8229bb6fff..4c8054e184908d 100644 --- a/arch/s390/kvm/gmap/kvm_mmu.c +++ b/arch/s390/kvm/gmap/kvm_mmu.c @@ -111,10 +111,10 @@ void s390_kvm_mmu_commit_memory_region(struct kvm *kvm, kvm_s390_update_cmma_dirty(kvm, old); switch (change) { case KVM_MR_DELETE: - rc = dat_delete_slot(mc, kvm->arch.gmap->asce, old->base_gfn, old->npages); + rc = dat_delete_slot(kvm->arch.gmap->asce, old->base_gfn, old->npages); break; case KVM_MR_MOVE: - rc = dat_delete_slot(mc, kvm->arch.gmap->asce, old->base_gfn, old->npages); + rc = dat_delete_slot(kvm->arch.gmap->asce, old->base_gfn, old->npages); if (rc) break; fallthrough; From 19192a4043277af380f83d550a55b92eeeaac7c6 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:36 +0200 Subject: [PATCH 0048/1417] KVM: s390: Move all code into s390_kvm_mmu_prepare_memory_region() Move all code from s390_kvm_mmu_commit_memory_region() into s390_kvm_mmu_prepare_memory_region(). This allows the function to fail gracefully if needed. The previous behaviour was to print a warning and continue execution with page tables inconsistent with the memslots. Fixes: e38c884df921 ("KVM: s390: Switch to new gmap") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-6-imbrenda@linux.ibm.com> --- arch/s390/kvm/gmap/kvm_mmu.c | 89 +++++++++++++++--------------------- arch/s390/kvm/gmap/kvm_mmu.h | 4 -- arch/s390/kvm/s390/s390.c | 1 - 3 files changed, 36 insertions(+), 58 deletions(-) diff --git a/arch/s390/kvm/gmap/kvm_mmu.c b/arch/s390/kvm/gmap/kvm_mmu.c index 4c8054e184908d..c2ffb5e59ec637 100644 --- a/arch/s390/kvm/gmap/kvm_mmu.c +++ b/arch/s390/kvm/gmap/kvm_mmu.c @@ -47,6 +47,9 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *new, enum kvm_mr_change change) { + struct kvm_s390_mmu_cache *mc __free(kvm_s390_mmu_cache) = NULL; + int rc = 0; + if (kvm_is_ucontrol(kvm) && new && new->id < KVM_USER_MEM_SLOTS) return -EINVAL; @@ -61,6 +64,10 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, * and munmap() stuff in this slot after doing this call at any * time. */ + if (change != KVM_MR_MOVE && change != KVM_MR_CREATE) { + WARN(1, "Unknown KVM MR CHANGE: %d\n", change); + return -EINVAL; + } if (new->userspace_addr & ~PAGE_MASK) return -EINVAL; if ((new->base_gfn + new->npages) * PAGE_SIZE > kvm->arch.mem_limit) @@ -69,65 +76,41 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, return -EINVAL; } - if (!kvm_s390_is_migration_mode(kvm)) - return 0; - - /* - * Turn off migration mode when: - * - userspace creates a new memslot with dirty logging off, - * - userspace modifies an existing memslot (MOVE or FLAGS_ONLY) and - * dirty logging is turned off. - * Migration mode expects dirty page logging being enabled to store - * its dirty bitmap. - */ - if (change != KVM_MR_DELETE && - !(new->flags & KVM_MEM_LOG_DIRTY_PAGES)) - WARN(kvm_s390_vm_stop_migration(kvm), - "Failed to stop migration mode"); - - return 0; -} - -void s390_kvm_mmu_commit_memory_region(struct kvm *kvm, - struct kvm_memory_slot *old, - const struct kvm_memory_slot *new, - enum kvm_mr_change change) -{ - struct kvm_s390_mmu_cache *mc __free(kvm_s390_mmu_cache) = NULL; - int rc = 0; - - guard(mutex)(&kvm->slots_arch_lock); + if (kvm->arch.migration_mode) { + /* + * Turn off migration mode when: + * - userspace creates a new memslot with dirty logging off, + * - userspace modifies an existing memslot (MOVE or FLAGS_ONLY) + * and dirty logging is turned off. + * Migration mode expects dirty page logging being enabled to + * store its dirty bitmap. + */ + if (change != KVM_MR_DELETE && + !(new->flags & KVM_MEM_LOG_DIRTY_PAGES)) + WARN(kvm_s390_vm_stop_migration(kvm), + "Failed to stop migration mode"); + } if (change == KVM_MR_FLAGS_ONLY) - return; - - mc = kvm_s390_new_mmu_cache(); - if (!mc) { - rc = -ENOMEM; - goto out; + return 0; + if (change != KVM_MR_DELETE) { + /* Enough capacity to add a new memslot */ + mc = kvm_s390_new_mmu_cache(); + if (!mc) + return -ENOMEM; } - scoped_guard(write_lock, &kvm->mmu_lock) { kvm_s390_update_cmma_dirty(kvm, old); - switch (change) { - case KVM_MR_DELETE: - rc = dat_delete_slot(kvm->arch.gmap->asce, old->base_gfn, old->npages); - break; - case KVM_MR_MOVE: + if (change == KVM_MR_DELETE || change == KVM_MR_MOVE) rc = dat_delete_slot(kvm->arch.gmap->asce, old->base_gfn, old->npages); - if (rc) - break; - fallthrough; - case KVM_MR_CREATE: + if (!rc && (change == KVM_MR_MOVE || change == KVM_MR_CREATE)) rc = dat_create_slot(mc, kvm->arch.gmap->asce, new->base_gfn, new->npages); - break; - case KVM_MR_FLAGS_ONLY: - break; - default: - WARN(1, "Unknown KVM MR CHANGE: %d\n", change); - } } -out: - if (rc) - pr_warn("failed to commit memory region\n"); + /* + * Can only be triggered if dat_{create,delete}_slot() found an + * internal inconsistency or if the mmu cache ran out of memory; + * both should be impossible. + */ + KVM_BUG_ON(rc, kvm); + return rc; } diff --git a/arch/s390/kvm/gmap/kvm_mmu.h b/arch/s390/kvm/gmap/kvm_mmu.h index cdbd390bd33cd2..43cde61bae0326 100644 --- a/arch/s390/kvm/gmap/kvm_mmu.h +++ b/arch/s390/kvm/gmap/kvm_mmu.h @@ -10,9 +10,5 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, const struct kvm_memory_slot *old, struct kvm_memory_slot *new, enum kvm_mr_change change); -void s390_kvm_mmu_commit_memory_region(struct kvm *kvm, - struct kvm_memory_slot *old, - const struct kvm_memory_slot *new, - enum kvm_mr_change change); #endif /* ARCH_KVM_GMAP_KVM_MMU_H */ diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c index 8f7e09d7d049ab..eca4a4359ab292 100644 --- a/arch/s390/kvm/s390/s390.c +++ b/arch/s390/kvm/s390/s390.c @@ -5781,7 +5781,6 @@ void kvm_arch_commit_memory_region(struct kvm *kvm, struct kvm_memory_slot *old, const struct kvm_memory_slot *new, enum kvm_mr_change change) { - s390_kvm_mmu_commit_memory_region(kvm, old, new, change); } /** From f3a557067d57ce6ae98d485c8009b223e16f5f36 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:37 +0200 Subject: [PATCH 0049/1417] KVM: s390: Add missing srcu in kvm_s390_set_irq_state() Like kvm_s390_inject_vcpu(), kvm_s390_set_irq_state() also needs the kvm->srcu or the slots lock when performing the Store status operation. Fix by taking kvm->srcu in kvm_s390_set_irq_state(). Fixes: ba5c1e9b6cee ("KVM: s390: interrupt subsystem, cpu timer, waitpsw") Fixes: 062e44a9319f ("KVM: s390: Use srcu in kvm_arch_vcpu_unlocked_ioctl()") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-7-imbrenda@linux.ibm.com> --- arch/s390/kvm/s390/interrupt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/s390/kvm/s390/interrupt.c b/arch/s390/kvm/s390/interrupt.c index 49b4e233e79155..f892f430788310 100644 --- a/arch/s390/kvm/s390/interrupt.c +++ b/arch/s390/kvm/s390/interrupt.c @@ -3229,9 +3229,9 @@ int kvm_s390_set_irq_state(struct kvm_vcpu *vcpu, void __user *irqstate, int len break; } } - if (storestatus) { - n = kvm_s390_store_status_unloaded(vcpu, KVM_S390_STORE_STATUS_NOADDR); + scoped_guard(srcu, &vcpu->kvm->srcu) + n = kvm_s390_store_status_unloaded(vcpu, KVM_S390_STORE_STATUS_NOADDR); return r ? r : n; } From 27554b9505ddfc0aeab466aeb60929dfa17284c7 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:38 +0200 Subject: [PATCH 0050/1417] KVM: s390: Fix potential races in dat skey functions When dat_cond_set_storage_key() finds a large page, it will conditionally set the storage key in absolute memory using large_crste_to_phys() to get the absolute address. There is a race window between dat_entry_walk() and large_crste_to_phys(): the large page could have been split concurrently, and large_crste_to_phys() might be called with a crste that does not designate a large page, leading to crashes. Similar issues were also present in dat_set_storage_key(). dat_get_storage_key() and dat_reset_reference_bit() did instead check for a potential concurrent splitting of the large page, but then handled it incorrectly. Fix by performing a READ_ONCE on the crste pointer, checking and using the result, instead of dereferencing the pointer again. In case a race is detacted, try dat_entry_walk() again. Fixes: 8e03e8316eb2 ("KVM: s390: KVM page table management functions: storage keys") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-8-imbrenda@linux.ibm.com> --- arch/s390/kvm/gmap/dat.c | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/arch/s390/kvm/gmap/dat.c b/arch/s390/kvm/gmap/dat.c index dcedd5479d828d..ff80d02c9f5643 100644 --- a/arch/s390/kvm/gmap/dat.c +++ b/arch/s390/kvm/gmap/dat.c @@ -621,17 +621,20 @@ int dat_get_storage_key(union asce asce, gfn_t gfn, union skey *skey) union pte *ptep; int rc; +again: skey->skey = 0; rc = dat_entry_walk(NULL, gfn, asce, DAT_WALK_ANY, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; if (!ptep) { - union crste crste; + union crste crste = READ_ONCE(*crstep); - crste = READ_ONCE(*crstep); - if (!crste.h.fc || !crste.s.fc1.pr) + if (!crste_leaf(crste) && !crste.h.i) + goto again; + if (!crste.s.fc1.pr) return 0; + skey->skey = page_get_storage_key(large_crste_to_phys(crste, gfn)); return 0; } @@ -662,13 +665,20 @@ int dat_set_storage_key(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t gf union pte *ptep; int rc; +again: rc = dat_entry_walk(mc, gfn, asce, DAT_WALK_LEAF_ALLOC, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; if (!ptep) { - page_set_storage_key(large_crste_to_phys(*crstep, gfn), skey.skey, !nq); + union crste crste = READ_ONCE(*crstep); + + /* A large page has been split concurrently, try again */ + if (!crste_leaf(crste)) + goto again; + + page_set_storage_key(large_crste_to_phys(crste, gfn), skey.skey, !nq); return 0; } @@ -718,15 +728,22 @@ int dat_cond_set_storage_key(struct kvm_s390_mmu_cache *mmc, union asce asce, gf union pte *ptep; int rc; +again: rc = dat_entry_walk(mmc, gfn, asce, DAT_WALK_LEAF_ALLOC, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; if (!ptep) { + union crste crste = READ_ONCE(*crstep); + + /* A large page has been split concurrently, try again */ + if (!crste_leaf(crste)) + goto again; if (!oldkey) oldkey = &prev; - return page_cond_set_storage_key(large_crste_to_phys(*crstep, gfn), skey, oldkey, + + return page_cond_set_storage_key(large_crste_to_phys(crste, gfn), skey, oldkey, nq, mr, mc); } @@ -768,7 +785,7 @@ int dat_reset_reference_bit(union asce asce, gfn_t gfn, union skey *skey) int rc; skey->skey = 0; - +again: rc = dat_entry_walk(NULL, gfn, asce, DAT_WALK_ANY, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; @@ -776,9 +793,12 @@ int dat_reset_reference_bit(union asce asce, gfn_t gfn, union skey *skey) if (!ptep) { union crste crste = READ_ONCE(*crstep); - if (!crste.h.fc || !crste.s.fc1.pr) + /* A large page has been split concurrently, try again */ + if (!crste_leaf(crste) && !crste.h.i) + goto again; + if (!crste.s.fc1.pr) return 0; - skey->skey = page_reset_referenced(large_crste_to_phys(*crstep, gfn)) << 1; + skey->skey = page_reset_referenced(large_crste_to_phys(crste, gfn)) << 1; return 0; } old = pgste_get_lock(ptep); From 4ca00a9154f998116fba9a32cce5bd938d228065 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:39 +0200 Subject: [PATCH 0051/1417] KVM: s390: Fix race in _destroy_pages_crste() Use READ_ONCE() in _destroy_pages_crste() to read the crste, avoid dereferencing the pointer multiple times. Fixes: a2c17f9270cc ("KVM: s390: New gmap code") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-9-imbrenda@linux.ibm.com> --- arch/s390/kvm/gmap/gmap.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/s390/kvm/gmap/gmap.c b/arch/s390/kvm/gmap/gmap.c index 4968330e9553bd..3f3fa864cc36b9 100644 --- a/arch/s390/kvm/gmap/gmap.c +++ b/arch/s390/kvm/gmap/gmap.c @@ -994,11 +994,13 @@ static long _destroy_pages_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct da static long _destroy_pages_crste(union crste *crstep, gfn_t gfn, gfn_t next, struct dat_walk *walk) { phys_addr_t origin, cur, end; + union crste crste; - if (!crstep->h.fc || !crstep->s.fc1.pr) + crste = READ_ONCE(*crstep); + if (!crste.h.fc || !crste.s.fc1.pr) return 0; - origin = crste_origin_large(*crstep); + origin = crste_origin_large(crste); cur = ((max(gfn, walk->start) - gfn) << PAGE_SHIFT) + origin; end = ((min(next, walk->end) - gfn) << PAGE_SHIFT) + origin; for ( ; cur < end; cur += PAGE_SIZE) From ef9fbe1b93f3b617b96e86d5cd76b3fa44514cb5 Mon Sep 17 00:00:00 2001 From: Krystian Kaniewski Date: Wed, 12 Aug 2026 10:16:41 +0200 Subject: [PATCH 0052/1417] RDMA/core: Reject unregistering netdevs in ib_get_eth_speed ib_device_get_netdev() intentionally returns a referenced net_device even when it is unregistering, so matching and cleanup callers can still find the association. The reference keeps struct net_device allocated, but does not guarantee that the device remains operational. ib_get_eth_speed() uses the returned device operationally by invoking its ethtool callback. Although that call is made under RTNL, the function does not verify the registration state first. An asynchronous RDMA port query can therefore call into a netdev after NETDEV_UNREGISTER and ndo_uninit have completed. Check for NETREG_REGISTERED while holding RTNL and return -ENODEV for a device which is being unregistered. Keeping RTNL across the check and the ethtool operation prevents unregister from starting between them. Keep the speed fallback and warning under RTNL as well, so the warning can safely read netdev->name. Drop the netdev reference before releasing RTNL once all accesses to the device are complete. Fixes: d41861942fc5 ("IB/core: Add generic function to extract IB speed from netdev") Reported-by: syzbot+5fe14f2ff4ccbace9a26@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=5fe14f2ff4ccbace9a26 Signed-off-by: Krystian Kaniewski Link: https://patch.msgid.link/20260812081708.32468-1-krystianmkaniewski@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/verbs.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/core/verbs.c b/drivers/infiniband/core/verbs.c index 04abc80c13273b..c43e25d3726673 100644 --- a/drivers/infiniband/core/verbs.c +++ b/drivers/infiniband/core/verbs.c @@ -2058,11 +2058,13 @@ int ib_get_eth_speed(struct ib_device *dev, u32 port_num, u16 *speed, u8 *width) return -ENODEV; rtnl_lock(); - rc = __ethtool_get_link_ksettings(netdev, &lksettings); - rtnl_unlock(); - - dev_put(netdev); + if (READ_ONCE(netdev->reg_state) != NETREG_REGISTERED) { + dev_put(netdev); + rtnl_unlock(); + return -ENODEV; + } + rc = __ethtool_get_link_ksettings(netdev, &lksettings); if (!rc && lksettings.base.speed != (u32)SPEED_UNKNOWN) { netdev_speed = lksettings.base.speed; } else { @@ -2071,6 +2073,8 @@ int ib_get_eth_speed(struct ib_device *dev, u32 port_num, u16 *speed, u8 *width) pr_warn("%s speed is unknown, defaulting to %u\n", netdev->name, netdev_speed); } + dev_put(netdev); + rtnl_unlock(); ib_get_width_and_speed(netdev_speed, lksettings.lanes, speed, width); From a44a3f175eaee7e5aeb6a8fed381c4a0d5f49236 Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Tue, 11 Aug 2026 19:19:16 +0300 Subject: [PATCH 0053/1417] RDMA/uverbs: Fix mmap_lock/disassociation_lock circular dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 51976c6cd786 ("RDMA/core: Provide rdma_user_mmap_disassociate() to disassociate mmap pages") introduced disassociation_lock to protect new mmap registrations against uverbs_user_mmap_disassociate(), but created an ABBA deadlock: Thread A (mmap / fork): mmap_lock -> disassociation_lock Thread B (disassociate): disassociation_lock -> mmap_lock Fix by removing disassociation_lock entirely and using the pre-existing hw_destroy_rwsem instead. hw_destroy_rwsem already provides the same protection: rdma_umap_open() and ib_uverbs_mmap() both use down_read_trylock() before registering a new VMA, so holding hw_destroy_rwsem in uverbs_user_mmap_disassociate() is sufficient to block new registrations. trylock is used in both mmap paths (not blocking down_read) because mmap_lock is already held on entry, and uverbs_user_mmap_disassociate() acquires mmap_lock internally — a blocking read would recreate the same deadlock. The only caller that was not taking hw_destroy_rwsem for write was rdma_user_mmap_disassociate(). Fix it to take the rwsem per-ufile while iterating under lists_mutex. This is safe because ib_uverbs_close() releases hw_destroy_rwsem entirely before acquiring lists_mutex, so the two locks are never held simultaneously. lockdep warning: [ 776.654252] ====================================================== [ 776.655214] WARNING: possible circular locking dependency detected [ 776.656167] 6.18.0for-upstream_debug_94e244d9ccab #1 Not tainted [ 776.657114] ------------------------------------------------------ [ 776.658087] devlink/14824 is trying to acquire lock: [ 776.658879] ffff88811170c800 (&mm->mmap_lock){++++}-{4:4}, at: uverbs_user_mmap_disassociate+0x168/0x780 [ib_uverbs] [ 776.660479] [ 776.660479] but task is already holding lock: [ 776.661460] ffff888142d92b08 (&file->disassociation_lock){+.+.}-{4:4}, at: uverbs_user_mmap_disassociate+0x39/0x780 [ib_uverbs] [ 776.663177] [ 776.663177] which lock already depends on the new lock. [ 776.663177] [ 776.664525] [ 776.664525] the existing dependency chain (in reverse order) is: [ 776.665724] [ 776.665724] -> #2 (&file->disassociation_lock){+.+.}-{4:4}: [ 776.666887] __mutex_lock+0x16d/0x2330 [ 776.667633] rdma_umap_open+0x129/0x280 [ib_uverbs] [ 776.668489] dup_mmap+0xa40/0x1790 [ 776.669170] copy_process+0x5dd2/0x6170 [ 776.669933] kernel_clone+0xb6/0x610 [ 776.670636] __do_sys_clone+0xb5/0xf0 [ 776.671354] do_syscall_64+0x70/0x12e0 [ 776.672083] entry_SYSCALL_64_after_hwframe+0x4b/0x53 [ 776.672940] [ 776.672940] -> #1 (&mm->mmap_lock/1){+.+.}-{4:4}: [ 776.673985] down_write_nested+0x90/0x1e0 [ 776.674751] dup_mmap+0x201/0x1790 [ 776.675448] copy_process+0x5dd2/0x6170 [ 776.676180] kernel_clone+0xb6/0x610 [ 776.676904] __do_sys_clone+0xb5/0xf0 [ 776.677615] do_syscall_64+0x70/0x12e0 [ 776.678351] entry_SYSCALL_64_after_hwframe+0x4b/0x53 [ 776.679239] [ 776.679239] -> #0 (&mm->mmap_lock){++++}-{4:4}: [ 776.680253] __lock_acquire+0x18c6/0x2ec0 [ 776.681018] lock_acquire+0x10e/0x2e0 [ 776.681742] down_read+0x95/0x430 [ 776.682395] uverbs_user_mmap_disassociate+0x168/0x780 [ib_uverbs] [ 776.683436] uverbs_destroy_ufile_hw+0x1ae/0x270 [ib_uverbs] [ 776.684416] ib_uverbs_remove_one+0x22b/0x420 [ib_uverbs] [ 776.685371] remove_client_context+0xa6/0xf0 [ib_core] [ 776.686342] disable_device+0x12b/0x240 [ib_core] [ 776.687249] __ib_unregister_device+0x269/0x460 [ib_core] [ 776.688233] ib_unregister_device+0x21/0x30 [ib_core] [ 776.689140] mlx5r_remove+0xd0/0x170 [mlx5_ib] [ 776.689999] device_release_driver_internal+0x3b2/0x560 [ 776.694876] bus_remove_device+0x1f5/0x3e0 [ 776.695638] device_del+0x3b9/0x990 [ 776.696329] mlx5_detach_device+0x17e/0x350 [mlx5_core] [ 776.697429] mlx5_unload_one_devl_locked+0x3f/0xb0 [mlx5_core] [ 776.698578] mlx5_devlink_reload_down+0x1f9/0x550 [mlx5_core] [ 776.699712] devlink_reload+0x13e/0x680 [ 776.700456] devlink_nl_reload_doit+0xc29/0x1160 [ 776.701293] genl_family_rcv_msg_doit+0x1c9/0x2a0 [ 776.702135] genl_rcv_msg+0x3f0/0x6b0 [ 776.702854] netlink_rcv_skb+0x11d/0x370 [ 776.703605] genl_rcv+0x24/0x40 [ 776.704236] netlink_unicast+0x5b4/0x970 [ 776.704984] netlink_sendmsg+0x730/0xbf0 [ 776.705748] __sock_sendmsg+0xc5/0x190 [ 776.706461] __sys_sendto+0x201/0x2f0 [ 776.707188] __x64_sys_sendto+0xdc/0x1b0 [ 776.707931] do_syscall_64+0x70/0x12e0 [ 776.708643] entry_SYSCALL_64_after_hwframe+0x4b/0x53 [ 776.709546] [ 776.709546] other info that might help us debug this: [ 776.709546] [ 776.710910] Chain exists of: [ 776.710910] &mm->mmap_lock --> &mm->mmap_lock/1 --> &file->disassociation_lock [ 776.710910] [ 776.712805] Possible unsafe locking scenario: [ 776.712805] [ 776.713828] CPU0 CPU1 [ 776.714589] ---- ---- [ 776.715347] lock(&file->disassociation_lock); [ 776.716097] lock(&mm->mmap_lock/1); [ 776.717067] lock(&file->disassociation_lock); [ 776.718199] rlock(&mm->mmap_lock); [ 776.718857] [ 776.718857] *** DEADLOCK *** Fixes: 51976c6cd786 ("RDMA/core: Provide rdma_user_mmap_disassociate() to disassociate mmap pages") Signed-off-by: Or Har-Toov Signed-off-by: Leon Romanovsky Signed-off-by: Edward Srouji Link: https://patch.msgid.link/20260811-fix-mmap-lockdep-v1-1-1151b41063b4@nvidia.com Acked-by: Junxian Huang Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/rdma_core.c | 1 - drivers/infiniband/core/uverbs_main.c | 25 +++++++++++-------------- include/rdma/uverbs_types.h | 2 -- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/drivers/infiniband/core/rdma_core.c b/drivers/infiniband/core/rdma_core.c index fd5651c003aed3..a7cbe643e33c6e 100644 --- a/drivers/infiniband/core/rdma_core.c +++ b/drivers/infiniband/core/rdma_core.c @@ -69,7 +69,6 @@ void ib_uverbs_release_file(struct kref *ref) if (file->disassociate_page) __free_pages(file->disassociate_page, 0); - mutex_destroy(&file->disassociation_lock); mutex_destroy(&file->umap_lock); mutex_destroy(&file->ucontext_lock); kfree(file); diff --git a/drivers/infiniband/core/uverbs_main.c b/drivers/infiniband/core/uverbs_main.c index 0d88b2ee68ffa1..2a046c888dfaba 100644 --- a/drivers/infiniband/core/uverbs_main.c +++ b/drivers/infiniband/core/uverbs_main.c @@ -644,12 +644,15 @@ static int ib_uverbs_mmap(struct file *filp, struct vm_area_struct *vma) goto out; } - mutex_lock(&file->disassociation_lock); + if (!down_read_trylock(&file->hw_destroy_rwsem)) { + ret = -EIO; + goto out; + } vma->vm_ops = &rdma_umap_ops; ret = ucontext->device->ops.mmap(ucontext, vma); - mutex_unlock(&file->disassociation_lock); + up_read(&file->hw_destroy_rwsem); out: srcu_read_unlock(&file->device->disassociate_srcu, srcu_key); return ret; @@ -671,7 +674,6 @@ static void rdma_umap_open(struct vm_area_struct *vma) /* We are racing with disassociation */ if (!down_read_trylock(&ufile->hw_destroy_rwsem)) goto out_zap; - mutex_lock(&ufile->disassociation_lock); /* * Disassociation already completed, the VMA should already be zapped. @@ -684,12 +686,10 @@ static void rdma_umap_open(struct vm_area_struct *vma) goto out_unlock; rdma_umap_priv_init(priv, vma, opriv->entry); - mutex_unlock(&ufile->disassociation_lock); up_read(&ufile->hw_destroy_rwsem); return; out_unlock: - mutex_unlock(&ufile->disassociation_lock); up_read(&ufile->hw_destroy_rwsem); out_zap: /* @@ -773,7 +773,7 @@ void uverbs_user_mmap_disassociate(struct ib_uverbs_file *ufile) { struct rdma_umap_priv *priv, *next_priv; - mutex_lock(&ufile->disassociation_lock); + lockdep_assert_held_write(&ufile->hw_destroy_rwsem); while (1) { struct mm_struct *mm = NULL; @@ -799,10 +799,8 @@ void uverbs_user_mmap_disassociate(struct ib_uverbs_file *ufile) break; } mutex_unlock(&ufile->umap_lock); - if (!mm) { - mutex_unlock(&ufile->disassociation_lock); + if (!mm) return; - } /* * The umap_lock is nested under mmap_lock since it used within @@ -832,8 +830,6 @@ void uverbs_user_mmap_disassociate(struct ib_uverbs_file *ufile) mmap_read_unlock(mm); mmput(mm); } - - mutex_unlock(&ufile->disassociation_lock); } /** @@ -851,8 +847,11 @@ void rdma_user_mmap_disassociate(struct ib_device *device) mutex_lock(&uverbs_dev->lists_mutex); list_for_each_entry(ufile, &uverbs_dev->uverbs_file_list, list) { - if (ufile->ucontext) + if (ufile->ucontext) { + down_write(&ufile->hw_destroy_rwsem); uverbs_user_mmap_disassociate(ufile); + up_write(&ufile->hw_destroy_rwsem); + } } mutex_unlock(&uverbs_dev->lists_mutex); } @@ -927,8 +926,6 @@ static int ib_uverbs_open(struct inode *inode, struct file *filp) mutex_init(&file->umap_lock); INIT_LIST_HEAD(&file->umaps); - mutex_init(&file->disassociation_lock); - filp->private_data = file; list_add_tail(&file->list, &dev->uverbs_file_list); mutex_unlock(&dev->lists_mutex); diff --git a/include/rdma/uverbs_types.h b/include/rdma/uverbs_types.h index 5a07f9a6dcd1f6..6f3622892c0cc6 100644 --- a/include/rdma/uverbs_types.h +++ b/include/rdma/uverbs_types.h @@ -180,8 +180,6 @@ struct ib_uverbs_file { struct page *disassociate_page; struct xarray idr; - - struct mutex disassociation_lock; }; extern const struct uverbs_obj_type_class uverbs_idr_class; From 1af874e9f4ce22ccf8b10ab5462f32c70d3be21a Mon Sep 17 00:00:00 2001 From: Shuangpeng Bai Date: Sun, 16 Aug 2026 00:45:10 -0400 Subject: [PATCH 0054/1417] IB/mlx4: Fix use-after-free on pkey sysfs registration failure register_pkey_tree() ignores errors from register_one_pkey_tree() and continues registering the remaining slaves. The per-slave error path has already released the pkey parent kobjects, but their pointers remain stored in the device. A later device cleanup therefore passes the stale pointers to kobject_put(), causing a use-after-free. Clear the parent pointers after releasing a failed slave tree and skip unregistered trees during device cleanup. This preserves the existing best-effort registration behavior while preventing a second cleanup of the failed tree. Fixes: c1e7e466120b ("IB/mlx4: Add iov directory in sysfs under the ib device") Cc: stable@vger.kernel.org Signed-off-by: Shuangpeng Bai Link: https://patch.msgid.link/20260816044510.3848996-1-shuangpeng.kernel@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/mlx4/sysfs.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/infiniband/hw/mlx4/sysfs.c b/drivers/infiniband/hw/mlx4/sysfs.c index e688ad66a895fe..5438224bf325ba 100644 --- a/drivers/infiniband/hw/mlx4/sysfs.c +++ b/drivers/infiniband/hw/mlx4/sysfs.c @@ -751,11 +751,13 @@ static int register_one_pkey_tree(struct mlx4_ib_dev *dev, int slave) kobject_put(p); } kobject_put(dev->dev_ports_parent[slave]); + dev->dev_ports_parent[slave] = NULL; err_ports: kobject_put(dev->pkeys.device_parent[slave]); /* extra put for the device_parent create_and_add */ kobject_put(dev->pkeys.device_parent[slave]); + dev->pkeys.device_parent[slave] = NULL; fail_dev: kobject_put(dev->iov_parent); @@ -785,6 +787,8 @@ static void unregister_pkey_tree(struct mlx4_ib_dev *device) return; for (slave = device->dev->persist->num_vfs; slave >= 0; --slave) { + if (!device->pkeys.device_parent[slave]) + continue; list_for_each_entry_safe(p, t, &device->pkeys.pkey_port_list[slave], entry) { From 8b852965b8eaf910c314dc346967ed82c8d4f235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Tue, 1 Sep 2026 10:06:27 -0300 Subject: [PATCH 0055/1417] Input: evdev - zero absinfo before partial copy in EVIOCSABS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EVIOCSABS handler copies at most the user supplied ioctl size into an uninitialized on-stack struct input_absinfo: if (copy_from_user(&abs, p, min_t(size_t, size, sizeof(struct input_absinfo)))) The size comes from _IOC_SIZE() of the ioctl command and is therefore fully controlled by userspace. A short size leaves the trailing part of the structure holding whatever was on the kernel stack, and the whole structure is then stored into the device: dev->absinfo[t] = abs; EVIOCGABS hands that back to userspace, disclosing the stale stack bytes. Only the resolution field is currently cleared, which covers the legacy struct layout but not an arbitrarily short size. Zero the structure before the copy so any part not supplied by the caller reads back as zero. The existing resolution fixup is kept, since it also handles a size that partially overlaps that field. Fixes: 448cd1664a57 ("Input: evdev - rearrange ioctl handling") Cc: stable@vger.kernel.org Signed-off-by: Iván Ezequiel Rodriguez Link: https://patch.msgid.link/20260901130629.24078-2-ivanrwcm25@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/evdev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index 3a718d6000063e..8bfaaa45e0b951 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -1229,6 +1229,8 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd, t = _IOC_NR(cmd) & ABS_MAX; + memset(&abs, 0, sizeof(abs)); + if (copy_from_user(&abs, p, min_t(size_t, size, sizeof(struct input_absinfo)))) return -EFAULT; From f84819ef8d66931ee3998fee3c4f03230f4cb6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Tue, 1 Sep 2026 10:06:28 -0300 Subject: [PATCH 0056/1417] Input: zero ff_effect before compat copy in input_ff_effect_from_user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the compat path input_ff_effect_from_user() aliases the caller's native struct ff_effect with the smaller struct ff_effect_compat and copies only the compat sized prefix: compat_effect = (struct ff_effect_compat *)effect; if (copy_from_user(compat_effect, buffer, sizeof(struct ff_effect_compat))) The tail of the native structure is never written. Callers pass an uninitialized on-stack object, for example evdev_do_ioctl() for EVIOCSFF, so those bytes keep their previous stack contents. input_ff_upload() then stores the full native structure in ff->effects[id], from where a uinput based force feedback daemon can read it back via UI_BEGIN_FF_UPLOAD, disclosing kernel stack memory to userspace. Zero the effect before the compat copy. Fixes: 2d56f3a32c0e ("Input: refactor evdev 32bit compat to be shareable with uinput") Cc: stable@vger.kernel.org Signed-off-by: Iván Ezequiel Rodriguez Link: https://patch.msgid.link/20260901130629.24078-3-ivanrwcm25@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/input-compat.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/input/input-compat.c b/drivers/input/input-compat.c index a5043193ead85a..8860a073029427 100644 --- a/drivers/input/input-compat.c +++ b/drivers/input/input-compat.c @@ -76,6 +76,8 @@ int input_ff_effect_from_user(const char __user *buffer, size_t size, */ compat_effect = (struct ff_effect_compat *)effect; + memset(effect, 0, sizeof(*effect)); + if (copy_from_user(compat_effect, buffer, sizeof(struct ff_effect_compat))) return -EFAULT; From 85f080fb87ed5cd3e46121be677f52c82f26a0ab Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Tue, 1 Sep 2026 20:26:49 +0800 Subject: [PATCH 0057/1417] Input: cyttsp5 - clamp the HID report size before memcpy The size field comes from the device and is used as the memcpy() length into response_buf, which is CY_MAX_INPUT bytes. Fixes: 5b0c03e24a06 ("Input: Add driver for Cypress Generation 5 touchscreen") Signed-off-by: Linkai Gong Link: https://patch.msgid.link/20260901122649.1173066-1-gonglinkai@kylinos.cn Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/cyttsp5.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/touchscreen/cyttsp5.c b/drivers/input/touchscreen/cyttsp5.c index 9266c07314beee..e878a02dc9b75b 100644 --- a/drivers/input/touchscreen/cyttsp5.c +++ b/drivers/input/touchscreen/cyttsp5.c @@ -710,6 +710,7 @@ static irqreturn_t cyttsp5_handle_irq(int irq, void *handle) size = 2; } else { report_id = ts->input_buf[2]; + size = min(size, CY_MAX_INPUT); } switch (report_id) { From 1f1d0812f6a8ab8e6f709c599f137c99646512cc Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Sat, 15 Aug 2026 13:44:19 -0700 Subject: [PATCH 0058/1417] gpiolib: of: don't mark hog nodes OF_POPULATED before a chip is found When a gpio-hog node is attached by a device-tree overlay before its parent GPIO chip has been registered, of_gpio_notify() sets OF_POPULATED on the node via of_node_test_and_set_flag() and only then discovers that there is no gpio_device for the parent, returning NOTIFY_DONE without clearing the flag. Since gpiochip_hog_lines() skips any hog child whose of_node carries OF_POPULATED, the leaked flag makes the hog silently ignored when the chip is registered later. Applying an overlay containing both a GPIO controller node and its hog children - and populating devices only after the overlay apply completes - hits this on every boot; the hog is only applied if the chip driver is unbound (which clears the flag in the remove path) and rebound. Look up the parent gpio_device before claiming the node so that a hog attached ahead of its chip stays unclaimed and is picked up normally by gpiochip_hog_lines() at registration time. Signed-off-by: Abdurrahman Hussain Fixes: a23226b7c1f6 ("gpiolib: handle gpio-hogs only once") Cc: stable@vger.kernel.org Reviewed-by: Daniel Drake Link: https://patch.msgid.link/20260815-gpiolib-of-hog-flag-leak-v1-1-6126aac5f6f3@nexthop.ai Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-of.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index 940b566946ce33..f36e4b171fa7de 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -788,13 +788,13 @@ static int of_gpio_notify(struct notifier_block *nb, unsigned long action, if (!of_property_read_bool(rd->dn, "gpio-hog")) return NOTIFY_DONE; /* not for us */ - if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) - return NOTIFY_DONE; - gdev = of_find_gpio_device_by_node(rd->dn->parent); if (!gdev) return NOTIFY_DONE; /* not for us */ + if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) + return NOTIFY_DONE; + ret = gpiochip_add_hog(gpio_device_get_chip(gdev), of_fwnode_handle(rd->dn)); if (ret < 0) { pr_err("%s: failed to add hogs for %pOF\n", __func__, From 3bb3e80faf21e432f6e6d89c55fb587c323b8813 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 1 Sep 2026 14:11:12 +0100 Subject: [PATCH 0059/1417] firmware: arm_ffa: Tear down driver during shutdown The platform core invokes a driver's shutdown callback, rather than its remove callback, while preparing devices for a normal kexec. Without a shutdown callback, the FF-A driver leaves notifications, partition devices, and the RX/TX mapping active before the replacement kernel is booted. Use ffa_remove() for shutdown so the existing cleanup runs before a normal kexec and other orderly system shutdowns. Reported-by: Nat Gurumoorthy Closes: https://lore.kernel.org/all/20260729162731.1383875-1-natg@google.com/ Reported-by: Carol L Soto Closes: https://lore.kernel.org/all/20260818224404.3694580-1-csoto@nvidia.com Reported-by: Maxi Saparov Closes: https://lore.kernel.org/all/20260826222337.73480-1-maxi.saparov@gmail.com Link: https://patch.msgid.link/20260901131112.3437516-1-sudeep.holla@kernel.org Tested-by: Carol L Soto Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 8654b3365c9b6f..28abc808fd32d2 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -2225,6 +2225,7 @@ static void ffa_remove(struct platform_device *pdev) static struct platform_driver ffa_driver = { .probe = ffa_probe, .remove = ffa_remove, + .shutdown = ffa_remove, .driver = { .name = FFA_PLATFORM_NAME, }, From 6d49beec658f61801e79019fd73691b17252dee3 Mon Sep 17 00:00:00 2001 From: Ian Luites Date: Mon, 31 Aug 2026 08:25:47 +0000 Subject: [PATCH 0060/1417] soundwire: dmi-quirks: Disable ghost Realtek on Asus GX651AX The Asus ROG Zephyrus Duo GX651AX exposes a Realtek RT722 device in ACPI which does not exist in the physical hardware. The device remains unattached while the CS42L43 and both CS35L56 devices attach successfully. This confuses the function topology machine driver into creating duplicate DAI links named SDW3-Playback-SimpleJack, and the sof_sdw probe fails with error -12. Add a model-specific quirk to remove the ghost RT722 device. Fixes: 45cf24da0a10 ("ASoC: Intel: soc-acpi-intel-ptl-match: Remove unnecessary cs42l43 match") Cc: stable@vger.kernel.org # 7.2.x Assisted-by: LLM Signed-off-by: Ian Luites Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260831082534.224716-1-ian@luites.com Signed-off-by: Vinod Koul --- drivers/soundwire/dmi-quirks.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/soundwire/dmi-quirks.c b/drivers/soundwire/dmi-quirks.c index 768255dd12db6d..62fa64b2241236 100644 --- a/drivers/soundwire/dmi-quirks.c +++ b/drivers/soundwire/dmi-quirks.c @@ -200,6 +200,13 @@ static const struct dmi_system_id adr_remap_quirk_table[] = { }, .driver_data = (void *)ghost_realtek, }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUS"), + DMI_MATCH(DMI_BOARD_NAME, "GX651AX"), + }, + .driver_data = (void *)ghost_realtek, + }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ASUS"), From 29871903f3a38e75e896f9a644713859490cb85f Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Tue, 1 Sep 2026 20:43:47 +0800 Subject: [PATCH 0061/1417] sched_ext: Don't deliver duplicate ops.cgroup_set_idle() for same value ops.cgroup_set_idle() is documented to be invoked when a cgroup transitions between idle and non-idle states, and scx_group_set_weight() already skips value-preserving writes. scx_group_set_idle() delivers every write unconditionally, so rewriting an already-correct cpu.idle value feeds the BPF scheduler a transition callback each time, which toggle- or accounting-based schedulers miscount. Mirror the weight guard and only deliver on an actual change. Verified with a probe scheduler printing each callback: rewriting cpu.idle=1 twice on an already-idle cgroup delivered two callbacks before and none after. Fixes: 347ed2d566da ("sched/ext: Implement cgroup_set_idle() callback") Link: https://lore.kernel.org/r/b53c61a1-4d7d-4232-941f-d48b0563d4ed@linux.dev Signed-off-by: Tao Cui Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 901fb0f8b9766e..5f242ab69cf45d 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -4933,7 +4933,8 @@ void scx_group_set_idle(struct task_group *tg, bool idle) percpu_down_read(&scx_cgroup_ops_rwsem); sch = scx_tg_knob_sched(tg); - if (scx_cgroup_enabled && sch && SCX_HAS_OP(sch, cgroup_set_idle)) + if (scx_cgroup_enabled && sch && SCX_HAS_OP(sch, cgroup_set_idle) && + tg->scx.idle != idle) SCX_CALL_OP(sch, cgroup_set_idle, NULL, tg_cgrp(tg), idle); /* Update the task group's idle state */ From 3b54dbd119805361695cb50ca6a875f4c7518b74 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 26 Aug 2026 13:27:24 +0100 Subject: [PATCH 0062/1417] power: sequencing: Fix build issue with COMPILE_TEST The POWER_SEQUENCING_TH1520_GPU driver depends on (ARCH_THEAD && AUXILIARY_BUS) || COMPILE_TEST. This means when COMPILE_TEST=y and ARCH_THEAD is not set, the driver can still be built even though it requires AUXILIARY_BUS, which may not be selected in that configuration, leading to a build failure. Fix this by dropping AUXILIARY_BUS from the dependency and instead selecting it directly, so the dependency is satisfied regardless of whether COMPILE_TEST or ARCH_THEAD is enabled. Fixes: 1a7312b93ab0 ("power: sequencing: extend build coverage with COMPILE_TEST=y") Signed-off-by: Biju Das Link: https://patch.msgid.link/20260826122742.153643-3-biju.das.jz@bp.renesas.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig index 1c5f5820f5b764..226c62704d9bb5 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -29,7 +29,8 @@ config POWER_SEQUENCING_QCOM_WCN config POWER_SEQUENCING_TH1520_GPU tristate "T-HEAD TH1520 GPU power sequencing driver" - depends on (ARCH_THEAD && AUXILIARY_BUS) || COMPILE_TEST + depends on ARCH_THEAD || COMPILE_TEST + select AUXILIARY_BUS help Say Y here to enable the power sequencing driver for the TH1520 SoC GPU. This driver handles the complex clock and reset sequence From 33da68f61d25ef8411489d06514ff627c1f88152 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:49 +0000 Subject: [PATCH 0063/1417] arm64: dts: renesas: r9a09g057: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: 050ee38d0002 ("arm64: dts: renesas: r9a09g057: Add GBETH nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260722085353.136986-2-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g057.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g057.dtsi b/arch/arm64/boot/dts/renesas/r9a09g057.dtsi index 639693d464a77d..188ce9f9c7c21d 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g057.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g057.dtsi @@ -1715,23 +1715,28 @@ mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; @@ -1816,23 +1821,28 @@ mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; From 66fcbdbeca0118b8aeac218b33fa18c394513543 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:50 +0000 Subject: [PATCH 0064/1417] arm64: dts: renesas: r9a09g056: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: c8c8a57c5b40 ("arm64: dts: renesas: r9a09g056: Add GBETH nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260722085353.136986-3-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g056.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g056.dtsi b/arch/arm64/boot/dts/renesas/r9a09g056.dtsi index 76fa34ff3d07e4..77c2221a9e2a3c 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g056.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g056.dtsi @@ -1585,23 +1585,28 @@ mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; @@ -1686,23 +1691,28 @@ mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; From 63016c3a91f2c458ca75869c8c782e899591f22d Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:51 +0000 Subject: [PATCH 0065/1417] arm64: dts: renesas: r9a09g047: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: 41ffbb1c42d3 ("arm64: dts: renesas: r9a09g047: Add GBETH nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260722085353.136986-4-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g047.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g047.dtsi b/arch/arm64/boot/dts/renesas/r9a09g047.dtsi index 73757e8e219706..060405d4a2ddea 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g047.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g047.dtsi @@ -1913,23 +1913,28 @@ mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; @@ -2013,23 +2018,28 @@ mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; From f9d9a1913c09366bf7b967d39575e06cb80128dc Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:52 +0000 Subject: [PATCH 0066/1417] arm64: dts: renesas: r9a09g077: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: 394c1e24a4cf ("arm64: dts: renesas: r9a09g077: Add GMAC nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260722085353.136986-5-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g077.dtsi | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g077.dtsi b/arch/arm64/boot/dts/renesas/r9a09g077.dtsi index 40494159831d8a..bac39390ead74d 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g077.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g077.dtsi @@ -642,36 +642,45 @@ mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; @@ -788,36 +797,45 @@ mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; @@ -934,36 +952,45 @@ mtl_tx_setup2: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; From 2ac7bad110be6ebe478d6bd57821f7f5259a1f54 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:53 +0000 Subject: [PATCH 0067/1417] arm64: dts: renesas: r9a09g087: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: c4698a34993b ("arm64: dts: renesas: r9a09g087: Add GMAC nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260722085353.136986-6-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g087.dtsi | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g087.dtsi b/arch/arm64/boot/dts/renesas/r9a09g087.dtsi index e8d4f76949ccb2..03b976d93e1058 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g087.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g087.dtsi @@ -643,36 +643,45 @@ mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; @@ -790,36 +799,45 @@ mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; @@ -937,36 +955,45 @@ mtl_tx_setup2: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; From 9cdfad5dd5529e1700f51feaed5e8a90044d7e35 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 18 Aug 2026 12:52:29 +0900 Subject: [PATCH 0068/1417] RDMA/srp: Fix srp_remove_target() Remove all logical units before disconnecting the transport because one or more SCSI commands may be submitted while removing logical units. Remove the SCSI host after the transport has been disconnected because the code that disconnects the transport needs resources that are freed by the code that removes the SCSI host (SCSI host tag set). Remove the srp_rport_get() and srp_rport_put() calls because the purpose of these calls was to keep the rport until tl_err_work is cancelled. Reported-by: Yehyeong Lee Closes: https://lore.kernel.org/linux-rdma/20260812190418.200337-1-yhlee@isslab.korea.ac.kr/ Signed-off-by: Bart Van Assche Signed-off-by: Yehyeong Lee Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/srp/ib_srp.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/ulp/srp/ib_srp.c b/drivers/infiniband/ulp/srp/ib_srp.c index 6b429ef63f8f3b..955f36efeebd35 100644 --- a/drivers/infiniband/ulp/srp/ib_srp.c +++ b/drivers/infiniband/ulp/srp/ib_srp.c @@ -1038,15 +1038,20 @@ static void srp_del_scsi_host_attr(struct Scsi_Host *shost) static void srp_remove_target(struct srp_target_port *target) { + struct scsi_device *sdev; struct srp_rdma_ch *ch; int i; WARN_ON_ONCE(target->state != SRP_TARGET_REMOVED); srp_del_scsi_host_attr(target->scsi_host); - srp_rport_get(target->rport); - srp_remove_host(target->scsi_host); - scsi_remove_host(target->scsi_host); + /* + * Remove all logical units. This must happen before the + * srp_disconnect_target() call because scsi_remove_device() may trigger + * submission of SCSI commands. See also sd_shutdown(). + */ + shost_for_each_device(sdev, target->scsi_host) + scsi_remove_device(sdev); srp_stop_rport_timers(target->rport); srp_disconnect_target(target); kobj_ns_drop(KOBJ_NS_TYPE_NET, to_ns_common(target->net)); @@ -1055,7 +1060,8 @@ static void srp_remove_target(struct srp_target_port *target) srp_free_ch_ib(target, ch); } cancel_work_sync(&target->tl_err_work); - srp_rport_put(target->rport); + srp_remove_host(target->scsi_host); + scsi_remove_host(target->scsi_host); kfree(target->ch); target->ch = NULL; From d85f0f0a7c85756fc992c70d869706f19dac9259 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Wed, 19 Aug 2026 10:08:04 +0900 Subject: [PATCH 0069/1417] IB/iser: reject a remote invalidation of an unregistered direction A write command whose data is sent entirely as immediate data is not registered. iser_reg_mem_fastreg() takes the DMA key path and leaves rdma_reg[ISER_DIR_OUT].desc at NULL, while iser_dma_map_task_data() has already set dir[ISER_DIR_OUT]. iser_check_remote_inv() looks at dir[] alone and hands the descriptor to iser_inv_desc(), which reads desc->sig_protected. A target that answers such a command with IB_WR_SEND_WITH_INV faults the initiator. Leaving those commands unregistered is deliberate. The same function already terminates the connection when a target sends a remote invalidation the initiator did not ask for. A target that invalidates a direction that was never registered is in the same class, so give it the same answer. Oops: general protection fault, probably for non-canonical address 0xdffffc0000000004: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000020-0x0000000000000027] CPU: 0 UID: 0 PID: 40 Comm: kworker/u8:2 Not tainted 7.2.0-rc5-ISERHOST-gf5098b6bae76-dirty #3 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: rxe_wq do_work RIP: 0010:iser_task_rsp+0x6d6/0xec0 Code: 48 c1 ea 03 80 3c 02 00 0f 85 ba 06 00 00 48 8b 9b 78 01 00 00 48 b8 00 00 00 00 00 fc ff df 48 8d 7b 20 48 89 fa 48 c1 ea 03 <0f> b6 04 02 84 c0 74 06 0f 8e 76 06 00 00 80 7b 20 00 0f 84 3d 04 RSP: 0018:ffff88811b008db8 EFLAGS: 00010202 RAX: dffffc0000000000 RBX: 0000000000000000 RCX: 0000000000001848 RDX: 0000000000000004 RSI: 1ffff11021587b12 RDI: 0000000000000020 RBP: ffff88810adc1ae4 R08: ffff888109b7f860 R09: ffffffff90a922c0 R10: ffff88810adc1a1c R11: 000000000000003c R12: ffff888109b7f800 R13: ffff88810adc1acc R14: ffff888109b7f820 R15: 0000000000000000 FS: 0000000000000000(0000) GS:ffff88818a676000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00000000005afe2b CR3: 000000010af23005 CR4: 0000000000770ef0 PKRU: 55555554 Call Trace: __ib_process_cq+0xe1/0x390 ib_poll_handler+0x6e/0x200 irq_poll_softirq+0x1df/0x480 ? clockevents_program_event+0x2ba/0x860 ? __pfx_irq_poll_softirq+0x10/0x10 handle_softirqs+0x18e/0x590 ? __pfx_handle_softirqs+0x10/0x10 ? __hrtimer_rearm_deferred+0x156/0x450 do_softirq+0x3b/0x60 __local_bh_enable_ip+0x61/0x70 __alloc_skb+0x732/0x890 ? _raw_spin_lock_irqsave+0x85/0xe0 ? __pfx___alloc_skb+0x10/0x10 ? _raw_read_unlock_irqrestore+0x16/0x50 rxe_init_packet+0x16b/0x4f0 prepare_ack_packet+0xb8/0x830 rxe_receiver+0x499/0x9980 ? __pfx_rxe_receiver+0x10/0x10 ? rxe_completer+0x29e5/0x38c0 ? hrtimer_start_range_ns_common+0x75f/0x1730 ? hrtimer_start_range_ns+0xa6/0x2c0 ? __pfx__raw_spin_lock_irqsave+0x10/0x10 ? __pfx_rxe_receiver+0x10/0x10 do_work+0x144/0x470 process_one_work+0x633/0x1030 ? assign_work+0x11d/0x370 worker_thread+0x45b/0xd10 ? __pfx_worker_thread+0x10/0x10 kthread+0x2c6/0x3b0 ? recalc_sigpending+0x15c/0x1e0 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x36e/0x5a0 ? __pfx_ret_from_fork+0x10/0x10 ? __switch_to+0x572/0xdd0 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Modules linked in: ---[ end trace 0000000000000000 ]--- Fixes: 59caaed7a72a ("IB/iser: Support the remote invalidation exception") Signed-off-by: Yehyeong Lee Link: https://patch.msgid.link/20260819010804.641772-1-yhlee@isslab.korea.ac.kr Reviewed-by: Max Gurtovoy Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/iser/iser_initiator.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/ulp/iser/iser_initiator.c b/drivers/infiniband/ulp/iser/iser_initiator.c index 12a2d12fef0706..7ea6888b479cf3 100644 --- a/drivers/infiniband/ulp/iser/iser_initiator.c +++ b/drivers/infiniband/ulp/iser/iser_initiator.c @@ -598,11 +598,8 @@ static int iser_check_remote_inv(struct iser_conn *iser_conn, struct ib_wc *wc, iser_dbg("conn %p: remote invalidation for rkey %#x\n", iser_conn, rkey); - if (unlikely(!iser_conn->snd_w_inv)) { - iser_err("conn %p: unexpected remote invalidation, terminating connection\n", - iser_conn); - return -EPROTO; - } + if (unlikely(!iser_conn->snd_w_inv)) + goto bad_inv; task = iscsi_itt_to_ctask(iser_conn->iscsi_conn, hdr->itt); if (likely(task)) { @@ -611,12 +608,16 @@ static int iser_check_remote_inv(struct iser_conn *iser_conn, struct ib_wc *wc, if (iser_task->dir[ISER_DIR_IN]) { desc = iser_task->rdma_reg[ISER_DIR_IN].desc; + if (unlikely(!desc)) + goto bad_inv; if (unlikely(iser_inv_desc(desc, rkey))) return -EINVAL; } if (iser_task->dir[ISER_DIR_OUT]) { desc = iser_task->rdma_reg[ISER_DIR_OUT].desc; + if (unlikely(!desc)) + goto bad_inv; if (unlikely(iser_inv_desc(desc, rkey))) return -EINVAL; } @@ -627,6 +628,11 @@ static int iser_check_remote_inv(struct iser_conn *iser_conn, struct ib_wc *wc, } return 0; + +bad_inv: + iser_err("conn %p: unexpected remote invalidation, terminating connection\n", + iser_conn); + return -EPROTO; } From a8fe3dfce8c0d8a76dc3d8486a5bff5feebe156f Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Fri, 21 Aug 2026 17:06:20 +0900 Subject: [PATCH 0070/1417] IB/isert: wait for deferred control PDU completions before releasing the connection isert_send_done() hands ISTATE_SEND_TASKMGTRSP, ISTATE_SEND_REJECT and ISTATE_SEND_TEXTRSP completions off to isert_comp_wq and returns. The work item then runs isert_completion_put() -> isert_put_cmd(), which reads isert_conn->conn and takes conn->cmd_lock. Nothing orders that work item against teardown. isert_wait_conn() queues isert_release_work, which frees isert_conn, and iscsit_close_connection() frees the iscsit_conn right after it returns, so the queued work can run against freed memory. Count the deferred control PDU completions per connection and let isert_wait_conn() wait for them before the release work is queued. ISTATE_SEND_LOGOUTRSP is deliberately not counted: that branch runs iscsit_logout_post_handler(), which ends up waiting for conn->conn_wait_comp, and that completion is only sent by iscsit_close_connection() after it has called iscsit_wait_conn(). Waiting for it here would deadlock. Its wait stays the existing isert_wait4logout(). The splat below is from a kernel with tracing printk()s and an msleep(200) injected into isert_do_control_comp() to widen the window: BUG: KASAN: slab-use-after-free in isert_put_cmd+0x53d/0x620 Read of size 8 at addr ffff8881054f1038 by task kworker/u17:1/182 CPU: 0 UID: 0 PID: 182 Comm: kworker/u17:1 Tainted: G B 7.2.0-rc5-TWIDE-gb8babf08acc7 #1 PREEMPT(lazy) Tainted: [B]=BAD_PAGE Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: isert_comp_wq isert_do_control_comp Call Trace: dump_stack_lvl+0x53/0x70 print_report+0xd0/0x630 ? __pfx__raw_spin_lock_irqsave+0x10/0x10 ? _raw_spin_unlock_irqrestore+0x3e/0x70 ? isert_put_cmd+0x53d/0x620 kasan_report+0xce/0x100 ? isert_put_cmd+0x53d/0x620 isert_put_cmd+0x53d/0x620 ? isert_completion_put+0x305/0x330 ? isert_do_control_comp+0x2ef/0x310 process_one_work+0x633/0x1030 ? assign_work+0x11d/0x370 worker_thread+0x45b/0xd10 ? __pfx_worker_thread+0x10/0x10 ? __pfx_worker_thread+0x10/0x10 kthread+0x2c6/0x3b0 ? recalc_sigpending+0x15c/0x1e0 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x36e/0x5a0 ? __pfx_ret_from_fork+0x10/0x10 ? __switch_to+0x572/0xdd0 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Allocated by task 48: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0x8f/0xa0 __kmalloc_cache_noprof+0x158/0x370 isert_cma_handler+0x1e3/0x2ae0 cma_cm_event_handler+0x3e/0x240 cma_ib_req_handler+0x17d9/0x4490 cm_process_work+0x41/0x330 cm_work_handler+0x5727/0xc160 process_one_work+0x633/0x1030 worker_thread+0x45b/0xd10 kthread+0x2c6/0x3b0 ret_from_fork+0x36e/0x5a0 ret_from_fork_asm+0x1a/0x30 Freed by task 184: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x43/0x70 kfree+0x121/0x380 iscsit_close_connection+0x7cf/0x1e60 iscsit_take_action_for_connection_exit+0x1b6/0x360 iscsi_target_tx_thread+0x472/0x690 kthread+0x2c6/0x3b0 ret_from_fork+0x36e/0x5a0 ret_from_fork_asm+0x1a/0x30 Fixes: b8d26b3be8b3 ("iser-target: Add iSCSI Extensions for RDMA (iSER) target driver") Signed-off-by: Yehyeong Lee Link: https://patch.msgid.link/20260821080620.1694119-1-yhlee@isslab.korea.ac.kr Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/isert/ib_isert.c | 22 ++++++++++++++++++++++ drivers/infiniband/ulp/isert/ib_isert.h | 2 ++ 2 files changed, 24 insertions(+) diff --git a/drivers/infiniband/ulp/isert/ib_isert.c b/drivers/infiniband/ulp/isert/ib_isert.c index 5087ea98307162..e69db43370ff93 100644 --- a/drivers/infiniband/ulp/isert/ib_isert.c +++ b/drivers/infiniband/ulp/isert/ib_isert.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "ib_isert.h" @@ -310,6 +311,7 @@ isert_init_conn(struct isert_conn *isert_conn) init_completion(&isert_conn->login_req_comp); init_waitqueue_head(&isert_conn->rem_wait); kref_init(&isert_conn->kref); + atomic_set(&isert_conn->ctrl_comp_cnt, 0); mutex_init(&isert_conn->mutex); INIT_WORK(&isert_conn->release_work, isert_release_work); } @@ -1694,6 +1696,8 @@ isert_do_control_comp(struct work_struct *work) struct isert_conn *isert_conn = isert_cmd->conn; struct ib_device *ib_dev = isert_conn->cm_id->device; struct iscsit_cmd *cmd = isert_cmd->iscsit_cmd; + /* The switch below may free isert_cmd. */ + bool counted = isert_cmd->ctrl_counted; isert_dbg("Cmd %p i_state %d\n", isert_cmd, cmd->i_state); @@ -1715,6 +1719,14 @@ isert_do_control_comp(struct work_struct *work) dump_stack(); break; } + + /* + * The count is what keeps isert_conn alive, so drop it last. The wait + * queue lives in the global hash table, not in isert_conn, so this is + * safe even if the waiter has already freed the connection. + */ + if (counted && atomic_dec_and_test(&isert_conn->ctrl_comp_cnt)) + wake_up_var(&isert_conn->ctrl_comp_cnt); } static void @@ -1758,6 +1770,12 @@ isert_send_done(struct ib_cq *cq, struct ib_wc *wc) case ISTATE_SEND_TEXTRSP: isert_unmap_tx_desc(tx_desc, ib_dev); + /* Paired with the wait in isert_wait_conn(). */ + isert_cmd->ctrl_counted = + isert_cmd->iscsit_cmd->i_state != ISTATE_SEND_LOGOUTRSP; + if (isert_cmd->ctrl_counted) + atomic_inc(&isert_conn->ctrl_comp_cnt); + INIT_WORK(&isert_cmd->comp_work, isert_do_control_comp); queue_work(isert_comp_wq, &isert_cmd->comp_work); return; @@ -2602,6 +2620,10 @@ static void isert_wait_conn(struct iscsit_conn *conn) isert_wait4cmds(conn); isert_wait4logout(isert_conn); + /* Paired with the count taken in isert_send_done(). */ + wait_var_event(&isert_conn->ctrl_comp_cnt, + !atomic_read(&isert_conn->ctrl_comp_cnt)); + queue_work(isert_release_wq, &isert_conn->release_work); } diff --git a/drivers/infiniband/ulp/isert/ib_isert.h b/drivers/infiniband/ulp/isert/ib_isert.h index 0bac5aa66c8028..519b17e54bd342 100644 --- a/drivers/infiniband/ulp/isert/ib_isert.h +++ b/drivers/infiniband/ulp/isert/ib_isert.h @@ -153,6 +153,7 @@ struct isert_cmd { struct work_struct comp_work; struct scatterlist sg; bool ctx_init_done; + bool ctrl_counted; }; static inline struct isert_cmd *tx_desc_to_cmd(struct iser_tx_desc *desc) @@ -187,6 +188,7 @@ struct isert_conn { struct mutex mutex; struct kref kref; struct work_struct release_work; + atomic_t ctrl_comp_cnt; bool logout_posted; bool snd_w_inv; wait_queue_head_t rem_wait; From 6c368f7baaea63c1c7c28c6df271511f2a1562c9 Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Fri, 21 Aug 2026 17:09:08 +0800 Subject: [PATCH 0071/1417] RDMA/bnxt_re: check create_singlethread_workqueue() in DCB setup bnxt_re_init_dcb_wq() ignores a failed allocation. The async DCB handler later calls queue_work() on the NULL pointer. Fixes: 51dc5312dcd9 ("RDMA/bnxt_re: Add support to handle DCB_CONFIG_CHANGE event") Signed-off-by: Linkai Gong Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/bnxt_re/main.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/hw/bnxt_re/main.c b/drivers/infiniband/hw/bnxt_re/main.c index ce72db1b4bc34d..17654a9e23fede 100644 --- a/drivers/infiniband/hw/bnxt_re/main.c +++ b/drivers/infiniband/hw/bnxt_re/main.c @@ -356,9 +356,13 @@ static int bnxt_re_update_qp1_tos_dscp(struct bnxt_re_dev *rdev) return bnxt_qplib_modify_qp(&rdev->qplib_res, &qp->qplib_qp); } -static void bnxt_re_init_dcb_wq(struct bnxt_re_dev *rdev) +static int bnxt_re_init_dcb_wq(struct bnxt_re_dev *rdev) { rdev->dcb_wq = create_singlethread_workqueue("bnxt_re_dcb_wq"); + if (!rdev->dcb_wq) + return -ENOMEM; + + return 0; } static void bnxt_re_uninit_dcb_wq(struct bnxt_re_dev *rdev) @@ -2339,7 +2343,9 @@ static int bnxt_re_dev_init(struct bnxt_re_dev *rdev, u8 op_type) } bnxt_re_debugfs_add_pdev(rdev); - bnxt_re_init_dcb_wq(rdev); + rc = bnxt_re_init_dcb_wq(rdev); + if (rc) + goto fail; bnxt_re_net_register_async_event(rdev); if (!rdev->is_virtfn) From 99c24a8968ebef0573825b5cb89d5985d51635b9 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Sat, 22 Aug 2026 23:33:43 -0400 Subject: [PATCH 0072/1417] RDMA/rtrs: guard against null kobj name In the client, if `init_path()` errors, the callee tries to clean up with `rtrs_clt_close_conns()`. However, this can lead to calling the event tracing code with `clt_path->kobj->name` being `NULL` and thus causing a null pointer dereference when trying to copy from it. This just adds a guard to check that the name is not `NULL` before copying from it. The server appears to have a similar pattern. Fixes: 5a93929d9f9a1 ("RDMA/rtrs-clt: Add event tracing support") Fixes: c16762b7bf54d ("RDMA/rtrs-srv: Add event tracing support") Reported-by: syzbot+1695193198994f4e7fed@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1695193198994f4e7fed Signed-off-by: Ryan Mehri Link: https://patch.msgid.link/20260823034303.163403-1-ryan.mehri1@gmail.com Reviewed-by: Jack Wang Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h | 2 +- drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h b/drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h index 7738e26768557d..29e23404bb7b8a 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h +++ b/drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h @@ -55,7 +55,7 @@ DECLARE_EVENT_CLASS(rtrs_clt_conn_class, __entry->max_reconnect_attempts = clt->max_reconnect_attempts; __entry->fail_cnt = clt_path->stats->reconnects.fail_cnt; __entry->success_cnt = clt_path->stats->reconnects.successful_cnt; - memcpy(__entry->sessname, kobject_name(&clt_path->kobj), NAME_MAX); + strscpy(__entry->sessname, kobject_name(&clt_path->kobj) ?: "", NAME_MAX); ), TP_printk("RTRS-CLT: sess='%s' state=%s attempts='%d' max-attempts='%d' fail='%d' success='%d'", diff --git a/drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h b/drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h index 587d3e03308126..a7d7b971e6c803 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h +++ b/drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h @@ -61,7 +61,7 @@ TRACE_EVENT(send_io_resp_imm, __entry->msg_id = id->msg_id; __entry->wr_cnt = atomic_read(&con->c.wr_cnt); __entry->signal_interval = s->signal_interval; - memcpy(__entry->sessname, kobject_name(&srv_path->kobj), NAME_MAX); + strscpy(__entry->sessname, kobject_name(&srv_path->kobj) ?: "", NAME_MAX); ), TP_printk("sess='%s' state='%s' dir=%s err='%d' inval='%d' glob-inval='%d' msgid='%u' wrcnt='%d' sig-interval='%u'", From 23d7e03a52ece66b992620aa9b8fa5f164077c0f Mon Sep 17 00:00:00 2001 From: Sriharsha Basavapatna Date: Mon, 24 Aug 2026 22:54:43 +0530 Subject: [PATCH 0073/1417] RDMA/bnxt_re: Avoid exposing umdbr to userspace The umdbr field in struct bnxt_re_db_region returns the raw unmapped PCI BAR address of the doorbell region. Avoid sharing this field to the userspace. Change this to a reserved field and stop populating it, keeping the ABI layout and size unchanged for existing binaries. Fixes: 1234a9d8aebb ("RDMA/bnxt_re: Support doorbell extensions") Signed-off-by: Sriharsha Basavapatna Link: https://patch.msgid.link/20260824172443.33943-1-sriharsha.basavapatna@broadcom.com Reviewed-by: Selvin Xavier Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/bnxt_re/uapi.c | 6 ++---- include/uapi/rdma/bnxt_re-abi.h | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/hw/bnxt_re/uapi.c b/drivers/infiniband/hw/bnxt_re/uapi.c index feaf98631fc5ac..a407c6bc469baa 100644 --- a/drivers/infiniband/hw/bnxt_re/uapi.c +++ b/drivers/infiniband/hw/bnxt_re/uapi.c @@ -462,7 +462,6 @@ static int UVERBS_HANDLER(BNXT_RE_METHOD_DBR_ALLOC)(struct uverbs_attr_bundle *a uobj->object = obj; uverbs_finalize_uobj_create(attrs, BNXT_RE_ALLOC_DBR_HANDLE); - dbr.umdbr = dpi->umdbr; dbr.dpi = dpi->dpi; ret = uverbs_copy_to_struct_or_zero(attrs, BNXT_RE_ALLOC_DBR_ATTR, &dbr, sizeof(dbr)); @@ -525,7 +524,6 @@ static int UVERBS_HANDLER(BNXT_RE_METHOD_GET_DEFAULT_DBR)(struct uverbs_attr_bun return PTR_ERR(ib_uctx); uctx = container_of(ib_uctx, struct bnxt_re_ucontext, ib_uctx); - dpi.umdbr = uctx->dpi.umdbr; dpi.dpi = uctx->dpi.dpi; ret = uverbs_copy_to_struct_or_zero(attrs, BNXT_RE_DEFAULT_DBR_ATTR, @@ -543,7 +541,7 @@ DECLARE_UVERBS_NAMED_METHOD(BNXT_RE_METHOD_DBR_ALLOC, UA_MANDATORY), UVERBS_ATTR_PTR_OUT(BNXT_RE_ALLOC_DBR_ATTR, UVERBS_ATTR_STRUCT(struct bnxt_re_db_region, - umdbr), + reserved2), UA_MANDATORY), UVERBS_ATTR_PTR_OUT(BNXT_RE_ALLOC_DBR_OFFSET, UVERBS_ATTR_TYPE(u64), @@ -563,7 +561,7 @@ DECLARE_UVERBS_NAMED_OBJECT(BNXT_RE_OBJECT_DBR, DECLARE_UVERBS_NAMED_METHOD(BNXT_RE_METHOD_GET_DEFAULT_DBR, UVERBS_ATTR_PTR_OUT(BNXT_RE_DEFAULT_DBR_ATTR, UVERBS_ATTR_STRUCT(struct bnxt_re_db_region, - umdbr), + reserved2), UA_MANDATORY)); DECLARE_UVERBS_GLOBAL_METHODS(BNXT_RE_OBJECT_DEFAULT_DBR, diff --git a/include/uapi/rdma/bnxt_re-abi.h b/include/uapi/rdma/bnxt_re-abi.h index 856a1b3036e9bf..15ed2a51f8f17a 100644 --- a/include/uapi/rdma/bnxt_re-abi.h +++ b/include/uapi/rdma/bnxt_re-abi.h @@ -250,7 +250,7 @@ struct bnxt_re_query_device_ex_resp { struct bnxt_re_db_region { __u32 dpi; __u32 reserved; - __aligned_u64 umdbr; + __aligned_u64 reserved2; }; enum bnxt_re_obj_dbr_alloc_attrs { From 90af7fde083e1b22c349c3a8b1626728e44e474c Mon Sep 17 00:00:00 2001 From: Yifei Gao Date: Tue, 4 Aug 2026 21:34:56 +0000 Subject: [PATCH 0074/1417] memstick: ms_block: destroy io_queue workqueue on removal msb_init_disk() creates the per-card ordered workqueue msb->io_queue with alloc_ordered_workqueue(). It is torn down with destroy_workqueue() only on the init error path; msb_remove() never destroys it. msb_stop() merely flushes the queue, and neither msb_data_clear() nor put_disk() free it. As a result every card insert/remove cycle leaks the workqueue and its kworker, exhausting kernel memory over repeated cycles. Destroy the workqueue in msb_remove() after the disk has been removed and the queue drained. Fixes: 0ab30494bc4f ("memstick: add support for legacy memorysticks") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Yifei Gao Signed-off-by: Ulf Hansson --- drivers/memstick/core/ms_block.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/memstick/core/ms_block.c b/drivers/memstick/core/ms_block.c index ce33907bfc2429..65154e569a9e72 100644 --- a/drivers/memstick/core/ms_block.c +++ b/drivers/memstick/core/ms_block.c @@ -2204,6 +2204,8 @@ static void msb_remove(struct memstick_dev *card) msb_data_clear(msb); mutex_unlock(&msb_disk_lock); + destroy_workqueue(msb->io_queue); + put_disk(msb->disk); memstick_set_drvdata(card, NULL); } From 08d4d9802d58bf032099091e6acf719f3298f28e Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Wed, 26 Aug 2026 15:31:46 +0800 Subject: [PATCH 0075/1417] RDMA/uverbs: Fix potential leak of resources->collection in flow_resources_alloc() The two array allocations are done unconditionally and only checked afterwards, so if the counters allocation fails while the collection allocation succeeds, the error path frees counters and the containing struct but never frees resources->collection, losing the only pointer to it. Fixes: de7498147d00 ("RDMA/uverbs: Refactor flow_resources_alloc() function") Signed-off-by: Li RongQing Link: https://patch.msgid.link/20260826073146.2203-1-lirongqing@baidu.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/uverbs_flow.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/infiniband/core/uverbs_flow.c b/drivers/infiniband/core/uverbs_flow.c index 1528a294f7f85f..de5a2769f08470 100644 --- a/drivers/infiniband/core/uverbs_flow.c +++ b/drivers/infiniband/core/uverbs_flow.c @@ -26,6 +26,7 @@ struct ib_uflow_resources *flow_resources_alloc(size_t num_specs) return resources; err: + kfree(resources->collection); kfree(resources->counters); kfree(resources); From 3476c28c9addfa253f505e6bd87f1f5598b961d0 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Wed, 26 Aug 2026 15:32:16 +0800 Subject: [PATCH 0076/1417] RDMA/mad: Fix receive buffer leak when PKey enforcement fails ib_mad_complete_recv() initializes mad_recv_wc->rmpp_list and then runs ib_mad_enforce_security() before linking recv_buf onto that list. On failure it calls ib_free_recv_mad(), which only walks rmpp_list and frees the ib_mad_private of every buffer found there. As the list is still empty at that point, nothing is freed at all. The caller cannot clean up either: ib_mad_recv_done() sets recv to NULL right after ib_mad_complete_recv() returns, assuming the MAD layer took ownership of the buffer. Every MAD that fails the PKey check therefore leaks one ib_mad_private (about 300 bytes per IB port MAD, ~2K for OPA), and a remote node can trigger this repeatedly by sending MADs with a wrong PKey. Link recv_buf onto rmpp_list right after the list is initialized, so the error path has something to free. Fixes: 47a2b338fe63 ("IB/core: Enforce security on management datagrams") Signed-off-by: Li RongQing Link: https://patch.msgid.link/20260826073216.2367-1-lirongqing@baidu.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/mad.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/core/mad.c b/drivers/infiniband/core/mad.c index e0b3b36b8b1496..3c91f00d09c6be 100644 --- a/drivers/infiniband/core/mad.c +++ b/drivers/infiniband/core/mad.c @@ -2059,6 +2059,8 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv, int ret; INIT_LIST_HEAD(&mad_recv_wc->rmpp_list); + list_add(&mad_recv_wc->recv_buf.list, &mad_recv_wc->rmpp_list); + ret = ib_mad_enforce_security(mad_agent_priv, mad_recv_wc->wc->pkey_index); if (ret) { @@ -2067,7 +2069,6 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv, return; } - list_add(&mad_recv_wc->recv_buf.list, &mad_recv_wc->rmpp_list); if (is_kernel_rmpp_data_response(mad_agent_priv, mad_recv_wc)) { spin_lock_irqsave(&mad_agent_priv->lock, flags); mad_send_wr = ib_find_send_mad(mad_agent_priv, mad_recv_wc); From 00baeade709fb66da647e8327e8398bb532e30f7 Mon Sep 17 00:00:00 2001 From: Cheng Xu Date: Fri, 28 Aug 2026 11:03:44 +0800 Subject: [PATCH 0077/1417] RDMA/erdma: Use IRQ-safe XArray helpers for QP and CQ tables Locked QP and CQ lookups from EQ interrupts can deadlock with create-path XArray updates. If an interrupt arrives while the create path holds the plain xa_lock, the lookup spins forever trying to acquire the same lock. Use IRQ-safe XArray helpers for all QP and CQ create-path updates, including the GSI QP store and error paths. Initialize both arrays with XA_FLAGS_LOCK_IRQ so sleeping allocations preserve interrupt state. Fixes: 98df2aee1459 ("RDMA/erdma: Hold CQ references when processing EQ events") Fixes: a52eeff32024 ("RDMA/erdma: Hold QP references for AE and CM processing") Signed-off-by: Cheng Xu Link: https://patch.msgid.link/20260828030344.88021-1-chengyou@linux.alibaba.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/erdma/erdma_main.c | 4 ++-- drivers/infiniband/hw/erdma/erdma_verbs.c | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/infiniband/hw/erdma/erdma_main.c b/drivers/infiniband/hw/erdma/erdma_main.c index 7e87a815e853b8..445182c6bc5d37 100644 --- a/drivers/infiniband/hw/erdma/erdma_main.c +++ b/drivers/infiniband/hw/erdma/erdma_main.c @@ -572,8 +572,8 @@ static int erdma_ib_device_add(struct pci_dev *pdev) INIT_LIST_HEAD(&dev->cep_list); spin_lock_init(&dev->lock); - xa_init_flags(&dev->qp_xa, XA_FLAGS_ALLOC1); - xa_init_flags(&dev->cq_xa, XA_FLAGS_ALLOC1); + xa_init_flags(&dev->qp_xa, XA_FLAGS_ALLOC1 | XA_FLAGS_LOCK_IRQ); + xa_init_flags(&dev->cq_xa, XA_FLAGS_ALLOC1 | XA_FLAGS_LOCK_IRQ); dev->next_alloc_cqn = 1; dev->next_alloc_qpn = 1; diff --git a/drivers/infiniband/hw/erdma/erdma_verbs.c b/drivers/infiniband/hw/erdma/erdma_verbs.c index 65b1af1e66232b..f18b88bba2817e 100644 --- a/drivers/infiniband/hw/erdma/erdma_verbs.c +++ b/drivers/infiniband/hw/erdma/erdma_verbs.c @@ -1021,15 +1021,15 @@ int erdma_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, init_completion(&qp->safe_free); if (qp->ibqp.qp_type == IB_QPT_GSI) { - old_entry = xa_store(&dev->qp_xa, 1, qp, GFP_KERNEL); + old_entry = xa_store_irq(&dev->qp_xa, 1, qp, GFP_KERNEL); if (xa_is_err(old_entry)) ret = xa_err(old_entry); else qp->ibqp.qp_num = 1; } else { - ret = xa_alloc_cyclic(&dev->qp_xa, &qp->ibqp.qp_num, qp, - XA_LIMIT(1, dev->attrs.max_qp - 1), - &dev->next_alloc_qpn, GFP_KERNEL); + ret = xa_alloc_cyclic_irq(&dev->qp_xa, &qp->ibqp.qp_num, qp, + XA_LIMIT(1, dev->attrs.max_qp - 1), + &dev->next_alloc_qpn, GFP_KERNEL); } if (ret < 0) { @@ -1089,7 +1089,7 @@ int erdma_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, else free_kernel_qp(qp); err_out_xa: - xa_erase(&dev->qp_xa, QP_ID(qp)); + xa_erase_irq(&dev->qp_xa, QP_ID(qp)); err_out: return ret; } @@ -1993,9 +1993,9 @@ int erdma_create_cq(struct ib_cq *ibcq, const struct ib_cq_init_attr *attr, refcount_set(&cq->refcount, 1); init_completion(&cq->free); - ret = xa_alloc_cyclic(&dev->cq_xa, &cq->cqn, cq, - XA_LIMIT(1, dev->attrs.max_cq - 1), - &dev->next_alloc_cqn, GFP_KERNEL); + ret = xa_alloc_cyclic_irq(&dev->cq_xa, &cq->cqn, cq, + XA_LIMIT(1, dev->attrs.max_cq - 1), + &dev->next_alloc_cqn, GFP_KERNEL); if (ret < 0) return ret; @@ -2041,7 +2041,7 @@ int erdma_create_cq(struct ib_cq *ibcq, const struct ib_cq_init_attr *attr, } err_out_xa: - xa_erase(&dev->cq_xa, cq->cqn); + xa_erase_irq(&dev->cq_xa, cq->cqn); return ret; } From 3fb905f07ea45b31c8f67ba6e4668de46f527e65 Mon Sep 17 00:00:00 2001 From: Jacob Moroni Date: Tue, 1 Sep 2026 16:00:14 +0000 Subject: [PATCH 0078/1417] RDMA/irdma: Enforce local fence for IB_WR_REG_MR Enforce local fence for IB_WR_REG_MR to avoid spurious FASTREG_VALID_MKEY async events during heavy invalidation and registration activity. Commit 69e8e429bca2 ("RDMA/irdma: Enforce local fence for LOCAL_INV WRs") was very similar, but was not sufficient to prevent all occurrences of these async events. Fixes: b48c24c2d710 ("RDMA/irdma: Implement device supported verb APIs") Signed-off-by: Jacob Moroni Link: https://patch.msgid.link/20260901160014.2026285-1-jmoroni@google.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/irdma/verbs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/irdma/verbs.c b/drivers/infiniband/hw/irdma/verbs.c index 9cfd84dd6869bf..5d71300da0ff22 100644 --- a/drivers/infiniband/hw/irdma/verbs.c +++ b/drivers/infiniband/hw/irdma/verbs.c @@ -4281,7 +4281,7 @@ static int irdma_post_send(struct ib_qp *ibqp, stag_info.total_len = iwmr->ibmr.length; stag_info.reg_addr_pa = *palloc->level1.addr; stag_info.first_pm_pbl_index = palloc->level1.idx; - stag_info.local_fence = ib_wr->send_flags & IB_SEND_FENCE; + stag_info.local_fence = true; if (iwmr->npages > IRDMA_MIN_PAGES_PER_FMR) stag_info.chunk_size = 1; err = irdma_sc_mr_fast_register(&iwqp->sc_qp, &stag_info, From 65e05ec252a9b79e75930d3c4dd42d8877db04c5 Mon Sep 17 00:00:00 2001 From: Anthony Krowiak Date: Tue, 18 Aug 2026 15:33:49 -0400 Subject: [PATCH 0079/1417] s390/vfio-ap: fix KVM GISC and page leak when queue removed from host config Three related problems exist in the handling of KVM interrupt and page resources when a queue is removed from the host's AP configuration while assigned to a mediated device (mdev). Problem 1: ~~~~~~~~~ AP_RESPONSE_Q_NOT_AVAIL not handled in vfio_ap_mdev_reset_queue() When the AP bus removes a queue device whose adapter or domain has been removed from the host's AP configuration, vfio_ap_mdev_remove_queue() is called. If the queue is still in the host's AP configuration at that point, it calls vfio_ap_mdev_reset_queue(), which issues a PQAP(ZAPQ). Since the adapter is already gone from the host configuration, ap_zapq() returns AP_RESPONSE_Q_NOT_AVAIL (0x01). This response code is not handled in vfio_ap_mdev_reset_queue()'s switch statement and falls through to the default case, which issues a WARN but does not call vfio_ap_free_aqic_resources(). As a result, if IRQ handling was enabled for the queue by the guest, the KVM GISC registration and the pinned guest page holding the notification indicator byte (NIB) are both leaked. This is fixed by adding AP_RESPONSE_Q_NOT_AVAIL to the same case as AP_RESPONSE_DECONFIGURED and AP_RESPONSE_CHECKSTOPPED in vfio_ap_mdev_reset_queue(). Like those response codes, Q_NOT_AVAIL indicates the queue is not operational and no further reset attempts are possible; the correct action is to free the IRQ resources immediately. Problem 2: ~~~~~~~~~ AP_RESPONSE_Q_NOT_AVAIL not handled in apq_status_check() In vfio_ap_mdev_reset_queue(), there are four cases that indicate a queue reset has not yet completed, in which case apq_reset_check() is queued to a work queue to verify completion of the reset operation. This function uses the PQAP(TAPQ) function to get the queue's status and calls apq_status_check() to verify whether the reset has completed, failed or needs to be executed again. As described in Problem #1 above, apq_reset_check() does not specifically check for AP_RESPONSE_Q_NOT_AVAIL, thereby potentially leaking KVM GISC registration and the pinned guest page holding the NIB. This is fixed by adding a case statement for AP_RESPONSE_Q_NOT_AVAIL to apq_status_check() and returning -ENODEV for that case. The caller, apq_reset_check() will then check for this return code and call vfio_ap_free_aqic_resources() to prevent the leak. Problem 3: ~~~~~~~~~ vfio_ap_free_aqic_resources() leaks saved_isc when kvm is NULL vfio_ap_free_aqic_resources() guards the call to kvm_s390_gisc_unregister() with: if (q->saved_isc != VFIO_AP_ISC_INVALID && !WARN_ON(!(q->matrix_mdev && q->matrix_mdev->kvm))) If matrix_mdev->kvm is NULL -- which can happen when vfio_ap_mdev_unset_kvm() has already run and cleared kvm before a subsequent cleanup path reaches this function -- the WARN_ON fires and the entire block is skipped. This leaves q->saved_isc set to a non-invalid value, creating a potential double-free on any subsequent call to this function. When kvm is NULL the KVM guest is already torn down, so kvm_s390_gisc_unregister() need not and cannot be called; however, q->saved_isc must always be cleared. Fix this by separating the kvm_s390_gisc_unregister() call from the q->saved_isc reset. The WARN_ON now guards only the genuinely impossible case of matrix_mdev being NULL. A NULL kvm is handled gracefully by skipping only the unregister call, and q->saved_isc = VFIO_AP_ISC_INVALID is set unconditionally whenever saved_isc was not already invalid. Additionally, add an else clause to the host-config check in vfio_ap_mdev_remove_queue() to call vfio_ap_free_aqic_resources() directly when the queue is not in the host's AP configuration. This serves as a backstop: when the AP bus fires the driver .remove callback after an adapter is removed from the host config, the queue is by definition no longer addressable, so vfio_ap_mdev_reset_queue() would always return Q_NOT_AVAIL. The else clause handles this case directly without the unnecessary ap_zapq() call, and ensures cleanup occurs even if kvm has already been set to NULL by a prior call to vfio_ap_mdev_unset_kvm(). Fixes: b9bd10c43456d ("s390/vfio-ap: do not reset queue removed from host config") Cc: stable@vger.kernel.org Signed-off-by: Anthony Krowiak Reviewed-by: Matthew Rosato Acked-by: Halil Pasic Signed-off-by: Claudio Imbrenda Message-ID: <20260818193349.1877940-2-akrowiak@linux.ibm.com> --- drivers/s390/crypto/vfio_ap_ops.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c index 940c0ff668beef..4db878c18f41c1 100644 --- a/drivers/s390/crypto/vfio_ap_ops.c +++ b/drivers/s390/crypto/vfio_ap_ops.c @@ -277,9 +277,9 @@ static void vfio_ap_free_aqic_resources(struct vfio_ap_queue *q) { if (!q) return; - if (q->saved_isc != VFIO_AP_ISC_INVALID && - !WARN_ON(!(q->matrix_mdev && q->matrix_mdev->kvm))) { - kvm_s390_gisc_unregister(q->matrix_mdev->kvm, q->saved_isc); + if (q->saved_isc != VFIO_AP_ISC_INVALID) { + if (!WARN_ON(!q->matrix_mdev) && q->matrix_mdev->kvm) + kvm_s390_gisc_unregister(q->matrix_mdev->kvm, q->saved_isc); q->saved_isc = VFIO_AP_ISC_INVALID; } if (q->saved_iova && !WARN_ON(!q->matrix_mdev)) { @@ -1935,6 +1935,8 @@ static int apq_status_check(int apqn, struct ap_queue_status *status) * a value indicating a reset needs to be performed again. */ return -EAGAIN; + case AP_RESPONSE_Q_NOT_AVAIL: + return -ENODEV; default: WARN(true, "failed to verify reset of queue %02x.%04x: TAPQ rc=%u\n", @@ -1961,6 +1963,10 @@ static void apq_reset_check(struct work_struct *reset_work) ret = apq_status_check(q->apqn, &status); if (ret == -EIO) return; + if (ret == -ENODEV) { + vfio_ap_free_aqic_resources(q); + return; + } if (ret == -EBUSY) { pr_notice_ratelimited(WAIT_MSG, elapsed, AP_QID_CARD(q->apqn), @@ -2004,6 +2010,7 @@ static void vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q) break; case AP_RESPONSE_DECONFIGURED: case AP_RESPONSE_CHECKSTOPPED: + case AP_RESPONSE_Q_NOT_AVAIL: vfio_ap_free_aqic_resources(q); break; default: @@ -2528,12 +2535,15 @@ void vfio_ap_mdev_remove_queue(struct ap_device *apdev) /* * If the queue is not in the host's AP configuration, then resetting * it will fail with response code 01, (APQN not valid); so, let's make - * sure it is in the host's config. + * sure it is in the host's config. If it is not, free the KVM GISC + * resources. */ if (test_bit_inv(apid, (unsigned long *)matrix_dev->info.apm) && test_bit_inv(apqi, (unsigned long *)matrix_dev->info.aqm)) { vfio_ap_mdev_reset_queue(q); flush_work(&q->reset_work); + } else { + vfio_ap_free_aqic_resources(q); } done: From d12ce6bce5ec5175c3581e01c71e7a5abb286d9b Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Wed, 12 Aug 2026 17:55:19 +0200 Subject: [PATCH 0080/1417] s390/uv: Fix loop condition in uv_find_secrets Systems with more than 85 UV secrets got -ENOENT for any secret past the first page. Fix this by setting the start index at the beginning of the loop in uv_find_secret() and not at the end. First test if there are more secrets left by comparing start_idx with list->next_secret_idx, and then set the start index to the next secret index. Fixes: 7c9137af2042 ("s390/uv: Retrieve UV secrets support") Acked-by: Claudio Imbrenda Reviewed-by: Christoph Schlameuss Signed-off-by: Steffen Eiden Signed-off-by: Claudio Imbrenda Message-ID: <20260812-uv_secrets_fix-v3-1-a85bd29e0666@linux.ibm.com> --- arch/s390/kernel/uv.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/s390/kernel/uv.c b/arch/s390/kernel/uv.c index dc14ebc0105b3c..52003c463fad0b 100644 --- a/arch/s390/kernel/uv.c +++ b/arch/s390/kernel/uv.c @@ -846,11 +846,14 @@ int uv_find_secret(const u8 secret_id[UV_SECRET_ID_LEN], struct uv_secret_list *list, struct uv_secret_list_item_hdr *secret) { - u16 start_idx = 0; + u16 start_idx; u16 list_rc; int ret; + list->next_secret_idx = 0; + do { + start_idx = list->next_secret_idx; uv_list_secrets(list, start_idx, &list_rc, NULL); if (list_rc != UVC_RC_EXECUTED && list_rc != UVC_RC_MORE_DATA) { if (list_rc == UVC_RC_INV_CMD) @@ -861,7 +864,6 @@ int uv_find_secret(const u8 secret_id[UV_SECRET_ID_LEN], ret = find_secret_in_page(secret_id, list, secret); if (ret == 0) return ret; - start_idx = list->next_secret_idx; } while (list_rc == UVC_RC_MORE_DATA && start_idx < list->next_secret_idx); return -ENOENT; From f47190b08b71e8482072978373ee88cb2dfbdaf4 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Wed, 12 Aug 2026 17:55:20 +0200 Subject: [PATCH 0081/1417] s390/uv: Prevent potential out-of-bounds read When the system has more than 85 secrets, the uv_secret_list struct array only holds up to 85 items per page, resulting in an out of bounds read in find_secret_in_page if the targeted secret is in the next page or not stored at all. Fix this by looping over the number of stored secrets which is the per sub-list count of stored secrets and not the overall count. Fixes: 7c9137af2042 ("s390/uv: Retrieve UV secrets support") Signed-off-by: Steffen Eiden Reviewed-by: Christoph Schlameuss Signed-off-by: Claudio Imbrenda Message-ID: <20260812-uv_secrets_fix-v3-2-a85bd29e0666@linux.ibm.com> --- arch/s390/kernel/uv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/kernel/uv.c b/arch/s390/kernel/uv.c index 52003c463fad0b..8ea9dd7704ffcf 100644 --- a/arch/s390/kernel/uv.c +++ b/arch/s390/kernel/uv.c @@ -825,7 +825,7 @@ static int find_secret_in_page(const u8 secret_id[UV_SECRET_ID_LEN], { u16 i; - for (i = 0; i < list->total_num_secrets; i++) { + for (i = 0; i < list->num_secr_stored; i++) { if (memcmp(secret_id, list->secrets[i].id, UV_SECRET_ID_LEN) == 0) { *secret = list->secrets[i].hdr; return 0; From 2ae16aaa78b5edc6e6d0904c84fd9cdfb762bcda Mon Sep 17 00:00:00 2001 From: Quanye Yang Date: Sun, 30 Aug 2026 15:09:55 +0800 Subject: [PATCH 0082/1417] RDMA/rtrs-clt: Fix CQ pool leak when connect is interrupted The client borrows shared CQ credits in the ADDR_RESOLVED handler via ib_cq_pool_get(), before the peer is connected. create_cm() can return -ERESTARTSYS from wait_event_interruptible_timeout() without destroying the CM ID. The init_conns() and stop-and-destroy paths then call destroy_con_cq_qp() while cq is still NULL (no PUT) and only afterwards rdma_destroy_id(). CMA serializes the handler against rdma_destroy_id() with handler_mutex, but that does not order the GET against destroy_con_cq_qp(). If ADDR_RESOLVED has already passed the DESTROYING check, it can take con_mutex, GET credits, and then lose the con to kfree. Device unregister later hits WARN_ON(cq->cqe_used) in ib_cq_pool_cleanup(). Set a per-connection flag under con_mutex before CQ/QP teardown so a racing ADDR_RESOLVED cannot borrow credits after teardown has begun. Reported-by: syzbot+d396918a29afb8543e1c@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d396918a29afb8543e1c Fixes: 3b89e92c2a95 ("RDMA/rtrs: Use new shared CQ mechanism") Signed-off-by: Quanye Yang Link: https://patch.msgid.link/20260830-rdma-rtrs-clt-cq-pool-leak-v1-1-b169434fd3df@proton.me Reviewed-by: Jack Wang Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/rtrs/rtrs-clt.c | 8 ++++++++ drivers/infiniband/ulp/rtrs/rtrs-clt.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/drivers/infiniband/ulp/rtrs/rtrs-clt.c b/drivers/infiniband/ulp/rtrs/rtrs-clt.c index 7b2c51ae614f81..eac38b57b00d7f 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-clt.c +++ b/drivers/infiniband/ulp/rtrs/rtrs-clt.c @@ -1732,6 +1732,8 @@ static void destroy_con_cq_qp(struct rtrs_clt_con *con) /* * Be careful here: destroy_con_cq_qp() can be called even * create_con_cq_qp() failed, see comments there. + * Caller must set con->destroyed under this lock first so a + * racing ADDR_RESOLVED cannot ib_cq_pool_get() after we PUT/SKIP. */ lockdep_assert_held(&con->con_mutex); rtrs_cq_qp_destroy(&con->c); @@ -1766,6 +1768,10 @@ static int rtrs_rdma_addr_resolved(struct rtrs_clt_con *con) int err; mutex_lock(&con->con_mutex); + if (con->destroyed) { + mutex_unlock(&con->con_mutex); + return -ECONNABORTED; + } err = create_con_cq_qp(con); mutex_unlock(&con->con_mutex); if (err) { @@ -2221,6 +2227,7 @@ static void rtrs_clt_stop_and_destroy_conns(struct rtrs_clt_path *clt_path) break; con = to_clt_con(clt_path->s.con[cid]); mutex_lock(&con->con_mutex); + con->destroyed = true; destroy_con_cq_qp(con); mutex_unlock(&con->con_mutex); destroy_cm(con); @@ -2387,6 +2394,7 @@ static int init_conns(struct rtrs_clt_path *clt_path) if (con->c.cm_id) { stop_cm(con); mutex_lock(&con->con_mutex); + con->destroyed = true; destroy_con_cq_qp(con); mutex_unlock(&con->con_mutex); destroy_cm(con); diff --git a/drivers/infiniband/ulp/rtrs/rtrs-clt.h b/drivers/infiniband/ulp/rtrs/rtrs-clt.h index 1305601a6251ee..ad64f4517c4b34 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-clt.h +++ b/drivers/infiniband/ulp/rtrs/rtrs-clt.h @@ -75,6 +75,8 @@ struct rtrs_clt_con { unsigned int cpu; struct mutex con_mutex; int cm_err; + /* Set under con_mutex before CQ/QP teardown. */ + bool destroyed; }; /** From d1fc569fcbc7be6de06b034cf954a95e30ca1fb2 Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Mon, 27 Jul 2026 01:26:15 -0700 Subject: [PATCH 0083/1417] dmaengine: mmp_pdma: fix wrong extended DRCMR base for SpacemiT K3 The extended DRCMR window on SpacemiT K3 starts at 0x1100. Commit 6587b8661a0b ("dmaengine: mmp_pdma: add SpacemiT K3 support") incorrectly set it to 0x1000, causing DRCMR accesses for request IDs >= 64 to target offsets 0x100 too low. The 0x1100 base has been verified on K3 silicon using real SPI and QSPI DMA transactions. The K3 DMA documentation [1] was updated on June 24, 2026, to reflect the corrected register addresses. Drop the bogus DRCMR_EXT_BASE_K3 macro and reuse DRCMR_EXT_BASE_DEFAULT for the K3 ops. Fixes: 6587b8661a0b ("dmaengine: mmp_pdma: add SpacemiT K3 support") Link: https://www.spacemit.com/community/document/info?nodepath=hardware/key_stone/k3/k3_docs/k3_usermanual/16_peripherals/dma.md&lang=en [1] Signed-off-by: Troy Mitchell Reviewed-by: Frank Li Link: https://patch.msgid.link/20260727-k3-pdma-fix-drcmr-base-v2-1-afba55cba1f3@linux.spacemit.com Signed-off-by: Vinod Koul --- drivers/dma/mmp_pdma.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c index 386e85cd4882a3..78e3e07e681df9 100644 --- a/drivers/dma/mmp_pdma.c +++ b/drivers/dma/mmp_pdma.c @@ -52,7 +52,6 @@ #define DCSR_EORINTR BIT(9) /* The end of Receive */ #define DRCMR_BASE 0x0100 -#define DRCMR_EXT_BASE_K3 0x1000 #define DRCMR_EXT_BASE_DEFAULT 0x1100 #define DRCMR_REQ_LIMIT 64 #define DRCMR_MAPVLD BIT(7) /* Map Valid (read / write) */ @@ -1219,7 +1218,7 @@ static const struct mmp_pdma_ops spacemit_k3_pdma_ops = { .get_desc_dst_addr = get_desc_dst_addr_64, .run_bits = (DCSR_RUN | DCSR_LPAEEN | DCSR_EORIRQEN | DCSR_EORSTOPEN), .dma_width = 64, - .drcmr_ext_base = DRCMR_EXT_BASE_K3, + .drcmr_ext_base = DRCMR_EXT_BASE_DEFAULT, }; static const struct of_device_id mmp_pdma_dt_ids[] = { From 0294b6dd515256c03ea2dbf508ddd3826d788579 Mon Sep 17 00:00:00 2001 From: Alexander Chesnokov Date: Wed, 12 Aug 2026 08:34:26 +0300 Subject: [PATCH 0084/1417] dmaengine: ti: k3-udma-glue: fix NULL dereference in k3_udma_glue_release_rx_chn() If devm_kcalloc() for rx_chn->flows fails in a channel request function, the error path calls k3_udma_glue_release_rx_chn(), which dereferences the NULL rx_chn->flows pointer in k3_udma_glue_release_rx_flow(). Skip the flow release loop in k3_udma_glue_release_rx_chn() when rx_chn->flows is not allocated. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: d70241913413 ("dmaengine: ti: k3-udma: Add glue layer for non DMAengine users") Cc: stable@vger.kernel.org Reported-by: Pavel Zhigulin Signed-off-by: Alexander Chesnokov Reviewed-by: Frank Li Link: https://patch.msgid.link/20260812053426.3521589-1-Alexander.Chesnokov@kaspersky.com Signed-off-by: Vinod Koul --- drivers/dma/ti/k3-udma-glue.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/dma/ti/k3-udma-glue.c b/drivers/dma/ti/k3-udma-glue.c index 686dc140293eba..70eaf7ee57e681 100644 --- a/drivers/dma/ti/k3-udma-glue.c +++ b/drivers/dma/ti/k3-udma-glue.c @@ -1243,8 +1243,9 @@ void k3_udma_glue_release_rx_chn(struct k3_udma_glue_rx_channel *rx_chn) rx_chn->psil_paired = false; } - for (i = 0; i < rx_chn->flow_num; i++) - k3_udma_glue_release_rx_flow(rx_chn, i); + if (rx_chn->flows) + for (i = 0; i < rx_chn->flow_num; i++) + k3_udma_glue_release_rx_flow(rx_chn, i); if (xudma_rflow_is_gp(rx_chn->common.udmax, rx_chn->flow_id_base)) xudma_free_gp_rflow_range(rx_chn->common.udmax, From a7df136ec529ee49a789c5029bc37b98b0d4bedd Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 13 Aug 2026 23:31:49 +0800 Subject: [PATCH 0085/1417] dmaengine: sprd: Fix runtime PM reference leak in probe pm_runtime_get_sync() increments a device's usage counter even when it fails. sprd_dma_probe() currently jumps directly to controller clock cleanup on that error, bypassing both pm_runtime_put_noidle() and pm_runtime_disable(). This can happen if the preceding unchecked pm_runtime_set_active() fails and the following runtime-resume attempt also returns an error. Enter the existing runtime-PM unwind path instead. This drops the reference without idling the partially initialized device, disables runtime PM, and then releases the controller clocks. The success path and propagated error code are unchanged. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 9b3b8171f7f4 ("dmaengine: sprd: Add Spreadtrum DMA driver") Signed-off-by: Ruoyu Wang Reviewed-by: Frank Li Reviewed-by: Baolin Wang Link: https://patch.msgid.link/20260813153149.3953497-1-ruoyuw560@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/sprd-dma.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dma/sprd-dma.c b/drivers/dma/sprd-dma.c index 087fea3af2e411..19b32a23c882de 100644 --- a/drivers/dma/sprd-dma.c +++ b/drivers/dma/sprd-dma.c @@ -1212,7 +1212,7 @@ static int sprd_dma_probe(struct platform_device *pdev) ret = pm_runtime_get_sync(&pdev->dev); if (ret < 0) - goto err_rpm; + goto err_register; ret = dma_async_device_register(&sdev->dma_dev); if (ret < 0) { @@ -1234,7 +1234,6 @@ static int sprd_dma_probe(struct platform_device *pdev) err_register: pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); -err_rpm: sprd_dma_disable(sdev); return ret; } From 0a85182723b65ad8bee8131bc38fcf0347d6679b Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Thu, 3 Sep 2026 14:06:26 +0800 Subject: [PATCH 0086/1417] sched_ext: Fix NULL sched deref in kfunc sub-sched error paths When the root scheduler has sub-scheds attached, the COMPAT kfunc wrappers scx_bpf_select_cpu_and() and scx_bpf_dsq_insert_vtime() refuse the call and report to @p's scheduler: scx_error(scx_task_sched(p), "... must be used"); The wrappers are reachable with tasks that have no scheduler. scx_bpf_select_cpu_and() is in the select_cpu kfunc group, which scx_kfunc_context_filter() opens to BPF_PROG_TYPE_SYSCALL programs; scx_bpf_dsq_insert_vtime() is in the enqueue_dispatch group, which ops.enqueue() and ops.dispatch() may call with any KF_RCU task -- the group has no kf_tasks validation, and scx_dsq_insert_preamble() checks task ownership with scx_task_on_sched() precisely because @p may be an arbitrary task. scx_task_sched(p) is p->scx.sched, which is NULL for tasks past sched_ext_dead() -- which clears it via scx_disable_and_exit_task() on exit -- and for idle tasks, which the enable paths skip as they are never scheduled through SCX. It is also an rcu_dereference_protected() that expects @p's pi_lock or rq lock, which neither wrapper holds. Passing NULL to scx_error() reaches scx_vexit(), which dereferences sch->exit_info, oopsing the kernel. One concrete trigger exercised while developing the fix: a BPF_PROG_TYPE_SYSCALL program calling the select_cpu_and wrapper on an exited-but-not-reaped task while a sub-scheduler was attached (its pid stays findable while the zombie is unreaped; faulting instruction is the scx_vexit() prologue "mov r15,[rdi+0x398]" with RDI=NULL and 0x398 the offset of sch->exit_info): sched_ext: BPF scheduler "kfunc_subsched_null" enabled sched_ext: BPF sub-scheduler "kfunc_subsched_null" enabled sched_ext: Unassociated program run_select_cpu_ (id 76) BUG: kernel NULL pointer dereference, address: 0000000000000398 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page Oops: Oops: 0000 [#1] SMP NOPTI CPU: 7 UID: 0 PID: 8201 Comm: kfunc_test_runn Tainted: G W RIP: 0010:scx_vexit+0x25/0xa0 Code: ... <4c> 8b bf 98 03 00 00 ... CR2: 0000000000000398 Call Trace: __scx_exit+0x4f/0x70 scx_bpf_select_cpu_and+0xab/0xb0 bpf_prog_430ed61a7b66e03a_run_select_cpu_and+0x9c/0xe7 ? __x64_sys_bpf+0x2c/0x40 bpf_prog_test_run_syscall+0x130/0x2f0 __sys_bpf+0x930/0x10d0 ? __x64_sys_bpf+0x2c/0x40 __x64_sys_bpf+0x2c/0x40 do_syscall_64+0xbc/0x460 entry_SYSCALL_64_after_hwframe+0x76/0x7e Read @p's scheduler under RCU instead, which the wrappers can do from their guard(rcu)(): fault it when it can be determined, and when it can't be determined -- @p is a task past sched_ext_dead() or an idle task -- there is nothing obviously wrong to report, so just refuse the call as before without faulting any scheduler. These COMPAT wrappers are scheduled for eventual removal once the deprecation grace period elapses, but until then -- and regardless of their removal timeline -- they must not oops the kernel on a task they are handed. Cc: stable@vger.kernel.org # v7.1+ Fixes: a5fa0708cbfd ("sched_ext: Enforce scheduling authority in dispatch and select_cpu operations") Suggested-by: Andrea Righi Signed-off-by: Wanwu Li Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 11 +++++++++-- kernel/sched/ext/idle.c | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 5f242ab69cf45d..7e414a7c53fcfb 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -8949,10 +8949,17 @@ __bpf_kfunc void scx_bpf_dsq_insert_vtime(struct task_struct *p, u64 dsq_id, #ifdef CONFIG_EXT_SUB_SCHED /* * Disallow if any sub-scheds are attached. There is no way to tell - * which scheduler called us, just error out @p's scheduler. + * which scheduler called us, so error out @p's scheduler -- read it + * under RCU as @p's locks aren't necessarily held here. @p may be a + * task past sched_ext_dead() or an idle task, in which case its + * scheduler can't be determined and there is nothing obviously wrong + * to report; just refuse the call. */ if (unlikely(!list_empty(&sch->children))) { - scx_error(scx_task_sched(p), "__scx_bpf_dsq_insert_vtime() must be used"); + struct scx_sched *tsch = scx_task_sched_rcu(p); + + if (tsch) + scx_error(tsch, "__scx_bpf_dsq_insert_vtime() must be used"); return; } #endif diff --git a/kernel/sched/ext/idle.c b/kernel/sched/ext/idle.c index d2973fb3af6d62..aa9fb6de0ad60b 100644 --- a/kernel/sched/ext/idle.c +++ b/kernel/sched/ext/idle.c @@ -1142,10 +1142,17 @@ __bpf_kfunc s32 scx_bpf_select_cpu_and(struct task_struct *p, s32 prev_cpu, u64 #ifdef CONFIG_EXT_SUB_SCHED /* * Disallow if any sub-scheds are attached. There is no way to tell - * which scheduler called us, just error out @p's scheduler. + * which scheduler called us, so error out @p's scheduler -- read it + * under RCU as @p's locks aren't necessarily held here. @p may be a + * task past sched_ext_dead() or an idle task, in which case its + * scheduler can't be determined and there is nothing obviously wrong + * to report; just refuse the call. */ if (unlikely(!list_empty(&sch->children))) { - scx_error(scx_task_sched(p), "__scx_bpf_select_cpu_and() must be used"); + struct scx_sched *tsch = scx_task_sched_rcu(p); + + if (tsch) + scx_error(tsch, "__scx_bpf_select_cpu_and() must be used"); return -EINVAL; } #endif From af72b5946d493cecced27d0951ea37c1d178601e Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Thu, 27 Aug 2026 15:43:02 +1000 Subject: [PATCH 0087/1417] wifi: mac80211: include TIM bitmap control for buffered S1G mcast traffic Currently when building the S1G TIM element, we only build the bitmap control if we have buffered unicast traffic. Since AID 0 sits within the bitmap control if we have buffered multicast traffic with no buffered unicast traffic the bitmap control won't be emitted and dozing stations will be unaware of buffered multicast. To fix, only exclude the bitmap control byte when we don't have both buffered unicast and multicast traffic. Fixes: ee6360945483 ("wifi: mac80211: support block bitmap S1G TIM encoding") Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260827054302.254124-1-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 3a1e2c9e1565ee..3896c7b2c4e5ce 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -5089,10 +5089,18 @@ static void ieee80211_beacon_add_tim_pvb(struct ps_data *ps, */ static void ieee80211_s1g_beacon_add_tim_pvb(struct ps_data *ps, struct sk_buff *skb, - bool mcast_traffic) + bool mcast_traffic, + bool ucast_traffic) { int blk; + /* + * if no unicast and multicast traffic don't emit a bitmap control + * or pvb + */ + if (!mcast_traffic && !ucast_traffic) + return; + /* * Emit a bitmap control block with a page slice number of 31 and a * page index of 0 which indicates as per IEEE80211-2024 9.4.2.5.1 @@ -5101,6 +5109,10 @@ static void ieee80211_s1g_beacon_add_tim_pvb(struct ps_data *ps, */ skb_put_u8(skb, mcast_traffic | (31 << 1)); + /* If there's no unicast traffic we don't need to include a PVB. */ + if (!ucast_traffic) + return; + /* Emit an encoded block for each non-zero sub-block */ for (blk = 0; blk < IEEE80211_MAX_SUPPORTED_S1G_TIM_BLOCKS; blk++) { u8 blk_bmap = 0; @@ -5182,25 +5194,16 @@ static void __ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata, ps->dtim_bc_mc = mcast_traffic; - if (have_bits) { - if (s1g) - ieee80211_s1g_beacon_add_tim_pvb(ps, skb, - mcast_traffic); - else - ieee80211_beacon_add_tim_pvb(ps, skb, mcast_traffic); + if (s1g) { + ieee80211_s1g_beacon_add_tim_pvb(ps, skb, mcast_traffic, + have_bits); + } else if (have_bits) { + ieee80211_beacon_add_tim_pvb(ps, skb, mcast_traffic); } else { - /* - * If there is no buffered unicast traffic for an S1G - * interface, we can exclude the bitmap control. This is in - * contrast to other phy types as they do include the bitmap - * control and pvb even when there is no buffered traffic. - */ - if (!s1g) { - /* Bitmap control */ - skb_put_u8(skb, mcast_traffic); - /* Part Virt Bitmap */ - skb_put_u8(skb, 0); - } + /* Bitmap control */ + skb_put_u8(skb, mcast_traffic); + /* Part Virt Bitmap */ + skb_put_u8(skb, 0); } tim->datalen = skb_tail_pointer(skb) - tim->data; From e6031f02269c0f51cf67886d177f02bc300b47cd Mon Sep 17 00:00:00 2001 From: Ivan Pustogarov Date: Thu, 3 Sep 2026 17:26:16 +0200 Subject: [PATCH 0088/1417] wifi: mac80211: avoid out-of-bounds read for empty PREQ elements ieee80211_mesh_preq_size_ok() derives the location of the PREQ bottom fields before checking whether the element contains even the fixed header. ieee80211_mesh_hwmp_preq_get_bottom() reads the flags byte to account for the optional Address Extension field. Consequently, an empty PREQ element causes a one-byte read beyond its declared payload. Move the helper call after both size checks, so the bottom fields are only accessed when they are present. Fixes: 8b40b1d24a60 ("wifi: mac80211: Fix overread in PREQ frame processing") Cc: stable@vger.kernel.org Signed-off-by: Ivan Pustogarov Link: https://patch.msgid.link/20260903152616.1646637-1-ivan@ipust.net Signed-off-by: Johannes Berg --- include/linux/ieee80211-mesh.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/ieee80211-mesh.h b/include/linux/ieee80211-mesh.h index 7eb15834531cf3..9e548b9173df1a 100644 --- a/include/linux/ieee80211-mesh.h +++ b/include/linux/ieee80211-mesh.h @@ -361,8 +361,7 @@ ieee80211_mesh_hwmp_perr_get_rcode(const u8 *ie, u8 dst_idx) /* IEEE Std 802.11-2016 9.4.2.113 PREQ element */ static inline bool ieee80211_mesh_preq_size_ok(const u8 *pos, u8 elen) { - struct ieee80211_mesh_hwmp_preq_bottom *preq_elem_bottom = - ieee80211_mesh_hwmp_preq_get_bottom(pos); + struct ieee80211_mesh_hwmp_preq_bottom *preq_elem_bottom; u8 target_count; int needed; @@ -378,6 +377,7 @@ static inline bool ieee80211_mesh_preq_size_ok(const u8 *pos, u8 elen) if (elen < needed) return false; + preq_elem_bottom = ieee80211_mesh_hwmp_preq_get_bottom(pos); target_count = preq_elem_bottom->target_count; /* IEEE Std 802.11-2016 Table 14-10 to 14-16 */ if (target_count < 1) From b5526b780f8b297a76030410b96ba29153afb98f Mon Sep 17 00:00:00 2001 From: Stanislaw Gruszka Date: Thu, 20 Aug 2026 11:30:59 +0200 Subject: [PATCH 0089/1417] wifi: iwlegacy: fix broadcast stations deallocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the error path of __il4965_up(), il_dealloc_bcast_stations() clears only IL_STA_UCODE_ACTIVE, leaving IL_STA_BCAST set. This causes the same broadcast stations to be deallocated again by __il4965_down(). This can occur when RF_KILL is toggled during driver startup. To fix clear the entire 'used' field, since we will not do any other operations on the station. Reported-and-tested-by: Martin-Éric Racine Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221733 Fixes: c2fd34469d16 ("iwl4965: Fix a memory leak in error handling code of __il4965_up") Cc: # 7.1.x: 57aa1718d595 wifi: iwlegacy: replace BUG_ON() with WARN_ON() on num_stations check Cc: # 6.x.x: 57aa1718d595 wifi: iwlegacy: replace BUG_ON() with WARN_ON() on num_stations check Cc: # 5.x.x: 57aa1718d595 wifi: iwlegacy: replace BUG_ON() with WARN_ON() on num_stations check Signed-off-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260820093059.18779-1-stf_xl@wp.pl Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/iwlegacy/common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlegacy/common.c b/drivers/net/wireless/intel/iwlegacy/common.c index 0bb807ff8edfff..e5113c6b2d5c5d 100644 --- a/drivers/net/wireless/intel/iwlegacy/common.c +++ b/drivers/net/wireless/intel/iwlegacy/common.c @@ -2326,7 +2326,7 @@ il_dealloc_bcast_stations(struct il_priv *il) if (!(il->stations[i].used & IL_STA_BCAST)) continue; - il->stations[i].used &= ~IL_STA_UCODE_ACTIVE; + il->stations[i].used = 0; il->num_stations--; if (WARN_ON(il->num_stations < 0)) il->num_stations = 0; From 8a1f3cf89ddcc700e25afe42cfad333059adcc94 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Thu, 20 Aug 2026 20:51:26 +0800 Subject: [PATCH 0090/1417] wifi: wlcore: release runtime PM ref on regdomain config failure wlcore_regdomain_config() gets a runtime PM reference before sending the regulatory-domain command. When wlcore_cmd_regdomain_config_locked() fails, the function queues recovery and returns without dropping that reference. Release the reference after handling the command result so both success and failure paths balance the preceding pm_runtime_resume_and_get(). The recovery worker takes a separate runtime PM reference and cannot release the reference held here. Fixes: fa2648a34e73 ("wlcore: Add support for runtime PM") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260820125126.12757-1-runyu.xiao@seu.edu.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/ti/wlcore/main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c index 5595f7a1fc0c36..edf6ca23c6c35f 100644 --- a/drivers/net/wireless/ti/wlcore/main.c +++ b/drivers/net/wireless/ti/wlcore/main.c @@ -3724,10 +3724,8 @@ void wlcore_regdomain_config(struct wl1271 *wl) goto out; ret = wlcore_cmd_regdomain_config_locked(wl); - if (ret < 0) { + if (ret < 0) wl12xx_queue_recovery_work(wl); - goto out; - } pm_runtime_put_autosuspend(wl->dev); out: From ba6cb7c0868a412c2eb68e8efd5aa38bfb258a14 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Fri, 7 Aug 2026 11:52:30 +0000 Subject: [PATCH 0091/1417] wifi: wilc1000: fix out-of-bounds read in P2P public action frames wilc_wfi_p2p_rx() and mgmt_tx() start parsing a frame once ieee80211_is_public_action() returns true. That helper only verifies the frame is long enough for the action category field, that is offsetofend(struct ieee80211_mgmt, u.action.category), 25 bytes. Both functions then read the P2P public action header up to oui_subtype at offset 30 and pass "size - ie_offset" to cfg80211_find_vendor_ie(), where ie_offset is offsetof(struct ieee80211_mgmt, u) + sizeof(*d), i.e. 32. A public action frame of 25 to 31 bytes passes the check but is shorter than that 32 byte header, so oui_subtype can be read out of bounds, and because the length is unsigned, "size - ie_offset" underflows to a value close to 4 GiB. cfg80211_find_vendor_ie() takes an unsigned int length, so even the size_t subtraction in mgmt_tx() is truncated to the same value. It then walks far past the buffer searching for a vendor element until it reaches unmapped memory. In the receive path the frame arrives over the air and needs no association, so a nearby unauthenticated device can crash the host while it is in P2P listen. Reject frames shorter than the P2P public action header in both paths before dereferencing it. Fixes: 4fb8b5aa2a11 ("staging: wilc1000: refactor p2p action frames handling API's") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Link: https://patch.msgid.link/20260807115230.136767-1-ali@iusegentoo.com Signed-off-by: Johannes Berg --- drivers/net/wireless/microchip/wilc1000/cfg80211.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/net/wireless/microchip/wilc1000/cfg80211.c b/drivers/net/wireless/microchip/wilc1000/cfg80211.c index bb2748a1932944..9ad21d41e99995 100644 --- a/drivers/net/wireless/microchip/wilc1000/cfg80211.c +++ b/drivers/net/wireless/microchip/wilc1000/cfg80211.c @@ -1058,6 +1058,13 @@ void wilc_wfi_p2p_rx(struct wilc_vif *vif, u8 *buff, u32 size) if (!ieee80211_is_public_action((struct ieee80211_hdr *)buff, size)) goto out_rx_mgmt; + /* ieee80211_is_public_action() only validates up to the category + * byte, so reject frames too short for the P2P public action header + * before dereferencing it or computing size - ie_offset. + */ + if (size < ie_offset) + goto out_rx_mgmt; + d = (struct wilc_p2p_pub_act_frame *)(&mgmt->u.action); if (d->oui_subtype != GO_NEG_REQ && d->oui_subtype != GO_NEG_RSP && d->oui_subtype != P2P_INV_REQ && d->oui_subtype != P2P_INV_RSP) @@ -1200,6 +1207,13 @@ static int mgmt_tx(struct wiphy *wiphy, goto out_set_timeout; } + /* ieee80211_is_public_action() only validates up to the category + * byte, so reject frames too short for the P2P public action header + * before dereferencing it or computing len - ie_offset. + */ + if (len < ie_offset) + goto out_set_timeout; + d = (struct wilc_p2p_pub_act_frame *)(&mgmt->u.action); if (d->oui_type != WLAN_OUI_TYPE_WFA_P2P || d->oui_subtype != GO_NEG_CONF) { From f9edf7cf63b96d2b776fca8d258d3c5256e40c8e Mon Sep 17 00:00:00 2001 From: Mariano Baragiola Date: Sun, 9 Aug 2026 09:49:47 -0300 Subject: [PATCH 0092/1417] wifi: virt_wifi: free skb when disconnected When the simulated link is disconnected, virt_wifi_start_xmit() returns NET_XMIT_DROP without freeing the skb. dev_hard_start_xmit() treats this return value as consumed, so every packet sent while disconnected leaks its skb. Free the skb before returning the drop status. Fixes: c7cdba31ed8b ("mac80211-next: rtnetlink wifi simulation device") Signed-off-by: Mariano Baragiola Link: https://patch.msgid.link/20260809124947.3590270-1-mbaragiola@linux.com Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/virt_wifi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/virtual/virt_wifi.c b/drivers/net/wireless/virtual/virt_wifi.c index 2335e45db8b853..b69a4650fba855 100644 --- a/drivers/net/wireless/virtual/virt_wifi.c +++ b/drivers/net/wireless/virtual/virt_wifi.c @@ -434,6 +434,7 @@ static netdev_tx_t virt_wifi_start_xmit(struct sk_buff *skb, priv->tx_packets++; if (!priv->is_connected) { priv->tx_failed++; + dev_kfree_skb_any(skb); return NET_XMIT_DROP; } From bbb9a0ab96d44a64529aafc7a16de460a1712f6a Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Sat, 15 Aug 2026 19:57:24 +0800 Subject: [PATCH 0093/1417] wifi: libertas_tf: fix UAF in lbtf_free_adapter() lbtf_free_adapter() calls lbtf_free_cmd_buffer() to free the command buffers before calling timer_delete_sync() to wait for the command timer callback. If the timer callback (command_timer_fn) is already running when lbtf_free_cmd_buffer() frees the command array, the callback dereferences priv->cur_cmd->cmdbuf which points to freed memory. Swap the order so that timer_delete_sync() runs first, ensuring any in-flight callback has completed before the command buffers are freed. Fixes: 06b16ae53192 ("libertas_tf: main.c, data paths and mac80211 handlers") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Link: https://patch.msgid.link/20260815115724.920628-1-yijiangshan@kylinos.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas_tf/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/libertas_tf/main.c b/drivers/net/wireless/marvell/libertas_tf/main.c index 42be6fa22f9c6a..411f075b6186a8 100644 --- a/drivers/net/wireless/marvell/libertas_tf/main.c +++ b/drivers/net/wireless/marvell/libertas_tf/main.c @@ -173,8 +173,8 @@ static int lbtf_init_adapter(struct lbtf_private *priv) static void lbtf_free_adapter(struct lbtf_private *priv) { lbtf_deb_enter(LBTF_DEB_MAIN); - lbtf_free_cmd_buffer(priv); timer_delete_sync(&priv->command_timer); + lbtf_free_cmd_buffer(priv); lbtf_deb_leave(LBTF_DEB_MAIN); } From a3d722190cdef18da4878b5efc27c3c386dda248 Mon Sep 17 00:00:00 2001 From: Peng Hao Date: Fri, 28 Aug 2026 19:15:31 +0800 Subject: [PATCH 0094/1417] wifi: mwifiex: fix IRQ leak using wrong index in MSI-X error path mwifiex_pcie_request_irq() registers each MSI-X vector with a per-index dev_id (&card->msix_ctx[i]). On a request_irq() failure the cleanup loop "for (j = 0; j < i; j++)" frees msix_entries[j].vector but passes the failed index's &card->msix_ctx[i] as the dev_id. free_irq() matches on (irq, dev_id), so it fails to find the action registered with &card->msix_ctx[j]: the already-requested IRQ j is not freed (leaked) and free_irq() warns about freeing a non-existent IRQ. Use &card->msix_ctx[j]. Fixes: 99074fc1e67b ("mwifiex: enable pcie MSIx interrupt mode support") Signed-off-by: Peng Hao Link: https://patch.msgid.link/20260828111531.56723-1-flyingpeng@tencent.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c index a760de191fce73..a9425e9a94f451 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie.c @@ -3068,7 +3068,7 @@ static int mwifiex_pcie_request_irq(struct mwifiex_adapter *adapter) ret); for (j = 0; j < i; j++) free_irq(card->msix_entries[j].vector, - &card->msix_ctx[i]); + &card->msix_ctx[j]); pci_disable_msix(pdev); } else { mwifiex_dbg(adapter, MSG, "MSIx enabled!"); From fa00193eb991f92b007aefe7afb6a7566976dacf Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 20 Aug 2026 14:21:55 +0800 Subject: [PATCH 0095/1417] wifi: mwifiex: prevent authentication frame length truncation mwifiex_cfg80211_authenticate() derives the authentication frame length from req->ie_len and req->auth_data_len, both of type size_t, but stores it in a u16. NL80211_ATTR_AUTH_DATA only has a minimum length policy. Since nla_len is a u16, a single attribute can carry up to 65531 bytes of payload, so the sum can exceed U16_MAX before it is assigned to pkt_len. The truncated pkt_len determines the skb frame area, while the copy length remains req->auth_data_len - 4, resulting in a heap buffer overflow. For example, with auth_data_len equal to 65510 and no IEs, the sum is 65546. It is truncated to 10 and then reduced by four to 6. The driver appends only six bytes to the skb with skb_put(), but then copies 65506 user-provided bytes into the authentication body. Reaching this path requires CAP_NET_ADMIN in the user namespace owning the network namespace, an up station netdev, and a suitable BSS/SAE authentication request. Compute the length in size_t, reject values that cannot be represented by the firmware's u16 frame length field, and only then assign it to pkt_len. Fixes: 36995892c271 ("wifi: mwifiex: add host mlme for client mode") Cc: stable@vger.kernel.org # 6.12+ Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260820062155.3981976-1-lilinmao@kylinos.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/cfg80211.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c index 7a1ba32f1fb367..936939ea9c4723 100644 --- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c +++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c @@ -4277,6 +4277,7 @@ mwifiex_cfg80211_authenticate(struct wiphy *wiphy, struct mwifiex_adapter *adapter = priv->adapter; struct sk_buff *skb; u16 pkt_len, auth_alg; + size_t frame_len; int ret; struct mwifiex_ieee80211_mgmt *mgmt; struct mwifiex_txinfo *tx_info; @@ -4349,10 +4350,17 @@ mwifiex_cfg80211_authenticate(struct wiphy *wiphy, mwifiex_cancel_scan(adapter); - pkt_len = (u16)req->ie_len + req->auth_data_len + + frame_len = req->ie_len + req->auth_data_len + MWIFIEX_MGMT_HEADER_LEN + MWIFIEX_AUTH_BODY_LEN; if (req->auth_data_len >= 4) - pkt_len -= 4; + frame_len -= 4; + + if (frame_len > U16_MAX) { + mwifiex_dbg(priv->adapter, ERROR, + "auth frame too long: %zu bytes\n", frame_len); + return -EINVAL; + } + pkt_len = frame_len; skb = dev_alloc_skb(MWIFIEX_MIN_DATA_HEADER_LEN + MWIFIEX_MGMT_FRAME_HEADER_SIZE + From e2de8d5eb2984416affdd9559e55f37c7f1bbf47 Mon Sep 17 00:00:00 2001 From: Bogdan Nicolae Date: Fri, 7 Aug 2026 11:34:18 -0500 Subject: [PATCH 0096/1417] wifi: brcmfmac: cyw: pass PMKID to firmware if present Zero out auth_status on initialization. Otherwise, garbage will leak from the stack to the firmware (when ssid is less than 32 bytes and/or when params->pmkid is set). Then, pass the params->pmkid to the firmware (without it, the firmware caches a garbage PMKID on successful authentication and denies a subsequent association request that includes the PMKID). Fixes: 66f909308a7c ("wifi: brcmfmac: cyw: support external SAE authentication in station mode") Signed-off-by: Bogdan Nicolae Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260807163418.487508-1-bogdan.nicolae@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c index 545eb9aae9660d..6d5098f6f00f97 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c @@ -198,7 +198,7 @@ brcmf_cyw_external_auth(struct wiphy *wiphy, struct net_device *dev, { struct brcmf_if *ifp; struct brcmf_pub *drvr; - struct brcmf_auth_req_status_le auth_status; + struct brcmf_auth_req_status_le auth_status = {}; int ret = 0; brcmf_dbg(TRACE, "Enter\n"); @@ -206,6 +206,9 @@ brcmf_cyw_external_auth(struct wiphy *wiphy, struct net_device *dev, ifp = netdev_priv(dev); drvr = ifp->drvr; if (params->status == WLAN_STATUS_SUCCESS) { + if (params->pmkid) + memcpy(auth_status.pmkid, params->pmkid, + WLAN_PMKID_LEN); auth_status.flags = cpu_to_le16(BRCMF_EXTAUTH_SUCCESS); } else { bphy_err(drvr, "External authentication failed: status=%d\n", From e667aee1c192d67d27c803007bfa9c6e0873e959 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Fri, 14 Aug 2026 15:47:04 +0200 Subject: [PATCH 0097/1417] wifi: mwifiex: bound the pairwise-cipher OUI walk to the IE length mwifiex_search_oui_in_ie() reads a pairwise-cipher (PTK) count from a beacon/probe-response RSN or WPA information element and then walks that many 4-byte OUIs, comparing each with memcmp(). The count comes straight from the (attacker-supplied) IE and is never checked against the element's own length, and the callers admit the element on element_id alone (has_ieee_hdr() / has_vendor_hdr(), no length check). A crafted RSN/WPA IE with a large pairwise count therefore makes the walk read up to 255 * 4 bytes past the element -- an out-of-bounds read of the kmemdup()'d beacon buffer, reachable from any AP whose beacon/probe response is processed during scan-result parsing. Pass the number of IE bytes available at the OUI list and bound the walk to the element. Keep the length signed and reject a negative value before any unsigned arithmetic, so a small or zero IE length cannot underflow to a large size_t and defeat the bound. Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: 5e6e3a92b9a4 ("wireless: mwifiex: initial commit for Marvell mwifiex driver") Cc: stable@vger.kernel.org Assisted-by: 0sec:multi-model Signed-off-by: Doruk Tan Ozturk Link: https://patch.msgid.link/20260814134704.85902-1-doruk@0sec.ai Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/scan.c | 25 ++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/scan.c b/drivers/net/wireless/marvell/mwifiex/scan.c index 97c0ec3b822e70..473f4623ea199d 100644 --- a/drivers/net/wireless/marvell/mwifiex/scan.c +++ b/drivers/net/wireless/marvell/mwifiex/scan.c @@ -104,12 +104,24 @@ has_vendor_hdr(struct ieee_types_vendor_specific *ie, u8 key) * a given oui in PTK. */ static u8 -mwifiex_search_oui_in_ie(struct ie_body *iebody, u8 *oui) +mwifiex_search_oui_in_ie(struct ie_body *iebody, u8 *oui, int ie_len) { + const size_t ptk_body_offset = offsetof(struct ie_body, ptk_body); u8 count; + /* ie_len is the number of bytes available at iebody. Keep it signed + * and reject a negative (underflowed) length before the unsigned + * comparisons below, so a small or zero IE length cannot wrap. + */ + if (ie_len < 0 || (size_t)ie_len < ptk_body_offset) + return MWIFIEX_OUI_NOT_PRESENT; + count = iebody->ptk_cnt[0]; + /* Reject an OUI count whose list would run past the element. */ + if (ptk_body_offset + count * sizeof(iebody->ptk_body) > (size_t)ie_len) + return MWIFIEX_OUI_NOT_PRESENT; + /* There could be multiple OUIs for PTK hence 1) Take the length. 2) Check all the OUIs for AES. @@ -143,11 +155,14 @@ mwifiex_is_rsn_oui_present(struct mwifiex_bssdescriptor *bss_desc, u32 cipher) u8 ret = MWIFIEX_OUI_NOT_PRESENT; if (has_ieee_hdr(bss_desc->bcn_rsn_ie, WLAN_EID_RSN)) { + int ie_len = (int)bss_desc->bcn_rsn_ie->ieee_hdr.len - + RSN_GTK_OUI_OFFSET; + iebody = (struct ie_body *) (((u8 *) bss_desc->bcn_rsn_ie->data) + RSN_GTK_OUI_OFFSET); oui = &mwifiex_rsn_oui[cipher][0]; - ret = mwifiex_search_oui_in_ie(iebody, oui); + ret = mwifiex_search_oui_in_ie(iebody, oui, ie_len); if (ret) return ret; } @@ -169,10 +184,14 @@ mwifiex_is_wpa_oui_present(struct mwifiex_bssdescriptor *bss_desc, u32 cipher) u8 ret = MWIFIEX_OUI_NOT_PRESENT; if (has_vendor_hdr(bss_desc->bcn_wpa_ie, WLAN_EID_VENDOR_SPECIFIC)) { + int ie_len = (int)bss_desc->bcn_wpa_ie->vend_hdr.len - + (int)sizeof(bss_desc->bcn_wpa_ie->vend_hdr.oui) - + WPA_GTK_OUI_OFFSET; + iebody = (struct ie_body *)((u8 *)bss_desc->bcn_wpa_ie->data + WPA_GTK_OUI_OFFSET); oui = &mwifiex_wpa_oui[cipher][0]; - ret = mwifiex_search_oui_in_ie(iebody, oui); + ret = mwifiex_search_oui_in_ie(iebody, oui, ie_len); if (ret) return ret; } From 1c25bfad93e69ce13f744a2fb919f02ea396a985 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Tue, 25 Aug 2026 19:25:23 +0800 Subject: [PATCH 0098/1417] wifi: mwifiex: validate action frame fixed fields mwifiex_process_mgmt_packet() accepts an rx_pkt_length as small as a four-address struct ieee80211_hdr plus the two-byte firmware length prefix. After stripping the prefix, mwifiex_parse_mgmt_packet() can receive a frame equal to sizeof(struct ieee80211_hdr). For action frames, the parser reads the category byte immediately after that header and, for a public action frame, reads the following action code byte without verifying that either field is present. A truncated frame can therefore make the parser consume up to two bytes past the firmware-declared frame length. If those bytes look like a TDLS discovery response, the malformed frame can spuriously update peer signal state. Require the category and public action-code fields before reading them. Use sizeof(*ieee_hdr) so the checks and field accesses directly match the firmware four-address layout being parsed before address4 is removed. Suggested-by: Johannes Berg Suggested-by: Brian Norris Fixes: 72e5aa8d2a6d ("mwifiex: support for parsing TDLS discovery frames") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/66f148d83eb9f0970b9abbccc85d1b61244e54ad.camel@sipsolutions.net/ Link: https://lore.kernel.org/all/20260708195911.84365-8-enderaoelyther@gmail.com/ Link: https://lore.kernel.org/all/20260723011013.76968-1-enderaoelyther@gmail.com/ Link: https://lore.kernel.org/all/20260723202257.688-1-enderaoelyther@gmail.com/ Link: https://lore.kernel.org/all/anuWyiPQja6_5vly@google.com/ Assisted-by: Codex:gpt-5 Assisted-by: Kimi:K3 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260825112523.95774-1-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/util.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/util.c b/drivers/net/wireless/marvell/mwifiex/util.c index 7d3631d212236c..71305efb77ac5e 100644 --- a/drivers/net/wireless/marvell/mwifiex/util.c +++ b/drivers/net/wireless/marvell/mwifiex/util.c @@ -317,10 +317,16 @@ mwifiex_parse_mgmt_packet(struct mwifiex_private *priv, u8 *payload, u16 len, switch (stype) { case IEEE80211_STYPE_ACTION: - category = *(payload + sizeof(struct ieee80211_hdr)); + if (len < sizeof(*ieee_hdr) + 1) + return -1; + + category = *(payload + sizeof(*ieee_hdr)); switch (category) { case WLAN_CATEGORY_PUBLIC: - action_code = *(payload + sizeof(struct ieee80211_hdr) + if (len < sizeof(*ieee_hdr) + 2) + return -1; + + action_code = *(payload + sizeof(*ieee_hdr) + 1); if (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) { addr2 = ieee_hdr->addr2; From 3687d7d48070838cc2953431b3a27717cab0aaf6 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 15 Aug 2026 21:52:27 +0800 Subject: [PATCH 0099/1417] wifi: mwifiex: validate scan response extents mwifiex_ret_802_11_scan() subtracts the fixed response fields and the firmware-provided BSS length from resp->size without first proving that either extent fits. A short response or oversized BSS length can therefore underflow tlv_buf_size and make the TLV parser walk beyond the command response. Compute the fixed extent from the selected normal or background scan response. Validate that the fixed fields and BSS data fit before deriving the TLV extent and entering the parser. Fixes: 5e6e3a92b9a4 ("wireless: mwifiex: initial commit for Marvell mwifiex driver") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260815135227.50392-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/scan.c | 29 ++++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/scan.c b/drivers/net/wireless/marvell/mwifiex/scan.c index 473f4623ea199d..bdd4b8465863bf 100644 --- a/drivers/net/wireless/marvell/mwifiex/scan.c +++ b/drivers/net/wireless/marvell/mwifiex/scan.c @@ -2115,6 +2115,7 @@ int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, u32 bytes_left; u32 idx; u32 tlv_buf_size; + size_t fixed_size; struct mwifiex_ie_types_chan_band_list_param_set *chan_band_tlv; struct chan_band_param_set *chan_band; u8 is_bgscan_resp; @@ -2130,6 +2131,14 @@ int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, else scan_rsp = &resp->params.scan_resp; + scan_resp_size = le16_to_cpu(resp->size); + fixed_size = scan_rsp->bss_desc_and_tlv_buffer - (u8 *)resp; + if (scan_resp_size < fixed_size) { + mwifiex_dbg(adapter, ERROR, + "SCAN_RESP: response is too short\n"); + ret = -1; + goto check_next_scan; + } if (scan_rsp->number_of_sets > MWIFIEX_MAX_AP) { mwifiex_dbg(adapter, ERROR, @@ -2147,8 +2156,6 @@ int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, "info: SCAN_RESP: bss_descript_size %d\n", bytes_left); - scan_resp_size = le16_to_cpu(resp->size); - mwifiex_dbg(adapter, INFO, "info: SCAN_RESP: returned %d APs before parsing\n", scan_rsp->number_of_sets); @@ -2156,15 +2163,17 @@ int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, bss_info = scan_rsp->bss_desc_and_tlv_buffer; /* - * The size of the TLV buffer is equal to the entire command response - * size (scan_resp_size) minus the fixed fields (sizeof()'s), the - * BSS Descriptions (bss_descript_size as bytesLef) and the command - * response header (S_DS_GEN) + * The TLV buffer follows the command-specific fixed fields and the BSS + * descriptions. Background-scan responses have an additional fixed + * field before scan_rsp, which is included in fixed_size. */ - tlv_buf_size = scan_resp_size - (bytes_left - + sizeof(scan_rsp->bss_descript_size) - + sizeof(scan_rsp->number_of_sets) - + S_DS_GEN); + if (bytes_left > scan_resp_size - fixed_size) { + mwifiex_dbg(adapter, ERROR, + "SCAN_RESP: BSS data exceeds response\n"); + ret = -1; + goto check_next_scan; + } + tlv_buf_size = scan_resp_size - fixed_size - bytes_left; tlv_data = (struct mwifiex_ie_types_data *) (scan_rsp-> bss_desc_and_tlv_buffer + From 5ce5721e8cbe3e80db8f43851cc2a2a92485ef4b Mon Sep 17 00:00:00 2001 From: Shmulik Cohen Date: Wed, 12 Aug 2026 22:04:10 +0300 Subject: [PATCH 0100/1417] wifi: libipw: reject too-short beacon and probe responses libipw_process_probe_response() and the libipw_network_init() call it makes assume the frame contains the full 36-byte beacon and probe response prefix, but the ipw2100 and ipw2200 receive paths only establish that a management frame carries the generic 24-byte three-address header. libipw_network_init() then computes the information element length as stats->len - sizeof(*beacon) stats->len is a u16 and sizeof() has type size_t, so the subtraction is evaluated as size_t and wraps instead of going negative. Truncating that to the u16 length parameter of libipw_parse_info_param() yields 65524 for a 24-byte beacon, and the parser then walks the receive buffer as if it held almost 64 KiB of information elements, reading past the allocation. Reject the frame before any fixed field is touched. Found by an AI-assisted review of length arithmetic in management frame parsers. Verified with a KUnit case under Generic KASAN on arm64 under QEMU; I do not have the hardware, so it is not tested on a real device. Fixes: b453872c35cf ("[NET] ieee80211 subsystem") Assisted-by: Claude:claude-opus-5 Signed-off-by: Shmulik Cohen Link: https://patch.msgid.link/20260812190412.18333-2-anuk909@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/libipw_rx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c index c8841f9b9ad918..2661dac6985e48 100644 --- a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c +++ b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c @@ -1421,6 +1421,9 @@ static void libipw_process_probe_response(struct libipw_device #endif unsigned long flags; + if (stats->len < sizeof(*beacon)) + return; + LIBIPW_DEBUG_SCAN("'%*pE' (%pM): %c%c%c%c %c%c%c%c-%c%c%c%c %c%c%c%c\n", info_element->len, info_element->data, beacon->header.addr3, From adb7118b7d2cfd7e8213c17d7d2829f353017754 Mon Sep 17 00:00:00 2001 From: Shmulik Cohen Date: Wed, 12 Aug 2026 22:04:11 +0300 Subject: [PATCH 0101/1417] wifi: libipw: reject too-short association responses libipw_handle_assoc_resp() reads the capability, status and aid fields of the 30-byte association response prefix and then computes the information element length as stats->len - sizeof(*frame) stats->len is a u16 and sizeof() has type size_t, so the subtraction is evaluated as size_t and wraps instead of going negative. Truncating that to the u16 length parameter of libipw_parse_info_param() turns a frame shorter than the fixed fields into a length near 64 KiB, and the parser then reads past the receive buffer. Both the ipw2100 and ipw2200 management receive paths reach this function having established only that the frame carries the generic 24-byte three-address header. Reject the frame before any fixed field is touched. Found by an AI-assisted review of length arithmetic in management frame parsers. Verified with a KUnit case under Generic KASAN on arm64 under QEMU; I do not have the hardware, so it is not tested on a real device. Fixes: 9e8571affd1c ("[PATCH] ieee80211: Add QoS (WME) support to the ieee80211 subsystem") Assisted-by: Claude:claude-opus-5 Signed-off-by: Shmulik Cohen Link: https://patch.msgid.link/20260812190412.18333-3-anuk909@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/libipw_rx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c index 2661dac6985e48..424349a6935e44 100644 --- a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c +++ b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c @@ -1209,6 +1209,9 @@ static int libipw_handle_assoc_resp(struct libipw_device *ieee, struct libipw_as struct libipw_network *network = &network_resp; struct net_device *dev = ieee->dev; + if (stats->len < sizeof(*frame)) + return 1; + network->flags = 0; network->qos_data.active = 0; network->qos_data.supported = 0; From c46cfaf8db42de0806076139fb40744d23041377 Mon Sep 17 00:00:00 2001 From: Shmulik Cohen Date: Wed, 12 Aug 2026 22:04:12 +0300 Subject: [PATCH 0102/1417] wifi: ipw2x00: bound management frame length to the receive buffer Both management receive paths establish a lower bound on the frame length and no upper bound, even though the length originates from the device. ipw2100_corruption_check() returns 0 without inspecting frame_size for management frames, and __ipw2100_rx_process() only rejects a frame smaller than the three-address header, so any reported size up to the u32 limit reaches libipw_rx_mgt() against a receive allocation of IPW_RX_NIC_BUFFER_LENGTH bytes. Check frame_size itself rather than stats.len, which is a u16: a size of 65566 truncates to 30 on assignment and would pass a check made afterwards. ipw_rx() likewise only rejects a frame shorter than the header length. Bound it against the DMA mapped receive buffer. The size passed to alloc_skb() is rounded up by the allocator, so skb_tailroom() can exceed IPW_RX_BUF_SIZE and is not a usable bound here; the existing uses of that idiom in the data paths are too permissive for the same reason. libipw then hands the remainder to libipw_parse_info_param(), which walks information elements for as long as the length allows, so an over-long reported length reads past the receive buffer without any wraparound being involved. The length is device-reported, so per Documentation/process/threat-model.rst this is a robustness fix rather than a vulnerability. Found by an AI-assisted review of length arithmetic in management frame parsers. Compile-tested only for these two hunks; I do not have the hardware, so they are not tested on a real device. Assisted-by: Claude:claude-opus-5 Signed-off-by: Shmulik Cohen Link: https://patch.msgid.link/20260812190412.18333-4-anuk909@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/ipw2100.c | 4 +++- drivers/net/wireless/intel/ipw2x00/ipw2200.c | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/ipw2x00/ipw2100.c b/drivers/net/wireless/intel/ipw2x00/ipw2100.c index 2b8a23865bfb29..43b4e432956beb 100644 --- a/drivers/net/wireless/intel/ipw2x00/ipw2100.c +++ b/drivers/net/wireless/intel/ipw2x00/ipw2100.c @@ -2712,7 +2712,9 @@ static void __ipw2100_rx_process(struct ipw2100_priv *priv) break; } #endif - if (stats.len < sizeof(struct libipw_hdr_3addr)) + if (sq->drv[i].frame_size < + sizeof(struct libipw_hdr_3addr) || + sq->drv[i].frame_size > IPW_RX_NIC_BUFFER_LENGTH) break; switch (WLAN_FC_GET_TYPE(le16_to_cpu(u->rx_data.header.frame_ctl))) { case IEEE80211_FTYPE_MGMT: diff --git a/drivers/net/wireless/intel/ipw2x00/ipw2200.c b/drivers/net/wireless/intel/ipw2x00/ipw2200.c index 4bc9bb406e8e85..8249d493ee22f4 100644 --- a/drivers/net/wireless/intel/ipw2x00/ipw2200.c +++ b/drivers/net/wireless/intel/ipw2x00/ipw2200.c @@ -8322,6 +8322,15 @@ static void ipw_rx(struct ipw_priv *priv) break; } + if (unlikely(le16_to_cpu(pkt->u.frame.length) > + IPW_RX_BUF_SIZE - + IPW_RX_FRAME_SIZE)) { + IPW_DEBUG_DROP("Received oversized packet. Dropping.\n"); + priv->net_dev->stats.rx_errors++; + priv->wstats.discard.misc++; + break; + } + switch (WLAN_FC_GET_TYPE (le16_to_cpu(header->frame_ctl))) { From ce858fa6b8a214dee5adb82358885fa024cdd887 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Mon, 31 Aug 2026 02:42:12 +0800 Subject: [PATCH 0103/1417] wifi: p54: validate curve data length in the calibration curve converters p54_convert_rev0() and p54_convert_rev1() read calibration curve data from the device-supplied EEPROM entry using channel and points-per-channel counts taken verbatim from that same entry, so an entry that declares more data than it carries drives an out-of-bounds read past the EEPROM buffer (verified with a KASAN reproducer of the conversion loop). The sibling converters p54_convert_output_limits() and p54_convert_db() already validate their counts against the entry length; this path was missed. Reject the entry when the counts do not fit in the entry data. Fixes: eff1a59c48e3 ("[P54]: add mac80211-based driver for prism54 softmac hardware") Cc: stable@vger.kernel.org Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Link: https://patch.msgid.link/20260831-p54-pda-validation-v2-1-dae566b388c8@cherr.cc Signed-off-by: Johannes Berg --- drivers/net/wireless/intersil/p54/eeprom.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intersil/p54/eeprom.c b/drivers/net/wireless/intersil/p54/eeprom.c index 95580921d93382..0dc848d77c5e95 100644 --- a/drivers/net/wireless/intersil/p54/eeprom.c +++ b/drivers/net/wireless/intersil/p54/eeprom.c @@ -414,17 +414,22 @@ static int p54_generate_channel_lists(struct ieee80211_hw *dev) } static int p54_convert_rev0(struct ieee80211_hw *dev, - struct pda_pa_curve_data *curve_data) + struct pda_pa_curve_data *curve_data, size_t len) { struct p54_common *priv = dev->priv; struct p54_pa_curve_data_sample *dst; struct pda_pa_curve_data_sample_rev0 *src; + size_t needed = curve_data->channels * + (sizeof(*src) * curve_data->points_per_channel + 2); size_t cd_len = sizeof(*curve_data) + (curve_data->points_per_channel*sizeof(*dst) + 2) * curve_data->channels; unsigned int i, j; void *source, *target; + if (len < sizeof(*curve_data) + needed) + return -EINVAL; + priv->curve_data = kmalloc(sizeof(*priv->curve_data) + cd_len, GFP_KERNEL); if (!priv->curve_data) @@ -466,17 +471,22 @@ static int p54_convert_rev0(struct ieee80211_hw *dev, } static int p54_convert_rev1(struct ieee80211_hw *dev, - struct pda_pa_curve_data *curve_data) + struct pda_pa_curve_data *curve_data, size_t len) { struct p54_common *priv = dev->priv; struct p54_pa_curve_data_sample *dst; struct pda_pa_curve_data_sample_rev1 *src; + size_t needed = curve_data->channels * + (sizeof(*src) * curve_data->points_per_channel + 3); size_t cd_len = sizeof(*curve_data) + (curve_data->points_per_channel*sizeof(*dst) + 2) * curve_data->channels; unsigned int i, j; void *source, *target; + if (len < sizeof(*curve_data) + needed) + return -EINVAL; + priv->curve_data = kzalloc(cd_len + sizeof(*priv->curve_data), GFP_KERNEL); if (!priv->curve_data) @@ -763,6 +773,7 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) case PDR_PRISM_PA_CAL_CURVE_DATA: { struct pda_pa_curve_data *curve_data = (struct pda_pa_curve_data *)entry->data; + if (data_len < sizeof(*curve_data)) { err = -EINVAL; goto err; @@ -770,10 +781,10 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) switch (curve_data->cal_method_rev) { case 0: - err = p54_convert_rev0(dev, curve_data); + err = p54_convert_rev0(dev, curve_data, data_len); break; case 1: - err = p54_convert_rev1(dev, curve_data); + err = p54_convert_rev1(dev, curve_data, data_len); break; default: wiphy_err(dev->wiphy, From d8efd84f49379ed28624098821f80e992657d935 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Mon, 31 Aug 2026 02:42:13 +0800 Subject: [PATCH 0104/1417] wifi: p54: require a full exp_if record in PDR_INTERFACE_LIST The PDR_INTERFACE_LIST loop only checks that the record start is within the entry before reading an entire struct exp_if from it. A truncated trailing record makes the if_id/variant reads cross the entry boundary into the heap beyond the EEPROM buffer (verified with a KASAN reproducer of the loop). The variant also feeds the synth front-end selection, so this is not only a leak. Advance only while a full record still fits in the entry. Fixes: eff1a59c48e3 ("[P54]: add mac80211-based driver for prism54 softmac hardware") Cc: stable@vger.kernel.org Acked-by: Christian Lamparter Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Link: https://patch.msgid.link/20260831-p54-pda-validation-v2-2-dae566b388c8@cherr.cc Signed-off-by: Johannes Berg --- drivers/net/wireless/intersil/p54/eeprom.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intersil/p54/eeprom.c b/drivers/net/wireless/intersil/p54/eeprom.c index 0dc848d77c5e95..0475222d54fc1b 100644 --- a/drivers/net/wireless/intersil/p54/eeprom.c +++ b/drivers/net/wireless/intersil/p54/eeprom.c @@ -812,7 +812,8 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) break; case PDR_INTERFACE_LIST: tmp = entry->data; - while ((u8 *)tmp < entry->data + data_len) { + while ((u8 *)tmp + sizeof(struct exp_if) <= + entry->data + data_len) { struct exp_if *exp_if = tmp; if (exp_if->if_id == cpu_to_le16(IF_ID_ISL39000)) synth = le16_to_cpu(exp_if->variant); From 6feadbecdae60a6324c967f3b1493741083793a3 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 6 Aug 2026 13:02:33 +0000 Subject: [PATCH 0105/1417] mmc: core: Cancel SDIO IRQ work before freeing host A host controller that uses sdio_signal_irq() schedules host->sdio_irq_work from its interrupt handler. That work is only cancelled on the suspend path (mmc_sdio_suspend()), not on the remove/free path, so a worker armed just before the controller freed its IRQ can run after mmc_host_classdev_release() has freed the host and dereference it through container_of(). Cancel host->sdio_irq_work in mmc_free_host(), like the existing host->detect drain added by commit 1036f69e2513 ("mmc: core: Cancel delayed work before releasing host"). This issue was found by an in-house static analysis tool. Fixes: 682696605c70 ("mmc: sdio: Add API to manage SDIO IRQs from a workqueue") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Ulf Hansson --- drivers/mmc/core/host.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index b7ce3137d4529d..828078b3f9622d 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -698,6 +698,7 @@ EXPORT_SYMBOL(mmc_remove_host); void mmc_free_host(struct mmc_host *host) { cancel_delayed_work_sync(&host->detect); + cancel_work_sync(&host->sdio_irq_work); mmc_pwrseq_free(host); put_device(&host->class_dev); } From 2b19cf3e50cddaff07b657dae1a8f30f06032852 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 14 Aug 2026 08:25:50 +0000 Subject: [PATCH 0106/1417] mmc: mmci: Fix use-after-free in busy-timeout work ux500_busy_complete() can queue ux500_busy_timeout_work for an R1b command, but mmci_remove() never cancels it. The work can subsequently dereference the devm-allocated mmci_host after it has been released. Mask the controller interrupts and disable the delayed work during removal. This drains any queued instance and stops an IRQ handler that is still in progress from queueing the work again once it has been disabled. This issue was found by an in-house static analysis tool. Fixes: b1a665932dc2 ("mmc: mmci: Add support for SW busy-end timeouts") Cc: stable@vger.kernel.org # v6.10+ Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/host/mmci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index e500051bd572f7..416bdb184ed4c7 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -2511,6 +2511,9 @@ static void mmci_remove(struct amba_device *dev) writel(0, host->base + MMCICOMMAND); writel(0, host->base + MMCIDATACTRL); + if (variant->busy_detect) + disable_delayed_work_sync(&host->ux500_busy_timeout_work); + mmci_dma_release(host); clk_disable_unprepare(host->clk); } From d3a421c82412344022982d5b91ba23194a0a6f29 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 7 Aug 2026 03:26:54 +0000 Subject: [PATCH 0107/1417] mmc: mxcmmc: cancel data work and watchdog on remove mxcmci_remove() frees the host through the devm tail, but neither it nor mmc_remove_host() drains the driver's own asynchronous state. host->watchdog, a 10 s timer armed on the DMA path in mxcmci_setup_data(), is deleted only by the DMA- and IRQ-complete paths, which the remove path does not explicitly drain; it can therefore fire after the host is freed and dereference it in mxcmci_watchdog(). host->datawork, armed from the IRQ handler on the PIO path, is not cancelled by the remove path either. Free the devm-registered IRQ, then cancel datawork and delete the watchdog in mxcmci_remove(), before dma_release_channel(). Freeing the IRQ first keeps a trailing handler from re-arming datawork between the cancel and the host free. Both callbacks are non-self-rearming. This issue was found by an in-house static analysis tool. Fixes: f6ad0a481342 ("mmc: mxcmmc: fix bug that may block a data transfer forever") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Ulf Hansson --- drivers/mmc/host/mxcmmc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mmc/host/mxcmmc.c b/drivers/mmc/host/mxcmmc.c index c405cfb8b269bc..097498a3f8ff4c 100644 --- a/drivers/mmc/host/mxcmmc.c +++ b/drivers/mmc/host/mxcmmc.c @@ -1173,6 +1173,10 @@ static void mxcmci_remove(struct platform_device *pdev) mmc_remove_host(mmc); + devm_free_irq(&pdev->dev, platform_get_irq(pdev, 0), host); + cancel_work_sync(&host->datawork); + timer_delete_sync(&host->watchdog); + if (host->pdata && host->pdata->exit) host->pdata->exit(&pdev->dev, mmc); From 5d132990475f02cfa1debe03d50b479432864ebd Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 14 Aug 2026 08:23:54 +0000 Subject: [PATCH 0108/1417] mmc: hsq: Fix use-after-free in retry work mmc_hsq_pump_requests() queues retry_work when request_atomic() returns -EBUSY; today sdhci-sprd is the only consumer that implements request_atomic(). The work is embedded in a devm-allocated mmc_hsq, but is never cancelled during driver removal. Work still pending at unbind can therefore run after the devm allocation has been released and dereference hsq->mmc and hsq->mrq. Use devm_work_autocancel() to cancel and drain retry_work before the devm allocation is released. By the time devres cleanup begins, mmc_remove_host() has already stopped the host, so no new requests can arm the work. This issue was found by an in-house static analysis tool. Fixes: 6db96e5810e0 ("mmc: host: Introduce the request_atomic() for the host") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Ulf Hansson --- drivers/mmc/host/mmc_hsq.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/mmc_hsq.c b/drivers/mmc/host/mmc_hsq.c index 79836705c17623..57e172bd348718 100644 --- a/drivers/mmc/host/mmc_hsq.c +++ b/drivers/mmc/host/mmc_hsq.c @@ -7,6 +7,7 @@ * Author: Baolin Wang */ +#include #include #include #include @@ -345,6 +346,7 @@ static const struct mmc_cqe_ops mmc_hsq_ops = { int mmc_hsq_init(struct mmc_hsq *hsq, struct mmc_host *mmc) { + int ret; int i; hsq->num_slots = HSQ_NUM_SLOTS; hsq->next_tag = HSQ_INVALID_TAG; @@ -363,7 +365,11 @@ int mmc_hsq_init(struct mmc_hsq *hsq, struct mmc_host *mmc) for (i = 0; i < HSQ_NUM_SLOTS; i++) hsq->tag_slot[i] = HSQ_INVALID_TAG; - INIT_WORK(&hsq->retry_work, mmc_hsq_retry_handler); + ret = devm_work_autocancel(mmc_dev(mmc), &hsq->retry_work, + mmc_hsq_retry_handler); + if (ret) + return ret; + spin_lock_init(&hsq->lock); init_waitqueue_head(&hsq->wait_queue); From ff894dced1a7ad7523f9c65dbdb53d02474cca0f Mon Sep 17 00:00:00 2001 From: "Diogo Ivo (Schneider Electric)" Date: Fri, 7 Aug 2026 13:06:57 +0200 Subject: [PATCH 0109/1417] mmc: sdhci_am654: Move tuning_loop to local variable The tuning_loop field in struct sdhci_am654_data is only used within sdhci_am654_platform_execute_tuning() as a loop counter that is initialized to 0 in sdhci_am654_init(). Since it shouldn't persist across function calls, otherwise every failure expends its "budget", move it to a local variable and remove the struct field along with the now-unnecessary initialization. Signed-off-by: Diogo Ivo (Schneider Electric) Reviewed-by: Judith Mendez Acked-by: Adrian Hunter Fixes: de31f6ab68a3 ("mmc: sdhci_am654: Reset Command and Data line after tuning") Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 2a27db2f558bb7..4b74a4115509c7 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -151,7 +151,6 @@ struct sdhci_am654_data { u32 flags; u32 quirks; bool dll_enable; - u32 tuning_loop; #define SDHCI_AM654_QUIRK_FORCE_CDTEST BIT(0) #define SDHCI_AM654_QUIRK_SUPPRESS_V1P8_ENA BIT(1) @@ -576,13 +575,14 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host); unsigned char timing = host->mmc->ios.timing; struct device *dev = mmc_dev(host->mmc); + unsigned int tuning_loop = 0; int itapdly; do { itapdly = sdhci_am654_do_tuning(host, opcode); if (itapdly >= 0) break; - } while (++sdhci_am654->tuning_loop < RETRY_TUNING_MAX); + } while (++tuning_loop < RETRY_TUNING_MAX); if (itapdly < 0) { dev_err(dev, "Failed to find itapdly, fail tuning\n"); @@ -806,9 +806,6 @@ static int sdhci_am654_init(struct sdhci_host *host) regmap_update_bits(sdhci_am654->base, CTL_CFG_3, TUNINGFORSDR50_MASK, TUNINGFORSDR50_MASK); - /* Use to re-execute tuning */ - sdhci_am654->tuning_loop = 0; - ret = sdhci_setup_host(host); if (ret) return ret; From 7197d9107d9545730153b82ea5a411c5208b443f Mon Sep 17 00:00:00 2001 From: "Diogo Ivo (Schneider Electric)" Date: Fri, 7 Aug 2026 13:06:58 +0200 Subject: [PATCH 0110/1417] mmc: sdhci_am654: Reset command and data lines on failed tuning The CMD/DATA reset after tuning should be performed regardless of whether tuning succeeded or failed, since tuning data may remain in the buffer in either case. Move the error return after the reset so that the controller is always cleaned up. Fixes: de31f6ab68a3 ("mmc: sdhci_am654: Reset Command and Data line after tuning") Cc: stable@vger.kernel.org Signed-off-by: Diogo Ivo (Schneider Electric) Reviewed-by: Judith Mendez Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 4b74a4115509c7..6abe206ed29250 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -442,15 +442,13 @@ static int sdhci_am654_execute_tuning(struct mmc_host *mmc, u32 opcode) struct sdhci_host *host = mmc_priv(mmc); int err = sdhci_execute_tuning(mmc, opcode); - if (err) - return err; /* * Tuning data remains in the buffer after tuning. * Do a command and data reset to get rid of it */ sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); - return 0; + return err; } static u32 sdhci_am654_cqhci_irq(struct sdhci_host *host, u32 intmask) From c9f47cc8c37f7659897142ffe216c250fbc1d4ed Mon Sep 17 00:00:00 2001 From: "Diogo Ivo (Schneider Electric)" Date: Fri, 7 Aug 2026 13:06:59 +0200 Subject: [PATCH 0111/1417] mmc: sdhci_am654: Clear ITAPDLY on tuning failure When tuning fails, stale ITAPDLY values can persist and interfere with subsequent I/O accesses, for example in DDR50 mode in cards with no tuning support. Move the ITAPDLY enable setting out of the tuning loop to after successful tuning, and explicitly clear ITAPDLY (delay and enable) when tuning fails so that we are sure only working values are actually left in hardware. Fixes: 901d16e46296 ("mmc: sdhci_am654: Add retry tuning") Cc: stable@vger.kernel.org Signed-off-by: Diogo Ivo (Schneider Electric) Reviewed-by: Judith Mendez Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 6abe206ed29250..49a46583c8bc47 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -527,7 +527,6 @@ static int sdhci_am654_do_tuning(struct sdhci_host *host, { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host); - unsigned char timing = host->mmc->ios.timing; struct window fail_window[ITAPDLY_LENGTH]; struct device *dev = mmc_dev(host->mmc); u8 curr_pass, itap; @@ -536,11 +535,8 @@ static int sdhci_am654_do_tuning(struct sdhci_host *host, memset(fail_window, 0, sizeof(fail_window)); - /* Enable ITAPDLY */ - sdhci_am654->itap_del_ena[timing] = 0x1; - for (itap = 0; itap < ITAPDLY_LENGTH; itap++) { - sdhci_am654_write_itapdly(sdhci_am654, itap, sdhci_am654->itap_del_ena[timing]); + sdhci_am654_write_itapdly(sdhci_am654, itap, 0x1); curr_pass = !mmc_send_tuning(host->mmc, opcode, NULL); @@ -584,10 +580,16 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, if (itapdly < 0) { dev_err(dev, "Failed to find itapdly, fail tuning\n"); + sdhci_am654_write_itapdly(sdhci_am654, 0, 0); + sdhci_am654->itap_del_ena[timing] = 0; + sdhci_am654->itap_del_sel[timing] = 0; return -1; } dev_dbg(dev, "Passed tuning, final itapdly=%d\n", itapdly); + + /* Enable ITAPDLY */ + sdhci_am654->itap_del_ena[timing] = 0x1; sdhci_am654_write_itapdly(sdhci_am654, itapdly, sdhci_am654->itap_del_ena[timing]); /* Save ITAPDLY */ sdhci_am654->itap_del_sel[timing] = itapdly; From 308d05225281d86150d88141990d6caf8c902349 Mon Sep 17 00:00:00 2001 From: "Diogo Ivo (Schneider Electric)" Date: Fri, 7 Aug 2026 13:07:00 +0200 Subject: [PATCH 0112/1417] mmc: sdhci_am654: Fallback to DT-provided itap delay on DDR50 tuning failure DDR50 mode is not required to support the tuning command CMD19, meaning that calibration may fail on cards that do not implement it, in which case a known-good itap delay value should be programmed into the host controller. Do this by reading the (already defined) itap delay DT property for DDR50 and, if tuning fails for this mode, fall back to the DT-provided itap delay value. If the DT does not provide a value for DDR50 fallback then this simply disables using itapdly. Fixes: 901d16e46296 ("mmc: sdhci_am654: Add retry tuning") Cc: stable@vger.kernel.org Signed-off-by: Diogo Ivo (Schneider Electric) Acked-by: Adrian Hunter Reviewed-by: Judith Mendez Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 49a46583c8bc47..2e332f52c3017d 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -126,7 +126,7 @@ static const struct timing_data td[] = { NULL, MMC_CAP_UHS_SDR104}, [MMC_TIMING_UHS_DDR50] = {"ti,otap-del-sel-ddr50", - NULL, + "ti,itap-del-sel-ddr50", MMC_CAP_UHS_DDR50}, [MMC_TIMING_MMC_DDR52] = {"ti,otap-del-sel-ddr52", "ti,itap-del-sel-ddr52", @@ -144,6 +144,8 @@ struct sdhci_am654_data { u32 otap_del_sel[ARRAY_SIZE(td)]; u32 itap_del_sel[ARRAY_SIZE(td)]; u32 itap_del_ena[ARRAY_SIZE(td)]; + u32 itap_del_sel_dt_ddr50; + u32 itap_del_ena_dt_ddr50; int clkbuf_sel; int trm_icp; int drv_strength; @@ -579,10 +581,19 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, } while (++tuning_loop < RETRY_TUNING_MAX); if (itapdly < 0) { - dev_err(dev, "Failed to find itapdly, fail tuning\n"); - sdhci_am654_write_itapdly(sdhci_am654, 0, 0); - sdhci_am654->itap_del_ena[timing] = 0; - sdhci_am654->itap_del_sel[timing] = 0; + if (timing == MMC_TIMING_UHS_DDR50) { + dev_dbg(dev, "Failed DDR50 tuning, fallback to DT ITAP\n"); + sdhci_am654->itap_del_sel[timing] = sdhci_am654->itap_del_sel_dt_ddr50; + sdhci_am654->itap_del_ena[timing] = sdhci_am654->itap_del_ena_dt_ddr50; + } else { + dev_err(dev, "Failed to find itapdly, fail tuning\n"); + sdhci_am654->itap_del_ena[timing] = 0; + sdhci_am654->itap_del_sel[timing] = 0; + } + + sdhci_am654_write_itapdly(sdhci_am654, + sdhci_am654->itap_del_sel[timing], + sdhci_am654->itap_del_ena[timing]); return -1; } @@ -758,6 +769,11 @@ static int sdhci_am654_get_otap_delay(struct sdhci_host *host, } } + sdhci_am654->itap_del_sel_dt_ddr50 = + sdhci_am654->itap_del_sel[MMC_TIMING_UHS_DDR50]; + sdhci_am654->itap_del_ena_dt_ddr50 = + sdhci_am654->itap_del_ena[MMC_TIMING_UHS_DDR50]; + return 0; } From da2ca406f45a6e21760243152ed8d2e8e72915c2 Mon Sep 17 00:00:00 2001 From: Rik van Riel Date: Sat, 8 Aug 2026 10:47:55 -0400 Subject: [PATCH 0113/1417] wifi: mac80211: avoid WARN in set_bitrate_mask when sdata not in driver ieee80211_set_bitrate_mask() checks if the interface is running via ieee80211_sdata_running(), but it does not check if the interface is still present in the driver. When sdata is running but IEEE80211_SDATA_IN_DRIVER is not set, the call reaches drv_set_bitrate_mask() in driver-ops.h which hits wlan1: Failed check-sdata-in-driver check, flags: 0x0 WARNING: net/mac80211/driver-ops.h:884 at drv_set_bitrate_mask Syzkaller triggers this via wext SIOCSIWRATE ioctl. The Call Trace shows wext_ioctl_dispatch() in wext-core.c dispatching the ioctl, calling ioctl_standard_call() for SIOCSIWRATE, which calls cfg80211_wext_siwrate() in wext-compat.c. That builds a bitrate mask and calls rdev_set_bitrate_mask() which ends up in ieee80211_set_bitrate_mask() in cfg.c. The interface is marked running via SDATA_STATE_RUNNING but flags is 0, so check_sdata_in_driver() fails. When the interface is being torn down, or when wext ioctl is issued during interface bringup before drv_add_interface() sets IN_DRIVER, the running check passes while IN_DRIVER is clear. Check IEEE80211_SDATA_IN_DRIVER in ieee80211_set_bitrate_mask() before calling the driver, returning -ENETDOWN. This avoids the WARN_ONCE in driver-ops.h and matches other cfg.c operations that bail early when not in driver. This change should be safe because wiphy mutex is held in cfg80211_wext_siwrate() via guard(wiphy), and IN_DRIVER is set/cleared under RTNL and wiphy paths in drv_add_interface() and drv_remove_interface() in driver-ops.c, so the check is race-free against driver add/remove. Returning -ENETDOWN is the same error other not-running paths use and does not introduce new locking. Reported-by: syzbot+af177aa139efdd13a9da@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=af177aa139efdd13a9da Link: https://lore.kernel.org/all/6a75205c.59b6c763.2bba34.00c3.GAE@google.com/ Fixes: 554a43d5e77e ("mac80211: check sdata_running on ieee80211_set_bitrate_mask") Cc: stable@vger.kernel.org Assisted-by: Hermes:muse-spark-1.2 syzkaller Signed-off-by: Rik van Riel Link: https://patch.msgid.link/20260808104755.319c686e@fangorn Reported-by: syzbot+dcaca020ca8377e7ced0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=dcaca020ca8377e7ced0 [also add second syzbot report] Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 23f4f9ec86d0ae..1f074799f85ffe 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -4113,6 +4113,9 @@ static int ieee80211_set_bitrate_mask(struct wiphy *wiphy, if (!ieee80211_sdata_running(sdata)) return -ENETDOWN; + if (!(sdata->flags & IEEE80211_SDATA_IN_DRIVER)) + return -ENETDOWN; + /* * If active validate the setting and reject it if it doesn't leave * at least one basic rate usable, since we really have to be able From 44caf1844a258534e891d2c4071b011097e19235 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:07:31 +0530 Subject: [PATCH 0114/1417] firmware: arm_scmi: Fix typo "upto" in comment Correct "upto" to "up to", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260904103732.7320-1-hemanth.selam@gmail.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index ef29fd223287dd..fb45b8e6459d6f 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -477,7 +477,7 @@ void *scmi_notification_instance_data_get(const struct scmi_handle *handle) * - exactly 'next_token' may be NOT available so pick xfer_id >= next_token * using find_next_zero_bit() starting from candidate next_token bit * - * - all tokens ahead upto (MSG_TOKEN_ID_MASK - 1) are used in-flight but we + * - all tokens ahead up to (MSG_TOKEN_ID_MASK - 1) are used in-flight but we * are plenty of free tokens at start, so try a second pass using * find_next_zero_bit() and starting from 0. * From f76017a7663c4ce5e379f8a8d39f032bdb1fd865 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 14 Apr 2026 07:18:05 -0700 Subject: [PATCH 0115/1417] nfsd: fix handling of NFSEXP_PNFS in the netlink codepath The rework of how block layouts were checked moved the check for NFSEXP_PNFS out of nfsd4_setup_layout_type() and into the callers. That patch didn't account for the new call in nfsd4_setup_layout_type(). Cc: Christoph Hellwig Fixes: da9baa5470dc ("exportfs,nfsd: rework checking for layout-based block device access support") Cc: stable@vger.kernel.org Reported-by: Olga Kornievskaia Signed-off-by: Jeff Layton Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260414-pnfs-exp-fix-v1-1-9face14c16c2@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/export.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index a47c90f40422b7..1f678583f61282 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -1006,7 +1006,8 @@ static int nfsd_nl_parse_one_export(struct cache_detail *cd, goto out_uuid; err = 0; - nfsd4_setup_layout_type(&exp); + if (exp.ex_flags & NFSEXP_PNFS) + nfsd4_setup_layout_type(&exp); } expp = svc_export_lookup(&exp); From e384abeb559d10d6505aec053ede9368d81d4c71 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 27 Aug 2026 20:55:57 +0100 Subject: [PATCH 0116/1417] mm/huge_memory: bypass THP tuneables for huge pfnmap mappings The sysfs THP tuneables at /sys/kernel/mm/transparent_huge_pages/ rather confusingly only control the behaviour of THP in some instances. They are not applicable to MADV_COLLAPSE operations, nor to DAX mappings. Long-term, THP is predicated upon compaction being able to obtain large folios to populate THP ranges. However, vm_normal_folio() returns NULL for PFN map mappings, thus their reference count is maintained by the driver, not core mm. As a consequence, the folios are not subject to reclaim nor compaction, so are not truly part of the THP mechanism at all. However, since commit 5dd40721f147 ("mm: allow THP orders for PFNMAPs") introduced the ability to establish huge PFN maps, they have been subject to THP tuneables. This is incorrect - if a huge PFN map is available (defined by vma->vm_ops->huge_fault being non-NULL for a VMA_PFNMAP_BIT VMA), then it should be mapped huge upon fault-in. Correct this by explicitly checking for this while ensuring that smaps continues to accurately report THPeligible statistics. While here, abstract the entire file-backed THP check in vma_can_map_huge_file(), with sensible separation of logic into helper functions. Note that drm_gem_shmem_mmap() and panthor_gem_mmap() establish huge PFN maps of shmem folios, however they are marked unevictable in drm_gem_get_pages(), and in any case would fail the reference check in __remove_mapping() even if they weren't. Failing to map huge PFN maps has resulted in significant real-world performance degradation, see links for details. [ziy@nvidia.com: rename some functions] Link: https://lore.kernel.org/DL1HIHWYJ7TB.1CY76SJS0V03L@nvidia.com Link: https://lore.kernel.org/20260827-hugepfn-allowable-orders-v1-1-94819c8807c8@kernel.org Fixes: 5dd40721f147 ("mm: allow THP orders for PFNMAPs") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Zi Yan Reported-by: Cedric Le Goater Closes: https://lore.kernel.org/linux-mm/20260805055544.1568534-1-clg@redhat.com/ Reported-by: Saravanan D Closes: https://lore.kernel.org/linux-mm/20260821070520.25759-1-saravanand@crusoe.ai/ Reviewed-by: Zi Yan Tested-by: Saravanan D Tested-by: Lance Yang Reviewed-by: SJ Park Reviewed-by: Baolin Wang Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Jason Gunthorpe Cc: Liam R. Howlett Cc: Peter Xu Cc: Ryan Roberts Cc: Signed-off-by: Andrew Morton --- mm/huge_memory.c | 86 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index afbb5974bd225a..1e5d68acf62a52 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -92,7 +92,7 @@ unsigned long huge_anon_orders_madvise __read_mostly; unsigned long huge_anon_orders_inherit __read_mostly; static bool anon_orders_configured __initdata; -static inline bool file_thp_enabled(struct vm_area_struct *vma) +static inline bool file_thp_enabled(const struct vm_area_struct *vma) { struct inode *inode; @@ -118,6 +118,67 @@ static bool vma_is_special_huge(const struct vm_area_struct *vma) return vma_test_any(vma, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT); } +static bool vma_file_bypass_thp_tuneables(const struct vm_area_struct *vma, + enum tva_type type) +{ + const bool has_huge_fault = vma->vm_ops->huge_fault; + + /* MADV_COLLAPSE ignores tuneables. */ + if (type == TVA_FORCED_COLLAPSE) + return true; + /* Huge PFN mappings are uncompactable so the policy doesn't apply. */ + if (vma_test(vma, VMA_PFNMAP_BIT) && has_huge_fault) + return true; + return false; +} + +static bool vma_file_allow_thp_tuneables(vm_flags_t vm_flags) +{ + /* THP=always? */ + if (hugepage_global_always()) + return true; + /* THP=madvise and marked MADV_HUGEPAGE? */ + if (hugepage_global_enabled() && (vm_flags & VM_HUGEPAGE)) + return true; + return false; +} + +static bool vma_file_check_thp_tuneables(const struct vm_area_struct *vma, + vm_flags_t vm_flags, enum tva_type type) +{ + return vma_file_bypass_thp_tuneables(vma, type) || + vma_file_allow_thp_tuneables(vm_flags); +} + +static bool vma_can_map_huge_file(const struct vm_area_struct *vma, + vm_flags_t vm_flags, enum tva_type type) +{ + const bool has_huge_fault = vma->vm_ops->huge_fault; + + /* + * Enforce THP collapse requirements as necessary. Anonymous vmas + * were already handled in thp_vma_allowable_orders(). + */ + if (!vma_file_check_thp_tuneables(vma, vm_flags, type)) + return false; + + switch (type) { + case TVA_PAGEFAULT: + /* + * Trust that ->huge_fault() handlers know what they are doing + * in fault path. + */ + return has_huge_fault; + case TVA_SMAPS: + if (has_huge_fault) + return true; + fallthrough; + default: + /* Only regular file is valid in collapse path. */ + return file_thp_enabled(vma); + } +} + unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma, vm_flags_t vm_flags, enum tva_type type, @@ -190,27 +251,8 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma, vma, vma_start_pgoff(vma), 0, forced_collapse); - if (!vma_is_anonymous(vma)) { - /* - * Enforce THP collapse requirements as necessary. Anonymous vmas - * were already handled in thp_vma_allowable_orders(). - */ - if (!forced_collapse && - (!hugepage_global_enabled() || (!(vm_flags & VM_HUGEPAGE) && - !hugepage_global_always()))) - return 0; - - /* - * Trust that ->huge_fault() handlers know what they are doing - * in fault path. - */ - if (((in_pf || smaps)) && vma->vm_ops->huge_fault) - return orders; - /* Only regular file is valid in collapse path */ - if (((!in_pf || smaps)) && file_thp_enabled(vma)) - return orders; - return 0; - } + if (!vma_is_anonymous(vma)) + return vma_can_map_huge_file(vma, vm_flags, type) ? orders : 0; if (vma_is_temporary_stack(vma)) return 0; From 397432cab17bccb600fd6c16ed593f1149042268 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Fri, 28 Aug 2026 12:20:37 +0100 Subject: [PATCH 0117/1417] mm/mremap: account mm->locked_vm correctly for MREMAP_DONTUNMAP When a VMA is mremap()'d with MREMAP_DONTUNMAP set, that results in the VMA being copied, but the source VMA not being unmapped. If the VMA is mlock()'d this is a legal operation, though the source VMA has its VMA_LOCKED_BIT cleared. However this is done in dontunmap_complete(), after mm->locked_vm was incremented via vrm_stat_account(), resulting in double-counting. Worse, this is not even corrected when source VMA is unmapped, due to the VMA_LOCKED_BIT flag having been cleared. This all works fine in the usual mremap() case (without MREMAP_DONTUNMAP), as the source VMA is unmapped with VMA_LOCKED_BIT intact, at which time mm->locked_vm is decremented accordingly. Resolve the issue by invoking vrm_stat_account() only after dontunmap_complete() has run. Note that MREMAP_DONTUNMAP requires old_len == new_len, so no need to account for a delta in size in this case. The bug was introduced by commit b714ccb02a76 ("mm/mremap: complete refactor of move_vma()") which incorrectly reordered the accounting and the clearing of the VMA_LOCKED_BIT flag. Link: https://lore.kernel.org/20260828-mremap-fix-locked-vm-v1-1-c80be7505d1e@kernel.org Fixes: b714ccb02a76 ("mm/mremap: complete refactor of move_vma()") Signed-off-by: Lorenzo Stoakes (ARM) Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260825-fix-mremap-dontunmap-pgoff-v1-1-39a40b2c98b3@kernel.org Reported-by: Kunwu Chan Closes: https://lore.kernel.org/all/20260828094823.594279-1-kunwu.chan@linux.dev/ Acked-by: Vlastimil Babka (SUSE) Tested-by: Kunwu Chan Reviewed-by: Kunwu Chan Cc: Jann Horn Cc: Liam R. Howlett Cc: Pedro Falcato Cc: Signed-off-by: Andrew Morton --- mm/mremap.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mm/mremap.c b/mm/mremap.c index 2b4b523a86b87c..7c368440fafe24 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -1355,12 +1355,11 @@ static void dontunmap_complete(struct vma_remap_struct *vrm, if (vma_is_anonymous(vma) && !vma->vm_file) vma_set_pgoff(vma, pgoff_unfaulted); } - - /* Because we won't unmap we don't need to touch locked_vm. */ } static unsigned long move_vma(struct vma_remap_struct *vrm) { + const bool is_dontunmap = vrm->flags & MREMAP_DONTUNMAP; struct mm_struct *mm = current->mm; struct vm_area_struct *new_vma; unsigned long hiwater_vm; @@ -1401,10 +1400,10 @@ static unsigned long move_vma(struct vma_remap_struct *vrm) */ hiwater_vm = mm->hiwater_vm; - vrm_stat_account(vrm, vrm->new_len); - if (unlikely(!err && (vrm->flags & MREMAP_DONTUNMAP))) + if (unlikely(is_dontunmap && !err)) dontunmap_complete(vrm, new_vma); - else + vrm_stat_account(vrm, vrm->new_len); + if (!is_dontunmap || err) unmap_source_vma(vrm); mm->hiwater_vm = hiwater_vm; From e1d56f046507befa20a5e0837d8075abdf5848fd Mon Sep 17 00:00:00 2001 From: Coiby Xu Date: Fri, 28 Aug 2026 16:41:06 +0800 Subject: [PATCH 0118/1417] mailmap: map Coiby Xu's address Point to my gmail address as I've left Red Hat. Link: https://lore.kernel.org/20260828084106.1494733-1-coiby.xu@gmail.com Signed-off-by: Coiby Xu Signed-off-by: Andrew Morton --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 37aad399e4e01a..b355f1c859587c 100644 --- a/.mailmap +++ b/.mailmap @@ -222,6 +222,7 @@ Chuck Lever Chuck Lever Chuck Lever Claudiu Beznea +Coiby Xu Colin Ian King Corey Minyard Damian Hobson-Garcia From 12e9ac7bc5b254048f886bf421e3a15491106c1f Mon Sep 17 00:00:00 2001 From: Nhat Pham Date: Fri, 28 Aug 2026 12:14:33 -0700 Subject: [PATCH 0119/1417] mm, swap: fix SWAP_USAGE_OFFLIST_BIT collision with real usage count SWAP_USAGE_OFFLIST_BIT is embedded in the si->inuse_pages usage counter, and is meant to sit above any value that counter can reach. However, it is defined from BITS_PER_TYPE(atomic_t), so it is bit 30. On a system with 4 KiB pages the flag collides with the usage count once that count reaches 4 TiB. swap_usage_in_pages() masks bit 30 out, so whenever the real count has that bit set, every caller of it reads 4 TiB low: * /proc/swaps understates Used by 4 TiB. * A raw count of exactly 2^30 masks to zero, so try_to_unuse() takes its "if (!swap_usage_in_pages(si)) goto success;" early exit and swapoff tears the device down while pages are still swapped out. Nothing in the rest of swapoff aborts the teardown, so those pages are lost. Independently of swapoff, the collision also corrupts the counter and the plist. On a device in normal use, a free that leaves bit 30 set in the count makes swap_usage_sub() see the flag where there is only count, and call add_to_avail_list(). It clears the bit with fetch_and(~SWAP_USAGE_OFFLIST_BIT), leaving the stored count 4 TiB below the real one, and calls plist_add() on a device that is already listed, tripping the WARN_ON(!plist_node_empty(node)) in plist_add() and linking the node a second time. Change the definition of SWAP_USAGE_OFFLIST_BIT to be based on atomic_long_t instead. Note that the usage counter field itself is of this same type, so it is still a valid bit. Link: https://lore.kernel.org/20260828191433.3304458-1-nphamcs@gmail.com Fixes: b228386cf237 ("mm, swap: clean up plist removal and adding") Signed-off-by: Nhat Pham Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260825153238.2695446-1-nphamcs%40gmail.com Suggested-by: Andrew Morton Reviewed-by: Andrew Morton Acked-by: Kairui Song Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Gregory Price Cc: Johannes Weiner Cc: Joshua Hahn Cc: Kemeng Shi Cc: Shakeel Butt Cc: Youngjun Park Cc: Signed-off-by: Andrew Morton --- mm/swapfile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/swapfile.c b/mm/swapfile.c index 53bf01d5f7f112..601979b97f95b2 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -156,7 +156,7 @@ static struct swap_info_struct *swap_entry_to_info(swp_entry_t entry) * This bit will be set if the device is not on the plist and not * usable, will be cleared if the device is on the plist. */ -#define SWAP_USAGE_OFFLIST_BIT (1UL << (BITS_PER_TYPE(atomic_t) - 2)) +#define SWAP_USAGE_OFFLIST_BIT (1UL << (BITS_PER_TYPE(atomic_long_t) - 2)) #define SWAP_USAGE_COUNTER_MASK (~SWAP_USAGE_OFFLIST_BIT) static long swap_usage_in_pages(struct swap_info_struct *si) { From 6e673d0879ef78c395cfe0d3ba316690a60055d8 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Fri, 28 Aug 2026 19:32:51 -0700 Subject: [PATCH 0120/1417] memcg: avoid charging the root memcg from obj_cgroup_charge_pages() obj_cgroup_charge_pages() resolves the objcg to its memcg and calls try_charge_memcg(), which does not short circuit the root memcg. That memcg can be the root memcg: obj_cgroup_is_root() reflects the memcg the objcg was created for and is never updated, while memcg_reparent_objcgs() does redirect objcg->memcg to the parent on rmdir. An objcg of a dying child of root therefore passes every obj_cgroup_is_root() filter but resolves to the root memcg. Folios keep the objcg they were charged with, so this is easy to reach through zswap: allocate anon memory in a cgroup, move the task out, remove the cgroup, then write to the root cgroup's memory.reclaim. The reclaimed folios are charged through the reparented objcg and end up in refill_stock() with the root memcg: WARNING: mm/memcontrol.c:2198 at refill_stock+0x644/0x940 refill_stock+0x644/0x940 try_charge_memcg+0x12d6/0x1570 __obj_cgroup_charge+0x35/0xf0 obj_cgroup_charge+0x1de/0x210 obj_cgroup_charge_zswap+0x83/0x270 zswap_store+0x1620/0x2000 swap_writeout+0x94c/0x14c0 shrink_folio_list+0x3388/0x52b0 [...] try_to_free_mem_cgroup_pages+0x30d/0x830 user_proactive_reclaim+0x504/0x840 memory_reclaim+0x1f/0x30 Beyond the warning, the charge is asymmetric: obj_cgroup_uncharge_pages() skips refill_stock() for the root memcg, so the root's page counter grows and is never uncharged. It is not user visible, since memory.current is not exposed on the root, but it is a leak. Use try_charge(), which returns early for the root memcg, restoring the symmetry with obj_cgroup_uncharge_pages(). The above sequence was scripted into a standalone reproducer (zswap on, swap on a virtio disk, 512MB of anon memory faulted in inside a child of the root cgroup, the task then migrated to the root cgroup, the child removed, followed by "echo 600M swappiness=max > memory.reclaim" on the root) and run in a CONFIG_DEBUG_VM=y VM. It reproduces the splat on the first zswap store of a reparented folio, with the same call chain as the report. With this patch applied the splat is gone while the zswap store count over the run is unchanged, so the same path is still exercised. cgroup selftests test_zswap, test_kmem and test_memcontrol show no new failures. Link: https://lore.kernel.org/20260829023251.474083-1-shakeel.butt@linux.dev Fixes: 20d6c1725228 ("memcg: avoid refill_stock for root memcg") Signed-off-by: Shakeel Butt Reported-by: Farhad Alemi Closes: https://lore.kernel.org/all/CA+0ovCgWzUMK+nNbbtH7eV65Ca=fDN4Ozu7iASgryjvv8Tk8zQ@mail.gmail.com/ Reviewed-by: Muchun Song Reviewed-by: Johannes Weiner Cc: Michal Hocko Cc: Roman Gushchin Cc: Signed-off-by: Andrew Morton --- mm/memcontrol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 1271d390b617e4..856a7d07586ccc 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -3158,7 +3158,7 @@ static int obj_cgroup_charge_pages(struct obj_cgroup *objcg, gfp_t gfp, memcg = get_mem_cgroup_from_objcg(objcg); - ret = try_charge_memcg(memcg, gfp, nr_pages); + ret = try_charge(memcg, gfp, nr_pages); if (ret) goto out; From e1d469a8d6c63032a5aed3679a13a086b61bd5cc Mon Sep 17 00:00:00 2001 From: Christopher Obbard Date: Sat, 29 Aug 2026 12:28:23 +0100 Subject: [PATCH 0121/1417] mailmap: update entry for Christopher Obbard I have changed employer; update my mailmap entry to point at my new email address. Link: https://lore.kernel.org/20260829-update-mail-oss-qualcomm-v2-1-1670c515f225@oss.qualcomm.com Signed-off-by: Christopher Obbard Signed-off-by: Andrew Morton --- .mailmap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index b355f1c859587c..90ff4831f59299 100644 --- a/.mailmap +++ b/.mailmap @@ -211,7 +211,8 @@ Christophe Leroy Christophe Leroy Christophe Leroy Christophe Ricard -Christopher Obbard +Christopher Obbard +Christopher Obbard Christoph Hellwig Christoph Manszewski Christoph Paasch From 848d2ce2fce15fbdc083fbf9691bfa72911033c4 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Sun, 30 Aug 2026 01:36:12 +0800 Subject: [PATCH 0122/1417] mm: filemap: retain mapped dropbehind folios Fault-around can map ready dropbehind folios without going through the normal page-cache lookup that clears dropbehind. A mapping represents a competing cached user, so retain the folio instead of forcibly unmapping it when writeback completes. For a mapped folio, folio_unmap_invalidate() can call unmap_mapping_folio(), which takes i_mmap_rwsem and may sleep. Retaining mapped folios avoids this path when folio_end_dropbehind() runs in non-preemptible task context. Tal was able to trigger a sleeping-in-atomic warning due to this [1]. Unmapped dropbehind folios continue through the existing invalidation path. Link: https://lore.kernel.org/4aba05e1a2c3b61cb337d373eb9b7a8db4ddd822.1788024049.git.qiwenjie@xiaomi.com Link: https://lore.kernel.org/076bb01b-6fcf-4691-be8c-0e8507c9fe64@columbia.edu [1] Fixes: fb7d3bc41493 ("mm/filemap: drop streaming/uncached pages when writeback completes") Signed-off-by: Wenjie Qi Reviewed-by: Matthew Wilcox (Oracle) Reviewed-by: Tal Zussman Tested-by: Tal Zussman Cc: Barry Song Cc: Jan Kara Cc: Jens Axboe Cc: Trond Myklebust Cc: Signed-off-by: Andrew Morton --- mm/filemap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/filemap.c b/mm/filemap.c index 6afec636881fb4..00fd89cf6f5509 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -1616,7 +1616,7 @@ static void filemap_end_dropbehind(struct folio *folio) return; if (!folio_test_clear_dropbehind(folio)) return; - if (mapping) + if (mapping && !folio_mapped(folio)) folio_unmap_invalidate(mapping, folio, 0); } From 641aade99f06df0037e52b5c81645461b7132947 Mon Sep 17 00:00:00 2001 From: Qi Zheng Date: Mon, 17 Aug 2026 17:03:25 +0800 Subject: [PATCH 0123/1417] fs: fix missed removal of super_fs_objects_eligible() Commit 0ef8faff490be ("fs: push nr_cached_objects memcg gating into individual filesystems") was meant to drop the blanket memcg gate in fs/super.c and let each ->nr_cached_objects() implementation decide for itself whether it is meaningful in per-memcg reclaim. However, when that patch was applied the removal of super_fs_objects_eligible() and its two call sites in super_cache_scan() / super_cache_count() was lost, so the helper is still gating every ->nr_cached_objects() hook and 0ef8faff490be is effectively a no-op. Consequences of the leftover gate: - XFS's inode-reclaim hook, which is intentionally driven from per-memcg contexts to free memcg-charged slab, is still short-circuited in fs/super.c exactly the regression from commit 0baad6f9b997 ("fs/super: skip non-memcg-aware nr_cached_objects in memcg slab shrink") that 0ef8faff490be was written to undo. Memcg-charged XFS inode slab therefore keeps piling up under per-memcg pressure until global reclaim kicks in. - Any future ->nr_cached_objects()/->free_cached_objects() that grows memcg awareness is likewise blocked before it can run, so filesystems cannot opt in to per-memcg reclaim on their own defeating the whole point of pushing the gating decision down into the callbacks. Drop the leftover helper and its call sites so the intent of 0ef8faff490be actually takes effect. Link: https://lore.kernel.org/cover.1786955972.git.zhengqi.arch@bytedance.com Link: https://lore.kernel.org/3b038d373c70ebac7cdabfb0035bb91d1d6e6cfe.1786955972.git.zhengqi.arch@bytedance.com Link: https://lore.kernel.org/all/20260715103516.2410175-1-usama.arif@linux.dev/ [0] Fixes: 0ef8faff490b ("fs: push nr_cached_objects memcg gating into individual filesystems") Signed-off-by: Qi Zheng Acked-by: Usama Arif Cc: Baolin Wang Cc: Christian Brauner Cc: David Hildenbrand Cc: Hugh Dickins Cc: Johannes Weiner Cc: Michal Hocko Cc: Muchun Song Cc: Roman Gushchin Cc: Shakeel Butt Cc: Signed-off-by: Andrew Morton --- fs/super.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/fs/super.c b/fs/super.c index 05e44317303874..3ecce24328f674 100644 --- a/fs/super.c +++ b/fs/super.c @@ -171,19 +171,6 @@ static void super_wake(struct super_block *sb, unsigned int flag) wake_up_var(&sb->s_flags); } -/* - * The s_op->nr_cached_objects hooks (used for example by btrfs and xfs) - * operate on filesystem-global state and ignore sc->memcg. Driving them - * from per-memcg shrink_slab_memcg() invocations only burns CPU walking - * per-cpu counters and queueing duplicate work: the actual reclaim happens on - * the global path (kswapd or root direct reclaim) regardless. Restrict them - * to that path. - */ -static inline bool super_fs_objects_eligible(struct shrink_control *sc) -{ - return !sc->memcg || mem_cgroup_is_root(sc->memcg); -} - /* * One thing we have to be careful of with a per-sb shrinker is that we don't * drop the last active reference to the superblock from within the shrinker. @@ -213,7 +200,7 @@ static unsigned long super_cache_scan(struct shrinker *shrink, if (!super_trylock_shared(sb)) return SHRINK_STOP; - if (sb->s_op->nr_cached_objects && super_fs_objects_eligible(sc)) + if (sb->s_op->nr_cached_objects) fs_objects = sb->s_op->nr_cached_objects(sb, sc); inodes = list_lru_shrink_count(&sb->s_inode_lru, sc); @@ -274,8 +261,7 @@ static unsigned long super_cache_count(struct shrinker *shrink, return 0; smp_rmb(); - if (sb->s_op && sb->s_op->nr_cached_objects && - super_fs_objects_eligible(sc)) + if (sb->s_op && sb->s_op->nr_cached_objects) total_objects = sb->s_op->nr_cached_objects(sb, sc); total_objects += list_lru_shrink_count(&sb->s_dentry_lru, sc); From 8e2b8614039853e68d5338e37821e8bcee9fc05f Mon Sep 17 00:00:00 2001 From: Seunguk Shin Date: Mon, 3 Aug 2026 13:34:55 +0100 Subject: [PATCH 0124/1417] fs/dax: check zero or empty entry before converting xarray entry Calling dax_to_folio() with empty entry causes kernel panic below when booting a VM with DAX enabled storage. This patch checks empty entry before calling dax_to_folio() on dax_associate_entry(), dax_disassociate_entry(), and dax_busy_page(). Commit 98c183a4fccf ("fs/dax: don't disassociate zero page entries") added guards in the associate and disassociate paths, but the guards still come after dax_to_folio(), and dax_busy_page() still has the same problem. [ 0.737679] EXT4-fs (pmem0p1): mounted filesystem 79676804-7c8b-491a-b2a6-9bae3c72af70 ro with ordered data mode. Quota mode: disabled. [ 0.737891] VFS: Mounted root (ext4 filesystem) readonly on device 259:1. [ 0.739119] devtmpfs: mounted [ 0.739476] Freeing unused kernel memory: 1920K [ 0.740156] Run /sbin/init as init process [ 0.740229] with arguments: [ 0.740286] /sbin/init [ 0.740321] with environment: [ 0.740369] HOME=/ [ 0.740400] TERM=linux [ 0.743162] Unable to handle kernel paging request at virtual address fffffdffbf000008 [ 0.743285] Mem abort info: [ 0.743316] ESR = 0x0000000096000006 [ 0.743371] EC = 0x25: DABT (current EL), IL = 32 bits [ 0.743444] SET = 0, FnV = 0 [ 0.743489] EA = 0, S1PTW = 0 [ 0.743545] FSC = 0x06: level 2 translation fault [ 0.743610] Data abort info: [ 0.743656] ISV = 0, ISS = 0x00000006, ISS2 = 0x00000000 [ 0.743720] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 0.743785] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 0.743848] swapper pgtable: 4k pages, 48-bit VAs, pgdp=00000000b9d17000 [ 0.743931] [fffffdffbf000008] pgd=10000000bfa3d403, p4d=10000000bfa3d403, pud=1000000040bfe403, pmd=0000000000000000 [ 0.744070] Internal error: Oops: 0000000096000006 [#1] SMP [ 0.748888] CPU: 0 UID: 0 PID: 1 Comm: init Not tainted 6.18.4 #1 NONE [ 0.749421] pstate: 004000c5 (nzcv daIF +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 0.749969] pc : dax_disassociate_entry.constprop.0+0x20/0x50 [ 0.750444] lr : dax_insert_entry+0xcc/0x408 [ 0.750802] sp : ffff80008000b9e0 [ 0.751083] x29: ffff80008000b9e0 x28: 0000000000000000 x27: 0000000000000000 [ 0.751682] x26: 0000000001963d01 x25: ffff0000004f7d90 x24: 0000000000000000 [ 0.752264] x23: 0000000000000000 x22: ffff80008000bcc8 x21: 0000000000000011 [ 0.752836] x20: ffff80008000ba90 x19: 0000000001963d01 x18: 0000000000000000 [ 0.753407] x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000000 [ 0.753970] x14: ffffbf3154b9ae70 x13: 0000000000000000 x12: ffffbf3154b9ae70 [ 0.754548] x11: ffffffffffffffff x10: 0000000000000000 x9 : 0000000000000000 [ 0.755122] x8 : 000000000000000d x7 : 000000000000001f x6 : 0000000000000000 [ 0.755707] x5 : 0000000000000000 x4 : 0000000000000000 x3 : fffffdffc0000000 [ 0.756287] x2 : 0000000000000008 x1 : 0000000040000000 x0 : fffffdffbf000000 [ 0.756871] Call trace: [ 0.757107] dax_disassociate_entry.constprop.0+0x20/0x50 (P) [ 0.757592] dax_iomap_pte_fault+0x4fc/0x808 [ 0.757951] dax_iomap_fault+0x28/0x30 [ 0.758258] ext4_dax_huge_fault+0x80/0x2dc [ 0.758594] ext4_dax_fault+0x10/0x3c [ 0.758892] __do_fault+0x38/0x12c [ 0.759175] __handle_mm_fault+0x530/0xcf0 [ 0.759518] handle_mm_fault+0xe4/0x230 [ 0.759833] do_page_fault+0x17c/0x4dc [ 0.760144] do_translation_fault+0x30/0x38 [ 0.760483] do_mem_abort+0x40/0x8c [ 0.760771] el0_ia+0x4c/0x170 [ 0.761032] el0t_64_sync_handler+0xd8/0xdc [ 0.761371] el0t_64_sync+0x168/0x16c [ 0.761677] Code: f9453021 f2dfbfe3 cb813080 8b001860 (f9400401) [ 0.762168] ---[ end trace 0000000000000000 ]--- [ 0.762550] note: init[1] exited with irqs disabled [ 0.762631] Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b Link: https://lore.kernel.org/m2y0enxtzk.fsf@arm.com Fixes: 38607c62b34b ("fs/dax: properly refcount fs dax pages") Signed-off-by: Seunguk Shin Reviewed-by: Jan Kara Reviewed-by: Alistair Popple Reported-by: Kiara Grouwstra Cc: Al Viro Cc: Christian Brauner Cc: Matthew Wilcox (Oracle) Cc: Signed-off-by: Andrew Morton --- fs/dax.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/dax.c b/fs/dax.c index 6ba50142eeb2fd..1fbba0d21c13d8 100644 --- a/fs/dax.c +++ b/fs/dax.c @@ -480,11 +480,12 @@ static void dax_associate_entry(void *entry, struct address_space *mapping, unsigned long address, bool shared) { unsigned long size = dax_entry_size(entry), index; - struct folio *folio = dax_to_folio(entry); + struct folio *folio; if (dax_is_zero_entry(entry) || dax_is_empty_entry(entry)) return; + folio = dax_to_folio(entry); index = linear_page_index(vma, address & ~(size - 1)); if (shared && (folio->mapping || dax_folio_is_shared(folio))) { if (folio->mapping) @@ -505,21 +506,23 @@ static void dax_associate_entry(void *entry, struct address_space *mapping, static void dax_disassociate_entry(void *entry, struct address_space *mapping, bool trunc) { - struct folio *folio = dax_to_folio(entry); + struct folio *folio; if (dax_is_zero_entry(entry) || dax_is_empty_entry(entry)) return; + folio = dax_to_folio(entry); dax_folio_put(folio); } static struct page *dax_busy_page(void *entry) { - struct folio *folio = dax_to_folio(entry); + struct folio *folio; if (dax_is_zero_entry(entry) || dax_is_empty_entry(entry)) return NULL; + folio = dax_to_folio(entry); if (folio_ref_count(folio) - folio_mapcount(folio)) return &folio->page; else From 0791a234b35d4b72187c497e05817f2c9019c3ab Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Tue, 1 Sep 2026 13:09:29 -0700 Subject: [PATCH 0125/1417] remove old lib/alloc_tag.c This was moved into mm/, but the original lib/ file somehow remained. Remove it. Reported-by: Suren Baghdasaryan Cc: Lorenzo Stoakes Signed-off-by: Andrew Morton --- lib/alloc_tag.c | 1029 ----------------------------------------------- 1 file changed, 1029 deletions(-) delete mode 100644 lib/alloc_tag.c diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c deleted file mode 100644 index e5b218176c5afe..00000000000000 --- a/lib/alloc_tag.c +++ /dev/null @@ -1,1029 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ALLOCINFO_FILE_NAME "allocinfo" -#define MODULE_ALLOC_TAG_VMAP_SIZE (100000UL * sizeof(struct alloc_tag)) -#define SECTION_START(NAME) (CODETAG_SECTION_START_PREFIX NAME) -#define SECTION_STOP(NAME) (CODETAG_SECTION_STOP_PREFIX NAME) - -#ifdef CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT -static bool mem_profiling_support = true; -#else -static bool mem_profiling_support; -#endif - -/* - * Memory allocation profiling is permanently disabled and cannot be enabled. - * Must be called after setup_early_mem_profiling(). - */ -bool mem_alloc_profiling_permanently_disabled(void) -{ - return !mem_profiling_support; -} - -static struct codetag_type *alloc_tag_cttype; - -#ifdef CONFIG_ARCH_MODULE_NEEDS_WEAK_PER_CPU -DEFINE_PER_CPU(struct alloc_tag_counters, _shared_alloc_tag); -EXPORT_SYMBOL(_shared_alloc_tag); -#endif - -DEFINE_STATIC_KEY_MAYBE(CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT, - mem_alloc_profiling_key); -EXPORT_SYMBOL(mem_alloc_profiling_key); - -DEFINE_STATIC_KEY_FALSE(mem_profiling_compressed); - -struct alloc_tag_kernel_section kernel_tags = { NULL, 0 }; -unsigned long alloc_tag_ref_mask; -int alloc_tag_ref_offs; - -struct allocinfo_private { - struct codetag_iterator iter; - struct codetag_iterator reported_iter; - bool print_header; -}; - -static void *allocinfo_start(struct seq_file *m, loff_t *pos) -{ - struct allocinfo_private *priv; - loff_t node = *pos; - - priv = (struct allocinfo_private *)m->private; - codetag_lock_module_list(alloc_tag_cttype); - if (node == 0) { - priv->print_header = true; - priv->iter = codetag_get_ct_iter(alloc_tag_cttype); - } else { - priv->iter = priv->reported_iter; - } - codetag_next_ct(&priv->iter); - return priv->iter.ct ? priv : NULL; -} - -static void *allocinfo_next(struct seq_file *m, void *arg, loff_t *pos) -{ - struct allocinfo_private *priv = (struct allocinfo_private *)arg; - struct codetag *ct; - - priv->reported_iter = priv->iter; - ct = codetag_next_ct(&priv->iter); - (*pos)++; - if (!ct) - return NULL; - - return priv; -} - -static void allocinfo_stop(struct seq_file *m, void *arg) -{ - codetag_unlock_module_list(alloc_tag_cttype); -} - -static void print_allocinfo_header(struct seq_buf *buf) -{ - /* Output format version, so we can change it. */ - seq_buf_printf(buf, "allocinfo - version: 2.0\n"); - seq_buf_printf(buf, "# \n"); -} - -static void alloc_tag_to_text(struct seq_buf *out, struct codetag *ct) -{ - struct alloc_tag *tag = ct_to_alloc_tag(ct); - struct alloc_tag_counters counter = alloc_tag_read(tag); - s64 bytes = counter.bytes; - - seq_buf_printf(out, "%12lli %8llu ", bytes, counter.calls); - codetag_to_text(out, ct); - if (unlikely(alloc_tag_is_inaccurate(tag))) - seq_buf_printf(out, " accurate:no"); - seq_buf_putc(out, ' '); - seq_buf_putc(out, '\n'); -} - -static int allocinfo_show(struct seq_file *m, void *arg) -{ - struct allocinfo_private *priv = (struct allocinfo_private *)arg; - char *bufp; - size_t n = seq_get_buf(m, &bufp); - struct seq_buf buf; - - seq_buf_init(&buf, bufp, n); - if (priv->print_header) { - print_allocinfo_header(&buf); - priv->print_header = false; - } - alloc_tag_to_text(&buf, priv->iter.ct); - seq_commit(m, seq_buf_used(&buf)); - return 0; -} - -static const struct seq_operations allocinfo_seq_op = { - .start = allocinfo_start, - .next = allocinfo_next, - .stop = allocinfo_stop, - .show = allocinfo_show, -}; - -size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep) -{ - struct codetag_iterator iter; - struct codetag *ct; - struct codetag_bytes n; - unsigned int i, nr = 0; - - if (IS_ERR_OR_NULL(alloc_tag_cttype)) - return 0; - - if (can_sleep) - codetag_lock_module_list(alloc_tag_cttype); - else if (!codetag_trylock_module_list(alloc_tag_cttype)) - return 0; - - iter = codetag_get_ct_iter(alloc_tag_cttype); - while ((ct = codetag_next_ct(&iter))) { - struct alloc_tag_counters counter = alloc_tag_read(ct_to_alloc_tag(ct)); - - n.ct = ct; - n.bytes = counter.bytes; - - for (i = 0; i < nr; i++) - if (n.bytes > tags[i].bytes) - break; - - if (i < count) { - nr -= nr == count; - memmove(&tags[i + 1], - &tags[i], - sizeof(tags[0]) * (nr - i)); - nr++; - tags[i] = n; - } - } - - codetag_unlock_module_list(alloc_tag_cttype); - - return nr; -} - -void pgalloc_tag_split(struct folio *folio, int old_order, int new_order) -{ - int i; - struct alloc_tag *tag; - unsigned int nr_pages = 1 << new_order; - - if (!mem_alloc_profiling_enabled()) - return; - - tag = __pgalloc_tag_get(&folio->page); - if (!tag) - return; - - for (i = nr_pages; i < (1 << old_order); i += nr_pages) { - union pgtag_ref_handle handle; - union codetag_ref ref; - - if (get_page_tag_ref(folio_page(folio, i), &ref, &handle)) { - /* Set new reference to point to the original tag */ - alloc_tag_ref_set(&ref, tag); - update_page_tag_ref(handle, &ref); - put_page_tag_ref(handle); - } - } -} - -void pgalloc_tag_swap(struct folio *new, struct folio *old) -{ - union pgtag_ref_handle handle_old, handle_new; - union codetag_ref ref_old, ref_new; - struct alloc_tag *tag_old, *tag_new; - - if (!mem_alloc_profiling_enabled()) - return; - - tag_old = __pgalloc_tag_get(&old->page); - if (!tag_old) - return; - tag_new = __pgalloc_tag_get(&new->page); - if (!tag_new) - return; - - if (!get_page_tag_ref(&old->page, &ref_old, &handle_old)) - return; - if (!get_page_tag_ref(&new->page, &ref_new, &handle_new)) { - put_page_tag_ref(handle_old); - return; - } - - /* - * Clear tag references to avoid debug warning when using - * __alloc_tag_ref_set() with non-empty reference. - */ - set_codetag_empty(&ref_old); - set_codetag_empty(&ref_new); - - /* swap tags */ - __alloc_tag_ref_set(&ref_old, tag_new); - update_page_tag_ref(handle_old, &ref_old); - __alloc_tag_ref_set(&ref_new, tag_old); - update_page_tag_ref(handle_new, &ref_new); - - put_page_tag_ref(handle_old); - put_page_tag_ref(handle_new); -} - -static void shutdown_mem_profiling(bool remove_file) -{ - if (mem_alloc_profiling_enabled()) - static_branch_disable(&mem_alloc_profiling_key); - - if (!mem_profiling_support) - return; - - if (remove_file) - remove_proc_entry(ALLOCINFO_FILE_NAME, NULL); - mem_profiling_support = false; -} - -void __init alloc_tag_sec_init(void) -{ - struct alloc_tag *last_codetag; - - if (!mem_profiling_support) - return; - - if (!static_key_enabled(&mem_profiling_compressed)) - return; - - kernel_tags.first_tag = (struct alloc_tag *)kallsyms_lookup_name( - SECTION_START(ALLOC_TAG_SECTION_NAME)); - last_codetag = (struct alloc_tag *)kallsyms_lookup_name( - SECTION_STOP(ALLOC_TAG_SECTION_NAME)); - kernel_tags.count = last_codetag - kernel_tags.first_tag; - - /* Check if kernel tags fit into page flags */ - if (kernel_tags.count > (1UL << NR_UNUSED_PAGEFLAG_BITS)) { - shutdown_mem_profiling(false); /* allocinfo file does not exist yet */ - pr_err("%lu allocation tags cannot be references using %d available page flag bits. Memory allocation profiling is disabled!\n", - kernel_tags.count, NR_UNUSED_PAGEFLAG_BITS); - return; - } - - alloc_tag_ref_offs = (LRU_REFS_PGOFF - NR_UNUSED_PAGEFLAG_BITS); - alloc_tag_ref_mask = ((1UL << NR_UNUSED_PAGEFLAG_BITS) - 1); - pr_debug("Memory allocation profiling compression is using %d page flag bits!\n", - NR_UNUSED_PAGEFLAG_BITS); -} - -#ifdef CONFIG_MODULES - -static struct maple_tree mod_area_mt = MTREE_INIT(mod_area_mt, MT_FLAGS_ALLOC_RANGE); -static struct vm_struct *vm_module_tags; -/* A dummy object used to indicate an unloaded module */ -static struct module unloaded_mod; -/* A dummy object used to indicate a module prepended area */ -static struct module prepend_mod; - -struct alloc_tag_module_section module_tags; - -static inline unsigned long alloc_tag_align(unsigned long val) -{ - if (!static_key_enabled(&mem_profiling_compressed)) { - /* No alignment requirements when we are not indexing the tags */ - return val; - } - - if (val % sizeof(struct alloc_tag) == 0) - return val; - return ((val / sizeof(struct alloc_tag)) + 1) * sizeof(struct alloc_tag); -} - -static bool ensure_alignment(unsigned long align, unsigned int *prepend) -{ - if (!static_key_enabled(&mem_profiling_compressed)) { - /* No alignment requirements when we are not indexing the tags */ - return true; - } - - /* - * If alloc_tag size is not a multiple of required alignment, tag - * indexing does not work. - */ - if (!IS_ALIGNED(sizeof(struct alloc_tag), align)) - return false; - - /* Ensure prepend consumes multiple of alloc_tag-sized blocks */ - if (*prepend) - *prepend = alloc_tag_align(*prepend); - - return true; -} - -static inline bool tags_addressable(void) -{ - unsigned long tag_idx_count; - - if (!static_key_enabled(&mem_profiling_compressed)) - return true; /* with page_ext tags are always addressable */ - - tag_idx_count = CODETAG_ID_FIRST + kernel_tags.count + - module_tags.size / sizeof(struct alloc_tag); - - return tag_idx_count < (1UL << NR_UNUSED_PAGEFLAG_BITS); -} - -static bool needs_section_mem(struct module *mod, unsigned long size) -{ - if (!mem_profiling_support) - return false; - - return size >= sizeof(struct alloc_tag); -} - -static bool clean_unused_counters(struct alloc_tag *start_tag, - struct alloc_tag *end_tag) -{ - struct alloc_tag *tag; - bool ret = true; - - for (tag = start_tag; tag <= end_tag; tag++) { - struct alloc_tag_counters counter; - - if (!tag->counters) - continue; - - counter = alloc_tag_read(tag); - if (!counter.bytes) { - free_percpu(tag->counters); - tag->counters = NULL; - } else { - ret = false; - } - } - - return ret; -} - -/* Called with mod_area_mt locked */ -static void clean_unused_module_areas_locked(void) -{ - MA_STATE(mas, &mod_area_mt, 0, module_tags.size); - struct module *val; - - mas_for_each(&mas, val, module_tags.size) { - struct alloc_tag *start_tag; - struct alloc_tag *end_tag; - - if (val != &unloaded_mod) - continue; - - /* Release area if all tags are unused */ - start_tag = (struct alloc_tag *)(module_tags.start_addr + mas.index); - end_tag = (struct alloc_tag *)(module_tags.start_addr + mas.last); - if (clean_unused_counters(start_tag, end_tag)) - mas_erase(&mas); - } -} - -/* Called with mod_area_mt locked */ -static bool find_aligned_area(struct ma_state *mas, unsigned long section_size, - unsigned long size, unsigned int prepend, unsigned long align) -{ - bool cleanup_done = false; - -repeat: - /* Try finding exact size and hope the start is aligned */ - if (!mas_empty_area(mas, 0, section_size - 1, prepend + size)) { - if (IS_ALIGNED(mas->index + prepend, align)) - return true; - - /* Try finding larger area to align later */ - mas_reset(mas); - if (!mas_empty_area(mas, 0, section_size - 1, - size + prepend + align - 1)) - return true; - } - - /* No free area, try cleanup stale data and repeat the search once */ - if (!cleanup_done) { - clean_unused_module_areas_locked(); - cleanup_done = true; - mas_reset(mas); - goto repeat; - } - - return false; -} - -static int vm_module_tags_populate(void) -{ - unsigned long phys_end = ALIGN_DOWN(module_tags.start_addr, PAGE_SIZE) + - (vm_module_tags->nr_pages << PAGE_SHIFT); - unsigned long new_end = module_tags.start_addr + module_tags.size; - - if (phys_end < new_end) { - struct page **next_page = vm_module_tags->pages + vm_module_tags->nr_pages; - unsigned long old_shadow_end = ALIGN(phys_end, MODULE_ALIGN); - unsigned long new_shadow_end = ALIGN(new_end, MODULE_ALIGN); - unsigned long more_pages; - unsigned long nr = 0; - - more_pages = ALIGN(new_end - phys_end, PAGE_SIZE) >> PAGE_SHIFT; - while (nr < more_pages) { - unsigned long allocated; - - allocated = alloc_pages_bulk_node(GFP_KERNEL | __GFP_NOWARN, - NUMA_NO_NODE, more_pages - nr, next_page + nr); - - if (!allocated) - break; - nr += allocated; - } - - if (nr < more_pages || - vmap_pages_range(phys_end, phys_end + (nr << PAGE_SHIFT), PAGE_KERNEL, - next_page, PAGE_SHIFT) < 0) { - release_pages_arg arg = { .pages = next_page }; - - /* Clean up and error out */ - release_pages(arg, nr); - return -ENOMEM; - } - - vm_module_tags->nr_pages += nr; - - /* - * Kasan allocates 1 byte of shadow for every 8 bytes of data. - * When kasan_alloc_module_shadow allocates shadow memory, - * its unit of allocation is a page. - * Therefore, here we need to align to MODULE_ALIGN. - */ - if (old_shadow_end < new_shadow_end) - kasan_alloc_module_shadow((void *)old_shadow_end, - new_shadow_end - old_shadow_end, - GFP_KERNEL); - } - - /* - * Mark the pages as accessible, now that they are mapped. - * With hardware tag-based KASAN, marking is skipped for - * non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc(). - */ - kasan_unpoison_vmalloc((void *)module_tags.start_addr, - new_end - module_tags.start_addr, - KASAN_VMALLOC_PROT_NORMAL); - - return 0; -} - -static void *reserve_module_tags(struct module *mod, unsigned long size, - unsigned int prepend, unsigned long align) -{ - unsigned long section_size = module_tags.end_addr - module_tags.start_addr; - MA_STATE(mas, &mod_area_mt, 0, section_size - 1); - unsigned long offset; - void *ret = NULL; - - /* If no tags return error */ - if (size < sizeof(struct alloc_tag)) - return ERR_PTR(-EINVAL); - - /* - * align is always power of 2, so we can use IS_ALIGNED and ALIGN. - * align 0 or 1 means no alignment, to simplify set to 1. - */ - if (!align) - align = 1; - - if (!ensure_alignment(align, &prepend)) { - shutdown_mem_profiling(true); - pr_err("%s: alignment %lu is incompatible with allocation tag indexing. Memory allocation profiling is disabled!\n", - mod->name, align); - return ERR_PTR(-EINVAL); - } - - mas_lock(&mas); - if (!find_aligned_area(&mas, section_size, size, prepend, align)) { - ret = ERR_PTR(-ENOMEM); - goto unlock; - } - - /* Mark found area as reserved */ - offset = mas.index; - offset += prepend; - offset = ALIGN(offset, align); - if (offset != mas.index) { - unsigned long pad_start = mas.index; - - mas.last = offset - 1; - mas_store(&mas, &prepend_mod); - if (mas_is_err(&mas)) { - ret = ERR_PTR(xa_err(mas.node)); - goto unlock; - } - mas.index = offset; - mas.last = offset + size - 1; - mas_store(&mas, mod); - if (mas_is_err(&mas)) { - mas.index = pad_start; - mas_erase(&mas); - ret = ERR_PTR(xa_err(mas.node)); - } - } else { - mas.last = offset + size - 1; - mas_store(&mas, mod); - if (mas_is_err(&mas)) - ret = ERR_PTR(xa_err(mas.node)); - } -unlock: - mas_unlock(&mas); - - if (IS_ERR(ret)) - return ret; - - if (module_tags.size < offset + size) { - int grow_res; - - module_tags.size = offset + size; - if (mem_alloc_profiling_enabled() && !tags_addressable()) { - shutdown_mem_profiling(true); - pr_warn("With module %s there are too many tags to fit in %d page flag bits. Memory allocation profiling is disabled!\n", - mod->name, NR_UNUSED_PAGEFLAG_BITS); - } - - grow_res = vm_module_tags_populate(); - if (grow_res) { - shutdown_mem_profiling(true); - pr_err("Failed to allocate memory for allocation tags in the module %s. Memory allocation profiling is disabled!\n", - mod->name); - return ERR_PTR(grow_res); - } - } - - return (struct alloc_tag *)(module_tags.start_addr + offset); -} - -static void release_module_tags(struct module *mod, bool used) -{ - MA_STATE(mas, &mod_area_mt, module_tags.size, module_tags.size); - struct alloc_tag *start_tag; - struct alloc_tag *end_tag; - struct module *val; - - mas_lock(&mas); - mas_for_each_rev(&mas, val, 0) - if (val == mod) - break; - - if (!val) /* module not found */ - goto out; - - if (!used) - goto release_area; - - start_tag = (struct alloc_tag *)(module_tags.start_addr + mas.index); - end_tag = (struct alloc_tag *)(module_tags.start_addr + mas.last); - if (!clean_unused_counters(start_tag, end_tag)) { - struct alloc_tag *tag; - - for (tag = start_tag; tag <= end_tag; tag++) { - struct alloc_tag_counters counter; - - if (!tag->counters) - continue; - - counter = alloc_tag_read(tag); - pr_info("%s:%u module %s func:%s has %llu allocated at module unload\n", - tag->ct.filename, tag->ct.lineno, tag->ct.modname, - tag->ct.function, counter.bytes); - } - } else { - used = false; - } -release_area: - mas_store(&mas, used ? &unloaded_mod : NULL); - val = mas_prev_range(&mas, 0); - if (val == &prepend_mod) - mas_store(&mas, NULL); -out: - mas_unlock(&mas); -} - -static int load_module(struct module *mod, struct codetag *start, struct codetag *stop) -{ - /* Allocate module alloc_tag percpu counters */ - struct alloc_tag *start_tag; - struct alloc_tag *stop_tag; - struct alloc_tag *tag; - - /* percpu counters for core allocations are already statically allocated */ - if (!mod) - return 0; - - start_tag = ct_to_alloc_tag(start); - stop_tag = ct_to_alloc_tag(stop); - for (tag = start_tag; tag < stop_tag; tag++) { - WARN_ON(tag->counters); - tag->counters = alloc_percpu(struct alloc_tag_counters); - if (!tag->counters) { - while (--tag >= start_tag) { - free_percpu(tag->counters); - tag->counters = NULL; - } - pr_err("Failed to allocate memory for allocation tag percpu counters in the module %s\n", - mod->name); - return -ENOMEM; - } - - /* - * Avoid a kmemleak false positive. The pointer to the counters is stored - * in the alloc_tag section of the module and cannot be directly accessed. - */ - kmemleak_ignore_percpu(tag->counters); - } - return 0; -} - -static void replace_module(struct module *mod, struct module *new_mod) -{ - MA_STATE(mas, &mod_area_mt, 0, module_tags.size); - struct module *val; - - mas_lock(&mas); - mas_for_each(&mas, val, module_tags.size) { - if (val != mod) - continue; - - mas_store_gfp(&mas, new_mod, GFP_KERNEL); - break; - } - mas_unlock(&mas); -} - -static int __init alloc_mod_tags_mem(void) -{ - /* Map space to copy allocation tags */ - vm_module_tags = execmem_vmap(MODULE_ALLOC_TAG_VMAP_SIZE); - if (!vm_module_tags) { - pr_err("Failed to map %lu bytes for module allocation tags\n", - MODULE_ALLOC_TAG_VMAP_SIZE); - module_tags.start_addr = 0; - return -ENOMEM; - } - - vm_module_tags->pages = kmalloc_objs(struct page *, - get_vm_area_size(vm_module_tags) >> PAGE_SHIFT, - GFP_KERNEL | __GFP_ZERO); - if (!vm_module_tags->pages) { - free_vm_area(vm_module_tags); - return -ENOMEM; - } - - module_tags.start_addr = (unsigned long)vm_module_tags->addr; - module_tags.end_addr = module_tags.start_addr + MODULE_ALLOC_TAG_VMAP_SIZE; - /* Ensure the base is alloc_tag aligned when required for indexing */ - module_tags.start_addr = alloc_tag_align(module_tags.start_addr); - - return 0; -} - -static void __init free_mod_tags_mem(void) -{ - release_pages_arg arg = { .pages = vm_module_tags->pages }; - - module_tags.start_addr = 0; - release_pages(arg, vm_module_tags->nr_pages); - kfree(vm_module_tags->pages); - free_vm_area(vm_module_tags); -} - -#else /* CONFIG_MODULES */ - -static inline int alloc_mod_tags_mem(void) { return 0; } -static inline void free_mod_tags_mem(void) {} - -#endif /* CONFIG_MODULES */ - -/* See: Documentation/mm/allocation-profiling.rst */ -static int __init setup_early_mem_profiling(char *str) -{ - bool compressed = false; - bool enable; - - if (!str || !str[0]) - return -EINVAL; - - if (!strncmp(str, "never", 5)) { - enable = false; - mem_profiling_support = false; - pr_info("Memory allocation profiling is disabled!\n"); - } else { - char *token = strsep(&str, ","); - - if (kstrtobool(token, &enable)) - return -EINVAL; - - if (str) { - - if (strcmp(str, "compressed")) - return -EINVAL; - - compressed = true; - } - mem_profiling_support = true; - pr_info("Memory allocation profiling is enabled %s compression and is turned %s!\n", - compressed ? "with" : "without", str_on_off(enable)); - } - - if (enable != mem_alloc_profiling_enabled()) { - if (enable) - static_branch_enable(&mem_alloc_profiling_key); - else - static_branch_disable(&mem_alloc_profiling_key); - } - if (compressed != static_key_enabled(&mem_profiling_compressed)) { - if (compressed) - static_branch_enable(&mem_profiling_compressed); - else - static_branch_disable(&mem_profiling_compressed); - } - - return 0; -} -early_param("sysctl.vm.mem_profiling", setup_early_mem_profiling); - -static __init bool need_page_alloc_tagging(void) -{ - if (static_key_enabled(&mem_profiling_compressed)) - return false; - - return mem_profiling_support; -} - -#ifdef CONFIG_MEM_ALLOC_PROFILING_DEBUG -/* - * Track page allocations before page_ext is initialized. - * Some pages are allocated before page_ext becomes available, leaving - * their codetag uninitialized. Track these early PFNs so we can clear - * their codetag refs later to avoid warnings when they are freed. - * - * Each page is cast to a pfn_pool: the first few bytes hold metadata - * (next pointer and slot count), the remainder stores PFNs. - */ -struct pfn_pool { - struct pfn_pool *next; - atomic_t count; - unsigned long pfns[]; -}; - -#define PFN_POOL_SIZE ((PAGE_SIZE - offsetof(struct pfn_pool, pfns)) / \ - sizeof(unsigned long)) - -/* - * Skip early PFN recording for a page allocation. Reuses the - * %__GFP_NO_OBJ_EXT bit. Used by __alloc_tag_add_early_pfn() to avoid - * recursion when allocating pages for the early PFN tracking list - * itself. - * - * Codetags of the pages allocated with __GFP_NO_CODETAG should be - * cleared (via clear_page_tag_ref()) before freeing the pages to prevent - * alloc_tag_sub_check() from triggering a warning. - */ -#define __GFP_NO_CODETAG __GFP_NO_OBJ_EXT - -static struct pfn_pool *current_pfn_pool __initdata; - -static void __init __alloc_tag_add_early_pfn(unsigned long pfn) -{ - struct pfn_pool *pool; - int idx; - - do { - pool = READ_ONCE(current_pfn_pool); - if (!pool || atomic_read(&pool->count) >= PFN_POOL_SIZE) { - struct page *new_page = alloc_page(__GFP_HIGH | __GFP_NO_CODETAG); - struct pfn_pool *new; - - if (!new_page) { - pr_warn_once("early PFN tracking page allocation failed\n"); - return; - } - new = page_address(new_page); - new->next = pool; - atomic_set(&new->count, 0); - if (cmpxchg(¤t_pfn_pool, pool, new) != pool) { - clear_page_tag_ref(new_page); - __free_page(new_page); - continue; - } - pool = new; - } - idx = atomic_read(&pool->count); - if (idx >= PFN_POOL_SIZE) - continue; - if (atomic_cmpxchg(&pool->count, idx, idx + 1) == idx) - break; - } while (1); - - pool->pfns[idx] = pfn; -} - -typedef void alloc_tag_add_func(unsigned long pfn); -static alloc_tag_add_func __rcu *alloc_tag_add_early_pfn_ptr __refdata = - RCU_INITIALIZER(__alloc_tag_add_early_pfn); - -void alloc_tag_add_early_pfn(unsigned long pfn, gfp_t gfp_flags) -{ - alloc_tag_add_func *alloc_tag_add; - - if (static_key_enabled(&mem_profiling_compressed)) - return; - - /* Skip allocations for the tracking list itself to avoid recursion. */ - if (gfp_flags & __GFP_NO_CODETAG) - return; - - rcu_read_lock(); - alloc_tag_add = rcu_dereference(alloc_tag_add_early_pfn_ptr); - if (alloc_tag_add) - alloc_tag_add(pfn); - rcu_read_unlock(); -} - -static void __init clear_early_alloc_pfn_tag_refs(void) -{ - struct pfn_pool *pool, *next; - struct page *page; - int i; - - if (static_key_enabled(&mem_profiling_compressed)) - return; - - rcu_assign_pointer(alloc_tag_add_early_pfn_ptr, NULL); - /* Make sure we are not racing with __alloc_tag_add_early_pfn() */ - synchronize_rcu(); - - for (pool = current_pfn_pool; pool; pool = next) { - int nr_pfns = atomic_read(&pool->count); - - for (i = 0; i < nr_pfns; i++) { - unsigned long pfn = pool->pfns[i]; - - if (pfn_valid(pfn)) { - union pgtag_ref_handle handle; - union codetag_ref ref; - - if (get_page_tag_ref(pfn_to_page(pfn), &ref, &handle)) { - /* - * An early-allocated page could be freed and reallocated - * after its page_ext is initialized but before we clear it. - * In that case, it already has a valid tag set. - * We should not overwrite that valid tag - * with CODETAG_EMPTY. - * - * Note: there is still a small race window between checking - * ref.ct and calling set_codetag_empty(). We accept this - * race as it's unlikely and the extra complexity of atomic - * cmpxchg is not worth it for this debug-only code path. - */ - if (ref.ct) { - put_page_tag_ref(handle); - continue; - } - - set_codetag_empty(&ref); - update_page_tag_ref(handle, &ref); - put_page_tag_ref(handle); - } - } - } - - next = pool->next; - page = virt_to_page(pool); - clear_page_tag_ref(page); - __free_page(page); - } -} -#else /* !CONFIG_MEM_ALLOC_PROFILING_DEBUG */ -static inline void __init clear_early_alloc_pfn_tag_refs(void) {} -#endif /* CONFIG_MEM_ALLOC_PROFILING_DEBUG */ - -static __init void init_page_alloc_tagging(void) -{ - clear_early_alloc_pfn_tag_refs(); -} - -struct page_ext_operations page_alloc_tagging_ops = { - .size = sizeof(union codetag_ref), - .need = need_page_alloc_tagging, - .init = init_page_alloc_tagging, -}; -EXPORT_SYMBOL(page_alloc_tagging_ops); - -#ifdef CONFIG_SYSCTL -/* - * Not using proc_do_static_key() directly to prevent enabling profiling - * after it was shut down. - */ -static int proc_mem_profiling_handler(const struct ctl_table *table, int write, - void *buffer, size_t *lenp, loff_t *ppos) -{ - if (write) { - /* - * Call from do_sysctl_args() which is a no-op since the same - * value was already set by setup_early_mem_profiling. - * Return success to avoid warnings from do_sysctl_args(). - */ - if (!current->mm) - return 0; - -#ifdef CONFIG_MEM_ALLOC_PROFILING_DEBUG - /* User can't toggle profiling while debugging */ - return -EACCES; -#endif - if (!mem_profiling_support) - return -EINVAL; - } - - return proc_do_static_key(table, write, buffer, lenp, ppos); -} - - -static const struct ctl_table memory_allocation_profiling_sysctls[] = { - { - .procname = "mem_profiling", - .data = &mem_alloc_profiling_key, - .mode = 0644, - .proc_handler = proc_mem_profiling_handler, - }, -}; - -static void __init sysctl_init(void) -{ - register_sysctl_init("vm", memory_allocation_profiling_sysctls); -} -#else /* CONFIG_SYSCTL */ -static inline void sysctl_init(void) {} -#endif /* CONFIG_SYSCTL */ - -static int __init alloc_tag_init(void) -{ - const struct codetag_type_desc desc = { - .section = ALLOC_TAG_SECTION_NAME, - .tag_size = sizeof(struct alloc_tag), -#ifdef CONFIG_MODULES - .needs_section_mem = needs_section_mem, - .alloc_section_mem = reserve_module_tags, - .free_section_mem = release_module_tags, - .module_load = load_module, - .module_replaced = replace_module, -#endif - }; - int res; - - sysctl_init(); - - if (!mem_profiling_support) { - pr_info("Memory allocation profiling is not supported!\n"); - return 0; - } - - if (!proc_create_seq_private(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_seq_op, - sizeof(struct allocinfo_private), NULL)) { - pr_err("Failed to create %s file\n", ALLOCINFO_FILE_NAME); - shutdown_mem_profiling(false); - return -ENOMEM; - } - - res = alloc_mod_tags_mem(); - if (res) { - pr_err("Failed to reserve address space for module tags, errno = %d\n", res); - shutdown_mem_profiling(true); - return res; - } - - alloc_tag_cttype = codetag_register_type(&desc); - if (IS_ERR(alloc_tag_cttype)) { - pr_err("Allocation tags registration failed, errno = %pe\n", alloc_tag_cttype); - free_mod_tags_mem(); - shutdown_mem_profiling(true); - return PTR_ERR(alloc_tag_cttype); - } - - return 0; -} -module_init(alloc_tag_init); From e14a3454806468b086fe2e4ca2e1bff95b528531 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Tue, 1 Sep 2026 11:01:09 -0700 Subject: [PATCH 0126/1417] mm/mlock: use the IRQ-safe accessor for NR_MLOCK in __munlock_folio() NR_MLOCK is updated from interrupt context. __free_pages_prepare() clears a stray PG_mlocked and adjusts NR_MLOCK, and a folio can reach it with the flag still set from a bio completion handler: __free_pages_ok+0x6af/0x7a0 __bio_release_pages+0xde/0x260 __iomap_dio_bio_end_io+0x16e/0x1a0 blk_update_request+0x14b/0x3d0 blk_mq_end_request+0x18/0x30 blk_done_softirq+0x49/0x60 The folio gets there like this. A MAP_SHARED file mapping is mlocked, so its page cache folios carry PG_mlocked, and an O_DIRECT write sourced from that mapping GUP-pins those same folios. munlock() then runs mlock_vma_pages_range(), which clears VM_LOCKED before walking the page tables to munlock each folio. A concurrent hole punch reaches the folio through the rmap (i_mmap_rwsem, not mmap_lock) and can land inside that window: __folio_remove_rmap() -> munlock_vma_folio() sees VM_LOCKED already clear, so it neither queues the folio on the mlock batch nor takes a reference, and the pte it clears makes the pending mlock_pte_range() walk skip the folio at its !pte_present() check. filemap_remove_folio() then drops the page cache reference, leaving the bio's pin as the last one, released from the completion handler above. So __zone_stat_mod_folio() here needs interrupts disabled, not merely preemption, and __munlock_folio() has a path where they are not: when the folio has already been taken off the LRU by somebody else the function jumps straight to the counter update without taking the lruvec lock. The read-modify-write of the per-CPU NR_MLOCK diff can then be interrupted by the softirq above, and one of the two decrements is lost, leaving Mlocked in /proc/meminfo permanently overstated. Use zone_stat_mod_folio(). mod_zone_state()'s this_cpu_try_cmpxchg() is atomic against a same-CPU interrupt and retries, and on the path where the lruvec lock is held its cost is negligible next to the lock itself. The UNEVICTABLE_PG* events are deliberately left on the __ accessors: they occupy different vm_event_states slots from the UNEVICTABLE_PGCLEARED that __free_pages_prepare() bumps, and nothing updates those two from interrupt context. Link: https://lore.kernel.org/20260901180109.3797944-1-shakeel.butt@linux.dev Fixes: 2fbb0c10d1e8 ("mm/munlock: mlock_page() munlock_page() batch by pagevec") Signed-off-by: Shakeel Butt Reported-by: syzbot+cd2073ee6d958a8d0fcd@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-mm/6a931c5a.08e933ee.dbf97.0093.GAE@google.com/ Acked-by: Hugh Dickins Cc: Jann Horn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Pedro Falcato Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- mm/mlock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/mlock.c b/mm/mlock.c index efa6716e4dfbdf..39215a3eab1fbf 100644 --- a/mm/mlock.c +++ b/mm/mlock.c @@ -141,7 +141,7 @@ static struct lruvec *__munlock_folio(struct folio *folio, struct lruvec *lruvec munlock: if (folio_test_clear_mlocked(folio)) { - __zone_stat_mod_folio(folio, NR_MLOCK, -nr_pages); + zone_stat_mod_folio(folio, NR_MLOCK, -nr_pages); if (isolated || !folio_test_unevictable(folio)) __count_vm_events(UNEVICTABLE_PGMUNLOCKED, nr_pages); else From 3265ef0b670180b0b946d73ee9825d6d91e98a08 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 06:09:55 -1000 Subject: [PATCH 0127/1417] sched_ext: Rename sch to root_sch in dispatch_one() dispatch_one() uses the root scheduler for everything it does, including the two decisions to keep running @prev, which are wrong when @prev belongs to a sub-scheduler. The function has to deal with @prev's scheduler too. Rename the root's local from sch to root_sch for clarity and to make room for it. No functional change. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 7e414a7c53fcfb..120540cdda74d1 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2919,7 +2919,7 @@ static inline void maybe_queue_balance_callback(struct rq *rq) static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev) { - struct scx_sched *sch = scx_root_protected_live(); + struct scx_sched *root_sch = scx_root_protected_live(); enum scx_dsp_verdict verdict; s32 cpu = cpu_of(rq); @@ -2928,7 +2928,7 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev scx_process_sync_ecaps(rq, prev); - if ((sch->ops.flags & SCX_OPS_HAS_CPU_PREEMPT) && + if ((root_sch->ops.flags & SCX_OPS_HAS_CPU_PREEMPT) && unlikely(rq->scx.cpu_released)) { /* * If the previous sched_class for the current CPU was not SCX, @@ -2936,8 +2936,8 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev * core. This callback complements ->cpu_release(), which is * emitted in switch_class(). */ - if (sch->ops.cpu_acquire) - SCX_CALL_OP(sch, cpu_acquire, rq, cpu, NULL); + if (root_sch->ops.cpu_acquire) + SCX_CALL_OP(root_sch, cpu_acquire, rq, cpu, NULL); rq->scx.cpu_released = false; } @@ -2955,7 +2955,7 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev * test. */ if ((prev->scx.flags & SCX_TASK_QUEUED) && prev->scx.slice && - !scx_bypassing(sch, cpu)) { + !scx_bypassing(root_sch, cpu)) { verdict = SCX_DSP_PREV; goto has_tasks; } @@ -2967,7 +2967,7 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev goto has_tasks; } - verdict = scx_dispatch_sched(sch, rq, prev, false); + verdict = scx_dispatch_sched(root_sch, rq, prev, false); if (verdict != SCX_DSP_NONE) goto has_tasks; @@ -2976,9 +2976,9 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev * %SCX_OPS_ENQ_LAST is in effect. */ if ((prev->scx.flags & SCX_TASK_QUEUED) && - (!(sch->ops.flags & SCX_OPS_ENQ_LAST) || scx_bypassing(sch, cpu)) && + (!(root_sch->ops.flags & SCX_OPS_ENQ_LAST) || scx_bypassing(root_sch, cpu)) && scx_task_can_stay_on_cpu(rq, prev)) { - __scx_add_event(sch, SCX_EV_DISPATCH_KEEP_LAST, 1); + __scx_add_event(root_sch, SCX_EV_DISPATCH_KEEP_LAST, 1); verdict = SCX_DSP_PREV; goto has_tasks; } From 90f19b2816f5d243a8daf36ca83d9d9d04f00e4c Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 06:09:56 -1000 Subject: [PATCH 0128/1417] sched_ext: Use @prev's scheduler for the keep decisions in dispatch_one() dispatch_one() tests ops flags and bypass state against the root scheduler in both places where it decides to keep running @prev: the early keep of a @prev with slice left tests the root's bypass state, and the keep-last at the end tests the root's SCX_OPS_ENQ_LAST and bypass state. Both are properties of the scheduler @prev belongs to, and put_prev_task_scx(), which acts on the outcome, reads them from that scheduler. When @prev belongs to a sub-scheduler the two sides disagree. The keep-last case is visible. The root set SCX_OPS_ENQ_LAST, so a lone @prev of a sub-scheduler is not kept and is enqueued with SCX_ENQ_LAST to a sub-scheduler that never opted in. This trips the WARN_ON_ONCE in put_prev_task_scx() for the missing flag, and the sub-scheduler queues the task like any other and triggers no follow-up scheduling event, which can lead to stalls. Test SCX_OPS_ENQ_LAST and bypass state on @prev's sched in both places and charge SCX_EV_DISPATCH_KEEP_LAST to it. Read the sched at each decision, as the dispatch in between can drop the rq lock. Fixes: 88234b075c3f ("sched_ext: Introduce scx_task_sched[_rcu]()") Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 120540cdda74d1..adf5993fa597d0 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2955,7 +2955,7 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev * test. */ if ((prev->scx.flags & SCX_TASK_QUEUED) && prev->scx.slice && - !scx_bypassing(root_sch, cpu)) { + !scx_bypassing(scx_task_sched(prev), cpu)) { verdict = SCX_DSP_PREV; goto has_tasks; } @@ -2972,15 +2972,20 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev goto has_tasks; /* - * Didn't find another task to run. Keep running @prev unless - * %SCX_OPS_ENQ_LAST is in effect. + * Didn't find another task to run. Keep running @prev unless its own + * scheduler set %SCX_OPS_ENQ_LAST and takes the enqueue instead, see + * put_prev_task_scx(). Read the scheduler here as the dispatch above + * may have dropped the rq lock while @prev changed class or scheduler. */ - if ((prev->scx.flags & SCX_TASK_QUEUED) && - (!(root_sch->ops.flags & SCX_OPS_ENQ_LAST) || scx_bypassing(root_sch, cpu)) && - scx_task_can_stay_on_cpu(rq, prev)) { - __scx_add_event(root_sch, SCX_EV_DISPATCH_KEEP_LAST, 1); - verdict = SCX_DSP_PREV; - goto has_tasks; + if (prev->scx.flags & SCX_TASK_QUEUED) { + struct scx_sched *prev_sch = scx_task_sched(prev); + + if ((!(prev_sch->ops.flags & SCX_OPS_ENQ_LAST) || + scx_bypassing(prev_sch, cpu)) && scx_task_can_stay_on_cpu(rq, prev)) { + __scx_add_event(prev_sch, SCX_EV_DISPATCH_KEEP_LAST, 1); + verdict = SCX_DSP_PREV; + goto has_tasks; + } } rq->scx.flags &= ~SCX_RQ_IN_DISPATCH; return SCX_DSP_NONE; From a0d356696f87700c8c2934e3881277b0d37f0b71 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 06:09:57 -1000 Subject: [PATCH 0129/1417] sched_ext: scx_qmap: Do not add IMMED to rescue inserts qmap's stranded fallback forces a task that can run on none of its self cids onto its first allowed cid with SCX_ENQ_RESCUE, and adds SCX_ENQ_IMMED when that cid is a time-share it holds. On such a cid the insert stops being a rescue request: 1. A task is enqueued while none of its allowed cids is in self_cids. At attach self_cids is still empty. 2. qmap inserts it into cid 0's local DSQ with SCX_ENQ_RESCUE | SCX_ENQ_IMMED. 3. The kernel finds ENQ_IMMED held on cid 0, admits the insert and skips the rescue diversion. 4. cid 0's cpu is busy, so the IMMED task is bounced back to qmap with SCX_ENQ_REENQ. 5. qmap's enqueue sees the same inputs and repeats step 2. Nothing runs in between. 6. The reenqueue limit ejects qmap with SCX_EXIT_ERROR_REENQ. The caps granted during the parent's ops.sub_attach() are delivered after the sub already holds its tasks, while the per-cid effective caps that mark the time-shares are delivered from the first dispatch after bypass lifts, so every attach that receives a time-share on a task's first allowed cid starts the loop. Drop IMMED from the rescue inserts so that step 3 diverts to the rescue path. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- tools/sched_ext/scx_qmap.bpf.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index 9f6e61d7ca0712..e4e51303bd296b 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -358,8 +358,8 @@ s32 BPF_STRUCT_OPS(qmap_select_cid, struct task_struct *p, } /* - * A received time-shared cid is held ENQ_IMMED-only, so inserts must set - * SCX_ENQ_IMMED. + * A received time-shared cid is held ENQ_IMMED-only, so inserts meant to run + * there must set SCX_ENQ_IMMED. */ static u64 needs_immed(s32 cid) { @@ -444,9 +444,11 @@ void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags) * didn't grant them or we delegated them to children - would starve in * SHARED/FIFO since we only pull from those on self cids. * - * Force it onto its first allowed cid's local DSQ. If we hold that cid - * it runs. Otherwise the insert carries SCX_ENQ_RESCUE and the kernel - * diverts the task to its rescue path. + * Force it onto its first allowed cid's local DSQ with SCX_ENQ_RESCUE. + * If we hold ENQ on that cid it runs. Otherwise the kernel diverts the + * task to its rescue path. IMMED would turn the insert into a legal + * placement on a time-shared cid and the kernel would bounce it back + * here instead of rescuing it. */ if (!cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) { s32 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0); @@ -455,7 +457,7 @@ void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags) taskc->force_local = false; __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | c, slice_ns, - enq_flags | needs_immed(c) | SCX_ENQ_RESCUE); + enq_flags | SCX_ENQ_RESCUE); return; } } @@ -618,7 +620,7 @@ static bool scan_shared_dsq(bool from_timer) if (c >= 0 && c < scx_bpf_nr_cids()) { __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c, - needs_immed(c) | SCX_ENQ_RESCUE); + SCX_ENQ_RESCUE); } continue; } @@ -659,7 +661,7 @@ static bool scan_shared_dsq(bool from_timer) if (c >= 0 && c < nr_cids) { __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c, - needs_immed(c) | SCX_ENQ_RESCUE); + SCX_ENQ_RESCUE); } continue; } From 63b4ff622244483e7c530e97d787a3d6c2c38a33 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 06:09:58 -1000 Subject: [PATCH 0130/1417] sched_ext: scx_qmap: Place only on cids whose caps are in effect qmap decides placements from self_cids, which redistribute() derives from the caps view at ops.sub_caps_updated() time. That view runs ahead of the cpus: a granted cid can be in self_cids before its cpu has reported the caps in effect through ops.sub_ecaps_updated(). ops.update_idle() only comes once BASE is in effect, so the idle-gated placements reach such a cid only through an idle bit left over from an earlier hold. The highpri scan has no gate at all: parent cpu Y, qmap cpu X grants ENQ on X to qmap sub_caps_updated() adds X to self_cids highpri scan moves a task to X with PREEMPT caps not in effect, move denied, task bounced with REENQ_CAP reject drain, enqueue the scan moves it to X again denied again dispatch syncs ecaps, sub_ecaps_updated(X) Every highpri move to X in that window is denied and bounced. The two callbacks are meant to split the roles: ops.sub_caps_updated() tracks what the node holds and drives what it delegates to its children, while ops.sub_ecaps_updated() says whether a task can run on a cpu now. qmap used the first for both. Track the caps in effect from ops.sub_ecaps_updated() as avail_cids and place only on self_cids & avail_cids, so that self_cids stays the delegation split and avail_cids gates the placement. The stranded tests keep self_cids, as they ask whether the split gives the task anywhere at all. A highpri task whose self_cids lack caps in effect waits for them instead of being moved and bounced. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- tools/sched_ext/scx_qmap.bpf.c | 83 ++++++++++++++++++++++++++-------- tools/sched_ext/scx_qmap.h | 3 ++ 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index e4e51303bd296b..062bb22ee65c4f 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -24,6 +24,9 @@ * time-share that stays self-local. * self - The excl cpus the node kept for itself, plus all of held_shared. * owner - Who holds a cid - a child slot, CID_SELF, or CID_NONE. + * avail - Cpus whose caps are in effect, per ops.sub_ecaps_updated(). + * usable - self AND avail. Placement decisions use this: self is the + * delegation split and can run ahead of what the cpus honor. * * The scheduler splits its held-excl cpus among self and the children in * proportion to each node's cpu.weight, handing each the floor of its share as @@ -208,8 +211,8 @@ static int qmap_spin_lock(struct bpf_res_spin_lock *lock) } /* - * Try prev_cid, then scan cpus_allowed AND idle_cids AND self_cids round-robin - * from prev_cid + 1. Atomic claim retries on race; bounded by + * Try prev_cid, then scan cpus_allowed AND idle_cids AND usable_cids + * round-robin from prev_cid + 1. Atomic claim retries on race; bounded by * IDLE_PICK_RETRIES to keep the verifier's insn budget in check. */ #define IDLE_PICK_RETRIES 16 @@ -221,7 +224,7 @@ static s32 pick_direct_dispatch_cid(struct task_struct *p, s32 prev_cid, s32 cid; u32 i; - if (cmask_test(prev_cid, &qa.self_cids.mask) && + if (cmask_test(prev_cid, &qa.usable_cids.mask) && cmask_test_and_clear(prev_cid, &qa.idle_cids.mask)) return prev_cid; @@ -229,7 +232,7 @@ static s32 pick_direct_dispatch_cid(struct task_struct *p, s32 prev_cid, bpf_for(i, 0, IDLE_PICK_RETRIES) { cid = cmask_next_and2_set_wrap(&taskc->cpus_allowed, &qa.idle_cids.mask, - &qa.self_cids.mask, cid + 1); + &qa.usable_cids.mask, cid + 1); barrier_var(cid); if (cid >= nr_cids) return -1; @@ -542,7 +545,7 @@ void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags) scx_bpf_dsq_insert(p, SHARED_DSQ, 0, enq_flags); cid = cmask_next_and2_set_wrap(&taskc->cpus_allowed, &qa.idle_cids.mask, - &qa.self_cids.mask, 0); + &qa.usable_cids.mask, 0); if (cid < scx_bpf_nr_cids()) scx_bpf_kick_cid(cid, SCX_KICK_IDLE); return; @@ -646,18 +649,23 @@ static bool scan_shared_dsq(bool from_timer) if (!(taskc = lookup_task_ctx(p))) return false; - /* only run highpri tasks on cids this node holds, not delegated ones */ + /* only run highpri tasks on cids this node can use right now */ if (cmask_test(this_cid, &taskc->cpus_allowed) && - cmask_test(this_cid, &qa.self_cids.mask)) + cmask_test(this_cid, &qa.usable_cids.mask)) cid = this_cid; else cid = cmask_next_and_set_wrap(&taskc->cpus_allowed, - &qa.self_cids.mask, + &qa.usable_cids.mask, this_cid + 1); if (cid >= nr_cids) { - /* stranded after the cull - rescue it from here */ - s32 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0); + s32 c; + + /* self cids lack caps in effect yet, leave it queued */ + if (cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) + continue; + /* stranded after the cull - rescue it from here */ + c = cmask_next_set_wrap(&taskc->cpus_allowed, 0); if (c >= 0 && c < nr_cids) { __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c, @@ -1115,7 +1123,7 @@ void BPF_STRUCT_OPS(qmap_update_idle, s32 cid, bool idle) /* * The kernel delivers update_idle() for every cid this node holds * SCX_CAP_BASE on. Track every cid's idle state regardless of - * delegation: the direct-dispatch pick masks idle_cids with self_cids + * delegation: the direct-dispatch pick masks idle_cids with usable_cids * at selection, so a cid already idle when it returns to self needs no * reseed here. */ @@ -1539,6 +1547,19 @@ static __noinline void account_alloc(void) } } +/* + * usable_cids = self_cids & avail_cids. The inputs have separate writers, + * apply_partition() and qmap_sub_ecaps_updated(), so the result is rebuilt in + * full under the partition guard, in scratch first so that readers never see + * self_cids alone. + */ +static void refresh_usable(void) +{ + cmask_copy(&qa.usable_scratch.mask, &qa.self_cids.mask); + cmask_and(&qa.usable_scratch.mask, &qa.avail_cids.mask); + cmask_copy(&qa.usable_cids.mask, &qa.usable_scratch.mask); +} + /* * apply_partition - execute the plan compute_partition() built * @@ -1561,6 +1582,7 @@ __noinline void apply_partition(void) /* no excl cpu: run own tasks on the held shares, evict children */ if (!qa.part.nr_excl) { cmask_copy(&qa.self_cids.mask, &qa.held_shared.mask); + refresh_usable(); bpf_for(i, 0, MAX_SUB_SCHEDS) if (qa.sub_sched_ctxs[i].cgroup_id) scx_bpf_sub_kill(qa.sub_sched_ctxs[i].cgroup_id, @@ -1598,6 +1620,7 @@ __noinline void apply_partition(void) else if (o == CID_SELF) cmask_set(cid, &qa.self_cids.mask); } + refresh_usable(); /* * Apply each child's exclusive cids as a delta against its previous @@ -1839,8 +1862,11 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init) cmask_init(&qa.rr_cids.mask, 0, nr_cids); cmask_init(&qa.prev_rr_cids.mask, 0, nr_cids); cmask_init(&qa.self_cids.mask, 0, nr_cids); + cmask_init(&qa.avail_cids.mask, 0, nr_cids); + cmask_init(&qa.usable_cids.mask, 0, nr_cids); cmask_init(&qa.to_revoke_cids.mask, 0, nr_cids); cmask_init(&qa.to_grant_cids.mask, 0, nr_cids); + cmask_init(&qa.usable_scratch.mask, 0, nr_cids); cmask_init(&qa.held_excl.mask, 0, nr_cids); cmask_init(&qa.held_shared.mask, 0, nr_cids); @@ -1854,14 +1880,16 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init) } /* - * The root starts holding every cid. qmap_sub_ecaps_updated() maintains - * per-cid shared state as effective caps settle, and redistribute() - * rebuilds owner and self from held caps. A non-root node starts with - * nothing. + * The root starts holding every cid and gets no ecaps notifications, so + * its avail set is fixed here. qmap_sub_ecaps_updated() maintains the + * per-cid state as effective caps settle, and redistribute() rebuilds + * owner and self from held caps. A non-root node starts with nothing. */ bpf_for(i, 0, nr_cids) { if (!sub_cgroup_id) { cmask_set(i, &qa.self_cids.mask); + cmask_set(i, &qa.avail_cids.mask); + cmask_set(i, &qa.usable_cids.mask); qa.part.cid_owner[i] = CID_SELF; } else { qa.part.cid_owner[i] = CID_NONE; @@ -2002,12 +2030,31 @@ void BPF_STRUCT_OPS(qmap_sub_ecaps_updated, s32 cid, u64 before, u64 after) { /* * Effective caps updated. Track which cids hold shared caps so a self - * task placed there enqueues IMMED. + * task placed there enqueues IMMED, and which cids have ENQ_IMMED in + * effect at all (avail, see the header comment). */ - if (after & SCX_CAP_ENQ_IMMED) + if (after & SCX_CAP_ENQ_IMMED) { qa.cid_shared[cid] = (after & SCX_CAP_ENQ) ? 0 : 1; - else + cmask_set(cid, &qa.avail_cids.mask); + } else { qa.cid_shared[cid] = 0; + cmask_clear(cid, &qa.avail_cids.mask); + } + + /* + * When another runner holds the partition guard, set part_pending: + * redistribute() drains it before releasing and rr_advance() checks it + * after, so the deferred refresh lands by the next rr tick. A + * repartition that lost the guard to us runs here. + */ + if (part_try_start()) { + refresh_usable(); + part_end(); + if (__sync_fetch_and_or(&part_pending, 0)) + redistribute(); + } else { + __sync_fetch_and_or(&part_pending, 1); + } } SCX_OPS_CID_DEFINE(qmap_ops, diff --git a/tools/sched_ext/scx_qmap.h b/tools/sched_ext/scx_qmap.h index c78d61806b3983..e95fffcf7b233a 100644 --- a/tools/sched_ext/scx_qmap.h +++ b/tools/sched_ext/scx_qmap.h @@ -165,12 +165,15 @@ struct qmap_arena { /* bpf-internal cmasks (embedded, see struct qmap_cmask) */ struct qmap_cmask self_cids; /* cids this node runs its own tasks on */ + struct qmap_cmask avail_cids; /* cids with caps in effect on the cpu */ + struct qmap_cmask usable_cids; /* self_cids & avail_cids, placeable right now */ struct qmap_cmask idle_cids; /* idle state of all cids regardless of delegation */ struct qmap_cmask rr_cids; /* the shared pool, as a mask for grant/revoke */ /* scratch cmasks */ struct qmap_cmask to_revoke_cids; /* delta cids to revoke */ struct qmap_cmask to_grant_cids; /* delta cids to grant */ + struct qmap_cmask usable_scratch; /* refresh_usable() build area */ struct qmap_cmask prev_rr_cids; /* previous shared pool, to clear stale grants */ struct qmap_cmask held_excl; /* cids held excl (ENQ): delegatable */ struct qmap_cmask held_shared; /* cids held shared (ENQ_IMMED only): self-local */ From 662ade4de9ff5eceb0820a9f8e9fac70ba6a815b Mon Sep 17 00:00:00 2001 From: Quanye Yang Date: Mon, 31 Aug 2026 20:30:58 +0800 Subject: [PATCH 0131/1417] RDMA/ucma: Serialize join and leave on copy_to_user failure rdma_join_multicast() queues RoCE work that later reads the ucma_multicast through event->param.ud.private_data, then list_add()s the CMA multicast at the head of id_priv->mc_list. rdma_leave_multicast() matches only by sockaddr and destroys the first hit. ucma_process_join() used to drop ctx->mutex after a successful join and retake it only if copy_to_user() failed. Two concurrent JOIN_MCAST calls with the same address can therefore insert a second CMA entry before the first thread's leave. leave then cancels the newer work and the older worker still dereferences the ucma_multicast that the first thread frees. Keep ctx->mutex held from rdma_join_multicast() through copy_to_user() and, on -EFAULT, through rdma_leave_multicast() so leave cannot miss this join. Do not leave if join itself failed: that path never published this address on mc_list, and a leave-by-addr would destroy an earlier successful join. Reported-by: syzbot+a6ffe86390c8a6afc818@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a6ffe86390c8a6afc818 Fixes: fe454dc31e84 ("RDMA/ucma: Fix use-after-free bug in ucma_create_uevent") Cc: stable@vger.kernel.org Signed-off-by: Quanye Yang Link: https://patch.msgid.link/20260831-rdma-ucma-mc-uaf-v1-1-b8eeb7046aff@proton.me Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/ucma.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/infiniband/core/ucma.c b/drivers/infiniband/core/ucma.c index 4929636f7c5316..a15182f7a7c5ba 100644 --- a/drivers/infiniband/core/ucma.c +++ b/drivers/infiniband/core/ucma.c @@ -1556,9 +1556,10 @@ static ssize_t ucma_process_join(struct ucma_file *file, mutex_lock(&ctx->mutex); ret = rdma_join_multicast(ctx->cm_id, (struct sockaddr *)&mc->addr, join_state, mc); - mutex_unlock(&ctx->mutex); - if (ret) + if (ret) { + mutex_unlock(&ctx->mutex); goto err_xa_erase; + } resp.id = mc->id; if (copy_to_user(u64_to_user_ptr(cmd->response), @@ -1566,6 +1567,7 @@ static ssize_t ucma_process_join(struct ucma_file *file, ret = -EFAULT; goto err_leave_multicast; } + mutex_unlock(&ctx->mutex); xa_store(&multicast_table, mc->id, mc, 0); @@ -1573,7 +1575,6 @@ static ssize_t ucma_process_join(struct ucma_file *file, return 0; err_leave_multicast: - mutex_lock(&ctx->mutex); rdma_leave_multicast(ctx->cm_id, (struct sockaddr *) &mc->addr); mutex_unlock(&ctx->mutex); ucma_cleanup_mc_events(mc); From 9a141d3dc869d18b2eab35e999f4790a9b84e40f Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Wed, 2 Sep 2026 17:06:32 +0300 Subject: [PATCH 0132/1417] IB/IPoIB: Avoid restoring OPER_UP after multicast flush ipoib_ib_dev_flush_light() temporarily clears IPOIB_FLAG_OPER_UP to prevent multicast joins while ipoib_mcast_dev_flush() is running, and restores the flag afterwards if it was previously set. This restore races with ipoib_ib_dev_down(). If the interface is brought down while the flush is in progress, ipoib_ib_dev_down() clears IPOIB_FLAG_OPER_UP, but the flush path may set it again after the device has already gone down. Since commit 894021a75291 ("IB/ipoib: Make the carrier_on_task race aware"), ipoib_mcast_carrier_on_task() relies on IPOIB_FLAG_OPER_UP being cleared to terminate its rtnl_trylock() retry loop. If the flag is left set after shutdown, the workqueue retries forever, causing teardown to deadlock when ipoib_ndo_uninit() waits in destroy_workqueue() while holding RTNL. Instead of overloading IPOIB_FLAG_OPER_UP to block multicast joins during a light flush, introduce a dedicated IPOIB_FLAG_MCAST_FLUSH flag. Use it together with IPOIB_FLAG_OPER_UP to determine whether multicast joins are allowed, avoiding the race with device shutdown. Fixes: 344bacca8cd8 ("IB/ipoib: Don't allow MC joins during light MC flush") Reported-by: Ben Davies Signed-off-by: Carolina Jubran Reviewed-by: Cosmin Ratiu Signed-off-by: Edward Srouji Link: https://patch.msgid.link/20260902-avoid-rest-oper-up-v1-1-04fcd4916cae@nvidia.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/ipoib/ipoib.h | 7 +++++++ drivers/infiniband/ulp/ipoib/ipoib_ib.c | 12 +++++++----- drivers/infiniband/ulp/ipoib/ipoib_multicast.c | 16 ++++++++-------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/drivers/infiniband/ulp/ipoib/ipoib.h b/drivers/infiniband/ulp/ipoib/ipoib.h index 91f866e3fb8bd2..143e03b6490210 100644 --- a/drivers/infiniband/ulp/ipoib/ipoib.h +++ b/drivers/infiniband/ulp/ipoib/ipoib.h @@ -87,6 +87,7 @@ enum { IPOIB_FLAG_INITIALIZED = 1, IPOIB_FLAG_ADMIN_UP = 2, IPOIB_PKEY_ASSIGNED = 3, + IPOIB_FLAG_MCAST_FLUSH = 4, IPOIB_FLAG_SUBINTERFACE = 5, IPOIB_STOP_REAPER = 7, IPOIB_FLAG_ADMIN_CM = 9, @@ -414,6 +415,12 @@ struct ipoib_dev_priv { const struct net_device_ops *rn_ops; }; +static inline bool ipoib_mcast_allowed(struct ipoib_dev_priv *priv) +{ + return test_bit(IPOIB_FLAG_OPER_UP, &priv->flags) && + !test_bit(IPOIB_FLAG_MCAST_FLUSH, &priv->flags); +} + struct ipoib_ah { struct net_device *dev; struct ib_ah *ah; diff --git a/drivers/infiniband/ulp/ipoib/ipoib_ib.c b/drivers/infiniband/ulp/ipoib/ipoib_ib.c index 5061d52a7b12c7..81bbb3f7c11321 100644 --- a/drivers/infiniband/ulp/ipoib/ipoib_ib.c +++ b/drivers/infiniband/ulp/ipoib/ipoib_ib.c @@ -1227,17 +1227,19 @@ static void __ipoib_ib_dev_flush(struct ipoib_dev_priv *priv, } if (level == IPOIB_FLUSH_LIGHT) { - int oper_up; ipoib_mark_paths_invalid(dev); - /* Set IPoIB operation as down to prevent races between: + /* Set MCAST_FLUSH to prevent races between: * the flush flow which leaves MCG and on the fly joins * which can happen during that time. mcast restart task * should deal with join requests we missed. + * + * Do not clear OPER_UP for this; restoring it races with + * ipoib_ib_dev_down() and can leave OPER_UP set after the + * device is down. */ - oper_up = test_and_clear_bit(IPOIB_FLAG_OPER_UP, &priv->flags); + set_bit(IPOIB_FLAG_MCAST_FLUSH, &priv->flags); ipoib_mcast_dev_flush(dev); - if (oper_up) - set_bit(IPOIB_FLAG_OPER_UP, &priv->flags); + clear_bit(IPOIB_FLAG_MCAST_FLUSH, &priv->flags); ipoib_reap_dead_ahs(priv); } diff --git a/drivers/infiniband/ulp/ipoib/ipoib_multicast.c b/drivers/infiniband/ulp/ipoib/ipoib_multicast.c index 6401af2fd548f1..379b78374e210c 100644 --- a/drivers/infiniband/ulp/ipoib/ipoib_multicast.c +++ b/drivers/infiniband/ulp/ipoib/ipoib_multicast.c @@ -74,7 +74,7 @@ static void __ipoib_mcast_schedule_join_thread(struct ipoib_dev_priv *priv, struct ipoib_mcast *mcast, bool delay) { - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + if (!ipoib_mcast_allowed(priv)) return; /* @@ -469,7 +469,7 @@ static int ipoib_mcast_join(struct net_device *dev, struct ipoib_mcast *mcast) int ret = 0; if (!priv->broadcast || - !test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + !ipoib_mcast_allowed(priv)) return -EINVAL; init_completion(&mcast->done); @@ -555,7 +555,7 @@ void ipoib_mcast_join_task(struct work_struct *work) unsigned long delay_until = 0; struct ipoib_mcast *mcast = NULL; - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + if (!ipoib_mcast_allowed(priv)) return; if (ib_query_port(priv->ca, priv->port, &port_attr)) { @@ -577,7 +577,7 @@ void ipoib_mcast_join_task(struct work_struct *work) netif_addr_unlock_bh(dev); spin_lock_irq(&priv->lock); - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + if (!ipoib_mcast_allowed(priv)) goto out; if (!priv->broadcast) { @@ -749,7 +749,7 @@ void ipoib_mcast_send(struct net_device *dev, u8 *daddr, struct sk_buff *skb) spin_lock_irqsave(&priv->lock, flags); - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags) || + if (!ipoib_mcast_allowed(priv) || !priv->broadcast || !test_bit(IPOIB_MCAST_FLAG_ATTACHED, &priv->broadcast->flags)) { ++dev->stats.tx_dropped; @@ -871,7 +871,7 @@ void ipoib_mcast_restart_task(struct work_struct *work) LIST_HEAD(remove_list); struct ib_sa_mcmember_rec rec; - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + if (!ipoib_mcast_allowed(priv)) /* * shortcut...on shutdown flush is called next, just * let it do all the work @@ -965,9 +965,9 @@ void ipoib_mcast_restart_task(struct work_struct *work) ipoib_mcast_remove_list(&remove_list); /* - * Double check that we are still up + * Double check that we are still up and not flushing */ - if (test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) { + if (ipoib_mcast_allowed(priv)) { spin_lock_irq(&priv->lock); __ipoib_mcast_schedule_join_thread(priv, NULL, 0); spin_unlock_irq(&priv->lock); From 33fb59da49c4c3f5c2ec9f9d4447a56857a02c02 Mon Sep 17 00:00:00 2001 From: Jeffin Philip Date: Fri, 4 Sep 2026 18:44:37 +0530 Subject: [PATCH 0133/1417] RDMA/core: fix refcount bug in iwpm_get_nlmsg_request() iwpm_get_nlmsg_request() initializes refcount _after_ list_add_tail() making it accessible to global list where another CPU can kref_get() on nlmsg_request causing a refcount "addition on 0" bug. Fix this by initializing kref _before_ list_add_tail() so refcount for nlmsg_request can be incremented/decremented normally. In addition, also initialize every field before list_add_tail(). Reported-by: syzbot+bd317784d628820741b5@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=bd317784d628820741b5 Fixes: 30dc5e63d6a5 ("RDMA/core: Add support for iWARP Port Mapper user space service") Cc: stable@vger.kernel.org Signed-off-by: Jeffin Philip Link: https://patch.msgid.link/20260904131437.12917-1-jeffinphilip14@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/iwpm_util.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/core/iwpm_util.c b/drivers/infiniband/core/iwpm_util.c index 990cf928b32a87..51af8c1c49f9e1 100644 --- a/drivers/infiniband/core/iwpm_util.c +++ b/drivers/infiniband/core/iwpm_util.c @@ -314,10 +314,6 @@ struct iwpm_nlmsg_request *iwpm_get_nlmsg_request(__u32 nlmsg_seq, if (!nlmsg_request) return NULL; - spin_lock_irqsave(&iwpm_nlmsg_req_lock, flags); - list_add_tail(&nlmsg_request->inprocess_list, &iwpm_nlmsg_req_list); - spin_unlock_irqrestore(&iwpm_nlmsg_req_lock, flags); - kref_init(&nlmsg_request->kref); kref_get(&nlmsg_request->kref); nlmsg_request->nlmsg_seq = nlmsg_seq; @@ -326,6 +322,11 @@ struct iwpm_nlmsg_request *iwpm_get_nlmsg_request(__u32 nlmsg_seq, nlmsg_request->err_code = 0; sema_init(&nlmsg_request->sem, 1); down(&nlmsg_request->sem); + + spin_lock_irqsave(&iwpm_nlmsg_req_lock, flags); + list_add_tail(&nlmsg_request->inprocess_list, &iwpm_nlmsg_req_list); + spin_unlock_irqrestore(&iwpm_nlmsg_req_lock, flags); + return nlmsg_request; } From 89ff16f0713917303210c560eec5cd0c13bd651f Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 12:53:44 -1000 Subject: [PATCH 0134/1417] sched_ext: scx_qmap: Fix pending partition work handoff qmap can leave partition work pending with no runner. The effective-cap callback publishes its request after failing to acquire part_busy, while redistribute() checks for pending work before releasing it. Either ordering can miss a request arriving as the current runner finishes, delaying the update until the round-robin timer runs. Publish requests before trying to become the runner and release part_busy before checking for more work. Have all holders drain pending requests after releasing it, including the stats flush. Distinguish mask refreshes from repartitions so an effective-cap update only rebuilds the partition when a repartition was also requested. Fixes: e9151ed5c944 ("tools/sched_ext: scx_qmap - Expand hierarchical sub-scheduling") Reported-by: Andrea Righi Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- tools/sched_ext/scx_qmap.bpf.c | 75 ++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index 062bb22ee65c4f..bda56c37acb54f 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -1295,11 +1295,16 @@ struct { __type(value, struct round_robin_timer); } round_robin_timer SEC(".maps"); +enum part_pending_flags { + PART_REFRESH = BIT_U64(0), + PART_REDISTRIBUTE = BIT_U64(1), +}; + /* * Partition update synchronization. qa.part can be written from concurrent * contexts. This single-runner guard admits one writer at a time without * holding a lock across the grant/revoke kfuncs. part_pending coalesces - * repartition requests that arrive while it is held. + * refresh and repartition requests that arrive while it is held. * * They live in .bss, not the arena: rr_advance() runs from a bpf_timer * callback, where the verifier rejects atomic ops on arena memory. @@ -1668,33 +1673,46 @@ __noinline void apply_partition(void) } } -/* - * Recompute the split off the node's held caps and apply it. The contexts this - * runs from (the sub-sched and cgroup callbacks, the rr timer) are not - * serialized by the kernel, so a single runner does the work. A caller that - * finds the guard held leaves part_pending set; the holder drains it before - * releasing, with the rr timer as a backstop. +/** + * execute_partition - Run pending partition updates + * + * The rr timer is the backstop if the loop reaches its iteration limit. */ -static void redistribute(void) +static void execute_partition(void) { + u64 pending; s32 i; - __sync_fetch_and_or(&part_pending, 1); + bpf_for(i, 0, 1024) { + if (!part_try_start()) + break; - if (!part_try_start()) - return; + pending = __sync_fetch_and_and(&part_pending, 0); + if (pending & PART_REDISTRIBUTE) { + /* charge elapsed time before repartitioning */ + account_alloc(); + compute_partition(); + apply_partition(); + } else if (pending & PART_REFRESH) { + refresh_usable(); + } - bpf_for(i, 0, 1024) { - __sync_fetch_and_and(&part_pending, 0); - /* charge elapsed time to the current partition before rebuilding it */ - account_alloc(); - compute_partition(); - apply_partition(); + /* + * Requests are published before trying the guard. Releasing it + * before checking pending work ensures a racing request is + * either observed here or handled by a caller that acquires the + * guard. + */ + part_end(); if (!__sync_fetch_and_or(&part_pending, 0)) break; } +} - part_end(); +static void redistribute(void) +{ + __sync_fetch_and_or(&part_pending, PART_REDISTRIBUTE); + execute_partition(); } /* @@ -1708,6 +1726,7 @@ int flush_alloc(void *ctx) if (part_try_start()) { account_alloc(); part_end(); + execute_partition(); } return 0; } @@ -1765,9 +1784,7 @@ static void rr_advance(void) part_end(); - /* a resplit queued while we held the guard supersedes this rotation */ - if (__sync_fetch_and_or(&part_pending, 0)) - redistribute(); + execute_partition(); } /* advance the time-shared cid pool every round_robin_ns */ @@ -2041,20 +2058,8 @@ void BPF_STRUCT_OPS(qmap_sub_ecaps_updated, s32 cid, u64 before, u64 after) cmask_clear(cid, &qa.avail_cids.mask); } - /* - * When another runner holds the partition guard, set part_pending: - * redistribute() drains it before releasing and rr_advance() checks it - * after, so the deferred refresh lands by the next rr tick. A - * repartition that lost the guard to us runs here. - */ - if (part_try_start()) { - refresh_usable(); - part_end(); - if (__sync_fetch_and_or(&part_pending, 0)) - redistribute(); - } else { - __sync_fetch_and_or(&part_pending, 1); - } + __sync_fetch_and_or(&part_pending, PART_REFRESH); + execute_partition(); } SCX_OPS_CID_DEFINE(qmap_ops, From ad7265b29995ff12b2ce834a6a6162d616462362 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 7 Sep 2026 09:55:44 +0200 Subject: [PATCH 0135/1417] .get_maintainer.ignore: add myself Since I've touched so many things all over I get CC'ed on far too many things - add myself here to avoid that. I'm also listed in MAINTAINERS for the right things. Signed-off-by: Johannes Berg --- .get_maintainer.ignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.get_maintainer.ignore b/.get_maintainer.ignore index 5ad082b4dd0360..d19b725fd803b9 100644 --- a/.get_maintainer.ignore +++ b/.get_maintainer.ignore @@ -4,6 +4,8 @@ Alyssa Rosenzweig Askar Safin Christoph Hellwig Jeff Kirsher +Johannes Berg +Johannes Berg Marc Gonzalez Nathan Chancellor Ralf Baechle From cdb669a3b8f844aca71fc3224990157d61562165 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Mon, 24 Aug 2026 11:14:19 +0800 Subject: [PATCH 0136/1417] HID: quirks: add ALWAYS_POLL quirk for SDINNOVATION gaming keyboard The SDINNOVATION gaming keyboard (USB ID 36ae:feab) stops reporting input events after its RGB lighting mode is switched about twice. Disabling USB autosuspend and unbinding the other HID interfaces make no difference; the issue does not occur on Windows. HID_QUIRK_ALWAYS_POLL alone resolves it, verified on 7.1.8 via usbhid.quirks=0x36ae:0xfeab:0x400. Reported-by: Marco Carvalho Link: https://bugzilla.redhat.com/show_bug.cgi?id=2514627 Cc: stable@vger.kernel.org Signed-off-by: Junjie Cao Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 3 +++ drivers/hid/hid-quirks.c | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index b3aca5aa917677..8b4f4b02aec016 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1296,6 +1296,9 @@ #define USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD 0xa006 #define USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD 0xa064 +#define USB_VENDOR_ID_SDINNOVATION 0x36ae +#define USB_DEVICE_ID_SDINNOVATION_GAMING_KBD 0xfeab + #define USB_VENDOR_ID_SEMICO 0x1a2c #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD 0x0023 #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD2 0x0027 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 8a0b51d47040c3..c8c6b29fc04d08 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -186,6 +186,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_2), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_PRO), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X65), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SDINNOVATION, USB_DEVICE_ID_SDINNOVATION_GAMING_KBD), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD2), HID_QUIRK_NO_INIT_REPORTS }, { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS }, { HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET }, From 3b55f350c68a0aceff108f47f9d31f47ebffaf7b Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Mon, 7 Sep 2026 14:10:24 +0200 Subject: [PATCH 0137/1417] bpf: Fix bpf_skb_change_tail wrt csum partial skbs Cilium generates ICMP "frag needed" replies from BPF when a LB DSR packet exceeds the egress MTU. The reply is built by first trimming the packet down to target size via bpf_skb_change_tail(), and then pushing the ICMP error headers in front of it. The trim is rejected for skbs which carry a checksum offload, e.g. TCP packets aggregated by GRO on ingress where tcp_gro_complete() leaves the skb as CHECKSUM_PARTIAL. __bpf_skb_min_len() raises the minimum length to the end of the L4 checksum field, so a trim to 42 bytes bails out with -EINVAL given a min_len of 52 in this case, and due to that the ICMP generator fails. This is not the case if GRO is turned off. Fix this bpf_skb_change_tail() restriction and drop the checksum offload when the new length no longer covers the checksum field. The BPF program rewrites the skb into an ICMP error and computes the checksum itself anyway. Fixes: 5293efe62df8 ("bpf: add bpf_skb_change_tail helper") Reported-by: Tom Hadlaw Reported-by: Yusuke Suzuki Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260907121025.1923656-1-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- net/core/filter.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 61940e75355233..8513167a858a87 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -3961,12 +3961,6 @@ static u32 __bpf_skb_min_len(const struct sk_buff *skb) if (offset > 0) min_len = offset; } - if (skb->ip_summed == CHECKSUM_PARTIAL) { - offset = skb_checksum_start_offset(skb) + - skb->csum_offset + sizeof(__sum16); - if (offset > 0) - min_len = offset; - } return min_len; } @@ -3983,6 +3977,11 @@ static int bpf_skb_grow_rcsum(struct sk_buff *skb, unsigned int new_len) static int bpf_skb_trim_rcsum(struct sk_buff *skb, unsigned int new_len) { + if (skb->ip_summed == CHECKSUM_PARTIAL && + new_len < skb_checksum_start_offset(skb) + skb->csum_offset + + sizeof(__sum16)) + skb->ip_summed = CHECKSUM_NONE; + return __skb_trim_rcsum(skb, new_len); } From 15e2565f1c43771af0bc5324971cabaad79ac286 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Mon, 7 Sep 2026 14:10:25 +0200 Subject: [PATCH 0138/1417] selftests/bpf: Add test for bpf_skb_change_tail on csum partial skbs Add a test which builds an ICMP error out of a TCP segment. A tcx prog on the client's egress side trims the first data segment down to the target size and pushes the ICMP error headers in front of it to then reflect the packet back to the sender. # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t tc_change_tail [...] #509 tc_change_tail:OK #510 tc_change_tail_pmtu:OK Summary: 2/0 PASSED, 0 SKIPPED, 0/0 FAILED Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260907121025.1923656-2-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- .../bpf/prog_tests/tc_change_tail_pmtu.c | 125 +++++++++++++++++ .../bpf/progs/test_tc_change_tail_pmtu.c | 129 ++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c create mode 100644 tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c diff --git a/tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c b/tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c new file mode 100644 index 00000000000000..7acdbd5757a988 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#include "test_progs.h" +#include "network_helpers.h" +#include "test_tc_change_tail_pmtu.skel.h" + +#define CLIENT_NS "tc-change-tail-cli-ns" +#define SERVER_NS "tc-change-tail-srv-ns" +#define CLIENT_IP "192.168.1.1" +#define SERVER_IP "192.168.1.2" + +#define TEST_PMTU 1000 +#define TEST_MSS_MAX (TEST_PMTU - 20 - 20) +#define TIMEOUT_MS 3000 +#define XFER_BYTES 8192 + +void test_tc_change_tail_pmtu(void) +{ + LIBBPF_OPTS(bpf_tcx_opts, tcx_opts); + int mss_before = 0, mss_after = 0, ifindex, port; + int srv_fd = -1, srv_conn_fd = -1, cli_fd = -1; + struct test_tc_change_tail_pmtu *skel = NULL; + struct nstoken *nstoken = NULL; + static char buf[XFER_BYTES]; + socklen_t optlen; + ssize_t bytes; + size_t total; + + if (!ASSERT_OK(make_netns(CLIENT_NS), "make client ns")) + return; + if (!ASSERT_OK(make_netns(SERVER_NS), "make server ns")) + goto out_client_ns; + + nstoken = open_netns(CLIENT_NS); + if (!ASSERT_OK_PTR(nstoken, "open client ns")) + goto out; + SYS(out, "ip link add veth1 type veth peer name veth2 netns " SERVER_NS); + SYS(out, "ip -4 addr add " CLIENT_IP "/24 dev veth1"); + SYS(out, "ip link set veth1 up"); + ifindex = if_nametoindex("veth1"); + if (!ASSERT_NEQ(ifindex, 0, "if_nametoindex")) + goto out; + close_netns(nstoken); + nstoken = NULL; + + nstoken = open_netns(SERVER_NS); + if (!ASSERT_OK_PTR(nstoken, "open server ns")) + goto out; + SYS(out, "ip -4 addr add " SERVER_IP "/24 dev veth2"); + SYS(out, "ip link set veth2 up"); + srv_fd = start_server(AF_INET, SOCK_STREAM, SERVER_IP, 0, TIMEOUT_MS); + if (!ASSERT_OK_FD(srv_fd, "start server")) + goto out; + close_netns(nstoken); + nstoken = NULL; + + skel = test_tc_change_tail_pmtu__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open and load skeleton")) + goto out; + + port = get_socket_local_port(srv_fd); + if (!ASSERT_GE(port, 0, "get server port")) + goto out; + + skel->bss->server_port = port; + skel->bss->pmtu = TEST_PMTU; + + nstoken = open_netns(CLIENT_NS); + if (!ASSERT_OK_PTR(nstoken, "open client ns")) + goto out; + + skel->links.change_tail_icmp = + bpf_program__attach_tcx(skel->progs.change_tail_icmp, ifindex, + &tcx_opts); + if (!ASSERT_OK_PTR(skel->links.change_tail_icmp, "attach tcx")) + goto out; + + cli_fd = connect_to_fd(srv_fd, TIMEOUT_MS); + if (!ASSERT_OK_FD(cli_fd, "connect to server")) + goto out; + srv_conn_fd = accept(srv_fd, NULL, NULL); + if (!ASSERT_OK_FD(srv_conn_fd, "accept connection")) + goto out; + if (!ASSERT_OK(settimeo(srv_conn_fd, TIMEOUT_MS), "set server timeout")) + goto out; + + optlen = sizeof(mss_before); + if (!ASSERT_OK(getsockopt(cli_fd, IPPROTO_TCP, TCP_MAXSEG, &mss_before, + &optlen), "get mss before")) + goto out; + + bytes = send(cli_fd, buf, sizeof(buf), 0); + if (!ASSERT_EQ(bytes, (ssize_t)sizeof(buf), "send data")) + goto out; + + for (total = 0; total < sizeof(buf); total += bytes) { + bytes = recv(srv_conn_fd, buf, sizeof(buf), 0); + if (bytes <= 0) + break; + } + + ASSERT_EQ(total, sizeof(buf), "receive data"); + ASSERT_OK(skel->data->change_tail_ret, "change tail"); + ASSERT_OK(skel->bss->adjust_room_ret, "adjust room"); + ASSERT_TRUE(skel->bss->icmp_sent, "icmp sent"); + + optlen = sizeof(mss_after); + if (!ASSERT_OK(getsockopt(cli_fd, IPPROTO_TCP, TCP_MAXSEG, &mss_after, + &optlen), "get mss after")) + goto out; + + ASSERT_LT(mss_after, mss_before, "mss reduced"); + ASSERT_LE(mss_after, TEST_MSS_MAX, "mss below pmtu"); +out: + close(srv_conn_fd); + close(cli_fd); + close(srv_fd); + test_tc_change_tail_pmtu__destroy(skel); + close_netns(nstoken); + remove_netns(SERVER_NS); +out_client_ns: + remove_netns(CLIENT_NS); +} diff --git a/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c b/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c new file mode 100644 index 00000000000000..5c4c07545bc9f2 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ICMP_SAMPLE_LEN (sizeof(struct iphdr) + 8) +#define ICMP_HDRS_LEN (sizeof(struct iphdr) + sizeof(struct icmphdr)) + +__be16 server_port = 0; +__u16 pmtu = 0; + +long change_tail_ret = 1; +long adjust_room_ret = 0; +bool icmp_sent = false; +bool icmp_err = false; + +static __always_inline __sum16 csum_fold(__wsum csum) +{ + csum = (csum & 0xffff) + (csum >> 16); + csum = (csum & 0xffff) + (csum >> 16); + + return (__sum16)~csum; +} + +SEC("tc/egress") +int change_tail_icmp(struct __sk_buff *skb) +{ + __u8 smac[ETH_ALEN], dmac[ETH_ALEN]; + void *data, *data_end; + struct icmphdr *icmp; + struct ethhdr *eth; + struct tcphdr *tcp; + __be32 saddr, daddr; + struct iphdr *ip; + __wsum csum; + + if (icmp_sent || icmp_err) + return TCX_PASS; + + data = (void *)(long)skb->data; + data_end = (void *)(long)skb->data_end; + + eth = data; + if ((void *)(eth + 1) > data_end) + return TCX_PASS; + if (eth->h_proto != bpf_htons(ETH_P_IP)) + return TCX_PASS; + + ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end) + return TCX_PASS; + if (ip->ihl != 5 || ip->protocol != IPPROTO_TCP) + return TCX_PASS; + + tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return TCX_PASS; + if (tcp->dest != server_port) + return TCX_PASS; + if (bpf_ntohs(ip->tot_len) <= sizeof(*ip) + tcp->doff * 4) + return TCX_PASS; + + __builtin_memcpy(smac, eth->h_source, ETH_ALEN); + __builtin_memcpy(dmac, eth->h_dest, ETH_ALEN); + saddr = ip->saddr; + daddr = ip->daddr; + + change_tail_ret = bpf_skb_change_tail(skb, ETH_HLEN + ICMP_SAMPLE_LEN, 0); + if (change_tail_ret) { + icmp_err = true; + return TCX_PASS; + } + + adjust_room_ret = bpf_skb_adjust_room(skb, ICMP_HDRS_LEN, + BPF_ADJ_ROOM_MAC, + BPF_F_ADJ_ROOM_NO_CSUM_RESET); + if (adjust_room_ret) { + icmp_err = true; + return TCX_DROP; + } + + data = (void *)(long)skb->data; + data_end = (void *)(long)skb->data_end; + + eth = data; + ip = (void *)(eth + 1); + icmp = (void *)(ip + 1); + if ((void *)icmp + sizeof(*icmp) + ICMP_SAMPLE_LEN > data_end) { + icmp_err = true; + return TCX_DROP; + } + + __builtin_memcpy(eth->h_dest, smac, ETH_ALEN); + __builtin_memcpy(eth->h_source, dmac, ETH_ALEN); + + __builtin_memset(icmp, 0, sizeof(*icmp)); + icmp->type = ICMP_DEST_UNREACH; + icmp->code = ICMP_FRAG_NEEDED; + icmp->un.frag.mtu = bpf_htons(pmtu); + + __builtin_memset(ip, 0, sizeof(*ip)); + ip->version = 4; + ip->ihl = 5; + ip->ttl = 64; + ip->protocol = IPPROTO_ICMP; + ip->tot_len = bpf_htons(ICMP_HDRS_LEN + ICMP_SAMPLE_LEN); + ip->saddr = daddr; + ip->daddr = saddr; + + csum = bpf_csum_diff(NULL, 0, (__be32 *)icmp, + sizeof(*icmp) + ICMP_SAMPLE_LEN, 0); + icmp->checksum = csum_fold(csum); + csum = bpf_csum_diff(NULL, 0, (__be32 *)ip, sizeof(*ip), 0); + ip->check = csum_fold(csum); + icmp_sent = true; + return bpf_redirect(skb->ifindex, BPF_F_INGRESS); +} + +char _license[] SEC("license") = "GPL"; From b7c0f8436f077e7f66c9f07714bd57068e2a0c31 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Fri, 21 Aug 2026 01:13:25 -0700 Subject: [PATCH 0139/1417] drm/msm/adreno: Only check for PAS when a zap shader is present Commit 0be72be03ca7 ("drm/msm: Switch to generic PAS TZ APIs") replaced the qcom_scm_is_available() check in adreno_zap_shader_load() with qcom_pas_is_available(). These are not equivalent: the former reports whether the SCM transport is up, the latter whether the TrustZone firmware implements the peripheral authentication service. On SC7180 Chromebooks (trogdor) TZ does not implement PAS at all. SCM call-availability queries return 0 for every PAS command while other services answer normally: svc 0x06 cmd 0x01 IS_CALL_AVAIL -> 1 svc 0x02 cmd 0x01 PAS_INIT_IMAGE -> 0 svc 0x02 cmd 0x05 PAS_AUTH_RESET -> 0 svc 0x02 cmd 0x07 PAS_IS_SUPPORTED -> 0 svc 0x0c cmd 0x16 MP_ASSIGN -> 1 svc 0x05 cmd 0x01 IO_READ -> 1 so qcom_scm_probe() never registers a PAS backend and qcom_pas_is_available() is false for the lifetime of the boot. That on its own need not matter, because sc7180-trogdor.dtsi does /delete-node/ &gpu_zap_shader;, and the intended path for such a board is for zap_shader_load_mdt() to find no zap-shader child, clear zap_available, return -ENODEV, and let the caller fall back to SECVID_TRUST_CNTL. The problem is the ordering. zap_available is a static initialised to true and is only ever cleared inside zap_shader_load_mdt(), but adreno_zap_shader_load() consults PAS before calling it. The discovery that decides whether a zap shader is needed at all can therefore never run, the flag is never cleared, and every call returns -EPROBE_DEFER: adreno 5000000.gpu: [drm:adreno_zap_shader_load] *ERROR* PAS is not available msm_dpu ae01000.display-controller: [drm:adreno_load_gpu] *ERROR* gpu hw init failed: -517 Nothing retries that deferral, either. adreno_zap_shader_load() is called from a6xx_hw_init() rather than from probe, so the -EPROBE_DEFER is not a probe return value: it propagates up until adreno_load_gpu() returns NULL. load_gpu() re-attempts on every DRM open while priv->gpu is NULL, each open fails identically, and PAS cannot become available in between - which is why the error repeats and userspace stays on llvmpipe. Move the availability check into zap_shader_load_mdt(), behind the zap-shader node lookup, so the driver only consults PAS once it knows it needs PAS. Boards with no zap-shader node take the intended -ENODEV fallback without ever asking, and boards that do have one keep the qcom_pas_is_available() gate. Fixes: 0be72be03ca7 ("drm/msm: Switch to generic PAS TZ APIs") Link: https://lore.kernel.org/r/20260808034716.58888-1-phollinsky@holtechnik.com Signed-off-by: Paul Hollinsky Reviewed-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/747583/ Message-ID: <20260821081325.89088-1-phollinsky@holtechnik.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index 8cd2020d4b7e99..ca5e4e560cdeda 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -52,6 +52,12 @@ static int zap_shader_load_mdt(struct msm_gpu *gpu, const char *fwname, return -ENODEV; } + /* We need PAS to be able to load the firmware */ + if (!qcom_pas_is_available()) { + DRM_DEV_ERROR(dev, "PAS is not available\n"); + return -EPROBE_DEFER; + } + ret = of_reserved_mem_region_to_resource(np, 0, &r); if (ret) { zap_available = false; @@ -170,18 +176,11 @@ static int zap_shader_load_mdt(struct msm_gpu *gpu, const char *fwname, int adreno_zap_shader_load(struct msm_gpu *gpu, u32 pasid) { struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu); - struct platform_device *pdev = gpu->pdev; /* Short cut if we determine the zap shader isn't available/needed */ if (!zap_available) return -ENODEV; - /* We need PAS to be able to load the firmware */ - if (!qcom_pas_is_available()) { - DRM_DEV_ERROR(&pdev->dev, "PAS is not available\n"); - return -EPROBE_DEFER; - } - return zap_shader_load_mdt(gpu, adreno_gpu->info->zapfw, pasid); } From a7783e585360ee05dfe21d3173dbbe985c94f29e Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:01 +0200 Subject: [PATCH 0140/1417] wifi: cfg80211: don't get the radio mask for netdev-less wdevs cfg80211_calculate_bi_data() calls rdev_get_radio_mask() with wdev->netdev, which can be NULL and then crashes in mac80211. To avoid that, invert the order of checks since wdev->netdev is always valid for beaconing interfaces. Assisted-by: LLM Fixes: abb4cfe3661a ("wifi: cfg80211: extend interface combination check for multi-radio") Reported-by: syzbot+abff43d2d045e37c0bb2@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=abff43d2d045e37c0bb2 Link: https://patch.msgid.link/20260904165614.2056a8b7dc91.I7412c5062d8166ad6c81ee7252cec49dea19a60f@changeid Signed-off-by: Johannes Berg --- net/wireless/util.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/net/wireless/util.c b/net/wireless/util.c index 3e584d0ca3e266..408ebb10924fbd 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -2477,16 +2477,15 @@ static void cfg80211_calculate_bi_data(struct wiphy *wiphy, u32 new_beacon_int, if (wdev->valid_links) continue; + wdev_bi = cfg80211_wdev_bi(wdev); + if (!wdev_bi) + continue; + /* skip wdevs not active on the given wiphy radio */ if (radio_idx >= 0 && !(rdev_get_radio_mask(rdev, wdev->netdev) & BIT(radio_idx))) continue; - wdev_bi = cfg80211_wdev_bi(wdev); - - if (!wdev_bi) - continue; - if (!*beacon_int_gcd) { *beacon_int_gcd = wdev_bi; continue; From 48b2c5c628b09cf36cbeca53e0432fc2a7518be7 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:02 +0200 Subject: [PATCH 0141/1417] wifi: cfg80211: check IP header size in cfg80211_classify8021d() A frame that looks like IP can be transmitted, but be too short, so the DS field is read incorrectly: BUG: KMSAN: uninit-value in cfg80211_classify8021d+0x99d/0x12b0 net/wireless/util.c:1027 cfg80211_classify8021d+0x99d/0x12b0 net/wireless/util.c:1027 ieee80211_select_queue+0x37a/0x9e0 net/mac80211/wme.c:180 __ieee80211_subif_start_xmit+0x60f/0x1d90 net/mac80211/tx.c:4304 ieee80211_subif_start_xmit+0xa8/0x6d0 net/mac80211/tx.c:4538 ... packet_sendmsg+0x9173/0xa2a0 net/packet/af_packet.c:3108 Use skb_header_pointer() like the MPLS case. Assisted-by: LLM Fixes: e31a16d6f64e ("wireless: move some utility functions from mac80211 to cfg80211") Reported-by: syzbot+878ddc3962f792e9af59@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=878ddc3962f792e9af59 Link: https://patch.msgid.link/20260904165614.5e61a4c80b92.I37d68d3f406cb3b90b32e6943418d66070b65197@changeid Signed-off-by: Johannes Berg --- net/wireless/util.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/net/wireless/util.c b/net/wireless/util.c index 408ebb10924fbd..5429cf3cfd2ffd 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -1039,12 +1039,30 @@ unsigned int cfg80211_classify8021d(struct sk_buff *skb, } switch (skb->protocol) { - case htons(ETH_P_IP): - dscp = ipv4_get_dsfield(ip_hdr(skb)) & 0xfc; + case htons(ETH_P_IP): { + const struct iphdr *iph; + struct iphdr _iph; + + iph = skb_header_pointer(skb, sizeof(struct ethhdr), + sizeof(*iph), &_iph); + if (!iph) + return 0; + + dscp = ipv4_get_dsfield(iph) & 0xfc; break; - case htons(ETH_P_IPV6): - dscp = ipv6_get_dsfield(ipv6_hdr(skb)) & 0xfc; + } + case htons(ETH_P_IPV6): { + const struct ipv6hdr *ip6h; + struct ipv6hdr _ip6h; + + ip6h = skb_header_pointer(skb, sizeof(struct ethhdr), + sizeof(*ip6h), &_ip6h); + if (!ip6h) + return 0; + + dscp = ipv6_get_dsfield(ip6h) & 0xfc; break; + } case htons(ETH_P_MPLS_UC): case htons(ETH_P_MPLS_MC): { struct mpls_label mpls_tmp, *mpls; From aba7b41faeecb7692458095ce6fafc341fe0b80e Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 1 Sep 2026 11:10:19 +0800 Subject: [PATCH 0142/1417] soundwire: cadence_master: wait and cancel cdns->work before clock stop A peripheral event could happen during the clock stop process. We need to wait for the event be handled before stopping the bus clock. Otherwise, we will get the IO transfer timed out issue. Fixes: af4cc917826f ("soundwire: cadence: mask Slave interrupt before stopping clock") Signed-off-by: Bard Liao Reviewed-by: David Lin Reviewed-by: Shuming Fan Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260901031019.233254-1-yung-chuan.liao@linux.intel.com Signed-off-by: Vinod Koul --- drivers/soundwire/cadence_master.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index e690237fe98192..a0a63b76793e29 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -1701,6 +1701,13 @@ int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake) return 0; } + /* + * wait for any in-flight peripheral event handling to complete before stopping the clock. + * No need to disable peripheral interrupts before canceling the work, as the peripheral + * interrupts are already masked before the work is scheduled. + */ + cancel_work_sync(&cdns->work); + /* * Before entering clock stop we mask the Slave * interrupts. This helps avoid having to deal with e.g. a From fb5a08026a68be0ec5f660588311fb1091bf5d58 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 16 Aug 2026 00:51:09 +0100 Subject: [PATCH 0143/1417] mips: econet: fix unmet dependencies for ECONET ECONET selects EARLY_PRINTK_8250, SERIAL_OF_PLATFORM, and SERIAL_8250 without ensuring their dependencies, EARLY_PRINTK and TTY are met. This causes unmet dependencies: WARNING: unmet direct dependencies detected for SERIAL_8250 Depends on [n]: TTY [=n] && HAS_IOMEM [=y] && !S390 Selected by [y]: - ECONET [=y] WARNING: unmet direct dependencies detected for EARLY_PRINTK_8250 Depends on [n]: EARLY_PRINTK [=n] && USE_GENERIC_EARLY_PRINTK_8250 [=y] Selected by [y]: - ECONET [=y] WARNING: unmet direct dependencies detected for SERIAL_OF_PLATFORM Depends on [n]: TTY [=n] && HAS_IOMEM [=y] && SERIAL_8250 [=y] && OF [=y] Selected by [y]: - ECONET [=y] However, in the discussion of v1 of this patch, Arnd pointed out that these selects don't belong here in the first place. [1] Let's remove them to resolve the unmet dependencies. These unmet dependencies were found by kconfirm, a static analysis tool for Kconfig. Fixes: 79ee1d20e37c ("mips: econet: Fix incorrect Kconfig dependencies") Fixes: 35fb26f94dfa ("mips: Add EcoNet MIPS platform support") Suggested-by: Arnd Bergmann Signed-off-by: Julian Braha Tested-by: Caleb James DeLisle Signed-off-by: Thomas Bogendoerfer --- arch/mips/Kconfig | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index e2eb9627bd14cc..c3f69def2008cc 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -396,10 +396,7 @@ config ECONET bool "EcoNet MIPS family" select BOOT_RAW select DEBUG_ZBOOT if DEBUG_KERNEL - select EARLY_PRINTK_8250 select ECONET_EN751221_TIMER - select SERIAL_8250 - select SERIAL_OF_PLATFORM select SYS_SUPPORTS_BIG_ENDIAN select SYS_HAS_CPU_MIPS32_R1 select SYS_HAS_CPU_MIPS32_R2 From e261bfd3c042bccd25c82b04de079bdf8bdd8c3a Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Mon, 17 Aug 2026 22:03:44 +0000 Subject: [PATCH 0144/1417] MIPS: config: Add EcoNet EN751221 defconfig Add config based on OpenWrt for EN751221 boards. Of note: EcoNet bootloader has a 7.2MB kernel size limit so ZBOOT is assumed to be needed in all cases. Signed-off-by: Caleb James DeLisle Tested-by: Randy Dunlap Signed-off-by: Thomas Bogendoerfer --- MAINTAINERS | 1 + arch/mips/configs/econet_en751221_defconfig | 264 ++++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 arch/mips/configs/econet_en751221_defconfig diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c9d..9b9e20d039f6f4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9324,6 +9324,7 @@ F: Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.y F: Documentation/devicetree/bindings/mips/econet.yaml F: Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml F: arch/mips/boot/dts/econet/ +F: arch/mips/configs/econet_en751221_defconfig F: arch/mips/econet/ F: drivers/clocksource/timer-econet-en751221.c F: drivers/irqchip/irq-econet-en751221.c diff --git a/arch/mips/configs/econet_en751221_defconfig b/arch/mips/configs/econet_en751221_defconfig new file mode 100644 index 00000000000000..b35d33fe40448f --- /dev/null +++ b/arch/mips/configs/econet_en751221_defconfig @@ -0,0 +1,264 @@ +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_KERNEL_XZ=y +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +# CONFIG_CROSS_MEMORY_ATTACH is not set +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_PREEMPT=y +CONFIG_CGROUPS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_RDMA=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_BPF=y +CONFIG_NAMESPACES=y +# CONFIG_TIME_NS is not set +CONFIG_USER_NS=y +CONFIG_BLK_DEV_INITRD=y +# CONFIG_RD_GZIP is not set +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_RD_XZ is not set +# CONFIG_RD_LZO is not set +# CONFIG_RD_LZ4 is not set +# CONFIG_RD_ZSTD is not set +# CONFIG_INITRAMFS_PRESERVE_MTIME is not set +CONFIG_LD_DEAD_CODE_DATA_ELIMINATION=y +CONFIG_EXPERT=y +# CONFIG_SGETMASK_SYSCALL is not set +# CONFIG_IO_URING is not set +# CONFIG_RSEQ is not set +# CONFIG_CACHESTAT_SYSCALL is not set +CONFIG_ECONET=y +CONFIG_CPU_MIPS32_R2=y +CONFIG_ZBOOT_LOAD_ADDRESS=0x80020000 +CONFIG_ARCH_FORCE_MAX_ORDER=11 +# CONFIG_MIPS_FP_SUPPORT is not set +CONFIG_NR_CPUS=2 +CONFIG_HZ_100=y +CONFIG_MIPS_RAW_APPENDED_DTB=y +# CONFIG_SCHED_SMT is not set +CONFIG_JUMP_LABEL=y +# CONFIG_STACKPROTECTOR_STRONG is not set +# CONFIG_GCC_PLUGINS is not set +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +# CONFIG_BLOCK_LEGACY_AUTOLOAD is not set +CONFIG_BLK_DEV_THROTTLING=y +# CONFIG_BLK_DEBUG_FS is not set +CONFIG_PARTITION_ADVANCED=y +CONFIG_OF_PARTITION=y +# CONFIG_MQ_IOSCHED_DEADLINE is not set +# CONFIG_MQ_IOSCHED_KYBER is not set +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_SLAB_FREELIST_RANDOM=y +CONFIG_SLAB_FREELIST_HARDENED=y +# CONFIG_COMPAT_BRK is not set +CONFIG_LRU_GEN=y +CONFIG_LRU_GEN_ENABLED=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_MROUTE=y +CONFIG_IP_MROUTE_MULTIPLE_TABLES=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_SYN_COOKIES=y +# CONFIG_INET_DIAG is not set +CONFIG_TCP_CONG_ADVANCED=y +# CONFIG_TCP_CONG_BIC is not set +# CONFIG_TCP_CONG_WESTWOOD is not set +# CONFIG_TCP_CONG_HTCP is not set +# CONFIG_IPV6_SIT is not set +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y +CONFIG_IPV6_PIMSM_V2=y +CONFIG_IPV6_SEG6_LWTUNNEL=y +CONFIG_MPTCP=y +CONFIG_NETFILTER=y +# CONFIG_NETFILTER_EGRESS is not set +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_MARK=y +CONFIG_NF_CONNTRACK_ZONES=y +CONFIG_NF_CONNTRACK_PROCFS=y +CONFIG_NF_CONNTRACK_TIMEOUT=y +# CONFIG_NF_CT_PROTO_SCTP is not set +CONFIG_NF_TABLES=m +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_NUMGEN=m +CONFIG_NFT_CT=m +CONFIG_NFT_FLOW_OFFLOAD=m +CONFIG_NFT_LOG=m +CONFIG_NFT_LIMIT=m +CONFIG_NFT_MASQ=m +CONFIG_NFT_REDIR=m +CONFIG_NFT_NAT=m +CONFIG_NFT_QUOTA=m +CONFIG_NFT_REJECT=m +CONFIG_NFT_HASH=m +CONFIG_NFT_FIB_INET=m +CONFIG_NF_FLOW_TABLE_INET=m +CONFIG_NF_FLOW_TABLE=m +CONFIG_NFT_FIB_IPV4=m +CONFIG_NF_TABLES_ARP=y +CONFIG_NF_LOG_IPV4=m +CONFIG_NFT_FIB_IPV6=m +CONFIG_NF_LOG_IPV6=m +CONFIG_NF_TABLES_BRIDGE=m +CONFIG_BRIDGE=y +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_FQ_CODEL=y +CONFIG_NET_SCH_DEFAULT=y +CONFIG_DEFAULT_FQ_CODEL=y +CONFIG_NET_SWITCHDEV=y +CONFIG_NET_L3_MASTER_DEV=y +CONFIG_RFKILL=y +# CONFIG_LWTUNNEL_BPF is not set +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCIEAER=y +# CONFIG_PCIEASPM is not set +CONFIG_PCI_MSI=y +# CONFIG_VGA_ARB is not set +CONFIG_PCIE_MEDIATEK=y +CONFIG_MTD=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_CFI=y +CONFIG_MTD_CFI_INTELEXT=y +CONFIG_MTD_CFI_AMDSTD=y +CONFIG_MTD_COMPLEX_MAPPINGS=y +CONFIG_MTD_SPI_NAND=y +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_BEB_LIMIT=13 +CONFIG_MTD_UBI_BLOCK=y +CONFIG_NETDEVICES=y +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_LITEX is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +CONFIG_PPP=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOE=m +CONFIG_PPP_ASYNC=m +# CONFIG_WLAN_VENDOR_ADMTEK is not set +# CONFIG_WLAN_VENDOR_ATH is not set +# CONFIG_WLAN_VENDOR_ATMEL is not set +# CONFIG_WLAN_VENDOR_BROADCOM is not set +# CONFIG_WLAN_VENDOR_INTEL is not set +# CONFIG_WLAN_VENDOR_INTERSIL is not set +# CONFIG_WLAN_VENDOR_MARVELL is not set +# CONFIG_WLAN_VENDOR_MEDIATEK is not set +# CONFIG_WLAN_VENDOR_MICROCHIP is not set +# CONFIG_WLAN_VENDOR_PURELIFI is not set +# CONFIG_WLAN_VENDOR_RALINK is not set +# CONFIG_WLAN_VENDOR_REALTEK is not set +# CONFIG_WLAN_VENDOR_RSI is not set +# CONFIG_WLAN_VENDOR_SILABS is not set +# CONFIG_WLAN_VENDOR_ST is not set +# CONFIG_WLAN_VENDOR_TI is not set +# CONFIG_WLAN_VENDOR_ZYDAS is not set +# CONFIG_WLAN_VENDOR_QUANTENNA is not set +# CONFIG_INPUT is not set +# CONFIG_SERIO is not set +# CONFIG_VT is not set +# CONFIG_LEGACY_PTYS is not set +# CONFIG_LEGACY_TIOCSTI is not set +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_OF_PLATFORM=y +# CONFIG_HW_RANDOM is not set +# CONFIG_DEVMEM is not set +CONFIG_SPI=y +# CONFIG_PTP_1588_CLOCK is not set +CONFIG_GPIOLIB=y +# CONFIG_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_MFD_SYSCON=y +# CONFIG_USB_PCI is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CLASS_MULTICOLOR=y +CONFIG_LEDS_GPIO=m +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_NETDEV=y +CONFIG_STAGING=y +# CONFIG_MIPS_PLATFORM_DEVICES is not set +CONFIG_COMMON_CLK_EN7523=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_RESET_CONTROLLER=y +CONFIG_PHY_ECONET_PCIE=y +# CONFIG_DNOTIFY is not set +CONFIG_FANOTIFY=y +CONFIG_OVERLAY_FS=y +# CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW is not set +# CONFIG_PROC_PAGE_MONITOR is not set +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_SUMMARY=y +CONFIG_JFFS2_FS_XATTR=y +CONFIG_JFFS2_COMPRESSION_OPTIONS=y +CONFIG_UBIFS_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_FILE_DIRECT=y +CONFIG_SQUASHFS_COMPILE_DECOMP_MULTI_PERCPU=y +# CONFIG_SQUASHFS_ZLIB is not set +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_EMBEDDED=y +CONFIG_KEYS=y +CONFIG_INIT_STACK_NONE=y +CONFIG_FORTIFY_SOURCE=y +CONFIG_HARDENED_USERCOPY=y +CONFIG_LIST_HARDENED=y +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_CCM=y +CONFIG_CRYPTO_GCM=y +# CONFIG_CRYPTO_HW is not set +# CONFIG_XZ_DEC_X86 is not set +# CONFIG_XZ_DEC_POWERPC is not set +# CONFIG_XZ_DEC_ARM is not set +# CONFIG_XZ_DEC_ARMTHUMB is not set +# CONFIG_XZ_DEC_ARM64 is not set +# CONFIG_XZ_DEC_SPARC is not set +# CONFIG_XZ_DEC_RISCV is not set +CONFIG_PRINTK_TIME=y +# CONFIG_DEBUG_MISC is not set +CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y +CONFIG_DEBUG_INFO_REDUCED=y +CONFIG_FRAME_WARN=1024 +CONFIG_STRIP_ASM_SYMS=y +CONFIG_MAGIC_SYSRQ=y +# CONFIG_MAGIC_SYSRQ_SERIAL is not set +CONFIG_DEBUG_FS=y +# CONFIG_SLUB_DEBUG is not set +CONFIG_SCHED_STACK_END_CHECK=y +CONFIG_PANIC_ON_OOPS=y +CONFIG_PANIC_TIMEOUT=1 +CONFIG_RCU_CPU_STALL_TIMEOUT=60 +# CONFIG_RCU_TRACE is not set +# CONFIG_FTRACE is not set From bbc448c541ed90d52f9ec22f17394304e3bf170e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Fri, 28 Aug 2026 16:07:33 +0200 Subject: [PATCH 0145/1417] mips: select CONFIG_WEAK_REORDERING_BEYOND_LLSC from CONFIG_EYEQ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On I6500 CPU cores, lld and scd give no ordering guarantees (same as all other instructions). To respect the assumption that arch_cmpxchg() is fully ordered, we must inject sync instructions above and below our lld/scd loops using the already in place WEAK_REORDERING_BEYOND_LLSC infrastructure. Otherwise, bad things can happen: [ 34.054496] CPU 3 Unable to handle kernel paging request at virtual address 0000000000000000, epc == a80000080838e01c, ra == a80000080838dfc4 [ 34.054559] Oops[#1]: [ 34.069561] CPU: 3 UID: 0 PID: 170 Comm: pipe_race Not tainted 7.2.0-rc6-01553-gb73c35220968-dirty #103 VOLUNTARY [ 34.079932] Hardware name: Mobile EyeQ5 MP5 Evaluation board [ 34.085592] $ 0 : 0000000000000000 0000000000000001 0000000000000000 0000000000000000 [ 34.093616] $ 4 : a800000808ee2618 000000000b7a879d 0000000000001000 0000000000000000 [ 34.101638] $ 8 : 0000000000e3f2c9 0000000000000000 a800000808a2a9f8 0000000000000000 [ 34.109660] $12 : a8000008139ffcd8 ffffffff84080018 a80000080837fae0 7878787878787878 [ 34.117682] $16 : a800000807e82940 0000000000001000 0000000000000000 0000000000000000 [ 34.125704] $20 : a800000802920e00 a8000008139ffdf8 a800000802649400 0000000000e3f2c9 [ 34.133726] $24 : 0000000000000006 00000001200406e0 [ 34.141783] $28 : a8000008139fc000 a8000008139ffd10 0000000000e3f2c8 a80000080838dfc4 [ 34.149837] epc : a80000080838e01c anon_pipe_read+0xd4/0x428 [ 34.155697] ra : a80000080838dfc4 anon_pipe_read+0x7c/0x428 [ 34.161549] Status: 140000e3 KX SX UX KERNEL EXL IE [ 34.166551] Cause : 40800408 (ExcCode 02) [ 34.170574] BadVA : 0000000000000000 [ 34.174161] PrId : 0001b028 (MIPS I6500) [ 34.178183] Process pipe_race (pid: 170, threadinfo=000000005ca35720, task=00000000e1013890, tls=000000014ebbb780) [ 34.188568] Stack : a800000802649400 0000000000000000 0000000000000000 a8000008139ffdd0 [ 34.196623] 0000000000000fba a800000808ee0000 0000000000000001 a8000008130c3e80 [ 34.204676] a8000008080d1280 a8000008139ffd58 a8000008139ffd58 1dbd2b22ea1dd500 [ 34.212729] a800000802649400 a800000808ee0000 ffffffffffffffea 0000000000000001 [ 34.220783] 0000000000001000 0000000000000000 00000001200ae518 ffffffffffffffff [ 34.228836] 000000fffbe0e530 a80000080837edf4 000000fffbe0e530 0000000000000000 [ 34.236890] 0000000000000000 0000000000000000 000000014ebb55a0 0000000000001000 [ 34.244943] 0000000000000001 a800000802649400 0000000000000000 0000000000000000 [ 34.252996] 0000000000000000 0000400400000000 0000000000000000 1dbd2b22ea1dd500 [ 34.261049] 00000000140000e3 a800000802649400 a800000802649400 a800000808ee0000 [ 34.269103] ... [ 34.271568] Call Trace: [ 34.274026] [] anon_pipe_read+0xd4/0x428 [ 34.279533] [] vfs_read+0x25c/0x318 [ 34.284607] [] ksys_read+0x104/0x138 [ 34.289763] [] syscall_common+0x44/0x68 [ 34.295187] [ 34.296689] Code: f84000cf 02209825 de020010 d8400004 02002825 0040f809 02802025 f84000c3 [ 34.306504] [ 34.308099] ---[ end trace 0000000000000000 ]--- My initial reproducer was the xdp-tools test suite. A standalone reproducer would be an lld/scd loop that, when the read is reordered by the CPU, triggers a fault. We can achieve this from userspace by stressing an anonymous pipe, which uses a mutex. Program used: // SPDX-License-Identifier: GPL-2.0 // pipe_race.c - reproducer for MIPS LL/SC reordering vs fs/pipe.c // // Two userspace processes on an anonymous pipe: // parent = writer: tight write() loop // child = reader: tight read() loop #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include int main(void) { long wrsize = 70; // bytes per write() long rdsize = 4096; // bytes per read() int pfd[2]; char *buf; pid_t pid; int ret; ret = pipe(pfd); assert(!ret); pid = fork(); assert(pid >= 0); if (pid == 0) { /* reader */ close(pfd[1]); buf = malloc(rdsize); assert(buf); for (;;) { ssize_t n = read(pfd[0], buf, rdsize); if (n < 0 && errno == EINTR) continue; if (n <= 0) _exit(n < 0 ? 1 : 0); } } close(pfd[0]); /* writer */ buf = malloc(wrsize); assert(buf); memset(buf, 'x', wrsize); for (;;) { ssize_t n = write(pfd[1], buf, wrsize); if (n < 0 && errno == EINTR) continue; if (n != wrsize) break; } kill(pid, SIGKILL); wait(NULL); return 0; } Fixes: fbe0fae601b7 ("MIPS: mobileye: Add EyeQ6H support") Cc: stable@vger.kernel.org Signed-off-by: Théo Lebrun Reviewed-by: Jiaxun Yang Signed-off-by: Thomas Bogendoerfer --- arch/mips/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index c3f69def2008cc..d7c67cebe065f9 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -658,6 +658,7 @@ config EYEQ select USB_UHCI_BIG_ENDIAN_MMIO if CPU_BIG_ENDIAN select USE_OF select HOTPLUG_PARALLEL if HOTPLUG_CPU + select WEAK_REORDERING_BEYOND_LLSC help Select this to build a kernel supporting EyeQ SoC from Mobileye. From 126f16e0a1b353c2ba5c7e2c8626cfa865934f9f Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 1 Sep 2026 19:33:55 +0000 Subject: [PATCH 0146/1417] MIPS: Octeon: apply USB FDT fixups also when USB is modular The uctl/usbn device-tree fixups in octeon_prune_device_tree() - which set the board's USB reference-clock frequency and type from __cvmx_helper_board_usb_get_clock_type() - are guarded by "#ifdef CONFIG_USB", which is false when USB is built as a module. The fixups then silently disappear and octeon-hcd sees whatever default the DTS carries (12MHz crystal in octeon_3xxx.dts), leaving the PHY dead or the bus erroring on boards with a different reference clock. Use IS_ENABLED() so USB=m gets the same fixups as USB=y. Fixes: 7fd57ab9d9cf ("MIPS: Octeon: Fix compile error when USB is not enabled.") Assisted-by: Claude:claude-opus-5 Signed-off-by: Orgad Shaneh Signed-off-by: Thomas Bogendoerfer --- arch/mips/cavium-octeon/octeon-platform.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/mips/cavium-octeon/octeon-platform.c b/arch/mips/cavium-octeon/octeon-platform.c index 47677b5d7ed001..f53c98372e0bef 100644 --- a/arch/mips/cavium-octeon/octeon-platform.c +++ b/arch/mips/cavium-octeon/octeon-platform.c @@ -18,7 +18,7 @@ #include #include -#ifdef CONFIG_USB +#if IS_ENABLED(CONFIG_USB) #include #include #include @@ -1080,7 +1080,7 @@ int __init octeon_prune_device_tree(void) ; } -#ifdef CONFIG_USB +#if IS_ENABLED(CONFIG_USB) /* OHCI/UHCI USB */ alias_prop = fdt_getprop(initial_boot_params, aliases, "uctl", NULL); From 8b0cc8707f65e0f51912e764e1b309b2559db1ec Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Tue, 18 Aug 2026 19:31:53 +0800 Subject: [PATCH 0147/1417] mmc: spi: reset bytes_xfered before retrying CRC failures mmc_spi_data_do() updates data->bytes_xfered after each block has been transferred successfully. If a later block in the same data request fails with a CRC error, data->bytes_xfered may therefore contain the number of bytes completed before the failing block. mmc_spi_request() has a private recovery path for such CRC failures. It sends STOP_TRANSMISSION, clears data->error and jumps back to crc_recover to issue the same command and data request again. However, it does not clear data->bytes_xfered before the retry. If the retry succeeds, the request is completed with the bytes from the failed attempt still included in data->bytes_xfered. For a multi-block request this can make the completed request report more bytes than were transferred by the successful retry, and can even exceed the request size when most blocks completed before the CRC error. This is most likely to be observed on MMC-over-SPI systems where long multi-block transfers occasionally hit a data CRC error but the mmc_spi-internal retry succeeds. The data itself is retried, but the completion accounting is not. Clear data->bytes_xfered together with data->error before repeating the request so the final completion reports only the bytes transferred by the successful attempt. Fixes: 061c6c847eeb ("mmc_spi: Recover from CRC errors for r/w operation over SPI.") Cc: stable@vger.kernel.org Signed-off-by: Xu Rao Signed-off-by: Ulf Hansson --- drivers/mmc/host/mmc_spi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/mmc_spi.c b/drivers/mmc/host/mmc_spi.c index b471a7795b4d0b..5217d713c82637 100644 --- a/drivers/mmc/host/mmc_spi.c +++ b/drivers/mmc/host/mmc_spi.c @@ -952,6 +952,7 @@ static void mmc_spi_request(struct mmc_host *mmc, struct mmc_request *mrq) status = mmc_spi_command_send(host, mrq, &stop, 0); crc_retry--; mrq->data->error = 0; + mrq->data->bytes_xfered = 0; goto crc_recover; } From 6cc27d82196385fe06853319f74312a7d8019726 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 2 Sep 2026 19:08:08 +0100 Subject: [PATCH 0148/1417] mm/vma: correctly unaccount on mmap_prepare() failure __mmap_setup() accounts memory for relevant mappings via: security_vm_enough_memory_mm() -> __vm_enough_memory() -> vm_acct_memory() If __mmap_setup() fails, this indicates that this accounting did not take place, and thus it's appropriate for __mmap_region() to jump to abort_munmap. However if call_mmap_prepare() fails, it also jumps there and any accounted memory is not correctly unaccounted. Fix this by handling each error separately. Link: https://lore.kernel.org/20260902-fix-unaccount-mmap_prepare-v1-1-ea070189fdfb@kernel.org Fixes: c84bf6dd2b83 ("mm: introduce new .mmap_prepare() file callback") Signed-off-by: Lorenzo Stoakes (ARM) Cc: Jann Horn Cc: Liam R. Howlett Cc: Pedro Falcato Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- mm/vma.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mm/vma.c b/mm/vma.c index 35e7a64855fadc..f29abb30956bb9 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -2859,10 +2859,12 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, map.check_ksm_early = can_set_ksm_flags_early(&map); error = __mmap_setup(&map, &desc, uf); - if (!error && have_mmap_prepare) - error = call_mmap_prepare(&map, &desc); if (error) goto abort_munmap; + if (have_mmap_prepare) + error = call_mmap_prepare(&map, &desc); + if (error) + goto unacct_error; if (map.check_ksm_early) update_ksm_flags(&map); From 932cfb25e7ce98d1f93895671ec186a3087e4f80 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Wed, 2 Sep 2026 15:37:59 +0800 Subject: [PATCH 0149/1417] mm/shrinker: fix bogus set_shrinker_bit() with cgroup.memory=nokmem With cgroup.memory=nokmem, shrinker_memcg_alloc() bails out early and never allocates an id, so shrinker->id keeps the 0 it got from the kzalloc() in shrinker_alloc(). __list_lru_init() then copies that 0 into lru->shrinker_id, where it looks like a valid bit index. Nothing calls expand_shrinker_info() on nokmem either, so shrinker_nr_max stays 0 and every memcg ends up with an empty map (map_nr_max == 0). deferred_split_folio() hands a real memcg to __list_lru_add() regardless of whether the lru is memcg aware, so the first THP queued in a cgroup does set_shrinker_bit(memcg, nid, 0) and trips the bounds check: WARNING: mm/shrinker.c:212 at set_shrinker_bit+0x7d/0x90, CPU#126 Call Trace: deferred_split_folio+0x18c/0x220 map_anon_folio_pmd_nopf+0xdd/0x130 map_anon_folio_pmd_pf+0x14/0xb0 do_huge_pmd_anonymous_page+0x1a1/0x620 __handle_mm_fault+0xea9/0x10d0 handle_mm_fault+0xe5/0x320 do_user_addr_fault+0x1cc/0x870 exc_page_fault+0x81/0x1b0 asm_exc_page_fault+0x27/0x30 Harmless, the WARN_ON_ONCE() is what keeps the out of bounds unit[] read from happening, but the id should not look valid in the first place. Clear it before returning. Two other spots could paper over this: drop the id in __list_lru_init() when nokmem turns memcg_aware off, or make deferred_split_folio() pass NULL like list_lru_add_obj() does. Both leave shrinker->id lying around for the next caller, so fix it where the id is handed out. Link: https://lore.kernel.org/20260902073800.305481-1-jiayuan.chen@linux.dev Fixes: fafaeceb89a5 ("mm: switch deferred split shrinker to list_lru") Signed-off-by: Jiayuan Chen Acked-by: Shakeel Butt Cc: Usama Arif Cc: Dave Chinner Cc: Johannes Weiner Cc: Kairui Song Cc: Muchun Song Cc: Roman Gushchin Cc: Signed-off-by: Andrew Morton --- mm/shrinker.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/shrinker.c b/mm/shrinker.c index a70aab124a0e7f..7ec2a9704f6f2f 100644 --- a/mm/shrinker.c +++ b/mm/shrinker.c @@ -227,6 +227,8 @@ static int shrinker_memcg_alloc(struct shrinker *shrinker) { int id; + shrinker->id = -1; + if (mem_cgroup_disabled()) return -ENOSYS; if (mem_cgroup_kmem_disabled() && !(shrinker->flags & SHRINKER_NONSLAB)) From 7891fbb9512f127826e1d5dbf380ee212bd15eb0 Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Tue, 1 Sep 2026 20:38:40 -0700 Subject: [PATCH 0150/1417] mm/folio: EXPORT_SYMBOL_FOR_KVM(lru_cache_drain_for_folio) To simplify independent development in the KVM and MM subsystems, now export to KVM the lru_cache_drain_for_folio() which MM added in 7.3-rc1. Link: https://lore.kernel.org/lkml/bd6c9c74-e374-a9d3-ba1f-8b6f430894fc@google.com/T/#u Link: https://lore.kernel.org/02876cea-5727-2ca4-bead-73659ea6fec4@google.com Signed-off-by: Ackerley Tng Signed-off-by: Hugh Dickins Acked-by: Vlastimil Babka (SUSE) Suggested-by: David Hildenbrand Reviewed-by: Fuad Tabba Reviewed-by: Binbin Wu Cc: Matthew Wilcox (Oracle) Cc: Sean Christopherson Signed-off-by: Andrew Morton --- mm/folio.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/folio.c b/mm/folio.c index c02dcea9c03c24..50a6dbe55998e7 100644 --- a/mm/folio.c +++ b/mm/folio.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "internal.h" #include "page_alloc.h" @@ -926,6 +927,7 @@ void lru_cache_drain_for_folio(const struct folio *folio, *drained = LRU_CACHE_DRAINED_ALL; } } +EXPORT_SYMBOL_FOR_KVM(lru_cache_drain_for_folio); atomic_t lru_disable_count = ATOMIC_INIT(0); From 504981db4f69bdd28054fb98c96a3a67f7248dde Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sat, 5 Sep 2026 16:47:27 +0900 Subject: [PATCH 0151/1417] dma-coherent: report a failed reserved memory assignment rmem_dma_device_init() drops the return value of dma_assign_coherent_memory() and always reports success. That call fails with -EBUSY when the device already has a coherent pool, and the file allows only "*one* such region of memory" per device. of_reserved_mem_device_init_by_idx() reads the zero as success. It logs "assigned reserved memory node" for a region that was not assigned and records the pairing, so of_reserved_mem_device_release() later runs rmem_dma_device_release() for it. That clears dev->dma_mem without looking at which region it was called for, dropping the pool the device did get and leaving it on ordinary memory. dma_declare_coherent_memory() checks the same call and releases the memory on failure, and rmem_swiotlb_device_init() propagates its own errors. Return the error here as well, so a device tree that assigns two pools to one device fails the probe instead of half working. Fixes: 7bfa5ab6fa1b ("drivers: dma-coherent: add initialization from device tree") Signed-off-by: Donggeun Yoo Link: https://lore.kernel.org/r/20260905074727.108029-1-donggeunyoo.kernel@gmail.com Signed-off-by: Marek Szyprowski --- kernel/dma/coherent.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/dma/coherent.c b/kernel/dma/coherent.c index 45bbae947f4be4..4d0266893bcc99 100644 --- a/kernel/dma/coherent.c +++ b/kernel/dma/coherent.c @@ -352,8 +352,7 @@ static int rmem_dma_device_init(struct reserved_mem *rmem, struct device *dev) min_not_zero(dev->coherent_dma_mask, dev->bus_dma_limit)) dev_warn(dev, "reserved memory is beyond device's set DMA address range\n"); - dma_assign_coherent_memory(dev, mem); - return 0; + return dma_assign_coherent_memory(dev, mem); } static void rmem_dma_device_release(struct reserved_mem *rmem, From b7d7914a9ae3097e63d113007e4fb44d33d515b1 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sat, 5 Sep 2026 17:42:10 +0900 Subject: [PATCH 0152/1417] swiotlb: use the adjusted address for the highmem page lookup swiotlb_bounce() reads the page frame number from the slot's recorded orig_addr, then advances orig_addr by tlb_offset to reach the address the caller asked about. The highmem branch mixes the two: the offset within the page comes from the adjusted address, the page from the value before it. Once the adjustment crosses a page boundary the pair no longer describes one location, and the whole copy lands one page below the intended one for a positive tlb_offset, one above for a negative one. DMA_FROM_DEVICE writes the device data over the wrong page and leaves the intended one stale, DMA_TO_DEVICE feeds the device from a page the mapping may not cover. Partial syncs through dma_sync_single_range_for_*() are what make tlb_offset non-zero. The branch test is picked the same way, so a slot recorded in lowmem can be adjusted into highmem and the lowmem path then hands a highmem address to phys_to_virt(). Take both from orig_addr once it is final and keep pfn in the branch that uses it. PhysHighMem() asks the question straight from the address, as dma-debug already does. Fixes: 5f89468e2f06 ("swiotlb: manipulate orig_addr when tlb_addr has offset") Cc: stable@vger.kernel.org Signed-off-by: Donggeun Yoo Reviewed-by: Michael Kelley Link: https://lore.kernel.org/r/20260905084210.148255-1-donggeunyoo.kernel@gmail.com Signed-off-by: Marek Szyprowski --- kernel/dma/swiotlb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c index ded7016a46a71f..aa2f1c4588b973 100644 --- a/kernel/dma/swiotlb.c +++ b/kernel/dma/swiotlb.c @@ -1019,7 +1019,6 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size int index = (tlb_addr - mem->start) >> IO_TLB_SHIFT; phys_addr_t orig_addr = mem->slots[index].orig_addr; size_t alloc_size = mem->slots[index].alloc_size; - unsigned long pfn = PFN_DOWN(orig_addr); unsigned char *vaddr = mem->vaddr + tlb_addr - mem->start; int tlb_offset; @@ -1052,7 +1051,8 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size size = alloc_size; } - if (PageHighMem(pfn_to_page(pfn))) { + if (PhysHighMem(orig_addr)) { + unsigned long pfn = PFN_DOWN(orig_addr); unsigned int offset = orig_addr & ~PAGE_MASK; struct page *page; unsigned int sz = 0; From a1c7570cedd03372812a5b693732880867babbca Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 12:01:24 +0300 Subject: [PATCH 0153/1417] x86/mm/pat: Acquire init_mm write lock on collapse to avoid UAF x86 implements page attribute modification using its Change Page Attributes (CPA) mechanism. This tracks properties of ranges such as cache mode through x86 page attributes, and as part of that logic manipulates kernel page tables. Since commit: 41d88484c71c ("x86/mm/pat: restore large ROX pages after fragmentation") ranges of kernel page table entries can be collapsed into huge page table entries as part of this logic. As part of this collapse, it frees the page tables which the collapsed entries previously pointed to, and it does so without any relevant locks being held to preclude concurrent kernel page table walkers. The only way this code can be reached is if CPA_COLLAPSE is specified, and this is only set in set_memory_rox() via: set_memory_rox() -> change_page_attr_set_clr() -> cpa_flush() -> cpa_collapse_large_pages() Notable users of this are execmem and BPF when manipulating executable mappings. However, this is problematic for ptdump as it walks ranges it does not own and thus runs the risk of a use-after-free on page tables freed underneath it. In addition, concurrent CPA collapse operations are possible which can also cause races. Resolve the issue by acquiring the mmap write lock on init_mm across the whole operation. It is safe to acquire a sleeping lock as all the callers invoke set_memory_rox() from process context and in any case, change_page_attr_set_clr() calls vm_unmap_alias() which ultimately takes a mutex, disallowing atomic context here. Fixes: 41d88484c71c ("x86/mm/pat: restore large ROX pages after fragmentation") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Kiryl Shutsemau (Meta) Reviewed-by: David Hildenbrand (Arm) Reviewed-by: Dave Hansen Reviewed-by: Will Deacon Reviewed-by: David Carlier Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Cc:stable@vger.kernel.org Link: https://patch.msgid.link/20260813-cpa-fixes-v2-1-39b4ff90f91d@kernel.org --- arch/x86/mm/pat/set_memory.c | 15 ++++++++++++++- include/linux/mmap_lock.h | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index c38faf39ce152d..4abddd7638828d 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -409,7 +410,7 @@ static void __cpa_flush_tlb(void *data) static int collapse_large_pages(unsigned long addr, struct list_head *pgtables); -static void cpa_collapse_large_pages(struct cpa_data *cpa) +static void __cpa_collapse_large_pages(struct cpa_data *cpa) { unsigned long start, addr, end; struct ptdesc *ptdesc, *tmp; @@ -443,6 +444,18 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa) } } +static void cpa_collapse_large_pages(struct cpa_data *cpa) +{ + /* + * Take the mmap write lock on init_mm to: + * - Avoid a use-after-free if raced by ptdump (which takes its own + * write lock on init_mm). + * - Serialise concurrent CPA walkers. + */ + scoped_guard(mmap_write_lock, &init_mm) + __cpa_collapse_large_pages(cpa); +} + static void cpa_flush(struct cpa_data *cpa, int cache) { unsigned int i; diff --git a/include/linux/mmap_lock.h b/include/linux/mmap_lock.h index bec0eab6ef035d..b8a13b8d36a45b 100644 --- a/include/linux/mmap_lock.h +++ b/include/linux/mmap_lock.h @@ -630,6 +630,8 @@ static inline void mmap_read_unlock(struct mm_struct *mm) DEFINE_GUARD(mmap_read_lock, struct mm_struct *, mmap_read_lock(_T), mmap_read_unlock(_T)) DEFINE_GUARD_COND(mmap_read_lock, _try, mmap_read_trylock(_T)) +DEFINE_GUARD(mmap_write_lock, struct mm_struct *, + mmap_write_lock(_T), mmap_write_unlock(_T)) static inline void mmap_read_unlock_non_owner(struct mm_struct *mm) { From d5d8b8662e6e5a565b47a0388640e88402f23274 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 12:01:25 +0300 Subject: [PATCH 0154/1417] x86/mm/pat: Acquire init_mm read lock on attribute changes to avoid UAF A previous commit protected against races between ptdump and CPA collapse, however one still exists between attribute changes and collapse as reported by Denis V. Lunev (linked). When an attribute change arises, a lockless page table walker obtains a PTE entry, which is later written to via set_pte_atomic(): ... -> change_page_attr_set_clr() -> __change_page_attr_set_clr() -> __change_page_attr() -> _lookup_address_cpa() -> lookup_address_in_pgd_attr() -> [ lockless page table walker ] -> set_pte_atomic() There is nothing preventing a concurrent CPA collapse which can free the PTE that was retrieved here, resulting in a use-after-free. With the mmap write lock taken on init_mm over CPA collapse, resolve this race by acquiring an mmap read lock on init_mm over __change_page_attr_set_clr(). This locks across the whole operation over which the walk and the PTE entry write occurs, solving the race. It is safe to do this here, as no spinlocks are held upon entry to __change_page_attr_set_clr(). However, the lock must not be held over an allocation, as allocation can trigger reclaim and shrinkers may call into CPA recursively, making deadlocks possible (init_mm -> ... -> fs_reclaim -> init_mm). A page table is allocated when a huge page needs to be split: -> change_page_attr_set_clr() -> __change_page_attr_set_clr() -> __change_page_attr() -> split_large_page() [ pagetable_alloc() ] -> __split_large_page() Avoid deadlocks by dropping the mmap lock across pagetable_alloc() in split_large_page() and track whether this is needed by adding a new 'init_mm_read_locked' flag to struct cpa_data. This is safe as __split_large_page() (called with locks re-established) revalidates that the page table entry is the same as it was prior to the locks being dropped and __change_page_attr() repeats the entire page table walk whenever a split occurs, so concurrent split and collapse are accounted for. Concurrent ptdump is also safe as the lock is only dropped over page table allocation during which time the page table has not yet been modified. The CPA_COLLAPSE flag is only set by set_memory_rox(), which exclusively operates upon vmalloc ranges, and on x86 only within the module mapping space. This is important, because some callers directly invoke __change_page_attr_set_clr(), bypassing this lock. However, none of these operate within the module mapping space. * cpa_process_alias() - a recursive helper called by __change_page_attr_set_clr(). * __set_memory_enc_pgtable() - operates on the direct mapping and (via __vmbus_establish_gpadl()) the vmalloc mapping space. * __set_pages_[n]p() - called by set_direct_map_[invalid, default, valid]_noflush(), __kernel_map_pages() - operates on the direct map. * kernel_[un]map_pages_in_pgd() - operates on EFI ranges. This work is based upon Denis V. Lunev's excellent analysis of the bug with gratitude. [ dhansen: move to imperative voice in changelog ] Fixes: 41d88484c71c ("x86/mm/pat: restore large ROX pages after fragmentation") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Link: https://lore.kernel.org/all/20260626163213.2284080-1-den@openvz.org/ Cc:stable@vger.kernel.org Link: https://patch.msgid.link/20260813-cpa-fixes-v2-2-39b4ff90f91d@kernel.org --- arch/x86/mm/pat/set_memory.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index 4abddd7638828d..cb5d6d6f71a4e8 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -50,7 +50,8 @@ struct cpa_data { unsigned int flags; unsigned int force_split : 1, force_static_prot : 1, - force_flush_all : 1; + force_flush_all : 1, + init_mm_read_locked : 1; struct page **pages; }; @@ -1240,7 +1241,11 @@ static int split_large_page(struct cpa_data *cpa, pte_t *kpte, struct ptdesc *ptdesc; spin_unlock(&cpa_lock); + if (cpa->init_mm_read_locked) + mmap_read_unlock(&init_mm); ptdesc = pagetable_alloc(GFP_KERNEL, 0); + if (cpa->init_mm_read_locked) + mmap_read_lock(&init_mm); spin_lock(&cpa_lock); if (!ptdesc) return -ENOMEM; @@ -2134,7 +2139,11 @@ static int change_page_attr_set_clr(unsigned long *addr, int numpages, cpa.curpage = 0; cpa.force_split = force_split; - ret = __change_page_attr_set_clr(&cpa, 1); + /* Avoid race with concurrent CPA collapse. */ + cpa.init_mm_read_locked = true; + scoped_guard(mmap_read_lock, &init_mm) + ret = __change_page_attr_set_clr(&cpa, 1); + cpa.init_mm_read_locked = false; /* * Check whether we really changed something: From 1587d3394e254639cc36516256031334095e6ef3 Mon Sep 17 00:00:00 2001 From: Pedro Falcato Date: Thu, 13 Aug 2026 12:01:26 +0300 Subject: [PATCH 0155/1417] x86/alternatives: Exclude text poking against change_page_attr() From time to time, the following BUG can be observed in the x86 alternatives patching code [0]: > kernel BUG at arch/x86/kernel/alternative.c:2576! > Oops: invalid opcode: 0000 [#1] SMP NOPTI > CPU: 0 UID: 0 PID: 355 Comm: (udev-worker) Not tainted 7.1.3-1-default #1 PREEMPT(full) openSUSE Tumbleweed 8c1795b03ec64f997e57a8ad38b1161e3b98da64 > Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS unknown 02/02/2022 > RIP: 0010:__text_poke+0x2aa/0x450 > Call Trace: > > smp_text_poke_batch_finish+0x2a7/0x320 > __static_call_transform+0xb7/0x220 > arch_static_call_transform+0x5b/0xb0 > __static_call_init+0xe9/0x270 > static_call_module_notify+0x11f/0x150 > notifier_call_chain+0x61/0xe0 > blocking_notifier_call_chain_robust+0x63/0xc0 > load_module+0x1c92/0x20c0 > init_module_from_file+0xd8/0x140 > idempotent_init_module+0x100/0x2f0 > __x64_sys_finit_module+0x71/0xe0 > do_syscall_64+0xe1/0x610 > entry_SYSCALL_64_after_hwframe+0x76/0x7e which matches the following BUG_ON() in alternative.c: /* * If something went wrong, crash and burn since recovery paths are not * implemented. */ BUG_ON(!pages[0] || (cross_page_boundary && !pages[1])); This can happen if vmalloc_to_page() fails, for any reason. Such can happen if text poking races with CPA, which can possibly result in the collapsing of page tables (or breaking of PMD hugepages). It is not a problem for most users of vmalloc_to_page() (they solely own the vmalloc'd range) but, when CONFIG_ARCH_HAS_EXECMEM_ROX=y, various modules own a single execmem vmalloc range, and can call set_memory_*() in parallel on it. This can happen to race against __text_poke and cause havoc in vmalloc_to_page(). Fix it by excluding against CPA using the init_mm mmap read lock. [ dhansen: Fix up SoB ordering. The actual code flow here was: Pedro=>Lorenzo=>Mike=>Me which is reflected in the SoB chain now. I *believe* Mike simply picked up Lorenzo's update to Pedro's post from the Link ] Fixes: 64f6a4e10c05 ("x86: re-enable EXECMEM_ROX support") Reported-by: Jiri Slaby Reported-by: Steffen Dirkwinkel Signed-off-by: Pedro Falcato Signed-off-by: Lorenzo Stoakes (ARM) Co-developed-by: Lorenzo Stoakes (ARM) Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Tested-by: Jiri Slaby Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Cc: stable@vger.kernel.org Link: https://bugzilla.opensuse.org/show_bug.cgi?id=1271202 [0] Link: https://lore.kernel.org/linux-mm/555ea1d43a12c30a8f1eaf10c899b3790d728f33.camel@dirkwinkel.cc/ Link: https://patch.msgid.link/20260813-cpa-fixes-v2-3-39b4ff90f91d@kernel.org --- arch/x86/kernel/alternative.c | 39 ++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index 91b1cdd165699a..add62db3e82cdd 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include #include #include @@ -2372,6 +2375,38 @@ static void text_poke_memset(void *dst, const void *src, size_t len) typedef void text_poke_f(void *dst, const void *src, size_t len); +static void __poke_vmalloc_pages(struct page **pages, void *addr, + bool cross_page_boundary) +{ + pages[0] = vmalloc_to_page(addr); + if (cross_page_boundary) + pages[1] = vmalloc_to_page(addr + PAGE_SIZE); +} + +static void poke_vmalloc_pages(struct page **pages, void *addr, + bool cross_page_boundary) +{ + if (in_dbg_master()) { + /* + * If called from kgdb cannot sleep, but all other CPUs stopped + * anyway so safe to proceed without locks + */ + __poke_vmalloc_pages(pages, addr, cross_page_boundary); + } else { + /* + * execmem ROX ranges are shared between modules and can be + * collapsed to huge PMD entries, and this collapse can happen + * concurrently with a racing set_memory_rox(). + * + * Prevent vmalloc_to_page() from racing by acquiring an + * init_mm read lock which pairs with the init_mm write lock in + * cpa_collapse_large_pages(). + */ + guard(mmap_read_lock)(&init_mm); + __poke_vmalloc_pages(pages, addr, cross_page_boundary); + } +} + static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t len) { bool cross_page_boundary = offset_in_page(addr) + len > PAGE_SIZE; @@ -2389,9 +2424,7 @@ static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t l BUG_ON(!after_bootmem); if (!core_kernel_text((unsigned long)addr)) { - pages[0] = vmalloc_to_page(addr); - if (cross_page_boundary) - pages[1] = vmalloc_to_page(addr + PAGE_SIZE); + poke_vmalloc_pages(pages, addr, cross_page_boundary); } else { pages[0] = virt_to_page(addr); WARN_ON(!PageReserved(pages[0])); From 9e4a3ec3411bb6bb59e3c1f29b75609f1e87aac4 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 12:01:27 +0300 Subject: [PATCH 0156/1417] x86/mm/pat: Allocate split page tables as kernel page tables A PTE is allocated directly without going through the standard page table allocation routines (such as pte_alloc_one_kernel()) when the CPA code splits a large page (__split_large_page()). This means the page table constructor is never called nor is the page table marked as a kernel page table. The former results in the folio associated with the page table not being marked as a page table (__pagetable_ctor() is never called thus neither is __folio_set_pgtable()) nor are statistics updated to reflect it (lruvec_stat_add_folio() is never called). The latter issue of failing to mark the page table as a kernel page table (ptdesc_set_kernel() is never called) is far more problematic. Since commit: 5ba2f0a15564 ("mm: introduce deferred freeing for kernel page tables") kernel page table freeing has been batched and since the subsequent commit: e37d5a2d60a3 ("iommu/sva: invalidate stale IOTLB entries for kernel address space") IOTLB cache entries for kernel page tables have been invalidated upon being freed. Since split page tables are freed without this invalidation, the IOTLB can contain stale entries for them. Resolve the issue by using the ordinary PTE allocation API at split time. This results in these kernel page tables invoking a page table constructor, and thus requires a page table destructor. Destructors are not always present, like for early allocated direct map page tables). Conditionally call pagetable_dtor_free() if the PG_table folio flag for the ptdesc is set, otherwise we free the page table via pagetable_free(). Regardless of which path is taken page tables marked as kernel page tables, which now includes split page tables, take the correct route through pagetable_free_kernel(). There is a user-visible side effect in that split page tables will appear in nr_page_table_pages in /proc/vmstat (as do other kernel page tables allocated after early boot), however this is a positive change. This issue started being markedly problematic after commit: 5ba2f0a15564 ("mm: introduce deferred freeing for kernel page tables") so choose this as the Fixes target. [ dhansen: rephrase in imperative mood ] Fixes: 5ba2f0a15564 ("mm: introduce deferred freeing for kernel page tables") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Acked-by: Vishal Moola Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260813-cpa-fixes-v2-4-39b4ff90f91d@kernel.org --- arch/x86/mm/pat/set_memory.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index cb5d6d6f71a4e8..4652487b5572b0 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -441,7 +441,15 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) list_for_each_entry_safe(ptdesc, tmp, &pgtables, pt_list) { list_del(&ptdesc->pt_list); - pagetable_free(ptdesc); + /* + * Only early alloc'd direct map should not be flagged PG_table + * here and those shouldn't be collapsed. However be abundantly + * cautious and handle the !PG_table case too. + */ + if (PageTable((ptdesc_page(ptdesc)))) + pagetable_dtor_free(ptdesc); + else + pagetable_free(ptdesc); } } @@ -1134,11 +1142,10 @@ static void split_set_pte(struct cpa_data *cpa, pte_t *pte, unsigned long pfn, static int __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, - struct ptdesc *ptdesc) + pte_t *pbase) { unsigned long lpaddr, lpinc, ref_pfn, pfn, pfninc = 1; - struct page *base = ptdesc_page(ptdesc); - pte_t *pbase = (pte_t *)page_address(base); + struct page *base = virt_to_page(pbase); unsigned int i, level; pgprot_t ref_prot; bool nx, rw; @@ -1238,20 +1245,20 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, static int split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address) { - struct ptdesc *ptdesc; + pte_t *pte; spin_unlock(&cpa_lock); if (cpa->init_mm_read_locked) mmap_read_unlock(&init_mm); - ptdesc = pagetable_alloc(GFP_KERNEL, 0); + pte = pte_alloc_one_kernel(&init_mm); if (cpa->init_mm_read_locked) mmap_read_lock(&init_mm); spin_lock(&cpa_lock); - if (!ptdesc) + if (!pte) return -ENOMEM; - if (__split_large_page(cpa, kpte, address, ptdesc)) - pagetable_free(ptdesc); + if (__split_large_page(cpa, kpte, address, pte)) + pte_free_kernel(&init_mm, pte); return 0; } From f7491d7c81db0e7c304a7bd757a76d2fbeaff80e Mon Sep 17 00:00:00 2001 From: Vernon Yang Date: Thu, 3 Sep 2026 11:16:08 +0800 Subject: [PATCH 0157/1417] x86/mm: Fix user-space data loss with MADV_FREE and THP Some of users of Polars (a data analytics library) have lost production data from this bug. They seem to have just the right combination of huge pages, MADV_FREE and heavy reclaim pressure. pmd_modify() masks the old value with (_HPAGE_CHG_MASK & ~_PAGE_DIRTY), silently discarding the hardware dirty bit. The subsequent pmd_mksaveddirty() call is supposed to transfer _PAGE_DIRTY into _PAGE_SAVED_DIRTY when write-protecting, but the dirty bit was already stripped from the value, so there is nothing left to transfer. Contrast with pte_modify(), which keeps _PAGE_DIRTY_BITS in its mask, and pud_modify(), which keeps _HPAGE_CHG_MASK untouched: pmd_modify() is the odd one out. Any pmd_modify() on a writable, dirty PMD loses the dirty state. One visible consequence is data loss with MADV_FREE on PMD-mapped THP: memset(buf, 0x5A, size); // PMD-mapped THP, PMD dirty madvise(buf, size, MADV_FREE); // PMD cleaned but left writable, // folio marked lazyfree memset(buf, 0x5A, size); // hardware sets _PAGE_DIRTY again mprotect(buf, size, PROT_READ); // pmd_modify() drops the dirty bit mprotect(buf, size, PROT_READ|PROT_WRITE); // ... memory pressure ... Reclaim (e.g. under memcg pressure) then finds the lazyfree folio with no dirty bit set anywhere and frees it in __discard_anon_folio_pmd_locked(), even though the data was rewritten after MADV_FREE; subsequent reads fault in fresh zero pages. NUMA hinting alone can trigger the same loss, as do_huge_pmd_numa_page() restores the PMD through pmd_modify() as well. PMD-mapped file THPs are affected too: mprotect()/NUMA hinting dropping the dirty bit means rewritten data is never written back. Fix it by keeping _PAGE_DIRTY in the preserved mask, exactly like pte_modify() and pud_modify() do. The existing pmd_mksaveddirty()/pmd_clear_saveddirty() pair then performs the hardware-dirty <-> saved-dirty transition based on the write bit, preserving the shadow-stack encoding rules. Fixes: bb3aadf7d446 ("x86/mm: Start actually marking _PAGE_SAVED_DIRTY") Closes: https://lore.kernel.org/r/CAJxLxMUGu1-L+O_nAONOwOXnS=cNbNApCWqdthRjd76LThtSPg@mail.gmail.com/ Reported-by: Orson Peters Signed-off-by: Vernon Yang Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Reviewed-by: Rick Edgecombe Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260903031608.1194238-1-vernon2gm@gmail.com --- arch/x86/include/asm/pgtable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h index d5f4917c1edcba..d551120a7c889b 100644 --- a/arch/x86/include/asm/pgtable.h +++ b/arch/x86/include/asm/pgtable.h @@ -806,7 +806,7 @@ static inline pmd_t pmd_modify(pmd_t pmd, pgprot_t newprot) pmdval_t val = pmd_val(pmd), oldval = val; pmd_t pmd_result; - val &= (_HPAGE_CHG_MASK & ~_PAGE_DIRTY); + val &= _HPAGE_CHG_MASK; val |= check_pgprot(newprot) & ~_HPAGE_CHG_MASK; val = flip_protnone_guard(oldval, val, PHYSICAL_PMD_PAGE_MASK); From 92c6a8d6470f7e7aa86c1f144818d8fa4fcbd5aa Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 21:01:24 +0900 Subject: [PATCH 0158/1417] dma-mapping: don't trace the DMA address when the allocation fails dma_alloc_attrs() passes *dma_handle to trace_dma_alloc() without checking whether the allocation succeeded. No backend writes it on failure: dma_direct_alloc(), iommu_dma_alloc() and the dma_map_ops instances assign it only on the path that returns a buffer. Callers usually pass an uninitialized automatic variable, so a failed allocation records whatever the stack held, next to the virt_addr=(null) that marks the record as an error: dma_alloc: dmatrace dir=BIDIRECTIONAL dma_addr=deadbeefdeadbeef size=1099511627776 virt_addr=0000000000000000 The device coherent pool path reaches the same call: a non-zero return from dma_alloc_from_dev_coherent() means the request was handled, not that it succeeded, so cpu_addr is NULL and dma_handle is untouched once the pool runs out. For an allocation event a NULL virt_addr already means the request failed, so the address field carries nothing. Report 0 for it in the event class rather than at each call site, which covers dma_alloc_pages() and dma_alloc_sgt_err() as well. Fixes: 038eb433dc14 ("dma-mapping: add tracing for dma-mapping API calls") Fixes: 68b6dbf1f441 ("dma-mapping: trace more error paths") Suggested-by: Marek Szyprowski Signed-off-by: Donggeun Yoo Link: https://lore.kernel.org/r/20260907120124.603373-1-donggeunyoo.kernel@gmail.com Reviewed-by: Sean Anderson Signed-off-by: Marek Szyprowski --- include/trace/events/dma.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/trace/events/dma.h b/include/trace/events/dma.h index 9df02c1511de7d..b06d8f99922de4 100644 --- a/include/trace/events/dma.h +++ b/include/trace/events/dma.h @@ -134,7 +134,7 @@ DECLARE_EVENT_CLASS(dma_alloc_class, TP_fast_assign( __assign_str(device); __entry->virt_addr = virt_addr; - __entry->dma_addr = dma_addr; + __entry->dma_addr = virt_addr ? dma_addr : 0; __entry->size = size; __entry->flags = flags; __entry->dir = dir; From 53823e25793a97d07e6e98e0904bbf74cac8bc76 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sat, 22 Aug 2026 02:58:47 +0800 Subject: [PATCH 0159/1417] mmc: sdio_uart: fix xmit_fifo leak when the port table is full sdio_uart_add_port() allocates the transmit fifo before claiming a slot in sdio_uart_table[]. When all UART_NR slots are taken, it returns -EBUSY with the fifo still allocated, but the probe error path only kfree()s the port, leaking the transmit fifo. Free the fifo in the failure path of sdio_uart_add_port() itself so the function retains nothing on error. Fixes: 8b197a5ce7a7 ("sdio_uart: Use kfifo instead of the messy circ stuff") Signed-off-by: Felix Gu Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/core/sdio_uart.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/core/sdio_uart.c b/drivers/mmc/core/sdio_uart.c index 7fd5dedf3ac038..705163c4297244 100644 --- a/drivers/mmc/core/sdio_uart.c +++ b/drivers/mmc/core/sdio_uart.c @@ -104,6 +104,9 @@ static int sdio_uart_add_port(struct sdio_uart_port *port) } spin_unlock(&sdio_uart_table_lock); + if (ret) + kfifo_free(&port->xmit_fifo); + return ret; } From 424103642d009f5a7dc33300a49d5606d1bf4fb5 Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:03 +0000 Subject: [PATCH 0160/1417] dmaengine: add dma_device_get() helper Add dma_device_get() helper to match dma_device_put() to make code symmetric. It wraps open-coded kref_get_unless_zero() and asserts that dma_list_mutex is held, matching its put counterpart. No functional change intended. Suggested-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-1-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index 6ffd8bd82154af..6b8af8607e5c5f 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -433,6 +433,12 @@ static void dma_device_release(struct kref *ref) device->device_release(device); } +static int __must_check dma_device_get(struct dma_device *device) +{ + lockdep_assert_held(&dma_list_mutex); + return kref_get_unless_zero(&device->ref); +} + static void dma_device_put(struct dma_device *device) { lockdep_assert_held(&dma_list_mutex); @@ -460,8 +466,7 @@ static int dma_chan_get(struct dma_chan *chan) if (!try_module_get(owner)) return -ENODEV; - ret = kref_get_unless_zero(&chan->device->ref); - if (!ret) { + if (!dma_device_get(chan->device)) { ret = -ENODEV; goto module_put_out; } From 44dab659064eb5c10adb0306510eebe848ed592d Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:04 +0000 Subject: [PATCH 0161/1417] dmaengine: Fix device kref underflow in dma_chan_put() dma_chan_get() takes chan->device->ref only on the slow path: /* no kref on fast path */ if (chan->client_count) { __module_get(owner); chan->client_count++; return 0; } if (!try_module_get(owner)) return -ENODEV; if (!dma_device_get(chan->device)) { // calls kref_get_unless_zero() dma_chan_put() drops the ref unconditionally, so every fast-path get/put pair drops one extra device reference. The bug fires when two conditions hold together: a non-private provider has a persistent client holding chan->client_count > 0 and another client cycles dmaengine_get()/dmaengine_put(). When the kref hits zero, the subsequent dma_find_channel() returns NULL even though the provider module is still loaded. Fix this by dropping device->ref only on the last put, matching the single slow-path get. Fixes: 8ad342a86359 ("dmaengine: Add reference counting to dma_device struct") Reviewed-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-2-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index 6b8af8607e5c5f..e380b801df735a 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -520,7 +520,9 @@ static void dma_chan_put(struct dma_chan *chan) chan->route_data = NULL; } - dma_device_put(chan->device); + /* This channel is not in use anymore, drop the device ref */ + if (!chan->client_count) + dma_device_put(chan->device); module_put(dma_chan_to_owner(chan)); } From e873c74132f0c5f1452816cd9bb26208f0bba1e1 Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:05 +0000 Subject: [PATCH 0162/1417] dmaengine: fix use-after-free in dma_chan_put() and dma_release_channel() When dma_device_put() drops the last reference on chan->device->ref, dma_device_release() runs and may free the dma_device along with its channels. dma_chan_put() then still reads chan->device->owner via dma_chan_to_owner() for the trailing module_put(). KASAN catches it: slab-use-after-free in dma_chan_put+0x3e6/0x4c0 Read of size 8 by task insmod/6319 Freed by task 6319: kfree+0x225/0x470 dma_chan_put+0x395/0x4c0 dmaengine_put+0xf8/0x160 Cache the module owner in dma_chan_put() before the put so the trailing module_put() does not need chan->device. Fixes: 8ad342a86359 ("dmaengine: Add reference counting to dma_device struct") Suggested-by: Sashiko Link: https://sashiko.dev/#/patchset/20260518-dmaengine-kref-fix-v1-1-4d6125048fb7@amd.com Reviewed-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-3-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index e380b801df735a..872ae0d2ed95da 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -500,10 +500,13 @@ static int dma_chan_get(struct dma_chan *chan) */ static void dma_chan_put(struct dma_chan *chan) { + struct module *owner; + /* This channel is not in use, bail out */ if (!chan->client_count) return; + owner = dma_chan_to_owner(chan); chan->client_count--; /* This channel is not in use anymore, free it */ @@ -523,7 +526,7 @@ static void dma_chan_put(struct dma_chan *chan) /* This channel is not in use anymore, drop the device ref */ if (!chan->client_count) dma_device_put(chan->device); - module_put(dma_chan_to_owner(chan)); + module_put(owner); } enum dma_status dma_sync_wait(struct dma_chan *chan, dma_cookie_t cookie) From dc750422170a563c7a81f6e49d36bb02c62ae37f Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:06 +0000 Subject: [PATCH 0163/1417] dmaengine: wait for RCU readers before releasing dma_device dma_issue_pending_all() walks the dma_device_list with list_for_each_entry_rcu() under rcu_read_lock(). dma_device_release() unlinks the device with list_del_rcu() and then calls device->device_release() (which in many drivers, such as plx_dma.c, directly calls kfree()). Because there is no grace period between unlinking the device and freeing it, concurrent RCU readers in dma_issue_pending_all() can access the device after it has been freed. The lockless walk originally relied on clients holding a dmaengine reference to pin the provider module, and therefore the device, for as long as they might traverse the list. Commit 8ad342a86359 ("dmaengine: Add reference counting to dma_device struct") decoupled the dma_device lifetime from the module reference, so the device can now be released while a reader is still walking the list. Add synchronize_rcu() before the device is freed, so RCU readers are guaranteed to have finished. Keep it unconditional: providers that do not implement device_release() free the device themselves once dma_async_device_unregister() returns. This call will delay for a grace period with dma_list_mutex held, which is safe and only teardown path is delayed. Fixes: 2ba05622b8b1 ("dmaengine: provide a common 'issue_pending_all' implementation") Suggested-by: Sashiko Link: https://sashiko.dev/#/patchset/20260526-dmaengine-kref-fix-v2-0-3df60afac01d@amd.com Reviewed-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-4-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index 872ae0d2ed95da..c71763047126a9 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -428,6 +428,7 @@ static void dma_device_release(struct kref *ref) list_del_rcu(&device->global_node); dma_channel_rebalance(); + synchronize_rcu(); if (device->device_release) device->device_release(device); From dab68a74e90b8e07f08ed9deaa5884857a3cfe89 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:03 +0200 Subject: [PATCH 0164/1417] wifi: cfg80211: don't free driver-owned scan requests When an interface goes down while a scan is running, cfg80211 completes the scan towards userspace and frees the scan request. However, the driver can be convinced that it owns the request, since the cancellation is (intended to be) asynchronous. The WARN_ON() in the netdev notifier was meant to catch this, but it's not actually avoidable, so it triggers and we get a UAF in scan_done(). There doesn't seem to be a great way around it, so just track that the driver is still convinced it owns the request, and then just free it on completion if it was already cancelled. Also remove the warnings since they can trigger in the intended architecture. Assisted-by: LLM Fixes: 4a58e7c38443 ("cfg80211: don't "leak" uncompleted scans") Reported-by: syzbot+189dcafc06865d38178d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=189dcafc06865d38178d Link: https://patch.msgid.link/20260904165614.375e543228b1.I03cbb5a54cb02d6bba5034286af1ed73aba134d1@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 11 +++++------ net/wireless/core.h | 10 ++++++++++ net/wireless/rdev-ops.h | 3 +++ net/wireless/scan.c | 31 +++++++++++++++++++++++++++++-- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index d13310fef691ae..8bb2cbd66b488f 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -244,9 +244,8 @@ void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev, rdev->opencount--; if (rdev->scan_req && rdev->scan_req->req.wdev == wdev) { - if (WARN_ON(!rdev->scan_req->notified && - (!rdev->int_scan_req || - !rdev->int_scan_req->notified))) + if (!rdev->scan_req->notified && + (!rdev->int_scan_req || !rdev->int_scan_req->notified)) rdev->scan_req->info.aborted = true; ___cfg80211_scan_done(rdev, false); } @@ -1758,9 +1757,9 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb, wiphy_lock(&rdev->wiphy); cfg80211_update_iface_num(rdev, wdev->iftype, -1); if (rdev->scan_req && rdev->scan_req->req.wdev == wdev) { - if (WARN_ON(!rdev->scan_req->notified && - (!rdev->int_scan_req || - !rdev->int_scan_req->notified))) + if (!rdev->scan_req->notified && + (!rdev->int_scan_req || + !rdev->int_scan_req->notified)) rdev->scan_req->info.aborted = true; ___cfg80211_scan_done(rdev, false); } diff --git a/net/wireless/core.h b/net/wireless/core.h index b4610f6685dcb5..a0c2b6ebe31fcc 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -24,6 +24,16 @@ struct cfg80211_scan_request_int { struct cfg80211_scan_info info; bool notified; + /* + * set while the request is handed to the driver, i.e. between + * rdev_scan() and cfg80211_scan_done() + */ + bool driver_owns; + /* + * set when cfg80211 is done with the request but the driver still + * owns it, so that cfg80211_scan_done() knows to just free it + */ + bool stale; /* must be last - variable members */ struct cfg80211_scan_request req; }; diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index 46849fe8d0b398..adcfd0278da32b 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -464,7 +464,10 @@ static inline int rdev_scan(struct cfg80211_registered_device *rdev, return -EINVAL; trace_rdev_scan(&rdev->wiphy, request); + request->driver_owns = true; ret = rdev->ops->scan(&rdev->wiphy, &request->req); + if (ret) + request->driver_owns = false; trace_rdev_return_int(&rdev->wiphy, ret); return ret; } diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 9e934b185e3416..4fe114f6aee3ba 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -1114,6 +1114,21 @@ int cfg80211_scan(struct cfg80211_registered_device *rdev) return 0; } +/* + * Release the scan request, but free it only if the driver is also done, + * e.g. mac80211 may cancel it asynchronously and still use it. + */ +static void cfg80211_put_scan_req(struct cfg80211_scan_request_int *req) +{ + if (!req) + return; + + if (req->driver_owns) + req->stale = true; + else + kfree(req); +} + void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool send_message) { @@ -1173,10 +1188,10 @@ void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, dev_put(wdev->netdev); - kfree(rdev->int_scan_req); + cfg80211_put_scan_req(rdev->int_scan_req); rdev->int_scan_req = NULL; - kfree(rdev->scan_req); + cfg80211_put_scan_req(rdev->scan_req); rdev->scan_req = NULL; if (!send_message) @@ -1199,6 +1214,18 @@ void cfg80211_scan_done(struct cfg80211_scan_request *request, struct cfg80211_scan_info old_info = intreq->info; trace_cfg80211_scan_done(intreq, info); + + intreq->driver_owns = false; + + if (intreq->stale) { + /* + * The scan is already completed as far as we're concerned, + * it was just kept around for the driver - done now, free it. + */ + kfree(intreq); + return; + } + WARN_ON(intreq != rdev->scan_req && intreq != rdev->int_scan_req); From 955d86e5f3b95b731991fdb84966c50b16314629 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Sun, 9 Aug 2026 21:36:15 +0000 Subject: [PATCH 0165/1417] arm64: hibernate: pass HVC_SET_VECTORS args to the resume hvc swsusp_arch_suspend_exit() reinstalls the restored kernel's hyp stub vectors with an hvc, but never passes the arguments. x0 is not set to HVC_SET_VECTORS and x1 is not set to the vector address, so the stub dispatch falls through and returns without writing vbar_el2. EL2 is left pointing at the trans_pgd copy of the vectors, a page that swsusp_free() releases right after resume. Set the arguments up the same way __hyp_set_vectors() does. Without this fix, Vladimir was able to trigger a hang when resuming from hibernation with CONFIG_PAGE_POISONING=y and page_poison=on. Fixes: 788bfdd97434 ("arm64: trans_pgd: hibernate: Add trans_pgd_copy_el2_vectors") Cc: stable@vger.kernel.org Signed-off-by: Bradley Morgan Reviewed-by: Vladimir Murzin Tested-by: Vladimir Murzin Acked-by: Mark Rutland Signed-off-by: Will Deacon --- arch/arm64/kernel/hibernate-asm.S | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/kernel/hibernate-asm.S b/arch/arm64/kernel/hibernate-asm.S index 0e1d9c3c6a9339..2baefe7a82d360 100644 --- a/arch/arm64/kernel/hibernate-asm.S +++ b/arch/arm64/kernel/hibernate-asm.S @@ -89,6 +89,8 @@ alternative_insn "dc cvau, x4", "dc civac, x4", ARM64_WORKAROUND_CLEAN_CACHE isb cbz x24, 3f /* Do we need to re-initialise EL2? */ + mov x1, x24 + mov x0, #HVC_SET_VECTORS hvc #0 3: ret SYM_CODE_END(swsusp_arch_suspend_exit) From 068843ed0902c552a13860c5ec6b2ca65b57a065 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:04 +0200 Subject: [PATCH 0166/1417] wifi: cfg80211: only group hidden BSSes with beacon entries When a probe response for an unknown BSS comes in, __cfg80211_bss_update() looks for an existing entry with the same BSSID and a hidden (zero-length or NUL-filled) SSID, and if it finds one it groups them, using the beacon IEs from the existing entry. But that could find another entry without a beacon, if it was also from a probe response (with SSID), so there's a group without beacon elements. If a beacon with a hidden SSID for that BSSID arrives later, cfg80211_combine_bsses() goes looking for the probe response entries that belong to it - i.e. entries with the same BSSID and channel that have no beacon IEs - and finds those two. They are already grouped with each other, so it hits its WARN_ON_ONCE(bss->pub.hidden_beacon_bss) WARN_ON_ONCE(!list_empty(&bss->hidden_list)) which are there because an entry without beacon elements is not supposed to be part of a group yet. Only combine entries when a beacon was already received, ones that are kept separate will be combined when a beacon arrives. Assisted-by: LLM Fixes: 4593c4cbe1c9 ("cfg80211: fix BSS list hidden SSID lookup") Reported-by: syzbot+1a797e1c81be78a2ace7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1a797e1c81be78a2ace7 Link: https://patch.msgid.link/20260904165614.bcfa64715745.Iad740347c86de56d4ff4f96a95f3c3afc47c42de@changeid Signed-off-by: Johannes Berg --- net/wireless/scan.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 4fe114f6aee3ba..604b10ef0f948c 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -2077,6 +2077,13 @@ __cfg80211_bss_update(struct cfg80211_registered_device *rdev, if (!hidden) hidden = rb_find_bss(rdev, tmp, BSS_CMP_HIDE_NUL); + /* + * Only group with an entry with beacon data, otherwise + * beacon data can never be filled/updated. + */ + if (hidden && + !rcu_access_pointer(hidden->pub.beacon_ies)) + hidden = NULL; if (hidden) { new->pub.hidden_beacon_bss = &hidden->pub; list_add(&new->hidden_list, From b377e1000d963e7182a987082b4b06580bd7ac84 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:05 +0200 Subject: [PATCH 0167/1417] wifi: cfg80211: don't filter by BSS type when removing stale entries When an assoc AP switches to a channel that already has a BSS entry, cfg80211_update_assoc_bss_entry() removes that entry before rehashing the real one, since the two would otherwise collide in the BSS rbtree. The lookup for that entry also required it to match the connection's BSS type, so an entry advertising e.g. the IBSS capability bit was left in place, and the following cfg80211_rehash_bss() then ran into it: WARN_ON(!cmp) Changing the type shouldn't really happen, but can be triggered by a rogue AP/device, so drop the check and remove any entries matching the comparison. Assisted-by: LLM Fixes: 0afd425b1b64 ("cfg80211: fix duplicated scan entries after channel switch") Reported-by: syzbot+dc6f4dce0d707900cdea@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=dc6f4dce0d707900cdea Link: https://patch.msgid.link/20260904165614.1f05dae1c546.Ib52d57b57caa912efee020f9d4a033a5160617ce@changeid Signed-off-by: Johannes Berg --- net/wireless/scan.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 604b10ef0f948c..caa9c6495f20cc 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -3502,11 +3502,6 @@ void cfg80211_update_assoc_bss_entry(struct wireless_dev *wdev, cbss->pub.channel = chan; list_for_each_entry(bss, &rdev->bss_list, list) { - if (!cfg80211_bss_type_match(bss->pub.capability, - bss->pub.channel->band, - wdev->conn_bss_type)) - continue; - if (bss == cbss) continue; From 708f9d43d6a2eb9c6b83fe62af628de9dffd9314 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:06 +0200 Subject: [PATCH 0168/1417] wifi: cfg80211: ibss: ref BSS entry for joined event When the IBSS is joined, we only record the BSSID/channel in the event and look up the BSS entry when processing it. However, that's racy, e.g. a new scan with NL80211_SCAN_FLAG_FLUSH can remove it, causing a warning in the event work: !bss WARNING: net/wireless/ibss.c:37 at __cfg80211_ibss_joined+0x3d3/0x440 Workqueue: cfg80211 cfg80211_event_work cfg80211_process_wdev_events+0x39f/0x5b0 net/wireless/util.c:1144 cfg80211_process_rdev_events+0xa1/0x110 net/wireless/util.c:1179 cfg80211_event_work+0x2f/0x40 net/wireless/core.c:393 Do the lookup early (the driver is expected to only join an IBSS that has a BSS entry) and keep a reference to it. Assisted-by: LLM Fixes: 667503ddcb96 ("cfg80211: fix locking") Reported-by: syzbot+7f064ba1704c2466e36d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7f064ba1704c2466e36d Link: https://patch.msgid.link/20260904165614.f49a213f0e49.I192bfe738750ebb5f2c4faa3019a428da64cd3ec@changeid Signed-off-by: Johannes Berg --- net/wireless/core.h | 6 ++---- net/wireless/ibss.c | 36 ++++++++++++++++++++---------------- net/wireless/util.c | 3 +-- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/net/wireless/core.h b/net/wireless/core.h index a0c2b6ebe31fcc..85dfb3ac803b85 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -290,8 +290,7 @@ struct cfg80211_event { bool locally_generated; } dc; struct { - u8 bssid[ETH_ALEN]; - struct ieee80211_channel *channel; + struct cfg80211_bss *bss; } ij; struct { u8 peer_addr[ETH_ALEN]; @@ -354,8 +353,7 @@ int __cfg80211_join_ibss(struct cfg80211_registered_device *rdev, void cfg80211_clear_ibss(struct net_device *dev, bool nowext); int cfg80211_leave_ibss(struct cfg80211_registered_device *rdev, struct net_device *dev, bool nowext); -void __cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, - struct ieee80211_channel *channel); +void __cfg80211_ibss_joined(struct net_device *dev, struct cfg80211_bss *bss); int cfg80211_ibss_wext_join(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev); diff --git a/net/wireless/ibss.c b/net/wireless/ibss.c index b1d748bdb504eb..7f6779d326b813 100644 --- a/net/wireless/ibss.c +++ b/net/wireless/ibss.c @@ -16,26 +16,18 @@ #include "rdev-ops.h" -void __cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, - struct ieee80211_channel *channel) +void __cfg80211_ibss_joined(struct net_device *dev, struct cfg80211_bss *bss) { struct wireless_dev *wdev = dev->ieee80211_ptr; - struct cfg80211_bss *bss; #ifdef CONFIG_CFG80211_WEXT union iwreq_data wrqu; #endif if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC)) - return; + goto put_bss; if (!wdev->u.ibss.ssid_len) - return; - - bss = cfg80211_get_bss(wdev->wiphy, channel, bssid, NULL, 0, - IEEE80211_BSS_TYPE_IBSS, IEEE80211_PRIVACY_ANY); - - if (WARN_ON(!bss)) - return; + goto put_bss; if (wdev->u.ibss.current_bss) { cfg80211_unhold_bss(wdev->u.ibss.current_bss); @@ -43,17 +35,22 @@ void __cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, } cfg80211_hold_bss(bss_from_pub(bss)); + /* the reference from the event is transferred to current_bss */ wdev->u.ibss.current_bss = bss_from_pub(bss); cfg80211_upload_connect_keys(wdev); - nl80211_send_ibss_bssid(wiphy_to_rdev(wdev->wiphy), dev, bssid, + nl80211_send_ibss_bssid(wiphy_to_rdev(wdev->wiphy), dev, bss->bssid, GFP_KERNEL); #ifdef CONFIG_CFG80211_WEXT memset(&wrqu, 0, sizeof(wrqu)); - memcpy(wrqu.ap_addr.sa_data, bssid, ETH_ALEN); + memcpy(wrqu.ap_addr.sa_data, bss->bssid, ETH_ALEN); wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); #endif + return; + +put_bss: + cfg80211_put_bss(wdev->wiphy, bss); } void cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, @@ -62,6 +59,7 @@ void cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, struct wireless_dev *wdev = dev->ieee80211_ptr; struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); struct cfg80211_event *ev; + struct cfg80211_bss *bss; unsigned long flags; trace_cfg80211_ibss_joined(dev, bssid, channel); @@ -69,13 +67,19 @@ void cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, if (WARN_ON(!channel)) return; + bss = cfg80211_get_bss(wdev->wiphy, channel, bssid, NULL, 0, + IEEE80211_BSS_TYPE_IBSS, IEEE80211_PRIVACY_ANY); + if (WARN_ON(!bss)) + return; + ev = kzalloc_obj(*ev, gfp); - if (!ev) + if (!ev) { + cfg80211_put_bss(wdev->wiphy, bss); return; + } ev->type = EVENT_IBSS_JOINED; - memcpy(ev->ij.bssid, bssid, ETH_ALEN); - ev->ij.channel = channel; + ev->ij.bss = bss; spin_lock_irqsave(&wdev->event_lock, flags); list_add_tail(&ev->list, &wdev->event_list); diff --git a/net/wireless/util.c b/net/wireless/util.c index 5429cf3cfd2ffd..f2464d2ce0d5aa 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -1235,8 +1235,7 @@ void cfg80211_process_wdev_events(struct wireless_dev *wdev) !ev->dc.locally_generated); break; case EVENT_IBSS_JOINED: - __cfg80211_ibss_joined(wdev->netdev, ev->ij.bssid, - ev->ij.channel); + __cfg80211_ibss_joined(wdev->netdev, ev->ij.bss); break; case EVENT_STOPPED: /* From 17a5f8571d1d40c88b78cfc154a7da0d60f13f37 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:07 +0200 Subject: [PATCH 0169/1417] wifi: cfg80211: fix NAN regulatory enforcement reg_wdev_chan_valid() returns early for any wdev that has no netdev, which is fine for P2P originally (and later PD still), but NAN has no netdev and yet enforcement code was added and is needed, but is dead code right now. Use wdev_running() instead so that netdev-less wdevs aren't skipped. P2P/PD don't do anything in the later switch, but NAN code can now be reached. Assisted-by: LLM Fixes: 0e8ec738a71e ("wifi: cfg80211: add support for NAN data interface") Link: https://patch.msgid.link/20260904165614.6abc075b5401.Ib90696e3fa49b1698c27d64db5360d51f6f187a9@changeid Signed-off-by: Johannes Berg --- net/wireless/reg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wireless/reg.c b/net/wireless/reg.c index a8336baf85dc63..9910b080b402a1 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -2345,7 +2345,7 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) iftype = wdev->iftype; /* make sure the interface is active */ - if (!wdev->netdev || !netif_running(wdev->netdev)) + if (!wdev_running(wdev)) return true; /* NAN doesn't have links, handle it separately */ From f4e72e3758072d7b063e0d8b93419eb915b69c2c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:08 +0200 Subject: [PATCH 0170/1417] wifi: cfg80211: reduce RTNL holding in regulatory enforcement Regulatory enforcement in reg_check_chans_work() does all work with the RTNL held, which can block the RTNL for a long time, which syzbot can hit and report hung tasks. Except for NAN, we don't need the RTNL for the enforcement, and the list iteration can be done with RCU instead. Split the enforcement off into new work structs: for NAN, we have to have the RTNL to close dependent NAN_DATA interfaces, everything else can use cfg80211_leave_locked() in a wiphy work. It'd be doable to use just a single work with RTNL, but then the RTNL would end up being used all the time, and really it only needs to be used for NAN. Assisted-by: LLM Reported-by: syzbot+adeb8550754921fece20@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=adeb8550754921fece20 Reported-by: syzbot+101224300649c3eb8af4@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=101224300649c3eb8af4 Link: https://patch.msgid.link/20260904165614.f65bd4d9fa35.I82dac71371d87f39e459fce931b0e5321e4f9767@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 3 +++ net/wireless/core.h | 2 ++ net/wireless/reg.c | 50 +++++++++++++++++++++++++++++++++++++-------- net/wireless/reg.h | 16 +++++++++++++++ 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 8bb2cbd66b488f..668380deec7da5 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -645,6 +645,8 @@ struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int sizeof_priv, INIT_WORK(&rdev->destroy_work, cfg80211_destroy_iface_wk); wiphy_work_init(&rdev->sched_scan_stop_wk, cfg80211_sched_scan_stop_wk); INIT_WORK(&rdev->sched_scan_res_wk, cfg80211_sched_scan_results_wk); + wiphy_work_init(&rdev->reg_check_chans_wk, reg_leave_invalid_chans_wk); + INIT_WORK(&rdev->reg_leave_nan_wk, reg_leave_invalid_nan_wk); INIT_WORK(&rdev->propagate_radar_detect_wk, cfg80211_propagate_radar_detect_wk); INIT_WORK(&rdev->propagate_cac_done_wk, cfg80211_propagate_cac_done_wk); @@ -1344,6 +1346,7 @@ void wiphy_unregister(struct wiphy *wiphy) cancel_delayed_work_sync(&rdev->dfs_update_channels_wk); cancel_delayed_work_sync(&rdev->background_cac_done_wk); flush_work(&rdev->destroy_work); + flush_work(&rdev->reg_leave_nan_wk); flush_work(&rdev->propagate_radar_detect_wk); flush_work(&rdev->propagate_cac_done_wk); flush_work(&rdev->mgmt_registrations_update_wk); diff --git a/net/wireless/core.h b/net/wireless/core.h index 85dfb3ac803b85..6138d207caf41a 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -114,6 +114,8 @@ struct cfg80211_registered_device { struct work_struct destroy_work; struct wiphy_work sched_scan_stop_wk; struct work_struct sched_scan_res_wk; + struct wiphy_work reg_check_chans_wk; + struct work_struct reg_leave_nan_wk; struct cfg80211_chan_def radar_chandef; struct work_struct propagate_radar_detect_wk; diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 9910b080b402a1..11665e0a7efc8a 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -2446,19 +2446,52 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) return true; } -static void reg_leave_invalid_chans(struct wiphy *wiphy) +void reg_leave_invalid_nan_wk(struct work_struct *work) { + struct cfg80211_registered_device *rdev; struct wireless_dev *wdev; - struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); + + rdev = container_of(work, struct cfg80211_registered_device, + reg_leave_nan_wk); + + /* stopping NAN closes its data interfaces, which needs the RTNL */ + rtnl_lock(); list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { bool valid; - scoped_guard(wiphy, wiphy) - valid = reg_wdev_chan_valid(wiphy, wdev); + if (wdev->iftype != NL80211_IFTYPE_NAN) + continue; + + scoped_guard(wiphy, &rdev->wiphy) + valid = reg_wdev_chan_valid(&rdev->wiphy, wdev); if (!valid) cfg80211_leave(rdev, wdev, -1); } + + rtnl_unlock(); +} + +void reg_leave_invalid_chans_wk(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); + struct wireless_dev *wdev; + + lockdep_assert_held(&wiphy->mtx); + + list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { + if (reg_wdev_chan_valid(wiphy, wdev)) + continue; + + /* + * Tearing down NAN needs the RTNL for closing NAN_DATA + * interfaces, handle that separately. + */ + if (wdev->iftype == NL80211_IFTYPE_NAN) + schedule_work(&rdev->reg_leave_nan_wk); + else + cfg80211_leave_locked(rdev, wdev, -1); + } } static void reg_check_chans_work(struct work_struct *work) @@ -2466,12 +2499,13 @@ static void reg_check_chans_work(struct work_struct *work) struct cfg80211_registered_device *rdev; pr_debug("Verifying active interfaces after reg change\n"); - rtnl_lock(); - for_each_rdev(rdev) - reg_leave_invalid_chans(&rdev->wiphy); + rcu_read_lock(); - rtnl_unlock(); + list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) + wiphy_work_queue(&rdev->wiphy, &rdev->reg_check_chans_wk); + + rcu_read_unlock(); } void reg_check_channels(void) diff --git a/net/wireless/reg.h b/net/wireless/reg.h index fc31c5f9a61abc..c587079ead8fbb 100644 --- a/net/wireless/reg.h +++ b/net/wireless/reg.h @@ -178,6 +178,22 @@ int reg_reload_regdb(void); */ void reg_check_channels(void); +/** + * reg_leave_invalid_chans_wk - check if channels are no longer usable and leave + * @wiphy: the wiphy to check + * @work: the work struct + */ +void reg_leave_invalid_chans_wk(struct wiphy *wiphy, struct wiphy_work *work); + +/** + * reg_leave_invalid_nan_wk - check channels and tear down NAN when unusable + * @work: the work struct + * + * Stopping a NAN interface needs the RTNL, so it cannot be done from + * reg_leave_invalid_chans_wk() which runs with the wiphy mutex held. + */ +void reg_leave_invalid_nan_wk(struct work_struct *work); + extern const u8 shipped_regdb_certs[]; extern unsigned int shipped_regdb_certs_len; extern const u8 extra_regdb_certs[]; From f5dd0626d2b07c6f5c8c763d75d13c6307393bca Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:05 +0200 Subject: [PATCH 0171/1417] wifi: mac80211: don't apply peer rates to off-channel frames All the off-channel frames (including scan) aren't really part of the connection, so don't apply the station rates even if they're being sent to the station in question (e.g. by accident). They don't use the rate mask via IEEE80211_TX_CTRL_DONT_USE_RATE_MASK, but the station might not have rates of them either, hitting the warning found by syzbot. Assisted-by: LLM Reported-by: syzbot+34463a129786910405dd@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=34463a129786910405dd Link: https://patch.msgid.link/20260904165722.ade6b07421b8.I59b7ea810eb021a7a68b3090828a757b6dd85e57@changeid Signed-off-by: Johannes Berg --- net/mac80211/rate.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c index 64768abb0a5f29..e910f03af77756 100644 --- a/net/mac80211/rate.c +++ b/net/mac80211/rate.c @@ -372,6 +372,14 @@ static void __rate_control_send_low(struct ieee80211_hw *hw, u32 rate_flags = 0; int i; + /* + * Frames that shouldn't use the rate mask could be anything, + * even on a different band, so don't take the sta into account + * to avoid ending up without rates. + */ + if (info->control.flags & IEEE80211_TX_CTRL_DONT_USE_RATE_MASK) + sta = NULL; + if (sband->band == NL80211_BAND_S1GHZ) { info->control.rates[0].flags |= IEEE80211_TX_RC_S1G_MCS; info->control.rates[0].idx = 0; From 23c68b4aaf5e18ab95532fc6714b737d5b7c701c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:06 +0200 Subject: [PATCH 0172/1417] wifi: mac80211: don't drop scan probe requests for lack of peer rates While software scanning, ieee80211_tx_h_rate_ctrl() warns and drops the frame if the target station has no usable bitrate on the band that's currently being scanned. But that's really meant for data frames, not if we happen to scan for the BSSID on the wrong band, which can be constructed easily. Skip the check for IEEE80211_TX_CTRL_DONT_USE_RATE_MASK, the previous commit also ignored the station rate mask for such frames as well. Assisted-by: LLM Reported-by: syzbot+0d516b33238bd97ee864@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0d516b33238bd97ee864 Link: https://patch.msgid.link/20260904165722.b57ea4ab82d3.Id6c9c42d5cef5901bfac88853647b03ba4077b3e@changeid Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 3896c7b2c4e5ce..d155fb319a556f 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -744,10 +744,12 @@ ieee80211_tx_h_rate_ctrl(struct ieee80211_tx_data *tx) assoc = test_sta_flag(tx->sta, WLAN_STA_ASSOC); /* - * Lets not bother rate control if we're associated and cannot - * talk to the sta. This should not happen. + * Lets not bother rate control if we're associated and cannot talk to + * the sta. This should not happen - except for frames that aren't + * really for the peer to start with and already ignore rates. */ - if (WARN(test_bit(SCAN_SW_SCANNING, &tx->local->scanning) && assoc && + if (!(info->control.flags & IEEE80211_TX_CTRL_DONT_USE_RATE_MASK) && + WARN(test_bit(SCAN_SW_SCANNING, &tx->local->scanning) && assoc && !rate_usable_index_exists(sband, &tx->sta->sta), "%s: Dropped data frame as no usable bitrate found while " "scanning and associated. Target station: " From 733f0fde95392ed5f61a4e36aee661ea8d0e8581 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:07 +0200 Subject: [PATCH 0173/1417] wifi: mac80211: don't start a ROC while scanning The ROC work can be pending when a scan starts (which requires ROC list to be empty, but that's possible), and then a new ROC can be added to the list and the work will pick it up. Avoid starting that ROC if a scan made it between things, as otherwise we'll hit a warning later: WARNING: net/mac80211/offchannel.c:404 at ieee80211_start_next_roc+0x256/0x2d0 Workqueue: events_unbound cfg80211_wiphy_work Call Trace: __ieee80211_scan_completed+0x4fd/0xe40 net/mac80211/scan.c:537 ieee80211_scan_work+0x472/0x1ff0 net/mac80211/scan.c:1193 cfg80211_wiphy_work+0x410/0x570 net/wireless/core.c:513 Assisted-by: LLM Fixes: aaa016ccd5df ("mac80211: rewrite remain-on-channel logic") Reported-by: syzbot+c3a167b5615df4ccd7fb@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c3a167b5615df4ccd7fb Link: https://patch.msgid.link/20260904165722.f9d5b150edd8.I61bc9de8c8d089096ad695213b9c85c7df38c3bd@changeid Signed-off-by: Johannes Berg --- net/mac80211/offchannel.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 7acef80d5f1fb8..e30767853c361b 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -460,6 +460,13 @@ static void __ieee80211_roc_work(struct ieee80211_local *local) return; if (!roc->started) { + /* + * The work can be started by a previous ROC work, but a scan + * can get between things; scan finish will retrigger us. + */ + if (local->scanning) + return; + WARN_ON(!local->emulate_chanctx); _ieee80211_start_next_roc(local); } else { From a7491b7efbd9136b120a12ed72af9c12121dd134 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:08 +0200 Subject: [PATCH 0174/1417] wifi: mac80211: don't warn when an IBSS has no channel to scan ieee80211_request_ibss_scan() warns when regulatory leaves no allowed channel, but that can happen as the regdomain can change while IBSS is operating, and it can continue to operate briefly during the 60s grace period until it's shut down. Just remove the warning in this case. Assisted-by: LLM Fixes: 34bcf7150241 ("mac80211: fix ibss scanning") Reported-by: syzbot+1634c5399e29d8b66789@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1634c5399e29d8b66789 Link: https://patch.msgid.link/20260904165722.fe380c27fef4.I0e8bee2e12a40d240851a4bc724d47753af46159@changeid Signed-off-by: Johannes Berg --- net/mac80211/scan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index eeff230bd909fe..8e950ef6d1ead9 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -1242,7 +1242,7 @@ int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata, } } - if (WARN_ON_ONCE(n_ch == 0)) + if (n_ch == 0) return -EINVAL; local->int_scan_req->n_channels = n_ch; From 362bd5bce29ed0f6fd3d39a7065567777d70606e Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:09 +0200 Subject: [PATCH 0175/1417] wifi: mac80211: don't offload TC setup on AP_VLAN interfaces AP_VLAN interfaces are purely virtual, so don't try to offload TC setup to drivers. We can't really use the AP interface either since we may not know it all the time, and it could technically even change. Just reject the TC offload so things get done in software. Assisted-by: LLM Fixes: 61587f1556fe ("wifi: mac80211: add support for letting drivers register tc offload support") Reported-by: syzbot+f1ba58d6b55abd13239e@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f1ba58d6b55abd13239e Link: https://patch.msgid.link/20260904165722.726cc076cecb.Iccfd88b13635425e850ce031376eb60a4ce5f4f8@changeid Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 43460a705a6bd0..8c300e045fdf98 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -935,6 +935,9 @@ static int ieee80211_netdev_setup_tc(struct net_device *dev, struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; + if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) + return -EOPNOTSUPP; + return drv_net_setup_tc(local, sdata, dev, type, type_data); } From bf29d085e0eba92388518719d044f4702a8c6644 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:10 +0200 Subject: [PATCH 0176/1417] wifi: mac80211: suppress chanctx warning for debugfs reset Before suspend all the channel contexts should removed, so the warning makes sense and should be there, but during reset the same code is called without first removing. Limit the check to the real suspend case. Assisted-by: LLM Fixes: 12e7f517029d ("mac80211: cleanup generic suspend/resume procedures") Reported-by: syzbot+56a1a45a9a2c04d425ff@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=56a1a45a9a2c04d425ff Link: https://patch.msgid.link/20260904165722.fe46395e310b.Ic4aaa95bd9d0ceb6a3cd7d84c425afee7d7d3dd7@changeid Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 2 +- net/mac80211/debugfs.c | 2 +- net/mac80211/ieee80211_i.h | 2 +- net/mac80211/pm.c | 8 +++++--- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 1f074799f85ffe..2b13c057312e5d 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -3475,7 +3475,7 @@ static int ieee80211_set_txq_params(struct wiphy *wiphy, static int ieee80211_suspend(struct wiphy *wiphy, struct cfg80211_wowlan *wowlan) { - return __ieee80211_suspend(wiphy_priv(wiphy), wowlan); + return __ieee80211_suspend(wiphy_priv(wiphy), wowlan, false); } static int ieee80211_resume(struct wiphy *wiphy) diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index 105653a16b68b4..e38631d7cfb423 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -384,7 +384,7 @@ static ssize_t reset_write(struct file *file, const char __user *user_buf, rtnl_lock(); wiphy_lock(local->hw.wiphy); - __ieee80211_suspend(&local->hw, NULL); + __ieee80211_suspend(&local->hw, NULL, true); ret = __ieee80211_resume(&local->hw); wiphy_unlock(local->hw.wiphy); diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 5761e962149148..d05f594673990a 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2432,7 +2432,7 @@ int ieee80211_reconfig(struct ieee80211_local *local); void ieee80211_stop_device(struct ieee80211_local *local, bool suspend); int __ieee80211_suspend(struct ieee80211_hw *hw, - struct cfg80211_wowlan *wowlan); + struct cfg80211_wowlan *wowlan, bool reset); static inline int __ieee80211_resume(struct ieee80211_hw *hw) { diff --git a/net/mac80211/pm.c b/net/mac80211/pm.c index 5a508d99e84f73..f63676c4485363 100644 --- a/net/mac80211/pm.c +++ b/net/mac80211/pm.c @@ -18,7 +18,8 @@ static void ieee80211_sched_scan_cancel(struct ieee80211_local *local) cfg80211_sched_scan_stopped_locked(local->hw.wiphy, 0); } -int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) +int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan, + bool reset) { struct ieee80211_local *local = hw_to_local(hw); struct ieee80211_sub_if_data *sdata; @@ -166,9 +167,10 @@ int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) /* * We disconnected on all interfaces before suspend, all channel - * contexts should be released. + * contexts should be released, but on 'reset' debugfs that's + * not true so don't check there. */ - WARN_ON(!list_empty(&local->chanctx_list)); + WARN_ON(!reset && !list_empty(&local->chanctx_list)); /* stop hardware - this must stop RX */ ieee80211_stop_device(local, true); From ac7472a24bd433b81c06582835dd1d5547c10da9 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:11 +0200 Subject: [PATCH 0177/1417] wifi: mac80211: abort chanswitch when leaving a mesh The code in ieee80211_stop_mesh() leaves CSA active, but leaving the mesh released the channel context, so the CSA finalize work crashes: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000003 KASAN: null-ptr-deref in range [0x0000000000000018-0x000000000000001f] RIP: 0010:ieee80211_put_srates_elem+0x42/0x640 net/mac80211/util.c:3272 Call Trace: ieee80211_mesh_build_beacon+0xa83/0x1b50 net/mac80211/mesh.c:1093 ieee80211_mesh_rebuild_beacon+0xc7/0x170 net/mac80211/mesh.c:1147 ieee80211_mesh_finish_csa+0x131/0x210 net/mac80211/mesh.c:1542 ieee80211_set_after_csa_beacon net/mac80211/cfg.c:4085 [inline] __ieee80211_csa_finalize net/mac80211/cfg.c:4133 [inline] ieee80211_csa_finalize+0x633/0x1150 net/mac80211/cfg.c:4155 cfg80211_wiphy_work+0x2ab/0x450 net/wireless/core.c:438 Abort the channel switch properly. Assisted-by: LLM Fixes: b8456a14e9d2 ("{nl,cfg,mac}80211: implement mesh channel switch userspace API") Reported-by: syzbot+81cd9dc1596563141d19@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=81cd9dc1596563141d19 Link: https://patch.msgid.link/20260904165722.d0b87eee08aa.I80550d6127e0bb26efb49a5fbe95be1aef1cd0cb@changeid Signed-off-by: Johannes Berg --- net/mac80211/mesh.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index d4507e4e6ec16d..bed7ac8382505b 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -1204,6 +1204,10 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata) netif_carrier_off(sdata->dev); + /* abort any running channel switch */ + sdata->vif.bss_conf.csa_active = false; + ieee80211_vif_unblock_queues_csa(sdata); + /* flush STAs and mpaths on this iface */ sta_info_flush(sdata, -1); ieee80211_free_keys(sdata, true); From 3f28551d0241254a75626d868041c6340285088b Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:12 +0200 Subject: [PATCH 0178/1417] wifi: mac80211: reset state when starting AP fails ieee80211_start_ap() can set enable_beacon (and beacon_int) and fail later, leaving it set forever. Scanning can then attempt to restore beaconing on such an interface, leading to: Oops: divide error: 0000 [#1] SMP KASAN NOPTI RIP: 0010:mac80211_hwsim_link_info_changed+0xca7/0xf00 Call Trace: drv_link_info_changed+0x413/0x860 net/mac80211/driver-ops.c:495 ieee80211_link_info_change_notify+0x24b/0x3c0 net/mac80211/main.c:427 ieee80211_offchannel_return+0x381/0x580 net/mac80211/offchannel.c:160 __ieee80211_scan_completed+0x993/0xe30 net/mac80211/scan.c:519 ieee80211_scan_work+0x472/0x2010 net/mac80211/scan.c:1193 cfg80211_wiphy_work+0x2b7/0x550 net/wireless/core.c:538 in hwsim. Also, cfg80211 then allows changing the interface type, and the off-channel path getgs confused about beaconing as well, leading to another warning: WARNING: net/mac80211/driver-ops.c:468 at drv_link_info_changed+0x583/0x880 ieee80211_link_info_change_notify+0x24b/0x3c0 net/mac80211/main.c:427 ieee80211_offchannel_stop_vifs+0x328/0x5c0 net/mac80211/offchannel.c:122 ieee80211_start_sw_scan net/mac80211/scan.c:583 [inline] __ieee80211_start_scan+0xfb6/0x1af0 net/mac80211/scan.c:882 Reset the state on failures to always have it correct. Assisted-by: LLM Fixes: d6a83228823f ("mac80211: track enable_beacon explicitly") Reported-by: syzbot+ca7a2759caaa6cd4e3db@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ca7a2759caaa6cd4e3db Reported-by: syzbot+c4686c3eb8b64032618f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c4686c3eb8b64032618f Link: https://patch.msgid.link/20260904165722.9629429a5221.I7f599412bfe12a09d41ea4901be9ad165d07d133@changeid Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 2b13c057312e5d..2d5a0abe35dbb0 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1929,6 +1929,9 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, return 0; error: + link_conf->enable_beacon = false; + link_conf->beacon_int = prev_beacon_int; + sdata->vif.cfg.ssid_len = 0; ieee80211_link_release_channel(link); return err; From 78183e8331958fda11cd2b6850bb424a9747c4b2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:13 +0200 Subject: [PATCH 0179/1417] wifi: mac80211: reset the LED state when ifup fails When the first interface comes up, the radio LED is turned on. This can start the TPT trigger timer, which continues running. But if bringing up the interface fails then the timer keeps running and won't be stopped by anything, eventually it can be freed: ODEBUG: free active (active state 0) object: ffff888127e12130 object type: timer_list hint: tpt_trig_timer+0x0/0x300 net/mac80211/led.c:145 WARNING: CPU: 0 PID: 5923 at lib/debugobjects.c:612 debug_print_object+0x1a2/0x2b0 debug_check_no_obj_freed+0x4b7/0x600 lib/debugobjects.c:1129 kfree+0x436/0x670 mm/slub.c:6818 ieee80211_led_exit+0x162/0x1c0 net/mac80211/led.c:210 ieee80211_unregister_hw+0x27e/0x3a0 net/mac80211/main.c:1706 rt2x00lib_remove_dev+0x55b/0x670 Undo the LED state in the error path. Assisted-by: LLM Fixes: 67408c8c7b9d ("mac80211: selective throughput LED trigger active") Reported-by: syzbot+e84ecca6d1fa09a9b3d9@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e84ecca6d1fa09a9b3d9 Link: https://patch.msgid.link/20260904165722.044aa432f873.I601a67a2cd558b8ef8416a07554ae7efe896e9d8@changeid Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 8c300e045fdf98..4c34c3287eb4af 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -1606,8 +1606,12 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up) err_del_interface: drv_remove_interface(local, sdata); err_stop: - if (!local->open_count) + if (!local->open_count) { + ieee80211_led_radio(local, false); + ieee80211_mod_tpt_led_trig(local, 0, + IEEE80211_TPT_LEDTRIG_FL_RADIO); drv_stop(local, false); + } if (sdata->vif.type == NL80211_IFTYPE_NAN_DATA) RCU_INIT_POINTER(sdata->u.nan_data.nmi, NULL); if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) From 6f0a100df8539ce90f37c14e1945f396ca2410bc Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:14 +0200 Subject: [PATCH 0180/1417] wifi: mac80211: only operate on TDLS peers in the TDLS code ieee80211_tdls_oper() can operate on the AP station, which then yields various warnings when the AP station is removed then or at a later point in time after being confused for a TDLS peer. Always check that the station is a TDLS peer. Assisted-by: LLM Fixes: dfe018bf9953 ("mac80211: handle TDLS high-level commands and frames") Fixes: 17e6a59a365a ("mac80211: cleanup TDLS state during failed setup") Reported-by: syzbot+a59b5291776979816910@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a59b5291776979816910 Link: https://patch.msgid.link/20260904165722.3bad8b79679b.I99618745e83cbe9b9804179387be15fcd3505ae3@changeid Signed-off-by: Johannes Berg --- net/mac80211/tdls.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c index dc2f662fe4c464..7f40b1d6293806 100644 --- a/net/mac80211/tdls.c +++ b/net/mac80211/tdls.c @@ -1142,6 +1142,7 @@ ieee80211_tdls_mgmt_setup(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_local *local = sdata->local; enum ieee80211_smps_mode smps_mode = sdata->deflink.u.mgd.driver_smps_mode; + struct sta_info *sta; int ret; /* don't support setup with forced SMPS mode that's not off */ @@ -1168,14 +1169,10 @@ ieee80211_tdls_mgmt_setup(struct wiphy *wiphy, struct net_device *dev, * Allow error packets to be sent - sometimes we don't even add a STA * before failing the setup. */ - if (status_code == 0) { - rcu_read_lock(); - if (!sta_info_get(sdata, peer)) { - rcu_read_unlock(); - ret = -ENOLINK; - goto out_unlock; - } - rcu_read_unlock(); + sta = sta_info_get(sdata, peer); + if ((status_code == 0 && !sta) || (sta && !sta->sta.tdls)) { + ret = -ENOLINK; + goto out_unlock; } ieee80211_flush_queues(local, sdata, false); @@ -1442,6 +1439,10 @@ int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, */ tdls_dbg(sdata, "TDLS oper %d peer %pM\n", oper, peer); + sta = sta_info_get(sdata, peer); + if (!sta || !sta->sta.tdls) + return -ENOLINK; + switch (oper) { case NL80211_TDLS_ENABLE_LINK: if (sdata->vif.bss_conf.csa_active) { @@ -1449,10 +1450,6 @@ int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, return -EBUSY; } - sta = sta_info_get(sdata, peer); - if (!sta || !sta->sta.tdls) - return -ENOLINK; - iee80211_tdls_recalc_chanctx(sdata, sta); iee80211_tdls_recalc_ht_protection(sdata, sta); From eeee52cfd1d639774c9812e8890631404a057dd2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:01:59 +0200 Subject: [PATCH 0181/1417] wifi: cfg80211: restore netns_immutable on failures Switching a wiphy's netns has to clear netns_immutable before moving interfaces, but then if any of the interfaces fails to move, it gets netns_immutable cleared forever. Then userspace can move it by itself, breaking the assumption that they all move together. Fix the order here and always reset netns_immutable after attempting the move. Assisted-by: LLM Fixes: 463d018323851 ("cfg80211: make aware of net namespaces") Link: https://patch.msgid.link/20260904170220.7ea88157dcbc.Id868585a790be8b9ece9b39b0db464a5963faaf3@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 668380deec7da5..043bb57b05565b 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -167,9 +167,9 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, continue; wdev->netdev->netns_immutable = false; err = dev_change_net_namespace(wdev->netdev, net, "wlan%d"); + wdev->netdev->netns_immutable = true; if (err) break; - wdev->netdev->netns_immutable = true; } if (err) { From a41bd1938a9bfe226d444172a7e20e4bd5097960 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:02:00 +0200 Subject: [PATCH 0182/1417] wifi: cfg80211: undo netns switch if renaming the wiphy fails Once all the interfaces have been moved, cfg80211_switch_netns() moves the wiphy itself by setting its network namespace and then renaming it, which makes sysfs move it. The rename can fail (but only on allocation failures), leaving things mixed up and hitting the warning there. Ignoring it isn't great, undo the move and let the change fail in this case. If undo fails then WARN, then things would again be stuck in two different network namespaces. Assisted-by: LLM Reported-by: syzbot+3515319a302224e081b4@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3515319a302224e081b4 Fixes: 463d018323851 ("cfg80211: make aware of net namespaces") Link: https://patch.msgid.link/20260904170220.7966cc705e33.Ib398351113bbd3cab85302467060cab378564421@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 88 +++++++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 043bb57b05565b..9ee1c36f1262b9 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -153,9 +153,25 @@ int cfg80211_dev_rename(struct cfg80211_registered_device *rdev, return 0; } +static int cfg80211_switch_wdev_netns(struct wireless_dev *wdev, + struct net *net) +{ + int err; + + if (!wdev->netdev) + return 0; + + wdev->netdev->netns_immutable = false; + err = dev_change_net_namespace(wdev->netdev, net, "wlan%d"); + wdev->netdev->netns_immutable = true; + + return err; +} + int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, struct net *net) { + struct net *old_net = wiphy_net(&rdev->wiphy); struct wireless_dev *wdev; int err = 0; @@ -163,58 +179,54 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, return -EOPNOTSUPP; list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { - if (!wdev->netdev) - continue; - wdev->netdev->netns_immutable = false; - err = dev_change_net_namespace(wdev->netdev, net, "wlan%d"); - wdev->netdev->netns_immutable = true; + err = cfg80211_switch_wdev_netns(wdev, net); if (err) - break; + goto undo; } - if (err) { - /* failed -- clean up to old netns */ - net = wiphy_net(&rdev->wiphy); - - list_for_each_entry_continue_reverse(wdev, - &rdev->wiphy.wdev_list, - list) { + scoped_guard(wiphy, &rdev->wiphy) { + list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { if (!wdev->netdev) continue; - wdev->netdev->netns_immutable = false; - err = dev_change_net_namespace(wdev->netdev, net, - "wlan%d"); - WARN_ON(err); - wdev->netdev->netns_immutable = true; + nl80211_notify_iface(rdev, wdev, + NL80211_CMD_DEL_INTERFACE); } - return err; - } + nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY); - guard(wiphy)(&rdev->wiphy); + wiphy_net_set(&rdev->wiphy, net); - list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { - if (!wdev->netdev) - continue; - nl80211_notify_iface(rdev, wdev, NL80211_CMD_DEL_INTERFACE); - } - - nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY); + /* this only fails on allocation failure */ + err = device_rename(&rdev->wiphy.dev, + dev_name(&rdev->wiphy.dev)); + if (err) + wiphy_net_set(&rdev->wiphy, old_net); - wiphy_net_set(&rdev->wiphy, net); + nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY); - err = device_rename(&rdev->wiphy.dev, dev_name(&rdev->wiphy.dev)); - WARN_ON(err); + list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { + if (!wdev->netdev) + continue; + nl80211_notify_iface(rdev, wdev, + NL80211_CMD_NEW_INTERFACE); + } + } - nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY); + if (!err) + return 0; - list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { - if (!wdev->netdev) - continue; - nl80211_notify_iface(rdev, wdev, NL80211_CMD_NEW_INTERFACE); - } + /* set to the last one to undo all of them */ + wdev = list_entry(&rdev->wiphy.wdev_list, typeof(*wdev), list); +undo: + /* + * Move back everything, if this fails again (allocation failures) + * then things get stuck in different network namespaces. + */ + list_for_each_entry_continue_reverse(wdev, &rdev->wiphy.wdev_list, + list) + WARN_ON(cfg80211_switch_wdev_netns(wdev, old_net)); - return 0; + return err; } static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data) From eee2efd82867b623982ac51925b5a1812a74c50d Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:02:01 +0200 Subject: [PATCH 0183/1417] wifi: mac80211: unlist vifs when their netdev is unregistered mac80211 only removes vifs from the local->interfaces list when an interface is removed via ieee80211_if_remove(), before it unregisters the netdev. However, it's possible for a netdev to be unregistered without going through that: When the netns that holds the wiphy is destroyed, the wiphy is supposed to move to the init_ns, but that can run into allocation failures. Then, mac80211 has an interface listed that doesn't exist, and will eventually hit BUG: failure at net/wireless/core.h:141/wiphy_to_rdev()! ... _cfg80211_unregister_wdev+0x24/0x36a [cfg80211] cfg80211_unregister_wdev+0x15/0x1d [cfg80211] ieee80211_remove_interfaces+0x1ff/0x257 [mac80211] ieee80211_unregister_hw+0x73/0x1d1 [mac80211] mac80211_hwsim_del_radio+0x114/0x166 [mac80211_hwsim] Remove the interface from the list in ->ndo_uninit if it's still around to avoid this. Assisted-by: LLM Fixes: 463d018323851 ("cfg80211: make aware of net namespaces") Link: https://patch.msgid.link/20260904170220.038ad73e6c04.I990abca78483e058746b6f42b4796717c3028164@changeid Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 4c34c3287eb4af..842bfb4a7cb684 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -924,9 +924,33 @@ static void ieee80211_teardown_sdata(struct ieee80211_sub_if_data *sdata) } } +/* + * The netdev can be unregistered without mac80211 doing it, e.g. by the netdev + * core when cfg80211 couldn't move it out of a network namespace that's being + * destroyed. Drop it from the interface list either way. + */ +static void ieee80211_unlist_sdata(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_sub_if_data *iter; + + ASSERT_RTNL(); + + list_for_each_entry(iter, &local->interfaces, list) { + if (iter != sdata) + continue; + guard(mutex)(&local->iflist_mtx); + list_del_rcu(&sdata->list); + return; + } +} + static void ieee80211_uninit(struct net_device *dev) { - ieee80211_teardown_sdata(IEEE80211_DEV_TO_SUB_IF(dev)); + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + + ieee80211_unlist_sdata(sdata); + ieee80211_teardown_sdata(sdata); } static int ieee80211_netdev_setup_tc(struct net_device *dev, From 4635b1a1c1d693178a537446a6e09963f0fdae52 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:02:02 +0200 Subject: [PATCH 0184/1417] wifi: cfg80211: get the wiphy out of a dying network namespace When a network namespace is destroyed, cfg80211_pernet_exit() moves any wiphy back to the initial namespace, and just warns if that fails. But moving an interface can fail (due to allocation failures), and then the wiphy is left behind with a garbage netns pointer: Kernel mode fault at addr 0x30 genlmsg_multicast_netns.constprop.0+0x46/0xcf [cfg80211] nl80211_notify_wiphy+0xcd/0xe8 [cfg80211] wiphy_unregister+0x169/0x3fc [cfg80211] Note that commit debac3a20dec ("net: Remove conflicting altnames for dying netns in __dev_change_net_namespace().") fixed another path that could reach it without allocation failures. Remove interfaces that cannot be moved instead of failing the switch, so that the wiphy always ends up in the initial namespace. In this case the netdev core will unregister the interfaces anyway. Assisted-by: LLM Reported-by: syzbot+c5f8a81e794d4a4f2014@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c5f8a81e794d4a4f2014 Fixes: 463d018323851 ("cfg80211: make aware of net namespaces") Link: https://patch.msgid.link/20260904170220.7f3edc6d9992.I5e57921011244d3d8ef14d89e738aa19a5d972a0@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 9ee1c36f1262b9..25dd1a4d6b4e8d 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -168,20 +168,24 @@ static int cfg80211_switch_wdev_netns(struct wireless_dev *wdev, return err; } -int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, - struct net *net) +static int __cfg80211_switch_netns(struct cfg80211_registered_device *rdev, + struct net *net, bool force) { struct net *old_net = wiphy_net(&rdev->wiphy); - struct wireless_dev *wdev; + struct wireless_dev *wdev, *tmp; int err = 0; - if (!(rdev->wiphy.flags & WIPHY_FLAG_NETNS_OK)) - return -EOPNOTSUPP; - - list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { + list_for_each_entry_safe(wdev, tmp, &rdev->wiphy.wdev_list, list) { err = cfg80211_switch_wdev_netns(wdev, net); - if (err) + if (!err) + continue; + if (!force) goto undo; + /* remove interfaces that fail to allow wiphy switching */ + dev_close(wdev->netdev); + scoped_guard(wiphy, &rdev->wiphy) + cfg80211_unregister_wdev(wdev); + err = 0; } scoped_guard(wiphy, &rdev->wiphy) { @@ -199,7 +203,7 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, /* this only fails on allocation failure */ err = device_rename(&rdev->wiphy.dev, dev_name(&rdev->wiphy.dev)); - if (err) + if (err && !force) wiphy_net_set(&rdev->wiphy, old_net); nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY); @@ -212,8 +216,8 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, } } - if (!err) - return 0; + if (!err || force) + return err; /* set to the last one to undo all of them */ wdev = list_entry(&rdev->wiphy.wdev_list, typeof(*wdev), list); @@ -229,6 +233,15 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, return err; } +int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, + struct net *net) +{ + if (!(rdev->wiphy.flags & WIPHY_FLAG_NETNS_OK)) + return -EOPNOTSUPP; + + return __cfg80211_switch_netns(rdev, net, false); +} + static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data) { struct cfg80211_registered_device *rdev = data; @@ -1882,7 +1895,7 @@ static void __net_exit cfg80211_pernet_exit(struct net *net) rtnl_lock(); for_each_rdev(rdev) { if (net_eq(wiphy_net(&rdev->wiphy), net)) - WARN_ON(cfg80211_switch_netns(rdev, &init_net)); + WARN_ON(__cfg80211_switch_netns(rdev, &init_net, true)); } rtnl_unlock(); } From 87840d4a3a21b1c19b867a80e16ba69dff284de2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:01:35 +0200 Subject: [PATCH 0185/1417] wifi: mac80211_hwsim: don't hand frames to mac80211 while stopping The code checks ->started for frames coming from wmediumd, but the radio can be stopped after the check and before frame delivery, causing mac80211 to hit the WARN_ON(!local->started). Expand the mutex for this case and synchronise against it when the radio is stopped to avoid being able to hit the warning with hwsim. Drop the error print that would've complicated the error path, it only triggers for allocation failures (already noisy) and malformed frames anyway. Assisted-by: LLM Fixes: 7882513bacb1 ("mac80211_hwsim driver support userspace frame tx/rx") Reported-by: syzbot+b4aa2b672b18f1d4dc5f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b4aa2b672b18f1d4dc5f Link: https://patch.msgid.link/20260904170140.5f69a10d606b.I4a7921d00643f69e439c7a3b221d104f66a3dcdc@changeid Signed-off-by: Johannes Berg --- .../wireless/virtual/mac80211_hwsim_main.c | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 02b6d81cccd118..b9446577ff4997 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -2327,7 +2327,12 @@ static void mac80211_hwsim_stop(struct ieee80211_hw *hw, bool suspend) struct sk_buff *skb; int i; - data->started = false; + /* + * Serialise against wmediumd userspace, so no more frames + * can be handed to mac80211 after this returns. + */ + scoped_guard(mutex, &data->mutex) + data->started = false; for (i = 0; i < ARRAY_SIZE(data->link_data); i++) hrtimer_cancel(&data->link_data[i].beacon_timer); @@ -6505,12 +6510,12 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, if (frame_data_len < sizeof(struct ieee80211_hdr_3addr) || frame_data_len > IEEE80211_MAX_DATA_LEN) - goto err; + goto out; /* Allocate new skb here */ skb = alloc_skb(frame_data_len, GFP_KERNEL); if (skb == NULL) - goto err; + goto out; /* Copy the data */ skb_put_data(skb, frame_data, frame_data_len); @@ -6535,10 +6540,17 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, goto out; } + /* + * Serialise against mac80211_hwsim_stop() - mac80211 doesn't allow + * frames reported while the HW is down, hence the ->started check + * must be under mutex. + */ + mutex_lock(&data2->mutex); + /* check if radio is configured properly */ if ((data2->idle && !data2->tmp_chan) || !data2->started) - goto out; + goto out_unlock; /* A frame is received from user space */ memset(&rx_status, 0, sizeof(rx_status)); @@ -6557,22 +6569,18 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, iter_data.channel = ieee80211_get_channel(data2->hw->wiphy, rx_status.freq); if (!iter_data.channel) - goto out; + goto out_unlock; rx_status.band = iter_data.channel->band; - mutex_lock(&data2->mutex); if (!hwsim_chans_compat(iter_data.channel, channel)) { ieee80211_iterate_active_interfaces_atomic( data2->hw, IEEE80211_IFACE_ITER_NORMAL, mac80211_hwsim_tx_iter, &iter_data); - if (!iter_data.receive) { - mutex_unlock(&data2->mutex); - goto out; - } + if (!iter_data.receive) + goto out_unlock; } - mutex_unlock(&data2->mutex); } else if (!channel) { - goto out; + goto out_unlock; } else { rx_status.freq = channel->center_freq; rx_status.band = channel->band; @@ -6580,7 +6588,7 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, rx_status.rate_idx = nla_get_u32(info->attrs[HWSIM_ATTR_RX_RATE]); if (rx_status.rate_idx >= data2->hw->wiphy->bands[rx_status.band]->n_bitrates) - goto out; + goto out_unlock; rx_status.signal = nla_get_u32(info->attrs[HWSIM_ATTR_SIGNAL]); hdr = (void *)skb->data; @@ -6590,10 +6598,11 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, rx_status.boottime_ns = ktime_get_boottime_ns(); mac80211_hwsim_rx(data2, &rx_status, skb); + mutex_unlock(&data2->mutex); return 0; -err: - pr_debug("mac80211_hwsim: error occurred in %s\n", __func__); +out_unlock: + mutex_unlock(&data2->mutex); out: dev_kfree_skb(skb); return -EINVAL; From e6c5ed7a98d7bc8b0f7918246f1c90ddb3f79dfa Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Fri, 4 Sep 2026 14:24:45 +0000 Subject: [PATCH 0186/1417] wifi: rsi: fix heap OOB write on key removal When a key is removed (data == NULL), rsi_hal_load_key() runs: memset(&set_key[FRAME_DESC_SZ], 0, frame_len - FRAME_DESC_SZ); set_key is a struct rsi_set_key *, so the subscript is scaled by sizeof(struct rsi_set_key) (160 bytes): &set_key[FRAME_DESC_SZ] is skb->data + 2560, and the memset writes 144 zero bytes starting 2.4KB past the end of the 160-byte skb data buffer, corrupting unrelated heap objects. The intended byte offset would have been (u8 *)set_key + FRAME_DESC_SZ. The write fires on every DISABLE_KEY callback, so plain disconnects, roams and interface teardowns trigger it on real networks. The memset is redundant: the whole buffer is zeroed right after allocation, so the frame sent to the device is byte-identical without it. Drop the else branch; normal operation is unaffected. Discovered by Atuin - Automated Vulnerability Discovery Engine. Fixes: dad0d04fa7ba ("rsi: Add RS9113 wireless driver") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Tianchu Chen Link: https://patch.msgid.link/90bb2b07007942064c04aa3729cedd9eb1e930b1@linux.dev Signed-off-by: Johannes Berg --- drivers/net/wireless/rsi/rsi_91x_mgmt.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/rsi/rsi_91x_mgmt.c b/drivers/net/wireless/rsi/rsi_91x_mgmt.c index bb167f03367bf7..d9dcbb2553176b 100644 --- a/drivers/net/wireless/rsi/rsi_91x_mgmt.c +++ b/drivers/net/wireless/rsi/rsi_91x_mgmt.c @@ -852,8 +852,6 @@ int rsi_hal_load_key(struct rsi_common *common, memcpy(set_key->tx_mic_key, &data[16], 8); memcpy(set_key->rx_mic_key, &data[24], 8); } - } else { - memset(&set_key[FRAME_DESC_SZ], 0, frame_len - FRAME_DESC_SZ); } skb_put(skb, frame_len); From c1ba7f7f18465e259cf1b4d9c73fc73853d7f790 Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Fri, 4 Sep 2026 13:39:34 +0000 Subject: [PATCH 0187/1417] wifi: wilc1000: fix RX buffer OOB-write in wilc_wlan_handle_isr_ext() wilc_wlan_handle_isr_ext() takes the RX transfer size from the device-reported interrupt status register (a 15-bit field shifted left by 2, up to 131068 bytes) and reads that many bytes from the device into rx_buffer, which is only WILC_RX_BUFF_SIZE (96K) large. The wrap check only handles the current offset; the size itself is never compared against the buffer, so a bogus SDIO device can make the driver OOB-write rx_buffer by up to ~32K with data it controls. The oversized transfer also leaves rx_buffer_offset past the end of the buffer, after which the unsigned wrap check stops working and the overflow can repeat. Drop any transfer whose size exceeds the RX buffer, acknowledging the data interrupt and re-arming the RX engine so the bogus frame is discarded and reception can continue. This also restores the rx_buffer_offset <= WILC_RX_BUFF_SIZE invariant the wrap check relies on. This is not expected to change driver behavior in most cases: without this check, an oversized transfer would most likely corrupt neighboring kernel memory instead of completing anyway, and the drop path performs the same interrupt acknowledgment and RX engine re-arming as the normal path, so subsequent transfers are received unaffected. Discovered by Atuin - Automated Vulnerability Discovery Engine. Fixes: c5c77ba18ea6 ("staging: wilc1000: Add SDIO/SPI 802.11 driver") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Tianchu Chen Link: https://patch.msgid.link/7c971924c6bdccf6c2f75704a5a746e9303aaf64@linux.dev Signed-off-by: Johannes Berg --- drivers/net/wireless/microchip/wilc1000/wlan.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/wireless/microchip/wilc1000/wlan.c b/drivers/net/wireless/microchip/wilc1000/wlan.c index 4b116fe6f9ea94..55a77a2e32887b 100644 --- a/drivers/net/wireless/microchip/wilc1000/wlan.c +++ b/drivers/net/wireless/microchip/wilc1000/wlan.c @@ -1197,6 +1197,15 @@ static void wilc_wlan_handle_isr_ext(struct wilc *wilc, u32 int_status) if (size <= 0) return; + /* A size exceeding the RX buffer is bogus; drop the transfer + * instead of overflowing the buffer. + */ + if (size > WILC_RX_BUFF_SIZE) { + wilc->hif_func->hif_clear_int_ext(wilc, + DATA_INT_CLR | ENABLE_RX_VMM); + return; + } + if (WILC_RX_BUFF_SIZE - offset < size) offset = 0; From e14bf37bb2b3853012ff160131d1c6233f7a9cc9 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:12 +0200 Subject: [PATCH 0188/1417] wifi: mac80211: don't allow injecting frames wider than the chanctx Frames injected on a monitor interface can carry a radiotap field requesting a bandwidth, which mac80211 passes down to the driver regardless of the the actual operational bandwidth. If the bandwidth requested is too wide, that triggers a warning in hwsim: WARN_ON(hwsim_get_chanwidth(bw) > hwsim_get_chanwidth(confbw)) Drop such frames entirely instead since they cannot be sent. Assisted-by: LLM Fixes: 646e76bb5daf ("mac80211: parse VHT info in injected frames") Reported-by: syzbot+435fdb053cf98bfa5778@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=435fdb053cf98bfa5778 Link: https://patch.msgid.link/20260908122838.201719-13-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- include/net/mac80211.h | 5 ++++- net/mac80211/iface.c | 2 +- net/mac80211/tx.c | 28 ++++++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 9d1fac6e808298..ed6a5874ff9650 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -7638,11 +7638,14 @@ bool ieee80211_tx_prepare_skb(struct ieee80211_hw *hw, * * @skb: packet injected by userspace * @dev: the &struct device of this 802.11 device + * @chandef: the channel definition the frame will be transmitted on, or + * %NULL to skip the bandwidth checks * * Return: %true if the radiotap header was parsed, %false otherwise */ bool ieee80211_parse_tx_radiotap(struct sk_buff *skb, - struct net_device *dev); + struct net_device *dev, + const struct cfg80211_chan_def *chandef); /** * struct ieee80211_noa_data - holds temporary data for tracking P2P NoA state diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 842bfb4a7cb684..ca66eb493ac7dd 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -991,7 +991,7 @@ static u16 ieee80211_monitor_select_queue(struct net_device *dev, /* reset flags and info before parsing radiotap header */ memset(info, 0, sizeof(*info)); - if (!ieee80211_parse_tx_radiotap(skb, dev)) + if (!ieee80211_parse_tx_radiotap(skb, dev, NULL)) return 0; /* doesn't matter, frame will be dropped */ len_rthdr = ieee80211_get_radiotap_len(skb->data); diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index d155fb319a556f..c343ed56506a61 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2105,8 +2105,29 @@ static bool ieee80211_validate_radiotap_len(struct sk_buff *skb) return true; } +static bool ieee80211_rate_bw_usable(u16 rate_flags, + const struct cfg80211_chan_def *chandef) +{ + int width; + + if (!chandef) + return true; + + if (rate_flags & IEEE80211_TX_RC_160_MHZ_WIDTH) + width = 160; + else if (rate_flags & IEEE80211_TX_RC_80_MHZ_WIDTH) + width = 80; + else if (rate_flags & IEEE80211_TX_RC_40_MHZ_WIDTH) + width = 40; + else + return true; + + return width <= cfg80211_chandef_get_width(chandef); +} + bool ieee80211_parse_tx_radiotap(struct sk_buff *skb, - struct net_device *dev) + struct net_device *dev, + const struct cfg80211_chan_def *chandef) { struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); struct ieee80211_radiotap_iterator iterator; @@ -2280,6 +2301,9 @@ bool ieee80211_parse_tx_radiotap(struct sk_buff *skb, struct ieee80211_supported_band *sband = local->hw.wiphy->bands[info->band]; + if (!ieee80211_rate_bw_usable(rate_flags, chandef)) + return false; + info->control.flags |= IEEE80211_TX_CTRL_RATE_INJECT; for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { @@ -2479,7 +2503,7 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb, * selected chandef above to accurately set injection rates and * retransmissions. */ - if (!ieee80211_parse_tx_radiotap(skb, dev)) + if (!ieee80211_parse_tx_radiotap(skb, dev, chandef)) goto fail_rcu; /* remove the injection radiotap header */ From 4504f3960dc4501c73be9f99eabda2e26e9db41e Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:13 +0200 Subject: [PATCH 0189/1417] wifi: mac80211: reset the AP_VLAN tailroom counter on ifdown On ifup, AP_VLAN interfaces get crypto_tx_tailroom_needed_cnt from the AP interface, but it's never decremented again unless the AP is also brought down. Thus, bringing the same AP_VLAN up again will increment the counter again and eventually hit the sanity check: WARN_ON_ONCE(sdata->crypto_tx_tailroom_needed_cnt != master->crypto_tx_tailroom_needed_cnt); Reset it on ifdown to avoid that. Assisted-by: LLM Fixes: f9dca80b98ca ("mac80211: fix AP_VLAN crypto tailroom calculation") Reported-by: syzbot+de3ee5362db09487ea37@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=de3ee5362db09487ea37 Link: https://patch.msgid.link/20260908122838.201719-14-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index ca66eb493ac7dd..889c32fd8de19f 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -616,6 +616,8 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do RCU_INIT_POINTER(sdata->vif.bss_conf.chanctx_conf, NULL); /* see comment in the default case below */ ieee80211_free_keys(sdata, true); + /* increased by AP value on ifup, so reset on ifdown */ + sdata->crypto_tx_tailroom_needed_cnt = 0; /* no need to tell driver */ break; case NL80211_IFTYPE_MONITOR: From 038e1d126304fd25d507fd4e671232df57bd1799 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:14 +0200 Subject: [PATCH 0190/1417] wifi: mac80211: require a peer station for TDLS setup confirm It's nonsense for the setup confirm to go to station that doesn't even exist, and it hits a warning when building the frame: WARN_ON_ONCE(!sta || !ap_sta) Only accept WLAN_TDLS_SETUP_CONFIRM when the station is already there as a TDLS station. Need to copy the call to ieee80211_tdls_prep_mgmt_packet() since the existing WLAN_TDLS_DISCOVERY_REQUEST already falls through to it. Assisted-by: LLM Fixes: 6f7eaa47e1de ("mac80211: add TDLS QoS param IE on setup-confirm") Reported-by: syzbot+e55106f8389651870be0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e55106f8389651870be0 Link: https://patch.msgid.link/20260908122838.201719-15-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/tdls.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c index 7f40b1d6293806..f663d28d9209cf 100644 --- a/net/mac80211/tdls.c +++ b/net/mac80211/tdls.c @@ -1281,6 +1281,24 @@ int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, peer_capability, initiator, extra_ies, extra_ies_len); break; + case WLAN_TDLS_SETUP_CONFIRM: { + struct sta_info *sta; + + sta = sta_info_get(sdata, peer); + if (!sta || !sta->sta.tdls) { + ret = -ENOLINK; + break; + } + + ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer, + link_id, action_code, + dialog_token, + status_code, + peer_capability, + initiator, extra_ies, + extra_ies_len, 0, NULL); + break; + } case WLAN_TDLS_DISCOVERY_REQUEST: /* * Protect the discovery so we can hear the TDLS discovery @@ -1289,7 +1307,6 @@ int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, */ drv_mgd_protect_tdls_discover(sdata->local, sdata, link_id); fallthrough; - case WLAN_TDLS_SETUP_CONFIRM: case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: /* no special handling */ ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer, From 370872d30349d81dec519e15ea2949fd63511cf7 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:15 +0200 Subject: [PATCH 0191/1417] wifi: mac80211: don't allow link changes when iface is down ieee80211_set_active_links() only checks that the interface is running in the inner __ieee80211_set_active_links(), after drv_can_activate_links() was already called, so using active_links on an interface that's down triggers the check-sdata-in-driver warning. Add the missing check in the debugfs file. Assisted-by: LLM Fixes: 3d9011029227 ("wifi: mac80211: implement link switching") Reported-by: syzbot+582469b3a9ef5f13606b@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=582469b3a9ef5f13606b Link: https://patch.msgid.link/20260908122838.201719-16-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/debugfs_netdev.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c index f3c6a41e49119d..8346d3eb114307 100644 --- a/net/mac80211/debugfs_netdev.c +++ b/net/mac80211/debugfs_netdev.c @@ -729,6 +729,9 @@ static ssize_t ieee80211_if_parse_active_links(struct ieee80211_sub_if_data *sda if (kstrtou16(buf, 0, &active_links) || !active_links) return -EINVAL; + if (!ieee80211_sdata_running(sdata)) + return -ENETDOWN; + return ieee80211_set_active_links(&sdata->vif, active_links) ?: buflen; } IEEE80211_IF_FILE_RW(active_links); From b481e64e4498e2c053d5954f546ee02338f6ab63 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:16 +0200 Subject: [PATCH 0192/1417] wifi: mac80211: don't RCU-dereference the mesh CSA settings we just set In the error path of ieee80211_mesh_csa_beacon() the settings that were just assigned are read back with rcu_dereference(), which lockdep then complains about. There's no need to read the pointer at all, tmp_csa_settings still is the right value anyway. Assisted-by: LLM Fixes: b8456a14e9d2 ("{nl,cfg,mac}80211: implement mesh channel switch userspace API") Reported-by: syzbot+b59873f5699e941717ca@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b59873f5699e941717ca Link: https://patch.msgid.link/20260908122838.201719-17-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/mesh.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index bed7ac8382505b..a35e2d5870b6f9 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -1559,7 +1559,6 @@ int ieee80211_mesh_csa_beacon(struct ieee80211_sub_if_data *sdata, ret = ieee80211_mesh_rebuild_beacon(sdata); if (ret) { - tmp_csa_settings = rcu_dereference(ifmsh->csa); RCU_INIT_POINTER(ifmsh->csa, NULL); kfree_rcu(tmp_csa_settings, rcu_head); return ret; From 0b1de9feeb8651f7a3bb53ed7c9006e3b5298c01 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:17 +0200 Subject: [PATCH 0193/1417] wifi: mac80211: don't access the TSF of a down interface The tsf debugfs files call the driver even if the interface isn't up, tgriggering check-sdata-in-driver warnings. Reject the access in that case. Assisted-by: LLM Fixes: 37a41b4affa3 ("mac80211: add ieee80211_vif param to tsf functions") Reported-by: syzbot+1c8c45017f784e646b47@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1c8c45017f784e646b47 Link: https://patch.msgid.link/20260908122838.201719-18-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/debugfs_netdev.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c index 8346d3eb114307..6aba2249367080 100644 --- a/net/mac80211/debugfs_netdev.c +++ b/net/mac80211/debugfs_netdev.c @@ -657,6 +657,9 @@ static ssize_t ieee80211_if_fmt_tsf( struct ieee80211_local *local = sdata->local; u64 tsf; + if (!ieee80211_sdata_running((struct ieee80211_sub_if_data *)sdata)) + return -ENETDOWN; + tsf = drv_get_tsf(local, (struct ieee80211_sub_if_data *)sdata); return scnprintf(buf, buflen, "0x%016llx\n", (unsigned long long) tsf); @@ -670,6 +673,9 @@ static ssize_t ieee80211_if_parse_tsf( int ret; int tsf_is_delta = 0; + if (!ieee80211_sdata_running(sdata)) + return -ENETDOWN; + if (strncmp(buf, "reset", 5) == 0) { if (local->ops->reset_tsf) { drv_reset_tsf(local, sdata); From cd54bf333f5631d3630bab0a832e9ae648f73515 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:18 +0200 Subject: [PATCH 0194/1417] wifi: mac80211: add HE 6 GHz capability in the scan elems len The HE 6 GHz Band Capability element is in the probe request for every band if 6 GHz is supported, so add the size to scan_ies_len. Otherwise, building probe request elements can fail, triggering the WARN_ON in __ieee80211_start_scan(). Assisted-by: LLM Fixes: 2ad2274c58ee ("mac80211: Add HE 6GHz capabilities element to probe request") Reported-by: syzbot+f961b9f94edbc266f1f8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f961b9f94edbc266f1f8 Link: https://patch.msgid.link/20260908122838.201719-19-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/mac80211/main.c b/net/mac80211/main.c index a59837b9f480f0..6408e8464338dc 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -1453,6 +1453,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) sizeof(struct ieee80211_he_mcs_nss_supp) + IEEE80211_HE_PPE_THRES_MAX_LEN; + if (local->hw.wiphy->bands[NL80211_BAND_6GHZ]) + local->scan_ies_len += + 3 + sizeof(struct ieee80211_he_6ghz_capa); + if (supp_eht) local->scan_ies_len += 3 + sizeof(struct ieee80211_eht_cap_elem) + From 860134b3af77970e006feab7e5decb8c84771c7f Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:19 +0200 Subject: [PATCH 0195/1417] wifi: mac80211: mesh: reset the CSA state when leaving ifmsh->csa is allocated in ieee80211_mesh_csa_beacon() and only freed in ieee80211_mesh_finish_csa(), i.e. when the channel switch completes. Leaving the mesh while a switch is still pending therefore leaks it. Additionally, ifmsh->csa_role and ifmsh->chsw_ttl have their state leak in this case, so things can get mixed up in addition to the memory leak. Refactor the reset and call it in ieee80211_stop_mesh() to fix it all. Assisted-by: LLM Reported-by: syzbot+f5752cd6b94fe38be666@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f5752cd6b94fe38be666 Fixes: b8456a14e9d2 ("{nl,cfg,mac}80211: implement mesh channel switch userspace API") Link: https://patch.msgid.link/20260908122838.201719-20-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/mesh.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index a35e2d5870b6f9..8f881412537587 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -1196,6 +1196,21 @@ int ieee80211_start_mesh(struct ieee80211_sub_if_data *sdata) return 0; } +static void ieee80211_mesh_reset_csa(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + struct mesh_csa_settings *csa; + + /* Reset the TTL value and Initiator flag */ + ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE; + ifmsh->chsw_ttl = 0; + + /* Remove the CSA and MCSP elements from the beacon */ + csa = sdata_dereference(ifmsh->csa, sdata); + RCU_INIT_POINTER(ifmsh->csa, NULL); + kfree_rcu(csa, rcu_head); +} + void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata) { struct ieee80211_local *local = sdata->local; @@ -1206,6 +1221,7 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata) /* abort any running channel switch */ sdata->vif.bss_conf.csa_active = false; + ieee80211_mesh_reset_csa(sdata); ieee80211_vif_unblock_queues_csa(sdata); /* flush STAs and mpaths on this iface */ @@ -1514,19 +1530,10 @@ static void ieee80211_mesh_rx_bcn_presp(struct ieee80211_sub_if_data *sdata, int ieee80211_mesh_finish_csa(struct ieee80211_sub_if_data *sdata, u64 *changed) { - struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; - struct mesh_csa_settings *tmp_csa_settings; - int ret = 0; + int ret; - /* Reset the TTL value and Initiator flag */ - ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE; - ifmsh->chsw_ttl = 0; + ieee80211_mesh_reset_csa(sdata); - /* Remove the CSA and MCSP elements from the beacon */ - tmp_csa_settings = sdata_dereference(ifmsh->csa, sdata); - RCU_INIT_POINTER(ifmsh->csa, NULL); - if (tmp_csa_settings) - kfree_rcu(tmp_csa_settings, rcu_head); ret = ieee80211_mesh_rebuild_beacon(sdata); if (ret) return -EINVAL; From ae97fff6495a8764bc0ef281cfe5444f701e527f Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:20 +0200 Subject: [PATCH 0196/1417] wifi: mac80211: mesh: release the channel if start fails ieee80211_join_mesh() acquires a channel context and then calls ieee80211_start_mesh(), which can fail. In that case, the chanctx isn't released then interface removal will attempt to unassign it after it's removed from the driver, hitting: wlan0: Failed check-sdata-in-driver check, flags: 0x0 WARNING: net/mac80211/driver-ops.c:366 at drv_unassign_vif_chanctx ieee80211_assign_link_chanctx __ieee80211_link_release_channel ieee80211_link_release_channel ieee80211_teardown_sdata unregister_netdevice_many_notify _cfg80211_unregister_wdev ieee80211_remove_interfaces ieee80211_unregister_hw mac80211_hwsim_del_radio hwsim_exit_net Correctly release the channel on start failures. Assisted-by: LLM Reported-by: syzbot+63a84ea9c0f57d6133fa@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=63a84ea9c0f57d6133fa Fixes: 2b5e19677592 ("mac80211: cache mesh beacon") Link: https://patch.msgid.link/20260908122838.201719-21-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 2d5a0abe35dbb0..d3558f0c755031 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -3323,7 +3323,11 @@ static int ieee80211_join_mesh(struct wiphy *wiphy, struct net_device *dev, if (err) return err; - return ieee80211_start_mesh(sdata); + err = ieee80211_start_mesh(sdata); + if (err) + ieee80211_link_release_channel(&sdata->deflink); + + return err; } static int ieee80211_leave_mesh(struct wiphy *wiphy, struct net_device *dev) From 50d3d79dc0743b616afb00d01a626c76758721f7 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:21 +0200 Subject: [PATCH 0197/1417] wifi: mac80211: set up the TX info early to fix failure paths The previous commit 2c51457d930f ("wifi: mac80211: free ack status frame on TX header build failure") cleaned up the leak, but still left the code a bit messy and the failed SKB didn't get reported to userspace. Fix this up by initialising skb->cb[] earlier, which allows using ieee80211_free_txskb() and therefore reports it for the failure in ieee80211_build_hdr(), and unifies the ieee80211_skb_resize() failure path with it. Assisted-by: LLM Fixes: c3e7724b6bc2 ("mac80211: use ieee80211_free_txskb to fix possible skb leaks") Link: https://patch.msgid.link/20260908122838.201719-22-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index c343ed56506a61..814399989b5e48 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2981,10 +2981,23 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, */ skb = skb_share_check(skb, GFP_ATOMIC); if (unlikely(!skb)) { - ret = -ENOMEM; - goto free; + /* skb_share_check() already freed the skb */ + if (info_id) + ieee80211_remove_ack_skb(local, info_id); + return ERR_PTR(-ENOMEM); } + /* set this up so failure paths can clean up ack skb */ + info = IEEE80211_SKB_CB(skb); + memset(info, 0, sizeof(*info)); + + info->flags = info_flags; + if (info_id) { + info->status_data = info_id; + info->status_data_idr = 1; + } + info->band = band; + hdr.frame_control = fc; hdr.duration_id = 0; hdr.seq_ctrl = 0; @@ -3023,10 +3036,8 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, head_need += local->tx_headroom; head_need = max_t(int, 0, head_need); if (ieee80211_skb_resize(sdata, skb, head_need, ENCRYPT_DATA)) { - ieee80211_free_txskb(&local->hw, skb); - skb = NULL; ret = -ENOMEM; - goto free; + goto free_txskb; } } @@ -3053,16 +3064,6 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, skb_reset_mac_header(skb); - info = IEEE80211_SKB_CB(skb); - memset(info, 0, sizeof(*info)); - - info->flags = info_flags; - if (info_id) { - info->status_data = info_id; - info->status_data_idr = 1; - } - info->band = band; - if (likely(!cookie)) { ctrl_flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); @@ -3086,16 +3087,17 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, pre_conf_link_id, link_id); #endif ret = -EINVAL; - goto free; + goto free_txskb; } } info->control.flags = ctrl_flags; return skb; + free_txskb: + ieee80211_free_txskb(&local->hw, skb); + return ERR_PTR(ret); free: - if (info_id) - ieee80211_remove_ack_skb(local, info_id); kfree_skb(skb); return ERR_PTR(ret); } From 3fc9f0558c95152b14d31badd5b27e5978bfb204 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 9 Sep 2026 07:41:54 -0700 Subject: [PATCH 0198/1417] drm/ci: Update xfails for kms_cursor_legacy regression These four started failing with some change not merged thru the msm tree. Update xfails to reflect reality. Signed-off-by: Rob Clark --- drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt b/drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt index 4546363447ffc3..0f3d85e4845eef 100644 --- a/drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt +++ b/drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt @@ -7,3 +7,10 @@ kms_hdmi_inject@inject-4k,Fail kms_lease@lease-uevent,Fail msm/msm_mapping@memptrs,Fail msm/msm_mapping@ring,Fail + +# Started failing with v7.3-rc2 backmerge +# https://gitlab.freedesktop.org/drm/msm/-/work_items/104 +kms_cursor_legacy@single-move,Fail +kms_cursor_legacy@torture-bo,Fail +kms_cursor_legacy@forked-bo,Fail +kms_cursor_legacy@torture-move,Fail From e2d5b01f878d76bd1142e512a0b979a1d3cd0abf Mon Sep 17 00:00:00 2001 From: Meijing Zhao Date: Wed, 2 Sep 2026 15:59:44 +0800 Subject: [PATCH 0199/1417] mm: memblock: show all region flags in debugfs Commit 493f349e38d0 ("memblock: Add flags and nid info in memblock debugfs") made memblock_debug_show() stop after finding the first set flag. A memblock region can carry multiple flags, so the remaining flags are hidden from debugfs. Walk all bits in the region flags and print every set flag separated by "|". Keep walking beyond flagname[] so that a set flag without a known name is reported as UNKNOWN rather than silently ignored. Fixes: 493f349e38d0 ("memblock: Add flags and nid info in memblock debugfs") Signed-off-by: Meijing Zhao Link: https://patch.msgid.link/20260902075944.3742866-1-zhaomeijing100@gmail.com Signed-off-by: Mike Rapoport (Microsoft) --- mm/memblock.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/mm/memblock.c b/mm/memblock.c index 9ce86349a29faf..021db49eb7fc65 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -2908,14 +2908,18 @@ static int memblock_debug_show(struct seq_file *m, void *private) else seq_printf(m, "%4c ", 'x'); if (reg->flags) { - for (j = 0; j < count; j++) { - if (reg->flags & (1U << j)) { - seq_printf(m, "%s\n", flagname[j]); - break; - } + unsigned int flags = reg->flags; + bool first = true; + + for (j = 0; flags; j++, flags >>= 1) { + if (!(flags & 1)) + continue; + if (!first) + seq_putc(m, '|'); + seq_puts(m, j < count ? flagname[j] : "UNKNOWN"); + first = false; } - if (j == count) - seq_printf(m, "%s\n", "UNKNOWN"); + seq_putc(m, '\n'); } else { seq_printf(m, "%s\n", "NONE"); } From 6e33dc90df108c76899dc544a42d17adcba61668 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Mon, 31 Aug 2026 13:21:43 +0300 Subject: [PATCH 0200/1417] MAINTAINERS: update memblock tree URLs memblock tree moved into mm/ namespace at git.kernel.org. Update the T: entries for memblock to match it. Link: https://patch.msgid.link/20260831102143.69265-1-rppt@kernel.org Signed-off-by: Mike Rapoport (Microsoft) --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c9d..1731d197c33ca9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17036,8 +17036,8 @@ MEMBLOCK AND MEMORY MANAGEMENT INITIALIZATION M: Mike Rapoport L: linux-mm@kvack.org S: Maintained -T: git git://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git for-next -T: git git://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git fixes +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mm/memblock.git for-next +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mm/memblock.git fixes F: Documentation/core-api/boot-time-mm.rst F: include/linux/kho/abi/memblock.h F: include/linux/memblock.h From e4a62833adff6ef0fe7c0b90393204fe3c26b5c5 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 9 Sep 2026 12:08:08 +0800 Subject: [PATCH 0201/1417] bpf: Disallow bpf_skb_pull_data() for LWT_SEG6LOCAL An LWT_SEG6LOCAL program can invalidate its cached SRH with bpf_lwt_seg6_adjust_srh() and then call bpf_skb_pull_data(). The latter may reallocate skb->head, leaving the per-CPU SRH pointer dangling. Post-program SRH validation then writes through that pointer. Disallow bpf_skb_pull_data() for LWT_SEG6LOCAL programs so the verifier rejects this unsafe helper combination. Other LWT program types continue to expose the helper through lwt_out_func_proto(). Fixes: 004d4b274e2a ("ipv6: sr: Add seg6local action End.BPF") Reported-by: co+adfca3e91be95776@bugs.sh Suggested-by: Alexei Starovoitov Signed-off-by: Weiming Shi Signed-off-by: Daniel Borkmann Reviewed-by: Emil Tsalapatis Closes: https://lore.kernel.org/all/GCy0KRM2IcQGoJQTjJEU9D0maBxXzEDHuQpq@bugs.sh/ Link: https://lore.kernel.org/bpf/DL9COXZQXX4V.1FN45QO2Q77ZH@gmail.com/ Link: https://lore.kernel.org/bpf/20260909040807.3885815-2-bestswngs@gmail.com --- net/core/filter.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/core/filter.c b/net/core/filter.c index 8513167a858a87..2a84f9d0113142 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -9044,6 +9044,8 @@ static const struct bpf_func_proto * lwt_seg6local_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) { switch (func_id) { + case BPF_FUNC_skb_pull_data: + return NULL; #if IS_ENABLED(CONFIG_IPV6_SEG6_BPF) case BPF_FUNC_lwt_seg6_store_bytes: return &bpf_lwt_seg6_store_bytes_proto; From d22c3e0088e85be8131f7a9283f759cdbb20726d Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Wed, 9 Sep 2026 11:29:53 +0200 Subject: [PATCH 0202/1417] selftests/ftrace: Fix unique symbol check in kprobe_non_uniq_symbol.tc The current regex also matches symbols in modules, which makes the test fail on s390 where name_show is present only once in the kernel, but also multiple times in modules: 000001b1401cdc20 t name_show 000001b0c05e6c40 t name_show [mdev] 000001b0c0495f30 t name_show [i2c_core] Fix this by changing the regular expression to only match the function name. Link: https://lore.kernel.org/all/20260909092954.2200558-1-svens@linux.ibm.com/ Fixes: 03b80ff8023a ("selftests/ftrace: Add new test case which checks non unique symbol") Signed-off-by: Sven Schnelle Reviewed-by: Steven Rostedt Signed-off-by: Masami Hiramatsu (Google) --- .../selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc index bc9514428dbaf1..07b1177c163449 100644 --- a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc +++ b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc @@ -6,7 +6,7 @@ SYMBOL='name_show' # We skip this test on kernel where SYMBOL is unique or does not exist. -if [ "$(grep -c -E "[[:alnum:]]+ t ${SYMBOL}" /proc/kallsyms)" -le '1' ]; then +if [ "$(grep -c -E "[[:alnum:]]+ t ${SYMBOL}$" /proc/kallsyms)" -le '1' ]; then exit_unsupported fi From 264bf9655c3d067d775a46f05eb8c871c488a864 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 6 Sep 2026 19:09:25 +0200 Subject: [PATCH 0203/1417] scsi: pm80xx: Fix the use_msix, use_tasklet and read_wwn parameter descriptions The MODULE_PARM_DESC() lines of use_msix, use_tasklet and read_wwn all name a parameter zoned, which does not exist, and the use_tasklet one repeats the use_msix text. modinfo shows three "zoned" entries and no description for the real parameters. Name the right parameters and describe use_tasklet. Fixes: efa1fca45082 ("scsi: pm8001: Remove PM8001_USE_MSIX") Fixes: 205430290ad0 ("scsi: pm8001: Remove PM8001_USE_TASKLET") Fixes: 80975adc79dd ("scsi: pm8001: Remove PM8001_READ_VPD") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/20260906170925.2524-1-kmehltretter@gmail.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/pm8001/pm8001_init.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/scsi/pm8001/pm8001_init.c b/drivers/scsi/pm8001/pm8001_init.c index 54b35893261a63..5af81c73a8f875 100644 --- a/drivers/scsi/pm8001/pm8001_init.c +++ b/drivers/scsi/pm8001/pm8001_init.c @@ -58,15 +58,15 @@ MODULE_PARM_DESC(link_rate, "Enable link rate.\n" bool pm8001_use_msix = true; module_param_named(use_msix, pm8001_use_msix, bool, 0444); -MODULE_PARM_DESC(zoned, "Use MSIX interrupts. Default: true"); +MODULE_PARM_DESC(use_msix, "Use MSIX interrupts. Default: true"); static bool pm8001_use_tasklet = true; module_param_named(use_tasklet, pm8001_use_tasklet, bool, 0444); -MODULE_PARM_DESC(zoned, "Use MSIX interrupts. Default: true"); +MODULE_PARM_DESC(use_tasklet, "Use tasklets for interrupt handling. Default: true"); static bool pm8001_read_wwn = true; module_param_named(read_wwn, pm8001_read_wwn, bool, 0444); -MODULE_PARM_DESC(zoned, "Get WWN from the controller. Default: true"); +MODULE_PARM_DESC(read_wwn, "Get WWN from the controller. Default: true"); uint pcs_event_log_severity = 0x03; module_param(pcs_event_log_severity, int, 0644); From 779f202a92ef10a426efc07d0f4267918cb07ca3 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 6 Sep 2026 19:10:09 +0200 Subject: [PATCH 0204/1417] scsi: qla2xxx: Fix the ql2xfc2target parameter description The module parameter is ql2xfc2target, but its MODULE_PARM_DESC() names qla2xfc2target, so modinfo describes a parameter that does not exist and shows no description for the real one. Use the parameter name in the description. Fixes: 877b03795fcf ("scsi: qla2xxx: Add option to disable FC2 Target support") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260906171009.2560-1-kmehltretter@gmail.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/qla2xxx/qla_os.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index c0efdbff5da710..3c412c7fb6fe76 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -352,7 +352,7 @@ MODULE_PARM_DESC(ql2xnvme_queues, int ql2xfc2target = 1; module_param(ql2xfc2target, int, 0444); -MODULE_PARM_DESC(qla2xfc2target, +MODULE_PARM_DESC(ql2xfc2target, "Enables FC2 Target support. " "0 - FC2 Target support is disabled. " "1 - FC2 Target support is enabled (default)."); From 1274045b0eda1df5a5cdd3e63ed48cf013b3b4ea Mon Sep 17 00:00:00 2001 From: Venkat Rao Bagalkote Date: Thu, 3 Sep 2026 13:13:29 +0530 Subject: [PATCH 0205/1417] scsi: ibmvfc: Add Kconfig dependency to fix link failure when NVME_FC=m Building with CONFIG_SCSI_IBMVFC=y and CONFIG_NVME_FC=m results in a link failure: ibmvfc-nvme.o: undefined reference to `nvme_fc_register_localport' ibmvfc-nvme.o: undefined reference to `nvme_fc_register_remoteport' ibmvfc-nvme.o: undefined reference to `nvme_fc_unregister_localport' ibmvfc-nvme.o: undefined reference to `nvme_fc_unregister_remoteport' ibmvfc-core.o: undefined reference to `nvme_fc_rescan_remoteport' IS_ENABLED() evaluates to 1 for both =y and =m, so the nvme_fc_* call sites are kept in the object file. When SCSI_IBMVFC=y (built-in) but NVME_FC=m (loadable module), the linker cannot resolve these symbols at vmlinux link time. Add the same "depends on NVME_FC || NVME_FC=n" constraint already used by SCSI_LPFC to prevent this configuration. Reported-by: Pavithra Closes: https://lore.kernel.org/all/327877a29337aa526cc50ac88fbddb86@linux.ibm.com/ Signed-off-by: Venkat Rao Bagalkote Tested-by: Pavithra Acked-by: Tyrel Datwyler Link: https://patch.msgid.link/20260903074329.6705-1-venkat88@linux.ibm.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index 4a2af0f702e1b4..1eec66195cf437 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -753,6 +753,7 @@ config SCSI_IBMVFC tristate "IBM Virtual FC support" depends on PPC_PSERIES && SCSI depends on SCSI_FC_ATTRS + depends on NVME_FC || NVME_FC=n help This is the IBM POWER Virtual FC Client From 0cb1fd924126f1f581621a5e804df98a02be9dff Mon Sep 17 00:00:00 2001 From: Arun Easi Date: Thu, 3 Sep 2026 10:55:47 -0700 Subject: [PATCH 0206/1417] scsi: fnic: Fix missed link-up when critical IRQ targets offline CPU When CPU Hyper Threading is disabled, sibling CPUs remain present but are reported offline. Managed MSI-X IRQs can still receive affinity masks that include those offline CPUs. If a driver-critical vector is managed, it can be parked on an offline CPU and the driver may miss critical events such as link-up. Keep driver-critical vectors unmanaged so they can be migrated by the IRQ core when their target CPU is offlined. Since HWQ-0 is unmanaged now, in some queue combinations there can be no mappings to it in mq_map. So without the blk-mq fix mentioned below, system may crash during cpu offline/online tests. Fixes: 8a8449ca5e33 ("scsi: fnic: Modify ISRs to support multiqueue (MQ)") Cc: stable@vger.kernel.org Depends-on: commit 10845a105bbc ("blk-mq: skip CPU offline notify on unmapped hctx") Reviewed-by: Sesidhar Baddela Reviewed-by: Arulprabhu Ponnusamy Reviewed-by: Gian Carlo Boffa Reviewed-by: Karan Tilak Kumar Signed-off-by: Arun Easi Reviewed-by: Laurence Oberman Link: https://patch.msgid.link/20260903175547.57971-1-aeasi@cisco.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/fnic/fnic.h | 2 +- drivers/scsi/fnic/fnic_isr.c | 13 ++++++++--- drivers/scsi/fnic/fnic_main.c | 41 ++++++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/drivers/scsi/fnic/fnic.h b/drivers/scsi/fnic/fnic.h index c576a7f5083e31..3ba1592940caf3 100644 --- a/drivers/scsi/fnic/fnic.h +++ b/drivers/scsi/fnic/fnic.h @@ -31,7 +31,7 @@ #define DRV_NAME "fnic" #define DRV_DESCRIPTION "Cisco FCoE HBA Driver" -#define DRV_VERSION "1.9.0.0" +#define DRV_VERSION "1.9.0.1" #define PFX DRV_NAME ": " #define DFX DRV_NAME "%d: " diff --git a/drivers/scsi/fnic/fnic_isr.c b/drivers/scsi/fnic/fnic_isr.c index 02856745580f5e..43149d8312ec62 100644 --- a/drivers/scsi/fnic/fnic_isr.c +++ b/drivers/scsi/fnic/fnic_isr.c @@ -245,7 +245,14 @@ int fnic_set_intr_mode_msix(struct fnic *fnic) unsigned int m = ARRAY_SIZE(fnic->wq); unsigned int o = ARRAY_SIZE(fnic->hw_copy_wq); unsigned int min_irqs = n + m + 1 + 1; /*rq, raw wq, wq, err*/ - + /* + * Make driver critical vectors unmanaged, or else it can get tied + * to an offline CPU. This can happen when hyper-threading is off. + */ + struct irq_affinity affd = { + .pre_vectors = n + m + 1, /* rq, raw wq, 1 ioq */ + .post_vectors = 1, /* err */ + }; /* * We need n RQs, m WQs, o Copy WQs, n+m+o CQs, and n+m+o+1 INTRs * (last INTR is used for WQ/RQ errors and notification area) @@ -263,8 +270,8 @@ int fnic_set_intr_mode_msix(struct fnic *fnic) int vec_count = 0; int vecs = fnic->rq_count + fnic->raw_wq_count + fnic->wq_copy_count + 1; - vec_count = pci_alloc_irq_vectors(fnic->pdev, min_irqs, vecs, - PCI_IRQ_MSIX | PCI_IRQ_AFFINITY); + vec_count = pci_alloc_irq_vectors_affinity(fnic->pdev, min_irqs, + vecs, PCI_IRQ_MSIX|PCI_IRQ_AFFINITY, &affd); FNIC_ISR_DBG(KERN_INFO, fnic, "allocated %d MSI-X vectors\n", vec_count); diff --git a/drivers/scsi/fnic/fnic_main.c b/drivers/scsi/fnic/fnic_main.c index 9b3025007075e7..f13c381a66d7fa 100644 --- a/drivers/scsi/fnic/fnic_main.c +++ b/drivers/scsi/fnic/fnic_main.c @@ -744,8 +744,19 @@ static int fnic_nvme_drv_init(struct fnic *fnic) return ret; } +static void fnic_mq_init_queue_map(struct fnic *fnic, + struct blk_mq_queue_map *qmap) +{ + unsigned int cpu; + + for_each_possible_cpu(cpu) + qmap->mq_map[cpu] = 0; +} + void fnic_mq_map_queues_cpus(struct Scsi_Host *host) { + const struct cpumask *mask; + unsigned int queue, cpu; struct fnic *fnic = *((struct fnic **) shost_priv(host)); struct pci_dev *l_pdev = fnic->pdev; int intr_mode = fnic->config.intr_mode; @@ -766,7 +777,35 @@ void fnic_mq_map_queues_cpus(struct Scsi_Host *host) return; } - blk_mq_map_hw_queues(qmap, &l_pdev->dev, FNIC_PCI_OFFSET); + fnic_mq_init_queue_map(fnic, qmap); + + /* + * Setup CPU to Queue mapping for all managed MSI-X IRQs. + * Q0 is driver critical and non-managed, hence start from Q1. + */ + for (queue = 1; queue < qmap->nr_queues; queue++) { + int irq_num = pci_irq_vector(fnic->pdev, + queue + FNIC_PCI_OFFSET); + + if (irq_num < 0) + continue; + + mask = pci_irq_get_affinity(fnic->pdev, + queue + FNIC_PCI_OFFSET); + if (!mask) { + shost_printk(KERN_ERR, host, + "failed to get irq_affinity map for queue:%d\n", irq_num); + continue; + } + FNIC_MAIN_DBG(KERN_INFO, fnic, + "got irq_affinity map for %d:\n", irq_num); + for_each_cpu(cpu, mask) { + qmap->mq_map[cpu] = qmap->queue_offset + queue; + FNIC_MAIN_DBG(KERN_INFO, fnic, + "[Q%d] cpu:%d <=> irq:%d\n", + queue, cpu, irq_num); + } + } } static int fnic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) From 3d676e458fe0c566f5a62753dc696b6a862fc412 Mon Sep 17 00:00:00 2001 From: Alberto Carboneri Date: Fri, 4 Sep 2026 13:54:37 +0000 Subject: [PATCH 0207/1417] scsi: core: Validate MODE SENSE lengths in scsi_cdl_enable() scsi_cdl_enable() uses length fields returned by MODE SENSE to locate the ATA feature mode page in a 64-byte stack buffer. A target can report a total length shorter than its mode header and block descriptors. The unsigned subtraction used for the MODE SELECT length can wrap, and the separately computed buf_data can point beyond buf. During automatic scan, enable is false, so the read-modify-write of buf_data[4] can clear the low two bits of a target-selected out-of-bounds stack byte. scsi_mode_select() can then copy up to 64 bytes from outside the buffer into the outgoing MODE SELECT payload, disclosing stack contents to the target. This is reachable while scanning a USB storage device that identifies as an ATA device and advertises CDL support. No filesystem mount or userspace access to the block device is required. On upstream commit cee9395acd80 ("Linux 7.3-rc1"), a build-specific, one-vCPU QEMU/Raw Gadget proof using QEMU-only multi-UDC allocator sampling executed a fixed proof command inside the guest and created a UID-0-owned marker during automatic enumeration, with KASLR and NX enabled. The issue was independently found during security research at Drivesec S.r.l. Cap the available length to the buffer size. Validate and consume the mode header and block descriptor lengths before using the page, and require the five bytes needed to access the CDL field. Fixes: 1b22cfb14142 ("scsi: core: Allow enabling and disabling command duration limits") Reported-by: Sashiko AI Review Closes: https://lore.kernel.org/linux-scsi/20260717192313.93D791F000E9@smtp.kernel.org/ Link: https://lore.kernel.org/linux-scsi/20260717222931.AC4EE1F000E9@smtp.kernel.org/ Link: https://lore.kernel.org/linux-scsi/df13ec87ac9b28e3b0a2d9eb26477e276ff0278a.camel@HansenPartnership.com/ Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Pimen Flavian Dei (Drivesec S.r.l.) Signed-off-by: Pimen Flavian Dei (Drivesec S.r.l.) Signed-off-by: Alberto Carboneri (Drivesec S.r.l.) Link: https://lore.kernel.org/linux-scsi/20260717192313.93D791F000E9@smtp.kernel.org/ Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/20260904135410.360314-1-acarboneri@drivesec.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/scsi.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c index 76cdad063f7bcd..f285521d9de6d5 100644 --- a/drivers/scsi/scsi.c +++ b/drivers/scsi/scsi.c @@ -727,6 +727,7 @@ int scsi_cdl_enable(struct scsi_device *sdev, bool enable) struct scsi_mode_data data; struct scsi_sense_hdr sshdr; char *buf_data; + size_t avail, offset; int len; ret = scsi_mode_sense(sdev, 0x08, 0x0a, 0xf2, buf, sizeof(buf), @@ -735,11 +736,24 @@ int scsi_cdl_enable(struct scsi_device *sdev, bool enable) return -EINVAL; /* Enable or disable CDL using the ATA feature page */ - len = min_t(size_t, sizeof(buf), - data.length - data.header_length - - data.block_descriptor_length); - buf_data = buf + data.header_length + - data.block_descriptor_length; + avail = min_t(size_t, data.length, sizeof(buf)); + if (data.header_length > avail) + return -EINVAL; + + offset = data.header_length; + avail -= data.header_length; + + if (data.block_descriptor_length > avail) + return -EINVAL; + + offset += data.block_descriptor_length; + avail -= data.block_descriptor_length; + + if (avail < 5) + return -EINVAL; + + buf_data = buf + offset; + len = avail; /* * If we want to enable CDL and CDL is already enabled on the From 57a78ad2305f299bc3f933ed36b3d402df04784a Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 9 Sep 2026 09:06:31 +0100 Subject: [PATCH 0208/1417] block: Fix start and length check added to iov_iter_extract_bvecs() Commit 14b007e17881 added an address check using iter_iov_addr() and a length check using iter_iov_len() to iov_iter_extract_bvecs(), but these cannot be used so and are unsafe in this circumstance as the functions have hardwired assumptions about the iterator type. They should only be used with ITER_UBUF or ITER_IOVEC-type iterators; they shouldn't be used with ITER_BVEC, ITER_KVEC, ITER_FOLIOQ, ITER_XARRAY or ITER_DISCARD iterators. This proves to be a problem for cachefiles as an iterator of type ITER_FOLIOQ is passed and iter_iov_addr() and iter_iov_len() both malfunction because iter->__iov in iter_iov() is not pointing to an iovec array. Fix this by using iov_iter_alignment() instead. Fixes: 14b007e17881 ("block: validate user space vectors during extraction") Signed-off-by: David Howells Link: https://patch.msgid.link/1667275.1788941191@warthog.procyon.org.uk Reviewed-by: Keith Busch Reviewed-by: Christoph Hellwig cc: Hannes Reinecke cc: Christoph Hellwig cc: Jens Axboe cc: Alexander Viro cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-block@vger.kernel.org cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- lib/iov_iter.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/iov_iter.c b/lib/iov_iter.c index 6665372ecf719c..2072c04e99d052 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -1921,15 +1921,29 @@ ssize_t iov_iter_extract_bvecs(struct iov_iter *iter, struct bio_vec *bv, unsigned short max_vecs, unsigned mem_align_mask, iov_iter_extraction_t extraction_flags) { - unsigned long start = (unsigned long)iter_iov_addr(iter); unsigned short entries_left = max_vecs - *nr_vecs; unsigned short nr_pages, i = 0; size_t left, offset, len; struct page **pages; ssize_t size; - if ((start | iter_iov_len(iter)) & mem_align_mask) + /* + * DMA engines typically have both memory address and length alignment + * requirements, so check these against the alignment mask. For UBUF, + * IOVEC and KVEC, only the current segment will be extracted from; for + * everything else we might extract from multiple segments, so we need + * to check those too. + */ + if (likely(iter_is_ubuf(iter) || + iter_is_iovec(iter) || + iov_iter_is_kvec(iter))) { + unsigned long start = (unsigned long)iter_iov_addr(iter); + + if ((start | iter_iov_len(iter)) & mem_align_mask) + return -EINVAL; + } else if (iov_iter_alignment(iter) & mem_align_mask) { return -EINVAL; + } /* * Move page array up in the allocated memory for the bio vecs as far as From d59ac79915daa567c69aa93d458d41844676e92a Mon Sep 17 00:00:00 2001 From: Disha Goel Date: Fri, 3 Jul 2026 20:37:42 +0530 Subject: [PATCH 0209/1417] selftests/filesystems: fix missing and stale TARGETS entries filesystems/eventfd, filesystems/open_tree_ns and filesystems/xattr were never added to TARGETS when introduced. filesystems/openat2 was moved from selftests/openat2/ but the TARGETS entry was never updated, leaving a stale entry pointing at a directory that no longer exists. Fix this by adding the four missing subdirectories to TARGETS and removing the stale openat2 entry. Link: https://lore.kernel.org/20260703150742.58991-1-disgoel@linux.ibm.com Fixes: 7c37857fc23a ("selftests: add eventfd selftests") Fixes: b8f7622aa6e3 ("selftests/open_tree: add OPEN_TREE_NAMESPACE tests") Fixes: 7e28fef5d4db ("selftests/xattr: path-based AF_UNIX socket xattr tests") Fixes: fe087927046c ("selftests: move openat2 tests to selftests/filesystems/") Signed-off-by: Disha Goel Reviewed-by: Christian Brauner (Amutable) Cc: "Darrick J. Wong" Cc: Jan Kara Cc: Jeff Layton Cc: Shuah Khan Cc: Wen Yang Signed-off-by: Andrew Morton Link: https://patch.msgid.link/20260904183659.B81CD1F00A3D@smtp.kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 2d960626750e3e..330d061f6366fc 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -35,8 +35,11 @@ TARGETS += fchmodat2 TARGETS += filesystems TARGETS += filesystems/binderfs TARGETS += filesystems/epoll +TARGETS += filesystems/eventfd TARGETS += filesystems/failfs TARGETS += filesystems/fat +TARGETS += filesystems/openat2 +TARGETS += filesystems/open_tree_ns TARGETS += filesystems/overlayfs TARGETS += filesystems/statmount TARGETS += filesystems/mount-notify @@ -47,6 +50,7 @@ TARGETS += filesystems/empty_mntns TARGETS += filesystems/fsmount_ns TARGETS += filesystems/fscontext_ns TARGETS += filesystems/mntns_cleanup +TARGETS += filesystems/xattr TARGETS += firmware TARGETS += fpu TARGETS += ftrace @@ -103,7 +107,6 @@ TARGETS += prctl TARGETS += proc TARGETS += pstore TARGETS += ptrace -TARGETS += openat2 TARGETS += rdma TARGETS += resctrl TARGETS += riscv From 1abd643f3783ea8f8e273c18697ff0413aa92dc7 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 9 Sep 2026 11:03:18 +0200 Subject: [PATCH 0210/1417] fs/ntfs3: use d_instantiate_new() in ntfs_create_inode() and murder syzbot's "WARNING in do_new_mount" saga ntfs_create_inode() creates a new inode via ntfs_new_inode(). It hashes it with insert_inode_locked() and so it's marked as I_NEW until unlock_new_inode(). ntfs 3 calls d_instantiate() in between though... Since the dentry was already hashed by the lookup before the create any path walk finds it without touching the parent's i_rwsem and so can lock the inode. If the inode is a directory unlock_new_inode() calls lockdep_annotate_inode_mutex_key() and marks i_rwsem with the i_mutex_dir_key class. That resets the count and the owner of a lock somebody else may already hold by now... syzbot has been spamming us with the same godforsaken bug "WARNING in do_new_mount" since 2023. I can't take it anymore so I went looking. Afaict, syzbot's executor chdirs into a freshly mounted ntfs3 image, creates a directory and then mounts some pseudofs on it. Everytime the mkdir() takes longer than syzbot waits mount() runs concurrently: mkdir("./sys") mount(NULL, "./sys", "sysfs") ntfs_create_inode() d_instantiate() user_path_at() finds the dentry do_lock_mount() inode_lock(inode) namespace_lock() unlock_new_inode() lockdep_annotate_inode_mutex_key() init_rwsem(&inode->i_rwsem) unlock_mount() inode_unlock(inode) The mount side then releases a lock that according to the rwsem nobody holds: DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && ...): count = 0x0, magic = 0xffff888043a854e8, owner = 0x0, curr 0xffff888000244880, list empty WARNING: CPU: 0 PID: 5346 at kernel/locking/rwsem.c:1368 __up_write Call Trace: inode_unlock include/linux/fs.h:877 [inline] unlock_mount fs/namespace.c:2892 [inline] do_new_mount_fc fs/namespace.c:3828 [inline] do_new_mount+0x777/0xa40 fs/namespace.c:3887 On PREEMPT_RT the same thing shows up as DEBUG_LOCKS_WARN_ON(rt_mutex_owner(lock) != current) WARNING: kernel/locking/rtmutex_common.h:193 at rt_mutex_slowunlock The up_write() underflows the reset count. A following inode_lock() on that directory then never returns. A path walk into the new directory racing with the mkdir() corrupts the lock the same way via inode_lock_shared() in lookup_slow(). Switch to d_instantiate_new() and drop the trailing unlock_new_inode(). All error paths bail out before that point with I_NEW still set and keep using discard_new_inode(). May we never see this fscking bug report again. Link: https://patch.msgid.link/20260909-work-ntfs3-d_instantiate_new-v1-1-2db697162ce8@kernel.org Fixes: 82cae269cfa9 ("fs/ntfs3: Add initialization of super block") Reviewed-by: Jan Kara Cc: stable@vger.kernel.org # v5.15+ Reported-by: syzbot+2a13ad6914e6fcec716c@syzkaller.appspotmail.com Closes: https://lore.kernel.org/6a9beced.a5e650b3.26d8a.000b.GAE@google.com Signed-off-by: Christian Brauner (Amutable) --- fs/ntfs3/inode.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 56b4f6469a280b..4ac26c80bd34f1 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -1866,10 +1866,10 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, goto out6; /* - * Call 'd_instantiate' after inode->i_op is set + * Call 'd_instantiate_new' after inode->i_op is set * but before finish_open. */ - d_instantiate(dentry, inode); + d_instantiate_new(dentry, inode); /* Set original time. inode times (i_ctime) may be changed in ntfs_init_acl. */ inode_set_atime_to_ts(inode, ni->i_crtime); @@ -1917,9 +1917,6 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, if (!fnd) ni_unlock(dir_ni); - if (!err) - unlock_new_inode(inode); - return err; } From 5a5d26f2cfe13467166219f6bf58099326912ddb Mon Sep 17 00:00:00 2001 From: Soheil Hassas Yeganeh Date: Mon, 31 Aug 2026 14:48:44 +0000 Subject: [PATCH 0211/1417] x86/cfi: Fix FineIBT hash offset in cfi_get_func_hash() The switch of the FineIBT preamble from "subl $hash, %r10d" to the shorter "subl $hash, %eax" moved the hash immediate from offset 7 to offset 5 of the preamble. fineibt_preamble_hash was updated to match, but the open-coded offset in cfi_get_func_hash() was missed and it still reads the hash at offset 7. cfi_get_func_hash() is used by the BPF JIT to give a struct_ops trampoline the CFI hash of the stub function it stands in for. With FineIBT the trampoline now gets the upper half of the real hash followed by the first two bytes of the next instruction, so the first indirect call from the kernel into a struct_ops program, tcp_init_congestion_control() calling ->init() of a BPF congestion control for example, fails the FineIBT check and the kernel dies with a CFI failure. Move the FineIBT preamble template and its offset defines above cfi_get_func_hash() and use fineibt_preamble_hash there, so every reader of the preamble shares one definition of its layout. The CFI_FINEIBT arm is only built with CONFIG_FINEIBT, the only configuration in which cfi_mode can take that value. cfi_get_func_arity() does not need the same treatment: the __bhi_args call whose displacement it reads still ends at the function address. Fixes: 85a2d4a890dc ("x86,ibt: Use UDB instead of 0xEA") Assisted-by: LLM Signed-off-by: Soheil Hassas Yeganeh Signed-off-by: Peter Zijlstra (Intel) Cc: stable@vger.kernel.org # 6.18+ Link: https://patch.msgid.link/20260831-b4-x86-cfi-fineibt-func-hash-v1-1-6ffc0af5c4ec@gmail.com --- arch/x86/kernel/alternative.c | 72 ++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index add62db3e82cdd..741d8767ddf895 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c @@ -1201,6 +1201,41 @@ static bool cfi_debug __ro_after_init; bool cfi_bhi __ro_after_init = false; #endif +#ifdef CONFIG_FINEIBT +/* + * : + * 0: f3 0f 1e fa endbr64 + * 4: 2d 78 56 34 12 sub $0x12345678, %eax + * 9: 2e 0f 85 03 00 00 00 jne,pn 13 + * 10: 0f 1f 40 d6 nopl -0x2a(%rax) + * + * Note that the JNE target is the 0xD6 byte inside the NOPL, this decodes as + * UDB on x86_64 and raises #UD. + */ +asm( ".pushsection .rodata \n" + "fineibt_preamble_start: \n" + " endbr64 \n" + " subl $0x12345678, %eax \n" + "fineibt_preamble_bhi: \n" + " cs jne.d32 fineibt_preamble_start+0x13 \n" + "#fineibt_func: \n" + " nopl -42(%rax) \n" + "fineibt_preamble_end: \n" + ".popsection\n" +); + +extern u8 fineibt_preamble_start[]; +extern u8 fineibt_preamble_bhi[]; +extern u8 fineibt_preamble_end[]; + +#define fineibt_preamble_size (fineibt_preamble_end - fineibt_preamble_start) +#define fineibt_preamble_bhi (fineibt_preamble_bhi - fineibt_preamble_start) +#define fineibt_preamble_ud 0x13 +#define fineibt_preamble_hash 5 + +#define fineibt_prefix_size (fineibt_preamble_size - ENDBR_INSN_SIZE) +#endif /* CONFIG_FINEIBT */ + #ifdef CONFIG_CFI u32 cfi_get_func_hash(void *func) { @@ -1208,9 +1243,11 @@ u32 cfi_get_func_hash(void *func) func -= cfi_get_offset(); switch (cfi_mode) { +#ifdef CONFIG_FINEIBT case CFI_FINEIBT: - func += 7; + func += fineibt_preamble_hash; break; +#endif case CFI_KCFI: func += 1; break; @@ -1366,39 +1403,6 @@ early_param("cfi", cfi_parse_cmdline); * anyway. */ -/* - * : - * 0: f3 0f 1e fa endbr64 - * 4: 2d 78 56 34 12 sub $0x12345678, %eax - * 9: 2e 0f 85 03 00 00 00 jne,pn 13 - * 10: 0f 1f 40 d6 nopl -0x2a(%rax) - * - * Note that the JNE target is the 0xD6 byte inside the NOPL, this decodes as - * UDB on x86_64 and raises #UD. - */ -asm( ".pushsection .rodata \n" - "fineibt_preamble_start: \n" - " endbr64 \n" - " subl $0x12345678, %eax \n" - "fineibt_preamble_bhi: \n" - " cs jne.d32 fineibt_preamble_start+0x13 \n" - "#fineibt_func: \n" - " nopl -42(%rax) \n" - "fineibt_preamble_end: \n" - ".popsection\n" -); - -extern u8 fineibt_preamble_start[]; -extern u8 fineibt_preamble_bhi[]; -extern u8 fineibt_preamble_end[]; - -#define fineibt_preamble_size (fineibt_preamble_end - fineibt_preamble_start) -#define fineibt_preamble_bhi (fineibt_preamble_bhi - fineibt_preamble_start) -#define fineibt_preamble_ud 0x13 -#define fineibt_preamble_hash 5 - -#define fineibt_prefix_size (fineibt_preamble_size - ENDBR_INSN_SIZE) - /* * : * 0: b8 78 56 34 12 mov $0x12345678, %eax From 9c182bc5d7817437a7d04ab96133f9191846d93d Mon Sep 17 00:00:00 2001 From: Florian Maillard Date: Mon, 24 Aug 2026 08:57:55 +0200 Subject: [PATCH 0212/1417] mmc: rtsx_pci_sdmmc: ignore broken write-protect on ThinkPad X260 The Realtek RTS522A card reader in the Lenovo ThinkPad X260 (subsystem 17aa:504a) incorrectly reports inserted SD cards as write-protected. This causes the MMC core to expose the card as read-only: mmcblk0: mmc0:aaaa SN256 238 GiB (ro) and /sys/block/mmcblk0/ro reports 1. Setting MMC_CAP2_NO_WRITE_PROTECT makes the card writable again. Limit the quirk to the affected Lenovo subsystem. Assisted-by: ChatGPT:GPT-5.6 Sol Signed-off-by: Florian Maillard Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/rtsx_pci_sdmmc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index 8dfbc62f165bc3..d25d4cb59bc97e 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -1425,6 +1425,11 @@ static void realtek_init_host(struct realtek_pci_sdmmc *host) mmc->caps = mmc->caps | MMC_CAP_AGGRESSIVE_PM; mmc->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE | MMC_CAP2_NO_SDIO; + + if (pcr->pci->device == 0x522a && + pcr->pci->subsystem_vendor == PCI_VENDOR_ID_LENOVO && + pcr->pci->subsystem_device == 0x504a) + mmc->caps2 |= MMC_CAP2_NO_WRITE_PROTECT; mmc->max_current_330 = 400; mmc->max_current_180 = 800; mmc->ops = &realtek_pci_sdmmc_ops; From 08b54e16d547d5c1aa61bf7a3595bb1620975eeb Mon Sep 17 00:00:00 2001 From: Zhu Ling Date: Fri, 4 Sep 2026 17:07:46 +0800 Subject: [PATCH 0213/1417] mmc: core: Fix OF node reference leak on card add failure mmc_of_find_child_device() returns a device node with its reference count incremented. mmc_add_card() stores the reference before calling device_add(), while the card is marked present only after device_add() succeeds. If device_add() fails, the callers release the card through mmc_remove_card(). However, mmc_remove_card() only drops the OF node reference for a present card, leaking the reference on this error path. Move of_node_put() outside the present-card conditional so the reference is released for both registered cards and card-add failures. Fixes: 25185f3f31c9 ("mmc: Add SDIO function devicetree subnode parsing") Cc: stable@vger.kernel.org Signed-off-by: Zhu Ling Reviewed-by: Shawn Lin Signed-off-by: Ulf Hansson --- drivers/mmc/core/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index be5cf338bdeb98..92ad39c0cabcd7 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -417,8 +417,8 @@ void mmc_remove_card(struct mmc_card *card) mmc_hostname(card->host), card->rca); } device_del(&card->dev); - of_node_put(card->dev.of_node); } + of_node_put(card->dev.of_node); if (host->cqe_enabled) { host->cqe_ops->cqe_disable(host); From cee9c863ee68cb27d66745eb03f60e357f4f8ad2 Mon Sep 17 00:00:00 2001 From: Alex Bereza Date: Mon, 17 Aug 2026 11:23:55 +0200 Subject: [PATCH 0214/1417] dmaengine: xilinx_dma: Fix hardware buffer descriptor reuse order xilinx_dma_alloc_chan_resources() builds a static ring of hardware buffer descriptors once and the driver uses this ring throughout the lifetime of a channel. This requires the allocation order of hardware buffer descriptors from chan->free_seg_list to stay in sync with the hardware buffer descriptor ring built at channel allocation time by returning oldest descriptors to chan->free_seg_list first. When chan->pending_list is not empty e.g. during xilinx_dma_terminate_all() the chan->free_seg_list and the order of the static hardware buffer descriptor ring get out of sync. Descriptors age in this order: pending -> active -> done. So freeing pending_list first returns the newest buffer descriptors to the chan->free_seg_list first and thus breaks the order required by the static hardware buffer descriptor ring. Then when the channel is reused, after a wrap around of the free_seg_list the DMA will find a hardware buffer descriptor with a length field that is still zeroed and stop with something like this: xilinx-vdma 86000000.dma: Channel 000000003a21d7b8 has errors 10, cdr 6de4c000 tdr 6de4c000 After this no more descriptors are completed and a consumer potentially blocks and waits forever. The only way to get out of this error state is to rebuild the static hardware buffer descriptor ring and the free_seg_list by releasing and re-acquiring the channel. Fix the order in which hardware buffer descriptors are returned to free_seg_list to ensure the mentioned requirement holds. Fixes: 23059408b6a3 ("dmaengine: xilinx_dma: Fix race condition in the driver for multiple descriptor scenario") Signed-off-by: Alex Bereza Reviewed-by: Frank Li Reviewed-by: Suraj Gupta Link: https://patch.msgid.link/20260817-fix-hw-buf-desc-reuse-v1-1-d79827a844c7@bereza.email Signed-off-by: Vinod Koul --- drivers/dma/xilinx/xilinx_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c index bef2b031dba194..0817b74f745055 100644 --- a/drivers/dma/xilinx/xilinx_dma.c +++ b/drivers/dma/xilinx/xilinx_dma.c @@ -920,9 +920,9 @@ static void xilinx_dma_free_descriptors(struct xilinx_dma_chan *chan) spin_lock_irqsave(&chan->lock, flags); - xilinx_dma_free_desc_list(chan, &chan->pending_list); xilinx_dma_free_desc_list(chan, &chan->done_list); xilinx_dma_free_desc_list(chan, &chan->active_list); + xilinx_dma_free_desc_list(chan, &chan->pending_list); spin_unlock_irqrestore(&chan->lock, flags); } From c90b6973daa37f4c283342dff881ae001dea4fe6 Mon Sep 17 00:00:00 2001 From: Christian Lugnberg Date: Mon, 17 Aug 2026 15:51:22 +0200 Subject: [PATCH 0215/1417] dmaengine: sun6i: fix non-atomic read of DMA position registers sun6i_get_chan_size() reads DMA_CHAN_LLI_ADDR and DMA_CHAN_CUR_CNT in two separate readl() calls with no synchronisation between them: pos = readl(pchan->base + DMA_CHAN_LLI_ADDR); bytes = readl(pchan->base + DMA_CHAN_CUR_CNT); DMA_CHAN_LLI_ADDR holds the physical address of the *next* descriptor the engine will load once the current one completes. DMA_CHAN_CUR_CNT holds the remaining byte count for the *current* descriptor. If the DMA engine advances to the next LLI entry between the two reads, pos becomes stale: it still points to what was the next descriptor at the time of the first read, but that descriptor is now the current one and CUR_CNT reflects its initial (full) byte count. The subsequent virtual-chain walk starts one entry too early and accumulates an extra full period's worth of bytes into the residue estimate. Fix this by re-reading DMA_CHAN_LLI_ADDR after DMA_CHAN_CUR_CNT and retrying if the value changed. This double-read pattern guarantees that both registers were sampled during the same descriptor interval. The cost is at most one extra readl() pair per call in the racy case, which occurs only at descriptor boundaries (~every 2 ms) and is negligible. Fixes: a90e173f3faf ("dmaengine: sun6i: Add cyclic capability") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Christian Lugnberg Reviewed-by: Frank Li Link: https://patch.msgid.link/20260817135723.12807-2-christian.lugnberg@soundtrack.io Signed-off-by: Vinod Koul --- drivers/dma/sun6i-dma.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c index f47a326dd7ffa6..04fe1f5042e962 100644 --- a/drivers/dma/sun6i-dma.c +++ b/drivers/dma/sun6i-dma.c @@ -354,8 +354,10 @@ static size_t sun6i_get_chan_size(struct sun6i_pchan *pchan) size_t bytes; dma_addr_t pos; - pos = readl(pchan->base + DMA_CHAN_LLI_ADDR); - bytes = readl(pchan->base + DMA_CHAN_CUR_CNT); + do { + pos = readl(pchan->base + DMA_CHAN_LLI_ADDR); + bytes = readl(pchan->base + DMA_CHAN_CUR_CNT); + } while (pos != readl(pchan->base + DMA_CHAN_LLI_ADDR)); if (pos == LLI_LAST_ITEM) return bytes; From 9096bdc8d930147f7c39a493a859acbd3a8485d8 Mon Sep 17 00:00:00 2001 From: Christian Lugnberg Date: Mon, 17 Aug 2026 15:51:23 +0200 Subject: [PATCH 0216/1417] dmaengine: sun6i: fix undefined behaviour in sun6i_dma_tx_status sun6i_dma_tx_status() calls vchan_find_desc() to look up the virtual descriptor for a given cookie, before checking whether the pointer vd is NULL: vd = vchan_find_desc(&vchan->vc, cookie); txd = to_sun6i_desc(&vd->tx); /* vd may be NULL here */ if (vd) { for (lli = txd->v_lli; ...) vchan_find_desc() returns NULL when the descriptor has already been completed or is in-flight on a physical channel and no longer present in the virtual channel's descriptor list. When vd is NULL, to_sun6i_desc() is called unconditionally on &vd->tx before the NULL check, which is undefined behaviour. Move the call inside the if (vd) guard to ensure it is only reached with a valid pointer. vd = vchan_find_desc(&vchan->vc, cookie); if (vd) { struct sun6i_desc *txd = to_sun6i_desc(&vd->tx); for (lli = txd->v_lli; ...) Fixes: 555859308723 ("dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Christian Lugnberg Reviewed-by: Frank Li Link: https://patch.msgid.link/20260817135723.12807-3-christian.lugnberg@soundtrack.io Signed-off-by: Vinod Koul --- drivers/dma/sun6i-dma.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c index 04fe1f5042e962..7704b016aed8ca 100644 --- a/drivers/dma/sun6i-dma.c +++ b/drivers/dma/sun6i-dma.c @@ -981,7 +981,6 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan, struct sun6i_pchan *pchan = vchan->phy; struct sun6i_dma_lli *lli; struct virt_dma_desc *vd; - struct sun6i_desc *txd; enum dma_status ret; unsigned long flags; size_t bytes = 0; @@ -993,9 +992,9 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan, spin_lock_irqsave(&vchan->vc.lock, flags); vd = vchan_find_desc(&vchan->vc, cookie); - txd = to_sun6i_desc(&vd->tx); if (vd) { + struct sun6i_desc *txd = to_sun6i_desc(&vd->tx); for (lli = txd->v_lli; lli != NULL; lli = lli->v_lli_next) bytes += lli->len; } else if (!pchan || !pchan->desc) { From f6504be006aa4bb4bd26285f410a885c17920d65 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Mon, 17 Aug 2026 22:44:33 +0200 Subject: [PATCH 0217/1417] dmaengine: pxa: fix double counting of the hw descriptors pxad_alloc_desc() was converted from kzalloc(struct_size(sw_desc, hw_desc, nb_hw_desc), GFP_NOWAIT) to kzalloc_flex(), which sets the __counted_by() counter sw_desc->nb_desc itself - but only where the compiler has __builtin_counted_by_ref(), so from gcc 15.1 or clang 22.1 on. The loop below it still increments nb_desc, which makes it come out doubled there and correct elsewhere. nb_desc is what pxad_free_desc() iterates over and what set_updater_desc() indexes from, so set it explicitly and drop the increment. The error path has to lower it to the number of descriptors allocated so far, otherwise pxad_free_desc() would free entries that were never allocated. Fixes: 69050f8d6d075 ("treewide: Replace kmalloc with kmalloc_obj for non-scalar types") Assisted-by: Claude:claude-opus-5 Signed-off-by: Sascha Hauer Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20260817-dmaengine-pxa-v1-1-850c215c1196@pengutronix.de Link: https://patch.msgid.link/20260817-dmaengine-pxa-v2-1-f42ab0569a48@pengutronix.de Signed-off-by: Vinod Koul --- drivers/dma/pxa_dma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/dma/pxa_dma.c b/drivers/dma/pxa_dma.c index fa2ee0b3e09f8c..fc43124fefa89b 100644 --- a/drivers/dma/pxa_dma.c +++ b/drivers/dma/pxa_dma.c @@ -744,6 +744,7 @@ pxad_alloc_desc(struct pxad_chan *chan, unsigned int nb_hw_desc) sw_desc = kzalloc_flex(*sw_desc, hw_desc, nb_hw_desc, GFP_NOWAIT); if (!sw_desc) return NULL; + sw_desc->nb_desc = nb_hw_desc; sw_desc->desc_pool = chan->desc_pool; for (i = 0; i < nb_hw_desc; i++) { @@ -752,10 +753,10 @@ pxad_alloc_desc(struct pxad_chan *chan, unsigned int nb_hw_desc) dev_err(&chan->vc.chan.dev->device, "%s(): Couldn't allocate the %dth hw_desc from dma_pool %p\n", __func__, i, sw_desc->desc_pool); + sw_desc->nb_desc = i; goto err; } - sw_desc->nb_desc++; sw_desc->hw_desc[i] = desc; if (i == 0) From 7ed1e3070c9b4bbd67d5519e14711038dd53ab13 Mon Sep 17 00:00:00 2001 From: Alex Bereza Date: Tue, 18 Aug 2026 09:36:29 +0200 Subject: [PATCH 0218/1417] dmaengine: xilinx_dma: Fix hardware buffer descriptor chain after cyclic DMA Using the DMA in cyclic mode modifies the hardware buffer descriptor chain in xilinx_dma_prep_dma_cyclic so that the last descriptor used by the cyclic transfer points back to the first descriptor, but it never restores the original descriptor ring. This breaks using non-cyclic mode after cyclic mode with an error like: xilinx-vdma 86000000.dma: Channel 00000000354d5c8d has errors 100, cdr 6de40000 tdr 6de40400 The only way to get out of this error state is to rebuild the hardware buffer descriptor ring by releasing and re-acquiring the channel. Fix using non-cyclic mode after cyclic mode by always restoring the original buffer descriptor ring in the same manner as it is set up by xilinx_dma_alloc_chan_resources(). Fixes: 23059408b6a3 ("dmaengine: xilinx_dma: Fix race condition in the driver for multiple descriptor scenario") Signed-off-by: Alex Bereza Reviewed-by: Frank Li Reviewed-by: Suraj Gupta Link: https://patch.msgid.link/20260817-fix-hw-buf-desc-after-cyclic-mode-v1-1-1fe47e701d6c@bereza.email Link: https://patch.msgid.link/20260818-fix-hw-buf-desc-after-cyclic-mode-v2-1-530ff44c6a81@bereza.email Signed-off-by: Vinod Koul --- drivers/dma/xilinx/xilinx_dma.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c index 0817b74f745055..cffe7c6fa64052 100644 --- a/drivers/dma/xilinx/xilinx_dma.c +++ b/drivers/dma/xilinx/xilinx_dma.c @@ -756,15 +756,25 @@ xilinx_aximcdma_alloc_tx_segment(struct xilinx_dma_chan *chan) return segment; } -static void xilinx_dma_clean_hw_desc(struct xilinx_axidma_desc_hw *hw) +static void xilinx_dma_clean_hw_desc(struct xilinx_dma_chan *chan, + struct xilinx_axidma_tx_segment *segment) { - u32 next_desc = hw->next_desc; - u32 next_desc_msb = hw->next_desc_msb; + dma_addr_t next; + u32 i; - memset(hw, 0, sizeof(struct xilinx_axidma_desc_hw)); + /* + * Restore the buffer descriptor's next descriptor pointer to the value + * set up in xilinx_dma_alloc_chan_resources(). Otherwise using the DMA + * in cyclic mode leaves the next descriptor pointer altered and + * prevents subsequent non-cyclic transfers. + */ + i = segment - chan->seg_v; + next = chan->seg_p + + sizeof(*chan->seg_v) * ((i + 1) % XILINX_DMA_NUM_DESCS); - hw->next_desc = next_desc; - hw->next_desc_msb = next_desc_msb; + memset(&segment->hw, 0, sizeof(segment->hw)); + segment->hw.next_desc = lower_32_bits(next); + segment->hw.next_desc_msb = upper_32_bits(next); } static void xilinx_mcdma_clean_hw_desc(struct xilinx_aximcdma_desc_hw *hw) @@ -786,7 +796,7 @@ static void xilinx_mcdma_clean_hw_desc(struct xilinx_aximcdma_desc_hw *hw) static void xilinx_dma_free_tx_segment(struct xilinx_dma_chan *chan, struct xilinx_axidma_tx_segment *segment) { - xilinx_dma_clean_hw_desc(&segment->hw); + xilinx_dma_clean_hw_desc(chan, segment); list_add_tail(&segment->node, &chan->free_seg_list); } From e08aca85c02ff290f785f07acae758f0daf5f49e Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Thu, 10 Sep 2026 09:35:13 -0400 Subject: [PATCH 0219/1417] RDMA/efa: Keep admin queues alive while IRQ is registered The management IRQ handler accesses both the admin completion queue and the async event queue. The driver registered the IRQ before constructing these queues and destroyed them before freeing the IRQ, so the handler's lifetime was not contained by the resources it accesses. Initialize the queues with interrupts masked, request the IRQ, and then switch to interrupt mode. On removal, reset the device and free the IRQ before destroying the queues. Also reset the device before destroying the queues if IRQ registration fails, because the device already has their DMA addresses. Fixes: b7f5e880f377 ("RDMA/efa: Add the efa module") Link: https://patch.msgid.link/20260907-use-after-free-of-admin-queue-struct-v1-1-dd9d9267fbf4@nvidia.com Reviewed-by: Michael Margolin Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/efa/efa_com.c | 4 +--- drivers/infiniband/hw/efa/efa_main.c | 15 +++++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/infiniband/hw/efa/efa_com.c b/drivers/infiniband/hw/efa/efa_com.c index 583b1cf0d721c6..cb2d448db2d79c 100644 --- a/drivers/infiniband/hw/efa/efa_com.c +++ b/drivers/infiniband/hw/efa/efa_com.c @@ -850,7 +850,7 @@ int efa_com_admin_init(struct efa_com_dev *edev, aq->dmadev = edev->dmadev; aq->efa_dev = edev->efa_dev; - set_bit(EFA_AQ_STATE_POLLING_BIT, &aq->state); + efa_com_set_admin_polling_mode(edev, true); sema_init(&aq->avail_cmds, aq->depth); @@ -868,8 +868,6 @@ int efa_com_admin_init(struct efa_com_dev *edev, if (err) goto err_destroy_sq; - efa_com_set_admin_polling_mode(edev, false); - err = efa_com_admin_init_aenq(edev, aenq_handlers); if (err) goto err_destroy_cq; diff --git a/drivers/infiniband/hw/efa/efa_main.c b/drivers/infiniband/hw/efa/efa_main.c index 4cd80727dcd2f3..b1df9bb8fac24d 100644 --- a/drivers/infiniband/hw/efa/efa_main.c +++ b/drivers/infiniband/hw/efa/efa_main.c @@ -619,18 +619,21 @@ static struct efa_dev *efa_probe_device(struct pci_dev *pdev) edev->aq.msix_vector_idx = dev->admin_msix_vector_idx; edev->aenq.msix_vector_idx = dev->admin_msix_vector_idx; - err = efa_set_mgmnt_irq(dev); + err = efa_com_admin_init(edev, &aenq_handlers); if (err) goto err_disable_msix; - err = efa_com_admin_init(edev, &aenq_handlers); + err = efa_set_mgmnt_irq(dev); if (err) - goto err_free_mgmnt_irq; + goto err_destroy_admin; + + efa_com_set_admin_polling_mode(edev, false); return dev; -err_free_mgmnt_irq: - efa_free_irq(dev, &dev->admin_irq); +err_destroy_admin: + efa_com_dev_reset(edev, EFA_REGS_RESET_INIT_ERR); + efa_com_admin_destroy(edev); err_disable_msix: efa_disable_msix(dev); err_reg_read_destroy: @@ -654,8 +657,8 @@ static void efa_remove_device(struct pci_dev *pdev, edev = &dev->edev; efa_com_dev_reset(edev, reset_reason); - efa_com_admin_destroy(edev); efa_free_irq(dev, &dev->admin_irq); + efa_com_admin_destroy(edev); efa_disable_msix(dev); efa_com_mmio_reg_read_destroy(edev); devm_iounmap(&pdev->dev, edev->reg_bar); From e22a3627b7151754f07f90ea3d1ab6e85f5d93f4 Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Thu, 10 Sep 2026 09:35:13 -0400 Subject: [PATCH 0220/1417] RDMA/efa: Keep EQ resources alive while IRQ is registered The completion IRQ handler accesses the EQ state and DMA buffer. Its IRQ was registered before that state was initialized, while teardown released the buffer before free_irq() synchronized the handler. Initialize the EQ without arming it, register the IRQ, and then arm it. Reverse the resource order during teardown by freeing the IRQ before destroying the EQ. Fixes: 2a152512a155 ("RDMA/efa: CQ notifications") Link: https://patch.msgid.link/20260907-use-after-free-of-admin-queue-struct-v1-2-dd9d9267fbf4@nvidia.com Reviewed-by: Michael Margolin Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/efa/efa_com.c | 3 +-- drivers/infiniband/hw/efa/efa_com.h | 1 + drivers/infiniband/hw/efa/efa_main.c | 18 ++++++++++-------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/infiniband/hw/efa/efa_com.c b/drivers/infiniband/hw/efa/efa_com.c index cb2d448db2d79c..04c6d63c449e13 100644 --- a/drivers/infiniband/hw/efa/efa_com.c +++ b/drivers/infiniband/hw/efa/efa_com.c @@ -1252,7 +1252,7 @@ static void efa_com_destroy_eq(struct efa_com_dev *edev, err); } -static void efa_com_arm_eq(struct efa_com_dev *edev, struct efa_com_eq *eeq) +void efa_com_arm_eq(struct efa_com_dev *edev, struct efa_com_eq *eeq) { u32 val = 0; @@ -1341,7 +1341,6 @@ int efa_com_eq_init(struct efa_com_dev *edev, struct efa_com_eq *eeq, eeq->phase = 1; eeq->depth = params.depth; eeq->cb = cb; - efa_com_arm_eq(edev, eeq); return 0; diff --git a/drivers/infiniband/hw/efa/efa_com.h b/drivers/infiniband/hw/efa/efa_com.h index 0341704d09216d..98fb6a42a6cb4d 100644 --- a/drivers/infiniband/hw/efa/efa_com.h +++ b/drivers/infiniband/hw/efa/efa_com.h @@ -169,6 +169,7 @@ int efa_com_admin_init(struct efa_com_dev *edev, void efa_com_admin_destroy(struct efa_com_dev *edev); int efa_com_eq_init(struct efa_com_dev *edev, struct efa_com_eq *eeq, efa_eqe_handler cb, u16 depth, u8 msix_vec); +void efa_com_arm_eq(struct efa_com_dev *edev, struct efa_com_eq *eeq); void efa_com_eq_destroy(struct efa_com_dev *edev, struct efa_com_eq *eeq); int efa_com_dev_reset(struct efa_com_dev *edev, enum efa_regs_reset_reason_types reset_reason); diff --git a/drivers/infiniband/hw/efa/efa_main.c b/drivers/infiniband/hw/efa/efa_main.c index b1df9bb8fac24d..753ed8430b6987 100644 --- a/drivers/infiniband/hw/efa/efa_main.c +++ b/drivers/infiniband/hw/efa/efa_main.c @@ -302,28 +302,30 @@ static void efa_set_host_info(struct efa_dev *dev) static void efa_destroy_eq(struct efa_dev *dev, struct efa_eq *eq) { - efa_com_eq_destroy(&dev->edev, &eq->eeq); efa_free_irq(dev, &eq->irq); + efa_com_eq_destroy(&dev->edev, &eq->eeq); } static int efa_create_eq(struct efa_dev *dev, struct efa_eq *eq, u32 msix_vec) { int err; - efa_setup_comp_irq(dev, eq, msix_vec); - err = efa_request_irq(dev, &eq->irq); + err = efa_com_eq_init(&dev->edev, &eq->eeq, efa_process_eqe, + dev->dev_attr.max_eq_depth, msix_vec); if (err) return err; - err = efa_com_eq_init(&dev->edev, &eq->eeq, efa_process_eqe, - dev->dev_attr.max_eq_depth, msix_vec); + efa_setup_comp_irq(dev, eq, msix_vec); + err = efa_request_irq(dev, &eq->irq); if (err) - goto err_free_comp_irq; + goto err_destroy_eq; + + efa_com_arm_eq(&dev->edev, &eq->eeq); return 0; -err_free_comp_irq: - efa_free_irq(dev, &eq->irq); +err_destroy_eq: + efa_com_eq_destroy(&dev->edev, &eq->eeq); return err; } From 3db7d7d583419f7b1f2e141e36418802dbb25cf8 Mon Sep 17 00:00:00 2001 From: Vadim Nikitushkin Date: Wed, 9 Sep 2026 23:50:28 +0300 Subject: [PATCH 0221/1417] drm/ttm: fix swapped-out resources never leaving their bulk_move range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ttm_tt_swapout() returns the number of pages swapped out on success and a negative error code on failure; for a populated ttm it never returns zero. Commit b2ed01e7ad3d ("drm/ttm: Fix ttm_bo_swapout() infinite LRU walk on swapout failure") moved the bulk_move bookkeeping in ttm_bo_swapout_cb() under "if (!ret)", so the ttm_resource_del_bulk_move_unevictable() / ttm_resource_move_to_lru_tail() pair is now skipped on every successful swapout. The equivalent change for the shrinker in commit 1d59f36e95f7 ("drm/ttm: Fix ttm_bo_shrink() infinite LRU walk on backup failure") tests "lret > 0", which is what was intended here as well. Before b2ed01e7ad3d the resource was taken off the bulk_move before the swapout; since then a swapped-out resource stays inside its BO's bulk_move range (and on the manager LRU) although it is unevictable. When it is later freed or the BO leaves the bulk_move (ttm_resource_free(), ttm_bo_set_bulk_move() via amdgpu_vm_bo_del()), ttm_resource_del_bulk_move() skips it because of its !ttm_resource_unevictable() guard, so a range endpoint in pos->first / pos->last is left pointing at freed memory. The next ttm_lru_bulk_move_tail() or ttm_resource_add_bulk_move() on that cursor is a use-after-free, seen as the resv WARN in ttm_lru_bulk_move_add(), "list_del corruption" in ttm_resource_move_to_lru_tail() or a NULL dereference in ttm_resource_manager_next() -- minutes to hours after a hibernation, or at process exit / reboot following one. Samuel Ainsworth's analysis of drm/amd issue 5387 (see Link) identified the dangling cursor; the missing removal at swapout time is the reason it dangles. Testing the condition for success restores the removal. On an AMD Phoenix APU (ASUS UM3406GA, gfx1103) running suspend-then-hibernate on a 7.0.y stable kernel carrying the backport (Ubuntu 7.0.0-31) the bug crashed 5 of 18 hibernation cycles; a function profile of one hibernation showed 336 ttm_tt_swapout() calls and zero ttm_resource_del_bulk_move_unevictable() calls. With this change the removal happens for every swapped-out resource and 12 further cycles were clean. Fixes: b2ed01e7ad3d ("drm/ttm: Fix ttm_bo_swapout() infinite LRU walk on swapout failure") Cc: stable@vger.kernel.org # v7.1+ Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5387 Link: https://lore.kernel.org/dri-devel/CAHYiNPa6aVacJoLOje-qZ1GyYx-9p0tN4NuP8D_eSL+UJeevXw@mail.gmail.com/ Signed-off-by: Vadim Nikitushkin Reviewed-by: Thomas Hellström Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260909205028.13799-1-bub4z0r@gmail.com --- drivers/gpu/drm/ttm/ttm_bo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index ef56c18ded1b81..a12af5b38a31c0 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -532,7 +532,7 @@ static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo, ret = ttm_resource_try_charge(bo, place, &alloc_state->charge_pool, force_space ? &alloc_state->limit_pool : NULL); - if (ret) { + if (ret > 0) { /* * -EAGAIN means the charge failed, which we treat * like an allocation failure. Therefore, return an From 9ff797e516dbc1ecb73701ec4c24055712d44411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Tue, 8 Sep 2026 08:55:20 +0000 Subject: [PATCH 0222/1417] RDMA/siw: Bound fragmented header copies by the remaining length MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit siw_get_hdr() can receive an extended DDP/RDMAP header across more than one TCP callback. The first callback may receive most of the header, while the next one still limits the copy to hdrlen - MIN_DDP_HDR instead of the number of missing bytes. This makes the destination move past the end of the header and overwrite the receive state, including fpdu_part_rcvd. A later callback can then use a negative fpdu_part_rcvd value as a copy offset, which creates an OOB write. Use the number of header bytes already received when calculating the next copy length. Fixes: 754209850df8 ("RDMA/siw: Always consume all skbuf data in sk_data_ready() upcall.") Signed-off-by: Jérémy Jean Link: https://patch.msgid.link/20260908085520.1746329-1-Jeremy.Jean@oss.cyber.gouv.fr Assisted-by: Codex:gpt-6 Acked-by: Bernard Metzler Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/siw/siw_qp_rx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/sw/siw/siw_qp_rx.c b/drivers/infiniband/sw/siw/siw_qp_rx.c index b566d163c5aabc..e5b641c8d694ad 100644 --- a/drivers/infiniband/sw/siw/siw_qp_rx.c +++ b/drivers/infiniband/sw/siw/siw_qp_rx.c @@ -1079,7 +1079,7 @@ static int siw_get_hdr(struct siw_rx_stream *srx) if (iwarp_pktinfo[opcode].hdr_len > sizeof(struct iwarp_ctrl_tagged)) { int hdrlen = iwarp_pktinfo[opcode].hdr_len; - bytes = min_t(int, hdrlen - MIN_DDP_HDR, srx->skb_new); + bytes = min_t(int, hdrlen - srx->fpdu_part_rcvd, srx->skb_new); skb_copy_bits(skb, srx->skb_offset, (char *)c_hdr + srx->fpdu_part_rcvd, bytes); From fcfe64715b425262af1b36f498f9197f3537ceed Mon Sep 17 00:00:00 2001 From: Vadim Nikitushkin Date: Thu, 10 Sep 2026 17:34:51 +0300 Subject: [PATCH 0223/1417] drm/ttm: apply the swapout bulk_move fix to the intended condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 3db7d7d58341 ("drm/ttm: fix swapped-out resources never leaving their bulk_move range") landed in drm-misc-fixes with its one-line change applied to the wrong "if": the "if (ret)" after ttm_resource_try_charge() in ttm_bo_alloc_at_place() became "if (ret > 0)", while the "if (!ret)" after ttm_tt_swapout() in ttm_bo_swapout_cb() that the patch targeted was left untouched. ttm_resource_try_charge() returns 0 or a negative error code, so with "ret > 0" a failed dmem cgroup charge no longer fails the allocation. Restore that check and apply the intended change: ttm_tt_swapout() returns the number of pages swapped out on success, so the bulk_move removal must run for ret > 0. Fixes: 3db7d7d58341 ("drm/ttm: fix swapped-out resources never leaving their bulk_move range") Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Vadim Nikitushkin Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260910143451.65853-1-bub4z0r@gmail.com --- drivers/gpu/drm/ttm/ttm_bo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index a12af5b38a31c0..9b85b5f388d41e 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -532,7 +532,7 @@ static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo, ret = ttm_resource_try_charge(bo, place, &alloc_state->charge_pool, force_space ? &alloc_state->limit_pool : NULL); - if (ret > 0) { + if (ret) { /* * -EAGAIN means the charge failed, which we treat * like an allocation failure. Therefore, return an @@ -1434,7 +1434,7 @@ ttm_bo_swapout_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *bo) if (ttm_tt_is_populated(tt)) { ret = ttm_tt_swapout(bdev, tt, swapout_walk->gfp_flags); - if (!ret) { + if (ret > 0) { spin_lock(&bdev->lru_lock); ttm_resource_del_bulk_move_unevictable(bo->resource, bo); ttm_resource_move_to_lru_tail(bo->resource); From f65d38155aef069c897a64643a03db237dd1e0c8 Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 3 Aug 2026 10:47:01 +0100 Subject: [PATCH 0224/1417] x86/div64: Fix addition of large constants in mul_u64_add_u64_div_u64() Adding constants over 2^31 fails to compile because the ADD instruction only supports 32bit signed immediates. Replace the "irm" constraint with "erm" so that the compiler loads large constants into a register. Found by a patch to drivers/iio/frequency/ad9910.c [ bp: Massage commit message. ] Fixes: 6480241f31f5 ("lib: add mul_u64_add_u64_div_u64() and mul_u64_u64_div_u64_roundup()") Signed-off-by: David Laight Signed-off-by: Borislav Petkov (AMD) Reviewed-by: H. Peter Anvin Link: https://patch.msgid.link/20260803094702.3852-2-david.laight.linux@gmail.com --- arch/x86/include/asm/div64.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/include/asm/div64.h b/arch/x86/include/asm/div64.h index 30fd06ede751c0..8a2d343f977ec7 100644 --- a/arch/x86/include/asm/div64.h +++ b/arch/x86/include/asm/div64.h @@ -111,7 +111,7 @@ static inline u64 mul_u64_add_u64_div_u64(u64 rax, u64 mul, u64 add, u64 div) if (!statically_true(!add)) asm ("addq %[add], %[lo]; adcq $0, %[hi]" : - [lo] "+r" (rax), [hi] "+r" (rdx) : [add] "irm" (add)); + [lo] "+r" (rax), [hi] "+r" (rdx) : [add] "erm" (add)); asm ("divq %[div]" : "+a" (rax), "+d" (rdx) : [div] "rm" (div)); From adf5967331318bcb436fc80069915231ec039352 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 6 Sep 2026 19:03:47 +0200 Subject: [PATCH 0225/1417] drm/msm: Fix the separate_gpu_kms parameter description The module parameter is separate_gpu_kms, but its MODULE_PARM_DESC() names separate_gpu_drm, so modinfo describes a parameter that does not exist and shows no description for the real one. Use the parameter name in the description. Fixes: 217ed15bd399 ("drm/msm: enable separate binding of GPU and display devices") Assisted-by: LLM Signed-off-by: Karl Mehltretter Patchwork: https://patchwork.freedesktop.org/patch/751407/ Message-ID: <20260906170347.2427-1-kmehltretter@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index db1b655dd055b5..f3d2eaa04f142d 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -55,7 +55,7 @@ MODULE_PARM_DESC(modeset, "Use kernel modesetting [KMS] (1=on (default), 0=disab module_param(modeset, bool, 0600); static bool separate_gpu_kms; -MODULE_PARM_DESC(separate_gpu_drm, "Use separate DRM device for the GPU (0=single DRM device for both GPU and display (default), 1=two DRM devices)"); +MODULE_PARM_DESC(separate_gpu_kms, "Use separate DRM device for the GPU (0=single DRM device for both GPU and display (default), 1=two DRM devices)"); module_param(separate_gpu_kms, bool, 0400); DECLARE_FAULT_ATTR(fail_gem_alloc); From 8061ee61b9426fe38350fa9eead2d9c50b03deb6 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 6 Sep 2026 19:03:01 +0200 Subject: [PATCH 0226/1417] drm/msm/adreno: Fix the skip_gpu parameter description The module parameter is skip_gpu, but its MODULE_PARM_DESC() names no_gpu, so modinfo describes a parameter that does not exist and shows no description for the real one. Use the parameter name in the description. Fixes: 3f17991488af ("drm/msm/adreno: Add a modparam to skip GPU") Assisted-by: LLM Signed-off-by: Karl Mehltretter Patchwork: https://patchwork.freedesktop.org/patch/751406/ Message-ID: <20260906170301.2393-1-kmehltretter@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/adreno_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_device.c b/drivers/gpu/drm/msm/adreno/adreno_device.c index 7f20320ef66af3..05c77fe27e6208 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_device.c +++ b/drivers/gpu/drm/msm/adreno/adreno_device.c @@ -25,7 +25,7 @@ MODULE_PARM_DESC(disable_acd, "Forcefully disable GPU ACD"); module_param_unsafe(disable_acd, bool, 0400); static bool skip_gpu; -MODULE_PARM_DESC(no_gpu, "Disable GPU driver register (0=enable GPU driver register (default), 1=skip GPU driver register"); +MODULE_PARM_DESC(skip_gpu, "Disable GPU driver register (0=enable GPU driver register (default), 1=skip GPU driver register"); module_param(skip_gpu, bool, 0400); extern const struct adreno_gpulist a2xx_gpulist; From 01c8d1f385f788f1bbbbb7687c4386d614281218 Mon Sep 17 00:00:00 2001 From: "Jonghyuk Kim(MalHyuk)" Date: Wed, 2 Sep 2026 10:27:20 +0900 Subject: [PATCH 0227/1417] drm/msm: RCU-free the scheduler-containing ring and VM objects Both struct msm_ringbuffer and struct msm_gem_vm embed a struct drm_gpu_scheduler. msm_ringbuffer_destroy() and the VM free callback msm_gem_vm_free() call drm_sched_fini() on the embedded scheduler and then free the containing object with plain kfree(). drm_sched_fence_get_timeline_name() returns fence->sched->name, and the scheduler fence keeps a .release callback so it is not ops-detached on signalling. A finished fence exported to userspace (the submit out-fence, or a VM_BIND fence, via sync_file / drm_syncobj) keeps pointing at the embedded scheduler after the ring/VM is freed, so a later get_timeline_name() -- reachable unprivileged through SYNC_IOC_FILE_INFO -- dereferences freed slab memory (KASAN slab-use-after-free read). Per the dma-fence lifetime contract the exporter must keep the data backing a signalled fence alive for an RCU grace period. Free the scheduler-containing objects with kfree_rcu() instead of kfree(). Fixes: 1d8a5ca436ee ("drm/msm: Conversion to drm scheduler") Fixes: 92395af63a99 ("drm/msm: Add VM_BIND submitqueue") Cc: stable@vger.kernel.org Signed-off-by: Jonghyuk Kim(MalHyuk) Patchwork: https://patchwork.freedesktop.org/patch/750234/ Message-ID: <20260902012720.880783-1-malhyuk97@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_gem.h | 3 +++ drivers/gpu/drm/msm/msm_gem_vma.c | 2 +- drivers/gpu/drm/msm/msm_ringbuffer.c | 2 +- drivers/gpu/drm/msm/msm_ringbuffer.h | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem.h b/drivers/gpu/drm/msm/msm_gem.h index dff60cbc9d95a8..7c6a8c01f91035 100644 --- a/drivers/gpu/drm/msm/msm_gem.h +++ b/drivers/gpu/drm/msm/msm_gem.h @@ -68,6 +68,9 @@ struct msm_gem_vm { /** @base: Inherit from drm_gpuvm. */ struct drm_gpuvm base; + /** @rcu: RCU-delayed free so an exported sched fence->sched stays valid. */ + struct rcu_head rcu; + /** * @sched: Scheduler used for asynchronous VM_BIND request. * diff --git a/drivers/gpu/drm/msm/msm_gem_vma.c b/drivers/gpu/drm/msm/msm_gem_vma.c index c11d021581e03d..1badec3caa7b8a 100644 --- a/drivers/gpu/drm/msm/msm_gem_vma.c +++ b/drivers/gpu/drm/msm/msm_gem_vma.c @@ -166,7 +166,7 @@ msm_gem_vm_free(struct drm_gpuvm *gpuvm) dma_fence_put(vm->last_fence); put_pid(vm->pid); kfree(vm->log); - kfree(vm); + kfree_rcu(vm, rcu); } /** diff --git a/drivers/gpu/drm/msm/msm_ringbuffer.c b/drivers/gpu/drm/msm/msm_ringbuffer.c index 59c69aa75649e8..38e1e6866301e5 100644 --- a/drivers/gpu/drm/msm/msm_ringbuffer.c +++ b/drivers/gpu/drm/msm/msm_ringbuffer.c @@ -140,5 +140,5 @@ void msm_ringbuffer_destroy(struct msm_ringbuffer *ring) msm_gem_kernel_put(ring->bo, ring->gpu->vm); - kfree(ring); + kfree_rcu(ring, rcu); } diff --git a/drivers/gpu/drm/msm/msm_ringbuffer.h b/drivers/gpu/drm/msm/msm_ringbuffer.h index 3631ec283c6e55..0bfb6b0f42faac 100644 --- a/drivers/gpu/drm/msm/msm_ringbuffer.h +++ b/drivers/gpu/drm/msm/msm_ringbuffer.h @@ -55,6 +55,7 @@ struct msm_ringbuffer { /* * The job scheduler for this ring. */ + struct rcu_head rcu; struct drm_gpu_scheduler sched; bool sched_initialized; From 88ce88e933e40f4ab4b81a5e9d0a10328583f593 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 23:35:07 +0200 Subject: [PATCH 0228/1417] bpf: Add KF_PERFMON kfunc flag Tracing related BPF helpers e.g. under bpf_base_func_proto() are gated behind CAP_PERFMON. However, the same is currently not true for kfuncs and they are accessible via plain CAP_BPF. Add a new KF_PERFMON flag which can be used such that check_kfunc_call() ensures env->allow_ptr_leaks is permitted. This follows similar pattern to existing KF_DESTRUCTIVE flag. The rejection returns -EPERM to match the other CAP_PERFMON gates in the verifier, that is, check_ptr_to_btf_access() and check_ptr_to_map_access(), which report the very same policy to user space. Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260910213510.49358-1-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- Documentation/bpf/kfuncs.rst | 10 ++++++++++ include/linux/btf.h | 1 + kernel/bpf/verifier.c | 14 ++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst index 85f73e0bbd0ff7..6691fe8a32c3d1 100644 --- a/Documentation/bpf/kfuncs.rst +++ b/Documentation/bpf/kfuncs.rst @@ -486,6 +486,16 @@ Example usage in BPF program: /* note that the last argument is omitted */ bpf_task_work_schedule_signal(task, &work->tw, &arrmap, task_work_callback); +2.5.10 KF_PERFMON flag +---------------------- + +The KF_PERFMON flag is used for kfuncs that can expose kernel memory or kernel +addresses to the BPF program, for example by reading through a pointer that the +verifier does not check. Calling such a kfunc requires CAP_PERFMON, or +CAP_SYS_ADMIN, in the same way that the equivalent BPF helpers are gated in +bpf_base_func_proto(). A program loaded with CAP_BPF alone is rejected at load +time. + 2.6 Registering the kfuncs -------------------------- diff --git a/include/linux/btf.h b/include/linux/btf.h index 89d5a5c4f11718..7c62ea17b1163b 100644 --- a/include/linux/btf.h +++ b/include/linux/btf.h @@ -80,6 +80,7 @@ #define KF_ARENA_ARG2 (1 << 15) /* kfunc takes an arena pointer as its second argument */ #define KF_IMPLICIT_ARGS (1 << 16) /* kfunc has implicit arguments supplied by the verifier */ #define KF_SPINLOCK_SAFE (1 << 17) /* kfunc is allowed inside bpf_spin_lock-ed region */ +#define KF_PERFMON (1 << 18) /* kfunc requires CAP_PERFMON */ /* * Tag marking a kernel function as a kfunc. This is meant to minimize the diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 72a3f5998dd27f..939e535a3442b6 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -11356,6 +11356,11 @@ static bool is_kfunc_destructive(struct bpf_call_arg_meta *meta) return meta->kfunc_flags & KF_DESTRUCTIVE; } +static bool is_kfunc_perfmon(struct bpf_call_arg_meta *meta) +{ + return meta->kfunc_flags & KF_PERFMON; +} + static bool is_kfunc_rcu(struct bpf_call_arg_meta *meta) { return meta->kfunc_flags & KF_RCU; @@ -13834,6 +13839,15 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn, return -EACCES; } + if (is_kfunc_perfmon(&meta) && !env->allow_ptr_leaks) { + verbose(env, "%s is allowed only to CAP_PERFMON and CAP_SYS_ADMIN\n", + func_name); + operation = bpf_diag_fmt(env, "kfunc %s", func_name); + bpf_diag_policy(env, insn_idx, operation, "the kfunc requires CAP_PERFMON", + "Load the program with CAP_PERFMON, or avoid the kfunc."); + return -EPERM; + } + sleepable = bpf_is_kfunc_sleepable(&meta); if (sleepable && !in_sleepable(env)) { verbose(env, "program must be sleepable to call sleepable kfunc %s\n", func_name); From 81c975aae375d2053fa2926ee3730f02e713140c Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 23:35:08 +0200 Subject: [PATCH 0229/1417] bpf: Require CAP_PERFMON for kfuncs reading memory Mark fault-safe probe reading kfuncs as KF_PERFMON, similarly as we do for the old-style BPF helper equivalents. bpf_rdonly_cast() is included in this list as well as it returns PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED for an unchecked object and is using fault-safe BPF_PROBE_MEM. Note that only the void form of bpf_rdonly_cast() produced a register that was readable without CAP_PERFMON. For a struct type id the kfunc returns PTR_TO_BTF_ID | PTR_UNTRUSTED, whose dereference has always been gated in check_ptr_to_btf_access(). The flag is not conditional on the type id, so for the latter it only moves the rejection from the dereference to the call itself, which is the better place to report it anyway. The bpf_stream_vprintk() and bpf_stream_print_stack() kfuncs are marked as well. The former ends up in the same bpf_bprintf_prepare() as the bpf_snprintf() helper, where %pks, %pus and %pI4 read through a program- supplied address and %pB resolves one into a symbol. The latter walks the stack and prints each instruction pointer via %pS. Lastly, bpf_get_kmem_cache() takes a raw scalar address that the verifier does not constrain and dereferences the page/slab metadata for it. Field reads on the returned pointer are still blocked (PTR_TO_BTF_ID | PTR_UNTRUSTED -> check_ptr_to_btf_access() results in -EPERM without the CAP), but the NULL/non-NULL result is observable. Reported-by: STAR Labs SG Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260910213510.49358-2-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- kernel/bpf/helpers.c | 60 ++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index b3cc5c8fc87566..712dca5a2c5bdd 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -4883,7 +4883,7 @@ BTF_ID(func, bpf_cgroup_release_dtor) BTF_KFUNCS_START(common_btf_ids) BTF_ID_FLAGS(func, bpf_cast_to_kern_ctx, KF_FASTCALL) -BTF_ID_FLAGS(func, bpf_rdonly_cast, KF_FASTCALL) +BTF_ID_FLAGS(func, bpf_rdonly_cast, KF_FASTCALL | KF_PERFMON) BTF_ID_FLAGS(func, bpf_rcu_read_lock) BTF_ID_FLAGS(func, bpf_rcu_read_unlock) BTF_ID_FLAGS(func, bpf_dynptr_slice, KF_RET_NULL) @@ -4920,26 +4920,26 @@ BTF_ID_FLAGS(func, bpf_wq_set_callback, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_wq_start) BTF_ID_FLAGS(func, bpf_preempt_disable) BTF_ID_FLAGS(func, bpf_preempt_enable) -BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW) +BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW | KF_PERFMON) BTF_ID_FLAGS(func, bpf_iter_bits_next, KF_ITER_NEXT | KF_RET_NULL) BTF_ID_FLAGS(func, bpf_iter_bits_destroy, KF_ITER_DESTROY) -BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_get_kmem_cache) +BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_get_kmem_cache, KF_PERFMON) BTF_ID_FLAGS(func, bpf_iter_kmem_cache_new, KF_ITER_NEW | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_local_irq_save) BTF_ID_FLAGS(func, bpf_local_irq_restore) #ifdef CONFIG_BPF_EVENTS -BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr) -BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr) -BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr) -BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr) -BTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE) +BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE | KF_PERFMON) #endif #ifdef CONFIG_DMA_SHARED_BUFFER BTF_ID_FLAGS(func, bpf_iter_dmabuf_new, KF_ITER_NEW | KF_SLEEPABLE) @@ -4947,26 +4947,26 @@ BTF_ID_FLAGS(func, bpf_iter_dmabuf_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPAB BTF_ID_FLAGS(func, bpf_iter_dmabuf_destroy, KF_ITER_DESTROY | KF_SLEEPABLE) #endif BTF_ID_FLAGS(func, __bpf_trap) -BTF_ID_FLAGS(func, bpf_strcmp); -BTF_ID_FLAGS(func, bpf_strcasecmp); -BTF_ID_FLAGS(func, bpf_strncasecmp); -BTF_ID_FLAGS(func, bpf_strchr); -BTF_ID_FLAGS(func, bpf_strchrnul); -BTF_ID_FLAGS(func, bpf_strnchr); -BTF_ID_FLAGS(func, bpf_strrchr); -BTF_ID_FLAGS(func, bpf_strlen); -BTF_ID_FLAGS(func, bpf_strnlen); -BTF_ID_FLAGS(func, bpf_strspn); -BTF_ID_FLAGS(func, bpf_strcspn); -BTF_ID_FLAGS(func, bpf_strstr); -BTF_ID_FLAGS(func, bpf_strcasestr); -BTF_ID_FLAGS(func, bpf_strnstr); -BTF_ID_FLAGS(func, bpf_strncasestr); +BTF_ID_FLAGS(func, bpf_strcmp, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strcasecmp, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strncasecmp, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strchr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strchrnul, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strnchr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strrchr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strlen, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strnlen, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strspn, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strcspn, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strstr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strcasestr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strnstr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strncasestr, KF_PERFMON); #if defined(CONFIG_BPF_LSM) && defined(CONFIG_CGROUPS) BTF_ID_FLAGS(func, bpf_cgroup_read_xattr, KF_RCU) #endif -BTF_ID_FLAGS(func, bpf_stream_vprintk, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE) -BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE) +BTF_ID_FLAGS(func, bpf_stream_vprintk, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE | KF_PERFMON) BTF_ID_FLAGS(func, bpf_task_work_schedule_signal, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_task_work_schedule_resume, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_dynptr_from_file) From f9191460cd805e84eace0884c75c67ec719fa1d8 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 23:35:09 +0200 Subject: [PATCH 0230/1417] bpf: Require CAP_PERFMON for untrusted read-only memory reads Marking bpf_rdonly_cast() KF_PERFMON CAP-limits one producer of PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED, but not the type itself. A global subprogram argument tagged __arg_untrusted results in the same register with no kfunc call. Reported-by: STAR Labs SG Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260910213510.49358-3-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 939e535a3442b6..8058f684a9ea4c 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -6453,6 +6453,15 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b return -EACCES; } + if (rdonly_untrusted && !env->allow_ptr_leaks) { + verbose(env, "%s access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN\n", + reg_type_str(env, reg->type)); + bpf_diag_policy(env, insn_idx, "read from untrusted read-only memory", + "the access requires CAP_PERFMON", + "Load the program with CAP_PERFMON, or avoid dereferencing untrusted pointers."); + return -EPERM; + } + /* * Accesses to untrusted PTR_TO_MEM are done through probe * instructions, hence no need to check bounds in that case. From a903f145a8914d9ecba009b398d135154e4a5d94 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 23:35:10 +0200 Subject: [PATCH 0231/1417] selftests/bpf: Add tests for the KF_PERFMON gates Add test cases where each one loads with CAP_BPF alone and checks that the program is correctly rejected. # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t verifier_kfunc_perfmon [...] #627/1 verifier_kfunc_perfmon/rdonly_cast_noperfmon:OK #627/2 verifier_kfunc_perfmon/rdonly_cast_noperfmon @unpriv:OK #627/3 verifier_kfunc_perfmon/probe_read_kernel_dynptr_noperfmon:OK #627/4 verifier_kfunc_perfmon/probe_read_kernel_dynptr_noperfmon @unpriv:OK #627/5 verifier_kfunc_perfmon/stream_vprintk_noperfmon:OK #627/6 verifier_kfunc_perfmon/stream_vprintk_noperfmon @unpriv:OK #627/7 verifier_kfunc_perfmon/get_kmem_cache_noperfmon:OK #627/8 verifier_kfunc_perfmon/get_kmem_cache_noperfmon @unpriv:OK #627/9 verifier_kfunc_perfmon/arg_untrusted_read_noperfmon:OK #627/10 verifier_kfunc_perfmon/arg_untrusted_read_noperfmon @unpriv:OK #627 verifier_kfunc_perfmon:OK Summary: 1/10 PASSED, 0 SKIPPED, 0/0 FAILED Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260910213510.49358-4-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/verifier.c | 2 + .../bpf/progs/verifier_kfunc_perfmon.c | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c index 64ac49ad67e63f..ec9e3907c75d61 100644 --- a/tools/testing/selftests/bpf/prog_tests/verifier.c +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c @@ -53,6 +53,7 @@ #include "verifier_iterating_callbacks.skel.h" #include "verifier_jeq_infer_not_null.skel.h" #include "verifier_jit_convergence.skel.h" +#include "verifier_kfunc_perfmon.skel.h" #include "verifier_ld_ind.skel.h" #include "verifier_ldsx.skel.h" #include "verifier_leak_ptr.skel.h" @@ -216,6 +217,7 @@ void test_verifier_int_ptr(void) { RUN(verifier_int_ptr); } void test_verifier_iterating_callbacks(void) { RUN(verifier_iterating_callbacks); } void test_verifier_jeq_infer_not_null(void) { RUN(verifier_jeq_infer_not_null); } void test_verifier_jit_convergence(void) { RUN(verifier_jit_convergence); } +void test_verifier_kfunc_perfmon(void) { RUN(verifier_kfunc_perfmon); } void test_verifier_load_acquire(void) { RUN(verifier_load_acquire); } void test_verifier_ld_ind(void) { RUN(verifier_ld_ind); } void test_verifier_ldsx(void) { RUN(verifier_ldsx); } diff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c new file mode 100644 index 00000000000000..76c39ef30e968c --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include "bpf_misc.h" + +void *user_ptr; +char dynptr_buf[8]; +u64 kaddr; + +extern struct kmem_cache *bpf_get_kmem_cache(u64 addr) __ksym; + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_rdonly_cast is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int rdonly_cast_noperfmon(void *ctx) +{ + char *p = bpf_rdonly_cast(0, 0); + + return p[0x7fff]; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_probe_read_kernel_dynptr is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int probe_read_kernel_dynptr_noperfmon(void *ctx) +{ + struct bpf_dynptr dptr; + + bpf_dynptr_from_mem(dynptr_buf, sizeof(dynptr_buf), 0, &dptr); + bpf_probe_read_kernel_dynptr(&dptr, 0, sizeof(dynptr_buf), user_ptr); + return 0; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_stream_vprintk is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int stream_vprintk_noperfmon(void *ctx) +{ + bpf_stream_printk(BPF_STDOUT, "%pB", (void *)kaddr); + return 0; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_get_kmem_cache is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int get_kmem_cache_noperfmon(void *ctx) +{ + return !!bpf_get_kmem_cache(kaddr); +} + +__weak int subprog_untrusted_read(void *p __arg_untrusted) +{ + return *(char *)p; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("rdonly_untrusted_mem access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int arg_untrusted_read_noperfmon(void *ctx) +{ + return subprog_untrusted_read(0); +} + +char _license[] SEC("license") = "GPL"; From 2936aed9b02162df7fbe08fcd6bfbb3270ad9f95 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 20:56:04 +0200 Subject: [PATCH 0232/1417] bpf: Clear scalar delta on narrowing stack spill check_stack_write_fixed_off() breaks the scalar link on a narrowing spill by zeroing the id of the destination stack slot, but leaves the delta in place. save_register_state() has just copied the source register verbatim, so the slot keeps the BPF_ADD_CONST delta of a register it is no longer linked to. This is the same shape as the case addressed in 1b327732c846 ("bpf: Clear delta when clearing reg id for non-{add,sub} ops"). Unlike the latter, no miscomputation seems reachable, so mainly consistency. Signed-off-by: Daniel Borkmann Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260910185604.44949-1-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 8058f684a9ea4c..1b0b1fb6287866 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -3582,7 +3582,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env, save_register_state(env, state, spi, reg, size); /* Break the relation on a narrowing spill. */ if (!reg_value_fits) - state->stack[spi].spilled_ptr.id = 0; + clear_scalar_id(&state->stack[spi].spilled_ptr); } else if (!reg && !(off % BPF_REG_SIZE) && is_bpf_st_mem(insn) && env->bpf_capable) { struct bpf_reg_state *tmp_reg = &env->fake_reg[0]; From b0b3dc66529676228cb938cbcad66920f735c223 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 20:22:55 +0800 Subject: [PATCH 0233/1417] bpf: Fix divide-by-zero in btf_struct_walk() When an access goes past the struct and the last member is a flexible array, btf_struct_walk() folds the offset back into a single element with (off - moff) % t->size, but never checks that the element type has a size. BTF takes an empty struct, so this in program BTF /* event could be empty */ struct event { #ifdef HAVE_TIMESTAMP __u64 ts; #endif }; struct batch { int nr; struct event events[]; }; divides by zero at prog load time. Getting there needs a PTR_TO_BTF_ID that is not MEM_ALLOC, e.g. a plain read of a local kptr stashed in a map from a sleepable program. Oops: divide error: 0000 [#1] SMP KASAN PTI RIP: 0010:btf_struct_walk+0x53f/0x1570 Call Trace: btf_struct_access+0x42a/0xcd0 check_ptr_to_btf_access+0x4dc/0x1160 check_mem_access+0x3a45/0x8740 check_load_mem+0x36a/0xd10 do_check_common+0x3ef0/0xb210 bpf_check+0x6d3b/0x8580 bpf_prog_load+0xf7c/0x2720 __sys_bpf+0xa83/0x3690 __x64_sys_bpf+0xc7/0x150 x64_sys_call+0x1f3f/0x27e0 do_syscall_64+0xe5/0x610 entry_SYSCALL_64_after_hwframe+0x76/0x7e Reject a zero-sized element type. The fixed array path in the same function already bails out on the same thing: btf_struct_walk() ... /* skip empty array */ if (moff == mtrue_end) continue; msize /= total_nelems; Fixes: 9c5f8a1008a1 ("bpf: Support variable length array in tracing programs") Signed-off-by: Jiayuan Chen Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260910122316.186384-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- kernel/bpf/btf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 9f33e95d57410c..d67a169ba2bb39 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -7187,7 +7187,7 @@ static int btf_struct_walk(struct bpf_verifier_log *log, const struct btf *btf, if (btf_type_is_int(t)) return WALK_SCALAR; - if (!btf_type_is_struct(t)) + if (!btf_type_is_struct(t) || !t->size) goto error; off = (off - moff) % t->size; From f77d21245710690f3fb02d19d8dd4dc17ee58c2c Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 20:22:56 +0800 Subject: [PATCH 0234/1417] selftests/bpf: Test BTF walk into a flexible array of zero-sized elements The program stashes a bpf_obj_new() object whose type ends with a flexible array of empty structs, then reads it back as an untrusted kptr. Without the previous patch this divides by zero in btf_struct_walk() instead of being rejected. # ./test_progs -t verifier_btf_flex_array ... #602 verifier_btf_flex_array:OK Summary: 1/1 PASSED, 0 SKIPPED, 0/0 FAILED Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260910122316.186384-2-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/verifier.c | 2 + .../bpf/progs/verifier_btf_flex_array.c | 56 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c index ec9e3907c75d61..8b439e194bcc5e 100644 --- a/tools/testing/selftests/bpf/prog_tests/verifier.c +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c @@ -23,6 +23,7 @@ #include "verifier_bpf_trap.skel.h" #include "verifier_bswap.skel.h" #include "verifier_btf_ctx_access.skel.h" +#include "verifier_btf_flex_array.skel.h" #include "verifier_btf_unreliable_prog.skel.h" #include "verifier_call_large_imm.skel.h" #include "verifier_cfg.skel.h" @@ -187,6 +188,7 @@ void test_verifier_bpf_get_stack(void) { RUN(verifier_bpf_get_stack); } void test_verifier_bpf_trap(void) { RUN(verifier_bpf_trap); } void test_verifier_bswap(void) { RUN(verifier_bswap); } void test_verifier_btf_ctx_access(void) { RUN(verifier_btf_ctx_access); } +void test_verifier_btf_flex_array(void) { RUN(verifier_btf_flex_array); } void test_verifier_btf_unreliable_prog(void) { RUN(verifier_btf_unreliable_prog); } void test_verifier_call_large_imm(void) { RUN(verifier_call_large_imm); } void test_verifier_cfg(void) { RUN(verifier_cfg); } diff --git a/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c b/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c new file mode 100644 index 00000000000000..59b84261f6226a --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#include "bpf_experimental.h" +#include "bpf_misc.h" + +struct test_empty_event {}; + +struct test_flex_batch { + int nr; + struct test_empty_event events[]; +}; + +struct map_value { + struct test_flex_batch __kptr *batch; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, int); + __type(value, struct map_value); + __uint(max_entries, 1); +} batches SEC(".maps"); + +SEC("syscall") +__description("btf walk into flexible array of zero-sized elements") +__failure __msg("access beyond struct test_flex_batch at off 4 size 1") +int stash_and_peek(void *ctx) +{ + struct test_flex_batch *b, *old; + struct map_value *v; + int key = 0; + + v = bpf_map_lookup_elem(&batches, &key); + if (!v) + return 0; + + b = bpf_obj_new(struct test_flex_batch); + if (!b) + return 0; + b->nr = 1; + + old = bpf_kptr_xchg(&v->batch, b); + if (old) + bpf_obj_drop(old); + + b = v->batch; + if (!b) + return 0; + + return b->nr + *(char *)&b->events[0]; +} + +char _license[] SEC("license") = "GPL"; From 01b245ba016d44861690594e10f67e026ce8552f Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 19:26:26 +0800 Subject: [PATCH 0235/1417] bpf: Fix out-of-bounds read of sk_protocol in bpf_sock_destroy() sk_protocol lives in struct sock, not in struct sock_common. A timewait or request sock handed to bpf_sock_destroy() by the tcp iterator is neither, so reading sk->sk_protocol runs past the object: ================================================================== BUG: KASAN: slab-out-of-bounds in bpf_sock_destroy+0xc7/0xe0 Read of size 2 at addr ffff8881047d11b4 by task test_progs/428 Tainted: [W]=WARN Call Trace: dump_stack_lvl+0x91/0xf0 print_report+0xd1/0x630 kasan_report+0xf3/0x130 __asan_report_load2_noabort+0x14/0x30 bpf_sock_destroy+0xc7/0xe0 bpf_prog_c3dd61f9d9cd9f37_iter_tcp6_timewait+0x9f/0xb7 bpf_iter_run_prog+0x538/0xde0 bpf_iter_tcp_seq_show+0x26b/0x4b0 bpf_seq_read+0x424/0x1210 vfs_read+0x197/0xe40 ksys_read+0x119/0x240 __x64_sys_read+0x72/0xc0 x64_sys_call+0x647/0x27e0 do_syscall_64+0xe5/0x610 entry_SYSCALL_64_after_hwframe+0x76/0x7e Only check sk_protocol on full socks. tcp_abort() already knows how to deal with TIME_WAIT and NEW_SYN_RECV socks. Also fix the comment, it never matched the code. Fixes: 4ddbcb886268 ("bpf: Add bpf_sock_destroy kfunc") Reported-by: Xiang Mei (Microsoft) Closes: https://lore.kernel.org/bpf/20260702224519.800135-1-xmei5@asu.edu/ Signed-off-by: Jiayuan Chen Reviewed-by: Kuniyuki Iwashima Link: https://lore.kernel.org/r/20260910112634.152195-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- net/core/filter.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 2a84f9d0113142..cae43b9991627d 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -12913,8 +12913,9 @@ __bpf_kfunc_start_defs(); * @sock: Pointer to socket to be destroyed * * Return: - * On error, may return EPROTONOSUPPORT, EINVAL. - * EPROTONOSUPPORT if protocol specific destroy handler is not supported. + * On error, may return EOPNOTSUPP, or whatever the protocol specific + * destroy handler returns. + * EOPNOTSUPP if protocol specific destroy handler is not supported. * 0 otherwise */ __bpf_kfunc int bpf_sock_destroy(struct sock_common *sock) @@ -12926,8 +12927,12 @@ __bpf_kfunc int bpf_sock_destroy(struct sock_common *sock) * Supporting protocols will need to acquire sock lock in the BPF context * prior to invoking this kfunc. */ - if (!sk->sk_prot->diag_destroy || (sk->sk_protocol != IPPROTO_TCP && - sk->sk_protocol != IPPROTO_UDP)) + if (!sk->sk_prot->diag_destroy) + return -EOPNOTSUPP; + + if (sk_fullsock(sk) && + sk->sk_protocol != IPPROTO_TCP && + sk->sk_protocol != IPPROTO_UDP) return -EOPNOTSUPP; return sk->sk_prot->diag_destroy(sk, ECONNABORTED); From eaab8cab451b9502ce224cd202550375b894a467 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 19:27:28 +0800 Subject: [PATCH 0236/1417] tcp: Skip cond_resched() in inet_csk_listen_stop() under BPF context bpf_sock_destroy() runs from the tcp iterator, under rcu_read_lock(). If the sock is a listener that still has children in its accept queue, tcp_abort() ends up in inet_csk_listen_stop() and the cond_resched() there trips the debug check: BUG: sleeping function called from invalid context at net/ipv4/inet_connection_sock.c:1523 in_atomic(): 0, irqs_disabled(): 0, non_block: 0, pid: 628, name: test_progs preempt_count: 0, expected: 0 RCU nest depth: 1, expected: 0 locks held by test_progs/628: 3, last CPU#3: #0: ffff8881158cee18 (&p->lock){+.+.}-{4:4}, at: bpf_seq_read+0x56/0x1210 #1: ffff8881106bb858 (sk_lock-AF_INET6){+.+.}-{0:0}, at: bpf_iter_tcp_seq_show+0x32b/0x4b0 #2: ffffffffb435af20 (rcu_read_lock){....}-{1:3}, at: bpf_iter_run_prog+0x46b/0xde0 CPU: 3 UID: 0 PID: 628 Comm: test_progs Tainted: G W 7.2.0+ #65 PREEMPT Tainted: [W]=WARN Call Trace: dump_stack_lvl+0xc1/0xf0 dump_stack+0x10/0x20 __might_resched+0x3d2/0x610 inet_csk_listen_stop+0x7b/0xbf0 tcp_abort+0x23b/0x3b0 bpf_sock_destroy+0xfc/0x140 bpf_prog_448133d24601754f_iter_tcp6_server+0x81/0x8a bpf_iter_run_prog+0x538/0xde0 bpf_iter_tcp_seq_show+0x26b/0x4b0 bpf_seq_read+0x424/0x1210 vfs_read+0x197/0xe40 ksys_read+0x119/0x240 __x64_sys_read+0x72/0xc0 x64_sys_call+0x647/0x27e0 do_syscall_64+0xe5/0x610 entry_SYSCALL_64_after_hwframe+0x76/0x7e RIP: 0033:0x7fad39b28aca RSP: 002b:00007ffc381c61c0 EFLAGS: 00000246 ORIG_RAX: 0000000000000000 RAX: ffffffffffffffda RBX: 00007ffc381c6a88 RCX: 00007fad39b28aca RDX: 0000000000000032 RSI: 00007ffc381c6250 RDI: 0000000000000014 RBP: 00007ffc381c61e0 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000003 R13: 0000000000000000 R14: 000055f077c1bbb0 R15: 00007fad3a0f3000 The commit that added the kfunc already guards lock_sock() in tcp_abort() and udp_abort() with has_current_bpf_ctx(), but missed the listener path. Do the same for the cond_resched(). The loop runs inside the iterator's rcu_read_lock(), it must not reschedule or report a quiescent state there. Fixes: 4ddbcb886268 ("bpf: Add bpf_sock_destroy kfunc") Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260910112736.153710-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- net/ipv4/inet_connection_sock.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 6257459bcee247..6a30f11384547e 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -1520,7 +1520,8 @@ void inet_csk_listen_stop(struct sock *sk) local_bh_enable(); sock_put(child); - cond_resched(); + if (!has_current_bpf_ctx()) + cond_resched(); } if (queue->fastopenq.rskq_rst_head) { /* Free all the reqs queued in rskq_rst_head. */ From 8036d3a5a6589ef0721d15e7c03976bc3996f7a8 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 19:28:02 +0800 Subject: [PATCH 0237/1417] selftests/bpf: Test bpf_sock_destroy() on TIME_WAIT and listener socks Add two subtests. tcp_timewait: the client shuts down first and the server closes after it, so the client sock ends up in TIME_WAIT. A tcp iterator then finds the timewait sock by the cookie it inherited from the client sock and destroys it. Iterate once more to make sure it is gone. Without the first fix bpf_sock_destroy() reads past the timewait sock and KASAN complains. tcp_listen_pending: connect to a listener but never accept, so the child sits in the accept queue, then destroy the listener. Without the second fix the cond_resched() in inet_csk_listen_stop() trips the might_sleep check under rcu_read_lock(). ./test_progs -a sock_destroy #444/1 sock_destroy/tcp_client:OK #444/2 sock_destroy/tcp_server:OK #444/3 sock_destroy/tcp_listen_pending:OK #444/4 sock_destroy/tcp_timewait:OK #444/5 sock_destroy/udp_client:OK #444/6 sock_destroy/udp_server:OK #444/7 sock_destroy/trace_tcp_destroy_sock:OK #444 sock_destroy:OK Summary: 1/7 PASSED, 0 SKIPPED, 0/0 FAILED Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260910112810.153801-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/sock_destroy.c | 121 ++++++++++++++++++ .../selftests/bpf/progs/sock_destroy_prog.c | 30 +++++ 2 files changed, 151 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/sock_destroy.c b/tools/testing/selftests/bpf/prog_tests/sock_destroy.c index 9c11938fe597de..78d642a02bdb68 100644 --- a/tools/testing/selftests/bpf/prog_tests/sock_destroy.c +++ b/tools/testing/selftests/bpf/prog_tests/sock_destroy.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include #include @@ -110,6 +111,122 @@ static void test_tcp_server(struct sock_destroy_prog *skel) close(serv); } +static void test_tcp_listen_pending(struct sock_destroy_prog *skel) +{ + int serv = -1, clien = -1, accept_serv = -1, n, serv_port; + struct pollfd pfd = { .events = POLLIN }; + char buf[1]; + + serv = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); + if (!ASSERT_GE(serv, 0, "start_server")) + goto cleanup; + serv_port = get_socket_local_port(serv); + if (!ASSERT_GE(serv_port, 0, "get_sock_local_port")) + goto cleanup; + skel->bss->serv_port = (__be16)serv_port; + + /* + * Connect but never accept, so the child sits in the accept queue + * of the listener. Wait until it's actually there. + */ + clien = connect_to_fd(serv, 0); + if (!ASSERT_GE(clien, 0, "connect_to_fd")) + goto cleanup; + pfd.fd = serv; + if (!ASSERT_EQ(poll(&pfd, 1, -1), 1, "poll listener")) + goto cleanup; + + /* Run iterator program that destroys server sockets. */ + start_iter_sockets(skel->progs.iter_tcp6_server); + + accept_serv = accept(serv, NULL, NULL); + if (!ASSERT_LT(accept_serv, 0, "accept on destroyed listener")) + goto cleanup; + ASSERT_EQ(errno, EINVAL, "error code on destroyed listener"); + + /* The unaccepted child was reset along with the listener. */ + n = recv(clien, buf, sizeof(buf), 0); + if (!ASSERT_LT(n, 0, "client recv on reset child")) + goto cleanup; + ASSERT_EQ(errno, ECONNRESET, "error code on reset child"); + +cleanup: + if (clien != -1) + close(clien); + if (accept_serv != -1) + close(accept_serv); + if (serv != -1) + close(serv); +} + +static void test_tcp_timewait(struct sock_destroy_prog *skel) +{ + int serv = -1, clien = -1, accept_serv = -1, n; + struct timeval tv = {}; + char buf[1]; + + serv = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); + if (!ASSERT_GE(serv, 0, "start_server")) + goto cleanup; + + clien = connect_to_fd(serv, 0); + if (!ASSERT_GE(clien, 0, "connect_to_fd")) + goto cleanup; + + accept_serv = accept(serv, NULL, NULL); + if (!ASSERT_GE(accept_serv, 0, "serv accept")) + goto cleanup; + + /* + * Active close from the client, then close the server side. Once + * recv() sees EOF the server FIN has been processed and the client + * sock is in TIME_WAIT. Block without timeout so a loaded CI box + * can't race us. + */ + if (!ASSERT_OK(setsockopt(clien, SOL_SOCKET, SO_RCVTIMEO, &tv, + sizeof(tv)), "clear rcvtimeo")) + goto cleanup; + if (!ASSERT_OK(shutdown(clien, SHUT_WR), "client shutdown")) + goto cleanup; + + /* + * Make sure the server has seen the client FIN before it closes, + * so the two FINs never cross. + */ + n = recv(accept_serv, buf, sizeof(buf), 0); + if (!ASSERT_EQ(n, 0, "server recv EOF")) + goto cleanup; + + close(accept_serv); + accept_serv = -1; + + /* block until return EOF */ + n = recv(clien, buf, sizeof(buf), 0); + if (!ASSERT_EQ(n, 0, "client recv EOF")) + goto cleanup; + + /* Run iterator program that destroys the timewait client sock. */ + skel->bss->tw_found = 0; + start_iter_sockets(skel->progs.iter_tcp6_timewait); + if (!ASSERT_EQ(skel->bss->tw_found, 1, "timewait sock found")) + goto cleanup; + + ASSERT_OK(skel->bss->tw_destroy_err, "destroy timewait sock"); + + /* The destroyed timewait sock must be gone. */ + skel->bss->tw_found = 0; + start_iter_sockets(skel->progs.iter_tcp6_timewait); + ASSERT_EQ(skel->bss->tw_found, 0, "timewait sock destroyed"); + +cleanup: + if (clien != -1) + close(clien); + if (accept_serv != -1) + close(accept_serv); + if (serv != -1) + close(serv); +} + static void test_udp_client(struct sock_destroy_prog *skel) { int serv = -1, clien = -1, n = 0; @@ -204,6 +321,10 @@ void test_sock_destroy(void) test_tcp_client(skel); if (test__start_subtest("tcp_server")) test_tcp_server(skel); + if (test__start_subtest("tcp_listen_pending")) + test_tcp_listen_pending(skel); + if (test__start_subtest("tcp_timewait")) + test_tcp_timewait(skel); if (test__start_subtest("udp_client")) test_udp_client(skel); if (test__start_subtest("udp_server")) diff --git a/tools/testing/selftests/bpf/progs/sock_destroy_prog.c b/tools/testing/selftests/bpf/progs/sock_destroy_prog.c index 9e0bf7a54cec9d..0a8887543218ac 100644 --- a/tools/testing/selftests/bpf/progs/sock_destroy_prog.c +++ b/tools/testing/selftests/bpf/progs/sock_destroy_prog.c @@ -7,6 +7,8 @@ #include "bpf_tracing_net.h" __be16 serv_port = 0; +int tw_found = 0; +int tw_destroy_err = 0; int bpf_sock_destroy(struct sock_common *sk) __ksym; @@ -100,6 +102,34 @@ int iter_tcp6_server(struct bpf_iter__tcp *ctx) return 0; } +SEC("iter/tcp") +int iter_tcp6_timewait(struct bpf_iter__tcp *ctx) +{ + struct sock_common *sk_common = ctx->sk_common; + __u64 *val; + int key = 0; + + if (!sk_common) + return 0; + + if (sk_common->skc_family != AF_INET6) + return 0; + + if (!bpf_skc_to_tcp_timewait_sock(sk_common)) + return 0; + + val = bpf_map_lookup_elem(&tcp_conn_sockets, &key); + if (!val) + return 0; + /* The timewait sock inherits the cookie of the closed client sock. */ + if (bpf_get_socket_cookie(sk_common) != *val) + return 0; + + tw_found++; + tw_destroy_err = bpf_sock_destroy(sk_common); + + return 0; +} SEC("iter/udp") int iter_udp6_client(struct bpf_iter__udp *ctx) From 27600805e62f800bacf990354632eae4e487d34c Mon Sep 17 00:00:00 2001 From: Yazen Ghannam Date: Thu, 3 Sep 2026 10:43:25 -0500 Subject: [PATCH 0238/1417] x86/amd_node: Fix PCI device reference counting in amd_smn_init() The local "root" pointer is a temporary variable used during the device search. Therefore, refcount related to the search iterators should be cleaned up after the search is complete. Use the __free() cleanup macro to ensure the refcount is decremented when the temporary pointer goes out of scope. Additionally, increment the refcount when caching a root pointer. This ensures the in-use refcount is separate from the temporary search refcounting. Finally, drop the redundant "root = NULL" before the second search loop. The pci_get_class() iterator always decrements the refcount of its "from" argument, so the first loop can only fall through with "root" already NULL. Fixes: 0a4b61d9c2e4 ("x86/amd_node: Fix AMD root device caching") Closes: https://sashiko.dev/#/patchset/20260806160159.230453-1-jason.andryuk%40amd.com Reported-by: Sashiko Assisted-by: LLM Signed-off-by: Yazen Ghannam Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Mario Limonciello (AMD) Cc: Link: https://patch.msgid.link/20260903154325.74343-1-yazen.ghannam@amd.com --- arch/x86/kernel/amd_node.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/arch/x86/kernel/amd_node.c b/arch/x86/kernel/amd_node.c index 762585775b5a24..b7926ba3610a33 100644 --- a/arch/x86/kernel/amd_node.c +++ b/arch/x86/kernel/amd_node.c @@ -251,7 +251,7 @@ __setup("amd_smn_debugfs_enable", amd_smn_enable_dfs); static int __init amd_smn_init(void) { u16 count, num_roots, roots_per_node, node, num_nodes; - struct pci_dev *root; + struct pci_dev *root __free(pci_dev_put) = NULL; if (!cpu_feature_enabled(X86_FEATURE_ZEN)) return 0; @@ -262,7 +262,6 @@ static int __init amd_smn_init(void) return 0; num_roots = 0; - root = NULL; while ((root = get_next_root(root))) { pci_dbg(root, "Reserving PCI config space\n"); @@ -299,14 +298,13 @@ static int __init amd_smn_init(void) count = 0; node = 0; - root = NULL; while (node < num_nodes && (root = get_next_root(root))) { /* Use one root for each node and skip the rest. */ if (count++ % roots_per_node) continue; pci_dbg(root, "is root for AMD node %u\n", node); - amd_roots[node++] = root; + amd_roots[node++] = pci_dev_get(root); } if (enable_dfs) { From 55a8e1451869233db837a97e8f3cbf9983bc8678 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V (Arm)" Date: Tue, 8 Sep 2026 17:02:32 +0530 Subject: [PATCH 0239/1417] x86/mm: Don't force unencrypted DMA for IOMMU-backed devices Commit 8277a12d0d60 ("dma-pool: track decrypted atomic pools and select them via attrs") exposed an issue with force_dma_unencrypted() on systems using host memory encryption. force_dma_unencrypted() checks whether the device DMA mask can address the encryption bit and, if not, requires DMA allocations to use unencrypted memory. However, this check is not applicable when the device is using the IOMMU. In that case, the device DMA mask constrains the IOVA seen by the device, not the backing physical address, so it does not need to cover the C-bit. This currently causes dma_alloc_attrs() to set __DMA_ATTR_ALLOC_CC_SHARED for such devices. iommu_dma_alloc() does not support that attribute and rejects the allocation, causing DMA allocations to fail. Do not force DMA allocations to be unencrypted when the device is using the IOMMU. This allows the IOMMU to map the encrypted physical pages as before and avoids incorrectly requesting CC_SHARED allocations. Fixes: 8277a12d0d60 ("dma-pool: track decrypted atomic pools and select them via attrs") Reported-by: Timo Witte Closes: https://lore.kernel.org/all/CANB4YXS=Nf-co3t8eMHtqrW4sn=1BDcP6o=EoTrgZm0WYMYTyw@mail.gmail.com/ Signed-off-by: Aneesh Kumar K.V (Arm) Link: https://lore.kernel.org/r/20260908113232.247457-1-aneesh.kumar@kernel.org [mszyprow: adjusted url and changed 'link' tag to the 'closes' one] Signed-off-by: Marek Szyprowski --- arch/x86/mm/mem_encrypt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/x86/mm/mem_encrypt.c b/arch/x86/mm/mem_encrypt.c index 95bae74fdab233..3aefdef5bcb0ba 100644 --- a/arch/x86/mm/mem_encrypt.c +++ b/arch/x86/mm/mem_encrypt.c @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -30,7 +31,7 @@ bool force_dma_unencrypted(struct device *dev) * device does not support DMA to addresses that include the * encryption mask. */ - if (cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT)) { + if (cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT) && !use_dma_iommu(dev)) { u64 dma_enc_mask = DMA_BIT_MASK(__ffs64(sme_me_mask)); u64 dma_dev_mask = min_not_zero(dev->coherent_dma_mask, dev->bus_dma_limit); From 143755bdabaa96776c24f878014608e9cb44f930 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 7 Sep 2026 04:00:15 +0200 Subject: [PATCH 0240/1417] dma-buf: Make DMABUF_DEBUG default to y on DEBUG_KERNEL kernels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 646013f513f3 ("dma-buf: enable DMABUF_DEBUG by default on DEBUG kernels") changed the default of DMABUF_DEBUG to "y if DEBUG", but no Kconfig symbol DEBUG exists, so the option has had no default since. Use DEBUG_KERNEL, the Kconfig symbol for a debug kernel. Fixes: 646013f513f3 ("dma-buf: enable DMABUF_DEBUG by default on DEBUG kernels") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260907020015.24719-1-kmehltretter@gmail.com --- drivers/dma-buf/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma-buf/Kconfig b/drivers/dma-buf/Kconfig index 7efc0f0d07126c..e4f078a326a413 100644 --- a/drivers/dma-buf/Kconfig +++ b/drivers/dma-buf/Kconfig @@ -43,7 +43,7 @@ config UDMABUF config DMABUF_DEBUG bool "DMA-BUF debug checks" depends on DMA_SHARED_BUFFER - default y if DEBUG + default y if DEBUG_KERNEL help This option enables additional checks for DMA-BUF importers and exporters. Specifically it validates that importers do not peek at the From b344ca94e8cc85796f16ea25e2e5a8e0303fe813 Mon Sep 17 00:00:00 2001 From: David Hu Date: Tue, 1 Sep 2026 17:08:48 +0000 Subject: [PATCH 0241/1417] dma-buf: Fix silent overflow for phys vec to sgt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In case MMIO size is bigger than 4G and peer2peer DMA goes through host bridge, we trigger a code path that assigns the total linked IOVA (which is greater than 4G) to mapped_len. Previously, `mapped_len` was declared as 32-bit `unsigned int`. When accumulating `size_t` lengths, this leads to a silent wrap-around. This truncation causes truncated lengths to be passed to functions like `fill_sg_entry()`. Fix this by changing `mapped_len` to `size_t` (64-bit). While at it, fix similar potential overflow issues in `calc_sg_nents` by using `check_add_overflow()` for `nents` and using `unsigned int` for the loop iterator in `fill_sg_entry` to match. Fixes: 3aa31a8bb11e ("dma-buf: provide phys_vec to scatter-gather mapping routine") Cc: stable@vger.kernel.org Cc: iommu@lists.linux.dev Reviewed-by: Pranjal Shrivastava Reviewed-by: Kevin Tian Reviewed-by: Leon Romanovsky Signed-off-by: David Hu Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260901170849.4052816-2-dhu@x6u.co --- drivers/dma-buf/dma-buf-mapping.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/dma-buf/dma-buf-mapping.c b/drivers/dma-buf/dma-buf-mapping.c index 794acff2546a34..80f6ab2f4809d4 100644 --- a/drivers/dma-buf/dma-buf-mapping.c +++ b/drivers/dma-buf/dma-buf-mapping.c @@ -5,12 +5,13 @@ */ #include #include +#include static struct scatterlist *fill_sg_entry(struct scatterlist *sgl, size_t length, dma_addr_t addr) { unsigned int len, nents; - int i; + unsigned int i; nents = DIV_ROUND_UP(length, UINT_MAX); for (i = 0; i < nents; i++) { @@ -40,8 +41,12 @@ static unsigned int calc_sg_nents(struct dma_iova_state *state, size_t i; if (!state || !dma_use_iova(state)) { - for (i = 0; i < nr_ranges; i++) - nents += DIV_ROUND_UP(phys_vec[i].len, UINT_MAX); + for (i = 0; i < nr_ranges; i++) { + unsigned int added = DIV_ROUND_UP(phys_vec[i].len, UINT_MAX); + + if (check_add_overflow(nents, added, &nents)) + return 0; + } } else { /* * In IOVA case, there is only one SG entry which spans @@ -95,9 +100,10 @@ struct sg_table *dma_buf_phys_vec_to_sgt(struct dma_buf_attachment *attach, size_t nr_ranges, size_t size, enum dma_data_direction dir) { - unsigned int nents, mapped_len = 0; struct dma_buf_dma *dma; struct scatterlist *sgl; + size_t mapped_len = 0; + unsigned int nents; dma_addr_t addr; size_t i; int ret; @@ -133,6 +139,8 @@ struct sg_table *dma_buf_phys_vec_to_sgt(struct dma_buf_attachment *attach, } nents = calc_sg_nents(dma->state, phys_vec, nr_ranges, size); + + /* sg_alloc_table will cleanly fail and return -EINVAL if nents == 0 */ ret = sg_alloc_table(&dma->sgt, nents, GFP_KERNEL | __GFP_ZERO); if (ret) goto err_free_state; From 06dd5e1ae8ce4e129791087c8c66594950f0ba03 Mon Sep 17 00:00:00 2001 From: David Hu Date: Tue, 1 Sep 2026 17:08:49 +0000 Subject: [PATCH 0242/1417] dma-buf: Split sgl by largest page-aligned chunk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, `fill_sg_entry()` splits the scatterlist using `UINT_MAX`. This creates a non-page-aligned DMA length (`0xFFFFFFFF`) for the first entry, resulting in non-page-aligned DMA addresses for all subsequent entries. While the underlying IOMMU mapping may be contiguous, hardware DMA engines often require explicit address alignment (e.g., page, cacheline, or storage sector boundaries). Passing unaligned addresses and lengths can cause explicit failures in DMA descriptor creation or silent data corruption if lower unaligned bits are truncated. In addition, a non-page-aligned sgl length will trigger an edge case in `ib_umem_find_best_pgsz()`. In case of a discontinuity in later buffers, we will have a `va` with lowest bit set to 1. That will lead to `ib_umem_find_best_pgsz()` always return 0, and break the promise to find best page size for the mapping on the NIC side. Fix this by splitting the scatterlist by the largest possible page aligned chunk within `UINT_MAX` (`ALIGN_DOWN(UINT_MAX, PAGE_SIZE)`). This ensures all scatterlist DMA addresses and lengths remain page aligned, while minimizing the total number of sgl entries. Page-aligned entries allow the system to cleanly chunk payloads into PCIe MaxPayloadSize (MPS) (e.g., 128 bytes, 256 bytes, 512 bytes). As a result, this may help reduce TLP fragmentation in P2P transfers and alleviate potential congestion within a logical PCIe switch partition, especially when Relaxed Ordering is not possible due to hardware constraints. Reported-by: sashiko-bot Closes: https://lore.kernel.org/all/20260609165431.778061F00893@smtp.kernel.org/ Fixes: 3aa31a8bb11e ("dma-buf: provide phys_vec to scatter-gather mapping routine") Cc: stable@vger.kernel.org Reviewed-by: Leon Romanovsky Signed-off-by: David Hu Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260901170849.4052816-3-dhu@x6u.co --- drivers/dma-buf/dma-buf-mapping.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/dma-buf/dma-buf-mapping.c b/drivers/dma-buf/dma-buf-mapping.c index 80f6ab2f4809d4..833be519e1e6a6 100644 --- a/drivers/dma-buf/dma-buf-mapping.c +++ b/drivers/dma-buf/dma-buf-mapping.c @@ -6,16 +6,17 @@ #include #include #include +#include + +#define MAX_SG_ENT_SZ ALIGN_DOWN(UINT_MAX, PAGE_SIZE) static struct scatterlist *fill_sg_entry(struct scatterlist *sgl, size_t length, dma_addr_t addr) { - unsigned int len, nents; - unsigned int i; + size_t len; - nents = DIV_ROUND_UP(length, UINT_MAX); - for (i = 0; i < nents; i++) { - len = min_t(size_t, length, UINT_MAX); + while (length) { + len = min(length, MAX_SG_ENT_SZ); length -= len; /* * DMABUF abuses scatterlist to create a scatterlist @@ -25,8 +26,10 @@ static struct scatterlist *fill_sg_entry(struct scatterlist *sgl, size_t length, * does not require the CPU list for mapping or unmapping. */ sg_set_page(sgl, NULL, 0, 0); - sg_dma_address(sgl) = addr + (dma_addr_t)i * UINT_MAX; + sg_dma_address(sgl) = addr; sg_dma_len(sgl) = len; + addr += len; + /* Unconditionally advance. On last segment, this becomes NULL */ sgl = sg_next(sgl); } @@ -42,7 +45,7 @@ static unsigned int calc_sg_nents(struct dma_iova_state *state, if (!state || !dma_use_iova(state)) { for (i = 0; i < nr_ranges; i++) { - unsigned int added = DIV_ROUND_UP(phys_vec[i].len, UINT_MAX); + unsigned int added = DIV_ROUND_UP(phys_vec[i].len, MAX_SG_ENT_SZ); if (check_add_overflow(nents, added, &nents)) return 0; @@ -53,7 +56,7 @@ static unsigned int calc_sg_nents(struct dma_iova_state *state, * for whole IOVA address space, but we need to make sure * that it fits sg->length, maybe we need more. */ - nents = DIV_ROUND_UP(size, UINT_MAX); + nents = DIV_ROUND_UP(size, MAX_SG_ENT_SZ); } return nents; From d313499df66159b4b7971d760d16729598ab7e5a Mon Sep 17 00:00:00 2001 From: Theodor Arsenij Larionov Trichkine Date: Tue, 25 Aug 2026 12:10:56 +0300 Subject: [PATCH 0243/1417] netfilter: nft_nat: fully initialise new_addr in netmap setup nft_nat_setup_netmap() builds the mapped address in an on-stack union nf_inet_addr. For an IPv4 mapping it writes only the 4-byte .ip member and the loop runs a single 32-bit iteration, but it then copies the whole 16-byte union into range->min_addr and range->max_addr, so the upper 12 bytes reach nf_nat_setup_info() uninitialised. KMSAN reports an uninit-value in nf_nat_setup_info() reached from nft_nat_eval(). The IPv6 path fills all 16 bytes and is not affected. Zero-initialise new_addr. Fixes: 3ff7ddb1353d ("netfilter: nft_nat: add netmap support") Signed-off-by: Theodor Arsenij Larionov Trichkine Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nft_nat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/nft_nat.c b/net/netfilter/nft_nat.c index e32cd9fbc7c2ec..cdbd800cac9698 100644 --- a/net/netfilter/nft_nat.c +++ b/net/netfilter/nft_nat.c @@ -64,8 +64,8 @@ static void nft_nat_setup_netmap(struct nf_nat_range2 *range, const struct nft_pktinfo *pkt, const struct nft_nat *priv) { + union nf_inet_addr new_addr = {}; struct sk_buff *skb = pkt->skb; - union nf_inet_addr new_addr; __be32 netmask; int i, len = 0; From 444e4c88c9c62a3d823069006563513fe7d5aa66 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Thu, 27 Aug 2026 12:32:56 +0200 Subject: [PATCH 0244/1417] netfilter: nf_tables: fix device name and prefix match in hook lookup Currently, a netdev chain or flowtable hooked to a device prefix can be unintentionally deleted by a control-plane request targeting an exact device name or even a shorter one due to the usage of min() to calculate the length to match. Fix this by making sure an exact device match never matches a prefix and that both the target and the candidate have the same length during delete operation. The add and update paths retain the existing overlap matching to prevent a single device from matching multiple hooks. Reported-by: Wei Fang Closes: https://lore.kernel.org/netfilter-devel/CANE+tVrDeNCHQVmsqkV2ozeBqyE3GtRDMhZgsg1bhw10yGNTRQ@mail.gmail.com/ Fixes: 6d07a289504a ("netfilter: nf_tables: Support wildcard netdev hook specs") Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_tables_api.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 31fbd5a28937f1..c0b754a2d45b0a 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -2440,11 +2440,14 @@ static struct nft_hook *nft_netdev_hook_alloc(struct net *net, } static struct nft_hook *nft_hook_list_find(struct list_head *hook_list, - const struct nft_hook *this) + const struct nft_hook *this, + bool strict) { struct nft_hook *hook; list_for_each_entry(hook, hook_list, list) { + if (strict && hook->ifnamelen != this->ifnamelen) + continue; if (!strncmp(hook->ifname, this->ifname, min(hook->ifnamelen, this->ifnamelen))) { if (hook->flags & NFT_HOOK_REMOVE) @@ -2486,7 +2489,7 @@ static int nf_tables_parse_netdev_hooks(struct net *net, err = PTR_ERR(hook); goto err_hook; } - if (nft_hook_list_find(hook_list, hook)) { + if (nft_hook_list_find(hook_list, hook, false)) { NL_SET_BAD_ATTR(extack, tmp); nft_netdev_hook_free(hook); err = -EEXIST; @@ -2943,7 +2946,7 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy, ops->hook = basechain->ops.hook; } - if (nft_hook_list_find(&basechain->hook_list, h)) { + if (nft_hook_list_find(&basechain->hook_list, h, false)) { list_del(&h->list); nft_netdev_hook_free(h); continue; @@ -2956,7 +2959,8 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy, !nft_trans_chain_update(trans)) continue; - if (nft_hook_list_find(&nft_trans_chain_hooks(trans), h)) { + if (nft_hook_list_find(&nft_trans_chain_hooks(trans), + h, false)) { nft_chain_release_hook(&hook); return -EEXIST; } @@ -3257,7 +3261,7 @@ static int nft_delchain_hook(struct nft_ctx *ctx, return err; list_for_each_entry(this, &chain_hook.list, list) { - hook = nft_hook_list_find(&basechain->hook_list, this); + hook = nft_hook_list_find(&basechain->hook_list, this, true); if (!hook) { err = -ENOENT; goto err_chain_del_hook; @@ -9053,7 +9057,7 @@ static int nft_register_flowtable_net_hooks(struct net *net, if (!nft_is_active_next(net, ft)) continue; - if (nft_hook_list_find(&ft->hook_list, hook)) { + if (nft_hook_list_find(&ft->hook_list, hook, false)) { err = -EEXIST; goto err_unregister_net_hooks; } @@ -9130,7 +9134,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh, return err; list_for_each_entry_safe(hook, next, &flowtable_hook.list, list) { - if (nft_hook_list_find(&flowtable->hook_list, hook)) { + if (nft_hook_list_find(&flowtable->hook_list, hook, false)) { list_del(&hook->list); nft_netdev_hook_free(hook); continue; @@ -9143,7 +9147,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh, !nft_trans_flowtable_update(trans)) continue; - if (nft_hook_list_find(&nft_trans_flowtable_hooks(trans), hook)) { + if (nft_hook_list_find(&nft_trans_flowtable_hooks(trans), hook, false)) { err = -EEXIST; goto err_flowtable_update_hook; } @@ -9363,7 +9367,7 @@ static int nft_delflowtable_hook(struct nft_ctx *ctx, return err; list_for_each_entry(this, &flowtable_hook.list, list) { - hook = nft_hook_list_find(&flowtable->hook_list, this); + hook = nft_hook_list_find(&flowtable->hook_list, this, true); if (!hook) { err = -ENOENT; goto err_flowtable_del_hook; From cbdd39ce42530a193c56beb206a3356cb6d01016 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 3 Sep 2026 01:28:56 +0200 Subject: [PATCH 0245/1417] netfilter: nf_nat: unregister and release hooks on error If nf_hook_entries_insert_raw() fails, the NAT hooks get never released, resulting in a memleak. Postpone setting nat_proto_net->nat_hook_ops when the hooks are registered to simplify the error path to decide whether the nat hooks need unwinding. Fixes: 1cd472bf036c ("netfilter: nf_nat: add nat hook register functions to nf_nat") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_nat_core.c | 46 ++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c index 8ac326e1eb5ba9..a4858c2b2d65a4 100644 --- a/net/netfilter/nf_nat_core.c +++ b/net/netfilter/nf_nat_core.c @@ -1224,31 +1224,45 @@ int nf_nat_register_fn(struct net *net, u8 pf, const struct nf_hook_ops *ops, } ret = nf_register_net_hooks(net, nat_ops, ops_count); - if (ret < 0) { - mutex_unlock(&nf_nat_proto_mutex); - for (i = 0; i < ops_count; i++) { - priv = nat_ops[i].priv; - kfree_rcu(priv, rcu_head); - } - kfree_rcu(nat_ops, rcu); - return ret; - } - - nat_proto_net->nat_hook_ops = nat_ops; + if (ret < 0) + goto err_free_hooks; + } else { + nat_ops = nat_proto_net->nat_hook_ops; } - nat_ops = nat_proto_net->nat_hook_ops; priv = nat_ops[hooknum].priv; if (WARN_ON_ONCE(!priv)) { - mutex_unlock(&nf_nat_proto_mutex); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto err_unregister_hooks; } ret = nf_hook_entries_insert_raw(&priv->entries, ops); - if (ret == 0) - nat_proto_net->users++; + if (ret) + goto err_unregister_hooks; + + if (!nat_proto_net->nat_hook_ops) + nat_proto_net->nat_hook_ops = nat_ops; + + nat_proto_net->users++; mutex_unlock(&nf_nat_proto_mutex); + + return 0; + +err_unregister_hooks: + if (nat_proto_net->nat_hook_ops) { + mutex_unlock(&nf_nat_proto_mutex); + return ret; + } + nf_unregister_net_hooks(net, nat_ops, ops_count); +err_free_hooks: + mutex_unlock(&nf_nat_proto_mutex); + for (i = 0; i < ops_count; i++) { + priv = nat_ops[i].priv; + kfree_rcu(priv, rcu_head); + } + kfree_rcu(nat_ops, rcu); + return ret; } From e75a9fa1d44bcbd66ea02e8781bcca6ea4076e0d Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 7 Sep 2026 21:04:05 +0200 Subject: [PATCH 0246/1417] netfilter: flowtable: hold reference on ct until flow is released nf_ct_put() releases the ct->ext area inmediately, the rcu typesafe semantics also allow to refer to the wrong conntrack from the flowtable datapath. Hold reference on ct until flow is released after rcu grace period. Add rcu_barrier() on module exit path, to ensure pending flow entries are release before module goes away. Fixes: 0ff90b6c2034 ("netfilter: nf_flow_offload: fix use-after-free and a resource leak") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_core.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c index 03241d4bfd5e82..934c6151f558b1 100644 --- a/net/netfilter/nf_flow_table_core.c +++ b/net/netfilter/nf_flow_table_core.c @@ -258,6 +258,14 @@ static void flow_offload_route_release(struct flow_offload *flow) nft_flow_dst_release(flow, FLOW_OFFLOAD_DIR_REPLY); } +static void flow_offload_free_rcu(struct rcu_head *rcu_head) +{ + struct flow_offload *flow = container_of(rcu_head, struct flow_offload, rcu_head); + + nf_ct_put(flow->ct); + kfree(flow); +} + void flow_offload_free(struct flow_offload *flow) { switch (flow->type) { @@ -267,8 +275,7 @@ void flow_offload_free(struct flow_offload *flow) default: break; } - nf_ct_put(flow->ct); - kfree_rcu(flow, rcu_head); + call_rcu(&flow->rcu_head, flow_offload_free_rcu); } EXPORT_SYMBOL_GPL(flow_offload_free); @@ -854,6 +861,7 @@ static int __init nf_flow_table_module_init(void) static void __exit nf_flow_table_module_exit(void) { + rcu_barrier(); nf_flow_table_offload_exit(); unregister_pernet_subsys(&nf_flow_table_net_ops); kmem_cache_destroy(flow_offload_cachep); From 49daa3d668b69a5454b5aba0078848a479f79f1c Mon Sep 17 00:00:00 2001 From: Shouping Wang Date: Thu, 10 Sep 2026 19:46:01 +0800 Subject: [PATCH 0247/1417] perf/arm-cmn: Fix wp_dev_sel2 setting for multi-DTM configurations When MXP_MULTIPLE_DTM_EN is TRUE, each DTM will monitor at most two device ports. In this case, {wp_dev_sel2, wp_dev_sel} will only use values 2'b00 and 2'b01 per DTM. Previously the setting allowed values beyond the supported range per DTM, which could cause each DTM to select invalid ports when MXP_MULTIPLE_DTM_EN is TRUE. Fix this by only setting CMN_DTM_WPn_CONFIG_WP_DEV_SEL2 when !multi_dtm. Fixes: 60d1504070c2 ("perf/arm-cmn: Support new IP features") Signed-off-by: Shouping Wang Reviewed-by: Robin Murphy Signed-off-by: Will Deacon --- drivers/perf/arm-cmn.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c index b162de3d9d169a..33ee2be9b38642 100644 --- a/drivers/perf/arm-cmn.c +++ b/drivers/perf/arm-cmn.c @@ -1582,13 +1582,14 @@ static void arm_cmn_claim_wp_idx(struct arm_cmn_dtm *dtm, static u32 arm_cmn_wp_config(struct perf_event *event, int wp_idx) { + struct arm_cmn *cmn = to_cmn(event->pmu); u32 config; u32 dev = CMN_EVENT_WP_DEV_SEL(event); u32 chn = CMN_EVENT_WP_CHN_SEL(event); u32 grp = CMN_EVENT_WP_GRP(event); u32 exc = CMN_EVENT_WP_EXCLUSIVE(event); u32 combine = CMN_EVENT_WP_COMBINE(event); - bool is_cmn600 = to_cmn(event->pmu)->part == PART_CMN600; + bool is_cmn600 = cmn->part == PART_CMN600; /* CMN-600 supports only primary and secondary matching groups */ if (is_cmn600) @@ -1596,8 +1597,11 @@ static u32 arm_cmn_wp_config(struct perf_event *event, int wp_idx) config = FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_DEV_SEL, dev) | FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_CHN_SEL, chn) | - FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_GRP, grp) | - FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_DEV_SEL2, dev >> 1); + FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_GRP, grp); + + if (!cmn->multi_dtm) + config |= FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_DEV_SEL2, dev >> 1); + if (exc) config |= is_cmn600 ? CMN600_WPn_CONFIG_WP_EXCLUSIVE : CMN_DTM_WPn_CONFIG_WP_EXCLUSIVE; From e4a6f57d22e079e23fafac51057fad534160b269 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 10 Sep 2026 06:53:27 -0700 Subject: [PATCH 0248/1417] arm64: hibernate: clone only the linear map that exists at runtime This is similar to commit 1537e55728ec2 ("arm64: trans_pgd: clone only the linear map that exists at runtime"), but in a different place. swsusp_arch_resume() clones the kernel linear map with trans_pgd_create_copy(..., PAGE_OFFSET, PAGE_END). PAGE_OFFSET comes from the compile-time VA_BITS, so a CONFIG_ARM64_VA_BITS_52 kernel booting on hardware without LPA2 -- vabits_actual is 48 and the fifth level is folded -- hands the walk a 3.9PB window while its linear map only spans the top 128TB. On a VA_BITS_52 4k kernel with CONFIG_KASAN_GENERIC in a 4GB VM, I see: swapper/0: page allocation failure: order:0, mode:0x920(GFP_ATOMIC|__GFP_ZERO) hibernate_page_alloc+0x10/0x1c swsusp_arch_resume+0x70/0x320 hibernation_restore+0xa4/0x138 software_resume+0x15c/0x270 PM: hibernation: Failed to load image, recovering. PM: hibernation: resume failed (-12) Fix it by copying the linear map that is the actual one, not the compiled one. Fixes: a6bbf5d4d9d1 ("arm64: mm: Add definitions to support 5 levels of paging") Signed-off-by: Breno Leitao Reviewed-by: Ard Biesheuvel Signed-off-by: Will Deacon --- arch/arm64/kernel/hibernate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kernel/hibernate.c b/arch/arm64/kernel/hibernate.c index 7bf1174277772e..424291c547f02a 100644 --- a/arch/arm64/kernel/hibernate.c +++ b/arch/arm64/kernel/hibernate.c @@ -423,8 +423,8 @@ int __nocfi swsusp_arch_resume(void) * Create a second copy of just the linear map, and use this when * restoring. */ - rc = trans_pgd_create_copy(&trans_info, &tmp_pg_dir, PAGE_OFFSET, - PAGE_END); + rc = trans_pgd_create_copy(&trans_info, &tmp_pg_dir, + _PAGE_OFFSET(vabits_actual), PAGE_END); if (rc) return rc; From 885bff055a0f251a51a0d4fd4f0a7b525582a3de Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:21 +0100 Subject: [PATCH 0249/1417] arm64: percpu: Fix this_cpu_write() casting The arm64 implementation of this_cpu_write() casts 'val' to unsigned long. This is necessary to handle cases where 'val' is a pointer type, and to avoid spurious compiler warnings for the (unreachable!) cases where the pointer type would be cast to a smaller integer type. Unfortunately, the cast is applied to 'val' rather than '(val)', which won't always generate the expected value when 'val' is an expression. For example, for this_cpu_write(pcp, zero - 1), where 'pcp' is a u64 and 'zero' is a u32: * 'zero' ===> (u32) 0x00000000 * 'zero - 1' ===> (u32) 0xffffffff * '(unsigned long)zero - 1' ===> (u64) 0xffffffffffffffff * '(unsigned long)(zero - 1)' ===> (u64) 0x00000000ffffffff Fix this by adding brackets around 'val'. Fixes: 959bf2fd03b5 ("arm64: percpu: Rewrite per-cpu ops to allow use of LSE atomics") Reported-by: David Laight Signed-off-by: Mark Rutland Reviewed-by: David Laight Reviewed-by: Jinjie Ruan Tested-by: Muhammad Usama Anjum Acked-by: Christopher Lameter (Ampere) Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: Catalin Marinas Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Cc: stable@vger.kernel.org Reviewed-by: Lorenzo Stoakes (ARM) Signed-off-by: Will Deacon --- arch/arm64/include/asm/percpu.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index b57b2bb0096774..63bbfd4944a374 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -179,13 +179,13 @@ PERCPU_RET_OP(add, add, ldadd) _pcp_protect_return(__percpu_read_64, pcp) #define this_cpu_write_1(pcp, val) \ - _pcp_protect(__percpu_write_8, pcp, (unsigned long)val) + _pcp_protect(__percpu_write_8, pcp, (unsigned long)(val)) #define this_cpu_write_2(pcp, val) \ - _pcp_protect(__percpu_write_16, pcp, (unsigned long)val) + _pcp_protect(__percpu_write_16, pcp, (unsigned long)(val)) #define this_cpu_write_4(pcp, val) \ - _pcp_protect(__percpu_write_32, pcp, (unsigned long)val) + _pcp_protect(__percpu_write_32, pcp, (unsigned long)(val)) #define this_cpu_write_8(pcp, val) \ - _pcp_protect(__percpu_write_64, pcp, (unsigned long)val) + _pcp_protect(__percpu_write_64, pcp, (unsigned long)(val)) #define this_cpu_add_1(pcp, val) \ _pcp_protect(__percpu_add_case_8, pcp, val) From 44274c657256b4911de82f8104e9e22f054cf742 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:22 +0100 Subject: [PATCH 0250/1417] arm64: percpu: Fix this_cpu_and() mask generation The arm64 implementation of this_cpu_and(pcp, val) is built in terms of ANDNOT operations, which requires the 'val' argument to be bitwise negated. The bitwise negation is not implemented correctly, with two bugs described below. (1) The bitwise negation is performed as '~val' rather than '~(val)'. This won't always generate the expected value when 'val' is an expression. For example, for this_cpu_and(pcp, 1 - 1): * 'val' is '1 - 1' ===> (int) 0x00000000 * '~val' is '~1 - 1' ===> (int) 0xfffffffd * '~(val)' is '~(1 - 1)' ===> (int) 0xffffffff ... and thus bit[1] of 'pcp' would be preserved unexpectedly by the ANDNOT operation. (2) The bitwise negation is performed on 'val' before it has been cast to (at least) the width of 'pcp'. This won't always generate the expected value for the upper bits. For example, for this_cpu_and(pcp, zero), where 'pcp' is a u64 and 'zero' is a u32: * 'zero' ===> (u32) 0x00000000 * '~(zero)' ===> (u32) 0xffffffff * '(u64)~(zero)' ===> (u64) 0x00000000ffffffff * '~((u64)(zero))' ===> (u64) 0xffffffffffffffff ... and thus bits[63:32] of 'pcp' would be preserved unexpectedly by the ANDNOT operation. Fix these issues by adding brackets around 'val', and by casting 'val' to an appropriately-sized type before bitwise negation. Fixes: 959bf2fd03b5 ("arm64: percpu: Rewrite per-cpu ops to allow use of LSE atomics") Signed-off-by: Mark Rutland Reviewed-by: Jinjie Ruan Tested-by: Muhammad Usama Anjum Acked-by: Christopher Lameter (Ampere) Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: Catalin Marinas Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Cc: stable@vger.kernel.org Signed-off-by: Will Deacon --- arch/arm64/include/asm/percpu.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index 63bbfd4944a374..31193bcf89a2b5 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -206,13 +206,13 @@ PERCPU_RET_OP(add, add, ldadd) _pcp_protect_return(__percpu_add_return_case_64, pcp, val) #define this_cpu_and_1(pcp, val) \ - _pcp_protect(__percpu_andnot_case_8, pcp, ~val) + _pcp_protect(__percpu_andnot_case_8, pcp, ~(u8)(val)) #define this_cpu_and_2(pcp, val) \ - _pcp_protect(__percpu_andnot_case_16, pcp, ~val) + _pcp_protect(__percpu_andnot_case_16, pcp, ~(u16)(val)) #define this_cpu_and_4(pcp, val) \ - _pcp_protect(__percpu_andnot_case_32, pcp, ~val) + _pcp_protect(__percpu_andnot_case_32, pcp, ~(u32)(val)) #define this_cpu_and_8(pcp, val) \ - _pcp_protect(__percpu_andnot_case_64, pcp, ~val) + _pcp_protect(__percpu_andnot_case_64, pcp, ~(u64)(val)) #define this_cpu_or_1(pcp, val) \ _pcp_protect(__percpu_or_case_8, pcp, val) From 8cf2093f5372952a9ebc805c418d45df7112cd14 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:23 +0100 Subject: [PATCH 0251/1417] arm64: percpu: Fix LSE operations on {8,16}-bit types The assembly for __percpu_##name##_case_##sz() and __percpu_##name##_return_case_##sz() doesn't use the 'sfx' macro argument to form the LSE instruction. Without 'sfx', a W register argument will imply a 32-bit memory location, and consequently {8,16}-bit ops will erroneously read and write 32 bits of memory when the LSE instruction is used. Fix this by appending 'sfx' to 'op_lse' to LSE instruction. It is not necessary (and not valid) to append 'sfx' to 'op_llsc', as 'op_llsc' is a register-register operation which does not access memory (and does not take a size suffix). Fixes: 959bf2fd03b5 ("arm64: percpu: Rewrite per-cpu ops to allow use of LSE atomics") Signed-off-by: Mark Rutland Reviewed-by: Jinjie Ruan Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: Catalin Marinas Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Cc: stable@vger.kernel.org Reviewed-by: Vladimir Murzin Signed-off-by: Will Deacon --- arch/arm64/include/asm/percpu.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index 31193bcf89a2b5..8cf4068ce1b562 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -77,7 +77,7 @@ __percpu_##name##_case_##sz(void *ptr, unsigned long val) \ " stxr" #sfx "\t%w[loop], %" #w "[tmp], %[ptr]\n" \ " cbnz %w[loop], 1b", \ /* LSE atomics */ \ - #op_lse "\t%" #w "[val], %" #w "[tmp], %[ptr]\n" \ + #op_lse #sfx "\t%" #w "[val], %" #w "[tmp], %[ptr]\n" \ __nops(3)) \ : [loop] "=&r" (loop), [tmp] "=&r" (tmp), \ [ptr] "+Q"(*(u##sz *)ptr) \ @@ -98,7 +98,7 @@ __percpu_##name##_return_case_##sz(void *ptr, unsigned long val) \ " stxr" #sfx "\t%w[loop], %" #w "[ret], %[ptr]\n" \ " cbnz %w[loop], 1b", \ /* LSE atomics */ \ - #op_lse "\t%" #w "[val], %" #w "[ret], %[ptr]\n" \ + #op_lse #sfx "\t%" #w "[val], %" #w "[ret], %[ptr]\n" \ #op_llsc "\t%" #w "[ret], %" #w "[ret], %" #w "[val]\n" \ __nops(2)) \ : [loop] "=&r" (loop), [ret] "=&r" (ret), \ From 3d1ba5cbfb622025690c218d8f20da92a9ecb383 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 9 Sep 2026 17:57:07 +0200 Subject: [PATCH 0252/1417] kselftest/arm64: Fix size of thread_data values for pthread_join() pthread_join() stores the thread's return value (a "void *", i.e. 8 bytes on 64 bit computers) into the address that is passed as second parameter. However, the entries of thread_data are only normal "int"s, i.e. only 4 bytes. The additional 4 bytes of the return value clobber whatever is adjacent on the stack, i.e. other members of the thread_data array (which will be re-written in the next iteration of the for-loop, so that nobody noticed this problem), or another other local variable on the stack for the last iteration. Use "intptr_t" to declare the thread_data array entries with the correct size. Fixes: 29f080881601c ("kselftest/arm64: check GCR_EL1 after context switch") Cc: stable@vger.kernel.org Signed-off-by: Thomas Huth Signed-off-by: Will Deacon --- tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c b/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c index d23f154d3288c5..5d9dc8bcfbf55b 100644 --- a/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c +++ b/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c @@ -69,7 +69,7 @@ void *execute_thread(void *x) int execute_test(pid_t pid) { pthread_t thread_id[MAX_THREADS]; - int thread_data[MAX_THREADS]; + intptr_t thread_data[MAX_THREADS]; for (int i = 0; i < MAX_THREADS; i++) pthread_create(&thread_id[i], NULL, From fdb9ebc7fb7788b8371fbef6c17dc5c8291e1429 Mon Sep 17 00:00:00 2001 From: Catalin Marinas Date: Mon, 7 Sep 2026 12:30:25 +0100 Subject: [PATCH 0253/1417] arm64: mte: Fix PTRACE_{PEEK,POKE}MTETAGS error documentation PTRACE_{PEEK,POKE}MTETAGS return -EIO rather than -EOPNOTSUPP (as documented) when no tags are copied from/to a mapping without PROT_MTE. This has been the behaviour since the interface was introduced, though the original intent was to distinguish between address not being accessible and mapped as untagged. Update the documentation to match the implementation (de-facto ABI). Since -EOPNOTSUPP was never returned, change the error assignment to -EIO as well to avoid confusion. Fixes: df9d7a22dd21 ("arm64: mte: Add Memory Tagging Extension documentation") Fixes: 18ddbaa02b7a ("arm64: mte: ptrace: Add PTRACE_{PEEK,POKE}MTETAGS support") Reported-by: Yury Khrustalev Cc: Will Deacon Cc: Mark Rutland Signed-off-by: Catalin Marinas Signed-off-by: Will Deacon --- Documentation/arch/arm64/memory-tagging-extension.rst | 5 ++--- arch/arm64/kernel/mte.c | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Documentation/arch/arm64/memory-tagging-extension.rst b/Documentation/arch/arm64/memory-tagging-extension.rst index e6fe428f0e2a4a..1d32fc5df1838a 100644 --- a/Documentation/arch/arm64/memory-tagging-extension.rst +++ b/Documentation/arch/arm64/memory-tagging-extension.rst @@ -208,11 +208,10 @@ will use the corresponding aligned address. tracer's space cannot be accessed or does not have valid tags. - ``-EPERM`` - the specified process cannot be traced. - ``-EIO`` - the tracee's address range cannot be accessed (e.g. invalid - address) and no tags copied. ``iov_len`` not updated. + address) or does not have valid tags (not mapped with the ``PROT_MTE`` + flag) and no tags copied. ``iov_len`` not updated. - ``-EFAULT`` - fault on accessing the tracer's memory (``struct iovec`` or ``iov_base`` buffer) and no tags copied. ``iov_len`` not updated. -- ``-EOPNOTSUPP`` - the tracee's address does not have valid tags (never - mapped with the ``PROT_MTE`` flag). ``iov_len`` not updated. **Note**: There are no transient errors for the requests above, so user programs should not retry in case of a non-zero system call return. diff --git a/arch/arm64/kernel/mte.c b/arch/arm64/kernel/mte.c index 1a9aad6ef22a04..31f5c6b0510e72 100644 --- a/arch/arm64/kernel/mte.c +++ b/arch/arm64/kernel/mte.c @@ -476,7 +476,7 @@ static int __access_remote_tags(struct mm_struct *mm, unsigned long addr, * was never mapped with PROT_MTE. */ if (!(vma->vm_flags & VM_MTE)) { - err = -EOPNOTSUPP; + err = -EIO; put_page(page); break; } From 9aa237cf66495b2426ddde8532e9b08a0ed83aaa Mon Sep 17 00:00:00 2001 From: Wei Jie LAW <98lawweijie@gmail.com> Date: Wed, 9 Sep 2026 09:15:13 +0800 Subject: [PATCH 0254/1417] HID: wacom: fix OOB read in wacom_wac_pen_serial_enforce() The 'wacom_wac_pen_serial_enforce()' function may calculate and pass an invalid offset to hid_field_extract(), resulting in memory reads at incorrect addresses -- possibly beyond the end of the report. If a field in the HID descriptor lists more usages than its Report Count actually reserves space for, the function's inner 'j' will walk past the end of the field: for (i = 0; i < report->maxfield; i++) { for (j = 0; j < report->field[i]->maxusage; j++) { ... value = hid_field_extract(hdev, raw_data + 1, offset + j * size, size); A descriptor listing 12288 usages against Report Count 1 has the loop extract the usage at index 12287 from bit offset 98296 -- about 12 KB past a 2-byte received report. The value is stored in wacom_wac->serial[0] and can reach userspace as an MSC_SERIAL event, making this an information disclosure. Clamp the loop to field->report_count, the number of value slots the report holds. Value slots past the last declared usage are still scanned; they reuse that usage (HID 1.11, 6.2.2.8). Verified on v6.12.105 with a UHID reproducer: a 2-byte report from such a descriptor trips KASAN before the patch and not after it. Fixes: 83417206427b ("HID: wacom: Queue events with missing type/serial data for later processing") Suggested-by: Jason Gerecke Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Assisted-by: GLM:glm-5.3 Signed-off-by: Wei Jie Law <98lawweijie@gmail.com> Reviewed-by: Jason Gerecke Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 0eafa483b7f757..40770affdbdeee 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -113,8 +113,9 @@ static int wacom_wac_pen_serial_enforce(struct hid_device *hdev, /* Queue events which have invalid tool type or serial number */ for (i = 0; i < report->maxfield; i++) { - for (j = 0; j < report->field[i]->maxusage; j++) { - struct hid_field *field = report->field[i]; + struct hid_field *field = report->field[i]; + + for (j = 0; j < field->report_count; j++) { struct hid_usage *usage = &field->usage[j]; unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid); unsigned int offset; From aaaea79efba5a27cb9e0a5a628046d829d3f2cbb Mon Sep 17 00:00:00 2001 From: Lovekesh Solanki Date: Wed, 5 Aug 2026 01:50:31 +0530 Subject: [PATCH 0255/1417] HID: multitouch: Add report ID mismatch quirk for ASUS ROG Z13 Folio Commit e716edafedad ("HID: multitouch: Check to ensure report responses match the request") introduced validating GET_FEATURE responses return the requested report ID. ASUS ROG Z13 Flow (2025) GZ302EA touchpad (USB 0b05:1a30) returns a different report ID for Win8 feature request. Before this check, the response was still processed and allowed device to switch into its full Touchpad Precision mode. After the validation, the response is discarded before hid_report_raw_event() processes it and device remains in fallback mode and no longer exposes ABS_MT_SLOT, ABS_MT_TOOL_TYPE or the multi-finger BTN_TOOL_* capabilities for palm rejection. Add a device quirk to allow the known firmware behavior for this device while preserving report ID validation for all other devices. The device previously matched the generic MT_CLS_WIN_8 entry, so base the new class on MT_CLS_WIN_8 to keep it on the same quirk set as before the regression. MT_QUIRK_CONFIDENCE must be set explicitly: it is normally enabled by the class name check in mt_touch_input_mapping(), which only matches the MT_CLS_WIN_8* names, and it is what makes ABS_MT_TOOL_TYPE available for touchpads. Fixes: e716edafedad ("HID: multitouch: Check to ensure report responses match the request") Signed-off-by: Lovekesh Solanki Reported-by: mayhemandcoffee Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221774 Tested-by: mayhemandcoffee Link: https://bugzilla.kernel.org/show_bug.cgi?id=221774 Signed-off-by: Jiri Kosina --- drivers/hid/hid-multitouch.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 451c7324e6a061..ab4ac261fe765a 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -79,6 +79,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) #define MT_QUIRK_YOGABOOK9I BIT(24) #define MT_QUIRK_KEEP_LATENCY_ON_CLOSE BIT(25) +#define MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH BIT(26) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -235,6 +236,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_APPLE_TOUCHBAR 0x0114 #define MT_CLS_YOGABOOK9I 0x0115 #define MT_CLS_EGALAX_P80H84 0x0116 +#define MT_CLS_ASUS_ROG_Z13_FOLIO 0x0117 #define MT_CLS_SIS 0x0457 #define MT_DEFAULT_MAXCONTACT 10 @@ -405,6 +407,16 @@ static const struct mt_class mt_classes[] = { .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE | MT_QUIRK_ASUS_CUSTOM_UP }, + { .name = MT_CLS_ASUS_ROG_Z13_FOLIO, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | + MT_QUIRK_WIN8_PTP_BUTTONS | + MT_QUIRK_CONFIDENCE | + MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH, + .export_all_inputs = true }, { .name = MT_CLS_VTL, .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE | @@ -507,6 +519,7 @@ static const struct attribute_group mt_attribute_group = { static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) { + struct mt_device *td = hid_get_drvdata(hdev); int ret; u32 size = hid_report_len(report); u8 *buf; @@ -528,8 +541,14 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) dev_warn(&hdev->dev, "failed to fetch feature %d\n", report->id); } else { - /* The report ID in the request and the response should match */ - if (report->id != buf[0]) { + /* + * The report ID in the request and the response should match. + * Some firmware (e.g. the ASUS ROG Z13 Folio + * touchpad) returns a mismatched ID on this specific fetch; + * tolerate it only for devices explicitly flagged as such. + */ + if (report->id != buf[0] && + !(td->mtclass.quirks & MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH)) { hid_err(hdev, "Returned feature report did not match the request\n"); goto free; } @@ -2715,6 +2734,12 @@ static const struct hid_device_id mt_devices[] = { HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288) }, + /* Asus ROG Flow Z13 (2025) GZ302EA keyboard-cover touchpad */ + { .driver_data = MT_CLS_ASUS_ROG_Z13_FOLIO, + HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, + USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO) }, + /* Generic MT device */ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH, HID_ANY_ID, HID_ANY_ID) }, From 9eb1a393c89a79c4210230d23e7d88d239c61d7b Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Wed, 26 Aug 2026 08:46:58 -0300 Subject: [PATCH 0256/1417] drm: Fix drm_pending_vblank_event leak in error path for out_fence_ptr When an out_fence_ptr is provided but DRM_MODE_PAGE_FLIP_EVENT is not set, a drm_pending_vblank_event will be allocated. If later, there is an allocation failure or another failure at setup_out_fence(), that event will not have base.fence set and it will not be released at complete_signaling(). Release the event and set crtc_state->event to NULL just like in the DRM_MODE_PAGE_FLIP_EVENT case when there is a failure at drm_event_reserve_init(). That is, prepare_signaling() releases the event and there is nothing to be done at complete_signaling(). Use drm_event_cancel_free() as that will also undo drm_event_reserve_init() in case it has been called. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260727-drm_crtc_atomic_commit_leak-v1-1-23d9948a9d7c@igalia.com?part=1 Fixes: 92c715fca907 ("drm/atomic: Fix double free in drm_atomic_state_default_clear") Signed-off-by: Thadeu Lima de Souza Cascardo Reviewed-by: Melissa Wen Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260826-drm_pending_vblank_event_leak-v4-1-f8de8b996b9d@igalia.com --- drivers/gpu/drm/drm_atomic_uapi.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index 5ea593b3a98ec5..68d59e17ffc2d5 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -1462,10 +1462,12 @@ static int prepare_signaling(struct drm_device *dev, struct dma_fence *fence; struct drm_out_fence_state *f; + ret = -ENOMEM; + f = krealloc(*fence_state, sizeof(**fence_state) * (*num_fences + 1), GFP_KERNEL); if (!f) - return -ENOMEM; + goto err_free_event; memset(&f[*num_fences], 0, sizeof(*f)); @@ -1474,12 +1476,12 @@ static int prepare_signaling(struct drm_device *dev, fence = drm_crtc_create_fence(crtc); if (!fence) - return -ENOMEM; + goto err_free_event; ret = setup_out_fence(&f[(*num_fences)++], fence); if (ret) { dma_fence_put(fence); - return ret; + goto err_free_event; } crtc_state->event->base.fence = fence; @@ -1535,6 +1537,11 @@ static int prepare_signaling(struct drm_device *dev, } return 0; + +err_free_event: + drm_event_cancel_free(dev, &crtc_state->event->base); + crtc_state->event = NULL; + return ret; } static void complete_signaling(struct drm_device *dev, From a1a5ad37e50ceb192c07ac7e2d7143638cd4d110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Onier?= Date: Wed, 12 Aug 2026 13:13:59 -0400 Subject: [PATCH 0257/1417] HID: winwing: fix use-after-free in force feedback teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit winwing_init_ff() passes the driver's private data, allocated with devm_kzalloc() in winwing_probe(), as the effect context to input_ff_create_memless(). The memoryless force-feedback core takes ownership of that pointer and frees it with kfree() from input_ff_destroy() (ml_ff_destroy()) when the input device is destroyed. Freeing a devm-managed allocation with kfree() is an invalid free, and the same object is then released again by devres when the HID device is torn down, a double free. As the allocation also embeds the LED class devices, their timers and work item live on freed memory and the slab gets corrupted. This triggers on unbind, rmmod, hot-unplug and on system suspend, where the firmware cache walks the now-corrupt devres list. KASAN reports: BUG: KASAN: invalid-free in input_ff_destroy Allocated by task N: winwing_probe Pass NULL as the memless context instead and fetch the driver data from the input device in winwing_play_effect(): the HID core already stores the hid_device as the input device's drvdata. The force-feedback core then owns nothing that it must not free. Fixes: 42d020b54edc ("HID: winwing: Enable rumble effects") Signed-off-by: René Onier Signed-off-by: Jiri Kosina --- drivers/hid/hid-winwing.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c index 9cd25a77999e65..19b92c2c6579a2 100644 --- a/drivers/hid/hid-winwing.c +++ b/drivers/hid/hid-winwing.c @@ -315,7 +315,8 @@ static void winwing_haptic_rumble_cb(struct work_struct *work) static int winwing_play_effect(struct input_dev *dev, void *context, struct ff_effect *effect) { - struct winwing_drv_data *data = (struct winwing_drv_data *) context; + struct hid_device *hdev = input_get_drvdata(dev); + struct winwing_drv_data *data = hid_get_drvdata(hdev); if (effect->type != FF_RUMBLE) return 0; @@ -342,7 +343,12 @@ static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput) input_set_capability(hidinput->input, EV_FF, FF_RUMBLE); - return input_ff_create_memless(hidinput->input, data, + /* + * input_ff_create_memless() takes ownership of the context pointer + * and frees it on teardown; do not hand it the devm-managed drvdata. + * winwing_play_effect() fetches it from the input device instead. + */ + return input_ff_create_memless(hidinput->input, NULL, winwing_play_effect); } From aa9dde93e05a837645fdfd577eea71f0733f0694 Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Fri, 14 Aug 2026 15:06:20 +0800 Subject: [PATCH 0258/1417] HID: alps: unregister DualPoint Stick input device on remove alps_input_configured() allocates a second input device ("DualPoint Stick") with input_allocate_device() and registers it, but the alps_driver struct has no .remove handler and input2 is not tracked in hdev->inputs. The default remove path (hid_hw_stop -> hidinput_disconnect) only iterates hdev->inputs, so input2 is never unregistered and leaks on every device removal. Add a .remove handler that stops the device first (preventing URB callbacks from touching input2 during teardown) and then unregisters input2. Fixes: 2562756dde55 ("HID: add Alps I2C HID Touchpad-Stick support") Cc: stable@vger.kernel.org Signed-off-by: Chen Changcheng Signed-off-by: Jiri Kosina --- drivers/hid/hid-alps.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 67179e3fe39b7d..370635f5b70435 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -823,6 +823,24 @@ static int alps_probe(struct hid_device *hdev, const struct hid_device_id *id) return 0; } +static void alps_remove(struct hid_device *hdev) +{ + struct alps_dev *data = hid_get_drvdata(hdev); + + /* + * input2 ("DualPoint Stick") is allocated separately and is not + * tracked in hdev->inputs, so the default remove path + * (hid_hw_stop -> hidinput_disconnect) does not unregister it. + * + * Stop the device first so that no URB callback can touch input2 + * while it is being unregistered, then drop it explicitly. + */ + hid_hw_stop(hdev); + + if (data->input2) + input_unregister_device(data->input2); +} + static const struct hid_device_id alps_id[] = { { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) }, @@ -845,6 +863,7 @@ static struct hid_driver alps_driver = { .input_configured = alps_input_configured, .resume = pm_ptr(alps_post_resume), .reset_resume = pm_ptr(alps_post_reset), + .remove = alps_remove, }; module_hid_driver(alps_driver); From d3aba3442798ce4a4c8ce3104d7b286d61e605f9 Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Fri, 14 Aug 2026 15:06:21 +0800 Subject: [PATCH 0259/1417] HID: alps: fix use-after-free on input2 registration failure alps_input_configured() stores data->input2 before calling input_register_device(). If registration fails, input_free_device() frees the input device but data->input2 still points to the freed memory. alps_input_configured() calls hid_hw_open() before allocating input2, so URBs are already active and raw_event can fire during the failure window. A U1_SP_ABSOLUTE_REPORT_ID report arriving then causes u1_raw_event() to dereference the freed data->input2 -> use-after-free. Fix by only storing input2 into drvdata after successful registration and adding a NULL guard in the raw_event path. Fixes: 2562756dde55 ("HID: add Alps I2C HID Touchpad-Stick support") Cc: stable@vger.kernel.org Signed-off-by: Chen Changcheng Signed-off-by: Jiri Kosina --- drivers/hid/hid-alps.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 370635f5b70435..0556cb5645ebbf 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -407,6 +407,8 @@ static int u1_raw_event(struct alps_dev *hdata, u8 *data, int size) return 1; case U1_SP_ABSOLUTE_REPORT_ID: + if (!hdata->input2) + return 0; sp_x = get_unaligned_le16(data+2); sp_y = get_unaligned_le16(data+4); @@ -738,7 +740,6 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) goto exit; } - data->input2 = input2; input2->phys = input->phys; input2->name = "DualPoint Stick"; input2->id.bustype = BUS_I2C; @@ -762,11 +763,12 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) __set_bit(INPUT_PROP_POINTER, input2->propbit); __set_bit(INPUT_PROP_POINTING_STICK, input2->propbit); - if (input_register_device(data->input2)) { + if (input_register_device(input2)) { input_free_device(input2); ret = -ENOENT; goto exit; } + data->input2 = input2; } exit: From 31fe2cb51133f00e1138c617fef8ee808a37714d Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Thu, 27 Aug 2026 13:29:49 +0200 Subject: [PATCH 0260/1417] HID: fix semantic patch and improve its performance Replace "expression" with "identifier" in the declaration of hdev. This is necessary because hdev is used as the name of a function parameter. Move the two uses of @p2 to the relevant function names. Convert <... ...>, meaning that the contained pattern is optional, to use ..., when any, and exists. This requires that the function contain calls to hid_hw_start, etc, which reduces the set of files that are considered for matching against this pattern. Reported-by: Ricardo Ribalda Signed-off-by: Julia Lawall Signed-off-by: Jiri Kosina --- scripts/coccinelle/hid/ff_race.cocci | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/coccinelle/hid/ff_race.cocci b/scripts/coccinelle/hid/ff_race.cocci index 479f5d1e31848e..e861de00cb10d0 100644 --- a/scripts/coccinelle/hid/ff_race.cocci +++ b/scripts/coccinelle/hid/ff_race.cocci @@ -7,18 +7,19 @@ virtual report -@r@ +@r exists@ identifier probe_fn; -expression hdev, flags; +identifier hdev; +expression flags; position p1, p2; @@ probe_fn(struct hid_device *hdev, ...) { - <... + ... when any hid_hw_start@p1(hdev, flags) ... - \(input_ff_create\|input_ff_create_memless\)@p2(...) - ...> + \(input_ff_create@p2\|input_ff_create_memless@p2\)(...) + ... when any } @script:python depends on report@ From d5ea0d226e8f0801d78702142a124d78c317d822 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 2 Sep 2026 22:09:26 +0800 Subject: [PATCH 0261/1417] mmc: sh_mmcif: initialize IRQ-thread mutex before requesting interrupt The threaded IRQ handler can run before devm_request_threaded_irq() returns, but thread_lock was initialized afterwards. Initialize it before requesting either interrupt. Fixes: 8047310ee984 ("mmc: sh_mmcif: fix a race, causing an Oops on SMP") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Signed-off-by: Ulf Hansson --- drivers/mmc/host/sh_mmcif.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 7706d01b149e62..8bd9cdedafa4dc 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -1461,6 +1461,7 @@ static int sh_mmcif_probe(struct platform_device *pdev) host->pd = pdev; spin_lock_init(&host->lock); + mutex_init(&host->thread_lock); mmc->ops = &sh_mmcif_ops; sh_mmcif_init_ocr(host); @@ -1515,8 +1516,6 @@ static int sh_mmcif_probe(struct platform_device *pdev) goto err_clk; } - mutex_init(&host->thread_lock); - ret = mmc_add_host(mmc); if (ret < 0) goto err_clk; From fce551616dfa4523c15fe50c27aa3baf8afda955 Mon Sep 17 00:00:00 2001 From: Andres Diaz Date: Mon, 31 Aug 2026 23:01:47 -0600 Subject: [PATCH 0262/1417] HID: logitech-hidpp: Add support for G502 X Lightspeed USB mouse The G502 X Lightspeed enumerates as 046d:c098 when connected with its cable. Add the id so the driver handles the wired device. Signed-off-by: Andres Diaz Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 1504de32b1c84b..493763a12518ea 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4924,6 +4924,8 @@ static const struct hid_device_id hidpp_devices[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) }, { /* Logitech G502 X Plus Wireless Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC095) }, + { /* Logitech G502 X Lightspeed Wireless Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC098) }, { /* Logitech G703 Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) }, { /* Logitech G703 Hero Gaming Mouse over USB */ From 7e749a7972829a53fd1e41568cf2018c180627d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20H=C3=A5kansson?= Date: Tue, 1 Sep 2026 22:30:44 +0200 Subject: [PATCH 0263/1417] HID: steelseries: Add support for Arctis 7 (2018) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The headset reports connection and battery status on HID interface 5. When the device is disconnected, ignore incoming battery reports as they will incorrectly report battery level 0. Clamp overreported battery values to 100% and add USB ID 1038:12ad to the driver's device tables. Signed-off-by: Erik Håkansson Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 3 +- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-quirks.c | 1 + drivers/hid/hid-steelseries-arctis.c | 44 ++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index a81bf51cbcf103..c43e824428f02e 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1210,7 +1210,8 @@ config HID_STEELSERIES depends on USB_HID help Support for Steelseries SRW-S1 steering wheel, and the Steelseries - Arctis 1 Wireless for XBox headset. + Arctis 1 Wireless for XBox, Arctis 7 (2018), Arctis 9, and Arctis + Nova headsets. config HID_SUNPLUS tristate "Sunplus wireless desktop" diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 8b4f4b02aec016..874ed8e4930249 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1398,6 +1398,7 @@ #define USB_VENDOR_ID_STEELSERIES 0x1038 #define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X 0x12b6 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_2018 0x12ad #define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X 0x2253 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7 0x2202 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index c8c6b29fc04d08..9ac90b7efcb14f 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -752,6 +752,7 @@ static const struct hid_device_id hid_have_special_driver[] = { #if IS_ENABLED(CONFIG_HID_STEELSERIES) { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_2018) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7) }, diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c index 23fb0cebd72ad1..fa33fbd9488989 100644 --- a/drivers/hid/hid-steelseries-arctis.c +++ b/drivers/hid/hid-steelseries-arctis.c @@ -101,6 +101,19 @@ static int steelseries_arctis_1_request_status(struct hid_device *hdev) return steelseries_send_output_report(hdev, data, sizeof(data)); } +static int steelseries_arctis_7_2018_request_status(struct hid_device *hdev) +{ + const u8 connection[] = { 0x06, 0x14 }; + const u8 battery[] = { 0x06, 0x18 }; + int ret; + + ret = steelseries_send_output_report(hdev, connection, sizeof(connection)); + if (ret) + return ret; + + return steelseries_send_output_report(hdev, battery, sizeof(battery)); +} + static int steelseries_arctis_9_request_status(struct hid_device *hdev) { const u8 data[] = { 0x00, 0x20 }; @@ -152,6 +165,27 @@ static void steelseries_arctis_1_parse_status(struct steelseries_device *sd, sd->battery_capacity = data[3]; } +static void steelseries_arctis_7_2018_parse_status(struct steelseries_device *sd, + u8 *data, int size) +{ + if (size < 3 || data[0] != 0x06) + return; + + switch (data[1]) { + case 0x14: + /* 0x03 means that the headset is connected to the transmitter. */ + sd->headset_connected = data[2] == 0x03; + break; + case 0x18: + if (!sd->headset_connected) + break; + + /* The Arctis 7 sometimes overreports battery. Cap to 100. */ + sd->battery_capacity = steelseries_map_capacity(data[2], 0, 100); + break; + } +} + static void steelseries_arctis_9_parse_status(struct steelseries_device *sd, u8 *data, int size) { @@ -232,6 +266,13 @@ static const struct steelseries_device_info arctis_1_info = { .parse_status = steelseries_arctis_1_parse_status, }; +static const struct steelseries_device_info arctis_7_2018_info = { + .sync_interface = 5, + .capabilities = SS_CAP_BATTERY, + .request_status = steelseries_arctis_7_2018_request_status, + .parse_status = steelseries_arctis_7_2018_parse_status, +}; + static const struct steelseries_device_info arctis_9_info = { .sync_interface = 0, .capabilities = SS_CAP_BATTERY, @@ -653,6 +694,9 @@ static const struct hid_device_id steelseries_arctis_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X), .driver_data = (unsigned long)&arctis_1_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_7_2018), + .driver_data = (unsigned long)&arctis_7_2018_info }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9), .driver_data = (unsigned long)&arctis_9_info }, From d76994443eed0af297cb82bd038ae9d76327ef90 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Wed, 2 Sep 2026 12:45:49 +0300 Subject: [PATCH 0264/1417] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() Extend critical section in roccat_connect() to ensure that partially initialized 'struct roccat_device' is never exposed in 'devices' list, and do the same in roccat_disconnect() to avoid racy 'devices' access against roccat_release(). Signed-off-by: Dmitry Antipov Signed-off-by: Jiri Kosina --- drivers/hid/hid-roccat.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c index 4f15eb95103968..5deb6da8d4f746 100644 --- a/drivers/hid/hid-roccat.c +++ b/drivers/hid/hid-roccat.c @@ -344,8 +344,6 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report return temp; } - mutex_unlock(&devices_lock); - init_waitqueue_head(&device->wait); INIT_LIST_HEAD(&device->readers); mutex_init(&device->readers_lock); @@ -356,6 +354,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report device->cbuf_end = 0; device->report_size = report_size; + mutex_unlock(&devices_lock); return minor; } EXPORT_SYMBOL_GPL(roccat_connect); @@ -369,15 +368,12 @@ void roccat_disconnect(int minor) mutex_lock(&devices_lock); device = devices[minor]; - mutex_unlock(&devices_lock); device->exist = 0; /* TODO exist maybe not needed */ device_destroy(device->dev->class, MKDEV(roccat_major, minor)); - mutex_lock(&devices_lock); devices[minor] = NULL; - mutex_unlock(&devices_lock); if (device->open) { hid_hw_close(device->hid); @@ -385,6 +381,8 @@ void roccat_disconnect(int minor) } else { roccat_free_device(device); } + + mutex_unlock(&devices_lock); } EXPORT_SYMBOL_GPL(roccat_disconnect); From 39e8e085710a2d36d9ceade11739fb7b87f5aeb0 Mon Sep 17 00:00:00 2001 From: Oleg Keri Date: Thu, 10 Sep 2026 08:30:50 +0200 Subject: [PATCH 0265/1417] HID: i2c-hid: add reset quirk for Lenovo Yoga Slim 7x Gen 11 keyboard The ITE controller behind the keyboard of the Lenovo Yoga Slim 7x Gen 11 (048d:83db) carries out a reset but never raises the interrupt that acknowledges it. i2c_hid_finish_hwreset() therefore waits out its full one second timeout and logs "device did not ack reset within 1000 ms" on every probe and every resume, before the keyboard comes up regardless. Set I2C_HID_QUIRK_NO_IRQ_AFTER_RESET for it, as is already done for several other ITE parts, so the reset is followed by a fixed 100 ms sleep instead. Signed-off-by: Oleg Keri Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/i2c-hid/i2c-hid-core.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 874ed8e4930249..06a1309589e35e 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -765,6 +765,7 @@ #define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a #define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396 #define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987 +#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD 0x83db #define USB_DEVICE_ID_ITE8595 0x8595 #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 0ff07fdab442a3..ad8d9f329404a4 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -126,6 +126,8 @@ static const struct i2c_hid_quirks { I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { USB_VENDOR_ID_ALPS_JP, HID_ANY_ID, From abd24922c2a9797d6184be0561dd85e7bcdfd091 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Fri, 4 Sep 2026 10:58:00 +0000 Subject: [PATCH 0266/1417] HID: hid-oxp: use cancel_delayed_work_sync() in remove oxp_hid_remove() uses cancel_delayed_work() for all three delayed work items. cancel_delayed_work() only dequeues a pending work item without waiting for a currently executing callback to finish. If any of the work callbacks (oxp_rgb_queue_fn, oxp_btn_queue_fn, oxp_mcu_init_fn) is running at the time of removal, the callback continues executing concurrently with hid_hw_close() and hid_hw_stop(), accessing the HID device after it has been closed and stopped. Use cancel_delayed_work_sync() instead to ensure that any in-progress work callback completes before device teardown proceeds. Fixes: 84910c459d65 ("HID: hid-oxp: Add OneXPlayer configuration driver") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani Reviewed-by: Derek J. Clark Link: https://lore.kernel.org/r/20260804-oxp-fix-v2-1-b2d56e4c8a2c@cherr.cc Link: https://lore.kernel.org/r/20260804-oxp-fix-v1-1-51a4fe787167@cherr.cc Signed-off-by: Jiri Kosina --- drivers/hid/hid-oxp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c index d2ded6b08ce9e2..1e691ebc1199e6 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -1552,9 +1552,9 @@ static int oxp_hid_probe(struct hid_device *hdev, static void oxp_hid_remove(struct hid_device *hdev) { - cancel_delayed_work(&drvdata.oxp_rgb_queue); - cancel_delayed_work(&drvdata.oxp_btn_queue); - cancel_delayed_work(&drvdata.oxp_mcu_init); + cancel_delayed_work_sync(&drvdata.oxp_rgb_queue); + cancel_delayed_work_sync(&drvdata.oxp_btn_queue); + cancel_delayed_work_sync(&drvdata.oxp_mcu_init); hid_hw_close(hdev); hid_hw_stop(hdev); } From 58d97b45f3f43af0afd9c74e8480d22d36fd3f72 Mon Sep 17 00:00:00 2001 From: Youth Cao Date: Fri, 4 Sep 2026 00:14:44 +0800 Subject: [PATCH 0267/1417] HID: i2c-hid: Add i2c-hid-quirk-bad-input-size quirk for 0911:5288 device I have recently acquired a cheap Apollo Lake-based laptop that uses a Hynitron CST128-A touchpad controller. While booting from a Debian LiveCD, the kernel log is flooded with the following error (though the touchpad works well): i2c_hid_acpi i2c-ALPS0001:00: i2c_hid_get_input: incomplete report (27/42405) The CST128-A was identified via ACPI as ALPS0001:00, and the I2C HID device ID (0911:5288) was shared with the Hantick 5288. Add the I2C_HID_QUIRK_BAD_INPUT_SIZE quirk option to the existing Hantick 5288 quirk entry to suppress the kernel log flood. Signed-off-by: Youth Cao Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index ad8d9f329404a4..a61fd1de520301 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -123,7 +123,7 @@ static const struct i2c_hid_quirks { __u32 quirks; } i2c_hid_quirks[] = { { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288, - I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET | I2C_HID_QUIRK_BAD_INPUT_SIZE }, { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD, From 1d00442cc4699accfaa2317fb18259ddd7556f9a Mon Sep 17 00:00:00 2001 From: Stuart Hayhurst Date: Tue, 18 Aug 2026 14:00:53 +0100 Subject: [PATCH 0268/1417] HID: corsair-void: Fix firmware event packet description The size was incorrectly stated as 4 bytes since the ID was missed out. Add the ID in and correct the indices for the firmware versions. Signed-off-by: Stuart Hayhurst Signed-off-by: Jiri Kosina --- drivers/hid/hid-corsair-void.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-corsair-void.c b/drivers/hid/hid-corsair-void.c index 071a663a6c2686..7be8c974d11e92 100644 --- a/drivers/hid/hid-corsair-void.c +++ b/drivers/hid/hid-corsair-void.c @@ -51,20 +51,23 @@ /* Receiver report information: (ID 102) */ /* -------------------------------------------------------------------------- */ /* - * When queried, the recevier responds with 4 bytes to describe the firmware - * The first 2 bytes are for the receiver, the second 2 are the headset + * When queried, the receiver responds with 5 bytes to describe the firmware + * The first byte is the ID, then 2 bytes for the receiver, then 2 for the headset * The headset firmware version will be 0 if no headset is connected * - * 0: Recevier firmware major version + * 0: Report ID + * 102 for the firmware packet + * + * 1: Receiver firmware major version * Major version of the receiver's firmware * - * 1: Recevier firmware minor version + * 2: Receiver firmware minor version * Minor version of the receiver's firmware * - * 2: Headset firmware major version + * 3: Headset firmware major version * Major version of the headset's firmware * - * 3: Headset firmware minor version + * 4: Headset firmware minor version * Minor version of the headset's firmware */ /* -------------------------------------------------------------------------- */ From f3c2b266b8fd7cf816d36168d19f67e716f2c080 Mon Sep 17 00:00:00 2001 From: Berke Durak Date: Mon, 7 Sep 2026 11:47:19 -0700 Subject: [PATCH 0269/1417] HID: elecom: Add support for ELECOM M-XT4DRBK (018E) The 2025 revision of the left-handed EX-G wireless trackball (M-XT4DRBK-G) reports USB ID 056e:018e instead of 056e:00fd. Its report descriptor declares an 8-bit button field (Report Count 8) but only five usages (Usage Maximum 5), so the sixth (Fn) button ends up as a duplicate of button 5 and is unusable. The report descriptor has the same layout as the M-XT3DRBK 018C (button usage maximum at offset 16, button report count at 22, button report size at 24, padding report size at 30), so reuse that fixup. Rename the existing M_XT4DRBK define to M_XT4DRBK_00FD to match the convention used for the other EX-G revisions. Tested on a Raspberry Pi 5 with the same fixup in an out-of-tree module on 6.12 (6.12.96+rpt-rpi-2712); all six buttons are reported after the fix, and they work (tested with xev as well). Report descriptor as sent by the device, pre-fix (056e:018e, 215 bytes): 05 01 09 02 A1 01 09 01 A1 00 85 01 05 09 19 01 29 05 15 00 25 01 95 08 75 01 81 02 95 01 75 00 81 01 05 01 09 30 09 31 16 00 80 26 FF 7F 75 10 95 02 81 06 C0 A1 00 05 01 09 38 15 81 25 7F 75 08 95 01 81 06 C0 A1 00 05 0C 0A 38 02 95 01 75 08 15 81 25 7F 81 06 C0 C0 06 01 FF 09 00 A1 01 85 02 09 00 15 00 26 FF 00 75 08 95 07 81 02 C0 05 0C 09 01 A1 01 85 05 15 00 26 3C 02 19 00 2A 3C 02 75 10 95 01 81 00 C0 05 01 09 80 A1 01 85 03 19 81 29 83 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 C0 06 BC FF 09 88 A1 01 85 04 95 01 75 08 15 00 26 FF 00 19 00 2A FF 00 81 00 C0 06 02 FF 09 02 A1 01 85 06 09 02 15 00 26 FF 00 75 08 95 07 B1 02 C0 Assisted-by: LLM Signed-off-by: Berke Durak Signed-off-by: Jiri Kosina --- drivers/hid/hid-elecom.c | 6 ++++-- drivers/hid/hid-ids.h | 3 ++- drivers/hid/hid-quirks.c | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c index 37d88ce57f6718..d21ead40bb076b 100644 --- a/drivers/hid/hid-elecom.c +++ b/drivers/hid/hid-elecom.c @@ -79,7 +79,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB: case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F: case USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC: - case USB_DEVICE_ID_ELECOM_M_XT4DRBK: + case USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD: /* * Report descriptor format: * 12: button bit count @@ -104,6 +104,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8); break; case USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C: + case USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E: /* * Report descriptor format: * 22: button bit count @@ -148,7 +149,8 @@ static const struct hid_device_id elecom_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 06a1309589e35e..9a473f72c8cb1a 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -477,7 +477,8 @@ #define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c -#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd +#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD 0x00fd +#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E 0x018e #define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe #define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff #define USB_DEVICE_ID_ELECOM_M_DT2DRBK 0x018d diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 9ac90b7efcb14f..8c4b7580a6ee23 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -432,7 +432,8 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, From 8e2a4b458ad25e13422bb059758c30a6562aa9cf Mon Sep 17 00:00:00 2001 From: Oscar Priego Verdugo Date: Mon, 17 Aug 2026 06:05:46 -0600 Subject: [PATCH 0270/1417] HID: elecom: fix bus type for M-XGL20DLBK The M-XGL20DLBK is matched as a USB device by hid-elecom, but its entry in hid_have_special_driver[] uses HID_BLUETOOTH_DEVICE. This prevents the special-driver quirk entry from matching the USB device handled by hid-elecom. Use HID_USB_DEVICE there as well. Fixes: 55633e681afb ("HID: elecom: add support for EX-G M-XGL20DLBK wireless mouse") Signed-off-by: Oscar Priego Verdugo Signed-off-by: Jiri Kosina --- drivers/hid/hid-quirks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 8c4b7580a6ee23..96a36c5ba04f79 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -426,7 +426,7 @@ static const struct hid_device_id hid_have_special_driver[] = { #endif #if IS_ENABLED(CONFIG_HID_ELECOM) { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, - { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, From a5c41fa7f925fda2db394329fa0b26243fa63a81 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:48 -0400 Subject: [PATCH 0271/1417] Revert "nouveau/gsp: fix suspend/resume regression on r570 firmware" This reverts commit 8302d0afeaec0bc57d951dd085e0cffe997d4d18. It turns out this looked like the right fix on some systems, but it's not - as this causes runtime PM to actually fail on many a laptop. Fixes: 8302d0afeaec ("nouveau/gsp: fix suspend/resume regression on r570 firmware") Cc: # v6.19+ Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-2-lyude@redhat.com (cherry picked from commit 94097122bfd701976bc1a62ccd434c13f3f67cde) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c | 2 +- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 2 +- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 8 ++++---- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c index f128330f30d7ba..40bf83ea33ac05 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c @@ -208,7 +208,7 @@ r535_fbsr_resume(struct nvkm_gsp *gsp) } static int -r535_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) +r535_fbsr_suspend(struct nvkm_gsp *gsp) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvkm_device *device = subdev->device; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index f544afa12b6bb5..4a3b771ded2551 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1749,7 +1749,7 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) sr->sysmemAddrOfSuspendResumeData = gsp->sr.radix3.lvl0.addr; sr->sizeOfSuspendResumeData = len; - ret = rm->api->fbsr->suspend(gsp, suspend == NVKM_RUNTIME_SUSPEND); + ret = rm->api->fbsr->suspend(gsp); if (ret) { nvkm_gsp_mem_dtor(&gsp->sr.meta); nvkm_gsp_radix3_dtor(gsp, &gsp->sr.radix3); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index 8ef8b4f6558830..2945d5b4e57070 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -62,7 +62,7 @@ r570_fbsr_resume(struct nvkm_gsp *gsp) } static int -r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtime) +r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) { NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS *ctrl; struct nvkm_gsp_object memlist; @@ -81,7 +81,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtim ctrl->hClient = gsp->internal.client.object.handle; ctrl->hSysMem = memlist.handle; ctrl->sysmemAddrOfSuspendResumeData = gsp->sr.meta.addr; - ctrl->bEnteringGcoffState = runtime ? 1 : 0; + ctrl->bEnteringGcoffState = 1; ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); if (ret) @@ -92,7 +92,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtim } static int -r570_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) +r570_fbsr_suspend(struct nvkm_gsp *gsp) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvkm_device *device = subdev->device; @@ -133,7 +133,7 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) return ret; /* Initialise FBSR on RM. */ - ret = r570_fbsr_init(gsp, &gsp->sr.fbsr, size, runtime); + ret = r570_fbsr_init(gsp, &gsp->sr.fbsr, size); if (ret) { nvkm_gsp_sg_free(device, &gsp->sr.fbsr); return ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h index fcd0221dcea1ea..e9ac47d86b69af 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h @@ -79,7 +79,7 @@ struct nvkm_rm_api { } *device; const struct nvkm_rm_api_fbsr { - int (*suspend)(struct nvkm_gsp *, bool runtime); + int (*suspend)(struct nvkm_gsp *); void (*resume)(struct nvkm_gsp *); } *fbsr; From 12f6eff11ccf9cad3b2dfcdd94184fdb9ffface2 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:49 -0400 Subject: [PATCH 0272/1417] drm/nouveau/gsp/r570: Set GcOff = 0 in fbsr Previously, it looked as if we were able to fix suspend/resume on some desktops by setting Gcoff based on whether or not we were entering runtime PM. This was a mistake though - the only time suspend/resume would end up actually working was if Gcoff = 0. It seems like it's likely the main reason for this is the FBSR GcOff argument actually controls GSP's behavior with regards to which buffers it decides to save across suspend/resume. When GcOff = 1, RM reserved regions are saved unless they are marked as LOST_ON_SUSPEND, and RM channel-context and kernel-client buffers are also saved -including- when they are LOST_ON_SUSPEND. This means with GcOff = 1, we end up having GSP save and restore buffers that actually need to be reinitialized on resume - causing the failures we're setting. Thanks to John Hubbard from Nvidia for providing some background on what these options do in the GSP firmware do! Signed-off-by: Lyude Paul Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: # v6.16+ Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-3-lyude@redhat.com (cherry picked from commit c7abe771e013848970421e5ca29c6b2f05c31965) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index 2945d5b4e57070..af5aa5065c3ddf 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -81,7 +81,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) ctrl->hClient = gsp->internal.client.object.handle; ctrl->hSysMem = memlist.handle; ctrl->sysmemAddrOfSuspendResumeData = gsp->sr.meta.addr; - ctrl->bEnteringGcoffState = 1; + ctrl->bEnteringGcoffState = 0; ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); if (ret) From 24fbd6d4bcf3363ef13ebe0d36dea93f30396c6d Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:50 -0400 Subject: [PATCH 0273/1417] drm/nouveau/gsp/r570: Enable S/R Display workaround in GSP There's two flags that we've never been setting when asking GSP to suspend the GPU, which OpenRM does set: GPU_STATE_FLAGS_PRESERVING GPU_STATE_FLAGS_PM_TRANSITION These flags aren't -supposed- to do much in GSP, they're mostly used by OpenRM itself for state tracking. The only thing they do from GSP's side is control whether or not a single display related workaround is applied during suspend. But as it turns out, that single workaround is actually quite crucial for getting runtime PM working with nouveau - and without it set we end up seeing a lot more failures with runtime PM resume. So, let's start setting it. Signed-off-by: Lyude Paul Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: # v6.16+ Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-4-lyude@redhat.com (cherry picked from commit ca57629b3eb912c77bc4357178a2130ea6c2d6df) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c | 3 ++- .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c index 1488771c63fc47..b45781cd0dfdce 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c @@ -207,7 +207,8 @@ r570_gsp_set_rmargs(struct nvkm_gsp *gsp, bool resume) args->srInitArguments.bInPMTransition = 0; } else { args->srInitArguments.oldLevel = NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3; - args->srInitArguments.flags = 0; + args->srInitArguments.flags = + GPU_STATE_FLAGS_PRESERVING | GPU_STATE_FLAGS_PM_TRANSITION; args->srInitArguments.bInPMTransition = 1; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h index b6075021e74f59..c458569af9d720 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h @@ -523,6 +523,14 @@ typedef struct #define NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3 (0x00000003U) +#define GPU_STATE_FLAGS_PRESERVING BIT(0) // GPU state is preserved +#define GPU_STATE_FLAGS_VGA_TRANSITION BIT(1) // To be used with GPU_STATE_FLAGS_PRESERVING. +#define GPU_STATE_FLAGS_PM_TRANSITION BIT(2) // To be used with GPU_STATE_FLAGS_PRESERVING. +#define GPU_STATE_FLAGS_PM_SUSPEND BIT(3) +#define GPU_STATE_FLAGS_PM_HIBERNATE BIT(4) +#define GPU_STATE_FLAGS_GC6_TRANSITION BIT(5) // To be used with GPU_STATE_FLAGS_PRESERVING. +#define GPU_STATE_FLAGS_FAST_UNLOAD BIT(6) // Used during windows restart, skips stateDestroy steps + typedef struct { // Magic for verification by secure ucode From bbb9293c9bb792f3f16c842f223b8c97bdbaf227 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:51 -0400 Subject: [PATCH 0274/1417] drm/nouveau/gsp: Increase delay for magic sleep in r535_gsp_fini() As it turns out, Turing isn't the only architecture that needs this. On this Dell Precision 7780 with an AD103 GPU, along with pretty much every other laptop I tested, runtime PM is still somewhat unreliable. At first glance it seems as if it's fixed, but lowering the autosuspend delay to 500ms and then doing a stress test of suspend/resume cycles on the GPU ends up causing everything to start timing out. After quite a lot of digging, I eventually landed back on this magic timeout in r535_gsp_fini(). As it turns out, increasing the timeout ends up fixing the runtime PM issues as far as I can tell, even during intense stress testing. Unfortunately after spending quite a bit of time trying to dig through OpenRM to figure out what this magic sleep is actually doing, I've also come up short with any reasonable explanation. In lieu of that, I'm going to include the observations I did make while trying to figure this out in hopes someone eventually does figure this out: * The magic sleep has to occur after fbsr is initialized. Performing it at any time before that doesn't appear to work. * In situations where runtime PM starts getting flaky, some rather interesting visual effects end up happening on occasion before the GPU fully falls over. In particular, squares that look like the result of an incomplete blitting operation to a tiled buffer end up showing up on applications like vkcube. Interestingly enough, they remain in precisely the same place between runtime PM cycles until the GPU falls over - even when restarting vkcube multiple times, and even when vkcube is actively updating the screen. Even more interestingly, they're not limited to a specific framebuffer - you can see the squares changing as the cube rotates around. We cannot however, say that this is likely to be a incomplete fbsr operation. The magic sleep happens before fbsr is actually saved (which happens on the GSP unload), so it's something else. * During a short bit of testing with a desktop that I have, the magic sleep seemed to make no difference to whether or not suspend/resume works. It seems to generally work almost always. So we can assume this is likely exclusive to runtime PM, not S3. As well, here's a list of the things I tried before settling on the magic sleep: * Hooking up NV2080_CTRL_CMD_INTERNAL_GCX_ENTRY_PREREQUISITE and then blocking runtime PM until OpenRM signals that GC6/GCOFF is ready appears to make no difference. * Hooking up some (maybe not all, unsure about that part) bits of comptag saving including: * Fetching static memsys information from GSP * Adding the size of the comptag storage to the fbsr data * Adding a GA103+ workaround for disabling raw compression mode during fbsr (it doesn't seem like it applies for any systems I tried it on anyhow) * Setting bPreserveVideoMemoryAllocations=1 in GspSystemInfo So, until we can figure this out properly - just sleep for longer. Signed-off-by: Lyude Paul Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: # v6.16+ Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-5-lyude@redhat.com (cherry picked from commit 09b47186a4164f3aaa3591313f80794443117342) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index 4a3b771ded2551..94925f1590ea48 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1761,8 +1761,12 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) * TODO: Debug the GSP firmware / RPC handling to find out why * without this Turing (but none of the other architectures) * ends up resetting all channels after resume. + * Additionally, runtime suspend on other architectures quickly + * becomes unreliable without this sleep. If you're experiencing + * issues with runtime suspend, try bumping this delay up and + * sending a patch if it fixes your GPU. */ - msleep(50); + msleep(200); } ret = r535_gsp_rpc_unloading_guest_driver(gsp, suspend); From 23c240d9509e15f72e4112fc95f0160ab32ec430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Thu, 10 Sep 2026 18:37:43 +0000 Subject: [PATCH 0275/1417] smb: client: validate absolute native symlink targets before NT fixups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With symlinkroot unset, an absolute target is copied without conversion to an NT drive path. Later code still assumes an NT prefix is present when modifying the target and calculating the print name length. For "/ab", this causes two failures: sym[5] and path[5] are written past their allocations, and plen -= 2 * poff subtracts an assumed 8-byte prefix from a 6-byte UTF-16 target, wrapping u16 plen to 65534. That underflow causes another overflow: memcpy() copies 65534 bytes into a 24-byte buffer. A user with write access to a mounted share can trigger these bugs with default settings. Validate the NT drive prefix, including an ASCII drive letter, before accessing fixed offsets or subtracting the prefix length. Fixes: 3363da82e02f ("smb: client: fix native SMB symlink traversal") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/reparse.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 8a1b9e8be5ba71..9e31fce7e0a528 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -3,6 +3,7 @@ * Copyright (c) 2024 Paulo Alcantara */ +#include #include #include #include @@ -159,15 +160,24 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode, convert_delimiter(sym, sep); /* - * For absolute NT symlinks it is required to pass also leading - * backslash and to not mangle NT object prefix "\\??\\" and not to - * mangle colon in drive letter. But cifs_convert_path_to_utf16() - * removes leading backslash and replaces '?' and ':'. So temporary - * mask these characters in NT object prefix by '_' and then change - * them back. + * Absolute NT symlinks must retain the leading backslash, "\\??\\" + * prefix and drive-letter colon. cifs_convert_path_to_utf16() strips + * the leading backslash and maps '?' and ':', so temporarily mask + * these characters with '_' and restore them after conversion. + * + * When symlinkroot is unset, sym comes directly from the caller. + * Validate the complete "\\??\\X:" prefix before using fixed offsets + * or subtracting the NT prefix length below. Require an ASCII drive + * letter so the prefix occupies six characters in UTF-16 too. */ - if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') + if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') { + if (!strstarts(sym, "\\??\\") || !isascii(sym[4]) || + !isalpha(sym[4]) || sym[5] != ':') { + rc = -EINVAL; + goto out; + } sym[0] = sym[1] = sym[2] = sym[5] = '_'; + } /* * On a POSIX paths mount the symlink target is stored verbatim, so From 3163cd7253432f262c2fa22edb51474d7afc76a0 Mon Sep 17 00:00:00 2001 From: George Emmanuel Thomas Date: Sat, 15 Aug 2026 22:13:35 +0530 Subject: [PATCH 0276/1417] drm/msm: remove stale perf counter XML TODO The adreno_perfcntrs macro already takes the XML file stem as its second argument, allowing each perf counter JSON file to select the appropriate register XML. The a2xx and a5xx entries already pass their respective XML file stems. Remove the stale TODO comment. Signed-off-by: George Emmanuel Thomas Patchwork: https://patchwork.freedesktop.org/patch/746745/ Message-ID: <20260815164335.158958-1-georgeemmanuelthomas@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index d0c3a4c6703be2..0b8f2cafed19dd 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -177,7 +177,6 @@ quiet_cmd_headergen = GENHDR $@ cmd_headergen = mkdir -p $(obj)/generated && $(PYTHON3) $(src)/registers/gen_header.py \ $(headergen-opts) --rnn $(src)/registers --xml $< c-defines > $@ -# TODO how to do this for a2xx/a5xx which have different .xml arg? quiet_cmd_headergen_json = GENHDRJSN $@ cmd_headergen_json = mkdir -p $(obj)/generated && $(PYTHON3) $(src)/registers/gen_header.py \ $(headergen-opts) --rnn $(src)/registers --xml $(filter %.xml,$^) perfcntrs --json $< > $@ From ed9ad3d4183053d4a8c43960a3d23ea8db71c30d Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Wed, 9 Sep 2026 15:07:37 +0200 Subject: [PATCH 0277/1417] drm/msm/a6xx: Add CX AO Counter registers used for a750 GPUs The a750 uses the CX AO Counters instead of the GMU_ALWAYS_ON_COUNTER register on A6xx and other A7xx GPUs. Signed-off-by: Neil Armstrong Patchwork: https://patchwork.freedesktop.org/patch/752234/ Message-ID: <20260909-topic-sm8650-gmu-a750-timestamp-reg-v2-1-091d74958951@linaro.org> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml b/drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml index 33404eb18fd023..b3082738c99d30 100644 --- a/drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml +++ b/drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml @@ -141,6 +141,8 @@ xsi:schemaLocation="https://gitlab.freedesktop.org/freedreno/ rules-fd.xsd"> + + From a2b65837980a513cf24825bc4a50b0430e3391e7 Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Wed, 9 Sep 2026 15:07:38 +0200 Subject: [PATCH 0278/1417] drm/msm/a6xx: Use CX AO Counter register for timestamp on a750 GPUs The a750 uses the GMU CX AO Counters instead of the GMU_ALWAYS_ON_COUNTER register on A6xx and other A7xx GPUs, use it when running a A750 GPU. The GMU_ALWAYS_ON_COUNTER at offset 0x1f888 doesn't seem to exist on the SM8650 A750 GMU and returns 0, but the CX AO counter at offset 0x1f880 returns some proper timestamp data. Signed-off-by: Neil Armstrong Patchwork: https://patchwork.freedesktop.org/patch/752235/ Message-ID: <20260909-topic-sm8650-gmu-a750-timestamp-reg-v2-2-091d74958951@linaro.org> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index f9de9329dee39a..081e79ea465285 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -23,9 +23,15 @@ static u64 a6xx_gmu_get_timestamp(struct msm_gpu *gpu) u64 count_hi, count_lo, temp; do { - count_hi = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_H); - count_lo = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_L); - temp = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_H); + if (adreno_is_a750_family(adreno_gpu)) { + count_hi = gmu_read(&a6xx_gpu->gmu, REG_A7XX_GMU_CX_AO_COUNTER_H); + count_lo = gmu_read(&a6xx_gpu->gmu, REG_A7XX_GMU_CX_AO_COUNTER_L); + temp = gmu_read(&a6xx_gpu->gmu, REG_A7XX_GMU_CX_AO_COUNTER_H); + } else { + count_hi = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_H); + count_lo = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_L); + temp = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_H); + } } while (unlikely(count_hi != temp)); return (count_hi << 32) | count_lo; From 79a71cc2568f4b5d42284da2aa26f3b4f47ce01b Mon Sep 17 00:00:00 2001 From: Jim Mattson Date: Wed, 2 Sep 2026 11:47:11 -0700 Subject: [PATCH 0279/1417] KVM: x86/pmu: Move Intel PMU global MSRs to intel_is_valid_msr() Commit c85cdc1cc1ea ("KVM: x86/pmu: Move handling PERF_GLOBAL_CTRL and friends to common x86") moved the existence check for the following Intel PMU MSRs to kvm_pmu_is_valid_msr(): - MSR_CORE_PERF_GLOBAL_STATUS - MSR_CORE_PERF_GLOBAL_CTRL - MSR_CORE_PERF_GLOBAL_OVF_CTRL That commit deemed these MSRs valid whenever pmu->version > 1. It intended to share the check with AMD PerfMonV2 because both vendor implementations require version 2 or greater for global PMU controls. However, as noted in the commit message, AMD uses different MSR indices for its global PMU registers. Commit 4a2771895ca6 ("KVM: x86/svm/pmu: Add AMD PerfMonV2 support") subsequently added AMD PerfMonV2 support and set pmu->version = 2. Because kvm_pmu_is_valid_msr() validated the Intel MSRs whenever pmu->version > 1, KVM incorrectly permitted AMD guests with PerfMonV2 to access these Intel MSRs without a #GP. Move the validation of these Intel MSRs to intel_is_valid_msr() and remove the common switch statement from kvm_pmu_is_valid_msr(). AMD already validates its own global PMU MSRs in amd_is_valid_msr(). Fixes: 4a2771895ca6 ("KVM: x86/svm/pmu: Add AMD PerfMonV2 support") Signed-off-by: Jim Mattson Reviewed-by: Like Xu Reviewed-by: Sandipan Das Link: https://patch.msgid.link/20260902184711.138538-1-jmattson@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/pmu.c | 8 -------- arch/x86/kvm/vmx/pmu_intel.c | 3 +++ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/arch/x86/kvm/pmu.c b/arch/x86/kvm/pmu.c index a7d60c8785cd46..d2fd47ee5ec800 100644 --- a/arch/x86/kvm/pmu.c +++ b/arch/x86/kvm/pmu.c @@ -823,14 +823,6 @@ void kvm_pmu_deliver_pmi(struct kvm_vcpu *vcpu) bool kvm_pmu_is_valid_msr(struct kvm_vcpu *vcpu, u32 msr) { - switch (msr) { - case MSR_CORE_PERF_GLOBAL_STATUS: - case MSR_CORE_PERF_GLOBAL_CTRL: - case MSR_CORE_PERF_GLOBAL_OVF_CTRL: - return kvm_pmu_has_perf_global_ctrl(vcpu_to_pmu(vcpu)); - default: - break; - } return kvm_pmu_call(msr_idx_to_pmc)(vcpu, msr) || kvm_pmu_call(is_valid_msr)(vcpu, msr); } diff --git a/arch/x86/kvm/vmx/pmu_intel.c b/arch/x86/kvm/vmx/pmu_intel.c index bfa8612fb4508f..70a8c481613569 100644 --- a/arch/x86/kvm/vmx/pmu_intel.c +++ b/arch/x86/kvm/vmx/pmu_intel.c @@ -187,6 +187,9 @@ static bool intel_is_valid_msr(struct kvm_vcpu *vcpu, u32 msr) int ret; switch (msr) { + case MSR_CORE_PERF_GLOBAL_STATUS: + case MSR_CORE_PERF_GLOBAL_CTRL: + case MSR_CORE_PERF_GLOBAL_OVF_CTRL: case MSR_CORE_PERF_FIXED_CTR_CTRL: return kvm_pmu_has_perf_global_ctrl(pmu); case MSR_IA32_PEBS_ENABLE: From f13368e0acffd6d6289629705cb821d921104725 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Wed, 26 Aug 2026 09:14:30 -0700 Subject: [PATCH 0280/1417] KVM: selftests: Use __GLIBC__, not _GNU_SOURCE, to detect actual glibc Use __GLIBC__ in the hardware disable test to detect when selftests are being built/linked against glibc and thus pthread_attr_setaffinity_np() is (hopefully) available. As pointed out by Sashiko and Hisam, _GNU_SOURCE is effectively a "request" macro to enable functionality, whereas __GLIBC__ is an announcement of support and selftests' idiomatic way of guarding code that's specific to glibc. Fixes: 496779b54943 ("KVM: selftests: Pre-set threads affinity in hardware disable test when possible") Reported-by: Sashiko Bot Closes: https://lore.kernel.org/all/20260731201140.5AF0C1F00AC4@smtp.kernel.org Suggested-by: Hisam Mehboob Link: https://patch.msgid.link/20260826161430.714316-1-seanjc@google.com Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/hardware_disable_test.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/kvm/hardware_disable_test.c b/tools/testing/selftests/kvm/hardware_disable_test.c index 43a36ef3ead83e..1c20892d67827e 100644 --- a/tools/testing/selftests/kvm/hardware_disable_test.c +++ b/tools/testing/selftests/kvm/hardware_disable_test.c @@ -37,7 +37,7 @@ static void *run_vcpu(void *arg) struct kvm_vcpu *vcpu = arg; struct kvm_run *run = vcpu->run; -#ifndef _GNU_SOURCE +#ifndef __GLIBC__ kvm_sched_setaffinity(0, sizeof(cpu_set_t), &threads_cpu_set); #endif @@ -51,7 +51,7 @@ static void *sleeping_thread(void *arg) { int fd; -#ifndef _GNU_SOURCE +#ifndef __GLIBC__ kvm_sched_setaffinity(0, sizeof(cpu_set_t), &threads_cpu_set); #endif @@ -71,7 +71,7 @@ static void run_test(u32 run) u32 i, j; TEST_ASSERT_EQ(pthread_attr_init(&attr), 0); -#ifdef _GNU_SOURCE +#ifdef __GLIBC__ TEST_ASSERT_EQ(pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &threads_cpu_set), 0); #endif From 8cd280282d1239bca68f8c3632ef1ac8556a396b Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 6 Aug 2026 14:46:18 -0700 Subject: [PATCH 0281/1417] KVM: Never clear KVM_REQ_VM_DEAD from a vCPU's requests Use kvm_test_request() instead of kvm_check_request() when querying KVM_REQ_VM_DEAD, i.e. don't clear KVM_REQ_VM_DEAD, as the entire purpose of KVM_REQ_VM_DEAD is to prevent the vCPU from enterring the guest ever again, even if userspace insists on redoing KVM_RUN. Ensuring KVM_REQ_VM_DEAD is never cleared will allow relaxing KVM's rule that ioctls can't be invoked on dead VMs, to only disallow ioctls if the VM is bugged, i.e. if KVM hit a KVM_BUG_ON(). Opportunistically add compile-time assertions to guard against clearing KVM_REQ_VM_DEAD through the standard APIs. Reviewed-by: Kai Huang Acked-by: Marc Zyngier Link: https://patch.msgid.link/20260806214618.82180-1-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/arm64/kvm/arm.c | 2 +- arch/x86/kvm/mmu/mmu.c | 2 +- arch/x86/kvm/vmx/tdx.c | 2 +- arch/x86/kvm/x86.c | 2 +- include/linux/kvm_host.h | 9 +++++++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 8b080804bc90b2..62c81d6ae8ee7c 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -1129,7 +1129,7 @@ static int kvm_vcpu_suspend(struct kvm_vcpu *vcpu) static int check_vcpu_requests(struct kvm_vcpu *vcpu) { if (kvm_request_pending(vcpu)) { - if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) return -EIO; if (kvm_check_request(KVM_REQ_SLEEP, vcpu)) diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index 064ecc33b92670..8e62476e477b8b 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -5058,7 +5058,7 @@ static int kvm_tdp_page_prefault(struct kvm_vcpu *vcpu, gpa_t gpa, if (signal_pending(current)) return -EINTR; - if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) return -EIO; cond_resched(); diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index b272c20586a744..6c842e9191a500 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -1995,7 +1995,7 @@ static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu) if (kvm_vcpu_has_events(vcpu) || signal_pending(current)) break; - if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) { + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) { ret = -EIO; break; } diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 79468ddfe47367..a137dc6dd8c673 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -8060,7 +8060,7 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) bool req_immediate_exit = false; if (kvm_request_pending(vcpu)) { - if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) { + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) { r = -EIO; goto out; } diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 03bfc92864b6e6..cf7fe835c4ad29 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -2324,13 +2324,18 @@ static inline bool kvm_test_request(int req, struct kvm_vcpu *vcpu) return test_bit(req & KVM_REQUEST_MASK, (void *)&vcpu->requests); } -static inline void kvm_clear_request(int req, struct kvm_vcpu *vcpu) +static __always_inline void kvm_clear_request(int req, struct kvm_vcpu *vcpu) { + BUILD_BUG_ON(req == KVM_REQ_VM_DEAD); + clear_bit(req & KVM_REQUEST_MASK, (void *)&vcpu->requests); } -static inline bool kvm_check_request(int req, struct kvm_vcpu *vcpu) +static __always_inline bool kvm_check_request(int req, struct kvm_vcpu *vcpu) { + /* Once a VM is dead, it needs to stay dead. */ + BUILD_BUG_ON(req == KVM_REQ_VM_DEAD); + if (kvm_test_request(req, vcpu)) { kvm_clear_request(req, vcpu); From ebc5660132ddd244b57f03ed324922013a3d7363 Mon Sep 17 00:00:00 2001 From: April Cardenas Date: Thu, 10 Sep 2026 00:16:49 -0500 Subject: [PATCH 0282/1417] smb/client: send lease break ACKs thru correct session for multiuser mounts Currently, when cifs_oplock_break handles a break request from the server it searches for the appropriate tlink to handle the request but incorrectly uses the current fsuid as the search key, eventually causing read errors for users with multiuser mounts on NetApp. Fix this by using the tlink from the cfile struct instead to respond through the correct session. As breaks are handled in a worker thread, the current fsuid isn't guaranteed to match the session that the break is intended for. This means that cifs_sb_tlink may search the rbtree using the wrong fsuid, and return a tlink with an incorrect session than the lease break was intended for. As a result, the breaks may be ACKed through an incorrect session. While it seems that Samba/Windows Servers 2016-2025 ignore this as long as the lease key is correct, we ran into a case where if you're using NetApp ONTAP or Azure NetApp Files they will reject the ACK and return `STATUS_LOCK_NOT_GRANTED` errors on any future read requests a user may initiate through their still held open file handle, and the server will eventually close the file. In the dmesg logs, the user may see errors like these: CIFS: Status code returned 0xc0000128 STATUS_FILE_CLOSED CIFS: VFS: Send error in read = -9 With a multiuser mount using NetApp, this issue is really easy for users to hit on a wide variety of kernel versions by attempting to copy a file from the share to the local machine through GNOME Files/Nautilus. This copy will always result in Nautilus throwing a `Bad File Descriptor` error to the user and fail. With this fix, you can copy files through Nautilus without issue. >From looking at the traces, it seems that glib will open the file first, and call listxattr before actually attempting to copy the file data. The listxattr call always triggers a break, causing the copy to fail. The proposed fix returns to the way the client grabbed the tlink before commit e8f5f849ffce2 ("cifs: fix potential oops in cifs_oplock_break"). The bulk of that commit (checking for list empty) remains untouched, and I think the change to using cifs_sb_tlink was intended to avoid a NULL/ERR deference on the tlink as well as update the reference count. I believe this fix should preserve those safety properties, but of course I'd appreciate any corrections here. Fixes: e8f5f849ffce2 ("cifs: fix potential oops in cifs_oplock_break") Cc: stable@vger.kernel.org Signed-off-by: April Cardenas Reviewed-by: Namjae Jeon Reviewed-by: Bharath S M Signed-off-by: Paulo Alcantara --- fs/smb/client/file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 1aa4844f8b8abb..0d428517f4542f 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -3354,8 +3354,8 @@ void cifs_oplock_break(struct work_struct *work) wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS, TASK_UNINTERRUPTIBLE); - tlink = cifs_sb_tlink(cifs_sb); - if (IS_ERR(tlink)) { + tlink = cifs_get_tlink(cfile->tlink); + if (IS_ERR_OR_NULL(tlink)) { /* drop the reference taken when the break was queued */ _cifsFileInfo_put(cfile, false /* do not wait for ourself */, false); goto out; From 77be3641f3e3a56e42a5ed889372ef395a933f3c Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Thu, 3 Sep 2026 16:51:35 +0500 Subject: [PATCH 0283/1417] debugfs: don't warn about uninitialized debugfs for an error parent Since commit c3a280ff728a ("debugfs: warn if file creation failed due to uninitialized debugfs") every boot with CONFIG_REF_TRACKER=y and CONFIG_DEBUG_FS=y prints two errors before the root filesystem is mounted: debugfs: Unable to create file 'net_refcnt@(____ptrval____)', debugfs is not initialized yet debugfs: Unable to create file 'net_notrefcnt@(____ptrval____)', debugfs is not initialized yet Nothing is actually wrong. Both files show up under /sys/kernel/debug/ref_tracker/ once the system is up. The kernel is reporting an error for a condition the caller has already accounted for. net_ns_init() runs directly from start_kernel(), before any initcall, and calls ref_tracker_dir_init() for init_net's two trackers. debugfs_init() is a core_initcall, so debugfs cannot possibly be up at that point. That is by design: ref_tracker_dir_debugfs() is documented as safe to call again later, and net/core/net_namespace.c has a late_initcall() that re-registers both directories once debugfs exists. ref_tracker also states that intent to debugfs. ref_tracker_debug_dir is initialised to ERR_PTR(-ENOENT) and only gets a real dentry in a late_initcall, so the early call hands debugfs_create_file() a parent that is already an error. debugfs_start_creating() honours that and returns the parent error, but only after the new pr_err() has fired. Move the IS_ERR(parent) check above the debugfs_initialized() test. A caller passing an error parent is propagating an earlier failure, which is the pattern debugfs documents and which the warning is not aimed at. A caller passing a valid or NULL parent too early - the case the warning was added for - still gets it. One behaviour change: an early caller with an error parent now gets PTR_ERR(parent) back instead of -ENOENT. All callers of these interfaces are documented to ignore the return value. Fixes: c3a280ff728a ("debugfs: warn if file creation failed due to uninitialized debugfs") Link: https://lore.kernel.org/all/6d1dc775f7d5e754d734907514534054f682bac5.1781171918.git.yk@y-koj.net/ Signed-off-by: Mikhail Gavrilov Tested-by: Yohei Kojima Link: https://patch.msgid.link/20260903115135.63210-1-mikhail.v.gavrilov@gmail.com Signed-off-by: Danilo Krummrich --- fs/debugfs/inode.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index e054e62919ec6d..a4d08bd3743bae 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -368,6 +368,9 @@ static struct dentry *debugfs_start_creating(const char *name, if (!debugfs_enabled) return ERR_PTR(-EPERM); + if (IS_ERR(parent)) + return parent; + if (!debugfs_initialized()) { pr_err("Unable to create file '%s', debugfs is not initialized yet\n", name); @@ -376,9 +379,6 @@ static struct dentry *debugfs_start_creating(const char *name, pr_debug("creating file '%s'\n", name); - if (IS_ERR(parent)) - return parent; - error = simple_pin_fs(&debug_fs_type, &debugfs_mount, &debugfs_mount_count); if (error) { From 2725ab3f5ad1c5f375c7c9fee4af02a9b138f701 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 21 Aug 2026 04:53:27 +0200 Subject: [PATCH 0284/1417] keys: fix lost wakeup when reaping a dead key type clear_bit() is atomic with respect to the word it modifies, but it is an unordered operation: it implies no memory barrier on either side (Documentation/atomic_bitops.txt). key_garbage_collector() clears KEY_GC_REAPING_KEYTYPE with clear_bit() and calls wake_up_bit() after reaping a dead key type. wake_up_bit() uses a lockless waitqueue check and requires a full barrier after the clear. The existing smp_mb() is before clear_bit(), so nothing orders the clear against that check. The GC can see an empty waitqueue while unregister_key_type() still sees the bit set. The final wakeup is then lost, leaving module unload stuck in wait_on_bit(). Use clear_and_wake_up_bit(). Its clear_bit_unlock() has RELEASE semantics, so the completed GC work stays ordered before the clear, and its smp_mb__after_atomic() orders the clear before the waitqueue check. Fixes: 0c061b5707ab ("KEYS: Correctly destroy key payloads when their keytype is removed") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Link: https://lore.kernel.org/r/20260821025327.61488-1-kmehltretter@gmail.com Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- security/keys/gc.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/security/keys/gc.c b/security/keys/gc.c index 748e83818a7604..eda445f815d47b 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -318,9 +318,7 @@ static void key_garbage_collector(struct work_struct *work) if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) { kdebug("dead wake"); - smp_mb(); - clear_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); - wake_up_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE); + clear_and_wake_up_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); } if (gc_state & KEY_GC_REAP_AGAIN) From 0d6a4268b06084baafd8ee5d66955c7e1c2e053b Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Fri, 21 Aug 2026 17:59:35 +0800 Subject: [PATCH 0285/1417] keys: translate request_key_auth pid for the reading procfs instance request_key_auth_describe() prints rka->pid into /proc/keys as a raw pid_t in the initial pid namespace. A reader can open /proc/keys through a mount in another pid namespace. That reader sees a number with no meaning there. The number can even name an unrelated task. The line needs VIEW on the key. So the reader either shares the key owner's uid or possesses the key. The fix keeps a struct pid. Commit 4f82f45730c6 ("net ip6 flowlabel: Make owner a union of struct pid * and kuid_t") gave /proc/net/ip6_flowlabel the same storage. The print goes through pid_nr_ns(). It renders against the pid namespace of the procfs instance the line is read through. Commit ad08978ab41c ("ipv6/flowlabel: simplify pid namespace lookup") moved that print to the same anchor. Output through an initial namespace /proc does not change. The line shows 0 for a requestor with no number in that namespace. Translating at read time was the alternative. find_pid_ns() can resolve a recycled number. The line would then name a live task with no connection to the key. A stored struct pid gives 0 instead when the requestor has no number there. Link: https://lore.kernel.org/keyrings/20260809110202.2180410-1-maoyixie.tju@gmail.com/ Fixes: 78b7280cce23 ("KEYS: Improve /proc/keys") Cc: stable@vger.kernel.org # v5.10+ Assisted-by: Claude:claude-opus-5 codeql Signed-off-by: Maoyi Xie Link: https://lore.kernel.org/r/20260821095935.1864998-1-maoyixie.tju@gmail.com Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- include/keys/request_key_auth-type.h | 2 +- security/keys/request_key_auth.c | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/include/keys/request_key_auth-type.h b/include/keys/request_key_auth-type.h index 01e42ee5f4099e..464636278c4f8b 100644 --- a/include/keys/request_key_auth-type.h +++ b/include/keys/request_key_auth-type.h @@ -22,7 +22,7 @@ struct request_key_auth { const struct cred *cred; void *callout_info; size_t callout_len; - pid_t pid; + struct pid *pid; char op[8]; } __randomize_layout; diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c index 282e09d8fa46c2..ed6f55b9cdd93e 100644 --- a/security/keys/request_key_auth.c +++ b/security/keys/request_key_auth.c @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -73,7 +75,10 @@ static void request_key_auth_describe(const struct key *key, seq_puts(m, "key:"); seq_puts(m, key->description); if (key_is_positive(key)) - seq_printf(m, " pid:%d ci:%zu", rka->pid, rka->callout_len); + seq_printf(m, " pid:%d ci:%zu", + pid_nr_ns(rka->pid, + proc_pid_ns(file_inode(m->file)->i_sb)), + rka->callout_len); } /* @@ -113,6 +118,7 @@ static void free_request_key_auth(struct request_key_auth *rka) if (rka->cred) put_cred(rka->cred); kfree(rka->callout_info); + put_pid(rka->pid); kfree(rka); } @@ -226,14 +232,14 @@ struct key *request_key_auth_new(struct key *target, const char *op, irka = cred->request_key_auth->payload.data[0]; rka->cred = get_cred(irka->cred); - rka->pid = irka->pid; + rka->pid = get_pid(irka->pid); up_read(&cred->request_key_auth->sem); } else { /* it isn't - use this process as the context */ rka->cred = get_cred(cred); - rka->pid = current->pid; + rka->pid = get_pid(task_pid(current)); } rka->target_key = key_get(target); From 114f00d738f15dd8c7318369edcdc53dd6d08763 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Tue, 1 Sep 2026 23:58:06 +0300 Subject: [PATCH 0286/1417] KEYS: trusted: Fix tpm2_load_cmd() boundary check tpm2_load_cmd() does boundary checks against the ASN.1 size i.e., payload->blob_len. Address this by passing the decoded blob size to tpm2_load_cmd(), and use it for the boundary checks. Cc: stable@vger.kernel.org # v5.13+ Fixes: f2219745250f ("security: keys: trusted: use ASN.1 TPM2 key format for the blobs") Reported-by: co+6a581c4284f721d4@bugs.sh Closes: https://bugs.sh/b/6a581c4284f721d4/ Reviewed-by: Stefano Garzarella Tested-by: Srish Srinivasan Link: https://lore.kernel.org/r/20260901205809.2028454-1-jarkko@kernel.org Signed-off-by: Jarkko Sakkinen --- security/keys/trusted-keys/trusted_tpm2.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 67225dd562a9f6..01f18bb370477e 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -99,7 +99,7 @@ struct tpm2_key_context { static int tpm2_key_decode(struct trusted_key_payload *payload, struct trusted_key_options *options, - u8 **buf) + u8 **buf, unsigned int *blob_len) { int ret; struct tpm2_key_context ctx; @@ -120,6 +120,7 @@ static int tpm2_key_decode(struct trusted_key_payload *payload, return -ENOMEM; *buf = blob; + *blob_len = ctx.priv_len + ctx.pub_len; options->keyhandle = ctx.parent; memcpy(blob, ctx.priv, ctx.priv_len); @@ -384,10 +385,11 @@ static int tpm2_load_cmd(struct tpm_chip *chip, int rc; u32 attrs; - rc = tpm2_key_decode(payload, options, &blob); + rc = tpm2_key_decode(payload, options, &blob, &blob_len); if (rc) { /* old form */ blob = payload->blob; + blob_len = payload->blob_len; payload->old_format = 1; } else { /* Bind for cleanup: */ @@ -399,17 +401,17 @@ static int tpm2_load_cmd(struct tpm_chip *chip, return -EINVAL; /* must be big enough for at least the two be16 size counts */ - if (payload->blob_len < 4) + if (blob_len < 4) return -EINVAL; private_len = get_unaligned_be16(blob); /* must be big enough for following public_len */ - if (private_len + 2 + 2 > (payload->blob_len)) + if (private_len + 2 + 2 > blob_len) return -E2BIG; public_len = get_unaligned_be16(blob + 2 + private_len); - if (private_len + 2 + public_len + 2 > payload->blob_len) + if (private_len + 2 + public_len + 2 > blob_len) return -E2BIG; pub = blob + 2 + private_len + 2; From 8697c431e297eb0d0ab13dda6bc172b48a34f05c Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 12 Sep 2026 00:31:16 +0300 Subject: [PATCH 0287/1417] KEYS: encrypted: fix integer overflow of datablob_len encrypted_key_alloc() stores datablob_len in a u16. It is computed from multiple string and payload lengths. If the result exceeds U16_MAX, the assignment truncates the allocation size. KASAN reports a 32760-byte slab-out-of-bounds write when __ekey_init() copies the master key description into the undersized buffer. The total payload length stored in key->datalen is also a u16. Use check_add_overflow() to reject values that do not fit either destination, and use kzalloc_flex() for the flexible-array allocation. Fixes: 7e70cb497850 ("keys: add new key-type encrypted") Cc: stable@vger.kernel.org Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Cen Zhang Signed-off-by: Francis Perron Reviewed-by: Jarkko Sakkinen Tested-by: R Nageswara Sastry Link: https://lore.kernel.org/r/20260909153433.83117-1-cenzhang@linux.microsoft.com Signed-off-by: Jarkko Sakkinen --- security/keys/encrypted-keys/encrypted.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index 59cb77b237b36d..e07092ea301ac1 100644 --- a/security/keys/encrypted-keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -579,6 +580,7 @@ static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, { struct encrypted_key_payload *epayload = NULL; unsigned short datablob_len; + unsigned short payload_totallen; unsigned short decrypted_datalen; unsigned short payload_datalen; unsigned int encrypted_datalen; @@ -632,16 +634,22 @@ static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, encrypted_datalen = roundup(decrypted_datalen, blksize); - datablob_len = format_len + 1 + strlen(master_desc) + 1 - + strlen(datalen) + 1 + ivsize + 1 + encrypted_datalen; + if (check_add_overflow(format_len + 1 + strlen(master_desc) + 1 + + strlen(datalen) + 1 + ivsize + 1, + encrypted_datalen, &datablob_len)) + return ERR_PTR(-EINVAL); + + if (check_add_overflow(datablob_len, + payload_datalen + HASH_SIZE + 1, + &payload_totallen)) + return ERR_PTR(-EINVAL); - ret = key_payload_reserve(key, payload_datalen + datablob_len - + HASH_SIZE + 1); + ret = key_payload_reserve(key, payload_totallen); if (ret < 0) return ERR_PTR(ret); - epayload = kzalloc(sizeof(*epayload) + payload_datalen + - datablob_len + HASH_SIZE + 1, GFP_KERNEL); + epayload = kzalloc_flex(*epayload, payload_data, payload_totallen, + GFP_KERNEL); if (!epayload) return ERR_PTR(-ENOMEM); From 764dcebb033764633700a036c7351a7c6350eec6 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 9 Sep 2026 23:31:23 +0000 Subject: [PATCH 0288/1417] neighbour: Add missing RCU annotation for neightbl_dump_info(). neightbl_dump_info() fetches the first non-default neigh_parms with list_next_entry(&tbl->parms, ...) and iterates through the list with list_for_each_entry_from_rcu(). However, list_next_entry() does not use RCU helper. Let's use list_for_each_entry_rcu() and skip the default parms. Fixes: 4ae34be50064 ("neighbour: Convert RTM_GETNEIGHTBL to RCU.") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260909233143.2401847-2-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/neighbour.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 1349c0eedb6425..49dd7df149ef37 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -2611,11 +2611,14 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) break; nidx = 0; - p = list_next_entry(&tbl->parms, list); - list_for_each_entry_from_rcu(p, &tbl->parms_list, list) { + + list_for_each_entry_rcu(p, &tbl->parms_list, list) { if (!net_eq(neigh_parms_net(p), net)) continue; + if (!p->dev) + continue; + if (nidx < neigh_skip) goto next; From 6d79b223ec44ada58ad37db42f539b60985a7722 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 9 Sep 2026 23:31:24 +0000 Subject: [PATCH 0289/1417] neighbour: Enforce min/max to NDTPA_INTERVAL_PROBE_TIME_MS. NDTPA_INTERVAL_PROBE_TIME_MS sets .type and .min but misses .validation_type, so no validation is applied: # ynl --family rt-neigh --do setneightbl \ --json '{"name": "arp_cache", "parms": {"interval-probe-time-ms": 0}}' # ynl --family rt-neigh --dump getneightbl --output-json | \ jq '.[] | select(.name == "arp_cache" and has("config")) | .parms["interval-probe-time-ms"]' 0 Moreover, nla_get_msecs() uses msecs_to_jiffies(), and u64 is silently cast to u32, so a larger value can bypass the min check: e.g. 4294967296 == 0x100000000 # ynl --family rt-neigh --do setneightbl \ --json '{"name": "arp_cache", "parms": {"interval-probe-time-ms": 4294967296}}' # ynl --family rt-neigh --dump getneightbl --output-json | \ jq '.[] | select(.name == "arp_cache" and has("config")) | .parms["interval-probe-time-ms"]' 0 msecs_to_jiffies() returns MAX_JIFFY_OFFSET if the value is larger than INT_MAX. Also, INT_MAX ms overflows int NEIGH_VAR() when HZ > 1000 (Alpha, MIPS), and passing a negative integer to queue_delayed_work(unsigned long delay) causes sign extension, which wraps around the expiry time to the past, resulting in it being handled as 0 delay in the timer wheel. Let's use NLA_POLICY_FULL_RANGE() and limit the max to 1 day. The same max check is applied to sysctl as well. Note that this controls the probe interval for NTF_MANAGED entries, so the max of 1 day is unlikely to break any deployments. Fixes: 211da42eaa45 ("net, neigh: introduce interval_probe_time_ms for periodic probe") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260909233143.2401847-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/rt-neigh.yaml | 3 +++ Documentation/networking/ip-sysctl.rst | 2 +- net/core/neighbour.c | 17 +++++++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Documentation/netlink/specs/rt-neigh.yaml b/Documentation/netlink/specs/rt-neigh.yaml index 0f46ef3135905d..c8e55c98d56497 100644 --- a/Documentation/netlink/specs/rt-neigh.yaml +++ b/Documentation/netlink/specs/rt-neigh.yaml @@ -341,6 +341,9 @@ attribute-sets: - name: interval-probe-time-ms type: u64 + checks: + min: 1 + max: 86400000 operations: enum-model: directional diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index 208f46967ee59b..b05829e44d8fcb 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -248,7 +248,7 @@ neigh/default/unres_qlen - INTEGER neigh/default/interval_probe_time_ms - INTEGER The probe interval for neighbor entries with NTF_MANAGED flag, - the min value is 1. + the min value is 1, and the max value is 86400000 (1 day). Default: 5000 diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 49dd7df149ef37..0db78a0dfb5168 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -2359,6 +2359,13 @@ static const struct nla_policy nl_neightbl_policy[NDTA_MAX+1] = { [NDTA_PARMS] = { .type = NLA_NESTED }, }; +#define NTBL_PARM_MS_MAX (24 * 60 * 60 * MSEC_PER_SEC) + +static const struct netlink_range_validation nl_ntbl_parm_ms_range = { + .min = 1, + .max = NTBL_PARM_MS_MAX, +}; + static const struct nla_policy nl_ntbl_parm_policy[NDTPA_MAX+1] = { [NDTPA_IFINDEX] = { .type = NLA_U32 }, [NDTPA_QUEUE_LEN] = { .type = NLA_U32 }, @@ -2375,7 +2382,8 @@ static const struct nla_policy nl_ntbl_parm_policy[NDTPA_MAX+1] = { [NDTPA_ANYCAST_DELAY] = { .type = NLA_U64 }, [NDTPA_PROXY_DELAY] = { .type = NLA_U64 }, [NDTPA_LOCKTIME] = { .type = NLA_U64 }, - [NDTPA_INTERVAL_PROBE_TIME_MS] = { .type = NLA_U64, .min = 1 }, + [NDTPA_INTERVAL_PROBE_TIME_MS] = NLA_POLICY_FULL_RANGE(NLA_U64, + &nl_ntbl_parm_ms_range), }; static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh, @@ -3672,12 +3680,13 @@ static int neigh_proc_dointvec_ms_jiffies_positive(const struct ctl_table *ctl, void *buffer, size_t *lenp, loff_t *ppos) { struct ctl_table tmp = *ctl; - int ret; + int ret, min, max; - int min = msecs_to_jiffies(1); + min = msecs_to_jiffies(1); + max = msecs_to_jiffies(NTBL_PARM_MS_MAX); tmp.extra1 = &min; - tmp.extra2 = NULL; + tmp.extra2 = &max; ret = proc_dointvec_ms_jiffies_minmax(&tmp, write, buffer, lenp, ppos); neigh_proc_update(ctl, write); From 7b430fcfc972f61b09cc19ca95997586af4a147d Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 9 Sep 2026 23:31:25 +0000 Subject: [PATCH 0290/1417] neighbour: Don't render blackhole_netdev via RTM_GETNEIGHTBL. The cited commits started to initialise blackhole_netdev with neigh_parms_alloc(). This is visible in init_net as the ifindex==0 entries via RTM_GETNEIGHTBL: # ynl --family rt-neigh --dump getneightbl --output-json \ | jq '.[] | select(.parms.ifindex == 0) | {name: .name, ifindex: .parms.ifindex}' { "name": "arp_cache", "ifindex": 0 } { "name": "ndisc_cache", "ifindex": 0 } For RTM_SETNEIGHTBL, ifindex being 0 means wildcard. Let's skip blackhole_netdev's parms in neightbl_dump_info(). Note that lookup_neigh_parms() does not need the same change because the default parms is always the first entry and matches with ifindex == 0. Fixes: e5f80fcf869a ("ipv6: give an IPv6 dev to blackhole_netdev") Fixes: 22600596b675 ("ipv4: give an IPv4 dev to blackhole_netdev") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260909233143.2401847-4-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/neighbour.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 0db78a0dfb5168..02bf940ce00aff 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -2624,7 +2624,7 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) if (!net_eq(neigh_parms_net(p), net)) continue; - if (!p->dev) + if (!p->dev || p->dev == blackhole_netdev) continue; if (nidx < neigh_skip) From 979aabdad8dd03394467ee484a1a70f3d40b19ba Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 9 Sep 2026 23:31:26 +0000 Subject: [PATCH 0291/1417] neighbour: Skip default parms when resumed in neightbl_dump_info(). neightbl_dump_info() calls neightbl_fill_info() in each loop to render the default parms. If there are many devices and neightbl_fill_param_info() failed, neightbl_fill_info() is called again when the dump resumes: # ynl --family rt-neigh --dump getneightbl --output-json | jq '.[] | {name: .name, ifindex: .parms.ifindex}' ... { "name": "ndisc_cache", "ifindex": null } ... { "name": "ndisc_cache", "ifindex": 6 } { "name": "ndisc_cache", "ifindex": null } { "name": "ndisc_cache", "ifindex": 5 } Let's skip neightbl_fill_info() if it is already called in neightbl_dump_info(). Note that we cannot use !neigh_skip instead of !default_skip because default_skip == 1 && neigh_skip == 0 could be true if the first neightbl_fill_param_info() fails. Also, nidx must be cleared at the end of each table loop; otherwise, if neightbl_fill_info() for a subsequent table fails, the leftover nidx from the previous table would be saved in cb->args[1], resulting in erroneously skipping parms of the subsequent table in the next dump. Fixes: c7fb64db001f ("[NETLINK]: Neighbour table configuration and statistics via rtnetlink") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260909233143.2401847-5-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/neighbour.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 02bf940ce00aff..7448320f7ad52c 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -2587,9 +2587,10 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) { const struct nlmsghdr *nlh = cb->nlh; struct net *net = sock_net(skb->sk); + int default_skip = cb->args[2]; + int neigh_skip = cb->args[1]; int family, tidx, nidx = 0; int tbl_skip = cb->args[0]; - int neigh_skip = cb->args[1]; struct neigh_table *tbl; if (cb->strict_check) { @@ -2613,12 +2614,13 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) if (tidx < tbl_skip || (family && tbl->family != family)) continue; - if (neightbl_fill_info(skb, tbl, NETLINK_CB(cb->skb).portid, + if (!default_skip && + neightbl_fill_info(skb, tbl, NETLINK_CB(cb->skb).portid, nlh->nlmsg_seq, RTM_NEWNEIGHTBL, NLM_F_MULTI) < 0) break; - nidx = 0; + default_skip = 1; list_for_each_entry_rcu(p, &tbl->parms_list, list) { if (!net_eq(neigh_parms_net(p), net)) @@ -2641,12 +2643,15 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) } neigh_skip = 0; + nidx = 0; + default_skip = 0; } out: rcu_read_unlock(); cb->args[0] = tidx; cb->args[1] = nidx; + cb->args[2] = default_skip; return skb->len; } From c60ae98c5aa64021751b38ab1313b19d620bf640 Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 11 Sep 2026 10:25:04 +0100 Subject: [PATCH 0292/1417] 9p: Fix v9fs_issue_write() to update i_size and remote_i_size Fix v9fs_issue_write() to update i_size and remote_i_size to the new size of the server file if we made it larger, using the start fpos and the count returned by p9_client_write() to calculate the new minimum file size. This assumes that if the 9P server makes a short write (say it hits ENOSPC), a reduced count is returned. Fixes: 5fb70e7275a6 ("netfs, 9p: Implement helpers for new write code") Reported-by: Michael Mulqueen Closes: https://lore.kernel.org/r/fbb9e395-1e07-4212-8f70-23f3cd498074@method-b.uk/ Cc: stable@vger.kernel.org Signed-off-by: David Howells Message-ID: <2226525.1789118704@warthog.procyon.org.uk> Signed-off-by: Dominique Martinet --- fs/9p/vfs_addr.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c index 1ac0b3dcc0778e..13cf87a5f90cb7 100644 --- a/fs/9p/vfs_addr.c +++ b/fs/9p/vfs_addr.c @@ -54,11 +54,37 @@ static void v9fs_begin_writeback(struct netfs_io_request *wreq) static void v9fs_issue_write(struct netfs_io_subrequest *subreq) { struct p9_fid *fid = subreq->rreq->netfs_priv; + struct inode *inode = subreq->rreq->inode; + struct netfs_inode *ictx = netfs_inode(inode); int err, len; len = p9_client_write(fid, subreq->start, &subreq->io_iter, &err); - if (len > 0) + if (len > 0) { + uoff_t end = subreq->start + len, i_size, remote, zp; + bool set = false; + + spin_lock(&inode->i_lock); + + /* We can read the sizes directly as we hold i_lock. */ + i_size = inode->i_size; + remote = ictx->_remote_i_size; + zp = ictx->_zero_point; + + if (end > i_size) { + i_size = end; + set = true; + } + if (end > remote) { + remote = end; + set = true; + } + + if (set) + netfs_write_sizes(inode, i_size, remote, zp); + spin_unlock(&inode->i_lock); + __set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); + } netfs_write_subrequest_terminated(subreq, len ?: err); } From 5cddf63367ef894ba0026dfa2d524346f7048de9 Mon Sep 17 00:00:00 2001 From: Krish Gulati Date: Sat, 12 Sep 2026 12:36:13 +0530 Subject: [PATCH 0293/1417] ALSA: hda/realtek: Add quirk for HP Victus 15-fa1xxx (MB 8BB1) mute LED The mute LED on this board does not respond to mute state changes because no fixup is matched for SSID 103c:8bb1. The reporter verified the LED can be toggled manually via COEF index 0x0B. Reported-by: Mazen Ahmed Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221982 Tested-by: Mazen Ahmed Signed-off-by: Krish Gulati Link: https://patch.msgid.link/20260912070618.21272-1-krishgulati7@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 3abee617e86e11..dc73fa95b00a0a 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7568,6 +7568,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bb1, "HP Victus 15-fa1xxx (MB 8BB1)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), From 221253723dc58bb901c3f27a7659823e63fc598c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 10 Sep 2026 17:52:23 +0200 Subject: [PATCH 0294/1417] ALSA: bcd2000: Fix race between rawmidi and disconnect Although we tried to fix the potential UAF issues at USB disconnect on bcd2000 driver, there is still an overlooked case -- namely, when a rawmidi trigger callback has been already running at USB disconnect handling, the in-flight function (e.g. bcd2000_midi_send()) could still access the URB, because the previous URB NULL-check & clearance was considered only for the URB complete callbacks, but not about the parallel rawmidi operations. For addressing the race, this patch introduced a new spinlock that covers each rawmidi operation as well as the rawmidi handling in the complete callback. The URB is cleared with the lock, so it guarantees that the pending rawmidi task already finished or a NULL check is effective. Fixes: 459d3a64766f ("ALSA: bcd2000: clear the URB pointers on disconnect") Link: https://patch.msgid.link/20260910155227.996210-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/bcd2000/bcd2000.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c index c5c542d17ccc15..2bd49bf8274899 100644 --- a/sound/usb/bcd2000/bcd2000.c +++ b/sound/usb/bcd2000/bcd2000.c @@ -43,6 +43,7 @@ struct bcd2000 { struct usb_interface *intf; int card_index; + spinlock_t midi_lock; int midi_out_active; struct snd_rawmidi *rmidi; struct snd_rawmidi_substream *midi_receive_substream; @@ -90,6 +91,8 @@ static void bcd2000_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) { struct bcd2000 *bcd2k = substream->rmidi->private_data; + + guard(spinlock_irqsave)(&bcd2k->midi_lock); bcd2k->midi_receive_substream = up ? substream : NULL; } @@ -195,6 +198,8 @@ static void bcd2000_midi_output_trigger(struct snd_rawmidi_substream *substream, { struct bcd2000 *bcd2k = substream->rmidi->private_data; + guard(spinlock_irqsave)(&bcd2k->midi_lock); + if (up) { bcd2k->midi_out_substream = substream; /* check if there is data userspace wants to send */ @@ -219,6 +224,7 @@ static void bcd2000_output_complete(struct urb *urb) return; /* check if there is more data userspace wants to send */ + guard(spinlock_irqsave)(&bcd2k->midi_lock); bcd2000_midi_send(bcd2k); } @@ -234,6 +240,8 @@ static void bcd2000_input_complete(struct urb *urb) if (!bcd2k || urb->status == -ESHUTDOWN) return; + guard(spinlock_irqsave)(&bcd2k->midi_lock); + if (urb->actual_length > 0) bcd2000_midi_handle_input(bcd2k, urb->transfer_buffer, urb->actual_length); @@ -348,16 +356,26 @@ static int bcd2000_init_midi(struct bcd2000 *bcd2k) return 0; } +static void bcd2000_midi_free(struct bcd2000 *bcd2k, + struct urb **urb_p) +{ + struct urb *urb = *urb_p; + + if (!urb) + return; + + usb_poison_urb(urb); + scoped_guard(spinlock_irq, &bcd2k->midi_lock) + *urb_p = NULL; + + usb_free_urb(urb); +} + static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k, struct usb_interface *interface) { - usb_poison_urb(bcd2k->midi_out_urb); - usb_poison_urb(bcd2k->midi_in_urb); - - usb_free_urb(bcd2k->midi_out_urb); - usb_free_urb(bcd2k->midi_in_urb); - bcd2k->midi_out_urb = NULL; - bcd2k->midi_in_urb = NULL; + bcd2000_midi_free(bcd2k, &bcd2k->midi_out_urb); + bcd2000_midi_free(bcd2k, &bcd2k->midi_in_urb); if (bcd2k->intf) { usb_set_intfdata(bcd2k->intf, NULL); @@ -393,6 +411,7 @@ static int bcd2000_probe(struct usb_interface *interface, bcd2k->card = card; bcd2k->card_index = card_index; bcd2k->intf = interface; + spin_lock_init(&bcd2k->midi_lock); snd_card_set_dev(card, &interface->dev); From effce1cb87ee0d8b3a8cbe7722968f4ea7efd360 Mon Sep 17 00:00:00 2001 From: Sophie D Date: Wed, 9 Sep 2026 21:49:10 -0400 Subject: [PATCH 0295/1417] drm/gud: Ignore damage clips in full update mode When running in full update mode, previously small updates (such as moving the mouse across the screen) would cause many full frames to be generated. This would bog down the bus and lower the effective framerate significantly - I was seeing a drop from 60 FPS to 2 FPS. Set ignore_damage_clips in full update mode so the damage iterator yields a single full-plane rectangle instead of one per clip. Fixes: 73cfd166e045 ("drm/gud: Replace simple display pipe with DRM atomic helpers") Cc: # 6.18.x Signed-off-by: Sophie D Reviewed-by: Thomas Zimmermann Acked-by: Ruben Wauters Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260910014910.8564-1-patches@scd31.com --- drivers/gpu/drm/gud/gud_pipe.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c index 5ef887d8485a3d..3388fdc8ea7b5a 100644 --- a/drivers/gpu/drm/gud/gud_pipe.c +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -482,6 +482,9 @@ int gud_plane_atomic_check(struct drm_plane *plane, if (!new_plane_state->visible) return 0; + if (gdrm->flags & GUD_DISPLAY_FLAG_FULL_UPDATE) + new_plane_state->ignore_damage_clips = true; + if (old_plane_state->rotation != new_plane_state->rotation) crtc_state->mode_changed = true; From 59ced288fcba9e91bd38e61a972ad782c4edb7d0 Mon Sep 17 00:00:00 2001 From: Sajal Gupta Date: Wed, 2 Sep 2026 18:00:57 +0530 Subject: [PATCH 0296/1417] drm/gud: fix out-of-bounds write in gud_plane_atomic_check() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plane property loop uses req->properties[num_properties + i] as write index while simultaneously incrementing `num_properties` inside the loop. At iteration i, num_properties has also incremented by i, so the write is done at `initial_num_properties + 2*i`, skipping every other index and advancing by 2 per iteration. With just 2 connector and 32 plane properties the last write happens at index 64, one slot past the end of the 64-slot (indices 0–63) allocation. A USB device can trigger OOB by advertising the maximum number of properties. Fix by dropping the redundant `+ i`; num_properties is already the correct running index, as gud_connector_fill_properties() fills the preceding slots. Fixes: 40e1a70b4aed ("drm: Add GUD USB Display driver") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260821071812.16500-1-sajal2005gupta%40gmail.com?part=1 Signed-off-by: Sajal Gupta Cc: Acked-by: Ruben Wauters Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260902123254.36987-1-sajal2005gupta@gmail.com --- drivers/gpu/drm/gud/gud_pipe.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c index 3388fdc8ea7b5a..aa7792966287bf 100644 --- a/drivers/gpu/drm/gud/gud_pipe.c +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -565,8 +565,8 @@ int gud_plane_atomic_check(struct drm_plane *plane, goto out; } - req->properties[num_properties + i].prop = cpu_to_le16(prop); - req->properties[num_properties + i].val = cpu_to_le64(val); + req->properties[num_properties].prop = cpu_to_le16(prop); + req->properties[num_properties].val = cpu_to_le64(val); num_properties++; } From ba970587a0e1203b6a0934b5b9174886b4c4d24c Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Sat, 12 Sep 2026 08:09:14 -0700 Subject: [PATCH 0297/1417] drm/msm/a6xx+: Increase GMU FW init timeout We were using 10ms, kgsl uses 100ms. In practice it is usually takes less than 10ms, but very occasionally goes a bit above 10ms, leading to a "GMU firmware inialization timed out", from which point things go south. There are probably some things that could be done to speed up init, like increasing GMU freq. But to be safe, increase the timeout to match kgsl. Signed-off-by: Rob Clark Reviewed-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/753014/ Message-ID: <20260912150915.28700-1-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index 27cac853975f90..6d49c51df1a279 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -320,7 +320,7 @@ static int a6xx_gmu_start(struct a6xx_gmu *gmu) gmu_write(gmu, REG_A6XX_GMU_CM3_SYSRESET, 0); ret = gmu_poll_timeout(gmu, REG_A6XX_GMU_CM3_FW_INIT_RESULT, val, - (val & mask) == reset_val, 100, 10000); + (val & mask) == reset_val, 100, 100000); if (ret) DRM_DEV_ERROR(gmu->dev, "GMU firmware initialization timed out\n"); From 0d7823cd4cda35f2060a685fa276ff7709915abc Mon Sep 17 00:00:00 2001 From: Siddharth Chintamaneni Date: Wed, 2 Sep 2026 17:14:13 +0000 Subject: [PATCH 0298/1417] bpf: Allow terminal gotox instructions check_subprogs() treats gotox as a direct jump and validates its reserved zero offset. When gotox is the final instruction, this produces a synthetic successor one instruction past the end of the subprogram and rejects an otherwise valid program. Skip direct-offset validation for gotox and accept it as a non-fallthrough terminal instruction. Its actual targets remain validated from the instruction-array jump table during CFG construction. Fixes: 493d9e0d6083 ("bpf, x86: add support for indirect jumps") Signed-off-by: Siddharth Chintamaneni Reviewed-by: Anton Protopopov Link: https://lore.kernel.org/r/20260902171414.96165-1-sidchintamaneni@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 1b0b1fb6287866..ddba53eaa3331b 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -3042,6 +3042,8 @@ static int check_subprogs(struct bpf_verifier_env *env) subprog[cur_subprog].exit_idx = i; goto next; } + if (insn_is_gotox(&insn[i])) + goto next; off = i + bpf_jmp_offset(&insn[i]) + 1; if (off < subprog_start || off >= subprog_end) { verbose(env, "jump out of range from insn %d to %d\n", i, off); @@ -3061,7 +3063,8 @@ static int check_subprogs(struct bpf_verifier_env *env) */ if (code != (BPF_JMP | BPF_EXIT) && code != (BPF_JMP32 | BPF_JA) && - code != (BPF_JMP | BPF_JA)) { + code != (BPF_JMP | BPF_JA) && + !insn_is_gotox(&insn[i])) { verbose(env, "last insn is not an exit or jmp\n"); bpf_diag_program_structure( env, i, "subprogram can fall through", From ac781acaef4904b9be64a7b5755c778e5d76ede6 Mon Sep 17 00:00:00 2001 From: Siddharth Chintamaneni Date: Wed, 2 Sep 2026 17:14:14 +0000 Subject: [PATCH 0299/1417] selftests/bpf: Test terminal gotox instructions Add tests that place gotox at the end of the main program and a subprogram, with each jump-table target preceding the gotox instruction. This tests gotox as a valid non-fallthrough terminal instruction. Signed-off-by: Siddharth Chintamaneni Reviewed-by: Anton Protopopov Link: https://lore.kernel.org/r/20260902171414.96165-2-sidchintamaneni@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/verifier_gotox.c | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_gotox.c b/tools/testing/selftests/bpf/progs/verifier_gotox.c index 5b18c9a27717bc..0e27c2c79c57ec 100644 --- a/tools/testing/selftests/bpf/progs/verifier_gotox.c +++ b/tools/testing/selftests/bpf/progs/verifier_gotox.c @@ -47,6 +47,54 @@ DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_src_reg, BPF_REG_1, 0, 0, __fa DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_non_zero_off, BPF_REG_0, 1, 0, __failure __msg("BPF_JA|BPF_X uses reserved fields")) DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_non_zero_imm, BPF_REG_0, 0, 1, __failure __msg("BPF_JA|BPF_X uses reserved fields")) +#define DEFINE_TERMINAL_GOTOX_PROG(NAME, BASE) \ + __naked void NAME(void) \ + { \ + asm volatile (" \ + .pushsection .jumptables,\"\",@progbits; \ +jt0_%=: \ + .quad ret0_%= - " BASE "; \ + .size jt0_%=, 8; \ + .global jt0_%=; \ + .popsection; \ + \ + r0 = jt0_%= ll; \ + r0 = *(u64 *)(r0 + 0); \ + goto end_%=; \ +ret0_%=: \ + r0 = 0; \ + exit; \ +end_%=: \ + .8byte %[gotox_r0]; \ +" : \ + : __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, \ + BPF_REG_0, 0, 0, 0)) \ + : __clobber_all); \ + } + +SEC("socket") +__success __retval(0) +DEFINE_TERMINAL_GOTOX_PROG(jump_table_terminal_gotox, "socket") + +static __noinline __used +DEFINE_TERMINAL_GOTOX_PROG(terminal_gotox_subprog1, ".text") + +static __noinline __used int terminal_gotox_subprog2(void) +{ + return 0; +} + +SEC("socket") +__success __retval(0) +__naked void jump_table_terminal_gotox_subprog(void) +{ + asm volatile (" \ + call terminal_gotox_subprog1; \ + call terminal_gotox_subprog2; \ + exit; \ +" ::: __clobber_all); +} + /* * Gotox is forbidden when there is no jump table loaded * which points to the sub-function where the gotox is used From 85136bf22404474a815fc0ed26ec0d1cbc1bc3f9 Mon Sep 17 00:00:00 2001 From: "Jose Fernandez (Anthropic)" Date: Wed, 9 Sep 2026 17:51:04 +0000 Subject: [PATCH 0300/1417] bpf: Avoid soft lockup in __htab_map_lookup_and_delete_batch() __htab_map_lookup_and_delete_batch() has no rescheduling point. The batch count bounds how many entries are copied out, not how many buckets are visited, so one BPF_MAP_LOOKUP_BATCH call can walk the map end to end. The empty-bucket fast path is worse: it stays inside a single rcu_read_lock() / bpf_disable_instrumentation() section for any run of consecutive empty buckets. That holds up on small maps, but it falls apart at scale. On a 144-CPU arm64 host running a CONFIG_PREEMPT_NONE kernel, periodic BPF_MAP_LOOKUP_BATCH calls against an LRU hash map with 16,777,216 buckets held a CPU inside the batch op for 77+ seconds and triggered the soft lockup watchdog. Commit 75134f16e7dd ("bpf: Add schedule points in batch ops") fixed this same problem in the generic batch ops, but not in this htab-native path, which every htab-based hash map variant uses for its lookup[_and_delete] batch ops. Complete that fix here. Leave the critical section after 64 consecutive empty buckets, call cond_resched_tasks_rcu_qs(), and resume at the saved bucket cursor. No locks are held at that point, and resuming from the cursor is already the function's behavior for non-empty buckets. Add the same call to the per-bucket loop after copy_to_user(), where every lock has been dropped. cond_resched_rcu() is not enough here: sleeping with bpf_prog_active elevated makes tracing programs on that CPU silently skip their invocations. Plain cond_resched() is not enough either. It is a no-op under PREEMPT and PREEMPT_LAZY, the only models arm64 and x86 have offered since commit 7dadeaa6e851 ("sched: Further restrict the preemption modes"). It is also never a Tasks RCU quiescent state, in any model: the reschedule counts as a preemption. The walking task stays a holdout and stalls every synchronize_rcu_tasks() caller, ftrace and BPF trampoline teardown included, until the syscall returns [1]. cond_resched_tasks_rcu_qs() is the usual tool for that [2]. It reports the quiescent state at each yield and still reschedules as cond_resched() does on PREEMPT_NONE and PREEMPT_VOLUNTARY kernels. Fixes: 057996380a42 ("bpf: Add batch ops to all htab bpf map") Cc: "Paul E. McKenney" Cc: Rik van Riel Link: https://lore.kernel.org/bpf/20260715215314.44423f47@fangorn/ [1] Link: https://lore.kernel.org/bpf/9d444098-7c03-4163-af12-bd0a79a51443@paulmck-laptop/ [2] Assisted-by: LLM Signed-off-by: Jose Fernandez (Anthropic) Signed-off-by: Josef Bacik Reviewed-by: Rik van Riel Link: https://lore.kernel.org/r/20260909-b4-htab-batch-resched-v2-1-0cb529d8f95a@toxicpanda.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/hashtab.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index 6f331c80130d76..a72dc5b9f184d4 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -1772,6 +1772,12 @@ static int htab_lru_percpu_map_lookup_and_delete_elem(struct bpf_map *map, flags); } +/* + * Max consecutive empty buckets to walk in one RCU + + * instrumentation-disabled section before rescheduling. + */ +#define HTAB_BATCH_EMPTY_RESCHED 64 + static int __htab_map_lookup_and_delete_batch(struct bpf_map *map, const union bpf_attr *attr, @@ -1793,6 +1799,7 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, unsigned long flags = 0; bool locked = false; struct htab_elem *l; + u32 empty_cnt = 0; struct bucket *b; int ret = 0; @@ -1971,12 +1978,21 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, } next_batch: - /* If we are not copying data, we can go to next bucket and avoid - * unlocking the rcu. + /* + * If we are not copying data, we can go to next bucket and avoid + * unlocking the rcu. Bound the walk though: after + * HTAB_BATCH_EMPTY_RESCHED consecutive empty buckets, fully exit + * the critical section (no locks are held here) and reschedule. */ if (!bucket_cnt && (batch + 1 < htab->n_buckets)) { batch++; - goto again_nocopy; + if (++empty_cnt < HTAB_BATCH_EMPTY_RESCHED) + goto again_nocopy; + empty_cnt = 0; + rcu_read_unlock(); + bpf_enable_instrumentation(); + cond_resched_tasks_rcu_qs(); + goto again; } rcu_read_unlock(); @@ -1990,11 +2006,13 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, } total += bucket_cnt; + empty_cnt = 0; batch++; if (batch >= htab->n_buckets) { ret = -ENOENT; goto after_loop; } + cond_resched_tasks_rcu_qs(); goto again; after_loop: From 75f8cf22463d82bb1fb0239a3d485fc8f4c8ef03 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 3 Sep 2026 18:09:20 +0800 Subject: [PATCH 0301/1417] bpf: Fix out-of-bounds read of rtt_min in sock_ops A sockops prog reading skops->rtt_min never checks the sk type: on the tcp_conn_request() path sock_ops->sk is a request_sock (non-full), and the ctx rewrite casts it to a tcp_sock (full) and reads rtt_min past the end of the request_sock, returning dirty adjacent memory. SEC("sockops") int prog(struct bpf_sock_ops *skops) { switch (skops->op) { case BPF_SOCK_OPS_RWND_INIT: leak = skops->rtt_min; /* reads the request_sock OOB */ ... } } For instance one such read returned rtt_min=0xffff8881, the high half of a leaked kernel pointer. Guarding that cast is exactly what SOCK_OPS_GET_FIELD() does -- it checks is_locked_tcp_sock and returns 0 when sock_ops->sk is not a locked full socket. Every other tcp_sock field in sock_ops goes through it; rtt_min is the only one open-coded, so it skips the check. Read rtt_min through SOCK_OPS_GET_FIELD() too. rtt_min is a bit special: it is a struct minmax and we only want the current min, so pass rtt_min.s[0].v. That is equivalent to the old hand-computed offset offsetof(struct tcp_sock, rtt_min) + sizeof_field(struct minmax_sample, t) (s[0] sits at rtt_min + 0 and .v at + sizeof(.t), i.e. what minmax_get() returns), so the loaded field is unchanged and only the full-sock guard is added. The two BUILD_BUG_ON()s that protected the hand-computed offset are no longer needed. Before patch: 0: r1 = *(u64 *)(r1 +0) ; r1 = skops->sk 1: r1 = *(u32 *)(r1 +2324) ; ((tcp_sock *)sk)->rtt_min.s[0].v After patch: 0: *(u64 *)(r1 +56) = r9 1: r9 = *(u8 *)(r1 +50) ; is_locked_tcp_sock 2: if r9 == 0 goto pc+4 ; not a locked full sock -> 0 3: r9 = *(u64 *)(r1 +56) 4: r1 = *(u64 *)(r1 +0) ; r1 = skops->sk 5: r1 = *(u32 *)(r1 +2324) ; rtt_min.s[0].v 6: goto pc+2 7: r9 = *(u64 *)(r1 +56) 8: r1 = 0 Fixes: 44f0e43037d3 ("bpf: Add support for reading sk_state and more") Reported-by: VEGA Signed-off-by: Jiayuan Chen Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/r/20260903100921.113374-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- net/core/filter.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index cae43b9991627d..532405988fd98b 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -11106,18 +11106,7 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type, break; case offsetof(struct bpf_sock_ops, rtt_min): - BUILD_BUG_ON(sizeof_field(struct tcp_sock, rtt_min) != - sizeof(struct minmax)); - BUILD_BUG_ON(sizeof(struct minmax) < - sizeof(struct minmax_sample)); - - *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF( - struct bpf_sock_ops_kern, sk), - si->dst_reg, si->src_reg, - offsetof(struct bpf_sock_ops_kern, sk)); - *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg, - offsetof(struct tcp_sock, rtt_min) + - sizeof_field(struct minmax_sample, t)); + SOCK_OPS_GET_FIELD(rtt_min, rtt_min.s[0].v, struct tcp_sock); break; case offsetof(struct bpf_sock_ops, bpf_sock_ops_cb_flags): From 7d70a0b02d262971201fdd1e221586bdd95c2910 Mon Sep 17 00:00:00 2001 From: Zhixing Chen Date: Thu, 3 Sep 2026 18:43:58 +0800 Subject: [PATCH 0302/1417] bpf: Use kvfree() in xdp_test_run_teardown() xdp_test_run_setup() allocates xdp->frames and xdp->skbs with kvmalloc_array(). The setup error path already releases both arrays with kvfree(), while the normal teardown path still uses kfree(). Use kvfree() in xdp_test_run_teardown() as well, so the release helper matches the allocator on both paths. Signed-off-by: Zhixing Chen Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/r/20260903104358.29228-1-running910@gmail.com Signed-off-by: Alexei Starovoitov --- net/bpf/test_run.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c index 5d51f6cb7d1545..513354e928cb58 100644 --- a/net/bpf/test_run.c +++ b/net/bpf/test_run.c @@ -205,8 +205,8 @@ static void xdp_test_run_teardown(struct xdp_test_data *xdp) { xdp_unreg_mem_model(&xdp->mem); page_pool_destroy(xdp->pp); - kfree(xdp->frames); - kfree(xdp->skbs); + kvfree(xdp->frames); + kvfree(xdp->skbs); } static bool frame_was_changed(const struct xdp_page_head *head) From 6d8c197c9992659a65525a07de4368c8401fdda7 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 8 Sep 2026 15:52:46 +0900 Subject: [PATCH 0303/1417] ntfs: use dynamic MFT tail reservation The ntfs MFT allocator historically treated records below 64 as a permanent extension area and stopped searching for $MFT extent records after record 400. Windows and ntfs3 do not maintain that on-disk layout, so an NTFS volume can have free MFT records while ntfs returns -ENOSPC when $MFT:$DATA needs another mapping-pairs extent. Use record 24 as the first normal record and maintain an in-memory tail reserve of up to four initialized records. Normal allocations skip the reserve, while $MFT metadata extent allocations consume it. When a new tail is initialized, allocate at least two records and reserve the following records to avoid recursive allocation during MFT extension. Existing free runs can seed the reserve on volumes mounted without one. For $MFT/$DATA, constrain an extent record to a record whose byte offset is below the new extent lowest VCN byte offset. This preserves bootstrap reachability without an arbitrary record 400 limit. If no safe record is available, validate and use reserved records 15, 12, 13, and 14 as bootstrap candidates while keeping their MFT bitmap entries in use. This allows existing Windows volumes to extend $MFT using their actual free records and prevents normal file allocation from consuming the metadata reserve. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 11 +- fs/ntfs/mft.c | 420 +++++++++++++++++++++++++++++++++++++---------- fs/ntfs/mft.h | 2 +- fs/ntfs/namei.c | 2 +- fs/ntfs/volume.h | 6 + 5 files changed, 349 insertions(+), 92 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 848a0d338b89dd..81b9e0971b3e6c 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -2917,7 +2917,7 @@ int ntfs_attr_add(struct ntfs_inode *ni, __le32 type, attr_ni = NULL; /* Allocate new extent. */ - err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL); + err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL, -1); if (err) { ntfs_error(sb, "Failed to allocate extent record"); goto err_out; @@ -3550,7 +3550,7 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra) * new extent and move attribute to it. */ ni = NULL; - err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL); + err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL, -1); if (err) { ntfs_error(sb, "Couldn't allocate MFT record, err : %d", err); return err; @@ -3976,7 +3976,10 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) unsigned int de_cnt = 0; /* Allocate new mft record. */ - err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL); + err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL, + base_ni->mft_no == FILE_MFT && + ni->type == AT_DATA && + ni->name == AT_UNNAMED ? stop_vcn : -1); if (err) { ntfs_error(sb, "Failed to allocate extent record"); goto put_err_out; @@ -4836,7 +4839,7 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi } /* Allocate new mft record. */ - err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL); + err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL, -1); if (err) { ntfs_error(sb, "Couldn't allocate MFT record"); goto put_err_out; diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 7e58c99f1728d4..61d4ee3a57fdda 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -841,12 +841,126 @@ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no, static const char *es = " Leaving inconsistent metadata. Unmount and run chkdsk."; -#define RESERVED_MFT_RECORDS 64 +#define FIRST_NORMAL_MFT_RECORD 24 +#define MFT_RECORD_RESERVE 4 /* - * ntfs_mft_bitmap_find_and_alloc_free_rec_nolock - see name + * Records 12-15 are marked in use by Windows but normally have no name + * and no links. Keep them as the last bootstrap option when a volume + * mounted without an in-memory tail reserve needs its first $MFT metadata + * extent. + */ +static bool mft_reserved_is_free(struct ntfs_volume *vol, + struct ntfs_inode *mft_ni, s64 mft_no) +{ + struct attr_record *a; + struct mft_record *m; + struct folio *folio; + void *mapped; + pgoff_t index = NTFS_MFT_NR_TO_PIDX(vol, mft_no); + unsigned int ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no); + u32 attrs_offset, bytes_in_use; + bool available = false, have_std = false; + int i; + + for (i = 0; i < mft_ni->nr_extents; i++) { + if (mft_ni->ext.extent_ntfs_inos[i] && + mft_ni->ext.extent_ntfs_inos[i]->mft_no == mft_no) + return false; + } + m = kmalloc(vol->mft_record_size, GFP_NOFS); + if (!m) + return false; + + folio = read_mapping_folio(vol->mft_ino->i_mapping, index, NULL); + if (IS_ERR(folio)) + goto free_m; + + folio_lock(folio); + mapped = kmap_local_folio(folio, 0); + memcpy(m, (u8 *)mapped + ofs, vol->mft_record_size); + kunmap_local(mapped); + folio_unlock(folio); + folio_put(folio); + if (post_read_mst_fixup((struct ntfs_record *)m, vol->mft_record_size)) + goto free_m; + + if (!ntfs_is_mft_record(m->magic) || + !(m->flags & MFT_RECORD_IN_USE) || m->base_mft_record || + m->link_count) + goto out; + + attrs_offset = le16_to_cpu(m->attrs_offset); + bytes_in_use = le32_to_cpu(m->bytes_in_use); + if (attrs_offset > bytes_in_use || bytes_in_use > vol->mft_record_size || + bytes_in_use - attrs_offset < sizeof(a->type)) + goto out; + + for (a = (struct attr_record *)((u8 *)m + attrs_offset); + (u8 *)a + sizeof(a->type) <= (u8 *)m + bytes_in_use;) { + u32 len; + + if (a->type == AT_END) { + if ((u8 *)a + sizeof(a->type) + sizeof(a->length) > + (u8 *)m + bytes_in_use) + break; + /* Also accept a record emptied by an earlier bootstrap. */ + available = have_std || + (u8 *)a == (u8 *)m + attrs_offset; + break; + } + if (a->type == AT_FILE_NAME) + break; + len = le32_to_cpu(a->length); + if (len < offsetof(struct attr_record, data) || + (u8 *)a + len > (u8 *)m + bytes_in_use) + break; + if (a->type == AT_STANDARD_INFORMATION) { + u32 value_len, value_ofs; + + if (have_std || a->non_resident || + len < offsetof(struct attr_record, + data.resident.reserved) + 1) + break; + value_len = le32_to_cpu(a->data.resident.value_length); + value_ofs = le16_to_cpu(a->data.resident.value_offset); + if (value_ofs > len || value_len > len - value_ofs) + break; + have_std = true; + } + a = (struct attr_record *)((u8 *)a + len); + } +out: + kfree(m); + return available; +free_m: + kfree(m); + return false; +} + +static s64 mft_reserve_end(const u8 *buf, s64 buf_start, s64 buf_end, + s64 start, s64 pass_end, s64 initialized_mft_records) +{ + s64 end = start + 1; + s64 limit = min_t(s64, start + MFT_RECORD_RESERVE, pass_end); + + if (limit > initialized_mft_records) + limit = initialized_mft_records; + if (limit > buf_end) + limit = buf_end; + while (end < limit && + !(buf[(end - buf_start) >> 3] & + (1 << ((end - buf_start) & 7)))) + end++; + return end; +} + +/* + * mft_bitmap_alloc_free_rec - find and allocate a free MFT record * @vol: volume on which to search for a free mft record * @base_ni: open base inode if allocating an extent mft record or NULL + * @max_mft_no: first record which must not be allocated, or -1 + * @new_reserve_end: if not NULL, end of a free run starting after the result * * Search for a free mft record in the mft bitmap attribute on the ntfs volume * @vol. @@ -862,10 +976,12 @@ static const char *es = " Leaving inconsistent metadata. Unmount and run chkds * * Locking: Caller must hold vol->mftbmp_lock for writing. */ -static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vol, - struct ntfs_inode *base_ni) +static s64 mft_bitmap_alloc_free_rec(struct ntfs_volume *vol, + struct ntfs_inode *base_ni, + s64 max_mft_no, s64 *new_reserve_end) { s64 pass_end, ll, data_pos, pass_start, ofs, bit; + s64 initialized_mft_records; unsigned long flags; struct address_space *mftbmp_mapping; u8 *buf = NULL, *byte; @@ -882,30 +998,36 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo read_lock_irqsave(&NTFS_I(vol->mft_ino)->size_lock, flags); pass_end = NTFS_I(vol->mft_ino)->allocated_size >> vol->mft_record_size_bits; + initialized_mft_records = NTFS_I(vol->mft_ino)->initialized_size >> + vol->mft_record_size_bits; read_unlock_irqrestore(&NTFS_I(vol->mft_ino)->size_lock, flags); read_lock_irqsave(&NTFS_I(vol->mftbmp_ino)->size_lock, flags); ll = NTFS_I(vol->mftbmp_ino)->initialized_size << 3; read_unlock_irqrestore(&NTFS_I(vol->mftbmp_ino)->size_lock, flags); if (pass_end > ll) pass_end = ll; - pass = 1; - if (!base_ni) - data_pos = vol->mft_data_pos; - else - data_pos = base_ni->mft_no + 1; - if (data_pos < RESERVED_MFT_RECORDS) - data_pos = RESERVED_MFT_RECORDS; - if (data_pos >= pass_end) { - data_pos = RESERVED_MFT_RECORDS; + if (max_mft_no >= 0 && pass_end > max_mft_no) + pass_end = max_mft_no; + if (base_ni && base_ni->mft_no == FILE_MFT) { + data_pos = FILE_first_user; pass = 2; - /* This happens on a freshly formatted volume. */ if (data_pos >= pass_end) return -ENOSPC; - } - - if (base_ni && base_ni->mft_no == FILE_MFT) { - data_pos = 0; - pass = 2; + } else { + pass = 1; + if (!base_ni) + data_pos = vol->mft_data_pos; + else + data_pos = base_ni->mft_no + 1; + if (data_pos < FIRST_NORMAL_MFT_RECORD) + data_pos = FIRST_NORMAL_MFT_RECORD; + if (data_pos >= pass_end) { + data_pos = FIRST_NORMAL_MFT_RECORD; + pass = 2; + /* This happens on a freshly formatted volume. */ + if (data_pos >= pass_end) + return -ENOSPC; + } } pass_start = data_pos; @@ -940,38 +1062,28 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo size, data_pos, bit); for (; bit < size && data_pos + bit < pass_end; bit &= ~7ull, bit += 8) { - /* - * If we're extending $MFT and running out of the first - * mft record (base record) then give up searching since - * no guarantee that the found record will be accessible. - */ - if (base_ni && base_ni->mft_no == FILE_MFT && bit > 400) { - folio_unlock(folio); - kunmap_local(buf); - folio_put(folio); - return -ENOSPC; - } - byte = buf + (bit >> 3); if (*byte == 0xff) continue; - b = ffz((unsigned long)*byte); - if (b < 8 && b >= (bit & 7)) { + b = bit & 7; + for (; b < 8; b++) { + if (*byte & (1 << b)) + continue; ll = data_pos + (bit & ~7ull) + b; + if (ll >= pass_end) + break; + /* Keep the dynamic tail reserve for $MFT metadata. */ + if ((!base_ni || base_ni->mft_no != FILE_MFT) && + ll >= vol->mft_record_reserve_pos && + ll < vol->mft_record_reserve_end) + continue; if (unlikely(ll >= (1ll << 32))) { folio_unlock(folio); kunmap_local(buf); folio_put(folio); return -ENOSPC; } - *byte |= 1 << b; - folio_mark_dirty(folio); - folio_unlock(folio); - kunmap_local(buf); - folio_put(folio); - ntfs_debug("Done. (Found and allocated mft record 0x%llx.)", - ll); - return ll; + goto found; } } ntfs_debug("After inner for loop: size 0x%x, data_pos 0x%llx, bit 0x%llx", @@ -994,7 +1106,8 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo * part of the zone which we omitted earlier. */ pass_end = pass_start; - data_pos = pass_start = RESERVED_MFT_RECORDS; + data_pos = FIRST_NORMAL_MFT_RECORD; + pass_start = FIRST_NORMAL_MFT_RECORD; ntfs_debug("pass %i, pass_start 0x%llx, pass_end 0x%llx.", pass, pass_start, pass_end); if (data_pos >= pass_end) @@ -1004,6 +1117,18 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo /* No free mft records in currently initialized mft bitmap. */ ntfs_debug("Done. (No free mft records left in currently initialized mft bitmap.)"); return -ENOSPC; +found: + if (new_reserve_end) + *new_reserve_end = mft_reserve_end(buf, data_pos, + data_pos + size, ll, pass_end, + initialized_mft_records); + *byte |= 1 << b; + folio_mark_dirty(folio); + folio_unlock(folio); + kunmap_local(buf); + folio_put(folio); + ntfs_debug("Done. (Found and allocated mft record 0x%llx.)", ll); + return ll; } static int ntfs_mft_attr_extend(struct ntfs_inode *ni) @@ -1471,8 +1596,9 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol) * @vol: volume on which to extend the mft data attribute * * Extend the mft data attribute on the ntfs volume @vol by 16 mft records - * worth of clusters or if not enough space for this by one mft record worth - * of clusters. + * worth of clusters or if not enough space for this by two mft records worth + * of clusters. Keeping at least two new records breaks the recursion between + * extending $MFT and allocating a record for a new $MFT attribute extent. * * Note: Only changes allocated_size, i.e. does not touch initialized_size or * data_size. @@ -1526,10 +1652,8 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol) } lcn = rl->lcn + rl->length; ntfs_debug("Last lcn of mft data attribute is 0x%llx.", lcn); - /* Minimum allocation is one mft record worth of clusters. */ - min_nr = NTFS_B_TO_CLU(vol, vol->mft_record_size); - if (!min_nr) - min_nr = 1; + /* Keep room for the allocating record and at least one MFT reserve. */ + min_nr = DIV_ROUND_UP_ULL((u64)vol->mft_record_size * 2, vol->cluster_size); /* Want to allocate 16 mft records worth of clusters. */ nr = vol->mft_record_size << 4 >> vol->cluster_size_bits; if (!nr) @@ -1928,6 +2052,7 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n * @ni: [OUT] on success, set to the allocated ntfs inode * @base_ni: [IN] open base inode if allocating an extent mft record or NULL * @ni_mrec: [OUT] on successful return this is the mapped mft record + * @mft_data_vcn: [IN] lowest VCN of a new $MFT/$DATA extent, or -1 * * Allocate an mft record in $MFT/$DATA of an open ntfs volume @vol. * @@ -1955,30 +2080,23 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n * optimize this we start scanning at the place specified by @base_ni or if * @base_ni is NULL we start where we last stopped and we perform wrap around * when we reach the end. Note, we do not try to allocate mft records below - * number 64 because numbers 0 to 15 are the defined system files anyway and 16 - * to 64 are special in that they are used for storing extension mft records - * for the $DATA attribute of $MFT. This is required to avoid the possibility - * of creating a runlist with a circular dependency which once written to disk - * can never be read in again. Windows will only use records 16 to 24 for - * normal files if the volume is completely out of space. We never use them - * which means that when the volume is really out of space we cannot create any - * more files while Windows can still create up to 8 small files. We can start - * doing this at some later time, it does not matter much for now. + * number 24 because numbers 0 to 15 are the defined system files and records + * 16 to 23 are kept for metadata compatibility. Records reserved dynamically + * at the initialized MFT tail are skipped by normal allocation and consumed by + * $MFT metadata extent allocation. * * When scanning the mft bitmap, we only search up to the last allocated mft - * record. If there are no free records left in the range 64 to number of + * record. If there are no free records left in the range 24 to number of * allocated mft records, then we extend the $MFT/$DATA attribute in order to * create free mft records. We extend the allocated size of $MFT/$DATA by 16 * records at a time or one cluster, if cluster size is above 16kiB. If there - * is not sufficient space to do this, we try to extend by a single mft record - * or one cluster, if cluster size is above the mft record size. + * is not sufficient space to do this, we try to extend by two mft records or + * one cluster, if a cluster already contains at least two mft records. * - * No matter how many mft records we allocate, we initialize only the first - * allocated mft record, incrementing mft data size and initialized size - * accordingly, open an struct ntfs_inode for it and return it to the caller, unless - * there are less than 64 mft records, in which case we allocate and initialize - * mft records until we reach record 64 which we consider as the first free mft - * record for use by normal files. + * When extending the initialized MFT tail, we also initialize up to four + * additional records and reserve them in memory for future $MFT metadata + * extents. If there are less than 24 mft records, records are initialized + * until record 24, which is the first record used for normal files. * * If during any stage we overflow the initialized data in the mft bitmap, we * extend the initialized size (and data size) by 8 bytes, allocating another @@ -2014,9 +2132,12 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n */ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, struct ntfs_inode **ni, struct ntfs_inode *base_ni, - struct mft_record **ni_mrec) + struct mft_record **ni_mrec, const s64 mft_data_vcn) { s64 ll, bit, old_data_initialized, old_data_size; + s64 max_mft_no = -1, reserve_start = -1, reserve_end = -1; + s64 candidate_reserve_end = -1; + s64 *reserve_endp; unsigned long flags; struct folio *folio; struct ntfs_inode *mft_ni, *mftbmp_ni; @@ -2027,7 +2148,9 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, unsigned int ofs; int err; __le16 seq_no, usn; - bool record_formatted = false; + bool record_formatted = false, from_reserve = false, tail_alloc = false; + bool reserve_created = false; + bool forced_reserved_record = false; unsigned int memalloc_flags; if (base_ni && *ni) @@ -2036,6 +2159,21 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, /* @mode and @base_ni are mutually exclusive. */ if (mode && base_ni) return -EINVAL; + if (mft_data_vcn >= 0 && + (!base_ni || base_ni->mft_no != FILE_MFT)) + return -EINVAL; + if (mft_data_vcn >= 0) { + u64 vbo; + + if ((u64)mft_data_vcn > (U64_MAX >> vol->cluster_size_bits)) + return -EOVERFLOW; + vbo = (u64)mft_data_vcn << vol->cluster_size_bits; + /* + * The whole extent record must be reachable without this + * extent, including when an MFT record spans multiple clusters. + */ + max_mft_no = vbo >> vol->mft_record_size_bits; + } if (base_ni) ntfs_debug("Entering (allocating an extent mft record for base mft record 0x%llx).", @@ -2050,10 +2188,39 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mutex_lock(&mft_ni->mrec_lock); mftbmp_ni = NTFS_I(vol->mftbmp_ino); search_free_rec: + from_reserve = false; + reserve_created = false; + candidate_reserve_end = -1; if (!base_ni || base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); - bit = ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(vol, base_ni); + if (base_ni && base_ni->mft_no == FILE_MFT && + vol->mft_record_reserve_pos < vol->mft_record_reserve_end && + (max_mft_no < 0 || vol->mft_record_reserve_pos < max_mft_no)) { + bit = vol->mft_record_reserve_pos; + err = ntfs_bitmap_set_bit(vol->mftbmp_ino, bit); + if (unlikely(err)) { + ntfs_error(vol->sb, + "Failed to allocate reserved MFT record 0x%llx.", + bit); + goto err_out; + } + vol->mft_record_reserve_pos++; + from_reserve = true; + ntfs_debug("Allocated MFT metadata record 0x%llx from tail reserve.", + bit); + goto have_alloc_rec; + } + reserve_endp = vol->mft_record_reserve_pos >= + vol->mft_record_reserve_end ? &candidate_reserve_end : NULL; + bit = mft_bitmap_alloc_free_rec(vol, base_ni, max_mft_no, reserve_endp); if (bit >= 0) { + if (candidate_reserve_end > bit + 1) { + vol->mft_record_reserve_pos = bit + 1; + vol->mft_record_reserve_end = candidate_reserve_end; + reserve_created = true; + ntfs_debug("Reserved free MFT records [0x%llx, 0x%llx) for metadata.", + bit + 1, candidate_reserve_end); + } ntfs_debug("Found and allocated free record (#1), bit 0x%llx.", (long long)bit); goto have_alloc_rec; @@ -2068,6 +2235,24 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, } if (base_ni && base_ni->mft_no == FILE_MFT) { + static const u8 bootstrap_records[] = { + FILE_reserved15, FILE_reserved12, FILE_reserved13, + FILE_reserved14, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(bootstrap_records); i++) { + if (max_mft_no >= 0 && bootstrap_records[i] >= max_mft_no) + continue; + if (!mft_reserved_is_free(vol, mft_ni, + bootstrap_records[i])) + continue; + bit = bootstrap_records[i]; + forced_reserved_record = true; + ntfs_debug("Using reserved MFT record %lld to bootstrap metadata extension.", + bit); + goto have_alloc_rec; + } memalloc_nofs_restore(memalloc_flags); return bit; } @@ -2087,10 +2272,10 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, old_data_initialized = mftbmp_ni->initialized_size; read_unlock_irqrestore(&mftbmp_ni->size_lock, flags); if (old_data_initialized << 3 > ll && - old_data_initialized > RESERVED_MFT_RECORDS / 8) { + old_data_initialized << 3 > FIRST_NORMAL_MFT_RECORD) { bit = ll; - if (bit < RESERVED_MFT_RECORDS) - bit = RESERVED_MFT_RECORDS; + if (bit < FIRST_NORMAL_MFT_RECORD) + bit = FIRST_NORMAL_MFT_RECORD; if (unlikely(bit >= (1ll << 32))) goto max_err_out; ntfs_debug("Found free record (#2), bit 0x%llx.", @@ -2176,6 +2361,11 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, read_lock_irqsave(&mft_ni->size_lock, flags); old_data_initialized = mft_ni->initialized_size; read_unlock_irqrestore(&mft_ni->size_lock, flags); + tail_alloc = (!base_ni || base_ni->mft_no != FILE_MFT) && + bit >= (old_data_initialized >> vol->mft_record_size_bits) && + vol->mft_record_reserve_pos >= vol->mft_record_reserve_end; + if (tail_alloc) + ll = (bit + 2) << vol->mft_record_size_bits; if (ll <= old_data_initialized) { ntfs_debug("Allocated mft record already initialized."); goto mft_rec_already_initialized; @@ -2208,6 +2398,29 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mft_ni->initialized_size); } read_unlock_irqrestore(&mft_ni->size_lock, flags); + if (tail_alloc) { + s64 bitmap_records; + + read_lock_irqsave(&mft_ni->size_lock, flags); + reserve_end = mft_ni->allocated_size >> + vol->mft_record_size_bits; + read_unlock_irqrestore(&mft_ni->size_lock, flags); + read_lock_irqsave(&mftbmp_ni->size_lock, flags); + bitmap_records = mftbmp_ni->initialized_size << 3; + read_unlock_irqrestore(&mftbmp_ni->size_lock, flags); + if (reserve_end > bitmap_records) + reserve_end = bitmap_records; + if (reserve_end > bit + 1 + MFT_RECORD_RESERVE) + reserve_end = bit + 1 + MFT_RECORD_RESERVE; + reserve_start = bit + 1; + if (reserve_end > reserve_start) { + ll = reserve_end << vol->mft_record_size_bits; + } else { + reserve_start = -1; + reserve_end = -1; + ll = (bit + 1) << vol->mft_record_size_bits; + } + } } else if (ll > mft_ni->allocated_size) { err = -ENOSPC; goto undo_mftbmp_alloc_nolock; @@ -2276,6 +2489,12 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mark_mft_record_dirty(ctx->ntfs_ino); ntfs_attr_put_search_ctx(ctx); unmap_mft_record(mft_ni); + if (reserve_start >= 0 && reserve_end > reserve_start) { + vol->mft_record_reserve_pos = reserve_start; + vol->mft_record_reserve_end = reserve_end; + ntfs_debug("Reserved MFT records [0x%llx, 0x%llx) for metadata.", + reserve_start, reserve_end); + } read_lock_irqsave(&mft_ni->size_lock, flags); ntfs_debug("Status of mft data after mft record initialization: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.", mft_ni->allocated_size, i_size_read(vol->mft_ino), @@ -2315,8 +2534,8 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, /* If we just formatted the mft record no need to do it again. */ if (!record_formatted) { /* Sanity check that the mft record is really not in use. */ - if (ntfs_is_file_record(m->magic) && - (m->flags & MFT_RECORD_IN_USE)) { + if (!forced_reserved_record && ntfs_is_file_record(m->magic) && + (m->flags & MFT_RECORD_IN_USE)) { ntfs_warning(vol->sb, "Mft record 0x%llx was marked free in mft bitmap but is marked used itself. Unmount and run chkdsk.", bit); @@ -2389,9 +2608,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, ntfs_error(vol->sb, "Failed to map allocated extent mft record 0x%llx.", bit); err = PTR_ERR(m_tmp); - /* Set the mft record itself not in use. */ - m->flags &= cpu_to_le16( - ~le16_to_cpu(MFT_RECORD_IN_USE)); + if (forced_reserved_record) { + m->base_mft_record = 0; + m->flags |= MFT_RECORD_IN_USE; + } else { + /* Set the mft record itself not in use. */ + m->flags &= cpu_to_le16(~le16_to_cpu(MFT_RECORD_IN_USE)); + } /* Make sure the mft record is written out to disk. */ ntfs_mft_mark_dirty(folio); folio_unlock(folio); @@ -2463,7 +2686,8 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, (*ni)->mft_no = bit; if (ni_mrec) *ni_mrec = (*ni)->mrec; - ntfs_dec_free_mft_records(vol, 1); + if (!forced_reserved_record) + ntfs_dec_free_mft_records(vol, 1); return 0; undo_data_init: write_lock_irqsave(&mft_ni->size_lock, flags); @@ -2475,10 +2699,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, if (!base_ni || base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); undo_mftbmp_alloc_nolock: - if (ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) { + if (!forced_reserved_record && ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) { ntfs_error(vol->sb, "Failed to clear bit in mft bitmap.%s", es); NVolSetErrors(vol); } + if ((from_reserve || reserve_created) && + vol->mft_record_reserve_pos == bit + 1) + vol->mft_record_reserve_pos = bit; if (!base_ni || base_ni->mft_no != FILE_MFT) up_write(&vol->mftbmp_lock); err_out: @@ -2514,9 +2741,11 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) int err; u16 seq_no; __le16 old_seq_no; + __le64 old_base_mft_record; struct mft_record *ni_mrec; unsigned int memalloc_flags; struct ntfs_inode *base_ni; + bool keep_reserved; if (!vol || !ni) return -EINVAL; @@ -2529,9 +2758,23 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) /* Cache the mft reference for later. */ mft_no = ni->mft_no; - - /* Mark the mft record as not in use. */ - ni_mrec->flags &= ~MFT_RECORD_IN_USE; + if (likely(ni->nr_extents >= 0)) + base_ni = ni; + else + base_ni = ni->ext.base_ntfs_ino; + keep_reserved = mft_no >= FILE_reserved12 && + mft_no <= FILE_reserved15 && + base_ni->mft_no == FILE_MFT; + + old_base_mft_record = ni_mrec->base_mft_record; + if (keep_reserved) { + /* Restore the special, unnamed form used by reserved records. */ + ni_mrec->base_mft_record = 0; + ni_mrec->flags |= MFT_RECORD_IN_USE; + } else { + /* Mark the mft record as not in use. */ + ni_mrec->flags &= ~MFT_RECORD_IN_USE; + } /* Increment the sequence number, skipping zero, if it is not zero. */ old_seq_no = ni_mrec->sequence_number; @@ -2560,16 +2803,20 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) if (err) goto sync_rollback; - if (likely(ni->nr_extents >= 0)) - base_ni = ni; - else - base_ni = ni->ext.base_ntfs_ino; + if (keep_reserved) { + unmap_mft_record(ni); + return 0; + } /* Clear the bit in the $MFT/$BITMAP corresponding to this record. */ memalloc_flags = memalloc_nofs_save(); if (base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no); + if (!err && base_ni->mft_no == FILE_MFT && + mft_no + 1 == vol->mft_record_reserve_pos && + mft_no < vol->mft_record_reserve_end) + vol->mft_record_reserve_pos = mft_no; if (base_ni->mft_no != FILE_MFT) up_write(&vol->mftbmp_lock); memalloc_nofs_restore(memalloc_flags); @@ -2595,6 +2842,7 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) "Eeek! Rollback failed in %s. Leaving inconsistent metadata!\n", __func__); ni_mrec->flags |= MFT_RECORD_IN_USE; ni_mrec->sequence_number = old_seq_no; + ni_mrec->base_mft_record = old_base_mft_record; NInoSetDirty(ni); write_mft_record(ni, ni_mrec, 0); unmap_mft_record(ni); diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h index 75a51a98d0f6e2..ed5c1d595c0d35 100644 --- a/fs/ntfs/mft.h +++ b/fs/ntfs/mft.h @@ -78,7 +78,7 @@ static inline int write_mft_record(struct ntfs_inode *ni, struct mft_record *m, int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, struct ntfs_inode **ni, struct ntfs_inode *base_ni, - struct mft_record **ni_mrec); + struct mft_record **ni_mrec, const s64 mft_data_vcn); int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni); int ntfs_mft_records_write(const struct ntfs_volume *vol, const u64 mref, const s64 count, struct mft_record *b); diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 7091b2496facb2..fdf52fac432915 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -480,7 +480,7 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d mark_inode_dirty(dir); err = ntfs_mft_record_alloc(dir_ni->vol, mode, &ni, NULL, - &ni_mrec); + &ni_mrec, -1); if (err) { iput(vi); return ERR_PTR(err); diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h index 65fd3908af261a..2b60d14fc7ef5c 100644 --- a/fs/ntfs/volume.h +++ b/fs/ntfs/volume.h @@ -55,6 +55,10 @@ * @attrdef_size: Size of the attribute definition table in bytes. * @attrdef: Table of attribute definitions. Obtained from FILE_AttrDef. * @mft_data_pos: Mft record number at which to allocate the next mft record. + * @mft_record_reserve_pos: First record in the in-memory MFT metadata reserve + * (protected by mftbmp_lock). + * @mft_record_reserve_end: First record beyond the MFT metadata reserve + * (protected by mftbmp_lock). * @mft_zone_start: First cluster of the mft zone. * @mft_zone_end: First cluster beyond the mft zone. * @mft_zone_pos: Current position in the mft zone. @@ -119,6 +123,8 @@ struct ntfs_volume { s32 attrdef_size; struct attr_def *attrdef; s64 mft_data_pos; + s64 mft_record_reserve_pos; + s64 mft_record_reserve_end; s64 mft_zone_start; s64 mft_zone_end; s64 mft_zone_pos; From b1d732e62a5b3942546e4edaab8976258e779287 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 6 Sep 2026 11:12:47 +0900 Subject: [PATCH 0304/1417] ntfs: repack $MFT/$ATTRIBUTE LIST Repack the non-resident $MFT/$ATTRIBUTE_LIST into a contiguous run when its mapping pairs no longer fit in the base MFT record. Propagate allocation and writeback errors, and check synchronous replacement writes. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 47 ++++++++--- fs/ntfs/attrlist.c | 202 ++++++++++++++++++++++++++++++++++++++++++--- fs/ntfs/inode.c | 10 ++- 3 files changed, 236 insertions(+), 23 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 81b9e0971b3e6c..d1696ce6acd577 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -3844,13 +3844,13 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) */ if (ni->type == AT_ATTRIBUTE_LIST) { ntfs_attr_put_search_ctx(ctx); - if (ntfs_inode_free_space(base_ni, mp_size - - cur_max_mp_size)) { - ntfs_debug("Attribute list is too big. Defragment the volume\n"); - return -ENOSPC; - } - if (ntfs_attrlist_update(base_ni)) - return -EIO; + err = ntfs_inode_free_space(base_ni, mp_size - + cur_max_mp_size); + if (err) + return err; + err = ntfs_attrlist_update(base_ni); + if (err) + return err; goto retry; } @@ -4522,13 +4522,39 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz ntfs_bytes_to_cluster(vol, ni->allocated_size), first_free_vcn - ntfs_bytes_to_cluster(vol, ni->allocated_size), - lcn_seek_from, DATA_ZONE, false, false, false); + lcn_seek_from, DATA_ZONE, false, + ni->type == AT_ATTRIBUTE_LIST, false); if (IS_ERR(rl)) { ntfs_debug("Cluster allocation failed (%lld)", (long long)first_free_vcn - ntfs_bytes_to_cluster(vol, ni->allocated_size)); return PTR_ERR(rl); } + /* + * A contiguous ATTRIBUTE_LIST allocation keeps its mapping + * pairs small enough to fit in the base MFT record. The + * allocator can return a short run when contiguity was + * requested, so discard it and retry normally if necessary. + */ + if (ni->type == AT_ATTRIBUTE_LIST && + (rl->vcn != ntfs_bytes_to_cluster(vol, + ni->allocated_size) || + rl->length != first_free_vcn - + ntfs_bytes_to_cluster(vol, ni->allocated_size) || + rl[1].length)) { + ntfs_cluster_free_from_rl(vol, rl); + kvfree(rl); + rl = ntfs_cluster_alloc(vol, + ntfs_bytes_to_cluster(vol, + ni->allocated_size), + first_free_vcn - + ntfs_bytes_to_cluster(vol, + ni->allocated_size), + lcn_seek_from, DATA_ZONE, false, + false, false); + if (IS_ERR(rl)) + return PTR_ERR(rl); + } } if (!NInoCompressed(ni)) { @@ -4921,7 +4947,8 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo ntfs_debug("Entering for inode 0x%llx, attr 0x%x, size %lld\n", (unsigned long long)ni->mft_no, ni->type, newsize); - if (ni->data_size == newsize) { + if (ni->data_size == newsize && + (!prealloc_size || prealloc_size <= ni->allocated_size)) { ntfs_debug("Size is already ok\n"); return 0; } @@ -4936,7 +4963,7 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo } if (NInoNonResident(ni)) { - if (newsize > ni->data_size) + if (newsize > ni->data_size || prealloc_size > ni->allocated_size) err = ntfs_non_resident_attr_expand(ni, newsize, prealloc_size, NVolDisableSparse(ni->vol) ? HOLES_NO : HOLES_OK, true); diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c index be3086d3433816..4e60f7e930102d 100644 --- a/fs/ntfs/attrlist.c +++ b/fs/ntfs/attrlist.c @@ -12,6 +12,9 @@ #include "mft.h" #include "attrib.h" #include "attrlist.h" +#include "lcnalloc.h" + +#define NTFS_MAX_ATTR_LIST_SIZE (256 * 1024) /* * ntfs_attrlist_need - check whether inode need attribute list @@ -51,11 +54,151 @@ int ntfs_attrlist_need(struct ntfs_inode *ni) return 0; } +/* + * Repack the $MFT/$ATTRIBUTE_LIST data into one run. + * + * The mapping pairs for an $ATTRIBUTE_LIST must remain in the base MFT + * record. Once that record has no room left, extending a fragmented list + * can require one more mapping-pairs byte than the record can hold. There + * is no attribute that can legally be moved out in that state: $STANDARD_ + * INFORMATION, $ATTRIBUTE_LIST, and the first $MFT/$DATA extent all have to + * stay in the base record. Move the list data to one contiguous run. The + * caller supplies the minimum allocation size so a recovery can use the + * smallest useful run while normal updates can still request the maximum + * legal list size as a reserve. + */ +static int ntfs_attrlist_repack(struct inode *attr_vi, + struct ntfs_inode *attr_ni, s64 min_alloc_size) +{ + struct ntfs_volume *vol = attr_ni->vol; + struct runlist_element *old_rl, *new_rl; + u8 *data = NULL; + s64 data_size, alloc_size, nr_clusters, written; + s64 old_alloc_size; + size_t old_rl_count, new_rl_count; + unsigned long flags; + int err, restore_err; + + if (attr_ni->mft_no != FILE_MFT || !NInoNonResident(attr_ni) || + min_alloc_size < 0) + return -EINVAL; + + err = ntfs_attr_map_whole_runlist(attr_ni); + if (err) + return err; + + data_size = attr_ni->data_size; + if (data_size < 0) + return -EIO; + + if (data_size) { + data = kvmalloc(data_size, GFP_NOFS); + if (!data) + return -ENOMEM; + + written = ntfs_inode_attr_pread(attr_vi, 0, data_size, data); + if (written != data_size) { + err = written < 0 ? (int)written : -EIO; + goto out_free_data; + } + } + + old_alloc_size = attr_ni->allocated_size; + alloc_size = max_t(s64, old_alloc_size, min_alloc_size); + nr_clusters = ntfs_bytes_to_cluster(vol, + alloc_size + vol->cluster_size - 1); + if (nr_clusters <= 0) { + err = -EFBIG; + goto out_free_data; + } + + /* A single run keeps the mapping pairs at the minimum size. */ + new_rl = ntfs_cluster_alloc(vol, 0, nr_clusters, -1, DATA_ZONE, + true, true, false); + if (IS_ERR(new_rl)) { + err = PTR_ERR(new_rl); + goto out_free_data; + } + + new_rl_count = 0; + if (new_rl->vcn == 0 && new_rl->length == nr_clusters && + !new_rl[1].length) + new_rl_count = 2; + + if (new_rl_count != 2) { + ntfs_cluster_free_from_rl(vol, new_rl); + kvfree(new_rl); + err = -ENOSPC; + goto out_free_data; + } + + old_rl = attr_ni->runlist.rl; + old_rl_count = attr_ni->runlist.count; + down_write(&attr_ni->runlist.lock); + attr_ni->runlist.rl = new_rl; + attr_ni->runlist.count = new_rl_count; + up_write(&attr_ni->runlist.lock); + + write_lock_irqsave(&attr_ni->size_lock, flags); + attr_ni->allocated_size = ntfs_cluster_to_bytes(vol, nr_clusters); + write_unlock_irqrestore(&attr_ni->size_lock, flags); + + /* Populate the replacement extent before publishing its mapping pairs. */ + if (data_size) { + written = ntfs_inode_attr_pwrite(attr_vi, 0, data_size, data, true); + if (written != data_size) { + err = written < 0 ? (int)written : -EIO; + goto restore_old_runlist; + } + } + + err = ntfs_attr_update_mapping_pairs(attr_ni, 0); + if (err) + goto restore_old_runlist; + + /* The new mapping is now authoritative; release the old data runs. */ + if (ntfs_cluster_free_from_rl(vol, old_rl)) { + ntfs_error(vol->sb, + "Failed to free old ATTRIBUTE_LIST extent: inode %#llx", + (long long)attr_ni->mft_no); + NVolSetErrors(vol); + } + kvfree(old_rl); + kvfree(data); + return 0; + +restore_old_runlist: + down_write(&attr_ni->runlist.lock); + attr_ni->runlist.rl = old_rl; + attr_ni->runlist.count = old_rl_count; + up_write(&attr_ni->runlist.lock); + + write_lock_irqsave(&attr_ni->size_lock, flags); + attr_ni->allocated_size = old_alloc_size; + write_unlock_irqrestore(&attr_ni->size_lock, flags); + + restore_err = ntfs_attr_update_mapping_pairs(attr_ni, 0); + if (restore_err) { + ntfs_error(vol->sb, "Failed to restore ATTRIBUTE_LIST mapping pairs (%d)", + restore_err); + NVolSetErrors(vol); + } + + ntfs_cluster_free_from_rl(vol, new_rl); + kvfree(new_rl); + err = err ? err : restore_err; + +out_free_data: + kvfree(data); + return err; +} + int ntfs_attrlist_update(struct ntfs_inode *base_ni) { struct inode *attr_vi; struct ntfs_inode *attr_ni; - int err; + s64 written; + int err, retry_err; /* * generic_shutdown_super() clears SB_ACTIVE before evicting cached @@ -74,21 +217,55 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) attr_ni = NTFS_I(attr_vi); err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO); - if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT) { - err = ntfs_attr_truncate(attr_ni, 0); - if (err || ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO) != 0) { + if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT && + NInoNonResident(attr_ni)) { + retry_err = ntfs_attrlist_repack(attr_vi, attr_ni, + base_ni->attr_list_size); + if (retry_err) { + ntfs_error(base_ni->vol->sb, "Failed to repack attribute list"); iput(attr_vi); + return retry_err; + } + + retry_err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, + HOLES_NO); + if (retry_err) { ntfs_error(base_ni->vol->sb, - "Failed to truncate attribute list of inode %#llx", - (long long)base_ni->mft_no); - return -EIO; + "Failed to resize attribute list after repack"); + iput(attr_vi); + return retry_err; } } else if (err) { iput(attr_vi); ntfs_error(base_ni->vol->sb, "Failed to truncate attribute list of inode %#llx", (long long)base_ni->mft_no); - return -EIO; + return err; + } + + /* + * Reserve the maximum legal list size while the MFT metadata area is + * still easy to allocate contiguously. This prevents a later list entry + * from needing another mapping-pairs byte in the full base MFT record. + * Failure to obtain the optional reserve must not reject the current + * metadata update; the repack retry above remains available if needed. + */ + if (base_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni) && + attr_ni->allocated_size < NTFS_MAX_ATTR_LIST_SIZE) { + retry_err = ntfs_attr_expand(attr_ni, base_ni->attr_list_size, + NTFS_MAX_ATTR_LIST_SIZE); + if (retry_err == -ENOSPC) { + retry_err = ntfs_attrlist_repack(attr_vi, attr_ni, + NTFS_MAX_ATTR_LIST_SIZE); + if (retry_err == -ENOSPC) + retry_err = 0; + } + if (retry_err) { + ntfs_error(base_ni->vol->sb, + "Failed to reserve attribute list space"); + iput(attr_vi); + return retry_err; + } } i_size_write(attr_vi, base_ni->attr_list_size); @@ -96,14 +273,15 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni)) NInoSetAttrListNonResident(base_ni); - if (ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size, - base_ni->attr_list, false) != - base_ni->attr_list_size) { + written = ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size, + base_ni->attr_list, false); + if (written != base_ni->attr_list_size) { + err = written < 0 ? (int)written : -EIO; iput(attr_vi); ntfs_error(base_ni->vol->sb, "Failed to write attribute list of inode %#llx", (long long)base_ni->mft_no); - return -EIO; + return err; } NInoSetAttrListDirty(base_ni); diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 5aedc045f65aa0..332825db477abb 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -3745,6 +3745,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, u64 rl_length = 0; s64 vcn; struct runlist_element *rl; + int bio_err; lcn_count = max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len)); vcn = ntfs_pidx_to_cluster(vol, folio->index); @@ -3787,8 +3788,15 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, goto err_unlock_folio; } - submit_bio_wait(bio); + bio_err = submit_bio_wait(bio); bio_put(bio); + if (bio_err) { + ntfs_error(vi->i_sb, + "Synchronous attribute write failed (%d)", + bio_err); + ret = bio_err; + goto err_unlock_folio; + } vcn += rl_length; offset += length; } while (lcn_count != 0); From 631946431ddc66a472c5cc629cd654e62dfa1f88 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 4 Sep 2026 14:46:38 +0900 Subject: [PATCH 0305/1417] ntfs: account for MFT records added during allocation When no free MFT record is available in the initialized $MFT/$BITMAP, ntfs_mft_record_alloc() extends $MFT/$DATA and formats the requested record together with a dynamically sized tail reserve. Those records become visible through the $MFT file size before charging the requested record to the free-record counter. Account for all newly visible records before releasing the MFT allocation lock, then subtract the one record being allocated. Keep MFT counter updates independent of the asynchronous free-cluster scan and update the counter when a record is successfully cleared in the MFT bitmap. Store the clamped result of the MFT bitmap scan and keep statfs from exposing an invalid cached count if an accounting error occurs. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/mft.c | 11 ++++++++--- fs/ntfs/super.c | 12 +++++++++--- fs/ntfs/volume.h | 6 ------ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 61d4ee3a57fdda..f0656d0bbeebed 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -1539,7 +1539,6 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol) ret = ntfs_attr_set(mftbmp_ni, old_initialized_size, 8, 0); if (likely(!ret)) { ntfs_debug("Done. (Wrote eight initialized bytes to mft bitmap."); - ntfs_inc_free_mft_records(vol, 8 * 8); return 0; } ntfs_error(vol->sb, "Failed to write to mft bitmap."); @@ -2135,6 +2134,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, struct mft_record **ni_mrec, const s64 mft_data_vcn) { s64 ll, bit, old_data_initialized, old_data_size; + s64 nr_new_mft_records = 0; s64 max_mft_no = -1, reserve_start = -1, reserve_end = -1; s64 candidate_reserve_end = -1; s64 *reserve_endp; @@ -2501,8 +2501,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mft_ni->initialized_size); WARN_ON(i_size_read(vol->mft_ino) > mft_ni->allocated_size); WARN_ON(mft_ni->initialized_size > i_size_read(vol->mft_ino)); + nr_new_mft_records = (i_size_read(vol->mft_ino) - old_data_size) >> + vol->mft_record_size_bits; read_unlock_irqrestore(&mft_ni->size_lock, flags); mft_rec_already_initialized: + /* Account for newly visible MFT records before dropping the lock. */ + if (nr_new_mft_records > 0) + ntfs_inc_free_mft_records(vol, nr_new_mft_records); /* * We can finally drop the mft bitmap lock as the mft data attribute * has been fully updated. The only disparity left is that the @@ -2813,6 +2818,8 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) if (base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no); + if (!err) + ntfs_inc_free_mft_records(vol, 1); if (!err && base_ni->mft_no == FILE_MFT && mft_no + 1 == vol->mft_record_reserve_pos && mft_no < vol->mft_record_reserve_end) @@ -2822,9 +2829,7 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) memalloc_nofs_restore(memalloc_flags); if (err) goto bitmap_rollback; - unmap_mft_record(ni); - ntfs_inc_free_mft_records(vol, 1); return 0; /* Rollback what we did... */ diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 60d43339c590a0..0867d6a82ebe20 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -2064,8 +2064,7 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol, /* If errors occurred we may well have gone below zero, fix this. */ if (nr_free < 0) nr_free = 0; - else - atomic64_set(&vol->free_mft_records, nr_free); + atomic64_set(&vol->free_mft_records, nr_free); ntfs_debug("Exiting."); return nr_free; @@ -2131,7 +2130,14 @@ static int ntfs_statfs(struct dentry *dentry, struct kstatfs *sfs) read_unlock_irqrestore(&mft_ni->size_lock, flags); /* Free inodes in fs (based on current total count). */ - sfs->f_ffree = atomic64_read(&vol->free_mft_records); + size = atomic64_read(&vol->free_mft_records); + if (unlikely(size < 0 || size > (s64)sfs->f_files)) + ntfs_warning(vol->sb, "Invalid free MFT record count %lld.", size); + if (size < 0) + size = 0; + else if (size > (s64)sfs->f_files) + size = sfs->f_files; + sfs->f_ffree = size; /* * File system id. This is extremely *nix flavour dependent and even diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h index 2b60d14fc7ef5c..bc85a95922458a 100644 --- a/fs/ntfs/volume.h +++ b/fs/ntfs/volume.h @@ -258,17 +258,11 @@ static inline void ntfs_dec_free_clusters(struct ntfs_volume *vol, s64 nr) static inline void ntfs_inc_free_mft_records(struct ntfs_volume *vol, s64 nr) { - if (!NVolFreeClusterKnown(vol)) - return; - atomic64_add(nr, &vol->free_mft_records); } static inline void ntfs_dec_free_mft_records(struct ntfs_volume *vol, s64 nr) { - if (!NVolFreeClusterKnown(vol)) - return; - atomic64_sub(nr, &vol->free_mft_records); } From 91709ba5d6d709b2b663287b7e871e2c6b480502 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 6 Sep 2026 21:37:28 +0900 Subject: [PATCH 0306/1417] ntfs: protect runlist updates with the runlist lock ntfs_non_resident_attr_shrink() calls runlist helpers that require the runlist write lock, but did not hold it while freeing clusters and truncating the runlist. Serialize those operations and the resident conversion with the runlist lock. ntfs_attr_map_cluster() can merge a newly allocated run before updating mapping pairs. If the update fails, free the clusters and restore both the in-memory runlist and on-disk mapping pairs from a saved runlist. Mark the volume in error if either rollback step fails. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 205 +++++++++++++++++++++++++++++++++++---------- fs/ntfs/attrib.h | 9 ++ fs/ntfs/attrlist.c | 46 ++++++---- fs/ntfs/attrlist.h | 2 + fs/ntfs/compress.c | 2 +- fs/ntfs/file.c | 3 +- fs/ntfs/inode.c | 2 +- fs/ntfs/mft.c | 13 +-- 8 files changed, 216 insertions(+), 66 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index d1696ce6acd577..c949ff7650759c 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -3574,7 +3574,8 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra) * update allocated and compressed size. */ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, - struct mft_record *m, struct ntfs_attr_search_ctx *ctx) + struct mft_record *m, struct ntfs_attr_search_ctx *ctx, + struct ntfs_inode *locked_ni, bool defer_attrlist) { int sparse, err = 0; struct ntfs_inode *base_ni; @@ -3610,6 +3611,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, le16_to_cpu(a->data.non_resident.mapping_pairs_offset) == 8) && !(le32_to_cpu(m->bytes_allocated) - le32_to_cpu(m->bytes_in_use))) { + if (defer_attrlist) + return -ENOSPC; if (!NInoAttrList(base_ni)) { err = ntfs_inode_add_attrlist(base_ni); if (err) @@ -3623,7 +3626,7 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, goto out; } - err = ntfs_attrlist_update(base_ni); + err = ntfs_attrlist_update_locked(base_ni, locked_ni); if (err) goto out; err = -EAGAIN; @@ -3703,6 +3706,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, * ntfs_attr_update_mapping_pairs - update mapping pairs for ntfs attribute * @ni: non-resident ntfs inode for which we need update * @from_vcn: update runlist starting this VCN + * @locked_ni: inode whose runlist write lock is already held + * @defer_attrlist: return -ENOSPC instead of updating an attribute list * * Build mapping pairs from @na->rl and write them to the disk. Also, this * function updates sparse bit, allocated and compressed size (allocates/frees @@ -3712,7 +3717,10 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, * call to this function. Vice-versa @na->compressed_size will be calculated and * set to correct value during this function. */ -int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) +static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, + s64 from_vcn, + struct ntfs_inode *locked_ni, + bool defer_attrlist) { struct ntfs_attr_search_ctx *ctx; struct ntfs_inode *base_ni; @@ -3804,7 +3812,8 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) continue; } - err = ntfs_attr_update_meta(a, ni, m, ctx); + err = ntfs_attr_update_meta(a, ni, m, ctx, locked_ni, + defer_attrlist); if (err < 0) { if (err == -EAGAIN) { ntfs_attr_put_search_ctx(ctx); @@ -3844,11 +3853,17 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) */ if (ni->type == AT_ATTRIBUTE_LIST) { ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + if (locked_ni == ni || defer_attrlist) { + err = -ENOSPC; + goto put_err_out; + } err = ntfs_inode_free_space(base_ni, mp_size - cur_max_mp_size); if (err) return err; - err = ntfs_attrlist_update(base_ni); + err = ntfs_attrlist_update_locked( + base_ni, locked_ni); if (err) return err; goto retry; @@ -3856,6 +3871,10 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) /* Add attribute list if it isn't present, and retry. */ if (!NInoAttrList(base_ni)) { + if (defer_attrlist) { + err = -ENOSPC; + goto put_err_out; + } ntfs_attr_put_search_ctx(ctx); if (ntfs_inode_add_attrlist(base_ni)) { ntfs_error(sb, "Can not add attrlist"); @@ -3883,13 +3902,21 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) } } + if (defer_attrlist && + (ctx->ntfs_ino->nr_extents == -1 || + NInoAttrList(ctx->ntfs_ino)) && + ctx->attr->type != AT_ATTRIBUTE_LIST) { + err = -ENOSPC; + goto put_err_out; + } + /* Update lowest vcn. */ a->data.non_resident.lowest_vcn = cpu_to_le64(stop_vcn); mark_mft_record_dirty(ctx->ntfs_ino); if ((ctx->ntfs_ino->nr_extents == -1 || NInoAttrList(ctx->ntfs_ino)) && ctx->attr->type != AT_ATTRIBUTE_LIST) { ctx->al_entry->lowest_vcn = cpu_to_le64(stop_vcn); - err = ntfs_attrlist_update(base_ni); + err = ntfs_attrlist_update_locked(base_ni, locked_ni); if (err) goto put_err_out; } @@ -4064,6 +4091,19 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) return err; } +int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni, + s64 from_vcn, + struct ntfs_inode *locked_ni) +{ + return __ntfs_attr_update_mapping_pairs(ni, from_vcn, locked_ni, + false); +} + +int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) +{ + return ntfs_attr_update_mapping_pairs_locked(ni, from_vcn, NULL); +} + /* * ntfs_attr_make_resident - convert a non-resident to a resident attribute * @ni: open ntfs attribute to make resident @@ -4197,7 +4237,9 @@ static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_searc * * Reduce the size of a non-resident, open ntfs attribute @na to @newsize bytes. */ -static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsize) +static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, + const s64 newsize, + struct ntfs_inode *locked_ni) { struct ntfs_volume *vol; struct ntfs_attr_search_ctx *ctx; @@ -4205,6 +4247,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz s64 nr_freed_clusters; int err; struct ntfs_inode *base_ni; + bool runlist_locked = locked_ni == ni; ntfs_debug("Inode 0x%llx attr 0x%x new size %lld\n", (unsigned long long)ni->mft_no, ni->type, (long long)newsize); @@ -4250,18 +4293,24 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz * clusters if there is a change. */ if (ntfs_bytes_to_cluster(vol, ni->allocated_size) != first_free_vcn) { - struct ntfs_attr_search_ctx *ctx; + /* + * ntfs_cluster_free() and ntfs_rl_truncate_nolock() + * both require this lock. + */ + if (!runlist_locked) + down_write(&ni->runlist.lock); err = ntfs_attr_map_whole_runlist(ni); if (err) { ntfs_debug("Eeek! ntfs_attr_map_whole_runlist failed.\n"); - return err; + goto unlock_runlist; } ctx = ntfs_attr_get_search_ctx(ni, NULL); if (!ctx) { ntfs_error(vol->sb, "%s: Failed to get search context", __func__); - return -ENOMEM; + err = -ENOMEM; + goto unlock_runlist; } /* Deallocate all clusters starting with the first free one. */ @@ -4269,7 +4318,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz if (nr_freed_clusters < 0) { ntfs_debug("Eeek! Freeing of clusters failed. Aborting...\n"); ntfs_attr_put_search_ctx(ctx); - return (int)nr_freed_clusters; + err = (int)nr_freed_clusters; + goto unlock_runlist; } ntfs_attr_put_search_ctx(ctx); @@ -4282,7 +4332,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz kvfree(ni->runlist.rl); ni->runlist.rl = NULL; ntfs_error(vol->sb, "Eeek! Run list truncation failed.\n"); - return -EIO; + err = -EIO; + goto unlock_runlist; } /* Prepare to mapping pairs update. */ @@ -4298,11 +4349,13 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz VFS_I(base_ni)->i_blocks = ni->allocated_size >> 9; /* Write mapping pairs for new runlist. */ - err = ntfs_attr_update_mapping_pairs(ni, 0 /*first_free_vcn*/); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (err) { ntfs_debug("Eeek! Mapping pairs update failed. Leaving inconstant metadata. Run chkdsk.\n"); - return err; + goto unlock_runlist; } + if (!runlist_locked) + up_write(&ni->runlist.lock); } /* Get the first attribute record. */ @@ -4344,7 +4397,11 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz /* If the attribute now has zero size, make it resident. */ if (!newsize && !NInoEncrypted(ni) && !NInoCompressed(ni)) { + if (!runlist_locked) + down_write(&ni->runlist.lock); err = ntfs_attr_make_resident(ni, ctx); + if (!runlist_locked) + up_write(&ni->runlist.lock); if (err) { /* If couldn't make resident, just continue. */ if (err != -EPERM) @@ -4361,6 +4418,11 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz put_err_out: ntfs_attr_put_search_ctx(ctx); return err; + +unlock_runlist: + if (!runlist_locked) + up_write(&ni->runlist.lock); + return err; } /* @@ -4369,13 +4431,14 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz * @prealloc_size: preallocation size (in bytes) to which to expand the attribute * @newsize: new size (in bytes) to which to expand the attribute * @holes: how to create a hole if expanding - * @need_lock: whether mrec lock is needed or not + * @locked_ni: inode whose runlist lock is already held * * Expand the size of a non-resident, open ntfs attribute @na to @newsize bytes, * by allocating new clusters. */ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsize, - const s64 prealloc_size, unsigned int holes, bool need_lock) + const s64 prealloc_size, unsigned int holes, + struct ntfs_inode *locked_ni) { s64 lcn_seek_from; s64 first_free_vcn; @@ -4573,7 +4636,8 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz /* Prepare to mapping pairs update. */ ni->allocated_size = ntfs_cluster_to_bytes(vol, first_free_vcn); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked( + ni, 0, locked_ni); if (err) { ntfs_debug("Mapping pairs update failed"); goto rollback; @@ -4617,11 +4681,11 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz ntfs_debug("Leaking clusters"); /* Now, truncate the runlist itself. */ - if (need_lock) + if (ni != locked_ni) down_write(&ni->runlist.lock); err2 = ntfs_rl_truncate_nolock(vol, &ni->runlist, ntfs_bytes_to_cluster(vol, org_alloc_size)); - if (need_lock) + if (ni != locked_ni) up_write(&ni->runlist.lock); if (err2) { /* @@ -4635,11 +4699,11 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz /* Prepare to mapping pairs update. */ ni->allocated_size = org_alloc_size; /* Restore mapping pairs. */ - if (need_lock) + if (ni != locked_ni) down_read(&ni->runlist.lock); - if (ntfs_attr_update_mapping_pairs(ni, 0)) + if (__ntfs_attr_update_mapping_pairs(ni, 0, locked_ni, true)) ntfs_error(sb, "Failed to restore old mapping pairs"); - if (need_lock) + if (ni != locked_ni) up_read(&ni->runlist.lock); if (NInoSparse(ni) || NInoCompressed(ni)) { @@ -4744,7 +4808,8 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi mark_mft_record_dirty(ctx->ntfs_ino); ntfs_attr_put_search_ctx(ctx); /* Resize non-resident attribute */ - return ntfs_non_resident_attr_expand(attr_ni, newsize, prealloc_size, holes, true); + return ntfs_non_resident_attr_expand( + attr_ni, newsize, prealloc_size, holes, NULL); } else if (err != -ENOSPC && err != -EPERM) { ntfs_error(sb, "Failed to make attribute non-resident"); goto put_err_out; @@ -4919,13 +4984,14 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, if (NInoNonResident(ni)) { if (newsize > i_size) { down_write(&ni->runlist.lock); - err = ntfs_non_resident_attr_expand(ni, newsize, 0, - NVolDisableSparse(ni->vol) ? - HOLES_NO : HOLES_OK, - false); + err = ntfs_non_resident_attr_expand( + ni, newsize, 0, + NVolDisableSparse(ni->vol) ? + HOLES_NO : HOLES_OK, ni); up_write(&ni->runlist.lock); } else - err = ntfs_non_resident_attr_shrink(ni, newsize); + err = ntfs_non_resident_attr_shrink( + ni, newsize, NULL); } else err = ntfs_resident_attr_resize(ni, newsize, 0, NVolDisableSparse(ni->vol) ? @@ -4934,7 +5000,9 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, return err; } -int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size) +int ntfs_attr_expand_locked(struct ntfs_inode *ni, const s64 newsize, + const s64 prealloc_size, + struct ntfs_inode *locked_ni) { int err = 0; @@ -4964,9 +5032,10 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo if (NInoNonResident(ni)) { if (newsize > ni->data_size || prealloc_size > ni->allocated_size) - err = ntfs_non_resident_attr_expand(ni, newsize, prealloc_size, - NVolDisableSparse(ni->vol) ? - HOLES_NO : HOLES_OK, true); + err = ntfs_non_resident_attr_expand( + ni, newsize, prealloc_size, + NVolDisableSparse(ni->vol) ? + HOLES_NO : HOLES_OK, locked_ni); } else err = ntfs_resident_attr_resize(ni, newsize, prealloc_size, NVolDisableSparse(ni->vol) ? @@ -4977,6 +5046,12 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo return err; } +int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, + const s64 prealloc_size) +{ + return ntfs_attr_expand_locked(ni, newsize, prealloc_size, NULL); +} + /* * ntfs_attr_truncate_i - resize an ntfs attribute * @ni: open ntfs inode to resize @@ -4989,7 +5064,9 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo * newly allocated space is marked as not initialised and no real allocation * on disk is performed. */ -int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes) +int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize, + unsigned int holes, + struct ntfs_inode *locked_ni) { int err; @@ -5023,15 +5100,23 @@ int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int if (NInoNonResident(ni)) { if (newsize > ni->data_size) - err = ntfs_non_resident_attr_expand(ni, newsize, 0, holes, true); + err = ntfs_non_resident_attr_expand( + ni, newsize, 0, holes, locked_ni); else - err = ntfs_non_resident_attr_shrink(ni, newsize); + err = ntfs_non_resident_attr_shrink( + ni, newsize, locked_ni); } else err = ntfs_resident_attr_resize(ni, newsize, 0, holes); ntfs_debug("Return status %d\n", err); return err; } +int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, + unsigned int holes) +{ + return ntfs_attr_truncate_i_locked(ni, newsize, holes, NULL); +} + /* * Resize an attribute, creating a hole if relevant */ @@ -5049,10 +5134,11 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, struct ntfs_volume *vol = ni->vol; struct ntfs_attr_search_ctx *ctx; struct runlist_element *rl, *rlc; + struct runlist_element *old_rl = NULL; s64 vcn = vcn_start, lcn, clu_count; s64 lcn_seek_from = -1; int err = 0; - size_t new_rl_count; + size_t new_rl_count, old_rl_count; err = ntfs_attr_map_whole_runlist(ni); if (err) @@ -5145,6 +5231,19 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, WARN_ON(rlc->vcn != vcn); lcn = rlc->lcn; clu_count = rlc->length; + old_rl_count = ni->runlist.count; + old_rl = kmemdup(ni->runlist.rl, + old_rl_count * sizeof(*old_rl), GFP_NOFS); + if (!old_rl) { + err = -ENOMEM; + if (ntfs_cluster_free_from_rl(vol, rlc)) { + ntfs_error(vol->sb, + "Failed to free cluster allocation after runlist backup failure."); + NVolSetErrors(vol); + } + kvfree(rlc); + goto out; + } rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, &new_rl_count); if (IS_ERR(rl)) { @@ -5168,15 +5267,32 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, if (update_mp) { ntfs_attr_reinit_search_ctx(ctx); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (err) { int err2; err2 = ntfs_cluster_free(ni, vcn, clu_count, ctx); - if (err2 < 0) + if (err2 < 0 || err2 != clu_count) { ntfs_error(vol->sb, - "Failed to free cluster allocation. Leaving inconstant metadata.\n"); - goto out; + "Failed to free cluster allocation. Leaving inconsistent metadata.\n"); + NVolSetErrors(vol); + goto out; + } + + /* + * Restore the runlist before repairing the on-disk + * mapping pairs. + */ + kvfree(ni->runlist.rl); + ni->runlist.rl = old_rl; + ni->runlist.count = old_rl_count; + old_rl = NULL; + if (ntfs_attr_update_mapping_pairs_locked( + ni, 0, ni)) { + ntfs_error(vol->sb, + "Failed to restore mapping pairs after allocation rollback.\n"); + NVolSetErrors(vol); + } } } else { VFS_I(ni)->i_blocks += clu_count << (vol->cluster_size_bits - 9); @@ -5188,6 +5304,7 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, *lcn_count = clu_count; *balloc = true; out: + kvfree(old_rl); ntfs_attr_put_search_ctx(ctx); return err; } @@ -5431,7 +5548,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6 ni->data_size += ntfs_cluster_to_bytes(vol, len); if (ntfs_cluster_to_bytes(vol, start_vcn) < ni->initialized_size) ni->initialized_size += ntfs_cluster_to_bytes(vol, len); - ret = ntfs_attr_update_mapping_pairs(ni, 0); + ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); up_write(&ni->runlist.lock); if (ret) return ret; @@ -5516,7 +5633,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn, } if (ni->allocated_size > 0) { - ret = ntfs_attr_update_mapping_pairs(ni, 0); + ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (ret) { up_write(&ni->runlist.lock); goto out_rl; @@ -5594,7 +5711,7 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 ni->runlist.rl = rl; ni->runlist.count = new_rl_count; - ret = ntfs_attr_update_mapping_pairs(ni, 0); + ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); up_write(&ni->runlist.lock); if (ret) { kvfree(punch_rl); @@ -5770,7 +5887,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo if (NInoRunlistDirty(ni)) { mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); down_write(&ni->runlist.lock); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (err) ntfs_error(ni->vol->sb, "Updating mapping pairs failed"); else diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h index e2224fbfaabe95..6b4fa9f576401f 100644 --- a/fs/ntfs/attrib.h +++ b/fs/ntfs/attrib.h @@ -112,7 +112,13 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, const s64 i_size); int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size); +int ntfs_attr_expand_locked(struct ntfs_inode *ni, const s64 newsize, + const s64 prealloc_size, + struct ntfs_inode *locked_ni); int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes); +int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize, + unsigned int holes, + struct ntfs_inode *locked_ni); int ntfs_attr_truncate(struct ntfs_inode *ni, const s64 newsize); int ntfs_attr_rm(struct ntfs_inode *ni); int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name, @@ -133,6 +139,9 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, __le16 *name, u8 name_len, u8 *val, u32 size, __le16 flags); int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn); +int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni, + s64 from_vcn, + struct ntfs_inode *locked_ni); struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn); /* diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c index 4e60f7e930102d..bb191953dcb1b1 100644 --- a/fs/ntfs/attrlist.c +++ b/fs/ntfs/attrlist.c @@ -68,7 +68,8 @@ int ntfs_attrlist_need(struct ntfs_inode *ni) * legal list size as a reserve. */ static int ntfs_attrlist_repack(struct inode *attr_vi, - struct ntfs_inode *attr_ni, s64 min_alloc_size) + struct ntfs_inode *attr_ni, s64 min_alloc_size, + struct ntfs_inode *locked_ni) { struct ntfs_volume *vol = attr_ni->vol; struct runlist_element *old_rl, *new_rl; @@ -78,10 +79,12 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, size_t old_rl_count, new_rl_count; unsigned long flags; int err, restore_err; - if (attr_ni->mft_no != FILE_MFT || !NInoNonResident(attr_ni) || min_alloc_size < 0) return -EINVAL; + /* The buffered I/O below can reacquire the attribute runlist lock. */ + if (attr_ni == locked_ni) + return -ENOSPC; err = ntfs_attr_map_whole_runlist(attr_ni); if (err) @@ -131,7 +134,6 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, err = -ENOSPC; goto out_free_data; } - old_rl = attr_ni->runlist.rl; old_rl_count = attr_ni->runlist.count; down_write(&attr_ni->runlist.lock); @@ -152,7 +154,7 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, } } - err = ntfs_attr_update_mapping_pairs(attr_ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(attr_ni, 0, locked_ni); if (err) goto restore_old_runlist; @@ -177,7 +179,8 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, attr_ni->allocated_size = old_alloc_size; write_unlock_irqrestore(&attr_ni->size_lock, flags); - restore_err = ntfs_attr_update_mapping_pairs(attr_ni, 0); + restore_err = ntfs_attr_update_mapping_pairs_locked( + attr_ni, 0, locked_ni); if (restore_err) { ntfs_error(vol->sb, "Failed to restore ATTRIBUTE_LIST mapping pairs (%d)", restore_err); @@ -193,7 +196,8 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, return err; } -int ntfs_attrlist_update(struct ntfs_inode *base_ni) +int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni, + struct ntfs_inode *locked_ni) { struct inode *attr_vi; struct ntfs_inode *attr_ni; @@ -215,20 +219,27 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) return err; } attr_ni = NTFS_I(attr_vi); + /* Truncation and page-cache writes can reacquire this runlist lock. */ + if (attr_ni == locked_ni) { + iput(attr_vi); + return -ENOSPC; + } - err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO); + err = ntfs_attr_truncate_i_locked( + attr_ni, base_ni->attr_list_size, HOLES_NO, locked_ni); if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni)) { retry_err = ntfs_attrlist_repack(attr_vi, attr_ni, - base_ni->attr_list_size); + base_ni->attr_list_size, locked_ni); if (retry_err) { ntfs_error(base_ni->vol->sb, "Failed to repack attribute list"); iput(attr_vi); return retry_err; } - retry_err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, - HOLES_NO); + retry_err = ntfs_attr_truncate_i_locked( + attr_ni, base_ni->attr_list_size, + HOLES_NO, locked_ni); if (retry_err) { ntfs_error(base_ni->vol->sb, "Failed to resize attribute list after repack"); @@ -252,11 +263,13 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) */ if (base_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni) && attr_ni->allocated_size < NTFS_MAX_ATTR_LIST_SIZE) { - retry_err = ntfs_attr_expand(attr_ni, base_ni->attr_list_size, - NTFS_MAX_ATTR_LIST_SIZE); + retry_err = ntfs_attr_expand_locked( + attr_ni, base_ni->attr_list_size, + NTFS_MAX_ATTR_LIST_SIZE, locked_ni); if (retry_err == -ENOSPC) { - retry_err = ntfs_attrlist_repack(attr_vi, attr_ni, - NTFS_MAX_ATTR_LIST_SIZE); + retry_err = ntfs_attrlist_repack( + attr_vi, attr_ni, + NTFS_MAX_ATTR_LIST_SIZE, locked_ni); if (retry_err == -ENOSPC) retry_err = 0; } @@ -289,6 +302,11 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) return 0; } +int ntfs_attrlist_update(struct ntfs_inode *base_ni) +{ + return ntfs_attrlist_update_locked(base_ni, NULL); +} + /* * ntfs_attrlist_entry_add - add an attribute list attribute entry * @ni: opened ntfs inode, which contains that attribute diff --git a/fs/ntfs/attrlist.h b/fs/ntfs/attrlist.h index 1892a3934d3ab7..10cc2cc8e20819 100644 --- a/fs/ntfs/attrlist.h +++ b/fs/ntfs/attrlist.h @@ -16,5 +16,7 @@ int ntfs_attrlist_need(struct ntfs_inode *ni); int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr); int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx); int ntfs_attrlist_update(struct ntfs_inode *base_ni); +int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni, + struct ntfs_inode *locked_ni); #endif /* defined _NTFS_ATTRLIST_H */ diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c index 197d8607fc6338..6927d115d6afe7 100644 --- a/fs/ntfs/compress.c +++ b/fs/ntfs/compress.c @@ -1450,7 +1450,7 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages, ni->runlist.rl = rl; rlc = NULL; - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); up_write(&ni->runlist.lock); if (err) err = -EIO; diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 8164326b7812a1..007d1614b9ac61 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -111,7 +111,8 @@ static int ntfs_trim_prealloc(struct inode *vi) ntfs_error(vol->sb, "Preallocated block rollback failed"); } else { ni->allocated_size = ntfs_cluster_to_bytes(vol, vcn_tr); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked( + ni, 0, ni); if (err) ntfs_error(vol->sb, "Failed to rollback mapping pairs for prealloc"); diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 332825db477abb..5d9d48135ecda6 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -2772,7 +2772,7 @@ int __ntfs_write_inode(struct inode *vi, int sync) if (NInoNonResident(ni) && NInoRunlistDirty(ni)) { down_write(&ni->runlist.lock); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (!err) NInoClearRunlistDirty(ni); up_write(&ni->runlist.lock); diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index f0656d0bbeebed..5b9723abb10f86 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -1131,7 +1131,8 @@ static s64 mft_bitmap_alloc_free_rec(struct ntfs_volume *vol, return ll; } -static int ntfs_mft_attr_extend(struct ntfs_inode *ni) +static int ntfs_mft_attr_extend(struct ntfs_inode *ni, + struct ntfs_inode *locked_ni) { int ret = 0; struct ntfs_inode *base_ni; @@ -1152,7 +1153,7 @@ static int ntfs_mft_attr_extend(struct ntfs_inode *ni) } } - ret = ntfs_attr_update_mapping_pairs(ni, 0); + ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, locked_ni); if (ret) pr_err("MP update failed\n"); @@ -1340,7 +1341,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)); if (unlikely(ret)) { - ret = ntfs_mft_attr_extend(mftbmp_ni); + ret = ntfs_mft_attr_extend(mftbmp_ni, mftbmp_ni); if (!ret) goto extended_ok; if (ret != -EAGAIN) @@ -1451,7 +1452,9 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) NVolSetErrors(vol); } mark_mft_record_dirty(ctx->ntfs_ino); - } else if (status.mp_extended && ntfs_attr_update_mapping_pairs(mftbmp_ni, 0)) { + } else if (status.mp_extended && + ntfs_attr_update_mapping_pairs_locked(mftbmp_ni, 0, + mftbmp_ni)) { ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es); NVolSetErrors(vol); } @@ -1776,7 +1779,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol) ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)); if (unlikely(ret)) { - ret = ntfs_mft_attr_extend(mft_ni); + ret = ntfs_mft_attr_extend(mft_ni, NULL); if (!ret) goto extended_ok; if (ret != -EAGAIN) From 1923eeffa63edeff427d76fc302bc5eb835771ce Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 7 Sep 2026 13:34:01 +0900 Subject: [PATCH 0307/1417] ntfs: propagate folio errors Return the error from __filemap_get_folio() instead of replacing it with -ENOMEM. Fixes: af0db57d4293 ("ntfs: update inode operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 5d9d48135ecda6..0a0b7c5547f732 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -3713,7 +3713,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, FGP_CREAT | FGP_LOCK, mapping_gfp_mask(mapping)); if (IS_ERR(folio)) { - ret = -ENOMEM; + ret = PTR_ERR(folio); break; } } else { From 8c5dc7587fdd45f957af81a9adc1e16863f300fc Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 7 Sep 2026 13:34:25 +0900 Subject: [PATCH 0308/1417] ntfs: ignore interrupted inode reads as corruption Do not mark the volume in error or report an inode as corrupt when reading it was interrupted by a signal. -EINTR and -ERESTARTSYS indicate a transient read failure rather than on-disk NTFS corruption. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 21 +++++++++++++-------- fs/ntfs/mft.c | 3 ++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 0a0b7c5547f732..210adf589319e6 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -1241,7 +1241,8 @@ static int ntfs_read_locked_inode(struct inode *vi) if (m) unmap_mft_record(ni); err_out: - if (err != -EOPNOTSUPP && err != -ENOMEM && vol_err == true) { + if (err != -EOPNOTSUPP && err != -ENOMEM && + err != -EINTR && err != -ERESTARTSYS && vol_err == true) { ntfs_error(vol->sb, "Failed with error code %i. Marking corrupt inode 0x%llx as bad. Run chkdsk.", err, ni->mft_no); @@ -1467,12 +1468,13 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi) ntfs_attr_put_search_ctx(ctx); unmap_mft_record(base_ni); err_out: - if (err != -ENOENT) + if (err != -ENOENT && err != -EINTR && err != -ERESTARTSYS) ntfs_error(vol->sb, "Failed with error code %i while reading attribute inode (mft_no 0x%llx, type 0x%x, name_len %i). Marking corrupt inode and base inode 0x%llx as bad. Run chkdsk.", err, ni->mft_no, ni->type, ni->name_len, base_ni->mft_no); - if (err != -ENOENT && err != -ENOMEM) + if (err != -ENOENT && err != -ENOMEM && + err != -EINTR && err != -ERESTARTSYS) NVolSetErrors(vol); return err; } @@ -1676,8 +1678,9 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi) /* Get the index bitmap attribute inode. */ bvi = ntfs_attr_iget(base_vi, AT_BITMAP, ni->name, ni->name_len); if (IS_ERR(bvi)) { - ntfs_error(vi->i_sb, "Failed to get bitmap attribute."); err = PTR_ERR(bvi); + if (err != -EINTR && err != -ERESTARTSYS) + ntfs_error(vi->i_sb, "Failed to get bitmap attribute."); goto unm_err_out; } bni = NTFS_I(bvi); @@ -1721,10 +1724,12 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi) if (m) unmap_mft_record(base_ni); err_out: - ntfs_error(vi->i_sb, - "Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.", - err, ni->mft_no, ni->name_len); - if (err != -EOPNOTSUPP && err != -ENOMEM) + if (err != -EINTR && err != -ERESTARTSYS) + ntfs_error(vi->i_sb, + "Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.", + err, ni->mft_no, ni->name_len); + if (err != -EOPNOTSUPP && err != -ENOMEM && + err != -EINTR && err != -ERESTARTSYS) NVolSetErrors(vol); return err; } diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 5b9723abb10f86..6d5e56378afd34 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -213,7 +213,8 @@ struct mft_record *map_mft_record(struct ntfs_inode *ni) return m; atomic_dec(&ni->count); - ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m)); + if (PTR_ERR(m) != -EINTR && PTR_ERR(m) != -ERESTARTSYS) + ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m)); return m; } From fc440366c47000b768d013e347f60e81d60328ca Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 7 Sep 2026 13:34:44 +0900 Subject: [PATCH 0309/1417] ntfs: discard inodes that fail initialization Discard a newly allocated normal, attribute, or index inode when initialization fails. Keeping an incompletely initialized inode in the inode cache can expose stale sequence data to later directory lookups. Fixes: d7aa04984f12 ("ntfs: return errors from inode initialization") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 210adf589319e6..b1f9a72ea96337 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -170,17 +170,18 @@ struct inode *ntfs_iget(struct super_block *sb, u64 mft_no) /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_inode(vi); - unlock_new_inode(vi); + if (err) + discard_new_inode(vi); + else + unlock_new_inode(vi); } /* * There is no point in keeping bad inodes around. This also * simplifies things in that we never need to check for bad inodes * elsewhere. */ - if (unlikely(err)) { - iput(vi); + if (unlikely(err)) vi = ERR_PTR(err); - } return vi; } @@ -231,17 +232,18 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type, /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_attr_inode(base_vi, vi); - unlock_new_inode(vi); + if (err) + discard_new_inode(vi); + else + unlock_new_inode(vi); } /* * There is no point in keeping bad attribute inodes around. This also * simplifies things in that we never need to check for bad attribute * inodes elsewhere. */ - if (unlikely(err)) { - iput(vi); + if (unlikely(err)) vi = ERR_PTR(err); - } return vi; } @@ -286,17 +288,18 @@ struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name, /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_index_inode(base_vi, vi); - unlock_new_inode(vi); + if (err) + discard_new_inode(vi); + else + unlock_new_inode(vi); } /* * There is no point in keeping bad index inodes around. This also * simplifies things in that we never need to check for bad index * inodes elsewhere. */ - if (unlikely(err)) { - iput(vi); + if (unlikely(err)) vi = ERR_PTR(err); - } return vi; } From 0c32a42fd96a0e7c06c8a648a6dd8f1ec0bf643b Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 7 Sep 2026 13:38:17 +0900 Subject: [PATCH 0310/1417] ntfs: unhash failed inode reads Remove a newly allocated inode from the inode hash before discarding it. NTFS may set i_nlink before a later initialization step fails, in which case iput alone can retain the incomplete inode in the cache. Fixes: bf6be898fbc5 ("ntfs: discard inodes that fail initialization") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index b1f9a72ea96337..a777de8a80c720 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -170,9 +170,10 @@ struct inode *ntfs_iget(struct super_block *sb, u64 mft_no) /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_inode(vi); - if (err) + if (err) { + remove_inode_hash(vi); discard_new_inode(vi); - else + } else unlock_new_inode(vi); } /* @@ -232,9 +233,10 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type, /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_attr_inode(base_vi, vi); - if (err) + if (err) { + remove_inode_hash(vi); discard_new_inode(vi); - else + } else unlock_new_inode(vi); } /* @@ -288,9 +290,10 @@ struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name, /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_index_inode(base_vi, vi); - if (err) + if (err) { + remove_inode_hash(vi); discard_new_inode(vi); - else + } else unlock_new_inode(vi); } /* From 229e8188307b9724cc676a49e9600c4acd24b571 Mon Sep 17 00:00:00 2001 From: Zhu Tianhao Date: Tue, 8 Sep 2026 05:41:00 +0900 Subject: [PATCH 0311/1417] ntfs: fix $MFTMirr write offset when it spans multiple folios When commit 115380f9a2f9 ("ntfs: update mft operations") refactored the MFT code to use folios, it assumed $MFTMirr is allocated contiguously, which is indeed what mkfs arranges. However, the folio rewrite kept a leftover from the old buffer-head/runlist based implementation: vol->cluster_size_mask is still applied to the mirror write offset. In fact it is no longer needed -- and in some configurations it is actively wrong. The bug triggers whenever the four mirror records do not fit in a single folio, i.e. when mft_record_size exceeds PAGE_SIZE / 4 (for example mft_record_size > 1KiB on 4KiB pages). For example, on the built-in 4Kn SSD (Apple SSD AP0256J) of a MacBookPro14,1 with 4096-byte MFT records, mirror record 3 is still written to the location of record 0. $MFTMirr therefore gets out of sync with $MFT and the following warning is printed on remount: ntfs: (device nvme0n1p3): check_mft_mirror(): $MFT and $MFTMirr record 0 do not match. Run chkdsk. Fix it by always adding the folio offset (folio->index << PAGE_SHIFT) to the base LCN address instead of applying the cluster_size_mask truncation. This is the standard file-offset calculation and is correct for every combination of cluster size, page size and MFT record size, provided $MFTMirr data is contiguous from mftmirr_lcn (which mkfs always arranges). Tested on the volume above: before the change mirror records 1-3 are all written to record 0's sector; after the change the write-back probe reports: ntfs: NTFSDBG pre mft_no=0x3 folio_idx=0x3 ofs=0x0 mftmirr_lcn=0x2540c5 clu_bits=12 rec_bits=12 PAGE_SHIFT=12 sect=0x12a0640 ntfs: NTFSDBG pre mft_no=0x0 folio_idx=0x0 ofs=0x0 mftmirr_lcn=0x2540c5 clu_bits=12 rec_bits=12 PAGE_SHIFT=12 sect=0x12a0628 which matches the expected sectors computed as: (NTFS_CLU_TO_B(mftmirr_lcn) + (folio->index << PAGE_SHIFT)) >> SECTOR_SHIFT record 0: (0x2540c5 << 12) + 0x0000 = 0x2540c5000 >> 9 = 0x12a0628 record 3: (0x2540c5 << 12) + 0x3000 = 0x2540c8000 >> 9 = 0x12a0640 A 13 MB file write succeeds on this volume and the file is byte-for-byte identical after a umount/mount cycle. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Assisted-by: UOS-AI Assisted-by: CodeBuddy:Hy4 preview Signed-off-by: Zhu Tianhao Reviewed-by: Baolin Liu Signed-off-by: Namjae Jeon --- fs/ntfs/mft.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 6d5e56378afd34..a09496622c7aee 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -463,7 +463,7 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, { u8 *kmirr; struct folio *folio; - unsigned int folio_ofs, lcn_folio_off = 0; + unsigned int folio_ofs; int err = 0; struct bio *bio; @@ -493,15 +493,11 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, memcpy(kmirr, m, vol->mft_record_size); kunmap_local(kmirr); - if (vol->cluster_size_bits > PAGE_SHIFT) { - lcn_folio_off = folio->index << PAGE_SHIFT; - lcn_folio_off &= vol->cluster_size_mask; - } - bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); bio->bi_iter.bi_sector = ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) + - lcn_folio_off + folio_ofs); + ((u64)folio->index << PAGE_SHIFT) + + folio_ofs); if (bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs)) err = submit_bio_wait(bio); From 76a986c980bb502c7688d605ac7a67fd257a9a1b Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sat, 12 Sep 2026 13:05:30 -0700 Subject: [PATCH 0312/1417] ALSA: usb-audio: Clamp implicit feedback packet count to URB capacity data_ep_set_params() allocates each data URB for exactly u->packets isochronous frames, so urb->iso_frame_desc[] has u->packets slots and ctx->packets is the driver's only record of that limit. For an implicit feedback sink, snd_usb_queue_pending_output_urbs() overwrites it with the sync source's packet count, which is calculated independently from the capture endpoint's parameters. When that count is larger, prepare_playback_urb() and prepare_silent_urb() can write iso_frame_desc[] past the allocation; their existing bounds limit payload bytes, not the descriptor index. The reproducer uses a high-speed UAC2 device declaring bInterval 1 for implicit feedback capture (8 packets) and bInterval 4 for playback (1 packet). On the first capture completion after the stream starts, it accesses seven descriptors spanning 112 bytes beyond the one-packet URB: BUG: KASAN: slab-out-of-bounds in prepare_playback_urb (sound/usb/pcm.c:1560) Write of size 4 at addr ffff88801e696ad0 by task vhci_rx/178 prepare_playback_urb (sound/usb/pcm.c:1560) prepare_outbound_urb (sound/usb/endpoint.c:340) snd_usb_queue_pending_output_urbs (sound/usb/endpoint.c:501) snd_complete_urb (sound/usb/endpoint.c:1834) __usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1657) usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1741) vhci_rx_loop (drivers/usb/usbip/vhci_rx.c:107) kthread (kernel/kthread.c:436) The buggy address belongs to the object at ffff88801e696a00 which belongs to the cache kmalloc-256 of size 256 The buggy address is located 0 bytes to the right of allocated 208-byte region [ffff88801e696a00, ffff88801e696ad0) Record the allocated packet count per endpoint and clamp both the adopted count and the packet-size copy to it. Fold the Format Type II delimiter into urb_packs before the allocation loop so the recorded limit matches every URB. Fixes: cf044e441902 ("ALSA: usb-audio: Update the number of packets properly at receiving") Reported-by: co+8eacd4fa193b1b28@bugs.sh Closes: https://lore.kernel.org/all/22xPn8drvIUtYgVeQnBiNqXuevOTpBAjepLz%40bugs.sh/ Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260912200530.1955491-1-xmei5@asu.edu Signed-off-by: Takashi Iwai --- sound/usb/card.h | 1 + sound/usb/endpoint.c | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/sound/usb/card.h b/sound/usb/card.h index e34d92d576a273..8299ac241c6031 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -116,6 +116,7 @@ struct snd_usb_endpoint { unsigned int phase; /* phase accumulator */ unsigned int maxpacksize; /* max packet size in bytes */ unsigned int maxframesize; /* max packet size in frames */ + unsigned int max_urb_packs; /* packets allocated per data URB */ unsigned int max_urb_frames; /* max URB size in frames */ unsigned int curpacksize; /* current packet size in bytes (for capture) */ unsigned int curframesize; /* current packet size in frames (for capture) */ diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 0835943d7b0ef7..b72ce1e9bfb1c3 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -492,9 +492,10 @@ int snd_usb_queue_pending_output_urbs(struct snd_usb_endpoint *ep, /* copy over the length information */ if (implicit_fb) { - ctx->packets = packet->packets; + ctx->packets = min_t(int, packet->packets, + ep->max_urb_packs); memcpy(ctx->packet_size, packet->packet_size, - packet->packets * sizeof(packet->packet_size[0])); + ctx->packets * sizeof(packet->packet_size[0])); } /* call the data handler to fill in playback data */ @@ -1242,15 +1243,16 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); } + if (fmt->fmt_type == UAC_FORMAT_TYPE_II) + urb_packs++; /* for transfer delimiter */ + ep->max_urb_packs = urb_packs; + /* allocate and initialize data urbs */ for (i = 0; i < ep->nurbs; i++) { struct snd_urb_ctx *u = &ep->urb[i]; u->index = i; u->ep = ep; u->packets = urb_packs; - - if (fmt->fmt_type == UAC_FORMAT_TYPE_II) - u->packets++; /* for transfer delimiter */ u->buffer_size = maxsize * u->packets; u->urb = usb_alloc_urb(u->packets, GFP_KERNEL); if (!u->urb) From fd95e68df6fe66344161a1329cbe5e5805e7b704 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 12 Sep 2026 18:21:42 +0200 Subject: [PATCH 0313/1417] ALSA: core: Fix potential UAF after asynchronous card release Usually a sound driver releases the resources assigned to the card via snd_card_free(), and it synchronizes with the whole release procedure. However, when the card is released asynchronously via snd_card_free_when_closed() like USB-audio driver, the situation is slightly different; although the snd_card_disconnect() call at the disconnection guarantees that any newer accesses will be gated, the in-flight tasks might be still accessing to the underlying card->dev device even after the disconnection, which would cause a use-after-free in the end, as reported by fuzzers. For addressing the bug above, this patch takes the refcount of card->dev at initialization of the card object, and releases at its destructor. This assures the availability of the card->dev in its whole lifecycle. Reported-by: Farhad Alemi Closes: https://lore.kernel.org/CA+0ovChexj4TrZL_2iG_P0WBEbZc5+73GfB3DkciQi=R8pZOnA@mail.gmail.com Closes: https://lore.kernel.org/CA+0ovCgQUQNN=Z1tJTouiCsDaXR5M-3-SQEGk-cpPXQkM5Xh+w@mail.gmail.com Cc: Link: https://patch.msgid.link/20260912162150.455144-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/init.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/core/init.c b/sound/core/init.c index 2f7f83a7611b27..d05bea3c87f536 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -310,7 +310,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, kfree(card); /* manually free here, as no destructor called */ return err; } - card->dev = parent; + card->dev = get_device(parent); card->number = idx; WARN_ON(IS_MODULE(CONFIG_SND) && !module); card->module = module; @@ -603,6 +603,7 @@ static int snd_card_do_free(struct snd_card *card) dev_warn(card->dev, "unable to free card info\n"); /* Not fatal error */ } + put_device(card->dev); if (card->release_completion) complete(card->release_completion); if (!managed) From 6c05d00af307560e6a9f1631d6270d3df5aa2272 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Thu, 10 Sep 2026 23:11:21 -0400 Subject: [PATCH 0314/1417] ALSA: virtio: reset device before deleting virtqueues virtsnd_remove() and virtsnd_freeze() delete the virtqueues before resetting the device. del_vqs() frees the vring backing, but does not provide a generic device quiesce operation. In particular, modern virtio-pci keeps enabled queues active until the device is reset. Reset the device before deleting the virtqueues so it can no longer access the vring memory when that memory is released. This also covers probe failures after DRIVER_OK, which unwind through virtsnd_remove(). Fixes: de3a9980d8c3 ("ALSA: virtio: add virtio sound driver") Fixes: 575483e90a32 ("ALSA: virtio: introduce device suspend/resume support") Cc: stable@vger.kernel.org Signed-off-by: Yuho Choi Link: https://patch.msgid.link/20260911031121.1542502-1-oss.patchbox@gmail.com Signed-off-by: Takashi Iwai --- sound/virtio/virtio_card.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c index 647190f4d5afc3..6f35276416fedd 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -354,8 +354,8 @@ static void virtsnd_remove(struct virtio_device *vdev) if (snd->card) snd_card_free(snd->card); - vdev->config->del_vqs(vdev); virtio_reset_device(vdev); + vdev->config->del_vqs(vdev); for (i = 0; snd->substreams && i < snd->nsubstreams; ++i) { struct virtio_pcm_substream *vss = &snd->substreams[i]; @@ -383,8 +383,8 @@ static int virtsnd_freeze(struct virtio_device *vdev) virtsnd_disable_event_vq(snd); virtsnd_ctl_msg_cancel_all(snd); - vdev->config->del_vqs(vdev); virtio_reset_device(vdev); + vdev->config->del_vqs(vdev); for (i = 0; i < snd->nsubstreams; ++i) cancel_work_sync(&snd->substreams[i].elapsed_period); From 48e97c59a49c9e90270f5e3d221db253b76de5da Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Thu, 16 Jul 2026 21:32:46 +0300 Subject: [PATCH 0315/1417] phy: renesas: rcar-gen3-usb2: Avoid long delay in atomic context The OTG PHY initialization sequence needs to wait for 20 ms at a specific step, as described in commit 72c0339c115b ("phy: renesas: rcar-gen3-usb2: follow the hardware manual procedure"). Commit 55a387ebb921 ("phy: renesas: rcar-gen3-usb2: Lock around hardware registers and driver data") tried to address various problems in the rcar-gen3-usb2 driver and converted the mutex protecting HW register accesses to a spin lock, leaving, however, a long delay in the critical section protected by the spin lock. This may become a problem, especially on RT kernels. To address this, release the spin lock before sleeping for 20 ms as required by the HW manual and reacquire it afterwards. To avoid other threads entering the critical section and configuring the HW while the software is waiting for the OTG initialization to complete, introduce the otg_initializing variable alongside the otg_init_done wait queue. Any other thread trying to configure the HW while the OTG PHY initialization is in progress waits for the wait queue instead of immediately returning errors to PHY users. The IRQs were also disabled while waiting for the OTG PHY initialization to complete, as the interrupt handler may also apply HW settings. The OTG can only be initialized once. It is initialized by the first PHY that calls struct phy_ops::rcar_gen3_phy_usb2_init(). To avoid failures when multiple PHYs call struct phy_ops::rcar_gen3_phy_usb2_init() simultaneously, and the PHY responsible for initializing the OTG either fails or deinit quiqly and another PHY takes over the PHY init role), the code waiting for the channel->otg_init_done wait queue retries up to NUM_OF_PHYS times. Fixes: 55a387ebb921 ("phy: renesas: rcar-gen3-usb2: Lock around hardware registers and driver data") Cc: stable@vger.kernel.org Reported-by: Pavel Machek Closes: https://lore.kernel.org/all/afhkX2Ys2BG1gnqy@duo.ucw.cz Reported-by: Nobuhiro Iwamatsu Closes: https://lore.kernel.org/all/afhkX2Ys2BG1gnqy@duo.ucw.cz Signed-off-by: Claudiu Beznea Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/all/afhkX2Ys2BG1gnqy@duo.ucw.cz Link: https://patch.msgid.link/20260716183246.3183877-1-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Vinod Koul --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 310 +++++++++++++++++++---- 1 file changed, 265 insertions(+), 45 deletions(-) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 9ae9975d3255bd..b5ba751805aae9 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -27,6 +27,7 @@ #include #include #include +#include #include /******* USB2.0 Host registers (original offset is +0x200) *******/ @@ -106,6 +107,13 @@ /* RZ/G2L specific */ #define USB2_LINECTRL1_USB2_IDMON BIT(0) +/* + * The OTG initialization is expected to finish in 20ms. Choose a large enough + * timeout to avoid waiters exit prematurely the waiting section under heavy + * CPU load. + */ +#define USB2_OTG_INIT_TIMEOUT msecs_to_jiffies(120) + #define NUM_OF_PHYS 4 enum rcar_gen3_phy_index { PHY_INDEX_BOTH_HC, @@ -138,12 +146,20 @@ struct rcar_gen3_chan { struct rcar_gen3_phy rphys[NUM_OF_PHYS]; struct regulator *vbus; struct work_struct work; + wait_queue_head_t otg_init_done; spinlock_t lock; /* protects access to hardware and driver data structure. */ enum usb_dr_mode dr_mode; bool extcon_host; bool is_otg_channel; bool uses_otg_pins; bool otg_internal_reg; + /* + * The OTG can be initialized only once and needs to release the spinlock + * and wait for 20 ms due to hardware constraints. If a thread executes + * PHY configuration code while the OTG PHY is waiting for the 20 ms, the + * thread will have to wait for the OTG PHY initialization to complete. + */ + bool otg_initializing; }; struct rcar_gen3_phy_drv_data { @@ -392,26 +408,58 @@ static ssize_t role_store(struct device *dev, struct device_attribute *attr, struct rcar_gen3_chan *ch = dev_get_drvdata(dev); bool is_b_device; enum phy_mode cur_mode, new_mode; + int retries = NUM_OF_PHYS; + unsigned long flags; + int ret = -EIO; - guard(spinlock_irqsave)(&ch->lock); + spin_lock_irqsave(&ch->lock, flags); - if (!ch->is_otg_channel || !rcar_gen3_is_any_otg_rphy_initialized(ch)) - return -EIO; + if (!ch->is_otg_channel) + goto unlock; + + while (retries-- && ch->otg_initializing) { + spin_unlock_irqrestore(&ch->lock, flags); + + ret = wait_event_timeout(ch->otg_init_done, !ch->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + goto exit; + + spin_lock_irqsave(&ch->lock, flags); + } + + /* If another thread started a new initialization just return -EBUSY. */ + if (ch->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } + + if (!rcar_gen3_is_any_otg_rphy_initialized(ch)) { + ret = -EIO; + goto unlock; + } - if (sysfs_streq(buf, "host")) + if (sysfs_streq(buf, "host")) { new_mode = PHY_MODE_USB_HOST; - else if (sysfs_streq(buf, "peripheral")) + } else if (sysfs_streq(buf, "peripheral")) { new_mode = PHY_MODE_USB_DEVICE; - else - return -EINVAL; + } else { + ret = -EINVAL; + goto unlock; + } /* is_b_device: true is B-Device. false is A-Device. */ is_b_device = rcar_gen3_check_id(ch); cur_mode = rcar_gen3_get_phy_mode(ch); /* If current and new mode is the same, this returns the error */ - if (cur_mode == new_mode) - return -EINVAL; + if (cur_mode == new_mode) { + ret = -EINVAL; + goto unlock; + } if (new_mode == PHY_MODE_USB_HOST) { /* And is_host must be false */ if (!is_b_device) /* A-Peripheral */ @@ -425,7 +473,10 @@ static ssize_t role_store(struct device *dev, struct device_attribute *attr, rcar_gen3_init_for_peri(ch); } - return count; +unlock: + spin_unlock_irqrestore(&ch->lock, flags); +exit: + return ret ?: count; } static ssize_t role_show(struct device *dev, struct device_attribute *attr, @@ -441,14 +492,11 @@ static ssize_t role_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RW(role); -static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) +static void rcar_gen3_init_otg_phase0(struct rcar_gen3_chan *ch) { void __iomem *usb2_base = ch->base; u32 val; - if (!ch->is_otg_channel || rcar_gen3_is_any_otg_rphy_initialized(ch)) - return; - /* Should not use functions of read-modify-write a register */ val = readl(usb2_base + USB2_LINECTRL1); val = (val & ~USB2_LINECTRL1_DP_RPD) | USB2_LINECTRL1_DPRPD_EN | @@ -471,7 +519,11 @@ static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); } } - mdelay(20); +} + +static void rcar_gen3_init_otg_phase1(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->base; writel(0xffffffff, usb2_base + USB2_OBINTSTA); writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTEN); @@ -502,6 +554,7 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) void __iomem *usb2_base = ch->base; struct device *dev = ch->dev; irqreturn_t ret = IRQ_NONE; + unsigned long flags; u32 status; pm_runtime_get_noresume(dev); @@ -509,33 +562,102 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) if (pm_runtime_suspended(dev)) goto rpm_put; - scoped_guard(spinlock, &ch->lock) { - status = readl(usb2_base + USB2_OBINTSTA); - if (status & ch->phy_data->obint_enable_bits) { - dev_vdbg(dev, "%s: %08x\n", __func__, status); - if (ch->phy_data->vblvl_ctrl) - writel(USB2_OBINTSTA_CLEAR, usb2_base + USB2_OBINTSTA); - else - writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTSTA); - rcar_gen3_device_recognition(ch); - rcar_gen3_configure_vblvl_ctrl(ch); - ret = IRQ_HANDLED; - } + spin_lock_irqsave(&ch->lock, flags); + + status = readl(usb2_base + USB2_OBINTSTA); + if (status & ch->phy_data->obint_enable_bits) { + dev_vdbg(dev, "%s: %08x\n", __func__, status); + if (ch->phy_data->vblvl_ctrl) + writel(USB2_OBINTSTA_CLEAR, usb2_base + USB2_OBINTSTA); + else + writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTSTA); + + ret = IRQ_HANDLED; + + /* This should not happen! */ + if (ch->otg_initializing) + goto unlock; + + rcar_gen3_device_recognition(ch); + rcar_gen3_configure_vblvl_ctrl(ch); } +unlock: + spin_unlock_irqrestore(&ch->lock, flags); rpm_put: pm_runtime_put_noidle(dev); return ret; } +static void rcar_gen3_phy_usb2_irqs_mask_all(struct rcar_gen3_chan *channel, + u32 *masked_irqs_bits) +{ + u32 val, bitmask = USB2_INT_ENABLE_UCOM_INTEN; + void __iomem *usb2_base = channel->base; + + for (unsigned int i = 0; i < NUM_OF_PHYS; i++) + bitmask |= channel->rphys[i].int_enable_bits; + + val = readl(usb2_base + USB2_INT_ENABLE); + *masked_irqs_bits = val & bitmask; + val &= ~bitmask; + writel(val, usb2_base + USB2_INT_ENABLE); + + /* + * Don't report channel->phy_data->obint_enable_bits IRQs. These are + * unmasked anyway in rcar_gen3_init_otg_phase1(). + */ + val = readl(usb2_base + USB2_OBINTEN); + val &= ~channel->phy_data->obint_enable_bits; + writel(val, usb2_base + USB2_OBINTEN); +} + +static void rcar_gen3_phy_usb2_irqs_unmask(struct rcar_gen3_chan *channel, + u32 irqs_bits) +{ + u32 val, bitmask = USB2_INT_ENABLE_UCOM_INTEN; + void __iomem *usb2_base = channel->base; + + for (unsigned int i = 0; i < NUM_OF_PHYS; i++) + bitmask |= channel->rphys[i].int_enable_bits; + + val = readl(usb2_base + USB2_INT_ENABLE); + val &= ~bitmask; + val |= irqs_bits; + writel(val, usb2_base + USB2_INT_ENABLE); +} + static int rcar_gen3_phy_usb2_init(struct phy *p) { struct rcar_gen3_phy *rphy = phy_get_drvdata(p); struct rcar_gen3_chan *channel = rphy->ch; void __iomem *usb2_base = channel->base; + int retries = NUM_OF_PHYS; + unsigned long flags; u32 val; + int ret; + + spin_lock_irqsave(&channel->lock, flags); - guard(spinlock_irqsave)(&channel->lock); + while (retries-- && channel->otg_initializing) { + spin_unlock_irqrestore(&channel->lock, flags); + + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + return ret; + + spin_lock_irqsave(&channel->lock, flags); + } + + /* If another thread started a new initialization just return -EBUSY. */ + if (channel->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } /* Initialize USB2 part */ val = readl(usb2_base + USB2_INT_ENABLE); @@ -548,8 +670,23 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) } /* Initialize otg part (only if we initialize a PHY with IRQs). */ - if (rphy->int_enable_bits) - rcar_gen3_init_otg(channel); + if (rphy->int_enable_bits && channel->is_otg_channel && + !rcar_gen3_is_any_otg_rphy_initialized(channel)) { + u32 masked_irq_bits = 0; + + rcar_gen3_init_otg_phase0(channel); + rcar_gen3_phy_usb2_irqs_mask_all(channel, &masked_irq_bits); + channel->otg_initializing = true; + spin_unlock_irqrestore(&channel->lock, flags); + + fsleep(20000); + + spin_lock_irqsave(&channel->lock, flags); + rcar_gen3_phy_usb2_irqs_unmask(channel, masked_irq_bits); + rcar_gen3_init_otg_phase1(channel); + channel->otg_initializing = false; + wake_up_all(&channel->otg_init_done); + } if (channel->phy_data->vblvl_ctrl) { /* SIDDQ mode release */ @@ -568,7 +705,10 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) rphy->initialized = true; - return 0; +unlock: + spin_unlock_irqrestore(&channel->lock, flags); + + return ret; } static int rcar_gen3_phy_usb2_exit(struct phy *p) @@ -576,9 +716,32 @@ static int rcar_gen3_phy_usb2_exit(struct phy *p) struct rcar_gen3_phy *rphy = phy_get_drvdata(p); struct rcar_gen3_chan *channel = rphy->ch; void __iomem *usb2_base = channel->base; + int retries = NUM_OF_PHYS; + unsigned long flags; u32 val; + int ret; + + spin_lock_irqsave(&channel->lock, flags); + + while (retries-- && channel->otg_initializing) { + spin_unlock_irqrestore(&channel->lock, flags); + + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + return ret; + + spin_lock_irqsave(&channel->lock, flags); + } - guard(spinlock_irqsave)(&channel->lock); + /* If another thread started a new initialization just return -EBUSY. */ + if (channel->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } rphy->initialized = false; @@ -588,7 +751,9 @@ static int rcar_gen3_phy_usb2_exit(struct phy *p) val &= ~USB2_INT_ENABLE_UCOM_INTEN; writel(val, usb2_base + USB2_INT_ENABLE); - return 0; +unlock: + spin_unlock_irqrestore(&channel->lock, flags); + return ret; } static int rcar_gen3_phy_usb2_power_on(struct phy *p) @@ -596,8 +761,10 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) struct rcar_gen3_phy *rphy = phy_get_drvdata(p); struct rcar_gen3_chan *channel = rphy->ch; void __iomem *usb2_base = channel->base; + int retries = NUM_OF_PHYS; + unsigned long flags; u32 val; - int ret = 0; + int ret; if (channel->vbus && !channel->otg_internal_reg) { ret = regulator_enable(channel->vbus); @@ -605,7 +772,27 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) return ret; } - guard(spinlock_irqsave)(&channel->lock); + spin_lock_irqsave(&channel->lock, flags); + + while (retries-- && channel->otg_initializing) { + spin_unlock_irqrestore(&channel->lock, flags); + + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + goto disable_regulator; + + spin_lock_irqsave(&channel->lock, flags); + } + + /* If another thread started a new initialization just return -EBUSY. */ + if (channel->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } if (!rcar_gen3_are_all_rphys_power_off(channel)) goto out; @@ -620,27 +807,59 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) /* The powered flag should be set for any other phys anyway */ rphy->powered = true; - return 0; +unlock: + spin_unlock_irqrestore(&channel->lock, flags); + +disable_regulator: + if (ret && channel->vbus && !channel->otg_internal_reg) + regulator_disable(channel->vbus); + + return ret; } static int rcar_gen3_phy_usb2_power_off(struct phy *p) { struct rcar_gen3_phy *rphy = phy_get_drvdata(p); struct rcar_gen3_chan *channel = rphy->ch; - int ret = 0; + int retries = NUM_OF_PHYS; + unsigned long flags; + int ret; - scoped_guard(spinlock_irqsave, &channel->lock) { - rphy->powered = false; + spin_lock_irqsave(&channel->lock, flags); - if (rcar_gen3_are_all_rphys_power_off(channel)) { - u32 val = readl(channel->base + USB2_USBCTR); + while (retries-- && channel->otg_initializing) { + spin_unlock_irqrestore(&channel->lock, flags); - val |= USB2_USBCTR_PLL_RST; - writel(val, channel->base + USB2_USBCTR); - } + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + return ret; + + spin_lock_irqsave(&channel->lock, flags); + } + + /* If another thread started a new initialization just return -EBUSY. */ + if (channel->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } + + rphy->powered = false; + + if (rcar_gen3_are_all_rphys_power_off(channel)) { + u32 val = readl(channel->base + USB2_USBCTR); + + val |= USB2_USBCTR_PLL_RST; + writel(val, channel->base + USB2_USBCTR); } - if (channel->vbus && !channel->otg_internal_reg) +unlock: + spin_unlock_irqrestore(&channel->lock, flags); + + if (!ret && channel->vbus && !channel->otg_internal_reg) ret = regulator_disable(channel->vbus); return ret; @@ -1022,6 +1241,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) return ret; spin_lock_init(&channel->lock); + init_waitqueue_head(&channel->otg_init_done); for (i = 0; i < NUM_OF_PHYS; i++) { channel->rphys[i].phy = devm_phy_create(dev, NULL, channel->phy_data->phy_usb2_ops); From de7f29a1fe1dc2864d8a47f8c39d508442cae167 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 11 Sep 2026 09:40:14 +0200 Subject: [PATCH 0316/1417] phy: mediatek: phy-mtk-hdmi-mt8195: Fix PLL calc divisor overflow When trying to calculate a PLL rate for target display resolutions above 2560x1440, 24bpp, 30Hz, the pixel clock value will be more than 32-bits long but the division to finally calculate the digital clock divider is being done with div_u64(), which expects a 32bit unsigned divisor. Fix the overflow by using div64_u64() instead. Fixes: 9d9ff3d2a4a5 ("phy: mediatek: hdmi: mt8195: fix wrong pll calculus") Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260911074015.9994-2-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c index 1426a2db984d53..e6ee8e0800222e 100644 --- a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c +++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c @@ -290,7 +290,7 @@ static int mtk_hdmi_pll_calc(struct mtk_hdmi_phy *hdmi_phy, struct clk_hw *hw, posdiv2 = 1; /* Digital clk divider, max /32 */ - digital_div = div_u64(ns_hdmipll_ck, posdiv1 * posdiv2 * pixel_clk); + digital_div = div64_u64(ns_hdmipll_ck, posdiv1 * posdiv2 * pixel_clk); if (!(digital_div <= 32 && digital_div >= 1)) return -EINVAL; From 486a70ef848264dcf9a57f0bb0452848db9537de Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 11 Sep 2026 09:40:15 +0200 Subject: [PATCH 0317/1417] phy: mediatek: phy-mtk-hdmi-mt8195: Fix TMDS clk bit ratio setting The comment in the mtk_phy_tmds_clk_ratio() function clearly and correctly explains that the TMDS ratio has to be 1/10 for data rates under 3.4Gbps, and 1/40 over that. Unfortunately though, the TXC_DIV register setting was wrong, as in value 3 means to divide by 8 and, in order to achieve the in spec 1/40 (tmds) data rate, this has to divide by 4 instead! Add definitions for the TXC_DIV register values clearly explaining the meanings (DIV2, DIV4, DIV8), and program the correct, DIV 4, value to the register in mtk_phy_tmds_clk_ratio(). This fixes out of spec clocking and, with this change, SoCs using the MT8195 class HDMI PHYs can now successfully be configured to output 3840x2160@60Hz over HDMI. Fixes: 45810d486bb4 ("phy: mediatek: add support for phy-mtk-hdmi-mt8195") Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260911074015.9994-3-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c | 2 +- drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c index e6ee8e0800222e..a4bc1268946d83 100644 --- a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c +++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c @@ -36,7 +36,7 @@ mtk_phy_tmds_clk_ratio(struct mtk_hdmi_phy *hdmi_phy, bool enable) * clock bit ratio 1:40, under 3.4Gbps, clock bit ratio 1:10 */ if (enable) - mtk_phy_update_field(regs + HDMI20_CLK_CFG, REG_TXC_DIV, 3); + mtk_phy_update_field(regs + HDMI20_CLK_CFG, REG_TXC_DIV, VAL_TXC_DIV4); else mtk_phy_clear_bits(regs + HDMI20_CLK_CFG, REG_TXC_DIV); } diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h index e26caaf4d104ce..58800d7659ca01 100644 --- a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h +++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h @@ -17,6 +17,9 @@ #define HDMI20_CLK_CFG 0x70 #define REG_TXC_DIV GENMASK(31, 30) +#define VAL_TXC_DIV2 1 +#define VAL_TXC_DIV4 2 +#define VAL_TXC_DIV8 3 #define HDMI_1_CFG_0 0x00 #define RG_HDMITX21_DRV_IBIAS_CLK GENMASK(10, 5) From c7a1c6e8004ab12a9c9bfdcb603f60f9bf4a3cee Mon Sep 17 00:00:00 2001 From: fangqiurong Date: Sat, 12 Sep 2026 21:15:18 +0800 Subject: [PATCH 0318/1417] sched_ext: Close the pre-enable ops error claim window scx_alloc_and_add_sched() publishes ops->priv before scx_root_enable_workfn() switches the state to SCX_ENABLING. An error claimed via scx_bpf_error_bstr() from an associated BPF program in that window is consumed by scx_disable_workfn(), which takes the pre-enable shortcut in scx_root_disable(). The shortcut returns without any teardown and restores SCX_DISABLED with an unconditional scx_set_enable_state() xchg racing the enable workfn's own transition. The enable then completes with the claim consumed: the scheduler stays up but can never be disabled again, and bpf_scx_unreg() frees it while still in use, resulting in a use-after-free. Both WARN_ON_ONCE()s fire back to back: WARNING: kernel/sched/ext/ext.c:7522 at scx_root_enable_workfn+0xeec/0x1be0, CPU#3: scx_enable_help/276 WARNING: kernel/sched/ext/ext.c:6398 at scx_root_disable+0xb50/0xdb8, CPU#0: sched_ext_helpe/664 scx_root_enable_workfn() switches to SCX_ENABLING before the scheduler allocation, so ops->priv is never visible while SCX_DISABLED. The allocation failure path restores SCX_DISABLED. Fixes: 105dcd005be2 ("sched_ext: Introduce scx_prog_sched()") Cc: stable@vger.kernel.org Signed-off-by: fangqiurong Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index adf5993fa597d0..83999203a63a56 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -7516,22 +7516,24 @@ static void scx_root_enable_workfn(struct kthread_work *work) #ifdef CONFIG_EXT_SUB_SCHED cgroup_get(cgrp); #endif + /* + * Transition to ENABLING to arm the disable path. Allocation failure + * still unwinds locally. Full disabling on failure applies only after + * scx_alloc_and_add_sched() succeeds. + */ + WARN_ON_ONCE(scx_set_enable_state(SCX_ENABLING) != SCX_DISABLED); + WARN_ON_ONCE(scx_root); + sch = scx_alloc_and_add_sched(cmd, cgrp, NULL); if (IS_ERR(sch)) { ret = PTR_ERR(sch); + WARN_ON_ONCE(scx_set_enable_state(SCX_DISABLED) != SCX_ENABLING); goto err_free_tid_hash; } if (sch->is_cid_type) static_branch_enable(&__scx_is_cid_type); - /* - * Transition to ENABLING and clear exit info to arm the disable path. - * Failure triggers full disabling from here on. - */ - WARN_ON_ONCE(scx_set_enable_state(SCX_ENABLING) != SCX_DISABLED); - WARN_ON_ONCE(scx_root); - atomic_long_set(&scx_nr_rejected, 0); for_each_possible_cpu(cpu) { From 1e713f9bb2ac583521f06b0eb4e22440b1e3d078 Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Sun, 13 Sep 2026 20:44:46 +0700 Subject: [PATCH 0319/1417] ALSA: pcm: set timer->private_data before registering the PCM timer snd_pcm_timer_init() calls snd_device_register() to link the new struct snd_timer into the global timer list while it still carries hw.c_resolution = snd_pcm_timer_resolution (and hw.start/hw.stop), and only afterwards sets timer->private_data = substream. Once the timer is on the list under register_mutex, a concurrent reader can already reach it through the same mutex and invoke these callbacks. /proc/asound/timers does this via c_resolution(), and snd_timer_open()+snd_timer_start() reach start()/stop() the same way. All three dereference timer->private_data, which for this brief window is NULL, giving a NULL-pointer dereference: substream = timer->private_data; return substream->runtime ? ... // substream is NULL Move the private_data/private_free assignment before snd_device_register() so the timer is never visible on the list without its private_data set. On the snd_device_register() failure path, private_free() (snd_pcm_timer_free()) can now run, but it only does substream->timer = NULL, which is already NULL at that point since substream->timer is set to the new timer just once, after a successful registration -- so the failure path stays safe. Reported-by: syzbot+19da64013c46df87f971@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=19da64013c46df87f971 Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Nguyen Ngoc Thang Link: https://patch.msgid.link/20260913134446.114724-1-ngocthang2710.1999@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_timer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/core/pcm_timer.c b/sound/core/pcm_timer.c index ab0e5bd70f8fa0..18bedd66435dc8 100644 --- a/sound/core/pcm_timer.c +++ b/sound/core/pcm_timer.c @@ -111,12 +111,15 @@ void snd_pcm_timer_init(struct snd_pcm_substream *substream) snd_pcm_direction_name(substream->stream), tid.card, tid.device, tid.subdevice); timer->hw = snd_pcm_timer; + /* Set before registering: a concurrent reader can invoke our hw + * callbacks as soon as the timer is on the global list. + */ + timer->private_data = substream; + timer->private_free = snd_pcm_timer_free; if (snd_device_register(timer->card, timer) < 0) { snd_device_free(timer->card, timer); return; } - timer->private_data = substream; - timer->private_free = snd_pcm_timer_free; substream->timer = timer; } From c65eae6f61d1778ff7a82e4aae4080e26f486af1 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Fri, 11 Sep 2026 22:38:04 -0300 Subject: [PATCH 0320/1417] smb: client: cancel reconnect work in clean_demultiplex_info() clean_demultiplex_info() cancels server->echo delayed work but not server->reconnect, which can cause a use-after-free when the demultiplex thread exits while a reconnect work is still queued: cifs_demultiplex_thread() cifs_readv_from_socket() cifs_reconnect() __cifs_reconnect() cifs_queue_server_reconn() mod_delayed_work(cifsiod_wq, &server->reconnect, 0) clean_demultiplex_info() cancel_delayed_work_sync(&server->echo) // echo canceled // reconnect NOT canceled kfree_sensitive(server) // server freed ...later, on cifsiod_wq: smb2_reconnect_server() server->srv_count // UAF read of freed server Fix this by canceling server->reconnect delayed work in clean_demultiplex_info() before the server is freed, the same way cifs_put_tcp_session() already does. Reported-by: syzbot+5003556314abc915a71f@syzkaller.appspotmail.com Closes: https://lore.kernel.org/r/6aa4a12d.f81106d8.2ab401.0023.GAE@google.com Fixes: 53e0e11efe92 ("CIFS: Fix a possible memory corruption during reconnect") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: David Howells Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Tom Talpey Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/connect.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index b6e98eb3167392..bef710d9a3c3c8 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1067,6 +1067,7 @@ clean_demultiplex_info(struct TCP_Server_Info *server) spin_unlock(&server->srv_lock); cancel_delayed_work_sync(&server->echo); + cancel_delayed_work_sync(&server->reconnect); spin_lock(&server->srv_lock); server->tcpStatus = CifsExiting; From 5f270f091256da1338c3631083e15d7f83cc05e1 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 9 Sep 2026 13:52:14 -0300 Subject: [PATCH 0321/1417] smb: client: fix rlist race and missing initialization TCP_Server_Info.rlist is allocated via kzalloc which zeros both ->next and ->prev to NULL instead of pointing to itself, making list_empty() always return false and list_add() dereference a NULL ->prev pointer. Also, cifs_signal_cifsd_for_reconnect() can be called concurrently from multiple cifsd threads, allowing the same server's rlist node to be added twice into the local list, corrupting it. Closes: https://sashiko.dev/#/patchset/20260911204446.1719356-1-pc%40manguebit.org Fixes: df0e03a4fb94 ("smb: client: fix potential deadlock when reconnecting channels") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: David Howells Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Tom Talpey Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/connect.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index bef710d9a3c3c8..4bd9f9fb93bbf8 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -174,6 +174,8 @@ cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server, nserver = ses->chans[i].server; if (!nserver) continue; + if (!list_empty(&nserver->rlist)) + continue; nserver->srv_count++; list_add(&nserver->rlist, &reco); } @@ -182,11 +184,15 @@ cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server, } } + spin_lock(&cifs_tcp_ses_lock); list_for_each_entry_safe(server, nserver, &reco, rlist) { list_del_init(&server->rlist); set_need_reco(server); + spin_unlock(&cifs_tcp_ses_lock); cifs_put_tcp_session(server, 0); + spin_lock(&cifs_tcp_ses_lock); } + spin_unlock(&cifs_tcp_ses_lock); } /* @@ -1824,6 +1830,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, spin_lock_init(&tcp_ses->mid_counter_lock); INIT_LIST_HEAD(&tcp_ses->tcp_ses_list); INIT_LIST_HEAD(&tcp_ses->smb_ses_list); + INIT_LIST_HEAD(&tcp_ses->rlist); INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request); INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server); mutex_init(&tcp_ses->reconnect_mutex); From e75c96157d45e498970158c8f7373d90102e33b9 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sat, 12 Sep 2026 14:20:08 -0300 Subject: [PATCH 0322/1417] smb: client: fix smbd_connection leak on cifs_get_tcp_session() error When an RDMA connection is successfully established via smbd_get_connection() but cifs_get_tcp_session() later fails (e.g. kthread_create() returns an error), the error path frees tcp_ses without first destroying the smbd_connection. Fix this by calling smbd_destroy() in the out_err cleanup path before kfree(tcp_ses). smbd_destroy() safely handles the case where smbd_conn is NULL, so it can be called unconditionally. Closes: https://sashiko.dev/#/patchset/20260912165503.521597-1-pc%40manguebit.org Fixes: 2f8946464b11 ("CIFS: SMBD: Upper layer connects to SMBDirect session") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Tom Talpey Cc: Stefan Metzmacher Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/connect.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index 4bd9f9fb93bbf8..28e1ddeb618207 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1934,6 +1934,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, kfree(tcp_ses->leaf_fullpath); if (tcp_ses->ssocket) sock_release(tcp_ses->ssocket); + smbd_destroy(tcp_ses); kfree(tcp_ses); } return ERR_PTR(rc); From ea9dadeac79cef509a4b8b4a3e3b39a741e63313 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 30 Jul 2026 16:05:13 +0300 Subject: [PATCH 0323/1417] drm/msm: mark the fbdev framebuffer as system memory msm_fbdev_driver_fbdev_probe() points screen_buffer at a kernel virtual mapping of the GEM object and uses the deferred sysmem fb ops, but never sets FBINFO_VIRTFB. The framebuffer core then assumes the memory is not in the virtual address space and warns on the first console draw: fb0: sys_fillrect: framebuffer is not in virtual address space. The drm_fbdev_dma, drm_fbdev_shmem and drm_fbdev_ttm helpers all set the flag for system memory. Do the same here. Signed-off-by: Dmitry Baryshkov Assisted-by: Claude:claude-opus-5 Patchwork: https://patchwork.freedesktop.org/patch/743293/ Link: https://lore.kernel.org/r/20260730-drm-msm-fbinfo-virt-v1-1-a27099a6dc58@oss.qualcomm.com Acked-by: Rob Clark # on IRC Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_fbdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c index 89ca9da3e1f272..dd6d6c507d77e5 100644 --- a/drivers/gpu/drm/msm/msm_fbdev.c +++ b/drivers/gpu/drm/msm/msm_fbdev.c @@ -155,6 +155,7 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, helper->fb = buffer->fb; fbi->fbops = &msm_fb_ops; + fbi->flags |= FBINFO_VIRTFB; /* system memory */ drm_fb_helper_fill_info(fbi, helper, sizes); From e249a6e2a130c08bb4d8b0a55cbe29754307e5c9 Mon Sep 17 00:00:00 2001 From: Jesse Casco Date: Sat, 8 Aug 2026 13:13:25 -0400 Subject: [PATCH 0324/1417] drm/msm/dp: skip PUSH_IDLE when the link was never enabled msm_dp_display_atomic_enable() returns early when link training fails, leaving ->power_on false and the main link down. msm_dp_display_atomic_disable() nevertheless writes DP_STATE_CTRL_PUSH_IDLE and waits for an idle-pattern completion that cannot arrive, so every failed enable is followed by "PUSH_IDLE pattern timedout". Every other step of the teardown is already gated on that flag: msm_dp_display_disable(), called from .atomic_post_disable(), returns early on !power_on. The PUSH_IDLE write is the only one that is not, so the controller's runtime-PM reference is then dropped without the link having been taken down. On glymur (Snapdragon X2 Elite) the consequence is not a warning. The SoC does not survive it: TrustZone force-stops the SOCCP and ADSP remote processors and the machine resets silently about 50 ms later, with no oops and no panic. On an ASUS Zenbook A16 (UX3607OA), whose eDP panel does not currently train, this reproduces without any compositor or GPU involvement: # eDP enable has already failed with "Failed link training (rc=-104)" echo 1 > /sys/class/graphics/fb0/blank [535.645455] === marker === [535.694833] qcom_q6v5_pas d00000.remoteproc: fatal error received: \ sys_m_smsm.c:512:TZ force stop [535.694875] remoteproc remoteproc0: crash detected in soccp: type fatal error [535.728857] qcom_q6v5_pas 6800000.remoteproc: fatal error received: \ sys_m_smsm.c:783:err fatal notification received from TZ Gate the PUSH_IDLE write on ->power_on so the disable path is consistent with the rest of the teardown. With this applied the same sequence is harmless and the machine stays up; without it, it resets every time. The unconditional write dates back to the original DP driver (c943b4948b58 ("drm/msm/dp: add displayPort driver support")), but the surrounding code has been restructured several times since, so no Fixes: tag is offered. Note that the eDP link-training failure that exposes this on the A16 is a separate problem in the glymur eDP PHY and is reported separately; this change is about not damaging the machine when training fails, for whatever reason. Tested on ASUS Zenbook A16 (UX3607OA), Snapdragon X2 Elite Extreme, on linux-next next-20260803 and next-20260807. The machine has since been running next-20260807 with this patch as its daily driver. Assisted-by: Anthropic:Claude-Opus-5 Signed-off-by: Jesse Casco Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/745167/ Link: https://lore.kernel.org/r/20260808171325.133041-1-jesse.casco@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index bc646d172abe04..5d2ddf1808fe13 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1458,6 +1458,20 @@ void msm_dp_display_atomic_disable(struct msm_dp *dp) msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); + /* + * If .atomic_enable() bailed out - link training failure is the common + * case - the mainlink was never brought up and ->power_on stayed false. + * Driving the PUSH_IDLE pattern into a controller that was never + * enabled times out, and .atomic_post_disable() then drops the + * controller's runtime-PM reference without tearing the PHY back down, + * because msm_dp_display_disable() returns early on !power_on. On + * glymur (Snapdragon X2 Elite) that combination is answered by a + * TrustZone-level SOCCP/ADSP force-stop and a silent SoC reset. + * There is nothing to push idle, so leave it alone. + */ + if (!dp->power_on) + return; + msm_dp_ctrl_push_idle(msm_dp_display->ctrl); } From 6fbbf1e152f34ad3913e4a6476680aba672c5068 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sat, 8 Aug 2026 21:16:24 +0800 Subject: [PATCH 0325/1417] drm/msm/adreno: fix autosuspend cleanup during teardown adreno_gpu_init() calls pm_runtime_use_autosuspend(), but adreno_gpu_cleanup() does not call the matching pm_runtime_dont_use_autosuspend() during teardown. If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without calling pm_runtime_dont_use_autosuspend() during teardown, this reference is not dropped and usage_count remains unbalanced. The documentation for pm_runtime_use_autosuspend() also notes that it is important to undo it with pm_runtime_dont_use_autosuspend() at driver exit time, unless runtime PM was initially enabled with devm_pm_runtime_enable(). Add the missing pm_runtime_dont_use_autosuspend() call to adreno_gpu_cleanup(). This issue was found by manual code inspection. Fixes: eeb754746b14 ("drm/msm/gpu: use pm-runtime") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/745110/ Link: https://lore.kernel.org/r/20260808131624.2854412-1-lgs201920130244@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index ca5e4e560cdeda..5832dc25d6bf40 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -1261,6 +1261,8 @@ void adreno_gpu_cleanup(struct adreno_gpu *adreno_gpu) for (i = 0; i < ARRAY_SIZE(adreno_gpu->info->fw); i++) release_firmware(adreno_gpu->fw[i]); + pm_runtime_dont_use_autosuspend(&gpu->pdev->dev); + if (priv && pm_runtime_enabled(&priv->gpu_pdev->dev)) pm_runtime_disable(&priv->gpu_pdev->dev); From 58995b11dfb7dda095d23f22fa4dc79b923b5adf Mon Sep 17 00:00:00 2001 From: William Bright Date: Wed, 12 Aug 2026 11:05:52 +0100 Subject: [PATCH 0326/1417] drm/msm/dp: fix link bandwidth check when wide bus is enabled msm_dp_display_mode_valid() halves the pixel clock when either YUV420 or wide bus is in use, then uses that halved value both for the controller pixel clock limit and for the DP link bandwidth check. Only YUV420 halves the data crossing the link. Wide bus widens the internal DPU to DP interface to two pixels per clock, halving the controller clock. Every pixel is still transmitted, so the link bandwidth requirement remains. As a result, modes needing up to twice the available link bandwidth pass validation. On the IMDT QCS8550 SBC (rev5 with CYPD6125), where DP runs over USB-C alt mode where only two lanes are available, 3840x2160@60 was accepted despite needing 9.6 Gbps against the 8.64 Gbps the link can carry. Use a separate link pixel clock that is only halved for YUV420 for the bandwidth calculation, leaving the wide bus halving to apply solely to the controller pixel clock limit. With this, 4k@60 is correctly rejected and 4k@30 selected instead. Fixes: df9cf852ca30 ("drm/msm/dp: account for widebus and yuv420 during mode validation") Assisted-by: Claude:claude-opus-5 Signed-off-by: William Bright Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/746145/ Link: https://lore.kernel.org/r/20260812-msm-dp-link-bw-v1-1-b0e3ce1190be@imd-tec.com [DB: dropped useless comment] Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 5d2ddf1808fe13..4dcbd9b99d06b8 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -756,6 +756,7 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, struct msm_dp_link_info *link_info; u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; int mode_pclk_khz = mode->clock; + int link_pclk_khz; bool is_yuv_420; if (!dp || !mode_pclk_khz || !dp->connector) { @@ -775,6 +776,8 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, if (is_yuv_420 && !msm_dp_display->panel->vsc_sdp_supported) return MODE_NO_420; + link_pclk_khz = is_yuv_420 ? mode_pclk_khz / 2 : mode_pclk_khz; + if (is_yuv_420 || msm_dp_display->wide_bus_supported) mode_pclk_khz /= 2; @@ -786,9 +789,9 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, mode_bpp = default_bpp; mode_bpp = msm_dp_panel_get_mode_bpp(msm_dp_display->panel, - mode_bpp, mode_pclk_khz); + mode_bpp, link_pclk_khz); - mode_rate_khz = mode_pclk_khz * mode_bpp; + mode_rate_khz = link_pclk_khz * mode_bpp; supported_rate_khz = link_info->num_lanes * link_info->rate * 8; if (mode_rate_khz > supported_rate_khz) From a5b5cc909931572aec446e129c035b76b3f0c1fa Mon Sep 17 00:00:00 2001 From: Saim Shujah Date: Fri, 28 Aug 2026 11:54:40 +0500 Subject: [PATCH 0327/1417] drm/msm/dpu: clear pending peripheral flush state dpu_hw_ctl_clear_pending_flush() resets the cached per-block state after a flush transaction, but misses pending_periph_flush_mask. The peripheral flush updater accumulates interface bits in this mask. A later transaction which sets the top-level peripheral flush bit can write stale interface bits to CTL_PERIPH_FLUSH together with the current state. Peripheral flush support was added after the helper started clearing every individual pending flush mask. Clear the peripheral mask together with the other cached child masks. Fixes: 64f7b81f0358 ("drm/msm/dpu: add support of new peripheral flush mechanism") Cc: stable@vger.kernel.org Signed-off-by: Saim Shujah Patchwork: https://patchwork.freedesktop.org/patch/748968/ Link: https://lore.kernel.org/r/20260828065440.140410-1-saimzst@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c index 36a497f1d6c120..e8729fe1cfefa9 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c @@ -118,6 +118,7 @@ static inline void dpu_hw_ctl_clear_pending_flush(struct dpu_hw_ctl *ctx) ctx->pending_intf_flush_mask = 0; ctx->pending_wb_flush_mask = 0; ctx->pending_cwb_flush_mask = 0; + ctx->pending_periph_flush_mask = 0; ctx->pending_merge_3d_flush_mask = 0; ctx->pending_dsc_flush_mask = 0; ctx->pending_cdm_flush_mask = 0; From 26eb3d92c7a4d7adb1ae1740ca6e8e100b11d1ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Larocque?= Date: Thu, 10 Sep 2026 12:44:25 -0400 Subject: [PATCH 0328/1417] Input: synaptics - disable InterTouch on ThinkPad T440p (board id 2722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lenovo ThinkPad T440p (PNP ID LEN0036, board id 2722) has a Synaptics touchpad whose SMBus companion is not ready at boot and takes roughly 200 seconds to appear. During this window the touchpad and TrackPoint are completely unresponsive on approximately 50% of boots, making the machine unusable until the companion finally registers. The device is in the topbuttonpad_pnp_ids[] SMBus allowlist, so the kernel attempts to use SMBus/RMI4 mode by default. When the companion is not ready, psmouse_smbus_init() leaves breadcrumbs and returns -EAGAIN, the PS/2 fallback path is taken, but the device does not function properly until the companion appears and RMI4 takes over. Disable SMBus InterTouch for board id 2722 so the touchpad and TrackPoint work immediately via PS/2 from boot. Users can still force SMBus with psmouse.synaptics_intertouch=1 if needed. Tested-by: Raphaël Larocque Signed-off-by: Raphaël Larocque Link: https://patch.msgid.link/20260910164425.12832-1-rlarocque@disroot.org Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/synaptics.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index 2170bbe4c5897f..a8ce89d41faa1a 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -1838,6 +1838,14 @@ static int synaptics_setup_intertouch(struct psmouse *psmouse, return -ENXIO; } + + /* Disable intertouch on known-broken board revisions */ + if (info->board_id == 2722) { + psmouse_info(psmouse, + "Disabling intertouch for board id %u\n", + info->board_id); + return -ENXIO; + } } psmouse_info(psmouse, "Trying to set up SMBus access\n"); From 25e424eb4ae1a662d9c3573218d06ac32f797fc5 Mon Sep 17 00:00:00 2001 From: Chris Sommers Date: Mon, 7 Sep 2026 11:27:23 -0700 Subject: [PATCH 0329/1417] Input: i8042 - add quirk for Acer Aspire Go 15 AG15-42P On the Acer Aspire Go 15 (AG15-42P), the internal keyboard drops out ~5 seconds after boot on both Linux and Linux-LTS kernels. Keystrokes on the built-in keyboard stop registering while the trackpad and external keyboards remain functional. Testing confirms that booting with the i8042.reset kernel parameter resolves the issue and keeps the internal keyboard responsive. Add SERIO_QUIRK_RESET_ALWAYS to i8042_dmi_quirk_table for the Acer Aspire AG15-42P to automatically apply this quirk on boot. Signed-off-by: Chris Sommers Link: https://patch.msgid.link/20260907182723.2709981-1-chris.sommers@icloud.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/serio/i8042-acpipnpio.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h index 9ecb0eed48c4bb..54eb3687000a59 100644 --- a/drivers/input/serio/i8042-acpipnpio.h +++ b/drivers/input/serio/i8042-acpipnpio.h @@ -259,6 +259,13 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = { }, .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS) }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire AG15-42P"), + }, + .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS) + }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Acer"), From 930a7312c946bf4731721cadd82bb9a2ada496ca Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Wed, 9 Sep 2026 16:08:25 +0200 Subject: [PATCH 0330/1417] spi: spi-qpic-snand: avoid writing QPIC_EBI2_ECC_BUF_CFG register The description of commit bfb34eced559 ("mtd: rawnand: qcom: avoid writing to obsolete register") says this: "QPIC_EBI2_ECC_BUF_CFG register got obsolete from QPIC V2.0 onwards. Avoid writing this register if QPIC version is V2.0 or newer." Although the referenced commit is related to the 'qcom-nandc' driver, however the hardware supported by the current driver is also based on QPIC v2.0 so we should avoid writing that register here as well. Remove the register writing code to avoid undefined behaviour. Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface") Signed-off-by: Gabor Juhos Reviewed-by: Md Sadre Alam Link: https://patch.msgid.link/20260909-qpic-snand-avoid-ebi2-reg-write-v1-1-9b1b1466cc75@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 61b1f2eb19ce5a..05efe6313b7fed 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -765,8 +765,6 @@ static int qcom_spi_read_cw_raw(struct qcom_nand_controller *snandc, u8 *data_bu qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, NAND_ERASED_CW_DETECT_CFG, 1, 0); qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, @@ -1104,8 +1102,6 @@ static void qcom_spi_config_page_write(struct qcom_nand_controller *snandc) { qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, - 1, NAND_BAM_NEXT_SGL); } static void qcom_spi_config_cw_write(struct qcom_nand_controller *snandc) From ed22ad5fdbdbf9b4cb4ad3003f60314b5a5eb89d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Sep 2026 11:39:33 +0200 Subject: [PATCH 0331/1417] Input: soc_button_array - fix MS Surface Pro 11 probe failure On the MS Surface Pro 11 soc_button_array probing races with the GPIO driver probing. If soc_button_array wins the race then gpiod_get() returns EPROBE_DEFER, which should normally take care of retrying later, but the soc_button_array code deliberately ignores EPROBE_DEFER causing it to fail its probe() which causes the volume and power buttons to now work. The ignoring of EPROBE_DEFER is there to deal with a problem specific to older Bay Trail (BYT) and Cherry Trail (CHT) tablets which often use this driver. Modify the error handling to only ignore EPROBE_DEFER on BYT and CHT platforms and propagate EPROBE_DEFER normally on other platforms. Fixes: bcf059578980 ("Input: soc_button_array - partial revert of support for newer surface devices") Cc: stable@vger.kernel.org Reported-by: Sergey Lebedev Closes: https://lore.kernel.org/lkml/20260830141355.55898-1-lsa.uz@pm.me/ Signed-off-by: Hans de Goede Tested-by: Sergey Lebedev Link: https://patch.msgid.link/20260909093934.29411-1-johannes.goede@oss.qualcomm.com Signed-off-by: Dmitry Torokhov --- drivers/input/misc/soc_button_array.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c index b8cad415c62ca2..264c41f80d2b88 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -16,6 +16,7 @@ #include #include #include +#include #include static bool use_low_level_irq; @@ -160,7 +161,7 @@ soc_button_device_create(struct platform_device *pdev, struct gpio_keys_platform_data *gpio_keys_pdata; const struct dmi_system_id *dmi_id; int invalid_acpi_index = -1; - int error, gpio, irq; + int error, gpio, irq = 0; int n_buttons = 0; for (info = button_info; info->name; info++) @@ -191,8 +192,9 @@ soc_button_device_create(struct platform_device *pdev, error = soc_button_lookup_gpio(&pdev->dev, info->acpi_index, &gpio, &irq); if (error || irq < 0) { /* - * Skip GPIO if not present. Note we deliberately - * ignore -EPROBE_DEFER errors here. On some devices + * Propagate -EPROBE_DEFER, skip button on other errors. + * + * -EPROBE_DEFER is ignored on Bay & Cherry Trail. Here * Intel is using so called virtual GPIOs which are not * GPIOs at all but some way for AML code to check some * random status bits without need a custom opregion. @@ -201,6 +203,12 @@ soc_button_device_create(struct platform_device *pdev, * we do not have a driver for these so they will never * show up, therefore we ignore -EPROBE_DEFER. */ + if ((error == -EPROBE_DEFER || irq == -EPROBE_DEFER) && + !(soc_intel_is_byt() || soc_intel_is_cht())) { + error = -EPROBE_DEFER; + goto err_free_mem; + } + continue; } From fb5022278b6ea7f1838e3ef78028d5d5e3375f65 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Sep 2026 11:39:34 +0200 Subject: [PATCH 0332/1417] Input: soc_button_array - check btns_desc->package.count Check that btns_desc->package.count is not 0 before accessing btns_desc->package.elements[0]. Fixes: 4c3362f44980 ("Input: soc_button_array - add support for ACPI 6.0 Generic Button Device") Cc: stable@vger.kernel.org Reported-by: Shashiko Closes: https://lore.kernel.org/linux-input/20260909091440.3384C1F00A3A@smtp.kernel.org/ Signed-off-by: Hans de Goede Link: https://patch.msgid.link/20260909093934.29411-2-johannes.goede@oss.qualcomm.com Signed-off-by: Dmitry Torokhov --- drivers/input/misc/soc_button_array.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c index 264c41f80d2b88..f08e29af531fd9 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -377,7 +377,7 @@ static struct soc_button_info *soc_button_get_button_info(struct device *dev) } } - if (!btns_desc) { + if (!btns_desc || !btns_desc->package.count) { dev_err(dev, "ACPI Button Descriptors not found\n"); button_info = ERR_PTR(-ENODEV); goto out; From ea48250a0dc9708c198e8665391973bfc6c86fd2 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 13 Sep 2026 17:19:48 -0700 Subject: [PATCH 0333/1417] Input: document that no new LED codes should be added Add a comment to input-event-codes.h clarifying that no new LED definitions should be added to the input subsystem. The existing LED_* definitions are legacy and grandfathered for backwards compatibility with userspace via evdev. Any new LED indicators should instead use the dedicated LED subsystem. Signed-off-by: Dmitry Torokhov --- include/uapi/linux/input-event-codes.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 3528168f7c6d70..4217950e5d16a6 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -970,6 +970,12 @@ /* * LEDs + * + * Do not add any new LED definitions to the input subsystem. The existing + * definitions are legacy and grandfathered for backwards compatibility with + * userspace (via evdev). Any new LEDs should be implemented using the + * LED subsystem (struct led_classdev). The input core provides a bridge to + * the LED subsystem in drivers/input/input-leds.c. */ #define LED_NUML 0x00 From 7bc369cb3d3f3656eb77285628ee264264d28ad4 Mon Sep 17 00:00:00 2001 From: Jeremy Nyberg Date: Sun, 13 Sep 2026 17:43:02 -0700 Subject: [PATCH 0334/1417] Input: xpad - fix PDP Marvel Xbox 360 controller The PDP Marvel Xbox 360 controller with USB ID 0e6f:0147 is incorrectly classified as an Xbox One controller. With the current XTYPE_XBOXONE classification, the controller is detected but produces no input, while its four player LEDs continue blinking indefinitely. Classify USB ID 0e6f:0147 as an Xbox 360 controller instead. Tested on a PDP Marvel Xbox 360 controller with USB ID 0e6f:0147. All inputs register correctly and the player LED indicates the current player. Fixes: c225370e01b8 ("Input: xpad - sync supported devices with 360Controller") Cc: stable@vger.kernel.org Signed-off-by: Jeremy Nyberg Link: https://patch.msgid.link/20260910071627.236014-1-SlickStretch3.0@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 2da0b7f1722aae..b7a9a940d250ec 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -215,7 +215,7 @@ static const struct xpad_device { { 0x0e6f, 0x0139, "Afterglow Prismatic Wired Controller", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x013a, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x0146, "Rock Candy Wired Controller for Xbox One", 0, XTYPE_XBOXONE }, - { 0x0e6f, 0x0147, "PDP Marvel Xbox One Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0147, "PDP Marvel Xbox 360 Controller", 0, XTYPE_XBOX360 }, { 0x0e6f, 0x015c, "PDP Xbox One Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, { 0x0e6f, 0x015d, "PDP Mirror's Edge Official Wired Controller for Xbox One", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x0161, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, From cba76c0f47af1a389d718c5bb69e75cbd67bba98 Mon Sep 17 00:00:00 2001 From: Roberts Kursitis Date: Sun, 6 Sep 2026 17:30:40 +0300 Subject: [PATCH 0335/1417] Input: xpad - add support for Azeron devices Azeron controllers (Cyro, Cyborg, Classic/Compact, Cyro Lefty, Cyborg II and Keyzen) present a standard Xbox 360 controller interface, so they work with the existing xpad driver once their USB IDs are added. The 0x16d0 vendor ID is a shared block, but this is safe because xpad only binds interfaces that match the Xbox 360 signature. Tested with an Azeron Keyzen. Signed-off-by: Roberts Kursitis Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260906143040.162418-1-roberts.kursitis@azeron.eu Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index b7a9a940d250ec..8e4e06b53c2ad0 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -292,6 +292,12 @@ static const struct xpad_device { { 0x1689, 0xfd00, "Razer Onza Tournament Edition", 0, XTYPE_XBOX360 }, { 0x1689, 0xfd01, "Razer Onza Classic Edition", 0, XTYPE_XBOX360 }, { 0x1689, 0xfe00, "Razer Sabertooth", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x1103, "Azeron Cyro", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x113c, "Azeron Cyborg", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x1192, "Azeron Classic/Compact", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x1212, "Azeron Cyro Lefty", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x12f7, "Azeron Cyborg II", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x13ea, "Azeron Keyzen", 0, XTYPE_XBOX360 }, { 0x17ef, 0x6182, "Lenovo Legion Controller for Windows", 0, XTYPE_XBOX360 }, { 0x1949, 0x041a, "Amazon Game Controller", 0, XTYPE_XBOX360 }, { 0x1a86, 0xe310, "Legion Go S", 0, XTYPE_XBOX360 }, @@ -534,6 +540,7 @@ static const struct usb_device_id xpad_table[] = { XPAD_XBOX360_VENDOR(0x15e4), /* Numark Xbox 360 controllers */ XPAD_XBOX360_VENDOR(0x162e), /* Joytech Xbox 360 controllers */ XPAD_XBOX360_VENDOR(0x1689), /* Razer Onza */ + XPAD_XBOX360_VENDOR(0x16d0), /* Azeron controllers */ XPAD_XBOX360_VENDOR(0x17ef), /* Lenovo */ XPAD_XBOX360_VENDOR(0x1949), /* Amazon controllers */ XPAD_XBOX360_VENDOR(0x1a86), /* Nanjing Qinheng Microelectronics (WCH) */ From a52ae68a937efc353251aec27fc995ff66cbe1ca Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:40:04 +0800 Subject: [PATCH 0336/1417] Input: eeti_ts - publish the OF module alias The EETI driver matches eeti,exc3000-i2c Device Tree clients, but only publishes the legacy eeti_ts I2C ID. The I2C core emits an OF modalias for a Device Tree client. Publish the existing OF match table within its CONFIG_OF guard. Fixes: e32d7f1b246c ("Input: eeti - add device tree matching table") Signed-off-by: hpp.iscas Link: https://patch.msgid.link/20260905134004.66336-1-hppiscas@163.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/eeti_ts.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/touchscreen/eeti_ts.c b/drivers/input/touchscreen/eeti_ts.c index b12602bc368efc..b870a939508bb2 100644 --- a/drivers/input/touchscreen/eeti_ts.c +++ b/drivers/input/touchscreen/eeti_ts.c @@ -276,6 +276,7 @@ static const struct of_device_id of_eeti_ts_match[] = { { .compatible = "eeti,exc3000-i2c", }, { } }; +MODULE_DEVICE_TABLE(of, of_eeti_ts_match); #endif static struct i2c_driver eeti_ts_driver = { From 45b0037899704caf9078be2be4de69361ca7d933 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 12:20:38 +0200 Subject: [PATCH 0337/1417] Input: trackpoint - fix the inertia attribute name in the ABI document The attribute is created as "inertia" (TRACKPOINT_INT_ATTR(inertia, ...) in drivers/input/mouse/trackpoint.c); the ABI file spells the path "intertia". The description below it already says inertia. Fix the spelling. Fixes: aebb47d4e7a9 ("Input: trackpoint: document sysfs interface") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260905102038.42882-1-kmehltretter@gmail.com Signed-off-by: Dmitry Torokhov --- Documentation/ABI/testing/sysfs-devices-platform-trackpoint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-devices-platform-trackpoint b/Documentation/ABI/testing/sysfs-devices-platform-trackpoint index df11901a6b3df5..7954434b0434ac 100644 --- a/Documentation/ABI/testing/sysfs-devices-platform-trackpoint +++ b/Documentation/ABI/testing/sysfs-devices-platform-trackpoint @@ -5,7 +5,7 @@ Contact: linux-input@vger.kernel.org Description: (RW) Trackpoint sensitivity. -What: /sys/devices/platform/i8042/.../intertia +What: /sys/devices/platform/i8042/.../inertia Date: Aug, 2005 KernelVersion: 2.6.14 Contact: linux-input@vger.kernel.org From 7e61560628d17ea6b1d8ee370f6d42694cff8758 Mon Sep 17 00:00:00 2001 From: Ulises Mendez Martinez Date: Fri, 4 Sep 2026 15:07:09 +0000 Subject: [PATCH 0338/1417] objtool: Validate disassembler headers in libopcodes probe commit 3f2de814c059 ("objtool: Fix libopcodes linking with static libraries") tested for libopcodes availability by linking a test snippet with a forward declaration of disassemble_init_for_target(). However, testing symbol linkage with an extern declaration only verifies the presence of the library (.so/.a) and bypasses checking for development headers (binutils-dev). On systems where libopcodes is present without development headers installed, the probe succeeds, enabling BUILD_DISAS. Subsequent compilation of objtool then fails: fatal error: 'bfd.h' file not found 113 | #include Additionally, the probe invokes $(HOSTCC) without $(HOSTCFLAGS), ignoring any sysroot or include flags specified for the host compiler. Fix this by including and directly in the test snippet, passing $(HOSTCFLAGS) so host compiler options are respected, and defining PACKAGE="objtool" to satisfy the configuration check in . Fixes: 3f2de814c059 ("objtool: Fix libopcodes linking with static libraries") Fixes: 436326bc525d ("objtool: fix build failure due to missing libopcodes check") Reported-by: Alice Ryhl Assisted-by: Antigravity:Gemini-Next Signed-off-by: Ulises Mendez Martinez Link: https://patch.msgid.link/20260904150710.2997558-1-umendez@google.com Signed-off-by: Josh Poimboeuf --- tools/objtool/Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index a4484fd22a96d1..4cc2e756af840b 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -89,9 +89,11 @@ LIBOPCODES_LIBS := $(shell \ "-lopcodes -lbfd" \ "-lopcodes -lbfd -liberty" \ "-lopcodes -lbfd -liberty -lz"; do \ - echo 'extern void disassemble_init_for_target(void *);' \ - 'int main(void) { disassemble_init_for_target(0); return 0; }' | \ - $(HOSTCC) -xc - -o /dev/null $$libs 2>/dev/null && \ + printf '%s\n' \ + '$(pound)include ' \ + '$(pound)include ' \ + 'int main(void) { disassemble_init_for_target(0); return 0; }' | \ + $(HOSTCC) $(HOSTCFLAGS) -DPACKAGE='"objtool"' -xc - -o /dev/null $$libs 2>/dev/null && \ echo "$$libs" && break; \ done) From 55fc280e951ab2b39f3dcb640edc1b1eebc6d173 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Mon, 31 Aug 2026 14:43:51 -0500 Subject: [PATCH 0339/1417] Input: tsc2007 - read "ti,poll-period" as u32 The "ti,poll-period" property is documented as a normal uint32 cell. The driver used a u64 helper, which makes the helper type disagree with the schema even though the stored value is still small. Read "ti,poll-period" with the u32 helper matching the documented DT cell size. Assisted-by: Codex:gpt-5-5 Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260831194352.1185860-1-robh@kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/tsc2007_core.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/input/touchscreen/tsc2007_core.c b/drivers/input/touchscreen/tsc2007_core.c index e4d7da0f4434e8..e2f49b37e18c0d 100644 --- a/drivers/input/touchscreen/tsc2007_core.c +++ b/drivers/input/touchscreen/tsc2007_core.c @@ -221,7 +221,6 @@ static int tsc2007_get_pendown_state_gpio(struct device *dev) static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) { u32 val32; - u64 val64; if (!device_property_read_u32(dev, "ti,max-rt", &val32)) ts->max_rt = val32; @@ -237,8 +236,8 @@ static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) if (!device_property_read_u32(dev, "ti,fuzzz", &val32)) ts->fuzzz = val32; - if (!device_property_read_u64(dev, "ti,poll-period", &val64)) - ts->poll_period = msecs_to_jiffies(val64); + if (!device_property_read_u32(dev, "ti,poll-period", &val32)) + ts->poll_period = msecs_to_jiffies(val32); else ts->poll_period = msecs_to_jiffies(1); From 971fa7ea8621e123feb9c8d7dc61be1c656bd945 Mon Sep 17 00:00:00 2001 From: Erich Sartison Date: Thu, 3 Sep 2026 12:31:37 +0200 Subject: [PATCH 0340/1417] Input: xpad - add support for Victrix Pro BFG Controller The controller doesn't currently work via USB-cable. Signed-off-by: Erich Sartison Link: https://patch.msgid.link/20260903103137.630170-1-byt.es@mailbox.org Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 8e4e06b53c2ad0..6406ceab988ade 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -227,6 +227,7 @@ static const struct xpad_device { { 0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360", 0, XTYPE_XBOX360 }, { 0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360", 0, XTYPE_XBOX360 }, { 0x0e6f, 0x0246, "Rock Candy Gamepad for Xbox One 2015", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x024c, "PDP Victrix Pro BFG Wired Controller for Xbox", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x02a0, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x02a1, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x02a2, "PDP Wired Controller for Xbox One - Crimson Red", 0, XTYPE_XBOXONE }, From 309731e95917125bbd13626a7a5600490a5bf44f Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 2 Sep 2026 23:40:04 +0800 Subject: [PATCH 0341/1417] Input: hp_sdc - shut down kicker timer on module exit hp_sdc_kicker() rearms hp_sdc.kicker with mod_timer() after scheduling the tasklet. The module exit path uses timer_delete_sync(). That waits for a callback already running but can still leave the timer rearmed. A callback can therefore leave the timer pending while hp_sdc_exit() tears down the driver, allowing timer activity to access dismantled driver state. Use timer_shutdown_sync() for final teardown. It waits for a running callback and prevents rearming after module exit begins. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Acked-by: Helge Deller Link: https://patch.msgid.link/20260902154004.3595416-1-runyu.xiao@seu.edu.cn Signed-off-by: Dmitry Torokhov --- drivers/input/serio/hp_sdc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/serio/hp_sdc.c b/drivers/input/serio/hp_sdc.c index 1461ef319f92a8..ecf5347b63ab74 100644 --- a/drivers/input/serio/hp_sdc.c +++ b/drivers/input/serio/hp_sdc.c @@ -981,7 +981,7 @@ static void hp_sdc_exit(void) free_irq(hp_sdc.irq, &hp_sdc); write_unlock_irq(&hp_sdc.lock); - timer_delete_sync(&hp_sdc.kicker); + timer_shutdown_sync(&hp_sdc.kicker); tasklet_kill(&hp_sdc.task); From 1c21452d02eec2f008e2c5535820f85adbd7587a Mon Sep 17 00:00:00 2001 From: Pu Lehui Date: Sat, 5 Sep 2026 02:11:39 +0000 Subject: [PATCH 0342/1417] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk Syzkaller repeatedly triggered UAF splats related to nodes in waiting_for_gp_ttrace within the bpf memalloc: BUG: KASAN: slab-use-after-free in llist_del_first+0x85/0x110 lib/llist.c:61 Read of size 8 at addr ffff8881572cd080 by task syz.4.470/5112 ... llist_del_first+0x85/0x110 lib/llist.c:61 alloc_bulk+0x193/0x460 kernel/bpf/memalloc.c:229 bpf_mem_refill+0x386/0x560 kernel/bpf/memalloc.c:436 Freed by task 14: ... __free_rcu kernel/bpf/memalloc.c:281 [inline] __free_rcu_tasks_trace+0x48/0xd0 kernel/bpf/memalloc.c:291 rcu_tasks_invoke_cbs+0x1ec/0x3e0 kernel/rcu/tasks.h:571 rcu_tasks_one_gp+0x13d/0x220 kernel/rcu/tasks.h:621 rcu_tasks_kthread+0xf3/0x120 kernel/rcu/tasks.h:651 The reason is that the UAF occurs after the RCU Tasks Trace GP expires: when the __free_rcu() callback runs, there is no synchronization protecting llist_del_all() against concurrent alloc_bulk() operating on waiting_for_gp_ttrace, leading to the race condition below: CPU0 CPU1 __free_rcu (RCU Tasks Trace callback) alloc_bulk llist_del_first(&c->waiting_for_gp_ttrace) entry = smp_load_acquire(&head->first); do { if (entry == NULL) return NULL; free_all(llist_del_all(&c->waiting_for_gp_ttrace)) llist_for_each_safe(pos, t, llnode) free_one(pos); next = READ_ONCE(entry->next); <-- trigger UAF } while (!try_cmpxchg(&head->first, &entry, next)); In addition, there is also a theoretical race condition on the free_by_rcu_ttrace list. This race requires two preconditions: an in-flight Tasks Trace GP keeping c->call_rcu_ttrace_in_progress == 1, and concurrent cross-CPU frees repopulating c->free_by_rcu_ttrace with new nodes. Under these conditions, the following scenario triggers UAF: // CPU0 // irq work is still busy (on PREEMPT_RT) alloc_bulk() llist_del_first(&c->free_by_rcu_ttrace) entry = smp_load_acquire(&head->first); do { if (entry == NULL) return NULL; // CPU1 bpf_mem_alloc_destroy() WRITE_ONCE(c->draining, true) // wait for CPU0 irq_work_sync() // CPU2 do_call_rcu_ttrace(tgt(CPU0)) if (c->draining) { llist_del_all(&c->free_by_rcu_ttrace) free_all() } // CPU0 continue next = READ_ONCE(entry->next); <-- trigger UAF while (!try_cmpxchg(&head->first, &entry, next)); Fix this by introducing a raw spinlock to synchronize the concurrent consumption on waiting_for_gp_ttrace and free_by_rcu_ttrace. Fixes: 04fabf00b4d3 ("bpf: Allow reuse from waiting_for_gp_ttrace list.") Suggested-by: Alexei Starovoitov Suggested-by: Hou Tao Signed-off-by: Pu Lehui Acked-by: Hou Tao Link: https://lore.kernel.org/r/20260905021139.4116529-1-pulehui@huaweicloud.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/memalloc.c | 50 ++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c index e9662db7198fe0..8a8f088e83e6f9 100644 --- a/kernel/bpf/memalloc.c +++ b/kernel/bpf/memalloc.c @@ -119,6 +119,7 @@ struct bpf_mem_cache { struct llist_head waiting_for_gp_ttrace; struct rcu_head rcu_ttrace; atomic_t call_rcu_ttrace_in_progress; + raw_spinlock_t lock; }; struct bpf_mem_caches { @@ -214,25 +215,24 @@ static void alloc_bulk(struct bpf_mem_cache *c, int cnt, int node, bool atomic) gfp = __GFP_NOWARN | __GFP_ACCOUNT; gfp |= atomic ? GFP_NOWAIT : GFP_KERNEL; - for (i = 0; i < cnt; i++) { - /* - * For every 'c' llist_del_first(&c->free_by_rcu_ttrace); is - * done only by one CPU == current CPU. Other CPUs might - * llist_add() and llist_del_all() in parallel. - */ - obj = llist_del_first(&c->free_by_rcu_ttrace); - if (!obj) - break; - add_obj_to_free_list(c, obj); - } - if (i >= cnt) - return; + /* + * c->lock serializes concurrent llist_del_first() against + * llist_del_all() in __free_rcu() and do_call_rcu_ttrace(). + */ + scoped_guard(raw_spinlock_irqsave, &c->lock) { + for (i = 0; i < cnt; i++) { + obj = llist_del_first(&c->free_by_rcu_ttrace); + if (!obj) + break; + add_obj_to_free_list(c, obj); + } - for (; i < cnt; i++) { - obj = llist_del_first(&c->waiting_for_gp_ttrace); - if (!obj) - break; - add_obj_to_free_list(c, obj); + for (; i < cnt; i++) { + obj = llist_del_first(&c->waiting_for_gp_ttrace); + if (!obj) + break; + add_obj_to_free_list(c, obj); + } } if (i >= cnt) return; @@ -279,8 +279,12 @@ static int free_all(struct bpf_mem_cache *c, struct llist_node *llnode, bool per static void __free_rcu(struct rcu_head *head) { struct bpf_mem_cache *c = container_of(head, struct bpf_mem_cache, rcu_ttrace); + struct llist_node *llnode; + + scoped_guard(raw_spinlock_irqsave, &c->lock) + llnode = llist_del_all(&c->waiting_for_gp_ttrace); - free_all(c, llist_del_all(&c->waiting_for_gp_ttrace), !!c->percpu_size); + free_all(c, llnode, !!c->percpu_size); atomic_set(&c->call_rcu_ttrace_in_progress, 0); } @@ -300,7 +304,8 @@ static void do_call_rcu_ttrace(struct bpf_mem_cache *c) if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 1)) { if (unlikely(READ_ONCE(c->draining))) { - llnode = llist_del_all(&c->free_by_rcu_ttrace); + scoped_guard(raw_spinlock_irqsave, &c->lock) + llnode = llist_del_all(&c->free_by_rcu_ttrace); free_all(c, llnode, !!c->percpu_size); } return; @@ -535,6 +540,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu) c->objcg = objcg; c->percpu_size = percpu_size; c->tgt = c; + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } @@ -557,7 +563,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu) c->objcg = objcg; c->percpu_size = percpu_size; c->tgt = c; - + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } @@ -609,7 +615,7 @@ int bpf_mem_alloc_percpu_unit_init(struct bpf_mem_alloc *ma, int size) c->objcg = objcg; c->percpu_size = percpu_size; c->tgt = c; - + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } From 451b1c19dc7cbbad194a6e717b6a732c443d7dd5 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Fri, 11 Sep 2026 02:39:25 +0800 Subject: [PATCH 0343/1417] hwmon: (k10temp) Fix model id range of Zen5 Turin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model 20h-2Fh are mobile processors with single CCD. For example, model 24h is Strix Point, i.e., Ryzen AI 7 (PRO) (H/HX) 360/365/370. Including mobile processors in the model id range of Zen5 Turin processors leads to bogus reporting: k10temp-pci-00c3 Adapter: PCI adapter Tctl: +54.1°C Tccd4: +148.6°C Tccd6: +148.4°C Tccd7: +149.1°C Tccd8: +149.2°C Tccd9: +149.2°C Tccd12: +149.1°C Tccd14: +22.0°C Tccd15: +22.0°C Tccd16: +22.0°C Fix it by removing the said range. Fixes: 8440d5aca227 ("hwmon: (k10temp) Add per-CCD temperature monitoring for Zen5 Turin") Signed-off-by: Rong Zhang Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260911-k10temp-fix-zen5-epyc-v1-1-643f5a248ae1@rong.moe Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 75a45010d687a2..3e7e63edc6a300 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -523,7 +523,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) } } else if (boot_cpu_data.x86 == 0x1a) { switch (boot_cpu_data.x86_model) { - case 0x00 ... 0x2f: /* Zen5 Turin */ + case 0x00 ... 0x1f: /* Zen5 Turin */ data->ccd_offset = 0x1F0; k10temp_get_ccd_support(data, 16); break; From 26d5ff79768548efb1e604bb6e8697c101e06269 Mon Sep 17 00:00:00 2001 From: Yibo Tan Date: Fri, 11 Sep 2026 15:18:09 +0800 Subject: [PATCH 0344/1417] hwmon: (pwm-fan) Stop RPM timer before freeing tach data sample_timer() rearms the RPM timer and accesses the devm-managed ctx->tachs and ctx->pulses_per_revolution arrays. The cleanup action which stops the timer is registered before those arrays are allocated. Since devres releases entries in reverse order, driver detach can free the arrays before pwm_fan_cleanup() shuts down the timer. A timer expiry in that window accesses the freed tach data. With a KASAN kernel, a test-only kprobe delayed entry to pwm_fan_cleanup() while normal sysfs unbind ran. Each of three runs reported three four-byte reads and two four-byte writes in sample_timer() after its backing devm allocations had been freed. The helper did not invoke the timer callback, cleanup actions or free functions. With the fix, three matching unbind runs completed without KASAN, BUG, WARNING, Oops or panic. Instrumentation confirmed that timer retirement completed before the first timer backing allocation was released. Split timer retirement from the power cleanup and register its devres action after the timer backing data and IRQ actions are installed. This preserves the early power rollback action while ensuring the timer is retired before its backing data is released. Use timer_shutdown_sync() because the callback can rearm itself. Fixes: 01695410d452 ("hwmon: (pwm-fan) Store tach data separately") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Yibo Tan Link: https://patch.msgid.link/20260911071809.130151-1-lhfff@tju.edu.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/pwm-fan.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index 3b87f65bae0581..c633d7f6464c79 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -483,7 +483,6 @@ static void pwm_fan_cleanup(void *__ctx) { struct pwm_fan_ctx *ctx = __ctx; - timer_delete_sync(&ctx->rpm_timer); if (ctx->pwm_shutdown) { ctx->enable_mode = pwm_enable_reg_enable; __set_pwm(ctx, ctx->pwm_shutdown); @@ -494,6 +493,13 @@ static void pwm_fan_cleanup(void *__ctx) } } +static void pwm_fan_timer_cleanup(void *__ctx) +{ + struct pwm_fan_ctx *ctx = __ctx; + + timer_shutdown_sync(&ctx->rpm_timer); +} + static int pwm_fan_probe(struct platform_device *pdev) { struct thermal_cooling_device *cdev; @@ -644,6 +650,10 @@ static int pwm_fan_probe(struct platform_device *pdev) } if (ctx->tach_count > 0) { + ret = devm_add_action_or_reset(dev, pwm_fan_timer_cleanup, ctx); + if (ret) + return ret; + ctx->sample_start = ktime_get(); mod_timer(&ctx->rpm_timer, jiffies + HZ); @@ -700,6 +710,7 @@ static void pwm_fan_shutdown(struct platform_device *pdev) { struct pwm_fan_ctx *ctx = platform_get_drvdata(pdev); + pwm_fan_timer_cleanup(ctx); pwm_fan_cleanup(ctx); } From 7bae83ffb133bc373d098fa6828cef2ef4da49fe Mon Sep 17 00:00:00 2001 From: "Thomas Richard (congatec GmbH)" Date: Fri, 11 Sep 2026 19:31:58 +0200 Subject: [PATCH 0345/1417] hwmon: (cgbc-hwmon) Fix current sensors ID lookup Current sensors on the Congatec Board Controller don't use consecutive IDs, unlike other sensor types (voltage, temperature, fan). The driver assumed consecutive IDs and performed a simple lookup, which caused an unknown sensor warning. Define current sensor IDs explicitly. Changes the warning on conga-SA7 (type and channel are correct now). Before: Board Controller returned an unknown sensor (type=2, channel=17), ignore it After: Board Controller returned an unknown sensor (bc_type=1, bc_id=11), ignore it Cc: stable@kernel.org Fixes: 08ebc9def79f ("hwmon: Add Congatec Board Controller monitoring driver") Signed-off-by: Thomas Richard (congatec GmbH) Link: https://patch.msgid.link/20260911-cgbc-hwmon-fix-and-new-sensors-v2-1-0c6bf078d173@bootlin.com Signed-off-by: Guenter Roeck --- drivers/hwmon/cgbc-hwmon.c | 93 ++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/drivers/hwmon/cgbc-hwmon.c b/drivers/hwmon/cgbc-hwmon.c index 3aff4e092132f6..2effa0b5628625 100644 --- a/drivers/hwmon/cgbc-hwmon.c +++ b/drivers/hwmon/cgbc-hwmon.c @@ -52,31 +52,36 @@ static const char * const cgbc_hwmon_labels_temp[] = { "BOTTOMDIM Temperature", }; +static const char * const cgbc_hwmon_labels_in[] = { + "CPU Voltage", + "DC Runtime Voltage", + "DC Standby Voltage", + "CMOS Battery Voltage", + "Battery Voltage", + "AC Voltage", + "Other Voltage", + "5V Voltage", + "5V Standby Voltage", + "3V3 Voltage", + "3V3 Standby Voltage", + "VCore A Voltage", + "VCore B Voltage", + "12V Voltage", +}; + +/* + * Current sensors are a bit special, they don't have consecutive IDs like + * other types of sensors. So they need to be defined explicitly. + */ static const struct { - enum hwmon_sensor_types type; const char *label; -} cgbc_hwmon_labels_in[] = { - { hwmon_in, "CPU Voltage" }, - { hwmon_in, "DC Runtime Voltage" }, - { hwmon_in, "DC Standby Voltage" }, - { hwmon_in, "CMOS Battery Voltage" }, - { hwmon_in, "Battery Voltage" }, - { hwmon_in, "AC Voltage" }, - { hwmon_in, "Other Voltage" }, - { hwmon_in, "5V Voltage" }, - { hwmon_in, "5V Standby Voltage" }, - { hwmon_in, "3V3 Voltage" }, - { hwmon_in, "3V3 Standby Voltage" }, - { hwmon_in, "VCore A Voltage" }, - { hwmon_in, "VCore B Voltage" }, - { hwmon_in, "12V Voltage" }, - { hwmon_curr, "DC Current" }, - { hwmon_curr, "5V Current" }, - { hwmon_curr, "12V Current" }, + int id; +} cgbc_hwmon_labels_curr[] = { + { "DC Current", 0x12 }, + { "5V Current", 0x18 }, + { "12V Current", 0x1E }, }; -#define CGBC_HWMON_NB_IN_SENSORS 14 - static const char * const cgbc_hwmon_labels_fan[] = { "CPU Fan", "Box Fan", @@ -114,7 +119,8 @@ static int cgbc_hwmon_probe_sensors(struct device *dev, struct cgbc_hwmon_data * for (i = 0; i < nb_sensors; i++) { enum cgbc_sensor_types type; - unsigned int channel; + unsigned int channel, id; + int j; /* * No need to request data for the first sensor. @@ -128,32 +134,49 @@ static int cgbc_hwmon_probe_sensors(struct device *dev, struct cgbc_hwmon_data * } type = FIELD_GET(CGBC_HWMON_TYPE_MASK, data[1]); - channel = FIELD_GET(CGBC_HWMON_ID_MASK, data[1]) - 1; + id = FIELD_GET(CGBC_HWMON_ID_MASK, data[1]); + channel = id - 1; if (type == CGBC_HWMON_TYPE_TEMP && channel < ARRAY_SIZE(cgbc_hwmon_labels_temp)) { sensor->type = hwmon_temp; sensor->label = cgbc_hwmon_labels_temp[channel]; - } else if (type == CGBC_HWMON_TYPE_IN && - channel < ARRAY_SIZE(cgbc_hwmon_labels_in)) { + } else if (type == CGBC_HWMON_TYPE_IN) { /* * The Board Controller doesn't differentiate current and voltage sensors. - * Get the sensor type from cgbc_hwmon_labels_in[channel].type instead. + * First check if it is a current sensor. */ - sensor->type = cgbc_hwmon_labels_in[channel].type; - sensor->label = cgbc_hwmon_labels_in[channel].label; + for (j = 0; j < ARRAY_SIZE(cgbc_hwmon_labels_curr); j++) { + if (id == cgbc_hwmon_labels_curr[j].id) { + sensor->type = hwmon_curr; + sensor->label = cgbc_hwmon_labels_curr[j].label; + channel = j; + } + } + + /* If it's not a current sensor, it may be a voltage sensor. */ + if (!sensor->label && channel < ARRAY_SIZE(cgbc_hwmon_labels_in)) { + sensor->type = hwmon_in; + sensor->label = cgbc_hwmon_labels_in[channel]; + } } else if (type == CGBC_HWMON_TYPE_FAN && channel < ARRAY_SIZE(cgbc_hwmon_labels_fan)) { sensor->type = hwmon_fan; sensor->label = cgbc_hwmon_labels_fan[channel]; - } else { - dev_warn(dev, "Board Controller returned an unknown sensor (type=%d, channel=%d), ignore it", - type, channel); + } + + if (!sensor->label) { + dev_warn(dev, "Board Controller returned an unknown sensor (bc_type=%d, bc_id=%d), ignore it", + type, id); continue; } sensor->active = FIELD_GET(CGBC_HWMON_ACTIVE_BIT, data[1]); sensor->channel = channel; sensor->index = i; + + dev_dbg(dev, "Found sensor: bc_type=%d, bc_id=%d, hwmon_type=%d, hwmon_channel=%d, hwmon_label='%s', active=%d\n", + type, id, sensor->type, sensor->channel, sensor->label, sensor->active); + sensor++; hwmon->nb_sensors++; } @@ -167,14 +190,6 @@ static struct cgbc_hwmon_sensor *cgbc_hwmon_find_sensor(struct cgbc_hwmon_data * struct cgbc_hwmon_sensor *sensor = NULL; int i; - /* - * The Board Controller doesn't differentiate current and voltage sensors. - * The channel value (from the Board Controller point of view) shall be computed for current - * sensors. - */ - if (type == hwmon_curr) - channel += CGBC_HWMON_NB_IN_SENSORS; - for (i = 0; i < hwmon->nb_sensors; i++) { if (hwmon->sensors[i].type == type && hwmon->sensors[i].channel == channel) { sensor = &hwmon->sensors[i]; From 3550d1dbbcb9f51b77e077e8958423ee2c401c6c Mon Sep 17 00:00:00 2001 From: "Thomas Richard (congatec GmbH)" Date: Fri, 11 Sep 2026 19:31:59 +0200 Subject: [PATCH 0346/1417] hwmon: (cgbc-hwmon) Add missing sensors Add the following sensors: - Alternate Board Temperature (temp11_input) - Top DIMM 1-7 Temperature (temp12_input to temp18_input) - Bottom DIMM 1 Temperature (temp19_input) - 12V Standby Voltage (in14_input) This fixes the following warning on conga-SA7: Board Controller returned an unknown sensor (bc_type=1, bc_id=11), ignore it Also update existing labels to match Congatec documentation. Cc: stable@kernel.org Fixes: 08ebc9def79f ("hwmon: Add Congatec Board Controller monitoring driver") Signed-off-by: Thomas Richard (congatec GmbH) Link: https://patch.msgid.link/20260911-cgbc-hwmon-fix-and-new-sensors-v2-2-0c6bf078d173@bootlin.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/cgbc-hwmon.rst | 42 +++++++++++++++--------- drivers/hwmon/cgbc-hwmon.c | 52 +++++++++++++++++++----------- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/Documentation/hwmon/cgbc-hwmon.rst b/Documentation/hwmon/cgbc-hwmon.rst index 3a5e6e6e8639ea..c6d09232392ac2 100644 --- a/Documentation/hwmon/cgbc-hwmon.rst +++ b/Documentation/hwmon/cgbc-hwmon.rst @@ -28,34 +28,44 @@ system. Name Description ============= ====================== temp1_input CPU temperature -temp2_input Box temperature +temp2_input Case temperature temp3_input Ambient temperature -temp4_input Board temperature -temp5_input Carrier temperature -temp6_input Chipset temperature -temp7_input Video temperature +temp4_input CPU Board temperature +temp5_input Carrier Board temperature +temp6_input System Chipset temperature +temp7_input Video Controller/Board temperature temp8_input Other temperature -temp9_input TOPDIM temperature -temp10_input BOTTOMDIM temperature -in0_input CPU voltage +temp9_input Top DIMM 0 temperature +temp10_input Bottom DIMM 0 temperature +temp11_input Alternate Board temperature +temp12_input Top DIMM 1 temperature +temp13_input Top DIMM 2 temperature +temp14_input Top DIMM 3 temperature +temp15_input Top DIMM 4 temperature +temp16_input Top DIMM 5 temperature +temp17_input Top DIMM 6 temperature +temp18_input Top DIMM 7 temperature +temp19_input Bottom DIMM 1 temperature +in0_input CPU Core voltage in1_input DC Runtime voltage in2_input DC Standby voltage in3_input CMOS Battery voltage -in4_input Battery voltage +in4_input Battery Supply voltage in5_input AC voltage in6_input Other voltage -in7_input 5V voltage +in7_input 5V Runtime voltage in8_input 5V Standby voltage -in9_input 3V3 voltage +in9_input 3V3 Runtime voltage in10_input 3V3 Standby voltage in11_input VCore A voltage in12_input VCore B voltage -in13_input 12V voltage -curr1_input DC current -curr2_input 5V current -curr3_input 12V current +in13_input 12V Runtime voltage +in14_input 12V Standby voltage +curr1_input DC Runtime current +curr2_input 5V Runtime current +curr3_input 12V Runtime current fan1_input CPU fan -fan2_input Box fan +fan2_input Case fan fan3_input Ambient fan fan4_input Chiptset fan fan5_input Video fan diff --git a/drivers/hwmon/cgbc-hwmon.c b/drivers/hwmon/cgbc-hwmon.c index 2effa0b5628625..230062a4690738 100644 --- a/drivers/hwmon/cgbc-hwmon.c +++ b/drivers/hwmon/cgbc-hwmon.c @@ -41,32 +41,42 @@ enum cgbc_sensor_types { static const char * const cgbc_hwmon_labels_temp[] = { "CPU Temperature", - "Box Temperature", + "Case Temperature", "Ambient Temperature", - "Board Temperature", - "Carrier Temperature", - "Chipset Temperature", - "Video Temperature", + "CPU Board Temperature", + "Carrier Board Temperature", + "System Chipset Temperature", + "Video Controller/Board Temperature", "Other Temperature", - "TOPDIM Temperature", - "BOTTOMDIM Temperature", + "Top DIMM 0 Temperature", + "Bottom DIMM 0 Temperature", + "Alternate Board Temperature", + "Top DIMM 1 Temperature", + "Top DIMM 2 Temperature", + "Top DIMM 3 Temperature", + "Top DIMM 4 Temperature", + "Top DIMM 5 Temperature", + "Top DIMM 6 Temperature", + "Top DIMM 7 Temperature", + "Bottom DIMM 1 Temperature", }; static const char * const cgbc_hwmon_labels_in[] = { - "CPU Voltage", + "CPU Core Voltage", "DC Runtime Voltage", "DC Standby Voltage", "CMOS Battery Voltage", - "Battery Voltage", + "Battery Supply Voltage", "AC Voltage", "Other Voltage", - "5V Voltage", + "5V Runtime Voltage", "5V Standby Voltage", - "3V3 Voltage", + "3V3 Runtime Voltage", "3V3 Standby Voltage", "VCore A Voltage", "VCore B Voltage", - "12V Voltage", + "12V Runtime Voltage", + "12V Standby Voltage", }; /* @@ -77,14 +87,14 @@ static const struct { const char *label; int id; } cgbc_hwmon_labels_curr[] = { - { "DC Current", 0x12 }, - { "5V Current", 0x18 }, - { "12V Current", 0x1E }, + { "DC Runtime Current", 0x12 }, + { "5V Runtime Current", 0x18 }, + { "12V Runtime Current", 0x1E }, }; static const char * const cgbc_hwmon_labels_fan[] = { "CPU Fan", - "Box Fan", + "Case Fan", "Ambient Fan", "Chipset Fan", "Video Fan", @@ -255,7 +265,12 @@ static const struct hwmon_channel_info * const cgbc_hwmon_info[] = { HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, - HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, @@ -263,7 +278,8 @@ static const struct hwmon_channel_info * const cgbc_hwmon_info[] = { HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, - HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL), + HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL), From 06bd6794b5fd2163880ac3bfe973d4cc61f359f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 11 Sep 2026 14:53:37 +0100 Subject: [PATCH 0347/1417] hwmon: (pmbus/core) increase number of phases and add new mask MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Increase the number of phases to 16 as a new upcoming device supports such a number. While at it, add a new mask for controlling the source of the output voltage. Note (groeck): This patch was meant to prepare for support of MAX20826 and compatible devices, which support more than 10 phases per page. However, Sashiko reports that the mp2975 driver already supports up to 14 phases, and the mp2856 driver supports up to 12 phases. This already has the potential for out-of-bounds writes when probing the affected chips, making this patch a bug fix. Fixes: 2c6fcbb21149 ("hwmon: (pmbus) Add support for MPS Multi-phase mp2975 controller") Fixes: f9e5f289b686 ("hwmon: (pmbus) Add support for MPS Multi-phase mp2856/mp2857 controller") Signed-off-by: Nuno Sá Link: https://patch.msgid.link/20260911-hwmon-max20826-support-v2-1-5e30cbd97d84@analog.com Cc: stable@vger.kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 2cd3216b3cd957..920c1102ab6df5 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -242,6 +242,7 @@ enum pmbus_regs { /* * OPERATION */ +#define PB_OPERATION_CONTROL_V_SRC GENMASK(5, 4) #define PB_OPERATION_CONTROL_ON BIT(7) /* @@ -386,7 +387,7 @@ enum pmbus_sensor_classes { }; #define PMBUS_PAGES 32 /* Per PMBus specification */ -#define PMBUS_PHASES 10 /* Maximum number of phases per page */ +#define PMBUS_PHASES 16 /* Maximum number of phases per page */ /* Functionality bit mask */ #define PMBUS_HAVE_VIN BIT(0) From 490a83d6386eec1d29f470c8d7331677fb46c3b7 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Tue, 8 Sep 2026 17:08:32 +0800 Subject: [PATCH 0348/1417] bpf, sockmap: Fix self-redirect copied_seq double-counting When a BPF stream_verdict program redirects an skb back to the same socket (self-redirect with BPF_F_INGRESS), sk_psock_verdict_apply() calls tcp_eat_skb() which advances tcp_sk->copied_seq. However, the skb is then delivered to the socket's psock ingress queue and later read by tcp_bpf_recvmsg_parser(), which also advances copied_seq via the copied_from_self accounting path. This double-counting causes copied_seq to advance by 2x the actual data length, triggering: TCP recvmsg seq # bug 2: copied BF2E806, seq BF2E7FD, \ rcvnxt BF2E806, fl 0 WARNING: net/ipv4/tcp.c:2745 at tcp_recvmsg_locked+0x72b/0x2640 Call Trace: tcp_recvmsg+0x10a/0x500 sock_recvmsg+0x168/0x1d0 __sys_recvfrom+0x19a/0x2a0 __x64_sys_recvfrom+0xe4/0x1f0 do_syscall_64+0xf7/0x530 entry_SYSCALL_64_after_hwframe+0x77/0x7f cleanup rbuf bug: copied BF2E806 seq BF2E806 rcvnxt BF2E806 WARNING: net/ipv4/tcp.c:1609 at tcp_cleanup_rbuf+0xf2/0x1c0 Call Trace: tcp_recvmsg_locked+0x8d1/0x2640 tcp_recvmsg+0x10a/0x500 sock_recvmsg+0x168/0x1d0 __sys_recvfrom+0x19a/0x2a0 __x64_sys_recvfrom+0xe4/0x1f0 do_syscall_64+0xf7/0x530 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fix this by converting self-redirect verdict to __SK_PASS at the beginning of sk_psock_verdict_apply(). This bypasses the __SK_REDIRECT case entirely (which calls sk_psock_eat_skb), letting the __SK_PASS path queue the skb to the psock ingress queue. The data is then read via tcp_bpf_recvmsg_parser(), which advances copied_seq exactly once through copied_from_self. Cross-socket redirects continue through __SK_REDIRECT with sk_psock_eat_skb() unchanged. Fixes: e5c6de5fa025 ("bpf, sockmap: Incorrectly handling copied_seq") Suggested-by: Jakub Sitnicki Suggested-by: Jiayuan Chen Signed-off-by: Geliang Tang Reviewed-by: Emil Tsalapatis Reviewed-by: Jiayuan Chen Link: https://lore.kernel.org/r/1a8e797a1b26e2f695aaac22ac644c2862f63466.1788858299.git.tanggeliang@kylinos.cn Signed-off-by: Alexei Starovoitov --- net/core/skmsg.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/core/skmsg.c b/net/core/skmsg.c index 2521b643fa05d4..df385a5a961e83 100644 --- a/net/core/skmsg.c +++ b/net/core/skmsg.c @@ -1000,6 +1000,10 @@ static int sk_psock_verdict_apply(struct sk_psock *psock, struct sk_buff *skb, int err = 0; u32 len, off; + if (verdict == __SK_REDIRECT && skb_bpf_ingress(skb) && + skb_bpf_redirect_fetch(skb) == psock->sk) + verdict = __SK_PASS; + switch (verdict) { case __SK_PASS: err = -EIO; From 953824e508b27d12837e32ef37ef6248e1f6fc7a Mon Sep 17 00:00:00 2001 From: Masoud Aghasi Date: Thu, 3 Sep 2026 09:27:34 +0100 Subject: [PATCH 0349/1417] bpf: Fix u32 overflow issue in map batch operations Several map batch operation implementations such as generic_map_lookup_batch() use calculations in the form of "values + cp * map->value_size" to compute the desired userspace memory address for reading or writing. This can overflow the u32 type (the result of "cp * map->value_size") when the map size exceeds 4GB. generic_map_lookup_batch() may corrupt values for some keys in userspace memory, and in some cases it mismatches values for some keys while still reporting success. Other batch operations may fail to delete or update some keys, or the syscall may return unexpected errors. Add size_t casts to prevent the affected offset and size calculations from overflowing. Fixes: cb4d03ab499d ("bpf: Add generic support for lookup batch op") Fixes: aa2e93b8e58e ("bpf: Add generic support for update and delete batch ops") Fixes: 057996380a42 ("bpf: Add batch ops to all htab bpf map") Signed-off-by: Masoud Aghasi Link: https://lore.kernel.org/r/20260903082734.623904-1-maghasi@disroot.org Signed-off-by: Alexei Starovoitov --- kernel/bpf/hashtab.c | 8 ++++---- kernel/bpf/syscall.c | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index a72dc5b9f184d4..4f495dcbf670c3 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -1997,10 +1997,10 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, rcu_read_unlock(); bpf_enable_instrumentation(); - if (bucket_cnt && (copy_to_user(ukeys + total * key_size, keys, - key_size * bucket_cnt) || - copy_to_user(uvalues + total * value_size, values, - value_size * bucket_cnt))) { + if (bucket_cnt && (copy_to_user(ukeys + (size_t)total * key_size, keys, + (size_t)key_size * bucket_cnt) || + copy_to_user(uvalues + (size_t)total * value_size, values, + (size_t)value_size * bucket_cnt))) { ret = -EFAULT; goto after_loop; } diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index c7bc9ba9b331f0..853b47f8138425 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2036,7 +2036,7 @@ int generic_map_delete_batch(struct bpf_map *map, for (cp = 0; cp < max_count; cp++) { err = -EFAULT; - if (copy_from_user(key, keys + cp * map->key_size, + if (copy_from_user(key, keys + (size_t)cp * map->key_size, map->key_size)) break; @@ -2098,9 +2098,9 @@ int generic_map_update_batch(struct bpf_map *map, struct file *map_file, for (cp = 0; cp < max_count; cp++) { err = -EFAULT; - if (copy_from_user(key, keys + cp * map->key_size, + if (copy_from_user(key, keys + (size_t)cp * map->key_size, map->key_size) || - copy_from_user(value, values + cp * value_size, value_size)) + copy_from_user(value, values + (size_t)cp * value_size, value_size)) break; err = bpf_map_update_value(map, map_file, key, value, @@ -2179,12 +2179,12 @@ int generic_map_lookup_batch(struct bpf_map *map, if (err) goto free_buf; - if (copy_to_user(keys + cp * map->key_size, key, + if (copy_to_user(keys + (size_t)cp * map->key_size, key, map->key_size)) { err = -EFAULT; goto free_buf; } - if (copy_to_user(values + cp * value_size, value, value_size)) { + if (copy_to_user(values + (size_t)cp * value_size, value, value_size)) { err = -EFAULT; goto free_buf; } From ef1fb82f12186dd26153b14d9fbcf4ec98db81b3 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 22:06:23 +0900 Subject: [PATCH 0350/1417] bpf, arm64: set up the frame pointer for the exception callback A program acting as exception boundary saves all callee-saved registers, so build_prologue() takes the exception_cb path and never calls push_callee_regs(). That is the only place find_used_callee_regs() runs, and with it the only place ctx->fp_used is set, so the callback prologue does not emit the mov x25, sp that points BPF_REG_FP at the frame the callback runs on. x25 keeps whatever it held when bpf_throw() was called. If the throw came from a subprogram that uses its own BPF stack, that is the subprogram's frame pointer, and since the subprogram never returns it never restores x25 either. Stack accesses through BPF_REG_FP are rewritten to be stack pointer relative, so those still land in the callback's own frame. Materializing the register does not: a callback that passes the address of a local variable to a helper hands over an address in the dead subprogram's frame. That address is below the callback's stack pointer by then, and the helper's own call chain covers it, so the helper can write over its own return address. 0x1234 below is the value the helper was asked to store: pc : 0x1234 lr : 0x1234 Call trace: 0x1234 (P) bpf_test_run+0x188/0x3e0 bpf_prog_test_run_skb+0x47c/0x998 __sys_bpf+0xbdc/0xdd8 Kernel panic - not syncing: Oops: Fatal exception in interrupt Set ctx->fp_used on the exception callback path so that the existing code further down sets x25 from the stack pointer. The epilogue restores it from the main program's save area along with the other callee-saved registers, as it already does. x86 sets the frame pointer for the callback from the argument it is passed, and powerpc computes it from the stack pointer. Fixes: 5d4fa9ec5643 ("bpf, arm64: Avoid blindly saving/restoring all callee-saved registers") Acked-by: Xu Kuohai Signed-off-by: Donggeun Yoo Link: https://lore.kernel.org/r/20260907130624.611942-2-donggeunyoo.kernel@gmail.com Signed-off-by: Alexei Starovoitov --- arch/arm64/net/bpf_jit_comp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c index c18e005a41dbeb..c5f55d6161fee0 100644 --- a/arch/arm64/net/bpf_jit_comp.c +++ b/arch/arm64/net/bpf_jit_comp.c @@ -600,6 +600,8 @@ static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf) * 12 registers are on the stack */ emit(A64_SUB_I(1, A64_SP, A64_FP, 96), ctx); + /* The callback may use its own BPF stack, set up fp for it. */ + ctx->fp_used = true; } /* Stack must be multiples of 16B */ From 26a43a5f8c31b9132dc40a449d0f6c7ecfaa1f40 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 22:06:24 +0900 Subject: [PATCH 0351/1417] selftests/bpf: cover the exception callback using its own BPF stack The existing exception tests do not reach a callback that materializes BPF_REG_FP into a register. They either throw from the main program, where BPF_REG_FP already holds the value the callback needs, or use a callback whose only stack accesses are frame pointer relative, which the arm64 JIT rewrites to be stack pointer relative. Add a test that throws from a subprogram using its own BPF stack, with a callback that hands the address of a local variable to bpf_probe_read_kernel(). The helper and the callback have to name the same slot for the value read back to be the one the helper stored. Signed-off-by: Donggeun Yoo Link: https://lore.kernel.org/r/20260907130624.611942-3-donggeunyoo.kernel@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/exceptions.c | 1 + .../testing/selftests/bpf/progs/exceptions.c | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/exceptions.c b/tools/testing/selftests/bpf/prog_tests/exceptions.c index 3588d6f97fd4e0..639866ce09a9f2 100644 --- a/tools/testing/selftests/bpf/prog_tests/exceptions.c +++ b/tools/testing/selftests/bpf/prog_tests/exceptions.c @@ -55,6 +55,7 @@ static void test_exceptions_success(void) RUN_SUCCESS(exception_ext, 0); RUN_SUCCESS(exception_ext_mod_cb_runtime, 35); RUN_SUCCESS(exception_throw_subprog, 1); + RUN_SUCCESS(exception_throw_subprog_stack_cb, 0x1234); RUN_SUCCESS(exception_assert_nz_gfunc, 1); RUN_SUCCESS(exception_assert_zero_gfunc, 1); RUN_SUCCESS(exception_assert_neg_gfunc, 1); diff --git a/tools/testing/selftests/bpf/progs/exceptions.c b/tools/testing/selftests/bpf/progs/exceptions.c index c8d716fbd41910..91c81971e58c23 100644 --- a/tools/testing/selftests/bpf/progs/exceptions.c +++ b/tools/testing/selftests/bpf/progs/exceptions.c @@ -212,6 +212,36 @@ int exception_throw_subprog(struct __sk_buff *ctx) return 0; } +u64 exception_cb_stack_src = 0x1234; + +/* + * The address handed to the helper has to be this callback's own stack + * slot, not one from a frame that is already gone. + */ +__noinline int exception_cb_stack(u64 cookie) +{ + volatile u64 val = 0xdead; + + bpf_probe_read_kernel((void *)&val, sizeof(val), &exception_cb_stack_src); + return val; +} + +/* Throws from a subprogram that has a stack of its own. */ +__noinline static int throwing_subprog_stack(struct __sk_buff *ctx) +{ + volatile u64 pad[4] = {}; + + bpf_throw(pad[0]); + return 0; +} + +SEC("tc") +__exception_cb(exception_cb_stack) +int exception_throw_subprog_stack_cb(struct __sk_buff *ctx) +{ + return throwing_subprog_stack(ctx); +} + __noinline int assert_nz_gfunc(u64 c) { volatile u64 cookie = c; From 2de887f891662814b1160cbfb8c1bf2a5a46d418 Mon Sep 17 00:00:00 2001 From: Xavier Goffin Date: Sun, 13 Sep 2026 23:11:55 +0200 Subject: [PATCH 0352/1417] ALSA: hda/realtek: Add mute LED quirk for HP OMEN 15-ax On the HP OMEN 15 ax-202nf, the keyboard mute LED is exposed through NID 0x1b rather than 0x18. This reuses the existing quirk (ALC269_FIXUP_HP_MUTE_LED_MIC3) to allow the keyboard LED to reflect the built-in speaker mute state. Tested on HP OMEN 15 ax-202nf with Realtek ALC295: - hda::mute/brightness properly follows mute state - mute/unmute via keyboard shortcut or via GUI volume control - state is kept on suspend & resume, and reboot - plugging a 3.5mm jack headset reflects the headset mute status - unplugging reverts the LED to the speaker mute status - USB/Bluetooth headsets are not covered Signed-off-by: Xavier Goffin Link: https://patch.msgid.link/20260913211155.20305-1-xaviergoffin42@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index dc73fa95b00a0a..fa83b739fb5080 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7358,6 +7358,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8158, "HP", ALC256_FIXUP_HP_HEADSET_MIC), SND_PCI_QUIRK(0x103c, 0x820d, "HP Pavilion 15", ALC295_FIXUP_HP_X360), SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), + SND_PCI_QUIRK(0x103c, 0x8259, "HP OMEN 15-ax202nf", ALC269_FIXUP_HP_MUTE_LED_MIC3), SND_PCI_QUIRK(0x103c, 0x827e, "HP x360", ALC295_FIXUP_HP_X360), SND_PCI_QUIRK(0x103c, 0x827f, "HP x360", ALC269_FIXUP_HP_MUTE_LED_MIC3), SND_PCI_QUIRK(0x103c, 0x82bf, "HP G3 mini", ALC221_FIXUP_HP_MIC_NO_PRESENCE), From beb34fe8312eda37b9cf1568550d722530444822 Mon Sep 17 00:00:00 2001 From: Kitty Makin Date: Mon, 14 Sep 2026 00:24:24 +0000 Subject: [PATCH 0353/1417] ALSA: usb-audio: Add capture quirk for Behringer FCA1616 The Behringer FCA1616 (1397:0004) returns silent capture samples unless its playback endpoint is active. Use the existing fixed implicit-feedback mechanism to keep playback endpoint 0x01 on interface 1 active during capture. Tested with 16-channel S32_LE capture at 44.1 and 48 kHz. Signed-off-by: Kitty Makin Link: https://patch.msgid.link/20260914002334.12691-1-autumnull@posteo.net Signed-off-by: Takashi Iwai --- sound/usb/implicit.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/usb/implicit.c b/sound/usb/implicit.c index 77f06da93151e8..bd4d569a8190ca 100644 --- a/sound/usb/implicit.c +++ b/sound/usb/implicit.c @@ -76,6 +76,7 @@ static const struct snd_usb_implicit_fb_match playback_implicit_fb_quirks[] = { /* Implicit feedback quirk table for capture: only FIXED type */ static const struct snd_usb_implicit_fb_match capture_implicit_fb_quirks[] = { + IMPLICIT_FB_FIXED_DEV(0x1397, 0x0004, 0x01, 1), /* Behringer FCA1616 */ {} /* terminator */ }; From 5f90f85eae4e9d2e9628b2019870994ba830b533 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 9 Sep 2026 14:37:07 +0200 Subject: [PATCH 0354/1417] power: sequencing: don't call .post_enable() if pwrseq_unit_enable() failed If the call to pwrseq_unit_enable() failed in pwrseq_enable(), bail out instead of calling target->post_enable() which assumes the target was successfully enabled. Fixes: 249ebf3f65f8 ("power: sequencing: implement the pwrseq core") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260909-pwrseq-kunit-v2-1-ef496afc89d2@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 721e888b658d1f..76c39600f690d9 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -912,6 +912,8 @@ int pwrseq_enable(struct pwrseq_desc *desc) if (!ret) desc->powered_on = true; } + if (ret) + return ret; if (target->post_enable) { ret = target->post_enable(pwrseq); From 115b303e8e093d964089ec6f3c40d984d77b33d0 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 9 Sep 2026 14:37:08 +0200 Subject: [PATCH 0355/1417] power: sequencing: fix NULL-pointer dereference in pwrseq_unit_new() If memory allocation fails in pwrseq_unit_setup_deps(), pwrseq_unit_put() is called to release the partially initialized unit. However, we've never initialized unit->list and pwrseq_unit_release() will unconditionally call list_del() on it. Initialize unit->list right after allocating the unit struct. Fixes: 249ebf3f65f8 ("power: sequencing: implement the pwrseq core") Cc: stable@vger.kernel.org Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260903-pwrseq-kunit-v1-0-1f893d2cabc2%40oss.qualcomm.com?part=1 Link: https://patch.msgid.link/20260909-pwrseq-kunit-v2-2-ef496afc89d2@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 76c39600f690d9..7751ce8cd8f5a4 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -101,6 +101,7 @@ static struct pwrseq_unit *pwrseq_unit_new(const struct pwrseq_unit_data *data) } kref_init(&unit->ref); + INIT_LIST_HEAD(&unit->list); INIT_LIST_HEAD(&unit->deps); unit->enable = data->enable; unit->disable = data->disable; From 242da4318d97380741516b595af3920207b2f0f1 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 9 Sep 2026 14:37:09 +0200 Subject: [PATCH 0356/1417] power: sequencing: fix NULL-pointer dereference in pwrseq_device_register() If dev_set_name() fails in pwrseq_device_register(), we jump to the err_put_pwrseq label before initializing pwrseq->targets. pwrseq_release() will try to iterate over targets unconditionally and subsequently dereference an invalid pointer. Move the call to dev_set_name() after the list head is initialized. Fixes: 249ebf3f65f8 ("power: sequencing: implement the pwrseq core") Cc: stable@vger.kernel.org Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260903-pwrseq-kunit-v1-0-1f893d2cabc2%40oss.qualcomm.com?part=2 Link: https://patch.msgid.link/20260909-pwrseq-kunit-v2-3-ef496afc89d2@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 7751ce8cd8f5a4..392d725374850e 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -505,10 +505,6 @@ pwrseq_device_register(const struct pwrseq_config *config) */ device_initialize(&pwrseq->dev); - ret = dev_set_name(&pwrseq->dev, "pwrseq.%d", pwrseq->id); - if (ret) - goto err_put_pwrseq; - pwrseq->owner = config->owner ?: THIS_MODULE; pwrseq->match = config->match; @@ -517,6 +513,10 @@ pwrseq_device_register(const struct pwrseq_config *config) INIT_LIST_HEAD(&pwrseq->targets); INIT_LIST_HEAD(&pwrseq->units); + ret = dev_set_name(&pwrseq->dev, "pwrseq.%d", pwrseq->id); + if (ret) + goto err_put_pwrseq; + ret = pwrseq_setup_targets(config->targets, pwrseq); if (ret) goto err_put_pwrseq; From 8dc2615d5702059b2b71fca6f93c0d7d10ae54cb Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Fri, 11 Sep 2026 16:30:58 +0900 Subject: [PATCH 0357/1417] arm64: dts: renesas: r8a779f0: Set UFS lane count Since commit e72323f3b09f ("scsi: ufs: core: Configure only active lanes during link"), the following error is observed on R-Car S4: ufshcd-renesas e6860000.ufs: Tx lane mismatch [config,reported] [2,1] ufshcd-renesas e6860000.ufs: link startup failed -67 ufshcd-renesas e6860000.ufs: error -ENOLINK: Initialization failed with error -67 ufshcd-renesas e6860000.ufs: probe with driver ufshcd-renesas failed with error -67 R-Car S4 has one UFS lane per direction, as described in section 152.1 of its hardware manual. Without lanes-per-direction, the UFS platform driver defaults to two lanes. Previously, the core used PA_CONNECTEDRXDATALANES and PA_CONNECTEDTXDATALANES to configure the link without checking them against lanes-per-direction, so the missing property did not prevent initialization. Explicitly set lanes-per-direction to 1, now that the validation is in place. Fixes: 5235d551779d ("arm64: dts: renesas: r8a779f0: Add UFS node") Cc: stable@vger.kernel.org # 7.2+ Signed-off-by: Koichiro Den Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260911073058.253000-1-den@valinux.co.jp Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r8a779f0.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/renesas/r8a779f0.dtsi b/arch/arm64/boot/dts/renesas/r8a779f0.dtsi index cbb161c863ac7b..a5118c2f27429b 100644 --- a/arch/arm64/boot/dts/renesas/r8a779f0.dtsi +++ b/arch/arm64/boot/dts/renesas/r8a779f0.dtsi @@ -901,6 +901,7 @@ clocks = <&cpg CPG_MOD 1514>, <&ufs30_clk>; clock-names = "fck", "ref_clk"; freq-table-hz = <200000000 200000000>, <38400000 38400000>; + lanes-per-direction = <1>; power-domains = <&sysc R8A779F0_PD_ALWAYS_ON>; resets = <&cpg 1514>; status = "disabled"; From 692f32609a30f75ca3401e25b504bfd06bd5662a Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 17 Aug 2026 12:22:11 -0400 Subject: [PATCH 0358/1417] pinctrl: meson: Fix typo in s4 group name One of the i2c pin groups has some junk at the end. The name should be i2c2_scl_h1, and indeed that's the name used by i2c2_pins3 in meson-s4.dtsi. Fixes: 775214d389c25 ("pinctrl: meson: add pinctrl driver support for Meson-S4 Soc") Signed-off-by: Sean Anderson Reviewed-by: Neil Armstrong Signed-off-by: Linus Walleij --- drivers/pinctrl/meson/pinctrl-meson-s4.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/meson/pinctrl-meson-s4.c b/drivers/pinctrl/meson/pinctrl-meson-s4.c index 872948699e9fee..365dafe457a9f1 100644 --- a/drivers/pinctrl/meson/pinctrl-meson-s4.c +++ b/drivers/pinctrl/meson/pinctrl-meson-s4.c @@ -854,7 +854,7 @@ static const char * const i2c1_groups[] = { static const char * const i2c2_groups[] = { "i2c2_sda_d", "i2c2_scl_d", "i2c2_sda_h8", "i2c2_scl_h9", - "i2c2_sda_h0", "i2c2_scl_h1l," + "i2c2_sda_h0", "i2c2_scl_h1", }; static const char * const i2c3_groups[] = { From 50fd0ada8d37587223001600933270b59cb30e19 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Mon, 14 Sep 2026 13:15:37 +0800 Subject: [PATCH 0359/1417] gpio: virtuser: skip free_irq when no IRQ is installed Disabling interrupt monitoring uses atomic_xchg() to clear the stored IRQ. When monitoring is already disabled, atomic_xchg() returns 0. It must not be passed to free_irq(). The bug is reproducible on an x86_64 QEMU guest with CONFIG_GPIO_VIRTUSER=y and CONFIG_GPIO_SIM=y. Configure a live gpio-virtuser device through configfs. Its input lookup must refer to a live gpio-sim bank, such as key gpio-sim-test with offset 0. The consumer's dev_name attribute is shown as below; then run: echo 0 > /sys/kernel/debug/gpio-virtuser//gpiod:input:0/interrupts On an unpatched kernel, this reaches gpio_virtuser_interrupts_set() with ld->irq still at its initial value 0, and free_irq() reports: Trying to free already-free IRQ 0 The same reproducer completes without the warning on the patched kernel. Fixes: 91581c4b3f29 ("gpio: virtuser: new virtual testing driver for the GPIO API") Assisted-by: LLM Signed-off-by: Runyu Xiao Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260914051537.15320-1-runyu.xiao@seu.edu.cn Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-virtuser.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-virtuser.c b/drivers/gpio/gpio-virtuser.c index 7d0d366be37a48..d5876c19cfefa2 100644 --- a/drivers/gpio/gpio-virtuser.c +++ b/drivers/gpio/gpio-virtuser.c @@ -692,7 +692,8 @@ static int gpio_virtuser_interrupts_set(void *data, u64 val) atomic_set(&ld->irq, irq); } else { irq = atomic_xchg(&ld->irq, 0); - free_irq(irq, ld); + if (irq) + free_irq(irq, ld); } return 0; From 51ae99659469edba2e83931efa26b77d36ca02c2 Mon Sep 17 00:00:00 2001 From: Sarah Emery Date: Fri, 28 Aug 2026 17:58:17 +0200 Subject: [PATCH 0360/1417] pinctrl: generic: serialise pinctrl_generic_dt_node_to_map() pinctrl_generic_add_group() documents that the caller must take care of locking, and pinmux_generic_add_function() needs it too, but pinctrl_generic_dt_node_to_map() calls them without holding pctldev->mutex, and the core caller in create_pinctrl() does not take it either. The driver core calls pinctrl_bind_pins() before probing a device, so two devices that reference the same pin controller can run pinctrl_generic_dt_node_to_map() on one pctldev at the same time. Both `add` functions take the new selector from pctldev->num_groups or pctldev->num_functions, and radix_tree_insert() at that index. Two racing callers can read the same selector before either has inserted, so the second insert collides and fails: k1-pinctrl d401e000.pinctrl: error -EEXIST: error adding function pcie2-0-cfg k1-pinctrl d401e000.pinctrl: does not have pin group pcie0-0-cfg.pcie0-0-pins leaving one consumer without its pin configuration. This was hit on a SpacemiT K3 board, where PCIe devices probe in parallel against the single shared pin controller. Take pctldev->mutex across the whole function, so that the groups and the function referring are in a single critical section. Fixes: 43722575e5cd ("pinctrl: add generic functions + pins mapper") Signed-off-by: Sarah Emery Signed-off-by: Linus Walleij --- drivers/pinctrl/pinctrl-generic.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pinctrl/pinctrl-generic.c b/drivers/pinctrl/pinctrl-generic.c index fd6bdb74028aa5..4277c87485135c 100644 --- a/drivers/pinctrl/pinctrl-generic.c +++ b/drivers/pinctrl/pinctrl-generic.c @@ -3,8 +3,10 @@ #define pr_fmt(fmt) "generic pinconfig core: " fmt #include +#include #include #include +#include #include #include @@ -196,6 +198,8 @@ static int pinctrl_generic_dt_node_to_map(struct pinctrl_dev *pctldev, int ngroups = 0; int ret; + guard(mutex)(&pctldev->mutex); + *maps = NULL; *num_maps = 0; From e6c5d6c589764a41218cbd81bd7d6c9b906e2cf0 Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Mon, 31 Aug 2026 11:50:58 +0100 Subject: [PATCH 0361/1417] pinctrl: mpfs-mssio: fix width of unused bank voltage setting The bank voltages are only 4 bits wide, so when a pin was unused the driver was not correctly interpreting it as being at zero volts, because the driver's value for unused had two extra set bits. CC: stable@vger.kernel.org Fixes: 488d704ed7b7 ("pinctrl: add polarfire soc mssio pinctrl driver") Signed-off-by: Conor Dooley Signed-off-by: Linus Walleij --- drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c index ea1026a0d22c8d..dafca82f3e54a4 100644 --- a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c +++ b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c @@ -86,7 +86,7 @@ static struct mpfs_pinctrl_bank_voltage mpfs_pinctrl_bank_voltages[8] = { { .uv = 1800000, .val = 4 }, { .uv = 2500000, .val = 6 }, { .uv = 3300000, .val = 8 }, - { .uv = 0, .val = 0x3f }, // pin unused + { .uv = 0, .val = 0xf }, // pin unused }; static int mpfs_pinctrl_get_drive_strength_ma(u32 drive_strength) From 8039b75af5808572ff3656eaed07d348aed3989a Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Mon, 31 Aug 2026 11:50:59 +0100 Subject: [PATCH 0362/1417] pinctrl: mpfs-mssio: use correct regmap function to set bank voltage regmap_assign_bits() is not the correct function to use for an RMW operation, as it maps to regmap_set_bits() or regmap_clear_bits() and the former will never zero a bit. Use regmap_update_bits() instead, which will actually set the bank voltages to what have been requested. CC: stable@vger.kernel.org Fixes: 488d704ed7b7 ("pinctrl: add polarfire soc mssio pinctrl driver") Signed-off-by: Conor Dooley Signed-off-by: Linus Walleij --- drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c index dafca82f3e54a4..92d38e5336de05 100644 --- a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c +++ b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c @@ -156,10 +156,10 @@ static void mpfs_pinctrl_set_bank_voltage(struct mpfs_pinctrl *pctrl, unsigned i u32 val = FIELD_PREP(MPFS_PINCTRL_BANK_VOLTAGE_MASK, bank_voltage); if (pin < MPFS_PINCTRL_BANK2_START) - regmap_assign_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK4_CFG_CR, + regmap_update_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK4_CFG_CR, MPFS_PINCTRL_BANK_VOLTAGE_MASK, val); else - regmap_assign_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK2_CFG_CR, + regmap_update_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK2_CFG_CR, MPFS_PINCTRL_BANK_VOLTAGE_MASK, val); } From 1589afe2d099d3e817873bc474676968d7080410 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Mon, 14 Sep 2026 00:43:24 -0700 Subject: [PATCH 0363/1417] ALSA: 6fire: fix OOB write from device-reported iso length usb6fire_pcm_in_urb_handler() sizes each outgoing isochronous packet as (actual_length - 4) / (in_n_analog << 2) * (out_n_analog << 2) + 4, where actual_length is the unsigned length the device reported for the matching IN packet. A packet completed with status 0 and actual_length < 4 wraps the subtraction to 0x7fffffec; a zero-length isochronous packet is legal on the bus, and the preceding loop rejects only non-zero status. The sum reaches memset() on out_urb->buffer, a 4832-byte object from kcalloc(PCM_MAX_PACKET_SIZE, PCM_N_PACKETS_PER_URB). Even without the wrap the result is out of bounds: at 88.2/96 kHz the 4-in/6-out scaling turns a full 420-byte IN packet into 628, so eight packets span 5024 bytes of that buffer. usb_submit_urb() rejects an over-long descriptor only after the memset() and the usb6fire_pcm_playback() copy of user PCM data have run. Guard the subtraction as the sibling usb6fire_pcm_capture() already does, and limit the frame count to what fits in rt->out_packet_size, the OUT endpoint's wMaxPacketSize. This bounds total_length by the buffer size while keeping each packet length aligned to a whole output frame. BUG: KASAN: out-of-bounds in usb6fire_pcm_in_urb_handler (sound/usb/6fire/pcm.c:338) Write of size 18446744073709551456 at addr ffff88802a3d0000 by task vhci_rx/5018 Call Trace: dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120) print_report (mm/kasan/report.c:378 mm/kasan/report.c:482) kasan_report (mm/kasan/report.c:595) kasan_check_range (mm/kasan/generic.c:186 mm/kasan/generic.c:200) __asan_memset (mm/kasan/shadow.c:84) usb6fire_pcm_in_urb_handler (sound/usb/6fire/pcm.c:338) __usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1657) usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1741) vhci_rx_loop (drivers/usb/usbip/vhci_rx.c:107 drivers/usb/usbip/vhci_rx.c:242) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) Allocated by task 10: __kmalloc_cache_noprof (mm/slub.c:5563) usb6fire_pcm_init (sound/usb/6fire/pcm.c:560 sound/usb/6fire/pcm.c:595) usb6fire_chip_probe (sound/usb/6fire/chip.c:133) usb_probe_interface (drivers/usb/core/driver.c:399) The buggy address belongs to the object at ffff88802a3d0000 which belongs to the cache kmalloc-8k of size 8192 The buggy address is located 0 bytes inside of 4832-byte region [ffff88802a3d0000, ffff88802a3d12e0) Kernel panic - not syncing: Fatal exception in interrupt Fixes: c6d43ba816d1 ("ALSA: usb/6fire - Driver for TerraTec DMX 6Fire USB") Reported-by: co+855929c2df672879@bugs.sh Closes: https://lore.kernel.org/all/gisnub8aWGLbyZLcDCSc7zWsHonMWGcyRgt5%40bugs.sh/ Assisted-by: LLM Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260914074324.3590843-1-xmei5@asu.edu Signed-off-by: Takashi Iwai --- sound/usb/6fire/pcm.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c index 21789db6657d3e..0285d79ace0fc7 100644 --- a/sound/usb/6fire/pcm.c +++ b/sound/usb/6fire/pcm.c @@ -335,11 +335,19 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) /* setup out urb structure */ for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { + unsigned int frames = 0; + isoc_out = &out_urb->instance->iso_frame_desc[i]; isoc_in = &in_urb->instance->iso_frame_desc[i]; + if (isoc_in->actual_length > 4) + frames = (isoc_in->actual_length - 4) + / (rt->in_n_analog << 2); + frames = min_t(unsigned int, frames, + (rt->out_packet_size - 4) + / (rt->out_n_analog << 2)); + isoc_out->offset = total_length; - isoc_out->length = (isoc_in->actual_length - 4) / (rt->in_n_analog << 2) - * (rt->out_n_analog << 2) + 4; + isoc_out->length = frames * (rt->out_n_analog << 2) + 4; isoc_out->status = 0; total_length += isoc_out->length; } From 4396d70bb7fec531bcf934fed016b2f3300c670b Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 18:35:38 -0400 Subject: [PATCH 0364/1417] mmc: sdhci-of-aspeed: Remove children before releasing SDC resources Probe failure and removal leave SDHCI child devices registered after the parent clock and managed resources are released. Unregister the OF children in reverse order before disabling the parent clock on both paths. Use of_platform_device_destroy() because manual child creation does not set the flag required by of_platform_depopulate(). This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: bb7b8ec62dfb ("mmc: sdhci-of-aspeed: Add support for the ASPEED SD controller") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Assisted-by: OpenAI:GPT-5.6 Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-aspeed.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-of-aspeed.c b/drivers/mmc/host/sdhci-of-aspeed.c index f5d973783cbe1a..d317626feb4aa6 100644 --- a/drivers/mmc/host/sdhci-of-aspeed.c +++ b/drivers/mmc/host/sdhci-of-aspeed.c @@ -560,12 +560,14 @@ static int aspeed_sdc_probe(struct platform_device *pdev) cpdev = of_platform_device_create(child, NULL, &pdev->dev); if (!cpdev) { ret = -ENODEV; - goto err_clk; + goto err_children; } } return 0; +err_children: + device_for_each_child_reverse(&pdev->dev, NULL, of_platform_device_destroy); err_clk: clk_disable_unprepare(sdc->clk); return ret; @@ -575,6 +577,7 @@ static void aspeed_sdc_remove(struct platform_device *pdev) { struct aspeed_sdc *sdc = dev_get_drvdata(&pdev->dev); + device_for_each_child_reverse(&pdev->dev, NULL, of_platform_device_destroy); clk_disable_unprepare(sdc->clk); } From d7fd1f98607f2cd358e583f9548bdf0173090a86 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 3 Sep 2026 14:06:02 +0900 Subject: [PATCH 0365/1417] ksmbd: follow SMB2 session expiration semantics ksmbd_session_register() destroys valid sessions after ten seconds of inactivity whenever a client starts another SessionSetup exchange. This confuses Session.IdleTime with Session.ExpirationTime. Windows can create additional authenticated sessions on an existing connection, so deleting the older session invalidates its tree connects and makes mapped drives fail with STATUS_NETWORK_NAME_DELETED. The session expiration rules require the server to change a valid session to expired only after its credential expiration time passes. A valid or expired session otherwise keeps its connection from being scavenged. Connections that have not negotiated a dialect, have no sessions, or have only InProgress sessions are disconnected after an implementation-specific timeout. Use the Windows-compatible 45 second value and run the expiration check periodically for both TCP and SMB Direct. Keep zero as an infinite credential expiration time, and count each Valid-to-Expired transition. Set expired sessions to InProgress when they reauthenticate. If authentication fails, remove the session from the global and per-connection tables immediately, including SMB3 multichannel connections. Retain protection against abandoned SessionId-zero exchanges by allowing only one InProgress authentication per connection. Additional exchanges fail with STATUS_INSUFFICIENT_RESOURCES, and stale InProgress sessions are reaped after the same 45 second setup timeout. This bounds the original unauthenticated memory-exhaustion path without evicting established sessions. Fixes: ea174a918939 ("ksmbd: destroy expired sessions") Reported-by: Mobin Aydinfar Signed-off-by: Namjae Jeon --- fs/smb/server/connection.c | 72 +++++++++++++++++ fs/smb/server/connection.h | 3 + fs/smb/server/mgmt/user_session.c | 124 ++++++++++++++++++++++++++++-- fs/smb/server/mgmt/user_session.h | 8 +- fs/smb/server/proc.c | 2 + fs/smb/server/server.c | 2 +- fs/smb/server/smb2pdu.c | 37 ++++++--- fs/smb/server/smb2pdu.h | 2 - fs/smb/server/stats.h | 1 + 9 files changed, 227 insertions(+), 24 deletions(-) diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c index 4cb92d6599ee43..d211861ff86f11 100644 --- a/fs/smb/server/connection.c +++ b/fs/smb/server/connection.c @@ -22,6 +22,8 @@ static DEFINE_MUTEX(init_lock); static struct ksmbd_conn_ops default_conn_ops; +static struct delayed_work session_expiration_work; +static bool stopping_session_expiration_work; DEFINE_HASHTABLE(conn_list, CONN_HASH_BITS); DECLARE_RWSEM(conn_list_lock); @@ -158,18 +160,28 @@ static void delete_proc_clients(void) {} static struct workqueue_struct *ksmbd_conn_wq; +static void ksmbd_session_expiration_worker(struct work_struct *work); + int ksmbd_conn_wq_init(void) { ksmbd_conn_wq = alloc_workqueue("ksmbd-conn-release", WQ_UNBOUND | WQ_MEM_RECLAIM, 0); if (!ksmbd_conn_wq) return -ENOMEM; + + WRITE_ONCE(stopping_session_expiration_work, false); + INIT_DELAYED_WORK(&session_expiration_work, + ksmbd_session_expiration_worker); + queue_delayed_work(ksmbd_conn_wq, &session_expiration_work, + KSMBD_SESSION_EXPIRATION_INTERVAL); return 0; } void ksmbd_conn_wq_destroy(void) { if (ksmbd_conn_wq) { + WRITE_ONCE(stopping_session_expiration_work, true); + cancel_delayed_work_sync(&session_expiration_work); destroy_workqueue(ksmbd_conn_wq); ksmbd_conn_wq = NULL; } @@ -279,6 +291,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void) return NULL; conn->need_neg = true; + conn->creation_time = jiffies; ksmbd_conn_set_new(conn); conn->local_nls = load_nls("utf8"); if (!conn->local_nls) @@ -588,6 +601,20 @@ bool ksmbd_conn_alive(struct ksmbd_conn *conn) if (kthread_should_stop()) return false; + /* + * Stale connections that have not completed NEGOTIATE and SESSION_SETUP + * must be disconnected. Do not race a request that is currently + * completing authentication. + */ + if (!atomic_read(&conn->req_running) && + time_after(jiffies, conn->creation_time + + KSMBD_UNAUTHENTICATED_CONN_TIMEOUT) && + (READ_ONCE(conn->need_neg) || + !ksmbd_conn_has_valid_or_expired_session(conn))) { + ksmbd_debug(CONN, "Connection setup timed out\n"); + return false; + } + if (atomic_read(&conn->stats.open_files_count) > 0) return true; @@ -605,6 +632,51 @@ bool ksmbd_conn_alive(struct ksmbd_conn *conn) return true; } +static void ksmbd_session_expiration_worker(struct work_struct *work) +{ + struct ksmbd_conn *conn, *target; + int bkt; + + if (!ksmbd_server_running()) + goto reschedule; + + ksmbd_expire_sessions(); + + /* + * An old connection without a Valid or Expired session must be + * disconnected. Process one connection at a time without holding + * conn_list_lock across transport shutdown. + */ +again: + target = NULL; + down_read(&conn_list_lock); + hash_for_each(conn_list, bkt, conn, hlist) { + if (ksmbd_conn_exiting(conn) || ksmbd_conn_releasing(conn) || + atomic_read(&conn->req_running) || + time_before_eq(jiffies, conn->creation_time + + KSMBD_UNAUTHENTICATED_CONN_TIMEOUT) || + (!READ_ONCE(conn->need_neg) && + ksmbd_conn_has_valid_or_expired_session(conn))) + continue; + + target = ksmbd_conn_get(conn); + break; + } + up_read(&conn_list_lock); + + if (target) { + ksmbd_debug(CONN, "Connection setup timed out\n"); + ksmbd_conn_abort(target); + ksmbd_conn_put(target); + goto again; + } + +reschedule: + if (!READ_ONCE(stopping_session_expiration_work)) + queue_delayed_work(ksmbd_conn_wq, &session_expiration_work, + KSMBD_SESSION_EXPIRATION_INTERVAL); +} + /* "+2" for BCC field (ByteCount, 2 bytes) */ #define SMB1_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb_hdr) + 2) #define SMB2_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb2_pdu)) diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h index 63484c8efbbdae..371f17b4f02a33 100644 --- a/fs/smb/server/connection.h +++ b/fs/smb/server/connection.h @@ -77,6 +77,7 @@ struct ksmbd_conn { struct rw_semaphore session_lock; /* smb session 1 per user */ struct xarray sessions; + unsigned long creation_time; unsigned long last_active; /* How many request are running currently */ atomic_t req_running; @@ -192,6 +193,8 @@ struct ksmbd_transport { #define KSMBD_TCP_RECV_TIMEOUT (7 * HZ) #define KSMBD_TCP_SEND_TIMEOUT (5 * HZ) +#define KSMBD_SESSION_EXPIRATION_INTERVAL (5 * HZ) +#define KSMBD_UNAUTHENTICATED_CONN_TIMEOUT (45 * HZ) #define KSMBD_TCP_PEER_SOCKADDR(c) ((struct sockaddr *)&((c)->peer_addr)) #define CONN_HASH_BITS 12 diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c index 2eb8f730e99e17..44dc3f800cd49c 100644 --- a/fs/smb/server/mgmt/user_session.c +++ b/fs/smb/server/mgmt/user_session.c @@ -22,6 +22,7 @@ static DEFINE_IDA(session_ida); #define SESSION_HASH_BITS 12 +#define KSMBD_MAX_PENDING_SESSIONS 1 static DEFINE_HASHTABLE(sessions_table, SESSION_HASH_BITS); static DECLARE_RWSEM(sessions_table_lock); @@ -432,26 +433,31 @@ struct ksmbd_session *__session_lookup(unsigned long long id) return NULL; } -static void ksmbd_expire_session(struct ksmbd_conn *conn) +static bool ksmbd_too_many_session_setups(struct ksmbd_conn *conn) { unsigned long id; struct ksmbd_session *sess; + unsigned int pending = 0; down_write(&sessions_table_lock); down_write(&conn->session_lock); xa_for_each(&conn->sessions, id, sess) { + if (READ_ONCE(sess->state) != SMB2_SESSION_IN_PROGRESS) + continue; + if (atomic_read(&sess->refcnt) <= 1 && - (sess->state != SMB2_SESSION_VALID || - time_after(jiffies, - sess->last_active + SMB2_SESSION_TIMEOUT))) { + time_after(jiffies, sess->last_active + + KSMBD_UNAUTHENTICATED_CONN_TIMEOUT)) { xa_erase(&conn->sessions, sess->id); ksmbd_session_remove_from_table(sess); ksmbd_session_destroy(sess); continue; } + pending++; } up_write(&conn->session_lock); up_write(&sessions_table_lock); + return pending >= KSMBD_MAX_PENDING_SESSIONS; } int ksmbd_session_register(struct ksmbd_conn *conn, @@ -461,9 +467,12 @@ int ksmbd_session_register(struct ksmbd_conn *conn, sess->dialect = conn->dialect; memcpy(sess->ClientGUID, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE); - ksmbd_expire_session(conn); - ret = xa_err(xa_store(&conn->sessions, sess->id, sess, - KSMBD_DEFAULT_GFP)); + /* Bound abandoned SessionId-zero authentication exchanges. */ + if (ksmbd_too_many_session_setups(conn)) + ret = -ENOSPC; + else + ret = xa_err(xa_store(&conn->sessions, sess->id, sess, + KSMBD_DEFAULT_GFP)); if (ret) { down_write(&sessions_table_lock); ksmbd_session_remove_from_table(sess); @@ -474,6 +483,105 @@ int ksmbd_session_register(struct ksmbd_conn *conn, return ret; } +void ksmbd_session_unregister(struct ksmbd_conn *conn, + struct ksmbd_session *sess) +{ + struct ksmbd_conn *session_conns[KSMBD_MAX_CHANNELS]; + struct channel *chann; + unsigned long index; + unsigned int nr_conns = 0, i; + bool removed = false; + + down_write(&sessions_table_lock); + if (!hlist_unhashed(&sess->hlist)) { + /* Keep each channel connection stable under sessions_table_lock. */ + down_read(&sess->chann_lock); + xa_for_each(&sess->ksmbd_chann_list, index, chann) { + if (nr_conns == ARRAY_SIZE(session_conns)) + break; + session_conns[nr_conns++] = chann->conn; + } + up_read(&sess->chann_lock); + + ksmbd_session_remove_from_table(sess); + removed = true; + } + + down_write(&conn->session_lock); + if (xa_load(&conn->sessions, sess->id) == sess) + xa_erase(&conn->sessions, sess->id); + up_write(&conn->session_lock); + for (i = 0; i < nr_conns; i++) { + if (session_conns[i] == conn) + continue; + down_write(&session_conns[i]->session_lock); + if (xa_load(&session_conns[i]->sessions, sess->id) == sess) + xa_erase(&session_conns[i]->sessions, sess->id); + up_write(&session_conns[i]->session_lock); + } + up_write(&sessions_table_lock); + + if (removed) + ksmbd_user_session_put(sess); +} + +bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn) +{ + struct ksmbd_session *sess; + unsigned long id; + int state, bkt; + bool found = false; + + down_read(&conn->session_lock); + xa_for_each(&conn->sessions, id, sess) { + state = READ_ONCE(sess->state); + if (state == SMB2_SESSION_VALID || + state == SMB2_SESSION_EXPIRED) { + found = true; + break; + } + } + up_read(&conn->session_lock); + if (found) + return true; + + /* A session bound through SMB3 multichannel is not in conn->sessions. */ + down_read(&sessions_table_lock); + hash_for_each(sessions_table, bkt, sess, hlist) { + state = READ_ONCE(sess->state); + if (state != SMB2_SESSION_VALID && + state != SMB2_SESSION_EXPIRED) + continue; + + down_read(&sess->chann_lock); + found = xa_load(&sess->ksmbd_chann_list, (long)conn); + up_read(&sess->chann_lock); + if (found) + break; + } + up_read(&sessions_table_lock); + return found; +} + +void ksmbd_expire_sessions(void) +{ + struct ksmbd_session *sess; + u64 now = ktime_get_real_seconds(); + int bkt; + + down_read(&sessions_table_lock); + hash_for_each(sessions_table, bkt, sess, hlist) { + if (READ_ONCE(sess->state) != SMB2_SESSION_VALID || + !sess->kerberos_expiry || now < sess->kerberos_expiry) + continue; + + if (cmpxchg(&sess->state, SMB2_SESSION_VALID, + SMB2_SESSION_EXPIRED) == SMB2_SESSION_VALID) + ksmbd_counter_inc(KSMBD_COUNTER_SESSION_TIMEOUTS); + } + up_read(&sessions_table_lock); +} + static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess) { struct channel *chann; @@ -488,7 +596,7 @@ static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess) return 0; } -void ksmbd_sessions_deregister(struct ksmbd_conn *conn) +void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn) { struct ksmbd_session *sess; unsigned long id; diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h index 3e52d4cc132472..217258551d6d09 100644 --- a/fs/smb/server/mgmt/user_session.h +++ b/fs/smb/server/mgmt/user_session.h @@ -72,6 +72,8 @@ struct ksmbd_session { struct rw_semaphore rpc_lock; }; +#define KSMBD_MAX_CHANNELS 32 + static inline int test_session_flag(struct ksmbd_session *sess, int bit) { return sess->flags & bit; @@ -98,7 +100,11 @@ bool is_ksmbd_session_in_connection(struct ksmbd_conn *conn, unsigned long long id); int ksmbd_session_register(struct ksmbd_conn *conn, struct ksmbd_session *sess); -void ksmbd_sessions_deregister(struct ksmbd_conn *conn); +void ksmbd_session_unregister(struct ksmbd_conn *conn, + struct ksmbd_session *sess); +void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn); +bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn); +void ksmbd_expire_sessions(void); struct ksmbd_session *__session_lookup(unsigned long long id); struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn, unsigned long long id); diff --git a/fs/smb/server/proc.c b/fs/smb/server/proc.c index 826353ed05538d..19f0f2cfbf5434 100644 --- a/fs/smb/server/proc.c +++ b/fs/smb/server/proc.c @@ -178,6 +178,8 @@ static int proc_show_ksmbd_stats(struct seq_file *m, void *v) proc_show_runtime_totals(m); seq_printf(m, "sessions:\t%lld\n", ksmbd_counter_sum(KSMBD_COUNTER_SESSIONS)); + seq_printf(m, "session_timeouts:\t%lld\n", + ksmbd_counter_sum(KSMBD_COUNTER_SESSION_TIMEOUTS)); seq_printf(m, "tree_connects:\t%lld\n", ksmbd_counter_sum(KSMBD_COUNTER_TREE_CONNS)); seq_printf(m, "requests:\t%lld\n", diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c index 0069d4e6a60a64..0827c8c510066b 100644 --- a/fs/smb/server/server.c +++ b/fs/smb/server/server.c @@ -414,7 +414,7 @@ static int ksmbd_server_process_request(struct ksmbd_conn *conn) static int ksmbd_server_terminate_conn(struct ksmbd_conn *conn) { - ksmbd_sessions_deregister(conn); + ksmbd_conn_sessions_cleanup(conn); destroy_lease_table(conn); return 0; } diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index b7ce6709462600..8acc5174530bbd 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -85,8 +85,6 @@ struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn return chann; } -#define KSMBD_MAX_CHANNELS 32 - static int register_session_channel(struct ksmbd_session *sess, struct ksmbd_conn *conn, const char *sess_key) @@ -933,8 +931,14 @@ static bool smb2_session_expired_cmd_allowed(struct ksmbd_work *work, static bool smb2_session_kerberos_expired(struct ksmbd_session *sess) { - return sess->kerberos_expiry && - ktime_get_real_seconds() >= sess->kerberos_expiry; + if (!sess->kerberos_expiry || + ktime_get_real_seconds() < sess->kerberos_expiry) + return false; + + if (cmpxchg(&sess->state, SMB2_SESSION_VALID, + SMB2_SESSION_EXPIRED) == SMB2_SESSION_VALID) + ksmbd_counter_inc(KSMBD_COUNTER_SESSION_TIMEOUTS); + return true; } /** @@ -969,9 +973,8 @@ int smb2_check_user_session(struct ksmbd_work *work) if (!work->next_smb2_rcv_hdr_off && sess_id) work->sess = ksmbd_session_lookup_all_states(conn, sess_id); if (work->sess) { - if (smb2_session_kerberos_expired(work->sess)) { - work->sess->state = SMB2_SESSION_EXPIRED; - } else if (work->sess->state != SMB2_SESSION_VALID) { + if (!smb2_session_kerberos_expired(work->sess) && + work->sess->state != SMB2_SESSION_VALID) { ksmbd_user_session_put(work->sess); work->sess = NULL; } @@ -996,8 +999,7 @@ int smb2_check_user_session(struct ksmbd_work *work) sess_id, work->sess->id); return -EINVAL; } - if (smb2_session_kerberos_expired(work->sess)) - work->sess->state = SMB2_SESSION_EXPIRED; + smb2_session_kerberos_expired(work->sess); if (work->sess->state != SMB2_SESSION_VALID) { pr_err("compound request on a non-valid session (state %d)\n", work->sess->state); @@ -1014,7 +1016,6 @@ int smb2_check_user_session(struct ksmbd_work *work) work->sess = ksmbd_session_lookup_all_states(conn, sess_id); if (work->sess) { if (smb2_session_kerberos_expired(work->sess)) { - work->sess->state = SMB2_SESSION_EXPIRED; return smb2_session_expired_cmd_allowed(work, cmd) ? 1 : -EKEYEXPIRED; } @@ -2436,7 +2437,7 @@ int smb2_sess_setup(struct ksmbd_work *work) struct ksmbd_conn *conn = work->conn; struct smb2_sess_setup_req *req; struct smb2_sess_setup_rsp *rsp; - struct ksmbd_session *sess; + struct ksmbd_session *sess = NULL; struct negotiate_message *negblob; unsigned int negblob_len, negblob_off; int rc = 0; @@ -2592,6 +2593,9 @@ int smb2_sess_setup(struct ksmbd_work *work) goto out_err; } + if (work->session_setup_reauth) + WRITE_ONCE(sess->state, SMB2_SESSION_IN_PROGRESS); + conn->binding = false; } work->sess = sess; @@ -2703,6 +2707,14 @@ int smb2_sess_setup(struct ksmbd_work *work) } if (rc < 0) { + bool setup_in_progress = sess && + READ_ONCE(sess->state) == SMB2_SESSION_IN_PROGRESS && + !(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING); + + /* Authentication errors must not leave the new session published. */ + if (setup_in_progress) + ksmbd_session_unregister(conn, sess); + if (sess && conn->dialect == SMB311_PROT_ID && (req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) { struct preauth_session *preauth_sess; @@ -2736,7 +2748,8 @@ int smb2_sess_setup(struct ksmbd_work *work) * For binding requests, session belongs to another * connection. Do not expire it. */ - if (!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) { + if (!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING) && + !setup_in_progress) { sess->last_active = jiffies; sess->kerberos_expiry = 0; sess->state = SMB2_SESSION_EXPIRED; diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h index 3f08d1ca5a38e2..a6200d8630e270 100644 --- a/fs/smb/server/smb2pdu.h +++ b/fs/smb/server/smb2pdu.h @@ -61,8 +61,6 @@ struct preauth_integrity_info { #define SMB2_SESSION_IN_PROGRESS BIT(0) #define SMB2_SESSION_VALID BIT(1) -#define SMB2_SESSION_TIMEOUT (10 * HZ) - /* Apple Defined Contexts */ #define SMB2_CREATE_AAPL "AAPL" diff --git a/fs/smb/server/stats.h b/fs/smb/server/stats.h index bc864efa0d46bd..8b32b8b4e8be49 100644 --- a/fs/smb/server/stats.h +++ b/fs/smb/server/stats.h @@ -15,6 +15,7 @@ enum { KSMBD_COUNTER_SESSIONS = 0, + KSMBD_COUNTER_SESSION_TIMEOUTS, KSMBD_COUNTER_TREE_CONNS, KSMBD_COUNTER_REQUESTS, KSMBD_COUNTER_STATUS_SUCCESS, From 65bcc5f89704efe5b9d69d4ea2c1002d90c31382 Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Mon, 14 Sep 2026 11:50:07 +0200 Subject: [PATCH 0366/1417] HID: amd_sfh: Validate PCI BAR size before mapping The amd_sfh driver maps PCI BAR 2 using pcim_iomap_regions() and subsequently accesses MMIO registers at offsets up to 0x10958 (e.g., AMD_P2C_MSG3 at 0x1068C). However, the driver never validates that the BAR size is large enough to cover these accesses. If the driver is bound to a device with a smaller BAR 2, this leads to an out-of-bounds memory access and a page fault during the probe function. For example, a page fault can occur when reading from privdata->mmio + AMD_P2C_MSG3 in mp2_select_ops(): BUG: unable to handle page fault for address: ffffc9000390368c PGD 100000067 P4D 100000067 PUD 1012c1067 PMD 105b64067 PTE 0 Oops: Oops: 0000 [#1] SMP KASAN NOPTI RIP: 0010:readl arch/x86/include/asm/io.h:59 [inline] RIP: 0010:mp2_select_ops drivers/hid/amd-sfh-hid/amd_sfh_pcie.c:282 [inline] RIP: 0010:amd_mp2_pci_probe+0x337/0x5f0 drivers/hid/amd-sfh-hid/amd_sfh_pcie.c:487 Call Trace: local_pci_probe drivers/pci/pci-driver.c:332 [inline] pci_call_probe drivers/pci/pci-driver.c:394 [inline] __pci_device_probe drivers/pci/pci-driver.c:455 [inline] pci_device_probe+0x431/0xc90 drivers/pci/pci-driver.c:489 Fix this by verifying that the length of BAR 2 is at least 128KB before attempting to map it. Since the maximum accessed offset is 0x10958, and PCI BAR sizes are powers of 2, any legitimate hardware will have a BAR size of at least 128KB. Fixes: 4f567b9f8141 ("SFH: PCIe driver to add support of AMD sensor fusion hub") Assisted-by: Gemini:gemini-3.7-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+4eadd4dfe9e66522bae8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4eadd4dfe9e66522bae8 Link: https://syzkaller.appspot.com/ai_job?id=3bc1c45c-548f-4ab5-8243-d2c8ec321d6c Signed-off-by: Slawomir Stepien Acked-by: Basavaraj Natikar Link: https://syzkaller.appspot.com/bug?extid=4eadd4dfe9e66522bae8 Signed-off-by: Jiri Kosina --- drivers/hid/amd-sfh-hid/amd_sfh_common.h | 4 ++++ drivers/hid/amd-sfh-hid/amd_sfh_pcie.c | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h index f8c6b7fc34fb12..3d29119a3765a8 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h +++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h @@ -13,11 +13,15 @@ #include #include #include +#include #include "amd_sfh_hid.h" #define PCI_DEVICE_ID_AMD_MP2 0x15E4 #define PCI_DEVICE_ID_AMD_MP2_1_1 0x164A +/* The BAR 2 size must cover the highest register offset (0x10958) */ +#define AMD_SFH_MIN_BAR_SIZE SZ_128K + #define AMD_C2P_MSG(regno) (0x10500 + ((regno) * 4)) #define AMD_P2C_MSG(regno) (0x10680 + ((regno) * 4)) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index eda26a094d3f17..061a63519d441d 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -497,6 +497,16 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i if (rc) return rc; + if (!(pci_resource_flags(pdev, 2) & IORESOURCE_MEM)) { + dev_err(&pdev->dev, "BAR 2 is not IORESOURCE_MEM\n"); + return -ENODEV; + } + + if (pci_resource_len(pdev, 2) < AMD_SFH_MIN_BAR_SIZE) { + dev_err(&pdev->dev, "BAR 2 is too small\n"); + return -EINVAL; + } + rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME); if (rc) return rc; From ba7a79b9bc87776c8c1808407a7508a8be3a789e Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Mon, 14 Sep 2026 10:13:50 +0200 Subject: [PATCH 0367/1417] wifi: cfg80211: verify if AP_VLAN belongs to the correct AP The get_vlan() only checks if NL80211_ATTR_STA_VLAN target is an AP/AP_VLAN/P2P_GO interface on the same wiphy. It has no notion of which specific AP a given AP_VLAN belongs to. Fix that by comparing the ethernet addresses of the two net devices. Given VLAN A' must have the same address as AP A. Otherwise, return error code. Signed-off-by: Slawomir Stepien Reported-by: Johannes Berg Link: https://lore.kernel.org/all/22e7ddfc50d7a6a16c437b876dab5fe223799610.camel@sipsolutions.net/ Link: https://patch.msgid.link/20260914081350.83484-1-sst@poczta.fm Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 44f2bad0867059..9fd1367483c6e0 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -8966,10 +8966,12 @@ int cfg80211_check_station_change(struct wiphy *wiphy, EXPORT_SYMBOL(cfg80211_check_station_change); /* - * Get vlan interface making sure it is running and on the right wiphy. + * Get vlan interface making sure it is running, on the right wiphy + * and actually belongs to the given AP/P2P_GO interface. */ static struct net_device *get_vlan(struct genl_info *info, - struct cfg80211_registered_device *rdev) + struct cfg80211_registered_device *rdev, + struct net_device *dev) { struct nlattr *vlanattr = info->attrs[NL80211_ATTR_STA_VLAN]; struct net_device *v; @@ -8999,6 +9001,12 @@ static struct net_device *get_vlan(struct genl_info *info, goto error; } + /* Check if the VLAN interface belongs to the AP interface */ + if (!dev || !ether_addr_equal(v->dev_addr, dev->dev_addr)) { + ret = -EINVAL; + goto error; + } + return v; error: dev_put(v); @@ -9296,7 +9304,7 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info) if (err) return err; - params.vlan = get_vlan(info, rdev); + params.vlan = get_vlan(info, rdev, dev); if (IS_ERR(params.vlan)) return PTR_ERR(params.vlan); @@ -9597,7 +9605,7 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) } /* must be last in here for error handling */ - params.vlan = get_vlan(info, rdev); + params.vlan = get_vlan(info, rdev, dev); if (IS_ERR(params.vlan)) return PTR_ERR(params.vlan); break; From 4ae3128c230372b43d0c417e0fcf816e8290e9fa Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Thu, 10 Sep 2026 10:04:16 +0200 Subject: [PATCH 0368/1417] wifi: cfg80211: do not support direct add of station to AP_VLAN interfaces Prevent userspace from adding stations directly to AP_VLAN type interfaces. Userspace should first add the station to the base interface (AP type) and then can use CMD_SET_STATION to move it to AP_VLAN. The other way is by using NL80211_ATTR_STA_VLAN. Without this path, we cannot check if the AP has been started before adding the station - wdev for AP_VLAN does not store information about the base AP interface. Signed-off-by: Slawomir Stepien Link: https://patch.msgid.link/20260910080418.725741-1-sst@poczta.fm Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 9fd1367483c6e0..677a78f72b0e34 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -9564,7 +9564,6 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) switch (wdev->iftype) { case NL80211_IFTYPE_AP: - case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_P2P_GO: /* ignore WME attributes if iface/sta is not capable */ if (!(rdev->wiphy.flags & WIPHY_FLAG_AP_UAPSD) || From e3d1acb0276742f288094cd7a497745364876bb8 Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Thu, 10 Sep 2026 10:04:17 +0200 Subject: [PATCH 0369/1417] wifi: cfg80211: move link_id validation earlier in nl80211_new_station() I do not see a reason why this check is so low in the function. Move it up right next to param fetch. This new position is more beneficial for AP/Link state check that will be added in upcoming commit. Signed-off-by: Slawomir Stepien Link: https://patch.msgid.link/20260910080418.725741-2-sst@poczta.fm Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 677a78f72b0e34..7b0ad66cf587f3 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -9384,6 +9384,16 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) params.link_sta_params.link_id = nl80211_link_id_or_invalid(info->attrs); + if (wdev->valid_links) { + if (params.link_sta_params.link_id < 0) + return -EINVAL; + if (!(wdev->valid_links & BIT(params.link_sta_params.link_id))) + return -ENOLINK; + } else { + if (params.link_sta_params.link_id >= 0) + return -EINVAL; + } + if (info->attrs[NL80211_ATTR_MLD_ADDR]) { mac_addr = nla_data(info->attrs[NL80211_ATTR_MLD_ADDR]); params.link_sta_params.mld_mac = mac_addr; @@ -9656,27 +9666,10 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) /* be aware of params.vlan when changing code here */ - if (wdev->valid_links) { - if (params.link_sta_params.link_id < 0) { - err = -EINVAL; - goto out; - } - if (!(wdev->valid_links & BIT(params.link_sta_params.link_id))) { - err = -ENOLINK; - goto out; - } - } else { - if (params.link_sta_params.link_id >= 0) { - err = -EINVAL; - goto out; - } - } - params.epp_peer = nla_get_flag(info->attrs[NL80211_ATTR_EPP_PEER]); err = rdev_add_station(rdev, wdev, mac_addr, ¶ms); -out: dev_put(params.vlan); return err; } From a842cfc1d6d85b34ad73959460def4d4641e82e8 Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Thu, 10 Sep 2026 10:04:18 +0200 Subject: [PATCH 0370/1417] wifi: cfg80211: check if AP has been started or joined a mesh before adding new station Adding a new station to AP makes only sense when the AP has been started (nl80211_start_ap()) or joined a mesh (__cfg80211_join_mesh()). Check if AP is up and beaconing on the link or joined the mesh, when adding new station. Return error if this isn't the case. Note that libertas devices need special handling since they do not implement join_mesh() and the decision must be made on channel definition. Reported-by: syzbot+9bdc0c5998ab45b05030@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9bdc0c5998ab45b05030 Signed-off-by: Slawomir Stepien Link: https://patch.msgid.link/20260910080418.725741-3-sst@poczta.fm Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 7b0ad66cf587f3..f18d526149c60b 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -9336,7 +9336,7 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info) static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; - int err; + int err, link_id; struct wireless_dev *wdev = info->user_ptr[1]; struct net_device *dev = wdev->netdev; struct station_parameters params; @@ -9575,6 +9575,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) switch (wdev->iftype) { case NL80211_IFTYPE_AP: case NL80211_IFTYPE_P2P_GO: + /* Add a new station only after the AP and link has been started */ + link_id = wdev->valid_links ? params.link_sta_params.link_id : 0; + if (!wdev->links[link_id].ap.beacon_interval) + return -ENETDOWN; + /* ignore WME attributes if iface/sta is not capable */ if (!(rdev->wiphy.flags & WIPHY_FLAG_AP_UAPSD) || !(params.sta_flags_set & BIT(NL80211_STA_FLAG_WME))) @@ -9619,6 +9624,19 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) return PTR_ERR(params.vlan); break; case NL80211_IFTYPE_MESH_POINT: + /* + * Add a new station only after the mesh has been started. + * libertas doesn't implement join_mesh(); it configures the + * mesh via sysfs and joins it when the channel is set, so + * use that as the started indication instead. + */ + if (rdev->ops->libertas_set_mesh_channel) { + if (!wdev->u.mesh.chandef.chan) + return -ENETDOWN; + } else if (!wdev->u.mesh.beacon_interval) { + return -ENETDOWN; + } + /* ignore uAPSD data */ params.sta_modify_mask &= ~STATION_PARAM_APPLY_UAPSD; From e5c8d7acd31b27057ea42cd405d0b3ece097bc89 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 9 Sep 2026 12:37:18 +0000 Subject: [PATCH 0371/1417] wifi: virt_wifi: don't transfer operstate before register virt_wifi_newlink() calls netif_stacked_transfer_operstate() before register_netdevice(). If the lower device is dormant, that queues the new netdev on lweventlist while it is still uninitialized. If registration fails after that, for example because of an invalid name such as "bad/name", free_netdev() immediately frees the object. A later linkwatch_fire_event() then use-after-frees the list entry. Move the transfer to after netdev_upper_dev_link(), as macvlan and ipvlan already do. Fixes: c7cdba31ed8b ("mac80211-next: rtnetlink wifi simulation device") Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Link: https://patch.msgid.link/f5a832fb0ab228ce6e2b5a91fba4ca8b79198a2f.1788948455.git.zihanx@nebusec.ai Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/virt_wifi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/virtual/virt_wifi.c b/drivers/net/wireless/virtual/virt_wifi.c index b69a4650fba855..48afc2432f93a8 100644 --- a/drivers/net/wireless/virtual/virt_wifi.c +++ b/drivers/net/wireless/virtual/virt_wifi.c @@ -558,7 +558,6 @@ static int virt_wifi_newlink(struct net_device *dev, } eth_hw_addr_inherit(dev, priv->lowerdev); - netif_stacked_transfer_operstate(priv->lowerdev, dev); dev->ieee80211_ptr = kzalloc_obj(*dev->ieee80211_ptr); @@ -584,6 +583,8 @@ static int virt_wifi_newlink(struct net_device *dev, goto unregister_netdev; } + netif_stacked_transfer_operstate(priv->lowerdev, dev); + dev->priv_destructor = virt_wifi_net_device_destructor; priv->being_deleted = false; priv->is_connected = false; From 06f42accaf3c6aecab1dcc57f68dde6c06c8b380 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Wed, 9 Sep 2026 15:11:24 +0900 Subject: [PATCH 0372/1417] wifi: libipw: reject TKIP frames without a full MIC libipw_michael_mic_verify() assumes that an skb contains an eight-byte Michael MIC. A short TKIP frame makes the unsigned payload length wrap, causing michael_mic() to read past the skb. Check that the MIC is present before verifying it, and use the existing MICHAEL_MIC_LEN constant for all MIC lengths in the verifier. Fixes: b453872c35cf ("[NET] ieee80211 subsystem") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/20260909061124.3802517-1-4ncienth@gmail.com Signed-off-by: Johannes Berg --- .../net/wireless/intel/ipw2x00/libipw_crypto_tkip.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_crypto_tkip.c b/drivers/net/wireless/intel/ipw2x00/libipw_crypto_tkip.c index 24bb28ab7a49bd..2b0cf0ec496ad1 100644 --- a/drivers/net/wireless/intel/ipw2x00/libipw_crypto_tkip.c +++ b/drivers/net/wireless/intel/ipw2x00/libipw_crypto_tkip.c @@ -474,14 +474,16 @@ static int libipw_michael_mic_verify(struct sk_buff *skb, int keyidx, int hdr_len, void *priv) { struct libipw_tkip_data *tkey = priv; - u8 mic[8]; + u8 mic[MICHAEL_MIC_LEN]; - if (!tkey->key_set) + if (!tkey->key_set || skb->len < hdr_len + MICHAEL_MIC_LEN) return -1; michael_mic(&tkey->key[24], (struct ieee80211_hdr *)skb->data, - skb->data + hdr_len, skb->len - 8 - hdr_len, mic); - if (memcmp(mic, skb->data + skb->len - 8, 8) != 0) { + skb->data + hdr_len, + skb->len - MICHAEL_MIC_LEN - hdr_len, mic); + if (memcmp(mic, skb->data + skb->len - MICHAEL_MIC_LEN, + MICHAEL_MIC_LEN) != 0) { struct ieee80211_hdr *hdr; hdr = (struct ieee80211_hdr *)skb->data; printk(KERN_DEBUG "%s: Michael MIC verification failed for " @@ -499,7 +501,7 @@ static int libipw_michael_mic_verify(struct sk_buff *skb, int keyidx, tkey->rx_iv32 = tkey->rx_iv32_new; tkey->rx_iv16 = tkey->rx_iv16_new; - skb_trim(skb, skb->len - 8); + skb_trim(skb, skb->len - MICHAEL_MIC_LEN); return 0; } From 247a82da6f563dcfd9074a68f99a0c0997d0679c Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:48 +0100 Subject: [PATCH 0373/1417] drm/xe/mmio_gem: forbid VMA split The fault handler assumes it always operates on a VMA spanning the entire GEM object. This does not hold when the VMA has been split, e.g. by a partial munmap or mprotect. In that case the handler may map wrong physical pages or cause SIGBUS. Handle this by forbidding VMA split, as partial unmaps are not deemed useful for MMIO GEMs. Suggested-by: Matthew Auld Signed-off-by: Ilia Levi Fixes: 1ffcf8b8ae8a ("drm/xe: Support for mmap-ing mmio regions") Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-11-matthew.auld@intel.com (cherry picked from commit f3391a0b12d7bf826a0b21600d2f294f3dce4c14) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 3741ae60f532bf..d54477b93b6e1c 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -39,10 +39,20 @@ struct xe_mmio_gem { phys_addr_t phys_addr; }; +static int xe_mmio_gem_vm_may_split(struct vm_area_struct *area, unsigned long addr) +{ + /* + * Forbid splitting. Together with VM_DONTEXPAND, this keeps the VMA + * matching the GEM object exactly. + */ + return -EINVAL; +} + static const struct vm_operations_struct vm_ops = { .open = drm_gem_vm_open, .close = drm_gem_vm_close, .fault = xe_mmio_gem_vm_fault, + .may_split = xe_mmio_gem_vm_may_split, }; static const struct drm_gem_object_funcs xe_mmio_gem_funcs = { From 819f189265a5955da743e78e20a713a038982e89 Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:49 +0100 Subject: [PATCH 0374/1417] drm/xe/mmio_gem: use write-back mapping for dummy page Currently vmf_insert_pfn() maps the dummy page as UC, inheriting the VMA's page protection which was set for the real MMIO region. This conflicts with the direct map's WB mapping of the same page, creating a cache type alias which is architecturally undefined on some platforms. Use vmf_insert_pfn_prot() with a WB pgprot instead. Also simplify to fault in the requested page instead of the whole VMA. Fixes: 1ffcf8b8ae8a ("drm/xe: Support for mmap-ing mmio regions") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260525125801.975038-6-ilia.levi%40intel.com Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Ilia Levi Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-12-matthew.auld@intel.com (cherry picked from commit 1e8e28e35df0e77ae1b22fc091c1f422f62fa5e9) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index d54477b93b6e1c..96f3bf46fd7559 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -172,14 +172,13 @@ static void xe_mmio_gem_release_dummy_page(struct drm_device *dev, void *res) __free_page((struct page *)res); } -static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_area_struct *vma) +static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_fault *vmf) { + struct vm_area_struct *vma = vmf->vma; struct drm_gem_object *base = vma->vm_private_data; struct drm_device *dev = base->dev; - vm_fault_t ret = VM_FAULT_NOPAGE; struct page *page; unsigned long pfn; - unsigned long i; page = alloc_page(GFP_KERNEL | __GFP_ZERO); if (!page) @@ -190,16 +189,8 @@ static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_area_struct *vma) pfn = page_to_pfn(page); - /* Map the entire VMA to the same dummy page */ - for (i = 0; i < base->size; i += PAGE_SIZE) { - unsigned long addr = vma->vm_start + i; - - ret = vmf_insert_pfn(vma, addr, pfn); - if (ret & VM_FAULT_ERROR) - break; - } - - return ret; + return vmf_insert_pfn_prot(vma, vmf->address, pfn, + vm_get_page_prot(vma->vm_flags)); } static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) @@ -219,7 +210,7 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) * It is assumed the userspace will receive the notification via some * other channel (e.g. drm uevent). */ - return xe_mmio_gem_vm_fault_dummy_page(vma); + return xe_mmio_gem_vm_fault_dummy_page(vmf); } for (i = 0; i < base->size; i += PAGE_SIZE) { From 2b04d6556964ae9f89819b86a0a7801e39c3aae5 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Fri, 4 Sep 2026 13:03:38 -0700 Subject: [PATCH 0375/1417] wifi: mac80211: refuse to make a monitor active when it has no queue A monitor interface only gets a TXQ if it's created active, and one can't be added later. Setting the flag on a down interface is still allowed, so the driver is handed a monitor with no queue. ath9k dereferences it: BUG: kernel NULL pointer dereference, address: 0000000000000066 RIP: 0010:ath_tx_node_init+0x49/0x170 [ath9k] ath9k_add_interface+0x10c/0x140 [ath9k] drv_add_interface+0x54/0x250 [mac80211] ieee80211_do_open+0x32f/0x800 [mac80211] Reached with CAP_NET_ADMIN by "iw dev X set monitor active" followed by "ip link set X up". RTNL is held, so netlink operations block behind it. Refuse the flag when there is no queue to give. Fixes: 79af1f866193 ("mac80211: avoid allocating TXQs that won't be used") Cc: stable@vger.kernel.org Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260904200338.10829-1-lucid_duck@justthetip.ca Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index d3558f0c755031..a1753335eb9d02 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -115,6 +115,10 @@ static int ieee80211_set_mon_options(struct ieee80211_sub_if_data *sdata, return -EBUSY; } + /* TXQs are reserved in ieee80211_if_add() and cannot be added later */ + if ((params->flags & MONITOR_FLAG_ACTIVE) && !sdata->vif.txq) + return -EOPNOTSUPP; + /* validate whether MU-MIMO can be configured */ if (!ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF) && !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR) && From 0c50663403b7aa271b1974ba32ee6c8296711142 Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:50 +0100 Subject: [PATCH 0376/1417] drm/xe/mmio_gem: simplify fault handler loop Make the iteration over the addresses in the VMA more explicit. No functional change, as the VMA matches the GEM object exactly. Signed-off-by: Ilia Levi Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-13-matthew.auld@intel.com (cherry picked from commit 6666ca9192f3bdf839aad33b9e1c9ebb7a222a29) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 96f3bf46fd7559..3b6d04efe81a48 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -200,7 +200,7 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) struct xe_mmio_gem *obj = to_xe_mmio_gem(base); struct drm_device *dev = base->dev; vm_fault_t ret = VM_FAULT_NOPAGE; - unsigned long i; + unsigned long addr, pfn; int idx; if (!drm_dev_enter(dev, &idx)) { @@ -213,13 +213,13 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) return xe_mmio_gem_vm_fault_dummy_page(vmf); } - for (i = 0; i < base->size; i += PAGE_SIZE) { - unsigned long addr = vma->vm_start + i; - unsigned long phys_addr = obj->phys_addr + i; - - ret = vmf_insert_pfn(vma, addr, PHYS_PFN(phys_addr)); + pfn = PHYS_PFN(obj->phys_addr); + for (addr = vma->vm_start; addr < vma->vm_end; addr += PAGE_SIZE) { + ret = vmf_insert_pfn(vma, addr, pfn); if (ret & VM_FAULT_ERROR) break; + + pfn++; } drm_dev_exit(idx); From 37fcbd7b2f8996d783932dab11bc668e169b0de6 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Tue, 8 Sep 2026 17:50:51 +0100 Subject: [PATCH 0377/1417] drm/xe/mmio_gem: Revoke drm_vma_node on xe_mmio_gem destroy xe_mmio_gem_create() calls drm_vma_node_allow() but nothing ever calls drm_vma_node_revoke(). The drm_vma_offset_file rb-tree entry allocated by drm_vma_node_allow() is not freed by drm_gem_object_release(), so it is leaked on every create/destroy cycle. Add a struct drm_file * parameter to xe_mmio_gem_destroy() and call drm_vma_node_revoke() from there, mirroring the drm_vma_node_allow() call in xe_mmio_gem_create(). Fixes: 1ffcf8b8ae8a ("drm/xe: Support for mmap-ing mmio regions") Suggested-by: Ilia Levi Assisted-by: Claude:claude-opus-4.6 Signed-off-by: Shuicheng Lin Reviewed-by: Ilia Levi Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-14-matthew.auld@intel.com (cherry picked from commit 32f0cb250598456d812fb7ca57a040282858323d) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 4 +++- drivers/gpu/drm/xe/xe_mmio_gem.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 3b6d04efe81a48..3c42d8358c7d83 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -138,14 +138,16 @@ static void xe_mmio_gem_free(struct drm_gem_object *base) /** * xe_mmio_gem_destroy - Destroy the GEM object that exposes an MMIO region * @gem: the GEM object to destroy + * @file: DRM file descriptor previously passed to xe_mmio_gem_create() * * This function releases resources associated with the GEM object created by * xe_mmio_gem_create(). * * See: "Exposing MMIO regions to userspace" */ -void xe_mmio_gem_destroy(struct xe_mmio_gem *gem) +void xe_mmio_gem_destroy(struct xe_mmio_gem *gem, struct drm_file *file) { + drm_vma_node_revoke(&gem->base.vma_node, file); xe_mmio_gem_free(&gem->base); } diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.h b/drivers/gpu/drm/xe/xe_mmio_gem.h index 4b76d5586ebb8e..80d7795f07c8ee 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.h +++ b/drivers/gpu/drm/xe/xe_mmio_gem.h @@ -15,6 +15,6 @@ struct xe_mmio_gem; struct xe_mmio_gem *xe_mmio_gem_create(struct xe_device *xe, struct drm_file *file, phys_addr_t phys_addr, size_t size); u64 xe_mmio_gem_mmap_offset(struct xe_mmio_gem *gem); -void xe_mmio_gem_destroy(struct xe_mmio_gem *gem); +void xe_mmio_gem_destroy(struct xe_mmio_gem *gem, struct drm_file *file); #endif /* _XE_MMIO_GEM_H_ */ From de40d31275cd57408ff1fdd97ea117a3f8c50cc5 Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:52 +0100 Subject: [PATCH 0378/1417] drm/xe/mmio_gem: cache the dummy page per object Currently, when the fault handler provides a dummy page, it allocates a new one on every invocation and ties its lifetime to the drm_device via drmm_add_action_or_reset(). Concurrent faults after hot-unplug therefore accumulate pages that persist until device teardown. Cache a single dummy page in the xe_mmio_gem object and use dma_resv lock to protect its allocation. Free it with the object. v2: use dma_resv lock to protect the allocation (Matt Auld) Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Ilia Levi Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-15-matthew.auld@intel.com (cherry picked from commit 8bf6213f9831e46313af4722a1ee6db1b7596378) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 3c42d8358c7d83..970b1e2f498156 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -5,9 +5,9 @@ #include "xe_mmio_gem.h" +#include #include #include -#include #include "xe_device_types.h" @@ -37,6 +37,7 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *); struct xe_mmio_gem { struct drm_gem_object base; phys_addr_t phys_addr; + struct page *dummy_page; /* protected by the GEM's dma_resv */ }; static int xe_mmio_gem_vm_may_split(struct vm_area_struct *area, unsigned long addr) @@ -131,6 +132,8 @@ static void xe_mmio_gem_free(struct drm_gem_object *base) { struct xe_mmio_gem *obj = to_xe_mmio_gem(base); + if (obj->dummy_page) + __free_page(obj->dummy_page); drm_gem_object_release(base); kfree(obj); } @@ -169,27 +172,29 @@ static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct * return 0; } -static void xe_mmio_gem_release_dummy_page(struct drm_device *dev, void *res) +static int alloc_dummy_page_if_needed(struct drm_gem_object *base) { - __free_page((struct page *)res); + struct xe_mmio_gem *obj = to_xe_mmio_gem(base); + + dma_resv_lock(base->resv, NULL); + if (!obj->dummy_page) + obj->dummy_page = alloc_page(GFP_KERNEL | __GFP_ZERO); + dma_resv_unlock(base->resv); + + return obj->dummy_page ? 0 : -ENOMEM; } static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; struct drm_gem_object *base = vma->vm_private_data; - struct drm_device *dev = base->dev; - struct page *page; + struct xe_mmio_gem *obj = to_xe_mmio_gem(base); unsigned long pfn; - page = alloc_page(GFP_KERNEL | __GFP_ZERO); - if (!page) - return VM_FAULT_OOM; - - if (drmm_add_action_or_reset(dev, xe_mmio_gem_release_dummy_page, page)) + if (alloc_dummy_page_if_needed(base)) return VM_FAULT_OOM; - pfn = page_to_pfn(page); + pfn = page_to_pfn(obj->dummy_page); return vmf_insert_pfn_prot(vma, vmf->address, pfn, vm_get_page_prot(vma->vm_flags)); From d0c09528781938362655d9c336364e777119bd6b Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:53 +0100 Subject: [PATCH 0379/1417] drm/xe/mmio_gem: fix destroy flow xe_mmio_gem_destroy() currently frees the GEM object directly, bypassing reference counting. Since existing VMAs hold a reference and the fault handler accesses the object through vma->vm_private_data, this is use-after-free. Additionally, nothing prevents the fault handler from installing PTEs to the real MMIO after destroy. Fix this with proper synchronization and refcounting. Also, do not set vm_pgoff to zero. Many DRM drivers do this because helpers like dma_mmap_pages() interpret vm_pgoff as an intra-buffer page offset; leaving the DRM fake offset there would break these helpers. Those drivers can get away with zeroing it because they map eagerly - all PTEs are established before mmap returns, so vm_pgoff is never consulted again. Our driver does not use such helpers and the newly introduced call to drm_vma_node_unmap() relies on vm_pgoff being untouched. v2: (Matt Auld) - use dma_resv lock to serialize fault handler with destroy - SIGBUS on access after destroy Fixes: 1ffcf8b8ae8a ("drm/xe: Support for mmap-ing mmio regions") Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Ilia Levi Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-16-matthew.auld@intel.com (cherry picked from commit fb2ee38bab8025ad6a7a9cbb4635c5a178e4a7bc) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 42 ++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 970b1e2f498156..5ffe03d3619028 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -38,6 +38,7 @@ struct xe_mmio_gem { struct drm_gem_object base; phys_addr_t phys_addr; struct page *dummy_page; /* protected by the GEM's dma_resv */ + bool destroyed; /* protected by the GEM's dma_resv */ }; static int xe_mmio_gem_vm_may_split(struct vm_area_struct *area, unsigned long addr) @@ -150,8 +151,22 @@ static void xe_mmio_gem_free(struct drm_gem_object *base) */ void xe_mmio_gem_destroy(struct xe_mmio_gem *gem, struct drm_file *file) { - drm_vma_node_revoke(&gem->base.vma_node, file); - xe_mmio_gem_free(&gem->base); + struct drm_gem_object *base = &gem->base; + struct drm_device *dev = base->dev; + + drm_vma_node_revoke(&base->vma_node, file); + + dma_resv_lock(base->resv, NULL); + gem->destroyed = true; + dma_resv_unlock(base->resv); + /* + * Setting 'destroyed' under lock takes care of the subsequent faults. + * Zap the existing PTEs to cut off access to the real MMIO through + * currently mapped pages. + */ + drm_vma_node_unmap(&base->vma_node, dev->anon_inode->i_mapping); + + drm_gem_object_put(base); } static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct *vma) @@ -162,8 +177,6 @@ static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct * if ((vma->vm_flags & VM_SHARED) == 0) return -EINVAL; - /* Set vm_pgoff (used as a fake buffer offset by DRM) to 0 */ - vma->vm_pgoff = 0; vma->vm_page_prot = pgprot_noncached(vma_get_page_prot(vma)); vm_flags_set(vma, VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP | VM_DONTCOPY | VM_NORESERVE); @@ -176,10 +189,9 @@ static int alloc_dummy_page_if_needed(struct drm_gem_object *base) { struct xe_mmio_gem *obj = to_xe_mmio_gem(base); - dma_resv_lock(base->resv, NULL); + dma_resv_assert_held(base->resv); if (!obj->dummy_page) obj->dummy_page = alloc_page(GFP_KERNEL | __GFP_ZERO); - dma_resv_unlock(base->resv); return obj->dummy_page ? 0 : -ENOMEM; } @@ -200,7 +212,7 @@ static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_fault *vmf) vm_get_page_prot(vma->vm_flags)); } -static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) +static vm_fault_t xe_mmio_gem_vm_fault_locked(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; struct drm_gem_object *base = vma->vm_private_data; @@ -210,6 +222,10 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) unsigned long addr, pfn; int idx; + dma_resv_assert_held(base->resv); + if (obj->destroyed) + return VM_FAULT_SIGBUS; + if (!drm_dev_enter(dev, &idx)) { /* * Provide a dummy page to avoid SIGBUS for events such as hot-unplug. @@ -232,3 +248,15 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) drm_dev_exit(idx); return ret; } + +static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; + struct drm_gem_object *base = vma->vm_private_data; + vm_fault_t ret; + + dma_resv_lock(base->resv, NULL); + ret = xe_mmio_gem_vm_fault_locked(vmf); + dma_resv_unlock(base->resv); + return ret; +} From 3c90e42a01426262f0cd166bc01b45c05562640d Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Wed, 9 Sep 2026 16:21:01 +0000 Subject: [PATCH 0380/1417] drm/xe/shrinker: Return the freed page count through a parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit __xe_shrinker_walk() and xe_shrinker_walk() return either the number of pages freed or a negative error, so the two cannot be reported at once. On error the pages already freed are dropped, and since xe_shrinker_scan() only accumulates non-negative returns while *scanned is updated by pointer, the shrinker tells mm that it scanned without freeing. Accumulate the count into a caller-provided counter and return only the status, so an error no longer discards what the walk had freed. Fixes: 00c8efc3180f ("drm/xe: Add a shrinker for xe bos") Assisted-by: Claude:claude-opus-5 Reviewed-by: Thomas Hellström Cc: Matthew Brost Link: https://patch.msgid.link/20260909162102.1097006-2-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin (cherry picked from commit d7aac1a0235a6ce41e30cec385e2db8c33dad12d) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_shrinker.c | 62 ++++++++++++++------------------ 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c index 83374cd5766085..89445cd202380a 100644 --- a/drivers/gpu/drm/xe/xe_shrinker.c +++ b/drivers/gpu/drm/xe/xe_shrinker.c @@ -54,13 +54,14 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea write_unlock(&shrinker->lock); } -static s64 __xe_shrinker_walk(struct xe_device *xe, +static int __xe_shrinker_walk(struct xe_device *xe, struct ttm_operation_ctx *ctx, const struct xe_bo_shrink_flags flags, - unsigned long to_scan, unsigned long *scanned) + unsigned long to_scan, unsigned long *scanned, + unsigned long *freed) { unsigned int mem_type; - s64 freed = 0, lret; + s64 lret; for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { struct ttm_resource_manager *man = ttm_manager_type(&xe->ttm, mem_type); @@ -82,7 +83,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, if (lret < 0) return lret; - freed += lret; + *freed += lret; if (*scanned >= to_scan) break; } @@ -90,7 +91,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, xe_assert(xe, !IS_ERR(ttm_bo)); } - return freed; + return 0; } /* @@ -99,40 +100,35 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, * add writeback. This avoids stalls and explicit writebacks with light or * moderate memory pressure. */ -static s64 xe_shrinker_walk(struct xe_device *xe, +static int xe_shrinker_walk(struct xe_device *xe, struct ttm_operation_ctx *ctx, const struct xe_bo_shrink_flags flags, - unsigned long to_scan, unsigned long *scanned) + unsigned long to_scan, unsigned long *scanned, + unsigned long *freed) { bool no_wait_gpu = true; struct xe_bo_shrink_flags save_flags = flags; - s64 lret, freed; + int ret; swap(no_wait_gpu, ctx->no_wait_gpu); save_flags.writeback = false; - lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); + ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); swap(no_wait_gpu, ctx->no_wait_gpu); - if (lret < 0 || *scanned >= to_scan) - return lret; + if (ret || *scanned >= to_scan) + return ret; - freed = lret; if (!ctx->no_wait_gpu) { - lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); - if (lret < 0) - return lret; - freed += lret; - if (*scanned >= to_scan) - return freed; + ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, + freed); + if (ret || *scanned >= to_scan) + return ret; } - if (flags.writeback) { - lret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned); - if (lret < 0) - return lret; - freed += lret; - } + if (flags.writeback) + ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, + freed); - return freed; + return ret; } static unsigned long @@ -214,7 +210,6 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con bool runtime_pm; bool purgeable; bool can_backup = !!(sc->gfp_mask & __GFP_FS); - s64 lret; nr_to_scan = sc->nr_to_scan; @@ -225,12 +220,9 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con /* Might need runtime PM. Try to wake early if it looks like it. */ runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); - if (purgeable && nr_scanned < nr_to_scan) { - lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, - nr_to_scan, &nr_scanned); - if (lret >= 0) - freed += lret; - } + if (purgeable && nr_scanned < nr_to_scan) + xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); sc->nr_scanned = nr_scanned; if (nr_scanned >= nr_to_scan || !can_backup) @@ -242,10 +234,8 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con shrink_flags.purge = false; - lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, - nr_to_scan, &nr_scanned); - if (lret >= 0) - freed += lret; + xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); sc->nr_scanned = nr_scanned; out: From 985862be16c7e4da808c51f393d631fb60c0be5c Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Wed, 9 Sep 2026 16:21:02 +0000 Subject: [PATCH 0381/1417] drm/xe/shrinker: Take a runtime PM ref before shrinking non-system memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit __xe_shrinker_walk() walks the SYSTEM and TT LRUs without a runtime PM reference. Shrinking a bo outside system memory invalidates its GPU mappings, which needs the device resumed, so while it is runtime suspended the page table zap trips an assert and the TLB invalidation returns -ENODEV: WARNING: drivers/gpu/drm/xe/xe_bo.c:770 at xe_bo_move_notify+0x1fc/0x450 [xe] xe_bo_shrink+0x20f/0x2b0 [xe] __xe_shrinker_walk+0x174/0x410 [xe] xe_shrinker_scan+0x10c/0x1e0 [xe] do_shrink_slab+0x176/0x7e0 drop_caches_sysctl_handler+0x9c/0xf0 Take a reference before walking a memory type other than XE_PL_SYSTEM and stop there if it cannot be acquired. Reuse the shrinker's existing acquire path, which resumes the device directly where reclaim allows that and otherwise queues the PM worker for a later scan. Stop the walk once the scan target is met, so a satisfied scan does not wake the device. System memory is still reclaimed while the device is suspended. Gate this on xe_device_is_l2_flush_optimized(), the same condition under which xe_bo_trigger_rebind() issues the invalidation for a non-fault-mode vm, so reclaim is unaffected elsewhere. The System CCS copy already has its own reference in xe_bo_shrink(). Only a non-fault-mode vm can reach this, since a fault-mode vm requires LR mode and that holds a runtime PM reference for the vm's lifetime. Reproduced with igt@xe_madvise@dontneed-before-exec while the GPU is runtime suspended. v2: simplify needs_rpm check. (Matt) retarget Fixes tag since the issue occurs with the non-fault-mode path added by 4e7ebff69aed. v3: handle this in xe_shrinker.c instead of xe_bo.c (Thomas) v4: stop the walk once the scan target is met. (Sashiko) v5: rebase on the freed page accounting fix. (Sashiko) v6: reuse the shrinker acquire path so runtime pm can be resumed directly instead of always queueing a worker. (Thomas) v7: replace xe_pm_runtime_put() with xe_shrinker_runtime_pm_put(). (Thomas) Fixes: 4e7ebff69aed ("drm/xe/xe3p_lpg: flush shrinker bo cachelines manually") Assisted-by: Claude:claude-opus-5 Cc: Tejas Upadhyay Cc: Matthew Brost Reviewed-by: Thomas Hellström Link: https://patch.msgid.link/20260909162102.1097006-3-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin (cherry picked from commit 628f92b28bf4c371c10207daf6fc4caee0c0db2e) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_shrinker.c | 78 +++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c index 89445cd202380a..deb4378c1ec154 100644 --- a/drivers/gpu/drm/xe/xe_shrinker.c +++ b/drivers/gpu/drm/xe/xe_shrinker.c @@ -54,13 +54,39 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea write_unlock(&shrinker->lock); } -static int __xe_shrinker_walk(struct xe_device *xe, +static bool __xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker) +{ + struct xe_device *xe = shrinker->xe; + + if (xe_pm_runtime_get_if_active(xe)) + return true; + + if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { + xe_pm_runtime_get(xe); + return true; + } + + queue_work(xe->unordered_wq, &shrinker->pm_worker); + + return false; +} + +static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) +{ + if (runtime_pm) + xe_pm_runtime_put(shrinker->xe); +} + +static int __xe_shrinker_walk(struct xe_shrinker *shrinker, struct ttm_operation_ctx *ctx, const struct xe_bo_shrink_flags flags, unsigned long to_scan, unsigned long *scanned, unsigned long *freed) { + struct xe_device *xe = shrinker->xe; unsigned int mem_type; + bool rpm = false; + int ret = 0; s64 lret; for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { @@ -75,23 +101,35 @@ static int __xe_shrinker_walk(struct xe_device *xe, if (!man || !man->use_tt) continue; + if (mem_type != XE_PL_SYSTEM && !rpm && + xe_device_is_l2_flush_optimized(xe)) { + if (!__xe_shrinker_runtime_pm_get(shrinker)) + break; + rpm = true; + } + ttm_bo_lru_for_each_reserved_guarded(&curs, man, &arg, ttm_bo) { if (!ttm_bo_shrink_suitable(ttm_bo, ctx)) continue; lret = xe_bo_shrink(ctx, ttm_bo, flags, scanned); - if (lret < 0) - return lret; + if (lret < 0) { + ret = lret; + goto out; + } *freed += lret; if (*scanned >= to_scan) - break; + goto out; } /* Trylocks should never error, just fail. */ xe_assert(xe, !IS_ERR(ttm_bo)); } - return 0; +out: + xe_shrinker_runtime_pm_put(shrinker, rpm); + + return ret; } /* @@ -100,7 +138,7 @@ static int __xe_shrinker_walk(struct xe_device *xe, * add writeback. This avoids stalls and explicit writebacks with light or * moderate memory pressure. */ -static int xe_shrinker_walk(struct xe_device *xe, +static int xe_shrinker_walk(struct xe_shrinker *shrinker, struct ttm_operation_ctx *ctx, const struct xe_bo_shrink_flags flags, unsigned long to_scan, unsigned long *scanned, @@ -112,20 +150,21 @@ static int xe_shrinker_walk(struct xe_device *xe, swap(no_wait_gpu, ctx->no_wait_gpu); save_flags.writeback = false; - ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); + ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, + freed); swap(no_wait_gpu, ctx->no_wait_gpu); if (ret || *scanned >= to_scan) return ret; if (!ctx->no_wait_gpu) { - ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, + ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, freed); if (ret || *scanned >= to_scan) return ret; } if (flags.writeback) - ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, + ret = __xe_shrinker_walk(shrinker, ctx, flags, to_scan, scanned, freed); return ret; @@ -176,22 +215,7 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force, return false; } - if (!xe_pm_runtime_get_if_active(xe)) { - if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { - xe_pm_runtime_get(xe); - return true; - } - queue_work(xe->unordered_wq, &shrinker->pm_worker); - return false; - } - - return true; -} - -static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) -{ - if (runtime_pm) - xe_pm_runtime_put(shrinker->xe); + return __xe_shrinker_runtime_pm_get(shrinker); } static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc) @@ -221,7 +245,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); if (purgeable && nr_scanned < nr_to_scan) - xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, + xe_shrinker_walk(shrinker, &ctx, shrink_flags, nr_to_scan, &nr_scanned, &freed); sc->nr_scanned = nr_scanned; @@ -234,7 +258,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con shrink_flags.purge = false; - xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, + xe_shrinker_walk(shrinker, &ctx, shrink_flags, nr_to_scan, &nr_scanned, &freed); sc->nr_scanned = nr_scanned; From 073a30d75f309812ed61af134f24ffef4107b13a Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 16:31:10 +0200 Subject: [PATCH 0382/1417] drm/vc4: Use managed KMS polling to fix UAF on unbind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vc4_kms_load() calls drm_kms_helper_poll_init() but the driver provides no matching drm_kms_helper_poll_fini(). The output poll work stays scheduled after unbind and runs on the freed drm_device: # modprobe vc4; rmmod vc4; sleep 10 BUG: KASAN: slab-use-after-free in delayed_work_timer_fn BUG: KASAN: slab-use-after-free in drm_client_dev_hotplug [drm] Workqueue: events output_poll_execute [drm_kms_helper] Allocated by task 171: __devm_drm_dev_alloc Freed by task 262 (rmmod): drm_dev_put / component_del Use drmm_kms_helper_poll_init() so polling is finalized with the device, as other drivers do. Fixes: c8b75bca92cb ("drm/vc4: Add KMS support for Raspberry Pi.") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260822143110.68594-1-kmehltretter@gmail.com Reviewed-by: Maíra Canal Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_kms.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c index b17e73bce3841a..82a7f2ac4c7e20 100644 --- a/drivers/gpu/drm/vc4/vc4_kms.c +++ b/drivers/gpu/drm/vc4/vc4_kms.c @@ -1188,7 +1188,7 @@ int vc4_kms_load(struct drm_device *dev) drm_mode_config_reset(dev); - drm_kms_helper_poll_init(dev); + drmm_kms_helper_poll_init(dev); return 0; } From bdf5f731957de48acada392f28e82bc019713adb Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Mon, 14 Sep 2026 17:41:36 +0700 Subject: [PATCH 0383/1417] hwmon: (gpio-fan) return IRQ_HANDLED from the shared alarm IRQ handler fan_alarm_irq_handler() always schedules alarm_work but returns IRQ_NONE, so the kernel treats every alarm interrupt as unhandled. On a shared line that risks the whole line being disabled as spurious. v1 just fixed that, but it was still IRQF_SHARED, and always returning IRQ_HANDLED there defeats spurious-interrupt detection for the line -- if the interrupt ever fires without a real event, nothing catches it, and a fault could spin the CPU in the handler. Sashiko flagged this in v1, and Guenter confirmed: this interrupt must not be shared. So v2 drops IRQF_SHARED too. Fixes: d6fe1360f42e ("hwmon: add generic GPIO fan driver") Reported-by: Sashiko AI review Link: https://lore.kernel.org/r/20260901160931.DD3811F00A3D@smtp.kernel.org Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Link: https://patch.msgid.link/20260914104136.1797979-1-congnt264@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/gpio-fan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index df8bd970760505..3f78375eeb4481 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -68,7 +68,7 @@ static irqreturn_t fan_alarm_irq_handler(int irq, void *dev_id) schedule_work(&fan_data->alarm_work); - return IRQ_NONE; + return IRQ_HANDLED; } static ssize_t fan1_alarm_show(struct device *dev, @@ -103,7 +103,7 @@ static int fan_alarm_init(struct gpio_fan_data *fan_data) irq_set_irq_type(alarm_irq, IRQ_TYPE_EDGE_BOTH); return devm_request_irq(dev, alarm_irq, fan_alarm_irq_handler, - IRQF_SHARED, "GPIO fan alarm", fan_data); + 0, "GPIO fan alarm", fan_data); } /* From 0ff9c7775e51ac6d47b1bb5c46f06b1434fe58a8 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 14:28:09 +0800 Subject: [PATCH 0384/1417] hwmon: (w83791d) remove fan/pwm 4-5 sysfs group on remove When the fan/pwm 4-5 pins are not used as GPIO, w83791d_probe() creates the w83791d_group_fanpwm45 sysfs group on the I2C client device. The probe error path removes this group when a later initialization step fails, but the normal remove path only removes w83791d_group. As a result, the optional fan/pwm 4-5 sysfs files can remain after the driver is unbound. The callbacks associated with these files access the driver data, which is devm allocated and released after driver unbind. Leaving the sysfs files behind can therefore result in accesses to stale driver data. Remove w83791d_group_fanpwm45 during normal teardown as well. This issue was found by manual code inspection. Fixes: 6e1ecd9b8f13 ("hwmon: (w83791d) fan 4/5 pins can also be used for gpio") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260914062809.1650538-1-lgs201920130244@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/w83791d.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/w83791d.c b/drivers/hwmon/w83791d.c index 4a777430af5cd4..4b07a25ae59e29 100644 --- a/drivers/hwmon/w83791d.c +++ b/drivers/hwmon/w83791d.c @@ -1415,6 +1415,7 @@ static void w83791d_remove(struct i2c_client *client) struct w83791d_data *data = i2c_get_clientdata(client); hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &w83791d_group_fanpwm45); sysfs_remove_group(&client->dev.kobj, &w83791d_group); } From c702a5f18b780e477eccbbab558e590e9673e4cb Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 15:36:38 +0800 Subject: [PATCH 0385/1417] hwmon: (w83793) release probe data through kref w83793_probe() initializes data->kref to manage the lifetime of the driver data. The normal remove path drops the driver-owned reference with kref_put(), while watchdog users take and release additional references through the same kref. However, the probe error path still frees data directly with kfree(). This bypasses the kref-managed lifetime and discards the initial reference without a matching kref_put(), leaving the reference accounting unbalanced. Drop the probe-owned reference with kref_put() instead and let w83793_release_resources() perform the final free, matching the normal remove path. This issue was found by manual code inspection. Fixes: 5852f9609d21 ("hwmon: (w83793) Add watchdog functionality") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260914073638.1662500-1-lgs201920130244@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/w83793.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/w83793.c b/drivers/hwmon/w83793.c index a548586369e1db..c6ef04c69856e8 100644 --- a/drivers/hwmon/w83793.c +++ b/drivers/hwmon/w83793.c @@ -1928,7 +1928,9 @@ static int w83793_probe(struct i2c_client *client) for (i = 0; i < ARRAY_SIZE(w83793_temp); i++) device_remove_file(dev, &w83793_temp[i].dev_attr); free_mem: - kfree(data); + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, w83793_release_resources); + mutex_unlock(&watchdog_data_mutex); exit: return err; } From 1c4f6202876a74e752a8cfd3696955f761671c7a Mon Sep 17 00:00:00 2001 From: Jaeho Cho Date: Mon, 14 Sep 2026 10:24:42 -0400 Subject: [PATCH 0386/1417] ALSA: hda/realtek: Enable mute LEDs on HP OmniBook 7 17-dc0xxx The HP OmniBook 7 Laptop 17-dc0xxx (SSID 103c:8d9c) has mute and mic-mute LEDs on its F6 and F9 keys, but neither lights up. Its quirk entry only sets up the two CS35L41 amplifiers, so no LED control is registered for either key. Writing to the ALC245 by hand with hda-verb, the mute LED responds to COEF 0x0b bits 2-3 and the mic-mute LED to GPIO 0x04, lit when the pin is low. That appears to match what ALC245_FIXUP_HP_X360_MUTE_LEDS already does, so add a fixup that chains the two-amp I2C setup to it and use it for this model. Tested on 7.2.4 with the patched module: hda::mute and hda::micmute are registered, and both LEDs follow the speaker and microphone mute state, including from the F6 and F9 keys. Signed-off-by: Jaeho Cho Link: https://patch.msgid.link/20260914142445.3476212-1-jaeho2025@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index fa83b739fb5080..5127a111c59cd7 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4303,6 +4303,7 @@ enum { ALC287_FIXUP_LEGION_16ACHG6, ALC287_FIXUP_CS35L41_I2C_2, ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED, + ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS, ALC287_FIXUP_CS35L41_I2C_4, ALC245_FIXUP_CS35L41_SPI_1, ALC245_FIXUP_CS35L41_SPI_2, @@ -6614,6 +6615,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_HP_MUTE_LED, }, + [ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_two, + .chained = true, + .chain_id = ALC245_FIXUP_HP_X360_MUTE_LEDS, + }, [ALC287_FIXUP_CS35L41_I2C_4] = { .type = HDA_FIXUP_FUNC, .v.func = cs35l41_fixup_i2c_four, @@ -7662,7 +7669,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8d92, "HP ZBook Firefly 16 G12", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8dcd, "HP Victus 15-fa2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8d9b, "HP 17 Turbine OmniBook 7 UMA", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8d9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8d9e, "HP 17 Turbine OmniBook X DIS", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2), From 5ab3dc647751996784cff20a51f3730f4e88afe4 Mon Sep 17 00:00:00 2001 From: Asai Neko Date: Mon, 14 Sep 2026 18:58:29 +0800 Subject: [PATCH 0387/1417] ALSA: usb-audio: skip the broken mute control on AVerMedia GC553Pro Skip the nonfunctional master mute control on the AVerMedia Live Gamer ULTRA S GC553Pro (07ca:1553). USB tracing shows that GET_CUR returns zero bytes instead of the required one-byte value, both through usbfs and during ALSA initialization. SET_CUR succeeds, but switching capture off does not mute HDMI audio. Before the change, the driver exposed a misleading PCM Capture Switch and logged: 3:2: failed to get current value for ch 0 (-22) With the patch applied, the switch and warning are absent. A ten-second sound recording through PipeWire confirmed that stereo 48 kHz, 16-bit capture still works. Tested on NixOS with the patched 7.3.0-rc3 kernel. The USB audio driver object builds with Clang and W=1; sparse and strict checkpatch pass. Signed-off-by: Asai Neko Link: https://patch.msgid.link/20260914-avermedia-gc553pro-alsa-v1-1-4c694e8b0cd5@sne.moe Signed-off-by: Takashi Iwai --- sound/usb/mixer_maps.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 69093c666282b9..41454cb403d04a 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -531,6 +531,15 @@ static const struct usbmix_name_map audient_id24_map[] = { {} }; +/* + * The GC553Pro returns no data for GET_CUR on its advertised mute control. + * SET_CUR succeeds but does not mute capture, so skip the control entirely. + */ +static const struct usbmix_name_map avermedia_gc553pro_map[] = { + { 3, NULL, UAC_FU_MUTE }, + {} +}; + /* * Control map entries */ @@ -577,6 +586,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x0763, 0x2031), .selector_map = c400_selectors, }, + { + .id = USB_ID(0x07ca, 0x1553), + .map = avermedia_gc553pro_map, + }, { .id = USB_ID(0x08bb, 0x2702), .map = linex_map, From 9d1e523b92d8cb11a4a7e5a2655d6824c2d0f6e3 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 4 Sep 2026 14:52:59 +0200 Subject: [PATCH 0388/1417] selftests/hid: add define for commonly used buf size If we want to add another report descriptor without report IDs with a report size bigger than 10, we have multiple magic values to replace. Put a #define once and for all, so we don't have dangling ones. Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hid_bpf.c | 38 ++++++++++++------------ tools/testing/selftests/hid/hid_common.h | 3 +- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index 7ab86296ff236a..0d03ad5245fcfe 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -5,7 +5,7 @@ #include struct hid_hw_request_syscall_args { - __u8 data[10]; + __u8 data[MAX_BUF_SIZE]; unsigned int hid; int retval; size_t size; @@ -175,7 +175,7 @@ TEST_F(hid_bpf, raw_event) const struct test_program progs[] = { { .name = "hid_first_event" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -226,7 +226,7 @@ TEST_F(hid_bpf, subprog_raw_event) const struct test_program progs[] = { { .name = "hid_subprog_first_event" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -284,7 +284,7 @@ TEST_F(hid_bpf, test_attach_detach) { .name = "hid_second_event" }, }; struct bpf_link *link; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err, link_fd; LOAD_PROGRAMS(progs); @@ -369,7 +369,7 @@ TEST_F(hid_bpf, test_hid_change_report) const struct test_program progs[] = { { .name = "hid_change_report_id" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -396,13 +396,13 @@ TEST_F(hid_bpf, test_hid_user_input_report_call) { struct hid_hw_request_syscall_args args = { .retval = -1, - .size = 10, + .size = MAX_BUF_SIZE, }; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, .ctx_in = &args, .ctx_size_in = sizeof(args), ); - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err, prog_fd; LOAD_BPF; @@ -442,7 +442,7 @@ TEST_F(hid_bpf, test_hid_user_output_report_call) { struct hid_hw_request_syscall_args args = { .retval = -1, - .size = 10, + .size = MAX_BUF_SIZE, }; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, .ctx_in = &args, @@ -491,7 +491,7 @@ TEST_F(hid_bpf, test_hid_user_raw_request_call) .retval = -1, .type = HID_FEATURE_REPORT, .request_type = HID_REQ_GET_REPORT, - .size = 10, + .size = MAX_BUF_SIZE, }; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, .ctx_in = &args, @@ -524,7 +524,7 @@ TEST_F(hid_bpf, test_hid_filter_raw_request_call) const struct test_program progs[] = { { .name = "hid_test_filter_raw_request" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -577,7 +577,7 @@ TEST_F(hid_bpf, test_hid_change_raw_request_call) const struct test_program progs[] = { { .name = "hid_test_hidraw_raw_request" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -603,7 +603,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_raw_request_call) const struct test_program progs[] = { { .name = "hid_test_infinite_loop_raw_request" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -626,7 +626,7 @@ TEST_F(hid_bpf, test_hid_filter_output_report_call) const struct test_program progs[] = { { .name = "hid_test_filter_output_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -679,7 +679,7 @@ TEST_F(hid_bpf, test_hid_change_output_report_call) const struct test_program progs[] = { { .name = "hid_test_hidraw_output_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -703,7 +703,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_output_report_call) const struct test_program progs[] = { { .name = "hid_test_infinite_loop_output_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -729,7 +729,7 @@ TEST_F(hid_bpf, test_multiply_events_wq) const struct test_program progs[] = { { .name = "hid_test_multiply_events_wq" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -767,7 +767,7 @@ TEST_F(hid_bpf, test_multiply_events) const struct test_program progs[] = { { .name = "hid_test_multiply_events" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -801,7 +801,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_input_report_call) const struct test_program progs[] = { { .name = "hid_test_infinite_loop_input_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -855,7 +855,7 @@ TEST_F(hid_bpf, test_hid_attach_flags) .insert_head = 0, }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); diff --git a/tools/testing/selftests/hid/hid_common.h b/tools/testing/selftests/hid/hid_common.h index e3b267446fa0ae..4567336f131d10 100644 --- a/tools/testing/selftests/hid/hid_common.h +++ b/tools/testing/selftests/hid/hid_common.h @@ -13,6 +13,7 @@ #include #define SHOW_UHID_DEBUG 0 +#define MAX_BUF_SIZE 10 #define min(a, b) \ ({ __typeof__(a) _a = (a); \ @@ -110,7 +111,7 @@ static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER; static pthread_mutex_t uhid_output_mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t uhid_output_cond = PTHREAD_COND_INITIALIZER; -static unsigned char output_report[10]; +static unsigned char output_report[MAX_BUF_SIZE]; /* no need to protect uhid_stopped, only one thread accesses it */ static bool uhid_stopped; From c4afa4862b878d56e0cc1021298794ac1b45bc49 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 4 Sep 2026 14:53:00 +0200 Subject: [PATCH 0389/1417] HID: bpf: fix __hid_bpf_hw_check_params report length Turns out that USB, I2C and other transport drivers (except uhid which just passes the data) still need to have the report ID in the first byte. Because they expect the first byte to be the report ID or 0, when the report ID is 0, they strip that first byte before forwarding to the device. This means that the transport layer forwards a buffer of size N-1 to the device, which gets rejected. Fixes: 5599f8019661 ("HID: bpf: export hid_hw_output_report as a BPF kfunc") Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/hid_bpf_dispatch.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index 536f6d01fd14c6..b1de1dd0f21d08 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -359,7 +359,7 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx) static int __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, - enum hid_report_type rtype) + enum hid_report_type rtype, bool hw_request) { struct hid_report_enum *report_enum; struct hid_report *report; @@ -388,6 +388,10 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, report_len = hid_report_len(report); + /* unnumbered reports need to have a report ID reserved in the first byte */ + if (hw_request && report_enum->numbered == 0) + report_len += 1; + if (*buf__sz > report_len) *buf__sz = report_len; @@ -420,7 +424,7 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype, true); if (ret) return ret; @@ -480,7 +484,7 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz) return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT, true); if (ret) return ret; @@ -506,7 +510,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, type); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, type, false); if (ret) return ret; From b6f69097c8271cca30314fd6e4c802f90737bfcc Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 4 Sep 2026 14:53:01 +0200 Subject: [PATCH 0390/1417] selftests/hid: add unnumbered variant to the hid_bpf tests A bug appeared in hid_bpf_dispatch.c where it wasn't properly handling unnumbered reports. Add a device variant without report IDs so we can also test them. Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hid_bpf.c | 53 ++++++++++++++++++++---- tools/testing/selftests/hid/hid_common.h | 24 ++++++++++- tools/testing/selftests/hid/progs/hid.c | 2 +- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index 0d03ad5245fcfe..32d81ba15a25ea 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -54,11 +54,27 @@ FIXTURE_TEARDOWN(hid_bpf) { hid_bpf_teardown(_metadata, self, variant); \ } while (0) +FIXTURE_VARIANT(hid_bpf) { + __u8 *rdesc; + size_t rdesc_size; +}; + +FIXTURE_VARIANT_ADD(hid_bpf, numbered) { + .rdesc = rdesc, + .rdesc_size = sizeof(rdesc), +}; + +FIXTURE_VARIANT_ADD(hid_bpf, unnumbered) { + .rdesc = fido2_rdesc, + .rdesc_size = sizeof(fido2_rdesc), +}; + FIXTURE_SETUP(hid_bpf) { int err; - err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a36, rdesc, sizeof(rdesc)); + err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a36, + variant->rdesc, variant->rdesc_size); ASSERT_OK(err); } @@ -409,8 +425,11 @@ TEST_F(hid_bpf, test_hid_user_input_report_call) args.hid = self->hid.hid_id; args.data[0] = 1; /* report ID */ - args.data[1] = 2; /* report ID */ - args.data[2] = 42; /* report ID */ + args.data[1] = 2; + args.data[2] = 42; + + if (variant->rdesc == fido2_rdesc) + args.data[0] = 0; prog_fd = bpf_program__fd(self->skel->progs.hid_user_input_report); @@ -428,8 +447,13 @@ TEST_F(hid_bpf, test_hid_user_input_report_call) /* read the data from hidraw */ memset(buf, 0, sizeof(buf)); err = read(self->hidraw_fd, buf, sizeof(buf)); - ASSERT_EQ(err, 6) TH_LOG("read_hidraw"); - ASSERT_EQ(buf[0], 1); + if (variant->rdesc == rdesc) { + ASSERT_EQ(err, 6) TH_LOG("read_hidraw"); + } else { + ASSERT_EQ(err, 64) + TH_LOG("read_hidraw"); + } + ASSERT_EQ(buf[0], args.data[0]); ASSERT_EQ(buf[1], 2); ASSERT_EQ(buf[2], 42); } @@ -455,8 +479,11 @@ TEST_F(hid_bpf, test_hid_user_output_report_call) args.hid = self->hid.hid_id; args.data[0] = 1; /* report ID */ - args.data[1] = 2; /* report ID */ - args.data[2] = 42; /* report ID */ + args.data[1] = 2; + args.data[2] = 42; + + if (variant->rdesc == fido2_rdesc) + args.data[0] = 0; prog_fd = bpf_program__fd(self->skel->progs.hid_user_output_report); @@ -472,9 +499,14 @@ TEST_F(hid_bpf, test_hid_user_output_report_call) ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts"); ASSERT_OK(cond_err) TH_LOG("error while calling waiting for the condition"); - ASSERT_EQ(args.retval, 3); + if (variant->rdesc == rdesc) { + ASSERT_EQ(args.retval, 3); + } else if (variant->rdesc == fido2_rdesc) { + ASSERT_EQ(args.retval, 65) + TH_LOG("report size error, should have 64 + 1 extra byte for the report ID 0"); + } - ASSERT_EQ(output_report[0], 1); + ASSERT_EQ(output_report[0], args.data[0]); ASSERT_EQ(output_report[1], 2); ASSERT_EQ(output_report[2], 42); @@ -886,6 +918,9 @@ TEST_F(hid_bpf, test_rdesc_fixup) }; int err, desc_size; + if (variant->rdesc != rdesc) + SKIP(return, "not compatible report descriptor"); + LOAD_PROGRAMS(progs); /* check that hid_rdesc_fixup() was executed */ diff --git a/tools/testing/selftests/hid/hid_common.h b/tools/testing/selftests/hid/hid_common.h index 4567336f131d10..b7890ba2878fdf 100644 --- a/tools/testing/selftests/hid/hid_common.h +++ b/tools/testing/selftests/hid/hid_common.h @@ -13,7 +13,7 @@ #include #define SHOW_UHID_DEBUG 0 -#define MAX_BUF_SIZE 10 +#define MAX_BUF_SIZE 128 #define min(a, b) \ ({ __typeof__(a) _a = (a); \ @@ -98,6 +98,28 @@ static unsigned char rdesc[] = { static __u8 feature_data[] = { 1, 2 }; +static __maybe_unused unsigned char fido2_rdesc[] = { + 0x06, 0xd0, 0xf1, /* Usage Page (FIDO Alliance) */ + 0x09, 0x01, /* Usage (U2F Authenticator Device) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x20, /* Usage (Input Report Data) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x00, /* Logical Maximum (255) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x40, /* Report Count (64) */ + 0x81, 0x02, /* Input (Data,Var,Abs) */ + 0x09, 0x21, /* Usage (Output Report Data) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x00, /* Logical Maximum (255) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x40, /* Report Count (64) */ + 0x91, 0x02, /* Output (Data,Var,Abs) */ + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x09, 0x22, /* Usage (Vendor Usage 0x22) */ + 0xb1, 0x02, /* Feature (Data,Var,Abs) */ + 0xc0, /* End Collection */ +}; + #define ASSERT_OK(data) ASSERT_FALSE(data) #define ASSERT_OK_PTR(ptr) ASSERT_NE(NULL, ptr) diff --git a/tools/testing/selftests/hid/progs/hid.c b/tools/testing/selftests/hid/progs/hid.c index 361dc7eaad22b6..48aa8088cc53e1 100644 --- a/tools/testing/selftests/hid/progs/hid.c +++ b/tools/testing/selftests/hid/progs/hid.c @@ -98,7 +98,7 @@ struct hid_bpf_ops change_report_id = { struct hid_hw_request_syscall_args { /* data needs to come at offset 0 so we can use it in calls */ - __u8 data[10]; + __u8 data[128]; unsigned int hid; int retval; size_t size; From c9e6e5f38bf75276605f1952b22285f5f3abcaff Mon Sep 17 00:00:00 2001 From: Slavin Liu Date: Sun, 13 Sep 2026 20:51:54 +0800 Subject: [PATCH 0391/1417] ALSA: hda: trace PCM open only after assigning a stream Stream assignment can fail when hardware streams are exhausted. Move the tracepoint after the NULL check because its payload accesses the assigned stream tag. Detected by static analysis and reviewed with AI-assisted source auditing. Fixes: 184865085b88 ("ALSA: hda - rename hda_intel_trace.h to hda_controller_trace.h") Assisted-by: LLM Signed-off-by: Slavin Liu Link: https://patch.msgid.link/20260913125154.109944-1-bolin.liu@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/hda/common/controller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index afec5c5546ec7e..18dae022b324f3 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -586,11 +586,11 @@ static int azx_pcm_open(struct snd_pcm_substream *substream) snd_hda_codec_pcm_get(apcm->info); mutex_lock(&chip->open_mutex); azx_dev = azx_assign_device(chip, substream); - trace_azx_pcm_open(chip, azx_dev); if (azx_dev == NULL) { err = -EBUSY; goto unlock; } + trace_azx_pcm_open(chip, azx_dev); runtime->private_data = azx_dev; runtime->hw = azx_pcm_hw; From ce9d5197d651cdd0fbb586c3d77c28438abe1b10 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 25 Aug 2026 10:13:14 +0200 Subject: [PATCH 0392/1417] wifi: ath12k: ahb: Revert undocumented ABI and dead code Commit 96f46607bbce ("wifi: ath12k: add AHB platform descriptor support") added undocumented OF ABI, by relying on a very specific node name. This is not allowed and was never acked by Devicetree maintainers. Additionally that part of code is not even used, because all devices have exactly the same user pd, so this was added "for future". Adding dead code just "for future" is heavily discouraged in kernel coding. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260825081313.71351-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/ahb.c | 51 +-------------------- 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c index 98a6606ffd76ac..6e9e9034cba1a3 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c @@ -15,21 +15,6 @@ #include "dp.h" #include "core.h" -/* - * Node name to UserPD ID mapping - * - * The io_start field is used for additional validation when the reg - * property is present in the device tree. If io_start is 0, only - * node_name matching is performed. - * - * For platforms where not all WiFi nodes have a 'reg' property, set - * io_start to 0 for those entries. The driver will match purely by - * node name in such cases. - */ -static const struct ath12k_ahb_userpd_map ath12k_wifi7_ahb_userpd_map[] = { - { .io_start = 0x0c000000, .node_name = "wifi", .upd_id = ATH12K_AHB_USERPD_ID_0 }, -}; - static const struct ath12k_ahb_desc ath12k_wifi7_ahb_desc[] = { [ATH12K_HW_IPQ5332_HW10] = { .hw_rev = ATH12K_HW_IPQ5332_HW10, @@ -55,40 +40,6 @@ static const struct of_device_id ath12k_wifi7_ahb_of_match[] = { MODULE_DEVICE_TABLE(of, ath12k_wifi7_ahb_of_match); -/* - * ath12k_wifi7_ahb_get_userpd_id - Resolve UserPD ID from DT properties - * @ab: ath12k base structure - * - * Returns: UserPD ID (1-based) on success, 0 on failure - * - * Resolution logic: - * 1. If reg property exist in DT, get userpd_id from io_start - * 2. If reg property is absent, get userpd_id from DT node name - * 3. Return 0 if no match found (probe will fail) - */ -static u32 ath12k_wifi7_ahb_get_userpd_id(struct ath12k_base *ab) -{ - const struct ath12k_ahb_userpd_map *map; - struct resource *res; - size_t i; - - res = platform_get_resource(ab->pdev, IORESOURCE_MEM, 0); - - for (i = 0; i < ARRAY_SIZE(ath12k_wifi7_ahb_userpd_map); i++) { - map = &ath12k_wifi7_ahb_userpd_map[i]; - - if (res) { - if (map->io_start && map->io_start == res->start) - return map->upd_id; - } else if (map->node_name && - of_node_name_eq(ab->dev->of_node, map->node_name)) { - return map->upd_id; - } - } - - return 0; -} - static int ath12k_wifi7_ahb_probe(struct platform_device *pdev) { const struct ath12k_ahb_desc *desc; @@ -106,7 +57,7 @@ static int ath12k_wifi7_ahb_probe(struct platform_device *pdev) ab->hw_rev = desc->hw_rev; ab->hif.ops = desc->ops; ab_ahb->scm_auth_enabled = desc->auth_enabled; - ab_ahb->userpd_id = ath12k_wifi7_ahb_get_userpd_id(ab); + ab_ahb->userpd_id = ATH12K_AHB_USERPD_ID_0; if (!ab_ahb->userpd_id) return -EOPNOTSUPP; From d9be5e75530772fc31637070d51e5717d6aeaa2a Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 10 Sep 2026 02:09:07 +0000 Subject: [PATCH 0393/1417] wifi: wcn36xx: Fix potential use-after-free in TX ack timer teardown wcn36xx_dxe_deinit() tears down the TX ack timer with timer_delete(), which only dequeues the timer and does not wait for a callback that is already executing; the preceding free_irq() calls synchronize the interrupt handlers only. The callback, wcn36xx_dxe_tx_timer(), can therefore be running past the teardown and use the wcn freed along with the ieee80211_hw in wcn36xx_remove(): it takes wcn->dxe_lock, reads wcn->tx_ack_skb and passes wcn->hw to ieee80211_tx_status_irqsafe(). Fix this by using timer_shutdown_sync(), which waits for a running callback and also prevents the timer from being rearmed again. The timer is set up again by wcn36xx_dxe_init() on the next start, so the start/stop cycle is unaffected. This issue was found by an in-house static analysis tool. Fixes: fdf21cc37149 ("wcn36xx: Add TX ack support") Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Reviewed-by: Loic Poulain Link: https://patch.msgid.link/20260910020907.3353-1-fanwu01@zju.edu.cn Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/wcn36xx/dxe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/wcn36xx/dxe.c b/drivers/net/wireless/ath/wcn36xx/dxe.c index 44020ec265fb69..801f1218ef89bf 100644 --- a/drivers/net/wireless/ath/wcn36xx/dxe.c +++ b/drivers/net/wireless/ath/wcn36xx/dxe.c @@ -1055,7 +1055,7 @@ void wcn36xx_dxe_deinit(struct wcn36xx *wcn) free_irq(wcn->tx_irq, wcn); free_irq(wcn->rx_irq, wcn); - timer_delete(&wcn->tx_ack_timer); + timer_shutdown_sync(&wcn->tx_ack_timer); if (wcn->tx_ack_skb) { ieee80211_tx_status_irqsafe(wcn->hw, wcn->tx_ack_skb); From 820b8cff81c796ba20573e04722ab62500713f97 Mon Sep 17 00:00:00 2001 From: Nicolas Escande Date: Fri, 31 Jul 2026 16:58:30 +0200 Subject: [PATCH 0394/1417] wifi: ath11k: cleanup arsta in ath11k_mac_peer_cleanup_all() When mac80211 removes a sta, it calls .sta_state() which in turn calls ath11k_mac_station_remove(). In that function we clean up both peers & arsta related resources. But when the firmware crashes, ath11k calls ieee80211_restart_hw(), which assumes that all driver related resources are cleaned up beforehand. This cleanup is supposedly done by ath11k_mac_peer_cleanup_all() but does not in fact free arsta->rx_stats / tx_stats. Extract the arsta cleanup from ath11k_mac_station_remove() into a new ath11k_mac_station_cleanup() and call it from both there and ath11k_mac_peer_cleanup_all(). This should handle kmemleaks reports like: unreferenced object 0xffffff801ae66400 (size 1024): comm "hostapd", pid 1306, jiffies 4295011565 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ backtrace (crc d61c08ec): kmemleak_alloc+0x3c/0x50 __kmalloc_cache_noprof+0x2b0/0x3e0 ath11k_mac_op_sta_state+0x1dc/0xb10 drv_sta_state+0xac/0x6f8 sta_info_insert_rcu+0x314/0x5e0 sta_info_insert+0x14/0x38 ieee80211_add_station+0x10c/0x1a0 nl80211_new_station+0x3e8/0x680 genl_family_rcv_msg_doit+0xc0/0x120 genl_rcv_msg+0x1b4/0x258 netlink_rcv_skb+0x4c/0x108 genl_rcv+0x38/0x60 netlink_unicast+0x190/0x278 netlink_sendmsg+0x15c/0x370 ____sys_sendmsg+0x120/0x290 ___sys_sendmsg+0x70/0xa0 Tested-on: QCN9074 hw1.0 PCI WLAN.HK.2.9.0.1-01977-QCAHKSWPL_SILICONZ-1 Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Signed-off-by: Nicolas Escande Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260731145830.769811-1-nico.escande@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/mac.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c index 2d55cdc4d165de..ae91b57c8422f1 100644 --- a/drivers/net/wireless/ath/ath11k/mac.c +++ b/drivers/net/wireless/ath/ath11k/mac.c @@ -873,6 +873,22 @@ static int ath11k_mac_set_kickout(struct ath11k_vif *arvif) return 0; } +static void ath11k_mac_station_cleanup(struct ieee80211_sta *sta) +{ + struct ath11k_sta *arsta; + + if (!sta) + return; + + arsta = ath11k_sta_to_arsta(sta); + + kfree(arsta->tx_stats); + arsta->tx_stats = NULL; + + kfree(arsta->rx_stats); + arsta->rx_stats = NULL; +} + void ath11k_mac_peer_cleanup_all(struct ath11k *ar) { struct ath11k_peer *peer, *tmp; @@ -885,6 +901,7 @@ void ath11k_mac_peer_cleanup_all(struct ath11k *ar) list_for_each_entry_safe(peer, tmp, &ab->peers, list) { ath11k_peer_rx_tid_cleanup(ar, peer); ath11k_peer_rhash_delete(ab, peer); + ath11k_mac_station_cleanup(peer->sta); list_del(&peer->list); kfree(peer); } @@ -9892,7 +9909,6 @@ static int ath11k_mac_station_remove(struct ath11k *ar, { struct ath11k_base *ab = ar->ab; struct ath11k_vif *arvif = ath11k_vif_to_arvif(vif); - struct ath11k_sta *arsta = ath11k_sta_to_arsta(sta); int ret; if (ab->hw_params.vdev_start_delay && @@ -9916,12 +9932,7 @@ static int ath11k_mac_station_remove(struct ath11k *ar, sta->addr, arvif->vdev_id); ath11k_mac_dec_num_stations(arvif, sta); - - kfree(arsta->tx_stats); - arsta->tx_stats = NULL; - - kfree(arsta->rx_stats); - arsta->rx_stats = NULL; + ath11k_mac_station_cleanup(sta); return ret; } From 3a35e787ac1acf94d33eebb26fd7248811cc66c9 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Mon, 24 Aug 2026 18:19:10 +0200 Subject: [PATCH 0395/1417] btrfs: zoned: handle RAID profiles in btrfs_can_activate_zone() btrfs_can_activate_zone() only accounts for the single and DUP profiles. For a RAID0, RAID1, RAID1C3, RAID1C4 or RAID10 block group the profile switch matches no case, so 'ret' stays false and the function reports that no zone can be activated, even when the devices have plenty of active zones left. As a side effect BTRFS_FS_NEED_ZONE_FINISH gets set and, since btrfs_can_activate_zone() bails out early once that bit is set, data allocations will fail permanently: writers loop on -EAGAIN and hang in btrfs_new_extent_direct() waiting for the bit to clear. Each of these profiles needs one active zone per device, just like single, so handle them the same way. Reviewed-by: Boris Burkov Signed-off-by: Johannes Thumshirn Signed-off-by: David Sterba --- fs/btrfs/zoned.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c index 9cc2c9c1a606b6..08a15465a0877d 100644 --- a/fs/btrfs/zoned.c +++ b/fs/btrfs/zoned.c @@ -2688,6 +2688,11 @@ bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, u64 flags) switch (flags & BTRFS_BLOCK_GROUP_PROFILE_MASK) { case 0: /* single */ + case BTRFS_BLOCK_GROUP_RAID0: + case BTRFS_BLOCK_GROUP_RAID1: + case BTRFS_BLOCK_GROUP_RAID1C3: + case BTRFS_BLOCK_GROUP_RAID1C4: + case BTRFS_BLOCK_GROUP_RAID10: ret = (atomic_read(&zinfo->active_zones_left) >= (1 + reserved)); break; case BTRFS_BLOCK_GROUP_DUP: From 0594e3423f4ba3137c734371169491f9a98e9af4 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Mon, 31 Aug 2026 13:38:01 +0800 Subject: [PATCH 0396/1417] btrfs: take commit root semaphore when iterating in mark_block_group_to_copy() mark_block_group_to_copy() iterates over the commit root with skip_locking=true. A concurrent transaction commit can swap and free the commit root during iteration, causing use-after-free when accessing extent buffers. Fix it by using path->need_commit_sem to protect the commit root search. Fixes: 78ce9fc269af ("btrfs: zoned: mark block groups to copy for device-replace") CC: stable@vger.kernel.org Assisted-by: Codex:gpt-5.5 Reviewed-by: Johannes Thumshirn Signed-off-by: Hongling Zeng Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/dev-replace.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c index bf0b78790171a9..1894c7ebe9d9fb 100644 --- a/fs/btrfs/dev-replace.c +++ b/fs/btrfs/dev-replace.c @@ -493,6 +493,7 @@ static int mark_block_group_to_copy(struct btrfs_fs_info *fs_info, path->reada = READA_FORWARD; path->search_commit_root = true; path->skip_locking = true; + path->need_commit_sem = true; key.objectid = src_dev->devid; key.type = BTRFS_DEV_EXTENT_KEY; From a1167d9420474ab9ed9efca99d86aeb6217c0265 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 10 Sep 2026 17:37:48 +0100 Subject: [PATCH 0397/1417] btrfs: tree-checker: print dev extent offset in error message If a dev extent's offset is not sector size aligned, the error message is printing the dev extent's objectid instead of the offset. This is a copy paste error, as before this check we check the objectid field. Fixes: 008e2512dc56 ("btrfs: tree-checker: add dev extent item checks") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index ab5abbb475e2ca..8b1b706e09960c 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -2129,7 +2129,7 @@ static int check_dev_extent_item(const struct extent_buffer *leaf, sectorsize))) { generic_err(leaf, slot, "invalid dev extent chunk offset, has %llu not aligned to %u", - btrfs_dev_extent_chunk_objectid(leaf, de), + btrfs_dev_extent_chunk_offset(leaf, de), sectorsize); return -EUCLEAN; } From f59d86d25e41c5ac511584d9d6808ee448fd42b0 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 10 Sep 2026 17:48:06 +0100 Subject: [PATCH 0398/1417] btrfs: tree-checker: fix error message regarding free space extent items The error message mentions a free space info item, but we are processing a free space extent item, so fix the message. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index 8b1b706e09960c..4b1e47173c637c 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -2306,7 +2306,7 @@ static int check_free_space_extent(struct extent_buffer *leaf, struct btrfs_key if (unlikely(btrfs_item_size(leaf, slot) != 0)) { generic_err(leaf, slot, - "invalid item size for free space info, has %u expect 0", + "invalid item size for free space extent, has %u expect 0", btrfs_item_size(leaf, slot)); return -EUCLEAN; } From 057dac23d329d5c5ed62352f2659a39fd46c6d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Koutn=C3=BD?= Date: Mon, 14 Sep 2026 14:19:10 +0200 Subject: [PATCH 0399/1417] cgroup: Avoid iteration of dying tasks with zero refcount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit 260fbcb92bbea ("cgroup: Move dying_tasks cleanup from cgroup_task_release() to cgroup_task_free()") extended the lifetime of tasks on the dying_tasks list. The iterators have provision to go through dying_tasks because of dying threadgroup leaders or explicit CSS_TASK_ITER_WITH_DEAD, however, it was expected that such tasks can obtain a new reference (that is possible before cgroup_task_release()/put_task_struct_rcu_user()). The tasks after cgroup_task_release() and before cgroup_task_free() are subject to race when they may or may not have ->usage count > 0. The race window is between css_task_iter_next() invocations when css_set_lock is released and we may arrive at a new ->task_pos. The iterator should not attempt to resurrect tasks whose ->usage count dropped to zero. (When that happens, __put_task_struct_rcu_cb() is already imminent and the returned task_struct would could be used after free.) As for the fix, we cannot simply check the signal->live count of a task on the dying list because that won't distinguish regular zombies waiting to be reaped from RCU remnant tasks that are going to be free'd. Therefore add an extra check to rule out ->usage==0 tasks from any iteration. The repeat: loop in css_task_iter_advance() doesn't consider ->usage count, so add a new loop to css_task_iter_next() to skip de-used tasks on the dying_list. Rough illustration of the possible race R (reader of cgroup.procs) T (thread) L (group leader) --------------------------------- -------------------------------- -------------------------------- L exits, signal->live > 0 cgroup_task_dead(L) css_set_skip_task_iters() // skips only cset->tasks list_add_tail(&L->cg_list, &cset->dying_tasks) css_task_iter_next() take css_set_lock css_task_iter_advance() leader && signal->live != 0 => it->task_pos = &L->cg_list release css_set_lock T exits --signal->live == 0 cgroup_task_dead(T) // css_set_lock release_task(T) cgroup_task_release(T) release_task(L) // zap_leader cgroup_task_release(L) put_task_struct_rcu_user(L) ...RCU... put_task_struct(L) L->usage = 0 /* L still on dying_tasks */ ...RCU... __put_task_struct(L) css_task_iter_next() // another iteration take css_set_lock it->task_pos = &L->cg_list get_task_struct(L) => addition on 0 drop css_set_lock cgroup_task_free(L) css_set_skip_task_iters() // dying skip comes too late free_task(L) cgroup_procs_show() task_pid_vnr(L) Fixes: 260fbcb92bbea ("cgroup: Move dying_tasks cleanup from cgroup_task_release() to cgroup_task_free()") Cc: stable@vger.kernel.org # v6.19+ Link: https://lists.debian.org/debian-kernel/2026/08/msg00220.html Reported-by: Noah Elias Feldt Reported-by: Salvatore Bonaccorso Tested-by: Salvatore Bonaccorso Signed-off-by: Michal Koutný Signed-off-by: Tejun Heo --- kernel/cgroup/cgroup.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index 353c8f83439aa7..a3d363502b7b84 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -5207,10 +5207,13 @@ struct task_struct *css_task_iter_next(struct css_task_iter *it) if (it->flags & CSS_TASK_ITER_SKIPPED) css_task_iter_advance(it); - if (it->task_pos) { + while (it->task_pos && !it->cur_task) { it->cur_task = list_entry(it->task_pos, struct task_struct, cg_list); - get_task_struct(it->cur_task); + /* a task on dying_tasks with zero refcount is only valid for + * RCU readers, not even interesting for + * CSS_TASK_ITER_WITH_DEAD, find another one */ + it->cur_task = tryget_task_struct(it->cur_task); css_task_iter_advance(it); } From 2028280686f4fa78e2f1f6dede4b6c1fd782b9e3 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 3 Sep 2026 15:19:10 +0300 Subject: [PATCH 0400/1417] drm/msm/dsi: round the byte clock rate after reparenting to the PHY PLL DSI 6G v2.9 hosts (SM8650, SM8750, Kaanapali, etc.) reparent the byte and pixel RCGs to the DSI PHY PLL at runtime from dsi_link_clk_set_rate_6g_v2_9(), after the PHY has been enabled. However dsi_calc_clk_rate_6g() runs earlier, in order to compute the bit clock request for the PHY. At that point the byte RCG still has its reset parent (XO), so clk_round_rate() returns a bogus rate, which then ends up in the PHY bit clock request and the PLL gets programmed to a wrong frequency, breaking the panel. Move the rounding to dsi_link_clk_set_rate_6g(), which is called after the RCGs have been reparented to the PLL. Storing the rounded rate at this point still makes later link_clk_set_rate() calls no-ops in the CCF. Derive the byte interface clock rate from the rounded byte clock rate, otherwise it would keep requesting the idealized rate and retrigger the PLL on every transfer. Reported-by: Abel Vesa Reported-by: Krzysztof Kozlowski Fixes: 6cd33b6f4155 ("drm/msm/dsi: round 6G byte clock rate to the PLL-achievable value") Assisted-by: LLM Signed-off-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Tested-by: Konrad Dybcio # SM6115P J606F Tested-by: Abel Vesa Reviewed-by: Abel Vesa Patchwork: https://patchwork.freedesktop.org/patch/750496/ Link: https://lore.kernel.org/r/20260903-fix-eliza-dsi-v1-1-3474a6c9f2e0@oss.qualcomm.com --- drivers/gpu/drm/msm/dsi/dsi_host.c | 36 ++++++++++++++++-------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c index 7e4e3718b536c9..b292dfd266d178 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_host.c +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c @@ -129,7 +129,7 @@ struct msm_dsi_host { struct clk *dsi_pll_pixel_clk; unsigned long byte_clk_rate; - unsigned long byte_intf_clk_rate; + bool byte_intf_clk_div_2; unsigned long pixel_clk_rate; unsigned long esc_clk_rate; @@ -382,8 +382,20 @@ int msm_dsi_runtime_resume(struct device *dev) int dsi_link_clk_set_rate_6g(struct msm_dsi_host *msm_host) { + unsigned long byte_intf_clk_rate; + long rounded_byte_clk_rate; int ret; + rounded_byte_clk_rate = clk_round_rate(msm_host->byte_clk, + msm_host->byte_clk_rate); + if (rounded_byte_clk_rate < 0) { + pr_err("%s: failed to round byte clock rate, %ld\n", + __func__, rounded_byte_clk_rate); + return rounded_byte_clk_rate; + } + + msm_host->byte_clk_rate = rounded_byte_clk_rate; + DBG("Set clk rates: pclk=%lu, byteclk=%lu", msm_host->pixel_clk_rate, msm_host->byte_clk_rate); @@ -401,7 +413,11 @@ int dsi_link_clk_set_rate_6g(struct msm_dsi_host *msm_host) } if (msm_host->byte_intf_clk) { - ret = clk_set_rate(msm_host->byte_intf_clk, msm_host->byte_intf_clk_rate); + byte_intf_clk_rate = msm_host->byte_clk_rate; + if (msm_host->byte_intf_clk_div_2) + byte_intf_clk_rate /= 2; + + ret = clk_set_rate(msm_host->byte_intf_clk, byte_intf_clk_rate); if (ret) { pr_err("%s: Failed to set rate byte intf clk, %d\n", __func__, ret); @@ -669,24 +685,12 @@ static void dsi_calc_pclk(struct msm_dsi_host *msm_host, bool is_bonded_dsi) int dsi_calc_clk_rate_6g(struct msm_dsi_host *msm_host, bool is_bonded_dsi) { - long rounded_byte_clk_rate; - if (!msm_host->mode) { pr_err("%s: mode not set\n", __func__); return -EINVAL; } dsi_calc_pclk(msm_host, is_bonded_dsi); - - rounded_byte_clk_rate = clk_round_rate(msm_host->byte_clk, - msm_host->byte_clk_rate); - if (rounded_byte_clk_rate < 0) { - pr_err("%s: failed to round byte clock rate, %ld\n", - __func__, rounded_byte_clk_rate); - return rounded_byte_clk_rate; - } - - msm_host->byte_clk_rate = rounded_byte_clk_rate; msm_host->esc_clk_rate = clk_get_rate(msm_host->esc_clk); return 0; } @@ -2495,9 +2499,7 @@ int msm_dsi_host_power_on(struct mipi_dsi_host *host, goto unlock_ret; } - msm_host->byte_intf_clk_rate = msm_host->byte_clk_rate; - if (phy_shared_timings->byte_intf_clk_div_2) - msm_host->byte_intf_clk_rate /= 2; + msm_host->byte_intf_clk_div_2 = phy_shared_timings->byte_intf_clk_div_2; msm_dsi_sfpb_config(msm_host, true); From f4fae975db08a9aeec0b15e145c7d4d0fe02a0ec Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sun, 13 Sep 2026 16:58:14 +0800 Subject: [PATCH 0401/1417] drm/msm/hdmi_phy: fix runtime PM cleanup on probe failure msm_hdmi_phy_probe() enables runtime PM before enabling the PHY resources and initializing the PLL, but failures from either operation return without calling the matching pm_runtime_disable(). The remove path disables runtime PM, but it is not called when probe fails. As a result, runtime PM remains enabled after an unsuccessful probe. Route failures after pm_runtime_enable() through a common error path and disable runtime PM before returning. This issue was found by manual code inspection. Fixes: 15b4a4523859 ("drm/msm/hdmi: Create a separate HDMI PHY driver") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Krzysztof Kozlowski Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/753043/ Link: https://lore.kernel.org/r/20260913085814.1509352-1-lgs201920130244@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/hdmi/hdmi_phy.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_phy.c b/drivers/gpu/drm/msm/hdmi/hdmi_phy.c index eb1088755cb3a8..77dce35cd45e03 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_phy.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_phy.c @@ -168,13 +168,13 @@ static int msm_hdmi_phy_probe(struct platform_device *pdev) ret = msm_hdmi_phy_resource_enable(phy); if (ret) - return ret; + goto err_pm_disable; ret = msm_hdmi_phy_pll_init(pdev, phy->cfg->type); if (ret) { DRM_DEV_ERROR(dev, "couldn't init PLL\n"); msm_hdmi_phy_resource_disable(phy); - return ret; + goto err_pm_disable; } msm_hdmi_phy_resource_disable(phy); @@ -182,6 +182,10 @@ static int msm_hdmi_phy_probe(struct platform_device *pdev) platform_set_drvdata(pdev, phy); return 0; + +err_pm_disable: + pm_runtime_disable(dev); + return ret; } static void msm_hdmi_phy_remove(struct platform_device *pdev) From a15fac810c76397ec9f62a6fc26c4d7ab6e238a7 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 13 Sep 2026 14:33:33 +0200 Subject: [PATCH 0402/1417] dt-bindings: display/msm: Use consistent indentation in the example Correct indentation in the examples to consistent 2- or 4-spaces indentation to fix dt-check-style warnings ("example 0 [indent-consistent] indent mismatch ..."). Preferred is 4-spaces, but re-indenting entire example just for that is too much churn. Signed-off-by: Krzysztof Kozlowski Patchwork: https://patchwork.freedesktop.org/patch/753054/ Link: https://lore.kernel.org/r/20260913123331.100293-4-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- .../display/msm/qcom,qcs8300-mdss.yaml | 6 +- .../display/msm/qcom,sar2130p-mdss.yaml | 4 +- .../bindings/display/msm/qcom,sm6150-dpu.yaml | 14 ++-- .../display/msm/qcom,sm8350-mdss.yaml | 2 +- .../display/msm/qcom,x1e80100-mdss.yaml | 68 +++++++++---------- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/Documentation/devicetree/bindings/display/msm/qcom,qcs8300-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,qcs8300-mdss.yaml index c41a86203e78a3..287cce4dbddc4e 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,qcs8300-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,qcs8300-mdss.yaml @@ -150,7 +150,7 @@ examples: reg = <0>; dpu_intf0_out: endpoint { - remote-endpoint = <&mdss_dp0_in>; + remote-endpoint = <&mdss_dp0_in>; }; }; @@ -352,9 +352,9 @@ examples: }; port@1 { - reg = <1>; + reg = <1>; - mdss_dp_out: endpoint { }; + mdss_dp_out: endpoint { }; }; }; diff --git a/Documentation/devicetree/bindings/display/msm/qcom,sar2130p-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,sar2130p-mdss.yaml index 44c1bb9e410941..fb3d24bbac7d42 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,sar2130p-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,sar2130p-mdss.yaml @@ -248,9 +248,9 @@ examples: remote-endpoint = <&usb_dp_qmpphy_dp_in>; }; }; - }; + }; - dp_opp_table: opp-table { + dp_opp_table: opp-table { compatible = "operating-points-v2"; opp-162000000 { diff --git a/Documentation/devicetree/bindings/display/msm/qcom,sm6150-dpu.yaml b/Documentation/devicetree/bindings/display/msm/qcom,sm6150-dpu.yaml index b4f4371722182e..8cbf99f9fcba55 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,sm6150-dpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,sm6150-dpu.yaml @@ -81,7 +81,7 @@ examples: port@1 { reg = <1>; dpu_intf1_out: endpoint { - remote-endpoint = <&mdss_dsi0_in>; + remote-endpoint = <&mdss_dsi0_in>; }; }; }; @@ -90,18 +90,18 @@ examples: compatible = "operating-points-v2"; opp-19200000 { - opp-hz = /bits/ 64 <19200000>; - required-opps = <&rpmhpd_opp_low_svs>; + opp-hz = /bits/ 64 <19200000>; + required-opps = <&rpmhpd_opp_low_svs>; }; opp-25600000 { - opp-hz = /bits/ 64 <25600000>; - required-opps = <&rpmhpd_opp_svs>; + opp-hz = /bits/ 64 <25600000>; + required-opps = <&rpmhpd_opp_svs>; }; opp-307200000 { - opp-hz = /bits/ 64 <307200000>; - required-opps = <&rpmhpd_opp_nom>; + opp-hz = /bits/ 64 <307200000>; + required-opps = <&rpmhpd_opp_nom>; }; }; }; diff --git a/Documentation/devicetree/bindings/display/msm/qcom,sm8350-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,sm8350-mdss.yaml index 68176de854b36b..f5acf528d0e416 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,sm8350-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,sm8350-mdss.yaml @@ -223,7 +223,7 @@ examples: phys = <&mdss_dsi0_phy>; ports { - #address-cells = <1>; + #address-cells = <1>; #size-cells = <0>; port@0 { diff --git a/Documentation/devicetree/bindings/display/msm/qcom,x1e80100-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,x1e80100-mdss.yaml index 8d698a2e055a88..8c65a80b46e4fb 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,x1e80100-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,x1e80100-mdss.yaml @@ -208,47 +208,47 @@ examples: #sound-dai-cells = <0>; ports { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - port@0 { - reg = <0>; + port@0 { + reg = <0>; - mdss_dp0_in: endpoint { - remote-endpoint = <&mdss_intf0_out>; - }; - }; + mdss_dp0_in: endpoint { + remote-endpoint = <&mdss_intf0_out>; + }; + }; - port@1 { - reg = <1>; + port@1 { + reg = <1>; - mdss_dp0_out: endpoint { - }; - }; + mdss_dp0_out: endpoint { + }; + }; }; mdss_dp0_opp_table: opp-table { - compatible = "operating-points-v2"; - - opp-160000000 { - opp-hz = /bits/ 64 <160000000>; - required-opps = <&rpmhpd_opp_low_svs>; - }; - - opp-270000000 { - opp-hz = /bits/ 64 <270000000>; - required-opps = <&rpmhpd_opp_svs>; - }; - - opp-540000000 { - opp-hz = /bits/ 64 <540000000>; - required-opps = <&rpmhpd_opp_svs_l1>; - }; - - opp-810000000 { - opp-hz = /bits/ 64 <810000000>; - required-opps = <&rpmhpd_opp_nom>; - }; + compatible = "operating-points-v2"; + + opp-160000000 { + opp-hz = /bits/ 64 <160000000>; + required-opps = <&rpmhpd_opp_low_svs>; + }; + + opp-270000000 { + opp-hz = /bits/ 64 <270000000>; + required-opps = <&rpmhpd_opp_svs>; + }; + + opp-540000000 { + opp-hz = /bits/ 64 <540000000>; + required-opps = <&rpmhpd_opp_svs_l1>; + }; + + opp-810000000 { + opp-hz = /bits/ 64 <810000000>; + required-opps = <&rpmhpd_opp_nom>; + }; }; }; }; From e1aeaf79dea51e6065da56924bc07e22d59012ac Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 13 Sep 2026 21:09:15 -0300 Subject: [PATCH 0403/1417] smb: client: fix unaligned access in WSL reparse point parser When wsl_to_fattr() parses WSL extended attributes, it computes a payload pointer from ea->ea_data + ea_name_length + 1. Since the smb2_file_full_ea_info struct is __packed and all WSL xattr names are 6 bytes long, the value pointer always lands at an odd byte offset, never satisfying __le32 or __le64 alignment requirements. The code then casts this pointer to __le32 * or __le64 * and dereferences it directly, which may cause alignment faults on some architectures. Replace all such casts with get_unaligned_le32() and get_unaligned_le64() in reparse_mkdev(), wsl_make_kuid(), wsl_make_kgid() and wsl_to_fattr(). Closes: https://sashiko.dev/#/patchset/20260906200517.725015-1-pc%40manguebit.org Fixes: 78e26bec4d6d ("smb: client: parse uid, gid, mode and dev from WSL reparse points") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: David Howells Cc: Tom Talpey Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 4 ++-- fs/smb/client/reparse.h | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 9e31fce7e0a528..6ac69f4d391a60 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1201,9 +1201,9 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, fattr->cf_gid = wsl_make_kgid(cifs_sb, v); } else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) { /* File type in reparse point tag and in xattr mode must match. */ - if (S_DT(fattr->cf_mode) != S_DT(le32_to_cpu(*(__le32 *)v))) + if (S_DT(fattr->cf_mode) != S_DT(get_unaligned_le32(v))) return false; - fattr->cf_mode = (umode_t)le32_to_cpu(*(__le32 *)v); + fattr->cf_mode = (umode_t)get_unaligned_le32(v); } else if (!strncmp(name, SMB2_WSL_XATTR_DEV, nlen)) { fattr->cf_rdev = reparse_mkdev(v); have_xattr_dev = true; diff --git a/fs/smb/client/reparse.h b/fs/smb/client/reparse.h index 49efd85b1e949e..05b2cecb449547 100644 --- a/fs/smb/client/reparse.h +++ b/fs/smb/client/reparse.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "fs_context.h" #include "cifsglob.h" #include "../common/smbfsctl.h" @@ -23,7 +24,7 @@ static inline dev_t reparse_mkdev(void *ptr) { - u64 v = le64_to_cpu(*(__le64 *)ptr); + u64 v = get_unaligned_le64(ptr); return MKDEV(v & 0xffffffff, v >> 32); } @@ -31,7 +32,7 @@ static inline dev_t reparse_mkdev(void *ptr) static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb, void *ptr) { - u32 uid = le32_to_cpu(*(__le32 *)ptr); + u32 uid = get_unaligned_le32(ptr); if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_OVERR_UID) return cifs_sb->ctx->linux_uid; @@ -41,7 +42,7 @@ static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb, static inline kgid_t wsl_make_kgid(struct cifs_sb_info *cifs_sb, void *ptr) { - u32 gid = le32_to_cpu(*(__le32 *)ptr); + u32 gid = get_unaligned_le32(ptr); if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_OVERR_GID) return cifs_sb->ctx->linux_gid; From e1253a82bb4c0fed6706a5839fc8b6e01be1abe2 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 13 Sep 2026 21:15:41 -0300 Subject: [PATCH 0404/1417] smb: client: fix fattr leaking on wsl_to_fattr() failure wsl_to_fattr() mutates fattr fields as it parses each WSL EA. If validation later fails, the function returns false with partially mutated fattr fields that callers do not reset. Fix this by parsing into local variables and only committing them to fattr on success. Closes: https://sashiko.dev/#/patchset/20260906200517.725015-1-pc%40manguebit.org Fixes: 78e26bec4d6d ("smb: client: parse uid, gid, mode and dev from WSL reparse points") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: David Howells Cc: Tom Talpey Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 6ac69f4d391a60..3a27773186ae43 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1149,29 +1149,30 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, u32 tag, struct cifs_fattr *fattr) { unsigned int sbflags = cifs_sb_flags(cifs_sb); + kuid_t uid = cifs_sb->ctx->linux_uid; + kgid_t gid = cifs_sb->ctx->linux_gid; struct smb2_file_full_ea_info *ea; bool have_xattr_dev = false; + dev_t rdev = 0; + umode_t mode; u32 next = 0; - fattr->cf_uid = cifs_sb->ctx->linux_uid; - fattr->cf_gid = cifs_sb->ctx->linux_gid; - - fattr->cf_mode &= ~S_IFMT; + mode = fattr->cf_mode & ~S_IFMT; switch (tag) { case IO_REPARSE_TAG_LX_SYMLINK: - fattr->cf_mode |= S_IFLNK; + mode |= S_IFLNK; break; case IO_REPARSE_TAG_LX_FIFO: - fattr->cf_mode |= S_IFIFO; + mode |= S_IFIFO; break; case IO_REPARSE_TAG_AF_UNIX: - fattr->cf_mode |= S_IFSOCK; + mode |= S_IFSOCK; break; case IO_REPARSE_TAG_LX_CHR: - fattr->cf_mode |= S_IFCHR; + mode |= S_IFCHR; break; case IO_REPARSE_TAG_LX_BLK: - fattr->cf_mode |= S_IFBLK; + mode |= S_IFBLK; break; } @@ -1195,26 +1196,29 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen)) { if (!(sbflags & CIFS_MOUNT_OVERR_UID)) - fattr->cf_uid = wsl_make_kuid(cifs_sb, v); + uid = wsl_make_kuid(cifs_sb, v); } else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen)) { if (!(sbflags & CIFS_MOUNT_OVERR_GID)) - fattr->cf_gid = wsl_make_kgid(cifs_sb, v); + gid = wsl_make_kgid(cifs_sb, v); } else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) { /* File type in reparse point tag and in xattr mode must match. */ - if (S_DT(fattr->cf_mode) != S_DT(get_unaligned_le32(v))) + if (S_DT(mode) != S_DT(get_unaligned_le32(v))) return false; - fattr->cf_mode = (umode_t)get_unaligned_le32(v); + mode = get_unaligned_le32(v); } else if (!strncmp(name, SMB2_WSL_XATTR_DEV, nlen)) { - fattr->cf_rdev = reparse_mkdev(v); + rdev = reparse_mkdev(v); have_xattr_dev = true; } } while (next); out: - /* Major and minor numbers for char and block devices are mandatory. */ if (!have_xattr_dev && (tag == IO_REPARSE_TAG_LX_CHR || tag == IO_REPARSE_TAG_LX_BLK)) return false; + fattr->cf_uid = uid; + fattr->cf_gid = gid; + fattr->cf_mode = mode; + fattr->cf_rdev = rdev; return true; } From f97d8c7bab7843631206a114986c9059da03efeb Mon Sep 17 00:00:00 2001 From: Aohan Mei Date: Fri, 11 Sep 2026 15:34:32 +0800 Subject: [PATCH 0405/1417] rds: ib: use rds_conn_drop() on protocol version mismatch rds_ib_cm_connect_complete() runs from the RDMA-CM event handler with conn->c_cm_lock held. When the peer negotiates a protocol version older than RDS_PROTOCOL_COMPAT_VERSION, the handler calls rds_conn_destroy(), which is only safe in the rmmod path: it synchronously tears the connection down and flush_work()es the shutdown work cp_down_w. That shutdown work (rds_conn_shutdown()) needs cp_cm_lock, which is the very lock the event handler still holds, so the flush never completes: the two workers wait on each other and the RDS connection workqueues stall for good. All other RDMA-CM failure paths (REJECTED, CONNECT_ERROR, DISCONNECTED) use rds_conn_drop(), which marks the connection RDS_CONN_ERROR and schedules the shutdown work asynchronously. Use it here as well. Fixes: f147dd9ecabf ("RDS/IB: Disallow connections less than RDS 3.1") Reported-by: TencentOS Corvus AI Cc: stable@vger.kernel.org Reviewed-by: Allison Henderson Signed-off-by: Aohan Mei Link: https://patch.msgid.link/20260911073436.3542080-1-ljp1205831794@gmail.com Signed-off-by: Jakub Kicinski --- net/rds/ib_cm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/rds/ib_cm.c b/net/rds/ib_cm.c index 4feb0edc360c8e..6e3110a04ae6bd 100644 --- a/net/rds/ib_cm.c +++ b/net/rds/ib_cm.c @@ -115,7 +115,7 @@ void rds_ib_cm_connect_complete(struct rds_connection *conn, struct rdma_cm_even &conn->c_laddr, &conn->c_faddr, RDS_PROTOCOL_MAJOR(conn->c_version), RDS_PROTOCOL_MINOR(conn->c_version)); - rds_conn_destroy(conn); + rds_conn_drop(conn); return; } } From 6fb0a9d9071f1ff0cc5cfc0782302d9c90d642cb Mon Sep 17 00:00:00 2001 From: Chunfeng Song Date: Thu, 10 Sep 2026 05:51:10 +0000 Subject: [PATCH 0406/1417] rust: net: phy: fix off-by-one bit positions in device status accessors The hand-written bitfield offsets in is_link_up(), is_autoneg_enabled() and is_autoneg_completed() were correct when the abstraction was merged: at that time autoneg, link, and autoneg_complete were at bits 13, 14, and 15 of struct phy_device's first bitfield unit. Commit 2796ff1e3dca ("net: phy: add flag is_genphy_driven to struct phy_device") later inserted is_genphy_driven just before autoneg, shifting the three fields up by one, so the accessors now read: is_link_up() reads bit 14 = autoneg is_autoneg_enabled() reads bit 13 = is_genphy_driven is_autoneg_completed() reads bit 15 = link The official ax88796b Rust driver uses all three accessors in its read_status() implementation, so it inherits the bug. phy_attach_direct() sets is_genphy_driven only when it falls back to the generic driver, and ax88796b has a real driver, so is_genphy_driven stays 0. The broken is_autoneg_enabled() therefore reads bit 13 as 0, compares it against AUTONEG_ENABLE (1), and always returns false, so read_status() never reaches the resolve_aneg_linkmode() call. The ordinary bindgen accessors take &self. Calling them through (*phydev).link() would create a shared reference to the complete bindings::phy_device, which is not appropriate for an object wrapped in Opaque. Use the bindgen-generated raw accessors (link_raw(), autoneg_raw(), and autoneg_complete_raw()) instead. They retain the bit positions and endianness handling generated from the C layout without creating a Rust reference to the complete phy_device. Drop the hand-written numbers together with the TODO comment that marked them as a stopgap. The raw accessors are only emitted by bindgen 0.71 and later, and were added at the Rust-for-Linux project's request, so this fix can only be backported to stable branches whose minimum bindgen version is at least that, hence the scope on the Cc: stable line below. Found by a static equivalence audit (C2RustDrv, a C-to-Rust driver migration tool) that compares hand-written bitfield offsets against the bindgen layout of struct phy_device. Verified by building the bindings and checking the generated accessors; no runtime testing was possible without PHY hardware. Fixes: 2796ff1e3dca ("net: phy: add flag is_genphy_driven to struct phy_device") Cc: stable@vger.kernel.org # Only 7.1.y and later (requires bindgen's raw pointer accessors). Link: https://github.com/rust-lang/rust-bindgen/issues/2674 Signed-off-by: Chunfeng Song Reviewed-by: FUJITA Tomonori Link: https://patch.msgid.link/20260910055110.167110-1-springbreeze@stu.pku.edu.cn Signed-off-by: Jakub Kicinski --- rust/kernel/net/phy.rs | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs index 956cda573ddba3..c4e7b1d6c6f4aa 100644 --- a/rust/kernel/net/phy.rs +++ b/rust/kernel/net/phy.rs @@ -123,39 +123,37 @@ impl Device { /// Gets the current link state. /// /// It returns true if the link is up. + #[inline] pub fn is_link_up(&self) -> bool { - const LINK_IS_UP: u64 = 1; - // TODO: the code to access to the bit field will be replaced with automatically - // generated code by bindgen when it becomes possible. - // SAFETY: The struct invariant ensures that we may access - // this field without additional synchronization. - let bit_field = unsafe { &(*self.0.get())._bitfield_1 }; - bit_field.get(14, 1) == LINK_IS_UP + let phydev = self.0.get().cast_const(); + // SAFETY: By the type invariant of `Device`, `phydev` points to a valid + // `struct phy_device`, and there is no concurrent write to this field. + let link = unsafe { bindings::phy_device::link_raw(phydev) }; + link == 1 } /// Gets the current auto-negotiation configuration. /// /// It returns true if auto-negotiation is enabled. + #[inline] pub fn is_autoneg_enabled(&self) -> bool { - // TODO: the code to access to the bit field will be replaced with automatically - // generated code by bindgen when it becomes possible. - // SAFETY: The struct invariant ensures that we may access - // this field without additional synchronization. - let bit_field = unsafe { &(*self.0.get())._bitfield_1 }; - bit_field.get(13, 1) == u64::from(bindings::AUTONEG_ENABLE) + let phydev = self.0.get().cast_const(); + // SAFETY: By the type invariant of `Device`, `phydev` points to a valid + // `struct phy_device`, and there is no concurrent write to this field. + let autoneg = unsafe { bindings::phy_device::autoneg_raw(phydev) }; + autoneg == bindings::AUTONEG_ENABLE } /// Gets the current auto-negotiation state. /// /// It returns true if auto-negotiation is completed. + #[inline] pub fn is_autoneg_completed(&self) -> bool { - const AUTONEG_COMPLETED: u64 = 1; - // TODO: the code to access to the bit field will be replaced with automatically - // generated code by bindgen when it becomes possible. - // SAFETY: The struct invariant ensures that we may access - // this field without additional synchronization. - let bit_field = unsafe { &(*self.0.get())._bitfield_1 }; - bit_field.get(15, 1) == AUTONEG_COMPLETED + let phydev = self.0.get().cast_const(); + // SAFETY: By the type invariant of `Device`, `phydev` points to a valid + // `struct phy_device`, and there is no concurrent write to this field. + let completed = unsafe { bindings::phy_device::autoneg_complete_raw(phydev) }; + completed == 1 } /// Sets the speed of the PHY. From bde5212360bd44506edec073ebbd6d0c72f75820 Mon Sep 17 00:00:00 2001 From: Ahmed Naseef Date: Sat, 12 Sep 2026 17:43:06 +0400 Subject: [PATCH 0407/1417] net: phy: mediatek: do not report link and per-speed LED rules together mtk_phy_led_hw_ctrl_get() reports TRIGGER_NETDEV_LINK whenever any of the speed bits in on_set is on, and in addition reports every individual TRIGGER_NETDEV_LINK_* bit that is set. The netdev trigger refuses that combination: netdev_led_attr_store() rejects TRIGGER_NETDEV_LINK together with any per-speed rule, and it validates the whole resulting mode rather than just the bit being written. Once the hardware has any link bit programmed, every write to the trigger attributes of that LED therefore fails with -EINVAL and the LED can no longer be configured. The rules are also fed back into the hardware: the trigger stores what is read back, and a later write of device_name programs it again, expanding TRIGGER_NETDEV_LINK to every speed in on_set. An LED configured for a single speed is thereby silently widened to "on at any link speed". Both are easy to see on the EcoNet EN7528, whose four PHYs share one LED block. The first LED programs the block correctly, the second reads those rules back and rewrites them widened, and the remaining two then read the widened value, so an LED configured for "link_10 link_100" ends up lit on a 1000 Mbps link. on_set holds every speed the LED can indicate and is exactly what mtk_phy_led_hw_ctrl_set() programs for TRIGGER_NETDEV_LINK, so report the speed independent rule only when all of them are on, and the individual speeds otherwise. The mapping is then the inverse of the one used when programming the LED and round trips without changing the register. Fixes: c66937b0f8db ("net: phy: mediatek-ge-soc: support PHY LEDs") Cc: stable@vger.kernel.org Signed-off-by: Ahmed Naseef Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260912134306.3544329-1-naseefkm@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/mediatek/mtk-phy-lib.c | 27 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/drivers/net/phy/mediatek/mtk-phy-lib.c b/drivers/net/phy/mediatek/mtk-phy-lib.c index dfd0f4e439a214..608072fbfde97e 100644 --- a/drivers/net/phy/mediatek/mtk-phy-lib.c +++ b/drivers/net/phy/mediatek/mtk-phy-lib.c @@ -156,20 +156,27 @@ int mtk_phy_led_hw_ctrl_get(struct phy_device *phydev, u8 index, if (!rules) return 0; - if (on & on_set) + /* TRIGGER_NETDEV_LINK must not be reported together with any of the + * per-speed rules, the netdev trigger rejects that combination. + * on_set holds every speed this LED can indicate and is what + * mtk_phy_led_hw_ctrl_set() programs for TRIGGER_NETDEV_LINK, so + * report the speed independent rule only when they are all on. + */ + if ((on & on_set) == on_set) { *rules |= BIT(TRIGGER_NETDEV_LINK); + } else { + if (on & MTK_PHY_LED_ON_LINK10) + *rules |= BIT(TRIGGER_NETDEV_LINK_10); - if (on & MTK_PHY_LED_ON_LINK10) - *rules |= BIT(TRIGGER_NETDEV_LINK_10); + if (on & MTK_PHY_LED_ON_LINK100) + *rules |= BIT(TRIGGER_NETDEV_LINK_100); - if (on & MTK_PHY_LED_ON_LINK100) - *rules |= BIT(TRIGGER_NETDEV_LINK_100); + if (on & MTK_PHY_LED_ON_LINK1000) + *rules |= BIT(TRIGGER_NETDEV_LINK_1000); - if (on & MTK_PHY_LED_ON_LINK1000) - *rules |= BIT(TRIGGER_NETDEV_LINK_1000); - - if (on & MTK_PHY_LED_ON_LINK2500) - *rules |= BIT(TRIGGER_NETDEV_LINK_2500); + if (on & MTK_PHY_LED_ON_LINK2500) + *rules |= BIT(TRIGGER_NETDEV_LINK_2500); + } if (on & MTK_PHY_LED_ON_FDX) *rules |= BIT(TRIGGER_NETDEV_FULL_DUPLEX); From 7616242a2b37883f7322aaa1d2bd6cd0fed28315 Mon Sep 17 00:00:00 2001 From: Andrea Mayer Date: Sun, 13 Sep 2026 21:44:21 +0200 Subject: [PATCH 0408/1417] seg6: set IPSKB_L3SLAVE from IP6SKB_L3SLAVE on IPIP decapsulation When an SRv6 packet arrives on an interface enslaved to a VRF, vrf_ip6_rcv() sets IP6SKB_L3SLAVE in IP6CB, but decap_and_validate() has never set IPSKB_L3SLAVE in IPCB. The bit stayed clear in the common case, and with CONFIG_IPV6_MIP6 the leftover frag_max_size of a reassembled outer packet could even set it, with no VRF involved. Commit 44930446dde4 ("ipv6: seg6: clear IPv4 control block on IPIP decapsulation") then made the unreliable bit reliably clear. The effect of the missing flag is visible with End.DX4 when a delivery to a local address of the node reaches the socket lookup. For example, a UDP socket bound to the enslaved ingress interface does not receive any of the decapsulated packets, while an unbound socket outside the VRF does. This contradicts Documentation/networking/vrf.rst: by default the scope of an unbound UDP or TCP socket is limited to the default VRF. Set IPSKB_L3SLAVE for IPv4 in decap_and_validate(), which already does the same for IPv6. The socket lookup then matches the decapsulated packet like any other packet received on that enslaved interface. Such a packet matches an unbound UDP or TCP socket only when udp_l3mdev_accept or tcp_l3mdev_accept is set. Fixes: 891ef8dd2a8d ("ipv6: sr: implement additional seg6local actions") Signed-off-by: Andrea Mayer Reviewed-by: David Ahern Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260913194421.31-1-andrea.mayer@uniroma2.it Signed-off-by: Jakub Kicinski --- net/ipv6/seg6_local.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c index 7b52122201858d..d1070aec7b72b9 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -257,10 +257,13 @@ static bool decap_and_validate(struct sk_buff *skb, int proto) return false; if (proto == IPPROTO_IPIP) { + bool l3slave = ipv6_l3mdev_skb(IP6CB(skb)->flags); int iif = IP6CB(skb)->iif; memset(IPCB(skb), 0, sizeof(*IPCB(skb))); IPCB(skb)->iif = iif; + if (l3slave) + IPCB(skb)->flags |= IPSKB_L3SLAVE; } else if (proto == IPPROTO_IPV6) { bool l3slave = ipv6_l3mdev_skb(IP6CB(skb)->flags); int iif = IP6CB(skb)->iif; From 9e92ad4630f5dd1838ce6bbe6b1bd2c73d34de36 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Thu, 10 Sep 2026 14:13:15 +0100 Subject: [PATCH 0409/1417] net: dsa: mxl862xx: disable the stats poll on teardown mxl862xx_setup() arms the stats poll before mxl862xx_setup_mdio(), and nothing stops it until dsa_register_switch() has returned an error to mxl862xx_probe(). DSA frees the dsa_port list before it returns, so a poll that fires once .setup or a later step of dsa_tree_setup() has failed walks freed ports. On shutdown the user ports stay registered, and the WORK_STOPPED flag test in mxl862xx_get_stats64() is not atomic with the cancel in mxl862xx_shutdown(), so a re-arm that read the flag before it was set queues the poll after cancel_delayed_work_sync() has returned. Arm the poll once .setup has succeeded and stop it from a .teardown op, which DSA calls on unregister and after a failed registration, in both cases before it frees the ports. Use disable_delayed_work_sync() there and in shutdown(): it drains a running poll as the cancel did and turns every later attempt to queue the work into a no-op, so the re-arm cannot bring the poll back. remove() and the probe error path only set WORK_STOPPED, which crc_err_work tests before it walks the ports. Fixes: a21d33a5265f ("net: dsa: mxl862xx: implement .get_stats64") Signed-off-by: Daniel Golle Link: https://patch.msgid.link/1eb6f7fc1789b67e4b11e3f4d5ff080d0b6f7cbb.1789045590.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mxl862xx/mxl862xx.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c index cfa7e3e269a28e..e05ad52cd297e2 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c @@ -685,10 +685,22 @@ static int mxl862xx_setup(struct dsa_switch *ds) if (ret) return ret; + ret = mxl862xx_setup_mdio(ds); + if (ret) + return ret; + schedule_delayed_work(&priv->stats_work, MXL862XX_STATS_POLL_INTERVAL); - return mxl862xx_setup_mdio(ds); + return 0; +} + +static void mxl862xx_teardown(struct dsa_switch *ds) +{ + struct mxl862xx_priv *priv = ds->priv; + + set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); + disable_delayed_work_sync(&priv->stats_work); } static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable) @@ -2047,9 +2059,7 @@ static void mxl862xx_get_stats64(struct dsa_switch *ds, int port, spin_unlock_bh(&priv->ports[port].stats_lock); - /* Trigger a fresh poll so the next read sees up-to-date counters. - * No-op if the work is already pending, running, or teardown started. - */ + /* Trigger a fresh poll so the next read sees up-to-date counters. */ if (!test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags)) schedule_delayed_work(&priv->stats_work, 0); } @@ -2057,6 +2067,7 @@ static void mxl862xx_get_stats64(struct dsa_switch *ds, int port, static const struct dsa_switch_ops mxl862xx_switch_ops = { .get_tag_protocol = mxl862xx_get_tag_protocol, .setup = mxl862xx_setup, + .teardown = mxl862xx_teardown, .port_setup = mxl862xx_port_setup, .port_teardown = mxl862xx_port_teardown, .phylink_get_caps = mxl862xx_phylink_get_caps, @@ -2131,7 +2142,6 @@ static int mxl862xx_probe(struct mdio_device *mdiodev) err = dsa_register_switch(ds); if (err) { set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); - cancel_delayed_work_sync(&priv->stats_work); mxl862xx_host_shutdown(priv); for (i = 0; i < MXL862XX_MAX_PORTS; i++) cancel_work_sync(&priv->ports[i].host_flood_work); @@ -2152,7 +2162,6 @@ static void mxl862xx_remove(struct mdio_device *mdiodev) priv = ds->priv; set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); - cancel_delayed_work_sync(&priv->stats_work); dsa_unregister_switch(ds); @@ -2181,7 +2190,7 @@ static void mxl862xx_shutdown(struct mdio_device *mdiodev) dsa_switch_shutdown(ds); set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); - cancel_delayed_work_sync(&priv->stats_work); + disable_delayed_work_sync(&priv->stats_work); mxl862xx_host_shutdown(priv); From 23ca4ddc4fce2c233a49e9fd34d4b5b02bd7324e Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Sun, 13 Sep 2026 21:00:52 +0200 Subject: [PATCH 0410/1417] net: bcmgenet: restore the hardware filters on open bcmgenet_hfb_init() runs INIT_LIST_HEAD() on priv->rxnfc_list, which drops every rule off the list, and bcmgenet_open() calls it on each ifup. Every rule the user configured is silently lost: # ethtool -N eth0 flow-type ether dst $MAC action 0 Added rule with ID 0 # ethtool -n eth0 | grep -c Filter: 1 # ip link set eth0 down && ip link set eth0 up # ethtool -n eth0 | grep -c Filter: 0 Initialise the lists once at probe and restore the rules on open, as bcmgenet_resume() already does. Fixes: 3e370952287c ("net: bcmgenet: add support for ethtool rxnfc flows") Signed-off-by: Nicolai Buchwitz Reviewed-by: Justin Chen Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260913190052.939955-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- .../net/ethernet/broadcom/genet/bcmgenet.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index a2305e6428d1f5..b916080f4ff176 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -749,8 +749,17 @@ static void bcmgenet_hfb_init(struct bcmgenet_priv *priv) INIT_LIST_HEAD(&priv->rxnfc_rules[i].list); priv->rxnfc_rules[i].state = BCMGENET_RXNFC_STATE_UNUSED; } +} + +static void bcmgenet_hfb_restore(struct bcmgenet_priv *priv) +{ + struct bcmgenet_rxnfc_rule *rule; bcmgenet_hfb_clear(priv); + + list_for_each_entry(rule, &priv->rxnfc_list, list) + if (rule->state != BCMGENET_RXNFC_STATE_UNUSED) + bcmgenet_hfb_create_rxnfc_filter(priv, rule); } static int bcmgenet_begin(struct net_device *dev) @@ -3376,8 +3385,8 @@ static int bcmgenet_open(struct net_device *dev) bcmgenet_set_hw_addr(priv, dev->dev_addr); - /* HFB init */ - bcmgenet_hfb_init(priv); + /* Restore the filters, the MAC was reset above */ + bcmgenet_hfb_restore(priv); /* Reinitialize TDMA and RDMA and SW housekeeping */ ret = bcmgenet_init_dma(priv, true); @@ -4075,6 +4084,7 @@ static int bcmgenet_probe(struct platform_device *pdev) /* Mii wait queue */ init_waitqueue_head(&priv->wq); + bcmgenet_hfb_init(priv); INIT_WORK(&priv->bcmgenet_irq_work, bcmgenet_irq_task); priv->clk_wol = devm_clk_get_optional(&priv->pdev->dev, "enet-wol"); @@ -4272,10 +4282,7 @@ static int bcmgenet_resume(struct device *d) bcmgenet_set_hw_addr(priv, dev->dev_addr); /* Restore hardware filters */ - bcmgenet_hfb_clear(priv); - list_for_each_entry(rule, &priv->rxnfc_list, list) - if (rule->state != BCMGENET_RXNFC_STATE_UNUSED) - bcmgenet_hfb_create_rxnfc_filter(priv, rule); + bcmgenet_hfb_restore(priv); /* Reinitialize TDMA and RDMA and SW housekeeping */ ret = bcmgenet_init_dma(priv, false); From 82431877d837a6c2593efd8e66ba28b35a220ca4 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 5 Sep 2026 23:36:30 +0000 Subject: [PATCH 0411/1417] sysctl: Check range in proc_dointvec_ms_jiffies_minmax Add the range check to do_proc_int_conv_ms_jiffies_minmax that commit d174174c6776 ("sysctl: replace SYSCTL_INT_CONV_CUSTOM macro with functions") incorrectly removed. Fixes: d174174c6776 ("sysctl: replace SYSCTL_INT_CONV_CUSTOM macro with functions") Signed-off-by: Kuniyuki Iwashima Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 213ae1d6a01479..70926b73905a79 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -181,7 +181,7 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, int *k_ptr, int dir, const struct ctl_table *tbl) { - return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, false, + return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, true, sysctl_u2k_int_conv_ms, sysctl_k2u_int_conv_ms); } From 318012c56576e09806747f64f89f8f3cde1f999f Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 5 Sep 2026 23:36:31 +0000 Subject: [PATCH 0412/1417] sysctl: Check range in do_proc_ulong_conv_ms_jiffies Add the range check back to do_proc_ulong_conv_ms_jiffies that commit b96b5c6708ea ("sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec") incorrectly removed. Append "_minmax" to the end of do_proc_ulong_conv_ms_jiffies so it is clear that there should be a range check. Fixes: b96b5c6708ea ("sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec") Signed-off-by: Kuniyuki Iwashima Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 70926b73905a79..41f88ebac74a1c 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -195,10 +195,10 @@ static int sysctl_k2u_ulong_conv_ms(ulong *u_ptr, const ulong *k_ptr) return proc_ulong_k2u_conv_kop(u_ptr, k_ptr, sysctl_jiffies_to_msecs); } -static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, - int dir, const struct ctl_table *tbl) +static int do_proc_ulong_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *tbl) { - return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, false, + return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, true, sysctl_u2k_ulong_conv_ms, sysctl_k2u_ulong_conv_ms); } @@ -229,8 +229,8 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, return -ENOSYS; } -static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, - int dir, const struct ctl_table *tbl) +static int do_proc_ulong_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *tbl) { return -ENOSYS; } @@ -333,7 +333,7 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return proc_doulongvec_conv(table, dir, buffer, lenp, ppos, - do_proc_ulong_conv_ms_jiffies); + do_proc_ulong_conv_ms_jiffies_minmax); } EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax); From afdf35cfae0d039a4a6c907fa5d8391f1ef0a0aa Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 10 Sep 2026 12:22:16 +0200 Subject: [PATCH 0413/1417] sysctl: Fix type truncation in sysctl_msec_to_jiffies Return MAX_JIFFY_OFFSET for all the values truncated when val (u64) is passed to msecs_to_jiffies (u32). This aligns with how very large millisecond values get translated into MAX_JIFFY_OFFSET. Fixes: b96b5c6708ea ("sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec") Suggested-by: Kuniyuki Iwashima Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 41f88ebac74a1c..80c35481153841 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -136,6 +136,8 @@ static int sysctl_k2u_int_conv_userhz(bool *negp, ulong *u_ptr, const int *k_ptr static ulong sysctl_msecs_to_jiffies(const ulong val) { + if (val > jiffies_to_msecs(MAX_JIFFY_OFFSET)) + return MAX_JIFFY_OFFSET; return msecs_to_jiffies(val); } From 3ed11c671ff7ec58c8fd96410233c677df23f407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Fri, 4 Sep 2026 10:26:13 +0200 Subject: [PATCH 0414/1417] dma-buf/dma-fence: fix checking signaling bit for timeline and driver name v3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The patch "dma-buf: dma-fence: Fix potential NULL pointer dereference" changed the check to test for the ops pointer instead of the signaled bit to avoid a potential NULL dereference when the ops pointer has been cleared. The problem is now that the ops pointer is cleared only when neither the release nor the wait callback is implemented and this isn't true for a lot of dma_fence implementations yet. So those implementations lost the RCU protection after signaling of the returned string resulting in potential use after free. Add the signaling check additional to the ops pointer check so that we have both the protection against NULL dereference as well as the RCU protection after signaling for the returned string. v2: improve comments to note RCU protection and explain why we check both signaling state and ops pointer v3: some comment improvements suggested by Philip Signed-off-by: Christian König Fixes: 035219a760ed ("dma-buf: dma-fence: Fix potential NULL pointer dereference") CC: stable@vger.kernel.org # 7.2+ Reported-by: Jonghyuk Kim(MalHyuk) Tested-by: Jonghyuk Kim(MalHyuk) Reviewed-by: Philipp Stanner Link: https://lore.kernel.org/r/20260914182740.1587-1-christian.koenig@amd.com --- drivers/dma-buf/dma-fence.c | 14 ++++++++++++-- include/linux/dma-fence.h | 6 ++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c index 05090fb0fd5ae5..bd58688b81a72c 100644 --- a/drivers/dma-buf/dma-fence.c +++ b/drivers/dma-buf/dma-fence.c @@ -1170,7 +1170,12 @@ const char __rcu *dma_fence_driver_name(struct dma_fence *fence) /* RCU protection is required for safe access to returned string */ ops = rcu_dereference(fence->ops); - if (ops) + + /* + * Make load ordering irrelevant by checking both signaled state and ops + * pointer and ops pointer is only set to NULL on newer implementations. + */ + if (!dma_fence_test_signaled_flag(fence) && ops) return (const char __rcu *)ops->get_driver_name(fence); else return (const char __rcu *)"detached-driver"; @@ -1203,7 +1208,12 @@ const char __rcu *dma_fence_timeline_name(struct dma_fence *fence) /* RCU protection is required for safe access to returned string */ ops = rcu_dereference(fence->ops); - if (ops) + + /* + * Make load ordering irrelevant by checking both signaled state and ops + * pointer and ops pointer is only set to NULL on newer implementations. + */ + if (!dma_fence_test_signaled_flag(fence) && ops) return (const char __rcu *)ops->get_timeline_name(fence); else return (const char __rcu *)"signaled-timeline"; diff --git a/include/linux/dma-fence.h b/include/linux/dma-fence.h index 158cd609f1036e..ffa99b930843e2 100644 --- a/include/linux/dma-fence.h +++ b/include/linux/dma-fence.h @@ -141,6 +141,9 @@ struct dma_fence_ops { * compute the name at runtime, without having it to store permanently * for each fence, or build a cache of some sort. * + * The returned string is RCU protected and can be freed after the fence + * signaled and a RCU grace period passed. + * * This callback is mandatory. */ const char * (*get_driver_name)(struct dma_fence *fence); @@ -153,6 +156,9 @@ struct dma_fence_ops { * having it to store permanently for each fence, or build a cache of * some sort. * + * The returned string is RCU protected and can be freed after the fence + * signaled and a RCU grace period passed. + * * This callback is mandatory. */ const char * (*get_timeline_name)(struct dma_fence *fence); From 5b644229bd677d52c4efa5973c1fab842c57f896 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 22:59:59 -0700 Subject: [PATCH 0415/1417] xfs: guard against igrab failure in xrep_findparent_from_dcache LOLLM suggests that we need to handle igrab returning NULL here. I don't think it's possible for the inode to enter I_FREEING or I_WILL_FREE while we have an active reference to the corresponding dentry, but we can code defensively anyway. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/findparent.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/xfs/scrub/findparent.c b/fs/xfs/scrub/findparent.c index eab3ac2704befe..d921fe5a9b0c76 100644 --- a/fs/xfs/scrub/findparent.c +++ b/fs/xfs/scrub/findparent.c @@ -473,6 +473,9 @@ xrep_findparent_from_dcache( pip = igrab(d_inode(parent)); dput(parent); + if (!pip) + goto out_dput; + if (S_ISDIR(pip->i_mode)) { ret = pip->i_ino; trace_xrep_findparent_from_dcache(sc->ip, ret); From afbccf99f7f82117cba9ad4b0b006692030f49e8 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:00:16 -0700 Subject: [PATCH 0416/1417] xfs: don't assert when XFS_SCRUB_TYPE_HEALTHY scans return corruption XFS_SCRUB_TYPE_HEALTHY is a synthentic scrub type so that xfs_scrub can tell the kernel "Hey, I finished a scan and saw no problems" and have the kernel forget that it saw indirect evidence of corruption. Unfortunately, as LOLLM points out, it's possible for the health system to record a new corruption just before xfs_scrub gets to XFS_SCRUB_TYPE_HEALTHY. In this case, the existing logic doesn't return early and instead wanders into unknown regions of type_to_health_flag and trips the assert because HEALTHY doesn't have a group assignment. Fix the logic so that we always return early for a HEALTHY scrub type, even if we decide not to call xchk_mark_all_healthy. Cc: stable@vger.kernel.org # v6.9 Fixes: a1f3e0cca41036 ("xfs: update health status if we get a clean bill of health") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/health.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/xfs/scrub/health.c b/fs/xfs/scrub/health.c index 2171bcf0f6c1f2..487ecc5f9f3c29 100644 --- a/fs/xfs/scrub/health.c +++ b/fs/xfs/scrub/health.c @@ -202,9 +202,9 @@ xchk_update_health( * there's no sick flag defined for it, so we branch here ahead of the * mask check. */ - if (sc->sm->sm_type == XFS_SCRUB_TYPE_HEALTHY && - !(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) { - xchk_mark_all_healthy(sc->mp); + if (sc->sm->sm_type == XFS_SCRUB_TYPE_HEALTHY) { + if (!(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) + xchk_mark_all_healthy(sc->mp); return; } From bb991b7f79dd34cc5f24db0f736bf75630c970e7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:00:31 -0700 Subject: [PATCH 0417/1417] xfs: fix attr fork block count checks in xrep_inode_blockcounts LOLLM points out that a file has an attr fork, it will call xchk_inode_count_blocks to set @ablocks to the number of fsblocks mapped by the attr fork; but then it'll compare @blocks (aka the count of fsblocks mapped by the data fork). We already checked that and we never do anything with @acount, so I think this is clearly a bug. Fix the comparison. Cc: stable@vger.kernel.org # v6.8 Fixes: 2d295fe65776d1 ("xfs: repair inode records") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index 8bc508336aa5f4..b87c2214623383 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1702,7 +1702,7 @@ xrep_inode_blockcounts( &acount); if (error) return error; - if (count >= sc->mp->m_sb.sb_dblocks) + if (acount >= sc->mp->m_sb.sb_dblocks) return -EFSCORRUPTED; error = xrep_ino_ensure_extent_count(sc, XFS_ATTR_FORK, nextents); From 1c32cdc986467eaffeedb6c5334852809555b82d Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:00:47 -0700 Subject: [PATCH 0418/1417] xfs: release orphanage dir inode if chown fails LOLLM points out that we leak the igrab'd reference to the orphanage directory inode if chowning it fails. Fix that. Cc: stable@vger.kernel.org # v6.10 Fixes: 1e58a8ccf2597c ("xfs: move orphan files to the orphanage") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/orphanage.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/orphanage.c b/fs/xfs/scrub/orphanage.c index 3aca66869b8000..21e31eeaa042f9 100644 --- a/fs/xfs/scrub/orphanage.c +++ b/fs/xfs/scrub/orphanage.c @@ -192,12 +192,16 @@ xrep_orphanage_create( /* Make sure the orphanage is owned by root. */ error = xrep_chown_orphanage(sc, XFS_I(orphanage_inode)); if (error) - goto out_dput_orphanage; + goto out_rele_orphanage; /* Stash the reference for later and bail out. */ sc->orphanage = XFS_I(orphanage_inode); sc->orphanage_ilock_flags = 0; + orphanage_inode = NULL; +out_rele_orphanage: + if (orphanage_inode) + xchk_irele(sc, XFS_I(orphanage_inode)); out_dput_orphanage: end_creating(orphanage_dentry); out_dput_root: From 8b4ad2814274d43ed8bdfcf857efb8c79815d9c6 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:01:03 -0700 Subject: [PATCH 0419/1417] xfs: release AGFL after walking it during rmapbt repair LOLLM suggests that we not hold the AGFL locked to the scrub transaction for any longer than we have to when we're rebuilding the rmapbt. Since we only took it to generate an rmap record for the AGFL blocks, I think we can safely release it. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rmap_repair.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/scrub/rmap_repair.c b/fs/xfs/scrub/rmap_repair.c index 590f9f41856ef7..725035bf490329 100644 --- a/fs/xfs/scrub/rmap_repair.c +++ b/fs/xfs/scrub/rmap_repair.c @@ -1109,6 +1109,7 @@ xrep_rmap_try_reserve( return error; error = xfs_agfl_walk(sc->mp, agf, agfl_bp, xrep_rmap_walk_agfl, &ra); + xfs_trans_brelse(sc->tp, agfl_bp); if (error) return error; From 984aab2d905a8557fafb27cd9e8713d6d12b3437 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:01:18 -0700 Subject: [PATCH 0420/1417] xfs: use correct jiffies comparison function in xchk_maybe_relax LOLLM points out that we're supposed to use time_after_eq, not a raw >= operation here, or else jiffies wraps can go unnoticed. Fix this. Cc: stable@vger.kernel.org # v6.10 Fixes: 271557de7cbfde ("xfs: reduce the rate of cond_resched calls inside scrub") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/scrub.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/scrub.h b/fs/xfs/scrub/scrub.h index 737a5d6db15ff3..b093945f363148 100644 --- a/fs/xfs/scrub/scrub.h +++ b/fs/xfs/scrub/scrub.h @@ -40,7 +40,7 @@ static inline int xchk_maybe_relax(struct xchk_relax *widget) return 0; widget->resched_nr = 0; - if (unlikely(widget->next_resched <= jiffies)) { + if (unlikely(time_after_eq(jiffies, widget->next_resched))) { cond_resched(); widget->next_resched = XCHK_RELAX_NEXT; } From 3083ba8dde765a9ab2337f3db68d00724a6b1202 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:53:39 -0700 Subject: [PATCH 0421/1417] xfs: check padding field in xfs_ioc_commit_range LOLLM points out that we don't check the ioctl padding field here, so let's do that. I don't think there are many users yet since exchrange requires a new feature flag, so it's a good time to try to plug this hole. Cc: stable@vger.kernel.org # v6.12 Fixes: 398597c3ef7fb1 ("xfs: introduce new file range commit ioctls") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_exchrange.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index c69ecd6a19de4f..07090487c581e8 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -902,7 +902,7 @@ xfs_ioc_commit_range( if (copy_from_user(&args, argp, sizeof(args))) return -EFAULT; - if (args.flags & ~XFS_EXCHANGE_RANGE_ALL_FLAGS) + if (args.pad || (args.flags & ~XFS_EXCHANGE_RANGE_ALL_FLAGS)) return -EINVAL; if (kern_f->magic != XCR_FRESH_MAGIC) return -EBUSY; From 8fc18580ec17f90beac4c933fbe4c74dcd3b7f36 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:53:55 -0700 Subject: [PATCH 0422/1417] xfs: don't call xfs_exchange_range_finish for a dry run LOLLM noticed that we strip file privileges and whatnot even for a dry run. We also shouldn't flush dirty data to disk or trim COW staging events for a dry run. Neither of those behaviors are allowed by the manpage, so fix that by exiting early on DRY_RUN in various functions. Cc: stable@vger.kernel.org # v6.10 Fixes: 42672471f938cd ("xfs: bind together the front and back ends of the file range exchange code") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_exchrange.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index 07090487c581e8..fafb4e3f065c75 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -633,6 +633,9 @@ xfs_exchrange_prep( if (error) return error; + if (fxr->flags & XFS_EXCHANGE_RANGE_DRY_RUN) + return 0; + trace_xfs_exchrange_flush(fxr, ip1, ip2); /* Flush the relevant ranges of both files. */ @@ -709,9 +712,11 @@ xfs_exchrange_contents( * other file write would do. This may involve turning on support for * logged xattrs if either file has security capabilities. */ - error = xfs_exchange_range_finish(fxr); - if (error) - goto out_unlock; + if (!(fxr->flags & XFS_EXCHANGE_RANGE_DRY_RUN)) { + error = xfs_exchange_range_finish(fxr); + if (error) + goto out_unlock; + } out_unlock: xfs_iunlock2_io_mmap(ip1, ip2); From 471e0b6e2ddac9e16b8dc2153d6e5575fefb8a2f Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:54:11 -0700 Subject: [PATCH 0423/1417] xfs: use the correct reservations for rtrmap/refcount recovery LOLLM noticed that we might reserve the wrong number of blocks for recovering rtrmap and rtrefcount updates after a crash. Fix that. Cc: stable@vger.kernel.org # v6.14 Fixes: 5e0679d1c62f25 ("xfs: support recovering rmap intent items targetting realtime extents") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_refcount_item.c | 8 ++++++-- fs/xfs/xfs_rmap_item.c | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_refcount_item.c b/fs/xfs/xfs_refcount_item.c index 8bccf89a77668b..682c6e1b45e3ee 100644 --- a/fs/xfs/xfs_refcount_item.c +++ b/fs/xfs/xfs_refcount_item.c @@ -508,6 +508,7 @@ xfs_refcount_recover_work( struct xfs_cui_log_item *cuip = CUI_ITEM(lip); struct xfs_trans *tp; struct xfs_mount *mp = lip->li_log->l_mp; + unsigned int dblocks; bool isrt = xfs_cui_item_isrt(lip); int i; int error = 0; @@ -543,8 +544,11 @@ xfs_refcount_recover_work( * full btree split on either end of the refcount range. */ resv = xlog_recover_resv(&M_RES(mp)->tr_itruncate); - error = xfs_trans_alloc(mp, &resv, mp->m_refc_maxlevels * 2, 0, - XFS_TRANS_RESERVE, &tp); + if (isrt) + dblocks = mp->m_rtrefc_maxlevels * 2; + else + dblocks = mp->m_refc_maxlevels * 2; + error = xfs_trans_alloc(mp, &resv, dblocks, 0, XFS_TRANS_RESERVE, &tp); if (error) return error; diff --git a/fs/xfs/xfs_rmap_item.c b/fs/xfs/xfs_rmap_item.c index 2a3a73a8566d11..000cff1ce324f0 100644 --- a/fs/xfs/xfs_rmap_item.c +++ b/fs/xfs/xfs_rmap_item.c @@ -573,6 +573,7 @@ xfs_rmap_recover_work( struct xfs_rui_log_item *ruip = RUI_ITEM(lip); struct xfs_trans *tp; struct xfs_mount *mp = lip->li_log->l_mp; + unsigned int dblocks; bool isrt = xfs_rui_item_isrt(lip); int i; int error = 0; @@ -596,8 +597,11 @@ xfs_rmap_recover_work( } resv = xlog_recover_resv(&M_RES(mp)->tr_itruncate); - error = xfs_trans_alloc(mp, &resv, mp->m_rmap_maxlevels, 0, - XFS_TRANS_RESERVE, &tp); + if (isrt) + dblocks = mp->m_rtrmap_maxlevels; + else + dblocks = mp->m_rmap_maxlevels; + error = xfs_trans_alloc(mp, &resv, dblocks, 0, XFS_TRANS_RESERVE, &tp); if (error) return error; From 46c1b6674a7b3a8385e7fa27f4efc6f45eddf967 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:54:26 -0700 Subject: [PATCH 0424/1417] xfs: fix integer overflows in xbitmap set functions LOLLM complains that the xbitmap set functions can suffer an integer underflow or overflow and thereby return the wrong left and right pointers. Fix that logic bomb, even though (AFAICT) we never actually try to set the *entire* bitmap. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bitmap.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/fs/xfs/scrub/bitmap.c b/fs/xfs/scrub/bitmap.c index c7fa908d92b25e..08f216d26ec632 100644 --- a/fs/xfs/scrub/bitmap.c +++ b/fs/xfs/scrub/bitmap.c @@ -122,8 +122,8 @@ xbitmap64_set( uint64_t start, uint64_t len) { - struct xbitmap64_node *left; - struct xbitmap64_node *right; + struct xbitmap64_node *left = NULL; + struct xbitmap64_node *right = NULL; uint64_t last = start + len - 1; int error; @@ -131,6 +131,7 @@ xbitmap64_set( left = xbitmap64_tree_iter_first(&bitmap->xb_root, start, last); if (left && left->bn_start <= start && left->bn_last >= last) return 0; + left = NULL; /* Clear out everything in the range we want to set. */ error = xbitmap64_clear(bitmap, start, len); @@ -138,11 +139,15 @@ xbitmap64_set( return error; /* Do we have a left-adjacent extent? */ - left = xbitmap64_tree_iter_first(&bitmap->xb_root, start - 1, start - 1); + if (start > 0) + left = xbitmap64_tree_iter_first(&bitmap->xb_root, start - 1, + start - 1); ASSERT(!left || left->bn_last + 1 == start); /* Do we have a right-adjacent extent? */ - right = xbitmap64_tree_iter_first(&bitmap->xb_root, last + 1, last + 1); + if (last < U64_MAX) + right = xbitmap64_tree_iter_first(&bitmap->xb_root, last + 1, + last + 1); ASSERT(!right || right->bn_start == last + 1); if (left && right) { @@ -397,8 +402,8 @@ xbitmap32_set( uint32_t start, uint32_t len) { - struct xbitmap32_node *left; - struct xbitmap32_node *right; + struct xbitmap32_node *left = NULL; + struct xbitmap32_node *right = NULL; uint32_t last = start + len - 1; int error; @@ -406,6 +411,7 @@ xbitmap32_set( left = xbitmap32_tree_iter_first(&bitmap->xb_root, start, last); if (left && left->bn_start <= start && left->bn_last >= last) return 0; + left = NULL; /* Clear out everything in the range we want to set. */ error = xbitmap32_clear(bitmap, start, len); @@ -413,11 +419,15 @@ xbitmap32_set( return error; /* Do we have a left-adjacent extent? */ - left = xbitmap32_tree_iter_first(&bitmap->xb_root, start - 1, start - 1); + if (start > 0) + left = xbitmap32_tree_iter_first(&bitmap->xb_root, start - 1, + start - 1); ASSERT(!left || left->bn_last + 1 == start); /* Do we have a right-adjacent extent? */ - right = xbitmap32_tree_iter_first(&bitmap->xb_root, last + 1, last + 1); + if (last < U32_MAX) + right = xbitmap32_tree_iter_first(&bitmap->xb_root, last + 1, + last + 1); ASSERT(!right || right->bn_start == last + 1); if (left && right) { From c54110d814c3e8ed6bbbb5e02874994ed9f668ac Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:54:42 -0700 Subject: [PATCH 0425/1417] xfs: only flag zero padding for dir3 data blocks, not dir3 block blocks LOLLM complains that xchk_directory_data_bestfree can be passed a directory block that is either in "block" or "data" format, but the check here unconditionally treats the dir3_block and dir3_data blocks as if they have the same header format (they don't). Consequently, we can incorrectly set the preen state on dir3_block blocks, which of course we can't preen away because dir3_block blocks do not have a padding field. Fix this. Cc: stable@vger.kernel.org # v7.1-rc4 Fixes: 939919ccddfcc3 ("xfs: check directory data block header padding in scrub") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/dir.c b/fs/xfs/scrub/dir.c index 2a037aae904d03..19d974c7e2b7a9 100644 --- a/fs/xfs/scrub/dir.c +++ b/fs/xfs/scrub/dir.c @@ -492,7 +492,7 @@ xchk_directory_data_bestfree( goto out; xchk_buffer_recheck(sc, bp); - if (xfs_has_crc(sc->mp)) { + if (!is_block && xfs_has_crc(sc->mp)) { struct xfs_dir3_data_hdr *hdr3 = bp->b_addr; if (hdr3->pad) From e9193f2f1ce32d02b9230094ffdbfab715ab6137 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:54:58 -0700 Subject: [PATCH 0426/1417] xfs: check di_forkoff correctly in scrub The di_forkoff check in xchk_dinode is incorrect, according to LOLLM. XFS_DFORK_BOFF returns a byte count relative to the start of the literal area, not the start of the inode. Therefore, this check won't flag di_forkoff values that are larger than the literal area but not the inode size itself. Fix this check; sadly the old APTR code was correct. Cc: stable@vger.kernel.org # v6.8 Fixes: 6b5d917780219d ("xfs: dont cast to char * for XFS_DFORK_*PTR macros") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c index 65b13e31191681..46e9bf4a43179e 100644 --- a/fs/xfs/scrub/inode.c +++ b/fs/xfs/scrub/inode.c @@ -607,7 +607,7 @@ xchk_dinode( } /* di_forkoff */ - if (XFS_DFORK_BOFF(dip) >= mp->m_sb.sb_inodesize) + if (dip->di_forkoff >= (XFS_LITINO(mp) >> 3)) xchk_ino_set_corrupt(sc, ino); if (naextents != 0 && dip->di_forkoff == 0) xchk_ino_set_corrupt(sc, ino); From 14e379600d3e57ab0872b049c28cfe5ef519d439 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 14 Sep 2026 13:35:38 +0200 Subject: [PATCH 0427/1417] xfs: don't try to get a reference to a NULL oz in xfs_get_cached_zone oz can be NULL when we resample it after taking i_flags_lock, so account for that. Fixes: 2d829cc76777 ("xfs: fix racy open zone caching") Signed-off-by: Christoph Hellwig Reviewed-by: Hans Holmberg Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_alloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index 28c1e48909fa8d..d7ec055a9a068e 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -820,7 +820,7 @@ xfs_get_cached_zone( spin_unlock(&ip->i_flags_lock); } - if (!atomic_inc_not_zero(&oz->oz_ref)) + if (oz && !atomic_inc_not_zero(&oz->oz_ref)) oz = NULL; out_unlock: rcu_read_unlock(); From ee415ce8cba154d07d02a6d2fbb27ff518264c3a Mon Sep 17 00:00:00 2001 From: Luca Coelho Date: Tue, 8 Sep 2026 13:06:51 +0300 Subject: [PATCH 0428/1417] drm/i915/display: check configuration index before shifting The calc_allowed_config_filter() function passes the return value of iter_pos_to_idx() directly to BIT(), but the helper can return -1 for an invalid iterator. The iterator already rejects negative indices before doing a configuration, so this should not matter in normal flows. In any case, for robustness, check the index explicitly and warn if it is negative, avoiding an undefined shift. Fixes: 39e30bdf2f92 ("drm/i915/dp_link_caps: Add link configuration iterator") Reviewed-by: Imre Deak Link: https://patch.msgid.link/20260908100659.113555-1-luciano.coelho@intel.com Signed-off-by: Luca Coelho (cherry picked from commit fe05cb9b9fb0ecc10409c4c6133257214b6cd8c8) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_dp_link_caps.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 7b6cc6055da82a..98657aa4d3d580 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -426,12 +426,15 @@ calc_allowed_config_filter(struct intel_dp_link_caps *link_caps, const struct intel_dp_link_config *forced_params) { struct intel_dp_link_caps_filter allowed_configs = INTEL_DP_LINK_CAPS_FILTER_NONE; + struct intel_display *display = to_intel_display(link_caps->dp); struct intel_dp_link_caps_order order = bw_desc_config_order(); struct intel_dp_link_caps_iter iter; struct intel_dp_link_config config; iter_start(&iter, link_caps, order, enabled_configs); for_each_dp_link_config(&iter, &config) { + int config_idx; + if (forced_params->rate && forced_params->rate != config.rate) continue; @@ -446,7 +449,11 @@ calc_allowed_config_filter(struct intel_dp_link_caps *link_caps, if (config.lane_count > max_limits->lane_count) continue; - allowed_configs.config_mask |= BIT(iter_pos_to_idx(link_caps, order, iter.pos)); + config_idx = iter_pos_to_idx(link_caps, order, iter.pos); + if (drm_WARN_ON(display->drm, config_idx < 0)) + continue; + + allowed_configs.config_mask |= BIT(config_idx); } intel_dp_link_caps_iter_end(&iter); From ce2b91bebc7bc0495fe3ad5ee47e4977fbb77fc5 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Mon, 14 Sep 2026 16:21:11 +0800 Subject: [PATCH 0429/1417] xfs: remove duplicate INO1_WRITTEN check Commit a23eca88448e ("xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN") duplicated commit b2d5a81dae38 ("xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN"), so xmi_can_exchange_reflink_flags() ended up with two identical XFS_EXCHMAPS_INO1_WRITTEN checks. The second one is dead code, since the first one already returned false. Remove it. Signed-off-by: Jiangshan Yi Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_exchmaps.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 49eda8d0994dee..3efed37cb98a8f 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -959,16 +959,6 @@ xmi_can_exchange_reflink_flags( { struct xfs_mount *mp = req->ip1->i_mount; - /* - * The INO1_WRITTEN optimization can skip exchanging hole and - * unwritten mappings, which means we cannot guarantee that all - * shared extents actually moved to the other file. Clearing the - * reflink flag of an inode that still holds shared extents breaks - * the CoW write path, so refuse to exchange the flags in that case. - */ - if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN) - return false; - /* * The INO1_WRITTEN optimization can skip exchanging hole and * unwritten mappings, which means we cannot guarantee that all From c16b885ad6b589b233534ee99ade82110d2bc381 Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Fri, 4 Sep 2026 18:06:41 +0530 Subject: [PATCH 0430/1417] xfs: remove unused xfs_reflink_remap_range declaration The implementation was inlined into xfs_file_remap_range() by commit 3fc9f5e40931 ("xfs: remove xfs_reflink_remap_range"), leaving this declaration orphaned. Signed-off-by: Anuj Gupta Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_reflink.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/fs/xfs/xfs_reflink.h b/fs/xfs/xfs_reflink.h index 9d1ed9bb0beed8..683c1841e64074 100644 --- a/fs/xfs/xfs_reflink.h +++ b/fs/xfs/xfs_reflink.h @@ -48,9 +48,6 @@ extern int xfs_reflink_end_cow(struct xfs_inode *ip, xfs_off_t offset, int xfs_reflink_end_atomic_cow(struct xfs_inode *ip, xfs_off_t offset, xfs_off_t count); extern int xfs_reflink_recover_cow(struct xfs_mount *mp); -extern loff_t xfs_reflink_remap_range(struct file *file_in, loff_t pos_in, - struct file *file_out, loff_t pos_out, loff_t len, - unsigned int remap_flags); extern int xfs_reflink_inode_has_shared_extents(struct xfs_trans *tp, struct xfs_inode *ip, bool *has_shared); extern int xfs_reflink_clear_inode_flag(struct xfs_inode *ip, From 2998147b59c9df0a51477c7a6b3d1f0ba3127dd4 Mon Sep 17 00:00:00 2001 From: Dong Chenchen Date: Thu, 10 Sep 2026 22:00:42 +0800 Subject: [PATCH 0431/1417] ipv4: icmp: reject RTN_UNREACHABLE input routes in icmp_route_lookup When the forward output route cannot be used in icmp_route_lookup(), it enters the "reverse path" and calls ip_route_input() on fl4_dec.daddr, the original packet's source address. ip_route_input() only returns an error for truly invalid packets. For unreachable addresses it will succeed and return an input route whose dst.output is set to ip_rt_bug(). The existing check only rejects RTN_LOCAL routes, so the RTN_UNREACHABLE route types can still be returned and later used for output, syzkaller triggering a WARN_ON_ONCE() in ip_rt_bug() as bellow: ------------[ cut here ]------------ WARNING: net/ipv4/route.c:1273 at ip_rt_bug+0x14/0x20 RIP: 0010:ip_rt_bug+0x14/0x20 Call Trace: ip_push_pending_frames+0xfa/0x100 __icmp_send+0x905/0xf10 ip_options_compile+0xc0/0xd0 ip_rcv_finish_core+0x321/0xae0 ip_rcv+0x1de/0x260 __netif_receive_skb_one_core+0x11a/0x130 netif_receive_skb+0x7b/0x260 tun_get_user+0x11bf/0x1c10 ------------[ cut here ]------------ Reject input route that is RTN_UNREACHABLE to fix it. The net warning is only printed for RTN_LOCAL, as RTN_UNREACHABLE is not the result of a race condition. Fixes: 8b7817f3a959 ("[IPSEC]: Add ICMP host relookup support") Suggested-by: Ido Schimmel Reviewed-by: Jiayuan Chen Reviewed-by: Ido Schimmel Signed-off-by: Dong Chenchen Link: https://patch.msgid.link/20260910140042.1880242-1-dongchenchen2@huawei.com Signed-off-by: Paolo Abeni --- net/ipv4/icmp.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c index 0caedfc7ca92f6..90c0e22c29bea6 100644 --- a/net/ipv4/icmp.c +++ b/net/ipv4/icmp.c @@ -581,16 +581,19 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4, skb_dstref_restore(skb_in, orefdst); /* - * At this point, fl4_dec.daddr should NOT be local (we - * checked fl4_dec.saddr above). However, a race condition - * may occur if the address is added to the interface - * concurrently. In that case, ip_route_input() returns a - * LOCAL route with dst.output=ip_rt_bug, which must not - * be used for output. + * fl4_dec.daddr is not expected to be local here, but it can be + * added to an interface concurrently, in which case + * ip_route_input() returns a LOCAL route. It can also fail to + * build a forwarding route towards fl4_dec.daddr, for example, + * when forwarding is disabled, and return an UNREACHABLE route. + * Both cases will result in a route with dst.output=ip_rt_bug, + * which must not be used for output. */ - if (!err && rt2 && rt2->rt_type == RTN_LOCAL) { + if (!err && rt2 && rt2->rt_type == RTN_LOCAL) net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n", &fl4_dec.daddr, &fl4_dec.saddr); + if (!err && rt2 && + (rt2->rt_type == RTN_LOCAL || rt2->rt_type == RTN_UNREACHABLE)) { dst_release(&rt2->dst); err = -EINVAL; } From 7c8810c2e69c3d9ca6df870b40ae9218e50b4fb1 Mon Sep 17 00:00:00 2001 From: Hohyun Sim Date: Thu, 10 Sep 2026 15:37:43 +0900 Subject: [PATCH 0432/1417] net: fddi: skfp: fix NULL deref when setting the MAC address while down skfp_ctl_set_mac_address() calls ResetAdapter() unconditionally, without checking netif_running(). ResetAdapter() first calls card_stop(), which sets smc->hw.hw_state to STOPPED, and then mac_drv_clear_tx_queue(), which walks the two transmit queues: for (i = QUEUE_S; i <= QUEUE_A0; i++) { queue = smc->hw.fp.tx[i] ; ... t = queue->tx_curr_get ; smc->hw.fp.tx[] is only populated by init_tx(), which is reached from skfp_open() through init_smt() -> init_fddi_driver() -> init_fplus() -> init_mac() -> init_tx(). The private area is allocated and zeroed by alloc_fddidev(), so on an interface that has never been brought up both queue pointers are still NULL. The hw_state test at the top of mac_drv_clear_tx_queue() does not catch this, because card_stop() has just set STOPPED; the function proceeds into the loop and dereferences NULL. ResetAdapter() does call init_smt() itself, but only after the queues have been cleared. Setting the MAC address on a down interface therefore oopses: ip link set dev fddi0 address 02:00:00:00:00:01 BUG: KASAN: null-ptr-deref in mac_drv_clear_tx_queue+0x68/0x2c0 [skfp] Read of size 8 at addr 0000000000000010 by task ip/302 Call Trace: mac_drv_clear_tx_queue+0x68/0x2c0 [skfp 6c01d4bab63c36978bd0a7d7e90837adb44cc37b] ResetAdapter+0x29/0x100 [skfp 6c01d4bab63c36978bd0a7d7e90837adb44cc37b] skfp_ctl_set_mac_address+0x57/0x80 [skfp 6c01d4bab63c36978bd0a7d7e90837adb44cc37b] netif_set_mac_address+0x1e4/0x2c0 do_setlink+0x684/0x2680 Address 0x10 is the offset of tx_curr_get, the third pointer in struct s_smt_tx_queue, on 64-bit. mac_drv_clear_rx_queue(), which ResetAdapter() calls immediately afterwards, dereferences smc->hw.fp.rx[QUEUE_R1] in the same way behind the same ineffective hw_state test; the transmit queue merely crashes first. Both are covered by the guard below. Skip the adapter reset when the interface is down. dev_addr_set() is left unconditional, so the new address is still recorded in dev->dev_addr. Nothing is lost by not resetting the adapter here: skfp_open() deliberately re-reads the factory address on every open, read_address(smc, NULL); eth_hw_addr_set(dev, smc->hw.fddi_canon_addr.a); and the comment above it states this is done to discard exactly such an address override across a close/open cycle. An address set while the interface is down could not have survived the following open even before this change, so the guard removes no working behaviour. Guarding the hardware side of ndo_set_mac_address() with netif_running() is established practice; skge_set_mac_address() has done so since commit 2eb3e621c4e0 ("skge: set mac address bonding fix"). Guarding the reset as a whole, rather than NULL-checking the queues, is also what the rest of the driver expects. After a previous open/close the queue pointers are stale but non-NULL, so there is no crash, yet ResetAdapter() goes on to call smt_online() and STI_FBI() ("Enable Board Interrupts") while skfp_close() has already called free_irq() - the adapter would be brought back online with no handler installed. The only other ResetAdapter() caller is skfp_interrupt(), which by construction runs only while the device is open. Found by automated driver testing against an emulated SysKonnect FDDI adapter under a KASAN-enabled 7.0.0 kernel. Triggering it requires CAP_NET_ADMIN. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Assisted-by: LLM KASAN Signed-off-by: Hohyun Sim Link: https://patch.msgid.link/20260910063743.110747-1-tlaghgus0425@korea.ac.kr Signed-off-by: Paolo Abeni --- drivers/net/fddi/skfp/skfddi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/fddi/skfp/skfddi.c b/drivers/net/fddi/skfp/skfddi.c index a273362c9e703c..feea7baa48168b 100644 --- a/drivers/net/fddi/skfp/skfddi.c +++ b/drivers/net/fddi/skfp/skfddi.c @@ -928,7 +928,8 @@ static int skfp_ctl_set_mac_address(struct net_device *dev, void *addr) dev_addr_set(dev, p_sockaddr->sa_data); spin_lock_irqsave(&bp->DriverLock, Flags); - ResetAdapter(smc); + if (netif_running(dev)) + ResetAdapter(smc); spin_unlock_irqrestore(&bp->DriverLock, Flags); return 0; /* always return zero */ From 2f3c2a6f963e57cf4f0f34cbf24ab11abb64d118 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 11 Sep 2026 13:00:07 +0530 Subject: [PATCH 0433/1417] xfs: fix typos and repeated words in comments Correct ten misspellings and eight accidentally doubled words in comments. No code changes. The doubled word in xfs_zone_alloc.c was not a duplicate: "so that is is reused" is "it is" misspelt, so that one reads "so that it is reused" rather than dropping a word. Signed-off-by: Hemanth Selam Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_ag.h | 2 +- fs/xfs/libxfs/xfs_alloc.c | 4 ++-- fs/xfs/libxfs/xfs_attr_leaf.c | 2 +- fs/xfs/libxfs/xfs_errortag.h | 2 +- fs/xfs/libxfs/xfs_exchmaps.c | 2 +- fs/xfs/libxfs/xfs_format.h | 2 +- fs/xfs/libxfs/xfs_inode_buf.c | 2 +- fs/xfs/scrub/agheader_repair.c | 2 +- fs/xfs/scrub/alloc_repair.c | 2 +- fs/xfs/scrub/dirtree.c | 2 +- fs/xfs/scrub/reap.c | 2 +- fs/xfs/xfs_bmap_item.c | 2 +- fs/xfs/xfs_inode.c | 2 +- fs/xfs/xfs_log_cil.c | 2 +- fs/xfs/xfs_platform.h | 2 +- fs/xfs/xfs_zone_alloc.c | 2 +- fs/xfs/xfs_zone_gc.c | 2 +- 17 files changed, 18 insertions(+), 18 deletions(-) diff --git a/fs/xfs/libxfs/xfs_ag.h b/fs/xfs/libxfs/xfs_ag.h index fd22fe59893176..ee636b66a72f6d 100644 --- a/fs/xfs/libxfs/xfs_ag.h +++ b/fs/xfs/libxfs/xfs_ag.h @@ -207,7 +207,7 @@ xfs_perag_next( } /* - * Per-ag geometry infomation and validation + * Per-ag geometry information and validation */ xfs_agblock_t xfs_ag_block_count(struct xfs_mount *mp, xfs_agnumber_t agno); void xfs_agino_range(struct xfs_mount *mp, xfs_agnumber_t agno, diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c index d99602bcc16ffe..f762dcce8d133d 100644 --- a/fs/xfs/libxfs/xfs_alloc.c +++ b/fs/xfs/libxfs/xfs_alloc.c @@ -3487,7 +3487,7 @@ xfs_alloc_read_agf( } /* - * Pre-proces allocation arguments to set initial state that we don't require + * Pre-process allocation arguments to set initial state that we don't require * callers to set up correctly, as well as bounds check the allocation args * that are set up. */ @@ -3608,7 +3608,7 @@ xfs_alloc_vextent_finish( * ABBA AGF deadlocks because a future allocation attempt in this * transaction may attempt to lock a lower number AGF. * - * We can't release the AGF until the transaction is commited, so at + * We can't release the AGF until the transaction is committed, so at * this point we must update the "first allocation" tracker to point at * this AG if the tracker is empty or points to a lower AG. This allows * the next allocation attempt to be modified appropriately to avoid diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c index b6288395f8533d..2c80f4fd0b78de 100644 --- a/fs/xfs/libxfs/xfs_attr_leaf.c +++ b/fs/xfs/libxfs/xfs_attr_leaf.c @@ -1715,7 +1715,7 @@ xfs_attr3_leaf_add_work( /* * This freemap entry starts at the old end of the * leaf entry array, so we need to adjust its base - * upward to accomodate the larger array. + * upward to accommodate the larger array. */ diff = sizeof(struct xfs_attr_leaf_entry); } else if (ichdr->freemap[i].size > 0 && diff --git a/fs/xfs/libxfs/xfs_errortag.h b/fs/xfs/libxfs/xfs_errortag.h index 6de207fed2d892..f0c83f1f0b3b41 100644 --- a/fs/xfs/libxfs/xfs_errortag.h +++ b/fs/xfs/libxfs/xfs_errortag.h @@ -83,7 +83,7 @@ #define XFS_RANDOM_DEFAULT 100 /* - * Table of errror injection knobs. The parameters to the XFS_ERRTAG macro are: + * Table of error injection knobs. The parameters to the XFS_ERRTAG macro are: * 1. The XFS_ERRTAG_ flag but without the prefix; * 2. The name of the sysfs knob; and * 3. The default value for the knob. diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 3efed37cb98a8f..6a66b6075e0af4 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -395,7 +395,7 @@ xfs_exchmaps_one_step( /* * Re-add both mappings. We exchange the file offsets between the two * maps and add the opposite map, which has the effect of filling the - * logical offsets we just unmapped, but with with the physical mapping + * logical offsets we just unmapped, but with the physical mapping * information exchanged. */ swap(irec1->br_startoff, irec2->br_startoff); diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h index dd0ed046fbe9c9..1a7a7e60a170c4 100644 --- a/fs/xfs/libxfs/xfs_format.h +++ b/fs/xfs/libxfs/xfs_format.h @@ -1051,7 +1051,7 @@ enum xfs_dinode_fmt { * block is 1KB in size. * * With XFS_MAX_EXTCNT_DATA_FORK_SMALL representing maximum extent count and - * with 1KB sized blocks, a file can reach upto, + * with 1KB sized blocks, a file can reach up to, * 1KB * (2^31) = 2TB * * This is much larger than the theoretical maximum size of a directory diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c index e4c3f7b24e952f..0340e2189921db 100644 --- a/fs/xfs/libxfs/xfs_inode_buf.c +++ b/fs/xfs/libxfs/xfs_inode_buf.c @@ -626,7 +626,7 @@ xfs_dinode_verify( * have di_nlink track the link count, even if the actual filesystem * only supported V1 inodes (i.e. di_onlink). When writing out the * ondisk inode, it would set both the ondisk di_nlink and di_onlink to - * the the incore di_nlink value, which is why we cannot check for + * the incore di_nlink value, which is why we cannot check for * di_nlink==0 on a V1 inode. V2/3 inodes would get written out with * di_onlink==0, so we can check that. */ diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index a66b611588c47f..ff1b4b361cf2e0 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1369,7 +1369,7 @@ xrep_iunlink_mark_ondisk( /* * Walk an iunlink bucket's inode list. For each inode that should be on this - * chain, clear its entry in in iunlink_bmp because it's ok and we don't need + * chain, clear its entry in iunlink_bmp because it's ok and we don't need * to touch it further. */ STATIC int diff --git a/fs/xfs/scrub/alloc_repair.c b/fs/xfs/scrub/alloc_repair.c index 95e318e4f3a6c7..2398e381959771 100644 --- a/fs/xfs/scrub/alloc_repair.c +++ b/fs/xfs/scrub/alloc_repair.c @@ -338,7 +338,7 @@ xrep_cntbt_extent_cmp( } /* - * Sort the free extents by length so so that we can put the records into the + * Sort the free extents by length so that we can put the records into the * cntbt in the correct order. Don't let userspace kill us if we're resorting * after allocating btree blocks. */ diff --git a/fs/xfs/scrub/dirtree.c b/fs/xfs/scrub/dirtree.c index 9b0ab23166124e..887383d2e94104 100644 --- a/fs/xfs/scrub/dirtree.c +++ b/fs/xfs/scrub/dirtree.c @@ -1021,7 +1021,7 @@ xchk_dirtree( return error; } -/* Does the directory targetted by this scrub have no parents? */ +/* Does the directory targeted by this scrub have no parents? */ bool xchk_dirtree_parentless(const struct xchk_dirtree *dl) { diff --git a/fs/xfs/scrub/reap.c b/fs/xfs/scrub/reap.c index f698b9be3dd1df..0dfe61bafc6a03 100644 --- a/fs/xfs/scrub/reap.c +++ b/fs/xfs/scrub/reap.c @@ -172,7 +172,7 @@ static inline bool xreap_is_dirty(const struct xreap_state *rs) } /* - * Decide if we need to roll the transaction to clear out the the log + * Decide if we need to roll the transaction to clear out the log * reservation that we allocated to buffer invalidations. */ static inline bool xreap_want_binval_roll(const struct xreap_state *rs) diff --git a/fs/xfs/xfs_bmap_item.c b/fs/xfs/xfs_bmap_item.c index 89f6e79a955f54..aa5b41629747d2 100644 --- a/fs/xfs/xfs_bmap_item.c +++ b/fs/xfs/xfs_bmap_item.c @@ -339,7 +339,7 @@ xfs_bmap_update_get_group( /* * Bump the intent count on behalf of the deferred rmap and refcount - * intent items that that we can queue when we finish this bmap work. + * intent items that we can queue when we finish this bmap work. * This new intent item will bump the intent count before the bmap * intent drops the intent count, ensuring that the intent count * remains nonzero across the transaction roll. diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 030a7c8f2c129a..621513d7215eff 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -2669,7 +2669,7 @@ xfs_irele( } /* - * Ensure all commited transactions touching the inode are written to the log. + * Ensure all committed transactions touching the inode are written to the log. */ int xfs_log_force_inode( diff --git a/fs/xfs/xfs_log_cil.c b/fs/xfs/xfs_log_cil.c index f9e07a32f60f71..9446ac44ba8851 100644 --- a/fs/xfs/xfs_log_cil.c +++ b/fs/xfs/xfs_log_cil.c @@ -1370,7 +1370,7 @@ xlog_cil_cleanup_whiteouts( * allocation context. However, we do not want to block on memory reclaim * recursing back into the filesystem because this push may have been triggered * by memory reclaim itself. Hence we really need to run under full GFP_NOFS - * contraints here. + * constraints here. */ static void xlog_cil_push_work( diff --git a/fs/xfs/xfs_platform.h b/fs/xfs/xfs_platform.h index 5d542e95fe444e..745d715b4c646b 100644 --- a/fs/xfs/xfs_platform.h +++ b/fs/xfs/xfs_platform.h @@ -153,7 +153,7 @@ static inline void delay(long ticks) /* * XFS wrapper structure for sysfs support. It depends on external data * structures and is embedded in various internal data structures to implement - * the XFS sysfs object heirarchy. Define it here for broad access throughout + * the XFS sysfs object hierarchy. Define it here for broad access throughout * the codebase. */ struct xfs_kobj { diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index d7ec055a9a068e..b75cf3bfe33c19 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -828,7 +828,7 @@ xfs_get_cached_zone( } /* - * Stash our zone in the inode so that is is reused for future allocations. + * Stash our zone in the inode so that it is reused for future allocations. * * The open_zone structure will be pinned until either the inode is freed or * until the cached open zone is replaced with a different one because the diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c index 5fdcf98a21338b..54b70ed2922f52 100644 --- a/fs/xfs/xfs_zone_gc.c +++ b/fs/xfs/xfs_zone_gc.c @@ -46,7 +46,7 @@ * before remapping. * * Once a zone does not contain any valid data, be that through GC or user - * block removal, it is queued for for a zone reset. The reset operation + * block removal, it is queued for a zone reset. The reset operation * carefully ensures that the RT device cache is flushed and all transactions * referencing the rmap have been committed to disk. */ From 82e47533221d4746947b74d2e79a478c36c6433a Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 4 Sep 2026 15:43:11 +0200 Subject: [PATCH 0434/1417] ata: libahci: clear PxCLBU and PxFBU for AHCI_HFLAG_32BIT_ONLY A user reported that commit 105c42566a55 ("ata: ahci: force 32-bit DMA for JMicron JMB582/JMB585") made the JMicron JMB585 unusable on his board. The failure is seen as soon as the ahci driver is probed, and booting with iommu=off does not solve the problem. Looking at the AHCI specification, PxCLBU and PxFBU are both read only '0' for HBAs that do not support 64-bit addressing. For HBAs that do support 64-bit addressing, the registers are read write, with a reset value that is Implementation Specific. When using the AHCI_HFLAG_32BIT_ONLY flag, the HBA does support 64-bit addressing, and a 32-bit DMA mask is set by simply clearing HOST_CAP_64. Thus, in this case, we need to explicitly clear the registers to 0. Fixes: 105c42566a55 ("ata: ahci: force 32-bit DMA for JMicron JMB582/JMB585") Fixes: c7a42156d99b ("ahci: disable 64bit dma on sb600") Cc: stable@vger.kernel.org Reported-by: Roland Waltersson Closes: https://lore.kernel.org/linux-ide/IA0PR17MB668730A4ECCD65F7A1DC3EDC9EB62@IA0PR17MB6687.namprd17.prod.outlook.com/ Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20260904134310.1465051-2-cassel@kernel.org Signed-off-by: Niklas Cassel --- drivers/ata/libahci.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index 6d72eb017b4987..08ab56e955662a 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -744,15 +744,28 @@ void ahci_start_fis_rx(struct ata_port *ap) struct ahci_port_priv *pp = ap->private_data; u32 tmp; - /* set FIS registers */ + /* + * On HBAs that only support 32-bit addressing PxCLBU is read only '0'. + * When applying the AHCI_HFLAG_32BIT_ONLY quirk, PxCLBU is RW, and the + * reset value is Implementation Specific, so we need to clear it to 0. + */ if (hpriv->cap & HOST_CAP_64) writel((pp->cmd_slot_dma >> 16) >> 16, port_mmio + PORT_LST_ADDR_HI); + else if (hpriv->flags & AHCI_HFLAG_32BIT_ONLY) + writel(0, port_mmio + PORT_LST_ADDR_HI); writel(pp->cmd_slot_dma & 0xffffffff, port_mmio + PORT_LST_ADDR); + /* + * On HBAs that only support 32-bit addressing PxFBU is read only '0'. + * When applying the AHCI_HFLAG_32BIT_ONLY quirk, PxFBU is RW, and the + * reset value is Implementation Specific, so we need to clear it to 0. + */ if (hpriv->cap & HOST_CAP_64) writel((pp->rx_fis_dma >> 16) >> 16, port_mmio + PORT_FIS_ADDR_HI); + else if (hpriv->flags & AHCI_HFLAG_32BIT_ONLY) + writel(0, port_mmio + PORT_FIS_ADDR_HI); writel(pp->rx_fis_dma & 0xffffffff, port_mmio + PORT_FIS_ADDR); /* enable FIS reception */ From 621d90169cef6c8da5b6134db5c0c4e23cdd09ce Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Tue, 11 Aug 2026 10:27:02 +0200 Subject: [PATCH 0435/1417] wifi: brcmfmac: fix lost 802.1x TX completion wakeup brcmf_txfinalize() decrements pend_8021x_cnt before a lockless waitqueue_active() check. atomic_dec() does not order the decrement against the check. The waiter can therefore observe a nonzero count while the waker observes an empty queue, losing the final wakeup and delaying key installation until the 950 ms timeout. Add smp_mb__after_atomic() to order the decrement before the queue check. wait_event_timeout() provides the matching barrier. LKMM confirms that this forbids the lost-wakeup outcome. Fixes: 21fff75d2fb6 ("brcmfmac: use wait_event_timeout for 8021x pending count") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260811082702.44521-1-kmehltretter@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index dad6f4563d1468..d2ae679856067a 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -555,6 +555,8 @@ void brcmf_txfinalize(struct brcmf_if *ifp, struct sk_buff *txp, bool success) if (type == ETH_P_PAE) { atomic_dec(&ifp->pend_8021x_cnt); + /* Order the decrement before waitqueue_active() */ + smp_mb__after_atomic(); if (waitqueue_active(&ifp->pend_8021x_wait)) wake_up(&ifp->pend_8021x_wait); } From 1eeca1d5e0920fbdad6449768fd2d4364e714180 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Sat, 15 Aug 2026 20:10:43 +0800 Subject: [PATCH 0436/1417] wifi: brcmsmac: fix UAF in brcms_free_timer() brcms_free_timer() calls brcms_del_timer() which uses the non-synchronous cancel_delayed_work() to cancel the timer's underlying delayed work. If the work callback (_brcms_timer) is already running, cancel_delayed_work() returns false without waiting, and brcms_free_timer() proceeds to kfree(t) while the callback still accesses t through container_of(). Add an explicit cancel_delayed_work_sync() after brcms_del_timer() to guarantee that any in-flight callback has completed before the timer structure is freed. Fixes: 5b435de0d786 ("net: wireless: add brcm80211 drivers") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260815121043.938414-1-yijiangshan@kylinos.cn Signed-off-by: Johannes Berg --- .../net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c index 6255d673d2d3e3..c1a2318d7ea6f1 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c @@ -1571,6 +1571,10 @@ void brcms_free_timer(struct brcms_timer *t) /* delete the timer in case it is active */ brcms_del_timer(t); + /* Ensure the callback has finished before freeing the timer + * structure, since brcms_del_timer() uses non-synchronous cancel. + */ + cancel_delayed_work_sync(&t->dly_wrk); if (wl->timers == t) { wl->timers = wl->timers->next; From 4de44f666d57be93bdc3b6467229a65d86195545 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:02 +0530 Subject: [PATCH 0437/1417] ALSA: core: seq: oss: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-2-hemanth.selam@gmail.com --- sound/core/seq/oss/seq_oss_init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c index 1aece46c8b064c..6586e07431c35f 100644 --- a/sound/core/seq/oss/seq_oss_init.c +++ b/sound/core/seq/oss/seq_oss_init.c @@ -376,7 +376,7 @@ delete_seq_queue(int queue) /* - * free device informations - private_free callback of port + * free device information - private_free callback of port */ static void free_devinfo(void *private) From 895cc1e1eaf9d446e20da8788c679ffc639b50ce Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:03 +0530 Subject: [PATCH 0438/1417] ALSA: hda: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-3-hemanth.selam@gmail.com --- sound/hda/codecs/hdmi/hdmi.c | 2 +- sound/hda/codecs/realtek/alc882.c | 2 +- sound/hda/codecs/sigmatel.c | 6 +++--- sound/hda/common/codec.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c index 1e7a05c8773311..a8aac0b36c73f9 100644 --- a/sound/hda/codecs/hdmi/hdmi.c +++ b/sound/hda/codecs/hdmi/hdmi.c @@ -1671,7 +1671,7 @@ int snd_hda_hdmi_generic_pcm_prepare(struct hda_pcm_stream *hinfo, * After S3, the audio driver restores pin:cvt selections * but this can happen before gfx is ready and such selection * is overlooked by HW. Thus multiple pins can share a same - * default convertor and mute control will affect each other, + * default converter and mute control will affect each other, * which can cause a resumed audio playback become silent * after S3. */ diff --git a/sound/hda/codecs/realtek/alc882.c b/sound/hda/codecs/realtek/alc882.c index fd466b6985f05e..800c3d6f5134bf 100644 --- a/sound/hda/codecs/realtek/alc882.c +++ b/sound/hda/codecs/realtek/alc882.c @@ -372,7 +372,7 @@ static const struct hda_fixup alc882_fixups[] = { [ALC883_FIXUP_ACER_EAPD] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { - /* eanable EAPD on Acer laptops */ + /* enable EAPD on Acer laptops */ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, { 0x20, AC_VERB_SET_PROC_COEF, 0x3050 }, { } diff --git a/sound/hda/codecs/sigmatel.c b/sound/hda/codecs/sigmatel.c index ee3bd21adc366a..9a8ba43667f770 100644 --- a/sound/hda/codecs/sigmatel.c +++ b/sound/hda/codecs/sigmatel.c @@ -361,7 +361,7 @@ static unsigned int stac_vref_led_power_filter(struct hda_codec *codec, return snd_hda_gen_path_power_filter(codec, nid, power_state); } -/* update mute-LED accoring to the master switch */ +/* update mute-LED according to the master switch */ static void stac_update_led_status(struct hda_codec *codec, bool muted) { struct sigmatel_spec *spec = codec->spec; @@ -1710,7 +1710,7 @@ static const struct hda_pintbl ref92hd73xx_pin_configs[] = { { 0x11, 0x01014030 }, // CD in { 0x12, 0x02319040 }, - // Digial Mic ins + // Digital Mic ins { 0x13, 0x90a000f0 }, { 0x14, 0x90a000f0 }, // Digital outs @@ -3817,7 +3817,7 @@ static const struct hda_pintbl dell_3st_pin_configs[] = { static void stac927x_fixup_ref_no_jd(struct hda_codec *codec, const struct hda_fixup *fix, int action) { - /* no jack detecion for ref-no-jd model */ + /* no jack detection for ref-no-jd model */ if (action == HDA_FIXUP_ACT_PRE_PROBE) codec->no_jack_detect = 1; } diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 7d17d773cfbf3a..b1965fb6681476 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -3772,7 +3772,7 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec, EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_open); /** - * snd_hda_multi_out_analog_prepare - Preapre the analog outputs. + * snd_hda_multi_out_analog_prepare - Prepare the analog outputs. * @codec: the HDA codec * @mout: hda_multi_out object * @stream_tag: stream tag to assign From 121f6bb1457d2f79d1139ba951dd63c1f6c6dfdb Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:04 +0530 Subject: [PATCH 0439/1417] ALSA: usb-audio: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-4-hemanth.selam@gmail.com --- sound/usb/mixer.c | 4 ++-- sound/usb/mixer_us16x08.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index ecaa8bc08d7ca9..33a6a12814106d 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -786,7 +786,7 @@ static int parse_term_uac2_iterm_unit(struct mixer_build *state, return err; /* save input term properties after recursion, - * to ensure they are not overriden by the recursion calls + * to ensure they are not overridden by the recursion calls */ term->id = id; term->type = le16_to_cpu(d->wTerminalType); @@ -809,7 +809,7 @@ static int parse_term_uac3_iterm_unit(struct mixer_build *state, return err; /* save input term properties after recursion, - * to ensure they are not overriden by the recursion calls + * to ensure they are not overridden by the recursion calls */ term->id = id; term->type = le16_to_cpu(d->wTerminalType); diff --git a/sound/usb/mixer_us16x08.h b/sound/usb/mixer_us16x08.h index 7b8583dd3b0701..e98fe55265d150 100644 --- a/sound/usb/mixer_us16x08.h +++ b/sound/usb/mixer_us16x08.h @@ -60,7 +60,7 @@ #define SND_US16X08_ID_EQHIGHMIDLEVEL 0x03 #define SND_US16X08_ID_EQHIGHLEVEL 0x04 -/* EQ frequence IDs */ +/* EQ frequency IDs */ #define SND_US16X08_ID_EQLOWFREQ 0x11 #define SND_US16X08_ID_EQLOWMIDFREQ 0x12 #define SND_US16X08_ID_EQHIGHMIDFREQ 0x13 From 6dfd44c8b49bc8e1320ed5763e4fc2e73bae2bab Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:05 +0530 Subject: [PATCH 0440/1417] ALSA: i2c: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-5-hemanth.selam@gmail.com --- sound/i2c/other/ak4113.c | 2 +- sound/i2c/other/ak4114.c | 2 +- sound/i2c/other/ak4xxx-adda.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/i2c/other/ak4113.c b/sound/i2c/other/ak4113.c index 6ef5fffda8d71c..9405d985a87ddb 100644 --- a/sound/i2c/other/ak4113.c +++ b/sound/i2c/other/ak4113.c @@ -130,7 +130,7 @@ void snd_ak4113_reinit(struct ak4113 *chip) scoped_guard(mutex, &chip->reinit_mutex) { ak4113_init_regs(chip); } - /* bring up statistics / event queing */ + /* bring up statistics / event queuing */ if (atomic_dec_and_test(&chip->wq_processing)) schedule_delayed_work(&chip->work, HZ / 10); } diff --git a/sound/i2c/other/ak4114.c b/sound/i2c/other/ak4114.c index 71efb29b6c7c0e..4c3dbbf8c3161a 100644 --- a/sound/i2c/other/ak4114.c +++ b/sound/i2c/other/ak4114.c @@ -135,7 +135,7 @@ void snd_ak4114_reinit(struct ak4114 *chip) scoped_guard(mutex, &chip->reinit_mutex) { ak4114_init_regs(chip); } - /* bring up statistics / event queing */ + /* bring up statistics / event queuing */ if (atomic_dec_and_test(&chip->wq_processing)) schedule_delayed_work(&chip->work, HZ / 10); } diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c index 9dd36b82a6ac47..e7410f0f866089 100644 --- a/sound/i2c/other/ak4xxx-adda.c +++ b/sound/i2c/other/ak4xxx-adda.c @@ -147,7 +147,7 @@ EXPORT_SYMBOL(snd_akm4xxx_reset); * Volume conversion table for non-linear volumes * from -63.5dB (mute) to 0dB step 0.5dB * - * Used for AK4524/AK4620 input/ouput attenuation, AK4528, and + * Used for AK4524/AK4620 input/output attenuation, AK4528, and * AK5365 input attenuation */ static const unsigned char vol_cvt_datt[128] = { From 668f4f105e95a746111e8aa80c75276f6222fcb0 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:06 +0530 Subject: [PATCH 0441/1417] ALSA: emu10k1: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-6-hemanth.selam@gmail.com --- include/sound/emu10k1.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 4f94565c9d15af..6474239a11c1e7 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -633,8 +633,8 @@ SUB_REG(PEFE, FILTERAMOUNT, 0x000000ff) /* Filter envlope amount */ #define A_ADCCR_RCHANENABLE 0x00000020 #define A_ADCCR_LCHANENABLE 0x00000010 -#define A_ADCCR_SAMPLERATE_MASK 0x0000000F /* Audigy sample rate convertor output rate */ -#define ADCCR_SAMPLERATE_MASK 0x00000007 /* Sample rate convertor output rate */ +#define A_ADCCR_SAMPLERATE_MASK 0x0000000F /* Audigy sample rate converter output rate */ +#define ADCCR_SAMPLERATE_MASK 0x00000007 /* Sample rate converter output rate */ #define ADCCR_SAMPLERATE_48 0x00000000 /* 48kHz sample rate */ #define ADCCR_SAMPLERATE_44 0x00000001 /* 44.1kHz sample rate */ #define ADCCR_SAMPLERATE_32 0x00000002 /* 32kHz sample rate */ From 96d3a4224ddd70d0c6d187138fcf77f2632b4af0 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:07 +0530 Subject: [PATCH 0442/1417] ALSA: emux: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-7-hemanth.selam@gmail.com --- include/sound/emux_legacy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/sound/emux_legacy.h b/include/sound/emux_legacy.h index 1127e30d33bbf3..55a7fbfb9bfa1a 100644 --- a/include/sound/emux_legacy.h +++ b/include/sound/emux_legacy.h @@ -5,7 +5,7 @@ /* * Copyright (c) 1999-2000 Takashi Iwai * - * Definitions of OSS compatible headers for Emu8000 device informations + * Definitions of OSS compatible headers for Emu8000 device information */ #include From 0dd77b42463e74a1ca4726afea3929d26f470041 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:08 +0530 Subject: [PATCH 0443/1417] ALSA: firewire: bebob: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-8-hemanth.selam@gmail.com --- sound/firewire/bebob/bebob_hwdep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c index 216d1fceb6e737..1c523525fad2ea 100644 --- a/sound/firewire/bebob/bebob_hwdep.c +++ b/sound/firewire/bebob/bebob_hwdep.c @@ -8,7 +8,7 @@ /* * This codes give three functionality. * - * 1.get firewire node infomation + * 1.get firewire node information * 2.get notification about starting/stopping stream * 3.lock/unlock stream */ From f86c83624a060b56e5d05828a98704534254b099 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:09 +0530 Subject: [PATCH 0444/1417] ALSA: opti9xx: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-9-hemanth.selam@gmail.com --- sound/isa/opti9xx/opti92x-ad1848.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/isa/opti9xx/opti92x-ad1848.c b/sound/isa/opti9xx/opti92x-ad1848.c index abaa3ed3ab5c2f..8a6db40ff89384 100644 --- a/sound/isa/opti9xx/opti92x-ad1848.c +++ b/sound/isa/opti9xx/opti92x-ad1848.c @@ -378,7 +378,7 @@ static int snd_opti9xx_configure(struct snd_opti9xx *chip, */ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(21), 0x82, 0xff); /* - * This bit sets OPTI931 to automaticaly select FM + * This bit sets OPTI931 to automatically select FM * or digital input signal. */ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(26), 0x01, 0x01); From ee46d680ec067e8842ebec739781c46cc1f8d388 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:10 +0530 Subject: [PATCH 0445/1417] ALSA: hal2: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-10-hemanth.selam@gmail.com --- sound/mips/hal2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/mips/hal2.c b/sound/mips/hal2.c index 2beb6a0dc3b7af..84f265335cf9c9 100644 --- a/sound/mips/hal2.c +++ b/sound/mips/hal2.c @@ -375,7 +375,7 @@ static void hal2_setup_dac(struct snd_hal2 *hal2) hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECTX); /* Setup the HAL2 for playback */ hal2_set_dac_rate(hal2); - /* Set endianess */ + /* Set endianness */ hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECTX); /* Set DMA bus */ hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr)); @@ -400,7 +400,7 @@ static void hal2_setup_adc(struct snd_hal2 *hal2) hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECR); /* Setup the HAL2 for record */ hal2_set_adc_rate(hal2); - /* Set endianess */ + /* Set endianness */ hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECR); /* Set DMA bus */ hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr)); From 73208e15dd559d2163425b09358aa0eda730d907 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:11 +0530 Subject: [PATCH 0446/1417] ALSA: ppc: tumbler: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-11-hemanth.selam@gmail.com --- sound/ppc/tumbler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c index 139c7b25927a97..7ba52e7a24d7d8 100644 --- a/sound/ppc/tumbler.c +++ b/sound/ppc/tumbler.c @@ -1134,7 +1134,7 @@ static long tumbler_find_device(const char *device, const char *platform, gp->active_val = 0x4; gp->inactive_val = 0x5; /* Here are some crude hacks to extract the GPIO polarity and - * open collector informations out of the do-platform script + * open collector information out of the do-platform script * as we don't yet have an interpreter for these things */ if (platform) From ea68d0abc10b5a59841548c7efc62485c2f95ce8 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:12 +0530 Subject: [PATCH 0447/1417] ALSA: ac97: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-12-hemanth.selam@gmail.com --- sound/pci/ac97/ac97_codec.c | 2 +- sound/pci/ac97/ac97_patch.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c index 0bb65be021d973..207ce083593faa 100644 --- a/sound/pci/ac97/ac97_codec.c +++ b/sound/pci/ac97/ac97_codec.c @@ -2960,7 +2960,7 @@ int snd_ac97_tune_hardware(struct snd_ac97 *ac97, { int result; - /* quirk overriden? */ + /* quirk overridden? */ if (override && strcmp(override, "-1") && strcmp(override, "default")) { result = apply_quirk_str(ac97, override); if (result < 0) diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c index 64cc39dd20083c..8577a2e0510c48 100644 --- a/sound/pci/ac97/ac97_patch.c +++ b/sound/pci/ac97/ac97_patch.c @@ -430,7 +430,7 @@ static int patch_yamaha_ymf753(struct snd_ac97 * ac97) /* Patch for Yamaha YMF753, Copyright (c) by David Shust, dshust@shustring.com. This chip has nonstandard and extended behaviour with regard to its S/PDIF output. The AC'97 spec states that the S/PDIF signal is to be output at pin 48. - The YMF753 will ouput the S/PDIF signal to pin 43, 47 (EAPD), or 48. + The YMF753 will output the S/PDIF signal to pin 43, 47 (EAPD), or 48. By default, no output pin is selected, and the S/PDIF signal is not output. There is also a bit to mute S/PDIF output in a vendor-specific register. */ From 4b3a2df06fbedca410497aceb6a15cbdd1848b28 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:13 +0530 Subject: [PATCH 0448/1417] ALSA: au88x0: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-13-hemanth.selam@gmail.com --- sound/pci/au88x0/au88x0.h | 2 +- sound/pci/au88x0/au88x0_mpu401.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/au88x0/au88x0.h b/sound/pci/au88x0/au88x0.h index 6cbb2bc4a0483a..f97a0303da3e8f 100644 --- a/sound/pci/au88x0/au88x0.h +++ b/sound/pci/au88x0/au88x0.h @@ -28,7 +28,7 @@ /* Vortex MPU401 defines. */ #define MIDI_CLOCK_DIV 0x61 -/* Standart MPU401 defines. */ +/* Standard MPU401 defines. */ #define MPU401_RESET 0xff #define MPU401_ENTER_UART 0x3f #define MPU401_ACK 0xfe diff --git a/sound/pci/au88x0/au88x0_mpu401.c b/sound/pci/au88x0/au88x0_mpu401.c index 164f6b7039ab56..dff4bf80ab9653 100644 --- a/sound/pci/au88x0/au88x0_mpu401.c +++ b/sound/pci/au88x0/au88x0_mpu401.c @@ -22,7 +22,7 @@ /* Vortex MPU401 defines. */ #define MIDI_CLOCK_DIV 0x61 -/* Standart MPU401 defines. */ +/* Standard MPU401 defines. */ #define MPU401_RESET 0xff #define MPU401_ENTER_UART 0x3f #define MPU401_ACK 0xfe From f1e7c286f48111dd21149cbf8d4979528c2e7e7d Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:14 +0530 Subject: [PATCH 0449/1417] ALSA: aw2: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-14-hemanth.selam@gmail.com --- sound/pci/aw2/aw2-saa7146.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/aw2/aw2-saa7146.c b/sound/pci/aw2/aw2-saa7146.c index c84f1a45194f17..6857a0ec29248a 100644 --- a/sound/pci/aw2/aw2-saa7146.c +++ b/sound/pci/aw2/aw2-saa7146.c @@ -147,7 +147,7 @@ void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip, unsigned long dw_page, dw_limit; /* Configure DMA for substream - Configuration informations: ALSA has allocated continuous memory + Configuration information: ALSA has allocated continuous memory pages. So we don't need to use MMU of saa7146. */ @@ -204,7 +204,7 @@ void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip, unsigned long dw_page, dw_limit; /* Configure DMA for substream - Configuration informations: ALSA has allocated continuous memory + Configuration information: ALSA has allocated continuous memory pages. So we don't need to use MMU of saa7146. */ From 1e1eeab47c3223676a61fa49869d45636f68fff3 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:15 +0530 Subject: [PATCH 0450/1417] ALSA: cs4281: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-15-hemanth.selam@gmail.com --- sound/pci/cs4281.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index f51f4bb6376646..27ceda6278c5e9 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -324,7 +324,7 @@ MODULE_PARM_DESC(dual_codec, "Secondary Codec ID (0 = disabled)."); #define BA0_SSCR_XLPSRC (1<<8) /* External SRC Loopback Mode */ #define BA0_SSCR_LPSRC (1<<7) /* SRC Loopback Mode */ #define BA0_SSCR_CDTX (1<<5) /* CD Transfer Data */ -#define BA0_SSCR_HVC (1<<3) /* Harware Volume Control Enable */ +#define BA0_SSCR_HVC (1<<3) /* Hardware Volume Control Enable */ #define BA0_FMLVC 0x0754 /* FM Synthesis Left Volume Control */ #define BA0_FMRVC 0x0758 /* FM Synthesis Right Volume Control */ From 54999c03def8f667f9105d2f6e75e82e5d05a759 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:16 +0530 Subject: [PATCH 0451/1417] ALSA: cs46xx: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-16-hemanth.selam@gmail.com --- sound/pci/cs46xx/cs46xx_lib.c | 6 +++--- sound/pci/cs46xx/dsp_spos_scb_lib.c | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c index 19a6927c079dd4..a16586f7d9138e 100644 --- a/sound/pci/cs46xx/cs46xx_lib.c +++ b/sound/pci/cs46xx/cs46xx_lib.c @@ -315,7 +315,7 @@ int snd_cs46xx_download(struct snd_cs46xx *chip, dst = chip->region.idx[bank+1].remap_addr + offset; len /= sizeof(u32); - /* writel already converts 32-bit value to right endianess */ + /* writel already converts 32-bit value to right endianness */ while (len-- > 0) { writel(*src++, dst); dst += sizeof(u32); @@ -474,7 +474,7 @@ int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip, dst = chip->region.idx[bank+1].remap_addr + offset; len /= sizeof(u32); - /* writel already converts 32-bit value to right endianess */ + /* writel already converts 32-bit value to right endianness */ while (len-- > 0) { writel(0, dst); dst += sizeof(u32); @@ -3039,7 +3039,7 @@ static int snd_cs46xx_chip_init(struct snd_cs46xx *chip) #endif /* - * Assert the vaid frame signal so that we can start sending commands + * Assert the valid frame signal so that we can start sending commands * to the AC97 codec. */ snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); diff --git a/sound/pci/cs46xx/dsp_spos_scb_lib.c b/sound/pci/cs46xx/dsp_spos_scb_lib.c index fd19365026b4a6..71eee5cb478f40 100644 --- a/sound/pci/cs46xx/dsp_spos_scb_lib.c +++ b/sound/pci/cs46xx/dsp_spos_scb_lib.c @@ -160,15 +160,15 @@ void cs46xx_dsp_remove_scb (struct snd_cs46xx *chip, struct dsp_scb_descriptor * { struct dsp_spos_instance * ins = chip->dsp_spos_instance; - /* check integrety */ + /* check integrity */ if (snd_BUG_ON(scb->index < 0 || scb->index >= ins->nscb || (ins->scbs + scb->index) != scb)) return; #if 0 - /* can't remove a SCB with childs before - removing childs first */ + /* can't remove a SCB with children before + removing children first */ if (snd_BUG_ON(scb->sub_list_ptr != ins->the_null_scb || scb->next_scb_ptr != ins->the_null_scb)) goto _end; @@ -1656,7 +1656,7 @@ int cs46xx_dsp_disable_spdif_out (struct snd_cs46xx *chip) return -EBUSY; } - /* check integrety */ + /* check integrity */ if (snd_BUG_ON(!ins->asynch_tx_scb)) return -EINVAL; if (snd_BUG_ON(!ins->spdif_pcm_input_scb)) From 77db026cb8d90529e856dcfd0304b4eff5ce89b7 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:17 +0530 Subject: [PATCH 0452/1417] ALSA: echoaudio: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-17-hemanth.selam@gmail.com --- sound/pci/echoaudio/echoaudio.c | 4 ++-- sound/pci/echoaudio/echoaudio.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c index 214d68657707d3..8b4637231debe0 100644 --- a/sound/pci/echoaudio/echoaudio.c +++ b/sound/pci/echoaudio/echoaudio.c @@ -270,7 +270,7 @@ static int pcm_open(struct snd_pcm_substream *substream, return -ENOMEM; pipe->index = -1; /* Not configured yet */ - /* Set up hw capabilities and contraints */ + /* Set up hw capabilities and constraints */ memcpy(&pipe->hw, &pcm_hardware_skel, sizeof(struct snd_pcm_hardware)); dev_dbg(chip->card->dev, "max_channels=%d\n", max_channels); pipe->constr.list = channels_list; @@ -1720,7 +1720,7 @@ static const struct snd_kcontrol_new snd_echo_vumeters = { -/*** Channels info - it exports informations about the number of channels ***/ +/*** Channels info - it exports information about the number of channels ***/ static int snd_echo_channels_info_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { diff --git a/sound/pci/echoaudio/echoaudio.h b/sound/pci/echoaudio/echoaudio.h index 7a5744bf3155a0..ac4507b7cc7856 100644 --- a/sound/pci/echoaudio/echoaudio.h +++ b/sound/pci/echoaudio/echoaudio.h @@ -394,8 +394,8 @@ struct echoaudio { u16 digital_modes; /* Bitmask of supported modes * (see ECHOCAPS_HAS_DIGITAL_MODE_*) */ - u16 input_clock_types; /* Suppoted input clock types */ - u16 output_clock_types; /* Suppoted output clock types - + u16 input_clock_types; /* Supported input clock types */ + u16 output_clock_types; /* Supported output clock types - * Layla20 only */ u16 device_id, subdevice_id; u16 *dsp_code; /* Current DSP code loaded, From 948f8c97b1ba941ac59e4dcc149513bbcf7724c6 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:18 +0530 Subject: [PATCH 0453/1417] ALSA: lx6464es: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-18-hemanth.selam@gmail.com --- sound/pci/lx6464es/lx_core.c | 2 +- sound/pci/lx6464es/lx_defs.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/lx6464es/lx_core.c b/sound/pci/lx6464es/lx_core.c index 9909f106787161..ae265dd193331d 100644 --- a/sound/pci/lx6464es/lx_core.c +++ b/sound/pci/lx6464es/lx_core.c @@ -364,7 +364,7 @@ int lx_dsp_get_mac(struct lx6464es *chip) macmsb = lx_dsp_reg_read(chip, eReg_ADMACESMSB) & 0x00FFFFFF; maclsb = lx_dsp_reg_read(chip, eReg_ADMACESLSB) & 0x00FFFFFF; - /* todo: endianess handling */ + /* todo: endianness handling */ chip->mac_address[5] = ((u8 *)(&maclsb))[0]; chip->mac_address[4] = ((u8 *)(&maclsb))[1]; chip->mac_address[3] = ((u8 *)(&maclsb))[2]; diff --git a/sound/pci/lx6464es/lx_defs.h b/sound/pci/lx6464es/lx_defs.h index eca5367ba561c7..89e433d3f7a2ff 100644 --- a/sound/pci/lx6464es/lx_defs.h +++ b/sound/pci/lx6464es/lx_defs.h @@ -315,7 +315,7 @@ enum stream_flags { #define ED_XILINX_ERROR (ED_RT | 0x07) #define ED_COBRANET_ITF_NOT_RESPONDING (ED_RT | 0x08) -/* Complete BOARD error code for the invaid standard object class */ +/* Complete BOARD error code for the invalid standard object class */ #define EB_ISO (ERROR_VALUE | E_SOURCE_BOARD | \ E_CLASS_INVALID_STD_OBJECT) #define EB_INVALID_EFFECT (EB_ISO | 0x00) From b86af3864b395ffc3484839408644a0bb614ee51 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:19 +0530 Subject: [PATCH 0454/1417] ALSA: pcxhr: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-19-hemanth.selam@gmail.com --- sound/pci/pcxhr/pcxhr_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/pcxhr/pcxhr_core.c b/sound/pci/pcxhr/pcxhr_core.c index b3b9ab4f303ee5..f855d3bfad4b63 100644 --- a/sound/pci/pcxhr/pcxhr_core.c +++ b/sound/pci/pcxhr/pcxhr_core.c @@ -802,7 +802,7 @@ static int pcxhr_prepair_pipe_start(struct pcxhr_mgr *mgr, err); return err; } - /* if the pipe couldn't be prepaired for start, + /* if the pipe couldn't be prepared for start, * retry it later */ if (rmh.stat[0] == 0) @@ -913,7 +913,7 @@ int pcxhr_set_pipe_state(struct pcxhr_mgr *mgr, int playback_mask, if (err) return err; if (state == 0) - break; /* success, all pipes prepaired */ + break; /* success, all pipes prepared */ mdelay(1); /* wait 1 millisecond and retry */ } } else { From 515b9e296462033cece3a7918ecfb89e96f492ed Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:20 +0530 Subject: [PATCH 0455/1417] ALSA: riptide: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-20-hemanth.selam@gmail.com --- sound/pci/riptide/riptide.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/riptide/riptide.c b/sound/pci/riptide/riptide.c index 99c00e46ce23dc..697fece744e7f5 100644 --- a/sound/pci/riptide/riptide.c +++ b/sound/pci/riptide/riptide.c @@ -264,7 +264,7 @@ MODULE_PARM_DESC(opl3_port, "OPL3 port # for Riptide driver."); #define SEND_SI2S(p,b) sendcmd(p,PARM,SI2S,WORD2(b),RET(0)) #define EOB_STATUS 0x80000000 /* status flags : block boundary */ -#define EOS_STATUS 0x40000000 /* : stoppped */ +#define EOS_STATUS 0x40000000 /* : stopped */ #define EOC_STATUS 0x20000000 /* : stream end */ #define ERR_STATUS 0x10000000 #define EMPTY_STATUS 0x08000000 @@ -453,7 +453,7 @@ struct snd_riptide { int in_suspend; }; -struct sgd { /* scatter gather desriptor */ +struct sgd { /* scatter gather descriptor */ __le32 dwNextLink; __le32 dwSegPtrPhys; __le32 dwSegLen; From 648324a4d3421828d7d40c93782ac980b16a8ba3 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:21 +0530 Subject: [PATCH 0456/1417] ALSA: rme32: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-21-hemanth.selam@gmail.com --- sound/pci/rme32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/rme32.c b/sound/pci/rme32.c index 454a30a2c07e17..1d5390621908e9 100644 --- a/sound/pci/rme32.c +++ b/sound/pci/rme32.c @@ -8,7 +8,7 @@ * Thanks to : Anders Torger , * Henk Hesselink * for writing the digi96-driver - * and RME for all informations. + * and RME for all information. * * **************************************************************************** * From 17e335c45b61069caaeeafe879554f743635eec2 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:22 +0530 Subject: [PATCH 0457/1417] ALSA: hdsp: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-22-hemanth.selam@gmail.com --- sound/pci/rme9652/hdsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c index 31cc2d91c8d2f8..0ade7305773183 100644 --- a/sound/pci/rme9652/hdsp.c +++ b/sound/pci/rme9652/hdsp.c @@ -3664,7 +3664,7 @@ snd_hdsp_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) snd_iprintf(buffer, "\n"); - /* Informations about H9632 specific controls */ + /* Information about H9632 specific controls */ if (hdsp->io_type == H9632) { char *tmp; From 162c237ca2fc5a2619c7fc156941f681b9acf8dd Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:23 +0530 Subject: [PATCH 0458/1417] ALSA: trident: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-23-hemanth.selam@gmail.com --- sound/pci/trident/trident_memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/trident/trident_memory.c b/sound/pci/trident/trident_memory.c index 81f6348191dc44..4ed0e7d683228c 100644 --- a/sound/pci/trident/trident_memory.c +++ b/sound/pci/trident/trident_memory.c @@ -27,7 +27,7 @@ #if PAGE_SIZE == 4096 /* page size == SNDRV_TRIDENT_PAGE_SIZE */ #define ALIGN_PAGE_SIZE PAGE_SIZE /* minimum page size for allocation */ -#define MAX_ALIGN_PAGES SNDRV_TRIDENT_MAX_PAGES /* maxmium aligned pages */ +#define MAX_ALIGN_PAGES SNDRV_TRIDENT_MAX_PAGES /* maximum aligned pages */ /* fill TLB entrie(s) corresponding to page with ptr */ #define set_tlb_bus(trident,page,addr) __set_tlb_bus(trident,page,addr) /* fill TLB entrie(s) corresponding to page with silence pointer */ From ad563e61f576b269e09e53232a1809541c87c8c1 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 15 Sep 2026 14:14:24 +0530 Subject: [PATCH 0459/1417] ALSA: vx: fix typos in comments Correct spelling mistakes in comments. No functional change. Signed-off-by: Hemanth Selam Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260915084424.1007757-24-hemanth.selam@gmail.com --- sound/pci/vx222/vx222_ops.c | 2 +- sound/pcmcia/vx/vxp_ops.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/pci/vx222/vx222_ops.c b/sound/pci/vx222/vx222_ops.c index b6459dbdb1b050..40f3716c478f49 100644 --- a/sound/pci/vx222/vx222_ops.c +++ b/sound/pci/vx222/vx222_ops.c @@ -431,7 +431,7 @@ static int vx2_load_dsp(struct vx_core *vx, int index, const struct firmware *ds /* * vx_test_and_ack - test and acknowledge interrupt * - * called from irq hander, too + * called from irq handler, too * * spinlock held! */ diff --git a/sound/pcmcia/vx/vxp_ops.c b/sound/pcmcia/vx/vxp_ops.c index 4211e7239138c4..e52b61125629d2 100644 --- a/sound/pcmcia/vx/vxp_ops.c +++ b/sound/pcmcia/vx/vxp_ops.c @@ -265,7 +265,7 @@ static int vxp_load_dsp(struct vx_core *vx, int index, const struct firmware *fw /* * vx_test_and_ack - test and acknowledge interrupt * - * called from irq hander, too + * called from irq handler, too * * spinlock held! */ From 18a6fe05fb6e18de29fa90d388bb34044114b3d8 Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Fri, 11 Sep 2026 13:50:21 +0300 Subject: [PATCH 0460/1417] net: bridge: mst: move switchdev call outside rcu This is a follow-up of one of sashiko's pre-existing bug reports. br_mst_set_state() calls switchdev_port_attr_set() for nonzero MSTIs while holding rcu_read_lock() which invokes the blocking switchdev notifier chain and may sleep. Nonzero MSTI changes come from netlink with rtnl held. Move the switchdev call before entering the rcu section and assert that rtnl is held. The call cannot be deferred because netlink needs its error and extack. Also DSA reads the old bridge MST state during the callback and checks it. A deferred callback will be late and will see the updated state. Fixes: 3a7c1661ae13 ("net: bridge: mst: fix vlan use-after-free") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260911105021.1385934-1-razor@blackwall.org Signed-off-by: Paolo Abeni --- net/bridge/br_mst.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/net/bridge/br_mst.c b/net/bridge/br_mst.c index 43a300ae6bfafc..1654efd3045b0a 100644 --- a/net/bridge/br_mst.c +++ b/net/bridge/br_mst.c @@ -107,21 +107,24 @@ int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state, struct net_bridge_vlan *v; int err = 0; - rcu_read_lock(); - vg = nbp_vlan_group_rcu(p); - if (!vg) - goto out; - /* MSTI 0 (CST) state changes are notified via the regular - * SWITCHDEV_ATTR_ID_PORT_STP_STATE. + * SWITCHDEV_ATTR_ID_PORT_STP_STATE. All other MSTIs are handled via + * netlink with RTNL held */ if (msti) { + ASSERT_RTNL(); + err = switchdev_port_attr_set(p->dev, &attr, extack); if (err && err != -EOPNOTSUPP) goto out; + err = 0; } - err = 0; + rcu_read_lock(); + vg = nbp_vlan_group_rcu(p); + if (!vg) + goto out_rcu_unlock; + list_for_each_entry_rcu(v, &vg->vlan_list, vlist) { if (v->brvlan->msti != msti) continue; @@ -129,8 +132,9 @@ int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state, br_mst_vlan_set_state(vg, v, state); } -out: +out_rcu_unlock: rcu_read_unlock(); +out: return err; } From 2cef2588c995722a901368def30befeef9ae55c6 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 12 Sep 2026 14:09:19 -0400 Subject: [PATCH 0461/1417] net/sched: hhf: cap hh_flows_limit at change time hhf_change() stores TCA_HHF_HH_FLOWS_LIMIT with no upper bound. A huge hh_flows_limit lets each new heavy-hitter flow pass the hh_flows_current_cnt check in alloc_new_hh() and forces a fixed-size kzalloc(GFP_ATOMIC) per flow under spoofed traffic, for unbounded memory growth. Bound the attribute with NLA_POLICY_MAX() at 2*HH_FLOWS_CNT (the hhf_init() default) and report the rejected value via extack. The deprecated nested parse is kept: legacy tc does not set NLA_F_NESTED on TCA_OPTIONS. Configs relying on hh_limit above the default were relying on unbounded, unsafe behaviour and are not supported going forward. hhf_init() also ran hhf_change() before setting the default hh_flows_limit, so a user-supplied hh_limit at add time was clobbered back to 2048. Set the default before hhf_change() so the configured value sticks. This is a follow-up to commit eb56a495f59b ("net/sched: hhf: clamp quantum in change and init paths"), which bounded the quantum of the same qdisc; the hh_flows_limit bound is the remaining unbounded knob of that series' scope. Conditions to recreate the bug: CAP_NET_ADMIN in a user namespace; tc qdisc change dev X root hhf hh_limit 4294967295 succeeds and the value is echoed by tc qdisc show, unbounding heavy-hitter flow allocations; also tc qdisc add dev X root hhf hh_limit 500 stores 2048 instead of 500. Fixes: 10239edf86f1 ("net-qdisc-hhf: Heavy-Hitter Filter (HHF) qdisc") Cc: stable@vger.kernel.org Reported-by: Sashiko (gemini) Closes: https://sashiko.dev/#/patchset/20260822195509.112717-1-jhs@mojatatu.com Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-B855.v1.20260911153152@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_hhf.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/net/sched/sch_hhf.c b/net/sched/sch_hhf.c index fc72f825fbd92a..5dec1ed969adf7 100644 --- a/net/sched/sch_hhf.c +++ b/net/sched/sch_hhf.c @@ -527,7 +527,7 @@ static void hhf_destroy(struct Qdisc *sch) static const struct nla_policy hhf_policy[TCA_HHF_MAX + 1] = { [TCA_HHF_BACKLOG_LIMIT] = { .type = NLA_U32 }, [TCA_HHF_QUANTUM] = { .type = NLA_U32 }, - [TCA_HHF_HH_FLOWS_LIMIT] = { .type = NLA_U32 }, + [TCA_HHF_HH_FLOWS_LIMIT] = NLA_POLICY_MAX(NLA_U32, 2 * HH_FLOWS_CNT), [TCA_HHF_RESET_TIMEOUT] = { .type = NLA_U32 }, [TCA_HHF_ADMIT_BYTES] = { .type = NLA_U32 }, [TCA_HHF_EVICT_TIMEOUT] = { .type = NLA_U32 }, @@ -546,7 +546,7 @@ static int hhf_change(struct Qdisc *sch, struct nlattr *opt, u32 new_hhf_non_hh_weight = q->hhf_non_hh_weight; err = nla_parse_nested_deprecated(tb, TCA_HHF_MAX, opt, hhf_policy, - NULL); + extack); if (err < 0) return err; @@ -624,6 +624,9 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt, q->hhf_evict_timeout = HZ; /* 1 sec */ q->hhf_non_hh_weight = 2; + /* Cap max active HHs at twice len of hh_flows table. */ + q->hh_flows_limit = 2 * HH_FLOWS_CNT; + if (opt) { int err = hhf_change(sch, opt, extack); @@ -639,8 +642,6 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt, for (i = 0; i < HH_FLOWS_CNT; i++) INIT_LIST_HEAD(&q->hh_flows[i]); - /* Cap max active HHs at twice len of hh_flows table. */ - q->hh_flows_limit = 2 * HH_FLOWS_CNT; q->hh_flows_overlimit = 0; q->hh_flows_total_cnt = 0; q->hh_flows_current_cnt = 0; From 0654f4dba1fbc697f2653aba30cd68587fcbf10e Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 12 Sep 2026 14:09:20 -0400 Subject: [PATCH 0462/1417] selftests/tc-testing: add hhf hh_limit cap tests Cover the new TCA_HHF_HH_FLOWS_LIMIT bound: values above 2*HH_FLOWS_CNT (4294967295, 65536, 2049) are rejected with the configured limit left untouched on both the change and the add path, the boundary value 2048 is accepted (installed at 100 first so the boundary change is load-bearing), and an add-time hh_limit 500 is preserved instead of being clobbered by the default. Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-B855.v1.20260911153152@mojatatu.com.2 Signed-off-by: Paolo Abeni --- .../tc-tests/qdiscs/hhf_flows_limit.json | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 tools/testing/selftests/tc-testing/tc-tests/qdiscs/hhf_flows_limit.json diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hhf_flows_limit.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hhf_flows_limit.json new file mode 100644 index 00000000000000..44538b9266b65b --- /dev/null +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hhf_flows_limit.json @@ -0,0 +1,128 @@ +[ + { + "id": "e3cc", + "name": "HHF hh_limit rejects value above 2*HH_FLOWS_CNT cap (4294967295)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY handle 1: root hhf" + ], + "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 1: root hhf hh_limit 4294967295", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 2048", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "f681", + "name": "HHF hh_limit rejects 65536 (above 2*HH_FLOWS_CNT cap)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY handle 1: root hhf" + ], + "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 1: root hhf hh_limit 65536", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 2048", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "223d", + "name": "HHF hh_limit accepts boundary value 2048 (2*HH_FLOWS_CNT)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY handle 1: root hhf hh_limit 100" + ], + "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 1: root hhf hh_limit 2048", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 2048", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "147f", + "name": "HHF hh_limit rejects first value above cap (2049)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY handle 1: root hhf" + ], + "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 1: root hhf hh_limit 2049", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 2048", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "4d4f", + "name": "HHF add-time hh_limit 500 is preserved (init does not clobber user value)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root hhf hh_limit 500", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 500", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "ca99", + "name": "HHF add-time hh_limit 4294967295 is rejected (no qdisc installed)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root hhf hh_limit 4294967295", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root", + "matchCount": "0", + "teardown": [] + } +] From ba13f1f32d96d7ce9069ae4f32404ecde8da89c4 Mon Sep 17 00:00:00 2001 From: Meijing Zhao Date: Tue, 15 Sep 2026 20:46:41 +0800 Subject: [PATCH 0463/1417] mm: memblock: add missing HugeTLB flag name Commit 7d163a75f821 ("memblock: make HugeTLB bootmem allocation work with KHO") added MEMBLOCK_RSRV_HUGETLB but did not add the corresponding entry to flagname[]. As a result, memblock debugfs cannot report the flag by name. Add the missing RSV_HUGETLB entry. Fixes: 7d163a75f821 ("memblock: make HugeTLB bootmem allocation work with KHO") Signed-off-by: Meijing Zhao Signed-off-by: Wandun Chen Link: https://lore.kernel.org/lkml/20260821020910.3428585-2-zhaomeijing100@gmail.com/ Link: https://patch.msgid.link/20260915124641.2498280-1-chenwandun1@gmail.com Signed-off-by: Mike Rapoport (Microsoft) --- mm/memblock.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/memblock.c b/mm/memblock.c index 021db49eb7fc65..e6fdf95a0534d4 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -2886,6 +2886,7 @@ static const char * const flagname[] = { [ilog2(MEMBLOCK_RSRV_NOINIT)] = "RSV_NIT", [ilog2(MEMBLOCK_RSRV_KERN)] = "RSV_KERN", [ilog2(MEMBLOCK_KHO_SCRATCH)] = "KHO_SCRATCH", + [ilog2(MEMBLOCK_RSRV_HUGETLB)] = "RSV_HUGETLB", }; static int memblock_debug_show(struct seq_file *m, void *private) From 8e759cd1f6444a946bd1fd2b2b29eea582eea1d5 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 14 Sep 2026 01:14:01 +0000 Subject: [PATCH 0464/1417] tcp: Don't call skb_clone_and_charge_r() for close()d listener in tcp_v6_do_rcv(). tcp_v6_do_rcv() no longer calls skb_clone_and_charge_r() for TCP_LISTEN since commit 073d89808c06 ("net: fix data-races around sk->sk_forward_alloc"). However, there is still a small race window between tcp_v6_rcv() and tcp_v6_do_rcv(), where concurrent close() changes TCP_LISTEN to TCP_CLOSE, causing skb_clone_and_charge_r() to be called locklessly and resulting in the splat below. [0] Let's avoid calling skb_clone_and_charge_r() for TCP_CLOSE as well. This is fine for non-listeners because tcp_rcv_state_process() drops skb for TCP_CLOSE and opt_skb was freed immediately anyway. [0]: sk->sk_forward_alloc WARNING: net/ipv4/af_inet.c:162 at inet_sock_destruct+0x64d/0x810 net/ipv4/af_inet.c:162, CPU#1: ksoftirqd/1/28 Modules linked in: CPU: 1 UID: 0 PID: 28 Comm: ksoftirqd/1 Not tainted 7.2.0 #17 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.17.0-debian-1.17.0-1 04/01/2014 RIP: 0010:inet_sock_destruct+0x64d/0x810 net/ipv4/af_inet.c:162 Code: 3d 49 ff e9 06 fd ff ff e8 d0 5b 83 f8 90 0f 0b 90 e9 35 fe ff ff e8 c2 5b 83 f8 90 0f 0b 90 e9 c5 fe ff ff e8 b4 5b 83 f8 90 <0f> 0b 90 e9 04 ff ff ff e8 a6 5b 83 f8 90 0f 0b 90 e9 65 fe ff ff RSP: 0018:ffffc90000677bb8 EFLAGS: 00010246 RAX: 0000000000000000 RBX: ffff8880117bde80 RCX: ffffffff8957eb41 RDX: ffff88801dad5d00 RSI: ffffffff8957ec3c RDI: 0000000000000005 RBP: 00000000fffff000 R08: ffffffff8957eb41 R09: 00000000fffff000 R10: 0000000000000005 R11: 0000000000000000 R12: dffffc0000000000 R13: ffff8880117bdf10 R14: ffffffff81c08eb7 R15: 0000000000000003 FS: 0000000000000000(0000) GS:ffff8880d7ae5000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f93a1021138 CR3: 00000000207a9000 CR4: 0000000000350ef0 Call Trace: __sk_destruct+0x82/0xae0 net/core/sock.c:2356 rcu_do_batch kernel/rcu/tree.c:2645 [inline] rcu_core+0x59c/0x1100 kernel/rcu/tree.c:2897 handle_softirqs+0x1e4/0x9b0 kernel/softirq.c:622 run_ksoftirqd kernel/softirq.c:1076 [inline] run_ksoftirqd+0x38/0x60 kernel/softirq.c:1068 smpboot_thread_fn+0x458/0xc80 kernel/smpboot.c:160 kthread+0x396/0x4a0 kernel/kthread.c:436 ret_from_fork+0x8e0/0xe40 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Fixes: e994b2f0fb92 ("tcp: do not lock listener to process SYN packets") Reported-by: Taras Madan Signed-off-by: Kuniyuki Iwashima Reviewed-by: Xuanqiang Luo Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260914011420.115556-1-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/ipv6/tcp_ipv6.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index df9c29eb5c1f40..7fa4ed2fd4f166 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -1604,7 +1604,8 @@ int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb) by tcp. Feel free to propose better solution. --ANK (980728) */ - if (np->rxopt.all && sk->sk_state != TCP_LISTEN) + if (np->rxopt.all && + !((1 << sk->sk_state) & (TCPF_LISTEN | TCPF_CLOSE))) opt_skb = skb_clone_and_charge_r(skb, sk); if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */ From 83a945a529d6e002dd7339c532288a931f463dba Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sat, 12 Sep 2026 14:48:48 +0000 Subject: [PATCH 0465/1417] tcp: do not let tcp_rmem be set below 4096 We can hit a division by zero crash in tcp_rcvbuf_grow() and tcp_rcv_space_adjust(): divide error: 0000 [#1] PREEMPT SMP RIP: 0010:tcp_rcvbuf_grow+0x187/0x450 net/ipv4/tcp_input.c:939 ... grow = div_u64(((u64)rcvwin << 1) * (newval - oldval), oldval); The division uses oldval = tp->rcvq_space.space as divisor. When tp->rcvq_space.space is zero, this leads to a divide-by-zero exception. tp->rcvq_space.space is initialized in tcp_init_buffer_space(): tp->rcvq_space.space = min3(tp->rcv_ssthresh, tp->rcv_wnd, (u32)TCP_INIT_CWND * tp->advmss); If tcp_rmem[1] is configured to very small values (such as 1), sk->sk_rcvbuf is initialized to 1. Then tcp_full_space(sk), which computes (sk->sk_rcvbuf * scaling_ratio) >> 8, truncates to 0. This sets tp->window_clamp = 0, tp->rcv_ssthresh = 0, and tp->rcvq_space.space = 0. Later, when data arrives and DRS is invoked, tcp_rcvbuf_grow() divides by oldval == 0. Back in 2015, commit b1cb59cf2efe ("net: sysctl_net_core: check SNDBUF and RCVBUF for min length") ensured that net.core.rmem_default and net.core.rmem_max cannot be set below SOCK_MIN_RCVBUF. Similarly, SO_RCVBUF setsockopt enforces max_t(int, val * 2, SOCK_MIN_RCVBUF). However, net.ipv4.tcp_rmem still had .extra1 = SYSCTL_ONE, allowing arbitrarily small values. Because SOCK_MIN_RCVBUF depends on sizeof(struct sk_buff) and cacheline alignment, its value varies across architectures and configuration options. Using a fixed constant of 4096 ensures a predictable, architecture- independent lower bound that is safely above SOCK_MIN_RCVBUF everywhere and matches the documented 4K default. Fix this by setting tcp_rmem.extra1 to 4096 and updating the documentation. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260912144848.3448026-1-edumazet@google.com Signed-off-by: Paolo Abeni --- Documentation/networking/ip-sysctl.rst | 2 ++ net/ipv4/sysctl_net_ipv4.c | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index b05829e44d8fcb..f7af0286341c9b 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -874,6 +874,8 @@ tcp_rmem - vector of 3 INTEGERs: min, default, max case this value is ignored. Default: between 131072 and 32MB, depending on RAM size. + Each of the three values cannot be set below 4096. + tcp_sack - BOOLEAN Enable select acknowledgments (SACKS). diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c index 2f0363bca2a88d..e3760daa347064 100644 --- a/net/ipv4/sysctl_net_ipv4.c +++ b/net/ipv4/sysctl_net_ipv4.c @@ -51,6 +51,8 @@ static int tcp_ecn_mode_max = 5; static u32 icmp_errors_extension_mask_all = GENMASK_U8(ICMP_ERR_EXT_COUNT - 1, 0); +static int tcp_min_rcvbuf = 4096; + /* obsolete */ static int sysctl_tcp_low_latency __read_mostly; @@ -1462,7 +1464,7 @@ static const struct ctl_table ipv4_net_table[] = { .maxlen = sizeof(init_net.ipv4.sysctl_tcp_rmem), .mode = 0644, .proc_handler = proc_dointvec_minmax, - .extra1 = SYSCTL_ONE, + .extra1 = &tcp_min_rcvbuf, }, { .procname = "tcp_comp_sack_delay_ns", From aaad136d56d91252517272b68cd533e5714698d5 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sat, 1 Aug 2026 01:35:50 +0900 Subject: [PATCH 0466/1417] RISC-V: KVM: Synchronize hrtimer callback during teardown The non-Sstc hrtimer callback clears next_set before its final uses of the enclosing vCPU. If teardown observes next_set as false while the callback is still running, kvm_riscv_vcpu_timer_cancel() skips hrtimer_cancel() and kvm_destroy_vcpus() can free the vCPU before the callback enters kvm_riscv_vcpu_set_interrupt(). A guest can arm the timer with SBI TIME and request shutdown with SBI legacy shutdown or SRST. A VMM that honors KVM_EXIT_SYSTEM_EVENT and destroys the VM supplies the teardown side of the race; no post-launch host ioctl is needed to arm or request teardown. On upstream master 62cc90241548, generic KASAN reported: BUG: KASAN: slab-use-after-free in do_raw_spin_lock Write of size 4 at addr ff60000005e58898 kvm_riscv_vcpu_set_interrupt kvm_riscv_vcpu_hrtimer_expired __hrtimer_run_queues hrtimer_interrupt The object was allocated by KVM_CREATE_VCPU and freed concurrently by: kvm_destroy_vcpus kvm_arch_destroy_vm kvm_destroy_vm __fput For deterministic validation, I added mdelay(1000) immediately after the existing next_set = false assignment. This only widens the existing post-clear callback window. A no-delay trace build naturally reached the callback-after-teardown-start/before-deinit ordering in 12 of 200 runs, but 1,500 stock-kernel stress iterations did not produce a KASAN report, so natural reproduction is timing-sensitive. Always invoke hrtimer_cancel() for an initialized timer. Preserve the existing -EINVAL result when the timer is no longer set, but only after synchronizing with a running callback. With this patch, hrtimer_cancel() blocked for the full widened callback window before vCPU destruction. KASAN reported no error in 100 fixed-and-widened runs or 200 fix-only timing-sweep runs. Fixes: 3a9f66cb25e1 ("RISC-V: KVM: Add timer functionality") Cc: stable@vger.kernel.org Assisted-by: OpenAI:GPT-5.6 Signed-off-by: Myeonghun Pak Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260731163550.46991-1-mhun512@gmail.com Signed-off-by: Anup Patel --- arch/riscv/kvm/vcpu_timer.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/riscv/kvm/vcpu_timer.c b/arch/riscv/kvm/vcpu_timer.c index ae53133c7ab035..a2cd277a40596d 100644 --- a/arch/riscv/kvm/vcpu_timer.c +++ b/arch/riscv/kvm/vcpu_timer.c @@ -61,10 +61,13 @@ static enum hrtimer_restart kvm_riscv_vcpu_hrtimer_expired(struct hrtimer *h) static int kvm_riscv_vcpu_timer_cancel(struct kvm_vcpu_timer *t) { - if (!t->init_done || !t->next_set) + if (!t->init_done) return -EINVAL; hrtimer_cancel(&t->hrt); + + if (!t->next_set) + return -EINVAL; t->next_set = false; return 0; From 52c6b7d20d3e791a9e75aa2be2a467990154c7cd Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Tue, 4 Aug 2026 21:40:18 +0800 Subject: [PATCH 0467/1417] RISC-V: KVM: Fix the conversion between vsip and hvip Per AIA spec 1.0 Section 6.3.2, the interrupt numbers 13-63 shares same bit position between related VS shadow CSRs and hypervisor CSRs. So there's a shift only for SSI, STI and SEI interrupt. Currently the KVM always does a shift for all the interrupts (include LCOFI with number 13) when doing the conversion between vsip and hvip. Fix this by only doing shift the SSI, STI and SEI. Add wrappers for doing the conversion between vsip and hvip. Fixes: 16b0bde9a37c ("RISC-V: KVM: Add perf sampling support for guests") Signed-off-by: Yicong Yang Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260804134018.85497-1-yang.yicong@picoheart.com Signed-off-by: Anup Patel --- arch/riscv/include/asm/csr.h | 20 ++++++++++++++++---- arch/riscv/kvm/vcpu.c | 3 +-- arch/riscv/kvm/vcpu_onereg.c | 8 +++----- tools/arch/riscv/include/asm/csr.h | 20 ++++++++++++++++---- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h index 6c823361be8658..621f84d1898f43 100644 --- a/arch/riscv/include/asm/csr.h +++ b/arch/riscv/include/asm/csr.h @@ -188,12 +188,24 @@ #define HGATP_MODE_SHIFT HGATP32_MODE_SHIFT #endif -/* VSIP & HVIP relation */ +/* + * VSIP & HVIP relation + * + * The bit positions are same between VSIP and HVIP for interrupt + * numbers 13-63, where there's a shift for the SSI, STI and SEI. + */ #define VSIP_TO_HVIP_SHIFT (IRQ_VS_SOFT - IRQ_S_SOFT) -#define VSIP_VALID_MASK ((_AC(1, UL) << IRQ_S_SOFT) | \ +#define VSIP_BIAS_MASK ((_AC(1, UL) << IRQ_S_SOFT) | \ (_AC(1, UL) << IRQ_S_TIMER) | \ - (_AC(1, UL) << IRQ_S_EXT) | \ - (_AC(1, UL) << IRQ_PMU_OVF)) + (_AC(1, UL) << IRQ_S_EXT)) +#define VSIP_NO_BIAS_MASK (_AC(1, UL) << IRQ_PMU_OVF) +#define VSIP_VALID_MASK (VSIP_BIAS_MASK | VSIP_NO_BIAS_MASK) +#define vsip_to_hvip(_vsip) ((((_vsip) & VSIP_BIAS_MASK) << \ + VSIP_TO_HVIP_SHIFT) | \ + ((_vsip) & VSIP_NO_BIAS_MASK)) +#define hvip_to_vsip(_hvip) ((((_hvip) >> VSIP_TO_HVIP_SHIFT) & \ + VSIP_BIAS_MASK) | \ + ((_hvip) & VSIP_NO_BIAS_MASK)) /* AIA CSR bits */ #define TOPI_IID_SHIFT 16 diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c index e062ca19f9d8f3..7f1636c44d413c 100644 --- a/arch/riscv/kvm/vcpu.c +++ b/arch/riscv/kvm/vcpu.c @@ -491,8 +491,7 @@ bool kvm_riscv_vcpu_has_interrupts(struct kvm_vcpu *vcpu, u64 mask) bool ret; raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); - ie = ((vcpu->arch.guest_csr.vsie & VSIP_VALID_MASK) - << VSIP_TO_HVIP_SHIFT) & (unsigned long)mask; + ie = vsip_to_hvip(vcpu->arch.guest_csr.vsie) & (unsigned long)mask; ie |= vcpu->arch.guest_csr.vsie & ~IRQ_LOCAL_MASK & (unsigned long)mask; ret = vcpu->arch.irqs_pending[0] & ie; diff --git a/arch/riscv/kvm/vcpu_onereg.c b/arch/riscv/kvm/vcpu_onereg.c index 841f2cf8716836..f16e25098af118 100644 --- a/arch/riscv/kvm/vcpu_onereg.c +++ b/arch/riscv/kvm/vcpu_onereg.c @@ -272,7 +272,7 @@ static int kvm_riscv_vcpu_general_get_csr(struct kvm_vcpu *vcpu, if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) { kvm_riscv_vcpu_flush_interrupts(vcpu); - *out_val = (csr->hvip >> VSIP_TO_HVIP_SHIFT) & VSIP_VALID_MASK; + *out_val = hvip_to_vsip(csr->hvip); *out_val |= csr->hvip & ~IRQ_LOCAL_MASK; } else *out_val = ((unsigned long *)csr)[reg_num]; @@ -293,10 +293,8 @@ static int kvm_riscv_vcpu_general_set_csr(struct kvm_vcpu *vcpu, reg_num = array_index_nospec(reg_num, regs_max); - if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) { - reg_val &= VSIP_VALID_MASK; - reg_val <<= VSIP_TO_HVIP_SHIFT; - } + if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) + reg_val = vsip_to_hvip(reg_val); ((unsigned long *)csr)[reg_num] = reg_val; diff --git a/tools/arch/riscv/include/asm/csr.h b/tools/arch/riscv/include/asm/csr.h index 21d8cee046383d..8df64314d61315 100644 --- a/tools/arch/riscv/include/asm/csr.h +++ b/tools/arch/riscv/include/asm/csr.h @@ -163,12 +163,24 @@ #define HGATP_MODE_SHIFT HGATP32_MODE_SHIFT #endif -/* VSIP & HVIP relation */ +/* + * VSIP & HVIP relation + * + * The bit positions are same between VSIP and HVIP for interrupt + * numbers 13-63, where there's a shift for the SSI, STI and SEI. + */ #define VSIP_TO_HVIP_SHIFT (IRQ_VS_SOFT - IRQ_S_SOFT) -#define VSIP_VALID_MASK ((_AC(1, UL) << IRQ_S_SOFT) | \ +#define VSIP_BIAS_MASK ((_AC(1, UL) << IRQ_S_SOFT) | \ (_AC(1, UL) << IRQ_S_TIMER) | \ - (_AC(1, UL) << IRQ_S_EXT) | \ - (_AC(1, UL) << IRQ_PMU_OVF)) + (_AC(1, UL) << IRQ_S_EXT)) +#define VSIP_NO_BIAS_MASK (_AC(1, UL) << IRQ_PMU_OVF) +#define VSIP_VALID_MASK (VSIP_BIAS_MASK | VSIP_NO_BIAS_MASK) +#define vsip_to_hvip(_vsip) ((((_vsip) & VSIP_BIAS_MASK) << \ + VSIP_TO_HVIP_SHIFT) | \ + ((_vsip) & VSIP_NO_BIAS_MASK)) +#define hvip_to_vsip(_hvip) ((((_hvip) >> VSIP_TO_HVIP_SHIFT) & \ + VSIP_BIAS_MASK) | \ + ((_hvip) & VSIP_NO_BIAS_MASK)) /* AIA CSR bits */ #define TOPI_IID_SHIFT 16 From 8ae12ccaec6ec74945d8c1ef39f2c1b8df779abc Mon Sep 17 00:00:00 2001 From: Xie Bo Date: Mon, 10 Aug 2026 09:21:15 +0800 Subject: [PATCH 0468/1417] RISC-V: KVM: Serialize IMSIC attributes with vCPU migration KVM device ioctls are not serialized against KVM_RUN. As a result, kvm_riscv_aia_imsic_rw_attr() can snapshot the physical CPU and HGEI of an IMSIC VS-file before a concurrent vCPU migration releases it. The HGEI can then be allocated to another vCPU before imsic_vsfile_rw() uses the stale tuple. A GET or SET attribute may consequently access the new owner's interrupt file. Serialize the entire IMSIC attribute operation with the target vCPU mutex. This prevents the VS-file from being migrated and recycled until the attribute access completes. Acquire the mutex killably so that the device ioctl remains interruptible while waiting for KVM_RUN to finish. Fixes: db8b7e97d613 ("RISC-V: KVM: Add in-kernel virtualization of AIA IMSIC") Cc: stable@vger.kernel.org Signed-off-by: Xie Bo Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260810-imsic-attr-race-v2-1-00ed95ad321e@ultrarisc.com Signed-off-by: Anup Patel --- arch/riscv/kvm/aia_imsic.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/arch/riscv/kvm/aia_imsic.c b/arch/riscv/kvm/aia_imsic.c index c1af23e79ae063..f17dc559a70921 100644 --- a/arch/riscv/kvm/aia_imsic.c +++ b/arch/riscv/kvm/aia_imsic.c @@ -965,9 +965,14 @@ int kvm_riscv_aia_imsic_rw_attr(struct kvm *kvm, unsigned long type, if (!vcpu) return -ENODEV; + if (mutex_lock_killable(&vcpu->mutex)) + return -EINTR; + imsic = vcpu->arch.aia_context.imsic_state; - if (!imsic) - return -ENODEV; + if (!imsic) { + rc = -ENODEV; + goto out_unlock; + } isel = KVM_DEV_RISCV_AIA_IMSIC_GET_ISEL(type); read_lock_irqsave(&imsic->vsfile_lock, flags); @@ -991,6 +996,8 @@ int kvm_riscv_aia_imsic_rw_attr(struct kvm *kvm, unsigned long type, rc = imsic_vsfile_rw(vsfile_hgei, vsfile_cpu, imsic->nr_eix, isel, write, val); +out_unlock: + mutex_unlock(&vcpu->mutex); return rc; } From ed54fdb460a6e81d5f8f38388d1f10b385dd4a58 Mon Sep 17 00:00:00 2001 From: Xie Bo Date: Mon, 10 Aug 2026 13:15:43 +0800 Subject: [PATCH 0469/1417] RISC-V: KVM: Release unused page after MMU invalidation If an MMU invalidation races with a G-stage fault, the fault handler skips installing the page but leaves ret set to zero. As a result, kvm_release_faultin_page() treats the page as used and can unnecessarily mark it dirty. Track the invalidation retry separately and release the page as unused, while preserving the existing return value so that the vCPU retries the fault. Fixes: 2ed90cb0938a ("KVM: RISC-V: Retry fault if vma_lookup() results become invalid") Cc: stable@vger.kernel.org Signed-off-by: Xie Bo Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260810051544.3953925-2-xb@ultrarisc.com Signed-off-by: Anup Patel --- arch/riscv/kvm/mmu.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c index 6035b5ec950398..d189fd58d7bff0 100644 --- a/arch/riscv/kvm/mmu.c +++ b/arch/riscv/kvm/mmu.c @@ -627,6 +627,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, int ret; kvm_pfn_t hfn; bool is_hugetlb; + bool unused = false; bool writable; unsigned int vma_pageshift; gfn_t gfn = gpa >> PAGE_SHIFT; @@ -719,8 +720,10 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, write_lock(&kvm->mmu_lock); - if (mmu_invalidate_retry(kvm, mmu_seq)) + if (mmu_invalidate_retry(kvm, mmu_seq)) { + unused = true; goto out_unlock; + } /* * Check if we are backed by a THP and thus use block mapping if @@ -743,7 +746,8 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, kvm_err("Failed to map in G-stage\n"); out_unlock: - kvm_release_faultin_page(kvm, page, ret && ret != -EEXIST, writable); + kvm_release_faultin_page(kvm, page, + unused || (ret && ret != -EEXIST), writable); write_unlock(&kvm->mmu_lock); return ret; } From f41fb17143df890855d3de980f8eb92dfd595817 Mon Sep 17 00:00:00 2001 From: Xie Bo Date: Mon, 10 Aug 2026 13:15:44 +0800 Subject: [PATCH 0470/1417] RISC-V: KVM: Propagate interrupted G-stage faults __kvm_faultin_pfn() reports an interrupted host page fault with KVM_PFN_ERR_SIGPENDING. RISC-V currently handles it as a generic error PFN and returns -EFAULT. Return -EINTR for the signal-pending sentinel so callers can distinguish an interrupted fault from an invalid userspace mapping. Do not log the expected interruption as a vCPU exit error. Fixes: 9d05c1fee837 ("RISC-V: KVM: Implement stage2 page table programming") Cc: stable@vger.kernel.org Signed-off-by: Xie Bo Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260810051544.3953925-3-xb@ultrarisc.com Signed-off-by: Anup Patel --- arch/riscv/kvm/mmu.c | 2 ++ arch/riscv/kvm/vcpu_exit.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c index d189fd58d7bff0..3e955d808743b4 100644 --- a/arch/riscv/kvm/mmu.c +++ b/arch/riscv/kvm/mmu.c @@ -708,6 +708,8 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, vma_pageshift, current); return 0; } + if (is_sigpending_pfn(hfn)) + return -EINTR; if (is_error_noslot_pfn(hfn)) return -EFAULT; diff --git a/arch/riscv/kvm/vcpu_exit.c b/arch/riscv/kvm/vcpu_exit.c index 88e0c369b3544b..8d36eb8abce1eb 100644 --- a/arch/riscv/kvm/vcpu_exit.c +++ b/arch/riscv/kvm/vcpu_exit.c @@ -283,7 +283,7 @@ int kvm_riscv_vcpu_exit(struct kvm_vcpu *vcpu, struct kvm_run *run, } /* Print details in-case of error */ - if (ret < 0) { + if (ret < 0 && ret != -EINTR) { kvm_err("VCPU exit error %d\n", ret); kvm_err("SEPC=0x%lx SSTATUS=0x%lx HSTATUS=0x%lx\n", vcpu->arch.guest_context.sepc, From b3d346838ec65fac7fd83f5dbcedd13cadfffddb Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Wed, 26 Aug 2026 15:50:09 +0800 Subject: [PATCH 0471/1417] KVM: riscv: Fix NACL hfence entry update order The SBI v3.0 specification (section 15.1.2) requires a nested HFENCE entry to be populated as follows: 1) find an unused entry with Config.Pending == 0 2) update the Page_Number and Page_Count words 3) update the Config word with Config.Pending set __kvm_riscv_nacl_hfence() writes the Config word first, so the SBI implementation (or NACL hardware) can observe a pending entry with pnum/pcount values left over from the previous use of that entry, resulting in incorrect TLB flush ranges. Write pnum and pcount first and the Config word last. Since the consumer is an external agent on coherent shared memory, use WRITE_ONCE() to stop the compiler from reordering the stores and smp_wmb() to make the parameter words globally visible before the Pending bit is set. Fixes: d466c19cead5 ("RISC-V: KVM: Add common nested acceleration support") Signed-off-by: Zongmin Zhou Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260826075009.68952-1-min_halo@163.com Signed-off-by: Anup Patel --- arch/riscv/kvm/nacl.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/arch/riscv/kvm/nacl.c b/arch/riscv/kvm/nacl.c index 9aff03c4f667ae..a5cda9a65156d4 100644 --- a/arch/riscv/kvm/nacl.c +++ b/arch/riscv/kvm/nacl.c @@ -42,12 +42,24 @@ void __kvm_riscv_nacl_hfence(void *shmem, } } - entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_CONFIG(i); - *entp = cpu_to_lelong(control); + /* + * Per SBI v3.0 section 15.1.2, the Page_Number and Page_Count + * words must be updated before the Config word with its Pending + * bit set. WRITE_ONCE() stops the compiler from reordering the + * stores and smp_wmb() makes the parameter words globally + * visible to the SBI implementation (or NACL hardware) before + * the Pending bit is set. + */ entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_PNUM(i); - *entp = cpu_to_lelong(page_num); + WRITE_ONCE(*entp, cpu_to_lelong(page_num)); entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_PCOUNT(i); - *entp = cpu_to_lelong(page_count); + WRITE_ONCE(*entp, cpu_to_lelong(page_count)); + + /* Ensure the parameter words are visible before the Pending bit */ + smp_wmb(); + + entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_CONFIG(i); + WRITE_ONCE(*entp, cpu_to_lelong(control)); } int kvm_riscv_nacl_enable(void) From b7749531a9b195f4fd7db92ca3cc49bda1a4dc8d Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Wed, 26 Aug 2026 14:41:17 +0800 Subject: [PATCH 0472/1417] RISC-V: KVM: Fix sdata leak and stale snapshot_addr in snapshot_set_shmem A guest may call SBI_PMU_SNAPSHOT_SET_SHMEM repeatedly. Each call overwrites kvpmu->sdata without freeing the old buffer (memory leak), and if a later kvm_vcpu_write_guest() fails, the error path frees sdata but leaves snapshot_addr stale. A subsequent SBI_PMU_COUNTER_START then passes the INVALID_GPA check and crashes the host with a NULL buffer in kvm_vcpu_read_guest(). Fix this by clearing the previously installed snapshot area before installing a new one, which keeps sdata and snapshot_addr consistent. The SBI spec suggests a single invocation but defines no error code for repeated calls, so KVM must tolerate them. Fixes: c2f41ddbcdd756 ("RISC-V: KVM: Implement SBI PMU Snapshot feature") Cc: stable@vger.kernel.org Signed-off-by: Zongmin Zhou Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260826064117.58029-1-min_halo@163.com Signed-off-by: Anup Patel --- arch/riscv/kvm/vcpu_pmu.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/riscv/kvm/vcpu_pmu.c b/arch/riscv/kvm/vcpu_pmu.c index 6ff741ee78036d..cc0c138651bca5 100644 --- a/arch/riscv/kvm/vcpu_pmu.c +++ b/arch/riscv/kvm/vcpu_pmu.c @@ -454,6 +454,14 @@ int kvm_riscv_vcpu_pmu_snapshot_set_shmem(struct kvm_vcpu *vcpu, unsigned long s } } + /* + * Clear any previously installed snapshot area to avoid leaking + * the old sdata and to keep sdata/snapshot_addr consistent if + * the re-install fails below. + */ + if (kvpmu->snapshot_addr != INVALID_GPA) + kvm_pmu_clear_snapshot_area(vcpu); + kvpmu->sdata = kzalloc(snapshot_area_size, GFP_ATOMIC | __GFP_ACCOUNT); if (!kvpmu->sdata) { sbiret = SBI_ERR_FAILURE; From 8b3fd1a8b305321171602bfa7c41212441cf69e4 Mon Sep 17 00:00:00 2001 From: SeungJu Cheon Date: Tue, 25 Aug 2026 17:37:17 +0900 Subject: [PATCH 0473/1417] RISC-V: KVM: Preserve firmware counter value across stop/start Firmware events accumulate in kvpmu->fw_event[].value while running, but counter stop only clears fw_event[].started without saving the value back to pmc->counter_val. A subsequent counter start without SBI_PMU_START_FLAG_SET_INIT_VALUE reloads the stale counter_val into fw_event[].value, losing all events counted so far. Save fw_event[].value into counter_val when actually stopping a running counter, and remove the now redundant synchronization from the snapshot path. Fixes: badc386869e2c ("RISC-V: KVM: Support firmware events") Signed-off-by: SeungJu Cheon Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260825083719.643970-2-suunj1331@gmail.com Signed-off-by: Anup Patel --- arch/riscv/kvm/vcpu_pmu.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/riscv/kvm/vcpu_pmu.c b/arch/riscv/kvm/vcpu_pmu.c index cc0c138651bca5..5dfe2054d12b2c 100644 --- a/arch/riscv/kvm/vcpu_pmu.c +++ b/arch/riscv/kvm/vcpu_pmu.c @@ -683,10 +683,12 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, goto out; } - if (!kvpmu->fw_event[fevent_code].started) + if (!kvpmu->fw_event[fevent_code].started) { sbiret = SBI_ERR_ALREADY_STOPPED; - - kvpmu->fw_event[fevent_code].started = false; + } else { + kvpmu->fw_event[fevent_code].started = false; + pmc->counter_val = kvpmu->fw_event[fevent_code].value; + } } else if (pmc->perf_event) { if (pmc->started) { /* Stop counting the counter */ @@ -704,9 +706,7 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, } if (snap_flag_set && !sbiret) { - if (pmc->cinfo.type == SBI_PMU_CTR_TYPE_FW) - pmc->counter_val = kvpmu->fw_event[fevent_code].value; - else if (pmc->perf_event) + if (pmc->perf_event) pmc->counter_val += perf_event_read_value(pmc->perf_event, &enabled, &running); /* From 057dd2639ceae79adced5d8fe52c32d562edcb3a Mon Sep 17 00:00:00 2001 From: SeungJu Cheon Date: Tue, 25 Aug 2026 17:37:18 +0900 Subject: [PATCH 0474/1417] RISC-V: KVM: Report snapshot write failure to the guest If kvm_vcpu_write_guest() fails while updating the PMU snapshot area on counter stop, the guest may receive SBI_SUCCESS without the snapshot being updated, leaving stale data in shared memory. Return SBI_ERR_FAILURE when the snapshot write fails. Fixes: c2f41ddbcdd7 ("RISC-V: KVM: Implement SBI PMU Snapshot feature") Signed-off-by: SeungJu Cheon Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260825083719.643970-3-suunj1331@gmail.com Signed-off-by: Anup Patel --- arch/riscv/kvm/vcpu_pmu.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/riscv/kvm/vcpu_pmu.c b/arch/riscv/kvm/vcpu_pmu.c index 5dfe2054d12b2c..8be87f8794732d 100644 --- a/arch/riscv/kvm/vcpu_pmu.c +++ b/arch/riscv/kvm/vcpu_pmu.c @@ -735,9 +735,10 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, } } - if (shmem_needs_update) - kvm_vcpu_write_guest(vcpu, kvpmu->snapshot_addr, kvpmu->sdata, - sizeof(struct riscv_pmu_snapshot_data)); + if (shmem_needs_update && + kvm_vcpu_write_guest(vcpu, kvpmu->snapshot_addr, kvpmu->sdata, + sizeof(struct riscv_pmu_snapshot_data))) + sbiret = SBI_ERR_FAILURE; out: retdata->err_val = sbiret; From c7e2cc38c56142cdab25e6f73602a8222bf9479b Mon Sep 17 00:00:00 2001 From: SeungJu Cheon Date: Tue, 25 Aug 2026 17:37:19 +0900 Subject: [PATCH 0475/1417] RISC-V: KVM: Fix perf-backed counter accounting across stop and read pmu_ctr_read() adds the event count returned by perf_event_read_value() to counter_val, which can accumulate the same count repeatedly across reads. kvm_riscv_vcpu_pmu_ctr_stop() also leaves counter_val stale by not folding the current event count into it. Make reads of perf-backed counters side-effect free, and use perf_event_pause() when stopping a counter to fold the current event count into counter_val while resetting it. This preserves the counter value across stop/start and lets the snapshot path use counter_val directly. Fixes: 0cb74b65d2e5 ("RISC-V: KVM: Implement perf support without sampling") Signed-off-by: SeungJu Cheon Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260825083719.643970-4-suunj1331@gmail.com Signed-off-by: Anup Patel --- arch/riscv/kvm/vcpu_pmu.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/arch/riscv/kvm/vcpu_pmu.c b/arch/riscv/kvm/vcpu_pmu.c index 8be87f8794732d..e095d1ff439f19 100644 --- a/arch/riscv/kvm/vcpu_pmu.c +++ b/arch/riscv/kvm/vcpu_pmu.c @@ -270,12 +270,13 @@ static int pmu_ctr_read(struct kvm_vcpu *vcpu, unsigned long cidx, return -EINVAL; pmc->counter_val = kvpmu->fw_event[fevent_code].value; + *out_val = pmc->counter_val; } else if (pmc->perf_event) { - pmc->counter_val += perf_event_read_value(pmc->perf_event, &enabled, &running); + *out_val = pmc->counter_val + + perf_event_read_value(pmc->perf_event, &enabled, &running); } else { return -EINVAL; } - *out_val = pmc->counter_val; return 0; } @@ -653,7 +654,6 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, { struct kvm_pmu *kvpmu = vcpu_to_pmu(vcpu); int i, pmc_index, sbiret = 0; - u64 enabled, running; struct kvm_pmc *pmc; int fevent_code; bool snap_flag_set = flags & SBI_PMU_STOP_FLAG_TAKE_SNAPSHOT; @@ -691,8 +691,11 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, } } else if (pmc->perf_event) { if (pmc->started) { - /* Stop counting the counter */ - perf_event_disable(pmc->perf_event); + /* + * Stop the counter and fold the live count into counter_val. + * Reset the event value to avoid redundant accumulation. + */ + pmc->counter_val += perf_event_pause(pmc->perf_event, true); pmc->started = false; } else { sbiret = SBI_ERR_ALREADY_STOPPED; @@ -706,9 +709,6 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, } if (snap_flag_set && !sbiret) { - if (pmc->perf_event) - pmc->counter_val += perf_event_read_value(pmc->perf_event, - &enabled, &running); /* * The counter and overflow indices in the snapshot region are w.r.to * cbase. Modify the set bit in the counter mask instead of the pmc_index From 41e81f7e3ef96594fb840445343c0ee7723aa550 Mon Sep 17 00:00:00 2001 From: Tan Chi Date: Mon, 14 Sep 2026 11:11:46 +0800 Subject: [PATCH 0476/1417] RISC-V: KVM: Fix HSM hart status error propagation kvm_sbi_hsm_vcpu_get_status() returns SBI_ERR_INVALID_PARAM when the requested hart does not exist. However, the HART_STATUS case returns from the SBI handler without storing this error in retdata->err_val. As a result, a guest querying the status of a non-existent hart observes SBI_SUCCESS instead of SBI_ERR_INVALID_PARAM. Use the common SBI error handling path for HART_STATUS after saving a valid hart state in retdata->out_val. This preserves the returned error when kvm_sbi_hsm_vcpu_get_status() fails. Fixes: bae0dfd74e01 ("RISC-V: KVM: Modify SBI extension handler to return SBI error code") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Tan Chi Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260914031146.446157-1-tanchi25@mails.ucas.ac.cn Signed-off-by: Anup Patel --- arch/riscv/kvm/vcpu_sbi_hsm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/riscv/kvm/vcpu_sbi_hsm.c b/arch/riscv/kvm/vcpu_sbi_hsm.c index f26207f84bab65..06a15629c26bbc 100644 --- a/arch/riscv/kvm/vcpu_sbi_hsm.c +++ b/arch/riscv/kvm/vcpu_sbi_hsm.c @@ -95,9 +95,9 @@ static int kvm_sbi_ext_hsm_handler(struct kvm_vcpu *vcpu, struct kvm_run *run, ret = kvm_sbi_hsm_vcpu_get_status(vcpu); if (ret >= 0) { retdata->out_val = ret; - retdata->err_val = 0; + ret = 0; } - return 0; + break; case SBI_EXT_HSM_HART_SUSPEND: switch (lower_32_bits(cp->a0)) { case SBI_HSM_SUSPEND_RET_DEFAULT: From f4fafaf02174c32bce2f9bb4196fadf13f1fd96e Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 9 Sep 2026 09:58:22 +0900 Subject: [PATCH 0477/1417] ksmbd: fix partial normalized name responses Windows may request FILE_NORMALIZED_NAME_INFORMATION with an output buffer that only fits the fixed portion of the variable-length response. Treat the fixed portion as FILE_NORMALIZED_NAME_INFORMATION_SIZE so ksmbd returns STATUS_BUFFER_OVERFLOW instead of STATUS_INFO_LENGTH_MISMATCH. This avoids rejecting valid partial normalized-name responses. Fixes: 6b8b79226bc3 ("ksmbd: fix partial file information responses") Reported-by: Mobin Aydinfar Reviewed-by: ChenXiaoSong Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 3 +++ fs/smb/server/smb2pdu.h | 1 + 2 files changed, 4 insertions(+) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 8acc5174530bbd..0436b7c898b1e9 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -7312,6 +7312,9 @@ static int smb2_get_info_file(struct ksmbd_work *work, case FILE_ALTERNATE_NAME_INFORMATION: fixed_len = FILE_ALTERNATE_NAME_INFORMATION_SIZE; break; + case FILE_NORMALIZED_NAME_INFORMATION: + fixed_len = FILE_NORMALIZED_NAME_INFORMATION_SIZE; + break; case FILE_STREAM_INFORMATION: fixed_len = FILE_STREAM_INFORMATION_SIZE; break; diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h index a6200d8630e270..ca8e27f7b712f7 100644 --- a/fs/smb/server/smb2pdu.h +++ b/fs/smb/server/smb2pdu.h @@ -212,6 +212,7 @@ struct file_sparse { #define FILE_ALLOCATION_INFORMATION_SIZE 19 #define FILE_END_OF_FILE_INFORMATION_SIZE 20 #define FILE_ALTERNATE_NAME_INFORMATION_SIZE 8 +#define FILE_NORMALIZED_NAME_INFORMATION_SIZE 8 #define FILE_STREAM_INFORMATION_SIZE 32 #define FILE_PIPE_INFORMATION_SIZE 23 #define FILE_PIPE_LOCAL_INFORMATION_SIZE 24 From 9fa26285ae70ac2d3d1b47459a6b4463ab053e1c Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 9 Sep 2026 09:58:48 +0900 Subject: [PATCH 0478/1417] ksmbd: keep compound responses on query info errors Do not reset the RFC1002 length of the complete response when a query info buffer is too small. The current command will add its error response through ksmbd_iov_pin_rsp(), while resetting the base length can truncate earlier responses in a compound request. This lets ksmbd return the earlier responses and the query-info error response together. Remove the now-unused rsp_org parameter from the pipe query-info helpers. Fixes: e2b76ab8b5c9 ("ksmbd: add support for read compound") Reported-by: Mobin Aydinfar Reviewed-by: ChenXiaoSong Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 0436b7c898b1e9..6b8809f67b9260 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -6275,21 +6275,18 @@ int smb2_query_dir(struct ksmbd_work *work) * @reqOutputBufferLength: max buffer length expected in command response * @fixed_len: minimum fixed response length * @rsp: query info response buffer contains output buffer length - * @rsp_org: base response buffer pointer in case of chained response * * Return: 0 on success, otherwise error */ static int buffer_check_err(int reqOutputBufferLength, unsigned int fixed_len, - struct smb2_query_info_rsp *rsp, - void *rsp_org) + struct smb2_query_info_rsp *rsp) { unsigned int output_len = le32_to_cpu(rsp->OutputBufferLength); if (reqOutputBufferLength < fixed_len) { pr_err("Invalid Buffer Size Requested\n"); rsp->hdr.Status = STATUS_INFO_LENGTH_MISMATCH; - *(__be32 *)rsp_org = cpu_to_be32(sizeof(struct smb2_hdr)); return -EINVAL; } @@ -6300,8 +6297,7 @@ static int buffer_check_err(int reqOutputBufferLength, return 0; } -static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp, - void *rsp_org) +static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp) { struct smb2_file_standard_info *sinfo; @@ -6316,8 +6312,7 @@ static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp, cpu_to_le32(sizeof(struct smb2_file_standard_info)); } -static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num, - void *rsp_org) +static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num) { struct smb2_file_internal_info *file_info; @@ -6331,8 +6326,7 @@ static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num, static int smb2_get_info_file_pipe(struct ksmbd_session *sess, struct smb2_query_info_req *req, - struct smb2_query_info_rsp *rsp, - void *rsp_org) + struct smb2_query_info_rsp *rsp) { u64 id; int rc; @@ -6357,16 +6351,16 @@ static int smb2_get_info_file_pipe(struct ksmbd_session *sess, switch (req->FileInfoClass) { case FILE_STANDARD_INFORMATION: - get_standard_info_pipe(rsp, rsp_org); + get_standard_info_pipe(rsp); rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), le32_to_cpu(rsp->OutputBufferLength), - rsp, rsp_org); + rsp); break; case FILE_INTERNAL_INFORMATION: - get_internal_info_pipe(rsp, id, rsp_org); + get_internal_info_pipe(rsp, id); rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), le32_to_cpu(rsp->OutputBufferLength), - rsp, rsp_org); + rsp); break; default: ksmbd_debug(SMB, "smb2_info_file_pipe for %u not supported\n", @@ -7202,8 +7196,7 @@ static int smb2_get_info_file(struct ksmbd_work *work, if (test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_PIPE)) { /* smb2 info file called for pipe */ - rc = smb2_get_info_file_pipe(work->sess, req, rsp, - work->response_buf); + rc = smb2_get_info_file_pipe(work->sess, req, rsp); goto iov_pin_out; } @@ -7321,7 +7314,7 @@ static int smb2_get_info_file(struct ksmbd_work *work, } rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), fixed_len, - rsp, work->response_buf); + rsp); } ksmbd_fd_put(work, fp); @@ -7592,7 +7585,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, } rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), fixed_len, - rsp, work->response_buf); + rsp); path_put(&path); if (!rc) @@ -7706,7 +7699,7 @@ static int smb2_get_info_sec(struct ksmbd_work *work, rsp->OutputBufferLength = cpu_to_le32(secdesclen); rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), le32_to_cpu(rsp->OutputBufferLength), - rsp, work->response_buf); + rsp); if (rc) goto err_out; From bd1041d3e1c03e17c2e594a271d2fc90da3a227a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 15 Sep 2026 08:33:18 +0200 Subject: [PATCH 0479/1417] ASoC: dt-bindings: Use consistent indentation in the example Correct indentation in the examples to consistent 2- or 4-spaces indentation to fix dt-check-style warnings ("example 0 [indent-consistent] indent mismatch ..."). Preferred is 4-spaces, but re-indenting entire example just for that is too much churn. While changing the lines, correct also node name to be generic in cirrus,cs35l45.yaml, cirrus,cs42l42.yaml and dialog,da7219.yaml. Acked-by: Charles Keepax Signed-off-by: Krzysztof Kozlowski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260915063317.28971-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Mark Brown --- .../bindings/sound/adi,adau1372.yaml | 10 +-- .../bindings/sound/adi,adau7118.yaml | 22 ++--- .../bindings/sound/amlogic,gx-sound-card.yaml | 20 ++--- .../bindings/sound/asahi-kasei,ak4619.yaml | 6 +- .../bindings/sound/cirrus,cs35l45.yaml | 44 +++++----- .../bindings/sound/cirrus,cs4270.yaml | 12 +-- .../bindings/sound/cirrus,cs42l42.yaml | 48 +++++------ .../bindings/sound/cirrus,cs42l84.yaml | 20 ++--- .../bindings/sound/cirrus,cs42xx8.yaml | 60 +++++++------- .../bindings/sound/dialog,da7219.yaml | 80 +++++++++--------- .../bindings/sound/fsl,imx-asrc.yaml | 2 +- .../devicetree/bindings/sound/fsl,sai.yaml | 14 ++-- .../bindings/sound/imx-audio-card.yaml | 4 +- .../bindings/sound/invensense,ics43432.yaml | 8 +- .../sound/loongson,ls-audio-card.yaml | 4 +- .../sound/mediatek,mt8173-afe-pcm.yaml | 22 ++--- .../bindings/sound/mediatek,mt8183-audio.yaml | 82 +++++++++---------- .../sound/mt8186-mt6366-da7219-max98357.yaml | 36 ++++---- .../sound/mt8186-mt6366-rt1019-rt5682s.yaml | 36 ++++---- .../sound/mt8192-mt6359-rt1015-rt5682.yaml | 46 +++++------ .../bindings/sound/mt8195-mt6359.yaml | 50 +++++------ .../bindings/sound/qcom,q6apm-lpass-dais.yaml | 12 +-- .../bindings/sound/renesas,rsnd.yaml | 16 ++-- .../bindings/sound/renesas,rz-ssi.yaml | 32 ++++---- .../bindings/sound/ti,tas5805m.yaml | 10 +-- .../bindings/sound/ti,tlv320dac3100.yaml | 2 +- .../bindings/sound/ti,tpa6130a2.yaml | 2 +- 27 files changed, 350 insertions(+), 350 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/adi,adau1372.yaml b/Documentation/devicetree/bindings/sound/adi,adau1372.yaml index 9a7ff50a0a22d1..532550de62d7ce 100644 --- a/Documentation/devicetree/bindings/sound/adi,adau1372.yaml +++ b/Documentation/devicetree/bindings/sound/adi,adau1372.yaml @@ -52,11 +52,11 @@ examples: #address-cells = <1>; #size-cells = <0>; audio-codec@3c { - compatible = "adi,adau1372"; - reg = <0x3c>; - #sound-dai-cells = <0>; - clock-names = "mclk"; - clocks = <&adau1372z_xtal>; + compatible = "adi,adau1372"; + reg = <0x3c>; + #sound-dai-cells = <0>; + clock-names = "mclk"; + clocks = <&adau1372z_xtal>; }; }; diff --git a/Documentation/devicetree/bindings/sound/adi,adau7118.yaml b/Documentation/devicetree/bindings/sound/adi,adau7118.yaml index 11f59c29b5751f..8272cdaf66a85d 100644 --- a/Documentation/devicetree/bindings/sound/adi,adau7118.yaml +++ b/Documentation/devicetree/bindings/sound/adi,adau7118.yaml @@ -68,20 +68,20 @@ examples: #address-cells = <1>; #size-cells = <0>; adau7118_codec: audio-codec@14 { - compatible = "adi,adau7118"; - reg = <0x14>; - #sound-dai-cells = <0>; - iovdd-supply = <&supply>; - dvdd-supply = <&supply>; - adi,pdm-clk-map = <1 1 0 0>; - adi,decimation-ratio = <16>; + compatible = "adi,adau7118"; + reg = <0x14>; + #sound-dai-cells = <0>; + iovdd-supply = <&supply>; + dvdd-supply = <&supply>; + adi,pdm-clk-map = <1 1 0 0>; + adi,decimation-ratio = <16>; }; }; /* example with hw standalone mode */ adau7118_codec_hw: adau7118-codec-hw { - compatible = "adi,adau7118"; - #sound-dai-cells = <0>; - iovdd-supply = <&supply>; - dvdd-supply = <&supply>; + compatible = "adi,adau7118"; + #sound-dai-cells = <0>; + iovdd-supply = <&supply>; + dvdd-supply = <&supply>; }; diff --git a/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml b/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml index 6fdf605ad59ce9..2be3b34e4f5fed 100644 --- a/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml +++ b/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml @@ -94,20 +94,20 @@ examples: audio-routing = "I2S ENCODER I2S IN", "I2S FIFO Playback"; dai-link-0 { - sound-dai = <&i2s_fifo>; + sound-dai = <&i2s_fifo>; }; dai-link-1 { - sound-dai = <&i2s_encoder>; - dai-format = "i2s"; - mclk-fs = <256>; + sound-dai = <&i2s_encoder>; + dai-format = "i2s"; + mclk-fs = <256>; - codec-0 { - sound-dai = <&codec0>; - }; + codec-0 { + sound-dai = <&codec0>; + }; - codec-1 { - sound-dai = <&codec1>; - }; + codec-1 { + sound-dai = <&codec1>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml index d412531ef9a2ba..2e81947bb8dd94 100644 --- a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml +++ b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml @@ -54,9 +54,9 @@ examples: #sound-dai-cells = <0>; port { - ak4619_endpoint: endpoint { - remote-endpoint = <&rsnd_endpoint>; - }; + ak4619_endpoint: endpoint { + remote-endpoint = <&rsnd_endpoint>; + }; }; }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml index fd0646b609f9a6..1ba809888a88c4 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml @@ -153,27 +153,27 @@ examples: #address-cells = <1>; #size-cells = <0>; - cs35l45: cs35l45@2 { - #sound-dai-cells = <1>; - compatible = "cirrus,cs35l45"; - reg = <2>; - spi-max-frequency = <5000000>; - vdd-a-supply = <&dummy_vreg>; - vdd-batt-supply = <&dummy_vreg>; - reset-gpios = <&gpio 110 0>; - cirrus,asp-sdout-hiz-ctrl = <(CS35L45_ASP_TX_HIZ_UNUSED | - CS35L45_ASP_TX_HIZ_DISABLED)>; - cirrus,sync-lsw-txid = <0x1>; - cirrus,sync-sw-txid = <0x1>; - cirrus,gpio-ctrl1 { - gpio-ctrl = <0x2>; - }; - cirrus,gpio-ctrl2 { - gpio-ctrl = <0x2>; - }; - cirrus,gpio-ctrl3 { - gpio-ctrl = <0x1>; - gpio-dir = <0x1>; - }; + cs35l45: audio-codec@2 { + #sound-dai-cells = <1>; + compatible = "cirrus,cs35l45"; + reg = <2>; + spi-max-frequency = <5000000>; + vdd-a-supply = <&dummy_vreg>; + vdd-batt-supply = <&dummy_vreg>; + reset-gpios = <&gpio 110 0>; + cirrus,asp-sdout-hiz-ctrl = <(CS35L45_ASP_TX_HIZ_UNUSED | + CS35L45_ASP_TX_HIZ_DISABLED)>; + cirrus,sync-lsw-txid = <0x1>; + cirrus,sync-sw-txid = <0x1>; + cirrus,gpio-ctrl1 { + gpio-ctrl = <0x2>; + }; + cirrus,gpio-ctrl2 { + gpio-ctrl = <0x2>; + }; + cirrus,gpio-ctrl3 { + gpio-ctrl = <0x1>; + gpio-dir = <0x1>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs4270.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs4270.yaml index 336e1177369428..61106294a1d568 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs4270.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs4270.yaml @@ -49,11 +49,11 @@ unevaluatedProperties: false examples: - | i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - codec@48 { - compatible = "cirrus,cs4270"; - reg = <0x48>; - }; + codec@48 { + compatible = "cirrus,cs4270"; + reg = <0x48>; + }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs42l42.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs42l42.yaml index af599d8735e260..ab4c7d22dc0ebe 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs42l42.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs42l42.yaml @@ -199,28 +199,28 @@ examples: - | #include i2c { - #address-cells = <1>; - #size-cells = <0>; - - cs42l42: cs42l42@48 { - compatible = "cirrus,cs42l42"; - reg = <0x48>; - VA-supply = <&dummy_vreg>; - VP-supply = <&dummy_vreg>; - VCP-supply = <&dummy_vreg>; - VD_FILT-supply = <&dummy_vreg>; - VL-supply = <&dummy_vreg>; - - reset-gpios = <&axi_gpio_0 1 0>; - interrupt-parent = <&gpio0>; - interrupts = <55 8>; - - cirrus,ts-inv = ; - cirrus,ts-dbnc-rise = ; - cirrus,ts-dbnc-fall = ; - cirrus,btn-det-init-dbnce = <100>; - cirrus,btn-det-event-dbnce = <10>; - cirrus,bias-lvls = <0x0F 0x08 0x04 0x01>; - cirrus,hs-bias-ramp-rate = ; - }; + #address-cells = <1>; + #size-cells = <0>; + + audio-codec@48 { + compatible = "cirrus,cs42l42"; + reg = <0x48>; + VA-supply = <&dummy_vreg>; + VP-supply = <&dummy_vreg>; + VCP-supply = <&dummy_vreg>; + VD_FILT-supply = <&dummy_vreg>; + VL-supply = <&dummy_vreg>; + + reset-gpios = <&axi_gpio_0 1 0>; + interrupt-parent = <&gpio0>; + interrupts = <55 8>; + + cirrus,ts-inv = ; + cirrus,ts-dbnc-rise = ; + cirrus,ts-dbnc-fall = ; + cirrus,btn-det-init-dbnce = <100>; + cirrus,btn-det-event-dbnce = <10>; + cirrus,bias-lvls = <0x0F 0x08 0x04 0x01>; + cirrus,hs-bias-ramp-rate = ; + }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml index 7f8338e8ae369b..94062c1d9d45f7 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml @@ -43,14 +43,14 @@ examples: #include #include i2c { - #address-cells = <1>; - #size-cells = <0>; - - jack_codec: codec@4b { - compatible = "cirrus,cs42l84"; - reg = <0x4b>; - reset-gpios = <&pinctrl_nub 4 GPIO_ACTIVE_LOW>; - interrupts-extended = <&pinctrl_ap 180 IRQ_TYPE_LEVEL_LOW>; - #sound-dai-cells = <0>; - }; + #address-cells = <1>; + #size-cells = <0>; + + jack_codec: codec@4b { + compatible = "cirrus,cs42l84"; + reg = <0x4b>; + reset-gpios = <&pinctrl_nub 4 GPIO_ACTIVE_LOW>; + interrupts-extended = <&pinctrl_ap 180 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs42xx8.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs42xx8.yaml index a1ae548c4b7b5e..b5cdc7e537f8d9 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs42xx8.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs42xx8.yaml @@ -75,37 +75,37 @@ unevaluatedProperties: false examples: - | i2c { - #address-cells = <1>; - #size-cells = <0>; - - codec@48 { - compatible = "cirrus,cs42888"; - reg = <0x48>; - clocks = <&codec_mclk 0>; - clock-names = "mclk"; - VA-supply = <®_audio>; - VD-supply = <®_audio>; - VLS-supply = <®_audio>; - VLC-supply = <®_audio>; - reset-gpios = <&gpio 1>; - }; + #address-cells = <1>; + #size-cells = <0>; + + codec@48 { + compatible = "cirrus,cs42888"; + reg = <0x48>; + clocks = <&codec_mclk 0>; + clock-names = "mclk"; + VA-supply = <®_audio>; + VD-supply = <®_audio>; + VLS-supply = <®_audio>; + VLC-supply = <®_audio>; + reset-gpios = <&gpio 1>; + }; }; spi { - #address-cells = <1>; - #size-cells = <0>; - cs-gpios = <&gpio 8 0>; - - codec@0 { - compatible = "cirrus,cs42888"; - reg = <0>; - spi-max-frequency = <6000000>; - clocks = <&codec_mclk 0>; - clock-names = "mclk"; - VA-supply = <®_audio>; - VD-supply = <®_audio>; - VLS-supply = <®_audio>; - VLC-supply = <®_audio>; - reset-gpios = <&gpio 1>; - }; + #address-cells = <1>; + #size-cells = <0>; + cs-gpios = <&gpio 8 0>; + + codec@0 { + compatible = "cirrus,cs42888"; + reg = <0>; + spi-max-frequency = <6000000>; + clocks = <&codec_mclk 0>; + clock-names = "mclk"; + VA-supply = <®_audio>; + VD-supply = <®_audio>; + VLS-supply = <®_audio>; + VLC-supply = <®_audio>; + reset-gpios = <&gpio 1>; + }; }; diff --git a/Documentation/devicetree/bindings/sound/dialog,da7219.yaml b/Documentation/devicetree/bindings/sound/dialog,da7219.yaml index 19137abdba3e5d..852f27e9dffea3 100644 --- a/Documentation/devicetree/bindings/sound/dialog,da7219.yaml +++ b/Documentation/devicetree/bindings/sound/dialog,da7219.yaml @@ -195,44 +195,44 @@ examples: - | #include i2c { - #address-cells = <1>; - #size-cells = <0>; - - codec: da7219@1a { - compatible = "dlg,da7219"; - reg = <0x1a>; - - interrupt-parent = <&gpio6>; - interrupts = <11 IRQ_TYPE_LEVEL_LOW>; - - VDD-supply = <&vdd_reg>; - VDDMIC-supply = <&vddmic_reg>; - VDDIO-supply = <&vddio_reg>; - - #clock-cells = <1>; - clock-output-names = "da7219-dai-wclk", "da7219-dai-bclk"; - - clocks = <&clks 201>; - clock-names = "mclk"; - - dlg,micbias-lvl = <2600>; - dlg,mic-amp-in-sel = "diff"; - - da7219_aad { - dlg,btn-cfg = <50>; - dlg,mic-det-thr = <500>; - dlg,jack-ins-deb = <20>; - dlg,jack-ins-det-pty = "low"; - dlg,jack-det-rate = "32_64"; - dlg,jack-rem-deb = <1>; - - dlg,a-d-btn-thr = <0xa>; - dlg,d-b-btn-thr = <0x16>; - dlg,b-c-btn-thr = <0x21>; - dlg,c-mic-btn-thr = <0x3E>; - - dlg,btn-avg = <4>; - dlg,adc-1bit-rpt = <1>; - }; - }; + #address-cells = <1>; + #size-cells = <0>; + + codec: audio-codec@1a { + compatible = "dlg,da7219"; + reg = <0x1a>; + + interrupt-parent = <&gpio6>; + interrupts = <11 IRQ_TYPE_LEVEL_LOW>; + + VDD-supply = <&vdd_reg>; + VDDMIC-supply = <&vddmic_reg>; + VDDIO-supply = <&vddio_reg>; + + #clock-cells = <1>; + clock-output-names = "da7219-dai-wclk", "da7219-dai-bclk"; + + clocks = <&clks 201>; + clock-names = "mclk"; + + dlg,micbias-lvl = <2600>; + dlg,mic-amp-in-sel = "diff"; + + da7219_aad { + dlg,btn-cfg = <50>; + dlg,mic-det-thr = <500>; + dlg,jack-ins-deb = <20>; + dlg,jack-ins-det-pty = "low"; + dlg,jack-det-rate = "32_64"; + dlg,jack-rem-deb = <1>; + + dlg,a-d-btn-thr = <0xa>; + dlg,d-b-btn-thr = <0x16>; + dlg,b-c-btn-thr = <0x21>; + dlg,c-mic-btn-thr = <0x3E>; + + dlg,btn-avg = <4>; + dlg,adc-1bit-rpt = <1>; + }; + }; }; diff --git a/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml b/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml index 608defc93c1e9f..cd69bad107c793 100644 --- a/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml @@ -186,5 +186,5 @@ examples: asrc_endpoint: endpoint { remote-endpoint = <&fe00_ep>; }; - }; + }; }; diff --git a/Documentation/devicetree/bindings/sound/fsl,sai.yaml b/Documentation/devicetree/bindings/sound/fsl,sai.yaml index ba65b3f3d0662b..eb0b9d8d907f0c 100644 --- a/Documentation/devicetree/bindings/sound/fsl,sai.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,sai.yaml @@ -260,13 +260,13 @@ examples: playback-only; sai1_endpoint0: endpoint { - dai-tdm-slot-num = <8>; - dai-tdm-slot-width = <32>; - dai-tdm-slot-width-map = <32 8 32>; - dai-format = "dsp_a"; - bitclock-master; - frame-master; - remote-endpoint = <&mcodec01_ep>; + dai-tdm-slot-num = <8>; + dai-tdm-slot-width = <32>; + dai-tdm-slot-width-map = <32 8 32>; + dai-format = "dsp_a"; + bitclock-master; + frame-master; + remote-endpoint = <&mcodec01_ep>; }; }; diff --git a/Documentation/devicetree/bindings/sound/imx-audio-card.yaml b/Documentation/devicetree/bindings/sound/imx-audio-card.yaml index 950e3eab2942e1..27636466ba6385 100644 --- a/Documentation/devicetree/bindings/sound/imx-audio-card.yaml +++ b/Documentation/devicetree/bindings/sound/imx-audio-card.yaml @@ -105,10 +105,10 @@ examples: format = "i2s"; fsl,mclk-equal-bclk; cpu { - sound-dai = <&sai1>; + sound-dai = <&sai1>; }; codec { - sound-dai = <&ak4458_1>, <&ak4458_2>; + sound-dai = <&ak4458_1>, <&ak4458_2>; }; }; fe-dai-link { diff --git a/Documentation/devicetree/bindings/sound/invensense,ics43432.yaml b/Documentation/devicetree/bindings/sound/invensense,ics43432.yaml index 7bd984817aa94a..054f80390b8d3d 100644 --- a/Documentation/devicetree/bindings/sound/invensense,ics43432.yaml +++ b/Documentation/devicetree/bindings/sound/invensense,ics43432.yaml @@ -42,10 +42,10 @@ examples: compatible = "invensense,ics43432"; port { - endpoint { - remote-endpoint = <&i2s1_endpoint>; - dai-format = "i2s"; - }; + endpoint { + remote-endpoint = <&i2s1_endpoint>; + dai-format = "i2s"; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml index dc7f4afbb7775f..69ea611d4250a1 100644 --- a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml +++ b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml @@ -79,7 +79,7 @@ examples: sound-dai = <&i2s>; }; codec { - sound-dai = <&es8323>; + sound-dai = <&es8323>; }; }; @@ -104,6 +104,6 @@ examples: }; codec { - sound-dai = <&es8388>; + sound-dai = <&es8388>; }; }; diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt8173-afe-pcm.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt8173-afe-pcm.yaml index d8993b5d457a90..4c66160e9e1b95 100644 --- a/Documentation/devicetree/bindings/sound/mediatek,mt8173-afe-pcm.yaml +++ b/Documentation/devicetree/bindings/sound/mediatek,mt8173-afe-pcm.yaml @@ -84,15 +84,15 @@ examples: <&topckgen CLK_TOP_I2S2_M_SEL>, <&topckgen CLK_TOP_I2S3_M_SEL>, <&topckgen CLK_TOP_I2S3_B_SEL>; - clock-names = "infra_sys_audio_clk", - "top_pdn_audio", - "top_pdn_aud_intbus", - "bck0", - "bck1", - "i2s0_m", - "i2s1_m", - "i2s2_m", - "i2s3_m", - "i2s3_b"; - memory-region = <&afe_dma_mem>; + clock-names = "infra_sys_audio_clk", + "top_pdn_audio", + "top_pdn_aud_intbus", + "bck0", + "bck1", + "i2s0_m", + "i2s1_m", + "i2s2_m", + "i2s3_m", + "i2s3_b"; + memory-region = <&afe_dma_mem>; }; diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt8183-audio.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt8183-audio.yaml index 031b0fa7b4dc1b..261247ca2c8733 100644 --- a/Documentation/devicetree/bindings/sound/mediatek,mt8183-audio.yaml +++ b/Documentation/devicetree/bindings/sound/mediatek,mt8183-audio.yaml @@ -182,47 +182,47 @@ examples: <&topckgen CLK_TOP_APLL12_DIV4>, <&topckgen CLK_TOP_APLL12_DIVB>, <&clk26m>; - clock-names = "aud_afe_clk", - "aud_dac_clk", - "aud_dac_predis_clk", - "aud_adc_clk", - "aud_adc_adda6_clk", - "aud_apll22m_clk", - "aud_apll24m_clk", - "aud_apll1_tuner_clk", - "aud_apll2_tuner_clk", - "aud_i2s1_bclk_sw", - "aud_i2s2_bclk_sw", - "aud_i2s3_bclk_sw", - "aud_i2s4_bclk_sw", - "aud_tdm_clk", - "aud_tml_clk", - "aud_infra_clk", - "mtkaif_26m_clk", - "top_mux_audio", - "top_mux_aud_intbus", - "top_syspll_d2_d4", - "top_mux_aud_1", - "top_apll1_ck", - "top_mux_aud_2", - "top_apll2_ck", - "top_mux_aud_eng1", - "top_apll1_d8", - "top_mux_aud_eng2", - "top_apll2_d8", - "top_i2s0_m_sel", - "top_i2s1_m_sel", - "top_i2s2_m_sel", - "top_i2s3_m_sel", - "top_i2s4_m_sel", - "top_i2s5_m_sel", - "top_apll12_div0", - "top_apll12_div1", - "top_apll12_div2", - "top_apll12_div3", - "top_apll12_div4", - "top_apll12_divb", - "top_clk26m_clk"; + clock-names = "aud_afe_clk", + "aud_dac_clk", + "aud_dac_predis_clk", + "aud_adc_clk", + "aud_adc_adda6_clk", + "aud_apll22m_clk", + "aud_apll24m_clk", + "aud_apll1_tuner_clk", + "aud_apll2_tuner_clk", + "aud_i2s1_bclk_sw", + "aud_i2s2_bclk_sw", + "aud_i2s3_bclk_sw", + "aud_i2s4_bclk_sw", + "aud_tdm_clk", + "aud_tml_clk", + "aud_infra_clk", + "mtkaif_26m_clk", + "top_mux_audio", + "top_mux_aud_intbus", + "top_syspll_d2_d4", + "top_mux_aud_1", + "top_apll1_ck", + "top_mux_aud_2", + "top_apll2_ck", + "top_mux_aud_eng1", + "top_apll1_d8", + "top_mux_aud_eng2", + "top_apll2_d8", + "top_i2s0_m_sel", + "top_i2s1_m_sel", + "top_i2s2_m_sel", + "top_i2s3_m_sel", + "top_i2s4_m_sel", + "top_i2s5_m_sel", + "top_apll12_div0", + "top_apll12_div1", + "top_apll12_div2", + "top_apll12_div3", + "top_apll12_div4", + "top_apll12_divb", + "top_clk26m_clk"; }; ... diff --git a/Documentation/devicetree/bindings/sound/mt8186-mt6366-da7219-max98357.yaml b/Documentation/devicetree/bindings/sound/mt8186-mt6366-da7219-max98357.yaml index 037f21443ad14c..d5b358838e2a8a 100644 --- a/Documentation/devicetree/bindings/sound/mt8186-mt6366-da7219-max98357.yaml +++ b/Documentation/devicetree/bindings/sound/mt8186-mt6366-da7219-max98357.yaml @@ -167,30 +167,30 @@ examples: "HDMI1", "TX"; hs-playback-dai-link { - link-name = "I2S0"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&da7219>; - }; + link-name = "I2S0"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&da7219>; + }; }; hs-capture-dai-link { - link-name = "I2S1"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&da7219>; - }; + link-name = "I2S1"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&da7219>; + }; }; spk-dp-playback-dai-link { - link-name = "I2S3"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&anx_bridge_dp>, <&max98357a>; - }; + link-name = "I2S3"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&anx_bridge_dp>, <&max98357a>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/mt8186-mt6366-rt1019-rt5682s.yaml b/Documentation/devicetree/bindings/sound/mt8186-mt6366-rt1019-rt5682s.yaml index ed93f18ef985cf..7f553575c73d3f 100644 --- a/Documentation/devicetree/bindings/sound/mt8186-mt6366-rt1019-rt5682s.yaml +++ b/Documentation/devicetree/bindings/sound/mt8186-mt6366-rt1019-rt5682s.yaml @@ -171,30 +171,30 @@ examples: "HDMI1", "TX"; hs-playback-dai-link { - link-name = "I2S0"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt5682s 0>; - }; + link-name = "I2S0"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt5682s 0>; + }; }; hs-capture-dai-link { - link-name = "I2S1"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt5682s 0>; - }; + link-name = "I2S1"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt5682s 0>; + }; }; spk-hdmi-playback-dai-link { - link-name = "I2S3"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&it6505dptx>, <&rt1019p>; - }; + link-name = "I2S3"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&it6505dptx>, <&rt1019p>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/mt8192-mt6359-rt1015-rt5682.yaml b/Documentation/devicetree/bindings/sound/mt8192-mt6359-rt1015-rt5682.yaml index c4e68f31aaabd7..841aab0178e3fc 100644 --- a/Documentation/devicetree/bindings/sound/mt8192-mt6359-rt1015-rt5682.yaml +++ b/Documentation/devicetree/bindings/sound/mt8192-mt6359-rt1015-rt5682.yaml @@ -165,38 +165,38 @@ examples: "Speakers", "Speaker"; spk-playback-dai-link { - link-name = "I2S3"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt1015p>; - }; + link-name = "I2S3"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt1015p>; + }; }; hs-playback-dai-link { - link-name = "I2S8"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt5682 0>; - }; + link-name = "I2S8"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt5682 0>; + }; }; hs-capture-dai-link { - link-name = "I2S9"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt5682 0>; - }; + link-name = "I2S9"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt5682 0>; + }; }; displayport-dai-link { - link-name = "TDM"; - dai-format = "dsp_a"; - codec { - sound-dai = <&anx_bridge_dp>; - }; + link-name = "TDM"; + dai-format = "dsp_a"; + codec { + sound-dai = <&anx_bridge_dp>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/mt8195-mt6359.yaml b/Documentation/devicetree/bindings/sound/mt8195-mt6359.yaml index 356e1feee96209..c6bd286780c9d4 100644 --- a/Documentation/devicetree/bindings/sound/mt8195-mt6359.yaml +++ b/Documentation/devicetree/bindings/sound/mt8195-mt6359.yaml @@ -156,46 +156,46 @@ examples: "Ext Spk", "Speaker"; mm-dai-link { - link-name = "ETDM1_IN_BE"; - mediatek,clk-provider = "cpu"; + link-name = "ETDM1_IN_BE"; + mediatek,clk-provider = "cpu"; }; hs-playback-dai-link { - link-name = "ETDM1_OUT_BE"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&headset_codec>; - }; + link-name = "ETDM1_OUT_BE"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&headset_codec>; + }; }; hs-capture-dai-link { - link-name = "ETDM2_IN_BE"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&headset_codec>; - }; + link-name = "ETDM2_IN_BE"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&headset_codec>; + }; }; spk-playback-dai-link { - link-name = "ETDM2_OUT_BE"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&spk_amplifier>; - }; + link-name = "ETDM2_OUT_BE"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&spk_amplifier>; + }; }; hdmi-dai-link { - link-name = "ETDM3_OUT_BE"; - codec { - sound-dai = <&hdmi_tx>; - }; + link-name = "ETDM3_OUT_BE"; + codec { + sound-dai = <&hdmi_tx>; + }; }; displayport-dai-link { - link-name = "DPTX_BE"; - codec { - sound-dai = <&dp_tx>; - }; + link-name = "DPTX_BE"; + codec { + sound-dai = <&dp_tx>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml b/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml index 4878c424ef030e..78587139f155f8 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml @@ -75,11 +75,11 @@ examples: #size-cells = <0>; dai@10 { - reg = ; - clocks = <&q6prmcc LPASS_CLK_ID_PRI_MI2S_IBIT - LPASS_CLK_ATTRIBUTE_COUPLE_NO>, - <&q6prmcc LPASS_CLK_ID_MCLK_1 - LPASS_CLK_ATTRIBUTE_COUPLE_NO>; - clock-names = "bclk", "mclk"; + reg = ; + clocks = <&q6prmcc LPASS_CLK_ID_PRI_MI2S_IBIT + LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_CLK_ID_MCLK_1 + LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "bclk", "mclk"; }; }; diff --git a/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml b/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml index e8a2acb926460d..d3516fb1b1300d 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml +++ b/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml @@ -432,14 +432,14 @@ examples: "ssi.1", "ssi.0"; rcar_sound,dvc { - dvc0: dvc-0 { - dmas = <&audma0 0xbc>; - dma-names = "tx"; - }; - dvc1: dvc-1 { - dmas = <&audma0 0xbe>; - dma-names = "tx"; - }; + dvc0: dvc-0 { + dmas = <&audma0 0xbc>; + dma-names = "tx"; + }; + dvc1: dvc-1 { + dmas = <&audma0 0xbe>; + dma-names = "tx"; + }; }; rcar_sound,mix { diff --git a/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml b/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml index 1394f78281fc25..d696fb33d9b8e7 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml +++ b/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml @@ -95,20 +95,20 @@ examples: ssi0: ssi@10049c00 { compatible = "renesas,r9a07g044-ssi", "renesas,rz-ssi"; - reg = <0x10049c00 0x400>; - interrupts = , - , - ; - interrupt-names = "int_req", "dma_rx", "dma_tx"; - clocks = <&cpg CPG_MOD R9A07G044_SSI0_PCLK2>, - <&cpg CPG_MOD R9A07G044_SSI0_PCLK_SFR>, - <&audio_clk1>, - <&audio_clk2>; - clock-names = "ssi", "ssi_sfr", "audio_clk1", "audio_clk2"; - power-domains = <&cpg>; - resets = <&cpg R9A07G044_SSI0_RST_M2_REG>; - dmas = <&dmac 0x2655>, - <&dmac 0x2656>; - dma-names = "tx", "rx"; - #sound-dai-cells = <0>; + reg = <0x10049c00 0x400>; + interrupts = , + , + ; + interrupt-names = "int_req", "dma_rx", "dma_tx"; + clocks = <&cpg CPG_MOD R9A07G044_SSI0_PCLK2>, + <&cpg CPG_MOD R9A07G044_SSI0_PCLK_SFR>, + <&audio_clk1>, + <&audio_clk2>; + clock-names = "ssi", "ssi_sfr", "audio_clk1", "audio_clk2"; + power-domains = <&cpg>; + resets = <&cpg R9A07G044_SSI0_RST_M2_REG>; + dmas = <&dmac 0x2655>, + <&dmac 0x2656>; + dma-names = "tx", "rx"; + #sound-dai-cells = <0>; }; diff --git a/Documentation/devicetree/bindings/sound/ti,tas5805m.yaml b/Documentation/devicetree/bindings/sound/ti,tas5805m.yaml index c2c2835a9e1dbd..f2ab274b02cd8b 100644 --- a/Documentation/devicetree/bindings/sound/ti,tas5805m.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tas5805m.yaml @@ -45,13 +45,13 @@ examples: #address-cells = <1>; #size-cells = <0>; tas5805m: tas5805m@2c { - reg = <0x2c>; - compatible = "ti,tas5805m"; + reg = <0x2c>; + compatible = "ti,tas5805m"; - pvdd-supply = <&audiopwr>; - pdn-gpios = <&tlmm 160 0>; + pvdd-supply = <&audiopwr>; + pdn-gpios = <&tlmm 160 0>; - ti,dsp-config-name = "mono_pbtl_48khz"; + ti,dsp-config-name = "mono_pbtl_48khz"; }; }; ... diff --git a/Documentation/devicetree/bindings/sound/ti,tlv320dac3100.yaml b/Documentation/devicetree/bindings/sound/ti,tlv320dac3100.yaml index 10299064cbc690..3bdaab1e50841f 100644 --- a/Documentation/devicetree/bindings/sound/ti,tlv320dac3100.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tlv320dac3100.yaml @@ -121,6 +121,6 @@ examples: AVDD-supply = <®ulator>; IOVDD-supply = <®ulator>; DVDD-supply = <®ulator>; - }; + }; }; diff --git a/Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml b/Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml index a42bf9bde69400..5634b601ce65c8 100644 --- a/Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml @@ -50,6 +50,6 @@ examples: reg = <0x60>; Vdd-supply = <&vmmc2>; power-gpio = <&gpio4 2 GPIO_ACTIVE_HIGH>; - }; + }; }; From f4c49ebff706f6bf2a0ffa8e06a8f94b695cbde9 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 15 Sep 2026 11:31:21 +0100 Subject: [PATCH 0480/1417] ASoC: cs35l56: Add KUnit tests for regmap defaults table Add test cases to cs35l56-shared-test to sanity-check the regmap defaults table: - Table is sorted in order of increasing address (which also means there cannot be duplicate entries). - Volatile registers don't have a default. - Defaulted registers are readable. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260915103121.3306373-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56-shared-test.c | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/sound/soc/codecs/cs35l56-shared-test.c b/sound/soc/codecs/cs35l56-shared-test.c index 4f52c8a192e551..34bb3d44b51f10 100644 --- a/sound/soc/codecs/cs35l56-shared-test.c +++ b/sound/soc/codecs/cs35l56-shared-test.c @@ -29,6 +29,7 @@ struct cs35l56_shared_test_priv { struct faux_device *gpio_dev; struct cs35l56_shared_test_mock_gpio *gpio_priv; struct regmap *registers; + const struct regmap_config *regmap_config; unsigned int reg_offset; struct cs35l56_base *cs35l56_base; u8 applied_pad_pull_state[CS35L56_MAX_GPIO]; @@ -614,6 +615,55 @@ static void cs35l56_shared_test_get_speaker_id_from_host_gpio(struct kunit *test KUNIT_EXPECT_EQ(test, cs35l56_get_speaker_id(priv->cs35l56_base), param->spkid); } +static void cs35l56_shared_test_sorted_regmap_defaults(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + const struct reg_default *defaults; + int i; + + KUNIT_ASSERT_NOT_NULL(test, priv->regmap_config); + defaults = priv->regmap_config->reg_defaults; + KUNIT_ASSERT_NOT_NULL(test, defaults); + KUNIT_ASSERT_NE(test, priv->regmap_config->num_reg_defaults, 0); + + for (i = 1; i < priv->regmap_config->num_reg_defaults; i++) + KUNIT_EXPECT_LT(test, defaults[i - 1].reg, defaults[i].reg); +} + +static void cs35l56_shared_test_regmap_defaults_not_volatile(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + const struct regmap_config *config = priv->regmap_config; + int i; + + KUNIT_ASSERT_NOT_NULL(test, config); + KUNIT_ASSERT_NOT_NULL(test, config->volatile_reg); + + for (i = 0; i < config->num_reg_defaults; i++) { + KUNIT_EXPECT_FALSE(test, + config->volatile_reg(cs35l56_base->dev, + config->reg_defaults[i].reg)); + } +} + +static void cs35l56_shared_test_regmap_defaults_readable(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + const struct regmap_config *config = priv->regmap_config; + int i; + + KUNIT_ASSERT_NOT_NULL(test, config); + KUNIT_ASSERT_NOT_NULL(test, config->readable_reg); + + for (i = 0; i < config->num_reg_defaults; i++) { + KUNIT_EXPECT_TRUE(test, + config->readable_reg(cs35l56_base->dev, + config->reg_defaults[i].reg)); + } +} + static int cs35l56_shared_test_case_regmap_init(struct kunit *test, const struct regmap_config *regmap_config) { @@ -645,6 +695,8 @@ static int cs35l56_shared_test_case_regmap_init(struct kunit *test, kunit_add_action_or_reset(test, regmap_exit_wrapper, cs35l56_base->regmap)); + priv->regmap_config = regmap_config; + return 0; } @@ -862,6 +914,10 @@ static struct kunit_case cs35l56_shared_test_cases[] = { cs35l56_shared_test_host_gpio_spkid_gen_params, { KUNIT_SPEED_SLOW }), + KUNIT_CASE(cs35l56_shared_test_sorted_regmap_defaults), + KUNIT_CASE(cs35l56_shared_test_regmap_defaults_not_volatile), + KUNIT_CASE(cs35l56_shared_test_regmap_defaults_readable), + { } }; From 99c3d59542935bc838ee1567da473388e81ea060 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Tue, 15 Sep 2026 12:10:37 +0200 Subject: [PATCH 0481/1417] ASoC: Intel: avs: Remove topology-loading wrappers With recent changes avs_load_topology() became unused whereas avs_remove_topology() is a simple wrapper. Drop them both. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260915101037.3087956-1-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/pcm.c | 4 ++-- sound/soc/intel/avs/topology.c | 25 ------------------------- sound/soc/intel/avs/topology.h | 3 --- 3 files changed, 2 insertions(+), 30 deletions(-) diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c index 8174d2ebfa9913..2d7ced953fde57 100644 --- a/sound/soc/intel/avs/pcm.c +++ b/sound/soc/intel/avs/pcm.c @@ -1065,7 +1065,7 @@ static int avs_component_probe(struct snd_soc_component *component) return 0; err_load_libs: - avs_remove_topology(component); + snd_soc_tplg_component_remove(component); return ret; } @@ -1083,7 +1083,7 @@ static void avs_component_remove(struct snd_soc_component *component) mutex_unlock(&adev->comp_list_mutex); if (mach->tplg_filename) { - ret = avs_remove_topology(component); + ret = snd_soc_tplg_component_remove(component); if (ret < 0) dev_err(component->dev, "unload topology failed: %d\n", ret); } diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c index 5d70be63a4a7c6..1085090d5a9cb9 100644 --- a/sound/soc/intel/avs/topology.c +++ b/sound/soc/intel/avs/topology.c @@ -2226,28 +2226,3 @@ struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp) return tplg; } - -int avs_load_topology(struct snd_soc_component *comp, const char *filename) -{ - const struct firmware *fw __free(firmware) = NULL; - int ret; - - ret = request_firmware(&fw, filename, comp->dev); - if (ret < 0) { - dev_err(comp->dev, "request topology \"%s\" failed: %d\n", filename, ret); - return ret; - } - - ret = snd_soc_tplg_component_load(comp, &avs_tplg_ops, fw); - if (ret < 0) - dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret); - - return ret; -} - -int avs_remove_topology(struct snd_soc_component *comp) -{ - snd_soc_tplg_component_remove(comp); - - return 0; -} diff --git a/sound/soc/intel/avs/topology.h b/sound/soc/intel/avs/topology.h index 189984ce7b51e5..25f3888a193973 100644 --- a/sound/soc/intel/avs/topology.h +++ b/sound/soc/intel/avs/topology.h @@ -233,7 +233,4 @@ struct avs_tplg_module { extern const struct snd_soc_tplg_ops avs_tplg_ops; struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp); -int avs_load_topology(struct snd_soc_component *comp, const char *filename); -int avs_remove_topology(struct snd_soc_component *comp); - #endif From 247b84c07b7ea4940f69995cc4fc4f20d8c0c615 Mon Sep 17 00:00:00 2001 From: Oder Chiou Date: Fri, 11 Sep 2026 16:45:46 +0800 Subject: [PATCH 0482/1417] ASoC: rt721: Use the function_status check to avoid duplicate presets This patch adds the function_status check to avoid duplicate presets. The codec driver reinitializes when the 'FUNCTION_NEEDS_INITIALIZATION' flag is raised. Signed-off-by: Oder Chiou Link: https://patch.msgid.link/20260911084546.1342894-1-oder_chiou@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca-sdw.c | 12 ++ sound/soc/codecs/rt721-sdca.c | 315 +++++++++++++++++------------- sound/soc/codecs/rt721-sdca.h | 5 + 3 files changed, 195 insertions(+), 137 deletions(-) diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c index eae7d662efae8e..d0f3fc1978c31b 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.c +++ b/sound/soc/codecs/rt721-sdca-sdw.c @@ -25,16 +25,22 @@ static bool rt721_sdca_readable_register(struct device *dev, unsigned int reg) case 0x2f50: case 0x2f51: case 0x2f58 ... 0x2f5d: + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_XUV, RT721_SDCA_CTL_XUV, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_GE49, RT721_SDCA_CTL_SELECTED_MODE, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_GE49, RT721_SDCA_CTL_DETECTED_MODE, 0): + case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): case SDW_SDCA_CTL(FUNC_NUM_HID, RT721_SDCA_ENT_HID01, RT721_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... SDW_SDCA_CTL(FUNC_NUM_HID, RT721_SDCA_ENT_HID01, RT721_SDCA_CTL_HIDTX_MESSAGE_LENGTH, 0): case RT721_BUF_ADDR_HID1 ... RT721_BUF_ADDR_HID2: + case SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): return true; default: return false; @@ -46,14 +52,20 @@ static bool rt721_sdca_volatile_register(struct device *dev, unsigned int reg) switch (reg) { case 0x2f01: case 0x2f51: + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_GE49, RT721_SDCA_CTL_DETECTED_MODE, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_XUV, RT721_SDCA_CTL_XUV, 0): + case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): case SDW_SDCA_CTL(FUNC_NUM_HID, RT721_SDCA_ENT_HID01, RT721_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... SDW_SDCA_CTL(FUNC_NUM_HID, RT721_SDCA_ENT_HID01, RT721_SDCA_CTL_HIDTX_MESSAGE_LENGTH, 0): case RT721_BUF_ADDR_HID1 ... RT721_BUF_ADDR_HID2: + case SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): return true; default: return false; diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index a9479d0e4941e5..0a936938385cea 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -142,151 +142,192 @@ static void rt721_sdca_btn_check_handler(struct work_struct *work) static void rt721_sdca_dmic_preset(struct rt721_sdca_priv *rt721) { - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_VREF1_HV_CTRL1, 0xe000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8007); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL9, 0x2a2a); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL10, 0x2a00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL6, 0x2a2a); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL5, 0x2626); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL8, 0x1e00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL7, 0x1515); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_CH_FLOAT_CTL3, 0x0304); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_CH_FLOAT_CTL4, 0x0304); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_HDA_LEGACY_CTL1, 0x0000); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT_IT26, - RT721_SDCA_CTL_VENDOR_DEF, 0), 0x01); - regmap_write(rt721->mbq_regmap, 0x5910009, 0x2e01); - rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, - RT721_RC_CALIB_CTRL0, 0x0b00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, - RT721_RC_CALIB_CTRL0, 0x0b40); - regmap_write(rt721->regmap, 0x2f5c, 0x25); + unsigned int mic_func_status; + struct device *dev = &rt721->slave->dev; + + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + &mic_func_status); + dev_dbg(dev, "%s mic func_status=0x%x\n", __func__, mic_func_status); + + if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt721->first_hw_init)) { + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF1_HV_CTRL1, 0xe000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8007); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL9, 0x2a2a); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL10, 0x2a00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL6, 0x2a2a); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL5, 0x2626); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL8, 0x1e00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL7, 0x1515); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL3, 0x0304); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL4, 0x0304); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_CTL1, 0x0000); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT_IT26, + RT721_SDCA_CTL_VENDOR_DEF, 0), 0x01); + regmap_write(rt721->mbq_regmap, 0x5910009, 0x2e01); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b40); + regmap_write(rt721->regmap, 0x2f5c, 0x25); + /* clear flag */ + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0), FUNCTION_NEEDS_INITIALIZATION); + } } static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) { - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_VREF1_HV_CTRL1, 0xe000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8007); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6420); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6421); - regmap_write(rt721->mbq_regmap, 0x5810000, 0xe421); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_CH_FLOAT_CTL6, 0x5561); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_REG, - RT721_GPIO_PAD_CTRL5, 0x8003); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_OT23, - RT721_SDCA_CTL_VENDOR_DEF, 0), 0x04); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_PDE23, - RT721_SDCA_CTL_FU_MUTE, CH_01), 0x00); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_PDE23, - RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, - RT721_SDCA_CTL_FU_MUTE, CH_01), 0x00); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, - RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); - regmap_write(rt721->regmap, 0x2f5d, 0x1); + unsigned int amp_func_status; + struct device *dev = &rt721->slave->dev; + + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + &_func_status); + dev_dbg(dev, "%s amp func_status=0x%x\n", __func__, amp_func_status); + + if ((amp_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt721->first_hw_init)) { + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF1_HV_CTRL1, 0xe000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8007); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x6420); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x6421); + regmap_write(rt721->mbq_regmap, 0x5810000, 0xe421); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL6, 0x5561); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_REG, + RT721_GPIO_PAD_CTRL5, 0x8003); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_OT23, + RT721_SDCA_CTL_VENDOR_DEF, 0), 0x04); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_PDE23, + RT721_SDCA_CTL_FU_MUTE, CH_01), 0x00); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_PDE23, + RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, + RT721_SDCA_CTL_FU_MUTE, CH_01), 0x00); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, + RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); + regmap_write(rt721->regmap, 0x2f5d, 0x1); + /* clear flag */ + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + FUNCTION_NEEDS_INITIALIZATION); + } } static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) { - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_VREF1_HV_CTRL1, 0xe000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8007); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_GE_REL_CTRL1, 0x8011); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_UMP_HID_CTRL3, 0xcf00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_UMP_HID_CTRL4, 0x000f); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_UMP_HID_CTRL1, 0x1100); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_UMP_HID_CTRL5, 0x0c12); - rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, - RT721_JD_1PIN_GAT_CTRL2, 0xc002); - rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, - RT721_RC_CALIB_CTRL0, 0x0b00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, - RT721_RC_CALIB_CTRL0, 0x0b40); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3333); - regmap_write(rt721->mbq_regmap, 0x5810035, 0x0036); - regmap_write(rt721->mbq_regmap, 0x5810030, 0xee00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL1, 0x0140); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x0021); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x8021); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL18, 0x5522); - regmap_write(rt721->mbq_regmap, 0x5b10007, 0x2000); - regmap_write(rt721->mbq_regmap, 0x5B10017, 0x1b0f); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, - RT721_CBJ_A0_GAT_CTRL1, 0x2205); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL4, 0xa105); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3b33); - regmap_write(rt721->mbq_regmap, 0x310400, 0x3043); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3f33); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON13, 0x6048); - regmap_write(rt721->mbq_regmap, 0x310401, 0x3000); - regmap_write(rt721->mbq_regmap, 0x310402, 0x1b00); - regmap_write(rt721->mbq_regmap, 0x310300, 0x000f); - regmap_write(rt721->mbq_regmap, 0x310301, 0x3000); - regmap_write(rt721->mbq_regmap, 0x310302, 0x1b00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON17, 0x0008); - rt_sdca_index_write(rt721->mbq_regmap, RT721_DAC_CTRL, - RT721_DAC_2CH_CTRL3, 0x55ff); - rt_sdca_index_write(rt721->mbq_regmap, RT721_DAC_CTRL, - RT721_DAC_2CH_CTRL4, 0xcc00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_MBIAS_LV_CTRL2, 0x6677); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_VREF2_LV_CTRL1, 0x7600); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL2, 0x1234); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL3, 0x3512); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL1, 0x4040); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL4, 0x1201); - rt_sdca_index_write(rt721->mbq_regmap, RT721_BOOST_CTRL, - RT721_BST_4CH_TOP_GATING_CTRL1, 0x002a); - regmap_write(rt721->regmap, 0x2f58, 0x07); - - regmap_write(rt721->regmap, 0x2f51, 0x00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_MISC_CTL, 0x0004); + unsigned int jack_func_status; + struct device *dev = &rt721->slave->dev; + + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + &jack_func_status); + dev_dbg(dev, "%s jack func_status=0x%x\n", __func__, jack_func_status); + + if ((jack_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt721->first_hw_init)) { + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF1_HV_CTRL1, 0xe000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8007); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_GE_REL_CTRL1, 0x8011); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL3, 0xcf00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL4, 0x000f); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL1, 0x1100); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL5, 0x0c12); + rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_CTRL2, 0xc002); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b40); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3333); + regmap_write(rt721->mbq_regmap, 0x5810035, 0x0036); + regmap_write(rt721->mbq_regmap, 0x5810030, 0xee00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL1, 0x0140); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x0021); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x8021); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL18, 0x5522); + regmap_write(rt721->mbq_regmap, 0x5b10007, 0x2000); + regmap_write(rt721->mbq_regmap, 0x5B10017, 0x1b0f); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x2205); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL4, 0xa105); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3b33); + regmap_write(rt721->mbq_regmap, 0x310400, 0x3043); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3f33); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6048); + regmap_write(rt721->mbq_regmap, 0x310401, 0x3000); + regmap_write(rt721->mbq_regmap, 0x310402, 0x1b00); + regmap_write(rt721->mbq_regmap, 0x310300, 0x000f); + regmap_write(rt721->mbq_regmap, 0x310301, 0x3000); + regmap_write(rt721->mbq_regmap, 0x310302, 0x1b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON17, 0x0008); + rt_sdca_index_write(rt721->mbq_regmap, RT721_DAC_CTRL, + RT721_DAC_2CH_CTRL3, 0x55ff); + rt_sdca_index_write(rt721->mbq_regmap, RT721_DAC_CTRL, + RT721_DAC_2CH_CTRL4, 0xcc00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_MBIAS_LV_CTRL2, 0x6677); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF2_LV_CTRL1, 0x7600); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL2, 0x1234); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL3, 0x3512); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL1, 0x4040); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL4, 0x1201); + rt_sdca_index_write(rt721->mbq_regmap, RT721_BOOST_CTRL, + RT721_BST_4CH_TOP_GATING_CTRL1, 0x002a); + regmap_write(rt721->regmap, 0x2f58, 0x07); + regmap_write(rt721->regmap, 0x2f51, 0x00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_MISC_CTL, 0x0004); + /* clear flag */ + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0), FUNCTION_NEEDS_INITIALIZATION); + } } static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) diff --git a/sound/soc/codecs/rt721-sdca.h b/sound/soc/codecs/rt721-sdca.h index 24ce188562baf6..b8873b18a648c7 100644 --- a/sound/soc/codecs/rt721-sdca.h +++ b/sound/soc/codecs/rt721-sdca.h @@ -214,6 +214,7 @@ struct rt721_sdca_dmic_kctrl_priv { #define RT721_SDCA_ENT_XU03 0x03 #define RT721_SDCA_ENT_XU0D 0x0d #define RT721_SDCA_ENT_FU55 0x55 +#define RT721_SDCA_ENT0 0x00 /* RT721 SDCA control */ #define RT721_SDCA_CTL_SAMPLE_FREQ_INDEX 0x10 @@ -229,6 +230,7 @@ struct rt721_sdca_dmic_kctrl_priv { #define RT721_SDCA_CTL_VENDOR_DEF 0x30 #define RT721_SDCA_CTL_XUV 0x34 #define RT721_SDCA_CTL_FU_CH_GAIN 0x0b +#define RT721_SDCA_CTL_FUNC_STATUS 0x10 /* RT721 SDCA channel */ #define CH_L 0x01 @@ -261,6 +263,9 @@ struct rt721_sdca_dmic_kctrl_priv { /* RT721 HID ID */ #define RT721_SDCA_HID_ID 0x11 +/* Function_Status */ +#define FUNCTION_NEEDS_INITIALIZATION BIT(5) + enum { RT721_AIF1, /* For headset mic and headphone */ RT721_AIF2, /* For speaker */ From 075bc7b1d3dde5ed43fbaabbc1a69f09b7fc3a47 Mon Sep 17 00:00:00 2001 From: Baineng Shou Date: Thu, 10 Sep 2026 10:16:52 +0800 Subject: [PATCH 0483/1417] dmaengine: mmp_pdma: fix wrong sg length in mmp_pdma_prep_slave_sg() In mmp_pdma_prep_slave_sg(), for_each_sg() iterates the scatterlist putting each entry into 'sg', but the entry length is read from 'sgl' (the list head) instead of 'sg' (the current entry): for_each_sg(sgl, sg, sg_len, i) { addr = sg_dma_address(sg); avail = sg_dma_len(sgl); /* should be 'sg' */ Consequently 'avail' is always the length of the first entry. For multi-sg lists this causes out-of-bounds reads when a later entry is shorter than the first, and silent data loss when it is longer. Single-sg or uniformly-sized lists happen to mask the issue. Fixes: c8acd6aa6bed3 ("dmaengine: mmp-pdma support") Signed-off-by: Baineng Shou Reviewed-by: Frank Li Link: https://patch.msgid.link/20260910021652.1296640-1-shoubaineng@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/mmp_pdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c index 78e3e07e681df9..ed520737882bcd 100644 --- a/drivers/dma/mmp_pdma.c +++ b/drivers/dma/mmp_pdma.c @@ -712,7 +712,7 @@ mmp_pdma_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl, for_each_sg(sgl, sg, sg_len, i) { addr = sg_dma_address(sg); - avail = sg_dma_len(sgl); + avail = sg_dma_len(sg); do { len = min_t(size_t, avail, PDMA_MAX_DESC_BYTES); From c7420dd9213de36a25e03c06e6d3367735970f31 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:24:59 +0900 Subject: [PATCH 0484/1417] ASoC: simple-card-utils: Add simple_util_remove_jack() simple_util_init_jack() acquires a GPIO descriptor with gpiod_get_optional() and hands it to snd_soc_jack_add_gpios(), which additionally installs an IRQ handler, a PM notifier, a delayed work and a sysfs export for it. simple-card-utils has no counterpart that releases any of this again. Add simple_util_remove_jack(), the counterpart of simple_util_init_jack(). It releases the jack GPIO and everything snd_soc_jack_add_gpios() installed for it via snd_soc_jack_free_gpios(), and clears sjack->gpio.desc so the descriptor is not left dangling after it has been put. This gives the generic card drivers a card->remove() path to release the jack when the card goes away. The devres cleanup alone is not enough: it is tied to the platform device, so it does not run when the card is unregistered and re-registered while the platform device stays bound - for example when a codec component is unbound and bound again. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260913102502.2805912-2-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- include/sound/simple_card_utils.h | 1 + sound/soc/generic/simple-card-utils.c | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index bd8c3a03357733..b0e00555092e60 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -219,6 +219,7 @@ static inline int simple_util_parse_aux_devs(struct simple_util_priv *priv, char int simple_util_init_jack(struct snd_soc_card *card, struct simple_util_jack *sjack, int is_hp, char *prefix, char *pin); +void simple_util_remove_jack(struct simple_util_jack *sjack); int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix); int simple_util_init_priv(struct simple_util_priv *priv, struct link_info *li); diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 5f3423129b1328..50842627434e68 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -825,6 +825,16 @@ int simple_util_init_jack(struct snd_soc_card *card, } EXPORT_SYMBOL_GPL(simple_util_init_jack); +void simple_util_remove_jack(struct simple_util_jack *sjack) +{ + if (!sjack->gpio.desc) + return; + + snd_soc_jack_free_gpios(&sjack->jack, 1, &sjack->gpio); + sjack->gpio.desc = NULL; +} +EXPORT_SYMBOL_GPL(simple_util_remove_jack); + int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix) { struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); From b59a20808727e25bc5c86586955b1901ccc10bd5 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:25:00 +0900 Subject: [PATCH 0485/1417] ASoC: simple-card: Free jack GPIOs on card remove simple_soc_probe() sets up the headphone and microphone jacks, which takes a reference on the jack detection GPIO and registers an IRQ, a PM notifier, a delayed work and a sysfs export for it. simple-card has no card->remove() callback, so none of this is released when the card is torn down. This goes unnoticed while the card is only torn down together with its platform device, because the devres cleanup registered by snd_soc_jack_add_gpios() is tied to that platform device. It becomes a problem once the card alone is unregistered and registered again while the platform device stays bound - for example when a codec component is unbound and bound again. card->probe() then runs a second time and gpiod_get_optional() fails with -EBUSY, because the descriptor is still held by the previous bind. The card is not registered again and audio stays broken until the platform device itself is unbound. Add simple_soc_remove() as the counterpart of simple_soc_probe() and release both jacks from there. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260913102502.2805912-3-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/generic/simple-card.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index e7ad7af04714dc..e2334bc10c3d7a 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -666,6 +666,16 @@ static int simple_soc_probe(struct snd_soc_card *card) return simple_ret(priv, ret); } +static int simple_soc_remove(struct snd_soc_card *card) +{ + struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + + simple_util_remove_jack(&priv->hp_jack); + simple_util_remove_jack(&priv->mic_jack); + + return 0; +} + static int simple_parse_of(struct simple_util_priv *priv) { struct snd_soc_card *card = simple_priv_to_card(priv); @@ -754,6 +764,7 @@ static int simple_probe(struct platform_device *pdev) card->owner = THIS_MODULE; card->dev = dev; card->probe = simple_soc_probe; + card->remove = simple_soc_remove; card->driver_name = "simple-card"; return simple_parse_of(priv); From 698fdd31522c42b486979054b195647d07cd71e8 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:25:01 +0900 Subject: [PATCH 0486/1417] ASoC: audio-graph-card: Free jack GPIOs on card remove graph_util_card_probe() sets up the headphone and microphone jacks, which takes a reference on the jack detection GPIO and registers an IRQ, a PM notifier, a delayed work and a sysfs export for it. audio-graph-card has no card->remove() callback, so none of this is released when the card is torn down. This goes unnoticed while the card is only torn down together with its platform device, because the devres cleanup registered by snd_soc_jack_add_gpios() is tied to that platform device. It becomes a problem once the card alone is unregistered and registered again while the platform device stays bound - for example when a codec component is unbound and bound again. card->probe() then runs a second time and gpiod_get_optional() fails with -EBUSY, because the descriptor is still held by the previous bind. The card is not registered again and audio stays broken until the platform device itself is unbound. Add graph_util_card_remove() as the counterpart of graph_util_card_probe() and hook it to card->remove. It lives in simple-card-utils.c so that audio-graph-card2 can use it as well. Signed-off-by: Chancel Liu Acked-by: Kuninori Morimoto Link: https://patch.msgid.link/20260913102502.2805912-4-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- include/sound/simple_card_utils.h | 1 + sound/soc/generic/audio-graph-card.c | 1 + sound/soc/generic/simple-card-utils.c | 11 +++++++++++ 3 files changed, 13 insertions(+) diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index b0e00555092e60..68faec56970e99 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -226,6 +226,7 @@ int simple_util_init_priv(struct simple_util_priv *priv, void simple_util_remove(struct platform_device *pdev); int graph_util_card_probe(struct snd_soc_card *card); +int graph_util_card_remove(struct snd_soc_card *card); int graph_util_is_ports0(struct device_node *port); int graph_util_parse_dai(struct simple_util_priv *priv, struct device_node *ep, struct snd_soc_dai_link_component *dlc, int *is_single_link); diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c index 0a8a6d891d1f08..127c2e6b308e09 100644 --- a/sound/soc/generic/audio-graph-card.c +++ b/sound/soc/generic/audio-graph-card.c @@ -629,6 +629,7 @@ static int graph_probe(struct platform_device *pdev) card->dapm_widgets = graph_dapm_widgets; card->num_dapm_widgets = ARRAY_SIZE(graph_dapm_widgets); card->probe = graph_util_card_probe; + card->remove = graph_util_card_remove; if (of_device_get_match_data(dev)) priv->dpcm_selectable = 1; diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 50842627434e68..98e1b419105b9e 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -1028,6 +1028,17 @@ int graph_util_card_probe(struct snd_soc_card *card) } EXPORT_SYMBOL_GPL(graph_util_card_probe); +int graph_util_card_remove(struct snd_soc_card *card) +{ + struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + + simple_util_remove_jack(&priv->hp_jack); + simple_util_remove_jack(&priv->mic_jack); + + return 0; +} +EXPORT_SYMBOL_GPL(graph_util_card_remove); + int graph_util_is_ports0(struct device_node *np) { struct device_node *parent __free(device_node) = of_get_parent(np); From df0afa439044d7099055d5dc27189a083d8c9899 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Sun, 13 Sep 2026 19:25:02 +0900 Subject: [PATCH 0487/1417] ASoC: audio-graph-card2: Free jack GPIOs on card remove audio-graph-card2 uses graph_util_card_probe() to set up the headphone and microphone jacks, but never released them again, so the jack GPIO, its IRQ, the PM notifier, the delayed work and the sysfs export stayed claimed when the card was unbound. Re-binding the card alone - for instance after a codec component is unbound and bound again - then made graph_util_card_probe() fail with -EBUSY and the card was never registered again. Hook up the graph_util_card_remove() counterpart added for audio-graph-card. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260913102502.2805912-5-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/generic/audio-graph-card2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c index 9fb3d3df5cf684..e11bc8135cb0ae 100644 --- a/sound/soc/generic/audio-graph-card2.c +++ b/sound/soc/generic/audio-graph-card2.c @@ -1310,6 +1310,7 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, goto end; card->probe = graph_util_card_probe; + card->remove = graph_util_card_remove; card->owner = THIS_MODULE; card->dev = dev; From 095858324f063dba830041f067872f0a08765d2f Mon Sep 17 00:00:00 2001 From: Hao-Qun Huang Date: Sun, 13 Sep 2026 03:20:49 +0800 Subject: [PATCH 0488/1417] spi: virtio: Use the per-transfer bits per word virtio_spi_transfer_one() puts spi->bits_per_word into the request header, so a transfer that sets its own word size reaches the backend with the device default instead. The SPI core has already copied that default into xfer->bits_per_word when the transfer leaves it at zero, the same way it does for xfer->speed_hz, which this function already uses. Per-transfer word sizes are ordinary SPI usage. mipi_dbi, for one, sends a 9-bit command and reads the reply as 8-bit data in the same message. With a 16-bit device default, a one-byte transfer asking for 8 bits goes out as a partial 16-bit word, which the backend may reject. Fixes: f98cabe3f6cf ("SPI: Add virtio SPI driver") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hao-Qun Huang Link: https://patch.msgid.link/20260913032049.11209.alvinhuang0603@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-virtio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-virtio.c b/drivers/spi/spi-virtio.c index 2256dfec5407da..3e181bd8bc94b6 100644 --- a/drivers/spi/spi-virtio.c +++ b/drivers/spi/spi-virtio.c @@ -168,7 +168,7 @@ static int virtio_spi_transfer_one(struct spi_controller *ctrl, /* Fill struct spi_transfer_head */ th->chip_select_id = spi_get_chipselect(spi, 0); - th->bits_per_word = spi->bits_per_word; + th->bits_per_word = xfer->bits_per_word; th->cs_change = xfer->cs_change; th->tx_nbits = xfer->tx_nbits; th->rx_nbits = xfer->rx_nbits; From 9a0b159ff18c8f6fcf982bb81e15a9ceb14db43a Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Mon, 14 Sep 2026 22:12:34 -1000 Subject: [PATCH 0489/1417] sched_ext: scx_qmap: Restore unused idle claims from ops.dispatch() scx_qmap tracks idle cids itself. pick_direct_dispatch_cid() claims a cid by clearing its bit and the task is inserted into that cid's local DSQ, which kicks the CPU. When the task does not arrive, for example because the insert fell back to the global DSQ after an affinity change, the CPU wakes, finds nothing and picks idle again. That is not an idle transition, so ops.update_idle() is not called and the cid stays marked busy until an unrelated task runs on it. Restore the claim from ops.dispatch(). The kick guarantees a dispatch on the kicked CPU, and when it finds nothing to run with a NULL @prev, the CPU is going back to idle. Document the pattern in ops.update_idle(), which reports only actual transitions. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi Cc: Andrea Righi --- kernel/sched/ext/internal.h | 6 ++++++ tools/sched_ext/scx_qmap.bpf.c | 14 ++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 076a351bb3f287..0adaf649d5e023 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -572,6 +572,12 @@ struct sched_ext_ops { * * Specify the %SCX_OPS_KEEP_BUILTIN_IDLE flag to keep the built-in idle * tracking. + * + * Only actual transitions are reported. A CPU that is claimed with an + * idle pick and kicked but dispatches no task returns to idle without a + * transition. A scheduler tracking idle CPUs itself must restore the + * idle state from ops.dispatch() when it returns without the next task + * to run. */ void (*update_idle)(s32 cpu, bool idle); diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index bda56c37acb54f..67b7c01cae5505 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -818,10 +818,10 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cid, struct task_struct *prev) batch--; cpuc->dsp_cnt--; if (!batch || !scx_bpf_dispatch_nr_slots()) { - if (scan_shared_dsq(false)) + if (scan_shared_dsq(false) || + scx_bpf_dsq_move_to_local(SHARED_DSQ, needs_immed(cid))) return; - scx_bpf_dsq_move_to_local(SHARED_DSQ, needs_immed(cid)); - return; + goto prev; } if (!cpuc->dsp_cnt) break; @@ -832,10 +832,14 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cid, struct task_struct *prev) if (scan_shared_dsq(false)) return; - +prev: /* * No other tasks. @prev will keep running. Update its core_sched_seq as * if the task were enqueued and dispatched immediately. + * + * No @prev to keep running means the CPU goes idle. If its claim was + * never used, that is not a transition and ops.update_idle() stays + * silent. Restore the claim here. */ if (prev) { taskc = lookup_task_ctx(prev); @@ -844,6 +848,8 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cid, struct task_struct *prev) taskc->core_sched_seq = qa.core_sched_tail_seqs[weight_to_idx(prev->scx.weight)]++; + } else { + cmask_set(cid, &qa.idle_cids.mask); } } From a9e3760b0838299649c0d57cca44daaf40ba3c33 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Mon, 14 Sep 2026 22:12:34 -1000 Subject: [PATCH 0490/1417] sched_ext: Maintain an online cid mask in the scheduler arena Schedulers on the default cid mapping treat [0, nr_online_cids) as the online set and restart on hotplug. Schedulers that install their own mapping with scx_bpf_cid_override() have no way to learn which cids are online: the count no longer identifies members and the CPU-form cpumask is unusable from cid programs. This is an obvious hole in the cid API. Add scx_bpf_online_cmask(), a kernel-maintained cmask in the scheduler's arena, allocated alongside the per-CPU scratch masks and populated after the cid mapping is finalized and before ops.init(), for child schedulers too. The pointer stays valid through ops.exit() with no reference to take. It is the arena offset as a void pointer, the same form struct_ops arena arguments arrive in. The verifier types the void return as a scalar for the program's arena cast. The mask follows the SCX hotplug notifications: seeded from cpu_active_mask and updated before ops.cid_online/offline() runs, so it lags cpu_online_mask only inside a hotplug transition. Updates walk the scheduler list under the lock that also serializes unlinking. Reads are live, not atomic snapshots. Root initialization excludes hotplug. v2: Reworded the getter kerneldoc (Andrea Righi). Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 75 ++++++++++++++++++++++-- kernel/sched/ext/internal.h | 3 +- kernel/sched/ext/sub.c | 10 ++-- tools/sched_ext/include/scx/common.bpf.h | 1 + 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 83999203a63a56..70b711c4de6e11 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -3670,8 +3670,20 @@ static void handle_hotplug(struct rq *rq, bool online) s16 *tbl = rcu_dereference_check(scx_cpu_to_cid_tbl, lockdep_is_cpus_held()); - if (tbl) + if (tbl) { + struct scx_sched *pos; + cpu_or_cid = tbl[cpu]; + + guard(raw_spinlock_irqsave)(&scx_sched_lock); + list_for_each_entry(pos, &scx_sched_all, all) { + struct scx_cmask *mask = pos->online_cmask; + + if (mask) + __assign_bit(cpu_or_cid, (unsigned long *)mask->bits, + online); + } + } } if (online && SCX_HAS_OP(sch, cpu_online)) @@ -5280,12 +5292,17 @@ static void free_exit_info(struct scx_exit_info *ei); static const char *scx_exit_reason(enum scx_exit_kind kind); static bool scx_claim_exit(struct scx_sched *sch, enum scx_exit_kind kind); -s32 scx_set_cmask_scratch_alloc(struct scx_sched *sch) +s32 scx_alloc_kern_arena_objs(struct scx_sched *sch) { size_t size = struct_size_t(struct scx_cmask, bits, SCX_CMASK_NR_WORDS(num_possible_cpus())); + struct scx_cmask *online; + struct scx_cmask_ref ref; int cpu; + /* hotplug stays excluded until the online mask is published */ + lockdep_assert_cpus_held(); + if (!sch->is_cid_type || !sch->arena_pool) return 0; @@ -5301,15 +5318,28 @@ s32 scx_set_cmask_scratch_alloc(struct scx_sched *sch) return -ENOMEM; scx_cmask_init(*slot, 0, num_possible_cpus()); } + + /* pack the online mask alongside the scratch masks */ + online = scx_arena_alloc(sch, size); + if (!online) + return -ENOMEM; + + scoped_guard(rcu) { + scx_cmask_ref_init_kern(sch, online, 0, num_possible_cpus(), &ref); + scx_cmask_ref_from_cpumask(&ref, cpu_active_mask); + } + sch->online_cmask = online; + return 0; } -static void scx_set_cmask_scratch_free(struct scx_sched *sch) +static void scx_free_kern_arena_objs(struct scx_sched *sch) { size_t size = struct_size_t(struct scx_cmask, bits, SCX_CMASK_NR_WORDS(num_possible_cpus())); int cpu; + scx_arena_free(sch, sch->online_cmask, size); if (!sch->set_cmask_scratch) return; @@ -5396,7 +5426,7 @@ static void scx_sched_free_rcu_work(struct work_struct *work) rhashtable_free_and_destroy(&sch->dsq_hash, NULL, NULL); free_exit_info(sch->exit_info); - scx_set_cmask_scratch_free(sch); + scx_free_kern_arena_objs(sch); scx_arena_pool_destroy(sch); if (sch->arena_map) bpf_map_put(sch->arena_map); @@ -7601,7 +7631,7 @@ static void scx_root_enable_workfn(struct kthread_work *work) goto err_disable; } - ret = scx_set_cmask_scratch_alloc(sch); + ret = scx_alloc_kern_arena_objs(sch); if (ret) { cpus_read_unlock(); goto err_disable; @@ -10338,13 +10368,45 @@ __bpf_kfunc u32 scx_bpf_nr_cids(void) * hotplug, which lets schedulers treat [0, nr_online_cids) as the online * range. Schedulers that prefer to handle hotplug without a restart should * install a custom mapping via scx_bpf_cid_override() and track onlining - * through the ops.cid_online / ops.cid_offline callbacks. + * through the ops.cid_online / ops.cid_offline callbacks, starting from the + * mask scx_bpf_online_cmask() returns. */ __bpf_kfunc u32 scx_bpf_nr_online_cids(void) { return num_online_cpus(); } +/** + * scx_bpf_online_cmask - Return the online cid mask in the scheduler arena + * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs + * + * Return a kernel-maintained cmask covering [0, scx_bpf_nr_cids()), or NULL if + * the calling program is not associated with a live cid-form scheduler or the + * mask is not allocated yet, as in ops.init_cids(). Treat the mask as read-only + * even though arena memory stays writable by the BPF scheduler. The mask + * follows the SCX hotplug notifications: a cid's bit is updated before + * ops.cid_online/offline() runs for it. The pointer is valid from ops.init() + * through ops.exit(). Root ops.init() runs with hotplug excluded. Other + * contexts can observe concurrent updates. + */ +__bpf_kfunc const void *scx_bpf_online_cmask(const struct bpf_prog_aux *aux) +{ + struct scx_sched *sch; + struct scx_cmask *online; + + guard(rcu)(); + + sch = scx_prog_sched(aux); + if (unlikely(!sch)) + return NULL; + online = sch->online_cmask; + if (unlikely(!online)) + return NULL; + + /* BPF rebases by the low 32 bits, like __arena callback args */ + return (void *)((unsigned long)online - sch->arena_kern_base); +} + /** * scx_bpf_this_cid - Return the cid of the CPU this program is running on * @@ -10708,6 +10770,7 @@ BTF_ID_FLAGS(func, scx_bpf_nr_node_ids) BTF_ID_FLAGS(func, scx_bpf_nr_cpu_ids) BTF_ID_FLAGS(func, scx_bpf_nr_cids) BTF_ID_FLAGS(func, scx_bpf_nr_online_cids) +BTF_ID_FLAGS(func, scx_bpf_online_cmask, KF_IMPLICIT_ARGS | KF_ARENA_RET) BTF_ID_FLAGS(func, scx_bpf_this_cid) BTF_ID_FLAGS(func, scx_bpf_get_possible_cpumask, KF_ACQUIRE) BTF_ID_FLAGS(func, scx_bpf_get_online_cpumask, KF_ACQUIRE) diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 0adaf649d5e023..3464e0f113c163 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -1561,6 +1561,7 @@ struct scx_sched { * and passes it to the callback's __arena argument. */ struct scx_cmask * __percpu *set_cmask_scratch; + struct scx_cmask *online_cmask; DECLARE_BITMAP(has_op, SCX_OPI_END); @@ -2087,7 +2088,7 @@ void scx_disable_and_exit_task(struct scx_sched *sch, struct task_struct *p); void scx_cgroup_lock(void); void scx_cgroup_unlock(void); #endif -s32 scx_set_cmask_scratch_alloc(struct scx_sched *sch); +s32 scx_alloc_kern_arena_objs(struct scx_sched *sch); void scx_disable_bypass_dsp(struct scx_sched *sch); void scx_bypass(struct scx_sched *sch, bool bypass); s32 scx_link_sched(struct scx_sched *sch); diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index 385302d199143e..f7aeb1488566d1 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -1805,6 +1805,12 @@ void scx_sub_enable_workfn(struct kthread_work *work) goto err_disable; } + scoped_guard(cpus_read_lock) { + ret = scx_alloc_kern_arena_objs(sch); + if (ret) + goto err_disable; + } + if (sch->ops.init) { ret = SCX_CALL_OP_RET(sch, init, NULL); if (ret) { @@ -1815,10 +1821,6 @@ void scx_sub_enable_workfn(struct kthread_work *work) sch->exit_info->flags |= SCX_EFLAG_INITIALIZED; } - ret = scx_set_cmask_scratch_alloc(sch); - if (ret) - goto err_disable; - struct scx_sub_attach_args sub_attach_args = { .ops = &sch->ops, .cgroup_path = sch->cgrp_path, diff --git a/tools/sched_ext/include/scx/common.bpf.h b/tools/sched_ext/include/scx/common.bpf.h index 76f5e025e1076c..2ddb01a059fda1 100644 --- a/tools/sched_ext/include/scx/common.bpf.h +++ b/tools/sched_ext/include/scx/common.bpf.h @@ -113,6 +113,7 @@ s32 scx_bpf_this_cid(void) __ksym __weak; struct task_struct *scx_bpf_cid_curr(s32 cid) __ksym __weak; u32 scx_bpf_nr_cids(void) __ksym __weak; u32 scx_bpf_nr_online_cids(void) __ksym __weak; +const void __arena *scx_bpf_online_cmask(void) __ksym __weak; u32 scx_bpf_cidperf_cap(s32 cid) __ksym __weak; u32 scx_bpf_cidperf_cur(s32 cid) __ksym __weak; s32 scx_bpf_cidperf_set(s32 cid, u32 perf) __ksym __weak; From 2b50adefed9808a56d84d1de803cad882cc787fa Mon Sep 17 00:00:00 2001 From: Nicolas Thibert Date: Tue, 8 Sep 2026 10:01:08 +0200 Subject: [PATCH 0491/1417] Bluetooth: btusb: fix NXP IW610 composite device handling The NXP IW610 module exposes itself as a composite USB device (0471:0215) with three interfaces: two real Bluetooth HCI interfaces (class 0xe0) and one vendor-specific WiFi interface (class 0xff) used by mwifiex-nxp. The composite device's whole USB descriptor reports class 0xe0/01/01 (Bluetooth), so btusb_table's generic USB_DEVICE_INFO(0xe0, 0x01, 0x01) entry matches every interface, not just the two real HCI ones -- btusb ends up binding the WiFi interface too, and mwifiex-nxp never gets it. Fix: 1. In btusb_table (the table the USB core actually matches against), explicitly ignore the WiFi interface via BTUSB_IGNORE, ahead of the generic entry. 2. In quirks_table, scope the existing BTUSB_MARVELL entry to the BT interface class instead of matching the whole device by VID/PID (harmless either way since quirks_table isn't consulted for initial binding, but keep it correct). Not upstream anywhere: checked NXP's own i.MX kernel fork (nxp-imx/linux-imx), no IW610 references in btusb.c on any branch -- their reference designs wire this chip differently (WiFi over SDIO per their release notes), so they never hit this. Signed-off-by: Nicolas Thibert Cc: stable@vger.kernel.org Assisted-by: LLM (Claude Sonnet 5, Anthropic) Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 002b9f975710fa..dc7191bf423490 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -71,6 +71,15 @@ static struct usb_driver btusb_driver; #define BTUSB_BROKEN_EXT_SCAN BIT(29) static const struct usb_device_id btusb_table[] = { + /* + * NXP IW610 (0471:0215): the composite device reports Bluetooth + * class at the whole-device level, so the generic entry below + * would also match this WiFi vendor interface. Ignore it here + * first so mwifiex-nxp can bind it instead. + */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0471, 0x0215, 0xff, 0xff, 0xff), + .driver_info = BTUSB_IGNORE }, + /* Generic Bluetooth USB device */ { USB_DEVICE_INFO(0xe0, 0x01, 0x01) }, @@ -477,6 +486,14 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, + /* + * NXP IW610 BT interfaces (Marvell-lineage silicon, same quirk as + * the 0x1286 entries above). Scoped to the BT interface class, + * not just VID/PID -- see the btusb_table entry above. + */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0471, 0x0215, 0xe0, 0x01, 0x01), + .driver_info = BTUSB_MARVELL }, + /* Intel Bluetooth devices */ { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, { USB_DEVICE(0x8087, 0x0026), .driver_info = BTUSB_INTEL_COMBINED }, From e8241766794cf551d787fa3a77c0d54bbea6f6aa Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Mon, 7 Sep 2026 00:37:43 +0100 Subject: [PATCH 0492/1417] Bluetooth: eir: validate service data length before reading UUID eir_get_service_data() reads a 16-bit UUID from the service data using get_unaligned_le16() without first checking that the data is long enough to hold a UUID16 (2 bytes). If a malformed EIR entry has a service data field with only 1 byte of payload (field_len=2), eir_get_data() returns dlen=1. The subsequent get_unaligned_le16() then reads 1 byte past the field boundary. Additionally, if the corrupted UUID happens to match, the length calculation "dlen - 2" underflows to SIZE_MAX since dlen is size_t. Current callers either pass NULL for the length parameter or bounds-check the returned length, but future callers may not. Add a check that dlen >= sizeof(u16) and skip fields that are too short to contain a valid UUID16. Fixes: 8f9ae5b3ae80 ("Bluetooth: eir: Add helpers for managing service data") Cc: stable@vger.kernel.org Signed-off-by: Aamir Ahmed Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/eir.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/eir.c b/net/bluetooth/eir.c index a55696820b227d..ee0136bfae40b5 100644 --- a/net/bluetooth/eir.c +++ b/net/bluetooth/eir.c @@ -373,7 +373,15 @@ void *eir_get_service_data(u8 *eir, size_t eir_len, u16 uuid, size_t *len) size_t dlen; while ((eir = eir_get_data(eir, eir_len, EIR_SERVICE_DATA, &dlen))) { - u16 value = get_unaligned_le16(eir); + u16 value; + + if (dlen < sizeof(value)) { + eir += dlen; + eir_len = eir_end - eir; + continue; + } + + value = get_unaligned_le16(eir); if (uuid == value) { if (len) From 6610c6fe4b8936c232048e6049bf77c70a6f759c Mon Sep 17 00:00:00 2001 From: ThangNN99 Date: Sun, 6 Sep 2026 22:21:27 +0700 Subject: [PATCH 0493/1417] Bluetooth: hci_core: Fix queuing tx_work after workqueue is drained hci_send_acl(), hci_send_sco() and hci_send_iso() queue hdev->tx_work unconditionally. They can run from the L2CAP/SCO/ISO socket send path while hci_dev_close_sync() is draining hdev->workqueue (HCIDEVDOWN racing with a socket write). Since that queue_work() is not chained work from the tx_work worker itself, __queue_work() sees the queue marked __WQ_DRAINING, warns "cannot queue %ps on wq %s", and drops the work: WARNING: CPU: 1 PID: 5985 at kernel/workqueue.c:2352 __queue_work Call Trace: queue_work_on l2cap_chan_send l2cap_sock_sendmsg ... hci_dev_close_sync() already sets HCI_CMD_DRAIN_WORKQUEUE before draining, but only hci_cmd_work() and handle_cmd_cnt_and_timer() check it before queuing. Route the tx_work producers through the same guard via a shared hci_sched_tx() helper. Fixes: 525daaea459f ("Bluetooth: hci_sync: Set HCI_CMD_DRAIN_WORKQUEUE during device close") Reported-by: syzbot+b6919040d9958e2fc1ae@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b6919040d9958e2fc1ae Signed-off-by: ThangNN99 Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index d7355c73f93e8c..c322e4736621cc 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -3236,6 +3236,17 @@ static void hci_queue_acl(struct hci_chan *chan, struct sk_buff_head *queue, bt_dev_dbg(hdev, "chan %p queued %d", chan, skb_queue_len(queue)); } +/* Queue hdev->tx_work, unless hdev->workqueue is being drained by + * hci_dev_close_sync(), which would otherwise WARN and drop the work. + */ +static void hci_sched_tx(struct hci_dev *hdev) +{ + rcu_read_lock(); + if (!hci_dev_test_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE)) + queue_work(hdev->workqueue, &hdev->tx_work); + rcu_read_unlock(); +} + void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags) { struct hci_dev *hdev = chan->conn->hdev; @@ -3244,7 +3255,7 @@ void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags) hci_queue_acl(chan, &chan->data_q, skb, flags); - queue_work(hdev->workqueue, &hdev->tx_work); + hci_sched_tx(hdev); } /* Send SCO data */ @@ -3269,7 +3280,7 @@ void hci_send_sco(struct hci_conn *conn, struct sk_buff *skb) bt_dev_dbg(hdev, "hcon %p queued %d", conn, skb_queue_len(&conn->data_q)); - queue_work(hdev->workqueue, &hdev->tx_work); + hci_sched_tx(hdev); } /* Send ISO data */ @@ -3340,7 +3351,7 @@ void hci_send_iso(struct hci_conn *conn, struct sk_buff *skb) hci_queue_iso(conn, &conn->data_q, skb); - queue_work(hdev->workqueue, &hdev->tx_work); + hci_sched_tx(hdev); } /* ---- HCI TX task (outgoing data) ---- */ From 4b837ebd0ea21ae5cc26f02dc042edc6fe7b46b9 Mon Sep 17 00:00:00 2001 From: Chandrashekar Devegowda Date: Tue, 8 Sep 2026 15:26:58 +0530 Subject: [PATCH 0494/1417] Bluetooth: btintel_pcie: validate TX skb length in send_sync btintel_pcie_prepare_tx() copies skb->len bytes into a fixed BTINTEL_PCIE_BUFFER_SIZE (4096) DMA slot via an unchecked memcpy. Oversized packets are currently rejected only in btintel_pcie_send_frame(); any future caller of btintel_pcie_send_sync() would silently overflow the DMA buffer. Add the bounds check in btintel_pcie_send_sync() itself, right before skb_push() and the DMA copy. Assisted-by: Copilot:claude-sonnet-5 code-review code-generation Fixes: 6e65a09f9275 ("Bluetooth: btintel_pcie: Add *setup* function to download firmware") Signed-off-by: Chandrashekar Devegowda Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 6d9649776ae789..405b23e2147359 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -404,6 +404,12 @@ static int btintel_pcie_send_sync(struct btintel_pcie_data *data, if (tfd_index > txq->count) return -ERANGE; + if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { + bt_dev_err(hdev, "TX skb too large (%u > %u)", skb->len, + BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN); + return -EMSGSIZE; + } + /* Firmware raises alive interrupt on HCI_OP_RESET or * BTINTEL_HCI_OP_RESET */ From d236517c264e41dc09833c708ef23bccb7a91219 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 6 Sep 2026 23:43:32 +0800 Subject: [PATCH 0495/1417] Bluetooth: coredump: Quiesce dump work on unregister hci_devcd_handle_pkt_init() arms dump_timeout and coredump producers queue dump_rx without holding an hdev reference. Unregister leaves both works live, so disconnecting during an active dump lets them access hdev after hci_release_dev() frees it. Shut down coredump processing during unregister. Close the producer gate under dump_q.lock before disabling both works, then free the active buffer and queued packets under hci_dev_lock. Serializing the gate with enqueue prevents controller-specific workers from adding packets after the final purge. Fixes: 9695ef876fd1 ("Bluetooth: Add support for hci devcoredump") Reported-by: syzbot+b170dbf55520ebf5969a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b170dbf55520ebf5969a Reported-by: Aby Sam Ross Link: https://lore.kernel.org/r/20260322210849.68743-1-abysamross@gmail.com Suggested-by: Aby Sam Ross Reported-by: Tristan Madani Link: https://lore.kernel.org/r/20260814231248.3096377-1-tristmd@gmail.com Reported-by: Xiang Mei Assisted-by: OpenAI Codex:gpt-5 Signed-off-by: Weiming Shi Reported-by: Xiang Mei Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/coredump.h | 2 + net/bluetooth/coredump.c | 65 +++++++++++++++++++++----------- net/bluetooth/hci_core.c | 1 + 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/include/net/bluetooth/coredump.h b/include/net/bluetooth/coredump.h index 1f071ab554163d..acc1849f66c0fd 100644 --- a/include/net/bluetooth/coredump.h +++ b/include/net/bluetooth/coredump.h @@ -70,6 +70,7 @@ struct hci_devcoredump { const char *hci_devcd_state_name(enum devcoredump_state state); void hci_devcd_reset(struct hci_dev *hdev); +void hci_devcd_shutdown(struct hci_dev *hdev); void hci_devcd_rx(struct work_struct *work); void hci_devcd_timeout(struct work_struct *work); @@ -89,6 +90,7 @@ static inline const char *hci_devcd_state_name(enum devcoredump_state state) } static inline void hci_devcd_reset(struct hci_dev *hdev) {} +static inline void hci_devcd_shutdown(struct hci_dev *hdev) {} static inline void hci_devcd_rx(struct work_struct *work) {} static inline void hci_devcd_timeout(struct work_struct *work) {} diff --git a/net/bluetooth/coredump.c b/net/bluetooth/coredump.c index 5bee863bd6d2c3..71fc8dab40047b 100644 --- a/net/bluetooth/coredump.c +++ b/net/bluetooth/coredump.c @@ -104,6 +104,22 @@ static void hci_devcd_free(struct hci_dev *hdev) hci_devcd_reset(hdev); } +void hci_devcd_shutdown(struct hci_dev *hdev) +{ + unsigned long flags; + + spin_lock_irqsave(&hdev->dump.dump_q.lock, flags); + hdev->dump.supported = false; + spin_unlock_irqrestore(&hdev->dump.dump_q.lock, flags); + + disable_work_sync(&hdev->dump.dump_rx); + disable_delayed_work_sync(&hdev->dump.dump_timeout); + + hci_dev_lock(hdev); + hci_devcd_free(hdev); + hci_dev_unlock(hdev); +} + /* Call with hci_dev_lock only. */ static int hci_devcd_alloc(struct hci_dev *hdev, u32 size) { @@ -442,7 +458,29 @@ EXPORT_SYMBOL(hci_devcd_register); static inline bool hci_devcd_enabled(struct hci_dev *hdev) { - return hdev->dump.supported; + return READ_ONCE(hdev->dump.supported); +} + +static int hci_devcd_queue(struct hci_dev *hdev, struct sk_buff *skb) +{ + unsigned long flags; + int err = 0; + + spin_lock_irqsave(&hdev->dump.dump_q.lock, flags); + if (!hdev->dump.supported) + err = -EOPNOTSUPP; + else + __skb_queue_tail(&hdev->dump.dump_q, skb); + spin_unlock_irqrestore(&hdev->dump.dump_q.lock, flags); + + if (err) { + kfree_skb(skb); + return err; + } + + queue_work(hdev->workqueue, &hdev->dump.dump_rx); + + return 0; } int hci_devcd_init(struct hci_dev *hdev, u32 dump_size) @@ -459,10 +497,7 @@ int hci_devcd_init(struct hci_dev *hdev, u32 dump_size) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_INIT; put_unaligned_le32(dump_size, skb_put(skb, 4)); - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_init); @@ -478,10 +513,7 @@ int hci_devcd_append(struct hci_dev *hdev, struct sk_buff *skb) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_SKB; - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_append); @@ -503,10 +535,7 @@ int hci_devcd_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_PATTERN; skb_put_data(skb, &p, sizeof(p)); - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_append_pattern); @@ -523,10 +552,7 @@ int hci_devcd_complete(struct hci_dev *hdev) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_COMPLETE; - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_complete); @@ -543,10 +569,7 @@ int hci_devcd_abort(struct hci_dev *hdev) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_ABORT; - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_abort); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index c322e4736621cc..d183efaf906321 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2673,6 +2673,7 @@ void hci_unregister_dev(struct hci_dev *hdev) disable_work_sync(&hdev->error_reset); disable_delayed_work_sync(&hdev->cmd_timer); disable_delayed_work_sync(&hdev->ncmd_timer); + hci_devcd_shutdown(hdev); hci_cmd_sync_clear(hdev); From 4914c499896121ae8b9d5b90f0abc5c8287ff396 Mon Sep 17 00:00:00 2001 From: Radek Podgorny Date: Wed, 9 Sep 2026 00:29:37 +0200 Subject: [PATCH 0496/1417] Bluetooth: put the peer's on-air address on air when we cannot resolve An identity address only reaches a peer that is advertising an RPA if the controller resolves it on our behalf. Where it cannot, the host has to put the peer's on-air address on air itself. hci_connect_le() still swaps the caller's identity address for the peer's cached RPA before creating the connection, but __hci_conn_add() resolves the RPA back to the identity address when it stores it, so the identity is what goes out. Storing the identity is right when the controller translates it on the way to the radio; without LL Privacy, or with this peer absent from the resolving list, nothing does. A peer advertising an RPA cannot answer its identity address, so the attempt burns a full create-connection timeout. That is not merely a slow connect: a controller without extended scanning cannot scan while it is initiating, so every dead attempt also takes the scanner off the air for the whole timeout. Measured on a CYW43438, which reports neither LL Privacy nor extended advertising (LE features 3f 00 00 08 00 00 00 00), against a peer advertising a resolvable private address the host holds the IRK for, with the connection requested on the peer's identity address: before: LE Create Connection to the identity address, public type 1.61s -> 22.07s, then LE Create Connection Cancel LE Connection Complete: Unknown Connection Identifier (0x02) after: LE Create Connection to the peer's RPA, random type LE Connection Complete: Success Advertising reports reaching the host per second, same window, same five unrelated devices on the adapter: before 1s:2 [nothing from 2s through 21s] 22s:5 23s:3 after 0s:11 1s:5 2s:2 3s:5 4s:3 5s:4 ... 21s:2 22s:1 23s:2 One dead connect costs twenty seconds of scanning for every device on the adapter, not just the one being dialled. Keep the RPA in conn->dst unless the controller will translate the identity address: address resolution enabled and the peer's identity actually programmed into the resolving list. Testing ll_privacy_capable() alone would not be enough: it reports the feature bit, not whether resolution is switched on and not whether this peer is in the list. Resolution is cleared with the other volatile flags on power-off and switched off again while suspend pauses scanning, and a peer's IRK is only programmed along the accept list path, so a direct-connect target, a peer without HCI_CONN_FLAG_ADDRESS_RESOLUTION, and one that did not fit in a full list are all absent from it. With the peer programmed, the identity address stays in conn->dst and the controller translates it: measured on an Intel controller, the host dials the identity and LE Enhanced Connection Complete reports Resolved Public with the peer's RPA in the separate peer resolvable private address field. With the peer absent from the list the same setup dials the RPA itself. Everything downstream already copes with an RPA in conn->dst: it is what every outgoing LE connection stored before 14b06c3a88f7, the connection complete event names the address that was dialled, and le_conn_complete_evt() resolves it back to the identity once the link is up. ISO links keep the unconditional conversion: they are created from an existing ACL or a periodic sync and never dial this address themselves. Keeping the RPA is only right while the peer is still using it, which is why the preceding patch drops the cached RPA as soon as the peer is seen advertising its identity address. Without that, a peer that turns privacy off would be dialled on the address it abandoned rather than the one it is answering on. Fixes: 14b06c3a88f7 ("Bluetooth: HCI: Always use the identity address when initializing a connection") Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-fable-5 Signed-off-by: Radek Podgorny Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 8de98af2fb5818..c9466cb2c7c008 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -1023,6 +1023,19 @@ static struct hci_conn *__hci_conn_add(struct hci_dev *hdev, int type, if (!hdev->le_mtu && hdev->acl_mtu < HCI_MIN_LE_MTU) return ERR_PTR(-ECONNREFUSED); irk = hci_get_irk(hdev, dst, dst_type); + /* An identity address only reaches a peer advertising an RPA + * if the controller translates it. Unless address resolution + * is enabled and this peer is programmed into the resolving + * list, keep the RPA the peer is on air with; + * le_conn_complete_evt() resolves it back once the link is + * up. + */ + if (irk && + (!hci_dev_test_flag(hdev, HCI_LL_RPA_RESOLUTION) || + !hci_bdaddr_list_lookup_with_irk(&hdev->le_resolv_list, + &irk->bdaddr, + irk->addr_type))) + irk = NULL; break; case SCO_LINK: case ESCO_LINK: From d0795cfd6f655f4de84868a4f4bb41a03f037b3d Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Mon, 24 Aug 2026 21:42:36 +0545 Subject: [PATCH 0497/1417] Bluetooth: hci_codec: validate vendor codec count length The Read Local Supported Codecs parsers consume the variable-sized standard codec array before parsing the vendor codec count. Although the initial reply-size check includes a vendor count byte in the fixed layout, it does not guarantee that the byte remains after the standard codec array. If a controller reply ends immediately after that array, calculating the vendor codec array size reads vnd_codecs->num beyond the skb data. Use skb_pull_data() to validate and consume each codec header before using its count in both command variants. Fixes: 8961987f3f5f ("Bluetooth: Enumerate local supported codec and cache details") Fixes: 9ae664028a9e ("Bluetooth: Add support for Read Local Supported Codecs V2") Cc: stable@vger.kernel.org Suggested-by: Luiz Augusto von Dentz Signed-off-by: Laxman Acharya Padhya Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_codec.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/net/bluetooth/hci_codec.c b/net/bluetooth/hci_codec.c index 5bc5003c387c66..7a7e813dcdda58 100644 --- a/net/bluetooth/hci_codec.c +++ b/net/bluetooth/hci_codec.c @@ -145,11 +145,12 @@ void hci_read_supported_codecs(struct hci_dev *hdev) skb_pull(skb, sizeof(rp->status)); - std_codecs = (void *)skb->data; + std_codecs = skb_pull_data(skb, sizeof(*std_codecs)); + if (!std_codecs) + goto error; /* validate codecs length before accessing */ - if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num) - + sizeof(std_codecs->num)) + if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num)) goto error; /* enumerate codec capabilities of standard codecs */ @@ -161,15 +162,14 @@ void hci_read_supported_codecs(struct hci_dev *hdev) LOCAL_CODEC_ACL_MASK | LOCAL_CODEC_SCO_MASK, &caps); } - skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num) - + sizeof(std_codecs->num)); + skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num)); - vnd_codecs = (void *)skb->data; + vnd_codecs = skb_pull_data(skb, sizeof(*vnd_codecs)); + if (!vnd_codecs) + goto error; /* validate vendor codecs length before accessing */ - if (skb->len < - flex_array_size(vnd_codecs, codec, vnd_codecs->num) - + sizeof(vnd_codecs->num)) + if (skb->len < flex_array_size(vnd_codecs, codec, vnd_codecs->num)) goto error; /* enumerate vendor codec capabilities */ @@ -214,11 +214,12 @@ void hci_read_supported_codecs_v2(struct hci_dev *hdev) skb_pull(skb, sizeof(rp->status)); - std_codecs = (void *)skb->data; + std_codecs = skb_pull_data(skb, sizeof(*std_codecs)); + if (!std_codecs) + goto error; /* check for payload data length before accessing */ - if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num) - + sizeof(std_codecs->num)) + if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num)) goto error; memset(&caps, 0, sizeof(caps)); @@ -229,15 +230,14 @@ void hci_read_supported_codecs_v2(struct hci_dev *hdev) &caps); } - skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num) - + sizeof(std_codecs->num)); + skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num)); - vnd_codecs = (void *)skb->data; + vnd_codecs = skb_pull_data(skb, sizeof(*vnd_codecs)); + if (!vnd_codecs) + goto error; /* check for payload data length before accessing */ - if (skb->len < - flex_array_size(vnd_codecs, codec, vnd_codecs->num) - + sizeof(vnd_codecs->num)) + if (skb->len < flex_array_size(vnd_codecs, codec, vnd_codecs->num)) goto error; for (i = 0; i < vnd_codecs->num; i++) { From 4e93c65f87825e1e012bce56615320aeb123815d Mon Sep 17 00:00:00 2001 From: Ibrahim Abdelkader Date: Wed, 19 Aug 2026 14:54:25 +0200 Subject: [PATCH 0498/1417] Bluetooth: hci_qca: Do not write to the serial port after it is closed hci_uart_close() closes the serdev port if HCI_QUIRK_NON_PERSISTENT_SETUP is set (for example, for the WCN399x family). A failed hci_dev_open_sync() following a successful qca_setup() calls hdev->close() but not hdev->shutdown(), so the port is closed while power->vregs_on is left true. qca_serdev_remove() then passes its power->vregs_on test and calls qca_power_off(), which writes to the closed port unconditionally. Seen on a WCN3988 by unbinding the driver after a controller failure. The trace below is from a 7.0.0 based kernel, where qca_power_off() was still named qca_power_shutdown(): Unable to handle kernel NULL pointer dereference at virtual address 0000000000000038 Call trace: tty_set_termios+0x50/0x238 (P) ttyport_set_baudrate+0x84/0xc0 serdev_device_set_baudrate+0x24/0x40 qca_power_shutdown+0x158/0x1fc [hci_uart] qca_serdev_remove+0x54/0x68 [hci_uart] serdev_drv_remove+0x1c/0x2c device_remove+0x4c/0x80 device_release_driver_internal+0x1cc/0x224 device_driver_detach+0x18/0x24 unbind_store+0xb4/0xc0 Check HCI_UART_PROTO_READY, which hci_uart_close() clears in the same place it closes the port, before writing to it. The regulator disable is left unconditional so the controller is still powered down. The dangling serport->tty that turns this into a use-after-free is addressed in a separate patch. Fixes: fa9ad876b8e0 ("Bluetooth: hci_qca: Add support for Qualcomm Bluetooth chip wcn3990") Signed-off-by: Ibrahim Abdelkader Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_qca.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index faa964735adb17..7089e9b639b2aa 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -2228,8 +2228,8 @@ static void qca_power_off(struct hci_uart *hu) bool sw_ctrl_state; struct qca_power *power; - /* From this point we go into power off state. But serial port is - * still open, stop queueing the IBS data and flush all the buffered + /* From this point we go into power off state. But serial port may + * still be open, stop queueing the IBS data and flush all the buffered * data in skb's. */ spin_lock_irqsave(&qca->hci_ibs_lock, flags); @@ -2251,8 +2251,14 @@ static void qca_power_off(struct hci_uart *hu) case QCA_WCN3990: case QCA_WCN3991: case QCA_WCN3998: - host_set_baudrate(hu, 2400); - qca_send_power_pulse(hu, false); + /* Both of these write to the serial port which may have + * already been closed by hci_uart_close(), which closes + * the port if HCI_QUIRK_NON_PERSISTENT_SETUP is set. + */ + if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) { + host_set_baudrate(hu, 2400); + qca_send_power_pulse(hu, false); + } break; default: break; From 9a10987a2f160a44a638c9a35994ca6e3089696e Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sat, 22 Aug 2026 01:43:50 +0800 Subject: [PATCH 0499/1417] Bluetooth: hci_sync: Serialize local codec list cleanup hci_dev_close_sync() clears hdev->local_codecs after releasing hdev->lock. Codec list additions and both traversals in sco_sock_getsockopt() use that lock, but the close path does not. A close and BT_CODEC query can therefore interleave as follows: hci_dev_close_sync() sco_sock_getsockopt() hci_dev_lock() fetch codec entry hci_codec_list_clear() kfree(entry) read entry->id The reader then accesses an entry which the close path has freed. KASAN reported: BUG: KASAN: slab-use-after-free in sco_sock_getsockopt+0xfa0/0xfe0 Read of size 1 at addr ffff8881001c3450 Call Trace: sco_sock_getsockopt+0xfa0/0xfe0 do_sock_getsockopt+0x537/0x7b0 __sys_getsockopt+0xf2/0x170 Allocated by task 92: hci_codec_list_add.isra.0+0x2c/0x440 hci_read_codec_capabilities+0x224/0x590 hci_read_supported_codecs+0x2c2/0x640 Freed by task 92: kfree+0x131/0x3c0 hci_codec_list_clear+0xd8/0x160 hci_dev_close_sync+0x92a/0xfa0 Take hdev->lock around the clear operation at its existing point in the close path. This makes the clear wait for active readers and prevents a new traversal until the list is empty without changing teardown ordering. Fixes: b938790e7054 ("Bluetooth: hci_codec: Fix leaking content of local_codecs") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 2a651a4d60e641..74e2b04c84b260 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -5673,7 +5673,9 @@ int hci_dev_close_sync(struct hci_dev *hdev) memset(hdev->eir, 0, sizeof(hdev->eir)); memset(hdev->dev_class, 0, sizeof(hdev->dev_class)); bacpy(&hdev->random_addr, BDADDR_ANY); + hci_dev_lock(hdev); hci_codec_list_clear(&hdev->local_codecs); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; From ca18ee413a7cb6f09885778039225e58bae0d607 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Thu, 10 Sep 2026 14:06:27 -0400 Subject: [PATCH 0500/1417] Bluetooth: ISO: Fix parent socket leak in iso_conn_ready() iso_get_sock() returns the parent socket with a reference held, which is dropped by sock_put() once the child socket has been set up. The error path taken when iso_sock_alloc() fails only calls release_sock() and returns, leaking the reference and thus the parent socket itself. Drop the reference on that path as well. Fixes: fa224d0c094a ("Bluetooth: ISO: Reassociate a socket with an active BIS") Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 75bfd5938b2ea7..4de332b8901f25 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -2289,6 +2289,7 @@ static void iso_conn_ready(struct iso_conn *conn) BTPROTO_ISO, GFP_ATOMIC, 0); if (!sk) { release_sock(parent); + sock_put(parent); return; } From 296e7f3c5071cc02dc22e1566e759179fa1792ae Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Thu, 10 Sep 2026 14:07:24 -0400 Subject: [PATCH 0501/1417] Bluetooth: ISO: set BT_LISTEN before requesting a BIG sync A BIS connection is matched to its parent socket by looking for a socket in BT_LISTEN state with the same BIG handle: iso_conn_ready() if (test_bit(HCI_CONN_BIG_SYNC, &hcon->flags)) parent = iso_get_sock(hdev, &hcon->src, &hcon->dst, BT_LISTEN, iso_match_big_hcon, hcon); The socket was only moved to BT_LISTEN after iso_conn_big_sync() returned, while the LE BIG Create Sync command has already been queued by then. If the BIG sync is established before the state is updated, which is easy to hit with an emulated controller as the command may complete in a few hundred microseconds, no parent is found and the BIS connections are never notified to the listening socket. The user space is then left waiting for connections that never arrive, e.g. bluetoothd never completes a MediaTransport1.Acquire of a Broadcast Sink transport. Move the socket to BT_LISTEN before requesting the BIG sync, so the state is visible by the time the command is queued, and restore the previous state if the request could not be started. Since the socket is briefly visible as a listening socket, child sockets may have been queued in the meantime, so drain the accept queue before restoring the state: the cleanup paths of BT_CONNECT2/BT_CONNECTED don't do it and the children would be left with a dangling parent pointer. Fixes: fbdc4bc47268 ("Bluetooth: ISO: Use defer setup to separate PA sync and BIG sync") Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 52 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 4de332b8901f25..eb99653f33f919 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -819,19 +819,24 @@ static void iso_sock_destruct(struct sock *sk) skb_queue_purge(&sk->sk_error_queue); } -static void iso_sock_cleanup_listen(struct sock *parent) +/* Close not yet accepted channels */ +static void iso_sock_flush_accept_q(struct sock *parent) { struct sock *sk; - BT_DBG("parent %p", parent); - - /* Close not yet accepted channels */ while ((sk = bt_accept_dequeue(parent, NULL))) { iso_sock_close(sk); iso_sock_kill(sk); /* Drop the reference handed back by bt_accept_dequeue(). */ sock_put(sk); } +} + +static void iso_sock_cleanup_listen(struct sock *parent) +{ + BT_DBG("parent %p", parent); + + iso_sock_flush_accept_q(parent); /* If listening socket has a hcon, properly disconnect it */ if (iso_pi(parent)->conn && iso_pi(parent)->conn->hcon) { @@ -1737,6 +1742,13 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, switch (sk->sk_state) { case BT_CONNECT2: if (test_bit(BT_SK_PA_SYNC, &pi->flags)) { + /* Move to BT_LISTEN before requesting the BIG + * sync: the BIS connections are matched to a + * parent socket in BT_LISTEN state, and they + * may be notified before the request returns. + */ + sk->sk_state = BT_LISTEN; + release_sock(sk); err = iso_conn_big_sync(sk); lock_sock(sk); @@ -1745,12 +1757,20 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, * connection may have been torn down * meanwhile and iso_chan_del() may have * already moved the socket to BT_CLOSED. - * Only move on to BT_LISTEN if the BIG sync - * was actually started and nothing else has - * changed the state. + * Only move back if the BIG sync could not be + * started and nothing else has changed the + * state. */ - if (!err && sk->sk_state == BT_CONNECT2) - sk->sk_state = BT_LISTEN; + if (err && sk->sk_state == BT_LISTEN) { + /* Discard any child socket that may + * have been queued while the socket + * was in BT_LISTEN, as the cleanup of + * BT_CONNECT2 doesn't drain the + * accept queue. + */ + iso_sock_flush_accept_q(sk); + sk->sk_state = BT_CONNECT2; + } } else { iso_conn_defer_accept(pi->conn->hcon); sk->sk_state = BT_CONFIG; @@ -1760,12 +1780,22 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, break; case BT_CONNECTED: if (test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags)) { + /* As above, the BIS connections may be + * notified before the request returns. + */ + sk->sk_state = BT_LISTEN; + release_sock(sk); err = iso_conn_big_sync(sk); lock_sock(sk); - if (!err && sk->sk_state == BT_CONNECTED) - sk->sk_state = BT_LISTEN; + if (err && sk->sk_state == BT_LISTEN) { + /* As above, don't leave any child + * socket behind in the accept queue. + */ + iso_sock_flush_accept_q(sk); + sk->sk_state = BT_CONNECTED; + } early_ret = true; } From 78b6abd6c7a7591aacdae657f813214dae4fcd3b Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 14 Sep 2026 14:56:53 +0800 Subject: [PATCH 0502/1417] Bluetooth: btmtk: fix wrong status for short WMT FUNC_CTRL events A too-short BTMTK_WMT_FUNC_CTRL event (WMT header only, no trailing 2-byte status word) is always treated as BTMTK_WMT_ON_UNDONE. This short form is how firmware acks a plain enable/disable request, and the actual result is carried in the header's own flag byte (0 = success), not a separate status word. Decode it from there instead of assuming failure. Verified setup on MT7920, MT7921, MT7922 and MT7925: no regression. Fixes: e3ac0d9f1a20 ("Bluetooth: btmtk: accept too short WMT FUNC_CTRL events") Assisted-by: Claude:claude-opus-5 Signed-off-by: Chris Lu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index 26d525acd65907..7ea8bcd8a7ecaa 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -721,7 +721,12 @@ static int btmtk_usb_hci_wmt_sync(struct hci_dev *hdev, case BTMTK_WMT_FUNC_CTRL: if (!skb_pull_data(data->evt_skb, sizeof(wmt_evt_funcc->status))) { - status = BTMTK_WMT_ON_UNDONE; + /* A plain enable/disable request is acked with just + * the WMT header and no trailing status word; the + * result is carried in the header's own flag byte. + */ + status = wmt_evt->whdr.flag ? BTMTK_WMT_ON_UNDONE : + BTMTK_WMT_ON_DONE; break; } From 8879e3e0a84a86954c855caceead4867e74a9a27 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 14 Sep 2026 14:56:54 +0800 Subject: [PATCH 0503/1417] Bluetooth: btmtksdio, btmtkuart: validate WMT event length before struct access btmtksdio.c and btmtkuart.c cast a received WMT event straight to struct btmtk_hci_wmt_evt and read its op/flag fields without checking the event is long enough to contain them, unlike btmtk.c. The FUNC_CTRL case then further casts to struct btmtk_hci_wmt_evt_funcc and reads its 2-byte status field, again without a length check. Firmware that sends a short or malformed WMT event makes both drivers read past the end of the received SKB. Mirror btmtk.c: validate the base WMT header with skb_pull_data() before touching any of its fields, and when a FUNC_CTRL event turns out to be the short, header-only form (a plain enable/disable ack with no status word), decode the result from the header's own flag byte instead (0 = success, otherwise failure). Verified setup on MT7920, MT7921, MT7922 and MT7925: no regression. Fixes: 9aebfd4a2200 ("Bluetooth: mediatek: add support for MediaTek MT7663S and MT7668S SDIO devices") Fixes: e0b67035a90b ("Bluetooth: mediatek: update the common setup between MT7622 and other devices") Assisted-by: Claude:claude-opus-5 Signed-off-by: Chris Lu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtksdio.c | 20 +++++++++++++++++++- drivers/bluetooth/btmtkuart.c | 20 +++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index 94aa60d9cc207e..7fab678925d193 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -217,7 +217,14 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev, } /* Parse and handle the return WMT event */ - wmt_evt = (struct btmtk_hci_wmt_evt *)bdev->evt_skb->data; + wmt_evt = skb_pull_data(bdev->evt_skb, sizeof(*wmt_evt)); + if (!wmt_evt) { + bt_dev_err(hdev, "WMT event too short (%u bytes)", + bdev->evt_skb->len); + err = -EINVAL; + goto err_free_skb; + } + if (wmt_evt->whdr.op != hdr->op) { bt_dev_err(hdev, "Wrong op received %d expected %d", wmt_evt->whdr.op, hdr->op); @@ -233,6 +240,17 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev, status = BTMTK_WMT_PATCH_DONE; break; case BTMTK_WMT_FUNC_CTRL: + if (!skb_pull_data(bdev->evt_skb, + sizeof(wmt_evt_funcc->status))) { + /* A plain enable/disable request is acked with just + * the WMT header and no trailing status word; the + * result is carried in the header's own flag byte. + */ + status = wmt_evt->whdr.flag ? BTMTK_WMT_ON_UNDONE : + BTMTK_WMT_ON_DONE; + break; + } + wmt_evt_funcc = (struct btmtk_hci_wmt_evt_funcc *)wmt_evt; if (be16_to_cpu(wmt_evt_funcc->status) == 0x404) status = BTMTK_WMT_ON_DONE; diff --git a/drivers/bluetooth/btmtkuart.c b/drivers/bluetooth/btmtkuart.c index 27aa48ff3ac2c6..4af6fbbbd302af 100644 --- a/drivers/bluetooth/btmtkuart.c +++ b/drivers/bluetooth/btmtkuart.c @@ -151,7 +151,14 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev, } /* Parse and handle the return WMT event */ - wmt_evt = (struct btmtk_hci_wmt_evt *)bdev->evt_skb->data; + wmt_evt = skb_pull_data(bdev->evt_skb, sizeof(*wmt_evt)); + if (!wmt_evt) { + bt_dev_err(hdev, "WMT event too short (%u bytes)", + bdev->evt_skb->len); + err = -EINVAL; + goto err_free_wc; + } + if (wmt_evt->whdr.op != hdr->op) { bt_dev_err(hdev, "Wrong op received %d expected %d", wmt_evt->whdr.op, hdr->op); @@ -167,6 +174,17 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev, status = BTMTK_WMT_PATCH_DONE; break; case BTMTK_WMT_FUNC_CTRL: + if (!skb_pull_data(bdev->evt_skb, + sizeof(wmt_evt_funcc->status))) { + /* A plain enable/disable request is acked with just + * the WMT header and no trailing status word; the + * result is carried in the header's own flag byte. + */ + status = wmt_evt->whdr.flag ? BTMTK_WMT_ON_UNDONE : + BTMTK_WMT_ON_DONE; + break; + } + wmt_evt_funcc = (struct btmtk_hci_wmt_evt_funcc *)wmt_evt; if (be16_to_cpu(wmt_evt_funcc->status) == 0x404) status = BTMTK_WMT_ON_DONE; From 7b60ee5f46f2ee329de661f7c68b6818d8136220 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Mon, 14 Sep 2026 09:47:29 +0000 Subject: [PATCH 0504/1417] Bluetooth: btmtksdio: Fix PM runtime reference leak in shutdown In btmtksdio_shutdown(), pm_runtime_get_sync() is called at the beginning of the function. However, if sending the WMT function control command fails later, the driver returns early. It bypasses the corresponding pm_runtime_put_noidle() and pm_runtime_disable() calls, leaking the PM usage counter and leaving PM runtime enabled indefinitely. Fall through to execute the PM runtime cleanup block even if WMT errors. Fixes: 7f3c563c575e ("Bluetooth: btmtksdio: Add runtime PM support to SDIO based Bluetooth") Signed-off-by: Tzung-Bi Shih Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtksdio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index 7fab678925d193..a15ae6598c6654 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -1262,10 +1262,8 @@ static int btmtksdio_shutdown(struct hci_dev *hdev) wmt_params.status = NULL; err = mtk_hci_wmt_sync(hdev, &wmt_params); - if (err < 0) { + if (err < 0) bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err); - return err; - } ignore_wmt_cmd: pm_runtime_put_noidle(bdev->dev); From 2ea5a87a5a7ae58cb2662b8a7d06f209383e1765 Mon Sep 17 00:00:00 2001 From: Sai Teja Aluvala Date: Fri, 11 Sep 2026 17:22:22 +0530 Subject: [PATCH 0505/1417] Bluetooth: btintel_pcie: fix off-by-one bounds check in RX submit btintel_pcie_submit_rx() used frbd_index > rxq->count to guard the FRBD array access, allowing frbd_index == rxq->count to pass through and index one element past the end of the array. Change the check to >= rxq->count so every out-of-range index is rejected. This issue was reported by Claude Mythos. Fixes: c2b636b3f788 (Bluetooth: btintel_pcie: Add support for PCIe transport) Signed-off-by: Sai Teja Aluvala Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 405b23e2147359..6e6e2b19815ca9 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -508,7 +508,7 @@ static int btintel_pcie_submit_rx(struct btintel_pcie_data *data) frbd_index = data->ia.tr_hia[BTINTEL_PCIE_RXQ_NUM]; - if (frbd_index > rxq->count) + if (frbd_index >= rxq->count) return -ERANGE; /* Prepare for RX submit. It updates the FRBD with the address of DMA From 555cd2bd860e7c4bdc3f4e4405b05515b0d9bc87 Mon Sep 17 00:00:00 2001 From: Radek Podgorny Date: Sun, 13 Sep 2026 22:28:02 +0200 Subject: [PATCH 0506/1417] Bluetooth: keep dst_type with dst when reusing an LE connection hci_connect_le() swaps the caller's identity address for the peer's cached RPA when one is known, and stamps the matching ADDR_LE_DEV_RANDOM on the local dst_type. On the conn-reuse path only the address is copied into the connection: if (conn) { bacpy(&conn->dst, dst); so conn->dst ends up holding an RPA while conn->dst_type still names the identity it was resolved from, and hci_le_create_conn_sync() puts that pair on air unchanged. An RPA declared as a public address is not something any peer can answer. Measured on a CYW43438 against a peer advertising an RPA the host holds the IRK for, connecting to the identity address over a raw L2CAP socket. The first attempt creates the connection, the second takes the reuse path: LE Create Connection 3C:78:95:78:37:C3 type public LE Create Connection 5B:75:A2:26:D6:18 type public LE Connection Complete: Unknown Connection Identifier (0x02) The second address is the peer's RPA. btmon annotates it with an OUI lookup rather than "(Resolvable)" precisely because the command declares it public; the same bit pattern annotates as resolvable once the type is right. The mistyped pair is also why nothing downstream repairs it. hci_bdaddr_is_rpa() tests the type before the address, so an RPA carrying a public type is not recognised as one, and hci_find_irk_by_addr() then searches for an identity address that does not match it either. Copy the type along with the address. The assignment used to be unconditional just below this block and covered both paths; it moved into hci_conn_add_unset(), which the reuse path does not go through. Cc: stable@vger.kernel.org Fixes: 14b06c3a88f7 ("Bluetooth: HCI: Always use the identity address when initializing a connection") Assisted-by: Claude:claude-opus-5 Signed-off-by: Radek Podgorny Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index c9466cb2c7c008..fa72cf8aaa7a0a 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -1518,7 +1518,15 @@ struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst, } if (conn) { + /* dst may just have been swapped for the peer's RPA above, and + * dst_type describes dst -- it has to travel with it. Leaving + * the identity type behind makes the pair describe a peer that + * does not exist, and nothing downstream repairs it: + * hci_bdaddr_is_rpa() tests the type before the address, so + * the RPA is never treated as one. + */ bacpy(&conn->dst, dst); + conn->dst_type = dst_type; } else { conn = hci_conn_add_unset(hdev, LE_LINK, dst, dst_type, role); if (IS_ERR(conn)) From 801fb950cae7048eb7d83b18857d1ca37b8cd5a4 Mon Sep 17 00:00:00 2001 From: Juan Perdomo Date: Sat, 12 Sep 2026 23:09:45 -0400 Subject: [PATCH 0507/1417] Bluetooth: RFCOMM: avoid socket lock inversion in listener cleanup rfcomm_sock_cleanup_listen() closes unaccepted child sockets through rfcomm_sock_close(), which takes the child socket lock before rfcomm_dlc_close() acquires rfcomm_mutex. The RFCOMM worker takes these locks in reverse order while handling connections and DLC state changes, so lockdep reports a possible deadlock. Close dequeued children without taking their socket lock. The accept queue owns a reference to each child, and bt_accept_dequeue() locks the child while unlinking it and clearing its parent pointer. Dropping the child lock makes it important to prevent a concurrent rfcomm_connect_ind() from enqueueing a new child after cleanup observes an empty queue. Set a listening socket to BT_CLOSED while its lock is still held, before dropping the lock and draining the queue. The state check in rfcomm_connect_ind() then rejects new children once cleanup starts. Reported-by: syzbot+0cece8fa7d83523f47a3@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0cece8fa7d83523f47a3 Fixes: b7ce436a5d79 ("Bluetooth: switch to lock_sock in RFCOMM") Signed-off-by: Juan Perdomo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/sock.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index 958081adb9b5cf..e2486bc11cbce0 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -242,9 +242,7 @@ static void __rfcomm_sock_close(struct sock *sk) */ static void rfcomm_sock_close(struct sock *sk) { - lock_sock(sk); __rfcomm_sock_close(sk); - release_sock(sk); } static void rfcomm_sock_init(struct sock *sk, struct sock *parent) @@ -905,6 +903,7 @@ static int rfcomm_sock_compat_ioctl(struct socket *sock, unsigned int cmd, unsig static int rfcomm_sock_shutdown(struct socket *sock, int how) { struct sock *sk = sock->sk; + bool cleanup_listen = false; int err = 0; BT_DBG("sock %p, sk %p", sock, sk); @@ -915,9 +914,17 @@ static int rfcomm_sock_shutdown(struct socket *sock, int how) lock_sock(sk); if (!sk->sk_shutdown) { sk->sk_shutdown = SHUTDOWN_MASK; + if (sk->sk_state == BT_LISTEN) { + /* Block new children before cleaning up without sk lock. */ + sk->sk_state = BT_CLOSED; + cleanup_listen = true; + } release_sock(sk); - __rfcomm_sock_close(sk); + if (cleanup_listen) + rfcomm_sock_cleanup_listen(sk); + else + __rfcomm_sock_close(sk); lock_sock(sk); if (sock_flag(sk, SOCK_LINGER) && sk->sk_lingertime && From 8861db305103107199b1426f25fde1fb6d465583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 3 Sep 2026 13:43:38 +0200 Subject: [PATCH 0508/1417] selinux: always fill AVC decision in avc_has_perm_noaudit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit avc_has_perm_noaudit() is documented to return a copy of the access decision in @avd, but its early return for an empty requested permission set leaves the buffer untouched. All callers pass an uninitialized stack variable and afterwards feed it to avc_audit(), and the inode hook even stores it in the per-task decision cache. Fill in a deny-all, audit-all decision, similar to avd_init(), so every caller receives a defined value at no cost on the hot path. Cc: stable@vger.kernel.org Fixes: e6f2f381e4015386 ("selinux: replace BUG_ONs with WARN_ONs in avc.c") Signed-off-by: Christian Göttsche Reviewed-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/avc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/security/selinux/avc.c b/security/selinux/avc.c index a9401d6c2e5f1c..560a82c6d68262 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -1149,8 +1149,11 @@ inline int avc_has_perm_noaudit(u32 ssid, u32 tsid, u32 denied; struct avc_node *node; - if (WARN_ON(!requested)) + if (WARN_ON(!requested)) { + /* Provide a deny-all, audit-all decision to the caller. */ + *avd = (struct av_decision){ .auditdeny = 0xffffffff }; return -EACCES; + } rcu_read_lock(); node = avc_lookup(ssid, tsid, tclass); From 8cd92f77ae4f5371a7d581f8324c24919670b304 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 21 Aug 2026 07:44:42 +0100 Subject: [PATCH 0509/1417] KVM: arm64: vgic-its: Free the caches when GITS_BASER changes A guest that disables the ITS and re-points or shrinks GITS_BASER with VALID still set keeps the devices and collections it mapped against the old table, as KVM frees them only when VALID is cleared. The contents of the table are IMPLEMENTATION DEFINED, so a write that gives GITS_BASER a different address or size may lose whatever the old value described. Free the list whenever the stored value changes, and drop the translation cache with it. The cache is not empty just because the ITS is disabled: its->enabled is written under the cmd_lock, while vgic_its_resolve_lpi() tests it under the its_lock, so an injection can still cache an entry after the ITS was disabled. Hence the invalidation inside the its_lock section. Test for a change rather than a write: its_restore_enable() rewrites GITS_BASER from its probe-time cache on resume, and KVM reports GITS_TYPER.HCC as 0, so nothing re-maps the boot CPU's collection afterwards. Fixes: 36d6961c2b481 ("KVM: arm/arm64: vgic-its: Free caches when GITS_BASER Valid bit is cleared") Suggested-by: Marc Zyngier Link: https://lore.kernel.org/all/87ecg9owwa.wl-maz@kernel.org/ Signed-off-by: Fuad Tabba Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260821064445.615838-2-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/vgic/vgic-its.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 9e782a4fea7e5c..ab89b0138efd07 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -1658,7 +1658,7 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, unsigned long val) { const struct vgic_its_abi *abi = vgic_its_get_abi(its); - u64 entry_size, table_type; + u64 old, entry_size, table_type; u64 reg, *regptr, clearbits = 0; /* When GITS_CTLR.Enable is 1, we ignore write accesses. */ @@ -1681,7 +1681,9 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, return; } - reg = update_64bit_reg(*regptr, addr & 7, len, val); + old = *regptr; + + reg = update_64bit_reg(old, addr & 7, len, val); reg &= ~GITS_BASER_RO_MASK; reg &= ~clearbits; @@ -1691,7 +1693,8 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, *regptr = reg; - if (!(reg & GITS_BASER_VALID)) { + /* The ITS driver rewrites an unchanged GITS_BASER on resume. */ + if (reg != old) { /* Take the its_lock to prevent a race with a save/restore */ mutex_lock(&its->its_lock); switch (table_type) { @@ -1702,6 +1705,8 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, vgic_its_free_collection_list(kvm, its); break; } + /* A concurrent injection may have cached a translation. */ + vgic_its_invalidate_cache(its); mutex_unlock(&its->its_lock); } } From 30908e7272479b6453745022abb9f2eb9c9933f7 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 21 Aug 2026 07:44:43 +0100 Subject: [PATCH 0510/1417] Revert "KVM: arm64: vgic-its: Don't save collections the table cannot hold" This reverts commit 9b10fb74e4b661543d188701bd4d024fc5c18f58. Freeing the collections when GITS_BASER changes removes the state this check rejected: vgic_its_cmd_handle_mapi(), vgic_its_cmd_handle_mapc() and vgic_its_restore_cte() all validate the ID against the current table before allocating, and the table can no longer change under the list. What remains is a collection whose entry is not backed by a memslot, which the write fails on anyway, so the check costs a save userspace should be able to issue reliably and buys nothing. The reverted commit credited the check with bounding the walk as well. It stays bounded without it: collection IDs are unique and each is below the table's capacity, so the list cannot be longer than the table. Suggested-by: Marc Zyngier Link: https://lore.kernel.org/all/87ecg9owwa.wl-maz@kernel.org/ Signed-off-by: Fuad Tabba Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260821064445.615838-3-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/vgic/vgic-its.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index ab89b0138efd07..313bf9e802bfc3 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -2546,9 +2546,6 @@ static int vgic_its_save_collection_table(struct vgic_its *its) max_size = GITS_BASER_NR_PAGES(baser) * SZ_64K; list_for_each_entry(collection, &its->collection_list, coll_list) { - if (!vgic_its_check_id(its, baser, collection->collection_id, NULL)) - return -EINVAL; - ret = vgic_its_save_cte(its, collection, gpa); if (ret) return ret; From cc5d96036e01ac330d24b2f0c336d60f82ab4930 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 21 Aug 2026 07:44:44 +0100 Subject: [PATCH 0511/1417] KVM: arm64: vgic-its: Skip unreachable devices instead of failing the save vgic_its_save_device_tables() aborts with -EINVAL when a device's entry falls outside the device table, which a guest can arrange on its own: an indirect table lets it clear an L1 entry's valid bit without touching GITS_BASER. That fails a save userspace should be able to issue reliably. Skip the device instead, and point the saved DTE chain past it, as commit ad1e686e2378d ("KVM: arm64: vgic-its: Point saved ITEs at the next valid entry") does for ITEs. compute_next_devid_offset() takes the next device off the list whether or not it was saved, so the predecessor would otherwise point at an entry the save never wrote. Restore follows that offset while it stays inside the table being scanned: within an L2 block, or anywhere in a flat table. Both need userspace to remove a memslot under the table, since dropping an L1 entry takes the whole block with it and scan_its_table() stops at the block boundary. Fixes: 57a9a117154c9 ("KVM: arm64: vgic-its: Device table save/restore") Suggested-by: Marc Zyngier Link: https://lore.kernel.org/all/86bjaz5s6v.wl-maz@kernel.org/ Signed-off-by: Fuad Tabba Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260821064445.615838-4-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/vgic/vgic-its.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 313bf9e802bfc3..0904ae850c3595 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -2024,18 +2024,22 @@ static int vgic_its_attr_regs_access(struct kvm_device *dev, return ret; } -static u32 compute_next_devid_offset(struct list_head *h, +static u32 compute_next_devid_offset(struct vgic_its *its, u64 baser, struct its_device *dev) { - struct its_device *next; - u32 next_offset; + struct its_device *next = dev; - if (list_is_last(&dev->dev_list, h)) - return 0; - next = list_next_entry(dev, dev_list); - next_offset = next->device_id - dev->device_id; + /* + * Point at the next device vgic_its_save_device_tables() saves. It + * sorts device_list first, so the subtraction cannot underflow. + */ + list_for_each_entry_continue(next, &its->device_list, dev_list) { + if (vgic_its_check_id(its, baser, next->device_id, NULL)) + return min_t(u32, next->device_id - dev->device_id, + VITS_DTE_MAX_DEVID_OFFSET); + } - return min_t(u32, next_offset, VITS_DTE_MAX_DEVID_OFFSET); + return 0; } static u32 compute_next_eventid_offset(struct list_head *h, struct its_ite *ite) @@ -2276,17 +2280,18 @@ static int vgic_its_restore_itt(struct vgic_its *its, struct its_device *dev) * vgic_its_save_dte - Save a device table entry at a given GPA * * @its: ITS handle + * @baser: GITS_BASER the caller is saving against * @dev: ITS device * @ptr: GPA */ -static int vgic_its_save_dte(struct vgic_its *its, struct its_device *dev, - gpa_t ptr) +static int vgic_its_save_dte(struct vgic_its *its, u64 baser, + struct its_device *dev, gpa_t ptr) { u64 val, itt_addr_field; u32 next_offset; itt_addr_field = dev->itt_addr >> 8; - next_offset = compute_next_devid_offset(&its->device_list, dev); + next_offset = compute_next_devid_offset(its, baser, dev); val = (1ULL << KVM_ITS_DTE_VALID_SHIFT | ((u64)next_offset << KVM_ITS_DTE_NEXT_SHIFT) | (itt_addr_field << KVM_ITS_DTE_ITTADDR_SHIFT) | @@ -2385,15 +2390,16 @@ static int vgic_its_save_device_tables(struct vgic_its *its) int ret; gpa_t eaddr; + /* Don't fail a save that userspace must be able to issue. */ if (!vgic_its_check_id(its, baser, dev->device_id, &eaddr)) - return -EINVAL; + continue; ret = vgic_its_save_itt(its, dev); if (ret) return ret; - ret = vgic_its_save_dte(its, dev, eaddr); + ret = vgic_its_save_dte(its, baser, dev, eaddr); if (ret) return ret; } From 6f182db39fb00548c26d689163ceb2fe4a802793 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 21 Aug 2026 07:44:45 +0100 Subject: [PATCH 0512/1417] KVM: arm64: selftests: Add ITS table save tests Cover the two ways a guest can leave a table that KVM_DEV_ARM_ITS_SAVE_TABLES has to cope with: a GITS_BASER write that changes the table, which drops the collections it described, and a device whose L2 block the guest invalidated, which the save skips. Each case then resets and restores, which is what the save exists for. Both fail without the preceding patches. The first save returns -EINVAL, and the second saves a DTE pointing 8192 entries ahead, at an entry the save never wrote. Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Fuad Tabba Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260821064445.615838-5-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/arm64/vgic_its_save.c | 441 ++++++++++++++++++ 2 files changed, 442 insertions(+) create mode 100644 tools/testing/selftests/kvm/arm64/vgic_its_save.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 96bab7002d39e9..6a1482e3a286b0 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -189,6 +189,7 @@ TEST_GEN_PROGS_arm64 += arm64/stage2_block_transitions TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config TEST_GEN_PROGS_arm64 += arm64/vgic_init TEST_GEN_PROGS_arm64 += arm64/vgic_irq +TEST_GEN_PROGS_arm64 += arm64/vgic_its_save TEST_GEN_PROGS_arm64 += arm64/vgic_lpi_stress TEST_GEN_PROGS_arm64 += arm64/vgic_v5 TEST_GEN_PROGS_arm64 += arm64/vpmu_counter_access diff --git a/tools/testing/selftests/kvm/arm64/vgic_its_save.c b/tools/testing/selftests/kvm/arm64/vgic_its_save.c new file mode 100644 index 00000000000000..864da01539f3f4 --- /dev/null +++ b/tools/testing/selftests/kvm/arm64/vgic_its_save.c @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * vgic_its_save - KVM_DEV_ARM_ITS_SAVE_TABLES against tables a guest broke. + * + * Both cases are reachable by a guest on its own, and neither may fail a save + * that userspace has to be able to issue: + * + * - Changing GITS_BASER drops the collections it described, so the save + * writes nothing but the terminating invalid entry. + * - A device the device table can no longer address is skipped, and the saved + * DTE chain skips it too rather than pointing at an entry never written. + * + * Both cases then reset and restore, which is what the save exists for. + * + * Copyright (c) 2026 Google LLC + * Author: Fuad Tabba + */ + +#include +#include +#include +#include + +#include "kvm_util.h" +#include "gic.h" +#include "gic_v3.h" +#include "gic_v3_its.h" +#include "processor.h" +#include "ucall.h" +#include "vgic.h" + +#define TEST_MEMSLOT_INDEX 1 + +/* All three ITS table entry sizes are 8 bytes in ABI 0. */ +#define ESZ 8 +#define ENTRIES_PER_PAGE (SZ_64K / ESZ) + +/* CTE and DTE layout, mirroring KVM's KVM_ITS_* in arch/arm64/kvm/vgic/vgic.h */ +#define CTE_VALID_MASK BIT_ULL(63) +#define DTE_VALID_MASK BIT_ULL(63) +#define DTE_NEXT_SHIFT 49 +#define DTE_NEXT_MASK GENMASK_ULL(62, 49) + +/* L1 entry of an indirect table: valid bit plus a 64K aligned L2 address. */ +#define L1E_VALID_MASK BIT_ULL(63) +#define L1E_ADDR_MASK GENMASK_ULL(51, 16) + +#define GITS_BASER_PAGES_MASK GENMASK_ULL(7, 0) + +#define POISON 0xdeadbeefdeadbeefULL + +/* The collection table starts at two pages and is shrunk to one. */ +#define COLL_TBL_PAGES 2 +#define COLL_TBL_SZ (COLL_TBL_PAGES * SZ_64K) + +/* One more collection than the shrunken table can hold. */ +#define NR_COLLECTIONS (ENTRIES_PER_PAGE + 1) + +/* Two devices, one per L2 block of the indirect device table. */ +#define DEVICE_A_ID 0 +#define DEVICE_B_ID ENTRIES_PER_PAGE + +/* + * its_send_mapd_cmd() encodes ilog2(itt_size) - 1 as num_eventid_bits, and + * vgic_its_restore_itt() scans BIT_ULL(num_eventid_bits) * ESZ, so the size + * handed to MAPD has to match the ITT allocated for it. + */ +#define ITT_EVENTID_BITS 13 +#define ITT_MAPD_SIZE BIT_ULL(ITT_EVENTID_BITS + 1) +#define ITT_SZ (BIT_ULL(ITT_EVENTID_BITS) * ESZ) + +static struct kvm_vm *vm; +static struct kvm_vcpu *vcpu; +static int its_fd; +static gpa_t gpa_base; + +static struct test_data { + gpa_t device_table; + gpa_t collection_table; + gpa_t cmdq_base; + void *cmdq_base_va; + + gpa_t lpi_prop_table; + gpa_t lpi_pend_table; + + void *device_l1_va; + gpa_t device_l2[2]; + gpa_t itt_tables; +} test_data; + +static unsigned long its_baser_offset(unsigned int type) +{ + int i; + + for (i = 0; i < GITS_BASER_NR_REGS; i++) { + unsigned long offset = GITS_BASER + (i * sizeof(u64)); + u64 baser = readq_relaxed(GITS_BASE_GVA + offset); + + if (GITS_BASER_TYPE(baser) == type) + return offset; + } + + GUEST_FAIL("Couldn't find an ITS BASER of type %u", type); + return -1; +} + +static void its_set_enable(bool enable) +{ + u32 ctlr = readl_relaxed(GITS_BASE_GVA + GITS_CTLR); + + if (enable) + ctlr |= GITS_CTLR_ENABLE; + else + ctlr &= ~GITS_CTLR_ENABLE; + + writel_relaxed(ctlr, GITS_BASE_GVA + GITS_CTLR); +} + +/* + * Shrink the collection table to a single page, leaving VALID set. BASER + * writes are ignored while the ITS is enabled. + */ +static void guest_shrink_coll_table(void) +{ + unsigned long offset = its_baser_offset(GITS_BASER_TYPE_COLLECTION); + u64 baser; + + its_set_enable(false); + + baser = readq_relaxed(GITS_BASE_GVA + offset); + baser &= ~GITS_BASER_PAGES_MASK; + writeq_relaxed(baser, GITS_BASE_GVA + offset); +} + +static void guest_baser_change(void) +{ + u32 coll_id; + + gic_init(GIC_V3, 1); + gic_rdist_enable_lpis(test_data.lpi_prop_table, SZ_64K, + test_data.lpi_pend_table); + + its_init(test_data.collection_table, COLL_TBL_SZ, + test_data.device_table, SZ_64K, + test_data.cmdq_base, SZ_64K); + + for (coll_id = 0; coll_id < NR_COLLECTIONS; coll_id++) + its_send_mapc_cmd(test_data.cmdq_base_va, 0, coll_id, true); + + guest_shrink_coll_table(); + + GUEST_DONE(); +} + +/* Turn the already installed device table into an indirect one. */ +static void guest_make_device_table_indirect(void) +{ + unsigned long offset = its_baser_offset(GITS_BASER_TYPE_DEVICE); + u64 baser; + + its_set_enable(false); + + baser = readq_relaxed(GITS_BASE_GVA + offset); + writeq_relaxed(baser | GITS_BASER_INDIRECT, GITS_BASE_GVA + offset); + + its_set_enable(true); +} + +static void guest_unreachable_device(void) +{ + u64 *l1; + + gic_init(GIC_V3, 1); + gic_rdist_enable_lpis(test_data.lpi_prop_table, SZ_64K, + test_data.lpi_pend_table); + + its_init(test_data.collection_table, SZ_64K, + test_data.device_table, SZ_64K, + test_data.cmdq_base, SZ_64K); + + guest_make_device_table_indirect(); + + /* Both L2 blocks present, so both MAPDs are in range. */ + l1 = test_data.device_l1_va; + l1[0] = L1E_VALID_MASK | (test_data.device_l2[0] & L1E_ADDR_MASK); + l1[1] = L1E_VALID_MASK | (test_data.device_l2[1] & L1E_ADDR_MASK); + + its_send_mapd_cmd(test_data.cmdq_base_va, DEVICE_A_ID, + test_data.itt_tables, ITT_MAPD_SIZE, true); + its_send_mapd_cmd(test_data.cmdq_base_va, DEVICE_B_ID, + test_data.itt_tables + ITT_SZ, ITT_MAPD_SIZE, true); + + /* + * Drop the block holding device B. No ITS command and no GITS_BASER + * write is involved, so nothing tells KVM the device is now + * unreachable. + */ + l1[1] = 0; + + GUEST_DONE(); +} + +static void run_guest(void) +{ + struct ucall uc; + + vcpu_run(vcpu); + switch (get_ucall(vcpu, &uc)) { + case UCALL_DONE: + break; + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + break; + default: + TEST_FAIL("Unexpected ucall: %lu", uc.cmd); + } +} + +static int save_tables(void) +{ + return __kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL, + KVM_DEV_ARM_ITS_SAVE_TABLES, NULL); +} + +static u64 its_reg_get(unsigned long offset) +{ + u64 val; + + kvm_device_attr_get(its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, offset, + &val); + return val; +} + +static void its_reg_set(unsigned long offset, u64 val) +{ + kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, offset, + &val); +} + +/* + * What a migration target does with the saved tables, in the order + * Documentation/virt/kvm/devices/arm-vgic-its.rst gives: the GITS_ registers + * first, then the tables. The reset in between clears GITS_BASER.Valid, + * which is why the registers have to be written back before the restore. + */ +static void reset_and_restore_tables(void) +{ + u64 baser[GITS_BASER_NR_REGS]; + int ret, i; + + for (i = 0; i < GITS_BASER_NR_REGS; i++) + baser[i] = its_reg_get(GITS_BASER + (i * sizeof(u64))); + + ret = __kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL, + KVM_DEV_ARM_ITS_CTRL_RESET, NULL); + TEST_ASSERT(!ret, "Expected the reset to succeed, got ret %d errno %d", + ret, errno); + + for (i = 0; i < GITS_BASER_NR_REGS; i++) + its_reg_set(GITS_BASER + (i * sizeof(u64)), baser[i]); + + ret = __kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL, + KVM_DEV_ARM_ITS_RESTORE_TABLES, NULL); + TEST_ASSERT(!ret, "Expected the restore to succeed, got ret %d errno %d", + ret, errno); +} + +static void poison_range(gpa_t base, size_t size) +{ + u64 *entry = addr_gpa2hva(vm, base); + size_t i; + + for (i = 0; i < size / ESZ; i++) + entry[i] = POISON; +} + +static void setup_memslot(size_t sz) +{ + size_t pages = sz / vm->page_size; + + gpa_base = ((vm_compute_max_gfn(vm) + 1) * vm->page_size) - sz; + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa_base, + TEST_MEMSLOT_INDEX, pages, 0); +} + +static gpa_t alloc_64k(size_t nr) +{ + size_t pages_per_64k = vm_calc_num_guest_pages(vm->mode, SZ_64K); + gpa_t gpa = vm_phy_pages_alloc(vm, nr * pages_per_64k, gpa_base, + TEST_MEMSLOT_INDEX); + + TEST_ASSERT(IS_ALIGNED(gpa, SZ_64K), + "Allocation at 0x%lx is not 64K aligned, GITS_BASER cannot address it", + gpa); + return gpa; +} + +static void map_to_guest(gpa_t gpa, size_t nr) +{ + size_t pages_per_64k = vm_calc_num_guest_pages(vm->mode, SZ_64K); + + virt_map(vm, gpa, gpa, nr * pages_per_64k); +} + +static void setup_common(void) +{ + test_data.cmdq_base = alloc_64k(1); + map_to_guest(test_data.cmdq_base, 1); + test_data.cmdq_base_va = (void *)test_data.cmdq_base; + + test_data.lpi_prop_table = alloc_64k(1); + test_data.lpi_pend_table = alloc_64k(1); +} + +static void teardown(void) +{ + close(its_fd); + kvm_vm_free(vm); + memset(&test_data, 0, sizeof(test_data)); +} + +/* + * A GITS_BASER write that changes the table drops the collections it + * described. The save then has an empty list, so it writes the terminating + * invalid entry and nothing else. + */ +static void test_baser_change_drops_collections(void) +{ + u64 *cte; + int ret, i; + + pr_info("Testing that a GITS_BASER change drops the collections\n"); + + vm = vm_create_with_one_vcpu(&vcpu, guest_baser_change); + setup_memslot((4 + COLL_TBL_PAGES) * SZ_64K); + its_fd = vgic_its_setup(vm); + + test_data.device_table = alloc_64k(1); + test_data.collection_table = alloc_64k(COLL_TBL_PAGES); + setup_common(); + + sync_global_to_guest(vm, test_data); + run_guest(); + + /* Anything KVM writes is then the only thing that changed. */ + poison_range(test_data.collection_table, COLL_TBL_SZ); + + ret = save_tables(); + TEST_ASSERT(!ret, "Expected the save to succeed, got %d errno %d", + ret, errno); + + cte = addr_gpa2hva(vm, test_data.collection_table); + + /* + * Finding the terminator at the head of the table is also what proves + * the reads below landed in the saved table rather than elsewhere. + */ + TEST_ASSERT(le64toh(cte[0]) == 0, + "CTE 0: expected the terminating invalid entry, got 0x%llx", + (unsigned long long)le64toh(cte[0])); + + for (i = 1; i < COLL_TBL_SZ / ESZ; i++) + TEST_ASSERT(cte[i] == POISON, + "CTE %d: expected it untouched, got 0x%llx", + i, (unsigned long long)cte[i]); + + reset_and_restore_tables(); + + teardown(); +} + +/* + * A device whose L2 block the guest dropped is skipped by the save, and the + * DTE chain skips it too: left alone, the surviving device would point at an + * entry the save never wrote. + */ +static void test_unreachable_device_skipped(void) +{ + u64 dte; + int ret; + + pr_info("Testing that an unreachable device is skipped by the save\n"); + + vm = vm_create_with_one_vcpu(&vcpu, guest_unreachable_device); + setup_memslot(9 * SZ_64K); + its_fd = vgic_its_setup(vm); + + test_data.device_table = alloc_64k(1); + test_data.collection_table = alloc_64k(1); + test_data.device_l2[0] = alloc_64k(1); + test_data.device_l2[1] = alloc_64k(1); + test_data.itt_tables = alloc_64k(2); + setup_common(); + + map_to_guest(test_data.device_table, 1); + test_data.device_l1_va = (void *)test_data.device_table; + + sync_global_to_guest(vm, test_data); + run_guest(); + + poison_range(test_data.device_l2[0], SZ_64K); + poison_range(test_data.device_l2[1], SZ_64K); + + ret = save_tables(); + TEST_ASSERT(!ret, "Expected the save to succeed, got %d errno %d", + ret, errno); + + dte = le64toh(*(u64 *)addr_gpa2hva(vm, test_data.device_l2[0])); + + /* Device A is still reachable, so it is saved. */ + TEST_ASSERT(dte & DTE_VALID_MASK, + "Device A: expected a valid DTE, got 0x%llx", + (unsigned long long)dte); + + /* + * Device B is the only device after it and was skipped, so nothing + * follows A in the saved chain. + */ + TEST_ASSERT(FIELD_GET(DTE_NEXT_MASK, dte) == 0, + "Device A: expected no next device, got offset %llu", + (unsigned long long)FIELD_GET(DTE_NEXT_MASK, dte)); + + /* And nothing was written into the block the guest dropped. */ + TEST_ASSERT(*(u64 *)addr_gpa2hva(vm, test_data.device_l2[1]) == POISON, + "Device B: expected its entry untouched"); + + reset_and_restore_tables(); + + teardown(); +} + +int main(void) +{ + TEST_REQUIRE(kvm_supports_vgic_v3()); + + test_baser_change_drops_collections(); + test_unreachable_device_skipped(); + + pr_info("All ok!\n"); + return 0; +} From 38b70fc453c3112f1a62583b89903ae41116cc27 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Tue, 1 Sep 2026 18:28:59 +0100 Subject: [PATCH 0513/1417] KVM: arm64: Fix spurious warning for benign stage 2 teardown race kvmtool was used to establish an L1 guest with 8 CPUs and 8 GiB of RAM, an L2 guest with 4 CPUs and 4 GiB of RAM and an L3 guest with 2 CPUs and 2 GiB of RAM, all of which was then exited. Under memory pressure in the L0 host warnings were observed due to migration triggered by compaction: WARNING: arch/arm64/kvm/mmu.c:336 at __unmap_stage2_range+0x64/0x80, CPU#5: kcompactd0/66 Which was, in turn, triggered by an MMU notifier for the host invalidation: mmu_notifier_invalidate_range_start() -> ... -> kvm_mmu_notifier_invalidate_range_start() -> kvm_mmu_unmap_gfn_range() -> kvm_unmap_gfn_range() -> kvm_nested_s2_unmap() -> kvm_stage2_unmap_range() -> __unmap_stage2_range() -> stage2_apply_range() <- -EINVAL, triggering a WARN_ON() Racing with L0's teardown of stage 2 page tables: exit_mm() -> mmput() -> __mmput() -> exit_mmap() -> mmu_notifier_release() -> ... -> kvm_mmu_notifier_release() -> kvm_flush_shadow_all() -> kvm_arch_flush_shadow_all() -> kvm_free_stage2_pgd() -> [ acquire kvm->mmu_lock for write ] -> mmu->pgt = NULL [ among other tasks ] -> [ release kvm->mmu_lock for write ] It turns out there is a benign race resulting in a spurious warning: Thread A - notify: migration | Thread B - notify: release -------------------------------|--------------------------------- < kvm->mmu_lock held > | stage2_apply_range() | get mmu->pgt, check !NULL | ... | kvm_arch_flush_shadow_all() cond_resched_rwlock_write(); | < contend, sleep kvm->mmu_lock > < drop kvm->mmu_lock > | < acquire kvm->mmu_lock> | ... | kvm_free_stage2_pgd() | mmu->pgt = NULL | < invalidate MMU > | ... | < release kvm->mmu_lock > [ scheduled ] | stage2_apply_range() | < loop to next > | get, mmu->pgt, check !NULL | is NULL, return -EINVAL | __unmap_stage2_range() | WARN_ON(-EINVAL) <--- entirely spurious - the race was handled correctly. Fix the spurious warning by updating stage2_apply_range() to no longer treat concurrent PGT teardown on lock release as an error - whether the walker is tearing down page tables or doing something else this is a legitimate reason to abort the operation without error. This keeps the warning in place for all other circumstances. In practice only __unmap_stage2_range() actually does anything with the error so this only impacts that. Fixes: ec14c272408a ("KVM: arm64: nv: Unmap/flush shadow stage 2 page tables") Cc: stable@vger.kernel.org Reviewed-by: Yuan Yao Reviewed-by: Marc Zyngier Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260901-kvm-arm-nested-virt-fix-v3-1-b154676f7e4c@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/kvm/mmu.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 9ba86450fe4af6..2d44cd6a5aed90 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -59,27 +59,36 @@ static phys_addr_t stage2_range_addr_end(phys_addr_t addr, phys_addr_t end) * long will also starve other vCPUs. We have to also make sure that the page * tables are not freed while we released the lock. */ -static int stage2_apply_range(struct kvm_s2_mmu *mmu, phys_addr_t addr, +static int stage2_apply_range(struct kvm_s2_mmu *mmu, phys_addr_t start, phys_addr_t end, int (*fn)(struct kvm_pgtable *, u64, u64), bool resched) { struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu); + bool lock_dropped = false; + phys_addr_t addr = start; int ret; u64 next; do { struct kvm_pgtable *pgt = mmu->pgt; + /* + * We may be raced on PGT teardown when we release the + * kvm->mmu_lock. That's fine as the PGT is legitimately no + * longer present. + */ if (!pgt) - return -EINVAL; + return lock_dropped ? 0 : -EINVAL; next = stage2_range_addr_end(addr, end); ret = fn(pgt, addr, next - addr); if (ret) break; - if (resched && next != end) + if (resched && next != end) { cond_resched_rwlock_write(&kvm->mmu_lock); + lock_dropped = true; + } } while (addr = next, addr != end); return ret; From 4c74e233cdedd11592775fae2a6243e67ca3f891 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Tue, 1 Sep 2026 18:29:00 +0100 Subject: [PATCH 0514/1417] KVM: arm64: nv: Fix null ptr deref on nested wp/unmap, teardown race Commit 7270cc9157f4 ("KVM: arm64: nv: Handle VNCR_EL2 invalidation from MMU notifiers") introduced VNCR_EL2 invalidation in both kvm_nested_s2_unmap() and kvm_nested_s2_wp(). However at the point of this being performed concurrent stage 2 teardown of a nested guest can cause kvm->arch.mmu.pgt to be set to NULL. This happens in kvm_flush_shadow_all() -> kvm_arch_flush_shadow_all() -> kvm_free_stage2_pgd() and is performed under the kvm->mmu_lock. Commit ec14c272408a ("KVM: arm64: nv: Unmap/flush shadow stage 2 page tables") introduced the teardown of the entire nested MMU range, which then invokes stage2_apply_range() with resched=true: mmu_notifier_invalidate_range_start() -> ... -> kvm_mmu_notifier_invalidate_range_start() -> kvm_mmu_unmap_gfn_range() -> kvm_unmap_gfn_range() -> kvm_nested_s2_unmap() -> kvm_stage2_unmap_range() -> __unmap_stage2_range() -> stage2_apply_range() This means that stage2_apply_range() can drop the kvm->mmu_lock and thus concurrent progress can be made in lockstep with kvm_arch_flush_shadow_all(). If kvm_arch_flush_shadow_all() advances ahead of stage2_apply_range() and completes its operation it guarantees a NULL pointer deref. Since kvm_free_stage2_pgd() is performed under the kvm->mmu_lock this will either be observed NULL or not and serialised against kvm_free_stage2_pgd(). Resolve the issue by abstracting the invalidation to a new function, kvm_invalidate_vncr_ipa_all(), and check that the pgt is non-NULL before dereferencing it. Fixes: 7270cc9157f4 ("KVM: arm64: nv: Handle VNCR_EL2 invalidation from MMU notifiers") Cc: stable@vger.kernel.org Reviewed-by: Marc Zyngier Signed-off-by: Lorenzo Stoakes (ARM) Tested-by: Jonathan Davies Link: https://patch.msgid.link/20260901-kvm-arm-nested-virt-fix-v3-2-b154676f7e4c@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/kvm/nested.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index 3c4fc566eafc63..a0808391a456ea 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -1260,6 +1260,17 @@ void kvm_handle_s1e2_tlbi(struct kvm_vcpu *vcpu, u32 inst, u64 val) invalidate_vncr_va(vcpu->kvm, &scope); } +static void kvm_invalidate_vncr_ipa_all(struct kvm *kvm) +{ + struct kvm_pgtable *pgt = kvm->arch.mmu.pgt; + + lockdep_assert_held_write(&kvm->mmu_lock); + + /* if the mmu lock was dropped, pgt teardown may have raced. */ + if (pgt) + kvm_invalidate_vncr_ipa(kvm, 0, BIT(pgt->ia_bits)); +} + void kvm_nested_s2_wp(struct kvm *kvm) { int i; @@ -1276,7 +1287,7 @@ void kvm_nested_s2_wp(struct kvm *kvm) kvm_stage2_wp_range(mmu, 0, kvm_phys_size(mmu)); } - kvm_invalidate_vncr_ipa(kvm, 0, BIT(kvm->arch.mmu.pgt->ia_bits)); + kvm_invalidate_vncr_ipa_all(kvm); } void kvm_nested_s2_unmap(struct kvm *kvm, bool may_block) @@ -1295,7 +1306,7 @@ void kvm_nested_s2_unmap(struct kvm *kvm, bool may_block) kvm_stage2_unmap_range(mmu, 0, kvm_phys_size(mmu), may_block); } - kvm_invalidate_vncr_ipa(kvm, 0, BIT(kvm->arch.mmu.pgt->ia_bits)); + kvm_invalidate_vncr_ipa_all(kvm); } void kvm_nested_s2_flush(struct kvm *kvm) From 2a2eb10795a1e495aebc7f829ccecb72c05b4fd9 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 25 Aug 2026 09:59:45 +0100 Subject: [PATCH 0515/1417] KVM: arm64: Validate the SVE vector length in pkvm_vcpu_init_sve() pkvm_vcpu_init_sve() clamps only the upper bound of the host-provided sve_max_vl, so an invalid vector length reaches sve_state_size_from_vl() and the WARN_ON() there, which is fatal at EL2. The existing !sve_state_size test rejects such a length, but only after the macro has run. Check sve_vl_valid() before deriving the state size. A valid length cannot yield a zero size, so the !sve_state_size test goes with it. Fixes: 5db1bef93342 ("KVM: arm64: Track SVE state in the hypervisor vcpu structure") Reported-by: Stefan Teodorescu Reviewed-by: Marc Zyngier Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260825085948.1674721-2-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/pkvm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index 459bd9eb7e4bc7..627f13fc98d41a 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -459,14 +459,15 @@ static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *h /* Limit guest vector length to the maximum supported by the host. */ sve_max_vl = min(READ_ONCE(host_vcpu->arch.sve_max_vl), kvm_host_sve_max_vl); - sve_state_size = sve_state_size_from_vl(sve_max_vl); sve_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sve_state)); - if (!sve_state || !sve_state_size) { + if (!sve_vl_valid(sve_max_vl) || !sve_state) { ret = -EINVAL; goto err; } + sve_state_size = sve_state_size_from_vl(sve_max_vl); + ret = hyp_pin_shared_mem(sve_state, sve_state + sve_state_size); if (ret) goto err; From a1b3c788ad31837e348075e93dbba3f447492776 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 25 Aug 2026 09:59:46 +0100 Subject: [PATCH 0516/1417] KVM: arm64: Do not clear VM-wide SVE feature on vCPU init failure pkvm_vcpu_init_sve() clears KVM_ARM_VCPU_SVE in kvm->arch.vcpu_features when it fails, but vcpu_has_sve() tests KVM_ARCH_FLAG_GUEST_HAS_SVE, which is left set. Later vCPUs on that VM then skip the SVE setup and register with a NULL sve_state, which the guest's first FP access hands to sve_load_state(). Return the error without touching vcpu_features. Fixes: 5db1bef93342 ("KVM: arm64: Track SVE state in the hypervisor vcpu structure") Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260825085948.1674721-3-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/pkvm.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index 627f13fc98d41a..4857a11d429216 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -450,7 +450,7 @@ static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *h unsigned int sve_max_vl; size_t sve_state_size; void *sve_state; - int ret = 0; + int ret; if (!vcpu_has_feature(vcpu, KVM_ARM_VCPU_SVE)) { vcpu_clear_flag(vcpu, VCPU_SVE_FINALIZED); @@ -461,24 +461,19 @@ static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *h sve_max_vl = min(READ_ONCE(host_vcpu->arch.sve_max_vl), kvm_host_sve_max_vl); sve_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sve_state)); - if (!sve_vl_valid(sve_max_vl) || !sve_state) { - ret = -EINVAL; - goto err; - } + if (!sve_vl_valid(sve_max_vl) || !sve_state) + return -EINVAL; sve_state_size = sve_state_size_from_vl(sve_max_vl); ret = hyp_pin_shared_mem(sve_state, sve_state + sve_state_size); if (ret) - goto err; + return ret; vcpu->arch.sve_state = sve_state; vcpu->arch.sve_max_vl = sve_max_vl; return 0; -err: - clear_bit(KVM_ARM_VCPU_SVE, vcpu->kvm->arch.vcpu_features); - return ret; } static int vm_copy_id_regs(struct pkvm_hyp_vcpu *hyp_vcpu) From 0d62fbf34d8fe7a3ff56692d39fbb8e6e9bf15fb Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 25 Aug 2026 09:59:47 +0100 Subject: [PATCH 0517/1417] KVM: arm64: Key unpin_host_sve_state() on the state it unpins unpin_host_sve_state() gates on the VM's SVE feature bit, but what it unpins is the state pkvm_vcpu_init_sve() pinned. A vCPU that completed init has sve_state set exactly when that bit is set, so the two agree. Gate on sve_state, which is what is being unpinned. Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260825085948.1674721-4-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/pkvm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index 4857a11d429216..453d3fa4a4204f 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -398,10 +398,10 @@ static void unpin_host_sve_state(struct pkvm_hyp_vcpu *hyp_vcpu) { void *sve_state; - if (!vcpu_has_feature(&hyp_vcpu->vcpu, KVM_ARM_VCPU_SVE)) + sve_state = hyp_vcpu->vcpu.arch.sve_state; + if (!sve_state) return; - sve_state = hyp_vcpu->vcpu.arch.sve_state; hyp_unpin_shared_mem(sve_state, sve_state + vcpu_sve_state_size(&hyp_vcpu->vcpu)); } From 4f16c5fc8dc4c5596e3777ab9f449a54e3f85fd5 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 25 Aug 2026 09:59:48 +0100 Subject: [PATCH 0518/1417] KVM: arm64: Derive GUEST_HAS_SVE from the SVE feature bit at EL2 pkvm_init_features_from_host() takes KVM_ARCH_FLAG_GUEST_HAS_SVE and KVM_ARM_VCPU_SVE from the host separately, but pkvm_vcpu_init_sve() tests the bit while vcpu_has_sve() reads the flag. A host that sets the flag without the bit gets a vCPU with a NULL sve_state that the world switch loads the guest's SVE state from. Derive the flag from the bit, and drop the protected path's copy of the host's flag, which is dead code since protected VMs are not allowed SVE. Fixes: 41d6028e28bd ("KVM: arm64: Convert the SVE guest vcpu flag to a vm flag") Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260825085948.1674721-5-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/pkvm.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index 453d3fa4a4204f..bb3e0dc0676ec9 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -360,7 +360,7 @@ static void pkvm_init_features_from_host(struct pkvm_hyp_vm *hyp_vm, const struc if (test_bit(KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS, &host_arch_flags)) hyp_vm->kvm.arch.midr_el1 = host_kvm->arch.midr_el1; - return; + goto out; } if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_MTE)) @@ -379,13 +379,14 @@ static void pkvm_init_features_from_host(struct pkvm_hyp_vm *hyp_vm, const struc if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_PTRAUTH_GENERIC)) set_bit(KVM_ARM_VCPU_PTRAUTH_GENERIC, allowed_features); - if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_SVE)) { + if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_SVE)) set_bit(KVM_ARM_VCPU_SVE, allowed_features); - kvm->arch.flags |= host_arch_flags & BIT(KVM_ARCH_FLAG_GUEST_HAS_SVE); - } bitmap_and(kvm->arch.vcpu_features, host_kvm->arch.vcpu_features, allowed_features, KVM_VCPU_MAX_FEATURES); +out: + __assign_bit(KVM_ARCH_FLAG_GUEST_HAS_SVE, &kvm->arch.flags, + kvm_vcpu_has_feature(kvm, KVM_ARM_VCPU_SVE)); } static void unpin_host_vcpu(struct kvm_vcpu *host_vcpu) From 64dc6f1db7e620f2e9337bb181f305fb0561da79 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 29 Aug 2026 07:48:55 +0200 Subject: [PATCH 0519/1417] KVM: arm64: Return -EINVAL for an empty SMCCC filter range at base 0 kvm_smccc_set_filter() only rejects a range if its inclusive end, base + nr_functions - 1, is below base. That catches an empty range (nr_functions == 0) at every nonzero base, but at base 0 the end wraps to U32_MAX and KVM tries to insert [0, U32_MAX], which overlaps the reserved Arm Architecture Calls ranges. KVM_ARM_VM_SMCCC_FILTER then returns -EEXIST instead of the -EINVAL that the smccc_filter selftest expects for an empty range. Reject a zero function count explicitly. Tested with a userspace reproducer on an arm64 VHE host under QEMU TCG: EEXIST before, EINVAL after. Fixes: 821d935c87bc ("KVM: arm64: Introduce support for userspace SMCCC filtering") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Steffen Eiden Reviewed-by: Fuad Tabba Tested-by: Fuad Tabba Link: https://patch.msgid.link/20260829054856.70549-2-kmehltretter@gmail.com Signed-off-by: Oliver Upton --- arch/arm64/kvm/hypercalls.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/hypercalls.c b/arch/arm64/kvm/hypercalls.c index b11b8821c9fbc6..dfa25bb6f25d45 100644 --- a/arch/arm64/kvm/hypercalls.c +++ b/arch/arm64/kvm/hypercalls.c @@ -185,7 +185,8 @@ static int kvm_smccc_set_filter(struct kvm *kvm, struct kvm_smccc_filter __user start = filter.base; end = start + filter.nr_functions - 1; - if (end < start || filter.action >= NR_SMCCC_FILTER_ACTIONS) + if (!filter.nr_functions || end < start || + filter.action >= NR_SMCCC_FILTER_ACTIONS) return -EINVAL; mutex_lock(&kvm->arch.config_lock); From 0a46eb5719fa57cc9d06025dcd8ba271643dee61 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 29 Aug 2026 07:48:56 +0200 Subject: [PATCH 0520/1417] KVM: arm64: selftests: Test empty SMCCC filter range at base 0 test_invalid_nr_functions() only checks an empty range at PSCI_0_2_FN64_CPU_ON, which KVM's end < start check happens to catch. It never exercised base 0, where the inclusive end wraps to U32_MAX instead. Add the base 0 case. Without the preceding fix it fails with EEXIST. Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Steffen Eiden Reviewed-by: Fuad Tabba Tested-by: Fuad Tabba Link: https://patch.msgid.link/20260829054856.70549-3-kmehltretter@gmail.com Signed-off-by: Oliver Upton --- tools/testing/selftests/kvm/arm64/smccc_filter.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/testing/selftests/kvm/arm64/smccc_filter.c b/tools/testing/selftests/kvm/arm64/smccc_filter.c index 21e41880261b43..a41ed3e016ba31 100644 --- a/tools/testing/selftests/kvm/arm64/smccc_filter.c +++ b/tools/testing/selftests/kvm/arm64/smccc_filter.c @@ -140,6 +140,10 @@ static void test_invalid_nr_functions(void) TEST_ASSERT(r < 0 && errno == EINVAL, "Attempt to filter 0 functions should return EINVAL"); + r = __set_smccc_filter(vm, 0, 0, KVM_SMCCC_FILTER_DENY); + TEST_ASSERT(r < 0 && errno == EINVAL, + "Attempt to filter 0 functions at base 0 should return EINVAL"); + kvm_vm_free(vm); } From 3a8c562892b96f35bba1e00d5e455a15963bbb92 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 8 Sep 2026 12:07:10 +0100 Subject: [PATCH 0521/1417] KVM: arm64: Transfer the hyp stack pages out of the host stage-2 fix_host_ownership() walks only the linear-map alias of each memblock region, and the per-CPU hyp stack, mapped in the private VA range for its guard page, has none. Walk each stack's VA range with the same walker. Fixes: 1a919b17ef012 ("KVM: arm64: Add guard pages for pKVM (protected nVHE) hypervisor stack") Reported-by: Hiroyuki Katsura Cc: stable@vger.kernel.org Signed-off-by: Fuad Tabba Reviewed-by: Vincent Donnefort Tested-by: Vincent Donnefort Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260908110713.1540304-2-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/setup.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/kvm/hyp/nvhe/setup.c b/arch/arm64/kvm/hyp/nvhe/setup.c index 75b00c32331025..362f2891cb32eb 100644 --- a/arch/arm64/kvm/hyp/nvhe/setup.c +++ b/arch/arm64/kvm/hyp/nvhe/setup.c @@ -269,6 +269,16 @@ static int fix_host_ownership(void) return ret; } + /* The stacks sit in the private VA range, not the linear map. */ + for (i = 0; i < hyp_nr_cpus; i++) { + struct kvm_nvhe_init_params *params = per_cpu_ptr(&kvm_init_params, i); + u64 start = params->stack_hyp_va - NVHE_STACK_SIZE; + + ret = kvm_pgtable_walk(&pkvm_pgtable, start, NVHE_STACK_SIZE, &walker); + if (ret) + return ret; + } + return 0; } From 5a8b505ede133fb30ca3b3a19d0db00c08237615 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 8 Sep 2026 12:07:11 +0100 Subject: [PATCH 0522/1417] KVM: arm64: Match hyp text by physical address in fix_host_ownership() On a non-hVHE host, fix_host_ownership_walker()'s test for PAGE_HYP_EXEC never matches: KVM_PGTABLE_PROT_UX is cleared at map time and only PX is reported on read-back. Hyp text is therefore donated rather than left read-only in the host stage-2, and the instruction dump in nvhe_hyp_panic_handler() reads a page the host has no access to. Match the text by physical address instead, in a helper a later patch reuses. A test on the permissions would leave any other executable mapping host-readable too. Fixes: 80cbfd7174f31 ("KVM: arm64: Honor UX/PX attributes for EL2 S1 mappings") Signed-off-by: Fuad Tabba Reviewed-by: Vincent Donnefort Tested-by: Vincent Donnefort Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260908110713.1540304-3-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 1 + arch/arm64/kvm/hyp/nvhe/mem_protect.c | 8 ++++++++ arch/arm64/kvm/hyp/nvhe/setup.c | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h index 29935c7da1dec2..cab27f7bd423ad 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h +++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h @@ -52,6 +52,7 @@ int __pkvm_host_test_clear_young_guest(u64 gfn, u64 nr_pages, bool mkold, struct int __pkvm_host_mkyoung_guest(u64 gfn, struct pkvm_hyp_vcpu *vcpu); bool addr_is_memory(phys_addr_t phys); +bool addr_is_hyp_text(phys_addr_t phys); int host_stage2_idmap_locked(phys_addr_t addr, u64 size, enum kvm_pgtable_prot prot); int host_stage2_set_owner_locked(phys_addr_t addr, u64 size, u8 owner_id); int kvm_host_prepare_stage2(void *pgt_pool_base); diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c index 39aa8911f62c1b..d026f446bd8efb 100644 --- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c +++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c @@ -450,6 +450,14 @@ bool addr_is_memory(phys_addr_t phys) return !!find_mem_range(phys, &range); } +bool addr_is_hyp_text(phys_addr_t phys) +{ + phys_addr_t start = ALIGN_DOWN(__hyp_pa(__hyp_text_start), PAGE_SIZE); + phys_addr_t end = PAGE_ALIGN(__hyp_pa(__hyp_text_end)); + + return phys >= start && phys < end; +} + static bool is_in_mem_range(u64 addr, struct kvm_mem_range *range) { return range->start <= addr && addr < range->end; diff --git a/arch/arm64/kvm/hyp/nvhe/setup.c b/arch/arm64/kvm/hyp/nvhe/setup.c index 362f2891cb32eb..bb667cd7080b44 100644 --- a/arch/arm64/kvm/hyp/nvhe/setup.c +++ b/arch/arm64/kvm/hyp/nvhe/setup.c @@ -217,7 +217,7 @@ static int fix_host_ownership_walker(const struct kvm_pgtable_visit_ctx *ctx, case PKVM_PAGE_OWNED: set_hyp_state(page, PKVM_PAGE_OWNED); /* hyp text is RO in the host stage-2 to be inspected on panic. */ - if (prot == PAGE_HYP_EXEC) { + if (addr_is_hyp_text(phys)) { set_host_state(page, PKVM_NOPAGE); return host_stage2_idmap_locked(phys, PAGE_SIZE, KVM_PGTABLE_PROT_R); } else { From 2245841401147b8022a5857061b870d82e595352 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 8 Sep 2026 12:07:12 +0100 Subject: [PATCH 0523/1417] KVM: arm64: Move the private VA allocation cursor to __io_map_next __io_map_base is the start of the private VA range only until the first allocation from it, after which it is the allocation cursor. Keep it as the start and move the cursor to __io_map_next, for the walk of the range the next patch adds. No functional change intended. Signed-off-by: Fuad Tabba Reviewed-by: Vincent Donnefort Tested-by: Vincent Donnefort Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260908110713.1540304-4-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/mm.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/mm.c b/arch/arm64/kvm/hyp/nvhe/mm.c index 3b0bee496bffb6..422ee57be95609 100644 --- a/arch/arm64/kvm/hyp/nvhe/mm.c +++ b/arch/arm64/kvm/hyp/nvhe/mm.c @@ -25,6 +25,7 @@ struct memblock_region hyp_memory[HYP_MEMBLOCK_REGIONS]; unsigned int hyp_memblock_nr; static u64 __io_map_base; +static u64 __io_map_next; struct hyp_fixmap_slot { u64 addr; @@ -50,7 +51,7 @@ static int __pkvm_alloc_private_va_range(unsigned long start, size_t size) hyp_assert_lock_held(&pkvm_pgd_lock); - if (!start || start < __io_map_base) + if (!start || start < __io_map_next) return -EINVAL; /* The allocated size is always a multiple of PAGE_SIZE */ @@ -60,7 +61,7 @@ static int __pkvm_alloc_private_va_range(unsigned long start, size_t size) if (cur > __hyp_vmemmap) return -ENOMEM; - __io_map_base = cur; + __io_map_next = cur; return 0; } @@ -70,7 +71,7 @@ static int __pkvm_alloc_private_va_range(unsigned long start, size_t size) * @size: The size of the VA range to reserve. * @haddr: The hypervisor virtual start address of the allocation. * - * The private virtual address (VA) range is allocated above __io_map_base + * The private virtual address (VA) range is allocated above __io_map_next * and aligned based on the order of @size. * * Return: 0 on success or negative error code on failure. @@ -81,7 +82,7 @@ int pkvm_alloc_private_va_range(size_t size, unsigned long *haddr) int ret; hyp_spin_lock(&pkvm_pgd_lock); - addr = __io_map_base; + addr = __io_map_next; ret = __pkvm_alloc_private_va_range(addr, size); hyp_spin_unlock(&pkvm_pgd_lock); @@ -341,7 +342,7 @@ static int create_fixblock(void) return -EINVAL; hyp_spin_lock(&pkvm_pgd_lock); - addr = ALIGN(__io_map_base, PMD_SIZE); + addr = ALIGN(__io_map_next, PMD_SIZE); ret = __pkvm_alloc_private_va_range(addr, PMD_SIZE); if (ret) goto unlock; @@ -426,6 +427,7 @@ int hyp_create_idmap(u32 hyp_va_bits) */ __io_map_base = start & BIT(hyp_va_bits - 2); __io_map_base ^= BIT(hyp_va_bits - 2); + __io_map_next = __io_map_base; __hyp_vmemmap = __io_map_base | BIT(hyp_va_bits - 3); return __pkvm_create_mappings(start, end - start, start, PAGE_HYP_EXEC); @@ -433,19 +435,19 @@ int hyp_create_idmap(u32 hyp_va_bits) int pkvm_create_stack(phys_addr_t phys, unsigned long *haddr) { - unsigned long addr, prev_base; + unsigned long addr, prev_next; size_t size; int ret; hyp_spin_lock(&pkvm_pgd_lock); - prev_base = __io_map_base; + prev_next = __io_map_next; /* * Efficient stack verification using the NVHE_STACK_SHIFT bit implies * an alignment of our allocation on the order of the size. */ size = NVHE_STACK_SIZE * 2; - addr = ALIGN(__io_map_base, size); + addr = ALIGN(__io_map_next, size); ret = __pkvm_alloc_private_va_range(addr, size); if (!ret) { @@ -461,7 +463,7 @@ int pkvm_create_stack(phys_addr_t phys, unsigned long *haddr) ret = kvm_pgtable_hyp_map(&pkvm_pgtable, addr + NVHE_STACK_SIZE, NVHE_STACK_SIZE, phys, PAGE_HYP); if (ret) - __io_map_base = prev_base; + __io_map_next = prev_next; } hyp_spin_unlock(&pkvm_pgd_lock); From cfe80c3837f93202970b6d8f706f79c5c7396f17 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 8 Sep 2026 12:07:13 +0100 Subject: [PATCH 0524/1417] KVM: arm64: Check every private mapping is hyp-owned at pKVM init fix_host_ownership() transfers only what it walks, so a hyp mapping outside the linear map is not manipulated by the walk. Walk the quarter of the VA space holding the private range and the vmemmap once the transfer is done, and fail init unless every valid leaf is hyp-owned: in the vmemmap when the page is memory, and in the host stage-2, where hyp text may instead be mapped without write access. A leaf that is not memory has no vmemmap entry and is checked against the host stage-2 alone. Hyp text is matched by physical address, since the only executable mapping in the range is the Spectre-v3a vectors, whose VA is a private allocation, and an executable mapping of anything else must not be host-readable. The vmemmap can be block-mapped, so the walker checks each page of a leaf. Suggested-by: Will Deacon Signed-off-by: Fuad Tabba Reviewed-by: Vincent Donnefort Tested-by: Vincent Donnefort Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260908110713.1540304-5-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 1 + arch/arm64/kvm/hyp/include/nvhe/mm.h | 1 + arch/arm64/kvm/hyp/nvhe/mem_protect.c | 12 ++++ arch/arm64/kvm/hyp/nvhe/mm.c | 59 +++++++++++++++++++ arch/arm64/kvm/hyp/nvhe/setup.c | 4 ++ 5 files changed, 77 insertions(+) diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h index cab27f7bd423ad..ec85a95471207b 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h +++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h @@ -55,6 +55,7 @@ bool addr_is_memory(phys_addr_t phys); bool addr_is_hyp_text(phys_addr_t phys); int host_stage2_idmap_locked(phys_addr_t addr, u64 size, enum kvm_pgtable_prot prot); int host_stage2_set_owner_locked(phys_addr_t addr, u64 size, u8 owner_id); +bool host_stage2_pte_is_hyp_owned(kvm_pte_t pte); int kvm_host_prepare_stage2(void *pgt_pool_base); int kvm_guest_prepare_stage2(struct pkvm_hyp_vm *vm, void *pgd); void kvm_guest_destroy_stage2(struct pkvm_hyp_vm *vm); diff --git a/arch/arm64/kvm/hyp/include/nvhe/mm.h b/arch/arm64/kvm/hyp/include/nvhe/mm.h index 6e83ce35c2f2e0..31cae95ddb7165 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/mm.h +++ b/arch/arm64/kvm/hyp/include/nvhe/mm.h @@ -29,6 +29,7 @@ int __pkvm_create_private_mapping(phys_addr_t phys, size_t size, enum kvm_pgtable_prot prot, unsigned long *haddr); int pkvm_create_stack(phys_addr_t phys, unsigned long *haddr); +int pkvm_check_host_ownership(void); int pkvm_alloc_private_va_range(size_t size, unsigned long *haddr); #endif /* __KVM_HYP_MM_H */ diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c index d026f446bd8efb..a6a47c1e058b3c 100644 --- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c +++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c @@ -641,6 +641,18 @@ int host_stage2_set_owner_locked(phys_addr_t addr, u64 size, u8 owner_id) return ret; } +bool host_stage2_pte_is_hyp_owned(kvm_pte_t pte) +{ + if (kvm_pte_valid(pte)) + return false; + + if (FIELD_GET(KVM_INVALID_PTE_TYPE_MASK, pte) != + KVM_HOST_INVALID_PTE_TYPE_DONATION) + return false; + + return FIELD_GET(KVM_HOST_DONATION_PTE_OWNER_MASK, pte) == PKVM_ID_HYP; +} + #define KVM_HOST_PTE_OWNER_GUEST_HANDLE_MASK GENMASK(15, 0) /* We need 40 bits for the GFN to cover a 52-bit IPA with 4k pages and LPA2 */ #define KVM_HOST_PTE_OWNER_GUEST_GFN_MASK GENMASK(55, 16) diff --git a/arch/arm64/kvm/hyp/nvhe/mm.c b/arch/arm64/kvm/hyp/nvhe/mm.c index 422ee57be95609..29ab5ee9d57fc0 100644 --- a/arch/arm64/kvm/hyp/nvhe/mm.c +++ b/arch/arm64/kvm/hyp/nvhe/mm.c @@ -472,6 +472,65 @@ int pkvm_create_stack(phys_addr_t phys, unsigned long *haddr) return ret; } +static int check_page_ownership(phys_addr_t phys) +{ + kvm_pte_t pte; + bool host_ok; + int ret; + + if (addr_is_memory(phys)) { + struct hyp_page *page = hyp_phys_to_page(phys); + + if (get_hyp_state(page) != PKVM_PAGE_OWNED || + get_host_state(page) != PKVM_NOPAGE) + return -EPERM; + } + + ret = kvm_pgtable_get_leaf(&host_mmu.pgt, phys, &pte, NULL); + if (ret) + return ret; + + /* Hyp text may stay host-readable, see fix_host_ownership_walker(). */ + if (kvm_pte_valid(pte) && addr_is_hyp_text(phys)) + host_ok = !(kvm_pgtable_stage2_pte_prot(pte) & KVM_PGTABLE_PROT_W); + else + host_ok = host_stage2_pte_is_hyp_owned(pte); + + return host_ok ? 0 : -EPERM; +} + +static int check_host_ownership_walker(const struct kvm_pgtable_visit_ctx *ctx, + enum kvm_pgtable_walk_flags visit) +{ + phys_addr_t phys, end; + int ret; + + if (!kvm_pte_valid(ctx->old)) + return 0; + + phys = kvm_pte_to_phys(ctx->old); + end = phys + kvm_granule_size(ctx->level); + for (; phys < end; phys += PAGE_SIZE) { + ret = check_page_ownership(phys); + if (ret) + return ret; + } + + return 0; +} + +int pkvm_check_host_ownership(void) +{ + struct kvm_pgtable_walker walker = { + .cb = check_host_ownership_walker, + .flags = KVM_PGTABLE_WALK_LEAF, + }; + + /* The private range and the vmemmap share one quarter of the VA space. */ + return kvm_pgtable_walk(&pkvm_pgtable, __io_map_base, + BIT(pkvm_pgtable.ia_bits - 2), &walker); +} + static void *admit_host_page(void *arg) { struct kvm_hyp_memcache *host_mc = arg; diff --git a/arch/arm64/kvm/hyp/nvhe/setup.c b/arch/arm64/kvm/hyp/nvhe/setup.c index bb667cd7080b44..45ac5f2ba4f7ae 100644 --- a/arch/arm64/kvm/hyp/nvhe/setup.c +++ b/arch/arm64/kvm/hyp/nvhe/setup.c @@ -334,6 +334,10 @@ void __noreturn __pkvm_init_finalise(void) if (ret) goto out; + ret = pkvm_check_host_ownership(); + if (ret) + goto out; + ret = hyp_ffa_init(ffa_proxy_pages); if (ret) goto out; From 33346f8960c7bb6a3b4e273b5cfe25c5a8be349f Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 11 Sep 2026 17:22:02 +0100 Subject: [PATCH 0525/1417] KVM: arm64: nv: Fix life cycle of the nested_mmus array The nested_mmus array holds the shadow page tables that are used when a guest is running a nested context. These structures are allocated on VCPU_INIT for whole guest, which implies that they may have to be relocated as the array grows. Should a VCPU_INIT occur whilst a vcpu is actively running an L2 and that the allocation requires relocation, that vcpu will still be running with a pointer to the previous structure, which will have been freed. Fix this by turning the array of structures to an array of pointers, which is now allocated at VM creation, sized to the absolute maximum that KVM can handle. In turn, each VCPU_INIT contributes S2_MMU_PER_VCPU to the pool. No reallocation is ever performed, and the life cycle of each object is much clearer: - the nested_mmus array is allocated in kvm_init_nested(), and freed in kvm_arch_destroy_vm() - s2_mmu structures are allocated in kvm_vcpu_init_nested(), and freed on kvm_arch_flush_shadow_all() Finally, the freeing of vcpu->arch.vncr_array is made consistent rather than being done on some failure paths, but not others. Fixes: 4f128f8e1aaa ("KVM: arm64: nv: Support multiple nested Stage-2 mmu structures") Reported-by: Shen Yongchao Reported-by: Karl Mehltretter Suggested-by: Karl Mehltretter Acked-by: Lorenzo Stoakes (ARM) Link: https://lore.kernel.org/r/20260803224405.41468-1-kmehltretter@gmail.com Signed-off-by: Marc Zyngier Cc: stable@vger.kernel.org Reviewed-by: Wei-Lin Chang Link: https://patch.msgid.link/20260911162203.1919330-2-maz@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/include/asm/kvm_host.h | 2 +- arch/arm64/include/asm/kvm_nested.h | 2 +- arch/arm64/kvm/arm.c | 8 ++- arch/arm64/kvm/nested.c | 89 +++++++++++++---------------- 4 files changed, 49 insertions(+), 52 deletions(-) diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h index 27fe0cd5b2d7a1..cd9b9d2462f96a 100644 --- a/arch/arm64/include/asm/kvm_host.h +++ b/arch/arm64/include/asm/kvm_host.h @@ -322,7 +322,7 @@ struct kvm_arch { * Stage 2 paging state for VMs with nested S2 using a virtual * VMID. */ - struct kvm_s2_mmu *nested_mmus; + struct kvm_s2_mmu **nested_mmus; size_t nested_mmus_size; int nested_mmus_next; diff --git a/arch/arm64/include/asm/kvm_nested.h b/arch/arm64/include/asm/kvm_nested.h index 1ed7083358096d..5b8edb2e8a87df 100644 --- a/arch/arm64/include/asm/kvm_nested.h +++ b/arch/arm64/include/asm/kvm_nested.h @@ -66,7 +66,7 @@ static inline u64 translate_ttbr0_el2_to_ttbr0_el1(u64 ttbr0) extern bool forward_smc_trap(struct kvm_vcpu *vcpu); extern bool forward_debug_exception(struct kvm_vcpu *vcpu); -extern void kvm_init_nested(struct kvm *kvm); +extern int kvm_init_nested(struct kvm *kvm); extern int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu); extern void kvm_init_nested_s2_mmu(struct kvm_s2_mmu *mmu); extern struct kvm_s2_mmu *lookup_s2_mmu(struct kvm_vcpu *vcpu); diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 8b080804bc90b2..b53219e048bdf1 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -236,8 +236,6 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) mutex_unlock(&kvm->lock); #endif - kvm_init_nested(kvm); - ret = kvm_share_hyp(kvm, kvm + 1); if (ret) return ret; @@ -252,6 +250,10 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) if (ret) goto err_free_cpumask; + ret = kvm_init_nested(kvm); + if (ret) + goto err_uninit_mmu; + if (is_protected_kvm_enabled()) { /* * If any failures occur after this is successful, make sure to @@ -280,6 +282,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) err_uninit_mmu: kvm_uninit_stage2_mmu(kvm); + kvfree(kvm->arch.nested_mmus); err_free_cpumask: free_cpumask_var(kvm->arch.supported_cpus); err_unshare_kvm: @@ -337,6 +340,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm) kvm_unshare_hyp(kvm, kvm + 1); + kvfree(kvm->arch.nested_mmus); kvm_arm_teardown_hypercalls(kvm); } diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index a0808391a456ea..2571f177e654b3 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -45,11 +45,15 @@ struct vncr_tlb { */ #define S2_MMU_PER_VCPU 2 -void kvm_init_nested(struct kvm *kvm) +int kvm_init_nested(struct kvm *kvm) { - kvm->arch.nested_mmus = NULL; + kvm->arch.nested_mmus = kvmalloc_objs(struct kvm_s2_mmu *, + KVM_MAX_VCPUS * S2_MMU_PER_VCPU, + GFP_KERNEL_ACCOUNT); kvm->arch.nested_mmus_size = 0; atomic_set(&kvm->arch.vncr_tlb_count, 0); + + return kvm->arch.nested_mmus ? 0 : -ENOMEM; } static int init_nested_s2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu) @@ -70,8 +74,9 @@ static int init_nested_s2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu) int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu) { struct kvm *kvm = vcpu->kvm; - struct kvm_s2_mmu *tmp; - int num_mmus, ret = 0; + int num_mmus; + + lockdep_assert_held(&kvm->arch.config_lock); if (test_bit(KVM_ARM_VCPU_HAS_EL2_E2H0, kvm->arch.vcpu_features) && !cpus_have_final_cap(ARM64_HAS_HCR_NV1)) @@ -84,51 +89,40 @@ int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu) if (!vcpu->arch.ctxt.vncr_array) return -ENOMEM; - /* - * Let's treat memory allocation failures as benign: If we fail to - * allocate anything, return an error and keep the allocated array - * alive. Userspace may try to recover by initializing the vcpu - * again, and there is no reason to affect the whole VM for this. - */ num_mmus = atomic_read(&kvm->online_vcpus) * S2_MMU_PER_VCPU; if (num_mmus > kvm->arch.nested_mmus_size) { - tmp = kvzalloc_objs(*tmp, num_mmus, GFP_KERNEL_ACCOUNT); - if (!tmp) - return -ENOMEM; + struct kvm_s2_mmu *tmp; + int i, ret = 0; - write_lock(&kvm->mmu_lock); - - if (kvm->arch.nested_mmus_size) { - memcpy(tmp, kvm->arch.nested_mmus, - size_mul(sizeof(*tmp), kvm->arch.nested_mmus_size)); + tmp = kvzalloc_objs(*tmp, S2_MMU_PER_VCPU, GFP_KERNEL_ACCOUNT); + if (!tmp) + ret = -ENOMEM; - for (int i = 0; i < kvm->arch.nested_mmus_size; i++) - tmp[i].pgt->mmu = &tmp[i]; + for (i = 0; !ret && i < S2_MMU_PER_VCPU; i++) { + ret = init_nested_s2_mmu(kvm, &tmp[i]); + if (ret) + break; } - swap(kvm->arch.nested_mmus, tmp); - - write_unlock(&kvm->mmu_lock); - - kvfree(tmp); - } + if (ret) { + while (--i >= 0) + kvm_free_stage2_pgd(&tmp[i]); - for (int i = kvm->arch.nested_mmus_size; !ret && i < num_mmus; i++) - ret = init_nested_s2_mmu(kvm, &kvm->arch.nested_mmus[i]); + kvfree(tmp); + free_page((unsigned long)vcpu->arch.ctxt.vncr_array); + vcpu->arch.ctxt.vncr_array = NULL; + return ret; + } - if (ret) { - for (int i = kvm->arch.nested_mmus_size; i < num_mmus; i++) - kvm_free_stage2_pgd(&kvm->arch.nested_mmus[i]); + guard(write_lock)(&kvm->mmu_lock); - free_page((unsigned long)vcpu->arch.ctxt.vncr_array); - vcpu->arch.ctxt.vncr_array = NULL; + for (i = 0; i < S2_MMU_PER_VCPU; i++) + kvm->arch.nested_mmus[i + kvm->arch.nested_mmus_size] = &tmp[i]; - return ret; + kvm->arch.nested_mmus_size += S2_MMU_PER_VCPU; } - kvm->arch.nested_mmus_size = num_mmus; - return 0; } @@ -742,7 +736,7 @@ void kvm_s2_mmu_iterate_by_vmid(struct kvm *kvm, u16 vmid, write_lock(&kvm->mmu_lock); for (int i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (!kvm_s2_mmu_valid(mmu)) continue; @@ -784,7 +778,7 @@ struct kvm_s2_mmu *lookup_s2_mmu(struct kvm_vcpu *vcpu) * if S2 translation is disabled. */ for (int i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (!kvm_s2_mmu_valid(mmu)) continue; @@ -823,7 +817,7 @@ static struct kvm_s2_mmu *get_s2_mmu_nested(struct kvm_vcpu *vcpu) for (i = kvm->arch.nested_mmus_next; i < (kvm->arch.nested_mmus_size + kvm->arch.nested_mmus_next); i++) { - s2_mmu = &kvm->arch.nested_mmus[i % kvm->arch.nested_mmus_size]; + s2_mmu = kvm->arch.nested_mmus[i % kvm->arch.nested_mmus_size]; if (atomic_read(&s2_mmu->refcnt) == 0) break; @@ -1281,7 +1275,7 @@ void kvm_nested_s2_wp(struct kvm *kvm) return; for (i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (kvm_s2_mmu_valid(mmu)) kvm_stage2_wp_range(mmu, 0, kvm_phys_size(mmu)); @@ -1300,7 +1294,7 @@ void kvm_nested_s2_unmap(struct kvm *kvm, bool may_block) return; for (i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (kvm_s2_mmu_valid(mmu)) kvm_stage2_unmap_range(mmu, 0, kvm_phys_size(mmu), may_block); @@ -1319,7 +1313,7 @@ void kvm_nested_s2_flush(struct kvm *kvm) return; for (i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (kvm_s2_mmu_valid(mmu)) kvm_stage2_flush_range(mmu, 0, kvm_phys_size(mmu)); @@ -1328,16 +1322,15 @@ void kvm_nested_s2_flush(struct kvm *kvm) void kvm_arch_flush_shadow_all(struct kvm *kvm) { - int i; - - for (i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + for (int i = kvm->arch.nested_mmus_size - 1; i >= 0; i--) { + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (!WARN_ON(atomic_read(&mmu->refcnt))) kvm_free_stage2_pgd(mmu); + + if ((i % S2_MMU_PER_VCPU) == 0) + kvfree(mmu); } - kvfree(kvm->arch.nested_mmus); - kvm->arch.nested_mmus = NULL; kvm->arch.nested_mmus_size = 0; kvm_uninit_stage2_mmu(kvm); } From e5843f4effaa2ffac3e789ecd4456403564961d4 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 11 Sep 2026 17:22:03 +0100 Subject: [PATCH 0526/1417] KVM: arm64: nv: Delay freeing of shadow S2 structures until VM destruction We free the shadow S2 structures from kvm_arch_flush_shadow_all(), which is a Bad Idea(tm). Freeing the page tables is fair game (this is what this callback is for), but freeing the container that could still be referenced by another part of the system is not great. Instead, grow separate destructors that gets called when we tear the VM down for good. From there, we can nuke both the individual MMUs as well as the global array that points to them, safe in the knowledge that the vcpus themselves have been destroyed already. Fixes: 4f128f8e1aaac ("KVM: arm64: nv: Support multiple nested Stage-2 mmu structures") Reviewed-by: Lorenzo Stoakes (ARM) Signed-off-by: Marc Zyngier Cc: stable@vger.kernel.org Reviewed-by: Wei-Lin Chang Link: https://patch.msgid.link/20260911162203.1919330-3-maz@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/include/asm/kvm_nested.h | 1 + arch/arm64/kvm/arm.c | 4 ++-- arch/arm64/kvm/nested.c | 15 ++++++++++----- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/arch/arm64/include/asm/kvm_nested.h b/arch/arm64/include/asm/kvm_nested.h index 5b8edb2e8a87df..586026e859030a 100644 --- a/arch/arm64/include/asm/kvm_nested.h +++ b/arch/arm64/include/asm/kvm_nested.h @@ -67,6 +67,7 @@ static inline u64 translate_ttbr0_el2_to_ttbr0_el1(u64 ttbr0) extern bool forward_smc_trap(struct kvm_vcpu *vcpu); extern bool forward_debug_exception(struct kvm_vcpu *vcpu); extern int kvm_init_nested(struct kvm *kvm); +extern void kvm_destroy_nested(struct kvm *kvm); extern int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu); extern void kvm_init_nested_s2_mmu(struct kvm_s2_mmu *mmu); extern struct kvm_s2_mmu *lookup_s2_mmu(struct kvm_vcpu *vcpu); diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index b53219e048bdf1..eaf583b7719314 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -282,7 +282,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) err_uninit_mmu: kvm_uninit_stage2_mmu(kvm); - kvfree(kvm->arch.nested_mmus); + kvm_destroy_nested(kvm); err_free_cpumask: free_cpumask_var(kvm->arch.supported_cpus); err_unshare_kvm: @@ -340,7 +340,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm) kvm_unshare_hyp(kvm, kvm + 1); - kvfree(kvm->arch.nested_mmus); + kvm_destroy_nested(kvm); kvm_arm_teardown_hypercalls(kvm); } diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index 2571f177e654b3..b191365d97cc27 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -56,6 +56,15 @@ int kvm_init_nested(struct kvm *kvm) return kvm->arch.nested_mmus ? 0 : -ENOMEM; } +void kvm_destroy_nested(struct kvm *kvm) +{ + for (int i = 0; i < kvm->arch.nested_mmus_size; i+= S2_MMU_PER_VCPU) + kvfree(kvm->arch.nested_mmus[i]); + + kvm->arch.nested_mmus_size = 0; + kvfree(kvm->arch.nested_mmus); +} + static int init_nested_s2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu) { /* @@ -1322,16 +1331,12 @@ void kvm_nested_s2_flush(struct kvm *kvm) void kvm_arch_flush_shadow_all(struct kvm *kvm) { - for (int i = kvm->arch.nested_mmus_size - 1; i >= 0; i--) { + for (int i = 0; i < kvm->arch.nested_mmus_size; i++) { struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (!WARN_ON(atomic_read(&mmu->refcnt))) kvm_free_stage2_pgd(mmu); - - if ((i % S2_MMU_PER_VCPU) == 0) - kvfree(mmu); } - kvm->arch.nested_mmus_size = 0; kvm_uninit_stage2_mmu(kvm); } From 49d9d295d69d07e850cae35933ba8519e2915f26 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Mon, 14 Sep 2026 10:38:38 +0100 Subject: [PATCH 0527/1417] KVM: arm64: Don't WARN on an unknown VM ioctl in protected mode kvm_pkvm_ioctl_allowed() WARNs when kvm_get_cap_for_kvm_ioctl() doesn't find the ioctl number in vm_ioctl_caps[], and kvm_arch_vm_ioctl() calls it for every number the generic code doesn't handle, so ioctl(vm_fd, 0xdeadbeef) from userspace taints a pKVM host and panics it under panic_on_warn. The lookup is fed userspace input: return false, and userspace gets the -EINVAL kvm_arch_vm_ioctl() returns for that number on a host without pKVM. Fixes: b12b3b04f6ba0 ("KVM: arm64: Check whether a VM IOCTL is allowed in pKVM") Cc: stable@vger.kernel.org Signed-off-by: Fuad Tabba Reviewed-by: Suzuki K Poulose Link: https://patch.msgid.link/20260914093838.1082637-1-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/include/asm/kvm_pkvm.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h index beea00e693a0a8..cad60569f0619d 100644 --- a/arch/arm64/include/asm/kvm_pkvm.h +++ b/arch/arm64/include/asm/kvm_pkvm.h @@ -65,8 +65,7 @@ static inline bool kvm_pkvm_ioctl_allowed(struct kvm *kvm, unsigned int ioctl) int r; r = kvm_get_cap_for_kvm_ioctl(ioctl, &ext); - - if (WARN_ON_ONCE(r < 0)) + if (r < 0) return false; return kvm_pkvm_ext_allowed(kvm, ext); From 96e6757cb0674acb86ee8b558ee6bffe0eec0bb0 Mon Sep 17 00:00:00 2001 From: Sebastian Ott Date: Mon, 14 Sep 2026 15:10:13 +0200 Subject: [PATCH 0528/1417] KVM: selftests: fix steal_time for arm64 with host page size > 4K Fix the following failure when running with 16K host page size: ==== Test Assertion Failure ==== lib/kvm_util.c:991: vm_adjust_num_guest_pages(vm->mode, npages) == npages pid=873 tid=873 errno=0 - Success 1 0x0000000000405a27: vm_mem_add at kvm_util.c:991 2 0x000000000040241f: check_steal_time_uapi at steal_time.c:223 (discriminator 7) 3 (inlined by) main at steal_time.c:539 (discriminator 7) 4 0x00007fff8b57af3b: ?? ??:0 5 0x00007fff8b57b007: ?? ??:0 6 0x0000000000402b6f: _start at ??:? Number of guest pages is not compatible with the host. Try npages=4 Fixes: fc240715fc50 ("KVM: selftests: arm64: Fix steal_time test after UAPI refactoring") Reported-by: Zenghui Yu Link: https://lore.kernel.org/kvmarm/7575a845-a542-4b16-b512-aec3126f97f3@linux.dev/T/#u Signed-off-by: Sebastian Ott Reviewed-by: Zenghui Yu (Huawei) Link: https://patch.msgid.link/20260914131013.60334-1-sebott@redhat.com Signed-off-by: Oliver Upton --- tools/testing/selftests/kvm/steal_time.c | 30 ++++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/kvm/steal_time.c b/tools/testing/selftests/kvm/steal_time.c index bc3c62b72c58c2..785d19f9ee8f03 100644 --- a/tools/testing/selftests/kvm/steal_time.c +++ b/tools/testing/selftests/kvm/steal_time.c @@ -27,6 +27,9 @@ static void *st_gva[NR_VCPUS]; static u64 guest_stolen_time[NR_VCPUS]; +static struct kvm_vm *vm_create_steal_time(u32 nr_vcpus, void *guest_code, + struct kvm_vcpu *vcpus[]); + #if defined(__x86_64__) /* steal_time must have 64-byte alignment */ @@ -210,17 +213,14 @@ static void check_steal_time_uapi(void) u64 st_ipa; int ret; - vm = vm_create_with_one_vcpu(&vcpu, NULL); - struct kvm_device_attr dev = { .group = KVM_ARM_VCPU_PVTIME_CTRL, .attr = KVM_ARM_VCPU_PVTIME_IPA, .addr = (u64)&st_ipa, }; + vm = vm_create_steal_time(1, NULL, &vcpu); vcpu_ioctl(vcpu, KVM_HAS_DEVICE_ATTR, &dev); - vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, 1, 0); - virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, 1); st_ipa = (ulong)ST_GPA_BASE | 1; ret = __vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &dev); @@ -500,13 +500,27 @@ static void run_vcpu(struct kvm_vcpu *vcpu) } } +static struct kvm_vm *vm_create_steal_time(u32 nr_vcpus, void *guest_code, + struct kvm_vcpu *vcpus[]) +{ + unsigned int gpages; + struct kvm_vm *vm; + + /* Create a VM and an identity mapped memslot for the steal time structure */ + vm = vm_create_with_vcpus(nr_vcpus, guest_code, vcpus); + gpages = vm_calc_num_guest_pages(VM_MODE_DEFAULT, STEAL_TIME_SIZE * nr_vcpus); + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, gpages, 0); + virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, gpages); + + return vm; +} + int main(int ac, char **av) { struct kvm_vcpu *vcpus[NR_VCPUS]; struct kvm_vm *vm; pthread_t thread; cpu_set_t cpuset; - unsigned int gpages; long stolen_time; long run_delay; bool verbose; @@ -517,11 +531,7 @@ int main(int ac, char **av) /* Set CPU affinity so we can force preemption of the VCPU */ cpu = pin_self_to_any_cpu(); - /* Create a VM and an identity mapped memslot for the steal time structure */ - vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus); - gpages = vm_calc_num_guest_pages(VM_MODE_DEFAULT, STEAL_TIME_SIZE * NR_VCPUS); - vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, gpages, 0); - virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, gpages); + vm = vm_create_steal_time(NR_VCPUS, guest_code, vcpus); ksft_print_header(); TEST_REQUIRE(is_steal_time_supported(vcpus[0])); From 089e4f3c4862ba3f29dff2361caa8084879194fd Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 1 Sep 2026 22:47:00 +0100 Subject: [PATCH 0529/1417] KVM: arm64: Fix FGT mapping for HFGITR_EL2.nGCSEPP The encoding to trap mapping currently maps a FGT on OP_GCSPOPX to HFGITR_EL2.nGCSEPP but as per DDI0601 2026-06 this FGT controls trapping of GCSPUSHX and GCSPOPCX, and not the separate GCSPOPX instruction. Update the mapping to reflect the architecture. Fixes: 863ac38984a82 ("KVM: arm64: Add missing HFGITR_EL2 FGT entries to nested virt") Reviewed-by: Leonardo Bras Signed-off-by: Mark Brown Reviewed-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260901-arm64-gcs-v20-2-f31750bdfadb@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/kvm/emulate-nested.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c index 625604019fb321..3806ff0920fe75 100644 --- a/arch/arm64/kvm/emulate-nested.c +++ b/arch/arm64/kvm/emulate-nested.c @@ -1445,7 +1445,7 @@ static const struct encoding_to_trap_config encoding_to_fgt[] __initconst = { SR_FGT(OP_AT_S1E1A, HFGITR, ATS1E1A, 1), SR_FGT(OP_COSP_RCTX, HFGITR, COSPRCTX, 1), SR_FGT(OP_GCSPUSHX, HFGITR, nGCSEPP, 0), - SR_FGT(OP_GCSPOPX, HFGITR, nGCSEPP, 0), + SR_FGT(OP_GCSPOPCX, HFGITR, nGCSEPP, 0), SR_FGT(OP_GCSPUSHM, HFGITR, nGCSPUSHM_EL1, 0), SR_FGT(OP_BRB_IALL, HFGITR, nBRBIALL, 0), SR_FGT(OP_BRB_INJ, HFGITR, nBRBINJ, 0), From 7de9a6fb44eae4f05e68c805d58b9c618815adfa Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 15 Sep 2026 12:34:44 -1000 Subject: [PATCH 0530/1417] sched_ext: Wait for SCX_OPSS_DISPATCHING before reenqueueing a task ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") moved the final ops_state store in scx_dispatch_enqueue() after the DSQ unlock so that the custody update and ops.dequeue() precede it. A task can thus be found on a DSQ while still SCX_OPSS_DISPATCHING. The dequeue and core-sched pick paths wait for the state to clear in ops_dequeue() but the reenqueue paths don't. A reenqueue in that window runs ops.enqueue() and sets SCX_OPSS_QUEUED before the dispatch has completed. The dispatcher's final store then overwrites it with SCX_OPSS_NONE and finish_dispatch() drops every later dispatch of the task. Wait for SCX_OPSS_DISPATCHING to clear before dequeueing a task for reenqueue, the same way ops_dequeue() does. Fixes: ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 13 +++++++++++++ kernel/sched/ext/internal.h | 1 + kernel/sched/ext/sub.c | 1 + 3 files changed, 15 insertions(+) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 70b711c4de6e11..725b890fcab96d 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -4404,6 +4404,17 @@ static bool local_task_should_reenq(struct rq *rq, struct task_struct *p, return *reenq_flags & SCX_REENQ_ANY; } +/* + * The dispatcher stores the final ops_state after dropping the DSQ lock, so @p + * can be found on a DSQ while still %SCX_OPSS_DISPATCHING. Reenqueueing @p + * before that store lands would have it clobber the new %SCX_OPSS_QUEUED. + */ +void scx_reenq_wait_dispatching(struct task_struct *p) +{ + if (unlikely(atomic_long_read_acquire(&p->scx.ops_state) == SCX_OPSS_DISPATCHING)) + wait_ops_state(p, SCX_OPSS_DISPATCHING); +} + static u32 reenq_local(struct scx_sched *sch, struct rq *rq, u64 reenq_flags) { LIST_HEAD(tasks); @@ -4447,6 +4458,7 @@ static u32 reenq_local(struct scx_sched *sch, struct rq *rq, u64 reenq_flags) if (!local_task_should_reenq(rq, p, &reenq_flags, &reason)) continue; + scx_reenq_wait_dispatching(p); scx_dispatch_dequeue(rq, p); if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK)) @@ -4570,6 +4582,7 @@ static void reenq_user(struct rq *rq, struct scx_dispatch_q *dsq, u64 reenq_flag } /* @p is on @dsq, its rq and @dsq are locked */ + scx_reenq_wait_dispatching(p); dispatch_dequeue_locked(p, dsq); raw_spin_unlock(&dsq->lock); diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 3464e0f113c163..115c96fbf9322f 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -2078,6 +2078,7 @@ void scx_kick_cpu(struct scx_sched *sch, s32 cpu, u64 flags); u64 __scx_bpf_now(struct rq *rq); void schedule_dsq_reenq(struct scx_sched *sch, struct scx_dispatch_q *dsq, u64 reenq_flags, struct rq *locked_rq); +void scx_reenq_wait_dispatching(struct task_struct *p); int __scx_init_task(struct scx_sched *sch, struct task_struct *p, struct cgroup *cgrp, bool fork); void scx_enable_task(struct scx_sched *sch, struct task_struct *p); diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index f7aeb1488566d1..a30c965b175d69 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -801,6 +801,7 @@ void scx_reenq_reject(struct rq *rq) if (WARN_ON_ONCE(p->migration_pending)) continue; + scx_reenq_wait_dispatching(p); scx_dispatch_dequeue(rq, p); if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK)) From 4a4263dfeabad72f95e8ab6e15146861fa4144dd Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 03:07:51 +0000 Subject: [PATCH 0531/1417] af_unix: Unify scc_index when finalising SCC in __unix_walk_scc(). Commit bfdb01283ee8 ("af_unix: Assign a unique index to SCC.") changed Tarjan's algorithm to update lowlink with lowlink, which is called lowpoint (unix_vertex.scc_index). unix_vertex_dead() assumes all vertices in an SCC share the same lowpoint, but this is not always true if an SCC has two or more back edges, depending on the order of DFS. For example, the graph below has two back edges from B to A and from C to B. A --> B --> C ^ | ^ | `----' `----' If DFS walks through A -> B -> C -> B (-> C -> B) -> A (-> B -> A), each index and scc_index will be updated as follows. A --> B --> C C = (3, 3) (index, scc_index) B = (2, 2) A = (1, 1) A ... B ... C C = (3, 2)<-. ^ | B = (2, 2) -' `----' A = (1, 1) A ... B ... C C = (3, 2) ^ | . . B = (2, 1)<-. `----' .... A = (1, 1) -' Then, unix_vertex_dead() thinks that B is passed to another SCC with scc_index 2, and the SCC is not garbage-collected. This does not happen if DFS walks in a different order below or starts from B. 1 3 A --> B --> C ^ | ^ | `----' `----' 2 4 Let's unify scc_index across the SCC when finalising it. Note that updating v->index was previously done in unix_scc_dead(), when called from __unix_walk_scc(), just to save one loop. Since __unix_walk_scc() now iterates over the SCC anyway, the update is moved back to __unix_walk_scc() and 'fast' argument is dropped. Fixes: 4090fa373f0e ("af_unix: Replace garbage collection algorithm.") Reported-by: James Burton Signed-off-by: Kuniyuki Iwashima Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260912030852.1467872-2-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/unix/garbage.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/net/unix/garbage.c b/net/unix/garbage.c index 9fcaaf55cba5d5..da774f56ca648b 100644 --- a/net/unix/garbage.c +++ b/net/unix/garbage.c @@ -374,7 +374,7 @@ static bool unix_vertex_dead(struct unix_vertex *vertex) static LIST_HEAD(unix_visited_vertices); static unsigned long unix_vertex_grouped_index = UNIX_VERTEX_INDEX_MARK2; -static bool unix_scc_dead(struct list_head *scc, bool fast) +static bool unix_scc_dead(struct list_head *scc) { struct unix_vertex *vertex; bool scc_dead = true; @@ -386,10 +386,6 @@ static bool unix_scc_dead(struct list_head *scc, bool fast) /* Don't restart DFS from this vertex. */ list_move_tail(&vertex->entry, &unix_visited_vertices); - /* Mark vertex as off-stack for __unix_walk_scc(). */ - if (!fast) - vertex->index = unix_vertex_grouped_index; - if (scc_dead) scc_dead = unix_vertex_dead(vertex); } @@ -521,6 +517,7 @@ static unsigned long __unix_walk_scc(struct unix_vertex *vertex, } if (vertex->index == vertex->scc_index) { + struct unix_vertex *v; struct list_head scc; /* SCC finalised. @@ -530,7 +527,13 @@ static unsigned long __unix_walk_scc(struct unix_vertex *vertex, */ __list_cut_position(&scc, &vertex_stack, &vertex->scc_entry); - if (unix_scc_dead(&scc, false)) { + list_for_each_entry_reverse(v, &scc, scc_entry) { + /* Mark vertex as off-stack and assign a unique ID. */ + v->index = unix_vertex_grouped_index; + v->scc_index = vertex->scc_index; + } + + if (unix_scc_dead(&scc)) { unix_collect_skb(&scc, hitlist); } else { if (unix_vertex_max_scc_index < vertex->scc_index) @@ -588,7 +591,7 @@ static void unix_walk_scc_fast(struct sk_buff_head *hitlist) vertex = list_first_entry(&unix_unvisited_vertices, typeof(*vertex), entry); list_add(&scc, &vertex->scc_entry); - if (unix_scc_dead(&scc, true)) { + if (unix_scc_dead(&scc)) { cyclic_sccs--; unix_collect_skb(&scc, hitlist); } From b645ccd410547d0e0e4a9543f828119e24dc7635 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 03:07:52 +0000 Subject: [PATCH 0532/1417] selftest: af_unix: Add test case with mixed lowpoint in scm_rights.c. The new test case creates two SCCs so that each of them has multiple scc_index. Without patch, GC cannot free the sockets and the test fails. # RUN scm_rights.dgram.mixed_lowpoints ... # scm_rights.c:176:mixed_lowpoints:Expected 0 (0) == ret (12) # mixed_lowpoints: Test terminated by assertion # FAIL scm_rights.dgram.mixed_lowpoints not ok 5 scm_rights.dgram.mixed_lowpoints ... # FAILED: 45 / 50 tests passed. # Totals: pass:45 fail:5 xfail:0 xpass:0 skip:0 error:0 With the patch, all tests pass. # PASSED: 50 / 50 tests passed. # Totals: pass:50 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Kuniyuki Iwashima Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260912030852.1467872-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- .../testing/selftests/net/af_unix/scm_rights.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/testing/selftests/net/af_unix/scm_rights.c b/tools/testing/selftests/net/af_unix/scm_rights.c index d82a79c21c1796..c165f250220a4a 100644 --- a/tools/testing/selftests/net/af_unix/scm_rights.c +++ b/tools/testing/selftests/net/af_unix/scm_rights.c @@ -378,4 +378,21 @@ TEST_F(scm_rights, backtrack_from_scc) close_sockets(10); } +TEST_F(scm_rights, mixed_lowpoint) +{ + create_sockets(6); + + send_fd(0, 1); + send_fd(1, 2); + send_fd(2, 1); + send_fd(1, 0); + + send_fd(3, 4); + send_fd(4, 5); + send_fd(5, 4); + send_fd(4, 3); + + close_sockets(6); +} + TEST_HARNESS_MAIN From 15989abd74f16f44bf953d056b95f1d2fda9b0cd Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 11 Sep 2026 11:20:15 +0200 Subject: [PATCH 0533/1417] net: stmmac: fix TSO header length truncation stmmac_tso_xmit() stores the protocol header length returned by stmmac_tso_header_size() in a u8. stmmac_tso_valid_packet() admits headers up to 1023 bytes, so a header longer than 255 bytes wraps modulo 256 (486 becomes 230, 256 becomes 0). A TCP over IPv6 socket carrying a few hundred bytes of sticky destination/hop-by-hop options makes skb_tcp_all_headers() exceed 255 while staying below the 1023-byte limit, so such an skb reaches stmmac_tso_xmit(). Widen proto_hdr_len to unsigned int, which is sufficient since the value is bounded by the hardware limit, and adjust the debug print specifier accordingly. Fixes: 9edfa7dab811 ("net: stmmac: enable TSO for IPv6") Signed-off-by: Lorenzo Bianconi Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260911-stmmac-fix-header-length-v1-1-8fc103334327@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 62c3441911e71e..1fb5f804ea2345 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4513,16 +4513,16 @@ static int stmmac_tso_get_num_desc(struct stmmac_tx_queue *tx_q, */ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) { + unsigned int first_entry, entry, tx_packets, proto_hdr_len; struct dma_desc *desc, *first, *mss_desc = NULL; struct stmmac_priv *priv = netdev_priv(dev); - unsigned int first_entry, entry, tx_packets; struct stmmac_txq_stats *txq_stats; int i, first_tx, nfrags, ndesc; struct stmmac_tx_queue *tx_q; bool set_ic, is_last_segment; u32 pay_len, mss, queue; - u8 proto_hdr_len, hdr; dma_addr_t des; + u8 hdr; nfrags = skb_shinfo(skb)->nr_frags; queue = skb_get_queue_mapping(skb); @@ -4570,7 +4570,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) } if (netif_msg_tx_queued(priv)) { - pr_info("%s: hdrlen %d, hdr_len %d, pay_len %d, mss %d\n", + pr_info("%s: hdrlen %d, hdr_len %u, pay_len %d, mss %d\n", __func__, hdr, proto_hdr_len, pay_len, mss); pr_info("\tskb->len %d, skb->data_len %d\n", skb->len, skb->data_len); From 6e05e46fa821a5c1b281355f1f622ac76cb6080a Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Thu, 10 Sep 2026 17:34:12 +0800 Subject: [PATCH 0534/1417] net/sched: act_api: release tail references on DELACTION failure A batched RTM_DELACTION request takes a temporary reference on each action before attempting any deletion. tcf_action_delete() clears each processed slot and drops its temporary reference before attempting the deletion. If deletion fails, tca_action_gd() calls tcf_action_put_many() to release the remaining references, but its tcf_act_for_each_action() iterator stops at the first NULL slot. When a batch stops at an action bound to a filter, this leaks a reference on each subsequent action. A later delete of an unbound action can then return success without removing it from the IDR. Walk the full array in tcf_action_put_many() and skip NULL slots to release the references held on the unprocessed actions. Fixes: a0e947c9ccff ("net/sched: act_api: avoid non-contiguous action array") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260910093413.34509-2-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 19501dc994641c..3f653721c45feb 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -1218,11 +1218,16 @@ static int tcf_action_put(struct tc_action *p) static void tcf_action_put_many(struct tc_action *actions[]) { - struct tc_action *a; int i; - tcf_act_for_each_action(i, a, actions) { - const struct tc_action_ops *ops = a->ops; + /* Deletion may have cleared entries before failing. */ + for (i = 0; i < TCA_ACT_MAX_PRIO; i++) { + struct tc_action *a = actions[i]; + const struct tc_action_ops *ops; + + if (!a) + continue; + ops = a->ops; if (tcf_action_put(a)) module_put(ops->owner); } From 14c5eb685cdefbd32e73d2723071ecbd8effbce9 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Thu, 10 Sep 2026 17:34:13 +0800 Subject: [PATCH 0535/1417] selftests: tc-testing: test action batch deletion failure cleanup Add tests for cleanup after a batched RTM_DELACTION request fails at a gact action bound to a filter. Check that subsequent actions retain their original reference counts and that earlier successful deletions are preserved. Cover failures at the first and middle entries. Verify that a remaining unbound action can be removed with one subsequent delete. Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260910093413.34509-3-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- .../tc-tests/actions/batch-delete.json | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json diff --git a/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json b/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json new file mode 100644 index 00000000000000..ef7ca4a6775bcb --- /dev/null +++ b/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json @@ -0,0 +1,115 @@ +[ + { + "id": "d710", + "name": "Release tail references after first action deletion fails", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ], + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC actions add action pass index 2", + "$TC actions add action pass index 3", + "$TC filter add dev $DEV1 protocol ip ingress u32 match u32 0 0 action gact index 1" + ], + "cmdUnderTest": "$TC actions del action gact index 1 action gact index 2 action gact index 3", + "expExitCode": "255", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 3\\b.*index 1 ref 2 bind 1\\b.*index 2 ref 1 bind 0\\b.*index 3 ref 1 bind 0\\b", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress", + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ] + ] + }, + { + "id": "d711", + "name": "Release tail references after middle action deletion fails", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ], + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC actions add action pass index 2", + "$TC actions add action pass index 3", + "$TC filter add dev $DEV1 protocol ip ingress u32 match u32 0 0 action gact index 2" + ], + "cmdUnderTest": "$TC actions del action gact index 1 action gact index 2 action gact index 3", + "expExitCode": "255", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 2\\b.*index 2 ref 2 bind 1\\b.*index 3 ref 1 bind 0\\b", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress", + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ] + ] + }, + { + "id": "d713", + "name": "Delete a tail action once after a failed batch", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ], + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC actions add action pass index 2", + "$TC filter add dev $DEV1 protocol ip ingress u32 match u32 0 0 action gact index 1" + ], + "cmdUnderTest": "$TC actions del action gact index 1 action gact index 2", + "expExitCode": "255", + "verifyCmd": "sh -c '$TC actions del action gact index 2 && $TC actions ls action gact'", + "matchPattern": "total acts 1\\b.*index 1 ref 2 bind 1\\b", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress", + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ] + ] + } +] From ecc7253683a3c55caa868ce0ee530fcb0044bd3c Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sat, 12 Sep 2026 23:30:48 +0000 Subject: [PATCH 0536/1417] pppoatm: ensure a writable skb header and linear data In pppoatm_send(), LLC encapsulation checks whether there is sufficient headroom for the 4-byte LLC header, but does not ensure that the skb header is writable. Normal transmit packets passing through ppp_start_xmit() have their header unshared via skb_cow_head(). However, packets can also reach pppoatm_send() via PPP channel bridging (PPPIOCBRIDGECHAN) without going through ppp_start_xmit(). Use skb_cow_head() to ensure both sufficient headroom and a writable header before pushing the LLC header. While at it: - Call pskb_may_pull(skb, 1) before inspecting skb->data[0] to prevent out-of-bounds reads on zero-length or non-linear frames (e.g. from bridging). - Defer SC_COMP_PROT protocol compression until after pppoatm_may_send() succeeds. This eliminates the temporary skb allocation on admission failure and completely removes the fragile "undo" heuristic at the nospace label, avoiding any risk of reading uninitialized headroom or performing an unbalanced skb_push(). Fixes: 4cf476ced45d ("ppp: add PPPIOCBRIDGECHAN and PPPIOCUNBRIDGECHAN ioctls") Signed-off-by: Eric Dumazet Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260912233048.3977192-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/atm/pppoatm.c | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/net/atm/pppoatm.c b/net/atm/pppoatm.c index 6da52d12df68e4..5214786e61d11a 100644 --- a/net/atm/pppoatm.c +++ b/net/atm/pppoatm.c @@ -292,10 +292,13 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) struct atm_vcc *vcc; int ret; + if (!pskb_may_pull(skb, 1)) { + kfree_skb(skb); + return DROP_PACKET; + } + ATM_SKB(skb)->vcc = pvcc->atmvcc; pr_debug("(skb=0x%p, vcc=0x%p)\n", skb, pvcc->atmvcc); - if (skb->data[0] == '\0' && (pvcc->flags & SC_COMP_PROT)) - (void) skb_pull(skb, 1); vcc = ATM_SKB(skb)->vcc; bh_lock_sock(sk_atm(vcc)); @@ -317,23 +320,13 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) switch (pvcc->encaps) { /* LLC encapsulation needed */ case e_llc: - if (skb_headroom(skb) < LLC_LEN) { - struct sk_buff *n; - n = skb_realloc_headroom(skb, LLC_LEN); - if (n != NULL && - !pppoatm_may_send(pvcc, n->truesize)) { - kfree_skb(n); - goto nospace; - } - consume_skb(skb); - skb = n; - if (skb == NULL) { - bh_unlock_sock(sk_atm(vcc)); - return DROP_PACKET; - } - } else if (!pppoatm_may_send(pvcc, skb->truesize)) + if (skb_cow_head(skb, LLC_LEN)) { + bh_unlock_sock(sk_atm(vcc)); + kfree_skb(skb); + return DROP_PACKET; + } + if (!pppoatm_may_send(pvcc, skb->truesize)) goto nospace; - memcpy(skb_push(skb, LLC_LEN), pppllc, LLC_LEN); break; case e_vc: if (!pppoatm_may_send(pvcc, skb->truesize)) @@ -346,6 +339,12 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) return 1; } + if (skb->data[0] == '\0' && (pvcc->flags & SC_COMP_PROT)) + skb_pull(skb, 1); + + if (pvcc->encaps == e_llc) + memcpy(skb_push(skb, LLC_LEN), pppllc, LLC_LEN); + atm_account_tx(vcc, skb); pr_debug("atm_skb(%p)->vcc(%p)->dev(%p)\n", skb, ATM_SKB(skb)->vcc, ATM_SKB(skb)->vcc->dev); @@ -355,13 +354,6 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) return ret; nospace: bh_unlock_sock(sk_atm(vcc)); - /* - * We don't have space to send this SKB now, but we might have - * already applied SC_COMP_PROT compression, so may need to undo - */ - if ((pvcc->flags & SC_COMP_PROT) && skb_headroom(skb) > 0 && - skb->data[-1] == '\0') - (void) skb_push(skb, 1); return 0; } From 455ebeadf714f51e1dbbd6a022c74c9215b1cd76 Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Sun, 13 Sep 2026 17:08:50 +0800 Subject: [PATCH 0537/1417] net: ip_tunnel: initialize `options_len` before referencing options The following command triggers a kernel panic: ip link add d0 type dummy; ip link set d0 up ip route add 10.30.0.0/16 \ encap ip id 300 geneve_opts 4660:66:11223344 dev d0 memcpy: detected buffer overflow: 4 byte write of buffer size 0 kernel BUG at lib/string_helpers.c:1044! ... ip_tun_parse_opts.part.0.cold+0x10/0x10 ip_tun_build_state+0x116/0x2a0 On kernels built with GCC 15+ and `CONFIG_FORTIFY_SOURCE`, the fortified `memcpy()` got 0 sized destination with request of 4 bytes length: static int ip_tun_parse_opts_geneve(...) { ... attr = tb[LWTUNNEL_IP_OPT_GENEVE_DATA]; data_len = nla_len(attr); /* == 4 */ struct geneve_opt *opt = ip_tunnel_info_opts(info) + opts_len; memcpy(opt->opt_data, nla_data(attr), data_len); /* ^^^^^^^^^^^^^ 0 since options_len is assigned afterwards */ Fixed by initializing the counter before the options are referenced. Matching what `tunnel_key_opts_set()` already does. Fixes: bb5e62f2d547 ("net: Add options as a flexible array to struct ip_tunnel_info") Cc: stable@vger.kernel.org Signed-off-by: Gris Ge Reviewed-by: Hangbin Liu Reviewed-by: Gustavo A. R. Silva Link: https://patch.msgid.link/20260913090851.468216-1-cnfourt@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/ip_tunnel_core.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/net/ipv4/ip_tunnel_core.c b/net/ipv4/ip_tunnel_core.c index 5168d546ea2fda..bab42b9e277f7a 100644 --- a/net/ipv4/ip_tunnel_core.c +++ b/net/ipv4/ip_tunnel_core.c @@ -680,8 +680,14 @@ static int ip_tun_get_optlen(struct nlattr *attr, } static int ip_tun_set_opts(struct nlattr *attr, struct ip_tunnel_info *info, - struct netlink_ext_ack *extack) + int opts_len, struct netlink_ext_ack *extack) { + /* `options_len` is the __counted_by() annotation of the `options` + * flexible array, it must be initialized before parsing writes + * into it. + */ + info->options_len = opts_len; + return ip_tun_parse_opts(attr, info, extack); } @@ -712,7 +718,8 @@ static int ip_tun_build_state(struct net *net, struct nlattr *attr, tun_info = lwt_tun_info(new_state); - err = ip_tun_set_opts(tb[LWTUNNEL_IP_OPTS], tun_info, extack); + err = ip_tun_set_opts(tb[LWTUNNEL_IP_OPTS], tun_info, opt_len, + extack); if (err < 0) { lwtstate_free(new_state); return err; @@ -753,7 +760,6 @@ static int ip_tun_build_state(struct net *net, struct nlattr *attr, } tun_info->mode = IP_TUNNEL_INFO_TX; - tun_info->options_len = opt_len; *ts = new_state; @@ -1006,7 +1012,8 @@ static int ip6_tun_build_state(struct net *net, struct nlattr *attr, tun_info = lwt_tun_info(new_state); - err = ip_tun_set_opts(tb[LWTUNNEL_IP6_OPTS], tun_info, extack); + err = ip_tun_set_opts(tb[LWTUNNEL_IP6_OPTS], tun_info, opt_len, + extack); if (err < 0) { lwtstate_free(new_state); return err; @@ -1040,7 +1047,6 @@ static int ip6_tun_build_state(struct net *net, struct nlattr *attr, } tun_info->mode = IP_TUNNEL_INFO_TX | IP_TUNNEL_INFO_IPV6; - tun_info->options_len = opt_len; *ts = new_state; From 6a038ef2b57922b6d9ca98ddac0df0681849b704 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 10 Sep 2026 20:46:09 +0000 Subject: [PATCH 0538/1417] drop_monitor: synchronize tracepoint unregistration on error path If register_trace_napi_poll() fails in net_dm_trace_on_set(), unregister_trace_kfree_skb() is called to roll back the kfree_skb tracepoint registration. However, tracepoint_synchronize_unregister() is omitted before calling cancel_work_sync() and module_put(). An in-flight probe executing concurrently on another CPU could call schedule_work() after cancel_work_sync() has already returned, leaving a pending work item scheduled after the module reference is dropped. If the module is then unloaded, executing the work item triggers a kernel panic. Add tracepoint_synchronize_unregister() after unregister_trace_kfree_skb() in the error path, matching net_dm_trace_off_set() and net_dm_hw_probe_unregister(). Fixes: 7c747838a558 ("drop_monitor: Split tracing enable / disable to different functions") Signed-off-by: Eric Dumazet Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260910204612.3762015-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index abaf108ac4db8c..018d19e3a71de0 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -1173,6 +1173,7 @@ static int net_dm_trace_on_set(struct netlink_ext_ack *extack) err_unregister_trace: unregister_trace_kfree_skb(ops->kfree_skb_probe, NULL); + tracepoint_synchronize_unregister(); err_module_put: for_each_possible_cpu(cpu) { struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu); From c391a40f71886b28c082b47270f0e856fa3e1150 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 10 Sep 2026 20:46:10 +0000 Subject: [PATCH 0539/1417] drop_monitor: use timer_shutdown_sync() to prevent timer rearming during teardown In drop_monitor teardown paths (net_dm_trace_off_set(), net_dm_hw_monitor_stop(), and error unwind paths in net_dm_trace_on_set() and net_dm_hw_monitor_start()), per-CPU timers are stopped using timer_delete_sync() followed by cancel_work_sync(). However, there is a circular dependency between send_timer and dm_alert_work: 1) sched_send_work() (timer callback) schedules dm_alert_work. 2) send_dm_alert() / net_dm_hw_summary_work() calls reset_per_cpu_data() or net_dm_hw_reset_per_cpu_data(). 3) If memory allocation fails under memory pressure in the reset function, it re-arms the timer via mod_timer(&data->send_timer, ...). If dm_alert_work is running concurrently while timer_delete_sync() executes on another CPU, an allocation failure in the worker will re-arm the timer after timer_delete_sync() has already returned. Once cancel_work_sync() completes and module_put() is called, the timer remains active in the timer wheel. If the module is then unloaded, the timer will fire and execute sched_send_work() in freed memory, triggering a kernel panic / use-after-free. Switch from timer_delete_sync() to timer_shutdown_sync(). This guarantees that any in-flight timer handler has finished and prevents subsequent re-arming attempts from running workers from succeeding. When monitoring is restarted later, timer_setup() is invoked, which cleanly re-initializes the timer. Fixes: 9398e9c0b1d4 ("drop_monitor: Perform cleanup upon probe registration failure") Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260910204612.3762015-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 018d19e3a71de0..873155ca724329 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -1083,7 +1083,7 @@ static int net_dm_hw_monitor_start(struct netlink_ext_ack *extack) struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu); struct sk_buff *skb; - timer_delete_sync(&hw_data->send_timer); + timer_shutdown_sync(&hw_data->send_timer); cancel_work_sync(&hw_data->dm_alert_work); while ((skb = __skb_dequeue(&hw_data->drop_queue))) { struct devlink_trap_metadata *hw_metadata; @@ -1117,7 +1117,7 @@ static void net_dm_hw_monitor_stop(struct netlink_ext_ack *extack) struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu); struct sk_buff *skb; - timer_delete_sync(&hw_data->send_timer); + timer_shutdown_sync(&hw_data->send_timer); cancel_work_sync(&hw_data->dm_alert_work); while ((skb = __skb_dequeue(&hw_data->drop_queue))) { struct devlink_trap_metadata *hw_metadata; @@ -1179,7 +1179,7 @@ static int net_dm_trace_on_set(struct netlink_ext_ack *extack) struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu); struct sk_buff *skb; - timer_delete_sync(&data->send_timer); + timer_shutdown_sync(&data->send_timer); cancel_work_sync(&data->dm_alert_work); while ((skb = __skb_dequeue(&data->drop_queue))) consume_skb(skb); @@ -1207,7 +1207,7 @@ static void net_dm_trace_off_set(void) struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu); struct sk_buff *skb; - timer_delete_sync(&data->send_timer); + timer_shutdown_sync(&data->send_timer); cancel_work_sync(&data->dm_alert_work); while ((skb = __skb_dequeue(&data->drop_queue))) consume_skb(skb); From c19b7d35086b7d240f1ca3088b0079d2bd39ffb9 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 10 Sep 2026 20:46:11 +0000 Subject: [PATCH 0540/1417] drop_monitor: use raw_cpu_ptr() in tracepoint probes syzbot reported a preemption warning in sk_skb_reason_drop(): BUG: using smp_processor_id() in preemptible [00000000] code: syz.0.17/5917 caller is net_dm_packet_trace_kfree_skb_hit+0x119/0x350 net/core/drop_monitor.c:519 In net_dm_packet_trace_kfree_skb_hit(), data = this_cpu_ptr(&dm_cpu_data) is evaluated before spin_lock_irqsave(&data->drop_queue.lock, flags). When kfree_skb() is called from preemptible context (e.g. process context during close() on /dev/net/tun), preemption is enabled, triggering the CONFIG_DEBUG_PREEMPT warning in smp_processor_id(). The same pattern exists in net_dm_hw_trap_summary_probe() and net_dm_hw_trap_packet_probe() for dm_hw_cpu_data. This is a false positive because each per-cpu structure is protected by its own spinlock. If the task migrates to another CPU right after reading the per-cpu pointer, the lock still safely synchronizes access to that queue. Use raw_cpu_ptr() instead of this_cpu_ptr() to silence CONFIG_DEBUG_PREEMPT without disturbing interrupt state or breaking PREEMPT_RT locking semantics. Fixes: ca30707dee2b ("drop_monitor: Add packet alert mode") Fixes: 5855357cd40e ("drop_monitor: Prepare probe functions for devlink tracepoint") Reported-by: syzbot+dc57fd6722deb17e92af@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6aa316b2.f81106d8.2ab401.0014.GAE@google.com/ Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260910204612.3762015-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 873155ca724329..795c15dd1771a2 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -448,7 +448,7 @@ net_dm_hw_trap_summary_probe(void *ignore, const struct devlink *devlink, if (metadata->trap_type == DEVLINK_TRAP_TYPE_CONTROL) return; - hw_data = this_cpu_ptr(&dm_hw_cpu_data); + hw_data = raw_cpu_ptr(&dm_hw_cpu_data); raw_spin_lock_irqsave(&hw_data->lock, flags); hw_entries = hw_data->hw_entries; @@ -516,7 +516,7 @@ static void net_dm_packet_trace_kfree_skb_hit(void *ignore, */ nskb->tstamp = tstamp; - data = this_cpu_ptr(&dm_cpu_data); + data = raw_cpu_ptr(&dm_cpu_data); spin_lock_irqsave(&data->drop_queue.lock, flags); if (skb_queue_len(&data->drop_queue) < net_dm_queue_len) @@ -983,7 +983,7 @@ net_dm_hw_trap_packet_probe(void *ignore, const struct devlink *devlink, NET_DM_SKB_CB(nskb)->hw_metadata = n_hw_metadata; nskb->tstamp = tstamp; - hw_data = this_cpu_ptr(&dm_hw_cpu_data); + hw_data = raw_cpu_ptr(&dm_hw_cpu_data); spin_lock_irqsave(&hw_data->drop_queue.lock, flags); if (skb_queue_len(&hw_data->drop_queue) < net_dm_queue_len) From 439f392084f8f7f59ab9d47a9579185accefe1d8 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 10 Sep 2026 20:46:12 +0000 Subject: [PATCH 0541/1417] drop_monitor: fix out-of-bounds write in reset_per_cpu_data() In reset_per_cpu_data(), al is computed as: al = sizeof(struct net_dm_alert_msg); al += dm_hit_limit * sizeof(struct net_dm_drop_point); al += sizeof(struct nlattr); skb = genlmsg_new(al, GFP_KERNEL); ... nla = nla_reserve(skb, NLA_UNSPEC, sizeof(struct net_dm_alert_msg)); ... msg = nla_data(nla); memset(msg, 0, al); Because al includes sizeof(struct nlattr) (the 4-byte attribute header), genlmsg_new() allocates al bytes of tailroom starting at nla. However, msg points to nla_data(nla), which is located sizeof(struct nlattr) bytes past nla. Calling memset(msg, 0, al) therefore writes al bytes starting from msg, exceeding the allocated buffer by sizeof(struct nlattr) (4 bytes) and corrupting skb_shared_info. Fix this by letting al represent only the payload length, allocating the skb with genlmsg_new(nla_total_size(al), GFP_KERNEL), and zeroing al bytes from msg. Fixes: 683703a26e46 ("drop_monitor: Update netlink protocol to include netlink attribute header in alert message") Signed-off-by: Eric Dumazet Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260910204612.3762015-5-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 795c15dd1771a2..edc660778408e1 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -141,9 +141,8 @@ static struct sk_buff *reset_per_cpu_data(struct per_cpu_dm_data *data) al = sizeof(struct net_dm_alert_msg); al += dm_hit_limit * sizeof(struct net_dm_drop_point); - al += sizeof(struct nlattr); - skb = genlmsg_new(al, GFP_KERNEL); + skb = genlmsg_new(nla_total_size(al), GFP_KERNEL); if (!skb) goto err; From 3f118c8217c109fd13ca61caa301d72c483897ef Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 12 Sep 2026 21:22:43 +0800 Subject: [PATCH 0542/1417] openvswitch: avoid reallocating confirmed conntrack labels ovs_ct_get_conn_labels() adds the labels extension when a conntrack entry does not have one. Confirmed conntracks can be read locklessly, so adding an extension may reallocate and free the extension block while another CPU accesses it. Only add the extension for unconfirmed conntracks. A confirmed conntrack without labels now fails the caller's label operation instead of reallocating its extension storage. Fixes: c2ac66735870 ("openvswitch: Allow matching on conntrack label") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Reviewed-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/372fbb062b40ae6723684f55484be86ff0064f8e.1789218015.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/openvswitch/conntrack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index 27115967e5d93a..0f433688e17b98 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -366,7 +366,7 @@ static struct nf_conn_labels *ovs_ct_get_conn_labels(struct nf_conn *ct) struct nf_conn_labels *cl; cl = nf_ct_labels_find(ct); - if (!cl) { + if (!cl && !nf_ct_is_confirmed(ct)) { nf_ct_labels_ext_add(ct); cl = nf_ct_labels_find(ct); } From f0ef4b1eaed000a304726a43091588e8426ba08a Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 14 Sep 2026 09:41:07 +0200 Subject: [PATCH 0543/1417] net: stmmac: do not overwrite phc_index when no PTP clock is registered stmmac_get_ts_info() reports phc_index as 0 when hardware timestamping is supported but no PTP clock has been registered yet (e.g. while the interface is down). Zero is a valid PHC index and would make userspace resolve the wrong clock; the absence of a clock should be reported as -1. The ethtool core already initializes phc_index to -1 before invoking the get_ts_info callback (ethtool_init_tsinfo()), so just drop the erroneous assignment. Fixes: 9364fa7fcf12 ("net: stmmac: Remove setting of RX software timestamp") Reviewed-by: Maxime Chevallier Reviewed-by: Rahul Rameshbabu Signed-off-by: Lorenzo Bianconi Reviewed-by: Gal Pressman Link: https://patch.msgid.link/20260914-stmmac-fix-phc_index-v2-1-bf3d90373fe4@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index 154cc0c7623da1..1be5310ca766c5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -1016,8 +1016,6 @@ static int stmmac_get_ts_info(struct net_device *dev, if (priv->ptp_clock) info->phc_index = ptp_clock_index(priv->ptp_clock); - else - info->phc_index = 0; info->tx_types = (1 << HWTSTAMP_TX_OFF) | (1 << HWTSTAMP_TX_ON); From 2842ce397dd09882530b42f7fdb0c855767eb24e Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Mon, 14 Sep 2026 13:52:58 +0300 Subject: [PATCH 0544/1417] net: bridge: vlan: fix bugs caused by switchdev deletion errors Allowing switchdev to prevent vlan deletion and error out in __vlan_del could cause multiple different issues - inconsistent state, memory leaks when flushing, NULL pointer dereference on bridge error when flushing. It doesn't make sense to allow it to stop __vlan_del, so log the error and continue with software vlan deletion. This is also consistent with 8021q behaviour. Suggested-by: Ido Schimmel Fixes: bf361ad38165 ("net: bridge: check __vlan_vid_del for error") Fixes: 5454f5c28eca ("net: bridge: vlan: check for errors from __vlan_del in __vlan_flush") Fixes: 2594e9064a57 ("bridge: vlan: add per-vlan struct and move to rhashtables") Fixes: 9c86ce2c1ae3 ("net: bridge: Notify about bridge VLANs") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260914105258.3436918-1-razor@blackwall.org Signed-off-by: Jakub Kicinski --- net/bridge/br_vlan.c | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c index 1e0e436629ece5..92b3cb621a264b 100644 --- a/net/bridge/br_vlan.c +++ b/net/bridge/br_vlan.c @@ -387,12 +387,12 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags, goto out; } -static int __vlan_del(struct net_bridge_vlan *v) +static void __vlan_del(struct net_bridge_vlan *v) { struct net_bridge_vlan *masterv = v; struct net_bridge_vlan_group *vg; struct net_bridge_port *p = NULL; - int err = 0; + int err; if (br_vlan_is_master(v)) { vg = br_vlan_group(v->br); @@ -406,12 +406,16 @@ static int __vlan_del(struct net_bridge_vlan *v) if (p) { err = __vlan_vid_del(p->dev, p->br, v); if (err) - goto out; + br_warn(p->br, + "port %u(%s) failed to delete vlan %u from switchdev: %pe\n", + (unsigned int)p->port_no, p->dev->name, + v->vid, ERR_PTR(err)); } else { err = br_switchdev_port_vlan_del(v->br->dev, v->vid); if (err && err != -EOPNOTSUPP) - goto out; - err = 0; + br_warn(v->br, + "failed to delete bridge vlan %u from switchdev: %pe\n", + v->vid, ERR_PTR(err)); } if (br_vlan_should_use(v)) { @@ -431,8 +435,6 @@ static int __vlan_del(struct net_bridge_vlan *v) } br_vlan_put_master(masterv); -out: - return err; } static void __vlan_group_free(struct net_bridge_vlan_group *vg) @@ -449,7 +451,6 @@ static void __vlan_flush(const struct net_bridge *br, { struct net_bridge_vlan *vlan, *tmp; u16 v_start = 0, v_end = 0; - int err; __vlan_delete_pvid(vg, vg->pvid); list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist) { @@ -463,13 +464,7 @@ static void __vlan_flush(const struct net_bridge *br, } v_end = vlan->vid; - err = __vlan_del(vlan); - if (err) { - br_err(br, - "port %u(%s) failed to delete vlan %d: %pe\n", - (unsigned int) p->port_no, p->dev->name, - vlan->vid, ERR_PTR(err)); - } + __vlan_del(vlan); } /* notify about the last/whole vlan range */ @@ -837,8 +832,9 @@ int br_vlan_delete(struct net_bridge *br, u16 vid) br_fdb_delete_by_port(br, NULL, vid, 0); vlan_tunnel_info_del(vg, v); + __vlan_del(v); - return __vlan_del(v); + return 0; } void br_vlan_flush(struct net_bridge *br) @@ -1368,8 +1364,9 @@ int nbp_vlan_delete(struct net_bridge_port *port, u16 vid) return -ENOENT; br_fdb_find_delete_local(port->br, port, port->dev->dev_addr, vid); br_fdb_delete_by_port(port->br, port, vid, 0); + __vlan_del(v); - return __vlan_del(v); + return 0; } void nbp_vlan_flush(struct net_bridge_port *port) From b52d695d062095327b944acf7daabbc816ab319b Mon Sep 17 00:00:00 2001 From: Stanley Jhu Date: Sat, 12 Sep 2026 21:16:25 +0800 Subject: [PATCH 0545/1417] scsi: ufs: core: Keep internal commands dispatchable during error handling Commit 08b12cda6c44 ("scsi: ufs: core: Switch to scsi_get_internal_cmd()") switched UFS internal commands to allocate requests on hba->host->pseudo_sdev->request_queue, which shares the host tagset with regular LUNs. During error recovery, ufshcd_err_handling_prepare() calls blk_mq_quiesce_tagset(&hba->host->tag_set), marking all queues in the tagset as quiesced, including pseudo_sdev->request_queue. When ufshcd_verify_dev_init() subsequently issues internal commands (e.g. NOP OUT UPIU) via blk_execute_rq(), blk_mq_run_hw_queue() skips running the quiesced queue, resulting in an unrecoverable circular wait deadlock. Keep quiescing the tagset and unquiesce the pseudo SCSI device on top of that, so internal commands stay dispatchable while the logical units remain quiesced. Re-quiesce the pseudo device before unquiescing the tagset so that quiesce_depth stays balanced. Clock scaling and ufshcd_pause_command_processing() are unaffected: they keep quiescing the whole tagset, internal commands included. Fixes: 08b12cda6c44 ("scsi: ufs: core: Switch to scsi_get_internal_cmd()") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/6f78c4bd-a70b-402d-abfd-599091b67674@acm.org/ Signed-off-by: Stanley Jhu Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260912131625.2301486-1-stanleyjhu@google.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 2ba244cf40ac7d..54f4e7d7de02e3 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -6817,11 +6817,17 @@ static void ufshcd_err_handling_prepare(struct ufs_hba *hba) } /* Wait for ongoing ufshcd_queuecommand() calls to finish. */ blk_mq_quiesce_tagset(&hba->host->tag_set); + /* + * Internal commands are submitted on the pseudo SCSI device. Let them + * through so that the error handler can recover the link. + */ + blk_mq_unquiesce_queue(hba->host->pseudo_sdev->request_queue); cancel_work_sync(&hba->eeh_work); } static void ufshcd_err_handling_unprepare(struct ufs_hba *hba) { + blk_mq_quiesce_queue_nowait(hba->host->pseudo_sdev->request_queue); blk_mq_unquiesce_tagset(&hba->host->tag_set); ufshcd_release(hba); if (ufshcd_is_clkscaling_supported(hba)) From ceac0de741bfb47ca255eee075257b3bb31f0651 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 11 Sep 2026 16:08:04 +0000 Subject: [PATCH 0546/1417] netlink: do not free nlk->groups while lockless readers can use it netlink_realloc_groups() uses krealloc() under netlink_table_grab(). Whenever NLGRPSZ(groups) lands in a different kmalloc bucket, the old bitmap is freed immediately. Two readers of nlk->groups / nlk->ngroups do not hold the netlink table lock: 1) sk_diag_dump_groups(). Hashed (bound) sockets are dumped from the rhashtable walk in __netlink_diag_dump(), which only holds RCU. Only the mc_list part of the dump takes nl_table_lock. 2) netlink_native_seq_show() (/proc/net/netlink), whose walk has been lockless since commit 21e4902aea80 ("netlink: Lockless lookup with RCU grace period in socket release"). Both can read a freed buffer, and sk_diag_dump_groups() can also read past the end of the old (smaller) buffer if it happens to load the old @groups pointer together with the new @ngroups value, copying the result into a NETLINK_DIAG_GROUPS attribute. This is the same class of bug that commit f773608026ee ("netlink: access nlk groups safely in netlink bind and getname") fixed for bind() and getname(); these two readers were missed. Simply grabbing the table lock in sk_diag_dump_groups() is not an option, because it is also called with nl_table_lock already held from the mc_list section of the dump. Make the lockless readers safe instead: - Allocate a new bitmap and free the old one after an RCU grace period, instead of relying on the implicit kfree() done by krealloc(). - Publish @groups before @ngroups, both with release semantics, and have the lockless readers load @ngroups first. A reader can then never pair the new (bigger) size with the old (smaller) buffer, and a reader picking up the new pointer while still seeing the old size is guaranteed to see the initialized bitmap. netlink_realloc_groups() is called from process context (bind() and setsockopt()), so kfree_rcu_mightsleep() can be used, once the table has been released. Fixes: 21e4902aea80 ("netlink: Lockless lookup with RCU grace period in socket release") Fixes: ad202074320c ("netlink: Use rhashtable walk interface in diag dump") Reported-by: James Burton Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260911160804.917099-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/netlink/af_netlink.c | 42 ++++++++++++++++++++++++++++++++-------- net/netlink/diag.c | 20 +++++++++++++++---- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index e6b1d9758c9c92..9fdf964224ab48 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -922,9 +922,9 @@ netlink_update_subscriptions(struct sock *sk, unsigned int subscriptions) static int netlink_realloc_groups(struct sock *sk) { + unsigned long *new_groups, *old_groups = NULL; struct netlink_sock *nlk = nlk_sk(sk); unsigned int groups; - unsigned long *new_groups; int err = 0; netlink_table_grab(); @@ -938,18 +938,37 @@ static int netlink_realloc_groups(struct sock *sk) if (nlk->ngroups >= groups) goto out_unlock; - new_groups = krealloc(nlk->groups, NLGRPSZ(groups), GFP_ATOMIC); - if (new_groups == NULL) { + /* Can not use krealloc(), because the old buffer might be freed + * immediately, while lockless readers (netlink diag dump and + * /proc/net/netlink) can still be looking at it. + */ + new_groups = kzalloc(NLGRPSZ(groups), GFP_ATOMIC); + if (!new_groups) { err = -ENOMEM; goto out_unlock; } - memset((char *)new_groups + NLGRPSZ(nlk->ngroups), 0, - NLGRPSZ(groups) - NLGRPSZ(nlk->ngroups)); + old_groups = nlk->groups; + if (old_groups) + memcpy(new_groups, old_groups, NLGRPSZ(nlk->ngroups)); + + /* Publish the new bitmap and its content: pairs with the address + * dependency in lockless readers, which can pick up the new pointer + * while still seeing the old (smaller) nlk->ngroups. + */ + smp_store_release(&nlk->groups, new_groups); + + /* Then publish the new size: pairs with smp_load_acquire() from + * lockless readers, so that they can not read NLGRPSZ(new ngroups) + * bytes from the old buffer. + */ + smp_store_release(&nlk->ngroups, groups); - nlk->groups = new_groups; - nlk->ngroups = groups; out_unlock: netlink_table_ungrab(); + + if (old_groups) + kfree_rcu_mightsleep(old_groups); + return err; } @@ -2705,12 +2724,19 @@ static int netlink_native_seq_show(struct seq_file *seq, void *v) } else { struct sock *s = v; struct netlink_sock *nlk = nlk_sk(s); + const unsigned long *groups; + + /* Lockless read : netlink_realloc_groups() can change + * nlk->groups under us. The old buffer is freed after an + * RCU grace period, and this walk is RCU protected. + */ + groups = READ_ONCE(nlk->groups); seq_printf(seq, "%pK %-3d %-10u %08x %-8d %-8d %-5d %-8d %-8u %-8llu\n", s, s->sk_protocol, nlk->portid, - nlk->groups ? (u32)nlk->groups[0] : 0, + groups ? (u32)groups[0] : 0, sk_rmem_alloc_get(s), sk_wmem_alloc_get(s), READ_ONCE(nlk->cb_running), diff --git a/net/netlink/diag.c b/net/netlink/diag.c index 0b3e021bd0ed29..7979bd9b26060e 100644 --- a/net/netlink/diag.c +++ b/net/netlink/diag.c @@ -12,12 +12,24 @@ static int sk_diag_dump_groups(struct sock *sk, struct sk_buff *nlskb) { struct netlink_sock *nlk = nlk_sk(sk); - - if (nlk->groups == NULL) + unsigned long *groups; + unsigned int ngroups; + + /* Hashed sockets are dumped from the rhashtable walk, which only + * holds rcu_read_lock(), while netlink_realloc_groups() can replace + * nlk->groups and nlk->ngroups at any time. + * + * Read nlk->ngroups first : this pairs with smp_store_release() + * from netlink_realloc_groups(), so that we can not use the new + * (bigger) size with the old (smaller) buffer. The old buffer is + * freed after an RCU grace period. + */ + ngroups = smp_load_acquire(&nlk->ngroups); + groups = READ_ONCE(nlk->groups); + if (!groups) return 0; - return nla_put(nlskb, NETLINK_DIAG_GROUPS, NLGRPSZ(nlk->ngroups), - nlk->groups); + return nla_put(nlskb, NETLINK_DIAG_GROUPS, NLGRPSZ(ngroups), groups); } static int sk_diag_put_flags(struct sock *sk, struct sk_buff *skb) From c9ee6511332687ea714ad8ab86a53cb837d86eea Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 14 Sep 2026 16:00:01 +0200 Subject: [PATCH 0547/1417] scsi: ufs: pltfrm: Add quirk for R-Car S4 lacking lanes-per-direction Since commit e72323f3b09f ("scsi: ufs: core: Configure only active lanes during link"), the following error is observed on R-Car S4: ufshcd-renesas e6860000.ufs: Tx lane mismatch [config,reported] [2,1] ufshcd-renesas e6860000.ufs: link startup failed -67 ufshcd-renesas e6860000.ufs: error -ENOLINK: Initialization failed with error -67 ufshcd-renesas e6860000.ufs: probe with driver ufshcd-renesas failed with error -67 R-Car S4 has one UFS lane per direction, as described in section 152.1 of its hardware manual. Without lanes-per-direction, the UFS platform driver defaults to two lanes. Previously, the core used PA_CONNECTEDRXDATALANES and PA_CONNECTEDTXDATALANES to configure the link without checking them against lanes-per-direction, so the missing property did not prevent initialization. While fixing the R-Car S4 DTS is the proper solution, doing only that would still break backwards compatibility with existing DTBs. Hence add a quirk to let lanes-per-direction default to one on R-Car S4. Fixes: e72323f3b09f9c89 ("scsi: ufs: core: Configure only active lanes during link") Reported-by: Koichiro Den Closes: https://lore.kernel.org/20260911073058.253000-1-den@valinux.co.jp Cc: stable@vger.kernel.org # 7.2+ Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/ae0cc2bd764e6dfffce99db3d8b44a55887c508c.1789394185.git.geert+renesas@glider.be Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/host/ufshcd-pltfrm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/ufs/host/ufshcd-pltfrm.c b/drivers/ufs/host/ufshcd-pltfrm.c index 5ac7afe759346d..169dbc1a75fe57 100644 --- a/drivers/ufs/host/ufshcd-pltfrm.c +++ b/drivers/ufs/host/ufshcd-pltfrm.c @@ -206,7 +206,11 @@ static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba) dev_dbg(hba->dev, "%s: failed to read lanes-per-direction, ret=%d\n", __func__, ret); - hba->lanes_per_direction = UFSHCD_DEFAULT_LANES_PER_DIRECTION; + /* Old R-Car S4 DTBs lack "lanes-per-direction = <1>" */ + if (of_device_is_compatible(dev->of_node, "renesas,r8a779f0-ufs")) + hba->lanes_per_direction = 1; + else + hba->lanes_per_direction = UFSHCD_DEFAULT_LANES_PER_DIRECTION; } } From bce07e2f37b5e4a427d36fd6b1c14067b27591db Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Sat, 1 Aug 2026 22:36:35 +0900 Subject: [PATCH 0548/1417] scsi: libiscsi_tcp: Check the data direction of a Data-In PDU The Data-In branch of iscsi_tcp_hdr_dissect() resolves the ITT to a task and copies the PDU's data segment into that command's scatterlist without asking whether the command was reading. iscsi_tcp_r2t_rsp() in the same file does ask, and rejects an R2T for a command that is not DMA_TO_DEVICE. A target that answers a WRITE command's ITT with a Data-In therefore has the initiator write target-supplied bytes into the pages that write was about to send. Those are the caller's own pinned pages for an O_DIRECT write, and page cache pages for a buffered one. Observed against a test target that emits one 512-byte Data-In naming a 128 KB write's ITT, after the R2T for that write. With O_DIRECT the caller's buffer ends up holding 512 bytes of the target's data while pwrite() returns 131072. Buffered is quieter: pwrite() and fsync() both succeed, nothing is logged, and reading those blocks back returns the target's bytes out of the page cache without a command going on the wire. Check the direction before using the scatterlist, the way the R2T path already does. Cc: stable@vger.kernel.org Signed-off-by: Yehyeong Lee Reviewed-by: Mike Christie Link: https://patch.msgid.link/20260801133635.1986706-1-yhlee@isslab.korea.ac.kr Fixes: a081c13e39b5 ("[SCSI] iscsi_tcp: split module into lib and lld") Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/libiscsi_tcp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/scsi/libiscsi_tcp.c b/drivers/scsi/libiscsi_tcp.c index 7223bb18b04809..d35f93451ee9bb 100644 --- a/drivers/scsi/libiscsi_tcp.c +++ b/drivers/scsi/libiscsi_tcp.c @@ -480,6 +480,9 @@ static int iscsi_tcp_data_in(struct iscsi_conn *conn, struct iscsi_task *task) int datasn = be32_to_cpu(rhdr->datasn); unsigned total_in_length = task->sc->sdb.length; + if (task->sc->sc_data_direction != DMA_FROM_DEVICE) + return ISCSI_ERR_PROTO; + /* * lib iscsi will update this in the completion handling if there * is status. From f06a44e235ef188689ba23ffc72e9e89b10951a9 Mon Sep 17 00:00:00 2001 From: "Ewan D. Milne" Date: Tue, 15 Sep 2026 13:38:00 -0400 Subject: [PATCH 0549/1417] scsi: devinfo: Add BLIST_SKIP_IO_HINTS for EMC Symmetrix EMC Symmetrix returns an error on MODE SENSE for page 0Ah subpage 05h because it does not implement the SBC-5 I/O hints. These commands began to be sent as a result of commit 4f53138fffc2 ("scsi: sd: Translate data lifetime information"). Add BLIST_SKIP_IO_HINTS to avoid sending these commands because in large configurations the failed commands are displacing other useful information in internal error logs. Signed-off-by: Ewan D. Milne Link: https://patch.msgid.link/20260915173800.39117-1-emilne@redhat.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/scsi_devinfo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/scsi_devinfo.c b/drivers/scsi/scsi_devinfo.c index 15ffbe93ac7227..88a911b35c949b 100644 --- a/drivers/scsi/scsi_devinfo.c +++ b/drivers/scsi/scsi_devinfo.c @@ -161,7 +161,7 @@ static struct { {"DGC", "DISK", NULL, BLIST_SPARSELUN}, /* EMC CLARiiON, no storage on LUN 0 */ {"EMC", "Invista", "*", BLIST_SPARSELUN | BLIST_LARGELUN}, {"EMC", "SYMMETRIX", NULL, BLIST_SPARSELUN | BLIST_LARGELUN | - BLIST_REPORTLUN2 | BLIST_RETRY_ITF}, + BLIST_REPORTLUN2 | BLIST_RETRY_ITF | BLIST_SKIP_IO_HINTS}, {"EMULEX", "MD21/S2 ESDI", NULL, BLIST_SINGLELUN}, {"easyRAID", "16P", NULL, BLIST_NOREPORTLUN}, {"easyRAID", "X6P", NULL, BLIST_NOREPORTLUN}, From 278210c60c6f6958bd2eeaa2120c862683b83d09 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 15 Sep 2026 22:20:59 +0200 Subject: [PATCH 0550/1417] scsi: leapraid: Avoid -Wformat-security warning When extra warnings are enabled, the alloc_ordered_workqueue() function cannot be called with a variable name for the format string: drivers/scsi/leapraid/leapraid_os.c: In function 'leapraid_probe': drivers/scsi/leapraid/leapraid_os.c:2062:58: error: format not a string literal and no format arguments [-Werror=format-security] 2062 | alloc_ordered_workqueue(adapter->fw_evt_s.fw_evt_name, 0); | ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~ As the string is only assembled before the call and not used anywhere else, just fold the earlier snprintf() into the alloc_ordered_workqueue() call. Fixes: 5597088c9e79 ("scsi: leapraid: Add new SCSI driver") Signed-off-by: Arnd Bergmann Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/20260915202134.3534708-1-arnd@kernel.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/leapraid/leapraid_func.h | 2 -- drivers/scsi/leapraid/leapraid_os.c | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/scsi/leapraid/leapraid_func.h b/drivers/scsi/leapraid/leapraid_func.h index 4c0b9ca728d87f..e8a0815bf95fd4 100644 --- a/drivers/scsi/leapraid/leapraid_func.h +++ b/drivers/scsi/leapraid/leapraid_func.h @@ -554,7 +554,6 @@ struct leapraid_fw_evt_work { /** * struct leapraid_fw_evt_struct - Firmware event handling structure * - * @fw_evt_name: Name of the firmware event. * @fw_evt_thread: Workqueue used for processing firmware events. * @fw_evt_lock: Spinlock protecting access to the firmware event list. * @fw_evt_list: Linked list of pending firmware events. @@ -565,7 +564,6 @@ struct leapraid_fw_evt_work { */ struct leapraid_fw_evt_struct { u32 leapraid_evt_masks[4]; - char fw_evt_name[48]; struct workqueue_struct *fw_evt_thread; spinlock_t fw_evt_lock; /* protects firmware event */ struct list_head fw_evt_list; diff --git a/drivers/scsi/leapraid/leapraid_os.c b/drivers/scsi/leapraid/leapraid_os.c index ee3242779dfdff..507f11862276f3 100644 --- a/drivers/scsi/leapraid/leapraid_os.c +++ b/drivers/scsi/leapraid/leapraid_os.c @@ -2054,12 +2054,10 @@ static int leapraid_probe(struct pci_dev *pdev, const struct pci_device_id *id) shost->transportt = leapraid_transport_template; shost->unique_id = adapter->adapter_attr.id; - snprintf(adapter->fw_evt_s.fw_evt_name, - sizeof(adapter->fw_evt_s.fw_evt_name), - "fw_event_%s%d", LEAPRAID_DRIVER_NAME, - adapter->adapter_attr.id); adapter->fw_evt_s.fw_evt_thread = - alloc_ordered_workqueue(adapter->fw_evt_s.fw_evt_name, 0); + alloc_ordered_workqueue("fw_event_%s%d", 0, + LEAPRAID_DRIVER_NAME, + adapter->adapter_attr.id); if (!adapter->fw_evt_s.fw_evt_thread) { dev_err(&adapter->pdev->dev, "%s: Failed to create fw event workqueue\n", __func__); From 8c0c602202b9a4909b00bc3354e3c0355bc69e65 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 29 Aug 2026 23:32:55 +0200 Subject: [PATCH 0551/1417] selinux: preserve user SID across nested backing files SELinux saves the user file SID in a backing-file security blob so it remains available after mmap() replaces vma->vm_file with a backing file. For nested backing files (overlayfs over overlayfs, or FUSE passthrough backed by overlayfs), user_file may itself be a backing file. Its fsec->sid is the SID of the mounter that opened it, rather than the user that opened the top-level file. mprotect() then checks fd { use } against the mounter SID. This can incorrectly deny access without a domain transition, or check the wrong target SID after one. Copy the saved user SID when user_file is a backing file. Keep using the regular file SID for the first backing layer. With two nested overlayfs mounts and SELinux enforcing, mprotect(PROT_READ) returns EACCES with an fd { use } denial against the mounter SID. With this change, mprotect() succeeds. Tested on arm64 QEMU with a small BusyBox initramfs and a purpose-built SELinux policy. The original test was also repeated with Fedora Cloud Base 44 userspace and gave the same result. Cc: stable@vger.kernel.org Fixes: 82544d36b172 ("selinux: fix overlayfs mmap() and mprotect() access checks") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Amir Goldstein Reviewed-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 9 ++++++++- security/selinux/include/objsec.h | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index e5e17f100aae68..b0bb5f8c90bfcf 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3843,13 +3843,20 @@ static int selinux_file_alloc_security(struct file *file) return 0; } +static inline u32 selinux_file_user_sid(const struct file *file) +{ + if (unlikely(file->f_mode & FMODE_BACKING)) + return selinux_backing_file(file)->uf_sid; + return selinux_file(file)->sid; +} + static int selinux_backing_file_alloc(struct file *backing_file, const struct file *user_file) { struct backing_file_security_struct *bfsec; bfsec = selinux_backing_file(backing_file); - bfsec->uf_sid = selinux_file(user_file)->sid; + bfsec->uf_sid = selinux_file_user_sid(user_file); return 0; } diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 3c0a16ec978b01..853f7266ed189b 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -87,7 +87,7 @@ struct file_security_struct { }; struct backing_file_security_struct { - u32 uf_sid; /* associated user file fsec->sid */ + u32 uf_sid; /* top-level user file fsec->sid */ }; struct superblock_security_struct { From 78fc54b934bfb2c18aad8154c7302067146946f9 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 29 Aug 2026 23:32:56 +0200 Subject: [PATCH 0552/1417] selinux: recheck intermediate backing files on mprotect() mprotect() can be used to bypass the SELinux checks that mmap() performs against the intermediate layers of a stacked filesystem. mmap() checks every backing layer as the request descends through the stack. mprotect() only has the lowest backing file in vma->vm_file, so it rechecks the top-level user and the lowest mounter, but skips the mounters of every layer in between. With two nested overlayfs mounts and a policy denying mounter_t -> middle_file_t:file { execute }, a direct mmap(PROT_EXEC) is denied: avc: denied { execute } for pid=71 comm="nested_exec" path="/payload" dev="overlay" ino=9 scontext=user_u:base_r:mounter_t tcontext=user_u:object_r:middle_file_t tclass=file permissive=0 while mmap(PROT_NONE) followed by mprotect(PROT_EXEC) succeeds. Preserve each intermediate path, mounter SID and file-description SID in the backing-file security blob, copying the saved entries when another backing layer is opened. Allocate the array only for nested backing files, and release it and the path references in the backing_file_free hook. During mprotect(), recheck fd { use } and the requested inode permissions for every saved mounter, and include the intermediate layers in the execmod checks. Policy for nested stacking may then need to grant intermediate mounters what a direct mmap() already requires, and execmod on intermediate labels for binaries using text relocations. Tested on arm64 QEMU with a small BusyBox initramfs and a purpose-built SELinux policy, on a mainline tree containing commit f2381b546e7e ("fs: fix user path of nested backing files"). Cc: stable@vger.kernel.org Fixes: 82544d36b172 ("selinux: fix overlayfs mmap() and mprotect() access checks") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Stephen Smalley [PM: subject tweak] Signed-off-by: Paul Moore --- security/selinux/hooks.c | 141 ++++++++++++++++++++++++++---- security/selinux/include/objsec.h | 8 ++ 2 files changed, 133 insertions(+), 16 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index b0bb5f8c90bfcf..3f4a6ddee32251 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1674,26 +1674,32 @@ static int cred_has_capability(const struct cred *cred, return rc; } -/* Check whether a task has a particular permission to an inode. - The 'adp' parameter is optional and allows other audit - data to be passed (e.g. the dentry). */ -static int inode_has_perm(const struct cred *cred, - struct inode *inode, - u32 perms, - struct common_audit_data *adp) +/* + * Check whether a SID has a particular permission to an inode. The 'adp' + * parameter is optional and allows other audit data to be passed (e.g. the + * dentry). + */ +static int inode_sid_has_perm(u32 sid, struct inode *inode, u32 perms, + struct common_audit_data *adp) { struct inode_security_struct *isec; - u32 sid; if (unlikely(IS_PRIVATE(inode))) return 0; - sid = cred_sid(cred); isec = selinux_inode(inode); return avc_has_perm(sid, isec->sid, isec->sclass, perms, adp); } +static int inode_has_perm(const struct cred *cred, + struct inode *inode, + u32 perms, + struct common_audit_data *adp) +{ + return inode_sid_has_perm(cred_sid(cred), inode, perms, adp); +} + /* Same as inode_has_perm, but pass explicit audit data containing the dentry to help the auditing code to more easily generate the pathname if needed. */ @@ -3854,13 +3860,63 @@ static int selinux_backing_file_alloc(struct file *backing_file, const struct file *user_file) { struct backing_file_security_struct *bfsec; + const struct backing_file_security_struct *ubfsec; + struct backing_file_security_layer *layer; + u32 i; bfsec = selinux_backing_file(backing_file); bfsec->uf_sid = selinux_file_user_sid(user_file); + if (!(user_file->f_mode & FMODE_BACKING)) + return 0; + + ubfsec = selinux_backing_file(user_file); + /* a wrapped count would make kmalloc_array() return ZERO_SIZE_PTR */ + if (unlikely(ubfsec->layer_count == U32_MAX)) + return -EOVERFLOW; + + /* + * The final VMA only retains the lowest backing file, so record the + * whole chain here rather than in the mmap hook, where concurrent + * mappings would have to be serialized. Size it dynamically: erofs + * inode sharing adds a backing file without bumping s_stack_depth. + */ + bfsec->layers = kmalloc_array(ubfsec->layer_count + 1, + sizeof(*bfsec->layers), GFP_KERNEL); + if (!bfsec->layers) + return -ENOMEM; + + for (i = 0; i < ubfsec->layer_count; i++) { + layer = &bfsec->layers[i]; + *layer = ubfsec->layers[i]; + path_get(&layer->path); + } + + /* f_path, not file_user_path(): this layer, not the top-level file */ + layer = &bfsec->layers[i]; + layer->path = user_file->f_path; + layer->mounter_sid = cred_sid(user_file->f_cred); + layer->fd_sid = selinux_file(user_file)->sid; + path_get(&layer->path); + bfsec->layer_count = ubfsec->layer_count + 1; return 0; } +static void selinux_backing_file_free(struct file *backing_file) +{ + struct backing_file_security_struct *bfsec; + + /* security_backing_file_free() may be called twice after an error */ + if (!backing_file_security(backing_file)) + return; + + bfsec = selinux_backing_file(backing_file); + while (bfsec->layer_count) + path_put(&bfsec->layers[--bfsec->layer_count].path); + kfree(bfsec->layers); + bfsec->layers = NULL; +} + /* * Check whether a task has the ioctl permission and cmd * operation to an inode. @@ -3978,6 +4034,53 @@ static int selinux_file_ioctl_compat(struct file *file, unsigned int cmd, static int default_noexec __ro_after_init; +static u32 file_map_prot_to_av(unsigned long prot, bool shared) +{ + u32 av = FILE__READ; + + if (shared && (prot & PROT_WRITE)) + av |= FILE__WRITE; + if (prot & PROT_EXEC) + av |= FILE__EXECUTE; + + return av; +} + +static int backing_mounters_has_perm(const struct file *file, u32 av) +{ + const struct backing_file_security_struct *bfsec; + const struct backing_file_security_layer *layer; + struct common_audit_data ad; + struct inode *inode; + u32 i; + int rc; + + if (WARN_ON_ONCE(!(file->f_mode & FMODE_BACKING))) + return -EIO; + + bfsec = selinux_backing_file(file); + for (i = 0; i < bfsec->layer_count; i++) { + layer = &bfsec->layers[i]; + inode = d_inode(layer->path.dentry); + + ad.type = LSM_AUDIT_DATA_PATH; + ad.u.path = layer->path; + + if (layer->mounter_sid != layer->fd_sid) { + rc = avc_has_perm(layer->mounter_sid, layer->fd_sid, + SECCLASS_FD, FD__USE, &ad); + if (rc) + return rc; + } + + rc = inode_sid_has_perm(layer->mounter_sid, inode, av, &ad); + if (rc) + return rc; + } + + return 0; +} + static int __file_map_prot_check(const struct file *file, unsigned long prot, bool shared, bool mounter_check, bool bf_user_file) @@ -4011,14 +4114,10 @@ static int __file_map_prot_check(const struct file *file, unsigned long prot, if (file) { const struct cred *cred = mounter_check ? file->f_cred : current_cred(); - /* "read" always possible, "write" only if shared */ - u32 av = FILE__READ; - if (shared && prot_write) - av |= FILE__WRITE; - if (prot_exec) - av |= FILE__EXECUTE; - return __file_has_perm(cred, file, av, bf_user_file); + return __file_has_perm(cred, file, + file_map_prot_to_av(prot, shared), + bf_user_file); } return 0; @@ -4113,6 +4212,7 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, int rc; const struct cred *cred = current_cred(); u32 sid = cred_sid(cred); + u32 av; const struct file *file = vma->vm_file; bool backing_file; bool shared = vma->vm_flags & VM_SHARED; @@ -4156,6 +4256,10 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, if (rc) return rc; if (backing_file) { + rc = backing_mounters_has_perm(file, + FILE__EXECMOD); + if (rc) + return rc; rc = file_has_perm(file->f_cred, file, FILE__EXECMOD); if (rc) @@ -4168,6 +4272,10 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, if (rc) return rc; if (backing_file) { + av = file_map_prot_to_av(prot, shared); + rc = backing_mounters_has_perm(file, av); + if (rc) + return rc; rc = file_map_prot_check(file, prot, shared, true); if (rc) return rc; @@ -7626,6 +7734,7 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = { LSM_HOOK_INIT(file_permission, selinux_file_permission), LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security), LSM_HOOK_INIT(backing_file_alloc, selinux_backing_file_alloc), + LSM_HOOK_INIT(backing_file_free, selinux_backing_file_free), LSM_HOOK_INIT(file_ioctl, selinux_file_ioctl), LSM_HOOK_INIT(file_ioctl_compat, selinux_file_ioctl_compat), LSM_HOOK_INIT(mmap_file, selinux_mmap_file), diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 853f7266ed189b..2f21568251ffeb 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -86,8 +86,16 @@ struct file_security_struct { u32 pseqno; /* Policy seqno at the time of file open */ }; +struct backing_file_security_layer { + struct path path; /* this layer's real path */ + u32 mounter_sid; /* SID of the mounter that opened it */ + u32 fd_sid; /* SID of its open file description */ +}; + struct backing_file_security_struct { u32 uf_sid; /* top-level user file fsec->sid */ + u32 layer_count; /* number of intermediate backing files */ + struct backing_file_security_layer *layers; }; struct superblock_security_struct { From e0c3e9d76adbe522dd420a766ce42d03ce887c29 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 17:15:44 +0800 Subject: [PATCH 0553/1417] i2c: imx: disable autosuspend on remove i2c_imx_probe() enables runtime PM autosuspend with pm_runtime_use_autosuspend(). The probe error path correctly undoes this setting with pm_runtime_dont_use_autosuspend(), but the normal remove path only disables runtime PM. The runtime PM API requires pm_runtime_use_autosuspend() to be undone with pm_runtime_dont_use_autosuspend() at driver exit unless runtime PM was enabled with devm_pm_runtime_enable(). Leaving the autosuspend flag set therefore leaves the runtime PM state incompletely cleaned up after the driver is unbound. Add the missing pm_runtime_dont_use_autosuspend() call to the remove path. This issue was found by manual code inspection. Fixes: 588eb93ea49f ("i2c: imx: add runtime pm support to improve the performance") Signed-off-by: Guangshuo Li Cc: # v4.5+ Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260914091544.1667137-1-lgs201920130244@gmail.com --- drivers/i2c/busses/i2c-imx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index 19ec056b00afeb..4dc3df6a41a4f4 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -1920,6 +1920,7 @@ static void i2c_imx_remove(struct platform_device *pdev) pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); } static int i2c_imx_runtime_suspend(struct device *dev) From 0d1cb83337f13af082afb68b28d3fdfe29cde7fb Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Tue, 15 Sep 2026 06:59:33 +0000 Subject: [PATCH 0554/1417] ata: libahci_platform: Fix device reference leak in ahci_platform_get_resources() of_find_device_by_node() takes a reference on the port platform device, which is only used to look up its port regulator and is never released, neither on success nor on the error paths. Drop the reference with put_device() once the regulator has been obtained, which covers both the success and error paths. Fixes: c7d7ddee7e24 ("ata: libahci: Allow using multiple regulators") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://lore.kernel.org/r/20260915065933.1733061-1-vulab@iscas.ac.cn Reviewed-by: Damien Le Moal Signed-off-by: Niklas Cassel --- drivers/ata/libahci_platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ata/libahci_platform.c b/drivers/ata/libahci_platform.c index 6e072d681341a5..f6524135ea1171 100644 --- a/drivers/ata/libahci_platform.c +++ b/drivers/ata/libahci_platform.c @@ -620,10 +620,10 @@ struct ahci_host_priv *ahci_platform_get_resources(struct platform_device *pdev, of_platform_device_create(child, NULL, NULL); port_dev = of_find_device_by_node(child); - if (port_dev) { rc = ahci_platform_get_regulator(hpriv, port, &port_dev->dev); + put_device(&port_dev->dev); if (rc == -EPROBE_DEFER) goto err_out; } From ad34235808b63a70ca4989b7a2852923193d06ef Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Mon, 7 Sep 2026 15:11:02 +0800 Subject: [PATCH 0555/1417] i2c: atr: fix dangling adapter pointer on add failure i2c_atr_add_adapter() stores atr->adapter[chan_id] before i2c_add_adapter() so that the I2C bus notifier can match child clients during registration. On failure the channel is freed but the slot was left pointing at freed memory, which can lead to use-after-free in i2c_atr_del_adapter() / cleanup and also block reuse with -EEXIST. Clear the slot on the i2c_add_adapter() error path before freeing chan. Fixes: a076a860acae ("media: i2c: add I2C Address Translator (ATR) support") Signed-off-by: Linkai Gong Cc: # v6.6+ Reviewed-by: Andy Shevchenko Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260907071102.1080840-1-gonglinkai@kylinos.cn --- drivers/i2c/i2c-atr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i2c/i2c-atr.c b/drivers/i2c/i2c-atr.c index e6d2af659d8101..ca29633dcd623a 100644 --- a/drivers/i2c/i2c-atr.c +++ b/drivers/i2c/i2c-atr.c @@ -855,6 +855,7 @@ int i2c_atr_add_adapter(struct i2c_atr *atr, struct i2c_atr_adap_desc *desc) ret = i2c_add_adapter(&chan->adap); if (ret) { + atr->adapter[chan_id] = NULL; dev_err(dev, "failed to add atr-adapter %u (error=%d)\n", chan_id, ret); goto err_free_alias_pool; From 2ab510e63197360945f915dd5631a77c63ac6b27 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Tue, 15 Sep 2026 16:05:57 +0100 Subject: [PATCH 0556/1417] drm/sched: Fix virtual runtime race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevent pushing a new job to an entity seeing it being the first in the queue, and hence entering the drm_sched_rq_add_entity() path, if the pop side in drm_sched_entity_pop_job() has just de-queued the job but not yet updated the saved virtual time. Restoring the unsaved virtual time, which is at this point not a delta but still an absolute value, pushes the said entity to the rear of the run queue for a potentially very long time. We close this race by pulling the locked sections out to encompass both the queue push/pop and corresponding rbtree management. This is aligned with the future direction to replace the current lockless job queue with one of the fully locked standard list primitives. Signed-off-by: Tvrtko Ursulin Fixes: 2fa4d8e2c109 ("drm/sched: Add fair scheduling policy") Suggested-by: Luke.Wildhardt@proton.me # via Claude Opus Tested-by: Luke.Wildhardt@proton.me Cc: Christian König Cc: Danilo Krummrich Cc: Philipp Stanner Cc: Pierre-Eric Pelloux-Prayer Cc: Matthew Brost Cc: Vitaly Prosyak Cc: stable@vger.kernel.org # v7.2+ [phasta: commit title] Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20260915150557.62847-1-tvrtko.ursulin@igalia.com --- drivers/gpu/drm/scheduler/sched_entity.c | 8 +++++++- drivers/gpu/drm/scheduler/sched_rq.c | 20 +++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/scheduler/sched_entity.c b/drivers/gpu/drm/scheduler/sched_entity.c index a4a7efdbf22968..673ca9cbf36283 100644 --- a/drivers/gpu/drm/scheduler/sched_entity.c +++ b/drivers/gpu/drm/scheduler/sched_entity.c @@ -559,9 +559,10 @@ struct drm_sched_job *drm_sched_entity_pop_job(struct drm_sched_entity *entity) */ smp_wmb(); + spin_lock(&entity->lock); spsc_queue_pop(&entity->job_queue); - drm_sched_rq_pop_entity(entity); + spin_unlock(&entity->lock); /* Jobs and entities might have different lifecycles. Since we're * removing the job from the entities queue, set the jobs entity pointer @@ -647,6 +648,9 @@ void drm_sched_entity_push_job(struct drm_sched_job *sched_job) * Make sure to set the submit_ts first, to avoid a race. */ sched_job->submit_ts = submit_ts = ktime_get(); + + spin_lock(&entity->lock); + first = spsc_queue_push(&entity->job_queue, &sched_job->queue_node); /* first job wakes up scheduler */ @@ -657,5 +661,7 @@ void drm_sched_entity_push_job(struct drm_sched_job *sched_job) if (sched) drm_sched_wakeup(sched); } + + spin_unlock(&entity->lock); } EXPORT_SYMBOL(drm_sched_entity_push_job); diff --git a/drivers/gpu/drm/scheduler/sched_rq.c b/drivers/gpu/drm/scheduler/sched_rq.c index 0464d324d98d5d..23f46ec610e783 100644 --- a/drivers/gpu/drm/scheduler/sched_rq.c +++ b/drivers/gpu/drm/scheduler/sched_rq.c @@ -257,19 +257,17 @@ static ktime_t drm_sched_entity_get_job_ts(struct drm_sched_entity *entity) struct drm_gpu_scheduler * drm_sched_rq_add_entity(struct drm_sched_entity *entity, ktime_t ts) { + struct drm_sched_rq *rq = entity->rq; struct drm_gpu_scheduler *sched; - struct drm_sched_rq *rq; /* Add the entity to the run queue */ - spin_lock(&entity->lock); - if (entity->stopped) { - spin_unlock(&entity->lock); + lockdep_assert_held(&entity->lock); + if (entity->stopped) { DRM_ERROR("Trying to push to a killed entity\n"); return NULL; } - rq = entity->rq; spin_lock(&rq->lock); sched = rq->sched; @@ -289,7 +287,6 @@ drm_sched_rq_add_entity(struct drm_sched_entity *entity, ktime_t ts) drm_sched_rq_update_fifo_locked(entity, rq, ts); spin_unlock(&rq->lock); - spin_unlock(&entity->lock); return sched; } @@ -343,16 +340,17 @@ drm_sched_rq_next_rr_ts(struct drm_sched_rq *rq, */ void drm_sched_rq_pop_entity(struct drm_sched_entity *entity) { + struct drm_sched_rq *rq = entity->rq; struct drm_sched_job *next_job; - struct drm_sched_rq *rq; + + lockdep_assert_held(&entity->lock); + + spin_lock(&rq->lock); /* * Update the entity's location in the min heap according to * the timestamp of the next job, if any. */ - spin_lock(&entity->lock); - rq = entity->rq; - spin_lock(&rq->lock); next_job = drm_sched_entity_queue_peek(entity); if (next_job) { ktime_t ts; @@ -375,8 +373,8 @@ void drm_sched_rq_pop_entity(struct drm_sched_entity *entity) drm_sched_entity_save_vruntime(entity, min_vruntime); } } + spin_unlock(&rq->lock); - spin_unlock(&entity->lock); } /** From dbd9d1cbf9700528c8595ab1fa7ef832e79821fe Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Tue, 15 Sep 2026 23:31:10 +0700 Subject: [PATCH 0557/1417] ALSA: usb-audio: fix list_add double-add in push_back_to_ready_list stop_urbs() clears ep->ready_playback_urbs with a bare INIT_LIST_HEAD() instead of unlinking each queued snd_urb_ctx. If a URB survives past wait_clear_urbs()'s forced STOPPING->STOPPED timeout, its ctx is left looking "linked" (stale next/prev) even though the list head has forgotten it. When the endpoint later restarts and re-queues that same ctx onto the (now real) ready list, and the old URB's completion handler then calls push_back_to_ready_list() for it a second time, the ctx is still the list's own tail and list_add's double-add check trips: kernel BUG at lib/list_debug.c:35 (list_add double add) Guard push_back_to_ready_list() with a list_empty() check so a still-linked ctx isn't re-added, and make stop_urbs() actually unlink each ctx via list_del_init() instead of only resetting the head, so a dropped ctx doesn't keep looking linked to that guard. Reported-by: syzbot+9fe3b8d9f5c64ff410a7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9fe3b8d9f5c64ff410a7 Signed-off-by: Nguyen Ngoc Thang Link: https://patch.msgid.link/20260915163110.58124-1-ngocthang2710.1999@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/endpoint.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index b72ce1e9bfb1c3..879d0451536e46 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -449,7 +449,9 @@ static void push_back_to_ready_list(struct snd_usb_endpoint *ep, struct snd_urb_ctx *ctx) { guard(spinlock_irqsave)(&ep->lock); - list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); + /* ctx may still be linked: a stale completion racing a stop/restart. */ + if (list_empty(&ctx->ready_list)) + list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); } /* @@ -1037,6 +1039,7 @@ void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep) */ static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) { + struct snd_urb_ctx *ctx, *n; unsigned int i; if (!force && atomic_read(&ep->running)) @@ -1046,7 +1049,9 @@ static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) return 0; scoped_guard(spinlock_irqsave, &ep->lock) { - INIT_LIST_HEAD(&ep->ready_playback_urbs); + /* Unlink each ctx; INIT_LIST_HEAD() alone would leave them looking linked. */ + list_for_each_entry_safe(ctx, n, &ep->ready_playback_urbs, ready_list) + list_del_init(&ctx->ready_list); ep->next_packet_head = 0; ep->next_packet_queued = 0; } From 51938dfa8a51a4f85328413fca9b6e21f9d2d088 Mon Sep 17 00:00:00 2001 From: Amit Machhiwal Date: Tue, 15 Sep 2026 22:04:15 +0530 Subject: [PATCH 0558/1417] KVM: PPC: Book3S HV: fix use-after-free in kvmhv_emulate_tlbie_all_lpid() kvmhv_emulate_tlbie_all_lpid() iterates the nested-guest IDR and drops mmu_lock before calling kvmhv_emulate_tlbie_lpid(), but does not hold a reference on the kvm_nested_guest pointer obtained from the IDR. A concurrent vCPU issuing a single-LPID tlbie (is=2, ric=2) can race through kvmhv_flush_nested() -> kvmhv_remove_nested() -> idr_remove / --refcnt -> kvmhv_release_nested() -> kfree(gp) in that window, leaving the iterating vCPU with a dangling pointer. The subsequent mutex_lock(&gp->tlb_lock) and accesses to gp->shadow_pgtable, gp->shadow_lpid and gp->l1_host all touch freed memory. The free path is fully L1-controlled. Fix this by incrementing gp->refcnt inside the loop before dropping mmu_lock, mirroring what kvmhv_get_nested() does, and releasing the reference with kvmhv_put_nested() after the per-guest work completes. This is the same get/put discipline already used at every other call site that drops mmu_lock while holding a nested-guest pointer. Fixes: e3b6b4661527 ("KVM: PPC: Book3S HV: Implement H_TLB_INVALIDATE hcall") Reviewed-by: Ritesh Harjani (IBM) Tested-by: R Nageswara Sastry Signed-off-by: Amit Machhiwal Signed-off-by: Gautam Menghani Signed-off-by: Madhavan Srinivasan --- arch/powerpc/kvm/book3s_hv_nested.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/powerpc/kvm/book3s_hv_nested.c b/arch/powerpc/kvm/book3s_hv_nested.c index 22e6166622556f..a6ff42d7666c10 100644 --- a/arch/powerpc/kvm/book3s_hv_nested.c +++ b/arch/powerpc/kvm/book3s_hv_nested.c @@ -1204,8 +1204,10 @@ static void kvmhv_emulate_tlbie_all_lpid(struct kvm_vcpu *vcpu, int ric) spin_lock(&kvm->mmu_lock); idr_for_each_entry(&kvm->arch.kvm_nested_guest_idr, gp, lpid) { + ++gp->refcnt; spin_unlock(&kvm->mmu_lock); kvmhv_emulate_tlbie_lpid(vcpu, gp, ric); + kvmhv_put_nested(gp); spin_lock(&kvm->mmu_lock); } spin_unlock(&kvm->mmu_lock); From 0a416ee20bcccddf91ca5b63696a23b9d11d73aa Mon Sep 17 00:00:00 2001 From: Amit Machhiwal Date: Tue, 15 Sep 2026 22:04:16 +0530 Subject: [PATCH 0559/1417] KVM: PPC: Book3S HV: fix secure device page leak on uv_page_in() failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In kvmppc_svm_page_in(), if uv_page_in() fails after kvmppc_uvmem_get_page() has succeeded, the secure device page is never released. kvmppc_uvmem_get_page() sets a bit in kvmppc_uvmem_bitmap, allocates a kvmppc_uvmem_page_pvt struct, marks the GFN as KVMPPC_GFN_UVMEM_PFN, and calls zone_device_page_init() which sets refcount=1 and locks the page. The subsequent goto out_finalize skips the *mig.dst assignment, so migrate_vma_finalize() is a no-op for the page, and none of those resources are ever reclaimed. Each occurrence permanently consumes one entry from the firmware-bounded secure memory pool (kvmppc_uvmem_bitmap), leaks pvt, and leaves the GFN marked as secure — making it unusable for the lifetime of the VM. The twin __kvmppc_svm_page_out() already handles the analogous uv_page_out() failure correctly with unlock_page(dpage); __free_page(dpage). Apply the same pattern here: unlock_page() followed by put_page(), which chains through free_zone_device_folio() into kvmppc_uvmem_folio_free() to clear the bitmap bit, free pvt, and reset the GFN state. Reachable whenever uv_page_in() returns an error (e.g. UV pool exhaustion) on any POWER9/10 + Ultravisor/PEF system. Fixes: ca9f4942670c ("KVM: PPC: Book3S HV: Support for running secure guests") Reviewed-by: Ritesh Harjani (IBM) Tested-by: R Nageswara Sastry Signed-off-by: Amit Machhiwal Signed-off-by: Gautam Menghani Signed-off-by: Madhavan Srinivasan --- arch/powerpc/kvm/book3s_hv_uvmem.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/kvm/book3s_hv_uvmem.c b/arch/powerpc/kvm/book3s_hv_uvmem.c index 5fbb95d90e9965..463aef870c4ebd 100644 --- a/arch/powerpc/kvm/book3s_hv_uvmem.c +++ b/arch/powerpc/kvm/book3s_hv_uvmem.c @@ -779,8 +779,11 @@ static int kvmppc_svm_page_in(struct vm_area_struct *vma, if (spage) { ret = uv_page_in(kvm->arch.lpid, pfn << page_shift, gpa, 0, page_shift); - if (ret) + if (ret) { + unlock_page(dpage); + put_page(dpage); goto out_finalize; + } } } From 0b271f7d7f5ed45bc498a03ce0aa9cfd8402fc71 Mon Sep 17 00:00:00 2001 From: Shivaprasad G Bhat Date: Tue, 15 Sep 2026 22:04:17 +0530 Subject: [PATCH 0560/1417] powerpc/iommu: Fix the overflow validation in iommu_tce_check_ioba The commit b1af23d836f8 ("KVM: PPC: iommu: Unify TCE checking") unified IOBA parameter checking across KVM and VFIO into iommu_tce_check_ioba(). While doing so, the passed in argument npages is ignored and constant value '1' is used leaving out a possible overflow as the callers can legitimately be using npages > 1 for H_STUFF_TCE or H_PUT_TCE_INDIRECT cases. Fix this by accounting for 'npages', checking for arithmetic overflow, and verifying that the entire requested range (ioba - offset + npages) does not exceed the table capacity 'size'. Fixes: b1af23d836f8 ("KVM: PPC: iommu: Unify TCE checking") Reviewed-by: Ritesh Harjani (IBM) Tested-by: R Nageswara Sastry Signed-off-by: Shivaprasad G Bhat Signed-off-by: Gautam Menghani Signed-off-by: Madhavan Srinivasan --- arch/powerpc/kernel/iommu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/kernel/iommu.c b/arch/powerpc/kernel/iommu.c index ee1b5cb557c9a6..1ae8384637b5fb 100644 --- a/arch/powerpc/kernel/iommu.c +++ b/arch/powerpc/kernel/iommu.c @@ -1076,7 +1076,7 @@ int iommu_tce_check_ioba(unsigned long page_shift, if (ioba < offset) return -EINVAL; - if ((ioba + 1) > (offset + size)) + if ((ioba + npages < ioba) || (ioba - offset + npages > size)) return -EINVAL; return 0; From 1f7745fb3580152ca902ef181b605f33cabfb1d0 Mon Sep 17 00:00:00 2001 From: Ran Hongyun Date: Mon, 13 Jul 2026 19:55:25 +0800 Subject: [PATCH 0561/1417] squashfs: Add dictionary size range check to prevent shift-out-of-bounds When an abnormal SquashFS image (COMP_OPTS flag is 1 but dictionary size is 0) is mounted, and performs shift operations using dictionarysize, the shift exponent is -1, causing a shift-out-of-bounds. Detail as below: squashfs_comp_opts(msblk, buffer, length) squashfs_xz_comp_opts() if (comp_opts) n = ffs(opts->dict_size) - 1;<----opts->dict_size=0, n=-1 if (opts->dict_size != (1 << n) && opts->dict_size != (1 << n) + (1 << (n + 1))) <----shift-out-of-bounds Fix it by adding a dictionary size range check before the shift operation. Fixes: ff750311d30a ("Squashfs: add compression options support to xz decompressor") Signed-off-by: Ran Hongyun Link: https://patch.msgid.link/20260713115525.2661734-1-ranhongyun1@huawei.com Reviewed-by: Phillip Lougher Reviewed-by: Zhihao Cheng Signed-off-by: Christian Brauner (Amutable) --- fs/squashfs/xz_wrapper.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/squashfs/xz_wrapper.c b/fs/squashfs/xz_wrapper.c index 0a4ff3ec9c8cd3..6610af24144934 100644 --- a/fs/squashfs/xz_wrapper.c +++ b/fs/squashfs/xz_wrapper.c @@ -57,10 +57,10 @@ static void *squashfs_xz_comp_opts(struct squashfs_sb_info *msblk, opts->dict_size = le32_to_cpu(comp_opts->dictionary_size); - /* the dictionary size should be 2^n or 2^n+2^(n+1) */ + /* the dictionary size should be positive and 2^n or 2^n+2^(n+1) */ n = ffs(opts->dict_size) - 1; - if (opts->dict_size != (1 << n) && opts->dict_size != (1 << n) + - (1 << (n + 1))) { + if (opts->dict_size <= 0 || (opts->dict_size != (1 << n) && + opts->dict_size != (1 << n) + (1 << (n + 1)))) { err = -EIO; goto out; } From 5a83606d9f31a38273ee803f3aae2e39247032c5 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Thu, 10 Sep 2026 17:49:04 +0800 Subject: [PATCH 0562/1417] drm/verisilicon: set blend mode for the cursor plane Blend mode properties are now required to expose pixel formats w/ alpha. Experiments show that the fixed blending mode for the cursor seems to be COVERAGE: - With a cursor plane filled with R=G=0, B=0xff, A=0x40, the cursor is visible on a pure-white background, which means the background is multiplied. - With a cursor plane filled with R=G=B=0xff, A=0x40, the cursor isn't pure white and non-white patterns can be see through, which means the cursor is multiplied. Add a fixed COVERAGE blend mode property for the cursor plane. Signed-off-by: Icenowy Zheng Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260910094904.3502741-1-zhengxingda@iscas.ac.cn --- drivers/gpu/drm/verisilicon/vs_cursor_plane.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/verisilicon/vs_cursor_plane.c b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c index fa4f601dd0c87c..da456dced88aad 100644 --- a/drivers/gpu/drm/verisilicon/vs_cursor_plane.c +++ b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -267,6 +268,7 @@ struct drm_plane *vs_cursor_plane_init(struct drm_device *drm_dev, return plane; drm_plane_helper_add(plane, &vs_cursor_plane_helper_funcs); + drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_COVERAGE)); return plane; } From e5d43d7e924d7e1d0c815a5c7407f2a40a6dd2f2 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Thu, 10 Sep 2026 17:49:59 +0800 Subject: [PATCH 0563/1417] drm/verisilicon: add primary modifier for format tables Currently the format tables are only used for the primary plane. Add primary modifiers to names related to the tables. Signed-off-by: Icenowy Zheng Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260910095000.3505878-1-zhengxingda@iscas.ac.cn --- drivers/gpu/drm/verisilicon/vs_hwdb.c | 12 ++++++------ drivers/gpu/drm/verisilicon/vs_hwdb.h | 4 ++-- drivers/gpu/drm/verisilicon/vs_primary_plane.c | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c index 2a0f7c59afa3a5..56aa450443068a 100644 --- a/drivers/gpu/drm/verisilicon/vs_hwdb.c +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c @@ -10,7 +10,7 @@ #include "vs_dc_top_regs.h" #include "vs_hwdb.h" -static const u32 vs_formats_array_no_yuv444[] = { +static const u32 vs_primary_formats_array_no_yuv444[] = { DRM_FORMAT_XRGB4444, DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, @@ -44,7 +44,7 @@ static const u32 vs_formats_array_no_yuv444[] = { /* TODO: non-RGB formats */ }; -static const u32 vs_formats_array_with_yuv444[] = { +static const u32 vs_primary_formats_array_with_yuv444[] = { DRM_FORMAT_XRGB4444, DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, @@ -79,13 +79,13 @@ static const u32 vs_formats_array_with_yuv444[] = { }; static const struct vs_formats vs_formats_no_yuv444 = { - .array = vs_formats_array_no_yuv444, - .num = ARRAY_SIZE(vs_formats_array_no_yuv444) + .primary_array = vs_primary_formats_array_no_yuv444, + .primary_num = ARRAY_SIZE(vs_primary_formats_array_no_yuv444) }; static const struct vs_formats vs_formats_with_yuv444 = { - .array = vs_formats_array_with_yuv444, - .num = ARRAY_SIZE(vs_formats_array_with_yuv444) + .primary_array = vs_primary_formats_array_with_yuv444, + .primary_num = ARRAY_SIZE(vs_primary_formats_array_with_yuv444) }; static struct vs_chip_identity vs_chip_identities[] = { diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.h b/drivers/gpu/drm/verisilicon/vs_hwdb.h index 2065ecb7304379..616076d931a571 100644 --- a/drivers/gpu/drm/verisilicon/vs_hwdb.h +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.h @@ -10,8 +10,8 @@ #include struct vs_formats { - const u32 *array; - unsigned int num; + const u32 *primary_array; + unsigned int primary_num; }; struct vs_chip_identity { diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane.c b/drivers/gpu/drm/verisilicon/vs_primary_plane.c index 1f2be41ae496c9..8e944916930191 100644 --- a/drivers/gpu/drm/verisilicon/vs_primary_plane.c +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c @@ -168,8 +168,8 @@ struct drm_plane *vs_primary_plane_init(struct drm_device *drm_dev, struct vs_dc plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0, &vs_primary_plane_funcs, - dc->identity.formats->array, - dc->identity.formats->num, + dc->identity.formats->primary_array, + dc->identity.formats->primary_num, NULL, DRM_PLANE_TYPE_PRIMARY, NULL); From 6c62dfd2820f0c32b0083668edbe0baca6e10b92 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Thu, 10 Sep 2026 17:50:00 +0800 Subject: [PATCH 0564/1417] drm/verisilicon: remove ARGB formats from primary plane As the blending of the primary plane is currently explicitly disabled (and it's not possible on DC8000), remove the ARGB formats from the primary plane format tables. Signed-off-by: Icenowy Zheng Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260910095000.3505878-2-zhengxingda@iscas.ac.cn --- drivers/gpu/drm/verisilicon/vs_hwdb.c | 32 --------------------------- 1 file changed, 32 deletions(-) diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c index 56aa450443068a..ebf6f843bc8856 100644 --- a/drivers/gpu/drm/verisilicon/vs_hwdb.c +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c @@ -15,32 +15,16 @@ static const u32 vs_primary_formats_array_no_yuv444[] = { DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, DRM_FORMAT_BGRX4444, - DRM_FORMAT_ARGB4444, - DRM_FORMAT_ABGR4444, - DRM_FORMAT_RGBA4444, - DRM_FORMAT_BGRA4444, DRM_FORMAT_XRGB1555, DRM_FORMAT_XBGR1555, DRM_FORMAT_RGBX5551, DRM_FORMAT_BGRX5551, - DRM_FORMAT_ARGB1555, - DRM_FORMAT_ABGR1555, - DRM_FORMAT_RGBA5551, - DRM_FORMAT_BGRA5551, DRM_FORMAT_RGB565, DRM_FORMAT_BGR565, DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888, - DRM_FORMAT_ARGB8888, - DRM_FORMAT_ABGR8888, - DRM_FORMAT_RGBA8888, - DRM_FORMAT_BGRA8888, - DRM_FORMAT_ARGB2101010, - DRM_FORMAT_ABGR2101010, - DRM_FORMAT_RGBA1010102, - DRM_FORMAT_BGRA1010102, /* TODO: non-RGB formats */ }; @@ -49,32 +33,16 @@ static const u32 vs_primary_formats_array_with_yuv444[] = { DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, DRM_FORMAT_BGRX4444, - DRM_FORMAT_ARGB4444, - DRM_FORMAT_ABGR4444, - DRM_FORMAT_RGBA4444, - DRM_FORMAT_BGRA4444, DRM_FORMAT_XRGB1555, DRM_FORMAT_XBGR1555, DRM_FORMAT_RGBX5551, DRM_FORMAT_BGRX5551, - DRM_FORMAT_ARGB1555, - DRM_FORMAT_ABGR1555, - DRM_FORMAT_RGBA5551, - DRM_FORMAT_BGRA5551, DRM_FORMAT_RGB565, DRM_FORMAT_BGR565, DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888, - DRM_FORMAT_ARGB8888, - DRM_FORMAT_ABGR8888, - DRM_FORMAT_RGBA8888, - DRM_FORMAT_BGRA8888, - DRM_FORMAT_ARGB2101010, - DRM_FORMAT_ABGR2101010, - DRM_FORMAT_RGBA1010102, - DRM_FORMAT_BGRA1010102, /* TODO: non-RGB formats */ }; From 53cf0f26eece095a3752642151150404bc3351cc Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 1 Sep 2026 13:37:36 -0700 Subject: [PATCH 0565/1417] crypto: caam - map job ring registers without claiming region devm_platform_ioremap_resource() ends up calling request_mem_region(), which fails with -EBUSY on i.MX SoCs: There the job rings are modelled as sub-regions of their parent fsl,sec-v4.0 register window, and caam_probe() already reserves the whole window exclusively via devm_of_iomap() before the children are populated. Every job ring therefore collides with its own parent and fails to probe, taking the hardware RNG offline (seen on i.MX6, i.MX7 and i.MX8 boards such as colibri-imx7 and verdin-imx8mp). Map the job ring registers with devm_ioremap() instead, which does not claim the (already owned) region. Fixes: 9a955c0a7d11 ("crypto: caam - simplify probe resource and IRQ handling") Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Reported-by: Emanuele Ghidoli Tested-by: Emanuele Ghidoli Tested-by: Richard Leitner # i.MX8MP Tested-by: Sahil Malhotra # i.MX8MM-EVK Signed-off-by: Herbert Xu --- drivers/crypto/caam/jr.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/crypto/caam/jr.c b/drivers/crypto/caam/jr.c index 239469f6882e63..cf725a6ac0fb9b 100644 --- a/drivers/crypto/caam/jr.c +++ b/drivers/crypto/caam/jr.c @@ -583,12 +583,29 @@ static int caam_jr_probe(struct platform_device *pdev) struct caam_drv_private_jr *jrpriv; static int total_jobrs; void __iomem *ctrl; + struct resource *r; int error; int irq; - ctrl = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(ctrl)) - return PTR_ERR(ctrl); + /* + * The job rings live inside the register window of their parent + * fsl,sec-v4.0 node, which caam_probe() already reserves (and maps) + * via devm_of_iomap(). A requested region that overlaps that + * reservation, e.g. from devm_platform_ioremap_resource(), would + * therefore fail with -EBUSY, so map the registers without claiming + * the region here. + */ + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "platform_get_resource() failed\n"); + return -EINVAL; + } + + ctrl = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (!ctrl) { + dev_err(&pdev->dev, "devm_ioremap() failed\n"); + return -ENOMEM; + } irq = platform_get_irq(pdev, 0); if (irq < 0) From a26204be587c57bd5c54fa513be26c4fd7bf252d Mon Sep 17 00:00:00 2001 From: Nemesa Garg Date: Wed, 9 Sep 2026 16:33:31 +0530 Subject: [PATCH 0566/1417] Revert "drm/i915/display: Clear SEL_FETCH_PLANE_CTL on plane disable" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7f1172a2ac0d7e50850785e2e65789c8aac8411a. This commit replaced the crtc_state->enable_psr2_sel_fetch guard in icl_plane_disable_sel_fetch_arm() and i9xx_cursor_disable_sel_fetch_arm() with HAS_PSR2_SEL_FETCH(). This is a display version check and says nothing about the pipe, so every plane and cursor disable on a display 12+ platform started writing SEL_FETCH_PLANE_CTL() / SEL_FETCH_CUR_CTL(), including on pipes that do not implement them. It shows up as an unclaimed register access on pipes driving HDMI where selective fetch was never enabled. The stale selective fetch enable bit that commit addressed is handled in the next patch. Fixes: 7f1172a2ac0d ("drm/i915/display: Clear SEL_FETCH_PLANE_CTL on plane disable") Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16876 Signed-off-by: Nemesa Garg Reviewed-by: Jouni Högander Signed-off-by: Suraj Kandpal Link: https://patch.msgid.link/20260909110332.3528029-2-nemesa.garg@intel.com (cherry picked from commit d393529394167e0f5f706657eebe84d8529ce4fc) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_cursor.c | 15 +++++---------- .../gpu/drm/i915/display/skl_universal_plane.c | 15 +++++---------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cursor.c b/drivers/gpu/drm/i915/display/intel_cursor.c index 86bb96ac449b03..0673f16f6fd0dc 100644 --- a/drivers/gpu/drm/i915/display/intel_cursor.c +++ b/drivers/gpu/drm/i915/display/intel_cursor.c @@ -530,18 +530,13 @@ static int i9xx_check_cursor(struct intel_crtc_state *crtc_state, } static void i9xx_cursor_disable_sel_fetch_arm(struct intel_dsb *dsb, - struct intel_plane *plane) + struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - /* - * Clear this whenever the hardware has selective fetch, not just when - * the current state uses it. The cursor may have been enabled with - * selective fetch earlier and had its enable bit orphaned when the - * feature was switched off. - */ - if (!HAS_PSR2_SEL_FETCH(display)) + if (!crtc_state->enable_psr2_sel_fetch) return; intel_de_write_dsb(display, dsb, SEL_FETCH_CUR_CTL(pipe), 0); @@ -591,7 +586,7 @@ static void i9xx_cursor_update_sel_fetch_arm(struct intel_dsb *dsb, if (crtc_state->enable_psr2_su_region_et) wa_16021440873(dsb, plane, crtc_state, plane_state); else - i9xx_cursor_disable_sel_fetch_arm(dsb, plane); + i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); } } @@ -700,7 +695,7 @@ static void i9xx_cursor_update_arm(struct intel_dsb *dsb, if (plane_state) i9xx_cursor_update_sel_fetch_arm(dsb, plane, crtc_state, plane_state); else - i9xx_cursor_disable_sel_fetch_arm(dsb, plane); + i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); if (plane->cursor.base != base || plane->cursor.size != fbc_ctl || diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c index 5cda1ab90e40f8..07a68329335219 100644 --- a/drivers/gpu/drm/i915/display/skl_universal_plane.c +++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c @@ -879,18 +879,13 @@ skl_plane_disable_arm(struct intel_dsb *dsb, } static void icl_plane_disable_sel_fetch_arm(struct intel_dsb *dsb, - struct intel_plane *plane) + struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - /* - * Clear this whenever the hardware has selective fetch, not just when - * the current state uses it. The plane may have been enabled with - * selective fetch earlier and had its enable bit orphaned when the - * feature was switched off. - */ - if (!HAS_PSR2_SEL_FETCH(display)) + if (!crtc_state->enable_psr2_sel_fetch) return; intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), 0); @@ -926,7 +921,7 @@ icl_plane_disable_arm(struct intel_dsb *dsb, skl_write_plane_wm(dsb, plane, crtc_state); - icl_plane_disable_sel_fetch_arm(dsb, plane); + icl_plane_disable_sel_fetch_arm(dsb, plane, crtc_state); if (plane_has_normalizer(plane)) intel_de_write_dsb(display, dsb, @@ -1646,7 +1641,7 @@ static void icl_plane_update_sel_fetch_arm(struct intel_dsb *dsb, intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), SEL_FETCH_PLANE_CTL_ENABLE); else - icl_plane_disable_sel_fetch_arm(dsb, plane); + icl_plane_disable_sel_fetch_arm(dsb, plane, crtc_state); } static void From c1d6580f70951de5952cf9142829c0d2d291aa43 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 16:40:14 +0700 Subject: [PATCH 0567/1417] ALSA: pcm: Propagate snd_pcm_stop() errors in snd_pcm_drop() snd_pcm_drop() currently ignores the return value of snd_pcm_stop() and returns a pre-initialized zero instead. Store the return value of snd_pcm_stop() in 'result' so that any error is propagated to the caller. Found by manual code inspection. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916094014.33609-1-phucduc.bui@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 000e926a1e6ab7..74054b9ca71273 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2267,7 +2267,7 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, static int snd_pcm_drop(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime; - int result = 0; + int result; if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; @@ -2282,7 +2282,7 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream) if (runtime->state == SNDRV_PCM_STATE_PAUSED) snd_pcm_pause(substream, false); - snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + result = snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ return result; From b61b6f95d6722ddbbbd09e689fa41b55fd36f9a5 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Fri, 11 Sep 2026 11:04:47 +0200 Subject: [PATCH 0568/1417] futex: Also allocate private hash on vfork() As Jann demonstrated, it is entirely feasible to access the mm through vfork(). Therefore we need to allocate a private hash on vfork() as well as any other CLONE_VM user. Specifically, it must be avoided to have (private) futex waiters before allocating the private hash. Fixes: ee9dce44362b ("futex: Drop CLONE_THREAD requirement for private default hash alloc") Reported-by: Jann Horn Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260911090447.GT788244@noisy.programming.kicks-ass.net --- kernel/fork.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/fork.c b/kernel/fork.c index a5934a3176346b..5ef413368912e1 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1996,9 +1996,9 @@ static bool need_futex_hash_allocate_default(u64 clone_flags) { /* * Allocate a default futex hash for any sibling that will - * share the parent's mm, except vfork. + * share the parent's mm. */ - return (clone_flags & (CLONE_VM | CLONE_VFORK)) == CLONE_VM; + return clone_flags & CLONE_VM; } /* From f0e9f963a3d209d7dc7ddd61116118ab5da2797d Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Fri, 11 Sep 2026 17:45:47 +0530 Subject: [PATCH 0569/1417] drm/xe/i2c: Disable IRQ on unbind Currently, struct xe_i2c is freed before SGUnit IRQ is disabled in unbind path, leaving a potential UAF in case I2C IRQ is hit during this small window. Explicitly disable I2C IRQ in xe_i2c_remove() and fix this. Fixes: 0bb78ce09926 ("drm/xe/i2c: Wire up reset/postinstall for I2C IRQ") Signed-off-by: Raag Jadav Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260911121547.2407261-1-raag.jadav@intel.com Signed-off-by: Matt Roper (cherry picked from commit 8ba5c8b8ab3fd362267c11df2cd5a90ee46f6e24) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_i2c.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index 399099ff0dbc17..f4f3819882897f 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -318,8 +318,10 @@ void xe_i2c_pm_resume(struct xe_device *xe, bool d3cold) static void xe_i2c_remove(void *data) { struct xe_i2c *i2c = data; + struct xe_device *xe = tile_to_xe(i2c->mmio->tile); unsigned int i; + xe_i2c_irq_reset(xe); xe_amc_exit(i2c); for (i = 0; i < XE_I2C_MAX_CLIENTS; i++) { @@ -329,6 +331,7 @@ static void xe_i2c_remove(void *data) bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); xe_i2c_unregister_adapter(i2c); + xe->i2c = NULL; } /** From a13f7f5d14af9baf34eb12c25962f8d3542b281d Mon Sep 17 00:00:00 2001 From: Ilya Titov Date: Thu, 3 Sep 2026 12:17:18 +0300 Subject: [PATCH 0570/1417] pinctrl: sunxi: keep a shadow copy of the data register output latches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Allwinner SoCs, reading a bank's data register returns the pin level, not the output latch, for pins that are muxed as inputs. Writing a GPIO therefore corrupts the output latches of all input-muxed pins in the same bank: the read-modify-write in sunxi_pinctrl_gpio_set() reads back their pin levels and writes those into their latches. This breaks emulated open-drain lines (e.g. a bit-banged I2C bus from i2c-gpio). Such a line is released high by muxing it as input and letting the pull-up raise it, so any concurrent GPIO write in the same bank stores 1 into its latch. Driving the line low afterwards is a non-atomic data-then-mux sequence in sunxi_pinctrl_gpio_direction_output(); if the poisoning write lands between the two steps, the pin actively drives high (push-pull) instead of low. Observed in practice as sporadic glitches on a T507 board bit-banging I2C on port E while other PE GPIOs are toggled. On a scope the failure is unmistakable: on a clock pulse where SCL should fall to GND, the line instead steps *above* its idle high level for the whole low phase — the pad drives a strong push-pull 3.3 V high, higher than the level the pull-up sustains on the loaded bus — before the next transition recovers it. The same can hit SDA, corrupting data instead of clocks. Steps to reproduce on any sunxi board with a bit-banged (i2c-gpio) bus: # background: toggle any other GPIO of the same bank, e.g. line 21 gpioset -c --toggle 100us 21=0 & # foreground: keep the bit-banged bus busy while :; do i2cdetect -y 0x50 0x57; done # watch SCL/SDA with a scope or logic analyzer: sporadic clock-low # phases driven high (above the pull-up level) instead of low The bank spinlock cannot help: the racing write is a perfectly valid whole-register RMW that faithfully writes back what the hardware returned. There are no set/clear registers on this IP to write a single bit atomically. Fix it the same way gpio-mmio handles hardware whose data register read does not return the output latch: keep a shadow copy of each bank's latches, base the read-modify-write on the shadow, and only write the register. The shadow is seeded from the hardware at probe time so pins left in output mode by the bootloader keep their state. Pins that reach output mode through the gpiolib paths write their value (and thereby their shadow bit) before the mux switch in sunxi_pinctrl_gpio_direction_output(); pins muxed to gpio_out directly through a pinmux node bypass that path, so sunxi_pmx_set() refreshes their shadow bit from the latch (readable once the pin is in output mode) to keep them driving their pre-existing level. Seeding the shadow reads the PIO registers at probe time, which requires the bus clock to be enabled. The clock was only requested at the very end of probe, after devm_pinctrl_register() had already claimed the pin hogs described in the device tree - which mux pins, and thus access registers, with the clock still gated. Move the request ahead of both. Boards whose bootloader leaves the PIO clock running are unaffected, which is why the pre-existing hog problem has gone unnoticed since commit 950707c0eb5c ("pinctrl: sunxi: add clock support"). Fixes: df7b34f4c3d2 ("pinctrl: sunxi: Fix gpio_set behaviour") Cc: stable@vger.kernel.org Signed-off-by: Ilya Titov Signed-off-by: Linus Walleij --- drivers/pinctrl/sunxi/pinctrl-sunxi.c | 76 +++++++++++++++++++++------ drivers/pinctrl/sunxi/pinctrl-sunxi.h | 7 +++ 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c index 25489beeb31253..2881a83be99c8a 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c @@ -837,6 +837,21 @@ static void sunxi_pmx_set(struct pinctrl_dev *pctldev, writel((readl(pctl->membase + reg) & ~mask) | config << shift, pctl->membase + reg); + /* + * A pin muxed to gpio_out directly through a pinmux node bypasses + * sunxi_pinctrl_gpio_set() and drives whatever its output latch + * holds. Now that the pin is in output mode the data register + * reads back the latch, so refresh the shadow to keep such pins + * driving their pre-existing level. + */ + if (config == SUN4I_FUNC_OUTPUT) { + u32 *shadow = &pctl->dat_shadow[pin / PINS_PER_BANK]; + + sunxi_data_reg(pctl, pin, ®, &shift, &mask); + *shadow = (*shadow & ~mask) | + (readl(pctl->membase + reg) & mask); + } + raw_spin_unlock_irqrestore(&pctl->lock, flags); } @@ -1017,21 +1032,29 @@ static int sunxi_pinctrl_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) { struct sunxi_pinctrl *pctl = gpiochip_get_data(chip); - u32 reg, shift, mask, val; + u32 *shadow = &pctl->dat_shadow[offset / PINS_PER_BANK]; + u32 reg, shift, mask; unsigned long flags; sunxi_data_reg(pctl, offset, ®, &shift, &mask); raw_spin_lock_irqsave(&pctl->lock, flags); - val = readl(pctl->membase + reg); - + /* + * Reading the data register returns the pin level, not the output + * latch, for pins muxed as inputs. A read-modify-write based on + * the register would therefore corrupt the latches of input-muxed + * pins in the same bank (e.g. an emulated open-drain I2C line + * released high), making them drive the wrong level once switched + * to output. Base the read-modify-write on a shadow copy of the + * latches instead. + */ if (value) - val |= mask; + *shadow |= mask; else - val &= ~mask; + *shadow &= ~mask; - writel(val, pctl->membase + reg); + writel(*shadow, pctl->membase + reg); raw_spin_unlock_irqrestore(&pctl->lock, flags); @@ -1572,7 +1595,7 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, struct pinctrl_pin_desc *pins; struct sunxi_pinctrl *pctl; struct pinmux_ops *pmxops; - int i, ret, last_pin, pin_idx; + int i, ret, last_pin, pin_idx, nbanks; struct clk *clk; pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL); @@ -1610,6 +1633,37 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, if (!pctl->irq_array) return -ENOMEM; + /* + * The bus clock has to be enabled before the pinctrl device + * registers, as the pin hogs claimed from there access registers. + */ + ret = of_clk_get_parent_count(node); + clk = devm_clk_get_enabled(&pdev->dev, ret == 1 ? NULL : "apb"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + /* + * Seed the output latch shadow from the hardware so pins the + * bootloader left in output mode keep their state; see + * sunxi_pinctrl_gpio_set() for why a shadow is needed. This must + * happen before the pinctrl device registers, as pin hogs can mux + * pins to gpio_out and thereby update the shadow. + */ + last_pin = pctl->desc->pins[pctl->desc->npins - 1].pin.number; + nbanks = DIV_ROUND_UP(last_pin + 1 - pctl->desc->pin_base, + PINS_PER_BANK); + pctl->dat_shadow = devm_kcalloc(&pdev->dev, nbanks, + sizeof(*pctl->dat_shadow), GFP_KERNEL); + if (!pctl->dat_shadow) + return -ENOMEM; + + for (i = 0; i < nbanks; i++) { + u32 reg, shift, mask; + + sunxi_data_reg(pctl, i * PINS_PER_BANK, ®, &shift, &mask); + pctl->dat_shadow[i] = readl(pctl->membase + reg); + } + ret = sunxi_pinctrl_build_state(pdev); if (ret) { dev_err(&pdev->dev, "dt probe failed: %d\n", ret); @@ -1665,7 +1719,6 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, if (!pctl->chip) return -ENOMEM; - last_pin = pctl->desc->pins[pctl->desc->npins - 1].pin.number; pctl->chip->owner = THIS_MODULE; pctl->chip->request = gpiochip_generic_request; pctl->chip->free = gpiochip_generic_free; @@ -1699,13 +1752,6 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, goto gpiochip_error; } - ret = of_clk_get_parent_count(node); - clk = devm_clk_get_enabled(&pdev->dev, ret == 1 ? NULL : "apb"); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - goto gpiochip_error; - } - pctl->irq = devm_kcalloc(&pdev->dev, pctl->desc->irq_banks, sizeof(*pctl->irq), diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.h b/drivers/pinctrl/sunxi/pinctrl-sunxi.h index 0daf7600e2fb01..498e88a19f71e6 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.h +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.h @@ -85,6 +85,7 @@ #define IO_BIAS_MASK GENMASK(3, 0) #define SUN4I_FUNC_INPUT 0 +#define SUN4I_FUNC_OUTPUT 1 #define SUN4I_FUNC_IRQ 6 #define SUN4I_FUNC_DISABLED_OLD 7 #define SUN4I_FUNC_DISABLED_NEW 15 @@ -175,6 +176,12 @@ struct sunxi_pinctrl { int *irq; unsigned *irq_array; raw_spinlock_t lock; + /* + * Output latch shadow, one word per bank. Seeded lockless at + * probe before the pinctrl device registers, protected by @lock + * afterwards. + */ + u32 *dat_shadow; struct pinctrl_dev *pctl_dev; unsigned long flags; u32 bank_mem_size; From cc337324cc6be1ce0ae7e5a068dcb7e99ae1b59c Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Mon, 14 Sep 2026 18:11:30 +0100 Subject: [PATCH 0571/1417] btrfs: fix creation of compressed inline extents that don't save space If the compressed data of an inline extent is larger than or equals to the size of the uncompressed data, we are still allowing the creation of the compressed inline extent, which does not result in any benefits, quite the contrary as we waste metadata space and have to decompress when reading. This is a recent regression introduced in commit 3eaf5f082c4c ("btrfs: extract inlined creation into a dedicated delalloc helper"). It happens because we are passing the block size to btrfs_compress_bio(), so we don't get -E2BIG from the compression code anymore, but we can not pass i_size either, because if i_size is smaller than sector size, we end up never creating lzo compressed inline extent for such small i_size values. So refuse the compressed result at run_delalloc_inline() if its size is not smaller than the uncompressed size (i_size). Reported-by: Hanabishi Link: https://lore.kernel.org/linux-btrfs/c97652a5-ac6b-4de6-aa23-3cdebc01d00b@gmail.com/ Fixes: 3eaf5f082c4c ("btrfs: extract inlined creation into a dedicated delalloc helper") CC: stable@vger.kernel.org # 7.1+ Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/inode.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 3668cbc7598e35..4f976b52996d93 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -2339,12 +2339,27 @@ static int run_delalloc_inline(struct btrfs_inode *inode, struct folio *locked_f } else if (inode->prop_compress) { compress_type = inode->prop_compress; } + /* + * We need to pass blocksize and not i_size, otherwise we can't + * create compressed inline extents for data smaller than sector + * size with lzo. + */ cb = btrfs_compress_bio(inode, 0, blocksize, compress_type, compress_level, 0); if (IS_ERR(cb)) { cb = NULL; /* Just fall back to non-compressed case. */ } else { compressed_size = cb->bbio.bio.bi_iter.bi_size; + /* + * If we did not save space, it's pointless and wasteful + * to have an inline compressed extent, so fallback to + * an uncompressed inline extent. + */ + if (compressed_size >= i_size) { + cleanup_compressed_bio(cb); + cb = NULL; + compressed_size = 0; + } } } if (!can_cow_file_range_inline(inode, 0, i_size, compressed_size)) { From 76bf149cd0298544631e756670b89c399c7acbca Mon Sep 17 00:00:00 2001 From: Daniel Linjama Date: Wed, 16 Sep 2026 09:15:56 +0300 Subject: [PATCH 0572/1417] btrfs: handle lack of space when cleaning up verity items When enable_verity() hits the qgroup limit, rollback_verity() needs its own metadata reservation. When the qgroup limit or lack of space refuses the rollback, the whole filesystem is forced read-only even though the qgroup limit was for one subvolume only. Also orphan cleanup at the next mount fails the same way, so the leftover items are never removed: with -EDQUOT the subvolume stays unreachable, and with -ENOSPC on a full filesystem the next read-write mount fails. Start transactions with btrfs_start_transaction_fallback_global_rsv() in btrfs_orphan_cleanup(), drop_verity_items() and rollback_verity(). Those calls only delete items and free the space in the end, so they may use the global reserve and skip the qgroup limit, which avoids -ENOSPC and -EDQUOT. Fixes: 146054090b08 ("btrfs: initial fsverity support") Reviewed-by: Qu Wenruo Signed-off-by: Daniel Linjama Signed-off-by: David Sterba --- fs/btrfs/inode.c | 3 ++- fs/btrfs/verity.c | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 4f976b52996d93..e97446fc9e1063 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -3892,7 +3892,8 @@ int btrfs_orphan_cleanup(struct btrfs_root *root) if (ret) goto out; } - trans = btrfs_start_transaction(root, 1); + /* Only deletes the orphan. */ + trans = btrfs_start_transaction_fallback_global_rsv(root, 1); if (IS_ERR(trans)) { ret = PTR_ERR(trans); goto out; diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c index 4e0ab584227419..600337a84fbee5 100644 --- a/fs/btrfs/verity.c +++ b/fs/btrfs/verity.c @@ -93,6 +93,20 @@ static loff_t merkle_file_pos(const struct inode *inode) return rounded; } +/* + * Start a transaction for removing verity items or the verity orphan. + * + * Like unlink, this only deletes items and frees space in the end, so the + * reservation may come from the global reserve when the filesystem is full + * (-ENOSPC) and is not subject to the qgroup limit (-EDQUOT). Otherwise a + * failed enable could never be cleaned up in either situation. + */ +static struct btrfs_trans_handle *start_verity_cleanup_trans(struct btrfs_root *root, + unsigned int num_items) +{ + return btrfs_start_transaction_fallback_global_rsv(root, num_items); +} + /* * Drop all the items for this inode with this key_type. * @@ -120,7 +134,7 @@ static int drop_verity_items(struct btrfs_inode *inode, u8 key_type) while (1) { /* 1 for the item being dropped */ - trans = btrfs_start_transaction(root, 1); + trans = start_verity_cleanup_trans(root, 1); if (IS_ERR(trans)) return PTR_ERR(trans); @@ -466,7 +480,7 @@ static int rollback_verity(struct btrfs_inode *inode) * 1 for updating the inode flag * 1 for deleting the orphan */ - trans = btrfs_start_transaction(root, 2); + trans = start_verity_cleanup_trans(root, 2); if (IS_ERR(trans)) { ret = PTR_ERR(trans); trans = NULL; From 7c431d61b69a3fd0784c20aa4cd0b8fb501b5653 Mon Sep 17 00:00:00 2001 From: ZHOU Jiaxiang Date: Wed, 16 Sep 2026 21:58:21 +0800 Subject: [PATCH 0573/1417] scsi: block: Fix zones_cond out-of-bounds write on zone report blk_revalidate_disk_zones() sizes the zones_cond array from the disk capacity and zone size, but the index used by blk_revalidate_zone_cond() comes from the device-driven report_zones() walk and is never checked against the array size. A device reporting more zones than fit the array makes blk_zone_set_cond() write out of bounds. One way to reach this is a zone count exceeding 32 bits: both blk_revalidate_zone_args.nr_zones and struct zoned_disk_info.nr_zones are unsigned int, so a disk advertising more than UINT_MAX zones (e.g. 2^32 + 1024 zones of one 512-byte logical block) gets its zone count truncated to a small value, undersizing the array while the report walk keeps counting upward. Check the index against the array size before storing the zone condition, and refuse to revalidate when the zone count does not fit 32 bits. Fixes: 6e945ffb6555 ("block: use zone condition to determine conventional zones") Signed-off-by: ZHOU Jiaxiang Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/7815D1B293A8F55E+20260916135822.32584-2-me@fxti.xyz Signed-off-by: Martin K. Petersen (Oracle) --- block/blk-zoned.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/block/blk-zoned.c b/block/blk-zoned.c index a5afb842bf35e0..475aa16bc41a5d 100644 --- a/block/blk-zoned.c +++ b/block/blk-zoned.c @@ -2018,12 +2018,17 @@ static int disk_revalidate_zone_resources(struct gendisk *disk, struct blk_revalidate_zone_args *args) { struct queue_limits *lim = &disk->queue->limits; + unsigned long long nr_zones; unsigned int pool_size; int ret = 0; args->disk = disk; - args->nr_zones = - DIV_ROUND_UP_ULL(get_capacity(disk), lim->chunk_sectors); + nr_zones = DIV_ROUND_UP_ULL(get_capacity(disk), lim->chunk_sectors); + if (nr_zones > UINT_MAX) { + pr_warn("%s: Too many zones (%llu)\n", disk->disk_name, nr_zones); + return -EINVAL; + } + args->nr_zones = nr_zones; /* Cached zone conditions: 1 byte per zone */ args->zones_cond = kzalloc(args->nr_zones, GFP_NOIO); @@ -2131,6 +2136,12 @@ static int blk_revalidate_zone_cond(struct blk_zone *zone, unsigned int idx, { enum blk_zone_cond cond = zone->cond; + if (idx >= args->nr_zones) { + pr_warn("%s: Zone report index %u exceeds zone count %u\n", + args->disk->disk_name, idx, args->nr_zones); + return -EINVAL; + } + /* Check that the zone condition is consistent with the zone type. */ switch (cond) { case BLK_ZONE_COND_NOT_WP: From b6ec0f79745967c751c85df373062c8d15e45fc4 Mon Sep 17 00:00:00 2001 From: ZHOU Jiaxiang Date: Wed, 16 Sep 2026 21:58:22 +0800 Subject: [PATCH 0574/1417] scsi: sd_zbc: Reject disks with too many zones sd_zbc_read_zones() computes the number of zones with 64-bit arithmetic and stores the result in the unsigned int nr_zones field of struct zoned_disk_info, silently truncating counts that exceed 32 bits. The truncated count is later used to size per-zone resources, while the device may still report more zones than fit. Moreover, sd_zbc_report_zones() counts the reported zones with a signed int zone_idx, which overflows past INT_MAX. Reject devices reporting more than INT_MAX zones at scan time; such a device is not realistic for any medium that exists today, and accepting it produces inconsistent zone bookkeeping. Fixes: 89d947561077 ("sd: Implement support for ZBC devices") Signed-off-by: ZHOU Jiaxiang Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/C41798AB5AA6BF2B+20260916135822.32584-3-me@fxti.xyz Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/sd_zbc.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/sd_zbc.c b/drivers/scsi/sd_zbc.c index 56e455fb5addd3..456beaf2e7690c 100644 --- a/drivers/scsi/sd_zbc.c +++ b/drivers/scsi/sd_zbc.c @@ -589,7 +589,7 @@ int sd_zbc_revalidate_zones(struct scsi_disk *sdkp) int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim, u8 buf[SD_BUF_SIZE]) { - unsigned int nr_zones; + u64 nr_zones; u32 zone_blocks = 0; int ret; @@ -621,6 +621,12 @@ int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim, goto err; nr_zones = round_up(sdkp->capacity, zone_blocks) >> ilog2(zone_blocks); + if (nr_zones > INT_MAX) { + sd_printk(KERN_ERR, sdkp, "Too many zones (%llu)\n", + nr_zones); + ret = -EINVAL; + goto err; + } sdkp->early_zone_info.nr_zones = nr_zones; sdkp->early_zone_info.zone_blocks = zone_blocks; From 8d836581f9b1f57ceaa2b47654754ef1260b410b Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Wed, 16 Sep 2026 15:00:32 +0200 Subject: [PATCH 0575/1417] ata: libata-scsi: fix ata_dsm_trim_pages() kernel-doc make htmldocs fails with: Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301: ERROR: Unexpected indentation. [docutils] The Return: section of ata_dsm_trim_pages() ends its first sentence with a colon and continues with an indented bullet list. reStructuredText requires a blank line before an indented block, so docutils chokes on the list. Simply adding the missing blank line does not work either: Return: is a kernel-doc "special section", which is terminated by the first blank line, so the bullet list would end up in the Description section, detached from the sentence introducing it. Spell the two bounds out as prose instead, so that the Return: section stays self-contained. Documentation-only change. Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND") Reported-by: Thomas Huth Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/ Reviewed-by: Damien Le Moal Reviewed-by: Thomas Huth Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org Signed-off-by: Niklas Cassel --- drivers/ata/libata-scsi.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index b3666519b648dc..7e22bbc382384e 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -2297,10 +2297,9 @@ static unsigned int ata_scsiop_inq_89(struct ata_device *dev, * logical block, so it can hold at most sector_size / 512 pages. * * Return: the maximum number of 512-byte pages a single translated WRITE SAME - * command may send to @dev (never less than one), that is the smaller of: - * - MAX PAGES PER DSM COMMAND (IDENTIFY DEVICE word 105), when the device - * reports a non-zero limit; and - * - the logical sector size expressed in 512-byte pages (see above). + * command may send to @dev, that is the smaller of MAX PAGES PER DSM COMMAND + * (IDENTIFY DEVICE word 105, when the device reports a non-zero limit) and + * the logical sector size expressed in 512-byte pages; never less than one. */ static unsigned int ata_dsm_trim_pages(struct ata_device *dev) { From e6cb0b4d4ecb8e71fd2200d907ab2e9663356f69 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 16 Sep 2026 05:29:26 +0500 Subject: [PATCH 0576/1417] hwmon: (hp-wmi-sensors) Fix use-after-free in fungible_show() nsensor->current_state is dynamically replaced as the sensor's state changes. update_numeric_sensor_from_wobj() does this by freeing the old string and installing a new one: if (strcmp(trimmed, nsensor->current_state)) { new_string = hp_wmi_strdup(dev, trimmed); if (new_string) { devm_kfree(dev, nsensor->current_state); nsensor->current_state = new_string; } } This function is only ever called from hp_wmi_update_info() while state->lock is held, so the free-and-replace itself is properly serialized against concurrent updates. fungible_show(), however, reads the same pointer after the lock has already been dropped: err = hp_wmi_update_info(state, info); if (err) return err; switch (prop) { ... case HP_WMI_PROPERTY_CURRENT_STATE: seq_printf(seqf, "%s\n", nsensor->current_state); break; hp_wmi_update_info() takes state->lock internally and releases it before returning, so by the time fungible_show() dereferences nsensor->current_state in seq_printf(), no lock is held. Two processes reading a sensor's current_state debugfs entry at overlapping times (or one reading it while another read of the same sensor triggers a refresh) can race: one thread's seq_printf() can be part-way through printing the string at the moment another thread's call into update_numeric_sensor_from_wobj() frees it with devm_kfree() and installs a new pointer, causing a use-after-free read. Take state->lock around the read in fungible_show() as well, so it can never run concurrently with the free-and-replace in update_numeric_sensor_from_wobj(). Fixes: 23902f98f8d4 ("hwmon: add HP WMI Sensors driver") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Acked-by: James Seo Link: https://patch.msgid.link/20260916002926.161595-1-meatuni001@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/hp-wmi-sensors.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c index 03c684ba83bd64..55aee16df57dd5 100644 --- a/drivers/hwmon/hp-wmi-sensors.c +++ b/drivers/hwmon/hp-wmi-sensors.c @@ -1247,7 +1247,9 @@ static int fungible_show(struct seq_file *seqf, enum hp_wmi_property prop) break; case HP_WMI_PROPERTY_CURRENT_STATE: + mutex_lock(&state->lock); seq_printf(seqf, "%s\n", nsensor->current_state); + mutex_unlock(&state->lock); break; case HP_WMI_PROPERTY_UNIT_MODIFIER: From 1d12fb94ac0975566545871dda100df34df5f845 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Tue, 15 Sep 2026 16:48:35 +0000 Subject: [PATCH 0577/1417] hwmon: (pmbus/tps53679) Fix TPS53676 phase page decoding tps53676_identify() reads the USER_DATA_03 phase configuration to count the phases assigned to each channel and derive the number of PMBus pages. In each 16-bit phase descriptor the channel (PAGE) is encoded in bit 4 and the firing order in bits 3:0, but the code tested bit 3 (0x08), which is part of the firing-order field. TPS53676 supports up to seven phases, so firing-order bit 3 is never set. As a result the existing test classifies every enabled phase as channel A. On a dual-channel configuration the phases assigned to channel B are therefore miscounted as channel A and page 1 is not exposed. Test the PAGE field (bit 4) instead. Fixes: cb3d37b59012 ("hwmon: (pmbus/tps53679) Add support for TI TPS53676") Cc: stable@vger.kernel.org Signed-off-by: Sanman Pradhan Link: https://patch.msgid.link/20260915164823.160977-2-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/tps53679.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c index 31e54608b3c9c0..9f27832703ff5b 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -187,7 +187,7 @@ static int tps53676_identify(struct i2c_client *client, return -EIO; for (i = 0; i < 2 * TPS53676_MAX_PHASES; i += 2) { if (buf[i + 1] & 0x80) { - if (buf[i] & 0x08) + if (buf[i] & BIT(4)) phases_b++; else phases_a++; From d2710c8d938ae6a825a6463158e6e6f31eac792a Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:12 +0200 Subject: [PATCH 0578/1417] signal: Prevent exec() race Hyunwoo debugged the following KASAN UAF splat: BUG: KASAN: slab-use-after-free in __send_signal_locked+0xb27/0xba0 Write of size 8 at addr ffff888007ed80c8 by task poc/79 ... Call Trace: __send_signal_locked+0xb27/0xba0 do_send_sig_info+0xa7/0x160 do_send_specific+0x76/0xa0 __x64_sys_tgkill+0x193/0x270 ... Allocated by task 80: do_timer_create+0x1a4/0x1030 __x64_sys_timer_create+0x145/0x190 ... Freed by task 12: kmem_cache_free_bulk+0x1f8/0x4a0 kvfree_rcu_bulk+0x14f/0x1c0 kfree_rcu_work+0x128/0x1a0 ... Last potentially related work creation: kvfree_call_rcu+0x39/0x390 __flush_itimer_signals+0x211/0x320 flush_itimer_signals+0x47/0x90 begin_new_exec+0xa6b/0x28c0 It turned out that this happens with a non-leader exec() as Hyunwoo explained: de_thread() calls exchange_tids() before release_task(leader), so the struct pid held by a SIGEV_THREAD_ID timer created against the leader's tid now points to the thread which called execve(). pid_task() returns that thread and lock_task_sighand() on it succeeds. If the timer signal is blocked, its sigqueue stays queued on the leader's task::pending. The next expiry of that timer can then run while release_task() flushes the queue. posixtimer_send_sigqueue() checks whether the sigqueue is already queued with a plain list_empty(), which only reads list_head::next. list_del_init() is not atomic and INIT_LIST_HEAD() stores list_head::next before list_head::prev, so the check can pass in between. list_add_tail() queues the entry on the task::pending of the live thread, and the list_head::prev store from the flush then overwrites the list_head::prev link that list_add_tail() has just set. __flush_itimer_signals() does not undo that either. With list_head::prev pointing at the entry itself, its list_del_init() only stores the same values again, so the entry is not removed from the list. It is still there after the last reference is dropped and the timer is freed by RCU, and the list_add_tail() of a later tgkill() follows that list_head::prev into the freed timer. This problem surfaced with the recent commit which moved the sigqueue flush out of the sighand lock held region. Hyonwoo proposed to fix this by using list_del_init_careful(), but that just papers over the problem. After some disucssions and various attempts to solve it, Eric pointed out that there is no reason to flush task::pending late in release_task() and it should be done in exit_signals() already. As nothing can collect and deliver signals which are queued in a dying task's pending queue, there is no reason to delay it further. But it has to be ensured that no signals can be queued into it after that point. exit_signals() sets PF_EXITING in task::flags, which can be used as an indicator for this. Cure it by: - Preventing signal queueing for task private signals (PIDTYPE_PID) when the task has PF_EXITING set in __send_signal_locked() and in posixtimer_send_sigqueue(). - Protecting the unlocked setting of PF_EXITING in exit_signals() for the task group empty and the group exit case with sighand lock - Flushing task::pending signals right there. Optimize that by moving the whole pending list to an on-stack list head under sighand lock and free the signals without the lock held. There has been quite some discussion about the lockless flush and the non-leader exec case on weakly ordered systems. The problem is that a third party which tries to send a posix timer signal relies on the PID lookup to find the target task and that lookup might result in the new leader when the signal was originaly directed to the old leader. In case that the signal was queued on the old leader then the lockless flush raised a concern over the following situation: old_leader new_leader third party A: flush_list() // list_del_init() stores to sigqueue LOCK (tasklist) old_leader->exit_state = EXIT_ZOMBIE; B: UNLOCK (tasklist) C: LOCK (tasklist) if (old_leader->exit_state) transfer_tids() D: store PID posix_timer_send_sigqueue() // Observes #D so t = new_leader E: t = get_target() F: LOCK (sighand) G: if (list_empty(sigqueue)) list_add(sigqueue) The concern was that the third party might observe #D but not observe #A and therefore would proceed to #G while the list_del() stores (#A) in flush_list() are not visible yet, which could result in list corruption. That would be possible if looking at it solely from a RELEASE+ACQUIRE ordering point of view, but B-C is a UNLOCK+LOCK hand-over, which is not the same as RELEASE+ACQUIRE: RELEASE+ACQUIRE: RCpc, only the CPUs involved agree on the ordering UNLOCK+LOCK: RCtso, the hand-over is store-ordering As B-C is UNLOCK+LOCK, which is RCtso and that does impose store order, A stores must happen before the D store. Combine with E-F, which has a data dependency from the LOAD to the LOCK and thereby constraints later LOADs, those sigqueue loads in G that come after F must in fact observe the A stores. Fixes: fb3bbcfe344e ("exit: change the release_task() paths to call flush_sigqueue() lockless") Reported-by: Hyunwoo Kim Debugged-by: Hyunwoo Kim Suggested-by: "Eric W. Biederman" Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Oleg Nesterov Reviewed-by: Frederic Weisbecker Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260911090541.572536604@kernel.org Closes: https://patch.msgid.link/aok1rdkBgZsynHZB@v4bel --- kernel/exit.c | 11 ++--- kernel/signal.c | 115 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 91 insertions(+), 35 deletions(-) diff --git a/kernel/exit.c b/kernel/exit.c index 4e028f15759788..424c44a42a4d71 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -302,12 +302,13 @@ void release_task(struct task_struct *p) free_pids(post.pids); release_thread(p); /* - * This task was already removed from the process/thread/pid lists - * and lock_task_sighand(p) can't succeed. Nobody else can touch - * ->pending or, if group dead, signal->shared_pending. We can call - * flush_sigqueue() lockless. + * This task was already removed from the process/thread/pid lists and + * lock_task_sighand(p) can't succeed. If it's the group leader then + * flush tsk->signal->shared_pending. tsk->pending has been flushed + * already in exit_signals(). Nothing else can touch + * signal->shared_pending anymore, so flush_sigqueue() can be invoked + * lockless. */ - flush_sigqueue(&p->pending); if (thread_group_leader(p)) flush_sigqueue(&p->signal->shared_pending); diff --git a/kernel/signal.c b/kernel/signal.c index ec30550951ecfa..d31ebcb6ed4db2 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -457,18 +457,42 @@ static void __sigqueue_free(struct sigqueue *q) kmem_cache_free(sigqueue_cachep, q); } -void flush_sigqueue(struct sigpending *queue) +/* + * flush_sigqueue_list() can only be invoked without holding sighand::siglock in + * the following cases: + * + * 1) When flushing task::pending _after_ setting task::flags PF_EXITING + * + * All functions which try to send a signal to @task will observe PF_EXITING + * and drop the signal. + * + * 2) When flushing task::signal::shared_pending _after_ the last task in a + * thread group was unhashed and task::sighand is NULL. + * + * Nothing can queue a signal anymore because sighand is NULL. + */ +static void flush_sigqueue_list(struct list_head *head) { - struct sigqueue *q; + struct sigqueue *q, *tmp; - sigemptyset(&queue->signal); - while (!list_empty(&queue->list)) { - q = list_entry(queue->list.next, struct sigqueue , list); + list_for_each_entry_safe(q, tmp, head, list) { list_del_init(&q->list); __sigqueue_free(q); } } +void flush_sigqueue(struct sigpending *queue) +{ + sigemptyset(&queue->signal); + flush_sigqueue_list(&queue->list); +} + +static void sigqueue_dequeue_pending(struct sigpending *queue, struct list_head *head) +{ + sigemptyset(&queue->signal); + list_splice_init(&queue->list, head); +} + /* * Flush all pending signals for this kthread. */ @@ -1019,6 +1043,21 @@ static inline bool legacy_queue(struct sigpending *signals, int sig) return (sig < SIGRTMIN) && sigismember(&signals->signal, sig); } +/* + * When PF_EXITING is set the task is on the way out and has t::pending + * flushed already. Prevent queueing of PIDTYPE_PID signals as they would + * be leaked. + */ +static inline bool task_can_queue_signal(struct task_struct *t, enum pid_type type) +{ + lockdep_assert_held(&t->sighand->siglock); + + if (!(t->flags & PF_EXITING)) + return true; + + return type != PIDTYPE_PID; +} + static int __send_signal_locked(int sig, struct kernel_siginfo *info, struct task_struct *t, enum pid_type type, bool force) { @@ -1030,6 +1069,10 @@ static int __send_signal_locked(int sig, struct kernel_siginfo *info, lockdep_assert_held(&t->sighand->siglock); result = TRACE_SIGNAL_IGNORED; + + if (!task_can_queue_signal(t, type)) + goto ret; + if (!prepare_signal(sig, t, force)) goto ret; @@ -1980,11 +2023,25 @@ static inline struct task_struct *posixtimer_get_target(struct k_itimer *tmr) struct task_struct *t = pid_task(tmr->it_pid, tmr->it_pid_type); if (t && tmr->it_pid_type != PIDTYPE_PID && - same_thread_group(t, current) && !current->exit_state) + same_thread_group(t, current) && !(current->flags & PF_EXITING)) t = current; return t; } +/* + * Find the target task for the POSIX timer signal and prevent that a + * PIDTYPE_PID signal is queued on a task which has PF_EXITING set. + */ +static inline struct task_struct *posixtimer_get_unignore_target(struct k_itimer *tmr) +{ + struct task_struct *t = posixtimer_get_target(tmr); + + if (t && task_can_queue_signal(t, tmr->it_pid_type)) + return t; + + return NULL; +} + void posixtimer_send_sigqueue(struct k_itimer *tmr) { struct sigqueue *q = &tmr->sigq; @@ -2002,6 +2059,9 @@ void posixtimer_send_sigqueue(struct k_itimer *tmr) if (!likely(lock_task_sighand(t, &flags))) return; + if (!task_can_queue_signal(t, tmr->it_pid_type)) + goto unlock; + /* * Update @tmr::sigqueue_seq for posix timer signals with sighand * locked to prevent a race against dequeue_signal(). @@ -2093,6 +2153,7 @@ void posixtimer_send_sigqueue(struct k_itimer *tmr) result = TRACE_SIGNAL_DELIVERED; out: trace_signal_generate(sig, &q->info, t, tmr->it_pid_type != PIDTYPE_PID, result); +unlock: unlock_task_sighand(t, &flags); } @@ -2148,7 +2209,7 @@ static void posixtimer_sig_unignore(struct task_struct *tsk, int sig) * has exited by now, drop the reference count. */ guard(rcu)(); - target = posixtimer_get_target(tmr); + target = posixtimer_get_unignore_target(tmr); if (target) posixtimer_queue_sigqueue(&tmr->sigq, target, tmr->it_pid_type); else @@ -3132,42 +3193,36 @@ static void retarget_shared_pending(struct task_struct *tsk, sigset_t *which) void exit_signals(struct task_struct *tsk) { + LIST_HEAD(sigq_list); int group_stop = 0; - sigset_t unblocked; /* * @tsk is about to have PF_EXITING set - lock out users which - * expect stable threadgroup. + * expect a stable threadgroup. */ cgroup_threadgroup_change_begin(tsk); - if (thread_group_empty(tsk) || (tsk->signal->flags & SIGNAL_GROUP_EXIT)) { + scoped_guard(spinlock_irq, &tsk->sighand->siglock) { tsk->flags |= PF_EXITING; - cgroup_threadgroup_change_end(tsk); - return; - } - spin_lock_irq(&tsk->sighand->siglock); - /* - * From now this task is not visible for group-wide signals, - * see wants_signal(), do_signal_stop(). - */ - tsk->flags |= PF_EXITING; + sigqueue_dequeue_pending(&tsk->pending, &sigq_list); - cgroup_threadgroup_change_end(tsk); + if (task_sigpending(tsk) && !thread_group_empty(tsk) && + !(tsk->signal->flags & SIGNAL_GROUP_EXIT)) { + sigset_t unblocked = tsk->blocked; - if (!task_sigpending(tsk)) - goto out; + signotset(&unblocked); + retarget_shared_pending(tsk, &unblocked); - unblocked = tsk->blocked; - signotset(&unblocked); - retarget_shared_pending(tsk, &unblocked); + if (unlikely(tsk->jobctl & JOBCTL_STOP_PENDING) && + task_participate_group_stop(tsk)) + group_stop = CLD_STOPPED; + } + } - if (unlikely(tsk->jobctl & JOBCTL_STOP_PENDING) && - task_participate_group_stop(tsk)) - group_stop = CLD_STOPPED; -out: - spin_unlock_irq(&tsk->sighand->siglock); + cgroup_threadgroup_change_end(tsk); + + flush_sigqueue_list(&sigq_list); /* * If group stop has completed, deliver the notification. This From acb03d3881818581052924a9bbbe92b8741ed448 Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Fri, 11 Sep 2026 11:09:17 +0200 Subject: [PATCH 0579/1417] exec: Cleanup POSIX timers right after de_thread() A per-thread CPU timer holds a reference to the PID of the thread it is attached to and, while it is armed, its node is queued in that thread's posix_cputimers. The task is looked up by that PID. When a non-leader thread exec()s, de_thread() changes which task owns that PID. pid_task(timer->it.cpu.pid, PIDTYPE_PID) then returns NULL, but the node is still queued on tsk, which is alive. timer_lock_sighand() takes a failed lookup to mean that the node is already dequeued, so it has nothing to undo. begin_new_exec() calls posix_cpu_timers_exit(me) right after exec_task_namespaces() and that removes the leftover node, so the state normally stays invisible. But bprm->point_of_no_return is set before de_thread(), so if unshare_files(), set_mm_exe_file(), exec_mmap() or exec_task_namespaces() fails, the task dies before it gets there. exit_itimers() then frees the k_itimer while its node is still queued, and reaping tsk later erases that freed node from the rbtree. In short: the non-leader thread B the parent timer_create(CLOCK_THREAD_CPUTIME_ID) timer_settime() arm_timer() // the node is queued on B execve() de_thread(B) exchange_tids(B, leader) // B's PID now belongs to the leader release_task(leader) __exit_signal(leader) posix_cpu_timers_exit(leader) // cleans leader's queue, not B's __unhash_process(leader) // that PID has no task anymore exec_mmap() mmap_read_lock_killable(old_mm) kill(B, SIGKILL) // -EINTR get_signal() do_exit() exit_itimers() posix_timer_delete() posix_cpu_timer_del() posix_timer_unhash_and_free() // freed while still queued wait4() release_task(B) posix_cpu_timers_exit(B) cleanup_timerqueue() timerqueue_del() // use-after-free Move the POSIX timer cleanup right after de_thread() before any of the later failure conditions brings the task into do_exit(). [ tglx: Move the cleanup right after de_thread() ] Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") Signed-off-by: Hyunwoo Kim Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Oleg Nesterov Reviewed-by: Frederic Weisbecker Cc: stable@vger.kernel.org Link: https://patch.msgid.link/ao7Q8miiuLAPVnWv@v4bel Link: https://patch.msgid.link/20260911090541.627712075@kernel.org --- fs/exec.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index d3081c8f7c10c0..819643408e6df8 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1115,6 +1115,17 @@ static struct file *bprm_identity_file(const struct linux_binprm *bprm) return bprm->file; } +static void posixtimer_exec(struct task_struct *me) +{ +#ifdef CONFIG_POSIX_TIMERS + spin_lock_irq(&me->sighand->siglock); + posix_cpu_timers_exit(me); + spin_unlock_irq(&me->sighand->siglock); + exit_itimers(me); + flush_itimer_signals(); +#endif +} + /* * Calling this is the point of no return. None of the failures will be * seen by userspace since either the process is already taking a fatal @@ -1152,6 +1163,16 @@ int begin_new_exec(struct linux_binprm * bprm) retval = de_thread(me); if (retval) goto out; + + /* + * This must be done here to ensure that POSIX CPU timers which were + * armed on the current task are dequeued from me::posix_cputimers. + * Otherwise in case of a TID switch the deletion of the related POSIX + * timer would not remove an enqueued timer because the TID lookup + * of the old TID fails. + */ + posixtimer_exec(me); + /* see the comment in check_unsafe_exec() */ current->fs->in_exec = 0; /* @@ -1206,14 +1227,6 @@ int begin_new_exec(struct linux_binprm * bprm) if (retval) goto out_unlock; -#ifdef CONFIG_POSIX_TIMERS - spin_lock_irq(&me->sighand->siglock); - posix_cpu_timers_exit(me); - spin_unlock_irq(&me->sighand->siglock); - exit_itimers(me); - flush_itimer_signals(); -#endif - /* * Make the signal table private. */ From ef56085dfd1df3a53ecce8a4ff440cf6d67f430d Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Mon, 14 Sep 2026 12:07:44 +0200 Subject: [PATCH 0580/1417] pinctrl: sunxi: A523: fix voltage withstand encoding The Allwinner A523 uses the same GPIO voltage "withstand" programming (setting the input level voltage thresholds) as the previous SoCs, but for some odd reason inverts the encoding of 1.8V vs. 3.3V. Add a new bias voltage type to note this difference, and select it for the A523. At the same time also use the newer "CTL" version, which in addition allows to turn off the withstand programming for I/O voltages other than exact 1.8V or 3.3V (for instance for 2.5V sometimes used for Ethernet PHYs). The A523 has that enable register, but didn't use it so far. This fixes eMMC and reportedly Ethernet operation on some A523 boards. Fixes: 648be4cd9517 ("pinctrl: sunxi: Add support for the Allwinner A523") Signed-off-by: Andre Przywara Tested-by: Per Larsson Tested-by: Juan Manuel Lopez Carrillo Reviewed-by: Chen-Yu Tsai Tested-by: Chen-Yu Tsai # Fixes eMMC on Orange Pi 4A Signed-off-by: Linus Walleij --- drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c | 2 +- drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c | 2 +- drivers/pinctrl/sunxi/pinctrl-sunxi.c | 6 ++++++ drivers/pinctrl/sunxi/pinctrl-sunxi.h | 2 ++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c index 462aa1c4a5fa65..e27e4945def263 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c +++ b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c @@ -26,7 +26,7 @@ static const u8 a523_r_irq_bank_muxes[SUNXI_PINCTRL_MAX_BANKS] = static struct sunxi_pinctrl_desc a523_r_pinctrl_data = { .irq_banks = ARRAY_SIZE(a523_r_irq_bank_map), .irq_bank_map = a523_r_irq_bank_map, - .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_SEL, + .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV, .pin_base = PL_BASE, }; diff --git a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c index b6f78f1f30ac4a..88d8acd5bc2456 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c +++ b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c @@ -26,7 +26,7 @@ static const u8 a523_irq_bank_muxes[SUNXI_PINCTRL_MAX_BANKS] = static struct sunxi_pinctrl_desc a523_pinctrl_data = { .irq_banks = ARRAY_SIZE(a523_irq_bank_map), .irq_bank_map = a523_irq_bank_map, - .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_SEL, + .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV, }; static int a523_pinctrl_probe(struct platform_device *pdev) diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c index 2881a83be99c8a..31cd142ce0f798 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c @@ -728,6 +728,7 @@ static int sunxi_pinctrl_set_io_bias_cfg(struct sunxi_pinctrl *pctl, { unsigned short bank; unsigned long flags; + bool inverted = false; u32 val, reg; int uV; @@ -766,6 +767,9 @@ static int sunxi_pinctrl_set_io_bias_cfg(struct sunxi_pinctrl *pctl, reg &= ~IO_BIAS_MASK; writel(reg | val, pctl->membase + sunxi_grp_config_reg(pin)); return 0; + case BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV: + inverted = true; + fallthrough; case BIAS_VOLTAGE_PIO_POW_MODE_CTL: val = uV > 1800000 && uV <= 2500000 ? BIT(bank) : 0; @@ -780,6 +784,8 @@ static int sunxi_pinctrl_set_io_bias_cfg(struct sunxi_pinctrl *pctl, fallthrough; case BIAS_VOLTAGE_PIO_POW_MODE_SEL: val = uV <= 1800000 ? 1 : 0; + if (inverted) + val = !val; raw_spin_lock_irqsave(&pctl->lock, flags); reg = readl(pctl->membase + pctl->pow_mod_sel_offset); diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.h b/drivers/pinctrl/sunxi/pinctrl-sunxi.h index 498e88a19f71e6..8bd00c6ff62802 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.h +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.h @@ -117,8 +117,10 @@ enum sunxi_desc_bias_voltage { * Bias voltage is set through PIO_POW_MOD_SEL_REG * and PIO_POW_MOD_CTL_REG register, as seen on * A100 and D1 SoC, for example. + * Some SoCs invert the encoding for 1.8V vs. 3.3V. */ BIAS_VOLTAGE_PIO_POW_MODE_CTL, + BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV, }; struct sunxi_desc_function { From 1d9bb9c870632adea249cfdec49ac37e6f069164 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 00:03:12 -0400 Subject: [PATCH 0581/1417] pinctrl: single: free the IRQ on domain creation failure pcs_irq_init_chained_handler() requests a shared IRQ on affected SoCs, but its domain creation failure path only removes a chained handler. That does not release the action installed by request_irq(). The probe can continue without interrupt support while leaving the shared IRQ action registered. Use pcs_irq_free() to undo the appropriate type of handler registration. At this point pcs->domain is NULL, so the helper only releases the parent IRQ handler. Then mark the IRQ invalid, as the other initialization error paths already do, to prevent another release from a later probe unwind or remove. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 3e6cee1786a1 ("pinctrl: single: Add support for wake-up interrupts") Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Signed-off-by: Linus Walleij --- drivers/pinctrl/pinctrl-single.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/pinctrl/pinctrl-single.c b/drivers/pinctrl/pinctrl-single.c index 4d5f85b7e6bbe2..e0eb7240a98595 100644 --- a/drivers/pinctrl/pinctrl-single.c +++ b/drivers/pinctrl/pinctrl-single.c @@ -1628,7 +1628,8 @@ static int pcs_irq_init_chained_handler(struct pcs_device *pcs, &pcs_irqdomain_ops, pcs_soc); if (!pcs->domain) { - irq_set_chained_handler(pcs_soc->irq, NULL); + pcs_irq_free(pcs); + pcs_soc->irq = -1; return -EINVAL; } From e922bad8b2d5028c51a096d083fea41cd0987154 Mon Sep 17 00:00:00 2001 From: Itai Handler Date: Thu, 10 Sep 2026 20:48:32 +0300 Subject: [PATCH 0582/1417] spi: spi-zynqmp-gqspi: stop the controller on shutdown The driver has no ->shutdown, and platform_drv_shutdown() has no fallback of its own. Unlike pci_device_shutdown(), which clears bus mastering when kexec_in_progress, nothing on the platform bus disarms a device that can still write to memory. The normal kexec path never calls ->suspend either, so the quiesce in zynqmp_qspi_suspend() is not reached. A controller that is still executing a DMA read may therefore keep writing to memory across a kexec. QSPIDMA_DST_ADDR still points at memory owned by the kernel that called kexec, DST_SIZE is non-zero and the flash is still clocked, so data can keep landing in RAM while the new kernel is being relocated, and after it has started executing. That destination is a physical address which means nothing to the new kernel, so the writes can corrupt whatever now occupies it: kernel text or data, page tables, or the initrd. Nothing reports an error and the resulting behaviour is undefined. This can be observed by reading GQSPI_EN (offset 0x114) and QSPIDMA_DST_ADDR/SIZE/STS/CTRL (offsets 0x800 to 0x80c) early in the new kernel, before the driver probes: without this patch GQSPI_EN reads 1 and QSPIDMA_DST_ADDR still points into the previous kernel's memory. Add a ->shutdown that stops the controller the way zynqmp_qspi_suspend() already does. spi_controller_suspend() stops the queue, waits for a message that is already executing and makes any later transfer fail with -ESHUTDOWN, so nothing can be cut short by the register write that follows. It may sleep, which is fine here: device_shutdown() runs in process context. Unlike ->suspend this cannot abort on error, because a controller left mastering the bus is worse than a truncated transfer, so a failure to drain is only logged. GQSPI_EN_OFST is then cleared, as zynqmp_qspi_remove() and zynqmp_qspi_suspend() already do. Skip that write only when pm_runtime_get_if_in_use() returns 0, i.e. runtime suspended: the clocks are gated, so the registers are unreachable and the controller cannot be mastering the bus. A negative return is not the same thing - it is what the CONFIG_PM=n stub always returns, and there probe() has enabled pclk and refclk for good, so the controller is running and must be stopped. Fixes: dfe11a11d523 ("spi: Add support for Zynq Ultrascale+ MPSoC GQSPI controller") Cc: stable@vger.kernel.org Signed-off-by: Itai Handler Link: https://patch.msgid.link/20260910174832.873352-1-itai.handler@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-zynqmp-gqspi.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/spi/spi-zynqmp-gqspi.c b/drivers/spi/spi-zynqmp-gqspi.c index 4d55090fa44337..15e9d3ef8839d6 100644 --- a/drivers/spi/spi-zynqmp-gqspi.c +++ b/drivers/spi/spi-zynqmp-gqspi.c @@ -1373,11 +1373,45 @@ static void zynqmp_qspi_remove(struct platform_device *pdev) clk_disable_unprepare(xqspi->pclk); } +static void zynqmp_qspi_shutdown(struct platform_device *pdev) +{ + struct zynqmp_qspi *xqspi = platform_get_drvdata(pdev); + int ret; + + /* + * Stop the queue and reject any later transfer first, so the write + * below cannot cut into a message that is still being executed. + * Unlike ->suspend this cannot abort on error: a controller left + * mastering the bus is worse than a truncated transfer. + */ + ret = spi_controller_suspend(xqspi->ctlr); + if (ret) + dev_warn(&pdev->dev, "could not stop the queue: %d\n", ret); + + /* + * Only a runtime suspended controller can be left alone: its clocks + * are gated, so it cannot be mastering the bus, and its registers + * must not be accessed either. Any other answer means it may be + * running and has to be stopped. In particular, on a kernel built + * without runtime PM this returns -EINVAL, and there the clocks + * enabled in probe() are never gated at all. + */ + ret = pm_runtime_get_if_in_use(&pdev->dev); + if (!ret) + return; + + zynqmp_gqspi_write(xqspi, GQSPI_EN_OFST, 0x0); + + if (ret > 0) + pm_runtime_put_noidle(&pdev->dev); +} + MODULE_DEVICE_TABLE(of, zynqmp_qspi_of_match); static struct platform_driver zynqmp_qspi_driver = { .probe = zynqmp_qspi_probe, .remove = zynqmp_qspi_remove, + .shutdown = zynqmp_qspi_shutdown, .driver = { .name = "zynqmp-qspi", .of_match_table = zynqmp_qspi_of_match, From f033482d76a9f18080c7a40c5f9c678bd7adc8f3 Mon Sep 17 00:00:00 2001 From: Christiano Amora Date: Wed, 16 Sep 2026 10:46:22 -0300 Subject: [PATCH 0583/1417] Bluetooth: SMP: reject Security Request over BR/EDR Bose QC Ultra Headphones (dual-mode, same public address on both transports) occasionally send an SMP Security Request on the BR/EDR SMP fixed channel right after the ACL link is encrypted. The kernel handles it as if it were an LE link: smp_cmd_security_req() has no transport check, smp_ltk_encrypt() looks up an LTK with the ACL connection's dst_type, and hci_find_ltk() matches the peer's LE LTK because the LE public address type is stored as ADDR_LE_DEV_PUBLIC (0), the same value as BDADDR_BREDR. HCI_OP_LE_START_ENC is then issued on the ACL handle, the controller rejects it with Invalid HCI Command Parameters, and hci_cs_le_start_enc() disconnects the link with HCI_ERROR_AUTH_FAILURE. The headphones drop within a second of connecting, before any profile is up; a manual reconnect works. btmon (MediaTek MT7922, kernel 7.0.12): > HCI Event: Encryption Change (0x08) plen 4 Status: Success (0x00) Handle: 50 Address: BC:87:FA:47:73:5E (Bose Corporation) Encryption: Enabled with AES-CCM (0x02) > ACL Data RX: Handle 50 flags 0x02 dlen 6 BR/EDR SMP: Security Request (0x0b) len 1 Authentication requirement: No bonding, No MITM, SC (0x08) < HCI Command: LE Start Encryption (0x08|0x0019) plen 28 Handle: 50 Address: BC:87:FA:47:73:5E (Bose Corporation) > HCI Event: Command Status (0x0f) plen 4 LE Start Encryption (0x08|0x0019) ncmd 1 Status: Invalid HCI Command Parameters (0x12) < HCI Command: Disconnect (0x01|0x0006) plen 3 Handle: 50 Address: BC:87:FA:47:73:5E (Bose Corporation) Reason: Authentication Failure (0x05) SMP over BR/EDR is limited to cross-transport key derivation; the Security Request procedure (Core Specification Vol 3, Part H, Section 2.4.6, PDU in Section 3.6.7) has no BR/EDR counterpart. Reply with Pairing Failed / Command Not Supported on a non-LE link, before the PDU is parsed, and keep the connection. The reply is sent directly rather than through smp_failure(): rejecting a command on the wrong transport is not an authentication failure, and MGMT_EV_AUTH_FAILED would make bluetoothd disconnect the device. Tested on the affected host (kernel 7.0.12, MediaTek MT7922, Bose QC Ultra) with the patched module built out of tree: 7 days and 49 reconnects without a drop, against 2 drops in the 3 days before the patch. Every disconnect in that week had a userspace or remote reason. Fixes: b5ae344d4c0f ("Bluetooth: Add full SMP BR/EDR support") Assisted-by: LLM Signed-off-by: Christiano Amora Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/smp.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c index 6091c47cb00284..d23f9d0729c40e 100644 --- a/net/bluetooth/smp.c +++ b/net/bluetooth/smp.c @@ -2269,6 +2269,23 @@ static u8 smp_cmd_security_req(struct l2cap_conn *conn, struct sk_buff *skb) bt_dev_dbg(hdev, "conn %p", conn); + /* SMP over BR/EDR only covers cross-transport key derivation; the + * Security Request procedure has no BR/EDR counterpart. Reject it + * here, otherwise smp_ltk_encrypt() finds the peer's LE LTK + * (ADDR_LE_DEV_PUBLIC and BDADDR_BREDR are both 0) and issues + * HCI_OP_LE_START_ENC on the ACL handle, which the controller + * rejects and hci_cs_le_start_enc() turns into a disconnect. Reply + * without smp_failure(): this is not an authentication failure, and + * MGMT_EV_AUTH_FAILED would make bluetoothd drop the device. + */ + if (hcon->type != LE_LINK) { + u8 reason = SMP_CMD_NOTSUPP; + + smp_send_cmd(conn, SMP_CMD_PAIRING_FAIL, sizeof(reason), + &reason); + return 0; + } + if (skb->len < sizeof(*rp)) return SMP_INVALID_PARAMS; From f2bbb36426581045a8bf7793da5419b9375e4348 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Tue, 15 Sep 2026 19:17:18 -0700 Subject: [PATCH 0584/1417] Bluetooth: btnxpuart: Fix skb leak in nxp_process_fw_dump() When CONFIG_DEV_COREDUMP=n, hci_devcd_append() returns -EOPNOTSUPP without freeing its skb argument. This leaks the cloned skb and also prevents nxp_set_ind_reset() from being called to perform recovery. Fix by guarding the hci_devcd_append(hdev, skb_clone(skb, GFP_ATOMIC)) call with IS_ENABLED(CONFIG_DEV_COREDUMP). Fixes: 998e447f443f ("Bluetooth: btnxpuart: Add support for HCI coredump feature") Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btnxpuart.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c index 25e7b41b349f12..4e23d71d00e8e0 100644 --- a/drivers/bluetooth/btnxpuart.c +++ b/drivers/bluetooth/btnxpuart.c @@ -1388,9 +1388,11 @@ static int nxp_process_fw_dump(struct hci_dev *hdev, struct sk_buff *skb) msecs_to_jiffies(20000)); } - err = hci_devcd_append(hdev, skb_clone(skb, GFP_ATOMIC)); - if (err < 0) - goto free_skb; + if (IS_ENABLED(CONFIG_DEV_COREDUMP)) { + err = hci_devcd_append(hdev, skb_clone(skb, GFP_ATOMIC)); + if (err < 0) + goto free_skb; + } if (buf_len == 0) { bt_dev_warn(hdev, "==== FW dump complete ==="); From 71af682ba4692c2ed9ace4c3d4ca462ae368c029 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Tue, 15 Sep 2026 12:08:22 +0000 Subject: [PATCH 0585/1417] Bluetooth: mgmt: Dequeue pending mesh_send_sync entries on cancel In send_cancel(), pending mesh_tx objects are removed from the hdev->mesh_pending list and freed via mesh_send_complete(). However, if a mesh transmission was already queued onto hdev->cmd_sync_work_list via mesh_next(), the queued entry retains a raw pointer to mesh_tx. When hci_cmd_sync_work later processes the entry, it attempts to execute mesh_send_sync and its destroy callback mesh_send_start_complete using the already freed mesh_tx pointer, leading to a use-after-free. Fix this by invoking hci_cmd_sync_dequeue() for mesh_send_sync on the target mesh_tx before completing it. If the entry is found and dequeued, its destroy callback will complete and free the object; otherwise, mesh_send_complete() is called directly. Additionally, ensure the transmission queue advances after cancellation or errors. In mesh_send_start_complete(), call mesh_next() on error unless err is -ECANCELED, because hci_cmd_sync_dequeue() holds hdev->cmd_sync_work_lock and calling mesh_next() synchronously would deadlock. Instead, advance the queue in send_cancel() once the lock is released and if no transmission is in progress. Fixes: b338d91703fa ("Bluetooth: Implement support for Mesh") Signed-off-by: Lee Jones Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index ac4864e56ec727..f740e745ae7883 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -2316,6 +2316,8 @@ static void mesh_send_start_complete(struct hci_dev *hdev, void *data, int err) hci_dev_clear_flag(hdev, HCI_MESH_SENDING); /* Send Complete Error Code for handle */ mesh_send_complete(hdev, mesh_tx, false); + if (err != -ECANCELED) + mesh_next(hdev, NULL, 0); return; } @@ -2425,19 +2427,28 @@ static int send_cancel(struct hci_dev *hdev, void *data) do { mesh_tx = mgmt_mesh_next(hdev, cmd->sk); - if (mesh_tx) - mesh_send_complete(hdev, mesh_tx, false); + if (mesh_tx) { + if (!hci_cmd_sync_dequeue(hdev, mesh_send_sync, + mesh_tx, NULL)) + mesh_send_complete(hdev, mesh_tx, false); + } } while (mesh_tx); } else { mesh_tx = mgmt_mesh_find(hdev, cancel->handle); - if (mesh_tx && mesh_tx->sk == cmd->sk) - mesh_send_complete(hdev, mesh_tx, false); + if (mesh_tx && mesh_tx->sk == cmd->sk) { + if (!hci_cmd_sync_dequeue(hdev, mesh_send_sync, + mesh_tx, NULL)) + mesh_send_complete(hdev, mesh_tx, false); + } } mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_MESH_SEND_CANCEL, 0, NULL, 0); + if (!hci_dev_test_flag(hdev, HCI_MESH_SENDING)) + mesh_next(hdev, NULL, 0); + return 0; } From e06d549fcd4a0ba381ed67ddf1ab3c7a6ca4314c Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 13:04:29 -0300 Subject: [PATCH 0586/1417] Bluetooth: hci_conn: fix CIS hold ownership on reuse Commit 69997d50ec57 ("Bluetooth: ISO: handle bound CIS cleanup via hci_conn") made hci_bind_cis() and hci_connect_cis() return a connection with one hold for the ISO layer. hci_bind_cis() currently takes that hold only after configuring a CIS, so its BT_CONNECTED and matching BT_BOUND paths return a bare lookup result. Its configuration failure path can likewise call hci_conn_drop() before taking a hold. Take the hold before any state-dependent return or configuration error so every successful return follows the documented ownership contract and every error drop is balanced. hci_connect_cis() also assumes hci_conn_link() always takes a new CIS hold before dropping the one returned by hci_bind_cis(). However, the helper returns an existing link without taking another hold. In that case, preserve the CIS hold for the caller and drop the redundant LE hold because the existing link already owns its parent hold. Returning early also avoids changing an existing CIS back to BT_CONNECT. Fixes: 69997d50ec57 ("Bluetooth: ISO: handle bound CIS cleanup via hci_conn") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index fa72cf8aaa7a0a..e32bb9c342a5f5 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -2079,6 +2079,8 @@ struct hci_conn *hci_bind_cis(struct hci_dev *hdev, bdaddr_t *dst, cis->conn_timeout = timeout; } + hci_conn_hold(cis); + if (cis->state == BT_CONNECTED) return cis; @@ -2120,7 +2122,6 @@ struct hci_conn *hci_bind_cis(struct hci_dev *hdev, bdaddr_t *dst, return ERR_PTR(-EINVAL); } - hci_conn_hold(cis); cis->state = BT_BOUND; return cis; @@ -2497,6 +2498,12 @@ struct hci_conn *hci_connect_cis(struct hci_dev *hdev, bdaddr_t *dst, return cis; } + /* The existing link already owns the hold on its parent. */ + if (cis->link) { + hci_conn_drop(le); + return cis; + } + link = hci_conn_link(le, cis); hci_conn_drop(cis); if (!link) { From 0fcd4dad555c96e0bd3a1b8c569f989be85c7341 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 13:04:30 -0300 Subject: [PATCH 0587/1417] Bluetooth: ISO: release unused CIS holds after channel attach hci_bind_cis() and hci_connect_cis() return one hci_conn hold for the ISO layer. A new channel association consumes that hold, which is eventually released by iso_conn_free(). There are two cases where iso_chan_add() does not create an association: it returns success when the socket is already attached to the same iso_conn, and it returns -EBUSY when another socket is attached. The hold returned for the current call is unused in both cases. This occurs when deferred setup calls iso_connect_cis() again for its existing socket, or when another socket attempts to reuse the CIS. Detect the idempotent case while the connection is locked and release the unused hold after iso_chan_add(). Also release it on -EBUSY. Do not drop it for other errors: a newly allocated iso_conn releases the transferred hold when its last temporary reference is put. Fixes: 69997d50ec57 ("Bluetooth: ISO: handle bound CIS cleanup via hci_conn") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index eb99653f33f919..7657c2a0abbf7c 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -496,6 +496,7 @@ static int iso_connect_cis(struct sock *sk) struct hci_dev *hdev; bdaddr_t src, dst; u8 src_type; + bool already_attached; int err; lock_sock(sk); @@ -568,8 +569,14 @@ static int iso_connect_cis(struct sock *sk) goto unlock; } + iso_conn_lock(conn); + already_attached = iso_pi(sk)->conn == conn && conn->sk == sk; + iso_conn_unlock(conn); + err = iso_chan_add(conn, sk, NULL); iso_conn_put(conn); + if (already_attached || err == -EBUSY) + hci_conn_drop(hcon); if (err) goto unlock; From e93fad891c72deb84cae49430163b384ebcc92b1 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 13:03:58 -0300 Subject: [PATCH 0588/1417] Bluetooth: hci_sock: reject out-of-range OCF values The raw HCI socket security filter has 128 OCF bits per supported OGF, but masks the 10-bit OCF with 127 before looking up the command. An unprivileged socket can therefore submit a reserved OCF that aliases an allowlisted command modulo 128. A conforming controller should reject reserved opcodes. Nevertheless, the security decision must apply to the opcode that will actually be sent, especially since controller-specific behavior is outside the host stack's control. Reject OCF values that cannot be represented by the security filter instead of aliasing them onto an unrelated command. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sock.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 070ca388f9ace8..6413593eee5155 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -1881,7 +1881,8 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg, u16 ocf = hci_opcode_ocf(opcode); if (((ogf > HCI_SFLT_MAX_OGF) || - !hci_test_bit(ocf & HCI_FLT_OCF_BITS, + (ocf > HCI_FLT_OCF_BITS) || + !hci_test_bit(ocf, &hci_sec_filter.ocf_mask[ogf])) && !capable(CAP_NET_RAW)) { err = -EPERM; From b0a6cf99afd57a39598b1beca0e86ef5004980de Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 13:03:07 -0300 Subject: [PATCH 0589/1417] Bluetooth: hci_sock: validate event length before filtering is_filtered_packet() reads the event code from skb->data[0] without first checking that the skb is nonempty. When an opcode filter is configured, it also reads the command opcode at offsets 3 or 4 without checking that a Command Complete or Command Status event is long enough. hci_send_to_sock() invokes the filter before hci_event_packet() validates the event header. A malformed event supplied by a controller or a vhci device can therefore cause an out-of-bounds read. Keep the unmasked event code for the opcode checks. The masked value is needed for the 64-bit event bitmap, but using it to identify command events aliases event codes above 0x3f. In particular, Synchronous Train Complete (0x4f) was treated as Command Status (0x0f) even though its payload has no opcode. Reject actual command events that are too short for the field being inspected. A truncated command event cannot match a configured opcode. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sock.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 6413593eee5155..6d56c77741e194 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -164,6 +164,7 @@ static bool is_filtered_packet(struct sock *sk, struct sk_buff *skb) { struct hci_filter *flt; int flt_type, flt_event; + u8 event; /* Apply filter */ flt = &hci_pi(sk)->filter; @@ -177,7 +178,11 @@ static bool is_filtered_packet(struct sock *sk, struct sk_buff *skb) if (hci_skb_pkt_type(skb) != HCI_EVENT_PKT) return false; - flt_event = (*(__u8 *)skb->data & HCI_FLT_EVENT_BITS); + if (skb->len < 1) + return true; + + event = *(__u8 *)skb->data; + flt_event = event & HCI_FLT_EVENT_BITS; if (!hci_test_bit(flt_event, &flt->event_mask)) return true; @@ -186,11 +191,17 @@ static bool is_filtered_packet(struct sock *sk, struct sk_buff *skb) if (!flt->opcode) return false; - if (flt_event == HCI_EV_CMD_COMPLETE && + if (event == HCI_EV_CMD_COMPLETE && skb->len < 5) + return true; + + if (event == HCI_EV_CMD_COMPLETE && flt->opcode != get_unaligned((__le16 *)(skb->data + 3))) return true; - if (flt_event == HCI_EV_CMD_STATUS && + if (event == HCI_EV_CMD_STATUS && skb->len < 6) + return true; + + if (event == HCI_EV_CMD_STATUS && flt->opcode != get_unaligned((__le16 *)(skb->data + 4))) return true; From 4c94557dd02569efa6c1072a0439addaef9a5224 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 13:03:32 -0300 Subject: [PATCH 0590/1417] Bluetooth: ISO: balance the parent hold in hci_bind_bis() hci_conn_link() takes a lifetime reference to its parent with hci_conn_get(), but only takes an operational hold on the child. hci_conn_unlink() later balances both a hold and a reference on the parent. The SCO and CIS paths pass a parent acquired from a connect helper, so it already has a hold. For an additional BIS, hci_bind_bis() obtains the parent from hci_conn_hash_lookup_big(), which returns a bare pointer. Unlinking the child then drops the parent's existing hold and can schedule it for disconnection while its socket is still using it. Take a hold on the parent before linking it and drop that hold if linking fails. A successful link transfers the hold to hci_conn_unlink(). Fixes: fa224d0c094a ("Bluetooth: ISO: Reassociate a socket with an active BIS") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index e32bb9c342a5f5..96195d2fd10fca 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -2375,10 +2375,13 @@ struct hci_conn *hci_bind_bis(struct hci_dev *hdev, bdaddr_t *dst, __u8 sid, parent = hci_conn_hash_lookup_big(hdev, conn->iso_qos.bcast.big); if (parent && parent != conn) { + hci_conn_hold(parent); link = hci_conn_link(parent, conn); hci_conn_drop(conn); - if (!link) + if (!link) { + hci_conn_drop(parent); return ERR_PTR(-ENOLINK); + } } return conn; From 6c78a213d9070b610c7f418af2c25b66180b7e37 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 13:02:39 -0300 Subject: [PATCH 0591/1417] Bluetooth: L2CAP: validate frame length before control and FCS access l2cap_data_rcv() unpacks either a two-byte or four-byte control field without first ensuring that it is present. A short ERTM or streaming-mode frame can therefore cause an out-of-bounds read. There is a second short-frame case when CRC16 is enabled. After the control field is pulled, l2cap_check_fcs() subtracts two from skb->len without checking it. If fewer than two bytes remain, the subtraction wraps; skb_trim() leaves the buffer unchanged and the subsequent FCS load reads past the logical end of the frame. Validate that the frame contains both its control field and, when enabled, its FCS before either field is accessed. Fixes: 1c2acffb76d4 ("Bluetooth: Add initial support for ERTM packets transfers") Fixes: fcc203c30d72 ("Bluetooth: Add support for FCS option to L2CAP") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_core.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 644e31160d55bc..aaa2a1cd489a53 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -6702,9 +6702,17 @@ static int l2cap_stream_rx(struct l2cap_chan *chan, struct l2cap_ctrl *control, static int l2cap_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb) { struct l2cap_ctrl *control = &bt_cb(skb)->l2cap; - u16 len; + u16 len, min_len; u8 event; + min_len = test_bit(FLAG_EXT_CTRL, &chan->flags) ? + L2CAP_EXT_CTRL_SIZE : L2CAP_ENH_CTRL_SIZE; + if (chan->fcs == L2CAP_FCS_CRC16) + min_len += L2CAP_FCS_SIZE; + + if (skb->len < min_len) + goto drop; + __unpack_control(chan, skb); len = skb->len; From b5dbb41b212c50c095a4dbee3017a84fe94f033b Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 12:59:52 -0300 Subject: [PATCH 0592/1417] Bluetooth: mgmt: fix race in read_unconf_index_list() read_unconf_index_list() counts unconfigured controllers before allocating its response, then checks the device flags again while filling it. hci_dev_list_lock stabilizes list membership, but it does not serialize the per-device flags. During asynchronous controller setup, the worker can set HCI_UNCONFIGURED and clear HCI_SETUP between the two passes. A controller omitted from the allocation count can then become eligible for the fill pass, causing an out-of-bounds write to rp->index[]. Allocate space for every device on hci_dev_list. Since list membership cannot change while hci_dev_list_lock is held, the response remains large enough regardless of flag transitions. The reported count and response length still include only eligible unconfigured controllers. Fixes: 73d1df2a7a10 ("Bluetooth: Add support for Read Unconfigured Index List command") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index f740e745ae7883..41956cdde982f6 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -496,13 +496,9 @@ static int read_unconf_index_list(struct sock *sk, struct hci_dev *hdev, read_lock(&hci_dev_list_lock); - count = 0; - list_for_each_entry(d, &hci_dev_list, list) { - if (hci_dev_test_flag(d, HCI_UNCONFIGURED)) - count++; - } + count = list_count_nodes(&hci_dev_list); - rp_len = sizeof(*rp) + (2 * count); + rp_len = sizeof(*rp) + (sizeof(__le16) * count); rp = kmalloc(rp_len, GFP_ATOMIC); if (!rp) { read_unlock(&hci_dev_list_lock); From ed7d8ed9a915633f90b0cf528b096abc80e250c9 Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Tue, 15 Sep 2026 09:49:40 +0800 Subject: [PATCH 0593/1417] ASoC: codecs: nau8360: Fix misleading return in nau8360_message_to_dsp() Fix a smatch static analysis warning: "missing error code? 'ret'". When there are no message parameters (!cmd_info->msg_param), 'ret' is guaranteed to be 0. Return 0 directly. Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202609081933.K4tVJnyh-lkp@intel.com/ Signed-off-by: Neo Chang Link: https://patch.msgid.link/20260915014940.317403-1-YLCHANG2@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8360-dsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/nau8360-dsp.c b/sound/soc/codecs/nau8360-dsp.c index 2e5b61bfc30324..65ffec8b3a39d7 100644 --- a/sound/soc/codecs/nau8360-dsp.c +++ b/sound/soc/codecs/nau8360-dsp.c @@ -211,7 +211,7 @@ static int nau8360_message_to_dsp(struct snd_soc_component *cp, snd_soc_component_write(cp, dsp_addr, payload); if (!cmd_info->msg_param) - return ret; + return 0; /* sending payload + padding */ payload = nau8360_pack_param(param_offset, param_size); From 6d5cd3882ce57f7a55c1ba47e64f946496d51264 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Tue, 15 Sep 2026 10:30:05 +0800 Subject: [PATCH 0594/1417] ASoC: codecs: ES8326: Add private members about HPF Add private members related to HPF. Add Kcontrol for HPF Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260915023006.22940-2-zhangyi@everest-semi.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8326.c | 124 +++++++++++++++++++++++++++++++++++++- sound/soc/codecs/es8326.h | 4 ++ 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c index b6eadc2e96598f..47d4efea49212e 100644 --- a/sound/soc/codecs/es8326.c +++ b/sound/soc/codecs/es8326.c @@ -33,11 +33,16 @@ struct es8326_priv { * while enabling or disabling or during an irq. */ struct mutex lock; + struct mutex hpf_lock; u8 jack_pol; u8 interrupt_src; u8 interrupt_clk; u8 hpl_vol; u8 hpr_vol; + u8 hpfl; + u8 hpfr; + u32 hpf_freq; + u32 capture_rate; bool jd_inverted; unsigned int sysclk; @@ -47,6 +52,48 @@ struct es8326_priv { int jack_remove_retry; }; +static const u32 hpf_table[10][10] = { + {1020, 754, 624, 559, 527, 511, 502, 498, 497, 496}, + {754, 495, 368, 306, 274, 259, 251, 247, 246, 244}, + {624, 368, 243, 182, 151, 136, 128, 124, 123, 121}, + {559, 306, 182, 120, 90, 75, 68, 63, 62, 60}, + {527, 274, 151, 90, 60, 45, 38, 33, 32, 31}, + {511, 259, 136, 75, 45, 30, 23, 19, 18, 17}, + {502, 251, 128, 68, 38, 23, 16, 13, 11, 11}, + {498, 247, 124, 63, 33, 19, 13, 10, 8, 8}, + {497, 246, 123, 62, 32, 18, 11, 8, 8, 0}, + {496, 244, 121, 60, 31, 17, 11, 8, 0, 0} +}; + +static bool find_best_hpf_freq(u32 target_hz, u8 *hpf1, u8 *hpf2) +{ + int best_row = -1, best_col = -1; + u32 min_diff = U32_MAX; + u32 f, diff; + int i, j; + + if (target_hz > 1020) + return false; + + for (i = 0; i < 10; i++) { + for (j = i; j < 10; j++) { + f = hpf_table[i][j]; + + diff = (target_hz > f) ? (target_hz - f) : (f - target_hz); + if (diff < min_diff) { + min_diff = diff; + best_row = i; + best_col = j; + } + } + } + + *hpf1 = best_col + ES8326_HPF_OFFSET; + *hpf2 = best_row + ES8326_HPF_OFFSET; + + return true; +} + static int es8326_crosstalk1_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -189,6 +236,48 @@ static int es8326_hprvol_set(struct snd_kcontrol *kcontrol, return 0; } +static int es8326_hpf_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = es8326->hpf_freq; + return 0; +} + +static int es8326_hpf_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + u32 freq; + bool hpf; + + if ((ucontrol->value.integer.value[0] > 1020) || (ucontrol->value.integer.value[0] < 0)) + return -EBUSY; + + if (es8326->hpf_freq == ucontrol->value.integer.value[0]) + return 0; + + if (es8326->capture_rate) { + guard(mutex)(&es8326->hpf_lock); + freq = (ucontrol->value.integer.value[0] * 48000) / es8326->capture_rate; + + hpf = find_best_hpf_freq(freq, &es8326->hpfl, &es8326->hpfr); + if (!hpf) + return -EBUSY; + + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS1, 0x0f, es8326->hpfl); + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS2, 0x0f, es8326->hpfr); + } else { + dev_dbg_ratelimited(component->dev, "PCM_STREAM_CAPTURE is not active.retain the input frequency\n"); + } + es8326->hpf_freq = ucontrol->value.integer.value[0]; + + return 1; +} + static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(dac_vol_tlv, -9550, 50, 0); static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(adc_vol_tlv, -9550, 50, 0); static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(adc_analog_pga_tlv, 0, 300, 0); @@ -269,6 +358,8 @@ static const struct snd_kcontrol_new es8326_snd_controls[] = { SOC_ENUM("ALC Capture Winsize", alc_winsize), SOC_SINGLE_TLV("ALC Capture Target Level", ES8326_ALC_LEVEL, 0, 0x0f, 0, drc_target_tlv), + SOC_SINGLE_EXT("ADC HPF Freq Select", SND_SOC_NOPM, 0, 1020, 0, + es8326_hpf_get, es8326_hpf_set), SOC_SINGLE_EXT("CROSSTALK1", SND_SOC_NOPM, 0, 31, 0, es8326_crosstalk1_get, es8326_crosstalk1_set), @@ -353,8 +444,6 @@ static bool es8326_writeable_register(struct device *dev, unsigned int reg) case ES8326_BIAS_SW2: case ES8326_BIAS_SW3: case ES8326_BIAS_SW4: - case ES8326_ADC_HPFS1: - case ES8326_ADC_HPFS2: return false; default: return true; @@ -558,6 +647,8 @@ static int es8326_pcm_hw_params(struct snd_pcm_substream *substream, struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); u8 srate = 0; int coeff, array; + u32 freq; + bool hpf; if (es8326->version == 0) { coeff_div = coeff_div_v0; @@ -612,6 +703,28 @@ static int es8326_pcm_hw_params(struct snd_pcm_substream *substream, dev_warn(component->dev, "Clock coefficients do not match"); } + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + es8326->capture_rate = params_rate(params); + freq = (es8326->hpf_freq * 48000) / params_rate(params); + hpf = find_best_hpf_freq(freq, &es8326->hpfl, &es8326->hpfr); + if (!hpf) { + dev_err(component->dev, "The HPF frequency is invalid\n"); + return -EINVAL; + } + } + + return 0; +} + +static int es8326_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + es8326->capture_rate = 0; + return 0; } @@ -636,6 +749,8 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x40); regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x30); } + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS1, 0x0f, 0x04); + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS2, 0x0f, 0x04); } } else { if (!es8326->calibrated) { @@ -728,6 +843,7 @@ static int es8326_set_bias_level(struct snd_soc_component *codec, static const struct snd_soc_dai_ops es8326_ops = { .hw_params = es8326_pcm_hw_params, + .hw_free = es8326_pcm_hw_free, .set_fmt = es8326_set_dai_fmt, .set_sysclk = es8326_set_dai_sysclk, .mute_stream = es8326_mute, @@ -781,6 +897,8 @@ static void es8326_capture_pop_handler(struct work_struct *work) struct es8326_priv *es8326 = container_of(work, struct es8326_priv, capture_pop_work.work); + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS1, 0x0f, es8326->hpfl); + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS2, 0x0f, es8326->hpfr); regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, 0x0F, 0x00); } @@ -1202,6 +1320,7 @@ static int es8326_probe(struct snd_soc_component *component) } dev_dbg(component->dev, "interrupt-clk %x", es8326->interrupt_clk); + es8326->hpf_freq = ES8326_HPF_DEFAULT; es8326_init(component); return 0; } @@ -1288,6 +1407,7 @@ static int es8326_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, es8326); es8326->i2c = i2c; mutex_init(&es8326->lock); + mutex_init(&es8326->hpf_lock); es8326->regmap = devm_regmap_init_i2c(i2c, &es8326_regmap_config); if (IS_ERR(es8326->regmap)) { ret = PTR_ERR(es8326->regmap); diff --git a/sound/soc/codecs/es8326.h b/sound/soc/codecs/es8326.h index 1c5b3ec70a1e9d..c78d528e7dfe1f 100644 --- a/sound/soc/codecs/es8326.h +++ b/sound/soc/codecs/es8326.h @@ -149,6 +149,10 @@ #define ES8326_ADC_SRC_DMIC_SDIN3 6 #define ES8326_ADC_SRC_DMIC_SDIN3_INV 7 +/* HPF Settings */ +#define ES8326_HPF_DEFAULT 8 +#define ES8326_HPF_OFFSET 4 + #define ES8326_ADC_AMIC ((ES8326_ADC_SRC_ANA_INV_SW1 << ES8326_ADC2_SHIFT) \ | (ES8326_ADC_SRC_ANA_INV_SW1 << ES8326_ADC1_SHIFT)) #define ES8326_ADC_DMIC ((ES8326_ADC_SRC_DMIC_SDIN2 << ES8326_ADC2_SHIFT) \ From b05772f71877011948b0995eb102279b01ef918a Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Tue, 15 Sep 2026 10:30:06 +0800 Subject: [PATCH 0595/1417] ASoC: codecs: ES8326: Adjust the standby configuration Adjust the configuration in SND_SOC_BIAS_STANDBY for better power consumption performance Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260915023006.22940-3-zhangyi@everest-semi.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8326.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c index 47d4efea49212e..918164005037aa 100644 --- a/sound/soc/codecs/es8326.c +++ b/sound/soc/codecs/es8326.c @@ -820,7 +820,8 @@ static int es8326_set_bias_level(struct snd_soc_component *codec, case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: - regmap_write(es8326->regmap, ES8326_ANA_PDN, 0x3b); + regmap_write(es8326->regmap, ES8326_PGA_PDN, 0x58); + regmap_write(es8326->regmap, ES8326_ANA_PDN, 0x13); regmap_update_bits(es8326->regmap, ES8326_CLK_CTL, 0x20, 0x00); regmap_write(es8326->regmap, ES8326_SDINOUT1_IO, ES8326_IO_INPUT); if (es8326->version > ES8326_VERSION_B) { @@ -983,7 +984,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) struct es8326_priv *es8326 = container_of(work, struct es8326_priv, jack_detect_work.work); struct snd_soc_component *comp = es8326->component; - unsigned int iface; + unsigned int iface, pga_status; guard(mutex)(&es8326->lock); iface = snd_soc_component_read(comp, ES8326_HPDET_STA); @@ -1079,12 +1080,19 @@ static void es8326_jack_detect_handler(struct work_struct *work) dev_dbg(comp->dev, "Headset detected\n"); snd_soc_jack_report(es8326->jack, SND_JACK_HEADSET, SND_JACK_HEADSET); - regmap_update_bits(es8326->regmap, ES8326_PGA_PDN, - 0x08, 0x08); - regmap_write(es8326->regmap, ES8326_ADC1_SRC, 0x00); - regmap_write(es8326->regmap, ES8326_ADC2_SRC, 0x00); - regmap_update_bits(es8326->regmap, ES8326_PGA_PDN, - 0x08, 0x00); + pga_status = snd_soc_component_read(comp, ES8326_PGA_PDN); + + if ((pga_status & 0x08) >> 3) { + regmap_write(es8326->regmap, ES8326_ADC1_SRC, 0x00); + regmap_write(es8326->regmap, ES8326_ADC2_SRC, 0x00); + } else { + regmap_update_bits(es8326->regmap, ES8326_PGA_PDN, + 0x08, 0x08); + regmap_write(es8326->regmap, ES8326_ADC1_SRC, 0x00); + regmap_write(es8326->regmap, ES8326_ADC2_SRC, 0x00); + regmap_update_bits(es8326->regmap, ES8326_PGA_PDN, + 0x08, 0x00); + } usleep_range(10000, 15000); } } From d034e836eefd7ce75e588f7031cffbeec594f5ac Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Tue, 1 Sep 2026 09:04:13 +0800 Subject: [PATCH 0596/1417] smb: client: fix use-after-free of iface in cifs_try_adding_channels() cifs_try_adding_channels() iterates ses->iface_list with list_for_each_entry_safe_from(), which captures the next entry (niface) under iface_lock. The loop body then drops iface_lock for the whole duration of cifs_ses_add_channel(). A concurrent interface refresh (SMB3_request_interfaces() -> parse_server_interfaces()) marks all ifaces inactive and removes and frees any that are not re-advertised via list_del() + kref_put(), where release_iface() is a bare kfree(). Since niface typically has no channel holding a reference, the list reference is its last and it can be freed inside the unlocked window. On continue, the iterator advance step then dereferences niface->iface_head.next, and the loop body reads iface->rdma_capable/is_active, both on freed memory. Fix this by never keeping an unreferenced list pointer across the unlocked window. Each channel attempt now re-scans the list from the head under iface_lock, takes a kref on the selected candidate, and passes only that referenced candidate to cifs_ses_add_channel(). weight_fulfilled still tracks selection progress, so restarting the scan preserves the original weighted distribution and the weight_fulfilled-before-kref_put ordering on the failure path. Add a per-pass attempts cap so a flapping interface refresh cannot keep the inner loop spinning within a single tries increment. Fixes: aa45dadd34e4 ("cifs: change iface_list from array to sorted linked list") Cc: stable@vger.kernel.org Assisted-by: Qoder:Qwen3.8-Max Signed-off-by: Joseph Qi Acked-by: Shyam Prasad N Signed-off-by: Paulo Alcantara --- fs/smb/client/sess.c | 106 ++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 42 deletions(-) diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index 7cf7dd104f7c31..e095f41b58828b 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -149,9 +149,9 @@ int cifs_try_adding_channels(struct cifs_ses *ses) int old_chan_count, new_chan_count; int left; int rc = 0; - int tries = 0; + int tries = 0, attempts; size_t iface_weight = 0, iface_min_speed = 0; - struct cifs_server_iface *iface = NULL, *niface = NULL; + struct cifs_server_iface *iface = NULL, *candidate = NULL; struct cifs_server_iface *last_iface = NULL; spin_lock(&ses->chan_lock); @@ -197,67 +197,89 @@ int cifs_try_adding_channels(struct cifs_ses *ses) break; } - if (!iface) - iface = list_first_entry(&ses->iface_list, struct cifs_server_iface, - iface_head); last_iface = list_last_entry(&ses->iface_list, struct cifs_server_iface, iface_head); iface_min_speed = last_iface->speed; + spin_unlock(&ses->iface_lock); - list_for_each_entry_safe_from(iface, niface, &ses->iface_list, - iface_head) { - /* do not mix rdma and non-rdma interfaces */ - if (iface->rdma_capable != ses->server->rdma) - continue; - - /* skip ifaces that are unusable */ - if (!iface->is_active || - (is_ses_using_iface(ses, iface) && - !iface->rss_capable)) - continue; + attempts = 0; + while (left > 0) { + spin_lock(&ses->iface_lock); - /* check if we already allocated enough channels */ - iface_weight = iface->speed / iface_min_speed; + /* + * iface_lock must be dropped while opening a channel, + * and a concurrent interface refresh may remove and + * free entries during that window, so no list entry + * may be kept across it without a reference. Scan + * the list from the beginning each time and only pass + * a referenced candidate to cifs_ses_add_channel(); + * weight_fulfilled tracks the progress so that no + * iface is selected beyond its weight. + */ + candidate = NULL; + list_for_each_entry(iface, &ses->iface_list, iface_head) { + /* do not mix rdma and non-rdma interfaces */ + if (iface->rdma_capable != ses->server->rdma) + continue; + + /* skip ifaces that are unusable */ + if (!iface->is_active || + (is_ses_using_iface(ses, iface) && + !iface->rss_capable)) + continue; + + /* check if we already allocated enough channels */ + iface_weight = iface->speed / iface_min_speed; + + if (iface->weight_fulfilled >= iface_weight) + continue; + + /* take ref before unlock */ + kref_get(&iface->refcount); + candidate = iface; + break; + } - if (iface->weight_fulfilled >= iface_weight) - continue; + if (!candidate) { + /* no usable iface. reset weight_fulfilled and start over */ + list_for_each_entry(iface, &ses->iface_list, iface_head) + iface->weight_fulfilled = 0; + spin_unlock(&ses->iface_lock); + break; + } - /* take ref before unlock */ - kref_get(&iface->refcount); + attempts++; + if (attempts > 3 * ses->chan_max) { + kref_put(&candidate->refcount, release_iface); + spin_unlock(&ses->iface_lock); + break; + } spin_unlock(&ses->iface_lock); - rc = cifs_ses_add_channel(ses, iface); + rc = cifs_ses_add_channel(ses, candidate); spin_lock(&ses->iface_lock); if (rc) { cifs_dbg(VFS, "failed to open extra channel on iface:%pIS rc=%d\n", - &iface->sockaddr, + &candidate->sockaddr, rc); /* failure to add chan should increase weight */ - iface->weight_fulfilled++; - kref_put(&iface->refcount, release_iface); + candidate->weight_fulfilled++; + kref_put(&candidate->refcount, release_iface); + spin_unlock(&ses->iface_lock); continue; } - iface->num_channels++; - iface->weight_fulfilled++; + candidate->num_channels++; + candidate->weight_fulfilled++; cifs_info("successfully opened new channel on iface:%pIS\n", - &iface->sockaddr); - break; - } - - /* reached end of list. reset weight_fulfilled and start over */ - if (list_entry_is_head(iface, &ses->iface_list, iface_head)) { - list_for_each_entry(iface, &ses->iface_list, iface_head) - iface->weight_fulfilled = 0; + &candidate->sockaddr); spin_unlock(&ses->iface_lock); - iface = NULL; - continue; - } - spin_unlock(&ses->iface_lock); - left--; - new_chan_count++; + left--; + new_chan_count++; + break; + } } return new_chan_count - old_chan_count; From a5e22cba3549b3b9ca592a6bc62329c9b85ce285 Mon Sep 17 00:00:00 2001 From: Oder Chiou Date: Wed, 16 Sep 2026 18:18:03 +0800 Subject: [PATCH 0597/1417] ASoC: rt721: Reset codec to fix abnormal sound The audio output may become abnormal after a warm reboot from Windows. Reset the codec once during hardware initialization to restore it to a known state and prevent the issue. Signed-off-by: Oder Chiou Link: https://patch.msgid.link/20260916101803.2301508-1-oder_chiou@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca-sdw.c | 3 +++ sound/soc/codecs/rt721-sdca.c | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c index eae7d662efae8e..910583162d3e5d 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.c +++ b/sound/soc/codecs/rt721-sdca-sdw.c @@ -70,6 +70,7 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re case 0x0310100: case 0x2000000 ... 0x2000003: case 0x2000013: + case 0x2000026: case 0x200002c: case 0x200003c: case 0x2000046: @@ -142,6 +143,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x200000d: case 0x2000019: case 0x2000020: + case 0x2000026: case 0x200002c: case 0x2000030: case 0x2000046: @@ -155,6 +157,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x5810039: case 0x5b10018: case 0x5b10019: + case 0x6100006: return true; default: return false; diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index a9479d0e4941e5..738644018fb9bd 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -1497,6 +1497,15 @@ int rt721_sdca_init(struct device *dev, struct regmap *regmap, &soc_sdca_dev_rt721, rt721_sdca_dai, ARRAY_SIZE(rt721_sdca_dai)); } +static void rt721_sdca_reset(struct rt721_sdca_priv *rt721) +{ + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_REG, + RT721_VD_HIDDEN_CTRL, RT721_HIDDEN_REG_SW_RESET, + RT721_HIDDEN_REG_SW_RESET); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_RESET_CTL, 0x1, 0x1); +} + int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) { struct rt721_sdca_priv *rt721 = dev_get_drvdata(dev); @@ -1530,9 +1539,17 @@ int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) } pm_runtime_get_noresume(&slave->dev); + + if (!rt721->first_hw_init) + rt721_sdca_reset(rt721); + rt721_sdca_dmic_preset(rt721); rt721_sdca_amp_preset(rt721); rt721_sdca_jack_preset(rt721); + + if (rt721->hs_jack && (!rt721->first_hw_init)) + rt721_sdca_jack_init(rt721); + if (rt721->first_hw_init) { regcache_cache_bypass(rt721->regmap, false); regcache_mark_dirty(rt721->regmap); From 11fc0048a6930f4fca44fe3bd16a0023e78846a2 Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Sun, 13 Sep 2026 13:31:32 -0400 Subject: [PATCH 0598/1417] ASoC: ux500: Parenthesize MSP_{RX,TX}_CLKPOL_BIT() arguments arm allmodconfig fails to build with gcc: In file included from sound/soc/ux500/ux500_msp_i2s.c:20: sound/soc/ux500/ux500_msp_i2s.h:151:38: error: suggest parentheses around arithmetic in operand of '^' [-Werror=parentheses] sound/soc/ux500/ux500_msp_i2s.c:204:21: note: in expansion of macro 'MSP_TX_CLKPOL_BIT' cc1: all warnings being treated as errors The macros never parenthesized their argument: #define MSP_TX_CLKPOL_BIT(n) ((n & TCKPOL_MASK) << TCKPOL_SHIFT) That went unnoticed while every caller passed a plain variable, but configure_protocol() now passes an XOR expression, which binds as "a ^ (b & MASK)" rather than "(a ^ b) & MASK", and gcc rightly complains. No functional change: tx_clk_pol and rx_clk_pol only ever hold MSP_FALLING_EDGE (0) or MSP_RISING_EDGE (1), and bclk_inverted is a bool, so masking before or after the XOR gives the same 0/1 result. Parenthesize the argument anyway - it fixes the build and stops the macros from silently mis-evaluating a future composite argument. Fixes: 9ccbacf5a012 ("ASoC: ux500: Validate MSP DAI configuration") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609051547.G9SJp8UQ-lkp@intel.com/ Assisted-by: LLM Signed-off-by: Sasha Levin Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260913173132.1172003-1-sashal@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_i2s.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h index 2bf2699bdc49f8..c66ef455e13802 100644 --- a/sound/soc/ux500/ux500_msp_i2s.h +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -147,8 +147,8 @@ enum msp_direction { #define RCKPOL_MASK BIT(0) #define TCKPOL_MASK BIT(0) #define SPICKM_MASK (BIT(1) | BIT(0)) -#define MSP_RX_CLKPOL_BIT(n) ((n & RCKPOL_MASK) << RCKPOL_SHIFT) -#define MSP_TX_CLKPOL_BIT(n) ((n & TCKPOL_MASK) << TCKPOL_SHIFT) +#define MSP_RX_CLKPOL_BIT(n) (((n) & RCKPOL_MASK) << RCKPOL_SHIFT) +#define MSP_TX_CLKPOL_BIT(n) (((n) & TCKPOL_MASK) << TCKPOL_SHIFT) #define P1ELEN_SHIFT 0 #define P1FLEN_SHIFT 3 From c17ae8c26eac16ad244daef44044d714f68a2ddc Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 15 Sep 2026 18:25:15 +0900 Subject: [PATCH 0599/1417] ASoC: hdmi-codec: Report a change when the channel status moves The put() callback of "IEC958 Playback Default" stores all 24 channel status bytes and then returns 0. The core notifies userspace only on a positive return, so a write that changes what the get() callback hands back is never announced, and a mixer holding the control open keeps showing the old value. Compare the stored bytes and return 1 when they move, the way snd_hda_spdif_default_put() does. The same shape is in img-spdif-out and uniperif_player. No board with this codec was to hand. The change is a comparison of driver state with no hardware behaviour in it, and mixer-test counts the missing notification as event_missing. Fixes: 7a8e1d44211e ("ASoC: hdmi-codec: Add iec958 controls") Signed-off-by: HyeongJun An Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260915092515.2638542-1-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/hdmi-codec.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index bc2c22436ba6ef..7aa50c5bd3df8a 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -426,10 +426,14 @@ static int hdmi_codec_iec958_default_put(struct snd_kcontrol *kcontrol, struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(component); + if (!memcmp(hcp->iec_status, ucontrol->value.iec958.status, + sizeof(hcp->iec_status))) + return 0; + memcpy(hcp->iec_status, ucontrol->value.iec958.status, sizeof(hcp->iec_status)); - return 0; + return 1; } static int hdmi_codec_iec958_mask_get(struct snd_kcontrol *kcontrol, From 03a5699a0a04309c597683967aaaf25d1e555ea2 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Mon, 14 Sep 2026 18:47:12 +0800 Subject: [PATCH 0600/1417] ASoC: codecs: rt712-sdca-dmic: fix uninitialized stream_config->type stream_config is not initialized before being passed to sdw_stream_add_slave(). The type field may contain garbage and is later copied to stream->type by sdw_config_stream(). Zero-initialize stream_config so type defaults to SDW_STREAM_PCM. While at it, use snd_sdw_params_to_config() helper instead of open-coding the same logic. Fixes: 63a511284c9e ("ASoC: rt712-sdca: Add RT712 SDCA driver for Mic topology") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260914104712.379574-1-yijiangshan@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca-dmic.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c index 8860d81134e70c..a9f3aa4e143ae7 100644 --- a/sound/soc/codecs/rt712-sdca-dmic.c +++ b/sound/soc/codecs/rt712-sdca-dmic.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "rt712-sdca.h" #include "rt712-sdca-dmic.h" @@ -632,10 +633,10 @@ static int rt712_sdca_dmic_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; struct sdw_stream_runtime *sdw_stream; - int retval, num_channels; + int retval; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s", __func__, dai->name); @@ -647,13 +648,8 @@ static int rt712_sdca_dmic_hw_params(struct snd_pcm_substream *substream, if (!rt712->slave) return -EINVAL; - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = params_channels(params); - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = SDW_DATA_DIR_TX; - - num_channels = params_channels(params); - port_config.ch_mask = GENMASK(num_channels - 1, 0); + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = 2; retval = sdw_stream_add_slave(rt712->slave, &stream_config, From 3482062c786ce4233f8ed3224d824184f53ec154 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 14 Sep 2026 13:26:11 +0100 Subject: [PATCH 0601/1417] ASoC: cs-amp-lib: Prevent NULL pointer if efi variable is zero length In cs_amp_alloc_get_efi_variable() the first call to cs_amp_get_efi_variable() might return EFI_SUCCESS if the variable exists with zero length. Trap this and return -ENOENT to prevent returning an unexpected NULL pointer. The first cs_amp_get_efi_variable() call was assumed to return EFI_BUFFER_TOO_SMALL if the variable existed, but if instead it returned EFI_SUCCESS this would be converted to 0 by cs_amp_convert_efi_status() and then be returned as a NULL pointer. Fixes: 00fd40bc7acec ("ASoC: cs-amp-lib: Support Dell SSIDExV2 UEFI variable") Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260914122611.2783563-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs-amp-lib.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c index 41a9a5b005c63f..9bc19d2e163945 100644 --- a/sound/soc/codecs/cs-amp-lib.c +++ b/sound/soc/codecs/cs-amp-lib.c @@ -317,6 +317,8 @@ static void *cs_amp_alloc_get_efi_variable(efi_char16_t *name, unsigned long size = 0; status = cs_amp_get_efi_variable(name, guid, NULL, &size, NULL); + if (status == EFI_SUCCESS) + return ERR_PTR(-ENOENT); if (status != EFI_BUFFER_TOO_SMALL) return ERR_PTR(cs_amp_convert_efi_status(status)); From 29218a4d11a31a8157389bc2b9e62dd768d7ea42 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:46:46 +0530 Subject: [PATCH 0602/1417] ASoC: amd: acp: bounds-check SoundWire link ID in machine drivers Add a bounds check in create_sdw_dailink() to validate that the SoundWire link ID derived from link_mask does not exceed the maximum supported by the platform. If the link ID is out of range or link_mask is zero, log an error and return -EINVAL to prevent accessing invalid CPU pin ID tables. Applied to both acp-sdw-sof-mach.c and acp-sdw-legacy-mach.c. Fixes: 6d8348ddc56e ("ASoC: amd: acp: refactor SoundWire machine driver code") Signed-off-by: Vijendar Mukunda Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260910161728.1452808-2-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-legacy-mach.c | 10 ++++++++++ sound/soc/amd/acp/acp-sdw-sof-mach.c | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index 6eac42bac855f8..2ea226a195c31c 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -205,6 +205,16 @@ static int create_sdw_dailink(struct snd_soc_card *card, return -EINVAL; } + if (!soc_end->link_mask) { + dev_err(dev, "invalid zero link_mask\n"); + return -EINVAL; + } + if ((ffs(soc_end->link_mask) - 1) >= amd_ctx->max_sdw_links) { + dev_err(dev, "link_id %d exceeds max_sdw_links %d\n", + ffs(soc_end->link_mask) - 1, amd_ctx->max_sdw_links); + return -EINVAL; + } + switch (amd_ctx->acp_rev) { case ACP63_PCI_REV: ret = get_acp63_cpu_pin_id(ffs(soc_end->link_mask - 1), diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index a9cd1f335167cd..6c74e67b134f07 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -121,6 +121,15 @@ static int create_sdw_dailink(struct snd_soc_card *card, return -EINVAL; } + if (!sof_end->link_mask) { + dev_err(dev, "invalid zero link_mask\n"); + return -EINVAL; + } + if ((ffs(sof_end->link_mask) - 1) >= amd_ctx->max_sdw_links) { + dev_err(dev, "link_id %d exceeds max_sdw_links %d\n", + ffs(sof_end->link_mask) - 1, amd_ctx->max_sdw_links); + return -EINVAL; + } switch (amd_ctx->acp_rev) { case ACP63_PCI_REV: ret = get_acp63_cpu_pin_id(ffs(sof_end->link_mask - 1), From 0b7d55d3a91200f2b1ed710f525a944b0a7d6369 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:46:47 +0530 Subject: [PATCH 0603/1417] ASoC: amd: acp: refactor codec config count in SOF SoundWire machine driver num_devs was used both as the endpoint count and as the output for asoc_sdw_parse_sdw_endpoints(), which overwrites it with the codec configuration count. Introduce a separate num_confs variable to hold the codec conf count so the two values remain distinct across codec_conf allocation and card->num_configs assignment. Fixes: 6d8348ddc56e ("ASoC: amd: acp: refactor SoundWire machine driver code") Signed-off-by: Vijendar Mukunda Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260910161728.1452808-3-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index 6c74e67b134f07..b7926967593fef 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -286,6 +286,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) int num_devs = 0; int num_ends = 0; int num_aux = 0; + int num_confs; int num_links; int be_id = 0; int ret; @@ -296,6 +297,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) return ret; } + num_confs = num_ends; /* One per DAI link, worst case is a DAI link for every endpoint */ struct asoc_sdw_dailink *sof_dais __free(kfree) = kzalloc_objs(*sof_dais, num_ends); @@ -312,7 +314,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) if (!sof_aux) return -ENOMEM; - ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); + ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); if (ret < 0) return ret; @@ -324,7 +326,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) dev_dbg(dev, "sdw %d, dmic %d", sdw_be_num, dmic_num); - codec_conf = devm_kcalloc(dev, num_devs, sizeof(*codec_conf), GFP_KERNEL); + codec_conf = devm_kcalloc(dev, num_confs, sizeof(*codec_conf), GFP_KERNEL); if (!codec_conf) return -ENOMEM; @@ -335,7 +337,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) return -ENOMEM; card->codec_conf = codec_conf; - card->num_configs = num_devs; + card->num_configs = num_confs; card->dai_link = dai_links; card->num_links = num_links; card->aux_dev = sof_aux; From 27098aaf28b96ab4e6891709062c343566d4882b Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:46:48 +0530 Subject: [PATCH 0604/1417] ASoC: amd: acp: fix ffs() operator precedence for SoundWire link ID ffs(link_mask - 1) computes ffs on (link_mask - 1) instead of subtracting 1 from the result of ffs(link_mask). For a typical power-of-2 link_mask this returns the wrong link ID, causing cpu_pin_id lookup to select the incorrect SoundWire manager. Fix the operator precedence to ffs(link_mask) - 1 in both acp-sdw-sof-mach.c and acp-sdw-legacy-mach.c. Fixes: 6d8348ddc56e ("ASoC: amd: acp: refactor SoundWire machine driver code") Signed-off-by: Vijendar Mukunda Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260910161728.1452808-4-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-legacy-mach.c | 4 ++-- sound/soc/amd/acp/acp-sdw-sof-mach.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index 2ea226a195c31c..1a05d4288a46e9 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -217,7 +217,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, switch (amd_ctx->acp_rev) { case ACP63_PCI_REV: - ret = get_acp63_cpu_pin_id(ffs(soc_end->link_mask - 1), + ret = get_acp63_cpu_pin_id(ffs(soc_end->link_mask) - 1, *be_id, &cpu_pin_id, dev); if (ret) return ret; @@ -225,7 +225,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, case ACP70_PCI_REV: case ACP71_PCI_REV: case ACP72_PCI_REV: - ret = get_acp70_cpu_pin_id(ffs(soc_end->link_mask - 1), + ret = get_acp70_cpu_pin_id(ffs(soc_end->link_mask) - 1, *be_id, &cpu_pin_id, dev); if (ret) return ret; diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index b7926967593fef..e6d545fd665e34 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -132,7 +132,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, } switch (amd_ctx->acp_rev) { case ACP63_PCI_REV: - ret = get_acp63_cpu_pin_id(ffs(sof_end->link_mask - 1), + ret = get_acp63_cpu_pin_id(ffs(sof_end->link_mask) - 1, *be_id, &cpu_pin_id, dev); if (ret) return ret; @@ -140,7 +140,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, case ACP70_PCI_REV: case ACP71_PCI_REV: case ACP72_PCI_REV: - ret = get_acp70_cpu_pin_id(ffs(sof_end->link_mask - 1), + ret = get_acp70_cpu_pin_id(ffs(sof_end->link_mask) - 1, *be_id, &cpu_pin_id, dev); if (ret) return ret; From d57616f8be5601d210bbb0f677b9cb88a5186c3c Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:46:49 +0530 Subject: [PATCH 0605/1417] ASoC: amd: acp: fix card name length warning in SOF SoundWire machine driver The ALSA snd_card driver[] field is 16 bytes (including the NUL terminator), leaving 15 usable characters. The SOF framework prepends a "sof-" prefix when registering the card, so card->name = "amd-soundwire" becomes driver name "sof-amd-soundwire" which is 17 characters and overflows the driver[16] buffer, triggering a kernel warning. Fix by shortening the card name to "amd-sdw"; the resulting driver name "sof-amd-sdw" fits within the 15-character limit. Signed-off-by: Vijendar Mukunda Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260910161728.1452808-5-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index e6d545fd665e34..ec3e1f5f1052c5 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -390,7 +390,7 @@ static int mc_probe(struct platform_device *pdev) ctx->private = amd_ctx; card = &ctx->card; card->dev = &pdev->dev; - card->name = "amd-soundwire"; + card->name = "amd-sdw"; card->owner = THIS_MODULE; card->late_probe = asoc_sdw_card_late_probe; From 0030f62683d5061d43b80577b7ab27196f1adb4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Mon, 14 Sep 2026 12:12:35 +0200 Subject: [PATCH 0606/1417] ASoC: adau1977: make the Kconfig symbols user selectable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SND_SOC_ADAU1977_{SPI,I2C} are missing Kconfig text, so they don't show up in menuconfig and can't be selected by a user - only by another symbol such as a machine driver. Add the text to make these symbols selectable and usable with generic machine drivers like the simple audio card. Signed-off-by: Alvin Šipraga Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260914-asoc-adau1977-fixes-v1-1-aa2f0cabd728@analog.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f9a47e262a77cb..d88593c2bac8b9 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -524,13 +524,13 @@ config SND_SOC_ADAU1977 tristate config SND_SOC_ADAU1977_SPI - tristate + tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - SPI" depends on SPI_MASTER select SND_SOC_ADAU1977 select REGMAP_SPI config SND_SOC_ADAU1977_I2C - tristate + tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - I2C" depends on I2C select SND_SOC_ADAU1977 select REGMAP_I2C From 528a0da3e55b24d1113b3658e94cf432e0020913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Mon, 14 Sep 2026 12:12:36 +0200 Subject: [PATCH 0607/1417] ASoC: adau1977-spi: drop __maybe_unused and of_match_ptr() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 5ab23c7923a1 ("modpost: Create modalias for builtin modules") MODULE_DEVICE_TABLE() is enough to reference a match table and the data isn't discarded by the linker even when the driver is built-in and CONFIG_OF is disabled. Drop the of_match_ptr() wrapping so that OF matching keeps working regardless of CONFIG_OF. This also means we can drop __maybe_unused since it's always used. The entries in adau1977_spi_of_match were also erroneously indented with spaces - replace the indentation with tabs to conform with coding style. Signed-off-by: Alvin Šipraga Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260914-asoc-adau1977-fixes-v1-2-aa2f0cabd728@analog.com Signed-off-by: Mark Brown --- sound/soc/codecs/adau1977-spi.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/adau1977-spi.c b/sound/soc/codecs/adau1977-spi.c index 878cde9d1014ed..c98da5ba9e9e24 100644 --- a/sound/soc/codecs/adau1977-spi.c +++ b/sound/soc/codecs/adau1977-spi.c @@ -53,18 +53,18 @@ static const struct spi_device_id adau1977_spi_ids[] = { }; MODULE_DEVICE_TABLE(spi, adau1977_spi_ids); -static const struct of_device_id adau1977_spi_of_match[] __maybe_unused = { - { .compatible = "adi,adau1977" }, - { .compatible = "adi,adau1978" }, - { .compatible = "adi,adau1979" }, - { }, +static const struct of_device_id adau1977_spi_of_match[] = { + { .compatible = "adi,adau1977" }, + { .compatible = "adi,adau1978" }, + { .compatible = "adi,adau1979" }, + { }, }; MODULE_DEVICE_TABLE(of, adau1977_spi_of_match); static struct spi_driver adau1977_spi_driver = { .driver = { .name = "adau1977", - .of_match_table = of_match_ptr(adau1977_spi_of_match), + .of_match_table = adau1977_spi_of_match, }, .probe = adau1977_spi_probe, .id_table = adau1977_spi_ids, From 76a8fe25b97881223976363044924d5cf0511749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Mon, 14 Sep 2026 12:12:37 +0200 Subject: [PATCH 0608/1417] ASoC: adau1977-i2c: add OF match table for I2C MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Like for SPI, the I2C driver needs an OF match table for the kernel to be able to automatically load the driver when built as a module. Add one. Signed-off-by: Alvin Šipraga Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260914-asoc-adau1977-fixes-v1-3-aa2f0cabd728@analog.com Signed-off-by: Mark Brown --- sound/soc/codecs/adau1977-i2c.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/codecs/adau1977-i2c.c b/sound/soc/codecs/adau1977-i2c.c index d1c6c4ddf5063a..5a11cafdff36d7 100644 --- a/sound/soc/codecs/adau1977-i2c.c +++ b/sound/soc/codecs/adau1977-i2c.c @@ -34,9 +34,18 @@ static const struct i2c_device_id adau1977_i2c_ids[] = { }; MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids); +static const struct of_device_id adau1977_i2c_of_match[] = { + { .compatible = "adi,adau1977" }, + { .compatible = "adi,adau1978" }, + { .compatible = "adi,adau1979" }, + { }, +}; +MODULE_DEVICE_TABLE(of, adau1977_i2c_of_match); + static struct i2c_driver adau1977_i2c_driver = { .driver = { .name = "adau1977", + .of_match_table = adau1977_i2c_of_match, }, .probe = adau1977_i2c_probe, .id_table = adau1977_i2c_ids, From 480a5528cb6268d626632d45d4a1f29b4c0c11df Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 07:27:37 +0000 Subject: [PATCH 0609/1417] ASoC: wm8994: Fix runtime PM imbalance in wm8994_component_probe() In wm8994_component_probe(), pm_runtime_get_sync() is invoked before reading the GPIO configuration registers. If either regmap_read() fails, the function jumps to err_irq without calling pm_runtime_put(), leaving the runtime PM usage count incremented and causing a leak. Add pm_runtime_put() on both GPIO read failure paths before jumping to err_irq. Fixes: f5a2cda4f1db ("ASoC: wm8994: Ensure the device is resumed in wm89xx_mic_detect functions") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260916072737.1970990-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/wm8994.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 97d8df3815ac71..96d3aae3686272 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -4360,6 +4360,7 @@ static int wm8994_component_probe(struct snd_soc_component *component) ret = regmap_read(control->regmap, WM8994_GPIO_1, ®); if (ret < 0) { dev_err(component->dev, "Failed to read GPIO1 state: %d\n", ret); + pm_runtime_put(component->dev); goto err_irq; } if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { @@ -4372,6 +4373,7 @@ static int wm8994_component_probe(struct snd_soc_component *component) ret = regmap_read(control->regmap, WM8994_GPIO_6, ®); if (ret < 0) { dev_err(component->dev, "Failed to read GPIO6 state: %d\n", ret); + pm_runtime_put(component->dev); goto err_irq; } if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { From e04dcb895b4622d67164e218a1179785e2c75999 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 07:29:00 +0000 Subject: [PATCH 0610/1417] ASoC: arizona-jack: Fix runtime PM imbalance in arizona_start_hpdet_acc_id() In arizona_start_hpdet_acc_id(), pm_runtime_get_sync() is called to keep the device powered during headphone detection. If setting the HPDET mode or starting the measurement fails and the function jumps to err, pm_runtime_put_autosuspend() was not called while info->hpdet_active was set to false, resulting in a runtime PM usage count leak. Add pm_runtime_put_autosuspend() in the err error path to keep runtime PM balanced. Fixes: 9dd5e53d9d2f ("extcon: arizona: Retry HPDET identification for high impedance") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260916072900.1971595-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/arizona-jack.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/arizona-jack.c b/sound/soc/codecs/arizona-jack.c index 819d080b118893..6587d46a186705 100644 --- a/sound/soc/codecs/arizona-jack.c +++ b/sound/soc/codecs/arizona-jack.c @@ -699,6 +699,7 @@ static void arizona_start_hpdet_acc_id(struct arizona_priv *info) snd_soc_jack_report(info->jack, SND_JACK_HEADPHONE, SND_JACK_LINEOUT | SND_JACK_HEADPHONE); + pm_runtime_put_autosuspend(arizona->dev); info->hpdet_active = false; } From 98fe8aab04cec6fcadeb10a757235225d550be36 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 22:21:18 +0800 Subject: [PATCH 0611/1417] ASoC: rt1015: make control calibration failures observable The bypass-boost control runs a hardware calibration while bypassing the register cache. It ignores every register access and cache restore failure, then publishes both the requested mode and calibration completion. Return failures from the complete calibration sequence, always restore normal cache operation and unlock DAPM, and only publish the control state after the calibration and final mode write both succeed. The mixer control callback is an effective error consumer, independent of deferred component resume. Fixes: da145172b236 ("ASoC: rt1015: Fix DC calibration on bypass boost mode") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830142118.36668-1-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1015.c | 81 +++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/sound/soc/codecs/rt1015.c b/sound/soc/codecs/rt1015.c index c6d59c2860644b..5d6be7cc67b44d 100644 --- a/sound/soc/codecs/rt1015.c +++ b/sound/soc/codecs/rt1015.c @@ -488,35 +488,72 @@ static int rt1015_bypass_boost_get(struct snd_kcontrol *kcontrol, return 0; } -static void rt1015_calibrate(struct rt1015_priv *rt1015) +static int rt1015_calibrate(struct rt1015_priv *rt1015) { struct snd_soc_component *component = rt1015->component; struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct regmap *regmap = rt1015->regmap; + int ret, sync_ret; snd_soc_dapm_mutex_lock(dapm); regcache_cache_bypass(regmap, true); - regmap_write(regmap, RT1015_CLK_DET, 0x0000); - regmap_write(regmap, RT1015_PWR4, 0x00B2); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x0009); + ret = regmap_write(regmap, RT1015_CLK_DET, 0x0000); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_PWR4, 0x00B2); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x0009); + if (ret) + goto restore_cache; + msleep(100); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000A); + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000A); + if (ret) + goto restore_cache; + msleep(100); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000C); + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000C); + if (ret) + goto restore_cache; + msleep(100); - regmap_write(regmap, RT1015_CLSD_INTERNAL8, 0x2028); - regmap_write(regmap, RT1015_CLSD_INTERNAL9, 0x0140); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000D); + ret = regmap_write(regmap, RT1015_CLSD_INTERNAL8, 0x2028); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_CLSD_INTERNAL9, 0x0140); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000D); + if (ret) + goto restore_cache; + msleep(300); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x0008); - regmap_write(regmap, RT1015_SYS_RST1, 0x05F5); - regmap_write(regmap, RT1015_CLK_DET, 0x8000); + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x0008); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_SYS_RST1, 0x05F5); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_CLK_DET, 0x8000); + +restore_cache: regcache_cache_bypass(regmap, false); regcache_mark_dirty(regmap); - regcache_sync(regmap); + sync_ret = regcache_sync(regmap); + if (!ret) + ret = sync_ret; snd_soc_dapm_mutex_unlock(dapm); + + return ret; } static int rt1015_bypass_boost_put(struct snd_kcontrol *kcontrol, @@ -525,6 +562,8 @@ static int rt1015_bypass_boost_put(struct snd_kcontrol *kcontrol, struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct rt1015_priv *rt1015 = snd_soc_component_get_drvdata(component); + int bypass_boost = rt1015->bypass_boost; + int ret; if (rt1015->dac_is_used) { dev_err(component->dev, "DAC is being used!\n"); @@ -534,13 +573,23 @@ static int rt1015_bypass_boost_put(struct snd_kcontrol *kcontrol, rt1015->bypass_boost = ucontrol->value.integer.value[0]; if (rt1015->bypass_boost == RT1015_Bypass_Boost && !rt1015->cali_done) { - rt1015_calibrate(rt1015); - rt1015->cali_done = 1; + ret = rt1015_calibrate(rt1015); + if (ret) + goto restore_boost; + + ret = regmap_write(rt1015->regmap, RT1015_MONO_DYNA_CTRL, + 0x0010); + if (ret) + goto restore_boost; - regmap_write(rt1015->regmap, RT1015_MONO_DYNA_CTRL, 0x0010); + rt1015->cali_done = 1; } return 0; + +restore_boost: + rt1015->bypass_boost = bypass_boost; + return ret; } static const char * const rt1015_dac_output_vol_select[] = { From 48ed992bfbe53537ad1cbc8ad68f7031e7ac0af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan=20Ghe=C8=9Bu?= Date: Tue, 8 Sep 2026 20:07:40 +0300 Subject: [PATCH 0612/1417] ASoC: SOF: imx: Prevent stack OOB read in DSP panic dump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 58bb5081cba1 ("ASoC: SOF: Xtensa: dump ar registers to restore call stack") added a shared Xtensa helper that iterates over a flexible array of AR registers (`ar[]`) controlled by `plat_hdr.numaregs`. While Intel IPC4 allocates dynamic storage for the AR block, the i.MX IPC3 path reads the oops message into a stack-allocated struct without backing storage for `ar[]`, while leaving `numaregs` unvalidated. This causes a stack out-of-bounds read when printing a DSP panic. Clear `numaregs` to 0 on i.MX since the AR block is not fetched or supported on this platform, preventing unsafe out-of-bounds memory accesses in the shared Xtensa helper. Fixes: 58bb5081cba1 ("ASoC: SOF: Xtensa: dump ar registers to restore call stack") Signed-off-by: Ștefan Ghețu Link: https://patch.msgid.link/20260908170740.276800-1-stefanghetu9@gmail.com Signed-off-by: Mark Brown --- sound/soc/sof/imx/imx-common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sof/imx/imx-common.c b/sound/soc/sof/imx/imx-common.c index 7a03c8cc5dd473..436fe49246ba80 100644 --- a/sound/soc/sof/imx/imx-common.c +++ b/sound/soc/sof/imx/imx-common.c @@ -34,6 +34,7 @@ void imx8_get_registers(struct snd_sof_dev *sdev, /* first read registers */ sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops)); + xoops->plat_hdr.numaregs = 0; /* then get panic info */ if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) { From b2c5435300c815373a2112522eb38857313b5b96 Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Sat, 12 Sep 2026 19:29:50 +0300 Subject: [PATCH 0613/1417] ASoC: tas2783-sdw: stop describing the Latency and XU ID/Version Controls The read-only Control list and tas2783_sdca_mbq_size() still describe the Latency Control of every Entity - twenty-one registers in all - and the XU ID and XU Version of the Extension Unit. The driver never reads any of them, and the platform firmware seen so far describes exactly one of the Latencies (FU21, as a DisCo constant) and neither of the two XU Controls. XU ID answers COMMAND_IGNORED on the bus in every power state. Since commit f4ffa3820949 ("ASoC: tas2783-sdw: do not cache read-only Controls") they have no defaults and are marked volatile, so every access goes to the bus. Remove them from both tables: the driver then neither declares them readable nor sizes them, and the read-only list is left with the readings the device actually reports - Clock Valid, the actual power state, the protection status and the algorithm ready flags. No functional change for the driver itself, which does not read any of the removed registers. Link: https://lore.kernel.org/all/70a91202-e801-4008-bec8-883b229f9f0f@linux.dev/ Signed-off-by: Andrey Golovko Link: https://patch.msgid.link/20260912163500.8412-1-andrey.golovko@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2783-sdw.c | 53 +++------------------------------- 1 file changed, 4 insertions(+), 49 deletions(-) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index 04e10473c7057e..85ab3fd83c7bb9 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -333,7 +333,6 @@ static int tas2783_sdca_mbq_size(struct device *dev, u32 reg) case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU26, 0x11, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_TG23, 0x10, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x01, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x08, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x0a, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x10, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x14, 0): @@ -419,7 +418,6 @@ static int tas2783_sdca_mbq_size(struct device *dev, u32 reg) case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x0b, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x0b, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x0b, 1): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x07, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x09, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x12, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x12, 0): @@ -431,27 +429,8 @@ static int tas2783_sdca_mbq_size(struct device *dev, u32 reg) case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT21, 0x11, 0): return 2; - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU23, 0x10, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT21, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT26, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT28, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT29, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT23, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT24, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT25, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT28, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x06, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x10, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x10, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x06, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x12, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x13, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_SAPU29, 0x05, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x06, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU26, 0x06, 0): return 4; default: @@ -465,47 +444,23 @@ static bool tas2783_readable_register(struct device *dev, unsigned int reg) } /* - * The read-only SDCA Controls the driver describes: the Latency of every - * Entity, the Clock Valid of every Clock Source, the actual power state of - * the Power Domain Entity, the protection status, the algorithm ready flag - * and the Extension Unit id, version and firmware download status. None of - * them is a setting; every one is a reading of device state. + * The read-only SDCA Controls the driver describes: the Clock Valid of every + * Clock Source, the actual power state of the Power Domain Entity, the + * protection status and the algorithm ready flags. None of them is a + * setting; every one is a reading of device state. */ static bool tas2783_read_only_control(unsigned int reg) { switch (reg) { - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21, 0x10, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU23, 0x10, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU26, 0x10, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x07, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x08, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS24, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS21, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS25, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS26, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS28, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE23, 0x10, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_UDMPU23, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x05, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x11, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU21, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU26, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT21, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT29, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT26, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT28, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT24, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT23, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT25, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT28, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MU26, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT127, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU127, 0x10, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS127, 0x02, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x08, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x04, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x04, 0): return true; From bb345be9d428a62cc2038cf0db3b5f01784839eb Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 13:36:01 +0700 Subject: [PATCH 0614/1417] ASoC: codecs: pcm6240: Return 0 explicitly on success The return value of pcmdev_dev_read() is already checked, and errors are returned immediately. Therefore, the remaining paths are successful paths and should return 0 explicitly instead of returning ret. Reported-by: Dan Carpenter Link: https://lore.kernel.org/all/aqfHiX3pE-0BiCt9@stanley.mountain/ Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916063601.23625-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/pcm6240.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c index db85ae2f8aed05..4b5569a5b0622a 100644 --- a/sound/soc/codecs/pcm6240.c +++ b/sound/soc/codecs/pcm6240.c @@ -620,12 +620,12 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, /* Set to wide-range mode, before using vol ctrl. */ if (!val && vol_ctrl_type == PCMDEV_PCM1690_VOL_CTRL) { ucontrol->value.integer.value[0] = -25500; - return ret; + return 0; } /* Set to fine mode, before using fine vol ctrl. */ if (val && vol_ctrl_type == PCMDEV_PCM1690_FINE_VOL_CTRL) { ucontrol->value.integer.value[0] = -12750; - return ret; + return 0; } } @@ -641,7 +641,7 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, val = mc->invert ? max - val : val; ucontrol->value.integer.value[0] = val; - return ret; + return 0; } static int pcmdevice_get_volsw(struct snd_kcontrol *kcontrol, From c774ec8f0a5d02a06d34c27f5a7de7e333b91265 Mon Sep 17 00:00:00 2001 From: Eva Kurchatova Date: Wed, 16 Sep 2026 23:44:31 +0300 Subject: [PATCH 0615/1417] selftests: cgroup: give the O_TMPFILE open in get_temp_fd() a mode O_TMPFILE, like O_CREAT, needs the third argument. Without it glibc refuses the call at compile time as soon as fortification is on: In function 'open', inlined from 'get_temp_fd' at test_memcontrol.c:33:9: /usr/include/bits/fcntl2.h:52:11: error: call to '__open_missing_mode' declared with attribute error: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments The fortify checks take effect only once the compiler optimises, and cgroup/Makefile builds with "-Wall -pthread" alone, so this goes unnoticed in a plain build. Building the tests with the flags distributions commonly use, -O2 -D_FORTIFY_SOURCE=3, loses test_memcontrol entirely. Fixes: 84092dbcf901 ("selftests: cgroup: add memory controller self-tests") Signed-off-by: Eva Kurchatova Signed-off-by: Tejun Heo --- tools/testing/selftests/cgroup/test_memcontrol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testing/selftests/cgroup/test_memcontrol.c index 3a84d068fbf368..0ed82347044ed7 100644 --- a/tools/testing/selftests/cgroup/test_memcontrol.c +++ b/tools/testing/selftests/cgroup/test_memcontrol.c @@ -30,7 +30,7 @@ static int page_size; int get_temp_fd(void) { - return open(".", O_TMPFILE | O_RDWR | O_EXCL); + return open(".", O_TMPFILE | O_RDWR | O_EXCL, 0600); } int alloc_pagecache(int fd, size_t size) From 089070b51ccbac411462a30a454690274c6e4270 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Wed, 16 Sep 2026 23:54:17 +0000 Subject: [PATCH 0616/1417] hwmon: (pmbus/tps53679) Select page 0 for single-page TPS53676 tps53676_identify() derives the number of PMBus pages but does not ensure that page 0 is selected for single-page configurations. pmbus_set_page() does not update the PAGE register when info->pages is 1, so if boot firmware leaves PAGE set to another value subsequent register accesses may target the wrong page. For single-page devices, select page 0 explicitly. Fixes: cb3d37b59012 ("hwmon: (pmbus/tps53679) Add support for TI TPS53676") Cc: stable@vger.kernel.org Signed-off-by: Sanman Pradhan Link: https://patch.msgid.link/20260916235406.681131-2-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/tps53679.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c index 9f27832703ff5b..6c25701b36fcec 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -200,6 +200,15 @@ static int tps53676_identify(struct i2c_client *client, if (phases_b > 0) { info->pages = 2; info->phases[1] = phases_b; + } else { + /* + * pmbus_set_page() does not update the PAGE register on + * single-page devices, so select page 0 explicitly in case + * the boot firmware left the device on another page. + */ + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + if (ret < 0) + return ret; } return 0; } From 92b68492eae701e5b0e9d142ffe229921af7b1fa Mon Sep 17 00:00:00 2001 From: James Seo Date: Wed, 16 Sep 2026 15:19:15 -0700 Subject: [PATCH 0617/1417] hwmon: (hp-wmi-sensors) Improve raw WMI string handling Commit c9ba59258094 ("hwmon: (hp-wmi-sensors) Fix failure to load on EliteDesk 800 G6") left out some logic for recognizing raw WMI strings in check_numeric_sensor_wobj(). This issue was reported by a user along with an incomplete and unsuitable proposed solution [1]. Add the missing logic and properly remedy the issue. Also slightly refactor how raw WMI strings are recognized elsewhere to make the intent that they should be treated as regular ACPI strings clearer. Reported-by: Muhammad Bilal Link: https://lore.kernel.org/linux-hwmon/20260916002907.161210-1-meatuni001@gmail.com/ [1] Fixes: c9ba59258094 ("hwmon: (hp-wmi-sensors) Fix failure to load on EliteDesk 800 G6") Signed-off-by: James Seo Link: https://patch.msgid.link/20260916221912.434119-5-james@equiv.tech Signed-off-by: Guenter Roeck --- drivers/hwmon/hp-wmi-sensors.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c index 55aee16df57dd5..cfa0d6ad0fb3f1 100644 --- a/drivers/hwmon/hp-wmi-sensors.c +++ b/drivers/hwmon/hp-wmi-sensors.c @@ -526,14 +526,12 @@ static int check_wobj(const union acpi_object *wobj, for (prop = 0; prop <= last_prop; prop++) { type = elements[prop].type; valid_type = property_map[prop]; - if (type != valid_type) { - if (type == ACPI_TYPE_BUFFER && - valid_type == ACPI_TYPE_STRING && - is_raw_wmi_string(elements[prop].buffer.pointer, - elements[prop].buffer.length)) - continue; + if (type == ACPI_TYPE_BUFFER && + is_raw_wmi_string(elements[prop].buffer.pointer, + elements[prop].buffer.length)) + type = ACPI_TYPE_STRING; + if (type != valid_type) return -EINVAL; - } } return 0; @@ -579,6 +577,7 @@ static int check_numeric_sensor_wobj(const union acpi_object *wobj, int prop = HP_WMI_PROPERTY_NAME; acpi_object_type valid_type; union acpi_object *elements; + union acpi_object *element; u32 elem_count; int last_prop; bool is_new; @@ -602,13 +601,20 @@ static int check_numeric_sensor_wobj(const union acpi_object *wobj, elem_count > HP_WMI_MAX_PROPERTIES) return -EINVAL; - type = elements[HP_WMI_PROPERTY_SIZE].type; + element = &elements[HP_WMI_PROPERTY_SIZE]; + type = element->type; switch (type) { case ACPI_TYPE_INTEGER: is_new = true; last_prop = HP_WMI_PROPERTY_RATE_UNITS; break; + case ACPI_TYPE_BUFFER: + if (!is_raw_wmi_string(element->buffer.pointer, + element->buffer.length)) + return -EINVAL; + fallthrough; + case ACPI_TYPE_STRING: is_new = false; last_prop = HP_WMI_PROPERTY_CURRENT_READING; @@ -631,6 +637,10 @@ static int check_numeric_sensor_wobj(const union acpi_object *wobj, for (i = 0; i < elem_count && prop <= last_prop; i++, prop++) { type = elements[i].type; valid_type = hp_wmi_property_map[prop]; + if (type == ACPI_TYPE_BUFFER && + is_raw_wmi_string(elements[i].buffer.pointer, + elements[i].buffer.length)) + type = ACPI_TYPE_STRING; if (type != valid_type) return -EINVAL; @@ -651,6 +661,10 @@ static int check_numeric_sensor_wobj(const union acpi_object *wobj, /* PossibleStates[0] has already been type-checked. */ for (j = 0; i + 1 < elem_count && j + 1 < count; j++) { type = elements[++i].type; + if (type == ACPI_TYPE_BUFFER && + is_raw_wmi_string(elements[i].buffer.pointer, + elements[i].buffer.length)) + type = ACPI_TYPE_STRING; if (type != valid_type) return -EINVAL; } From 7f4a5ec6258fd7c92633ec4b0493fc51166d9398 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 12 Sep 2026 14:08:30 -0400 Subject: [PATCH 0618/1417] net/sched: codel: bound the dropping loop per dequeue call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CoDel control law schedules the next drop one interval/sqrt(count) after the previous drop, using the configured interval (codel_params.interval). For very small intervals the scheduled step rounds down to zero, so the dropping loop in codel_dequeue() never advances and drains the entire backlog under the qdisc lock in one call - an unprivileged user can trigger a soft lockup this way. Fix in the shared codel code used by both codel and fq_codel: 1. Make the control-law step at least 1 tick so the dropping loop always moves forward. 2. Cap the dropping loop at CODEL_MAX_DROPS_PER_DEQUEUE (256) drops per codel_dequeue() call, resyncing drop_next to now when the cap is hit: the catch-up owed to the loop grows with the idle gap and the backlog, which no interval threshold can bound. This is a deliberate behaviour change after long idle gaps. The cap applies to fq_codel (4b549a2ef4be) and the mac80211 TXQ path (fixed interval, cap only). The target sojourn delay (codel_params.target) is not validated: it does not feed the control law, so a sub-tick value is aggressive rather than deadlock-prone. Conditions to recreate the bug: - tc qdisc add dev lo root handle 1: tbf rate 1kbit burst 2kb limit 1000000 - tc qdisc add dev lo parent 1:1 handle 10: codel interval 2us target 1ms noecn limit 1000000 (same for fq_codel) - unpatched kernel: tc accepts it; a UDP flood under the 1kbit tbf soft-lockups (watchdog: BUG: soft lockup) while one codel_dequeue() call drops the backlog under the qdisc lock - patched kernel: same setup, at most 256 drops per dequeue call, no soft lockup Testing: claim reproducer and interval 2us/3us variants run clean; tdc qdisc category passes (see the selftests patch). Fixes: 76e3cc126bb2 ("codel: Controlled Delay AQM") Reported-by: Vega Reviewed-by: Eric Dumazet Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Reviewed-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/QDISC-1L5H.v1.20260912080102@mojatatu.com Signed-off-by: Jakub Kicinski --- include/net/codel.h | 5 +++++ include/net/codel_impl.h | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/net/codel.h b/include/net/codel.h index aa80f744826cd7..183d43c2bd4348 100644 --- a/include/net/codel.h +++ b/include/net/codel.h @@ -140,6 +140,11 @@ struct codel_vars { /* needed shift to get a Q0.32 number from rec_inv_sqrt */ #define REC_INV_SQRT_SHIFT (32 - REC_INV_SQRT_BITS) +/* Cap on drops per codel_dequeue() call: the loop's work depends on the + * idle gap and backlog, both outside our control; resync when exceeded. + */ +#define CODEL_MAX_DROPS_PER_DEQUEUE 256 + /** * struct codel_stats - contains codel shared variables and stats * @maxpacket: largest packet we've seen so far diff --git a/include/net/codel_impl.h b/include/net/codel_impl.h index 2c1f0ec309e9fe..8f26132d45b7f7 100644 --- a/include/net/codel_impl.h +++ b/include/net/codel_impl.h @@ -93,12 +93,17 @@ static void codel_Newton_step(struct codel_vars *vars) * CoDel control_law is t + interval/sqrt(count) * We maintain in rec_inv_sqrt the reciprocal value of sqrt(count) to avoid * both sqrt() and divide operation. + * + * Clamp the increment to at least 1 tick: a very small interval (or a + * large count) can truncate it to zero, stalling the dropping loop. */ static codel_time_t codel_control_law(codel_time_t t, codel_time_t interval, u32 rec_inv_sqrt) { - return t + reciprocal_scale(interval, rec_inv_sqrt << REC_INV_SQRT_SHIFT); + return t + max_t(u32, 1, + reciprocal_scale(interval, + rec_inv_sqrt << REC_INV_SQRT_SHIFT)); } static bool codel_should_drop(const struct sk_buff *skb, @@ -154,6 +159,7 @@ static struct sk_buff *codel_dequeue(void *ctx, codel_skb_dequeue_t dequeue_func) { struct sk_buff *skb = dequeue_func(vars, ctx); + unsigned int drops = 0; codel_time_t now; bool drop; @@ -180,6 +186,14 @@ static struct sk_buff *codel_dequeue(void *ctx, */ while (vars->dropping && codel_time_after_eq(now, vars->drop_next)) { + if (++drops > CODEL_MAX_DROPS_PER_DEQUEUE) { + /* fell far behind the schedule */ + WRITE_ONCE(vars->drop_next, + codel_control_law(now, + params->interval, + vars->rec_inv_sqrt)); + break; + } /* dont care of possible wrap * since there is no more divide. */ From f6fb2ac5e19ae4b66112a698050db80e51f841c3 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 12 Sep 2026 14:08:31 -0400 Subject: [PATCH 0619/1417] selftests/tc-testing: add codel/fq_codel interval boundary cases Add tdc cases locking the codel/fq_codel small-interval uAPI after the dropping-loop bound (previous patch): sub-tick and two-tick intervals are ACCEPTED (the loop bound makes them safe), the 1024us boundary is accepted, and a sub-tick target sojourn delay is accepted (it does not participate in the control law): codel: 6e44/a8c3/a695/9793 - interval 1us/3us/1024us and target 1us accepted (rendered 0us/2us/1.02ms/0us by tc) fq_codel: 1b4d/3540/49c5/3e0f - interval 1us/3us/1024us and target 1us accepted The positive cases match the full rendered qdisc line (tc renders interval 1us as 0us, 3us as 2us, 1024us as 1.02ms), mirroring the existing tests in these files. These cases do not test the dropping-loop bound itself: tdc cannot observe per-dequeue drop counts. c797 (fq_codel target 1 interval 1) passes unmodified on the patched kernel, which is the uAPI evidence for the previous patch. Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-1L5H.v1.20260912080102@mojatatu.com.2 Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/qdiscs/codel.json | 72 +++++++++++++++++++ .../tc-testing/tc-tests/qdiscs/fq_codel.json | 72 +++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/codel.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/codel.json index 6d515d0e5ed696..a894e6f0e26776 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/codel.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/codel.json @@ -213,5 +213,77 @@ "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1p target 5ms interval 100ms", "matchCount": "1", "teardown": ["$TC qdisc del dev $DEV1 handle 1: root"] + }, + { + "id": "6e44", + "name": "Create CODEL with 1us interval, accepted (sub-tick, uAPI locked)", + "category": [ + "qdisc", + "codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root codel interval 1us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1000p target 5ms interval 0us", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "a8c3", + "name": "Create CODEL with 3us interval, accepted (two ticks)", + "category": [ + "qdisc", + "codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root codel interval 3us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1000p target 5ms interval 2us", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "a695", + "name": "Create CODEL with 1024us interval boundary accepted", + "category": [ + "qdisc", + "codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root codel interval 1024us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1000p target 5ms interval 1.02ms", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "9793", + "name": "Create CODEL with 1us target, accepted (target not in control law)", + "category": [ + "qdisc", + "codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root codel target 1us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1000p target 0us interval 100ms", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] } ] diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/fq_codel.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/fq_codel.json index 4ce62b857fd7ab..de6a1b8d954aef 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/fq_codel.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/fq_codel.json @@ -316,5 +316,77 @@ "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 1p flows 1024 quantum.*target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64", "matchCount": "1", "teardown": ["$TC qdisc del dev $DEV1 handle 1: root"] + }, + { + "id": "1b4d", + "name": "Create FQ_CODEL with 1us interval, accepted (sub-tick, uAPI locked)", + "category": [ + "qdisc", + "fq_codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root fq_codel interval 1us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 10240p flows 1024 quantum [0-9]+ target 5ms interval 0us memory_limit 32Mb ecn drop_batch 64", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "3540", + "name": "Create FQ_CODEL with 3us interval, accepted (two ticks)", + "category": [ + "qdisc", + "fq_codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root fq_codel interval 3us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 10240p flows 1024 quantum [0-9]+ target 5ms interval 2us memory_limit 32Mb ecn drop_batch 64", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "49c5", + "name": "Create FQ_CODEL with 1024us interval boundary accepted", + "category": [ + "qdisc", + "fq_codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root fq_codel interval 1024us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 10240p flows 1024 quantum [0-9]+ target 5ms interval 1.02ms memory_limit 32Mb ecn drop_batch 64", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "3e0f", + "name": "Create FQ_CODEL with 1us target, accepted (target not in control law)", + "category": [ + "qdisc", + "fq_codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root fq_codel target 1us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 10240p flows 1024 quantum [0-9]+ target 0us interval 100ms memory_limit 32Mb ecn drop_batch 64", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] } ] From 88f113634028ca90a857031837d8061d1a9e1a7b Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:05:11 +0000 Subject: [PATCH 0620/1417] watchdog: sp5100_tco: Fix pci_dev reference leak in sp5100_tco_init() sp5100_tco_init() stores the PCI device matched by for_each_pci_dev() in the global sp5100_tco_pci and keeps its reference for the lifetime of the driver, but neither sp5100_tco_exit() nor the error paths of sp5100_tco_init() call pci_dev_put(), leaking the reference on driver registration failure and on every module load/unload cycle. Drop the reference when the platform driver or device registration fails and when the module is unloaded. Fixes: 15e28bf13008 ("watchdog: Add support for sp5100 chipset TCO") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260916170511.2086199-1-vulab@iscas.ac.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/sp5100_tco.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 7e99c3b1f3676b..72ad59649ccc41 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -605,8 +605,10 @@ static int __init sp5100_tco_init(void) pr_info("SP5100/SB800 TCO WatchDog Timer Driver\n"); err = platform_driver_register(&sp5100_tco_driver); - if (err) + if (err) { + pci_dev_put(sp5100_tco_pci); return err; + } sp5100_tco_platform_device = platform_device_register_simple(TCO_DRIVER_NAME, -1, NULL, 0); @@ -619,6 +621,7 @@ static int __init sp5100_tco_init(void) unreg_platform_driver: platform_driver_unregister(&sp5100_tco_driver); + pci_dev_put(sp5100_tco_pci); return err; } @@ -626,6 +629,7 @@ static void __exit sp5100_tco_exit(void) { platform_device_unregister(sp5100_tco_platform_device); platform_driver_unregister(&sp5100_tco_driver); + pci_dev_put(sp5100_tco_pci); } module_init(sp5100_tco_init); From a9ce4053dc945c5372dedba5017ee675b30dc0c5 Mon Sep 17 00:00:00 2001 From: Mark Amirkan Date: Sun, 13 Sep 2026 17:14:09 -0700 Subject: [PATCH 0621/1417] net: lan743x: fix RX checksum use-after-free lan743x_rx_process_buffer() adds each non-first receive buffer to the head skb's frag_list. On the last descriptor, lan743x_rx_trim_skb() linearizes the head and frees the fragment skb metadata. The checksum-success path then writes ip_summed through the local skb pointer, which still points to the final fragment. This causes a use-after-free write when a packet spans more than one receive buffer. Set ip_summed on the surviving head skb instead. Multi-buffer receive can occur after a live MTU increase because existing ring entries keep their old buffer size until they are replenished. A KUnit test invoking lan743x_rx_process_buffer() with a two-buffer packet produced a one-byte KASAN use-after-free write before this change. The same test passed after the change. The driver object also builds with W=1. This was not tested on physical LAN743x hardware. Fixes: cd6910501cfd ("net: lan743x: Add support for Rx IP & TCP checksum offload") Cc: stable@vger.kernel.org Signed-off-by: Mark Amirkan Reviewed-by: Chenguang Zhao Link: https://patch.msgid.link/20260913-b4-send-lan743x-uaf-v1-1-73d563d08ba9@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/lan743x_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microchip/lan743x_main.c b/drivers/net/ethernet/microchip/lan743x_main.c index 24ae56a3c9ed76..82d3ec20ba259c 100644 --- a/drivers/net/ethernet/microchip/lan743x_main.c +++ b/drivers/net/ethernet/microchip/lan743x_main.c @@ -2604,7 +2604,7 @@ static int lan743x_rx_process_buffer(struct lan743x_rx *rx) rx->adapter->netdev); if (rx->adapter->netdev->features & NETIF_F_RXCSUM) { if (!is_ice && !is_tce && !is_icsm) - skb->ip_summed = CHECKSUM_UNNECESSARY; + rx->skb_head->ip_summed = CHECKSUM_UNNECESSARY; } netdev_dbg(netdev, "sending %d byte frame to OS", rx->skb_head->len); From 33ff111d7ba3beb86e28938d6382bb5beabd865a Mon Sep 17 00:00:00 2001 From: Mark Amirkan Date: Sun, 13 Sep 2026 10:28:08 +0000 Subject: [PATCH 0622/1417] net/packet: clear RX owner on VNET header error Commit 61fad6816fc1 ("net/packet: tpacket_rcv: avoid a producer race condition") added rx_owner_map and made tpacket_rcv() claim a V1 or V2 ring slot before converting the virtio-net header. If the conversion fails, the drop path leaves the slot claimed. With a one-frame TPACKET_V2 ring, an unsupported UDP GSO packet leaves the only slot unavailable, so the ring also drops the next valid packet. Clear the ownership bit on this error path. TPACKET_V3 already clears its block state here. Fixes: 61fad6816fc1 ("net/packet: tpacket_rcv: avoid a producer race condition") Cc: stable@vger.kernel.org Signed-off-by: Mark Amirkan Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260913-b4-send-packet-vnet-v1-1-5545ffb528ae@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 76bde7906d4946..50cae32ae26922 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -2384,7 +2384,9 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev, virtio_net_hdr_from_skb(skb, h.raw + macoff - sizeof(struct virtio_net_hdr), vio_le(), true, 0)) { - if (po->tp_version == TPACKET_V3) + if (po->tp_version <= TPACKET_V2) + __clear_bit(slot_id, po->rx_ring.rx_owner_map); + else prb_clear_blk_fill_status(&po->rx_ring); goto drop_n_account; } From 60404266ef3e0a1cd8f7a164060e0c83efb72f4b Mon Sep 17 00:00:00 2001 From: Mark Amirkan Date: Sun, 13 Sep 2026 10:30:05 +0000 Subject: [PATCH 0623/1417] mptcp: return sk_wait_data() errors from recvmsg() Commit 581302298524 ("mptcp: error out earlier on disconnect") made mptcp_recvmsg() stop when sk_wait_data() returns an error. The error is stored in err, but the function then jumps to a path which returns copied. When no data was copied, recvmsg() therefore returns zero and reports a false EOF. Store the result in copied, which is the value returned by the function. This also keeps the usual partial-read result when data was copied before the error. A recvmsg() blocked in one thread reproduces the issue when another thread disconnects the same MPTCP socket with connect(AF_UNSPEC). Before this change recvmsg() returns zero; afterwards it returns -EPIPE. Fixes: 581302298524 ("mptcp: error out earlier on disconnect") Cc: stable@vger.kernel.org Signed-off-by: Mark Amirkan Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260913-b4-send-mptcp-recv-error-v1-1-4eaa3684a8b8@gmail.com Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 0098e283093182..8dc25ef1542c58 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2459,7 +2459,7 @@ static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, mptcp_cleanup_rbuf(msk, copied); err = sk_wait_data(sk, &timeo, last); if (err < 0) { - err = copied ? : err; + copied = copied ? : err; goto out_err; } } From 37213e61120297920ae4c937fcb326a360da5084 Mon Sep 17 00:00:00 2001 From: Mark Amirkan Date: Sun, 13 Sep 2026 10:31:08 +0000 Subject: [PATCH 0624/1417] net/packet: avoid truncating TPACKET_V3 private size tpacket_req3.tp_sizeof_priv is an unsigned int, and packet_set_ring() validates the full value against the block size. init_prb_bdqc() then stores it in the unsigned short blk_sizeof_priv field. Commit 2b6867c2ce76 ("net/packet: fix overflow in check for priv area size") fixed the validation arithmetic, but an accepted value above USHRT_MAX still narrows when it is stored. For a 131072-byte block, tp_sizeof_priv=65536 is valid. The narrowing makes offset_to_first_pkt 48 instead of 65584, so packet records can be placed in the private area that userspace asked the kernel to preserve. blk_sizeof_priv is internal state, so widen it to hold the validated UAPI value. Fixes: f6fb8f100b80 ("af-packet: TPACKET_V3 flexible buffer implementation.") Cc: stable@vger.kernel.org Signed-off-by: Mark Amirkan Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260913-b4-send-packet-private-v1-1-925eab2cd388@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/packet/internal.h b/net/packet/internal.h index b76e645cd78d1a..f5c8cd0eed2c14 100644 --- a/net/packet/internal.h +++ b/net/packet/internal.h @@ -21,7 +21,7 @@ struct tpacket_kbdq_core { unsigned int hdrlen; unsigned char reset_pending_on_curr_blk; unsigned short kactive_blk_num; - unsigned short blk_sizeof_priv; + unsigned int blk_sizeof_priv; unsigned short version; From 150dba2c69e93302af24a0c868eebe4871e2e107 Mon Sep 17 00:00:00 2001 From: Farhad Alemi Date: Sat, 12 Sep 2026 07:40:09 +0000 Subject: [PATCH 0625/1417] net: remove WARN_ON_ONCE() from the dev_fill_forward_path() loop check ipip_fill_forward_path() and ip6_tnl_fill_forward_path() look up the route to the tunnel's remote endpoint and set ctx->dev to its device, which is the tunnel itself when that route resolves back to the tunnel. dev_fill_forward_path() then makes no progress and trips WARN_ON_ONCE(last_dev == ctx->dev) as soon as a flowtable tries to offload a flow through the tunnel. That routing loop is a configuration any CAP_NET_ADMIN user can set up, and ip_tunnel_xmit() and ip6_tnl_xmit() already treat it as a tx error, so remove the warning and just fail the walk, as commit 008e7a7c293b ("net: remove WARN_ON_ONCE when accessing forward path array") did for the path stack overflow. Fixes: ab427db17885 ("netfilter: flowtable: Add IPIP rx sw acceleration") Fixes: d98103575dcd ("netfilter: flowtable: Add IP6IP6 rx sw acceleration") Closes: https://lore.kernel.org/all/CA+0ovCgaRvbd0Udj70b2xxG8Cx3CaCpNhnf1V4RWQuDveZYZhA@mail.gmail.com/ Suggested-by: Pablo Neira Ayuso Signed-off-by: Farhad Alemi Reviewed-by: Xuanqiang Luo Link: https://patch.msgid.link/CA+0ovCgKDOk+Bg6Gh5Lwx94u_jJjQ30-vY1JcY2BYfhnWJJbPA@mail.gmail.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index ecfbd72d5d1a41..c67900354fa64b 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -789,7 +789,7 @@ int dev_fill_forward_path(struct net_device_path_ctx *ctx, goto err_out; stack->num_paths++; - if (WARN_ON_ONCE(last_dev == ctx->dev)) + if (last_dev == ctx->dev) goto err_out; } From 5ae916fabca141b79b32e2e57f3c915c0f1e1b2e Mon Sep 17 00:00:00 2001 From: Yige Jiang Date: Sun, 13 Sep 2026 14:41:02 +0800 Subject: [PATCH 0626/1417] net: netsec: fix device_node reference leak on phy_np netsec_of_probe() takes a reference on the PHY device_node with of_parse_phandle() and stores it in priv->phy_np, but the driver never drops it. One device_node reference is leaked per probe, on the success path as well as on every error path reached after netsec_of_probe(). Neither consumer takes ownership. of_mdio_parse_addr() is a static inline taking a const struct device_node * that only reads the "reg" property. of_phy_connect() borrows as well: of_phy_get_and_connect() in drivers/net/mdio/of_mdio.c brackets its own call with of_node_get() at :364 and of_node_put() at :373, which would be a double put if of_phy_connect() consumed the reference. The node is still in use at netsec_netdev_open() time, where it is passed to of_phy_connect(), so it has device lifetime. Release it at the probe error label, which every failure path after the acquire funnels through, and in netsec_remove(). Both releases precede free_netdev(), since priv is netdev_priv(ndev). The ACPI probe path leaves priv->phy_np NULL and of_node_put(NULL) is a no-op. There is no end-user visible symptom on currently supported platforms: a device_node is only freed once OF_DYNAMIC is enabled and the node has been detached, so on a static device tree the imbalance is inert. It is observable as a refcount that grows across bind/unbind cycles, and would matter under device tree overlays. Found by static analysis of reference acquire/release pairing rather than from a runtime report. No reproducer was produced and the change has not been runtime tested; it is compile-tested only (arm64, CONFIG_SNI_NETSEC=m via COMPILE_TEST). Fixes: 533dd11a12f6 ("net: socionext: Add Synquacer NetSec driver") Signed-off-by: Yige Jiang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260913064102.37452-1-yigejiang86@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/socionext/netsec.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/socionext/netsec.c b/drivers/net/ethernet/socionext/netsec.c index d14a6584473c84..79a0a324c921d7 100644 --- a/drivers/net/ethernet/socionext/netsec.c +++ b/drivers/net/ethernet/socionext/netsec.c @@ -2149,6 +2149,7 @@ static int netsec_probe(struct platform_device *pdev) pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); free_ndev: + of_node_put(priv->phy_np); free_netdev(ndev); dev_err(&pdev->dev, "init failed\n"); @@ -2166,6 +2167,7 @@ static void netsec_remove(struct platform_device *pdev) netif_napi_del(&priv->napi); pm_runtime_disable(&pdev->dev); + of_node_put(priv->phy_np); free_netdev(priv->ndev); } From 490599ab23134962a6d18a024e84541d77bdb999 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 14 Sep 2026 19:23:27 -0700 Subject: [PATCH 0627/1417] eth: fbnic: ring the doorbell if a burst ends in a drop fbnic_tx_map() skips the doorbell write, and the completion request, for every packet handed to it with xmit_more set, counting on the packet which ends the burst to publish them all. When that packet is dropped instead - skb_put_padto(), skb_cow_head() or a DMA mapping failure - nothing rings. The descriptors of the preceding packets stay invisible to the HW until the next transmit on that queue, which for a burst-then-idle workload may never come. Remember the meta descriptor of the last packet left without a doorbell and flush it from the error paths. The completion request has to be set on that descriptor rather than simply writing the tail, otherwise the HW would transmit the packets but never report a head, and the ring would fill up and stall for good. This is very similar to Joe's recent series of fixes for bnxt. Not seen in real life, reproduced under QEMU with failure injection. Fixes: 9a57bacd574b ("eth: fbnic: Add basic Tx handling") Reviewed-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260915022327.913218-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 42 +++++++++++++++----- drivers/net/ethernet/meta/fbnic/fbnic_txrx.h | 9 ++++- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index 401f8b8ae1cae4..e7918d3f6aba9c 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -311,6 +311,29 @@ fbnic_rx_csum(u64 rcd, struct sk_buff *skb, struct fbnic_ring *rcq, } } +static void fbnic_tx_doorbell(struct fbnic_ring *ring, __le64 *meta) +{ + *meta |= cpu_to_le64(FBNIC_TWD_FLAG_REQ_COMPLETION); + ring->deferred_meta = -1; + + /* Force DMA writes to flush before writing to tail */ + dma_wmb(); + + writel(ring->tail, ring->doorbell); +} + +/* Packets handed to us with xmit_more set are left in the ring without a + * doorbell, and without a completion request, in the expectation that the + * packet ending the burst will ring for all of them. If that packet gets + * dropped instead we have to ring here, otherwise the descriptors sit in + * the ring until the next transmit, which may never come. + */ +static void fbnic_tx_flush_doorbell(struct fbnic_ring *ring) +{ + if (ring->deferred_meta >= 0) + fbnic_tx_doorbell(ring, &ring->desc[ring->deferred_meta]); +} + static bool fbnic_tx_map(struct fbnic_ring *ring, struct sk_buff *skb, __le64 *meta) { @@ -378,14 +401,10 @@ fbnic_tx_map(struct fbnic_ring *ring, struct sk_buff *skb, __le64 *meta) /* Verify there is room for another packet */ fbnic_maybe_stop_tx(skb->dev, ring, FBNIC_MAX_SKB_DESC); - if (fbnic_tx_sent_queue(skb, ring)) { - *meta |= cpu_to_le64(FBNIC_TWD_FLAG_REQ_COMPLETION); - - /* Force DMA writes to flush before writing to tail */ - dma_wmb(); - - writel(tail, ring->doorbell); - } + if (fbnic_tx_sent_queue(skb, ring)) + fbnic_tx_doorbell(ring, meta); + else + ring->deferred_meta = meta - ring->desc; return false; dma_error: @@ -425,8 +444,10 @@ fbnic_xmit_frame_ring(struct sk_buff *skb, struct fbnic_ring *ring) * otherwise try next time */ desc_needed = skb_shinfo(skb)->nr_frags + 10; - if (fbnic_maybe_stop_tx(skb->dev, ring, desc_needed)) + if (fbnic_maybe_stop_tx(skb->dev, ring, desc_needed)) { + fbnic_tx_flush_doorbell(ring); return NETDEV_TX_BUSY; + } *meta = cpu_to_le64(FBNIC_TWD_FLAG_DEST_MAC); @@ -447,6 +468,8 @@ fbnic_xmit_frame_ring(struct sk_buff *skb, struct fbnic_ring *ring) err_free: dev_kfree_skb_any(skb); err_count: + fbnic_tx_flush_doorbell(ring); + u64_stats_update_begin(&ring->stats.syncp); ring->stats.dropped++; u64_stats_update_end(&ring->stats.syncp); @@ -2491,6 +2514,7 @@ static void fbnic_enable_twq0(struct fbnic_ring *twq) fbnic_ring_wr32(twq, FBNIC_QUEUE_TWQ0_CTL, FBNIC_QUEUE_TWQ_CTL_RESET); twq->tail = 0; twq->head = 0; + twq->deferred_meta = -1; /* Store descriptor ring address and size */ fbnic_ring_wr32(twq, FBNIC_QUEUE_TWQ0_BAL, lower_32_bits(twq->dma)); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h index e03c9d2c38dca1..f5899446dcc510 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h @@ -128,9 +128,14 @@ struct fbnic_ring { /* Rx BDQs only */ struct page_pool *page_pool; - /* Deferred_head is used to cache the head for TWQ1 if + /* TWQ0 only, index of the meta descriptor of the last packet + * placed in the ring without ringing the doorbell, -1 if the + * doorbell is in sync with the tail. + */ + s32 deferred_meta; + + /* TCQ only, used to cache the head for TWQ1 if * an attempt is made to clean TWQ1 with zero napi_budget. - * We do not use it for any other ring. */ s32 deferred_head; }; From 9ed55f3dbef4f4adfe65eb03b0c35c53229a8490 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 15 Sep 2026 04:30:54 +0000 Subject: [PATCH 0628/1417] net: lock the socket in sock_gettstamp() sk->sk_flags must only be changed while holding the socket lock, because sock_set_flag() and sock_reset_flag() use non atomic operations (__set_bit() and __clear_bit()). sock_gettstamp() is one of the last places where a bit of sk->sk_flags is changed from a syscall without owning the socket lock, through sock_enable_timestamp(sk, SOCK_TIMESTAMP). sk_set_memalloc() and sk_clear_memalloc() also change sk->sk_flags without the socket lock, but their callers (nbd, iscsi_tcp, nvme-tcp, sunrpc, wireguard) need a careful audit, this will be addressed in a separate patch. Jungwoo Lee and Wongi Lee reported an UDP socket use-after-free caused by this bug: a SIOCGSTAMPNS_NEW ioctl racing with bind() can cancel the SOCK_RCU_FREE bit that udp_lib_get_port() just set, because both threads perform a read-modify-write on the same word. CPU 0 (bind) CPU 1 (SIOCGSTAMPNS_NEW) -------------------------------- ---------------------------- read sk_flags = F read sk_flags = F compute F | BIT(SOCK_RCU_FREE) compute F | BIT(SOCK_TIMESTAMP) store F | BIT(SOCK_RCU_FREE) sk_add_node_rcu(sk, ...) store F | BIT(SOCK_TIMESTAMP) After the lost update, SOCK_RCU_FREE is clear while the socket is visible to lockless UDP receive lookups. sk_destruct() then frees the socket immediately instead of waiting for a RCU grace period, while the receive path still holds a reference-less pointer to it: BUG: KASAN: slab-use-after-free in ipv4_pktinfo_prepare+0x30/0x410 Read of size 8 at addr ffff888008806610 by task exploit/207 CPU: 0 UID: 1000 PID: 207 Comm: exploit Not tainted 6.12.95+ #1 ipv4_pktinfo_prepare+0x30/0x410 udp_queue_rcv_one_skb+0x51c/0x1180 udp_unicast_rcv_skb+0x109/0x350 ip_protocol_deliver_rcu+0x14b/0x310 ip_local_deliver_finish+0x29d/0x390 ip_local_deliver+0x24d/0x2a0 Only grab the socket lock when SOCK_TIMESTAMP has to be set, to keep the common case lockless. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Jungwoo Lee Reported-by: Wongi Lee Signed-off-by: Eric Dumazet Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260915043055.3441600-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/sock.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/core/sock.c b/net/core/sock.c index fa60b7494c5869..d5e302e21e85b0 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -3911,7 +3911,14 @@ int sock_gettstamp(struct socket *sock, void __user *userstamp, struct sock *sk = sock->sk; struct timespec64 ts; - sock_enable_timestamp(sk, SOCK_TIMESTAMP); + /* sk->sk_flags must only be changed under the socket lock, + * because sock_set_flag() uses non atomic operations. + */ + if (!sock_flag(sk, SOCK_TIMESTAMP)) { + lock_sock(sk); + sock_enable_timestamp(sk, SOCK_TIMESTAMP); + release_sock(sk); + } ts = ktime_to_timespec64(sock_read_timestamp(sk)); if (ts.tv_sec == -1) return -ENOENT; From 400cb663ca019bae6eb878f06f1094ddf7c0b0df Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 14:48:49 +0800 Subject: [PATCH 0629/1417] watchdog: digicolor: Avoid division by zero clk_get_rate() could return 0. Avoid a division by zero panic. Since get_timeleft() cannot propagate errors, check the clock rate early in probe() and cache the rate in the driver data as it is unlikely to change at runtime. Fixes: 336694a01dae ("watchdog: digicolor: driver for Conexant Digicolor CX92755 SoC") Cc: stable@vger.kernel.org Signed-off-by: Tzung-Bi Shih Acked-by: Baruch Siach Link: https://patch.msgid.link/20260913064851.8239-2-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/digicolor_wdt.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/watchdog/digicolor_wdt.c b/drivers/watchdog/digicolor_wdt.c index 073d37867f4790..de1a3267a97233 100644 --- a/drivers/watchdog/digicolor_wdt.c +++ b/drivers/watchdog/digicolor_wdt.c @@ -25,6 +25,7 @@ struct dc_wdt { void __iomem *base; struct clk *clk; spinlock_t lock; + unsigned long rate; }; static unsigned timeout; @@ -61,7 +62,7 @@ static int dc_wdt_start(struct watchdog_device *wdog) { struct dc_wdt *wdt = watchdog_get_drvdata(wdog); - dc_wdt_set(wdt, wdog->timeout * clk_get_rate(wdt->clk)); + dc_wdt_set(wdt, wdog->timeout * wdt->rate); return 0; } @@ -79,7 +80,7 @@ static int dc_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t) { struct dc_wdt *wdt = watchdog_get_drvdata(wdog); - dc_wdt_set(wdt, t * clk_get_rate(wdt->clk)); + dc_wdt_set(wdt, t * wdt->rate); wdog->timeout = t; return 0; @@ -90,7 +91,7 @@ static unsigned int dc_wdt_get_timeleft(struct watchdog_device *wdog) struct dc_wdt *wdt = watchdog_get_drvdata(wdog); uint32_t count = readl_relaxed(wdt->base + TIMER_A_COUNT); - return count / clk_get_rate(wdt->clk); + return count / wdt->rate; } static const struct watchdog_ops dc_wdt_ops = { @@ -130,7 +131,11 @@ static int dc_wdt_probe(struct platform_device *pdev) wdt->clk = devm_clk_get(dev, NULL); if (IS_ERR(wdt->clk)) return PTR_ERR(wdt->clk); - dc_wdt_wdd.max_timeout = U32_MAX / clk_get_rate(wdt->clk); + + wdt->rate = clk_get_rate(wdt->clk); + if (!wdt->rate) + return -EINVAL; + dc_wdt_wdd.max_timeout = U32_MAX / wdt->rate; dc_wdt_wdd.timeout = dc_wdt_wdd.max_timeout; dc_wdt_wdd.parent = dev; From 5af7d2cbd20f893def03c8310a460ade66a5d822 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 14:48:50 +0800 Subject: [PATCH 0630/1417] watchdog: rtd119x: Avoid division by zero clk_get_rate() could return 0. Avoid a division by zero panic. Fixes: 2bdf6acbfead ("watchdog: Add Realtek RTD1295") Cc: stable@vger.kernel.org Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260913064851.8239-3-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/rtd119x_wdt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/rtd119x_wdt.c b/drivers/watchdog/rtd119x_wdt.c index 984905695dde51..0bfadb58917b9d 100644 --- a/drivers/watchdog/rtd119x_wdt.c +++ b/drivers/watchdog/rtd119x_wdt.c @@ -98,6 +98,7 @@ static int rtd119x_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rtd119x_watchdog_device *data; + unsigned long rate; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -111,10 +112,14 @@ static int rtd119x_wdt_probe(struct platform_device *pdev) if (IS_ERR(data->clk)) return PTR_ERR(data->clk); + rate = clk_get_rate(data->clk); + if (!rate) + return -EINVAL; + data->wdt_dev.info = &rtd119x_wdt_info; data->wdt_dev.ops = &rtd119x_wdt_ops; data->wdt_dev.timeout = 120; - data->wdt_dev.max_timeout = 0xffffffff / clk_get_rate(data->clk); + data->wdt_dev.max_timeout = 0xffffffff / rate; data->wdt_dev.min_timeout = 1; data->wdt_dev.parent = dev; From 6274281c41efa8dd1aa5234c59ad904ff89d7af4 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 14:48:51 +0800 Subject: [PATCH 0631/1417] watchdog: rzv2h: Avoid division by zero clk_get_rate() could return 0. Avoid a division by zero panic. Fixes: f6febd0a30b6 ("watchdog: Add Watchdog Timer driver for RZ/V2H(P)") Cc: stable@vger.kernel.org Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260913064851.8239-4-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/rzv2h_wdt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/rzv2h_wdt.c b/drivers/watchdog/rzv2h_wdt.c index 3b6abb66a1da00..83540dd9a37b6d 100644 --- a/drivers/watchdog/rzv2h_wdt.c +++ b/drivers/watchdog/rzv2h_wdt.c @@ -278,6 +278,7 @@ static int rzv2h_wdt_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct rzv2h_wdt_priv *priv; struct clk *count_clk; + unsigned long rate; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -314,8 +315,12 @@ static int rzv2h_wdt_probe(struct platform_device *pdev) return dev_err_probe(dev, -EINVAL, "Invalid count source\n"); } + rate = clk_get_rate(count_clk); + if (!rate) + return dev_err_probe(dev, -EINVAL, "Invalid clock rate\n"); + priv->wdev.max_hw_heartbeat_ms = (MILLI * priv->of_data->timeout_cycles * - priv->of_data->cks_div) / clk_get_rate(count_clk); + priv->of_data->cks_div) / rate; dev_dbg(dev, "max hw timeout of %dms\n", priv->wdev.max_hw_heartbeat_ms); ret = devm_pm_runtime_enable(dev); From 1d9763f34a85680db1e8233d654fdb85e5f897cc Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 00:33:34 +0800 Subject: [PATCH 0632/1417] watchdog: msc313e: Propagate error code in resume() If msc313e_wdt_start() fails during system resume, the error is currently ignored. Consequently, the watchdog isn't running without the user's knowledge. Propagate the error code and print a message if msc313e_wdt_start() fails. Signed-off-by: Tzung-Bi Shih Fixes: e9800b7994642 ("watchdog: Add Mstar MSC313e WDT driver") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260912163334.28636-1-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index 4a5cce2a16b185..9a400562d1aef9 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -191,11 +191,15 @@ static int __maybe_unused msc313e_wdt_suspend(struct device *dev) static int __maybe_unused msc313e_wdt_resume(struct device *dev) { struct msc313e_wdt_priv *priv = dev_get_drvdata(dev); + int ret = 0; - if (watchdog_active(&priv->wdev) || watchdog_hw_running(&priv->wdev)) - msc313e_wdt_start(&priv->wdev); + if (watchdog_active(&priv->wdev) || watchdog_hw_running(&priv->wdev)) { + ret = msc313e_wdt_start(&priv->wdev); + if (ret) + dev_err(dev, "Failed to restart watchdog (err=%d)\n", ret); + } - return 0; + return ret; } static SIMPLE_DEV_PM_OPS(msc313e_wdt_pm_ops, msc313e_wdt_suspend, msc313e_wdt_resume); From 22737cfced627ffcb4b5c36d63bb3d4476f63213 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 14:51:26 +0800 Subject: [PATCH 0633/1417] watchdog: msc313e: Fix premature reset during timeout update Updating the 32-bit hardware timeout requires writing to two 16-bit registers sequentially. If the watchdog is actively running, this non-atomic update might trigger a premature system reset. Clear the watchdog counter before updating the registers to prevent the timer from timing out prematurely against an intermediate threshold. Fixes: e9800b799464 ("watchdog: Add Mstar MSC313e WDT driver") Cc: stable@vger.kernel.org Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260913065126.8350-1-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index 9a400562d1aef9..d62586f6e09d0a 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -46,6 +46,9 @@ static void msc313e_wdt_set_hw_timeout(struct msc313e_wdt_priv *priv, { u32 t = timeout * clk_get_rate(priv->clk); + /* Clear before to prevent premature reset during non-atomic updates. */ + writew(1, priv->base + REG_WDT_CLR); + writew(t & 0xffff, priv->base + REG_WDT_MAX_PRD_L); writew((t >> 16) & 0xffff, priv->base + REG_WDT_MAX_PRD_H); writew(1, priv->base + REG_WDT_CLR); @@ -76,6 +79,9 @@ static int msc313e_wdt_stop(struct watchdog_device *wdev) { struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev); + /* Clear before to prevent premature reset during non-atomic updates. */ + writew(1, priv->base + REG_WDT_CLR); + writew(0, priv->base + REG_WDT_MAX_PRD_L); writew(0, priv->base + REG_WDT_MAX_PRD_H); writew(0, priv->base + REG_WDT_CLR); From 5071122bf5a628494db16d98d253f622a5aab074 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Mon, 14 Sep 2026 14:23:53 +0800 Subject: [PATCH 0634/1417] watchdog: da9062: fix suspend/resume handling of HW_RUNNING watchdog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit da9062_wdt_suspend() and da9062_wdt_resume() only check watchdog_active(), when the watchdog is left running by the driver sets WDOG_HW_RUNNING in da9062_wdt_probe() but userspace never opens the device, so WDOG_ACTIVE remains cleared, the wdt_disable() will not be executed in da9062_wdt_suspend. In this case, the suspend callback is a no-op and the watchdog keeps counting during system suspend, leading to an unexpected system reset. Check WDOG_HW_RUNNING and wdt->wdd,can fix this issue. Fixes: f6c98b08381c7 ("watchdog: da9062: add power management ops") Cs: stable@vger.kernel.org Signed-off-by: Li Jun Link: https://patch.msgid.link/20260914062353.582205-1-lijun01@kylinos.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/da9062_wdt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/da9062_wdt.c b/drivers/watchdog/da9062_wdt.c index 426962547df160..4d558652e9e710 100644 --- a/drivers/watchdog/da9062_wdt.c +++ b/drivers/watchdog/da9062_wdt.c @@ -256,7 +256,7 @@ static int __maybe_unused da9062_wdt_suspend(struct device *dev) if (!wdt->use_sw_pm) return 0; - if (watchdog_active(wdd)) + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) return da9062_wdt_stop(wdd); return 0; @@ -270,7 +270,7 @@ static int __maybe_unused da9062_wdt_resume(struct device *dev) if (!wdt->use_sw_pm) return 0; - if (watchdog_active(wdd)) + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) return da9062_wdt_start(wdd); return 0; From 1dd85662fee6e2ac580b1c4f9a0c0a7ae6e31f0e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 14 Sep 2026 23:26:41 +0200 Subject: [PATCH 0635/1417] net: ethernet: cortina: Ack RX overrun interrupt correctly The RX overrun interrupt is reported in interrupt status register 4, but gmac_irq() acknowledges it using the RX descriptor error bit from status register 0. For GMAC0 this writes the GMAC1 overrun bit, while for GMAC1 the shift leaves no bit in the 32-bit register. Acknowledge the same per-port RX overrun bit that was detected. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260914-b4-gemini-ethernet-fixes-2-v2-1-5ab39a047b90@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cortina/gemini.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index f08de623e6f7c7..2dd2fa801829c1 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -1798,7 +1798,7 @@ static irqreturn_t gmac_irq(int irq, void *data) if (val & (GMAC0_RX_OVERRUN_INT_BIT << (netdev->dev_id * 8))) { spin_lock(&geth->irq_lock); - writel(GMAC0_RXDERR_INT_BIT << (netdev->dev_id * 8), + writel(GMAC0_RX_OVERRUN_INT_BIT << (netdev->dev_id * 8), geth->base + GLOBAL_INTERRUPT_STATUS_4_REG); u64_stats_update_begin(&port->ir_stats_syncp); ++port->stats.rx_fifo_errors; From 8f0ca55016a7647109ae2bc91bcb346fc8b13785 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:07:04 +0000 Subject: [PATCH 0636/1417] watchdog: starfive-wdt: Fix runtime PM leak in starfive_wdt_pm_start() starfive_wdt_pm_start() takes a runtime PM reference with pm_runtime_get_sync(), which increments the usage counter even when it fails, and returns the error without dropping it again. The watchdog core does not invoke the stop callback when start fails, so the reference taken on the error path is leaked. Use pm_runtime_resume_and_get() instead, which keeps the usage counter balanced when the resume fails. Fixes: db728ea9c7be ("drivers: watchdog: Add StarFive Watchdog driver") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260916170704.2086331-1-vulab@iscas.ac.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/starfive-wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/starfive-wdt.c b/drivers/watchdog/starfive-wdt.c index af55adc4a3c69b..5a3254c83d3bb8 100644 --- a/drivers/watchdog/starfive-wdt.c +++ b/drivers/watchdog/starfive-wdt.c @@ -371,7 +371,7 @@ static void starfive_wdt_stop(struct starfive_wdt *wdt) static int starfive_wdt_pm_start(struct watchdog_device *wdd) { struct starfive_wdt *wdt = watchdog_get_drvdata(wdd); - int ret = pm_runtime_get_sync(wdd->parent); + int ret = pm_runtime_resume_and_get(wdd->parent); if (ret < 0) return ret; From 42d1221d321e55afc7bba9109a77aaf5a817c8a3 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 31 Aug 2026 12:27:20 -0700 Subject: [PATCH 0637/1417] scsi: megaraid_sas: Protect megasas_get_ctrl_info() in megasas_resume() Protect the megasas_get_ctrl_info() call in megasas_resume() with instance->reset_mutex using scoped_guard(). megasas_get_ctrl_info() may release and reacquire instance->reset_mutex. Hence, calling this function without holding instance->reset_mutex is not safe. Fixes: c3b10a55abc9 ("scsi: megaraid_sas: Update controller info during resume") Cc: Kashyap Desai Cc: Sumit Saxena Cc: Shivasharan S Cc: Chandrakanth patil Signed-off-by: Bart Van Assche Link: https://patch.msgid.link/f06b5ee432b21cf293f0663e15b64f75a84b9fd5.1788204406.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/megaraid/megaraid_sas_base.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/megaraid/megaraid_sas_base.c b/drivers/scsi/megaraid/megaraid_sas_base.c index b95f187297aed8..4f9a53769966fb 100644 --- a/drivers/scsi/megaraid/megaraid_sas_base.c +++ b/drivers/scsi/megaraid/megaraid_sas_base.c @@ -7886,7 +7886,9 @@ megasas_resume(struct device *dev) goto fail_init_mfi; } - if (megasas_get_ctrl_info(instance) != DCMD_SUCCESS) + scoped_guard(mutex, &instance->reset_mutex) + rval = megasas_get_ctrl_info(instance); + if (rval != DCMD_SUCCESS) goto fail_init_mfi; tasklet_init(&instance->isr_tasklet, instance->instancet->tasklet, From 5d063822ac5184939c1ed377a339a01d8ae814e8 Mon Sep 17 00:00:00 2001 From: Guanglei Zhu Date: Fri, 11 Sep 2026 10:17:32 +0800 Subject: [PATCH 0638/1417] net: wwan: mhi_wwan_mbim: guard against a cyclic NDP chain The NDP traversal in mhi_mbim_rx() only stops when wNextNdpIndex is zero. Nothing requires the offsets to advance, so a modem that points an NDP at itself, or at an earlier NDP, keeps the loop spinning forever on one CPU. Break out when the next NDP offset is not larger than the current one. Fixes: aa730a9905b7 ("net: wwan: Add MHI MBIM network driver") Cc: stable@vger.kernel.org Suggested-by: Loic Poulain Signed-off-by: Guanglei Zhu Verified in a QEMU guest with a fault injector feeding the driver's receive callback an NTB whose single NDP points at itself: the unpatched driver spins in mhi_mbim_rx() with one CPU pinned at 100% and the thread never returns. With this check the loop terminates within one iteration. Changes in v2: move the non-increasing check to the wNextNdpIndex retrieval site, as suggested by Loic Poulain, instead of tracking the previous offset in a separate variable. Link: https://patch.msgid.link/20260911021734.1396599-1-zhugl3@xiaopeng.com Signed-off-by: Jakub Kicinski --- drivers/net/wwan/mhi_wwan_mbim.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/wwan/mhi_wwan_mbim.c b/drivers/net/wwan/mhi_wwan_mbim.c index a949987125976a..5679948546a5e1 100644 --- a/drivers/net/wwan/mhi_wwan_mbim.c +++ b/drivers/net/wwan/mhi_wwan_mbim.c @@ -349,9 +349,13 @@ static void mhi_mbim_rx(struct mhi_mbim_context *mbim, struct sk_buff *skb) unlock: rcu_read_unlock(); next_ndp: - /* Other NDP to process? */ - ndpoffset = (int)le16_to_cpu(ndp16.wNextNdpIndex); - if (!ndpoffset) + /* Other NDP to process? The offsets must advance, or a + * self-referencing NDP keeps the loop spinning forever. + */ + n = (int)le16_to_cpu(ndp16.wNextNdpIndex); + if (n > ndpoffset) + ndpoffset = n; + else break; } From 31550d585589fde1ae95bf7f7a8188b2d2fdf1c7 Mon Sep 17 00:00:00 2001 From: Guanglei Zhu Date: Fri, 11 Sep 2026 10:17:33 +0800 Subject: [PATCH 0639/1417] net: wwan: mhi_wwan_mbim: check skb_copy_bits() return value mhi_mbim_rx() ignores the return value of skb_copy_bits() when it copies each datagram out of the NTB. The datagram offset and length come from the DPE, which is only checked to lie within the NTB itself, so a modem can point a datagram outside the received skb. The copy then fails and the freshly allocated skbn is passed to netif_rx() with its uninitialized contents still in place, leaking kernel heap memory into the network stack. Free the skb and account an error when the copy fails. Fixes: aa730a9905b7 ("net: wwan: Add MHI MBIM network driver") Cc: stable@vger.kernel.org Suggested-by: Loic Poulain Signed-off-by: Guanglei Zhu Verified in a QEMU guest with a fault injector pointing a DPE outside the received NTB: the copy fails, and the unpatched driver hands the uninitialized skbn to the network stack (observed as "unknown protocol" on bytes that were never written). With this check the failed datagram is dropped and counted as an rx error. Changes in v2: factor the free-and-count sequence out into mhi_mbim_rx_drop(), shared with the unknown-protocol path, as suggested by Loic Poulain. Link: https://patch.msgid.link/20260911021734.1396599-2-zhugl3@xiaopeng.com Signed-off-by: Jakub Kicinski --- drivers/net/wwan/mhi_wwan_mbim.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/net/wwan/mhi_wwan_mbim.c b/drivers/net/wwan/mhi_wwan_mbim.c index 5679948546a5e1..336f89756f10d8 100644 --- a/drivers/net/wwan/mhi_wwan_mbim.c +++ b/drivers/net/wwan/mhi_wwan_mbim.c @@ -251,6 +251,14 @@ static int mbim_rx_verify_ndp16(struct sk_buff *skb, struct usb_cdc_ncm_ndp16 *n return ret; } +static void mhi_mbim_rx_drop(struct mhi_mbim_link *link, struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + u64_stats_update_begin(&link->rx_syncp); + u64_stats_inc(&link->rx_errors); + u64_stats_update_end(&link->rx_syncp); +} + static void mhi_mbim_rx(struct mhi_mbim_context *mbim, struct sk_buff *skb) { int ndpoffset; @@ -320,7 +328,10 @@ static void mhi_mbim_rx(struct mhi_mbim_context *mbim, struct sk_buff *skb) continue; skb_put(skbn, dgram_len); - skb_copy_bits(skb, dgram_offset, skbn->data, dgram_len); + if (skb_copy_bits(skb, dgram_offset, skbn->data, dgram_len)) { + mhi_mbim_rx_drop(link, skbn); + continue; + } switch (skbn->data[0] & 0xf0) { case 0x40: @@ -332,10 +343,7 @@ static void mhi_mbim_rx(struct mhi_mbim_context *mbim, struct sk_buff *skb) default: net_err_ratelimited("%s: unknown protocol\n", link->ndev->name); - dev_kfree_skb_any(skbn); - u64_stats_update_begin(&link->rx_syncp); - u64_stats_inc(&link->rx_errors); - u64_stats_update_end(&link->rx_syncp); + mhi_mbim_rx_drop(link, skbn); continue; } From c7ead9704249d57d4693a04697e3bbd285138fa9 Mon Sep 17 00:00:00 2001 From: Guanglei Zhu Date: Fri, 11 Sep 2026 10:17:34 +0800 Subject: [PATCH 0640/1417] net: wwan: t7xx: validate the netif index in t7xx_ccmni_recv_skb() The netif index carried in the DPMAIF PIT header is five bits wide, but ccmni_inst[] only has room for NIC_DEV_MAX (21) entries. t7xx_ccmni_recv_skb() indexes the array without a bounds check, so indexes 21 to 31 read past it. The out-of-bounds value lands in the callback table that follows the array, which is never NULL, so the existing !ccmni check does not catch it and the driver dereferences whatever sits there as a struct t7xx_ccmni. Drop the skb when the index is out of range. Fixes: 05d19bf500f8 ("net: wwan: t7xx: Add WWAN network interface") Cc: stable@vger.kernel.org Signed-off-by: Guanglei Zhu Verified in a QEMU guest with a fault injector setting the netif index to 25: the unpatched driver reads a value past ccmni_inst[], which lands in the callback table, and dereferences it far enough to queue the skb. With this check the packet is dropped. Well-formed traffic on index 0 is unaffected. Changes in v2: none. Link: https://patch.msgid.link/20260911021734.1396599-3-zhugl3@xiaopeng.com Signed-off-by: Jakub Kicinski --- drivers/net/wwan/t7xx/t7xx_netdev.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wwan/t7xx/t7xx_netdev.c b/drivers/net/wwan/t7xx/t7xx_netdev.c index fc0a7cb181df2c..8f32c2d2693129 100644 --- a/drivers/net/wwan/t7xx/t7xx_netdev.c +++ b/drivers/net/wwan/t7xx/t7xx_netdev.c @@ -420,6 +420,10 @@ static void t7xx_ccmni_recv_skb(struct t7xx_ccmni_ctrl *ccmni_ctlb, struct sk_bu skb_cb = T7XX_SKB_CB(skb); netif_id = skb_cb->netif_idx; + if (netif_id >= NIC_DEV_MAX) { + dev_kfree_skb(skb); + return; + } ccmni = READ_ONCE(ccmni_ctlb->ccmni_inst[netif_id]); if (!ccmni) { dev_kfree_skb(skb); From 90e4b849dfa6fc8e6c050bcfe1b331b69c015d28 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 11 Sep 2026 10:58:29 +0200 Subject: [PATCH 0641/1417] net: stmmac: propagate FPE preemption-class mapping errors stmmac_fpe_map_preemption_class() dispatches through the stmmac_do_void_callback() helper, which forces the callback's return value to 0 whenever the op pointer is populated. As a result the -EINVAL returned by dwmac5_fpe_map_preemption_class() (e.g. when a preemptible TC owns more than one TXQ under SP scheduling) is silently swallowed by every caller. Switch the dispatch macro to stmmac_do_callback() so the callback's real result is propagated, and honour it in the taprio and mqprio qdisc offload. Note that the taprio "if (ret)" check in tc_taprio_configure() used to be dead code and now becomes live: a preemptible TC spanning more than one TXQ under SP scheduling cannot be programmed in hardware, so a taprio or mqprio configuration that previously returned success while leaving the preemption-class register unprogrammed now fails with -EINVAL. For taprio, the failure also runs the disable path, tearing down the schedule that was just installed; this is the intended behaviour. Fixes: 195e4f409a40 ("net: stmmac: support fp parameter of tc-mqprio") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260911-stmmac-tc_setup_dwmac510_mqprio-error-path-v3-1-a76b1e2547c1@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- .../net/ethernet/stmicro/stmmac/stmmac_tc.c | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 04dafec021b4f9..9314bcb85c221d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -494,7 +494,7 @@ struct stmmac_ops { #define stmmac_set_arp_offload(__priv, __args...) \ stmmac_do_void_callback(__priv, mac, set_arp_offload, __args) #define stmmac_fpe_map_preemption_class(__priv, __args...) \ - stmmac_do_void_callback(__priv, mac, fpe_map_preemption_class, __args) + stmmac_do_callback(__priv, mac, fpe_map_preemption_class, __args) /* PTP and HW Timer helpers */ struct stmmac_hwtimestamp { diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c index 14cabe76e53ec8..5398616fcdfea4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c @@ -970,7 +970,7 @@ static int tc_taprio_configure(struct stmmac_priv *priv, struct netlink_ext_ack *extack = qopt->mqprio.extack; struct timespec64 time, current_time, qopt_time; ktime_t current_time_ns; - int i, ret = 0; + int err, i, ret = 0; u64 ctr; if (qopt->base_time < 0) @@ -1120,9 +1120,9 @@ static int tc_taprio_configure(struct stmmac_priv *priv, mutex_unlock(&priv->est_lock); } - stmmac_fpe_map_preemption_class(priv, priv->dev, extack, 0); + err = stmmac_fpe_map_preemption_class(priv, priv->dev, extack, 0); - return ret; + return qopt->cmd == TAPRIO_CMD_DESTROY ? err : ret; } static void tc_taprio_stats(struct stmmac_priv *priv, @@ -1237,14 +1237,15 @@ static int tc_query_caps(struct stmmac_priv *priv, } } -static void stmmac_reset_tc_mqprio(struct net_device *ndev, - struct netlink_ext_ack *extack) +static int stmmac_reset_tc_mqprio(struct net_device *ndev, + struct netlink_ext_ack *extack) { struct stmmac_priv *priv = netdev_priv(ndev); netdev_reset_tc(ndev); netif_set_real_num_tx_queues(ndev, priv->plat->tx_queues_to_use); - stmmac_fpe_map_preemption_class(priv, ndev, extack, 0); + + return stmmac_fpe_map_preemption_class(priv, ndev, extack, 0); } static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv, @@ -1257,10 +1258,8 @@ static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv, u32 num_tc = qopt->num_tc; int err; - if (!num_tc) { - stmmac_reset_tc_mqprio(ndev, extack); - return 0; - } + if (!num_tc) + return stmmac_reset_tc_mqprio(ndev, extack); err = netdev_set_num_tc(ndev, num_tc); if (err) From 02fffd1939f6b45892f61822459953ce95e42948 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 11 Sep 2026 10:58:30 +0200 Subject: [PATCH 0642/1417] net: stmmac: preserve real_num_tx_queues on mqprio setup failure With the FPE preemption-class mapping error now propagated from stmmac_fpe_map_preemption_class(), tc_setup_dwmac510_mqprio() can fail on the mapping step. The error path used to call stmmac_reset_tc_mqprio(), which resets the number of real TX queues to priv->plat->tx_queues_to_use (the platform maximum), overwriting the value that was active before the offload was attempted (for example a lower count left over from a previous mqprio configuration). The issue can be triggered using the following configuration: # First mqprio config lowers the hw queue count below the platform # default (e.g. 8 TX queues). $tc qdisc add dev eth0 root handle 1: mqprio queues 2@0 2@2 # Replace mqprio configuration with a second one that fails FPE # preemption-class mapping. stmmac driver resets the real_num_tx_queues # to the platform maximum, losing the previous configuration. $tc qdisc replace dev eth0 root handle 2: mqprio queues 2@0 2@2 fp E P Save ndev->real_num_tx_queues before lowering it and restore it, together with the TC-to-queue and priority-to-TC mappings, when the FPE preemption-class mapping fails, instead of resetting the queue count to the platform maximum. Note that a failed setup makes the qdisc layer run mqprio_destroy() on the new qdisc. Because priv->hw_offload is only assigned after ndo_setup_tc() succeeds, mqprio_destroy() calls netdev_set_num_tc(dev, 0), so dev->num_tc ends up 0 regardless of the driver-side restore and the previous qdisc is not reactivated. The restore is still needed to keep real_num_tx_queues and to avoid leaving the failed configuration's TC-to-queue and priority-to-TC mappings in place. Fixes: 195e4f409a40 ("net: stmmac: support fp parameter of tc-mqprio") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260911-stmmac-tc_setup_dwmac510_mqprio-error-path-v3-2-a76b1e2547c1@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_tc.c | 82 ++++++++++++++----- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c index 5398616fcdfea4..42a00446e9b41a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c @@ -1237,6 +1237,30 @@ static int tc_query_caps(struct stmmac_priv *priv, } } +static int stmmac_set_ndev_tcs(struct net_device *ndev, u8 ntc, + struct netdev_tc_txq *tc_to_txq) +{ + int i, err; + + netdev_reset_tc(ndev); + if (!ntc) + return 0; + + err = netdev_set_num_tc(ndev, ntc); + if (err) + return err; + + for (i = 0; i < ntc; i++) { + u16 count, offset; + + count = tc_to_txq[i].count; + offset = tc_to_txq[i].offset; + netdev_set_tc_queue(ndev, i, count, offset); + } + + return 0; +} + static int stmmac_reset_tc_mqprio(struct net_device *ndev, struct netlink_ext_ack *extack) { @@ -1251,43 +1275,61 @@ static int stmmac_reset_tc_mqprio(struct net_device *ndev, static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv, struct tc_mqprio_qopt_offload *mqprio) { + unsigned int ndev_num_tx_queues, num_tx_queues = 0; + struct netdev_tc_txq ndev_tc_to_txq[TC_MAX_QUEUE]; + struct netdev_tc_txq tc_to_txq[TC_MAX_QUEUE] = {}; struct netlink_ext_ack *extack = mqprio->extack; struct tc_mqprio_qopt *qopt = &mqprio->qopt; - u32 offset, count, num_stack_tx_queues = 0; struct net_device *ndev = priv->dev; - u32 num_tc = qopt->num_tc; - int err; + u8 ndev_prio_tc_map[TC_BITMASK + 1]; + int i, err, ndev_ntc; - if (!num_tc) + if (!qopt->num_tc) return stmmac_reset_tc_mqprio(ndev, extack); - err = netdev_set_num_tc(ndev, num_tc); - if (err) - return err; - - for (u32 tc = 0; tc < num_tc; tc++) { - offset = qopt->offset[tc]; - count = qopt->count[tc]; - num_stack_tx_queues += count; + if (qopt->num_tc > ARRAY_SIZE(tc_to_txq)) + return -EINVAL; - err = netdev_set_tc_queue(ndev, tc, count, offset); - if (err) - goto err_reset_tc; + /* save current tc values for reset */ + ndev_ntc = netdev_get_num_tc(ndev); + for (i = 0; i < ARRAY_SIZE(ndev->tc_to_txq); i++) + ndev_tc_to_txq[i].combined = + READ_ONCE(ndev->tc_to_txq[i].combined); + for (i = 0; i < ARRAY_SIZE(ndev_prio_tc_map); i++) + ndev_prio_tc_map[i] = READ_ONCE(ndev->prio_tc_map[i]); + + for (i = 0; i < qopt->num_tc; i++) { + tc_to_txq[i] = (struct netdev_tc_txq) { + .count = qopt->count[i], + .offset = qopt->offset[i], + }; + num_tx_queues += qopt->count[i]; } - err = netif_set_real_num_tx_queues(ndev, num_stack_tx_queues); + err = stmmac_set_ndev_tcs(ndev, qopt->num_tc, tc_to_txq); + if (err) + goto error_reset_tc; + + ndev_num_tx_queues = ndev->real_num_tx_queues; + err = netif_set_real_num_tx_queues(ndev, num_tx_queues); if (err) - goto err_reset_tc; + goto error_reset_tc; err = stmmac_fpe_map_preemption_class(priv, ndev, extack, mqprio->preemptible_tcs); if (err) - goto err_reset_tc; + goto error_reset_num_tx_queues; return 0; -err_reset_tc: - stmmac_reset_tc_mqprio(ndev, extack); +error_reset_num_tx_queues: + if (netif_set_real_num_tx_queues(ndev, ndev_num_tx_queues)) + netdev_warn(ndev, "Failed to restore %u TX queues\n", + ndev_num_tx_queues); +error_reset_tc: + stmmac_set_ndev_tcs(ndev, ndev_ntc, ndev_tc_to_txq); + for (i = 0; i < ARRAY_SIZE(ndev_prio_tc_map); i++) + netdev_set_prio_tc_map(ndev, i, ndev_prio_tc_map[i]); return err; } From a41f24c612c3f5139a3143307eb85bbcf1bd4d07 Mon Sep 17 00:00:00 2001 From: Daniel Zahka Date: Tue, 15 Sep 2026 16:11:37 -0700 Subject: [PATCH 0643/1417] net: psp: avoid conflicts with skb->decrypted and sk_validate_xmit_skb() PSP conflicts with TLS ULP in its usage of both skb->decrypted and sk->sk_validate_xmit_skb(). Make PSP mutually exclusive with TLS ULP, the only other user of either of these. As other users of skb->decrypted come along, they can be added to sk_has_decrypt_user(). It would make sense to also assert that sk->sk_validate_xmit_skb() is also NULL in both of these setup paths for similar future proofing, but the PSP listener/sk_clone() path is still broken and it could be seen as a regression to not allow rx assoc to run on a child of a listener socket with PSP tx assoc state. Include all TCP ULPs in the sk_has_decrypt_user() check, even though TLS is the only one that conflicts with PSP via the decrypted bit. This is intentional because PSP was not designed to be used with ULPs. It is best to close off surface area that may make bugs reachable, until someone wishes to design and test an actual user of PSP with ULPs. Fixes: 6b46ca260e22 ("net: psp: add socket security association code") Signed-off-by: Daniel Zahka Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260915-psp-ktls-fix-v2-1-0eedc3b148ec@gmail.com Signed-off-by: Jakub Kicinski --- include/net/sock.h | 2 ++ net/core/sock.c | 7 +++++++ net/ipv4/tcp_ulp.c | 4 ++++ net/psp/psp_sock.c | 4 ++++ 4 files changed, 17 insertions(+) diff --git a/include/net/sock.h b/include/net/sock.h index 51185222aac292..60ea55dc18854a 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -2312,6 +2312,8 @@ static inline void sk_gso_disable(struct sock *sk) sk->sk_route_caps &= ~NETIF_F_GSO_MASK; } +bool sk_has_decrypt_user(const struct sock *sk); + static inline int skb_do_copy_data_nocache(struct sock *sk, struct sk_buff *skb, struct iov_iter *from, char *to, int copy, int offset) diff --git a/net/core/sock.c b/net/core/sock.c index d5e302e21e85b0..d23333bb4f3faf 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -142,6 +142,7 @@ #include +#include #include #include #include @@ -2670,6 +2671,12 @@ void sk_setup_caps(struct sock *sk, struct dst_entry *dst) } EXPORT_SYMBOL_GPL(sk_setup_caps); +bool sk_has_decrypt_user(const struct sock *sk) +{ + return psp_sk_assoc(sk) || + (sk_is_inet(sk) && inet_csk_has_ulp(sk)); /* for tls */ +} + /* * Simple resource managers for sockets. */ diff --git a/net/ipv4/tcp_ulp.c b/net/ipv4/tcp_ulp.c index 2aa442128630e5..b58045df101e5a 100644 --- a/net/ipv4/tcp_ulp.c +++ b/net/ipv4/tcp_ulp.c @@ -136,6 +136,10 @@ static int __tcp_set_ulp(struct sock *sk, const struct tcp_ulp_ops *ulp_ops) if (icsk->icsk_ulp_ops) goto out_err; + err = -EINVAL; + if (sk_has_decrypt_user(sk)) + goto out_err; + if (sk->sk_socket) clear_bit(SOCK_SUPPORT_ZC, &sk->sk_socket->flags); diff --git a/net/psp/psp_sock.c b/net/psp/psp_sock.c index 1a2a6b7516b0f8..e9b53eedf8dba0 100644 --- a/net/psp/psp_sock.c +++ b/net/psp/psp_sock.c @@ -143,6 +143,10 @@ int psp_sock_assoc_set_rx(struct sock *sk, struct psp_assoc *pas, NL_SET_ERR_MSG(extack, "Socket already has PSP state"); err = -EBUSY; goto exit_unlock; + } else if (sk_has_decrypt_user(sk)) { + NL_SET_ERR_MSG(extack, "Socket has incompatible state"); + err = -EINVAL; + goto exit_unlock; } refcount_inc(&pas->refcnt); From b4288c59bda883b0e5cd95099dc3b0b7b7fc50f6 Mon Sep 17 00:00:00 2001 From: Daniel Zahka Date: Tue, 15 Sep 2026 16:11:38 -0700 Subject: [PATCH 0644/1417] selftests: drv-net: psp: test PSP and TCP ULP mutual exclusion Test both setting PSP after TLS ULP, and TLS ULP after PSP. Add CONFIG_TLS=y to the drivers/net/config. Signed-off-by: Daniel Zahka Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260915-psp-ktls-fix-v2-2-0eedc3b148ec@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/config | 1 + tools/testing/selftests/drivers/net/psp.py | 46 ++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config index b6989c7d3d9d1f..4838adf27fa1b1 100644 --- a/tools/testing/selftests/drivers/net/config +++ b/tools/testing/selftests/drivers/net/config @@ -21,5 +21,6 @@ CONFIG_NET_SCH_INGRESS=y CONFIG_NET_SCH_PRIO=m CONFIG_PPP=y CONFIG_PPPOE=y +CONFIG_TLS=y CONFIG_VLAN_8021Q=m CONFIG_XDP_SOCKETS=y diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py index 315648a770d00a..a5b1e14f120f02 100755 --- a/tools/testing/selftests/drivers/net/psp.py +++ b/tools/testing/selftests/drivers/net/psp.py @@ -23,6 +23,8 @@ from lib.py import bkg, rand_port, wait_port_listen from lib.py import ip +TCP_ULP = 31 + def _get_outq(s): one = b'\0' * 4 @@ -333,6 +335,50 @@ def assoc_version_mismatch(cfg): ksft_eq(the_exception.nl_msg.error, -errno.EINVAL) +def _require_tls_ulp(): + with socket.create_server(("localhost", 0)) as srv, \ + socket.create_connection(srv.getsockname()) as s: + try: + s.setsockopt(socket.SOL_TCP, TCP_ULP, b"tls") + except OSError as exc: + raise KsftSkipEx("kTLS not available") from exc + + +def assoc_psp_ulp_exclusive(cfg): + """ Test that a TCP ULP cannot be attached to a PSP socket """ + _init_psp_dev(cfg) + _require_tls_ulp() + + with _make_clr_conn(cfg) as s: + try: + cfg.pspnl.rx_assoc({"version": 0, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + with ksft_raises(OSError) as cm: + s.setsockopt(socket.SOL_TCP, TCP_ULP, b"tls") + ksft_eq(cm.exception.errno, errno.EINVAL) + finally: + _close_conn(cfg, s) + + +def assoc_ulp_psp_exclusive(cfg): + """ Test that a PSP assoc cannot be added to a socket with a TCP ULP """ + _init_psp_dev(cfg) + _require_tls_ulp() + + with _make_clr_conn(cfg) as s: + try: + s.setsockopt(socket.SOL_TCP, TCP_ULP, b"tls") + with ksft_raises(NlError) as cm: + cfg.pspnl.rx_assoc({"version": 0, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL) + ksft_eq(cm.exception.nl_msg.extack['bad-attr'], ".sock-fd") + finally: + _close_conn(cfg, s) + + def assoc_twice(cfg): """ Test reusing Tx assoc for two sockets """ _init_psp_dev(cfg) From 2f91fc9a96cdab6c03d436246056552e04677636 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:08:56 +0530 Subject: [PATCH 0645/1417] s390: Fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Heiko Carstens --- drivers/s390/block/dasd_3990_erp.c | 6 +++--- drivers/s390/block/dasd_eckd.c | 4 ++-- drivers/s390/char/raw3270.c | 2 +- drivers/s390/char/vmlogrdr.c | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/s390/block/dasd_3990_erp.c b/drivers/s390/block/dasd_3990_erp.c index 736459477c1964..121cb9ae5c678a 100644 --- a/drivers/s390/block/dasd_3990_erp.c +++ b/drivers/s390/block/dasd_3990_erp.c @@ -103,7 +103,7 @@ dasd_3990_erp_int_req(struct dasd_ccw_req * erp) /* first time set initial retry counter and erp_function */ /* and retry once without blocking queue */ - /* (this enables easier enqueing of the cqr) */ + /* (this enables easier enqueuing of the cqr) */ if (erp->function != dasd_3990_erp_int_req) { erp->retries = 256; @@ -302,7 +302,7 @@ dasd_3990_erp_action_4(struct dasd_ccw_req * erp, char *sense) /* first time set initial retry counter and erp_function */ /* and retry once without waiting for state change pending */ - /* interrupt (this enables easier enqueing of the cqr) */ + /* interrupt (this enables easier enqueuing of the cqr) */ if (erp->function != dasd_3990_erp_action_4) { DBF_DEV_EVENT(DBF_INFO, device, "%s", @@ -1078,7 +1078,7 @@ dasd_3990_erp_bus_out(struct dasd_ccw_req * erp) /* first time set initial retry counter and erp_function */ /* and retry once without blocking queue */ - /* (this enables easier enqueing of the cqr) */ + /* (this enables easier enqueuing of the cqr) */ if (erp->function != dasd_3990_erp_bus_out) { erp->retries = 256; erp->function = dasd_3990_erp_bus_out; diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 8d976dd58a6c44..9d1b11020b4e82 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -2060,7 +2060,7 @@ dasd_eckd_psf_ssc(struct dasd_device *device, int enable_pav, } /* - * Valide storage server of current device. + * Valid storage server of current device. */ static int dasd_eckd_validate_server(struct dasd_device *device, unsigned long flags) @@ -5672,7 +5672,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_raw(struct dasd_device *startdev, char *dst; /* - * raw track access needs to be mutiple of 64k and on 64k boundary + * raw track access needs to be multiple of 64k and on 64k boundary * For read requests we can fix an incorrect alignment by padding * the request with dummy pages. */ diff --git a/drivers/s390/char/raw3270.c b/drivers/s390/char/raw3270.c index aa9c4d81225cbe..6861bf27d15fc5 100644 --- a/drivers/s390/char/raw3270.c +++ b/drivers/s390/char/raw3270.c @@ -420,7 +420,7 @@ struct raw3270_ua { /* Query Reply structure for Usable Area */ char flags0; char flags1; short w; /* Width of usable area */ - short h; /* Heigth of usavle area */ + short h; /* Height of usavle area */ char units; /* 0x00:in; 0x01:mm */ int xr; int yr; diff --git a/drivers/s390/char/vmlogrdr.c b/drivers/s390/char/vmlogrdr.c index 383e7e2bd69f4d..005735a899203c 100644 --- a/drivers/s390/char/vmlogrdr.c +++ b/drivers/s390/char/vmlogrdr.c @@ -444,7 +444,7 @@ static int vmlogrdr_receive_data(struct vmlogrdr_priv_t *priv) spin_unlock_bh(&priv->priv_lock); /* An rc of 5 indicates that the record was bigger than * the buffer, which is OK for us. A 9 indicates that the - * record was purged befor we could receive it. + * record was purged before we could receive it. */ if (rc == 5) rc = 0; From 4525a911049543c23885a540a788d13be318a486 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 7 Sep 2026 07:58:47 +0200 Subject: [PATCH 0646/1417] s390/pci/docs: Fix sriov_numvfs attribute name The attribute is sriov_numvfs (drivers/pci/iov.c); the document names it sriov_numvf, which does not exist. Use sriov_numvfs. Fixes: de267a7c71ba ("s390/pci: Documentation for zPCI") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Signed-off-by: Heiko Carstens --- Documentation/arch/s390/pci.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/arch/s390/pci.rst b/Documentation/arch/s390/pci.rst index 80f4ba19315994..565434626fb5ca 100644 --- a/Documentation/arch/s390/pci.rst +++ b/Documentation/arch/s390/pci.rst @@ -67,7 +67,7 @@ Entries specific to zPCI functions and entries that hold zPCI information. A physical function that currently supports a virtual function cannot be powered off until all virtual functions are removed with: - echo 0 > /sys/bus/pci/devices/DDDD:BB:dd.f/sriov_numvf + echo 0 > /sys/bus/pci/devices/DDDD:BB:dd.f/sriov_numvfs * /sys/bus/pci/devices/DDDD:BB:dd.f/: From 29d9e5835d89223aa913dcf7b942cc1c148bdd25 Mon Sep 17 00:00:00 2001 From: Vineeth Vijayan Date: Thu, 10 Sep 2026 11:32:01 +0200 Subject: [PATCH 0647/1417] s390/cio: Fix cio_update_schib() to not cache invalid schib When pmcw.dnv is 0, the contents of all SCHIB fields are unpredictable. Zero sch->schib in that case to prevent subsequent code from making decisions based on unpredictable data. Reported-by: William Bezenah Signed-off-by: Vineeth Vijayan Reviewed-by: Peter Oberparleiter Fixes: 8c58a229688c ("s390/cio: Do not unregister the subchannel based on DNV") Signed-off-by: Heiko Carstens --- drivers/s390/cio/cio.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/s390/cio/cio.c b/drivers/s390/cio/cio.c index 70dc8cc765948a..e1c62eb60cca70 100644 --- a/drivers/s390/cio/cio.c +++ b/drivers/s390/cio/cio.c @@ -453,7 +453,8 @@ EXPORT_SYMBOL_GPL(cio_commit_config); /** * cio_update_schib - Perform stsch and update schib if subchannel is valid. * @sch: subchannel on which to perform stsch - * Return zero on success, -ENODEV otherwise. + * Return zero on success, -ENODEV if the subchannel is not operational, + * -EACCES if the subchannel has no valid device. */ int cio_update_schib(struct subchannel *sch) { @@ -462,10 +463,12 @@ int cio_update_schib(struct subchannel *sch) if (stsch(sch->schid, &schib)) return -ENODEV; - memcpy(&sch->schib, &schib, sizeof(schib)); - - if (!css_sch_is_valid(&schib)) + if (!css_sch_is_valid(&schib)) { + memset(&sch->schib, 0, sizeof(sch->schib)); return -EACCES; + } + + memcpy(&sch->schib, &schib, sizeof(schib)); return 0; } From f6f2985eabdb2bfdc82ce90a1ea3ec53ba795f34 Mon Sep 17 00:00:00 2001 From: Vineeth Vijayan Date: Thu, 10 Sep 2026 11:32:02 +0200 Subject: [PATCH 0648/1417] s390/cio: Check pmcw.dnv before pmcw.ena in I/O entry points The device number valid (dnv) bit in the PMCW must be checked before acting on any other PMCW fields for IO-type subchannels. A subchannel with dnv=0 has no valid device number associated, making it meaningless to evaluate the enabled (ena) state or issue any I/O instruction against it. Reported-by: William Bezenah Signed-off-by: Vineeth Vijayan Reviewed-by: Peter Oberparleiter Fixes: 8c58a229688c ("s390/cio: Do not unregister the subchannel based on DNV") Signed-off-by: Heiko Carstens --- drivers/s390/cio/device_ops.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c index 61c07b4a0fe892..c1ba4a19368f27 100644 --- a/drivers/s390/cio/device_ops.c +++ b/drivers/s390/cio/device_ops.c @@ -142,6 +142,8 @@ int ccw_device_clear(struct ccw_device *cdev, unsigned long intparm) if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) @@ -198,6 +200,8 @@ int ccw_device_start_timeout_key(struct ccw_device *cdev, struct ccw1 *cpa, if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) @@ -379,6 +383,8 @@ int ccw_device_halt(struct ccw_device *cdev, unsigned long intparm) if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) @@ -413,6 +419,8 @@ int ccw_device_resume(struct ccw_device *cdev) if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) @@ -548,6 +556,8 @@ int ccw_device_tm_start_timeout_key(struct ccw_device *cdev, struct tcw *tcw, int rc; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_VERIFY) { @@ -694,6 +704,8 @@ int ccw_device_tm_intrg(struct ccw_device *cdev) { struct subchannel *sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state != DEV_STATE_ONLINE) From 9590f4d83880dfb5a81906e48e72779248fbe8f0 Mon Sep 17 00:00:00 2001 From: Vineeth Vijayan Date: Thu, 10 Sep 2026 11:32:03 +0200 Subject: [PATCH 0649/1417] s390/cio: Guard PMCW field accesses with dnv check When PMCW.DNV is 0, no I/O device is associated with the subchannel. However, several code paths access PMCW fields directly from the cached sch->schib without first invoking the update helper. Add explicit DNV validation before accessing PMCW fields from the cached SCHIB to avoid using invalid data. Reported-by: William Bezenah Signed-off-by: Vineeth Vijayan Reviewed-by: Peter Oberparleiter Fixes: 8c58a229688c ("s390/cio: Do not unregister the subchannel based on DNV") Signed-off-by: Heiko Carstens --- drivers/s390/cio/chp.c | 3 +++ drivers/s390/cio/device.c | 9 +++++---- drivers/s390/cio/device_fsm.c | 3 +++ drivers/s390/cio/device_ops.c | 9 +++++++++ drivers/s390/cio/vfio_ccw_fsm.c | 2 +- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/s390/cio/chp.c b/drivers/s390/cio/chp.c index c890f21a82ce37..eaf0527bff6cc5 100644 --- a/drivers/s390/cio/chp.c +++ b/drivers/s390/cio/chp.c @@ -78,6 +78,9 @@ u8 chp_get_sch_opm(struct subchannel *sch) int opm; int i; + if (!sch->schib.pmcw.dnv) + return 0; + opm = 0; chp_id_init(&chpid); for (i = 0; i < 8; i++) { diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index fb591118ecb2ae..68dd4a62975d22 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -922,7 +922,7 @@ static int ccw_device_move_to_sch(struct ccw_device *cdev, if (!sch_is_pseudo_sch(old_sch)) { spin_lock_irq(&old_sch->lock); - old_enabled = old_sch->schib.pmcw.ena; + old_enabled = old_sch->schib.pmcw.dnv && old_sch->schib.pmcw.ena; rc = 0; if (old_enabled) rc = cio_disable_subchannel(old_sch); @@ -941,7 +941,7 @@ static int ccw_device_move_to_sch(struct ccw_device *cdev, CIO_MSG_EVENT(0, "device_move(0.%x.%04x,0.%x.%04x)=%d\n", cdev->private->dev_id.ssid, cdev->private->dev_id.devno, sch->schid.ssid, - sch->schib.pmcw.dev, rc); + sch->schid.sch_no, rc); if (old_enabled) { /* Try to re-enable the old subchannel. */ spin_lock_irq(&old_sch->lock); @@ -1207,7 +1207,7 @@ static void io_subchannel_quiesce(struct subchannel *sch) cdev = sch_get_cdev(sch); if (cio_is_console(sch->schid)) goto out_unlock; - if (!sch->schib.pmcw.ena) + if (!sch->schib.pmcw.dnv || !sch->schib.pmcw.ena) goto out_unlock; ret = cio_disable_subchannel(sch); if (ret != -EBUSY) @@ -1254,7 +1254,8 @@ static int recovery_check(struct device *dev, void *data) switch (cdev->private->state) { case DEV_STATE_ONLINE: sch = to_subchannel(cdev->dev.parent); - if ((sch->schib.pmcw.pam & sch->opm) == sch->vpm) + if (sch->schib.pmcw.dnv && + (sch->schib.pmcw.pam & sch->opm) == sch->vpm) break; fallthrough; case DEV_STATE_DISCONNECTED: diff --git a/drivers/s390/cio/device_fsm.c b/drivers/s390/cio/device_fsm.c index ab419d40a8a7aa..b5686c25c83c7f 100644 --- a/drivers/s390/cio/device_fsm.c +++ b/drivers/s390/cio/device_fsm.c @@ -170,6 +170,9 @@ __recover_lost_chpids(struct subchannel *sch, int old_lpm) int mask, i; struct chp_id chpid; + if (!sch->schib.pmcw.dnv) + return; + chp_id_init(&chpid); for (i = 0; i<8; i++) { mask = 0x80 >> i; diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c index c1ba4a19368f27..f2f7f8cba410bc 100644 --- a/drivers/s390/cio/device_ops.c +++ b/drivers/s390/cio/device_ops.c @@ -490,6 +490,8 @@ struct channel_path_desc_fmt0 *ccw_device_get_chp_desc(struct ccw_device *cdev, struct chp_id chpid; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return NULL; chp_id_init(&chpid); chpid.id = sch->schib.pmcw.chpid[chp_idx]; return chp_get_chp_desc(chpid); @@ -510,6 +512,8 @@ u8 *ccw_device_get_util_str(struct ccw_device *cdev, int chp_idx) struct chp_id chpid; u8 *util_str; + if (!sch->schib.pmcw.dnv) + return NULL; chp_id_init(&chpid); chpid.id = sch->schib.pmcw.chpid[chp_idx]; chp = chpid_to_chp(chpid); @@ -662,6 +666,9 @@ int ccw_device_get_mdc(struct ccw_device *cdev, u8 mask) struct chp_id chpid; int mdc = 0, i; + if (!sch->schib.pmcw.dnv) + return 0; + /* Adjust requested path mask to excluded varied off paths. */ if (mask) mask &= sch->lpm; @@ -798,6 +805,8 @@ int ccw_device_get_chpid(struct ccw_device *cdev, int chp_idx, u8 *chpid) if ((chp_idx < 0) || (chp_idx > 7)) return -EINVAL; + if (!sch->schib.pmcw.dnv) + return -ENODEV; mask = 0x80 >> chp_idx; if (!(sch->schib.pmcw.pim & mask)) return -ENODEV; diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c index 5fd94e9d5c6181..9a000b0231d60e 100644 --- a/drivers/s390/cio/vfio_ccw_fsm.c +++ b/drivers/s390/cio/vfio_ccw_fsm.c @@ -399,7 +399,7 @@ static void fsm_close(struct vfio_ccw_private *private, spin_lock_irq(&sch->lock); - if (!sch->schib.pmcw.ena) + if (!sch->schib.pmcw.dnv || !sch->schib.pmcw.ena) goto err_unlock; ret = cio_disable_subchannel(sch); From 5535d5e61a77ad79118ea7665ce1c14f857f0f12 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Thu, 3 Sep 2026 16:42:12 +0800 Subject: [PATCH 0650/1417] drm/loongson: Create blend mode property for cursor plane After commit 860e748bddcc929 ("drm: ensure blend mode supported if pixel format with alpha exposed") we get warnings at boot: loongson 0000:00:06.1: [drm] [PLANE:41:ls-cursor-plane-0] pixel format with alpha exposed but blend mode not setup. Please fix. loongson 0000:00:06.1: [drm] [PLANE:46:ls-cursor-plane-1] pixel format with alpha exposed but blend mode not setup. Please fix. The reason is the cursor plane supports color formats with alpha but the driver doesn't create blend mode property, which triggers the warning in validate_blend_mode_for_alpha_formats(). The loongson DC HW doesn't support DRM_MODE_BLEND_PREMULTI, so create blend mode property with DRM_MODE_BLEND_COVERAGE for cursor planes since it is the only one implemented in the driver. Signed-off-by: Huacai Chen Reviewed-by: Jianmin Lv Signed-off-by: Icenowy Zheng Link: https://patch.msgid.link/20260903084212.3621540-1-chenhuacai@loongson.cn --- drivers/gpu/drm/loongson/lsdc_plane.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/loongson/lsdc_plane.c b/drivers/gpu/drm/loongson/lsdc_plane.c index bea42215796d42..bcc0ffa17bdf2c 100644 --- a/drivers/gpu/drm/loongson/lsdc_plane.c +++ b/drivers/gpu/drm/loongson/lsdc_plane.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -765,7 +766,7 @@ int ls7a1000_cursor_plane_init(struct drm_device *ddev, drm_plane_helper_add(plane, &ls7a1000_cursor_plane_helper_funcs); - return 0; + return drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_COVERAGE)); } int ls7a2000_cursor_plane_init(struct drm_device *ddev, @@ -790,5 +791,5 @@ int ls7a2000_cursor_plane_init(struct drm_device *ddev, drm_plane_helper_add(plane, &ls7a2000_cursor_plane_helper_funcs); - return 0; + return drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_COVERAGE)); } From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: [PATCH 0651/1417] x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- arch/x86/include/asm/text-patching.h | 4 ++-- arch/x86/kernel/alternative.c | 6 ++++-- arch/x86/kernel/kprobes/core.c | 5 ++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/arch/x86/include/asm/text-patching.h b/arch/x86/include/asm/text-patching.h index f2d142a0a862eb..ea09381070e82d 100644 --- a/arch/x86/include/asm/text-patching.h +++ b/arch/x86/include/asm/text-patching.h @@ -164,9 +164,9 @@ unsigned long int3_emulate_pop(struct pt_regs *regs) } static __always_inline -void int3_emulate_call(struct pt_regs *regs, unsigned long func) +void int3_emulate_call(struct pt_regs *regs, unsigned long ip, unsigned long func) { - int3_emulate_push(regs, regs->ip - INT3_INSN_SIZE + CALL_INSN_SIZE); + int3_emulate_push(regs, ip); int3_emulate_jmp(regs, func); } diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index 741d8767ddf895..582c6d8307d15d 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c @@ -2176,6 +2176,7 @@ int3_exception_notify(struct notifier_block *self, unsigned long val, void *data unsigned long selftest = (unsigned long)&int3_selftest_asm; struct die_args *args = data; struct pt_regs *regs = args->regs; + unsigned long ip; OPTIMIZER_HIDE_VAR(selftest); @@ -2188,7 +2189,8 @@ int3_exception_notify(struct notifier_block *self, unsigned long val, void *data if (regs->ip - INT3_INSN_SIZE != selftest) return NOTIFY_DONE; - int3_emulate_call(regs, (unsigned long)&int3_selftest_callee); + ip = regs->ip - INT3_INSN_SIZE + CALL_INSN_SIZE; + int3_emulate_call(regs, ip, (unsigned long)&int3_selftest_callee); return NOTIFY_STOP; } @@ -2758,7 +2760,7 @@ noinstr int smp_text_poke_int3_handler(struct pt_regs *regs) break; case CALL_INSN_OPCODE: - int3_emulate_call(regs, (long)ip + tpl->disp); + int3_emulate_call(regs, (long)ip, (long)ip + tpl->disp); break; case JMP32_INSN_OPCODE: diff --git a/arch/x86/kernel/kprobes/core.c b/arch/x86/kernel/kprobes/core.c index 4e5f8c1736ec14..133ff20caccd45 100644 --- a/arch/x86/kernel/kprobes/core.c +++ b/arch/x86/kernel/kprobes/core.c @@ -510,10 +510,9 @@ NOKPROBE_SYMBOL(kprobe_emulate_ret); static void kprobe_emulate_call(struct kprobe *p, struct pt_regs *regs) { - unsigned long func = regs->ip - INT3_INSN_SIZE + p->ainsn.size; + unsigned long ip = regs->ip - INT3_INSN_SIZE + p->ainsn.size; - func += p->ainsn.rel32; - int3_emulate_call(regs, func); + int3_emulate_call(regs, ip, ip + p->ainsn.rel32); } NOKPROBE_SYMBOL(kprobe_emulate_call); From fc3ae66514ca5e87251f79044236e3e8babd24a3 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 14 Sep 2026 16:20:27 +0100 Subject: [PATCH 0652/1417] netfs: Fix netfs_read_gaps() to use separate sink folios Fix netfs_read_gaps() to use separate folios rather than re-using a single sink folio to discard the unwanted data so that cifs checksum checking sees all the data that was fetched. Fixes: 7f84a7b9892d ("netfs: Make netfs_read_folio() handle streaming-write pages") Reported-by: Frank Sorenson Closes: https://lore.kernel.org/r/a385053c-1c4a-4060-a3bb-befa007ddb33@redhat.com/ Signed-off-by: David Howells Link: https://patch.msgid.link/3228134.1789399227@warthog.procyon.org.uk Tested-by: Frank Sorenson Reviewed-by: Paulo Alcantara cc: Paulo Alcantara cc: Namjae Jeon cc: netfs@lists.linux.dev cc: linux-cifs@vger.kernel.org cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 424df70a5c30f1..105194de6e13e0 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -482,15 +482,14 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) struct netfs_group *group = netfs_folio_group(folio); struct netfs_folio *finfo = netfs_folio_info(folio); struct netfs_inode *ctx = netfs_inode(mapping->host); - struct folio *sink = NULL; - struct bio_vec *bvec; + struct bio_vec *bvec = NULL; unsigned int from = finfo->dirty_offset; unsigned int to = from + finfo->dirty_len; - unsigned int off = 0, i = 0; + unsigned int off = 0; size_t flen = folio_size(folio); size_t nr_bvec = flen / PAGE_SIZE + 2; size_t part; - int ret; + int ret, i = 0, sink_from = -1, sink_to = -1; _enter("%lx", folio->index); @@ -515,24 +514,23 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) if (!bvec) goto discard; - sink = folio_alloc(GFP_KERNEL, 0); - if (!sink) { - kfree(bvec); - goto discard; - } - trace_netfs_folio(folio, netfs_folio_trace_read_gaps); - rreq->direct_bv = bvec; - rreq->direct_bv_count = nr_bvec; if (from > 0) { bvec_set_folio(&bvec[i++], folio, from, 0); off = from; } + sink_from = i; while (off < to) { + struct folio *sink = folio_alloc(GFP_KERNEL, 0); + + if (!sink) + goto discard; part = min_t(size_t, to - off, PAGE_SIZE); - bvec_set_folio(&bvec[i++], sink, part, 0); + bvec_set_folio(&bvec[i], sink, part, 0); off += part; + sink_to = i; + i++; } if (to < flen) bvec_set_folio(&bvec[i++], folio, flen - to, to); @@ -553,8 +551,10 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) folio_mark_uptodate(folio); } - if (sink) - folio_put(sink); + if (sink_to >= 0) + for (; sink_from <= sink_to; sink_from++) + folio_put(bvec_folio(&bvec[sink_from])); + kfree(bvec); folio_unlock(folio); netfs_put_request(rreq, netfs_rreq_trace_put_return); return ret < 0 ? ret : 0; @@ -563,6 +563,10 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) netfs_put_failed_request(rreq); alloc_error: folio_unlock(folio); + if (sink_to >= 0) + for (; sink_from <= sink_to; sink_from++) + folio_put(bvec_folio(&bvec[sink_from])); + kfree(bvec); return ret; } From 065f3ce5936e68da75f3dc18201d290073d78f3c Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:37:05 -0700 Subject: [PATCH 0653/1417] xfs: call xfs_dquot_set_prealloc_limits if we installed default rtb limits Now that we have quotas for the realtime volume, we also have precomputed watermark limits for the realtime block counts. These precomputations should be done any time we change the rtb limits, which means that xfs_qm_adjust_dqlimits needs to ensure that if we installed a default rtb limit. Cc: stable@vger.kernel.org # v6.13 Fixes: 5dd70852b03901 ("xfs: create quota preallocation watermarks for realtime quota") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_dquot.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_dquot.c b/fs/xfs/xfs_dquot.c index b4f6c594808ce9..e696ee36c2e8d2 100644 --- a/fs/xfs/xfs_dquot.c +++ b/fs/xfs/xfs_dquot.c @@ -139,10 +139,14 @@ xfs_qm_adjust_dqlimits( dq->q_ino.softlimit = defq->ino.soft; if (!dq->q_ino.hardlimit) dq->q_ino.hardlimit = defq->ino.hard; - if (!dq->q_rtb.softlimit) + if (!dq->q_rtb.softlimit) { dq->q_rtb.softlimit = defq->rtb.soft; - if (!dq->q_rtb.hardlimit) + prealloc = 1; + } + if (!dq->q_rtb.hardlimit) { dq->q_rtb.hardlimit = defq->rtb.hard; + prealloc = 1; + } if (prealloc) xfs_dquot_set_prealloc_limits(dq); From 41c4c41cf6c44f98db2916e1781f537d9ba6461a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:37:20 -0700 Subject: [PATCH 0654/1417] xfs: fix rtgroup repair estimations When I added online fsck for realtime reflink, I forgot to update xrep_calc_rtgroup_resblks to factor in the size of the refcount btree when it guesses how much space we need to start a repair. This hasn't been a huge problem in practice because there are few filesystems with (a) realtime, (b) rtgroups, (c) reflink, and (d) no rmap. But let's fix this before someone stumbles upon it, especially since LOLLM flagged this for me. Cc: stable@vger.kernel.org # v6.14 Fixes: 83ccffc489975d ("xfs: online repair of the realtime refcount btree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/repair.c | 19 +++++++++++++++++-- fs/xfs/scrub/trace.h | 12 ++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c index 11697a8b2a1d13..c2a437416227a8 100644 --- a/fs/xfs/scrub/repair.c +++ b/fs/xfs/scrub/repair.c @@ -399,6 +399,7 @@ xrep_calc_rtgroup_resblks( struct xfs_mount *mp = sc->mp; struct xfs_scrub_metadata *sm = sc->sm; uint64_t usedlen; + xfs_extlen_t refcbt_sz = 0; xfs_extlen_t rmapbt_sz = 0; if (!(sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR)) @@ -411,13 +412,27 @@ xrep_calc_rtgroup_resblks( usedlen = xfs_rtbxlen_to_blen(mp, xfs_rtgroup_extents(mp, sm->sm_agno)); ASSERT(usedlen <= XFS_MAX_RGBLOCKS); + if (xfs_has_reflink(mp)) + refcbt_sz = xfs_rtrefcountbt_calc_size(mp, usedlen); + if (xfs_has_rmapbt(mp)) rmapbt_sz = xfs_rtrmapbt_calc_size(mp, usedlen); + /* + * Guess how many blocks we need to rebuild the rmapbt. For + * non-reflink filesystems we can't have more records than used blocks. + * However, with reflink it's possible to have more than one rmap + * record per rtgroup block. We don't know how many rmaps there could + * be in the rtgroup, so we start off with what we hope is an generous + * over-estimation. + */ + if (refcbt_sz > 0 && rmapbt_sz > 0) + rmapbt_sz *= 2; + trace_xrep_calc_rtgroup_resblks_btsize(mp, sm->sm_agno, usedlen, - rmapbt_sz); + rmapbt_sz, refcbt_sz); - return rmapbt_sz; + return max(rmapbt_sz, refcbt_sz); } #endif /* CONFIG_XFS_RT */ diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 0f5adc293962fa..cb85f75ce10180 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -2376,25 +2376,29 @@ TRACE_EVENT(xrep_calc_ag_resblks_btsize, #ifdef CONFIG_XFS_RT TRACE_EVENT(xrep_calc_rtgroup_resblks_btsize, TP_PROTO(struct xfs_mount *mp, xfs_rgnumber_t rgno, - xfs_rgblock_t usedlen, xfs_rgblock_t rmapbt_sz), - TP_ARGS(mp, rgno, usedlen, rmapbt_sz), + xfs_rgblock_t usedlen, xfs_rgblock_t rmapbt_sz, + xfs_rgblock_t refcbt_sz), + TP_ARGS(mp, rgno, usedlen, rmapbt_sz, refcbt_sz), TP_STRUCT__entry( __field(dev_t, dev) __field(xfs_rgnumber_t, rgno) __field(xfs_rgblock_t, usedlen) __field(xfs_rgblock_t, rmapbt_sz) + __field(xfs_rgblock_t, refcbt_sz) ), TP_fast_assign( __entry->dev = mp->m_super->s_dev; __entry->rgno = rgno; __entry->usedlen = usedlen; __entry->rmapbt_sz = rmapbt_sz; + __entry->refcbt_sz = refcbt_sz; ), - TP_printk("dev %d:%d rgno 0x%x usedlen %u rmapbt %u", + TP_printk("dev %d:%d rgno 0x%x usedlen %u rmapbt %u refcountbt %u", MAJOR(__entry->dev), MINOR(__entry->dev), __entry->rgno, __entry->usedlen, - __entry->rmapbt_sz) + __entry->rmapbt_sz, + __entry->refcbt_sz) ); #endif /* CONFIG_XFS_RT */ From d7b92cbe566f6fe54368f62e4b515d9f80c43a18 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:37:36 -0700 Subject: [PATCH 0655/1417] xfs: don't cross reference rmapbt with bitmaps if they're incomplete LOLLM points out that runtime errors (e.g. ENOMEM) when we're trying to compute space usag bitmaps are silently dropped by the rmapbt scrubber. We ought to flag that as an incomplete scrub instead of reporting cross-referencing errors based on faulty data. Cc: stable@vger.kernel.org # v6.4 Fixes: fed050f3452da0 ("xfs: cross-reference rmap records with ag btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rmap.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/fs/xfs/scrub/rmap.c b/fs/xfs/scrub/rmap.c index 0cd3eecd2ca5b4..68e2847c962bb2 100644 --- a/fs/xfs/scrub/rmap.c +++ b/fs/xfs/scrub/rmap.c @@ -493,11 +493,18 @@ xchk_rmapbt_walk_ag_metadata( * If there's an error, set XFAIL and disable the bitmap * cross-referencing checks, but proceed with the scrub anyway. */ - if (error) - xchk_btree_xref_process_error(sc, sc->sa.rmap_cur, - sc->sa.rmap_cur->bc_nlevels - 1, &error); - else - cr->bitmaps_complete = true; + if (error) { + if (!xchk_btree_xref_process_error(sc, sc->sa.rmap_cur, + sc->sa.rmap_cur->bc_nlevels - 1, &error)) { + /* only set incomplete if we didn't set xfail */ + if (error) + xchk_set_incomplete(sc); + } + + return 0; + } + + cr->bitmaps_complete = true; return 0; } @@ -567,7 +574,8 @@ xchk_rmapbt( if (error) goto out; - xchk_rmapbt_check_bitmaps(sc, cr); + if (cr->bitmaps_complete) + xchk_rmapbt_check_bitmaps(sc, cr); out: xagb_bitmap_destroy(&cr->refcbt_owned); From ab1c416d2377cdc16123ef521aac4da1c468c3d4 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:37:52 -0700 Subject: [PATCH 0656/1417] xfs: don't let memory failures leak blocks and kill repairs LOLLM complains that a memory allocation failure in xrep_newbt_add_blocks results in online repair leaking blocks that were previously allocated to write a new btree, but the problem is worse than that -- a limitation of the codebase is that the callers cannot undo the transaction /and/ return the error -- either you undo all changes and commit the transaction, or you error out and the filesystem goes down. However, the new btree space reservation object isn't that big (~48 bytes). Let's just do a NOFAIL allocation and the problem goes away. Cc: stable@vger.kernel.org # v6.8 Fixes: be408417630427 ("xfs: implement block reservation accounting for btrees we're staging") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/newbt.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/xfs/scrub/newbt.c b/fs/xfs/scrub/newbt.c index c82f4631fd9c27..584076b2a6eed9 100644 --- a/fs/xfs/scrub/newbt.c +++ b/fs/xfs/scrub/newbt.c @@ -193,9 +193,11 @@ xrep_newbt_add_blocks( struct xrep_newbt_resv *resv; int error; - resv = kmalloc_obj(struct xrep_newbt_resv, XCHK_GFP_FLAGS); - if (!resv) - return -ENOMEM; + /* + * We have no way to clean up the allocated space *and* return an + * ENOMEM if we fail to allocate this control structure. + */ + resv = kmalloc_obj(struct xrep_newbt_resv, GFP_KERNEL | __GFP_NOFAIL); INIT_LIST_HEAD(&resv->list); resv->agbno = XFS_FSB_TO_AGBNO(mp, args->fsbno); From d80993655f7be2a461c0a63e2022da5757f47dac Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:38:07 -0700 Subject: [PATCH 0657/1417] xfs: don't merge different file IO error types LOLLM noticed that we can accidentally merge file range health monitoring events even if they have different errors. We shouldn't do that. Cc: stable@vger.kernel.org # v7.0 Fixes: dfa8bad3a8796c ("xfs: convey file I/O errors to the health monitor") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index c3749675ef1921..2bdd747f1ead24 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -247,7 +247,9 @@ xfs_healthmon_merge_events( case XFS_HEALTHMON_DIOWRITE: case XFS_HEALTHMON_DATALOST: /* logically adjacent file ranges can merge */ - if (existing->fino != new->fino || existing->fgen != new->fgen) + if (existing->fino != new->fino || + existing->fgen != new->fgen || + existing->error != new->error) return false; if (existing->fpos + existing->flen == new->fpos) { From f8f6382ff13109e19d0fc1d0224ff7d29641e56a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:38:23 -0700 Subject: [PATCH 0658/1417] xfs: fix blockgc group quota scanning when usrquota isn't enforced LOLLM noticed the copy-paste error here -- if user quotas aren't enforced but we're near the group quota limit, we fail to set FLAG_GID and hence we might not actually free any preallocations, causing unnecessary EDQUOT. Fix that. Cc: stable@vger.kernel.org # v5.12 Fixes: c237dd7c709432 ("xfs: flush eof/cowblocks if we can't reserve quota for inode creation") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_icache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c index 82dac88e3c4c71..de8be344e987d0 100644 --- a/fs/xfs/xfs_icache.c +++ b/fs/xfs/xfs_icache.c @@ -1653,7 +1653,7 @@ xfs_blockgc_free_dquots( do_work = true; } - if (XFS_IS_UQUOTA_ENFORCED(mp) && gdqp && xfs_dquot_lowsp(gdqp)) { + if (XFS_IS_GQUOTA_ENFORCED(mp) && gdqp && xfs_dquot_lowsp(gdqp)) { icw.icw_gid = make_kgid(mp->m_super->s_user_ns, gdqp->q_id); icw.icw_flags |= XFS_ICWALK_FLAG_GID; do_work = true; From 65f39d09d73718611cee40399179323b5d4ead00 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:38:38 -0700 Subject: [PATCH 0659/1417] xfs: fix cursor and pointer handling when recovering iunlink buckets LOLLM pointed out a bug in xlog_recover_iunlink_bucket: 1. We don't null out prev_ip after releasing it, which can lead to UAF problems if the inodegc flush call in the loop fails. at which point I noticed even more bugs: 2. If the inodegc flush inside the loop fails, we also leak @ip. 3. We set prev_agino to agino having already advanced agino, which results in inodes with i_prev_unlinked set to itself. 4. If we exit the bottom of the loop with prev_ip set, then prev_ip aliases ip and we also set its i_prev_unlinked to itself. Bugs 3 and 4 introduce loops into the unlinked list, though these loops don't surface because we immediately flush each unlinked inode after loading it. Fix all of these issues. Cc: stable@vger.kernel.org # v6.0 Fixes: 04755d2e5821b3 ("xfs: refactor xlog_recover_process_iunlinks()") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log_recover.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c index e7e49529658be3..cf0d610265fe76 100644 --- a/fs/xfs/xfs_log_recover.c +++ b/fs/xfs/xfs_log_recover.c @@ -2736,12 +2736,13 @@ xlog_recover_iunlink_bucket( { struct xfs_mount *mp = pag_mount(pag); struct xfs_inode *prev_ip = NULL; - struct xfs_inode *ip; xfs_agino_t prev_agino, agino; int error = 0; agino = be32_to_cpu(agi->agi_unlinked[bucket]); while (agino != NULLAGINO) { + struct xfs_inode *ip; + error = xfs_iget(mp, NULL, xfs_agino_to_ino(pag, agino), 0, 0, &ip); if (error) @@ -2750,11 +2751,11 @@ xlog_recover_iunlink_bucket( ASSERT(VFS_I(ip)->i_nlink == 0); ASSERT(VFS_I(ip)->i_mode != 0); xfs_iflags_clear(ip, XFS_IRECOVERY); - agino = ip->i_next_unlinked; if (prev_ip) { ip->i_prev_unlinked = prev_agino; xfs_irele(prev_ip); + prev_ip = NULL; /* * Ensure the inode is removed from the unlinked list @@ -2766,18 +2767,20 @@ xlog_recover_iunlink_bucket( * complete. */ error = xfs_inodegc_flush(mp); - if (error) - break; + if (error) { + xfs_irele(ip); + return error; + } } prev_agino = agino; + agino = ip->i_next_unlinked; prev_ip = ip; } if (prev_ip) { int error2; - ip->i_prev_unlinked = prev_agino; xfs_irele(prev_ip); error2 = xfs_inodegc_flush(mp); From ffb48dccce1960a9ea24463a2f3c21d124d6b672 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:38:54 -0700 Subject: [PATCH 0660/1417] xfs: drop dquot flush lock when we can't find a buffer to flush LOLLM noticed that xfs_qm_flush_one fails to drop the dquot flush lock if it can't grab the buffer associated with the dquot. Since there's no buffer, nobody else is going to drop the dqflock, so we need to do it ourselves. Cc: stable@vger.kernel.org # v6.13 Fixes: ca378189fdfa89 ("xfs: convert quotacheck to attach dquot buffers") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_qm.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c index 99a82107b8e6f2..54d00d543b513a 100644 --- a/fs/xfs/xfs_qm.c +++ b/fs/xfs/xfs_qm.c @@ -1432,16 +1432,22 @@ xfs_qm_flush_one( error = xfs_dquot_use_attached_buf(dqp, &bp); if (error) - goto out_unlock; + goto out_dqflock; if (!bp) { error = -EFSCORRUPTED; - goto out_unlock; + goto out_dqflock; } error = xfs_qm_dqflush(dqp, bp); if (!error) xfs_buf_delwri_queue(bp, buffer_list); xfs_buf_relse(bp); + mutex_unlock(&dqp->q_qlock); + xfs_qm_dqrele(dqp); + return error; + +out_dqflock: + xfs_dqfunlock(dqp); out_unlock: mutex_unlock(&dqp->q_qlock); xfs_qm_dqrele(dqp); From 476582d754cdc5110f806001417fea6c77824c13 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:39:10 -0700 Subject: [PATCH 0661/1417] xfs: don't let hidden_space go negative in xfs_metafile_resv_init LOLLM points out that if the amount of fdblocks that we can reserve for a metadata btree file goes below the space already used by that file, then the hidden_space subtraction can underflow, causing xfs_dec_fdblocks to subtract a huge amount of space. We never want the target to be less than the used sapce, so fix the logic that adjusts dblocks_avail downwards. Also fix an error in the adjacent comment. Cc: stable@vger.kernel.org # v6.15 Fixes: 1df8d75030b787 ("xfs: make metabtree reservations global") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_metafile.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/xfs/libxfs/xfs_metafile.c b/fs/xfs/libxfs/xfs_metafile.c index 71f004e9dc6451..1f54d39003c27c 100644 --- a/fs/xfs/libxfs/xfs_metafile.c +++ b/fs/xfs/libxfs/xfs_metafile.c @@ -297,14 +297,14 @@ xfs_metafile_resv_init( goto out_unlock; /* - * Space taken by the per-AG metadata btrees are accounted on-disk as - * used space. We therefore only hide the space that is reserved but - * not used by the trees. + * Space taken by metadata btrees are accounted on-disk as used space. + * We therefore only hide the space that is reserved but not used by + * the trees. */ if (used > target) target = used; else if (target > dblocks_avail) - target = dblocks_avail; + target = max(dblocks_avail, used); hidden_space = target - used; error = xfs_dec_fdblocks(mp, hidden_space, true); From fe2f9135df43db849e74f03956d322ac20b59af7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:39:25 -0700 Subject: [PATCH 0662/1417] xfs: fix wild memcpy access when formatting ondisk rtrefcount btree roots LOLLM noticed that the inode btree root formatting methods copy too many bytes -- there's only one set of keys in node blocks, not two. This causes memory corruption of whatever's beyond the buffers. Cc: stable@vger.kernel.org # v6.14 Fixes: f0415af60f482a ("xfs: wire up a new metafile type for the realtime refcount") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtrefcount_btree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c index e2950dbe20681b..dcc89b8e149b05 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c @@ -617,7 +617,7 @@ xfs_rtrefcountbt_from_disk( fpp = xfs_rtrefcount_droot_ptr_addr(dblock, 1, maxrecs); tpp = xfs_rtrefcount_broot_ptr_addr(mp, rblock, 1, rblocklen); numrecs = be16_to_cpu(dblock->bb_numrecs); - memcpy(tkp, fkp, 2 * sizeof(*fkp) * numrecs); + memcpy(tkp, fkp, sizeof(*fkp) * numrecs); memcpy(tpp, fpp, sizeof(*fpp) * numrecs); } else { frp = xfs_rtrefcount_droot_rec_addr(dblock, 1); @@ -703,7 +703,7 @@ xfs_rtrefcountbt_to_disk( fpp = xfs_rtrefcount_broot_ptr_addr(mp, rblock, 1, rblocklen); tpp = xfs_rtrefcount_droot_ptr_addr(dblock, 1, maxrecs); numrecs = be16_to_cpu(rblock->bb_numrecs); - memcpy(tkp, fkp, 2 * sizeof(*fkp) * numrecs); + memcpy(tkp, fkp, sizeof(*fkp) * numrecs); memcpy(tpp, fpp, sizeof(*fpp) * numrecs); } else { frp = xfs_rtrefcount_rec_addr(rblock, 1); From e9d810279f84b30738f7790c0ed15f8dd5b9024a Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 09:47:01 +0000 Subject: [PATCH 0663/1417] gpio: arizona: Fix runtime PM leak in arizona_gpio_direction_out() Switching a persistent GPIO line from input to output acquires a runtime PM reference on the parent device, but if the subsequent regmap_update_bits() fails the reference is never dropped and no later direction_in() can balance it since the direction was never changed. Drop the reference on the update failure path. Fixes: 27a49ed17e22 ("gpio: arizona: Add support for GPIOs that need to be maintained") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260916094701.2007509-1-vulab@iscas.ac.cn Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-arizona.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-arizona.c b/drivers/gpio/gpio-arizona.c index a7e98d395d8e5c..88cf013f0d90de 100644 --- a/drivers/gpio/gpio-arizona.c +++ b/drivers/gpio/gpio-arizona.c @@ -115,8 +115,12 @@ static int arizona_gpio_direction_out(struct gpio_chip *chip, if (value) value = ARIZONA_GPN_LVL; - return regmap_update_bits(arizona->regmap, ARIZONA_GPIO1_CTRL + offset, - ARIZONA_GPN_DIR | ARIZONA_GPN_LVL, value); + ret = regmap_update_bits(arizona->regmap, ARIZONA_GPIO1_CTRL + offset, + ARIZONA_GPN_DIR | ARIZONA_GPN_LVL, value); + if (ret < 0 && (val & ARIZONA_GPN_DIR) && persistent) + pm_runtime_put_autosuspend(chip->parent); + + return ret; } static int arizona_gpio_set(struct gpio_chip *chip, unsigned int offset, From 02af7eac17bc62a72335c1fc8c4af4471654f6cc Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 14 Sep 2026 17:44:06 -0700 Subject: [PATCH 0664/1417] gpio: mvebu: keep resume masks within the irqchip cache mvebu_gpio_resume() writes the edge/level mask registers saved at suspend time straight back to hardware, bypassing the irqchip's mask_cache_priv. genirq skips mask_irq() for a line it already considers masked, so restoring a bit in hardware that genirq thinks is still masked leaves that line unmasked behind genirq's back. An asserted level line then has nobody to ack it, and the moment interrupts are re-enabled the chained handler storms, hanging resume. AND the restored mask values with the matching irqchip mask cache so only lines genirq currently considers unmasked are unmasked again. Read the caches under gc->lock to keep them consistent with the mask/unmask handlers. Tested on Helios4 (armhf): 5 suspend cycles woken by magic packet, no hang; mvebu_gpio_resume() returns in 6 usecs. Assisted-by: LLM Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260915004406.115230-1-rosenp@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mvebu.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c index 93b8a08b04b9f9..a2796240fe14b8 100644 --- a/drivers/gpio/gpio-mvebu.c +++ b/drivers/gpio/gpio-mvebu.c @@ -1034,6 +1034,8 @@ static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state) static int mvebu_gpio_resume(struct platform_device *pdev) { struct mvebu_gpio_chip *mvchip = platform_get_drvdata(pdev); + u32 edge_cache = ~0U, level_cache = ~0U; + unsigned long flags; int i; regmap_write(mvchip->regs, GPIO_OUT_OFF + mvchip->offset, @@ -1045,32 +1047,51 @@ static int mvebu_gpio_resume(struct platform_device *pdev) regmap_write(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset, mvchip->in_pol_reg); + /* + * genirq skips mask_irq() for a line it already considers masked, so + * unmasking one behind its back leaves an asserted level line that + * nobody masks. Restore only bits the irqchip cache still has set. + * + * Snapshot the caches under the raw spinlock, but release it before + * the regmap writes below: regmap_write() takes a sleepable lock on + * PREEMPT_RT. + */ + if (mvchip->domain) { + struct irq_chip_generic *gc; + + gc = irq_get_domain_generic_chip(mvchip->domain, 0); + raw_spin_lock_irqsave(&gc->lock, flags); + level_cache = gc->chip_types[0].mask_cache_priv; + edge_cache = gc->chip_types[1].mask_cache_priv; + raw_spin_unlock_irqrestore(&gc->lock, flags); + } + switch (mvchip->soc_variant) { case MVEBU_GPIO_SOC_VARIANT_ORION: case MVEBU_GPIO_SOC_VARIANT_A8K: regmap_write(mvchip->regs, GPIO_EDGE_MASK_OFF + mvchip->offset, - mvchip->edge_mask_regs[0]); + mvchip->edge_mask_regs[0] & edge_cache); regmap_write(mvchip->regs, GPIO_LEVEL_MASK_OFF + mvchip->offset, - mvchip->level_mask_regs[0]); + mvchip->level_mask_regs[0] & level_cache); break; case MVEBU_GPIO_SOC_VARIANT_MV78200: for (i = 0; i < 2; i++) { regmap_write(mvchip->regs, GPIO_EDGE_MASK_MV78200_OFF(i), - mvchip->edge_mask_regs[i]); + mvchip->edge_mask_regs[i] & edge_cache); regmap_write(mvchip->regs, GPIO_LEVEL_MASK_MV78200_OFF(i), - mvchip->level_mask_regs[i]); + mvchip->level_mask_regs[i] & level_cache); } break; case MVEBU_GPIO_SOC_VARIANT_ARMADAXP: for (i = 0; i < 4; i++) { regmap_write(mvchip->regs, GPIO_EDGE_MASK_ARMADAXP_OFF(i), - mvchip->edge_mask_regs[i]); + mvchip->edge_mask_regs[i] & edge_cache); regmap_write(mvchip->regs, GPIO_LEVEL_MASK_ARMADAXP_OFF(i), - mvchip->level_mask_regs[i]); + mvchip->level_mask_regs[i] & level_cache); } break; default: From c51e89c5b5db3e1927738fad7cd18b66dde68aed Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 15 Sep 2026 17:21:59 +0100 Subject: [PATCH 0665/1417] netfs, afs: Fix symlink reading Fix the reading of symlinks from the cache in afs by making netfslib trim the amount read down to i_size. The problem is that afs sets the size of the iterator to the size of the buffer (PAGE_SIZE) so that the cache can round the read size up to the cache's DIO size. Note that this also impacts the reading of AFS mountpoints as they're just stored as symlinks with an odd file mode. Link: https://patch.msgid.link/3912795.1789489319@warthog.procyon.org.uk Fixes: c0410adf3da6 ("afs: Fix the locking used by afs_get_link()") Reviewed-by: Paulo Alcantara cc: Paulo Alcantara cc: Marc Dionne cc: linux-afs@lists.infradead.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/read_collect.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 5cf22087d2439d..a94197ef018113 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -435,6 +435,11 @@ static void netfs_rreq_assess_single(struct netfs_io_request *rreq) netfs_single_mark_inode_dirty(rreq->inode); } + /* To do DIO, the cache has to round the size up, so we need to undo + * the rounding. + */ + rreq->transferred = min(rreq->transferred, rreq->i_size); + if (rreq->iocb) { rreq->iocb->ki_pos += rreq->transferred; if (rreq->iocb->ki_complete) { From 7459c021874246c196f397686e100702f059b9e7 Mon Sep 17 00:00:00 2001 From: Julian Sun Date: Tue, 15 Sep 2026 12:49:12 +0800 Subject: [PATCH 0666/1417] fs: avoid repeated scans in evict_inodes() We observed hung tasks when users attempted to unmount a filesystem after its disk had been removed while still in use. During device removal, fs_bdev_mark_dead() calls evict_inodes() while holding s_umount. Each time evict_inodes() drops s_inode_list_lock to reschedule, it restarts the walk from the head of s_inodes. With many referenced inodes at the head of the list, these restarts repeatedly scan the same inodes without reclaiming them. This can keep s_umount held for a long time, blocking concurrent umount attempts and triggering hung-task reports. Keep the current inode, already marked I_FREEING, out of the disposal batch until s_inode_list_lock is reacquired. Resume the walk from this inode and dispose of it in a later batch or at the end of the walk. The zero-refcount and state checks under i_lock allow this walker to claim the inode by setting I_FREEING and removing it from the LRU. Other reclaimers skip the inode, leaving this walker responsible for eviction. Only evict() removes it from s_inodes, so keeping it out of the disposal batch ensures that it remains on the list while the lock is dropped. After reacquiring the lock, reading its current next pointer accounts for concurrent removal of following inodes. The existing inode lifetime rules prohibit acquiring a reference to an inode marked I_FREEING or I_WILL_FREE. __iget() requires its caller to hold i_lock and establish that taking a reference is valid. Inode lookup and igrab() check these flags under i_lock when acquiring a reference from zero. ihold() requires an existing reference, which would keep i_count nonzero and prevent this walker from claiming the inode. These rules already allow iput_final() and the inode shrinker to release i_lock after setting I_FREEING and before eviction completes. A temporary __iget() reference would also keep the inode on the list, but its release must preserve last-reference handling. Another user can acquire a reference, update lazy timestamps and drop its reference while the pin is held. If the pin becomes the last reference, dropping it with atomic_dec_and_test() and evicting directly bypasses iput()'s lazytime handling and can lose those timestamp updates. Releasing the pin with iput() preserves that handling, but does not guarantee eviction. fs_bdev_mark_dead() runs with SB_ACTIVE set, so iput() may retain the inode in cache, whereas evict_inodes() must evict eligible zero-reference inodes. The inode may also have been freed when iput() returns, so the walker cannot then use it to force eviction. Using I_FREEING preserves the existing eviction behavior without introducing an additional last-reference transition. The xfstests auto group passed on ext4 and XFS with known unrelated failures excluded. No new issues were observed, and the previously reproducible hung task no longer occurs with this patch. Fixes: ac05fbb40062 ("inode: don't softlockup when evicting inodes") Signed-off-by: Julian Sun Link: https://patch.msgid.link/20260915044912.3183440-1-sunjunchao@bytedance.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/inode.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/fs/inode.c b/fs/inode.c index ba7da39be4a315..a9d37be390a173 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -880,7 +880,6 @@ void evict_inodes(struct super_block *sb) struct inode *inode; LIST_HEAD(dispose); -again: spin_lock(&sb->s_inode_list_lock); list_for_each_entry(inode, &sb->s_inodes, i_sb_list) { if (icount_read_once(inode)) @@ -899,19 +898,19 @@ void evict_inodes(struct super_block *sb) inode_state_set(inode, I_FREEING); inode_lru_list_del(inode); spin_unlock(&inode->i_lock); - list_add(&inode->i_lru, &dispose); /* - * We can have a ton of inodes to evict at unmount time given - * enough memory, check to see if we need to go to sleep for a - * bit so we don't livelock. + * Keep this inode out of dispose so it stays on s_inodes while + * the list lock is dropped. I_FREEING prevents new references + * and leaves eviction to us, so we can resume the walk from it. */ if (need_resched()) { spin_unlock(&sb->s_inode_list_lock); cond_resched(); dispose_list(&dispose); - goto again; + spin_lock(&sb->s_inode_list_lock); } + list_add(&inode->i_lru, &dispose); } spin_unlock(&sb->s_inode_list_lock); From f6988c90671e83db79df1b7b9d6fdb0e5947fd84 Mon Sep 17 00:00:00 2001 From: "Patrick Lu (Anthropic)" Date: Fri, 11 Sep 2026 18:49:49 +0000 Subject: [PATCH 0667/1417] writeback: bound cleanup_offline_cgwb() rescans by rotating scanned inodes cleanup_offline_cgwb() prepares at most WB_MAX_INODES_PER_ISW inodes per call and is called again until the dying wb is drained, but every call walks wb->b_attached and then wb->b_dirty_time from the same end. Inodes already prepared (they stay on the list with I_WB_SWITCH set until the switch worker runs) and inodes that cannot be switched (I_FREEING, I_WILL_FREE, !SB_ACTIVE, DAX, already on the target wb) stay where they are, so each pass rescans a growing run of them under wb->list_lock and a full drain is quadratic in the number of inodes on the list. With ~17M inodes attached to one dying cgwb we saw this end in soft lockups, with CPUs reported stuck for 21-48s. Walk both lists from the oldest end and move every scanned inode to the newest end, so the next pass starts where the previous one stopped and the drain becomes linear. b_attached is unordered, so nobody sees the reorder there. b_dirty_time is ordered by dirtied_when, but the oldest unscanned inode stays at the end move_expired_inodes() picks from, sync takes the whole list regardless of order, and prepared inodes leave the list as soon as the switch work runs and get a new dirtied_time_when on the new wb anyway, so the only inodes left out of order are the ones that can never switch (DAX), and only on the dying wb. Fixes: c22d70a162d3 ("writeback, cgroup: release dying cgwbs by switching attached inodes") Cc: stable@vger.kernel.org Acked-by: Tejun Heo Acked-by: Roman Gushchin Signed-off-by: Patrick Lu (Anthropic) Link: https://patch.msgid.link/20260911-wb-cgwb-rotate-v2-1-a9ab253a1295@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/fs-writeback.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index e744f9f9d43fae..ea3eb40bf828e4 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -727,19 +727,34 @@ static bool isw_prepare_wbs_switch(struct bdi_writeback *new_wb, struct inode_switch_wbs_context *isw, struct list_head *list, int *nr) { - struct inode *inode; + struct inode *inode, *tmp; + LIST_HEAD(scanned); + bool full = false; + + /* + * Walk from the oldest end and move scanned inodes to the newest + * end, so the next scan resumes at unscanned inodes instead of + * re-walking an ever-growing run of prepared and skipped ones. + * For b_dirty_time this keeps the oldest unscanned inode at the + * end move_expired_inodes() picks from; b_attached is unordered. + */ + list_for_each_entry_safe_reverse(inode, tmp, list, i_io_list) { + list_move(&inode->i_io_list, &scanned); - list_for_each_entry(inode, list, i_io_list) { if (!inode_prepare_wbs_switch(inode, new_wb)) continue; isw->inodes[*nr] = inode; (*nr)++; - if (*nr >= WB_MAX_INODES_PER_ISW - 1) - return true; + if (*nr >= WB_MAX_INODES_PER_ISW - 1) { + full = true; + break; + } } - return false; + list_splice(&scanned, list); + + return full; } /** From eeff736ebf5d6c6c605808d0e411a214efadadb9 Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Mon, 14 Sep 2026 09:56:47 +0800 Subject: [PATCH 0668/1417] ASoC: codecs: nau8360: Block DSP path selection when firmware load fails Block the DSP path selection if the firmware fails to load to prevent invalid routing states. If the firmware is not ready, nau8360_dac_mux_put_enum() will print a rate-limited warning and return -EBUSY. This rejects the invalid userspace request and maintains the original hardware state without causing audio wrong status. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-sound/aqK_oOpMAXVhxkHJ@stanley.mountain/ Suggested-by: Mark Brown Signed-off-by: Neo Chang Link: https://patch.msgid.link/20260914015648.311986-2-YLCHANG2@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8360.c | 9 +++++++-- sound/soc/codecs/nau8360.h | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/nau8360.c b/sound/soc/codecs/nau8360.c index 89b8ee80d6c868..f5dee12127cd09 100644 --- a/sound/soc/codecs/nau8360.c +++ b/sound/soc/codecs/nau8360.c @@ -715,8 +715,13 @@ static int nau8360_dac_mux_put_enum(struct snd_kcontrol *kcontrol, int ret = 0; if (snd_soc_dapm_get_bias_level(dapm) > SND_SOC_BIAS_STANDBY) { - dev_warn(nau8360->dev, "changing path is not allowed during playback"); - return ret; + dev_warn_ratelimited(nau8360->dev, "changing path is not allowed during playback"); + return -EBUSY; + } + + if (item[0] == NAU8360_DAC_SRC_DSP && !nau8360->load_fw_done) { + dev_warn_ratelimited(nau8360->dev, "Cannot enable DSP: Firmware not ready or disabled\n"); + return -EBUSY; } mutex_lock(&nau8360->lock); diff --git a/sound/soc/codecs/nau8360.h b/sound/soc/codecs/nau8360.h index cc640ba8c8387f..71396747c0fad8 100644 --- a/sound/soc/codecs/nau8360.h +++ b/sound/soc/codecs/nau8360.h @@ -870,6 +870,12 @@ enum { NAU8360_TDM_TXN, }; +/* DAC Source Path*/ +enum { + NAU8360_DAC_SRC_HW1 = 0, + NAU8360_DAC_SRC_DSP, +}; + /* PLL Source */ enum { NAU8360_PLL_MCLK, From 4862e73eaf088c54ac79a1c849b3194d93e2e3a4 Mon Sep 17 00:00:00 2001 From: Neo Chang Date: Mon, 14 Sep 2026 09:56:48 +0800 Subject: [PATCH 0669/1417] ASoC: codecs: nau8360: Fix AB-BA deadlock in mux update nau8360_dac_mux_put_enum() acquires nau8360->lock before the DAPM mutex, causing an AB-BA lock inversion. This deadlocks with concurrent stream startups or mixer updates, which acquire locks in the reverse order. Fix this by replacing nau8360->lock with the DAPM mutex to protect pre-checks, releasing it before calling snd_soc_dapm_put_enum_double(). Reported-by: Sashiko AI Link: https://sashiko.dev/#/patchset/20260908030342.222655-1-YLCHANG2@nuvoton.com?part=1 Signed-off-by: Neo Chang Link: https://patch.msgid.link/20260914015648.311986-3-YLCHANG2@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8360.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/nau8360.c b/sound/soc/codecs/nau8360.c index f5dee12127cd09..f0942739d7abff 100644 --- a/sound/soc/codecs/nau8360.c +++ b/sound/soc/codecs/nau8360.c @@ -689,6 +689,8 @@ static void nau8360_dsp_switch(struct snd_soc_component *component, bool enable) struct regmap *regmap = nau8360->regmap; int value = NAU8360_PEQ_BAND_8; + mutex_lock(&nau8360->lock); + /* If DSP is enabled, unstall HW3 engine and DSP, loading DSP firmware, * and configure PEQ after dsp reset. */ @@ -702,6 +704,7 @@ static void nau8360_dsp_switch(struct snd_soc_component *component, bool enable) regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_BAND_MASK, value << NAU8360_PEQ_BAND_SFT); + mutex_unlock(&nau8360->lock); } static int nau8360_dac_mux_put_enum(struct snd_kcontrol *kcontrol, @@ -714,27 +717,26 @@ static int nau8360_dac_mux_put_enum(struct snd_kcontrol *kcontrol, unsigned int *item = ucontrol->value.enumerated.item; int ret = 0; + snd_soc_dapm_mutex_lock(dapm); if (snd_soc_dapm_get_bias_level(dapm) > SND_SOC_BIAS_STANDBY) { dev_warn_ratelimited(nau8360->dev, "changing path is not allowed during playback"); + snd_soc_dapm_mutex_unlock(dapm); return -EBUSY; } if (item[0] == NAU8360_DAC_SRC_DSP && !nau8360->load_fw_done) { dev_warn_ratelimited(nau8360->dev, "Cannot enable DSP: Firmware not ready or disabled\n"); + snd_soc_dapm_mutex_unlock(dapm); return -EBUSY; } - - mutex_lock(&nau8360->lock); + snd_soc_dapm_mutex_unlock(dapm); ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); if (ret <= 0) - goto unlock; + return ret; nau8360_dsp_switch(component, snd_soc_enum_item_to_val(e, item[0])); -unlock: - mutex_unlock(&nau8360->lock); - return ret; } From e419e9a6cbfe1d0947dd5ab8dabe9a5ed4b4f572 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:01 +0530 Subject: [PATCH 0670/1417] ASoC: SOF: amd: return -EINVAL for unknown PCI revision in acp7x suspend amd_sof_acp7x_suspend() is registered only for ACP7.B and ACP7.F platforms. If an unexpected PCI revision reaches the switch statement the default case previously fell through, leaving ACP_CONTROL with a stale value (enable=false) before writing ZSC_DSP_CTRL=1. Return -EINVAL instead to surface the programming error immediately. Fixes: 1c9646f3180e ("ASoC: SOF: amd: add system and runtime PM ops for ACP7x") Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-2-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index f89ad86260b4ed..688df7acd2e21c 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1197,7 +1197,8 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) enable = true; break; default: - break; + dev_err(sdev->dev, "Unexpected PCI revision: 0x%x\n", acp_data->pci_rev); + return -EINVAL; } snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); From db2167f2e3f72db64d01356cc9f2924d32fd5a51 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:02 +0530 Subject: [PATCH 0671/1417] ASoC: SOF: amd: fix amd_sof_acp_remove() teardown ordering Call free_irq() before amd_sof_sdw_exit() in amd_sof_acp_remove(). amd_sof_sdw_exit() sets adata->sdw to NULL; if an IRQ fires between that point and the subsequent free_irq(), acp_irq_handler() may dereference the now-NULL pdev[] pointer, causing a use-after-free. Fixes: d948218424bf ("ASoC: SOF: amd: add code for invoking soundwire manager helper functions") Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-3-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 688df7acd2e21c..e4cdb74c075db6 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1048,12 +1048,12 @@ void amd_sof_acp_remove(struct snd_sof_dev *sdev) { struct acp_dev_data *adata = sdev->pdata->hw_pdata; - if (adata->sdw) - amd_sof_sdw_exit(sdev); - if (sdev->ipc_irq) free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); + if (adata->dmic_dev) platform_device_unregister(adata->dmic_dev); From b6aaef8ca8c305170de83e621c7e16d56731312a Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:03 +0530 Subject: [PATCH 0672/1417] ASoC: SOF: amd: fix amd_sof_acp_probe() error unwind ordering Call amd_sof_sdw_exit() after free_irq() in the free_ipc_irq error label of amd_sof_acp_probe(). Without this, an IRQ that fires between sdw context teardown and free_irq() can dereference freed SoundWire resources. Fixes: d948218424bf ("ASoC: SOF: amd: add code for invoking soundwire manager helper functions") Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-4-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index e4cdb74c075db6..5570f3d1348dd9 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1038,6 +1038,8 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) free_ipc_irq: free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); unregister_dev: platform_device_unregister(adata->dmic_dev); return ret; From 203ea76c4b0a84f0c70e0ab27503a29f77e0a471 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:04 +0530 Subject: [PATCH 0673/1417] ASoC: SOF: amd: add ACP7.B/7.F PDM controller scan and pdata propagation Add acp_sof_scan_pdm_devices() to read the acp-audio-ep-port ACPI _DSD property from the PDM child device on ACP7.B/7.F platforms. Value 4 selects PDM0 (ACP7X_PDM_DMIC0), value 5 selects PDM1 (ACP7X_PDM_DMIC1). Unrecognized values are reported via dev_warn(). The selected controller is stored in acp_dev_data.pdm_sel and propagated to the machine driver via mach->pdata in amd_sof_machine_select() so the machine driver probe can register the correct SOF DMIC DAI link. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-5-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/soc_amd_sdw_common.h | 13 +++++++++++ sound/soc/sof/amd/acp-common.c | 17 ++++++++++++++ sound/soc/sof/amd/acp.c | 31 ++++++++++++++++++++++++++ sound/soc/sof/amd/acp.h | 10 +++++++++ 4 files changed, 71 insertions(+) diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index 3930cc46fa5871..17e4e97fb3d1d6 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -23,6 +23,19 @@ #define ACP71_PCI_REV 0x71 #define ACP72_PCI_REV 0x72 +/** + * struct amd_pdm_pdata - platform data passed via mach->pdata to machine driver + * @pdm_sel: active PDM controller (ACP7X_PDM_DMIC0 or ACP7X_PDM_DMIC1), + * non-zero when a PDM controller was identified via ACPI _DSD + * + * Carries the PDM controller selection for ACP7.B/7.F platforms, derived + * from the acp-audio-ep-port ACPI _DSD property and passed via mach->pdata + * to the machine driver. + */ +struct amd_pdm_pdata { + unsigned int pdm_sel; +}; + #define SOC_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define ASOC_SDW_FOUR_SPK BIT(4) #define ASOC_SDW_ACP_DMIC BIT(5) diff --git a/sound/soc/sof/amd/acp-common.c b/sound/soc/sof/amd/acp-common.c index df656cdc152773..33540f7c421b19 100644 --- a/sound/soc/sof/amd/acp-common.c +++ b/sound/soc/sof/amd/acp-common.c @@ -16,6 +16,7 @@ #include "acp.h" #include "acp-dsp-offset.h" #include +#include "../../amd/acp/soc_amd_sdw_common.h" /** * amd_sof_ipc_dump() - This function is called when IPC tx times out. @@ -177,6 +178,7 @@ struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev) struct acp_dev_data *acp_data = sdev->pdata->hw_pdata; const struct sof_dev_desc *desc = sof_pdata->desc; struct snd_soc_acpi_mach *mach = NULL; + struct amd_pdm_pdata *pdm_pdata; if (desc->machines) mach = snd_soc_acpi_find_machine(desc->machines); @@ -188,7 +190,22 @@ struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev) } } + mach = devm_kmemdup(sdev->dev, mach, sizeof(*mach), GFP_KERNEL); + if (!mach) { + dev_err(sdev->dev, "failed to allocate machine entry copy\n"); + return NULL; + } + mach->mach_params.subsystem_rev = acp_data->pci_rev; + + if (acp_data->pdm_sel) { + pdm_pdata = devm_kzalloc(sdev->dev, sizeof(*pdm_pdata), GFP_KERNEL); + if (!pdm_pdata) + return NULL; + pdm_pdata->pdm_sel = acp_data->pdm_sel; + mach->pdata = pdm_pdata; + } + sof_pdata->tplg_filename = mach->sof_tplg_filename; sof_pdata->fw_filename = mach->fw_filename; diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 5570f3d1348dd9..37909f2d86a415 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -853,6 +853,31 @@ int amd_sof_acp_resume(struct snd_sof_dev *sdev) } EXPORT_SYMBOL_NS(amd_sof_acp_resume, "SND_SOC_SOF_AMD_COMMON"); +static void acp_sof_scan_pdm_devices(struct snd_sof_dev *sdev, + struct acpi_device *pdm_dev) +{ + struct acp_dev_data *acp_data = sdev->pdata->hw_pdata; + struct fwnode_handle *fwnode, *child; + u32 ep_port_val; + + fwnode = acpi_fwnode_handle(pdm_dev); + child = fwnode_get_next_child_node(fwnode, NULL); + if (!child) + return; + + if (!fwnode_property_read_u32(child, "acp-audio-ep-port", &ep_port_val)) { + if (ep_port_val == ACP_DEV_PORT_PDM) + acp_data->pdm_sel = ACP7X_PDM_DMIC0; + else if (ep_port_val == ACP_DEV_PORT_PDM2) + acp_data->pdm_sel = ACP7X_PDM_DMIC1; + else + dev_warn(sdev->dev, + "acp-audio-ep-port: unrecognized value %u\n", + ep_port_val); + } + fwnode_handle_put(child); +} + #if IS_ENABLED(CONFIG_SND_SOC_SOF_AMD_SOUNDWIRE) static int acp_sof_scan_sdw_devices(struct snd_sof_dev *sdev, u64 addr) { @@ -1070,6 +1095,7 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) const struct sof_amd_acp_desc *chip; const union acpi_object *obj; struct acpi_device *adev; + struct acpi_device *pdm_dev; unsigned int addr; unsigned int irqflags; int ret; @@ -1123,6 +1149,11 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) } if (adev) { + /* DMIC ACPI child address is 2 on ACP7x platforms */ + pdm_dev = acpi_find_child_device(adev, ACP7X_DMIC_ADDR, 0); + if (pdm_dev) + acp_sof_scan_pdm_devices(sdev, pdm_dev); + if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ACPI_TYPE_INTEGER, &obj)) adata->acp_sof_signed_firmware_image = obj->integer.value; diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 16d66b2eaa70bb..2326b9d2e4c6f0 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -125,6 +125,14 @@ #define ACP_SRAM_PAGE_COUNT 128 #define ACP6X_SDW_MAX_MANAGER_COUNT 2 #define ACP70_SDW_MAX_MANAGER_COUNT ACP6X_SDW_MAX_MANAGER_COUNT +/* ACPI _DSD acp-audio-ep-port values for PDM controller selection */ +#define ACP_DEV_PORT_PDM 4 +#define ACP_DEV_PORT_PDM2 5 +/* ACPI child device address for the ACP7x PDM/DMIC device */ +#define ACP7X_DMIC_ADDR 2 +/* ACP7X PDM controller selection values for acp_dev_data.pdm_sel; 0 = not set */ +#define ACP7X_PDM_DMIC0 1 +#define ACP7X_PDM_DMIC1 2 #define ACP_DSP_MSG_SET 1 #define ACP_DSP_ACK_SET 1 @@ -279,6 +287,8 @@ struct acp_dev_data { bool acp70_sdw0_wake_event; /* acp70_sdw1_wake_event flag set to true when wake irq asserted for SW1 instance */ bool acp70_sdw1_wake_event; + /* PDM controller index selected from ACPI acp-audio-ep-port; passed to machine driver */ + unsigned int pdm_sel; unsigned int pci_rev; int acp_sof_signed_firmware_image; }; From 59c03f40a53e697d9f29359fbf39834a7e15cc5b Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:05 +0530 Subject: [PATCH 0674/1417] ASoC: SOF: amd: update SoundWire specific acp descriptor fields for ACP7.B/7.F Populate the SoundWire descriptor fields in acp7x_chip_info so the SOF core can locate and enumerate the four SoundWire managers on ACP7.B/7.F platforms. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-6-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.h | 3 +++ sound/soc/sof/amd/pci-acp7x.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 2326b9d2e4c6f0..47e475de8b4dab 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -128,6 +128,9 @@ /* ACPI _DSD acp-audio-ep-port values for PDM controller selection */ #define ACP_DEV_PORT_PDM 4 #define ACP_DEV_PORT_PDM2 5 +#define SDW_ACPI_ADDR_ACP7X SDW_ACPI_ADDR_ACP63 +#define ACP7X_SDW_MAX_MANAGER_COUNT 4 + /* ACPI child device address for the ACP7x PDM/DMIC device */ #define ACP7X_DMIC_ADDR 2 /* ACP7X PDM controller selection values for acp_dev_data.pdm_sel; 0 = not set */ diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 532e1531379591..0594c1f3deb6bb 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -40,6 +40,8 @@ static const struct sof_amd_acp_desc acp7x_chip_info = { .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, .reg_start_addr = ACP7X_REG_START, + .sdw_max_link_count = ACP7X_SDW_MAX_MANAGER_COUNT, + .sdw_acpi_dev_addr = SDW_ACPI_ADDR_ACP7X, .reg_end_addr = ACP7X_REG_END, }; From 612684a9d2afd1a49017282b2666fb919dbc4f4c Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:06 +0530 Subject: [PATCH 0675/1417] ASoC: SOF: amd: enable SoundWire build for ACP7.B/7.F Select SND_SOC_SOF_AMD_SOUNDWIRE_LINK_BASELINE in Kconfig for SND_SOC_SOF_AMD_ACP7X so that the SoundWire stack is built when ACP7.B/7.F SOF support is enabled. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-7-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sof/amd/Kconfig b/sound/soc/sof/amd/Kconfig index 903e7ec3b3ba41..d64f682cfbea68 100644 --- a/sound/soc/sof/amd/Kconfig +++ b/sound/soc/sof/amd/Kconfig @@ -109,6 +109,7 @@ config SND_SOC_SOF_AMD_ACP7X depends on SND_SOC_SOF_PCI depends on AMD_NODE select SND_SOC_SOF_AMD_COMMON + select SND_SOC_SOF_AMD_SOUNDWIRE_LINK_BASELINE help Select this option for SOF support on AMD ACP7.B and ACP7.F PCI revision based platforms. From 750176135630f1eda6e946ecb4155c2b7afa199b Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:07 +0530 Subject: [PATCH 0676/1417] ASoC: SOF: amd: wire SoundWire probe and remove into ACP7.B/7.F paths In amd_sof_acp7x_probe(), scan DSDT for SoundWire peripherals via acp_sof_scan_sdw_devices() and call amd_sof_sdw_probe() on success. In the error unwind, call free_irq() before amd_sof_sdw_exit() so the IRQ cannot fire after the SoundWire context is freed. In amd_sof_acp7x_remove(), call amd_sof_sdw_exit() after free_irq() to prevent a use-after-free: the IRQ handler dereferences pdev[] entries freed by amd_sof_sdw_exit(). Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-8-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 37909f2d86a415..a900dbc1de20d5 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1148,6 +1148,18 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) goto unregister_dev; } + /* scan SoundWire capabilities exposed by DSDT */ + ret = acp_sof_scan_sdw_devices(sdev, chip->sdw_acpi_dev_addr); + if (ret < 0) { + dev_dbg(sdev->dev, "skipping SoundWire, not detected with ACPI scan\n"); + goto skip_soundwire; + } + ret = amd_sof_sdw_probe(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: SoundWire probe error\n"); + goto free_ipc_irq; + } +skip_soundwire: if (adev) { /* DMIC ACPI child address is 2 on ACP7x platforms */ pdm_dev = acpi_find_child_device(adev, ACP7X_DMIC_ADDR, 0); @@ -1191,6 +1203,8 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) free_ipc_irq: free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); unregister_dev: platform_device_unregister(adata->dmic_dev); return ret; @@ -1204,6 +1218,9 @@ void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) if (sdev->ipc_irq) free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); + if (adata->dmic_dev) platform_device_unregister(adata->dmic_dev); From db294afbd61aabd857fce39372a1bd1c8b173441 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:08 +0530 Subject: [PATCH 0677/1417] ASoC: SOF: amd: add ACP7.B/7.F clock-stop detection in check_acp_sdw_enable_status Extend check_acp_sdw_enable_status() with a pci_rev switch so that ACP7.B/7.F, which has 4 SoundWire manager instances, reads all four SW_EN registers to determine whether any manager is in clock-stop mode. The existing two-manager read (ACP_SW0_EN, ACP_SW1_EN) is preserved for ACP63/ACP70/ACP71/ACP72 platforms. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-9-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp-dsp-offset.h | 2 ++ sound/soc/sof/amd/acp.c | 23 +++++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h index bea1bd3afa70d2..74abbc834d71ca 100644 --- a/sound/soc/sof/amd/acp-dsp-offset.h +++ b/sound/soc/sof/amd/acp-dsp-offset.h @@ -156,5 +156,7 @@ #define ACP7X_IDMA_ERROR_MASK 0x1FF9FF #define ACP7X_ZSC_DSP_CTRL 0x001014 #define ACP7X_PME_EN ACP70_PME_EN +/* SW enable base for SDW0; manager N uses ACP7X_SW_EN + (N * 0x2000) */ +#define ACP7X_SW_EN 0x5200 #endif diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index a900dbc1de20d5..a4ca4f78e10609 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -779,15 +779,30 @@ static int acp_init(struct snd_sof_dev *sdev) static bool check_acp_sdw_enable_status(struct snd_sof_dev *sdev) { struct acp_dev_data *acp_data; - u32 sdw0_en, sdw1_en; + u32 sdw0_en, sdw1_en, sdw2_en, sdw3_en; acp_data = sdev->pdata->hw_pdata; if (!acp_data->sdw) return false; - sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW0_EN); - sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW1_EN); - acp_data->sdw_en_stat = sdw0_en || sdw1_en; + switch (acp_data->pci_rev) { + case ACP63_PCI_ID: + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: + sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW0_EN); + sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW1_EN); + acp_data->sdw_en_stat = sdw0_en || sdw1_en; + break; + case ACP7B_PCI_ID: + case ACP7F_PCI_ID: + sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (0 * 0x2000)); + sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (1 * 0x2000)); + sdw2_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (2 * 0x2000)); + sdw3_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (3 * 0x2000)); + acp_data->sdw_en_stat = sdw0_en || sdw1_en || sdw2_en || sdw3_en; + break; + } return acp_data->sdw_en_stat; } From c9b000c028792c313861b123c508b3dc7f7114ab Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:09 +0530 Subject: [PATCH 0678/1417] ASoC: SOF: amd: add ACP7.B/7.F SoundWire IO IRQ handling ACP7.B/7.F exposes four SoundWire managers (SDW0-SDW3), each with dedicated interrupt, wake-enable, PME status and error registers. Add acp7x_irq_handler() to dispatch: - Per-manager data IRQs via sof_acp7x_handle_sdw_manager_irq() - Host-wake and PME events via sof_amd_check_and_handle_acp7x_sdw_wake_irq() which checks both sources independently per manager so simultaneous host-wake and PME events are not missed - Error conditions via sof_amd_acp7x_clear_sdw_err_regs() which writes back the updated error status to clear only the affected manager bit Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-10-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp-dsp-offset.h | 15 +++ sound/soc/sof/amd/acp.c | 187 ++++++++++++++++++++++++++++- sound/soc/sof/amd/acp.h | 2 + sound/soc/sof/amd/acp7x.h | 37 ++++++ 4 files changed, 235 insertions(+), 6 deletions(-) create mode 100644 sound/soc/sof/amd/acp7x.h diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h index 74abbc834d71ca..3984cd627db6df 100644 --- a/sound/soc/sof/amd/acp-dsp-offset.h +++ b/sound/soc/sof/amd/acp-dsp-offset.h @@ -159,4 +159,19 @@ /* SW enable base for SDW0; manager N uses ACP7X_SW_EN + (N * 0x2000) */ #define ACP7X_SW_EN 0x5200 +/* ACP7X SoundWire IO registers (non-gsync, up to 4 managers SW0-SW3) */ +#define ACP7X_EXTERNAL_SW_INTR_STAT 0x001A24 +#define ACP7X_SW_WAKE_EN 0x001458 +#define ACP7X_SW_PME_STS 0x001474 +#define ACP7X_SDW_STAT BIT(23) +#define ACP7X_ERROR_IRQ BIT(29) +#define ACP7X_SW_WAKE_EN_MASK BIT(0) +#define ACP7X_SW_HOST_WAKE_MASK BIT(22) +#define ACP7X_SDW_HOST_WAKE_STAT BIT(24) +#define ACP7X_SW_FIFO_ERROR_REASON 0x50C4 +#define ACP7X_SW_ERROR_REASON1 0x50CC +#define ACP7X_SW_ERROR_REASON2 0x50D4 +#define ACP7X_EXTERNAL_SDW_STAT BIT(16) +#define ACP7X_SW_ERR_STAT_MASK BIT(11) + #endif diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index a4ca4f78e10609..eff86b1cee3cd0 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -22,11 +22,20 @@ #include "../ops.h" #include "acp.h" #include "acp-dsp-offset.h" +#include "../../amd/acp/soc_amd_sdw_common.h" +#include "acp7x.h" static bool enable_fw_debug; module_param(enable_fw_debug, bool, 0444); MODULE_PARM_DESC(enable_fw_debug, "Enable Firmware debug"); +static const u32 acp7x_sof_sdw_ext_stat[ACP7X_SDW_MAX_MANAGER_COUNT] = { + ACP7X_SDW_STAT << 0, + ACP7X_SDW_STAT << 1, + ACP7X_SDW_STAT << 2, + ACP7X_SDW_STAT << 3, +}; + static struct acp_quirk_entry quirk_valve_galileo = { .signed_fw_image = true, .skip_iram_dram_size_mod = true, @@ -581,30 +590,196 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) return IRQ_NONE; } +/* ACP7X SoundWire IO data tables */ + +const struct sof_amd_acp7x_sdw_err_regs acp7x_sdw_err_regs[ACP7X_SDW_MAX_MANAGER_COUNT] = { + { ACP7X_SW_ERR_STAT_MASK << 0, + ACP7X_SW_FIFO_ERROR_REASON + (0 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (0 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (0 * 0x2000) }, + { ACP7X_SW_ERR_STAT_MASK << 1, + ACP7X_SW_FIFO_ERROR_REASON + (1 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (1 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (1 * 0x2000) }, + { ACP7X_SW_ERR_STAT_MASK << 2, + ACP7X_SW_FIFO_ERROR_REASON + (2 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (2 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (2 * 0x2000) }, + { ACP7X_SW_ERR_STAT_MASK << 3, + ACP7X_SW_FIFO_ERROR_REASON + (3 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (3 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (3 * 0x2000) }, +}; + +const struct acp7x_sdw_wake_src acp7x_sdw_wake_sources[] = { + { ACP7X_SDW_HOST_WAKE_STAT << 0, ACP7X_SW_PME_STS + (0 * 4), + ACP7X_SW_WAKE_EN_MASK << 0, 0 }, + { ACP7X_SDW_HOST_WAKE_STAT << 1, ACP7X_SW_PME_STS + (1 * 4), + ACP7X_SW_WAKE_EN_MASK << 1, 1 }, + { ACP7X_SDW_HOST_WAKE_STAT << 2, ACP7X_SW_PME_STS + (2 * 4), + ACP7X_SW_WAKE_EN_MASK << 2, 2 }, + { ACP7X_SDW_HOST_WAKE_STAT << 3, ACP7X_SW_PME_STS + (3 * 4), + ACP7X_SW_WAKE_EN_MASK << 3, 3 }, +}; + +/* ACP7X SoundWire IO interrupt and wake helpers */ + +static void sof_amd_acp7x_handle_one_sdw_err(struct snd_sof_dev *sdev, u32 *err_stat, + u32 acp_error_stat_reg, + const struct sof_amd_acp7x_sdw_err_regs *regs) +{ + if (!(*err_stat & regs->err_stat_mask)) + return; + + *err_stat &= ~regs->err_stat_mask; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, acp_error_stat_reg, *err_stat); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, regs->fifo_err_reason, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, regs->err_reason1, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, regs->err_reason2, 0); +} + +/* Clears SoundWire error registers for all managers; runs in hard IRQ context. */ +static void sof_amd_acp7x_clear_sdw_err_regs(struct snd_sof_dev *sdev) +{ + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u32 err_stat; + unsigned int instance; + + err_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->acp_error_stat); + + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) + sof_amd_acp7x_handle_one_sdw_err(sdev, &err_stat, desc->acp_error_stat, + &acp7x_sdw_err_regs[instance]); +} + +static void sof_amd_handle_acp7x_sdw_wake_event(struct acp_dev_data *adata) +{ + struct amd_sdw_manager *amd_manager; + unsigned int instance; + + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) { + if (!adata->acp7x_sdw_wake_event[instance]) + continue; + if (!adata->sdw->pdev[instance]) + continue; + + amd_manager = dev_get_drvdata(&adata->sdw->pdev[instance]->dev); + if (amd_manager) + pm_request_resume(amd_manager->dev); + adata->acp7x_sdw_wake_event[instance] = false; + } +} + +static int sof_amd_check_and_handle_acp7x_sdw_wake_irq(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + unsigned int ext_intr_stat1; + unsigned int sdw_pme_stat, sdw_wake_en; + unsigned int i; + bool sdw_wake_irq = false; + + ext_intr_stat1 = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat1); + + for (i = 0; i < ARRAY_SIZE(acp7x_sdw_wake_sources); i++) { + const struct acp7x_sdw_wake_src *src = &acp7x_sdw_wake_sources[i]; + + bool woke = false; + + if (ext_intr_stat1 & src->host_stat_mask) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat1, + src->host_stat_mask); + woke = true; + } + + sdw_pme_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, src->pme_sts_reg); + if (sdw_pme_stat) { + sdw_wake_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN); + sdw_wake_en &= ~src->wake_en_mask; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN, sdw_wake_en); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, src->pme_sts_reg, sdw_pme_stat); + woke = true; + } + + if (woke) { + adata->acp7x_sdw_wake_event[src->instance] = true; + sdw_wake_irq = true; + } + } + + if (sdw_wake_irq) { + sof_amd_handle_acp7x_sdw_wake_event(adata); + return WAKE_IRQ_HANDLED; + } + return WAKE_IRQ_NONE; +} + +static void sof_acp7x_handle_sdw_manager_irq(struct snd_sof_dev *sdev, + struct acp_dev_data *adata, + const struct sof_amd_acp_desc *desc, + unsigned int instance, + u32 ext_stat_mask) +{ + struct amd_sdw_manager *amd_manager; + unsigned int sdw_intr_stat; + u32 sw_intr_reg = ACP7X_EXTERNAL_SW_INTR_STAT + (instance * 4); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ext_stat_mask); + sdw_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, sw_intr_reg); + if (sdw_intr_stat & ACP7X_EXTERNAL_SDW_STAT) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, sw_intr_reg, ACP7X_EXTERNAL_SDW_STAT); + if (!adata->sdw->pdev[instance]) + return; + amd_manager = dev_get_drvdata(&adata->sdw->pdev[instance]->dev); + if (amd_manager) + schedule_work(&amd_manager->amd_sdw_irq_thread); + } +} + static irqreturn_t acp7x_irq_handler(int irq, void *dev_id) { struct snd_sof_dev *sdev = dev_id; + struct acp_dev_data *adata = sdev->pdata->hw_pdata; const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); unsigned int base = desc->dsp_intr_base; unsigned int val; unsigned int ext_intr_stat; - int irq_flag = 0; + unsigned int instance; + int irq_flag = 0, wake_irq_flag = 0; + bool dsp_irq = false; val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET); if (val & ACP_DSP_TO_HOST_IRQ) { snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET, ACP_DSP_TO_HOST_IRQ); - return IRQ_WAKE_THREAD; + dsp_irq = true; } ext_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat); - if (ext_intr_stat & ACP_ERROR_IRQ_MASK) { - snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); - snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); + + if (adata->sdw) { + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) { + if (ext_intr_stat & acp7x_sof_sdw_ext_stat[instance]) { + sof_acp7x_handle_sdw_manager_irq(sdev, adata, desc, instance, + acp7x_sof_sdw_ext_stat[instance]); + irq_flag = 1; + } + } + } + + if (adata->sdw) + wake_irq_flag = sof_amd_check_and_handle_acp7x_sdw_wake_irq(sdev); + + if (ext_intr_stat & ACP7X_ERROR_IRQ) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP7X_ERROR_IRQ); + sof_amd_acp7x_clear_sdw_err_regs(sdev); irq_flag = 1; } - if (irq_flag) + if (dsp_irq) + return IRQ_WAKE_THREAD; + + if (irq_flag || wake_irq_flag) return IRQ_HANDLED; return IRQ_NONE; diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 47e475de8b4dab..e3c85b5cb20cf3 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -290,6 +290,8 @@ struct acp_dev_data { bool acp70_sdw0_wake_event; /* acp70_sdw1_wake_event flag set to true when wake irq asserted for SW1 instance */ bool acp70_sdw1_wake_event; + /* per-manager wake event flags; indexed by SoundWire manager instance (0-3) */ + bool acp7x_sdw_wake_event[ACP7X_SDW_MAX_MANAGER_COUNT]; /* PDM controller index selected from ACPI acp-audio-ep-port; passed to machine driver */ unsigned int pdm_sel; unsigned int pci_rev; diff --git a/sound/soc/sof/amd/acp7x.h b/sound/soc/sof/amd/acp7x.h new file mode 100644 index 00000000000000..5151d8757c12d1 --- /dev/null +++ b/sound/soc/sof/amd/acp7x.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2026 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Vijendar Mukunda + */ + +#ifndef __SOF_AMD_ACP7X_H +#define __SOF_AMD_ACP7X_H + +/* Return values for sof_amd_check_and_handle_acp7x_sdw_wake_irq() */ +#define WAKE_IRQ_HANDLED 1 +#define WAKE_IRQ_NONE 0 + +/* ACP7X SoundWire IO structures */ + +struct sof_amd_acp7x_sdw_err_regs { + u32 err_stat_mask; + u32 fifo_err_reason; + u32 err_reason1; + u32 err_reason2; +}; + +struct acp7x_sdw_wake_src { + u32 host_stat_mask; + u32 pme_sts_reg; + u32 wake_en_mask; + u8 instance; +}; + +extern const struct sof_amd_acp7x_sdw_err_regs acp7x_sdw_err_regs[ACP7X_SDW_MAX_MANAGER_COUNT]; +extern const struct acp7x_sdw_wake_src acp7x_sdw_wake_sources[]; + +#endif /* __SOF_AMD_ACP7X_H */ From b99242b8d4b34a95f844e5b9e6e02a98fcaddc0d Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:10 +0530 Subject: [PATCH 0679/1417] ASoC: SOF: amd: enable SoundWire host wake interrupt in acp_init During acp_init() for ACP7.B/7.F, read ACP7X_SW_WAKE_EN and enable the corresponding host-wake interrupt mask in ACP7X_EXTERNAL_INTR_CNTL1 only for managers that have wake-enable set (SW_WAKE_EN bit i enables ACP7X_SW_HOST_WAKE_MASK << i in INTR_CNTL1). This ensures only the active managers arm their host-wake interrupt when ACP enters D0. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-11-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index eff86b1cee3cd0..da987a51491c0c 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -903,6 +903,8 @@ static int acp_init(struct snd_sof_dev *sdev) const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); struct acp_dev_data *acp_data; unsigned int sdw0_wake_en, sdw1_wake_en; + u32 sdw_wake_en, intr_mask; + unsigned int i; int ret; /* power on */ @@ -946,6 +948,20 @@ static int acp_init(struct snd_sof_dev *sdev) snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_DSP0_IDMA_ERROR_MASK, ACP7X_IDMA_ERROR_MASK); + /* + * Enable host-wake interrupt per manager based on SW_WAKE_EN: + * SW_WAKE_EN bit i enables ACP7X_SW_HOST_WAKE_MASK << i in INTR_CNTL1. + */ + sdw_wake_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN); + intr_mask = 0; + for (i = 0; i < ACP7X_SDW_MAX_MANAGER_COUNT; i++) { + if (sdw_wake_en & BIT(i)) + intr_mask |= ACP7X_SW_HOST_WAKE_MASK << i; + } + if (intr_mask) + snd_sof_dsp_update_bits(sdev, ACP_DSP_BAR, + ACP7X_EXTERNAL_INTR_CNTL1, + intr_mask, intr_mask); break; } return 0; From be2823854e00558f46f5bbb124bb3f3a3bb9a94a Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 18:50:11 +0530 Subject: [PATCH 0680/1417] ASoC: SOF: amd: add SoundWire PM ops for ACP7.B/7.F Add handle_amd_sof_acp7x_sdw_pme_event() to iterate over all four SoundWire managers on runtime PM resume, clear PME status and wake-enable bits (under acp_lock to prevent races with the IRQ path), and request runtime resume for each active manager. Extend amd_sof_acp7x_suspend() with a clock-stop fast path: when SoundWire managers are in clock-stop mode, write ZSC_DSP_CTRL=1 and call acp_dsp_reset() instead of the full acp_reset() to preserve SoundWire bus state. Extend amd_sof_acp7x_resume() with a matching clock-stop resume path that restores ZSC_DSP_CTRL and re-arms PME_EN. Introduce amd_sof_acp7x_suspend_runtime() and amd_sof_acp7x_resume_runtime() as separate runtime PM callbacks. The runtime resume path calls handle_amd_sof_acp7x_sdw_pme_event() after re-initialising hardware to clear any PME state that arrived during the suspend window. Signed-off-by: Vijendar Mukunda Link: https://patch.msgid.link/20260910132251.1171943-12-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 104 +++++++++++++++++++++++++++++++++------- sound/soc/sof/amd/acp.h | 1 + 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index da987a51491c0c..b059039c9e0d1e 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1365,6 +1365,9 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) dev_err(sdev->dev, "error: SoundWire probe error\n"); goto free_ipc_irq; } + if (adata->info.link_mask) + adata->is_sdw_dev = true; + skip_soundwire: if (adev) { /* DMIC ACPI child address is 2 on ACP7x platforms */ @@ -1434,6 +1437,44 @@ void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) } EXPORT_SYMBOL_NS(amd_sof_acp7x_remove, "SND_SOC_SOF_AMD_COMMON"); +static void handle_amd_sof_acp7x_sdw_pme_event(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata; + struct amd_sdw_manager *amd_manager; + u32 sdw_pme_stat; + u32 sdw_wake_en; + u32 pme_reg; + u32 wake_mask; + unsigned int instance; + + adata = sdev->pdata->hw_pdata; + if (!adata->sdw) + return; + + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) { + pme_reg = ACP7X_SW_PME_STS + (instance * 4); + wake_mask = ACP7X_SW_WAKE_EN_MASK << instance; + + sdw_pme_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, pme_reg); + if (!sdw_pme_stat) + continue; + + mutex_lock(&adata->acp_lock); + sdw_wake_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN); + sdw_wake_en &= ~wake_mask; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN, sdw_wake_en); + mutex_unlock(&adata->acp_lock); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pme_reg, sdw_pme_stat); + + if (!adata->sdw->pdev[instance]) + continue; + + amd_manager = dev_get_drvdata(&adata->sdw->pdev[instance]->dev); + if (amd_manager) + pm_request_resume(amd_manager->dev); + } +} + int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) { struct acp_dev_data *acp_data; @@ -1442,6 +1483,11 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) acp_data = sdev->pdata->hw_pdata; + if (acp_data->is_sdw_dev && check_acp_sdw_enable_status(sdev)) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); + return acp_dsp_reset(sdev); + } + ret = acp_reset(sdev); if (ret) { dev_err(sdev->dev, "ACP Reset failed\n"); @@ -1463,13 +1509,25 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) } EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend, "SND_SOC_SOF_AMD_COMMON"); -int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) +int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) +{ + return amd_sof_acp7x_suspend(sdev, 0); +} +EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); + +int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) { struct acp_dev_data *acp_data; int ret; acp_data = sdev->pdata->hw_pdata; + if (acp_data->sdw_en_stat) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); + return acp_dsp_reset(sdev); + } + ret = acp_init(sdev); if (ret) { dev_err(sdev->dev, "ACP Init failed\n"); @@ -1481,30 +1539,40 @@ int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) return ret; } - switch (acp_data->pci_rev) { - case ACP7B_PCI_ID: - case ACP7F_PCI_ID: - snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); - break; - default: - break; - } + if (acp_data->is_sdw_dev) + handle_amd_sof_acp7x_sdw_pme_event(sdev); return 0; } -EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); +EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); -int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) +int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) { - return amd_sof_acp7x_suspend(sdev, 0); -} -EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); + struct acp_dev_data *acp_data; + int ret; -int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) -{ - return amd_sof_acp7x_resume(sdev); + acp_data = sdev->pdata->hw_pdata; + + if (acp_data->sdw_en_stat) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); + return acp_dsp_reset(sdev); + } + + ret = acp_init(sdev); + if (ret) { + dev_err(sdev->dev, "ACP Init failed\n"); + return ret; + } + ret = acp_memory_init(sdev); + if (ret) { + dev_err(sdev->dev, "ACP Memory init failed\n"); + return ret; + } + + return 0; } -EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); +EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("AMD ACP sof driver"); diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index e3c85b5cb20cf3..1bc6b572448d80 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -294,6 +294,7 @@ struct acp_dev_data { bool acp7x_sdw_wake_event[ACP7X_SDW_MAX_MANAGER_COUNT]; /* PDM controller index selected from ACPI acp-audio-ep-port; passed to machine driver */ unsigned int pdm_sel; + bool is_sdw_dev; unsigned int pci_rev; int acp_sof_signed_firmware_image; }; From 5a3c23d4aa89cdb7a3f2a42465c2cabcb71ebcdd Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:08 +0700 Subject: [PATCH 0681/1417] ASoC: mediatek: mt8189: Propagate APLL enable errors mt8189_apll1_enable() and mt8189_apll2_enable() currently ignore errors from regmap_update_bits() and do not clean up resources when clock enable operations fail. Propagate these errors and roll back the clocks and tuner state on errors. Fixes: dc637ffeed6c ("ASoC: mediatek: mt8189: support audio clock control") Signed-off-by: bui duc phuc Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260916050020.14575-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-clk.c | 78 ++++++++++++++++------ 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c index fc7a7a73b0cf7d..f088ccf8044883 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c @@ -454,30 +454,47 @@ int mt8189_apll1_enable(struct mtk_base_afe *afe) ret = mt8189_afe_enable_top_cg(afe, MT8189_CG_APLL1_CK); if (ret) - return ret; + goto err_clear_mux_setting; ret = mt8189_afe_enable_top_cg(afe, MT8189_PDN_APLL_TUNER1); if (ret) - return ret; + goto err_disable_apll1_ck; /* sel 44.1kHz:1, apll_div:7, upper bound:3 */ - regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, - XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | - UPPER_BOUND_MASK_SFT, - (0x1 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) | - (3 << UPPER_BOUND_SFT)); + ret = regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, + XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | + UPPER_BOUND_MASK_SFT, + (0x1 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) | + (3 << UPPER_BOUND_SFT)); + if (ret) + goto err_disable_apll_tuner1; /* apll1 freq tuner enable */ - regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, - FREQ_TUNER_EN_MASK_SFT, - 0x1 << FREQ_TUNER_EN_SFT); + ret = regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x1 << FREQ_TUNER_EN_SFT); + if (ret) + goto err_disable_apll_tuner1; /* audio apll1 on */ ret = mt8189_afe_enable_top_cg(afe, MT8189_AUDIO_APLL1_EN_ON); if (ret) - return ret; + goto err_clear_freq_tuner_en; return 0; + +err_clear_freq_tuner_en: + regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x0); +err_disable_apll_tuner1: + mt8189_afe_disable_top_cg(afe, MT8189_PDN_APLL_TUNER1); +err_disable_apll1_ck: + mt8189_afe_disable_top_cg(afe, MT8189_CG_APLL1_CK); +err_clear_mux_setting: + apll1_mux_setting(afe, false); + + return ret; } void mt8189_apll1_disable(struct mtk_base_afe *afe) @@ -506,30 +523,47 @@ int mt8189_apll2_enable(struct mtk_base_afe *afe) ret = mt8189_afe_enable_top_cg(afe, MT8189_CG_APLL2_CK); if (ret) - return ret; + goto err_clear_mux_setting; ret = mt8189_afe_enable_top_cg(afe, MT8189_PDN_APLL_TUNER2); if (ret) - return ret; + goto err_disable_apll2_ck; /* sel 48kHz: 2, apll_div: 7, upper bound: 3*/ - regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, - XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | - UPPER_BOUND_MASK_SFT, - (0x2 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) | - (3 << UPPER_BOUND_SFT)); + ret = regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, + XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | + UPPER_BOUND_MASK_SFT, + (0x2 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) | + (3 << UPPER_BOUND_SFT)); + if (ret) + goto err_disable_apll_tuner2; /* apll2 freq tuner enable */ - regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, - FREQ_TUNER_EN_MASK_SFT, - 0x1 << FREQ_TUNER_EN_SFT); + ret = regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x1 << FREQ_TUNER_EN_SFT); + if (ret) + goto err_disable_apll_tuner2; /* audio apll2 on */ ret = mt8189_afe_enable_top_cg(afe, MT8189_AUDIO_APLL2_EN_ON); if (ret) - return ret; + goto err_clear_freq_tuner_en; return 0; + +err_clear_freq_tuner_en: + regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x0); +err_disable_apll_tuner2: + mt8189_afe_disable_top_cg(afe, MT8189_PDN_APLL_TUNER2); +err_disable_apll2_ck: + mt8189_afe_disable_top_cg(afe, MT8189_CG_APLL2_CK); +err_clear_mux_setting: + apll2_mux_setting(afe, false); + + return ret; } void mt8189_apll2_disable(struct mtk_base_afe *afe) From bd9a8d9def207a8d7eeaa4dda1c53c5b4fca4008 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:09 +0700 Subject: [PATCH 0682/1417] ASoC: mediatek: mt8189: Propagate MCK enable errors mt8189_mck_enable() currently returns without restoring the clock state when setting the clock parent, enabling the divider, or setting the divider rate fails. Propagate the error and disable clocks enabled by the function. Fixes: dc637ffeed6c ("ASoC: mediatek: mt8189: support audio clock control") Signed-off-by: bui duc phuc Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260916050020.14575-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-clk.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c index f088ccf8044883..56362c4756c920 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c @@ -639,7 +639,7 @@ int mt8189_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) ret = mt8189_afe_set_clk_parent(afe, afe_priv->clk[m_sel_id], afe_priv->clk[apll_clk_id]); if (ret) - return ret; + goto err_disable_m_sel_clk; } /* enable div, set rate */ @@ -650,13 +650,21 @@ int mt8189_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) ret = mt8189_afe_enable_clk(afe, afe_priv->clk[div_clk_id]); if (ret) - return ret; + goto err_disable_m_sel_clk; ret = mt8189_afe_set_clk_rate(afe, afe_priv->clk[div_clk_id], rate); if (ret) - return ret; + goto err_disable_div_clk; return 0; + +err_disable_div_clk: + mt8189_afe_disable_clk(afe, afe_priv->clk[div_clk_id]); +err_disable_m_sel_clk: + if (m_sel_id >= 0) + mt8189_afe_disable_clk(afe, afe_priv->clk[m_sel_id]); + + return ret; } int mt8189_mck_disable(struct mtk_base_afe *afe, int mck_id) From b6b47040e897f18df1ed53c658ab5159b035b5d1 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:10 +0700 Subject: [PATCH 0683/1417] ASoC: mediatek: mt8189: Validate MCK ID mt8189_mck_disable() only checks for negative MCK IDs. Reject IDs outside the valid MCK range as well. Fixes: dc637ffeed6c ("ASoC: mediatek: mt8189: support audio clock control") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-clk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c index 56362c4756c920..7fe9d01006d515 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c @@ -675,8 +675,8 @@ int mt8189_mck_disable(struct mtk_base_afe *afe, int mck_id) dev_dbg(afe->dev, "mck_id: %d.\n", mck_id); - if (mck_id < 0) { - dev_err(afe->dev, "mck_id = %d < 0\n", mck_id); + if (mck_id >= MT8189_MCK_NUM || mck_id < 0) { + dev_err(afe->dev, "mck_id = %d\n", mck_id); return -EINVAL; } From 29de6666879997c9dc375344fd046363d0c3bcdb Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:11 +0700 Subject: [PATCH 0684/1417] ASoC: mediatek: mt8189: Propagate reg_rw clock errors mt8189_afe_enable_reg_rw_clk() currently ignores errors from clock enable and parent operations. Propagate these errors and clean up the clocks before returning the error. Fixes: dc637ffeed6c ("ASoC: mediatek: mt8189: support audio clock control") Signed-off-by: bui duc phuc Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260916050020.14575-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-clk.c | 31 +++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c index 7fe9d01006d515..1132c924158d7d 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c @@ -700,17 +700,36 @@ int mt8189_mck_disable(struct mtk_base_afe *afe, int mck_id) int mt8189_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) { struct mt8189_afe_private *afe_priv = afe->platform_priv; + int ret; /* bus clock for AFE internal access, like AFE SRAM */ - mt8189_afe_enable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS]); - mt8189_afe_set_clk_parent(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS], - afe_priv->clk[MT8189_CLK_TOP_CLK26M]); + ret = mt8189_afe_enable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS]); + if (ret) + return ret; + + ret = mt8189_afe_set_clk_parent(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS], + afe_priv->clk[MT8189_CLK_TOP_CLK26M]); + if (ret) + goto err_disable_audiointbus_clk; + /* enable audio clock source */ - mt8189_afe_enable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H]); - mt8189_afe_set_clk_parent(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H], - afe_priv->clk[MT8189_CLK_TOP_CLK26M]); + ret = mt8189_afe_enable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H]); + if (ret) + goto err_disable_audiointbus_clk; + + ret = mt8189_afe_set_clk_parent(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H], + afe_priv->clk[MT8189_CLK_TOP_CLK26M]); + if (ret) + goto err_disable_audio_h_clk; return 0; + +err_disable_audio_h_clk: + mt8189_afe_disable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H]); +err_disable_audiointbus_clk: + mt8189_afe_disable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS]); + + return ret; } int mt8189_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) From ea8c96a05da7b7ba6234a225209ac0c2dcc57e41 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:12 +0700 Subject: [PATCH 0685/1417] ASoC: mediatek: mt8189: Use dev_err_probe() for clock errors Use dev_err_probe() when obtaining clocks to avoid redundant error messages, particularly for probe deferral. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-clk.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c index 1132c924158d7d..594c214fa69762 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c @@ -793,10 +793,9 @@ int mt8189_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT8189_CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "devm_clk_get %s fail\n", aud_clks[i]); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } ret = mt8189_afe_disable_apll(afe); From 483e827b005f122755785a3b6094e2bc19eb7685 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:13 +0700 Subject: [PATCH 0686/1417] ASoC: mediatek: mt8189: Propagate runtime resume errors mt8189_afe_runtime_resume() currently ignores errors from regmap operations and mt8189_afe_enable_main_clock(). Propagate these errors and clean up the state before returning the error. Fixes: 7eb153585598 ("ASoC: mediatek: mt8189: add platform driver") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-pcm.c | 32 ++++++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c index 77cf2b604f6ce8..39cc2804ad4c97 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c @@ -2332,20 +2332,40 @@ static int mt8189_afe_runtime_resume(struct device *dev) } regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) + goto err_set_cache_only; /* set audio 26M request */ - regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x1); - regmap_update_bits(afe->regmap, AFE_CBIP_CFG0, 0x1, 0x1); + ret = regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x1); + if (ret) + goto err_set_cache_only; + + ret = regmap_update_bits(afe->regmap, AFE_CBIP_CFG0, 0x1, 0x1); + if (ret) + goto err_clear_26m_req; /* force cpu use 8_24 format when writing 32bit data */ - regmap_update_bits(afe->regmap, AFE_MEMIF_CON0, - CPU_HD_ALIGN_MASK_SFT, 0 << CPU_HD_ALIGN_SFT); + ret = regmap_update_bits(afe->regmap, AFE_MEMIF_CON0, + CPU_HD_ALIGN_MASK_SFT, 0 << CPU_HD_ALIGN_SFT); + if (ret) + goto err_clear_26m_req; /* enable AFE */ - mt8189_afe_enable_main_clock(afe); + ret = mt8189_afe_enable_main_clock(afe); + if (ret) + goto err_clear_26m_req; return 0; + +err_clear_26m_req: + regmap_update_bits(afe->regmap, + AFE_SPM_CONTROL_REQ, 0x1, 0x0); +err_set_cache_only: + regcache_cache_only(afe->regmap, true); + mt8189_afe_disable_reg_rw_clk(afe); + + return ret; } static int mt8189_afe_component_probe(struct snd_soc_component *component) From e9e9e514eeef7a5c6121ece355f8ea2701e1613f Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:14 +0700 Subject: [PATCH 0687/1417] ASoC: mediatek: mt8189: Remove redundant error message The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() and dev_err_probe() call is redundant and can be removed. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-afe-pcm.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c index 39cc2804ad4c97..11dba86c3e81d2 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c @@ -2486,13 +2486,12 @@ static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) afe->base_addr = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(afe->base_addr)) - return dev_err_probe(dev, PTR_ERR(afe->base_addr), - "AFE base_addr not found\n"); + return PTR_ERR(afe->base_addr); /* init audio related clock */ ret = mt8189_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "init clock error.\n"); + return ret; /* init memif */ /* IPM2.0 no need banding */ @@ -2526,13 +2525,13 @@ static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); if (irq_id < 0) - return dev_err_probe(dev, irq_id, "no irq found"); + return irq_id; ret = devm_request_irq(dev, irq_id, mt8189_afe_irq_handler, IRQF_TRIGGER_NONE, "Afe_ISR_Handle", afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); @@ -2601,10 +2600,8 @@ static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) &mt8189_afe_component, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_err(dev, "afe component err: %d\n", ret); + if (ret) return ret; - } return 0; From 6347f276da8c6c3e6e8262b30b9cd14dfbc48248 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:15 +0700 Subject: [PATCH 0688/1417] ASoC: mediatek: mt8189: Propagate APLL errors mtk_apll_event() currently ignores errors returned by the APLL enable functions. Propagate these errors. Fixes: 7eb153585598 ("ASoC: mediatek: mt8189: add platform driver") Signed-off-by: bui duc phuc Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260916050020.14575-9-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-dai-i2s.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c index 94c49a662e2d53..4826ee250d7034 100644 --- a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c +++ b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c @@ -485,6 +485,7 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", __func__, w->name, event); @@ -492,9 +493,11 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: if (strcmp(w->name, APLL1_W_NAME) == 0) - mt8189_apll1_enable(afe); + ret = mt8189_apll1_enable(afe); else - mt8189_apll2_enable(afe); + ret = mt8189_apll2_enable(afe); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: if (strcmp(w->name, APLL1_W_NAME) == 0) From 65b9d4a783cc266d057a64c1f04f690e9abb1bd3 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:16 +0700 Subject: [PATCH 0689/1417] ASoC: mediatek: mt8189: Propagate MCLK errors mtk_mclk_en_event() currently ignores errors returned by the MCK enable and disable functions. Propagate these errors. Fixes: 7eb153585598 ("ASoC: mediatek: mt8189: add platform driver") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-10-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-dai-i2s.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c index 4826ee250d7034..8cd5ddf62d4675 100644 --- a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c +++ b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c @@ -519,6 +519,7 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mtk_afe_i2s_priv *i2s_priv; + int ret; dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", __func__, w->name, event); @@ -529,17 +530,18 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8189_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); + ret = mt8189_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); break; case SND_SOC_DAPM_POST_PMD: i2s_priv->mclk_rate = 0; - mt8189_mck_disable(afe, i2s_priv->mclk_id); + ret = mt8189_mck_disable(afe, i2s_priv->mclk_id); break; default: + ret = 0; break; } - return 0; + return ret; } static const struct snd_soc_dapm_widget mtk_dai_i2s_widgets[] = { From 9c73b5a43c99f905ebeab4744a8ba0b28232b189 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:17 +0700 Subject: [PATCH 0690/1417] ASoC: mediatek: mt8189: Validate sysclk frequency A zero frequency causes a modulo-by-zero error when validating the I2S clock frequency. Reject it before performing the validation. Fixes: 7eb153585598 ("ASoC: mediatek: mt8189: add platform driver") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-11-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-dai-i2s.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c index 8cd5ddf62d4675..c9e80ef42f6088 100644 --- a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c +++ b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c @@ -1287,6 +1287,9 @@ static int mtk_dai_i2s_set_sysclk(struct snd_soc_dai *dai, dir != SND_SOC_CLOCK_OUT) return -EINVAL; + if (!freq) + return -EINVAL; + i2s_priv = afe_priv->dai_priv[dai->id]; if (!i2s_priv) return -EINVAL; From 242d0280eabba05c085a51ad0a9c9a7a3afa7b03 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:18 +0700 Subject: [PATCH 0691/1417] ASoC: mediatek: mt8189: Propagate TDM clock errors mtk_tdm_bck_en_event() and mtk_tdm_mck_en_event() currently ignore errors returned by the MCK enable and disable functions. Propagate these errors. Fixes: 9f202872ba04 ("ASoC: mediatek: mt8189: support TDM in platform driver") Signed-off-by: bui duc phuc Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260916050020.14575-12-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-dai-tdm.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c b/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c index 5d68a55ccc45cd..9aebd0320614e7 100644 --- a/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c +++ b/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c @@ -261,22 +261,24 @@ static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, struct mt8189_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d, bck: %d\n", w->name, event, dai_id, tdm_priv->bck_rate); switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8189_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); + ret = mt8189_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); break; case SND_SOC_DAPM_POST_PMD: - mt8189_mck_disable(afe, tdm_priv->bck_id); + ret = mt8189_mck_disable(afe, tdm_priv->bck_id); break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, @@ -288,23 +290,25 @@ static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, struct mt8189_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d, mclk %d\n", w->name, event, dai_id, tdm_priv->mclk_rate); switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8189_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); + ret = mt8189_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); break; case SND_SOC_DAPM_POST_PMD: tdm_priv->mclk_rate = 0; - mt8189_mck_disable(afe, tdm_priv->mclk_id); + ret = mt8189_mck_disable(afe, tdm_priv->mclk_id); break; default: + ret = 0; break; } - return 0; + return ret; } static const struct snd_soc_dapm_widget mtk_dai_tdm_widgets[] = { From 197ec30e1b608ade5d2eb9271d01c59480a6f9a1 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Wed, 16 Sep 2026 12:00:19 +0700 Subject: [PATCH 0692/1417] ASoC: mediatek: mt8189: Validate TDM MCLK frequency mtk_dai_tdm_cal_mclk() accepts zero or negative frequencies. Reject invalid frequencies before configuring the TDM clock. Fixes: 9f202872ba04 ("ASoC: mediatek: mt8189: support TDM in platform driver") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260916050020.14575-13-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8189/mt8189-dai-tdm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c b/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c index 9aebd0320614e7..e113c52fe06a96 100644 --- a/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c +++ b/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c @@ -383,6 +383,9 @@ static int mtk_dai_tdm_cal_mclk(struct mtk_base_afe *afe, int apll; int apll_rate; + if (freq <= 0) + return -EINVAL; + apll = mt8189_get_apll_by_rate(afe, freq); apll_rate = mt8189_get_apll_rate(afe, apll); From 9ca4ba24259183ce15665be86b2956cd896c4687 Mon Sep 17 00:00:00 2001 From: James Clark Date: Tue, 15 Sep 2026 11:58:17 +0700 Subject: [PATCH 0693/1417] net: macb: fix ordering around PTP timestamp read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PTP_SYS_OFFSET_EXTENDED returns system timestamps that do not correctly bracket the PHC register read on MACB/GEM. On a Raspberry Pi 5, the returned interval can be as short as 37 ns, while an ordered register read takes approximately 1 us. This biases the midpoint used by phc2sys, causing CLOCK_REALTIME to run approximately 0.5 us ahead when synchronized to the PHC. gem_tsu_get_time() reads the nanoseconds register using the driver's relaxed MMIO accessor. On weakly ordered systems, the subsequent system timestamp can be taken before the register read completes. The internal smp_rmb() in the pre-timestamp path also does not guarantee ordering against the subsequent MMIO read. Add rmb() before and after the bracketed nanoseconds read in both the normal and seconds rollover paths so the system timestamps bracket the PHC read. Adding the post-read barrier increases the minimum interval on the same Raspberry Pi 5 to approximately 1 us. Fixes: e51bb5c2784c ("net: macb: ptp: Switch to gettimex64() interface") Tested-by: Nicolai Buchwitz # Raspberry Pi CM5, min bracket 37 ns -> 981 ns Reviewed-by: Nicolai Buchwitz Reviewed-by: Théo Lebrun Assisted-by: LLM Signed-off-by: James Clark Link: https://patch.msgid.link/20260915045823.76100-1-jjc@jclark.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cadence/macb_ptp.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/ethernet/cadence/macb_ptp.c b/drivers/net/ethernet/cadence/macb_ptp.c index 6d916638998869..14ae57fa00cba7 100644 --- a/drivers/net/ethernet/cadence/macb_ptp.c +++ b/drivers/net/ethernet/cadence/macb_ptp.c @@ -50,7 +50,12 @@ static int gem_tsu_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts, spin_lock_irqsave(&bp->tsu_clk_lock, flags); ptp_read_system_prets(sts); + /* explicit barriers are needed because gem_readl() is relaxed */ + if (sts) + rmb(); first = gem_readl(bp, TN); + if (sts) + rmb(); ptp_read_system_postts(sts); secl = gem_readl(bp, TSL); sech = gem_readl(bp, TSH); @@ -62,7 +67,11 @@ static int gem_tsu_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts, * (assume all done within 1s) */ ptp_read_system_prets(sts); + if (sts) + rmb(); ts->tv_nsec = gem_readl(bp, TN); + if (sts) + rmb(); ptp_read_system_postts(sts); secl = gem_readl(bp, TSL); sech = gem_readl(bp, TSH); From 95b2e0361c88753afa030c10698be0a1a5b67650 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Thu, 17 Sep 2026 12:56:43 +0200 Subject: [PATCH 0694/1417] ALSA: seq: Serialize compat port-info ioctls The native sequencer ioctl path serializes handler calls with client->ioctl_mutex, but the translated port-info compat path invokes the same handlers through snd_seq_kernel_client_ctl() without taking that mutex. This lets concurrent compat CREATE_PORT requests pass the port-count check before any request reaches the serialized insertion. The computed integer port index can then exceed the address field range and wrap to an existing index. Subsequent subscriber teardown can resolve the duplicate address to the wrong port and access a freed subscriber. Take ioctl_mutex while dispatching converted port-info requests, matching the native ioctl path. All translated port-info commands share this helper, so their accesses to the client port state are serialized as well. Fixes: b3defb791b26 ("ALSA: seq: Make ioctls race-free") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol gpt-6-astra Signed-off-by: Kyle Zeng Signed-off-by: Bruno Produit Link: https://patch.msgid.link/20260917105643.90102-1-bruno.produit@trailofbits.com Signed-off-by: Takashi Iwai --- sound/core/seq/seq_compat.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c index 22679dca9aae32..80110501da6ff5 100644 --- a/sound/core/seq/seq_compat.c +++ b/sound/core/seq/seq_compat.c @@ -44,7 +44,9 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned return -EFAULT; data->kernel = NULL; - err = snd_seq_kernel_client_ctl(client->number, cmd, data); + scoped_guard(mutex, &client->ioctl_mutex) { + err = snd_seq_kernel_client_ctl(client->number, cmd, data); + } if (err < 0) return err; From 4d4bc656580bcc448f8e6d6b3b3a903120eb29bb Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 14 Sep 2026 12:58:06 -0700 Subject: [PATCH 0695/1417] docs: sound: hdspm: clean up driver doc. Do some overall document cleanups: - add driver name for clarification - fix some punctuation issues - correct some capitalization - correct some grammar - convert a few words from German to English - eliminate double use of "note:" - unindent Precise Pointer bullets so that they are not part of the Hint - drop precise_ptr documentation since it has been modified to be "always on" Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260914195806.1544938-1-rdunlap@infradead.org Signed-off-by: Takashi Iwai --- Documentation/sound/cards/hdspm.rst | 109 ++++++++++------------------ 1 file changed, 40 insertions(+), 69 deletions(-) diff --git a/Documentation/sound/cards/hdspm.rst b/Documentation/sound/cards/hdspm.rst index 5373e51ed076f7..deba922679422f 100644 --- a/Documentation/sound/cards/hdspm.rst +++ b/Documentation/sound/cards/hdspm.rst @@ -6,9 +6,11 @@ Software Interface ALSA-DSP MADI Driver 2004 - winfried ritsch +driver name - snd-hdspm + Full functionality has been added to the driver. Since some of -the Controls and startup-options are ALSA-Standard and only the +the Controls and startup options are ALSA-Standard, only the special Controls are described and discussed below. @@ -18,7 +20,7 @@ Hardware functionality Audio transmission ------------------ -* number of channels -- depends on transmission mode +* number of channels -- depends on transmission mode The number of channels chosen is from 1..Nmax. The reason to use for a lower number of channels is only resource allocation, @@ -29,29 +31,29 @@ Audio transmission * Single Speed -- 1..64 channels .. note:: - (Note: Choosing the 56channel mode for transmission or as + Choosing the 56-channel mode for transmission or as receiver, only 56 are transmitted/received over the MADI, but all 64 channels are available for the mixer, so channel count - for the driver) + for the driver. * Double Speed -- 1..32 channels .. note:: - Note: Choosing the 56-channel mode for - transmission/receive-mode , only 28 are transmitted/received + Choosing the 56-channel mode for + transmission/receive-mode, only 28 are transmitted/received over the MADI, but all 32 channels are available for the mixer, - so channel count for the driver + so channel count for the driver. * Quad Speed -- 1..16 channels .. note:: Choosing the 56-channel mode for - transmission/receive-mode , only 14 are transmitted/received + transmission/receive-mode, only 14 are transmitted/received over the MADI, but all 16 channels are available for the mixer, - so channel count for the driver + so channel count for the driver. -* Format -- signed 32 Bit Little Endian (SNDRV_PCM_FMTBIT_S32_LE) +* Format -- signed 32-bit Little Endian (SNDRV_PCM_FMTBIT_S32_LE) * Sample Rates -- @@ -67,30 +69,6 @@ Audio transmission * fragments -- 2 -* Hardware-pointer -- 2 Modi - - - The Card supports the readout of the actual Buffer-pointer, - where DMA reads/writes. Since of the bulk mode of PCI it is only - 64 Byte accurate. SO it is not really usable for the - ALSA-mid-level functions (here the buffer-ID gives a better - result), but if MMAP is used by the application. Therefore it - can be configured at load-time with the parameter - precise-pointer. - - -.. hint:: - (Hint: Experimenting I found that the pointer is maximum 64 to - large never to small. So if you subtract 64 you always have a - safe pointer for writing, which is used on this mode inside - ALSA. In theory now you can get now a latency as low as 16 - Samples, which is a quarter of the interrupt possibilities.) - - * Precise Pointer -- off - interrupt used for pointer-calculation - - * Precise Pointer -- on - hardware pointer used. Controller ---------- @@ -104,7 +82,7 @@ hwdep-interface. Also all 128+256 Peak and RMS-Meter can be accessed via the hwdep-interface. Since it could be a performance problem always copying and converting Peak and RMS-Levels even if you just need -one, I decided to export the hardware structure, so that of +one, I decided to export the hardware structure, so that if needed some driver-guru can implement a memory-mapping of mixer or peak-meters over ioctl, or also to do only copying and no conversion. A test-application shows the usage of the controller. @@ -112,9 +90,9 @@ conversion. A test-application shows the usage of the controller. * Latency Controls --- not implemented !!! .. note:: - Note: Within the windows-driver the latency is accessible of a - control-panel, but buffer-sizes are controlled with ALSA from - hwparams-calls and should not be changed in run-state, I did not + Within the Windows driver the latency is accessible via + Control Panel, but buffer sizes are controlled with ALSA from + hwparams calls and should not be changed in run-state. I did not implement it here. @@ -127,12 +105,12 @@ conversion. A test-application shows the usage of the controller. * Values -- "Master" "Slave" .. note:: - !!!! This is a hardware-function but is in conflict with the - Clock-source controller, which is a kind of ALSA-standard. I + !!!! This is a hardware function but is in conflict with the + Clock-source controller, which is a kind of ALSA-standard. It makes sense to set the card to a special mode (master at some - frequency or slave), since even not using an Audio-application + frequency or slave), since even not using an Audio application a studio should have working synchronisations setup. So use - Clock-source-controller instead !!!! + Clock-source controller instead !!!! * Clock Source @@ -145,11 +123,11 @@ conversion. A test-application shows the usage of the controller. "Internal 96.0 kHz" Choose between Master at a specific Frequency and so also the - Speed-mode or Slave (Autosync). Also see "Preferred Sync Ref" + Speed-mode or Slave (Autosync). Also see "Preferred Sync Ref". .. warning:: - !!!! This is no pure hardware function but was implemented by - ALSA by some ALSA-drivers before, so I use it also. !!! + !!!! This is not a pure hardware function but was implemented by + ALSA by some ALSA drivers before, so I use it also. !!! * Preferred Sync Ref @@ -165,7 +143,7 @@ conversion. A test-application shows the usage of the controller. chosen. If it is not available another is used if possible. .. note:: - Note: Since MADI has a much higher bit-rate than word-clock, the + Since MADI has a much higher bit-rate than word-clock, the card should synchronise better in MADI Mode. But since the RME-PLL is very good, there are almost no problems with word-clock too. I never found a difference. @@ -179,12 +157,12 @@ conversion. A test-application shows the usage of the controller. * Values -- 0 1 - Using 64-channel-modus (1) or 56-channel-modus for + Using 64-channel mode (1) or 56-channel mode for MADI-transmission (0). .. note:: - Note: This control is for output only. Input-mode is detected + This control is for output only. Input mode is detected automatically from hardware sending MADI. @@ -200,7 +178,7 @@ conversion. A test-application shows the usage of the controller. Don't use to lower 5 Audio-bits on AES as additional Bits. -* Safe Mode oder Auto Input +* Safe Mode or Auto Input * Name -- "Safe Mode" @@ -222,7 +200,7 @@ conversion. A test-application shows the usage of the controller. * Values -- optical coaxial - Choosing the Input, optical or coaxial. If Safe-mode is active, + Choosing the Input, optical or coaxial. If Safe mode is active, this is the preferred Input. Mixer @@ -240,7 +218,7 @@ Mixer Here as a first value the channel-index is taken to get/set the corresponding mixer channel, where 0-63 are the input to output fader and 64-127 the playback to outputs fader. Value 0 - is channel muted 0 and 32768 an amplification of 1. + is channel muted 0 and 32768 an amplification of 1. * Chn 1-64 @@ -250,14 +228,14 @@ Mixer * Line Out - * Name -- "Line Out" + * Name -- "Line Out" * Access -- Read Write * Values -- 0 1 Switching on and off the analog out, which has nothing to do - with mixing or routing. the analog outs reflects channel 63,64. + with mixing or routing. The analog outs reflects channel 63,64. Information (only read access) @@ -280,7 +258,7 @@ Information (only read access) Should be "Autosync Rate", but Name used is - ALSA-Scheme. External Sample frequency liked used on Autosync is + ALSA-Scheme. External Sample frequency like used on Autosync is reported. @@ -315,19 +293,19 @@ Information (only read access) Sync-Reference is either "WordClock", "MADI" or none. -* RX 64ch --- noch nicht implementiert +* RX 64ch --- not yet implemented - MADI-Receiver is in 64 channel mode oder 56 channel mode. + MADI-Receiver is in 64-channel mode or 56-channel mode. -* AB_inp --- not tested +* AB_inp --- not tested Used input for Auto-Input. * actual Buffer Position --- not implemented - !!! this is a ALSA internal function, so no control is used !!! + !!! This is an ALSA internal function, so no control is used !!! @@ -352,22 +330,15 @@ Calling Parameter note: ALSA-standard -* precise_ptr int array (min = 1, max = 8) - - Enable precise pointer, or disable. - -.. note:: - note: Use only when the application supports this (which is a special case). - * line_outs_monitor int array (min = 1, max = 8) Send playback streams to analog outs by default. .. note:: - note: each playback channel is mixed to the same numbered output + Each playback channel is mixed to the same numbered output channel (routed). This is against the ALSA-convention, where all channels have to be muted on after loading the driver, but was - used before on other cards, so i historically use it again) + used before on other cards, so I historically use it again. @@ -375,5 +346,5 @@ Calling Parameter Enable Analog Out on Channel 63/64 by default. -.. note :: - note: here the analog output is enabled (but not routed). +.. note:: + Here the analog output is enabled (but not routed). From 14cb1e7702e5cb3c58888f6aed498381a73927d2 Mon Sep 17 00:00:00 2001 From: Dmitriy Okunev Date: Mon, 14 Sep 2026 12:15:57 +0300 Subject: [PATCH 0696/1417] net: mvpp2: prevent buffer overflow in page_pool allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per‑processor buffering scheme is supported only if the number of pools (nrxqs * 2) does not exceed MVPP2_BM_MAX_POOLS (8). This is already checked in mvpp2_probe() during the initial activation of percpu_pools. However, mvpp2_change_mtu() may later call mvpp2_bm_switch_buffers(priv, true) without this check, which can lead to an out-of-bounds access in the priv->page_pool array in mvpp2_bm_init(). The array is sized to hold MVPP2_PORT_MAX_RXQ entries, and mvpp2_get_nrxqs() may return exactly that value. The per-CPU scheme then doubles it to nrxqs * 2, exceeding the array bounds. Check that the hardware version is MVPP22 or newer and that the number of pools (nrxqs * 2) does not exceed MVPP2_BM_MAX_POOLS before switching to per-CPU mode. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 7d04b0b13b11 ("mvpp2: percpu buffers") Signed-off-by: Dmitriy Okunev Link: https://patch.msgid.link/20260914091557.71769-1-dokunevdmitriy@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c index ccc24a1301f22d..848ee655c6ea66 100644 --- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c +++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c @@ -5086,7 +5086,8 @@ static int mvpp2_change_mtu(struct net_device *dev, int mtu) netdev_warn(dev, "mtu %d too high, switching to shared buffers", mtu); mvpp2_bm_switch_buffers(priv, false); } - } else { + } else if (priv->hw_version >= MVPP22 && + mvpp2_get_nrxqs(priv) * 2 <= MVPP2_BM_MAX_POOLS) { bool jumbo = false; int i; From d798162eb364df2e77a56fdbe5bae54440152d3b Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 15 Sep 2026 14:30:47 -0700 Subject: [PATCH 0697/1417] dpll: reject a reference sync pin which is not on the pin's dpll dpll_pin_ref_sync_state_set() resolves the partner's driver private data with dpll_pin_on_dpll_priv() and passes the result to ref_sync_get() and ref_sync_set() without looking at it. The helper returns NULL when the partner holds no ref on that dpll. Of the two drivers implementing the feature only zl3073x dereferences the pointer (sync_pin->id); ice ignores it, so ice cannot fault here. The NULL is a teardown race, not a steady state - zl3073x registers every input pin with every channel, so the partner is normally present on the dpll the base pin resolves to. zl3073x_dev_stop() unregisters pins one at a time, taking and dropping dpll_lock for each, and between the partner's turn and the base pin's the partner is out of that dpll's pin_refs while still registered with the channels not yet torn down, so dpll_pin_available() keeps passing. That path is not only driver removal: devlink reload and devlink dev flash both run zl3073x_dev_stop(). Reproduced by holding that state open with a mock dpll device, which is where the frame name comes from: BUG: kernel NULL pointer dereference, address: 0000000000000000 Oops: Oops: 0000 [#1] SMP NOPTI RIP: 0010:mock_ref_sync_get+0x5/0x30 Call Trace: dpll_pin_ref_sync_set+0x19f/0x4a0 dpll_nl_pin_set_doit+0x17d/0x840 genl_family_rcv_msg_doit+0xd6/0x130 genl_rcv_msg+0x181/0x2b0 netlink_rcv_skb+0x55/0x100 genl_rcv+0x23/0x30 netlink_unicast+0x24d/0x370 netlink_sendmsg+0x1e2/0x420 __sys_sendto+0x1db/0x1f0 __x64_sys_sendto+0x1f/0x30 do_syscall_64+0xe1/0x490 Commit d2e914a4a0d0 ("dpll: fix NULL pointer dereference in dpll_msg_add_pin_ref_sync()") added the same guard to the read side, which the kernel walks into by itself because the delete notification is emitted from inside the unregister; the write side needs a pin-set to land in the window and was left alone. Test the priv rather than look up pin_refs directly, so that the two halves key off the same condition. Fixes: 58256a26bfb3 ("dpll: add reference sync get/set") Signed-off-by: Jakub Kicinski Reviewed-by: Ivan Vecera Link: https://patch.msgid.link/20260915213047.1352286-1-kuba@kernel.org Signed-off-by: Paolo Abeni --- drivers/dpll/dpll_netlink.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 523d76a5fd493a..45365214fbefa2 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -1202,6 +1202,7 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, const enum dpll_pin_state state, struct netlink_ext_ack *extack) { + void *pin_priv, *ref_sync_pin_priv; const struct dpll_pin_ops *ops; enum dpll_pin_state old_state; struct dpll_pin *ref_sync_pin; @@ -1230,9 +1231,15 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, return -EOPNOTSUPP; } dpll = ref->dpll; - ret = ops->ref_sync_get(pin, dpll_pin_on_dpll_priv(dpll, pin), - ref_sync_pin, - dpll_pin_on_dpll_priv(dpll, ref_sync_pin), + pin_priv = dpll_pin_on_dpll_priv(dpll, pin); + ref_sync_pin_priv = dpll_pin_on_dpll_priv(dpll, ref_sync_pin); + /* Pin may have been unregistered from this dpll already */ + if (!ref_sync_pin_priv) { + NL_SET_ERR_MSG(extack, + "reference sync pin not registered with the dpll"); + return -ENODEV; + } + ret = ops->ref_sync_get(pin, pin_priv, ref_sync_pin, ref_sync_pin_priv, &old_state, extack); if (ret) { NL_SET_ERR_MSG(extack, "unable to get old reference sync state"); @@ -1241,9 +1248,7 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, if (state == old_state) return 0; - ret = ops->ref_sync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - ref_sync_pin, - dpll_pin_on_dpll_priv(dpll, ref_sync_pin), + ret = ops->ref_sync_set(pin, pin_priv, ref_sync_pin, ref_sync_pin_priv, state, extack); if (ret) { NL_SET_ERR_MSG_FMT(extack, From f81e6c3fb06327bc49cdd6e559845293ba06a704 Mon Sep 17 00:00:00 2001 From: Inbal Schussheim Date: Mon, 14 Sep 2026 12:04:07 +0300 Subject: [PATCH 0698/1417] tcp: exclude old ACKs from tcp fast path Exclude old ACKs before SND.UNA from the tcp fast path as well as ACKs after SND.NXT. Such ACKs will fall through to the slow path, where tcp_ack() performs the appropriate validation and challenge ACK handling according to RFC5961 and Commit 3d501dd326fb1c7 ("tcp: do not accept ACK of bytes we never sent"). This prevents old ACKs from being accepted or modifying connection state as part of the fast path before appropriate ACK validation is applied. In particular, this prevents payload carried by a segment with an excessively old ACK from advancing RCV.NXT before the ACK is rejected. Fixes: 31770e34e43d ("tcp: Revert "tcp: remove header prediction"") Reported-by: Amit Klein Reported-by: Tamir Shahar Reported-by: Inbal Schussheim Suggested-by: Eric Dumazet Cc: stable@vger.kernel.org Signed-off-by: Inbal Schussheim Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260914090408.1435080-2-inbal.lipshtat@mail.huji.ac.il Signed-off-by: Paolo Abeni --- net/ipv4/tcp_input.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 0f60a1dbf92746..92bc60716f33d8 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -6490,6 +6490,7 @@ static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb, * or pure receivers (this means either the sequence number or the ack * value must stay constant) * - Unexpected TCP option. + * - ACK sequence number is outside [SND.UNA, SND.NXT]. * * When these conditions are not satisfied it drops into a standard * receive procedure patterned after RFC793 to handle all cases. @@ -6539,7 +6540,7 @@ void tcp_rcv_established(struct sock *sk, struct sk_buff *skb) if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags && TCP_SKB_CB(skb)->seq == tp->rcv_nxt && - !after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) { + between(TCP_SKB_CB(skb)->ack_seq, tp->snd_una, tp->snd_nxt)) { int tcp_header_len = tp->tcp_header_len; s32 delta = 0; int flag = 0; From d841cd7513f3d48018175ecb1fb972cfd3c3c10b Mon Sep 17 00:00:00 2001 From: Inbal Schussheim Date: Mon, 14 Sep 2026 12:04:08 +0300 Subject: [PATCH 0699/1417] selftests: net: packetdrill: test exclusion of old ACK from TCP fast path Add a packetdrill test for an in-sequence data segment carrying an excessively old ACK. Verify that the segment falls through from the TCP fast path to the slow path, where the existing ACK validation rejects it and sends a challenge ACK. The payload is not accepted and RCV.NXT remains unchanged. Based on the reproducer from Commit 3d501dd326fb ("tcp: do not accept ACK of bytes we never sent"). Signed-off-by: Inbal Schussheim Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260914090408.1435080-3-inbal.lipshtat@mail.huji.ac.il Signed-off-by: Paolo Abeni --- .../tcp_rfc5961_reject-old-ack.pkt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rfc5961_reject-old-ack.pkt diff --git a/tools/testing/selftests/net/packetdrill/tcp_rfc5961_reject-old-ack.pkt b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_reject-old-ack.pkt new file mode 100644 index 00000000000000..32dd9de1d3662e --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_reject-old-ack.pkt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 + +`./defaults.sh +sysctl -q net.ipv4.tcp_invalid_ratelimit=0 +` + +// Test rejection of data segments carrying excessively old ACKs + +0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 ++0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 ++0 bind(3, ..., ...) = 0 ++0 listen(3, 1024) = 0 + +// ---------------- Handshake ------------------- // ++0 < S 0:0(0) win 65535 ++0 > S. 0:0(0) ack 1 <...> ++0 < . 1:1(0) ack 1 win 65535 ++0 accept(3, ..., ...) = 4 + +// Populate receive memory so the following segment can use +// header prediction. ++0 < P. 1:501(500) ack 1 win 65535 ++0 > . 1:1(0) ack 501 + +// Send an in-sequence data segment carrying an excessively old ACK. ++0 < P. 501:1501(1000) ack 2794967397 win 65535 + +// Challenge ACK; RCV.NXT must remain 501. ++0 > . 1:1(0) ack 501 From a5117e1eccac6ee3bd4aed7cacf8ebcb6b3eb309 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 15 Sep 2026 13:04:23 +0000 Subject: [PATCH 0700/1417] net: skbuff: do not leave stale header offsets after pskb_carve() pskb_carve_inside_header() and pskb_carve_inside_nonlinear() remove the first bytes of a packet and reallocate skb->head. All the headers that were present before the operation are gone, but both functions call skb_headers_offset_update(skb, 0), which is a no-op : skb->mac_header, skb->network_header, skb->transport_header and skb->csum_start keep their old values and now describe bytes which are no longer there. Both helpers size the new head from the old skb_end_offset(), so the stale offsets still land inside the new allocation. They point past skb_tail_pointer() though, to bytes that were never initialized. pskb_carve_inside_nonlinear() is the worst case, because it leaves a zombie skb with an empty linear part (skb->data == skb_tail_pointer(skb), skb_headlen(skb) == 0), while skb_mac_header_was_set() is still true and skb->mac_header is way ahead of skb->data. The only user of pskb_extract() is rds_tcp_data_recv(), and the carved skb is queued on tinc->ti_skb_list. When the RDS incoming message is released, rds_tcp_inc_free() calls skb_queue_purge(), which frees the skbs with SKB_DROP_REASON_QUEUE_PURGE. This is visible from drop_monitor, which then tries to pull back to the (bogus) mac header : skbuff: __skb_pull(len=234) skb len=6968 data_len=6968 headroom=0 headlen=0 tailroom=0 end-tail=384 mac=(234,14) mac_len=14 net=(248,40) trans=288 shinfo(txflags=0 nr_frags=1 gso(size=1428 type=16 segs=5)) csum(0x100120 start=288 offset=16 ip_summed=3 complete_sw=0 valid=1 level=0) hash(0x7b446c6c sw=0 l4=1) proto=0x86dd pkttype=0 iif=60 kernel BUG at ./include/linux/skbuff.h:2847! Add skb_carve_reset_headers() to mark the mac and transport headers as not set, reset the network header, clear skb->mac_len, and drop a now meaningless CHECKSUM_PARTIAL (csum_start no longer describes anything). Invalidate the inner offsets as well. Unlike mac_header and transport_header they have no "unset" sentinel, so a leftover non-zero value still looks like a real header. Zero skb->inner_mac_header, skb->inner_network_header, skb->inner_transport_header, skb->inner_protocol and skb->encapsulation, so that all the header state is invalidated in one place. v2: fixed an inaccurate changelog. The stale offsets stay inside the new skb->head, which is never smaller than the old one, they simply point past skb_tail_pointer() to bytes that are gone. Thanks to Xuanqiang Luo for insisting on this. Also invalidate the inner header state, as suggested by the netdev AI review : https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260911114922.621937-1-edumazet%40google.com Fixes: 6fa01ccd8830 ("skbuff: Add pskb_extract() helper function") Reported-by: syzbot+586af68eb819833c2d91@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6aa3e9d3.f2639fcc.29487d.0028.GAE@google.com/ Cc: Xuanqiang Luo Cc: Allison Henderson Cc: rds-devel@oss.oracle.com Signed-off-by: Eric Dumazet Reviewed-by: Xuanqiang Luo Link: https://patch.msgid.link/20260915130423.3956471-1-edumazet@google.com Signed-off-by: Paolo Abeni --- net/core/skbuff.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index cc3b4b70288b4e..609f2c7f4a47ad 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -6832,6 +6832,34 @@ struct sk_buff *alloc_skb_with_frags(unsigned long header_len, } EXPORT_SYMBOL(alloc_skb_with_frags); +/* pskb_carve_inside_header() and pskb_carve_inside_nonlinear() + * remove the first bytes of a packet and reallocate skb->head. + * + * Whatever headers were present before the operation are gone, + * we must not leave stale offsets, otherwise users of this skb + * (skb_dump(), drop_monitor, taps, ...) would read or pull garbage. + */ +static void skb_carve_reset_headers(struct sk_buff *skb) +{ + skb_unset_mac_header(skb); + skb_unset_transport_header(skb); + skb_reset_network_header(skb); + skb->mac_len = 0; + + /* Inner offsets have no "unset" marker, zero them so that + * skb_inner_network_header_was_set() becomes false and no + * consumer mistakes them for a real (and long gone) header. + */ + skb->inner_mac_header = 0; + skb->inner_network_header = 0; + skb->inner_transport_header = 0; + skb->inner_protocol = 0; + skb->encapsulation = 0; + + if (skb->ip_summed == CHECKSUM_PARTIAL) + skb->ip_summed = CHECKSUM_NONE; +} + /* carve out the first off bytes from skb when off < headlen */ static int pskb_carve_inside_header(struct sk_buff *skb, const u32 off, const int headlen, gfp_t gfp_mask) @@ -6887,7 +6915,7 @@ static int pskb_carve_inside_header(struct sk_buff *skb, const u32 off, skb->head_frag = 0; skb_set_end_offset(skb, size); skb_set_tail_pointer(skb, skb_headlen(skb)); - skb_headers_offset_update(skb, 0); + skb_carve_reset_headers(skb); skb->cloned = 0; skb->hdr_len = 0; skb->nohdr = 0; @@ -7027,7 +7055,7 @@ static int pskb_carve_inside_nonlinear(struct sk_buff *skb, const u32 off, skb->data = data; skb_set_end_offset(skb, size); skb_reset_tail_pointer(skb); - skb_headers_offset_update(skb, 0); + skb_carve_reset_headers(skb); skb->cloned = 0; skb->hdr_len = 0; skb->nohdr = 0; From 1fca688e9443003e33cf30453e7a7560367656c9 Mon Sep 17 00:00:00 2001 From: shechenglong Date: Mon, 7 Sep 2026 11:51:47 +0800 Subject: [PATCH 0701/1417] drm/client: fix restore of partially initialized client I got a null-ptr-deref report when closing a DRM file descriptor: WARNING: drivers/gpu/drm/drm_atomic.c:2031 at __drm_atomic_helper_set_config+0x18e/0x1b0 [drm] Call Trace: drm_client_modeset_commit_atomic+0x16b/0x220 [drm] drm_client_modeset_commit_locked+0x56/0x160 [drm] drm_client_modeset_commit+0x21/0x40 [drm] __drm_fb_helper_restore_fbdev_mode_unlocked.part.0+0x7b/0x80 drm_fbdev_client_restore+0xe/0x20 [drm_client_lib] drm_client_dev_restore+0x9f/0xc0 [drm] drm_release+0xc5/0xe0 [drm] The warning is followed by a NULL pointer dereference: BUG: kernel NULL pointer dereference, address: 0000000000000008 RIP: __drm_fb_helper_restore_fbdev_mode_unlocked.part.0+0x41/0x80 [drm_kms_helper] Call Trace: drm_fbdev_client_restore+0xe/0x20 [drm_client_lib] drm_client_dev_restore+0x9f/0xc0 [drm] drm_release+0xc5/0xe0 [drm] __fput+0xdc/0x2b0 __x64_sys_close+0x39/0x80 do_syscall_64+0x8d/0x460 entry_SYSCALL_64_after_hwframe+0x76/0x7e drm_client_register() adds the DRM client to the device client list before invoking the initial hotplug callback. If the hotplug callback fails, the client remains registered. For the fbdev client, a failure during drm_fb_helper_initial_config() causes the partially initialized fbdev helper to be cleaned up. drm_fb_helper_fini() releases fb_helper->info and leaves it NULL. The fbdev client therefore remains registered even though there is no fully initialized framebuffer device. Later, when userspace closes the DRM file descriptor, drm_release() can invoke the restore callbacks of registered DRM clients: drm_release() drm_client_dev_restore() drm_fbdev_client_restore() drm_fb_helper_restore_fbdev_mode_unlocked() drm_fbdev_client_restore() currently restores the fbdev state unconditionally. For a partially initialized fbdev client this can submit an incomplete modeset state and subsequently access fbdev state which has not been initialized, resulting in the warning and NULL pointer dereference above. drm_fbdev_client_unregister() already uses fb_helper->info to distinguish a fully probed framebuffer device from a partially initialized client. Use the same condition in drm_fbdev_client_restore() and skip restore if no framebuffer device has been successfully initialized. Signed-off-by: shechenglong Reviewed-by: Thomas Zimmermann Fixes: 5d08c44e47b9 ("drm/fbdev: Add memory-agnostic fbdev client") Signed-off-by: Thomas Zimmermann Cc: # v6.13+ Link: https://patch.msgid.link/20260907035147.1339-1-shechenglong@xfusion.com --- drivers/gpu/drm/clients/drm_fbdev_client.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/clients/drm_fbdev_client.c b/drivers/gpu/drm/clients/drm_fbdev_client.c index 91d196a397cf2e..1c16bc1084c409 100644 --- a/drivers/gpu/drm/clients/drm_fbdev_client.c +++ b/drivers/gpu/drm/clients/drm_fbdev_client.c @@ -42,6 +42,14 @@ static int drm_fbdev_client_restore(struct drm_client_dev *client, bool force) { struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); + /* + * The client is registered before the initial fbdev probe. + * If probing failed, the client remains registered but there + * is no valid fbdev framebuffer to restore. + */ + if (!fb_helper->info || !fb_helper->fb) + return 0; + drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper, force); return 0; From 2b0f561f21b27c40c91ea4975268a06092bd7e9c Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Thu, 17 Sep 2026 15:05:57 +0200 Subject: [PATCH 0702/1417] mptcp: avoid unneeded actions on subflow reset Once in a blue moon, the mptcp receive path can recursively call mptcp_data_ready() via state change under unlucky error conditions, and then try to hold the data lock again. Break the recursion loop explicitly checking for the exceptional condition. Add a new flag instead of using an existing one like 'closing', to exit early in subflow_state_change(), and explicitly flush the RX queue at reset time. This avoids unneeded processing to check for available data -- calling get_mapping_status() and more on a dying subflow -- but also in error reporting and worker scheduling. Note that we must consume the currently peeked skb before invoking mptcp_dss_corruption to avoid consuming it again after the eventual reset has freed it. Fixes: e32d262c89e2 ("mptcp: handle consistently DSS corruption") Cc: stable@vger.kernel.org Reported-by: Xinyang Ge Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260917-net-mptcp-misc-fixes-7-3-rc4-v2-1-0cf5c72667c8@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 4 ++-- net/mptcp/protocol.h | 3 ++- net/mptcp/subflow.c | 11 +++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 8dc25ef1542c58..d9fc3be9d2db48 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -856,12 +856,12 @@ static bool __mptcp_move_skbs_from_subflow(struct mptcp_sock *msk, mptcp_dss_corruption(msk, ssk); } } else { + sk_eat_skb(ssk, skb); + if (unlikely(!fin)) { DEBUG_NET_WARN_ON_ONCE(1); mptcp_dss_corruption(msk, ssk); } - - sk_eat_skb(ssk, skb); } WRITE_ONCE(tp->copied_seq, seq); diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 2b4c2742647731..0384d6a023f9d8 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -585,7 +585,8 @@ struct mptcp_subflow_context { is_mptfo : 1, /* subflow is doing TFO */ close_event_done : 1, /* has done the post-closed part */ mpc_drop : 1, /* the MPC option has been dropped in a rtx */ - __unused : 9; + resetting : 1, /* subflow is resetting */ + __unused : 8; bool data_avail; bool scheduled; bool pm_listener; /* a listener managed by the kernel PM? */ diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index 01db7edce18a63..f0a6725d2c3762 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -438,6 +438,10 @@ void mptcp_subflow_reset(struct sock *ssk) /* must hold: tcp_done() could drop last reference on parent */ sock_hold(sk); + subflow->resetting = 1; + + /* No need to delay the actual close for to-be discarded data. */ + __skb_queue_purge(&ssk->sk_receive_queue); mptcp_send_active_reset_reason(ssk); tcp_done(ssk); if (!test_and_set_bit(MPTCP_WORK_CLOSE_SUBFLOW, &mptcp_sk(sk)->flags)) @@ -1883,6 +1887,13 @@ static void subflow_state_change(struct sock *sk) __subflow_state_change(sk); + /* Rx queue processing is unneeded, error reporting will take place at + * __mptcp_close_ssk() time and subflow reset can't happen in case of + * fallback: subflow_sched_work_if_closed() would be a no-op. + */ + if (subflow->resetting) + return; + /* as recvmsg() does not acquire the subflow socket for ssk selection * a fin packet carrying a DSS can be unnoticed if we don't trigger * the data available machinery here. From 42064de57fb83231fcc89663a94885f228a1ee53 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Thu, 17 Sep 2026 15:05:58 +0200 Subject: [PATCH 0703/1417] mptcp: close race between scheduler and state change The mptcp scheduler may race with subflow sockets state change: data transmission on the selected socket may fail and a later release could try to use mss_now reset to 0 for a divide operation. Address the issue by explicitly checking for the critical scenario. Fixes: c886d70286bf ("mptcp: do not queue data on closed subflows") Cc: stable@vger.kernel.org Reported-by: Shardul Bankar Reported-by: Xinyang Ge Closes: https://lore.kernel.org/20260525194828.1137119-1-shardul.b@mpiricsoftware.com Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260917-net-mptcp-misc-fixes-7-3-rc4-v2-2-0cf5c72667c8@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index d9fc3be9d2db48..577d0134b9ece8 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -1664,7 +1664,9 @@ struct sock *mptcp_subflow_get_send(struct mptcp_sock *msk) static void mptcp_push_release(struct sock *ssk, struct mptcp_sendmsg_info *info) { - tcp_push(ssk, 0, info->mss_now, tcp_sk(ssk)->nonagle, info->size_goal); + if (info->mss_now) + tcp_push(ssk, 0, info->mss_now, tcp_sk(ssk)->nonagle, + info->size_goal); release_sock(ssk); } From f3ef03357396d4b147d8e76c75fb612c2f264ffc Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Thu, 17 Sep 2026 15:05:59 +0200 Subject: [PATCH 0704/1417] mptcp: fix bad accounting in __mptcp_subflow_push_pending() If __subflow_push_pending() errors out we should avoid updating the copied byte counters, to avoid mismatch push call later on. Fixes: 0fa1b3783a17 ("mptcp: use get_send wrapper") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260917-net-mptcp-misc-fixes-7-3-rc4-v2-3-0cf5c72667c8@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 577d0134b9ece8..e89a69ab927c91 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -1854,7 +1854,8 @@ static void __mptcp_subflow_push_pending(struct sock *sk, struct sock *ssk, bool ret = __subflow_push_pending(sk, ssk, &info); if (ret <= 0) keep_pushing = false; - copied += ret; + else + copied += ret; } mptcp_for_each_subflow(msk, subflow) { From 9413959fa9fe2d94d4814f8cc2b60409f4cd46b5 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 25 Aug 2026 23:09:32 +0800 Subject: [PATCH 0705/1417] drm/amd/pm: report energy accumulator for smu 14.0.3 add energy accumulator on pmfw 0x00685000 and above version. Signed-off-by: Kevin Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher (cherry picked from commit 4aa733ab15b303e2a40e2985ac21a0e01f24cc4a) --- drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c index 56a5c11bc19614..ed99e61f18e11e 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c @@ -2142,6 +2142,7 @@ static void smu_v14_0_2_init_msg_ctl(struct smu_context *smu) static ssize_t smu_v14_0_2_get_gpu_metrics(struct smu_context *smu, void **table) { + uint32_t mp1_ver = amdgpu_ip_version(smu->adev, MP1_HWIP, 0); struct gpu_metrics_v1_3 *gpu_metrics = (struct gpu_metrics_v1_3 *)smu_driver_table_ptr( smu, SMU_DRIVER_TABLE_GPU_METRICS); @@ -2171,6 +2172,8 @@ static ssize_t smu_v14_0_2_get_gpu_metrics(struct smu_context *smu, metrics->Vcn1ActivityPercentage); gpu_metrics->average_socket_power = metrics->AverageSocketPower; + if (mp1_ver == IP_VERSION(14, 0, 3) && smu->smc_fw_version >= 0x00685000) + gpu_metrics->energy_accumulator = metrics->EnergyAccumulator; if (metrics->AverageGfxActivity <= SMU_14_0_2_BUSY_THRESHOLD) gpu_metrics->average_gfxclk_frequency = metrics->AverageGfxclkFrequencyPostDs; From 63e19ef3ddab806c472748c825f4dc88dcd994e8 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Tue, 25 Aug 2026 23:21:53 -0400 Subject: [PATCH 0706/1417] drm/amd/display: Atomize IRQ register read/modify/write ops [Why] The OTG_GLOBAL_SYNC_STATUS register controls various HW IRQ sources for the output timing generator (OTG). VUPDATE_NO_LOCK is one of them. To enable the IRQ, driver sets the VUPDATE_NO_LOCK_EN bit in the GLOBAL_SYNC_STATUS register. To ack the IRQ after it fires, the driver sets the VUPDATE_NO_LOCK_CLEAR bit in the same GLOBAL_SYNC_STATUS register. The bit sets are done through read/modify/write operations, which are not atomic. Thus, the following race is possible: Thread A: IRQ handler: *HW IRQ fires* # IRQ disable val = read(GLOBAL_SYNC_STATUS) unset(val, VUPDATE_NO_LOCK_EN) write(val, GLOBAL_SYNC_STATUS) # ACK reads VUPDATE_NO_LOCK_EN unset val1 = read(GLOBAL_SYNC_STATUS) set(val1, VUPDATE_NO_LOCK_CLEAR) # IRQ enable val = read(GLOBAL_SYNC_STATUS) set(val, VUPDATE_NO_LOCK_EN) write(val, GLOBAL_SYNC_STATUS) # BAD! clears VUPDATE_NO_LOCK_EN write(val1, GLOBAL_SYNC_STATUS) Regarding the tagged Fixes: change, it appears the change made this race more likely to occur. Since VUPDATE_NO_LOCK is now the sole IRQ source for vblank handling, a single race on high refresh panels can lead to a time out. [How] The GLOBAL_SYNC_STATUS register is only one example, other IRQ control registers also share the same scheme. On top of GLOBAL_SYNC_STATUS, let's clean up those as well. To keep things simple, Let's atomize the IRQ rmw ops via a single driver-wide spinlock. Due to the small scope of this lock, it is unlikely to cause noticeable overhead on top of all the existing locking within the IRQ set/handle paths. Since DM is responsible for locking, wrap dc_interrupt_set/ack with the spinlock in the new amdgpu_dm_irq_set/ack functions. Migrate/drop all references in DM to dc_interrupt_set/ack to use amdgpu_dm_irq_set/ack instead. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5616 Fixes: c87e6635d2db ("drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock") Reviewed-by: Mario Limonciello Signed-off-by: Leo Li Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 70de0a0216583a53c946155f8c8adedfdca6b4e7) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 12 ++++ .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 3 +- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 3 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 64 +++++++++++-------- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 28 ++++++++ .../amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 16 +++-- .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 8 ++- .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 34 +++++----- 9 files changed, 117 insertions(+), 55 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 08b8605029ab0d..36d2f86f000a97 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1420,7 +1420,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, if (acrtc && state->stream_status[i].plane_count != 0 && amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst; - rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; + rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; if (rc) drm_warn(adev_to_drm(adev), "Failed to %s pflip interrupts\n", enable ? "enable" : "disable"); @@ -1444,7 +1444,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, /* During gpu-reset we disable and then enable vblank irq, so * don't use amdgpu_irq_get/put() to avoid refcount change. */ - if (!dc_interrupt_set(adev->dm.dc, irq_source, enable)) + if (!amdgpu_dm_irq_set(adev, irq_source, enable)) drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis"); } else if (acrtc && state->stream_status[i].plane_count != 0) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 3524931451c864..881c8d1c3cc0df 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -552,6 +552,18 @@ struct amdgpu_display_manager { struct common_irq_params vupdate_params[DC_IRQ_SOURCE_VUPDATE6 - DC_IRQ_SOURCE_VUPDATE1 + 1]; + /** + * @irq_reg_lock: + * + * Serializes the read-modify-writes of the HW interrupt control + * registers. Several interrupt sources share one register - e.g. the + * enable and clear bits of both VSTARTUP (vblank) and VUPDATE_NO_LOCK + * live in OTG_GLOBAL_SYNC_STATUS. Therefore, enabling one source must + * not race with acking another. Held only across amdgpu_dm_irq_set() + * and amdgpu_dm_irq_ack(). + */ + spinlock_t irq_reg_lock; + /** * @dmub_trace_params: * diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 62eac6e65334bb..1d941be73561c6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -31,6 +31,7 @@ #include "amdgpu_dm_psr.h" #include "amdgpu_dm_replay.h" #include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_irq.h" #include "amdgpu_dm_plane.h" #include "amdgpu_dm_trace.h" #include "amdgpu_dm_debugfs.h" @@ -91,7 +92,7 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable) irq_source = IRQ_TYPE_VUPDATE + acrtc->otg_inst; - rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; + rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; DRM_DEBUG_VBL("crtc %d - vupdate irq %sabling: r=%d\n", acrtc->crtc_id, enable ? "en" : "dis", rc); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 298de7b75ca82b..ced8b3d2d762e0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -1439,12 +1439,13 @@ void dm_helpers_free_gpu_mem( bool dm_helpers_dmub_outbox_interrupt_control(struct dc_context *ctx, bool enable) { + struct amdgpu_device *adev = ctx->driver_context; enum dc_irq_source irq_source; bool ret; irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; - ret = dc_interrupt_set(ctx->dc, irq_source, enable); + ret = amdgpu_dm_irq_set(adev, irq_source, enable); DRM_DEBUG_DRIVER("Dmub trace irq %sabling: r=%d\n", enable ? "en" : "dis", ret); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index d0239a3de2e1dc..74a8735168aaa5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -396,6 +396,7 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev) DRM_DEBUG_KMS("DM_IRQ\n"); spin_lock_init(&adev->dm.irq_handler_list_table_lock); + spin_lock_init(&adev->dm.irq_reg_lock); adev->dm.irq_wq = alloc_workqueue("amdgpu_dm_irq", WQ_UNBOUND | WQ_HIGHPRI, 0); @@ -530,7 +531,7 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) */ for (src = DC_IRQ_SOURCE_HPD1; src <= DC_IRQ_SOURCE_HPD6RX; src++) { hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; - dc_interrupt_set(adev->dm.dc, src, false); + amdgpu_dm_irq_set(adev, src, false); DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -568,7 +569,7 @@ void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) - dc_interrupt_set(adev->dm.dc, src, true); + amdgpu_dm_irq_set(adev, src, true); } DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -594,7 +595,7 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) - dc_interrupt_set(adev->dm.dc, src, true); + amdgpu_dm_irq_set(adev, src, true); } DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -690,6 +691,23 @@ STATIC_IFN_KUNIT void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, } EXPORT_IF_KUNIT(amdgpu_dm_irq_immediate_work); +bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, + bool enable) +{ + guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); + + return dc_interrupt_set(adev->dm.dc, src, enable); +} +EXPORT_IF_KUNIT(amdgpu_dm_irq_set); + +void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src) +{ + guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); + + dc_interrupt_ack(adev->dm.dc, src); +} +EXPORT_IF_KUNIT(amdgpu_dm_irq_ack); + /** * amdgpu_dm_irq_handler - Generic DM IRQ handler * @adev: amdgpu base driver device containing the DM device @@ -710,7 +728,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_irq_handler(struct amdgpu_device *adev, entry->src_id, entry->src_data[0]); - dc_interrupt_ack(adev->dm.dc, src); + amdgpu_dm_irq_ack(adev, src); /* Call high irq work immediately */ amdgpu_dm_irq_immediate_work(adev, src); @@ -750,7 +768,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, enum dc_irq_source src = amdgpu_dm_hpd_to_dal_irq_source(type); bool st = (state == AMDGPU_IRQ_STATE_ENABLE); - dc_interrupt_set(adev->dm.dc, src, st); + amdgpu_dm_irq_set(adev, src, st); return 0; } EXPORT_IF_KUNIT(amdgpu_dm_set_hpd_irq_state); @@ -785,7 +803,7 @@ static inline int dm_irq_state(struct amdgpu_device *adev, if (dc && dc->caps.ips_support && dc->idle_optimizations_allowed) dc_allow_idle_optimizations(dc, false); - dc_interrupt_set(adev->dm.dc, irq_source, st); + amdgpu_dm_irq_set(adev, irq_source, st); return 0; } @@ -842,7 +860,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *a enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; bool st = (state == AMDGPU_IRQ_STATE_ENABLE); - dc_interrupt_set(adev->dm.dc, irq_source, st); + amdgpu_dm_irq_set(adev, irq_source, st); return 0; } EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_outbox_irq_state); @@ -870,7 +888,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *ad enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX0; bool st = (state == AMDGPU_IRQ_STATE_ENABLE); - dc_interrupt_set(adev->dm.dc, irq_source, st); + amdgpu_dm_irq_set(adev, irq_source, st); return 0; } EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_trace_irq_state); @@ -937,9 +955,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_set_irq_funcs); void amdgpu_dm_outbox_init(struct amdgpu_device *adev) { - dc_interrupt_set(adev->dm.dc, - DC_IRQ_SOURCE_DMCUB_OUTBOX, - true); + amdgpu_dm_irq_set(adev, DC_IRQ_SOURCE_DMCUB_OUTBOX, true); } EXPORT_IF_KUNIT(amdgpu_dm_outbox_init); @@ -962,7 +978,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) /* First, clear all hpd and hpdrx interrupts */ for (i = DC_IRQ_SOURCE_HPD1; i <= DC_IRQ_SOURCE_HPD6RX; i++) { - if (!dc_interrupt_set(adev->dm.dc, i, false)) + if (!amdgpu_dm_irq_set(adev, i, false)) drm_err(dev, "Failed to clear hpd(rx) source=%d on init\n", i); } @@ -991,7 +1007,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) * of dm. Note that only hpd interrupt types are registered with * base driver; hpd_rx types aren't. IOW, amdgpu_irq_get/put on * hpd_rx isn't available. DM currently controls hpd_rx - * explicitly with dc_interrupt_set() + * explicitly with amdgpu_dm_irq_set() */ if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { irq_type = dc_link->irq_source_hpd - DC_IRQ_SOURCE_HPD1; @@ -1000,23 +1016,21 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) * and what bios reports as the # of connectors with hpd * sources. Since the # of hpd source types registered * with base driver == mode_info.num_hpd, we have to - * fallback to dc_interrupt_set for the remaining types. + * fallback to amdgpu_dm_irq_set for the remaining types. */ if (irq_type < adev->mode_info.num_hpd) { if (amdgpu_irq_get(adev, &adev->hpd_irq, irq_type)) drm_err(dev, "DM_IRQ: Failed get HPD for source=%d)!\n", dc_link->irq_source_hpd); } else { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd, - true); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, + true); } } if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd_rx, - true); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, + true); } } drm_connector_list_iter_end(&iter); @@ -1061,16 +1075,14 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) drm_err(dev, "DM_IRQ: Failed put HPD for source=%d!\n", dc_link->irq_source_hpd); } else { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd, - false); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, + false); } } if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd_rx, - false); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, + false); } } drm_connector_list_iter_end(&iter); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index 4c200a9614a774..bc16ecc67329c4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -89,6 +89,34 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, enum dc_irq_source irq_source, void *ih_index); +/** + * amdgpu_dm_irq_set - enable or disable a DC interrupt source. + * + * @adev: AMD DRM device + * @src: DC interrupt source to toggle + * @enable: true to enable the source, false to disable it + * + * DM-wide replacement for dc_interrupt_set(). As locking is DM's + * responsibility, this is a thin wrapper serializes the underlying + * read-modify-write against the other interrupt sources sharing HW control + * registers with @src, so DM must never call dc_interrupt_set() directly. + * + * Returns: true if the source was toggled. + */ +bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, + bool enable); + +/** + * amdgpu_dm_irq_ack - acknowledge a DC interrupt source. + * + * @adev: AMD DRM device + * @src: DC interrupt source to acknowledge + * + * DM-wide replacement for dc_interrupt_ack(), serialized the same way as + * amdgpu_dm_irq_set(). + */ +void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src); + void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev); void amdgpu_dm_outbox_init(struct amdgpu_device *adev); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c index 0d998f20425038..ae0f4da962521a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c @@ -436,7 +436,7 @@ static void dm_test_crtc_set_vupdate_irq_no_otg(struct kunit *test) * dm_test_crtc_set_vupdate_irq_dc_busy - Test vupdate irq when DC rejects request * @test: The KUnit test context * - * With an OTG instance assigned but no DC attached, dc_interrupt_set() returns + * With an OTG instance assigned but no DC attached, amdgpu_dm_irq_set() returns * false and the function must report the request as busy (-EBUSY). */ static void dm_test_crtc_set_vupdate_irq_dc_busy(struct kunit *test) @@ -453,12 +453,12 @@ static void dm_test_crtc_set_vupdate_irq_dc_busy(struct kunit *test) acrtc->base.dev = &adev->ddev; acrtc->otg_inst = 0; - /* adev->dm.dc is NULL, so dc_interrupt_set() returns false. */ + /* adev->dm.dc is NULL, so amdgpu_dm_irq_set() returns false. */ KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, true), -EBUSY); } -/* Per-source funcs let dc_interrupt_set() succeed without register access. */ +/* Per-source funcs let amdgpu_dm_irq_set() succeed without register access. */ static bool dm_test_vupdate_irq_src_set(struct irq_service *irq_service, const struct irq_source_info *info, bool enable) @@ -477,7 +477,7 @@ static struct irq_source_info_funcs dm_test_vupdate_irq_src_funcs = { .ack = dm_test_vupdate_irq_src_ack, }; -/* A .set that fails so dc_interrupt_set() reports the source as busy. */ +/* A .set that fails so amdgpu_dm_irq_set() reports the source as busy. */ static bool dm_test_vupdate_irq_src_set_busy(struct irq_service *irq_service, const struct irq_source_info *info, bool enable) @@ -519,7 +519,9 @@ static void dm_test_crtc_set_vupdate_irq_enable(struct kunit *test) irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); - /* Populate the per-source info table so dc_interrupt_set() succeeds. */ + /* + * Populate the per-source info table so amdgpu_dm_irq_set() succeeds. + */ info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, info); @@ -1018,7 +1020,9 @@ static void dm_test_crtc_enable_vblank_vupdate_busy(struct kunit *test) irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); - /* Per-source .set fails so dc_interrupt_set() reports the source busy. */ + /* + * Per-source .set fails so amdgpu_dm_irq_set() reports the source busy. + */ info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, info); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 82e0c984693cbb..639512bea27540 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -2442,17 +2442,21 @@ static void dm_test_is_dp_sink_present_null_priv(struct kunit *test) * dm_test_dmub_outbox_interrupt_control_null_dc - Test outbox irq control with NULL dc * @test: The KUnit test context * - * dc_interrupt_set() is NULL-safe and returns false when dc is NULL, so the + * amdgpu_dm_irq_set() is NULL-safe and returns false when dc is NULL, so the * helper returns false without touching real interrupt hardware. */ static void dm_test_dmub_outbox_interrupt_control_null_dc(struct kunit *test) { + struct amdgpu_device *adev; struct dc_context *ctx; + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; - /* ctx->dc is NULL → dc_interrupt_set returns false */ + /* adev->dm.dc is NULL → amdgpu_dm_irq_set returns false */ KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, true)); KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, false)); } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index 861ee9eaa0321a..95322d8c861320 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -268,7 +268,7 @@ static bool dm_test_irq_src_ack(struct irq_service *irq_service, return true; } -/* Per-source funcs let dc_interrupt_set() succeed without register access. */ +/* Per-source funcs let amdgpu_dm_irq_set() succeed without register access. */ static struct irq_source_info_funcs dm_test_irq_src_funcs = { .set = dm_test_irq_src_set, .ack = dm_test_irq_src_ack, @@ -290,7 +290,7 @@ static struct dc *dm_test_alloc_dc_with_irq_service(struct kunit *test, KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); /* - * Populate the per-source info table so dc_interrupt_set()/_ack() + * Populate the per-source info table so amdgpu_dm_irq_set()/_ack() * succeed without touching hardware registers. */ info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, @@ -1227,7 +1227,7 @@ static void dm_test_irq_suspend_empty(struct kunit *test) KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); /* - * With no registered handlers the HW dc_interrupt_set() calls are + * With no registered handlers the amdgpu_dm_irq_set() calls are * skipped, so suspend must complete without touching the (absent) DC. */ amdgpu_dm_irq_suspend(adev); @@ -1275,11 +1275,11 @@ static void dm_test_irq_resume_late_empty(struct kunit *test) } /** - * dm_test_irq_suspend_registered - Test suspend reaches the dc_interrupt_set path + * dm_test_irq_suspend_registered - Test suspend reaches the irq set path * @test: The KUnit test context * * Registers a low-context HPD handler so the handler list is non-empty, - * forcing amdgpu_dm_irq_suspend() to call dc_interrupt_set() (NULL-safe with + * forcing amdgpu_dm_irq_suspend() to call amdgpu_dm_irq_set() (NULL-safe with * no DC) and flush_work() on the registered handler. */ static void dm_test_irq_suspend_registered(struct kunit *test) @@ -1330,11 +1330,11 @@ static void dm_test_irq_suspend_disables_polling(struct kunit *test) } /** - * dm_test_irq_resume_early_registered - Test early resume reaches dc_interrupt_set + * dm_test_irq_resume_early_registered - Test early resume reaches irq set * @test: The KUnit test context * * Registers a low-context HPD RX handler so early resume calls - * dc_interrupt_set() for the short-pulse interrupt source. + * amdgpu_dm_irq_set() for the short-pulse interrupt source. */ static void dm_test_irq_resume_early_registered(struct kunit *test) { @@ -1358,10 +1358,10 @@ static void dm_test_irq_resume_early_registered(struct kunit *test) } /** - * dm_test_irq_resume_late_registered - Test late resume reaches dc_interrupt_set + * dm_test_irq_resume_late_registered - Test late resume reaches irq set * @test: The KUnit test context * - * Registers a low-context HPD handler so late resume calls dc_interrupt_set() + * Registers a low-context HPD handler so late resume calls amdgpu_dm_irq_set() * for the HPD interrupt source. */ static void dm_test_irq_resume_late_registered(struct kunit *test) @@ -1592,7 +1592,7 @@ static void dm_test_set_crtc_irq_state_enable(struct kunit *test) /* * otg_inst >= 0 computes the irq source and reaches the NULL-safe - * dc_interrupt_set(); the ips_support branch is skipped (dc == NULL). + * amdgpu_dm_irq_set(); the ips_support branch is skipped (dc == NULL). */ acrtc->otg_inst = 3; adev->mode_info.crtcs[0] = acrtc; @@ -1671,8 +1671,8 @@ static void dm_test_set_vupdate_irq_state_enable(struct kunit *test) * * With a non-NULL DC that advertises IPS support and currently allows idle * optimizations, dm_irq_state() must call dc_allow_idle_optimizations() before - * dc_interrupt_set(). disable_idle_power_optimizations makes that call a safe - * early return, and per-source stub funcs let dc_interrupt_set() succeed. + * amdgpu_dm_irq_set(). disable_idle_power_optimizations makes that call a safe + * early return, and per-source stub funcs let amdgpu_dm_irq_set() succeed. */ static void dm_test_set_crtc_irq_state_allows_idle(struct kunit *test) { @@ -1891,7 +1891,7 @@ static void dm_test_set_hpd_irq_state_null_dc(struct kunit *test) adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - /* dc_interrupt_set() is a no-op when dc is NULL, so both states + /* amdgpu_dm_irq_set() is a no-op when dc is NULL, so both states * return 0 without dereferencing the (absent) DC. */ KUNIT_EXPECT_EQ(test, amdgpu_dm_set_hpd_irq_state(adev, NULL, AMDGPU_HPD_1, @@ -1951,7 +1951,7 @@ static void dm_test_outbox_init_null_dc(struct kunit *test) adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - /* Single dc_interrupt_set() call must be skipped when dc is NULL. */ + /* Single amdgpu_dm_irq_set() call must be skipped when dc is NULL. */ amdgpu_dm_outbox_init(adev); } @@ -1969,7 +1969,7 @@ static void dm_test_hpd_init_empty_connectors(struct kunit *test) /* * With an empty connector list the per-connector loop is skipped and - * the initial clear loop relies on dc_interrupt_set() being a no-op + * the initial clear loop relies on amdgpu_dm_irq_set() being a no-op * for a NULL dc, so init must complete without touching the DC. */ amdgpu_dm_hpd_init(adev); @@ -2004,7 +2004,7 @@ static void dm_test_hpd_init_fini_with_connectors(struct kunit *test) /* * num_hpd = 0 forces irq_type >= num_hpd so the loop takes the HW - * fallback (dc_interrupt_set()) instead of amdgpu_irq_get(); with a + * fallback (amdgpu_dm_irq_set()) instead of amdgpu_irq_get(); with a * NULL dc that fallback is a safe no-op. */ adev->mode_info.num_hpd = 0; @@ -2090,7 +2090,7 @@ static void dm_test_hpd_init_fini_irq_ref(struct kunit *test) /* * num_hpd >= 1 makes irq_type (0) < num_hpd, so the loop takes the * amdgpu_irq_get()/amdgpu_irq_put() branch instead of the - * dc_interrupt_set() fallback. The mock device has irq.installed == + * amdgpu_dm_irq_set() fallback. The mock device has irq.installed == * false, so both calls fail early with -ENOENT (logging an error) * without touching the base-driver irq state. */ From 5f28bb1c2cd9dcdb76a20d61b3ea069b85893c59 Mon Sep 17 00:00:00 2001 From: Vladimir Marioukhine Date: Wed, 12 Aug 2026 13:19:46 -0400 Subject: [PATCH 0707/1417] drm/amdkfd: implement restore_mqd callbacks for GFX12/12.1 kfd_mqd_manager_v12.c (GFX 12.0) and kfd_mqd_manager_v12_1.c (GFX 12.1) do not implement restore_mqd callbacks, leaving the function pointers NULL and causing CRIU restore to return -EOPNOTSUPP on GFX12. Implement restore_mqd for both compute and SDMA queues in kfd_mqd_manager_v12.c and kfd_mqd_manager_v12_1.c, modeled after the GFX 11 implementation with the following improvements: - update cp_mqd_base_addr_lo/hi to the newly allocated MQD address, fixing a pre-existing gap shared with v11 where the in-MQD copy still pointed at the old checkpoint-time address after restore - memset the full allocation before memcpy for compute queues to avoid stale data in the GTT sub-allocator tail; SDMA MQDs use sizeof(*m) since they are packed at mqd_size stride in a shared BO checkpoint_mqd registration is deferred to a follow-up patch that also implements get_checkpoint_info, so that checkpoint and restore are enabled together as a complete and testable unit. Note: GFX12.1 restore handles XCC0 only. Multi-XCC CRIU restore is currently unreachable due to a separate validation issue in kfd_criu_restore_queue(). A pr_warn_once() is emitted if a multi-XCC device is encountered. Signed-off-by: Vladimir Marioukhine Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit b1f9601237d050f5df478464cf51bf1fff29a256) Cc: stable@vger.kernel.org --- .../drm/amd/amdkfd/kfd_device_queue_manager.c | 7 +- .../gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c | 59 ++++++++++++++++ .../drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 68 +++++++++++++++++++ 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 9811e4e10291f6..2f78395a0c31cb 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -770,10 +770,12 @@ static int create_queue_nocpsch(struct device_queue_manager *dqm, mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( q->properties.type)]; if (qd && !mqd_mgr->restore_mqd) { - pr_debug("restore_mqd not implemented for this GPU\n"); + pr_debug("restore_mqd not implemented for queue type %d\n", + q->properties.type); retval = -EOPNOTSUPP; goto deallocate_vmid; } + if (q->properties.type == KFD_QUEUE_TYPE_COMPUTE) { retval = allocate_hqd(dqm, q); if (retval) @@ -2250,7 +2252,8 @@ static int create_queue_cpsch(struct device_queue_manager *dqm, struct queue *q, mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( q->properties.type)]; if (qd && !mqd_mgr->restore_mqd) { - pr_debug("restore_mqd not implemented for this GPU\n"); + pr_debug("restore_mqd not implemented for queue type %d\n", + q->properties.type); retval = -EOPNOTSUPP; goto out_deallocate_doorbell; } diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c index 7c387fa900766a..63f25a60baa5be 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c @@ -380,6 +380,63 @@ static int debugfs_show_mqd_sdma(struct seq_file *m, void *data) #endif +static void restore_mqd(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, const void *mqd_src, + const void *ctl_stack_src, const u32 ctl_stack_size) +{ + u64 addr; + struct v12_compute_mqd *m; + + m = (struct v12_compute_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); + memcpy(m, mqd_src, sizeof(*m)); + + /* Update MQD base address to the newly allocated location */ + m->cp_mqd_base_addr_lo = lower_32_bits(addr); + m->cp_mqd_base_addr_hi = upper_32_bits(addr); + + m->cp_hqd_pq_doorbell_control &= + ~CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK; + m->cp_hqd_pq_doorbell_control |= + qp->doorbell_off << CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + pr_debug("cp_hqd_pq_doorbell_control 0x%x\n", m->cp_hqd_pq_doorbell_control); + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + +static void restore_mqd_sdma(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, + const void *mqd_src, + const void *ctl_stack_src, + const u32 ctl_stack_size) +{ + u64 addr; + struct v12_sdma_mqd *m; + + m = (struct v12_sdma_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); + memcpy(m, mqd_src, sizeof(*m)); + + m->sdmax_rlcx_doorbell_offset = + qp->doorbell_off << SDMA0_QUEUE0_DOORBELL_OFFSET__OFFSET__SHIFT; + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + struct mqd_manager *mqd_manager_init_v12(enum KFD_MQD_TYPE type, struct kfd_node *dev) { @@ -407,6 +464,7 @@ struct mqd_manager *mqd_manager_init_v12(enum KFD_MQD_TYPE type, mqd->mqd_size = sizeof(struct v12_compute_mqd); mqd->get_wave_state = get_wave_state; mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd; #endif @@ -453,6 +511,7 @@ struct mqd_manager *mqd_manager_init_v12(enum KFD_MQD_TYPE type, mqd->is_occupied = kfd_is_occupied_sdma; mqd->mqd_size = sizeof(struct v12_sdma_mqd); mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd_sdma; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd_sdma; #endif diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c index 431a940f91f3b9..708bbb08c089de 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c @@ -641,6 +641,72 @@ static int debugfs_show_mqd_sdma(struct seq_file *m, void *data) #endif +static void restore_mqd_v12_1(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, const void *mqd_src, + const void *ctl_stack_src, const u32 ctl_stack_size) +{ + u64 addr; + struct v12_1_compute_mqd *m; + + /* + * GFX12.1 is multi-XCC capable but this restore handles XCC0 only. + * Multi-XCC CRIU restore is currently unreachable because + * kfd_criu_restore_queue() validates against unscaled mqd_size. + */ + if (NUM_XCC(mm->dev->xcc_mask) > 1) + pr_warn_once("GFX12.1 multi-XCC CRIU restore not fully supported\n"); + + m = (struct v12_1_compute_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size) * + NUM_XCC(mm->dev->xcc_mask)); + memcpy(m, mqd_src, sizeof(*m)); + + /* Update MQD base address to the newly allocated location */ + m->cp_mqd_base_addr_lo = lower_32_bits(addr); + m->cp_mqd_base_addr_hi = upper_32_bits(addr); + + m->cp_hqd_pq_doorbell_control &= + ~CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK; + m->cp_hqd_pq_doorbell_control |= + qp->doorbell_off << CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + pr_debug("cp_hqd_pq_doorbell_control 0x%x\n", m->cp_hqd_pq_doorbell_control); + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + +static void restore_mqd_sdma_v12_1(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, + const void *mqd_src, + const void *ctl_stack_src, + const u32 ctl_stack_size) +{ + u64 addr; + struct v12_sdma_mqd *m; + + m = (struct v12_sdma_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); + memcpy(m, mqd_src, sizeof(*m)); + + m->sdmax_rlcx_doorbell_offset = + qp->doorbell_off << SDMA0_SDMA_QUEUE0_DOORBELL_OFFSET__OFFSET__SHIFT; + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + struct mqd_manager *mqd_manager_init_v12_1(enum KFD_MQD_TYPE type, struct kfd_node *dev) { @@ -668,6 +734,7 @@ struct mqd_manager *mqd_manager_init_v12_1(enum KFD_MQD_TYPE type, mqd->mqd_size = sizeof(struct v12_1_compute_mqd); mqd->get_wave_state = get_wave_state_v12_1; mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd_v12_1; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd; #endif @@ -714,6 +781,7 @@ struct mqd_manager *mqd_manager_init_v12_1(enum KFD_MQD_TYPE type, mqd->is_occupied = kfd_is_occupied_sdma; mqd->mqd_size = sizeof(struct v12_sdma_mqd); mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd_sdma_v12_1; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd_sdma; #endif From 636139603b99d2e3a18a46cf3f8d39313ce8042e Mon Sep 17 00:00:00 2001 From: Mike Lothian Date: Sat, 12 Sep 2026 00:29:08 +0100 Subject: [PATCH 0708/1417] drm/amdgpu: hold a runtime PM reference for P2P dma-buf attachments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_dma_buf_map() adds VRAM to the allowed domains for a peer2peer attachment. GTT is only a fallback placement when VRAM is preferred, so ttm_bo_validate() migrates the buffer from GTT into VRAM. While the exporting device is runtime suspended its SDMA rings are down and the move fails: amdgpu: Move buffer fallback to memcpy unavailable An importer on a second GPU reaches this holding no runtime PM reference on the exporter, e.g. a compositor on the APU submitting a frame that references a buffer exported by an idle dGPU: amdgpu_cs_ioctl -> amdgpu_cs_parser_bos -> amdgpu_cs_bo_validate -> ttm_bo_validate -> amdgpu_bo_move -> dma_buf_map_attachment -> amdgpu_dma_buf_map -> ttm_bo_validate -> amdgpu_bo_move Pinning a dma-buf into VRAM has the same requirement, which commit 030631e97b20 ("drm/amdgpu: revert "take runtime pm reference when we attach a buffer" v2") called out as the one case that would need the reference back. Take it in attach and drop it in detach. pm_runtime_get_if_active() never resumes the device, so it cannot deadlock against the reservation taken during resume, which is why the old pm_runtime_get_sync() had to go. If the device is not active, clear peer2peer instead: the buffer then stays in GTT, which remains accessible while the GPU is powered down. If runtime PM is disabled, take a plain reference so the put in detach stays balanced. Fixes: 030631e97b20 ("drm/amdgpu: revert "take runtime pm reference when we attach a buffer" v2") Suggested-by: Christian König Reviewed-by: Christian König Signed-off-by: Mike Lothian Assisted-by: Claude:Opus-5 [Claude Code] Signed-off-by: Alex Deucher (cherry picked from commit 062ff15e30a48d14fb7d7558eba84f8dc97197f0) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c | 43 ++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c index b33c300e26e2eb..9adf3eed8822c7 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c @@ -43,6 +43,7 @@ #include #include #include +#include static const struct dma_buf_attach_ops amdgpu_dma_buf_attach_ops; @@ -100,15 +101,54 @@ static int amdgpu_dma_buf_attach(struct dma_buf *dmabuf, pci_p2pdma_distance(adev->pdev, attach->dev, false) < 0) attach->peer2peer = false; + /* + * Only allow P2P while the exporter is active, and keep it active + * until detach. With runtime PM disabled take a plain reference so + * the put in detach stays balanced. + */ + if (attach->peer2peer) { + struct device *dev = adev_to_drm(adev)->dev; + int ret = pm_runtime_get_if_active(dev); + + if (!ret) + attach->peer2peer = false; + else if (ret < 0) + pm_runtime_get_noresume(dev); + } + r = dma_resv_lock(bo->tbo.base.resv, NULL); if (r) - return r; + goto err_pm_put; amdgpu_vm_bo_update_shared(bo); dma_resv_unlock(bo->tbo.base.resv); return 0; + +err_pm_put: + if (attach->peer2peer) + pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); + return r; +} + +/** + * amdgpu_dma_buf_detach - &dma_buf_ops.detach implementation + * + * @dmabuf: DMA-buf where we remove the attachment from + * @attach: the attachment to remove + * + * Drop the runtime PM reference taken in amdgpu_dma_buf_attach(). + */ +static void amdgpu_dma_buf_detach(struct dma_buf *dmabuf, + struct dma_buf_attachment *attach) +{ + struct drm_gem_object *obj = dmabuf->priv; + struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj); + struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev); + + if (attach->peer2peer) + pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); } /** @@ -350,6 +390,7 @@ static void amdgpu_dma_buf_vunmap(struct dma_buf *dma_buf, struct iosys_map *map const struct dma_buf_ops amdgpu_dmabuf_ops = { .attach = amdgpu_dma_buf_attach, + .detach = amdgpu_dma_buf_detach, .pin = amdgpu_dma_buf_pin, .unpin = amdgpu_dma_buf_unpin, .map_dma_buf = amdgpu_dma_buf_map, From 723d4dc628d764b19cf9efca14b82cca5ff020c9 Mon Sep 17 00:00:00 2001 From: Dmitriy Chumachenko Date: Mon, 14 Sep 2026 17:33:03 +0300 Subject: [PATCH 0709/1417] drm/amdgpu: check ras and obj before dereference nbio_v7_9_handle_ras_controller_intr_no_bifring() dereferences ras and obj without checking either for NULL. Both amdgpu_ras_get_context() and amdgpu_ras_find_obj() can return NULL, e.g. during the window between adev->nbio.ras being set (early in amdgpu_ras_init(), by design, to enable the fatal-error interrupt as soon as possible) and the PCIE_BIF ras object actually being created in RAS late_init. Any interrupt in that window crashes in hard-IRQ context. This is analogous to commit d190b459b2a4 ("drm/amdgpu: the warning dereferencing obj for nbio_v7_4"), which fixed the same issue in the nbio_v7_4 handler. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 7692e1ee2446 ("drm/amdgpu: add RAS fatal error handler for NBIO v7.9") Reviewed-by: Tao Zhou Signed-off-by: Dmitriy Chumachenko Signed-off-by: Alex Deucher (cherry picked from commit c7071767a50a32ed727cf800ac84372429e3b4b3) --- drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c b/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c index bdfd2917e3cabc..def02993b7cfd1 100644 --- a/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c +++ b/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c @@ -535,7 +535,7 @@ static void nbio_v7_9_handle_ras_controller_intr_no_bifring(struct amdgpu_device RAS_CNTLR_INTERRUPT_CLEAR, 1); WREG32_SOC15(NBIO, 0, regBIF_BX0_BIF_DOORBELL_INT_CNTL, bif_doorbell_intr_cntl); - if (!ras->disable_ras_err_cnt_harvest) { + if (ras && !ras->disable_ras_err_cnt_harvest && obj) { /* * clear error status after ras_controller_intr * according to hw team and count ue number From 04de4007d32385b8b6a5dd72bff3146dfdc592c3 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 15 Sep 2026 10:59:45 -0500 Subject: [PATCH 0710/1417] drm/amdgpu: Fix GPU PCIe link capability reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit eb53125a7ad9 ("drm/amd: Add dedicated helper for amdgpu_device_find_parent()") made amdgpu_device_gpu_bandwidth() query the first device outside the dGPU. That is the host side of the physical link, not the GPU side. As a result, the ASIC and platform capability masks can both be based on the host port. drm_amdgpu_info_device then exposes the host capabilities to userspace, such as Gen5 x16 for a Gen4 x8 GPU. Cache both ends of the physical link during device initialization. Use link_dev for the GPU capability and link_partner for the platform capability and _PR3 detection. Reported-by: "Marek Olšák" Closes: https://lore.kernel.org/amd-gfx/CAAxE2A4VhsAzzO1QjBjUg+NgnbD04ZzMyN6xsUJxjKJHH6hxiw@mail.gmail.com/ Suggested-by: Lijo Lazar Fixes: eb53125a7ad9 ("drm/amd: Add dedicated helper for amdgpu_device_find_parent()") Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 7ea6a47224e2c6e89a3a682d7fbaace4817a55aa) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 3 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 48 ++++++++-------------- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 7974f9b7944f3a..a9c6f5d4a6397c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -621,6 +621,9 @@ enum amdgpu_enforce_isolation_mode { struct amdgpu_device { struct device *dev; struct pci_dev *pdev; + /* The two ends of the physical PCIe link outside the device. */ + struct pci_dev *link_dev; + struct pci_dev *link_partner; struct drm_device ddev; #ifdef CONFIG_DRM_AMD_ACP diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 104d1d2cbad962..933804349dbf32 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1954,18 +1954,17 @@ static void amdgpu_uid_fini(struct amdgpu_device *adev) adev->uid_info = NULL; } -static struct pci_dev *amdgpu_device_find_parent(struct amdgpu_device *adev) +static void amdgpu_device_init_pcie_links(struct amdgpu_device *adev) { - struct pci_dev *parent = adev->pdev; + adev->link_dev = adev->pdev; + adev->link_partner = pci_upstream_bridge(adev->link_dev); - /* skip upstream/downstream switches internal to dGPU */ - while ((parent = pci_upstream_bridge(parent))) { - if (parent->vendor == PCI_VENDOR_ID_ATI) - continue; - break; + /* Skip upstream/downstream switches internal to the dGPU. */ + while (adev->link_partner && + adev->link_partner->vendor == PCI_VENDOR_ID_ATI) { + adev->link_dev = adev->link_partner; + adev->link_partner = pci_upstream_bridge(adev->link_dev); } - - return parent; } /** @@ -1981,7 +1980,6 @@ static struct pci_dev *amdgpu_device_find_parent(struct amdgpu_device *adev) static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) { struct amdgpu_ip_block *ip_block; - struct pci_dev *parent; bool total, skip_bios, early_full_gpu_access = false; uint32_t bios_flags; int i, r; @@ -2077,10 +2075,9 @@ static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) !dev_is_removable(&adev->pdev->dev)) adev->flags |= AMD_IS_PX; - if (!(adev->flags & AMD_IS_APU)) { - parent = amdgpu_device_find_parent(adev); - adev->has_pr3 = parent ? pci_pr3_present(parent) : false; - } + if (!(adev->flags & AMD_IS_APU)) + adev->has_pr3 = adev->link_partner && + pci_pr3_present(adev->link_partner); adev->pm.pp_feature = amdgpu_pp_feature_mask; if (amdgpu_sriov_vf(adev) || sched_policy == KFD_SCHED_POLICY_NO_HWS) @@ -3776,6 +3773,7 @@ int amdgpu_device_init(struct amdgpu_device *adev, adev->shutdown = false; adev->flags = flags; + amdgpu_device_init_pcie_links(adev); if (amdgpu_force_asic_type >= 0 && amdgpu_force_asic_type < CHIP_LAST) adev->asic_type = amdgpu_force_asic_type; @@ -5872,11 +5870,9 @@ static void amdgpu_device_partner_bandwidth(struct amdgpu_device *adev, *width = PCIE_LNK_WIDTH_UNKNOWN; if (amdgpu_device_pcie_dynamic_switching_supported(adev)) { - struct pci_dev *parent = amdgpu_device_find_parent(adev); - - if (parent) { - *speed = pcie_get_speed_cap(parent); - *width = pcie_get_width_cap(parent); + if (adev->link_partner) { + *speed = pcie_get_speed_cap(adev->link_partner); + *width = pcie_get_width_cap(adev->link_partner); } } else { /* use the current speeds rather than max if switching is not supported */ @@ -5898,21 +5894,11 @@ static void amdgpu_device_gpu_bandwidth(struct amdgpu_device *adev, enum pci_bus_speed *speed, enum pcie_link_width *width) { - struct pci_dev *parent = adev->pdev; - if (!speed || !width) return; - /* use the device itself */ - *speed = pcie_get_speed_cap(adev->pdev); - *width = pcie_get_width_cap(adev->pdev); - - /* use the link outside the device */ - parent = amdgpu_device_find_parent(adev); - if (parent) { - *speed = pcie_get_speed_cap(parent); - *width = pcie_get_width_cap(parent); - } + *speed = pcie_get_speed_cap(adev->link_dev); + *width = pcie_get_width_cap(adev->link_dev); } /** From c883d0a132d430ef7ebb23fd94323be94d0fbdb8 Mon Sep 17 00:00:00 2001 From: David Francis Date: Wed, 5 Aug 2026 09:51:35 -0400 Subject: [PATCH 0711/1417] drm/amdkfd: Avoid integer underflow with ffs in EOP ring size calc The low 6 bits of cp_hqd_eop_control store the base-2 logarithm of the EOP ring size. This was calculated as ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1 But ffs can in theory return 1 or 0, so this could underflow (although in practice the ring buffer size cannot be less than 4096). Change this to ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4) using properties of logarithms. Reviewed-by: Kent Russell Signed-off-by: David Francis Signed-off-by: Alex Deucher (cherry picked from commit 4f18c56630383c14bfc6b2d65f88f2f895d2121a) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c index e034da638c07a3..4f8a8a1a61860e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c @@ -204,7 +204,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c index 350fcbbba4b295..bf015dc5b8684d 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c @@ -242,7 +242,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c index 63f25a60baa5be..6ea09b031caf8f 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c @@ -217,7 +217,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c index 708bbb08c089de..c709db0210ced8 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c @@ -295,7 +295,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = From 8ee521b8b189799e361d4233c5180ba56656d4d4 Mon Sep 17 00:00:00 2001 From: David Francis Date: Wed, 5 Aug 2026 09:16:51 -0400 Subject: [PATCH 0712/1417] drm/amdkfd: Avoid integer underflow in EOP ring size calculation. The low 6 bits of cp_hqd_eop_control store the base-2 logarithm of the EOP ring size. This was calculated as order_base_2(q->eop_ring_buffer_size / 4) - 1 But order_base_2 can in theory return 0, so this could underflow (although in practice the ring buffer size cannot be less than 4096). Change this to order_base_2(q->eop_ring_buffer_size / 8) using properties of logarithms. Also add to the above comment to make the mathematics more clear. Reviewed-by: Kent Russell Signed-off-by: David Francis Signed-off-by: Alex Deucher (cherry picked from commit f0f43fcf8b2b3a924cad9444340921c96ed5f634) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c | 6 +++++- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c index b95720198e28d5..6e6bc1ec0b6459 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c @@ -285,6 +285,10 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, 1 << CP_HQD_IB_CONTROL__IB_EXE_DISABLE__SHIFT; /* + * The lowest 6 bits of eop_control store the EOP ring size. If + * their value is X, the ring size is 2^(X + 1) dwords, or + * 2^(X + 3) bytes. + * * HW does not clamp this field correctly. Maximum EOP queue size * is constrained by per-SE EOP done signal count, which is 8-bit. * Limit is 0xFF EOP entries (= 0x7F8 dwords). CP will not submit @@ -296,7 +300,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? - min(0xA, order_base_2(q->eop_ring_buffer_size / 4) - 1) : 0; + min(0xA, order_base_2(q->eop_ring_buffer_size / 8)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c index 60b87a5006983b..029572548c1494 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c @@ -208,6 +208,9 @@ static void __update_mqd(struct mqd_manager *mm, void *mqd, mtype << CP_HQD_IB_CONTROL__MTYPE__SHIFT; /* + * The lowest 6 bits of eop_control store the EOP ring size. If + * their value is X, the ring size is 2^(X + 1) dwords, or + * 2^(X + 3) bytes. * HW does not clamp this field correctly. Maximum EOP queue size * is constrained by per-SE EOP done signal count, which is 8-bit. * Limit is 0xFF EOP entries (= 0x7F8 dwords). CP will not submit @@ -215,7 +218,7 @@ static void __update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control |= q->eop_ring_buffer_size ? min(0xA, - order_base_2(q->eop_ring_buffer_size / 4) - 1) : 0; + order_base_2(q->eop_ring_buffer_size / 8)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = From 0d2f4cfa564355fcbbc71498fd8ec09243036109 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Tue, 15 Sep 2026 14:07:39 +0530 Subject: [PATCH 0713/1417] drm/amd/display: Fix NULL dereference in dcn50/dcn60 init_hw dc->clk_mgr is checked for NULL earlier in dcn50_init_hw() and dcn60_init_hw(), but dcn50_initialize_min_clocks() and dcn401_initialize_min_clocks() are called without any guard, causing Smatch to report potential NULL dereferences. Guard both call sites with the same pattern used throughout both functions: if (dc->clk_mgr && dc->clk_mgr->funcs) Also fix dcn50_initialize_min_clocks() which calls get_dispclk_from_dentist without checking the function pointer, unlike the dcn401 equivalent which guards that call. Fix kernel-doc in dcn60_hwseq.c by adding missing parameter descriptions for @probe in dcn60_update_probe_status() and @type in is_probe_measurement_type_for_hubbub(). Fixes: 7f7d7ea1fa51 ("drm/amd/display: Add new sources for DCN6") Reported-by: Dan Carpenter Cc: Aurabindo Pillai Cc: Ivan Lipski Cc: Dan Wheeler Cc: Roman Li Cc: Alex Hung Cc: Tom Chung Signed-off-by: Srinivasan Shanmugam Reviewed-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit 325c9a827cdd748e126eafeadaffc556204773d2) --- drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c | 6 ++++-- drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c index a7f8fd03faea54..e549556b967967 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c @@ -67,7 +67,8 @@ static void dcn50_initialize_min_clocks(struct dc *dc) * audio corruption. Read current DISPCLK from DENTIST and request the same * freq to ensure that the timing is valid and unchanged. */ - clocks->dispclk_khz = dc->clk_mgr->funcs->get_dispclk_from_dentist(dc->clk_mgr); + if (dc->clk_mgr->funcs->get_dispclk_from_dentist) + clocks->dispclk_khz = dc->clk_mgr->funcs->get_dispclk_from_dentist(dc->clk_mgr); } clocks->ref_dtbclk_khz = dc->clk_mgr->bw_params->clk_table.entries[0].dtbclk_mhz * 1000; clocks->fclk_p_state_change_support = true; @@ -639,7 +640,8 @@ void dcn50_init_hw(struct dc *dc) dc->res_pool->hubbub->funcs->allow_self_refresh_control(dc->res_pool->hubbub, !dc->res_pool->hubbub->ctx->dc->debug.disable_stutter); - dcn50_initialize_min_clocks(dc); + if (dc->clk_mgr && dc->clk_mgr->funcs) + dcn50_initialize_min_clocks(dc); /* On HW init, allow idle optimizations after pipes have been turned off. * diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c index 72c2d3ca52f637..61ad6efa7a7456 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c @@ -643,7 +643,8 @@ void dcn60_init_hw(struct dc *dc) dc->res_pool->hubbub->funcs->allow_self_refresh_control(dc->res_pool->hubbub, !dc->res_pool->hubbub->ctx->dc->debug.disable_stutter); - dcn401_initialize_min_clocks(dc); + if (dc->clk_mgr && dc->clk_mgr->funcs) + dcn401_initialize_min_clocks(dc); /* On HW init, allow idle optimizations after pipes have been turned off. * @@ -1001,6 +1002,7 @@ static void dcn60_build_hubbub_perfmon_sequence( /** * dcn60_update_probe_status - Set the valid flag on a latched probe result. * @status: result sink whose u was written by the GET BLS step during execute + * @probe: current probe state used to determine measurement type and validity */ static void dcn60_update_probe_status(struct dc_probe_status *status) { @@ -1024,6 +1026,7 @@ static void dcn60_update_probe_status(struct dc_probe_status *status) /** * is_probe_measurement_type_for_hubbub - Returns true if the probe type is * served by the hubbub perfmon block on DCN60. + * @type: the probe measurement type to classify */ static bool is_probe_measurement_type_for_hubbub(enum dc_probe_type type) { From d5212d2d50f36a132568534ca637b56ae50f3f02 Mon Sep 17 00:00:00 2001 From: Khanh Le Date: Thu, 17 Sep 2026 03:02:42 +0000 Subject: [PATCH 0714/1417] ASoC: ak4619: Add suspend and resume callbacks After resuming from S2R, the register cache is out of sync with the hardware. Implement suspend and resume callbacks to properly restore the configuration of the codec on resume. Signed-off-by: Khanh Le Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87a4pgzijx.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/codecs/ak4619.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/sound/soc/codecs/ak4619.c b/sound/soc/codecs/ak4619.c index d9c9f6b200284a..5252b30669d48b 100644 --- a/sound/soc/codecs/ak4619.c +++ b/sound/soc/codecs/ak4619.c @@ -799,7 +799,26 @@ static const struct snd_soc_dai_ops ak4619_dai_ops = { .num_auto_selectable_formats = ARRAY_SIZE(ak4619_dai_formats), }; +static int ak4619_suspend(struct snd_soc_component *component) +{ + struct regmap *regmap = dev_get_regmap(component->dev, NULL); + + regcache_cache_only(regmap, true); + regcache_mark_dirty(regmap); + return 0; +} + +static int ak4619_resume(struct snd_soc_component *component) +{ + struct regmap *regmap = dev_get_regmap(component->dev, NULL); + + regcache_cache_only(regmap, false); + return regcache_sync(regmap); +} + static const struct snd_soc_component_driver soc_component_dev_ak4619 = { + .suspend = ak4619_suspend, + .resume = ak4619_resume, .set_bias_level = ak4619_set_bias_level, .controls = ak4619_snd_controls, .num_controls = ARRAY_SIZE(ak4619_snd_controls), From 7f9caa70aef0950e06d395ca0035831214d88187 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 15 Sep 2026 12:51:24 -0500 Subject: [PATCH 0715/1417] drm/amdgpu: Skip KFD mapping clear before initialization amdgpu_amdkfd_clear_kfd_mapping() assumes that a non-NULL kfd_dev has a fully populated node array. This is not true when KFD device initialization fails after probe. For example, kgd2kfd_device_init() sets num_nodes before checking PCIe atomics support. On Polaris systems without the required atomics, it returns before allocating nodes[0], but the kfd_dev remains attached to the amdgpu device. A later GPU reset then dereferences nodes[0]->id. Require the authoritative KFD initialization flag before walking the node array, matching the existing KFD reset and teardown paths. Fixes: 70cadefcc616 ("drm/amdgpu: unmap all user mappings of framebuffer and doorbell before mode1 reset") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5833 Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 4ac1835823c47903fbb278bbf474773c46f59edc) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index 816d8817f0b2fe..054870e9078d73 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -330,7 +330,7 @@ void amdgpu_amdkfd_clear_kfd_mapping(struct amdgpu_device *adev) struct kfd_dev *kfd = adev->kfd.dev; unsigned int i; - if (!kfd) + if (!kfd || !kfd->init_complete) return; for (i = 0; i < kfd->num_nodes; i++) { From 5155002b03b24ba3ef91c5c313b8cf0171b24904 Mon Sep 17 00:00:00 2001 From: Chengjun Yao Date: Tue, 8 Sep 2026 10:15:43 +0800 Subject: [PATCH 0716/1417] drm/amdgpu: fix rmmio iounmap skipped on device removal amdgpu_pci_remove() calls drm_dev_unplug() before fini_sw(), so drm_dev_enter() is already false there and the iounmap() guarded by it is skipped. This .remove path runs on both hot-unplug and plain rmmod, so the register BAR ioremap mapping leaks one instance per unload. Unmap rmmio unconditionally (guard only on non-NULL) and drop the now unused idx. Fixes: 62d5f9f7110a ("drm/amdgpu: Unmap MMIO mappings when device is not unplugged") Signed-off-by: Chengjun Yao Reviewed-by: Asad Kamal Signed-off-by: Alex Deucher (cherry picked from commit dd6f86a97260e5207d3329ad03aa89fdad61b1e6) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 933804349dbf32..9269e780feb730 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -4335,7 +4335,7 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev) void amdgpu_device_fini_sw(struct amdgpu_device *adev) { - int i, idx; + int i; bool px; amdgpu_device_ip_fini(adev); @@ -4377,11 +4377,9 @@ void amdgpu_device_fini_sw(struct amdgpu_device *adev) if ((adev->pdev->class >> 8) == PCI_CLASS_DISPLAY_VGA) vga_client_unregister(adev->pdev); - if (drm_dev_enter(adev_to_drm(adev), &idx)) { - + if (adev->rmmio) { iounmap(adev->rmmio); adev->rmmio = NULL; - drm_dev_exit(idx); } if (IS_ENABLED(CONFIG_PERF_EVENTS)) From 2ac2fe765ef475f409616ac0b57c4a3922749b0f Mon Sep 17 00:00:00 2001 From: Francis Marlou Pacaro Date: Fri, 4 Sep 2026 08:20:21 +0800 Subject: [PATCH 0717/1417] drm/amd/display: fix MALL hysteresis timer underflow at high refresh rates dcn30_apply_idle_power_optimizations() derives the MALL frame cache hysteresis timer with tmr_delay = (uint32_t)(div_u64(..., denom) - 64LL); div_u64() returns a u64, so when the quotient is smaller than 64 the subtraction wraps instead of going negative and tmr_delay ends up huge. The loop that follows tries to squeeze it into the 6 bit register field by doubling denom, but that only makes the quotient smaller, so tmr_delay can never converge. tmr_scale is bumped past 3 and the function gives up with /* Delay exceeds range of hysteresis timer */ ASSERT(false); even though the requested delay is too *short* to encode, not too long. With mall_additional_timer_percent left at its default of 0, the quotient drops below 64 once the refresh rate used for the calculation goes above ~243 Hz. Every DCN 3.0 display above that loses MALL static screen entirely and splats a WARN once per boot. Reproduced on Navi 23 (RX 6600) driving 1920x1080, resetting /sys/kernel/debug/clear_warn_once between modes: refresh MALL ASSERT 144 Hz enabled no 240 Hz enabled no 280 Hz skipped yes 360 Hz skipped yes Commit 3bb68cec4db8 ("drm/amd/display: Add Overflow check to skip MALL") already covered the other end of the range, where a large stutter period makes the delay too long to encode. Cover the short end by clamping to 0, which selects the shortest hysteresis the register can express, 65.28us * 64 = ~4.18ms. That is marginally longer than what the formula asks for at these refresh rates, and erring long is the safe direction: it only delays MALL entry, it can never enter early. The numerator does not change between iterations, only denom does, so compute it once and keep both call sites inside 100 columns. The genuinely out of range case at very low refresh rates still reaches the ASSERT, which is where it belongs. Fixes: 52f2e83e2fe5 ("drm/amdgpu/display: add MALL support (v2)") Signed-off-by: Francis Marlou Pacaro Reviewed-by: Leo Li Signed-off-by: Alex Deucher (cherry picked from commit 387550e53e1405f1f960b62b22f8783db17c8e1d) --- .../gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c index cb163902e12e7b..d669b47af12032 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c @@ -1064,10 +1064,12 @@ bool dcn30_apply_idle_power_optimizations(struct dc *dc, bool enable) */ unsigned int denom = refresh_hz * 6528; unsigned int stutter_period = dc->current_state->perf_params.stutter_period_us; + uint64_t num = (1000000LL + 2 * stutter_period * refresh_hz) * + (100LL + dc->debug.mall_additional_timer_percent); + uint64_t tmr_ticks; - tmr_delay = (uint32_t)(div_u64(((1000000LL + 2 * stutter_period * refresh_hz) * - (100LL + dc->debug.mall_additional_timer_percent) + denom - 1), - denom) - 64LL); + tmr_ticks = div_u64(num + denom - 1, denom); + tmr_delay = tmr_ticks > 64 ? (uint32_t)(tmr_ticks - 64) : 0; /* In some cases the stutter period is really big (tiny modes) in these * cases MALL cant be enabled, So skip these cases to avoid a ASSERT() @@ -1089,9 +1091,8 @@ bool dcn30_apply_idle_power_optimizations(struct dc *dc, bool enable) } denom *= 2; - tmr_delay = (uint32_t)(div_u64(((1000000LL + 2 * stutter_period * refresh_hz) * - (100LL + dc->debug.mall_additional_timer_percent) + denom - 1), - denom) - 64LL); + tmr_ticks = div_u64(num + denom - 1, denom); + tmr_delay = tmr_ticks > 64 ? (uint32_t)(tmr_ticks - 64) : 0; } /* Copy HW cursor */ From df5cdc2c832ca4e8a6d774596b9005558761a403 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 16 Sep 2026 12:00:24 -1000 Subject: [PATCH 0718/1417] sched_ext: Derive SCX_RQ_IN_WAKEUP from the core enqueue flags schedule_deferred_locked() skips scheduling a deferred action while SCX_RQ_IN_WAKEUP is set and relies on the task_woken_scx() call that follows a wakeup enqueue to run it. enqueue_task_scx() sets the flag from the merged enqueue flags, which include the flags stashed for a remote activation. move_remote_task_to_local_dsq() thus sets SCX_RQ_IN_WAKEUP on the destination rq when the moved task was woken up, although no task_woken_scx() follows that activation. An IMMED insert into a busy destination requests a local reenqueue during that enqueue. The request gets linked but not scheduled and stays pending until an unrelated wakeup or preemption on that CPU runs the deferred actions. The IMMED task sits behind the running task in the meantime. If nothing runs them before the scheduler is disabled, the request outlives the scheduler and points into its freed per-cpu area, which the next scheduler dereferences from run_deferred(). Test the core enqueue flags for the wakeup bit. Only the core's wakeup path is followed by task_woken_scx(). Fixes: 57ccf5ccdc56 ("sched_ext: Fix enqueue_task_scx() truncation of upper enqueue flags") Cc: stable@vger.kernel.org # v7.1+ Reported-by: Andrea Righi Link: https://lore.kernel.org/all/20260916145807.3250167-1-arighi@nvidia.com/ Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 725b890fcab96d..e207ccd17164f3 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2141,7 +2141,12 @@ static void enqueue_task_scx(struct rq *rq, struct task_struct *p, int core_enq_ int sticky_cpu = p->scx.sticky_cpu; u64 enq_flags = core_enq_flags | rq->scx.remote_activate_enq_flags; - if (enq_flags & ENQUEUE_WAKEUP) + /* + * SCX_RQ_IN_WAKEUP promises a task_woken_scx() call once this enqueue + * returns. Only the core's wakeup path delivers one. The flags stashed + * for a remote activation may carry the wakeup bit without it. + */ + if (core_enq_flags & ENQUEUE_WAKEUP) rq->scx.flags |= SCX_RQ_IN_WAKEUP; /* From 67b4411538c8341692548429d43256f25be99f7a Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 18:00:36 +0000 Subject: [PATCH 0719/1417] drm/nouveau: Fix bridge reference leak in nv1a_ram_new() pci_get_domain_bus_and_slot() takes a reference to the PCI device, which is never released once the memory size has been read from its config space. Drop the reference before returning. Fixes: 2fa6d6cdaf283c05 ("drm/nouveau: deprecate pci_get_bus_and_slot()") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260916180036.2090118-1-vulab@iscas.ac.cn --- drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c index 18241c6ba5fa36..4d52a158f3205b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c @@ -51,6 +51,8 @@ nv1a_ram_new(struct nvkm_fb *fb, struct nvkm_ram **pram) mib = ((mem >> 4) & 127) + 1; } + pci_dev_put(bridge); + return nvkm_ram_new_(&nv04_ram_func, fb, NVKM_RAM_TYPE_STOLEN, mib * 1024 * 1024, pram); } From cb86607ada73185f321baa7f9d94a08a3a40bbf5 Mon Sep 17 00:00:00 2001 From: fangqiurong Date: Thu, 17 Sep 2026 15:52:41 +0800 Subject: [PATCH 0720/1417] sched_ext: Don't run ops.dequeue() with a DSQ lock held ops.dequeue() is invoked with the source user DSQ's lock still held on the consume and move paths (scx_consume_dispatch_q(), move_task_between_dsqs()). A BPF scheduler which locks the source user DSQ from ops.dequeue() - e.g. by iterating it with bpf_iter_scx_dsq - self-deadlocks. ops.dequeue() can only call the "any" kfuncs and none of them can lock a builtin DSQ, so the global and bypass paths can't deadlock; however, all DSQ locks share one lockdep class, so iterating any user DSQ from ops.dequeue() on those paths trips the recursion check. Move the invocation after the DSQ unlock on all three paths. SCX_TASK_IN_CUSTODY is cleared under the lock serializing the transfer so that the callback is invoked exactly once. Fixes: ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") Cc: stable@vger.kernel.org # v7.1+ Acked-by: Andrea Righi Signed-off-by: fangqiurong Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 44 ++++++++++++++++++------------------- kernel/sched/ext/internal.h | 3 +-- kernel/sched/ext/sub.c | 10 ++++----- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index e207ccd17164f3..f568fd9973f61f 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -1499,27 +1499,22 @@ static inline bool task_scx_migrating(struct task_struct *p) return p->scx.sticky_cpu >= 0; } -/* - * Call ops.dequeue() if the task is in BPF custody and not migrating. - * Clears %SCX_TASK_IN_CUSTODY when the callback is invoked. - */ -static void call_task_dequeue(struct scx_sched *sch, struct rq *rq, - struct task_struct *p, u64 deq_flags) +/* Must be called under the lock serializing @p's custody transfers. */ +static bool task_leave_custody(struct task_struct *p) { if (!(p->scx.flags & SCX_TASK_IN_CUSTODY) || task_scx_migrating(p)) - return; - - if (SCX_HAS_OP(sch, dequeue)) - SCX_CALL_OP_TASK(sch, dequeue, rq, p, deq_flags); + return false; p->scx.flags &= ~SCX_TASK_IN_CUSTODY; + return true; } static void rq_owned_post_enq(struct scx_sched *sch, struct rq *rq, struct scx_dispatch_q *dsq, struct task_struct *p, u64 enq_flags) { - call_task_dequeue(sch, rq, p, 0); + if (task_leave_custody(p) && SCX_HAS_OP(sch, dequeue)) + SCX_CALL_OP_TASK(sch, dequeue, rq, p, 0); /* * Only local inserts get the wakeup treatment below. Rejects kick the @@ -1705,20 +1700,28 @@ static void scx_dispatch_enqueue(struct scx_sched *sch, struct rq *rq, if (is_rq_owned) { rq_owned_post_enq(sch, rq, dsq, p, enq_flags); } else { + bool call_dequeue = false; + /* * Global and bypass DSQs are terminal - the task leaves the - * scheduler's custody, so ops.dequeue() fires here. It can run + * scheduler's custody, so ops.dequeue() fires. It can run * without @p's rq lock (finish_dispatch() passes the dispatch * rq); that's safe because dequeue_task_scx() waits on * SCX_OPSS_DISPATCHING (see the ops_state note above) and so * can't race it. A non-terminal DSQ keeps the task in custody. + * The custody transfer happens under @dsq->lock so that later + * consumers see the flag clear; the callback runs after + * @dsq->lock is dropped because it may lock a DSQ itself. */ if (dsq->id == SCX_DSQ_GLOBAL || dsq->id == SCX_DSQ_BYPASS) - call_task_dequeue(sch, rq, p, 0); + call_dequeue = task_leave_custody(p); else p->scx.flags |= SCX_TASK_IN_CUSTODY; raw_spin_unlock(&dsq->lock); + + if (call_dequeue && SCX_HAS_OP(sch, dequeue)) + SCX_CALL_OP_TASK(sch, dequeue, rq, p, 0); } /* @@ -2215,7 +2218,7 @@ static void ops_dequeue(struct rq *rq, struct task_struct *p, u64 deq_flags) /* * A queued task must always be in BPF scheduler's custody. If * SCX_TASK_IN_CUSTODY is clear, finish_dispatch() on another - * CPU has already passed call_task_dequeue() (which clears the + * CPU has already passed task_leave_custody() (which clears the * flag), but has not yet written SCX_OPSS_NONE. That final * store does not require this rq's lock, so retrying with * cpu_relax() is bounded: we will observe NONE (or DISPATCHING, @@ -2263,7 +2266,8 @@ static void ops_dequeue(struct rq *rq, struct task_struct *p, u64 deq_flags) * NONE but the task may still have %SCX_TASK_IN_CUSTODY set until * it is enqueued on the destination. */ - call_task_dequeue(sch, rq, p, deq_flags); + if (task_leave_custody(p) && SCX_HAS_OP(sch, dequeue)) + SCX_CALL_OP_TASK(sch, dequeue, rq, p, deq_flags); } static bool dequeue_task_scx(struct rq *rq, struct task_struct *p, int core_deq_flags) @@ -2379,14 +2383,10 @@ static void wakeup_preempt_scx(struct rq *rq, struct task_struct *p, int wake_fl } void scx_move_local_task_to_local_dsq(struct scx_sched *sch, struct task_struct *p, - u64 enq_flags, struct scx_dispatch_q *src_dsq, - struct rq *dst_rq) + u64 enq_flags, struct rq *dst_rq) { struct scx_dispatch_q *dst_dsq = scx_resolve_local_dsq(sch, dst_rq, p, &enq_flags); - /* @p is on @dst_rq, an rq-owned @src_dsq is covered by the rq lock */ - if (!dsq_is_rq_owned(src_dsq)) - lockdep_assert_held(&src_dsq->lock); lockdep_assert_rq_held(dst_rq); WARN_ON_ONCE(p->scx.holding_cpu >= 0); @@ -2634,8 +2634,8 @@ static struct rq *move_task_between_dsqs(struct scx_sched *sch, /* @p is going from a non-local DSQ to a local DSQ */ if (src_rq == dst_rq) { scx_task_unlink_from_dsq(p, src_dsq); - scx_move_local_task_to_local_dsq(sch, p, enq_flags, src_dsq, dst_rq); raw_spin_unlock(&src_dsq->lock); + scx_move_local_task_to_local_dsq(sch, p, enq_flags, dst_rq); } else { raw_spin_unlock(&src_dsq->lock); move_remote_task_to_local_dsq(sch, p, enq_flags, src_rq, dst_rq); @@ -2685,8 +2685,8 @@ bool scx_consume_dispatch_q(struct scx_sched *sch, struct rq *rq, if (rq == task_rq) { scx_task_unlink_from_dsq(p, dsq); - scx_move_local_task_to_local_dsq(sch, p, enq_flags, dsq, rq); raw_spin_unlock(&dsq->lock); + scx_move_local_task_to_local_dsq(sch, p, enq_flags, rq); return true; } diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 115c96fbf9322f..d150de10a5c944 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -2065,8 +2065,7 @@ void scx_dispatch_dequeue(struct rq *rq, struct task_struct *p); void scx_do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags, int sticky_cpu); void scx_move_local_task_to_local_dsq(struct scx_sched *sch, struct task_struct *p, - u64 enq_flags, struct scx_dispatch_q *src_dsq, - struct rq *dst_rq); + u64 enq_flags, struct rq *dst_rq); bool scx_consume_dispatch_q(struct scx_sched *sch, struct rq *rq, struct scx_dispatch_q *dsq, u64 enq_flags); bool scx_consume_global_dsq(struct scx_sched *sch, struct rq *rq); diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index a30c965b175d69..3036190b06628c 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -555,8 +555,8 @@ static void scx_rescue_timerfn(struct timer_list *timer) scx.dsq_list.node); scx_task_unlink_from_dsq(p, &rq->scx.rescue.dsq); scx_rescue_admit(rq, p, slice); - scx_move_local_task_to_local_dsq(scx_task_sched(p), p, SCX_ENQ_IGNORE_CAPS, - &rq->scx.rescue.dsq, rq); + scx_move_local_task_to_local_dsq(scx_task_sched(p), p, + SCX_ENQ_IGNORE_CAPS, rq); if (sched_class_above(&ext_sched_class, rq->curr->sched_class)) resched_curr(rq); } else if (p->scx.dsq && rq->scx.rescue.budget > 2 * scx_rescue_quantum_ns) { @@ -572,7 +572,7 @@ static void scx_rescue_timerfn(struct timer_list *timer) scx_task_unlink_from_dsq(p, &rq->scx.local_dsq); scx_move_local_task_to_local_dsq(scx_task_sched(p), p, SCX_ENQ_HEAD | SCX_ENQ_PREEMPT | SCX_ENQ_IGNORE_CAPS, - &rq->scx.local_dsq, rq); + rq); } out_arm: scx_rescue_timer_arm(rq); @@ -596,8 +596,8 @@ void scx_rescue_flush(struct rq *rq) /* and flush out all pending ones */ list_for_each_entry_safe(p, n, &rq->scx.rescue.dsq.list, scx.dsq_list.node) { scx_task_unlink_from_dsq(p, &rq->scx.rescue.dsq); - scx_move_local_task_to_local_dsq(scx_task_sched(p), p, SCX_ENQ_IGNORE_CAPS, - &rq->scx.rescue.dsq, rq); + scx_move_local_task_to_local_dsq(scx_task_sched(p), p, + SCX_ENQ_IGNORE_CAPS, rq); } timer_delete(&rq->scx.rescue.timer); From 9ec7ba20c97d92fa59b351832aeeb934b3b16177 Mon Sep 17 00:00:00 2001 From: fangqiurong Date: Thu, 17 Sep 2026 15:52:42 +0800 Subject: [PATCH 0721/1417] selftests/sched_ext: Test that ops.dequeue() can iterate the consumed DSQ Add a scheduler whose ops.dequeue() iterates the source user DSQ with bpf_iter_scx_dsq. The iteration takes the DSQ's raw spinlock; on a kernel that runs ops.dequeue() while the consume path still holds that lock, the first task consumed self-deadlocks the CPU with IRQs disabled. The watchdog cannot recover from that state, so on an unfixed kernel this test wedges the system instead of failing cleanly. On a fixed kernel the scheduler runs clean and the test passes. Signed-off-by: fangqiurong Signed-off-by: Tejun Heo --- tools/testing/selftests/sched_ext/Makefile | 1 + .../selftests/sched_ext/dequeue_iter.bpf.c | 73 +++++++++++++++++ .../selftests/sched_ext/dequeue_iter.c | 79 +++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 tools/testing/selftests/sched_ext/dequeue_iter.bpf.c create mode 100644 tools/testing/selftests/sched_ext/dequeue_iter.c diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile index 3cfe90e0f34fa2..49897727f535bf 100644 --- a/tools/testing/selftests/sched_ext/Makefile +++ b/tools/testing/selftests/sched_ext/Makefile @@ -164,6 +164,7 @@ all_test_bpfprogs := $(foreach prog,$(wildcard *.bpf.c),$(INCLUDE_DIR)/$(patsubs auto-test-targets := \ create_dsq \ dequeue \ + dequeue_iter \ enq_last_no_enq_fails \ ddsp_bogus_dsq_fail \ ddsp_vtimelocal_fail \ diff --git a/tools/testing/selftests/sched_ext/dequeue_iter.bpf.c b/tools/testing/selftests/sched_ext/dequeue_iter.bpf.c new file mode 100644 index 00000000000000..76c2c71a90b617 --- /dev/null +++ b/tools/testing/selftests/sched_ext/dequeue_iter.bpf.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ops.dequeue() of this scheduler iterates the user DSQ it consumes + * tasks from with bpf_iter_scx_dsq, which takes the DSQ lock. + * On a kernel that still runs ops.dequeue() with that lock held, the + * iteration self-deadlocks the CPU - this test wedges the system on + * unfixed kernels instead of failing cleanly. + * + * Copyright (c) 2026 fangqiurong + */ + +#include + +char _license[] SEC("license") = "GPL"; + +UEI_DEFINE(uei); + +#define TEST_DSQ_ID 1000 + +u64 dq_count; + +s32 BPF_STRUCT_OPS_SLEEPABLE(dequeue_iter_init) +{ + return scx_bpf_create_dsq(TEST_DSQ_ID, -1); +} + +s32 BPF_STRUCT_OPS(dequeue_iter_select_cpu, struct task_struct *p, + s32 prev_cpu, u64 wake_flags) +{ + return prev_cpu; +} + +void BPF_STRUCT_OPS(dequeue_iter_enqueue, struct task_struct *p, u64 enq_flags) +{ + scx_bpf_dsq_insert(p, TEST_DSQ_ID, SCX_SLICE_DFL, enq_flags); +} + +void BPF_STRUCT_OPS(dequeue_iter_dispatch, s32 cpu, struct task_struct *task) +{ + scx_bpf_dsq_move_to_local(TEST_DSQ_ID, 0); +} + +void BPF_STRUCT_OPS(dequeue_iter_dequeue, struct task_struct *p, u64 deq_flags) +{ + struct bpf_iter_scx_dsq it; + struct task_struct *t; + + if (!bpf_iter_scx_dsq_new(&it, TEST_DSQ_ID, 0)) { + while ((t = bpf_iter_scx_dsq_next(&it))) + ; + } + bpf_iter_scx_dsq_destroy(&it); + + __sync_fetch_and_add(&dq_count, 1); +} + +void BPF_STRUCT_OPS(dequeue_iter_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); + scx_bpf_destroy_dsq(TEST_DSQ_ID); +} + +SEC(".struct_ops.link") +struct sched_ext_ops dequeue_iter_ops = { + .init = (void *)dequeue_iter_init, + .select_cpu = (void *)dequeue_iter_select_cpu, + .enqueue = (void *)dequeue_iter_enqueue, + .dispatch = (void *)dequeue_iter_dispatch, + .dequeue = (void *)dequeue_iter_dequeue, + .exit = (void *)dequeue_iter_exit, + .timeout_ms = 1000U, + .name = "dequeue_iter", +}; diff --git a/tools/testing/selftests/sched_ext/dequeue_iter.c b/tools/testing/selftests/sched_ext/dequeue_iter.c new file mode 100644 index 00000000000000..f60711bf3b3952 --- /dev/null +++ b/tools/testing/selftests/sched_ext/dequeue_iter.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 fangqiurong + */ +#include +#include +#include +#include +#include "dequeue_iter.bpf.skel.h" +#include "scx_test.h" + +#define DQ_TARGET 10 +#define DQ_DEADLINE_MS 3000 + +static unsigned long long now_ms(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + return ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000; +} + +static enum scx_test_status setup(void **ctx) +{ + struct dequeue_iter *skel; + + skel = dequeue_iter__open(); + SCX_FAIL_IF(!skel, "Failed to open"); + SCX_ENUM_INIT(skel); + SCX_FAIL_IF(dequeue_iter__load(skel), "Failed to load skel"); + + *ctx = skel; + + return SCX_TEST_PASS; +} + +static enum scx_test_status run(void *ctx) +{ + struct dequeue_iter *skel = ctx; + struct bpf_link *link; + unsigned long long end; + + link = bpf_map__attach_struct_ops(skel->maps.dequeue_iter_ops); + SCX_FAIL_IF(!link, "Failed to attach scheduler"); + + end = now_ms() + DQ_DEADLINE_MS; + while (skel->bss->dq_count < DQ_TARGET && !UEI_EXITED(skel, uei) && + now_ms() < end) + usleep(100); + + bpf_link__destroy(link); + + SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG)); + + if (skel->bss->dq_count < DQ_TARGET) { + SCX_ERR("ops.dequeue() fired only %llu times", + (unsigned long long)skel->bss->dq_count); + return SCX_TEST_FAIL; + } + + return SCX_TEST_PASS; +} + +static void cleanup(void *ctx) +{ + struct dequeue_iter *skel = ctx; + + dequeue_iter__destroy(skel); +} + +struct scx_test dequeue_iter = { + .name = "dequeue_iter", + .description = "Verify ops.dequeue() can iterate its source user DSQ", + .setup = setup, + .run = run, + .cleanup = cleanup, +}; +REGISTER_SCX_TEST(&dequeue_iter) From a603fe4e000366d187849667c9afb38da5259632 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Thu, 17 Sep 2026 11:10:01 +0530 Subject: [PATCH 0722/1417] ASoC: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Signed-off-by: Hemanth Selam Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260917054001.15728-1-hemanth.selam@gmail.com Signed-off-by: Mark Brown --- sound/soc/atmel/atmel-pcm-dma.c | 2 +- sound/soc/bcm/cygnus-ssp.h | 2 +- sound/soc/codecs/adau1701.c | 4 ++-- sound/soc/codecs/cs35l33.c | 2 +- sound/soc/codecs/cs35l34.c | 2 +- sound/soc/codecs/cs42l42.c | 2 +- sound/soc/codecs/cs42l73.c | 4 ++-- sound/soc/codecs/cx20442.c | 2 +- sound/soc/codecs/da7210.c | 2 +- sound/soc/codecs/hdac_hda.c | 2 +- sound/soc/codecs/hdac_hdmi.c | 2 +- sound/soc/codecs/max98927.c | 4 ++-- sound/soc/codecs/mt6359.c | 8 ++++---- sound/soc/codecs/nau8825.c | 10 +++++----- sound/soc/codecs/rt1320-sdw.c | 2 +- sound/soc/codecs/rt5631.h | 8 ++++---- sound/soc/codecs/rt5640.c | 2 +- sound/soc/codecs/rt5645.c | 2 +- sound/soc/codecs/rt5651.c | 2 +- sound/soc/codecs/rt5670.c | 2 +- sound/soc/codecs/tlv320dac33.c | 2 +- sound/soc/intel/atom/sst-mfld-dsp.h | 2 +- sound/soc/intel/atom/sst/sst.h | 2 +- sound/soc/intel/boards/sof_cirrus_common.c | 2 +- sound/soc/qcom/qdsp6/q6asm-dai.c | 2 +- sound/soc/ti/davinci-i2s.c | 2 +- sound/soc/ti/davinci-mcasp.c | 2 +- sound/soc/ti/omap-mcbsp.c | 2 +- sound/soc/uniphier/aio.h | 2 +- sound/soc/ux500/ux500_msp_i2s.c | 2 +- 30 files changed, 43 insertions(+), 43 deletions(-) diff --git a/sound/soc/atmel/atmel-pcm-dma.c b/sound/soc/atmel/atmel-pcm-dma.c index 7306e04da513b1..e4b73ae3df4f4c 100644 --- a/sound/soc/atmel/atmel-pcm-dma.c +++ b/sound/soc/atmel/atmel-pcm-dma.c @@ -47,7 +47,7 @@ static const struct snd_pcm_hardware atmel_pcm_dma_hardware = { * atmel_pcm_dma_irq: SSC interrupt handler for DMAENGINE enabled SSC * * We use DMAENGINE to send/receive data to/from SSC so this ISR is only to - * check if any overrun occured. + * check if any overrun occurred. */ static void atmel_pcm_dma_irq(u32 ssc_sr, struct snd_pcm_substream *substream) diff --git a/sound/soc/bcm/cygnus-ssp.h b/sound/soc/bcm/cygnus-ssp.h index 4925e03c3c3045..7848e15efbe148 100644 --- a/sound/soc/bcm/cygnus-ssp.h +++ b/sound/soc/bcm/cygnus-ssp.h @@ -27,7 +27,7 @@ struct ringbuf_regs { unsigned wraddr; unsigned baseaddr; unsigned endaddr; - unsigned fmark; /* freemark for play, fullmark for caputure */ + unsigned fmark; /* freemark for play, fullmark for capture */ unsigned period_bytes; unsigned buf_size; }; diff --git a/sound/soc/codecs/adau1701.c b/sound/soc/codecs/adau1701.c index 505d52c636278b..4afbf46ffaa397 100644 --- a/sound/soc/codecs/adau1701.c +++ b/sound/soc/codecs/adau1701.c @@ -4,7 +4,7 @@ * * Copyright 2011 Analog Devices Inc. * Author: Lars-Peter Clausen - * based on an inital version by Cliff Cai + * based on an initial version by Cliff Cai */ #include @@ -699,7 +699,7 @@ static int adau1701_probe(struct snd_soc_component *component) */ adau1701->pll_clkdiv = ADAU1707_CLKDIV_UNSET; - /* initalize with pre-configured pll mode settings */ + /* initialize with pre-configured pll mode settings */ ret = adau1701_reset(component, adau1701->pll_clkdiv, 0); if (ret < 0) goto exit_regulators_disable; diff --git a/sound/soc/codecs/cs35l33.c b/sound/soc/codecs/cs35l33.c index d0f9231f5e4428..b8c19f17570197 100644 --- a/sound/soc/codecs/cs35l33.c +++ b/sound/soc/codecs/cs35l33.c @@ -578,7 +578,7 @@ static int cs35l33_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, | CS35L33_X_LOC); } - /* disconnect {vp,vbst}_mon routes: eanble later if set in tx_mask*/ + /* disconnect {vp,vbst}_mon routes: enable later if set in tx_mask*/ snd_soc_dapm_del_routes(dapm, cs35l33_vp_vbst_mon_route, ARRAY_SIZE(cs35l33_vp_vbst_mon_route)); diff --git a/sound/soc/codecs/cs35l34.c b/sound/soc/codecs/cs35l34.c index 3f265bd760f7c9..a72be629a31e55 100644 --- a/sound/soc/codecs/cs35l34.c +++ b/sound/soc/codecs/cs35l34.c @@ -297,7 +297,7 @@ static int cs35l34_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, CS35L34_X_STATE | CS35L34_X_LOC, CS35L34_X_STATE | CS35L34_X_LOC); - /* disconnect {vp,vbst}_mon routes: eanble later if set in tx_mask*/ + /* disconnect {vp,vbst}_mon routes: enable later if set in tx_mask*/ while (slot >= 0) { /* configure VMON_TX_LOC */ if (slot_num == 0) diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index 9e40c03ada4e89..b5d69d5516a664 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -1682,7 +1682,7 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) if (cs42l42->suspended || !cs42l42->init_done) return IRQ_NONE; - /* Read sticky registers to clear interurpt */ + /* Read sticky registers to clear interrupt */ for (i = 0; i < ARRAY_SIZE(stickies); i++) { regmap_read(cs42l42->regmap, irq_params_table[i].status_addr, &(stickies[i])); diff --git a/sound/soc/codecs/cs42l73.c b/sound/soc/codecs/cs42l73.c index 72076ccf4570ee..72c4213ed74bf5 100644 --- a/sound/soc/codecs/cs42l73.c +++ b/sound/soc/codecs/cs42l73.c @@ -761,7 +761,7 @@ static const struct snd_soc_dapm_route cs42l73_audio_map[] = { {"ASPOUTL", "ASP-IP Volume", "ASPL Output Mixer"}, {"ASPOUTR", "ASP-IP Volume", "ASPR Output Mixer"}, - /* Auxillary Capture */ + /* Auxiliary Capture */ {"XSPL Output Mixer", NULL, "Input Left Capture"}, {"XSPR Output Mixer", NULL, "Input Right Capture"}, @@ -1126,7 +1126,7 @@ static int cs42l73_set_bias_level(struct snd_soc_component *component, mdelay(cs42l73->shutdwn_delay); cs42l73->shutdwn_delay = 0; } else { - mdelay(15); /* Min amount of time requred to power + mdelay(15); /* Min amount of time required to power * down. */ } diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index 0dea84cd8ef2d9..d3c7a93702200b 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -200,7 +200,7 @@ static int cx20442_write(struct snd_soc_component *component, unsigned int reg, * Line discpline related code * * Any of the callback functions below can be used in two ways: - * 1) registerd by a machine driver as one of line discipline operations, + * 1) registered by a machine driver as one of line discipline operations, * 2) called from a machine's provided line discipline callback function * in case when extra machine specific code must be run as well. */ diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c index 597510de561a6d..40d759938edcc2 100644 --- a/sound/soc/codecs/da7210.c +++ b/sound/soc/codecs/da7210.c @@ -1158,7 +1158,7 @@ static int da7210_probe(struct snd_soc_component *component) snd_soc_component_write(component, DA7210_PLL_DIV3, DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP); - /* Diable PLL and bypass it */ + /* Disable PLL and bypass it */ snd_soc_component_write(component, DA7210_PLL, DA7210_PLL_FS_48000); /* Activate all enabled subsystem */ diff --git a/sound/soc/codecs/hdac_hda.c b/sound/soc/codecs/hdac_hda.c index 1c06fdbf0e712e..7414be08d3d5ba 100644 --- a/sound/soc/codecs/hdac_hda.c +++ b/sound/soc/codecs/hdac_hda.c @@ -520,7 +520,7 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) /* * hdac_device core already sets the state to active and calls * get_noresume. So enable runtime and set the device to suspend. - * pm_runtime_enable is also called during codec registeration + * pm_runtime_enable is also called during codec registration */ pm_runtime_put(&hdev->dev); pm_runtime_suspend(&hdev->dev); diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c index d9e6fa6d3e9935..b3f677e7ddc962 100644 --- a/sound/soc/codecs/hdac_hdmi.c +++ b/sound/soc/codecs/hdac_hdmi.c @@ -1753,7 +1753,7 @@ static int hdmi_codec_resume(struct device *dev) /* * As the ELD notify callback request is not entertained while the * device is in suspend state. Need to manually check detection of - * all pins here. pin capablity change is not support, so use the + * all pins here. pin capability change is not support, so use the * already set pin caps. * * NOTE: this is safe to call even if the codec doesn't actually resume. diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c index d8dfaefdca7e8f..285c9d1ea72140 100644 --- a/sound/soc/codecs/max98927.c +++ b/sound/soc/codecs/max98927.c @@ -672,7 +672,7 @@ static int max98927_probe(struct snd_soc_component *component) 0x80); regmap_write(max98927->regmap, MAX98927_R0026_PCM_TO_SPK_MONOMIX_B, 0x1); - /* Set inital volume (+13dB) */ + /* Set initial volume (+13dB) */ regmap_write(max98927->regmap, MAX98927_R0036_AMP_VOL_CTRL, 0x38); regmap_write(max98927->regmap, MAX98927_R003C_SPK_GAIN, 0x05); /* Enable DC blocker */ @@ -870,7 +870,7 @@ static int max98927_i2c_probe(struct i2c_client *i2c) /* voltage/current slot configuration */ max98927_slot_config(i2c, max98927); - /* codec registeration */ + /* codec registration */ ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_max98927, max98927_dai, ARRAY_SIZE(max98927_dai)); diff --git a/sound/soc/codecs/mt6359.c b/sound/soc/codecs/mt6359.c index b15bdb15dbb079..c51eae602ca04b 100644 --- a/sound/soc/codecs/mt6359.c +++ b/sound/soc/codecs/mt6359.c @@ -82,7 +82,7 @@ static void mt6359_reset_capture_gpio(struct mt6359_priv *priv) 0x3 << 0, 0x0); } -/* use only when doing mtkaif calibraiton at the boot time */ +/* use only when doing mtkaif calibration at the boot time */ static void mt6359_set_dcxo(struct mt6359_priv *priv, bool enable) { regmap_update_bits(priv->regmap, MT6359_DCXO_CW12, @@ -90,7 +90,7 @@ static void mt6359_set_dcxo(struct mt6359_priv *priv, bool enable) (enable ? 1 : 0) << RG_XO_AUDIO_EN_M_SFT); } -/* use only when doing mtkaif calibraiton at the boot time */ +/* use only when doing mtkaif calibration at the boot time */ static void mt6359_set_clksq(struct mt6359_priv *priv, bool enable) { /* Enable/disable CLKSQ 26MHz */ @@ -99,7 +99,7 @@ static void mt6359_set_clksq(struct mt6359_priv *priv, bool enable) (enable ? 1 : 0) << RG_CLKSQ_EN_SFT); } -/* use only when doing mtkaif calibraiton at the boot time */ +/* use only when doing mtkaif calibration at the boot time */ static void mt6359_set_aud_global_bias(struct mt6359_priv *priv, bool enable) { regmap_update_bits(priv->regmap, MT6359_AUDDEC_ANA_CON13, @@ -107,7 +107,7 @@ static void mt6359_set_aud_global_bias(struct mt6359_priv *priv, bool enable) (enable ? 0 : 1) << RG_AUDGLB_PWRDN_VA32_SFT); } -/* use only when doing mtkaif calibraiton at the boot time */ +/* use only when doing mtkaif calibration at the boot time */ static void mt6359_set_topck(struct mt6359_priv *priv, bool enable) { regmap_update_bits(priv->regmap, MT6359_AUD_TOP_CKPDN_CON0, diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index 5165aeba4b6752..2eb0efc983a03f 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -345,10 +345,10 @@ static u32 nau8825_intlog10_dec3(u32 value) /** * nau8825_xtalk_sidetone - computes cross talk suppression sidetone gain. * - * @sig_org: orignal signal level + * @sig_org: original signal level * @sig_cros: cross talk signal level * - * The orignal and cross talk signal vlues need to be characterized. + * The original and cross talk signal vlues need to be characterized. * Once these values have been characterized, this sidetone value * can be converted to decibel with the equation below. * sidetone = 20 * log (original signal level / crosstalk signal level) @@ -671,11 +671,11 @@ static void nau8825_xtalk_imm_stop(struct nau8825 *nau8825) * Thus, the measurement function has four states to complete whole sequence. * 1. Prepare state : Prepare the resource for detection and transfer to HPR * IMM stat to make JKR1(HPR) impedance measure. - * 2. HPR IMM state : Read out orignal signal level of JKR1(HPR) and transfer + * 2. HPR IMM state : Read out original signal level of JKR1(HPR) and transfer * to HPL IMM state to make JKTIP(HPL) impedance measure. * 3. HPL IMM state : Read out cross talk signal level of JKTIP(HPL) and * transfer to IMM state to determine suppression sidetone gain. - * 4. IMM state : Computes cross talk suppression sidetone gain with orignal + * 4. IMM state : Computes cross talk suppression sidetone gain with original * and cross talk signal level. Apply this gain and then restore codec * configuration. Then transfer to Done state for ending. */ @@ -724,7 +724,7 @@ static void nau8825_xtalk_measure(struct nau8825 *nau8825) nau8825->xtalk_state = NAU8825_XTALK_IMM; break; case NAU8825_XTALK_IMM: - /* In impedance measure state, the orignal and cross talk + /* In impedance measure state, the original and cross talk * signal level vlues are ready. The side tone gain is deter- * mined with these signal level. After all, restore codec * configuration. diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index 444a91755be98a..b13622e54ae067 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -2084,7 +2084,7 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x80); /* RAE run */ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x01); - /* Phase sync eanble */ + /* Phase sync enable */ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); break; } diff --git a/sound/soc/codecs/rt5631.h b/sound/soc/codecs/rt5631.h index 8a6b99a48c7c0f..00734207ac9f43 100644 --- a/sound/soc/codecs/rt5631.h +++ b/sound/soc/codecs/rt5631.h @@ -415,7 +415,7 @@ #define RT5631_ADDA_FILTER_CLK_SEL_256FS (0 << 7) /* 256FS */ #define RT5631_ADDA_FILTER_CLK_SEL_384FS (1 << 7) /* 384FS */ -/* Power managment addition 1 (0x3A) */ +/* Power management addition 1 (0x3A) */ #define RT5631_PWR_MAIN_I2S_EN (0x1 << 15) #define RT5631_PWR_MAIN_I2S_BIT 15 #define RT5631_PWR_CLASS_D (0x1 << 12) @@ -435,7 +435,7 @@ #define RT5631_PWR_DAC_R_TO_MIXER (0x1 << 5) #define RT5631_PWR_DAC_R_TO_MIXER_BIT 5 -/* Power managment addition 2 (0x3B) */ +/* Power management addition 2 (0x3B) */ #define RT5631_PWR_OUTMIXER_L (0x1 << 15) #define RT5631_PWR_OUTMIXER_L_BIT 15 #define RT5631_PWR_OUTMIXER_R (0x1 << 14) @@ -461,7 +461,7 @@ #define RT5631_PWR_PLL2 (0x1 << 0) #define RT5631_PWR_PLL2_BIT 0 -/* Power managment addition 3(0x3C) */ +/* Power management addition 3(0x3C) */ #define RT5631_PWR_VREF (0x1 << 15) #define RT5631_PWR_VREF_BIT 15 #define RT5631_PWR_FAST_VREF_CTRL (0x1 << 14) @@ -489,7 +489,7 @@ #define RT5631_PWR_HP_AMP_DRIVING (0x1 << 0) #define RT5631_PWR_HP_AMP_DRIVING_BIT 0 -/* Power managment addition 4(0x3E) */ +/* Power management addition 4(0x3E) */ #define RT5631_PWR_SPK_L_VOL (0x1 << 15) #define RT5631_PWR_SPK_L_VOL_BIT 15 #define RT5631_PWR_SPK_R_VOL (0x1 << 14) diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c index 4fa1eee8e87f36..03d0ac3359f5e4 100644 --- a/sound/soc/codecs/rt5640.c +++ b/sound/soc/codecs/rt5640.c @@ -1216,7 +1216,7 @@ static const struct snd_soc_dapm_widget rt5640_dapm_widgets[] = { 0, rt5640_spk_l_mix, ARRAY_SIZE(rt5640_spk_l_mix)), SND_SOC_DAPM_MIXER("SPK MIXR", RT5640_PWR_MIXER, RT5640_PWR_SM_R_BIT, 0, rt5640_spk_r_mix, ARRAY_SIZE(rt5640_spk_r_mix)), - /* Ouput Volume */ + /* Output Volume */ SND_SOC_DAPM_PGA("SPKVOL L", RT5640_PWR_VOL, RT5640_PWR_SV_L_BIT, 0, NULL, 0), SND_SOC_DAPM_PGA("SPKVOL R", RT5640_PWR_VOL, diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index bf9f278c3de9d3..bb448254275f65 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -2209,7 +2209,7 @@ static const struct snd_soc_dapm_widget rt5645_dapm_widgets[] = { 0, rt5645_out_l_mix, ARRAY_SIZE(rt5645_out_l_mix)), SND_SOC_DAPM_MIXER("OUT MIXR", RT5645_PWR_MIXER, RT5645_PWR_OM_R_BIT, 0, rt5645_out_r_mix, ARRAY_SIZE(rt5645_out_r_mix)), - /* Ouput Volume */ + /* Output Volume */ SND_SOC_DAPM_SWITCH("SPKVOL L", RT5645_PWR_VOL, RT5645_PWR_SV_L_BIT, 0, &spk_l_vol_control), SND_SOC_DAPM_SWITCH("SPKVOL R", RT5645_PWR_VOL, RT5645_PWR_SV_R_BIT, 0, diff --git a/sound/soc/codecs/rt5651.c b/sound/soc/codecs/rt5651.c index 9723b4c7540cb1..b911757376f5c7 100644 --- a/sound/soc/codecs/rt5651.c +++ b/sound/soc/codecs/rt5651.c @@ -1024,7 +1024,7 @@ static const struct snd_soc_dapm_widget rt5651_dapm_widgets[] = { 0, rt5651_out_l_mix, ARRAY_SIZE(rt5651_out_l_mix)), SND_SOC_DAPM_MIXER("OUT MIXR", RT5651_PWR_MIXER, RT5651_PWR_OM_R_BIT, 0, rt5651_out_r_mix, ARRAY_SIZE(rt5651_out_r_mix)), - /* Ouput Volume */ + /* Output Volume */ SND_SOC_DAPM_SWITCH("OUTVOL L", RT5651_PWR_VOL, RT5651_PWR_OV_L_BIT, 0, &outvol_l_control), SND_SOC_DAPM_SWITCH("OUTVOL R", RT5651_PWR_VOL, diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index f772f093479ad4..62d4e7cea5af35 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -1901,7 +1901,7 @@ static const struct snd_soc_dapm_widget rt5670_dapm_widgets[] = { 0, rt5670_out_l_mix, ARRAY_SIZE(rt5670_out_l_mix)), SND_SOC_DAPM_MIXER("OUT MIXR", RT5670_PWR_MIXER, RT5670_PWR_OM_R_BIT, 0, rt5670_out_r_mix, ARRAY_SIZE(rt5670_out_r_mix)), - /* Ouput Volume */ + /* Output Volume */ SND_SOC_DAPM_MIXER("HPOVOL MIXL", RT5670_PWR_VOL, RT5670_PWR_HV_L_BIT, 0, rt5670_hpvoll_mix, ARRAY_SIZE(rt5670_hpvoll_mix)), diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 55a95b62a6505e..b91ab42882d1da 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -914,7 +914,7 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, /* OSC calibration time */ dac33_write(component, DAC33_CALIB_TIME, 96); - /* adjustment treshold & step */ + /* adjustment threshold & step */ dac33_write(component, DAC33_INT_OSC_CTRL_B, DAC33_ADJTHRSHLD(2) | DAC33_ADJSTEP(1)); diff --git a/sound/soc/intel/atom/sst-mfld-dsp.h b/sound/soc/intel/atom/sst-mfld-dsp.h index c8f0816edb53eb..59606259a06e81 100644 --- a/sound/soc/intel/atom/sst-mfld-dsp.h +++ b/sound/soc/intel/atom/sst-mfld-dsp.h @@ -189,7 +189,7 @@ struct ipc_dsp_hdr { union ipc_header_high { struct { u32 msg_id:8; /* Message ID - Max 256 Message Types */ - u32 task_id:4; /* Task ID associated with this comand */ + u32 task_id:4; /* Task ID associated with this command */ u32 drv_id:4; /* Identifier for the driver to track*/ u32 rsvd1:8; /* Reserved */ u32 result:4; /* Reserved */ diff --git a/sound/soc/intel/atom/sst/sst.h b/sound/soc/intel/atom/sst/sst.h index c43946c5ecee06..e24d9dad7ef63f 100644 --- a/sound/soc/intel/atom/sst/sst.h +++ b/sound/soc/intel/atom/sst/sst.h @@ -182,7 +182,7 @@ struct sst_block { * @pcm_substream : PCM substream * @period_elapsed : PCM period elapsed callback * @sfreq : stream sampling freq - * @cumm_bytes : cummulative bytes decoded + * @cumm_bytes : cumulative bytes decoded */ struct stream_info { unsigned int status; diff --git a/sound/soc/intel/boards/sof_cirrus_common.c b/sound/soc/intel/boards/sof_cirrus_common.c index 88fc6cb2bfd49b..ec4bda40ae00f2 100644 --- a/sound/soc/intel/boards/sof_cirrus_common.c +++ b/sound/soc/intel/boards/sof_cirrus_common.c @@ -151,7 +151,7 @@ static const char * const cs35l41_name_prefixes[] = { "WL", "WR", "TL", "TR" }; * UID 0x2 -> TL * UID 0x3 -> TR * Note: If there are less than 4 Amps, UIDs still map to WL/WR/TL/TR. Dynamic code will only create - * dai links for UIDs which exist, and ignore non-existant ones. Only 2 or 4 amps are expected. + * dai links for UIDs which exist, and ignore non-existent ones. Only 2 or 4 amps are expected. * Return number of codecs found. */ static int cs35l41_compute_codec_conf(void) diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c index 4f09fdd4090584..cd4f82bb8cc706 100644 --- a/sound/soc/qcom/qdsp6/q6asm-dai.c +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -551,7 +551,7 @@ static void compress_event_handler(uint32_t opcode, uint32_t token, prtd->stream_id, CMD_CLOSE); /* - * vaild stream ids start from 1, So we are + * valid stream ids start from 1, So we are * toggling this between 1 and 2. */ prtd->stream_id = (prtd->stream_id == 1 ? 2 : 1); diff --git a/sound/soc/ti/davinci-i2s.c b/sound/soc/ti/davinci-i2s.c index ec7eb2f19b6fce..3614f614117d39 100644 --- a/sound/soc/ti/davinci-i2s.c +++ b/sound/soc/ti/davinci-i2s.c @@ -49,7 +49,7 @@ * incompatible with ASP and with either McBSP. * * In short: this uses ASP to implement I2S, not McBSP. - * And it won't be the only DaVinci implemention of I2S. + * And it won't be the only DaVinci implementation of I2S. */ #define DAVINCI_MCBSP_DRR_REG 0x00 #define DAVINCI_MCBSP_DXR_REG 0x04 diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index ad9e194a3d0d1d..d461ab4196552d 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -1818,7 +1818,7 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, !mcasp->async_mode) max_channels = mcasp->channels; /* - * But we can always allow channels upto the amount of + * But we can always allow channels up to the amount of * the available tdm_slots. */ if (max_channels < tdm_slots) diff --git a/sound/soc/ti/omap-mcbsp.c b/sound/soc/ti/omap-mcbsp.c index 5734388a4e4b1e..889e357b64d080 100644 --- a/sound/soc/ti/omap-mcbsp.c +++ b/sound/soc/ti/omap-mcbsp.c @@ -1237,7 +1237,7 @@ static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, break; case OMAP_MCBSP_SYSCLK_CLKR_EXT: regs->pcr0 |= SCLKME; - /* Disable ouput on CLKR pin in master mode */ + /* Disable output on CLKR pin in master mode */ regs->pcr0 &= ~CLKRM; break; default: diff --git a/sound/soc/uniphier/aio.h b/sound/soc/uniphier/aio.h index 1900ea01ce3dd1..87b27256cf8088 100644 --- a/sound/soc/uniphier/aio.h +++ b/sound/soc/uniphier/aio.h @@ -183,7 +183,7 @@ struct uniphier_aio_selector { * * These are examples for sound data paths: * - * For caputure device: + * For capture device: * (outer of AIO) -> iport -> iif -> ch -> rb -> (CPU) * For playback device: * (CPU) -> rb -> ch -> oif -> oport -> (outer of AIO) diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index 683b485fb57085..bd2cfc618290f6 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -19,7 +19,7 @@ #include "ux500_msp_i2s.h" - /* Protocol desciptors */ + /* Protocol descriptors */ static const struct msp_protdesc prot_descs[] = { { /* I2S */ MSP_SINGLE_PHASE, From 3565893cc72cdf6b795cf6a33e7ff9605322334d Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Wed, 16 Sep 2026 05:16:38 +0000 Subject: [PATCH 0723/1417] btrfs: clear free space tree creation state on rebuild failure btrfs_rebuild_free_space_tree() sets BTRFS_FS_CREATING_FREE_SPACE_TREE before rebuilding the free space tree. Several error paths return without clearing this flag. The transaction restart failure path can leave the flag set on a live filesystem, causing delayed reference processing to be skipped. Clear it on all free space tree rebuild failure paths. Keep BTRFS_FS_FREE_SPACE_TREE_UNTRUSTED set, since a failed rebuild leaves the free space tree untrusted. Callers must fall back to extent-tree caching. Fixes: 882af9f13e83 ("btrfs: handle free space tree rebuild in multiple transactions") CC: stable@vger.kernel.org # 6.14+ Assisted-by: LLM Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Guanghui Yang <3497809730@qq.com> Signed-off-by: David Sterba --- fs/btrfs/free-space-tree.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/free-space-tree.c b/fs/btrfs/free-space-tree.c index 1b3d82ae3de80e..b7a4a6ade30f0e 100644 --- a/fs/btrfs/free-space-tree.c +++ b/fs/btrfs/free-space-tree.c @@ -1353,7 +1353,7 @@ int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info) if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); btrfs_end_transaction(trans); - return ret; + goto out_clear; } node = rb_first_cached(&fs_info->block_group_cache_tree); @@ -1371,14 +1371,16 @@ int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info) if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); btrfs_end_transaction(trans); - return ret; + goto out_clear; } next: if (btrfs_should_end_transaction(trans)) { btrfs_end_transaction(trans); trans = btrfs_start_transaction(free_space_root, 1); - if (IS_ERR(trans)) - return PTR_ERR(trans); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + goto out_clear; + } } node = rb_next(node); } @@ -1390,6 +1392,10 @@ int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info) ret = btrfs_commit_transaction(trans); clear_bit(BTRFS_FS_FREE_SPACE_TREE_UNTRUSTED, &fs_info->flags); return ret; + +out_clear: + clear_bit(BTRFS_FS_CREATING_FREE_SPACE_TREE, &fs_info->flags); + return ret; } static int __add_block_group_free_space(struct btrfs_trans_handle *trans, From 97fcd34aa9fd73cefe3120ac9a82ca9d7763922f Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 16 Sep 2026 15:43:41 +0100 Subject: [PATCH 0724/1417] btrfs: abort transaction on failure to update inode for hole punching and reflinking If we fail to update the inode we error out without aborting the transaction, which can result in a persistent inconsistency if after the failure the transaction is committed, as we have dropped file extent items from a range and either punched a hole or insert a new file extent item for that range (for reflinks). So add the missing transaction abort. Fixes: 2aaa66558172 ("Btrfs: add hole punching") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/file.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c index 20e15dc30bfb55..f978c6524aa03f 100644 --- a/fs/btrfs/file.c +++ b/fs/btrfs/file.c @@ -2509,8 +2509,10 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode, inode_set_ctime_current(&inode->vfs_inode)); ret = btrfs_update_inode(trans, inode); - if (ret) + if (unlikely(ret)) { + btrfs_abort_transaction(trans, ret); break; + } btrfs_end_transaction(trans); btrfs_btree_balance_dirty(fs_info); From aeab4c62875748ecfd390a47ac1d91ea7c9a6abb Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 16 Sep 2026 16:49:37 +0100 Subject: [PATCH 0725/1417] btrfs: check if there is space for chunk item when validating sys chunk array We checked if have enough remaining space for a key before dereferencing a key, but we then dereference a chunk item, to get the number of stripes, without checking if there is space for the item. So add a check to see if there is enough space for a chunk item before dereferencing the item to extract the stripe count. Fixes: 2a9bb78cfd36 ("btrfs: validate system chunk array at btrfs_validate_super()") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 819727460bcf4d..dc7ad92876c0b3 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -2357,6 +2357,10 @@ static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, key.type, cur); return -EUCLEAN; } + + if (unlikely(cur + sizeof(*chunk) > sys_array_size)) + goto short_read; + chunk = (struct btrfs_chunk *)(sb->sys_chunk_array + cur); num_stripes = btrfs_stack_chunk_num_stripes(chunk); if (unlikely(cur + btrfs_chunk_item_size(num_stripes) > sys_array_size)) From b797b52e88a66598a972111b02ef13edd8d03ed2 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sat, 12 Sep 2026 18:12:06 +0930 Subject: [PATCH 0726/1417] btrfs: add "/dev/root" exception for device path update [BEHAVIOR CHANGE] Since commit 108cc8733989 ("btrfs: fix a lockdep caused by path resolution during device scan"), users with btrfs rootfs but without an initramfs are complaining that grub2 can no longer detect the rootfs device: /usr/sbin/grub-probe: error: cannot find a device for / (is /dev mounted?). [CAUSE] Although using btrfs without an initramfs is not recommended (if a new device is added to the rootfs, the system can no longer boot, as there is no way to register all devices), there is still a minority of users doing this. If there is no initramfs but the rootfs is on a block-device-based filesystem, the kernel boot sequence initializes a minimal ramfs/tmpfs, creates "/dev/root" with the proper device number for the rootfs, and then invokes mount using "/dev/root". That's why the end user will get the mount output: /dev/root on / rw To be honest, this is a user space problem: no one should trust the device path shown in mount, only the device number. E.g. one can even use "/proc/self/fd/*" to mount an fs, and that proc path will be registered, and no one else can mount that fs using that path. Before commit 108cc8733989 ("btrfs: fix a lockdep caused by path resolution during device scan"), btrfs had an internal path lookup workaround to address such weird paths, it works by checking if the existing device path can still resolve to the device number. But that path resolution is deadlock prone, thus it's replaced by a simple devt check. This works fine in most cases, as a btrfs device is registered by udev at boot time, thus all paths are sane. However this will not work for systems without an initramfs, causing the unreachable "/dev/root" path to exist forever without a way to rename it. [WORKAROUND] Add an exception to the device path rename requirement. If the device has the name "/dev/root", we know it's booted without an initramfs, and only for that case we allow device path update. And if someone intentionally created "/dev/root" after boot, the existing devt checks will reject that weird name as usual. This should satisfy the minority of users, and still keep most of the existing guards preventing unexpected/unnecessary device path updates. Fixes: 108cc8733989 ("btrfs: fix a lockdep caused by path resolution during device scan") Link: https://lore.kernel.org/linux-btrfs/CAKLYgeL7nrA4nXcewdv9Fqg_s=3GS=vmoypnEiZBKQ7rySZFuQ@mail.gmail.com/ Link: https://lore.kernel.org/linux-btrfs/dfbe1e27-dab8-4d55-8cf3-0b28eeac5df4@gmail.com/ Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/volumes.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 427aa8e24fc314..7bbc8e745e6b87 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -740,6 +740,36 @@ const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb) return has_metadata_uuid ? sb->metadata_uuid : sb->fsid; } +static bool should_rename_device(const struct btrfs_device *dev) +{ + bool ret; + const char *old_name; + + rcu_read_lock(); + old_name = rcu_dereference(dev->name); + /* + * For systems booted without an initramfs, the rootfs has the device + * name "/dev/root". + * + * Although using btrfs without an initramfs is not recommended (if a + * new device is added to the rootfs, the system can no longer boot, as + * there is no way to register all devices), there is still a minority + * of users doing this. + * + * And after the system is up, a later device scan on the real block + * device file will never get this device's name updated, as the + * device->devt is still the same. + * + * Here we add one and only one exception for "/dev/root", to allow the + * device name to be updated even if the new path points to the same + * block device. + */ + ret = (strcmp(old_name, "/dev/root") == 0); + rcu_read_unlock(); + + return ret; +} + /* * Add new device to list of registered devices * @@ -860,7 +890,8 @@ static noinline struct btrfs_device *device_list_add(const char *path, MAJOR(path_devt), MINOR(path_devt), current->comm, task_pid_nr(current)); - } else if (!device->name || device->devt != path_devt) { + } else if (!device->name || device->devt != path_devt || + should_rename_device(device)) { const char *old_name; /* From 72de4807ba84da485dda1a91572d66da9149e95a Mon Sep 17 00:00:00 2001 From: Anand Jain Date: Sun, 13 Sep 2026 02:06:28 +0800 Subject: [PATCH 0727/1417] btrfs: derive f_fsid with dev_t only when temp_fsid is active Commit c2a74ed0494c ("btrfs: derive f_fsid from on-disk fsid and dev_t") mixed dev_t into f_fsid for all single-device setups to avoid f_fsid collisions with cloned filesystems. However, doing this unconditionally breaks backward compatibility. statfs(2) f_fsid changes after a kernel upgrade, and also can shift across reboots or dev re-attaches as dev_t values change. Fix this by only mixing dev_t when temp_fsid is active. This means for non-temp_fsid setups or the original mount, we use the old method of deriving fsid based on the UUID. So in the case of a cloned Btrfs filesystem, we won't be able to maintain the same fsid across mount recycle if the mount order changes. Reported-by: Dave Hansen Link: https://lore.kernel.org/linux-btrfs/be0c08f5-2f31-40f5-8a3b-f2f58b3e00ff@intel.com Fixes: c2a74ed0494c ("btrfs: derive f_fsid from on-disk fsid and dev_t") CC: stable@vger.kernel.org # 7.2 Signed-off-by: Anand Jain Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/super.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index 464129b1b0d4cb..ddb620ac241b42 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c @@ -1836,8 +1836,12 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf) f_fsid.val[0] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root) >> 32; f_fsid.val[1] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root); - /* Hash dev_t to avoid f_fsid collision with cloned filesystems. */ - if (fs_info->fs_devices->total_devices == 1) { + /* + * Hash dev_t to avoid f_fsid collisions with cloned filesystems. + * Only do this when a clone is present so the original filesystem + * (mounted first) maintains backward-compatible f_fsid behavior. + */ + if (fs_info->fs_devices->temp_fsid) { __kernel_fsid_t dev_fsid = u64_to_fsid(huge_encode_dev(fs_info->fs_devices->latest_dev->bdev->bd_dev)); From 40c2096961b4e8f48d1fc17406a90c5a36ac0a8d Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 14 Sep 2026 15:19:20 +0200 Subject: [PATCH 0728/1417] bpf: Verify global subprogs in each sleepability context Global subprograms are verified independently with a fresh verifier root. do_check_common() currently seeds that root's in_sleepable state from the program, even though a global subprogram can also run from callbacks whose execution context differs from the program's main entry point. In particular, workqueue and task-work callbacks are sleepable even when the containing program is not. A global subprogram of that program is therefore verified as non-sleepable, making in_rcu_cs() true and allowing loads of RCU-protected kptrs to produce trusted MEM_RCU pointers. The same subprogram can then be called from a sleepable callback without a classic RCU reader. It can retain such a pointer while the object is freed and use it after free. The verifier's execution-context predicates are complementary. A state is sleepable only when in_sleepable is set and no RCU, preemption, IRQ, or lock region is active. Each condition which prevents sleeping also provides RCU protection, while in_rcu_cs() treats a non-sleepable state as implicitly protected. Use this relationship to represent a global subprogram caller with only the result of in_sleepable_context(). A protected sleepable caller is normalized to in_sleepable=false at the independent verification root. This both prevents sleepable operations and makes in_rcu_cs() true without copying caller-owned lock state. Track only the contexts in which each global subprogram is actually reached. Verify it once if all reachable calls use the same context, and twice only if both sleepable and non-sleepable calls reach it. Calls found while verifying globals or asynchronous callbacks mark further contexts for checking. Repeat the existing subprogram walk until all called contexts have been verified; unreachable global calls remain unchecked. Accumulate instruction counts over those verification passes. Preserve the total recorded before each pass, since path accounting has already added this pass's synchronous instructions and its root total must also include asynchronous subprograms. This makes an unprotected callback verify the global subprogram as sleepable, turning its RCU-protected kptr load into an untrusted pointer. Protected callers and global subprograms which do not depend on implicit RCU protection remain valid. Fixes: 81f1d7a583fa ("bpf: wq: add bpf_wq_set_callback_impl") Fixes: 38aa7003e369 ("bpf: task work scheduling kfuncs") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260914131923.2544250-2-memxor@gmail.com Signed-off-by: Eduard Zingerman --- include/linux/bpf.h | 5 +-- kernel/bpf/verifier.c | 71 ++++++++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index e57af902560c36..1d2676782d707c 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1651,8 +1651,9 @@ static inline void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags struct bpf_func_info_aux { u16 linkage; bool unreliable; - bool called : 1; - bool verified : 1; + /* Indexed by in_sleepable. */ + bool called[2]; + bool verified[2]; }; enum bpf_jit_poke_reason { diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index ddba53eaa3331b..5d7080c260d84d 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9931,6 +9931,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn, if (err == -EFAULT) return err; if (bpf_subprog_is_global(env, subprog)) { + struct bpf_func_info_aux *sub_aux = subprog_aux(env, subprog); const char *sub_name = bpf_subprog_name(env, subprog); const char *operation; bool returns_void; @@ -9962,11 +9963,10 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn, if (env->log.level & BPF_LOG_LEVEL) verbose(env, "Func#%d ('%s') is global and assumed valid.\n", subprog, sub_name); + sub_aux->called[in_sleepable_context(env)] = true; returns_void = subprog_returns_void(env, subprog); if (env->subprog_info[subprog].changes_pkt_data) clear_all_pkt_pointers(env); - /* mark global subprog for verifying after main prog */ - subprog_aux(env, subprog)->called = true; if (returns_void) bpf_diag_record_scrub(env, &caller->regs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED); else @@ -10784,11 +10784,7 @@ int bpf_get_helper_proto(struct bpf_verifier_env *env, int func_id, /* Check if we're in a sleepable context. */ static inline bool in_sleepable_context(struct bpf_verifier_env *env) { - return !env->cur_state->active_rcu_locks && - !env->cur_state->active_preempt_locks && - !env->cur_state->active_locks && - !env->cur_state->active_irq_id && - in_sleepable(env); + return !in_rcu_cs(env); } static const char *non_sleepable_context_description(struct bpf_verifier_env *env) @@ -19447,13 +19443,14 @@ static void free_states(struct bpf_verifier_env *env) } } -static int do_check_common(struct bpf_verifier_env *env, int subprog) +static int do_check_common(struct bpf_verifier_env *env, int subprog, bool is_sleepable) { bool pop_log = !(env->log.level & BPF_LOG_LEVEL2); struct bpf_subprog_info *sub = subprog_info(env, subprog); struct bpf_prog_aux *aux = env->prog->aux; struct bpf_verifier_state *state; struct bpf_reg_state *regs; + u32 old_insns_total = sub->insns_total; u32 insn_processed = env->insn_processed; int ret, i; @@ -19466,7 +19463,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog) state->curframe = 0; state->speculative = false; state->branches = 1; - state->in_sleepable = env->prog->sleepable; + state->in_sleepable = is_sleepable; state->frame[0] = kzalloc_obj(struct bpf_func_state, GFP_KERNEL_ACCOUNT); if (!state->frame[0]) { kfree(state); @@ -19607,8 +19604,10 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog) * not accounted as callees by account_current_path(). * Accumulate their total counts as total counts of the main or * global subprog hosting the async call. + * Start from the saved total of earlier contexts: adding to the current + * total would count this pass's synchronous paths twice. */ - env->subprog_info[subprog].insns_total = env->insn_processed - insn_processed; + sub->insns_total = old_insns_total + (env->insn_processed - insn_processed); return ret; } @@ -19636,14 +19635,19 @@ static int do_check_subprogs(struct bpf_verifier_env *env) { struct bpf_prog_aux *aux = env->prog->aux; struct bpf_func_info_aux *sub_aux; - int i, ret, new_cnt; + int context, i, ret, new_cnt; if (!aux->func_info) return 0; - /* exception callback is presumed to be always called */ - if (env->exception_callback_subprog) - subprog_aux(env, env->exception_callback_subprog)->called = true; + /* + * Callbacks cannot throw, so the exception callback always runs in the + * main program's context. It is presumed to be always called. + */ + if (env->exception_callback_subprog) { + sub_aux = subprog_aux(env, env->exception_callback_subprog); + sub_aux->called[env->prog->sleepable] = true; + } again: new_cnt = 0; @@ -19652,29 +19656,28 @@ static int do_check_subprogs(struct bpf_verifier_env *env) continue; sub_aux = subprog_aux(env, i); - if (!sub_aux->called || sub_aux->verified) - continue; + for (context = 0; context < ARRAY_SIZE(sub_aux->called); context++) { + if (!sub_aux->called[context] || sub_aux->verified[context]) + continue; - env->insn_idx = env->subprog_info[i].start; - WARN_ON_ONCE(env->insn_idx == 0); - ret = do_check_common(env, i); - if (ret) { - return ret; - } else if (env->log.level & BPF_LOG_LEVEL) { - verbose(env, "Func#%d ('%s') is safe for any args that match its prototype\n", - i, bpf_subprog_name(env, i)); - } + env->insn_idx = env->subprog_info[i].start; + WARN_ON_ONCE(env->insn_idx == 0); + ret = do_check_common(env, i, context); + if (ret) + return ret; + if (env->log.level & BPF_LOG_LEVEL) + verbose(env, "Func#%d ('%s') is safe for any args " + "that match its prototype\n", + i, bpf_subprog_name(env, i)); - /* We verified new global subprog, it might have called some - * more global subprogs that we haven't verified yet, so we - * need to do another pass over subprogs to verify those. - */ - sub_aux->verified = true; - new_cnt++; + sub_aux->verified[context] = true; + new_cnt++; + } } - /* We can't loop forever as we verify at least one global subprog on - * each pass. + /* + * We can't loop forever as each pass verifies at least one new context, + * and there are only two contexts per global subprog. */ if (new_cnt) goto again; @@ -19687,7 +19690,7 @@ static int do_check_main(struct bpf_verifier_env *env) int ret; env->insn_idx = 0; - ret = do_check_common(env, 0); + ret = do_check_common(env, 0, env->prog->sleepable); if (!ret) env->prog->aux->stack_depth = env->subprog_info[0].stack_depth; return ret; From a452e729b7be317837bb2157d5d88aa3de9873e6 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 14 Sep 2026 15:19:21 +0200 Subject: [PATCH 0729/1417] selftests/bpf: Test global subprog callback contexts Exercise global subprogram verification from workqueue callbacks, which can run in a sleepable context even when the containing program is not sleepable. An unprotected callback must not let the global subprogram use implicit RCU protection inherited from the program. Add a negative case which loads an RCU-protected task kptr in a global subprogram reached from a workqueue callback. It fails on an unfixed kernel because the program is incorrectly accepted. Also cover a workqueue callback protected by an explicit RCU read-side critical section, where the same global subprogram remains valid. Call the same harmless global subprogram directly from the main program and from an unprotected callback. Mark it __weak __noinline so both calls survive optimization, and check that its instruction statistics account for both verification contexts. This also verifies that global calls from callbacks are not rejected wholesale. Workqueue callbacks return zero explicitly after the global call, as required by their contract. Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260914131923.2544250-3-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../bpf/progs/verifier_async_cb_context.c | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c index e0926767bbd3cb..1b653bfb63eb5a 100644 --- a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c +++ b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c @@ -9,6 +9,11 @@ char _license[] SEC("license") = "GPL"; +struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym; +void bpf_task_release(struct task_struct *p) __ksym; +void bpf_rcu_read_lock(void) __ksym; +void bpf_rcu_read_unlock(void) __ksym; + /* Timer tests */ struct timer_elem { @@ -164,6 +169,7 @@ int syscall_btf_find_prog(void *ctx) struct wq_elem { struct bpf_wq w; + struct task_struct __kptr *task; }; struct { @@ -217,6 +223,106 @@ int wq_sleepable_prog(void *ctx) return 0; } +__noinline int wq_global_acquire(void) +{ + struct task_struct *task, *acquired; + struct wq_elem *val; + int key = 0; + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + task = val->task; + if (!task) + return 0; + + acquired = bpf_task_acquire(task); + if (acquired) + bpf_task_release(acquired); + return 0; +} + +static int wq_global_rcu_cb(void *map, int *key, void *value) +{ + wq_global_acquire(); + return 0; +} + +SEC("fentry/bpf_fentry_test1") +__failure __msg("R1 must be a rcu pointer") +int wq_global_rcu_prog(void *ctx) +{ + struct wq_elem *val; + int key = 0; + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + bpf_wq_init(&val->w, &wq_map, 0); + bpf_wq_set_callback(&val->w, wq_global_rcu_cb, 0); + return 0; +} + +static int wq_global_rcu_lock_cb(void *map, int *key, void *value) +{ + bpf_rcu_read_lock(); + wq_global_acquire(); + bpf_rcu_read_unlock(); + return 0; +} + +SEC("fentry/bpf_fentry_test1") +__success +int wq_global_rcu_lock_prog(void *ctx) +{ + struct wq_elem *val; + int key = 0; + + /* Verify the same global subprog in non-sleepable and protected contexts. */ + wq_global_acquire(); + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + bpf_wq_init(&val->w, &wq_map, 0); + bpf_wq_set_callback(&val->w, wq_global_rcu_lock_cb, 0); + return 0; +} + +__weak __noinline int wq_global_no_rcu(void) +{ + return 0; +} + +static int wq_global_no_rcu_cb(void *map, int *key, void *value) +{ + wq_global_no_rcu(); + return 0; +} + +SEC("fentry/bpf_fentry_test1") +__success __log_level(4) +__msg("subprog {{[0-9]+}} (wq_global_no_rcu) global insns_self 4 insns_total 4 stack 0") +int wq_global_no_rcu_prog(void *ctx) +{ + struct wq_elem *val; + int key = 0; + + /* Verify the same global in non-sleepable and unprotected contexts. */ + wq_global_no_rcu(); + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + bpf_wq_init(&val->w, &wq_map, 0); + bpf_wq_set_callback(&val->w, wq_global_no_rcu_cb, 0); + return 0; +} + /* Task work tests */ struct task_work_elem { From 70504de0bb627848667207bec7ccfd647deb8814 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Fri, 11 Sep 2026 00:18:24 +0800 Subject: [PATCH 0730/1417] xsk: Use a 32-bit compare in xsk_map_gen_lookup xsk_map_gen_lookup() loads a u32 key and compares it with max_entries using BPF_JMP_IMM. BPF immediates are sign-extended to 64 bits, so a max_entries value of 0x80000000 or higher becomes a threshold larger than every zero-extended 32-bit key. An out-of-range index then skips the bounds check and the generated lookup reads past xsk_map[]. Compare with BPF_JMP32_IMM so the check stays in 32-bit unsigned range. Fixes: e65650f291ee ("bpf: Implement map_gen_lookup() callback for XSKMAP") Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Alexei Starovoitov Reviewed-by: Emil Tsalapatis Link: https://patch.msgid.link/7d2cb8e8dfaa9eb8fdff85156987a60960787dc3.1789056660.git.zhilinz@nebusec.ai Signed-off-by: Eduard Zingerman --- net/xdp/xskmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/xdp/xskmap.c b/net/xdp/xskmap.c index 3bff346308d0f1..bf00d6463c1915 100644 --- a/net/xdp/xskmap.c +++ b/net/xdp/xskmap.c @@ -124,7 +124,7 @@ static int xsk_map_gen_lookup(struct bpf_map *map, struct bpf_insn *insn_buf) struct bpf_insn *insn = insn_buf; *insn++ = BPF_LDX_MEM(BPF_W, ret, index, 0); - *insn++ = BPF_JMP_IMM(BPF_JGE, ret, map->max_entries, 5); + *insn++ = BPF_JMP32_IMM(BPF_JGE, ret, map->max_entries, 5); *insn++ = BPF_ALU64_IMM(BPF_LSH, ret, ilog2(sizeof(struct xsk_sock *))); *insn++ = BPF_ALU64_IMM(BPF_ADD, mp, offsetof(struct xsk_map, xsk_map)); *insn++ = BPF_ALU64_REG(BPF_ADD, ret, mp); From 05762c5bc1cfdcac36747994fde2c04387a457f1 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:52 -0500 Subject: [PATCH 0731/1417] smb: client: fix next_buffer UAF and NextCommand bounds in compound PDUs Fix several related bounds checking and pointer lifecycle issues in receive_encrypted_standard()'s handling of compound encrypted frames: - Clear next_buffer after assigning it to server->bigbuf. A stale next_buffer pointer can lead to a use-after-free on subsequent error paths. - Update pdu_length to the decrypted plaintext size (buf_size). Using the pre-decryption length allows NextCommand to point into stale ciphertext residue. - Reject next_cmd values smaller than MID_HEADER_SIZE(server). - Fix an integer overflow in the upper bound check by verifying pdu_length - next_cmd < MID_HEADER_SIZE(server), ensuring the trailing slice is large enough for a header. Fixes: b24df3e30cbf ("cifs: update receive_encrypted_standard to handle compounded responses") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index cb4fd09f996e02..fcf7033889c7ea 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -5365,6 +5365,7 @@ receive_encrypted_standard(struct TCP_Server_Info *server, length = decrypt_raw_data(server, buf, buf_size, NULL, false); if (length) return length; + pdu_length = buf_size; next_is_large = server->large_buf; one_more: @@ -5377,8 +5378,15 @@ receive_encrypted_standard(struct TCP_Server_Info *server, } if (next_cmd) { - if (WARN_ON_ONCE(next_cmd > pdu_length)) + if (next_cmd < MID_HEADER_SIZE(server) || + next_cmd > pdu_length || + pdu_length - next_cmd < MID_HEADER_SIZE(server)) { + unsigned int max_next = pdu_length > (unsigned int)MID_HEADER_SIZE(server) ? + pdu_length - (unsigned int)MID_HEADER_SIZE(server) : 0; + cifs_server_dbg(VFS, "invalid NextCommand offset %u out of range [%zu, %u]\n", + next_cmd, MID_HEADER_SIZE(server), max_next); return -1; + } if (next_is_large) next_buffer = (char *)cifs_buf_get(); else @@ -5414,6 +5422,7 @@ receive_encrypted_standard(struct TCP_Server_Info *server, server->bigbuf = buf = next_buffer; else server->smallbuf = buf = next_buffer; + next_buffer = NULL; goto one_more; } else if (ret != 0) { /* From b4694f269e66dfcd66991446375285723b6957bd Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:53 -0500 Subject: [PATCH 0732/1417] smb: client: validate minimum PDU size before smb2_get_data_area_len() __smb2_calc_size() calls smb2_get_data_area_len(), which reads command-specific struct fields to locate the data area. However, smb2_check_message() only validates StructureSize2, meaning a truncated response could cause smb2_get_data_area_len() to read out-of-bounds. Replace has_smb2_data_area[] with smb2_min_pdu_len[], which is now used to indicate both whether a command's response has a data area and the size of that fixed response struct. A non-zero entry means the command has a data area, and is the minimum length required before the struct is read. For each command with a data area, PDUs shorter than this minimum size are rejected instead of parsed. The minimum is not applied to SMB2 error responses, which carry only the 9-byte error body, the same exemption the StructureSize2 check above it already makes. STATUS_MORE_PROCESSING_REQUIRED is treated as a normal reply, since an in-progress SESSION_SETUP response carries a full body and a security blob. Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2misc.c | 69 ++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c index 9068175e57cd0d..0cfe60ae42c380 100644 --- a/fs/smb/client/smb2misc.c +++ b/fs/smb/client/smb2misc.c @@ -85,6 +85,36 @@ static const __le16 smb2_rsp_struct_sizes[NUMBER_OF_SMB2_COMMANDS] = { /* SMB2_OPLOCK_BREAK */ cpu_to_le16(24) }; +/* + * Minimum received PDU size for commands whose response carries a + * variable-length data area. A non-zero entry marks the command as + * having one, and gives the length smb2_check_message() requires + * before smb2_get_data_area_len() reads the offset and length fields + * out of the fixed response struct. + */ +static const size_t smb2_min_pdu_len[NUMBER_OF_SMB2_COMMANDS] = { + /* SMB2_NEGOTIATE */ sizeof(struct smb2_negotiate_rsp), + /* SMB2_SESSION_SETUP */ sizeof(struct smb2_sess_setup_rsp), + /* SMB2_LOGOFF */ 0, + /* SMB2_TREE_CONNECT */ 0, + /* SMB2_TREE_DISCONNECT */ 0, + /* SMB2_CREATE */ sizeof(struct smb2_create_rsp), + /* SMB2_CLOSE */ 0, + /* SMB2_FLUSH */ 0, + /* SMB2_READ */ sizeof(struct smb2_read_rsp), + /* SMB2_WRITE */ 0, + /* SMB2_LOCK */ 0, + /* SMB2_IOCTL */ sizeof(struct smb2_ioctl_rsp), + /* SMB2_CANCEL */ 0, + /* SMB2_ECHO */ 0, + /* SMB2_QUERY_DIRECTORY */ sizeof(struct smb2_query_directory_rsp), + /* SMB2_CHANGE_NOTIFY */ sizeof(struct smb2_change_notify_rsp), + /* SMB2_QUERY_INFO */ sizeof(struct smb2_query_info_rsp), + /* SMB2_SET_INFO */ 0, + /* SMB2_OPLOCK_BREAK */ 0, +}; + +#define smb2_has_data_area(cmd) (smb2_min_pdu_len[cmd] != 0) #define SMB311_NEGPROT_BASE_SIZE (sizeof(struct smb2_hdr) + sizeof(struct smb2_negotiate_rsp)) static __u32 get_neg_ctxt_len(struct smb2_hdr *hdr, __u32 len, @@ -233,6 +263,16 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len, } } + if ((shdr->Status == STATUS_SUCCESS || + shdr->Status == STATUS_MORE_PROCESSING_REQUIRED || + pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2_LE) && + smb2_has_data_area(command) && + len < smb2_min_pdu_len[command]) { + cifs_server_dbg(VFS, "SMB2 command %d response too short: %u < %zu\n", + command, len, smb2_min_pdu_len[command]); + return 1; + } + have_data = false; data_area_overlap = false; calc_len = __smb2_calc_size(buf, &have_data, &data_area_overlap); @@ -298,33 +338,6 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len, return 0; } -/* - * The size of the variable area depends on the offset and length fields - * located in different fields for various SMB2 responses. SMB2 responses - * with no variable length info, show an offset of zero for the offset field. - */ -static const bool has_smb2_data_area[NUMBER_OF_SMB2_COMMANDS] = { - /* SMB2_NEGOTIATE */ true, - /* SMB2_SESSION_SETUP */ true, - /* SMB2_LOGOFF */ false, - /* SMB2_TREE_CONNECT */ false, - /* SMB2_TREE_DISCONNECT */ false, - /* SMB2_CREATE */ true, - /* SMB2_CLOSE */ false, - /* SMB2_FLUSH */ false, - /* SMB2_READ */ true, - /* SMB2_WRITE */ false, - /* SMB2_LOCK */ false, - /* SMB2_IOCTL */ true, - /* SMB2_CANCEL */ false, /* BB CHECK this not listed in documentation */ - /* SMB2_ECHO */ false, - /* SMB2_QUERY_DIRECTORY */ true, - /* SMB2_CHANGE_NOTIFY */ true, - /* SMB2_QUERY_INFO */ true, - /* SMB2_SET_INFO */ false, - /* SMB2_OPLOCK_BREAK */ false -}; - /* * Returns the pointer to the beginning of the data area. Length of the data * area and the offset to it (from the beginning of the smb are also returned. @@ -451,7 +464,7 @@ __smb2_calc_size(void *buf, bool *have_data, bool *data_area_overlap) */ len += le16_to_cpu(pdu->StructureSize2); - if (has_smb2_data_area[le16_to_cpu(shdr->Command)] == false) + if (!smb2_has_data_area(le16_to_cpu(shdr->Command))) goto calc_size_exit; smb2_get_data_area_len(&offset, &data_length, shdr); From f73726b83e4756fdaa099e1bc1143293bd57ad79 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:54 -0500 Subject: [PATCH 0733/1417] smb: client: fix server->total_read for compound encrypted PDUs In receive_encrypted_standard(), server->total_read is left at the full decrypted frame size when walking sub-PDUs of a compound encrypted frame. As a result, cifs_handle_standard() passes this full size to smb2_check_message(), causing the PDU length guards to incorrectly validate the entire compound frame instead of the current sub-PDU. This allows truncated non-last sub-PDUs to bypass length validation, leading to out-of-bounds reads in smb2_get_data_area_len(). Fix this by setting server->total_read to the true length of the current sub-PDU: next_cmd for non-last sub-PDUs, and the remaining pdu_length for the last one. Fixes: b24df3e30cbf ("cifs: update receive_encrypted_standard to handle compounded responses") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index fcf7033889c7ea..7f2177f6fc0145 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -5371,6 +5371,7 @@ receive_encrypted_standard(struct TCP_Server_Info *server, one_more: shdr = (struct smb2_hdr *)buf; next_cmd = le32_to_cpu(shdr->NextCommand); + server->total_read = next_cmd ? next_cmd : pdu_length; if (*num_mids >= MAX_COMPOUND) { cifs_server_dbg(VFS, "too many PDUs in compound\n"); From e83330c55edc0c3ac08aa6c95e49e4694c65523b Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:55 -0500 Subject: [PATCH 0734/1417] smb: client: fix missing lower-bound check on DFS referral string offsets parse_dfs_referrals() checks that DfsPathOffset and NetworkAddressOffset do not exceed the buffer end, but fails to check that they don't point inside the referral header itself. If a server provides an offset smaller than sizeof(struct dfs_referral_level_3), the derived string pointer overlaps with the struct fields, causing cifs_strndup_from_utf16() to interpret header data as UTF-16 strings. Fix this by enforcing that string offsets are at least sizeof(*ref). Fixes: 4ecce920e13a ("CIFS: move DFS response parsing out of SMB1 code") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/misc.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index 945194fe7a9754..05168284f20512 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -788,7 +788,11 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size, node->ref_flag = le16_to_cpu(ref->ReferralEntryFlags); /* copy DfsPath */ - if (le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) { + if (le16_to_cpu(ref->DfsPathOffset) < sizeof(*ref) || + le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) { + cifs_dbg(VFS, "%s: DfsPathOffset %u out of range [%zu, %td]\n", + __func__, le16_to_cpu(ref->DfsPathOffset), + sizeof(*ref), data_end - (char *)ref); rc = -EINVAL; goto parse_DFS_referrals_exit; } @@ -802,7 +806,11 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size, } /* copy link target UNC */ - if (le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) { + if (le16_to_cpu(ref->NetworkAddressOffset) < sizeof(*ref) || + le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) { + cifs_dbg(VFS, "%s: NetworkAddressOffset %u out of range [%zu, %td]\n", + __func__, le16_to_cpu(ref->NetworkAddressOffset), + sizeof(*ref), data_end - (char *)ref); rc = -EINVAL; goto parse_DFS_referrals_exit; } From 1b3221bb121079ad79a1f3c3aa360ba649832e7a Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:56 -0500 Subject: [PATCH 0735/1417] smb: client: reject short Next offsets in parse_server_interfaces() In parse_server_interfaces(), the server-supplied Next offset is validated against bytes_left, but not against the size of the interface structure itself. A small, non-zero Next value can pass the bounds check but advance the pointer by less than sizeof(*p). This causes the next iteration of the loop to read misaligned, overlapping structure fields. Fix this by ensuring the Next offset is at least sizeof(*p). Fixes: 7d34ec36abb8 ("smb3: fix for slab out of bounds on mount to ksmbd") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 7f2177f6fc0145..bda940cb378496 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -785,9 +785,9 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, break; } /* Validate that Next doesn't point beyond the buffer */ - if (next > bytes_left) { - cifs_dbg(VFS, "%s: invalid Next pointer %zu > %zd\n", - __func__, next, bytes_left); + if (next < sizeof(*p) || next > bytes_left) { + cifs_dbg(VFS, "%s: invalid Next pointer %zu out of range [%zu, %zd]\n", + __func__, next, sizeof(*p), bytes_left); rc = -EINVAL; goto out; } From eeb5ef6083e1cefa2ef75041b5597ff228b8d7bb Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:57 -0500 Subject: [PATCH 0736/1417] smb: client: fix OOB struct field reads in move_smb2_ea_to_cifs() In move_smb2_ea_to_cifs(), the while (src_size > 0) loop condition is insufficient. It allows iteration to continue even if the remaining src_size is too small to contain a complete smb2_ea_info structure. Consequently, reads of ea_name_length and ea_value_length can occur out-of-bounds. Fix this by ensuring src_size >= sizeof(*src) before attempting to read any structure fields. Additionally, reject any next_entry_offset that is smaller than sizeof(*src) or that would advance the pointer beyond the available buffer. Note that for calls where the server returns a malformed EA list, the error returned to userspace changes from -ENODATA (getxattr) or -ERANGE (listxattr) to -EIO. This correctly signals a server protocol error rather than misleadingly indicating "attribute not present" or "output buffer too small". Fixes: 95907fea4fd8 ("cifs: Add support for reading attributes on SMB2+") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 25 +++++++++++++++++-------- fs/smb/client/trace.h | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index bda940cb378496..ee3c98e3f3165f 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -1053,8 +1053,9 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size, char *name, *value; size_t buf_size = dst_size; size_t name_len, value_len, user_name_len; + u32 next_off; - while (src_size > 0) { + while (src_size >= sizeof(*src)) { name_len = (size_t)src->ea_name_length; value_len = (size_t)le16_to_cpu(src->ea_value_length); @@ -1110,14 +1111,22 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size, if (!src->next_entry_offset) break; - if (src_size < le32_to_cpu(src->next_entry_offset)) { - /* stop before overrun buffer */ - rc = -ERANGE; - break; + next_off = le32_to_cpu(src->next_entry_offset); + if (next_off < sizeof(*src) || src_size < next_off) { + cifs_dbg(FYI, "EA next_entry_offset %u out of range [%zu, %zu]\n", + next_off, sizeof(*src), src_size); + rc = smb_EIO2(smb_eio_trace_ea_next_offset, + next_off, src_size); + goto out; + } + src_size -= next_off; + src = (void *)((char *)src + next_off); + if (src_size > 0 && src_size < sizeof(*src)) { + cifs_dbg(FYI, "EA next_entry_offset %u left truncated entry (%zu bytes)\n", + next_off, src_size); + rc = smb_EIO2(smb_eio_trace_ea_next_offset, next_off, src_size); + goto out; } - src_size -= le32_to_cpu(src->next_entry_offset); - src = (void *)((char *)src + - le32_to_cpu(src->next_entry_offset)); } /* didn't find the named attribute */ diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index b442cccd153085..bb8d0197cb54f4 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -27,6 +27,7 @@ EM(smb_eio_trace_copychunk_overcopy_c, "copychunk_overcopy_c") \ EM(smb_eio_trace_create_rsp_too_small, "create_rsp_too_small") \ EM(smb_eio_trace_dfsref_no_rsp, "dfsref_no_rsp") \ + EM(smb_eio_trace_ea_next_offset, "ea_next_offset") \ EM(smb_eio_trace_ea_overrun, "ea_overrun") \ EM(smb_eio_trace_extract_will_pin, "extract_will_pin") \ EM(smb_eio_trace_forced_shutdown, "forced_shutdown") \ From b09d092eb24ad0110f16a9b7c1ed5d2a0c1733dc Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:58 -0500 Subject: [PATCH 0737/1417] smb: client: fix missing iov bounds check in parse_posix_sids() In parse_posix_sids(), sidsbuf_end is calculated using the server-supplied out_len without being validated against the actual length of the received iov (iov_len). If a server provides an inflated out_len, sidsbuf_end will point past the end of the iov. This defeats the bounds guards in posix_info_sid_size(), allowing out-of-bounds reads into adjacent kernel memory. Fix this by rejecting responses where the calculated sidsbuf_end would exceed the received iov boundaries or cause pointer wraparound. Fixes: a90f37e3d7ac ("smb: client: parse owner/group when creating reparse points") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2inode.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 96063e35518650..13fe8e3b48f319 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -77,6 +77,17 @@ static int parse_posix_sids(struct cifs_open_info_data *data, sidsbuf = (u8 *)qi + le16_to_cpu(qi->OutputBufferOffset) + qi_len; sidsbuf_end = sidsbuf + out_len - qi_len; + if (sidsbuf_end < sidsbuf) { + cifs_dbg(VFS, "%s: server-supplied out_len %u caused pointer wraparound\n", + __func__, out_len); + return -EINVAL; + } + if (sidsbuf_end > (u8 *)rsp_iov->iov_base + rsp_iov->iov_len) { + cifs_dbg(VFS, "%s: server-supplied out_len %u overruns iov by %td bytes\n", + __func__, out_len, + sidsbuf_end - ((u8 *)rsp_iov->iov_base + rsp_iov->iov_len)); + return -EINVAL; + } owner_len = posix_info_sid_size(sidsbuf, sidsbuf_end); if (owner_len == -1) From 4775c3b7a597907e0b97556c7986fda238a377ae Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:59 -0500 Subject: [PATCH 0738/1417] smb: client: fix potential OOB read in smb3_enum_snapshots() If snapshot_array_size is smaller than GMT_TOKEN_SIZE, smb3_enum_snapshots() sets ret_data_len to sizeof(struct smb_snapshot_array) without verifying the actual length of the server's reply. Because SMB2_ioctl() places no lower bound on the server-supplied OutputCount and allocates retbuf to exactly that length, a short reply results in ret_data_len exceeding the size of retbuf. The subsequent copy_to_user() then reads past the end of retbuf, leaking adjacent slab memory to userspace. The subsequent clamp check is ineffective as it only reduces ret_data_len. Fix this by rejecting replies shorter than sizeof(struct smb_snapshot_array) with -EIO. Note that the bound is set to the 12-byte struct size rather than the 16-byte MIN_SNAPSHOT_ARRAY_SIZE defined in MS-SMB2 3.3.5.15.1, because 12 bytes is exactly what copy_to_user() attempts to read. Fixes: e02789a53d71 ("smb3: enumerating snapshots was leaving part of the data off end") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index ee3c98e3f3165f..3464470d329775 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -2463,8 +2463,14 @@ smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon, * and retry the ioctl again with larger array size sufficient * to hold all of the snapshot GMT tokens on the second try. */ - if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE) + if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE) { + if (ret_data_len < sizeof(struct smb_snapshot_array)) { + rc = -EIO; + kfree(retbuf); + return rc; + } ret_data_len = sizeof(struct smb_snapshot_array); + } /* * We return struct SRV_SNAPSHOT_ARRAY, followed by From 5f0306e731e2f46e91419eae57eee3a241c055e0 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:34:00 -0500 Subject: [PATCH 0739/1417] smb: client: fix reparse buffer bounds in cifs_query_reparse_point() In cifs_query_reparse_point(), the start >= end check before casting to struct reparse_data_buffer * only ensures the start pointer is within the response. It fails to verify that there is enough space remaining for the fixed 8-byte header of the structure. If a server provides a DataOffset that leaves less than 8 bytes remaining, the check passes, but subsequent reads of ReparseTag and ReparseDataLength will occur out-of-bounds. Fix this by ensuring the remaining space is at least the size of the reparse_data_buffer structure before accessing its fields. Fixes: 56e84c64fc25 ("cifs: Fix validation of SMB1 query reparse point response") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index f9aff07127940d..6dddbd84b93b57 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -3080,7 +3080,7 @@ int cifs_query_reparse_point(const unsigned int xid, end = 2 + get_bcc(&io_rsp->hdr) + (__u8 *)&io_rsp->ByteCount; start = (__u8 *)&io_rsp->hdr.Protocol + data_offset; - if (start >= end) { + if (start >= end || (size_t)(end - start) < sizeof(*buf)) { rc = smb_EIO2(smb_eio_trace_qreparse_data_area, (unsigned long)start - (unsigned long)io_rsp, (unsigned long)end - (unsigned long)io_rsp); From 5ea72f7b7139b123713a7983448f910bc4514d9e Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 18:02:02 +0000 Subject: [PATCH 0740/1417] drm/nouveau: Fix gem reference leak in validate_init() On the ttm_bo_reserve() failure and "vma not found" error paths, the loop breaks without adding the looked-up object to any validate list, so the reference taken by drm_gem_object_lookup() is never released; validate_fini() only walks the spliced lists. Drop the reference before breaking out on both paths. Fixes: 19ca10d82e33bcfe ("drm/nouveau/gem: lookup VMAs for buffers referenced by pushbuf ioctl") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260916180202.2090231-1-vulab@iscas.ac.cn --- drivers/gpu/drm/nouveau/nouveau_gem.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c index 0b7123b163e491..51188be57221ff 100644 --- a/drivers/gpu/drm/nouveau/nouveau_gem.c +++ b/drivers/gpu/drm/nouveau/nouveau_gem.c @@ -522,6 +522,7 @@ validate_init(struct nouveau_channel *chan, struct drm_file *file_priv, if (unlikely(ret)) { if (ret != -ERESTARTSYS) NV_PRINTK(err, cli, "fail reserve\n"); + drm_gem_object_put(gem); break; } } @@ -531,6 +532,7 @@ validate_init(struct nouveau_channel *chan, struct drm_file *file_priv, struct nouveau_vma *vma = nouveau_vma_find(nvbo, vmm); if (!vma) { NV_PRINTK(err, cli, "vma not found!\n"); + drm_gem_object_put(gem); ret = -EINVAL; break; } From 1e04611d3735543bd80a67d9d13dc13f503746fb Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 18:03:42 +0000 Subject: [PATCH 0741/1417] drm/nouveau: Fix runtime PM leak in nouveau_connector_detect() If nvif_outp_edid_get() fails, nouveau_connector_detect() returns early without dropping the runtime PM reference taken at the start of the function, keeping the device powered on until the next successful detect. Balance the reference on the error path like the other exit paths do. Fixes: 0cd7e0718139 ("drm/nouveau/disp: add output method to fetch edid") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260916180342.2090360-1-vulab@iscas.ac.cn --- drivers/gpu/drm/nouveau/nouveau_connector.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c index b0b0ad9a0c248c..4cfc9c7c2ae0c7 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.c +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -600,8 +600,11 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) new_edid = drm_get_edid(connector, nv_encoder->i2c); } else { ret = nvif_outp_edid_get(&nv_encoder->outp, (u8 **)&new_edid); - if (ret < 0) + if (ret < 0) { + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put_autosuspend(dev->dev); return connector_status_disconnected; + } } nouveau_connector_set_edid(nv_connector, new_edid); From 3d743adf090cd4c9a2120c1e02b0482e88aa0d2d Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Thu, 17 Sep 2026 16:10:15 +0200 Subject: [PATCH 0742/1417] spi: fsl-qspi: Reprogram the clock rate when the operation frequency changes fsl_qspi_select_mem() returns early when the chip select has not changed, which happens before it reaches clk_set_rate(). Since the rate is now taken from the spi-mem operation rather than from the SPI device, the controller honours op->max_freq exactly once per chip select and ignores it for every operation after that. q->selected is only reset to -1 in fsl_qspi_default_setup(), i.e. at probe and on resume, so on the common single chip select board the very first operation latches a rate that all subsequent operations inherit, whatever frequency they asked for. This results in operations being issued with the wrong frequency. Cache the operation frequency the clock was programmed for next to the selected chip select, and redo the clock setup when either changes. Fixes: 2438db5253eb ("spi: fsl-qspi: Support per spi-mem operation frequency switches") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Frieder Schrempf Acked-by: Han Xu Link: https://patch.msgid.link/20260917-fsl-qspi-freq-op-fix-v1-1-5fbe6b02f738@kontron.de Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-qspi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-fsl-qspi.c b/drivers/spi/spi-fsl-qspi.c index 57358851029ba5..d2c2090442f840 100644 --- a/drivers/spi/spi-fsl-qspi.c +++ b/drivers/spi/spi-fsl-qspi.c @@ -289,6 +289,7 @@ struct fsl_qspi { struct pm_qos_request pm_qos_req; struct device *dev; int selected; + u32 selected_freq; u32 memmap_phy; }; @@ -551,7 +552,8 @@ static void fsl_qspi_select_mem(struct fsl_qspi *q, struct spi_device *spi, unsigned long rate = op->max_freq; int ret; - if (q->selected == spi_get_chipselect(spi, 0)) + if (q->selected == spi_get_chipselect(spi, 0) && + q->selected_freq == op->max_freq) return; if (needs_4x_clock(q)) @@ -571,6 +573,7 @@ static void fsl_qspi_select_mem(struct fsl_qspi *q, struct spi_device *spi, } q->selected = spi_get_chipselect(spi, 0); + q->selected_freq = op->max_freq; fsl_qspi_invalidate(q); } From ebdc596c42626d1a6dcbc7cd7ad3f842b2cb2c70 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 16 Sep 2026 15:04:53 -0700 Subject: [PATCH 0743/1417] ASoC: fsl: mpc5200: gate AC97 driver on PPC_MPC52xx The MPC5200 PSC AC97 driver only applies to the MPC52xx family of SoCs, but only depended on PPC_BESTCOMM. Restrict the driver to MPC52xx platforms so it is not offered (and does not fail to build) on other bestcomm-based machines. Fixes the following linking error: ERROR: modpost: sound/soc/fsl/mpc5200_psc_ac97.ko: symbol 'mpc5200_psc_ac97_gpio_reset' undefined! Assisted-by: LLM Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260916220453.260472-1-rosenp@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 04940879dfd80b..101bc952aa1446 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -234,7 +234,7 @@ config SND_SOC_MPC5200_I2S config SND_SOC_MPC5200_AC97 tristate "Freescale MPC5200 PSC in AC97 mode driver" - depends on PPC_BESTCOMM + depends on PPC_BESTCOMM && PPC_MPC52xx select SND_SOC_AC97_BUS select SND_MPC52xx_DMA select PPC_BESTCOMM_GEN_BD From 4cae4ca34992d210d115e8e891f1e88c9df5f24c Mon Sep 17 00:00:00 2001 From: Seiji Adachi Date: Thu, 17 Sep 2026 22:18:51 +0900 Subject: [PATCH 0744/1417] ASoC: amd: yc: Add Lenovo ThinkPad E16 Gen 2 (21JN) to quirks Signed-off-by: Seiji Adachi Link: https://patch.msgid.link/20260917131851.2122144-1-seiji_adachi@yahoo.co.jp Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 385fede6d77f3d..ea173bdab1d55b 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -248,6 +248,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "21J6"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21JN"), + } + }, { .driver_data = &acp6x_card, .matches = { From 97077ac87afe9e91ec074ef0be64454e7ccbf344 Mon Sep 17 00:00:00 2001 From: Peiyang He Date: Wed, 16 Sep 2026 18:31:38 +0800 Subject: [PATCH 0745/1417] drm/nouveau: fix double-free in nvif_vmm_dtor On failure, nouveau_cli_init() calls nouveau_cli_fini() to tear the client down. Then, nouveau_drm_open() also enters into its cleanup path and calls nouveau_cli_fini() AGAIN. nouveau_cli_fini() calls nouveau_vmm_fini(): void nouveau_vmm_fini(struct nouveau_vmm *vmm) { nouveau_svmm_fini(&vmm->svmm); nvif_vmm_dtor(&vmm->vmm); vmm->cli = NULL; } Inside nvif_vmm_dtor(), vmm->page is freed unconditionally: void nvif_vmm_dtor(struct nvif_vmm *vmm) { kfree(vmm->page); nvif_object_dtor(&vmm->object); } vmm->page is never cleared after being freed, so the second call of nvif_vmm_dtor() will cause a double-free. Found by fuzzing the nouveau driver with a modified Syzkaller: BUG: KASAN: double-free in nvif_vmm_dtor+0x31/0x50 drivers/gpu/drm/nouveau/nvif/vmm.c:194 Free of addr ffff888010fcdc30 by task syz.0.173/2567 CPU: 1 UID: 0 PID: 2567 Comm: syz.0.173 Not tainted 7.2.0 #24 PREEMPT(lazy) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: __dump_stack lib/dump_stack.c:94 [inline] dump_stack_lvl+0x95/0xe0 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0xcb/0x5a0 mm/kasan/report.c:482 kasan_report_invalid_free+0xaa/0xd0 mm/kasan/report.c:557 check_slab_allocation+0xe4/0x110 mm/kasan/common.c:235 kasan_slab_pre_free include/linux/kasan.h:199 [inline] slab_free_hook mm/slub.c:2622 [inline] slab_free mm/slub.c:6377 [inline] kfree+0x192/0x590 mm/slub.c:6692 nvif_vmm_dtor+0x31/0x50 drivers/gpu/drm/nouveau/nvif/vmm.c:194 nouveau_vmm_fini+0x16/0x50 drivers/gpu/drm/nouveau/nouveau_vmm.c:127 nouveau_cli_fini+0x10e/0x210 drivers/gpu/drm/nouveau/nouveau_drm.c:225 nouveau_drm_open+0x24e/0x740 drivers/gpu/drm/nouveau/nouveau_drm.c:1255 drm_file_alloc+0x5f2/0xad0 drivers/gpu/drm/drm_file.c:176 drm_open_helper+0x1d7/0x4a0 drivers/gpu/drm/drm_file.c:335 drm_open+0x190/0x3d0 drivers/gpu/drm/drm_file.c:388 drm_stub_open+0x1f2/0x460 drivers/gpu/drm/drm_drv.c:1211 chrdev_open+0x21c/0x660 fs/char_dev.c:411 do_dentry_open+0x59d/0x12b0 fs/open.c:947 vfs_open+0x82/0x390 fs/open.c:1052 do_open fs/namei.c:4700 [inline] path_openat+0x2345/0x3420 fs/namei.c:4863 do_file_open+0x207/0x460 fs/namei.c:4892 do_sys_openat2+0xd1/0x1d0 fs/open.c:1368 do_sys_open fs/open.c:1374 [inline] __do_sys_openat fs/open.c:1390 [inline] __se_sys_openat fs/open.c:1385 [inline] __x64_sys_openat+0x144/0x200 fs/open.c:1385 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x115/0x690 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7fc6d687594d Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b0 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007fc6d5295008 EFLAGS: 00000246 ORIG_RAX: 0000000000000101 RAX: ffffffffffffffda RBX: 00007fc6d6b06180 RCX: 00007fc6d687594d RDX: 0000000000022501 RSI: 0000200000000000 RDI: ffffffffffffff9c RBP: 00007fc6d691c303 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007fc6d6b06218 R14: 00007fc6d6b06180 R15: 00007ffd9451d760 Allocated by task 2567 on cpu 1 at 163.593900s: kasan_save_stack+0x24/0x50 mm/kasan/common.c:57 kasan_save_track+0x17/0x60 mm/kasan/common.c:78 poison_kmalloc_redzone mm/kasan/common.c:398 [inline] __kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415 kasan_kmalloc include/linux/kasan.h:263 [inline] __do_kmalloc_node mm/slub.c:5334 [inline] __kmalloc_noprof+0x304/0x7c0 mm/slub.c:5359 _kmalloc_noprof include/linux/slab.h:992 [inline] nvif_vmm_ctor+0x3c0/0x7e0 drivers/gpu/drm/nouveau/nvif/vmm.c:237 nouveau_vmm_init+0x40/0x90 drivers/gpu/drm/nouveau/nouveau_vmm.c:134 nouveau_cli_init+0x7b9/0xe10 drivers/gpu/drm/nouveau/nouveau_drm.c:293 nouveau_drm_open+0x236/0x740 drivers/gpu/drm/nouveau/nouveau_drm.c:1243 drm_file_alloc+0x5f2/0xad0 drivers/gpu/drm/drm_file.c:176 drm_open_helper+0x1d7/0x4a0 drivers/gpu/drm/drm_file.c:335 drm_open+0x190/0x3d0 drivers/gpu/drm/drm_file.c:388 drm_stub_open+0x1f2/0x460 drivers/gpu/drm/drm_drv.c:1211 chrdev_open+0x21c/0x660 fs/char_dev.c:411 do_dentry_open+0x59d/0x12b0 fs/open.c:947 vfs_open+0x82/0x390 fs/open.c:1052 do_open fs/namei.c:4700 [inline] path_openat+0x2345/0x3420 fs/namei.c:4863 do_file_open+0x207/0x460 fs/namei.c:4892 do_sys_openat2+0xd1/0x1d0 fs/open.c:1368 do_sys_open fs/open.c:1374 [inline] __do_sys_openat fs/open.c:1390 [inline] __se_sys_openat fs/open.c:1385 [inline] __x64_sys_openat+0x144/0x200 fs/open.c:1385 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x115/0x690 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 2567 on cpu 1 at 163.601355s: kasan_save_stack+0x24/0x50 mm/kasan/common.c:57 kasan_save_track+0x17/0x60 mm/kasan/common.c:78 kasan_save_free_info+0x3b/0x60 mm/kasan/generic.c:584 poison_slab_object mm/kasan/common.c:253 [inline] __kasan_slab_free+0x61/0x80 mm/kasan/common.c:285 kasan_slab_free include/linux/kasan.h:235 [inline] slab_free_hook mm/slub.c:2677 [inline] slab_free mm/slub.c:6377 [inline] kfree+0x383/0x590 mm/slub.c:6692 nvif_vmm_dtor+0x31/0x50 drivers/gpu/drm/nouveau/nvif/vmm.c:194 nouveau_vmm_fini+0x16/0x50 drivers/gpu/drm/nouveau/nouveau_vmm.c:127 nouveau_cli_fini+0x10e/0x210 drivers/gpu/drm/nouveau/nouveau_drm.c:225 nouveau_cli_init+0x593/0xe10 drivers/gpu/drm/nouveau/nouveau_drm.c:324 nouveau_drm_open+0x236/0x740 drivers/gpu/drm/nouveau/nouveau_drm.c:1243 drm_file_alloc+0x5f2/0xad0 drivers/gpu/drm/drm_file.c:176 drm_open_helper+0x1d7/0x4a0 drivers/gpu/drm/drm_file.c:335 drm_open+0x190/0x3d0 drivers/gpu/drm/drm_file.c:388 drm_stub_open+0x1f2/0x460 drivers/gpu/drm/drm_drv.c:1211 chrdev_open+0x21c/0x660 fs/char_dev.c:411 do_dentry_open+0x59d/0x12b0 fs/open.c:947 vfs_open+0x82/0x390 fs/open.c:1052 do_open fs/namei.c:4700 [inline] path_openat+0x2345/0x3420 fs/namei.c:4863 do_file_open+0x207/0x460 fs/namei.c:4892 do_sys_openat2+0xd1/0x1d0 fs/open.c:1368 do_sys_open fs/open.c:1374 [inline] __do_sys_openat fs/open.c:1390 [inline] __se_sys_openat fs/open.c:1385 [inline] __x64_sys_openat+0x144/0x200 fs/open.c:1385 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x115/0x690 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff888010fcdc30 which belongs to the cache kmalloc-16 of size 16 The buggy address is located 0 bytes inside of 16-byte region [ffff888010fcdc30, ffff888010fcdc40) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10fcd flags: 0x100000000000000(node=0|zone=1) page_type: f5(slab) raw: 0100000000000000 ffff88800d441640 dead000000000100 dead000000000122 raw: 0000000000000000 0000000000550055 00000000f5000000 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff888010fcdb00: fc fc 00 04 fc fc fc fc fa fb fc fc fc fc fa fb ffff888010fcdb80: fc fc fc fc fa fb fc fc fc fc 00 07 fc fc fc fc >ffff888010fcdc00: fa fb fc fc fc fc fa fb fc fc fc fc fa fb fc fc ^ ffff888010fcdc80: fc fc fa fb fc fc fc fc 00 04 fc fc fc fc fa fb ffff888010fcdd00: fc fc fc fc 00 00 fc fc fc fc fa fb fc fc fc fc Fix by removing the redundant teardown in nouveau_drm_open(), since nouveau_cli_init() already does the cleanup work. Also clear vmm->page after its freeing. Cc: stable@vger.kernel.org Fixes: 20d8a88e557a ("drm/nouveau: tidy up the client init/fini interfaces") Signed-off-by: Peiyang He Assisted-by: LLM Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/03BA723D9E5FF725+20260916103138.2651605-1-peiyang_he@smail.nju.edu.cn --- drivers/gpu/drm/nouveau/nouveau_drm.c | 4 +--- drivers/gpu/drm/nouveau/nvif/vmm.c | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 4d1ad718e09b75..b4dfaf70762ade 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -1250,10 +1250,8 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv) mutex_unlock(&drm->clients_lock); done: - if (ret && cli) { - nouveau_cli_fini(cli); + if (ret && cli) kfree(cli); - } pm_runtime_mark_last_busy(dev->dev); pm_runtime_put_autosuspend(dev->dev); diff --git a/drivers/gpu/drm/nouveau/nvif/vmm.c b/drivers/gpu/drm/nouveau/nvif/vmm.c index 65c3e883b11934..579af70766f2db 100644 --- a/drivers/gpu/drm/nouveau/nvif/vmm.c +++ b/drivers/gpu/drm/nouveau/nvif/vmm.c @@ -192,6 +192,7 @@ void nvif_vmm_dtor(struct nvif_vmm *vmm) { kfree(vmm->page); + vmm->page = NULL; nvif_object_dtor(&vmm->object); } From 8d78e4f906cf0a1984cf3c852653727835e1a9e0 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 17 Sep 2026 15:58:51 +0200 Subject: [PATCH 0746/1417] ASoC: rt766: fix HID dependency for SND_SOC_SDCA_HID Selecting SND_SOC_SDCA_HID only works if HID is enabled: WARNING: unmet direct dependencies detected for SND_SOC_SDCA_HID Depends on [n]: SOUND [=y] && SND [=y] && SND_SOC [=y] && SND_SOC_SDCA [=y] && (HID [=n]=y [=y] || HID [=n]=SND_SOC_SDCA [=y]) Selected by [y]: - SND_SOC_RT766_SDCA_SDW [=y] && SOUND [=y] && SND [=y] && SND_SOC [=y] && SOUNDWIRE [=y] && SND_SOC_SDCA [=y] Change the dependency to require SND_SOC_SDCA_CLASS instead, which solves the problem without extra selects. Fixes: b50ecf5873df ("ASoC: rt766: add RT766/RT767 SDCA driver") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260917135950.405423-1-arnd@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index d88593c2bac8b9..8e251e1d84b4bc 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1989,9 +1989,7 @@ config SND_SOC_RT715_SDCA_SDW config SND_SOC_RT766_SDCA_SDW tristate "Realtek RT766 SDCA Codec - SDW" depends on SOUNDWIRE - depends on SND_SOC_SDCA - select SND_SOC_SDCA_HID - select SND_SOC_SDCA_IRQ + depends on SND_SOC_SDCA_CLASS select REGMAP_SOUNDWIRE select REGMAP_SOUNDWIRE_MBQ From 64ca4cdd1031206424e6455f0ae4fb0560d8f46a Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Mon, 17 Aug 2026 14:50:40 +0800 Subject: [PATCH 0747/1417] drm/nouveau/dmem: pin VRAM for the whole registered range Commit c32287471077 ("gpu/drm/nouveau: enable THP support for GPU memory migration") grew the device-private region that nouveau_dmem_chunk_alloc() registers from DMEM_CHUNK_SIZE to DMEM_CHUNK_SIZE * NR_CHUNKS, but left the VRAM buffer object backing that region at DMEM_CHUNK_SIZE. nouveau_dmem_page_addr() returns chunk->bo->offset plus the page's offset within the registered region, so every page past the first chunk resolves to VRAM outside the buffer object. Size the buffer object to the region it backs. Fixes: c32287471077 ("gpu/drm/nouveau: enable THP support for GPU memory migration") Reported-by: Yuhao Jiang Assisted-by: LLM Cc: stable@vger.kernel.org Signed-off-by: Junrui Luo Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260817-nouveau-fixes-v1-1-f518d0c735f3@outlook.com --- drivers/gpu/drm/nouveau/nouveau_dmem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_dmem.c b/drivers/gpu/drm/nouveau/nouveau_dmem.c index ad4570c50be70c..e74d7bb975a863 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dmem.c +++ b/drivers/gpu/drm/nouveau/nouveau_dmem.c @@ -339,8 +339,8 @@ nouveau_dmem_chunk_alloc(struct nouveau_drm *drm, struct page **ppage, chunk->pagemap.ops = &nouveau_dmem_pagemap_ops; chunk->pagemap.owner = drm->dev; - ret = nouveau_bo_new_pin(&drm->client, NOUVEAU_GEM_DOMAIN_VRAM, DMEM_CHUNK_SIZE, - &chunk->bo); + ret = nouveau_bo_new_pin(&drm->client, NOUVEAU_GEM_DOMAIN_VRAM, + DMEM_CHUNK_SIZE * NR_CHUNKS, &chunk->bo); if (ret) goto out_release; From cb4c7603678ccef4c52b38159f2aaad867586dbc Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 17 Sep 2026 14:54:25 -0400 Subject: [PATCH 0748/1417] drm/nouveau/gsp/r570: Add support for INTERNAL_GCX_ENTRY_PREREQUISITE OpenRM's runtime PM handling looks a bit different then nouveau's, one part in particular that differs from us: OpenRM actually consults GSP to ask whether the GPU should be allowed to enter Gc6 and/or GcOff before runtime suspending the GPU. In the event the card isn't ready, runtime suspend is simply delayed for a few seconds before retrying. Implement the command used for querying GSP about this, NV2080_CTRL_CMD_INTERNAL_GCX_ENTRY_PREREQUISITE, and check to ensure that the GPU is ready for runtime suspend in nouveau_pmops_runtime_suspend() using this query. If the GPU can't be runtime suspended, update the last busy counter of the device and then return -EBUSY from nouveau_pmops_runtime_suspend() - essentially delaying the runtime suspend process by whatever autosuspend_delay_ms is set to. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260917185916.1089621-2-lyude@redhat.com --- drivers/gpu/drm/nouveau/include/nvif/cl0080.h | 10 ++++++ drivers/gpu/drm/nouveau/include/nvif/device.h | 1 + .../gpu/drm/nouveau/include/nvkm/subdev/gsp.h | 2 ++ drivers/gpu/drm/nouveau/nouveau_drm.c | 13 +++++++ drivers/gpu/drm/nouveau/nvif/device.c | 13 +++++++ .../gpu/drm/nouveau/nvkm/engine/device/user.c | 35 +++++++++++++++++++ .../gpu/drm/nouveau/nvkm/subdev/gsp/base.c | 10 ++++++ .../gpu/drm/nouveau/nvkm/subdev/gsp/priv.h | 1 + .../drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c | 28 +++++++++++++++ .../nvkm/subdev/gsp/rm/r570/nvrm/gsp.h | 7 ++++ .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h | 2 ++ 11 files changed, 122 insertions(+) diff --git a/drivers/gpu/drm/nouveau/include/nvif/cl0080.h b/drivers/gpu/drm/nouveau/include/nvif/cl0080.h index ea8267e0d8da27..9e639df1da467a 100644 --- a/drivers/gpu/drm/nouveau/include/nvif/cl0080.h +++ b/drivers/gpu/drm/nouveau/include/nvif/cl0080.h @@ -4,6 +4,7 @@ #define NV_DEVICE_V0_INFO 0x00 #define NV_DEVICE_V0_TIME 0x01 +#define NV_DEVICE_V0_GCX_READY 0x02 struct nv_device_info_v0 { __u8 version; @@ -55,6 +56,15 @@ struct nv_device_time_v0 { __u64 time; }; +#define NV_DEVICE_GC6_READY BIT(0) +#define NV_DEVICE_GCOFF_READY BIT(1) + +struct nv_device_gcx_ready_v0 { + __u8 version; + __u8 pad01[6]; + __u8 ready; +}; + #define NV_DEVICE_INFO_UNIT (0xffffffffULL << 32) #define NV_DEVICE_INFO(n) ((n) | (0x00000000ULL << 32)) #define NV_DEVICE_HOST(n) ((n) | (0x00000001ULL << 32)) diff --git a/drivers/gpu/drm/nouveau/include/nvif/device.h b/drivers/gpu/drm/nouveau/include/nvif/device.h index 7877a2a79da9b7..ce2fadcb05d9ef 100644 --- a/drivers/gpu/drm/nouveau/include/nvif/device.h +++ b/drivers/gpu/drm/nouveau/include/nvif/device.h @@ -22,4 +22,5 @@ int nvif_device_ctor(struct nvif_client *, const char *name, struct nvif_device void nvif_device_dtor(struct nvif_device *); int nvif_device_map(struct nvif_device *); u64 nvif_device_time(struct nvif_device *); +int nvif_device_gcx_ready(struct nvif_device *); #endif diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h index 64fed208e4cf85..cd10c370536955 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h @@ -495,6 +495,8 @@ nvkm_gsp_event_dtor(struct nvkm_gsp_event *event) int nvkm_gsp_intr_stall(struct nvkm_gsp *, enum nvkm_subdev_type, int); int nvkm_gsp_intr_nonstall(struct nvkm_gsp *, enum nvkm_subdev_type, int); +int nvkm_gsp_gcx_ready(struct nvkm_gsp *gsp); + int gv100_gsp_new(struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); int tu102_gsp_new(struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); int tu116_gsp_new(struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index b4dfaf70762ade..d0eb29583cb19d 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -1148,6 +1148,7 @@ nouveau_pmops_runtime_suspend(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); struct nouveau_drm *drm = pci_get_drvdata(pdev); + struct nvif_device *nvif = &drm->client.device; int ret; if (!nouveau_pmops_runtime()) { @@ -1155,6 +1156,18 @@ nouveau_pmops_runtime_suspend(struct device *dev) return -EBUSY; } + // Check if the GPU itself is ready for runtime suspend, otherwise mark as busy and check + // again in a bit. + ret = nvif_device_gcx_ready(nvif); + if (ret < 0) { + NV_ERROR(drm, "Failed to query GCX readiness (returned %d)\n", ret); + return -EBUSY; + } else if (!(ret & NV_DEVICE_GCOFF_READY)) { + NV_DEBUG(drm, "GPU isn't ready for suspend yet, delaying...\n"); + pm_runtime_mark_last_busy(dev); + return -EBUSY; + } + nouveau_switcheroo_optimus_dsm(); ret = nouveau_do_suspend(drm, true); pci_save_state(pdev); diff --git a/drivers/gpu/drm/nouveau/nvif/device.c b/drivers/gpu/drm/nouveau/nvif/device.c index 24880931039f64..1be9fbe6cb7036 100644 --- a/drivers/gpu/drm/nouveau/nvif/device.c +++ b/drivers/gpu/drm/nouveau/nvif/device.c @@ -38,6 +38,19 @@ nvif_device_time(struct nvif_device *device) return device->user.func->time(&device->user); } +int +nvif_device_gcx_ready(struct nvif_device *device) +{ + struct nv_device_gcx_ready_v0 args = {}; + int ret; + + ret = nvif_object_mthd(&device->object, NV_DEVICE_V0_GCX_READY, &args, sizeof(args)); + if (ret) + return ret; + + return args.ready; +} + int nvif_device_map(struct nvif_device *device) { diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c index 23d11d8221cb6f..f78e6b9b429231 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -189,6 +190,38 @@ nvkm_udevice_time(struct nvkm_udevice *udev, void *data, u32 size) return ret; } +static int +nvkm_udevice_gcx_ready(struct nvkm_udevice *udev, void *data, u32 size) +{ + struct nvkm_object *object = &udev->object; + struct nvkm_device *device = udev->device; + struct nvkm_gsp *gsp = device->gsp; + union { + struct nv_device_gcx_ready_v0 v0; + } *args = data; + int ret = -ENOSYS; + + if (!gsp) { + args->v0.ready = NV_DEVICE_GC6_READY | NV_DEVICE_GCOFF_READY; + return 0; + } + + nvif_ioctl(object, "device gcx ready size %d\n", size); + ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false); + if (!ret) { + nvif_ioctl(object, "device gcx ready vers %d\n", args->v0.version); + + ret = nvkm_gsp_gcx_ready(gsp); + if (ret < 0) + return ret; + + args->v0.ready = ret; + ret = 0; + } + + return ret; +} + static int nvkm_udevice_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size) { @@ -199,6 +232,8 @@ nvkm_udevice_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size) return nvkm_udevice_info(udev, data, size); case NV_DEVICE_V0_TIME: return nvkm_udevice_time(udev, data, size); + case NV_DEVICE_V0_GCX_READY: + return nvkm_udevice_gcx_ready(udev, data, size); default: break; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c index 9ba1316831e7e9..e475d0e8fa7bf9 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c @@ -20,6 +20,7 @@ * OTHER DEALINGS IN THE SOFTWARE. */ #include "priv.h" +#include int nvkm_gsp_intr_nonstall(struct nvkm_gsp *gsp, enum nvkm_subdev_type type, int inst) @@ -47,6 +48,15 @@ nvkm_gsp_intr_stall(struct nvkm_gsp *gsp, enum nvkm_subdev_type type, int inst) return -ENOENT; } +int +nvkm_gsp_gcx_ready(struct nvkm_gsp *gsp) +{ + if (!gsp->rm->api->gsp->gcx_ready) + return NV_DEVICE_GC6_READY | NV_DEVICE_GCOFF_READY; + + return gsp->rm->api->gsp->gcx_ready(gsp); +} + static int nvkm_gsp_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h index 71b7203bef5073..b07797813b0490 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h @@ -93,6 +93,7 @@ void r535_gsp_dtor(struct nvkm_gsp *); int r535_gsp_oneinit(struct nvkm_gsp *); int r535_gsp_init(struct nvkm_gsp *); int r535_gsp_fini(struct nvkm_gsp *, enum nvkm_suspend_state suspend); +int r535_gsp_gcx_ready(struct nvkm_gsp *gsp); int nvkm_gsp_new_(const struct nvkm_gsp_fwif *, struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c index b45781cd0dfdce..89b801c1e60988 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c @@ -10,6 +10,7 @@ #include "nvrm/gsp.h" #include "nvrm/rpcfn.h" #include "nvrm/msgfn.h" +#include "nvif/cl0080.h" #include #include @@ -215,6 +216,32 @@ r570_gsp_set_rmargs(struct nvkm_gsp *gsp, bool resume) args->bDmemStack = 1; } +int +r570_gsp_gcx_ready(struct nvkm_gsp *gsp) +{ + NV2080_CTRL_INTERNAL_GCX_ENTRY_PREREQUISITE_PARAMS *ctrl; + int ret = 0; + + ctrl = nvkm_gsp_rm_ctrl_rd(&gsp->internal.device.subdevice, + NV2080_CTRL_CMD_INTERNAL_GCX_ENTRY_PREREQUISITE, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + if (ctrl->bIsGC6Satisfied) + ret |= NV_DEVICE_GC6_READY; + if (ctrl->bIsGCOFFSatisfied) + ret |= NV_DEVICE_GCOFF_READY; + + nvkm_debug(&gsp->subdev, + "GCX ready status: GC6=%s GCOFF=%s\n", + str_yes_no(ctrl->bIsGC6Satisfied), str_yes_no(ctrl->bIsGCOFFSatisfied)); + + nvkm_gsp_rm_ctrl_done(&gsp->internal.device.subdevice, ctrl); + return ret; +} + + const struct nvkm_rm_api_gsp r570_gsp = { .set_rmargs = r570_gsp_set_rmargs, @@ -223,4 +250,5 @@ r570_gsp = { .xlat_mc_engine_idx = r570_gsp_xlat_mc_engine_idx, .drop_post_nocat_record = r570_gsp_drop_post_nocat_record, .sr_data_size = r570_gsp_sr_data_size, + .gcx_ready = r570_gsp_gcx_ready, }; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h index c458569af9d720..2814629fddd220 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h @@ -639,4 +639,11 @@ typedef struct GSP_FMC_BOOT_PARAMS } GSP_FMC_BOOT_PARAMS; #define GSP_FW_HEAP_PARAM_BASE_RM_SIZE_GH100 (14 << 20) // Hopper+ + +#define NV2080_CTRL_CMD_INTERNAL_GCX_ENTRY_PREREQUISITE (0x2080a7d7) + +typedef struct NV2080_CTRL_INTERNAL_GCX_ENTRY_PREREQUISITE_PARAMS { + NvBool bIsGC6Satisfied; + NvBool bIsGCOFFSatisfied; +} NV2080_CTRL_INTERNAL_GCX_ENTRY_PREREQUISITE_PARAMS; #endif diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h index e9ac47d86b69af..86970129ad96ac 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h @@ -40,6 +40,7 @@ struct nvkm_rm_api { void (*drop_send_user_shared_data)(struct nvkm_gsp *); void (*drop_post_nocat_record)(struct nvkm_gsp *); u32 (*sr_data_size)(struct nvkm_gsp *); + int (*gcx_ready)(struct nvkm_gsp *gsp); } *gsp; const struct nvkm_rm_api_rpc { @@ -174,6 +175,7 @@ int r535_gr_chan_new(struct nvkm_gr *, struct nvkm_chan *, const struct nvkm_ocl int r535_gr_promote_ctx(struct r535_gr *, bool golden, struct nvkm_vmm *, struct nvkm_memory **pctxbuf_mem, struct nvkm_vma **pctxbuf_vma, struct nvkm_gsp_object *chan); +int r570_gsp_gcx_ready(struct nvkm_gsp *gsp); extern const struct nvkm_rm_api_engine r535_nvdec; extern const struct nvkm_rm_api_engine r535_nvenc; extern const struct nvkm_rm_api_engine r535_nvjpg; From 3217000f0b7e4f67e5b386d5b3491ec1b9b584b8 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 17 Sep 2026 14:54:26 -0400 Subject: [PATCH 0749/1417] drm/nouveau/gsp/r535: Add support for MEMSYS_GET_STATIC_CONFIG This is a GSP structure describing various characteristics of the memory management system that GSP provides. Start by fetching it during driver load, but don't do anything with the information we get from it just yet. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260917185916.1089621-3-lyude@redhat.com --- .../gpu/drm/nouveau/include/nvkm/subdev/gsp.h | 4 ++ .../drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 17 +++++++ .../nvkm/subdev/gsp/rm/r535/nvrm/gsp.h | 45 +++++++++++++++++++ .../drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c | 8 ++++ .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h | 1 + 5 files changed, 75 insertions(+) diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h index cd10c370536955..ed5c6e0e68d3c1 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h @@ -156,6 +156,10 @@ struct nvkm_gsp { struct sg_table fbsr; } sr; + struct { + bool use_raw_mode_comptagline_alloc; + } memsys; + struct { struct nvkm_gsp_mem mem; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index 94925f1590ea48..63aa30f9474715 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1782,6 +1782,23 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) return 0; } +int +r535_gsp_get_static_memsys_info(struct nvkm_gsp *gsp) +{ + NV2080_CTRL_INTERNAL_MEMSYS_GET_STATIC_CONFIG_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_rd(&gsp->internal.device.subdevice, + NV2080_CTRL_CMD_INTERNAL_MEMSYS_GET_STATIC_CONFIG, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + gsp->memsys.use_raw_mode_comptagline_alloc = ctrl->bUseRawModeComptaglineAllocation; + + nvkm_gsp_rm_ctrl_done(&gsp->internal.device.subdevice, ctrl); + return 0; +} + int r535_gsp_init(struct nvkm_gsp *gsp) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/gsp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/gsp.h index b6683a5bf870ce..7b10b7548c575a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/gsp.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/gsp.h @@ -782,6 +782,51 @@ typedef struct NV2080_CTRL_INTERNAL_INTR_GET_KERNEL_TABLE_PARAMS { #define GSP_FW_HEAP_PARAM_CLIENT_ALLOC_SIZE ((48 << 10) * 2048) // Support 2048 channels +typedef struct NV2080_CTRL_INTERNAL_MEMSYS_GET_STATIC_CONFIG_PARAMS { + /*! Determines if RM should use 1 to 1 Comptagline allocation policy */ + NvBool bOneToOneComptagLineAllocation; + + /*! Determines if RM should use 1 to 4 Comptagline allocation policy */ + NvBool bUseOneToFourComptagLineAllocation; + + /*! Determines if RM should use raw Comptagline allocation policy */ + NvBool bUseRawModeComptaglineAllocation; + + /*! Has COMPBIT_BACKING_SIZE been overridden to zero (i.e. disabled)? */ + NvBool bDisableCompbitBacking; + + /*! Determine if we need to disable post L2 compression */ + NvBool bDisablePostL2Compression; + + /*! Is ECC DRAM feature supported? */ + NvBool bEnabledEccFBPA; + + NvBool bL2PreFill; + + /*! L2 cache size */ + NV_DECLARE_ALIGNED(NvU64 l2CacheSize, 8); + + /*! Indicate whether fpba is present or not */ + NvBool bFbpaPresent; + + /*! Size covered by one comptag */ + NvU32 comprPageSize; + + /*! log32(comprPageSize) */ + NvU32 comprPageShift; + + /*! RAM type */ + NvU32 ramType; + + /*! LTC count */ + NvU32 ltcCount; + + /*! LTS per LTC count */ + NvU32 ltsPerLtcCount; +} NV2080_CTRL_INTERNAL_MEMSYS_GET_STATIC_CONFIG_PARAMS; + +#define NV2080_CTRL_CMD_INTERNAL_MEMSYS_GET_STATIC_CONFIG (0x20800a1c) /* finn: Evaluated from "(FINN_NV20_SUBDEVICE_0_INTERNAL_INTERFACE_ID << 8) | NV2080_CTRL_INTERNAL_MEMSYS_GET_STATIC_CONFIG_PARAMS_MESSAGE_ID" */ + typedef union rpc_message_rpc_union_field_v03_00 { NvU32 spare; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c index 89b801c1e60988..ea38a94211f4b1 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c @@ -138,6 +138,14 @@ r570_gsp_get_static_info(struct nvkm_gsp *gsp) } } + ret = r535_gsp_get_static_memsys_info(gsp); + if (ret) { + nvkm_error(&gsp->subdev, "Retrieving static memsys info failed\n"); + return ret; + } + nvkm_debug(&gsp->subdev, "memsys: Use raw mode for comptag allocations? %s\n", + str_yes_no(gsp->memsys.use_raw_mode_comptagline_alloc)); + return 0; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h index 86970129ad96ac..17480d4e527ac0 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h @@ -176,6 +176,7 @@ int r535_gr_promote_ctx(struct r535_gr *, bool golden, struct nvkm_vmm *, struct nvkm_memory **pctxbuf_mem, struct nvkm_vma **pctxbuf_vma, struct nvkm_gsp_object *chan); int r570_gsp_gcx_ready(struct nvkm_gsp *gsp); +int r535_gsp_get_static_memsys_info(struct nvkm_gsp *gsp); extern const struct nvkm_rm_api_engine r535_nvdec; extern const struct nvkm_rm_api_engine r535_nvenc; extern const struct nvkm_rm_api_engine r535_nvjpg; From c7ef611a43bb3ab7e7738349a860418336d507db Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 17 Sep 2026 14:54:27 -0400 Subject: [PATCH 0750/1417] drm/nouveau/gsp/r570: Add comp mode workaround from issue #3172217 One of the things that OpenRM does right before initiating fbsr is apply a special workaround (nvidia issue #3172217) which temporarily disables raw compression mode on the GPU. It is later re-enabled after resuming with fbsr completes. Since we don't currently save the compbit backing with fbsr, this shouldn't currently make any functional difference in the suspend/resume process. But it will be required for implementing support for saving and restoring compbit backings from the GPU. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260917185916.1089621-4-lyude@redhat.com --- .../nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 48 +++++++++++++++++++ .../nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h | 6 +++ 2 files changed, 54 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index af5aa5065c3ddf..f73d9b29e891dd 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -26,6 +26,35 @@ r570_fbsr_suspend_channels(struct nvkm_gsp *gsp, bool suspend) return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); } +static int +r570_memsys_enable_raw_comp_mode(struct nvkm_gsp *gsp, bool enable) +{ + NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS *ctrl; + int ret; + + ctrl = nvkm_gsp_rm_ctrl_get(&gsp->internal.device.subdevice, + NV2080_CTRL_CMD_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->bRawMode = enable; + + ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); + if (!ret) + nvkm_debug(&gsp->subdev, "memsys: Raw compression mode %s\n", + str_enabled_disabled(enable)); + + return ret; +} + +static bool +r570_need_raw_comp_war(struct nvkm_gsp *gsp, struct nvkm_device *device) +{ + return (device->card_type == GA100 || device->card_type == AD100) && + gsp->memsys.use_raw_mode_comptagline_alloc; +} + static void r570_fbsr_resume(struct nvkm_gsp *gsp) { @@ -33,6 +62,7 @@ r570_fbsr_resume(struct nvkm_gsp *gsp) struct nvkm_instmem *imem = device->imem; struct nvkm_instobj *iobj; struct nvkm_vmm *vmm; + int ret; /* Restore BAR2 page tables via BAR0 window, and re-enable BAR2. */ list_for_each_entry(iobj, &imem->boot, head) { @@ -54,6 +84,13 @@ r570_fbsr_resume(struct nvkm_gsp *gsp) vmm = nvkm_bar_bar1_vmm(device); vmm->func->flush(vmm, 0); + /* Re-enable raw mode if it was previously disabled */ + if (r570_need_raw_comp_war(gsp, device)) { + ret = r570_memsys_enable_raw_comp_mode(gsp, true); + if (ret) + nvkm_error(&gsp->subdev, "Failed to re-enable raw comp mode\n"); + } + /* Resume channel scheduling. */ r570_fbsr_suspend_channels(device->gsp, false); @@ -104,6 +141,17 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp) /* Stop channel scheduling. */ r570_fbsr_suspend_channels(gsp, true); + /* Temporarily disable raw mode to prevent FBSR restore operations from corrupting + * compressed surfaces. Required for ampere and ada. + * + * Nvidia bug #3172217 + */ + if (r570_need_raw_comp_war(gsp, device)) { + ret = r570_memsys_enable_raw_comp_mode(gsp, false); + if (ret) + return ret; + } + /* Save BAR2 allocations to system memory. */ list_for_each_entry(iobj, &imem->list, head) { if (iobj->preserve) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h index 8af432375f7a27..9050a8274b2730 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h @@ -16,4 +16,10 @@ typedef struct NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS { NV_DECLARE_ALIGNED(NvU64 sysmemAddrOfSuspendResumeData, 8); } NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS; +#define NV2080_CTRL_CMD_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE (0x20800a6f) /* finn: Evaluated from "(FINN_NV20_SUBDEVICE_0_INTERNAL_INTERFACE_ID << 8) | NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS_MESSAGE_ID" */ + +typedef struct NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS { + NvBool bRawMode; +} NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS; + #endif From 82f4394bbe223fda560153257e5cf21bdb12b6a3 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 17 Sep 2026 14:54:28 -0400 Subject: [PATCH 0751/1417] drm/nouveau/gsp/r570: Start saving comptag backing stores One of the portions of OpenRM's fbsr process that we never implemented is the saving and restoring of comptag backing stores. This isn't strictly necessary for fbsr to work (as long as we don't specify bEnteringGcOff = 1), but implementing it brings us much closer to matching OpenRM's fbsr process - which means we can rely on things being well tested on Nvidia's side. Now that we have the required driver workarounds in place and fetch the required information from GSP's memsys on driver load, let's implement support for this by fetching the required space for the compbit backing stores and adding it to the amount of memory that we allocate for fbsr. With this, we should be able to safely enable bEnteringGcOff in fbsr. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260917185916.1089621-5-lyude@redhat.com --- .../nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 27 ++++++++++++++++++- .../nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h | 23 ++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index f73d9b29e891dd..a93a39ef7c690f 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -26,6 +26,23 @@ r570_fbsr_suspend_channels(struct nvkm_gsp *gsp, bool suspend) return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); } +static int +r570_fb_get_compbit_store_size(struct nvkm_gsp *gsp, u64 *size) +{ + NV0080_CTRL_FB_GET_COMPBIT_STORE_INFO_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_rd(&gsp->internal.device.object, + NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + *size = ctrl->Size; + + nvkm_gsp_rm_ctrl_done(&gsp->internal.device.object, ctrl); + return 0; +} + static int r570_memsys_enable_raw_comp_mode(struct nvkm_gsp *gsp, bool enable) { @@ -135,7 +152,7 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp) struct nvkm_device *device = subdev->device; struct nvkm_instmem *imem = device->imem; struct nvkm_instobj *iobj; - u64 size; + u64 size, compbit_store_size; int ret; /* Stop channel scheduling. */ @@ -152,6 +169,12 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp) return ret; } + ret = r570_fb_get_compbit_store_size(gsp, &compbit_store_size); + if (ret < 0) + return ret; + nvkm_debug(&gsp->subdev, "fbsr: Compbit backing store size: 0x%llx bytes\n", + compbit_store_size); + /* Save BAR2 allocations to system memory. */ list_for_each_entry(iobj, &imem->list, head) { if (iobj->preserve) { @@ -174,6 +197,8 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp) size = gsp->fb.heap.size; size += gsp->fb.rsvd_size; size += gsp->fb.bios.vga_workspace.size; + size += compbit_store_size; + nvkm_debug(subdev, "fbsr: size: 0x%llx bytes\n", size); ret = nvkm_gsp_sg(device, size, &gsp->sr.fbsr); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h index 9050a8274b2730..cb3e448415b42d 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h @@ -16,6 +16,29 @@ typedef struct NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS { NV_DECLARE_ALIGNED(NvU64 sysmemAddrOfSuspendResumeData, 8); } NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS; +#define NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO (0x801306) /* finn: Evaluated from "(FINN_NV01_DEVICE_0_FB_INTERFACE_ID << 8) | NV0080_CTRL_FB_GET_COMPBIT_STORE_INFO_PARAMS_MESSAGE_ID" */ + +typedef struct NV0080_CTRL_FB_GET_COMPBIT_STORE_INFO_PARAMS { + NV_DECLARE_ALIGNED(NvU64 Size, 8); + NV_DECLARE_ALIGNED(NvU64 Address, 8); + NvU32 AddressSpace; + NvU32 MaxCompbitLine; + NvU32 comptagsPerCacheLine; + NvU32 cacheLineSize; + NvU32 cacheLineSizePerSlice; + NvU32 cacheLineFetchAlignment; + NV_DECLARE_ALIGNED(NvU64 backingStoreBase, 8); + NvU32 gobsPerComptagPerSlice; + NvU32 backingStoreCbcBase; + NvU32 comptaglineAllocationPolicy; + NV_DECLARE_ALIGNED(NvU64 privRegionStartOffset, 8); + NvU32 cbcCoveragePerSlice; +} NV0080_CTRL_FB_GET_COMPBIT_STORE_INFO_PARAMS; + +#define NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO_ADDRESS_SPACE_UNKNOWN 0 // ADDR_UNKNOWN +#define NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO_ADDRESS_SPACE_SYSMEM 1 // ADDR_SYSMEM +#define NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO_ADDRESS_SPACE_FBMEM 2 // ADDR_FBMEM + #define NV2080_CTRL_CMD_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE (0x20800a6f) /* finn: Evaluated from "(FINN_NV20_SUBDEVICE_0_INTERNAL_INTERFACE_ID << 8) | NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS_MESSAGE_ID" */ typedef struct NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS { From adb87c20081e5ed5b6eef1270fc08b28bd77e865 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 17 Sep 2026 14:54:29 -0400 Subject: [PATCH 0752/1417] drm/nouveau/gsp/r570: Enable Gcoff in fbsr again Now that we're properly saving the compbit backing stores on fbsr init, we can start setting bEnteringGcOff = 1 again without things breaking, which brings us closer to following the exact same code-paths OpenRM does for fbsr. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260917185916.1089621-6-lyude@redhat.com --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index a93a39ef7c690f..469e7eed1d6f8e 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -135,7 +135,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) ctrl->hClient = gsp->internal.client.object.handle; ctrl->hSysMem = memlist.handle; ctrl->sysmemAddrOfSuspendResumeData = gsp->sr.meta.addr; - ctrl->bEnteringGcoffState = 0; + ctrl->bEnteringGcoffState = 1; ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); if (ret) From 3359a372efb6d585c97019ee1b7f1874442bcebe Mon Sep 17 00:00:00 2001 From: Peiyang He Date: Mon, 7 Sep 2026 13:22:04 +0800 Subject: [PATCH 0753/1417] drm/nouveau/uvmm: fix UAF in nouveau_uvmm_sm when BO is in TTM_PL_SYSTEM nouveau_uvmm_sm() calls op_map(), which passes bo->resource through nouveau_mem() to nouveau_uvma_map(). nouveau_uvmm_vmm_map() then reads mem->mem.type. But this is only valid when bo->resource is backed by struct nouveau_mem, as is the case for VRAM and TT resources. If the BO is left in TTM_PL_SYSTEM, bo->resource is only a struct ttm_resource. Treating it as struct nouveau_mem makes the mem->mem.type read past the end of the resource, causing a KASAN: slab-use-after-free Read in nouveau_uvmm_sm report: BUG: KASAN: slab-use-after-free in nouveau_uvmm_vmm_map drivers/gpu/drm/nouveau/nouveau_uvmm.c:152 [inline] BUG: KASAN: slab-use-after-free in nouveau_uvma_map drivers/gpu/drm/nouveau/nouveau_uvmm.c:199 [inline] BUG: KASAN: slab-use-after-free in op_map drivers/gpu/drm/nouveau/nouveau_uvmm.c:849 [inline] BUG: KASAN: slab-use-after-free in nouveau_uvmm_sm.constprop.0+0x6ab/0x900 drivers/gpu/drm/nouveau/nouveau_uvmm.c:903 Read of size 1 at addr ffff888127d3e3a0 by task kworker/0:1/11 CPU: 0 UID: 0 PID: 11 Comm: kworker/0:1 Not tainted 7.2.0 #5 PREEMPT(lazy) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: nouveau_sched_wq_2224 drm_sched_run_job_work Call Trace: __dump_stack lib/dump_stack.c:94 [inline] dump_stack_lvl+0x95/0xe0 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0xcb/0x5a0 mm/kasan/report.c:482 kasan_report+0xca/0x100 mm/kasan/report.c:595 nouveau_uvmm_vmm_map drivers/gpu/drm/nouveau/nouveau_uvmm.c:152 [inline] nouveau_uvma_map drivers/gpu/drm/nouveau/nouveau_uvmm.c:199 [inline] op_map drivers/gpu/drm/nouveau/nouveau_uvmm.c:849 [inline] nouveau_uvmm_sm.constprop.0+0x6ab/0x900 drivers/gpu/drm/nouveau/nouveau_uvmm.c:903 nouveau_uvmm_sm_unmap drivers/gpu/drm/nouveau/nouveau_uvmm.c:932 [inline] nouveau_uvmm_bind_job_run+0xd6/0x250 drivers/gpu/drm/nouveau/nouveau_uvmm.c:1532 nouveau_job_run drivers/gpu/drm/nouveau/nouveau_sched.c:350 [inline] nouveau_sched_run_job+0x62/0xd0 drivers/gpu/drm/nouveau/nouveau_sched.c:364 drm_sched_run_job_work+0x356/0xa10 drivers/gpu/drm/scheduler/sched_main.c:1061 process_one_work+0x8a5/0x1900 kernel/workqueue.c:3322 process_scheduled_works kernel/workqueue.c:3405 [inline] worker_thread+0x5dd/0xd80 kernel/workqueue.c:3486 kthread+0x31d/0x420 kernel/kthread.c:436 ret_from_fork+0x662/0x940 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Allocated by task 2224 on cpu 0 at 66.550027s: kasan_save_stack+0x24/0x50 mm/kasan/common.c:57 kasan_save_track+0x17/0x60 mm/kasan/common.c:78 poison_kmalloc_redzone mm/kasan/common.c:398 [inline] __kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415 kasan_kmalloc include/linux/kasan.h:263 [inline] __do_kmalloc_node mm/slub.c:5334 [inline] __kmalloc_noprof+0x304/0x7c0 mm/slub.c:5359 _kmalloc_noprof include/linux/slab.h:992 [inline] dma_resv_list_alloc+0x27/0x90 drivers/dma-buf/dma-resv.c:106 dma_resv_reserve_fences+0x60e/0xa30 drivers/dma-buf/dma-resv.c:205 ttm_bo_alloc_resource+0x12c/0xbd0 drivers/gpu/drm/ttm/ttm_bo.c:721 ttm_bo_validate+0x1bc/0x4a0 drivers/gpu/drm/ttm/ttm_bo.c:856 ttm_bo_init_reserved+0x2c3/0x570 drivers/gpu/drm/ttm/ttm_bo.c:970 nouveau_bo_init+0x159/0x2c0 drivers/gpu/drm/nouveau/nouveau_bo.c:359 nouveau_gem_new+0x234/0x5f0 drivers/gpu/drm/nouveau/nouveau_gem.c:272 nouveau_gem_ioctl_new+0x1eb/0x420 drivers/gpu/drm/nouveau/nouveau_gem.c:352 drm_ioctl_kernel+0x192/0x350 drivers/gpu/drm/drm_ioctl.c:817 drm_ioctl+0x4f8/0xb40 drivers/gpu/drm/drm_ioctl.c:914 nouveau_drm_ioctl+0xea/0x2c0 drivers/gpu/drm/nouveau/nouveau_drm.c:1338 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x180/0x1d0 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x115/0x690 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 2223 on cpu 0 at 66.554063s: kasan_save_stack+0x24/0x50 mm/kasan/common.c:57 kasan_save_track+0x17/0x60 mm/kasan/common.c:78 kasan_save_free_info+0x3b/0x60 mm/kasan/generic.c:584 poison_slab_object mm/kasan/common.c:253 [inline] __kasan_slab_free+0x61/0x80 mm/kasan/common.c:285 kasan_slab_free include/linux/kasan.h:235 [inline] slab_free_hook mm/slub.c:2677 [inline] __rcu_free_sheaf_prepare+0xb6/0x2e0 mm/slub.c:2928 rcu_free_sheaf+0x1b/0x120 mm/slub.c:5978 rcu_do_batch kernel/rcu/tree.c:2645 [inline] rcu_core+0x521/0x1490 kernel/rcu/tree.c:2897 handle_softirqs+0x1b1/0x8a0 kernel/softirq.c:622 __do_softirq kernel/softirq.c:656 [inline] invoke_softirq kernel/softirq.c:496 [inline] __irq_exit_rcu+0x137/0x1c0 kernel/softirq.c:735 irq_exit_rcu+0x9/0x20 kernel/softirq.c:752 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline] sysvec_apic_timer_interrupt+0x70/0x80 arch/x86/kernel/apic/apic.c:1062 asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674 The buggy address belongs to the object at ffff888127d3e380 which belongs to the cache kmalloc-96 of size 96 The buggy address is located 32 bytes inside of freed 96-byte region [ffff888127d3e380, ffff888127d3e3e0) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x127d3e flags: 0x200000000000000(node=0|zone=2) page_type: f5(slab) raw: 0200000000000000 ffff888100041280 dead000000000122 0000000000000000 raw: 0000000000000000 0000000000200020 00000000f5000000 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff888127d3e280: 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc ffff888127d3e300: 00 00 00 00 00 00 00 00 00 00 00 fc fc fc fc fc >ffff888127d3e380: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc ^ ffff888127d3e400: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc ffff888127d3e480: 00 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc Fix by resetting the placement to the BO's valid domains before calling nouveau_bo_validate(), matching the handling in nouveau_uvmm_bo_validate(), so map jobs do not run for SYSTEM resources; Reject BO that cannot reside in VRAM or GART; Also skip op_map() when the GPUVA has been invalidated, matching the handling in the unmap and remap paths. Found when fuzzing the nouveau driver with a modified Syzkaller. Fixes: b88baab82871 ("drm/nouveau: implement new VM_BIND uAPI") Cc: stable@vger.kernel.org Signed-off-by: Peiyang He Assisted-by: Codex:gpt-5.5 Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/0D77BEC410CE0129+20260907052204.1431488-1-peiyang_he@smail.nju.edu.cn --- drivers/gpu/drm/nouveau/nouveau_uvmm.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c index fc125fd44a9b7e..2026fe6b48c694 100644 --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c @@ -846,6 +846,9 @@ op_map(struct nouveau_uvma *uvma) { struct nouveau_bo *nvbo = nouveau_gem_object(uvma->va.gem.obj); + if (drm_gpuva_invalidated(&uvma->va)) + return; + nouveau_uvma_map(uvma, nouveau_mem(nvbo->bo.resource)); } @@ -1232,6 +1235,7 @@ bind_lock_validate(struct nouveau_job *job, struct drm_exec *exec, drm_gpuva_for_each_op(va_op, op->ops) { struct drm_gem_object *obj = op_gem_obj(va_op); + struct nouveau_bo *nvbo; if (unlikely(!obj)) continue; @@ -1246,8 +1250,13 @@ bind_lock_validate(struct nouveau_job *job, struct drm_exec *exec, if (va_op->op == DRM_GPUVA_OP_UNMAP) continue; - ret = nouveau_bo_validate(nouveau_gem_object(obj), - true, false); + nvbo = nouveau_gem_object(obj); + if (!(nvbo->valid_domains & + (NOUVEAU_GEM_DOMAIN_VRAM | NOUVEAU_GEM_DOMAIN_GART))) + return -EINVAL; + + nouveau_bo_placement_set(nvbo, nvbo->valid_domains, 0); + ret = nouveau_bo_validate(nvbo, true, false); if (ret) return ret; } From c0078f4d8c8fcd8edfe8c53ffe77d48565c1957e Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Tue, 15 Sep 2026 20:14:12 +0000 Subject: [PATCH 0754/1417] mailmap: add entry for Wei Wang My Meta email address is no longer active. Map it to my current address so that git and get_maintainer.pl stop pointing at a dead address for my contributions. Signed-off-by: Wei Wang Link: https://patch.msgid.link/20260915201412.2201757-1-weiwan@google.com Signed-off-by: Jakub Kicinski --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 1f5540bc33f1fb..526217c884951d 100644 --- a/.mailmap +++ b/.mailmap @@ -975,6 +975,7 @@ Vladimir Davydov Vlastimil Babka WangYuli WangYuli +Wei Wang Weiwen Hu WeiXiong Liao Wen Gong From c9dc7d730319ad64b51570c5387f1fee7b07b510 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Thu, 13 Aug 2026 17:50:03 +0800 Subject: [PATCH 0755/1417] PCI: imx6: Move clock enable after core reset assertion Commit 610fa91d9863 ("PCI: imx6: Assert PERST# before enabling regulators") inadvertently moved clock enablement before core reset assertion, breaking PCI device initialization on i.MX6Q Apalis platforms with ASM1061/ASM1062 SATA controllers connected: imx6q-pcie 1ffc000.pcie: host bridge /soc/pcie@1ffc000 ranges: imx6q-pcie 1ffc000.pcie: IO 0x0001f80000..0x0001f8ffff -> 0x0000000000 imx6q-pcie 1ffc000.pcie: MEM 0x0001000000..0x0001efffff -> 0x0001000000 imx6q-pcie 1ffc000.pcie: config reg[1] 0x01f00000 == cpu 0x01f00000 imx6q-pcie 1ffc000.pcie: iATU: unroll F, 4 ob, 4 ib, align 64K, limit 4G imx6q-pcie 1ffc000.pcie: Link: Only Gen1 is enabled imx6q-pcie 1ffc000.pcie: Link failed to come up. LTSSM: POLL_CONFIG imx6q-pcie 1ffc000.pcie: probe with driver imx6q-pcie failed with error -110 NOTE: It is not 100% clear if the issue is specific to the ASM1061/ASM1062 device or on the specific power-up sequence (reset vs cold-power-on). To fix this regression, restore the original sequence where clocks are enabled after asserting core reset and configuring the controller type. Fixes: 610fa91d9863 ("PCI: imx6: Assert PERST# before enabling regulators") Reported-by: Leonardo Costa Closes: https://lore.kernel.org/all/bl7i3obu2clzsgeoct2a4mtfhv6typcjdqmgneropf3hpgwve6@n2m5uhlduw57/T/#u Reported-by: Franz Schnyder Closes: https://lore.kernel.org/all/t65y5d54axtksbfs7r4olcefqhwm6m4dz3njgnrnf7fcotj74i@o7avoznlafbj/ Signed-off-by: Richard Zhu Signed-off-by: Manivannan Sadhasivam [bhelgaas: move to pci/for-linus for v7.3] Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org # 7.2+ Link: https://patch.msgid.link/20260813095003.356062-1-hongxing.zhu@oss.nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 39790e66b98dc7..f7a2eb257c1691 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1394,12 +1394,6 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) } } - ret = imx_pcie_clk_enable(imx_pcie); - if (ret) { - dev_err(dev, "unable to enable pcie clocks: %d\n", ret); - goto err_pwrctrl_power_off; - } - if (pp->bridge && imx_check_flag(imx_pcie, IMX_PCIE_FLAG_HAS_LUT)) { pp->bridge->enable_device = imx_pcie_enable_device; pp->bridge->disable_device = imx_pcie_disable_device; @@ -1415,6 +1409,12 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) imx_pcie_configure_type(imx_pcie); + ret = imx_pcie_clk_enable(imx_pcie); + if (ret) { + dev_err(dev, "unable to enable pcie clocks: %d\n", ret); + goto err_pwrctrl_power_off; + } + if (imx_pcie->phy) { ret = phy_init(imx_pcie->phy); if (ret) { From f7eae6d8d768fabd6b59779ca7da79e02c74e113 Mon Sep 17 00:00:00 2001 From: "Jonghyuk Kim(MalHyuk)" Date: Wed, 2 Sep 2026 10:27:16 +0900 Subject: [PATCH 0756/1417] drm/nouveau: RCU-free the scheduler-containing nouveau_sched struct nouveau_sched embeds a struct drm_gpu_scheduler (base). nouveau_sched_destroy() calls nouveau_sched_fini() (which does drm_sched_fini(&sched->base)) and then frees the object with plain kfree(sched). drm_sched_fence_get_timeline_name() returns fence->sched->name, and the scheduler fence keeps a .release callback so it is not ops-detached on signalling. A finished fence exported to userspace via drm_syncobj / sync_file therefore keeps pointing at &sched->base after nouveau_sched_destroy(), and a later get_timeline_name() -- reachable unprivileged through SYNC_IOC_FILE_INFO -- dereferences freed memory (KASAN slab-use-after-free read). Per the dma-fence lifetime contract the exporter must keep the data backing a signalled fence alive for an RCU grace period. Free the scheduler-containing object with kfree_rcu() instead of kfree(). Fixes: 5f03a507b29e ("drm/nouveau: implement 1:1 scheduler - entity relationship") Cc: stable@vger.kernel.org Signed-off-by: Jonghyuk Kim(MalHyuk) Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260902012717.880724-1-malhyuk97@gmail.com --- drivers/gpu/drm/nouveau/nouveau_sched.c | 2 +- drivers/gpu/drm/nouveau/nouveau_sched.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.c b/drivers/gpu/drm/nouveau/nouveau_sched.c index 8b9f935afe094f..b3f02c490ecb67 100644 --- a/drivers/gpu/drm/nouveau/nouveau_sched.c +++ b/drivers/gpu/drm/nouveau/nouveau_sched.c @@ -517,7 +517,7 @@ nouveau_sched_destroy(struct nouveau_sched **psched) struct nouveau_sched *sched = *psched; nouveau_sched_fini(sched); - kfree(sched); + kfree_rcu(sched, rcu); *psched = NULL; } diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.h b/drivers/gpu/drm/nouveau/nouveau_sched.h index 20cd1da8db73c3..51ce8dcf62858b 100644 --- a/drivers/gpu/drm/nouveau/nouveau_sched.h +++ b/drivers/gpu/drm/nouveau/nouveau_sched.h @@ -98,6 +98,7 @@ void nouveau_job_free(struct nouveau_job *job); struct nouveau_sched { struct drm_gpu_scheduler base; + struct rcu_head rcu; struct drm_sched_entity entity; struct workqueue_struct *wq; struct mutex mutex; From 50e80e2bb5e2be8515205b9c496b9640ddefa434 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Tue, 15 Sep 2026 01:02:07 +0800 Subject: [PATCH 0757/1417] bpf: Skip unsettled links in link iterator bpf_link_prime() inserts a link into link_idr before anon_inode_getfile() succeeds and before bpf_link_settle() publishes the ID in link->id. bpf_link_by_id() treats such an ID-zero link as unsettled, but the link iterator takes a reference without this check. If anon_inode_getfile() then fails, the creator removes the ID and frees its still-private link directly. The iterator is left with a dangling reference and its next bpf_link_put() accesses freed memory. Treat ID-zero entries as transient in bpf_link_get_curr_or_next(), just as bpf_link_by_id() does. BUG: KASAN: slab-use-after-free in bpf_link_put Write of size 8 by task exp/384 Call Trace: bpf_link_put kernel/bpf/syscall.c:3372 bpf_link_seq_next kernel/bpf/link_iter.c:33 bpf_seq_read kernel/bpf/bpf_iter.c:158 vfs_read fs/read_write.c:572 ksys_read fs/read_write.c:716 do_syscall_64 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe arch/x86/entry/entry_64.S:121 Kernel panic - not syncing: KASAN: panic_on_warn set ... Fixes: 9f8836127308 ("bpf: Add bpf_link iterator") Reported-by: Xiang Mei Signed-off-by: Weiming Shi Signed-off-by: Andrii Nakryiko Link: https://lore.kernel.org/bpf/20260914170206.170723-2-bestswngs@gmail.com --- kernel/bpf/syscall.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 853b47f8138425..244a939b9d2da4 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -6042,7 +6042,10 @@ struct bpf_link *bpf_link_get_curr_or_next(u32 *id) again: link = idr_get_next(&link_idr, id); if (link) { - link = bpf_link_inc_not_zero(link); + if (link->id) + link = bpf_link_inc_not_zero(link); + else + link = ERR_PTR(-EAGAIN); if (IS_ERR(link)) { (*id)++; goto again; From fefd9480ec361969f1a836df46326a1801062c26 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sat, 8 Aug 2026 21:41:37 +0800 Subject: [PATCH 0758/1417] drm/nouveau: fix autosuspend cleanup during teardown nouveau_drm_device_init() calls pm_runtime_use_autosuspend(), but nouveau_drm_device_fini() does not call the matching pm_runtime_dont_use_autosuspend(). If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without calling pm_runtime_dont_use_autosuspend() during teardown, this reference is not dropped and usage_count remains unbalanced. The documentation for pm_runtime_use_autosuspend() also notes that it is important to undo it with pm_runtime_dont_use_autosuspend() at driver exit time, unless runtime PM was initially enabled with devm_pm_runtime_enable(). Add the missing pm_runtime_dont_use_autosuspend() call to the common device teardown path. This issue was found by manual code inspection. Fixes: 5addcf0a5f0f ("nouveau: add runtime PM support (v0.9)") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260808134137.2864847-1-lgs201920130244@gmail.com --- drivers/gpu/drm/nouveau/nouveau_drm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index d0eb29583cb19d..2c7077a49888d7 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -585,6 +585,7 @@ nouveau_drm_device_fini(struct nouveau_drm *drm) if (nouveau_pmops_runtime()) { pm_runtime_get_sync(dev->dev); pm_runtime_forbid(dev->dev); + pm_runtime_dont_use_autosuspend(dev->dev); } nouveau_led_fini(dev); From 28114992dd2f03724eb3d024839b3f9f299656d3 Mon Sep 17 00:00:00 2001 From: Syed Saba Kareem Date: Thu, 17 Sep 2026 22:21:49 +0530 Subject: [PATCH 0759/1417] ASoC: amd: acp: enable TAS2783 and add RT712-VB SoundWire machine The ACP7.0 SoundWire machine table did not cover boards that combine TI TAS2783 smart amplifiers on SoundWire link 0 with a Realtek RT712-VB codec on link 1. In addition, the TAS2783 SoundWire codec driver was not selected by the AMD legacy SoundWire machine config, so it was not built for these platforms. Select SND_SOC_TAS2783_SDW from SND_SOC_AMD_LEGACY_SDW_MACH so the TAS2783 SoundWire codec driver is built together with the AMD legacy (no-DSP) SoundWire machine. Add a new link/address map, acp70_tas2783a_l0_rt712_vb_l1, describing the TAS2783 amplifiers on link 0 and the RT712-VB codec on link 1, and register a corresponding entry in snd_soc_acpi_amd_acp70_sdw_machines[]. The entry uses snd_soc_acpi_amd_sdca_is_device_rt712_vb() as its machine_check callback so it only matches platforms that actually expose the RT712-VB device. Signed-off-by: Syed Saba Kareem Link: https://patch.msgid.link/20260917165314.1880855-1-syed.sabakareem@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/Kconfig | 1 + sound/soc/amd/acp/amd-acp70-acpi-match.c | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig index a04c301df4d616..e880e44b9763fc 100644 --- a/sound/soc/amd/acp/Kconfig +++ b/sound/soc/amd/acp/Kconfig @@ -181,6 +181,7 @@ config SND_SOC_AMD_LEGACY_SDW_MACH select SND_SOC_RT715_SDW select SND_SOC_RT715_SDCA_SDW select SND_SOC_RT722_SDCA_SDW + select SND_SOC_TAS2783_SDW help This option enables Legacy(No DSP) sound card support for SoundWire enabled AMD platforms along with ACP PDM controller. diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c index 815e088b437d7f..2a9da6de7bbcae 100644 --- a/sound/soc/amd/acp/amd-acp70-acpi-match.c +++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c @@ -748,7 +748,27 @@ static const struct snd_soc_acpi_link_adr acp70_rt721_only[] = { {} }; +static const struct snd_soc_acpi_link_adr acp70_tas2783a_l0_rt712_vb_l1[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(tas2783_2_adr), + .adr_d = tas2783_2_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt712_vb_1_group1_adr), + .adr_d = rt712_vb_1_group1_adr, + }, + {} +}; + struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { + { + .link_mask = BIT(0) | BIT(1), + .links = acp70_tas2783a_l0_rt712_vb_l1, + .machine_check = snd_soc_acpi_amd_sdca_is_device_rt712_vb, + .drv_name = "amd_sdw", + }, { .link_mask = BIT(0), .links = acp70_tas2783_2, From 717e0a25036b6c92cecace30913b2d874a4c22b8 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 16:34:39 +0000 Subject: [PATCH 0760/1417] cifs: Fix server use-after-free in cifs_chan_skip_or_disable() When a secondary channel is no longer supported by the server, cifs_chan_skip_or_disable() drops the channel reference with cifs_put_tcp_session() and then continues to use the server pointer by calling cifs_signal_cifsd_for_reconnect() on it and reading its primary_server pointer. cifs_put_tcp_session() can drop the last reference of the channel and tear it down, so both the channel and the primary server (whose reference is also dropped by cifs_put_tcp_session()) can be freed before they are signaled for reconnect. Signal the channel and the primary server and capture the primary server pointer before dropping the channel reference with cifs_put_tcp_session(). Fixes: f591062bdbf4 ("cifs: handle servers that still advertise multichannel after disabling") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2pdu.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index dea05aeb53a18d..880ce12f50c481 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -189,18 +189,19 @@ cifs_chan_skip_or_disable(struct cifs_ses *ses, spin_unlock(&ses->chan_lock); /* - * the above reference of server by channel - * needs to be dropped without holding chan_lock - * as cifs_put_tcp_session takes a higher lock - * i.e. cifs_tcp_ses_lock + * signal the channel and its primary server to + * reconnect before dropping the above reference of + * server by channel, which is done without holding + * chan_lock as cifs_put_tcp_session takes a higher + * lock i.e. cifs_tcp_ses_lock */ - cifs_put_tcp_session(server, from_reconnect); - cifs_signal_cifsd_for_reconnect(server, false); /* mark primary server as needing reconnect */ pserver = server->primary_server; cifs_signal_cifsd_for_reconnect(pserver, false); + + cifs_put_tcp_session(server, from_reconnect); skip_terminate: return -EHOSTDOWN; } From 8e0b235bd918d06f54ba8fddd2c3ddc36ca59c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20T=C3=B6pel?= Date: Tue, 15 Sep 2026 12:49:15 +0200 Subject: [PATCH 0761/1417] eth: fbnic: Fix payload page pool error cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The payload page pool pointer contains an error pointer when its allocation fails. The cleanup path passes that error pointer to page_pool_destroy() instead of destroying the header page pool. This can dereference the error pointer and leave the header page pool allocated. Destroy the header page pool instead. Fixes: 8a11010fdd96 ("eth: fbnic: allocate unreadable page pool for the payloads") Reported-by: Sashiko Link: https://lore.kernel.org/netdev/178915061000.219967.7726187707862333281@kernel.org/ Signed-off-by: Björn Töpel Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260915104917.3978113-1-bjorn@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index e7918d3f6aba9c..661dee1661afeb 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -1622,7 +1622,7 @@ fbnic_alloc_qt_page_pools(struct fbnic_net *fbn, struct fbnic_q_triad *qt, return 0; err_destroy_sub0: - page_pool_destroy(pp); + page_pool_destroy(qt->sub0.page_pool); return PTR_ERR(pp); } From e7d3e2f46dd5a69046e6d95a0f189155a5516b93 Mon Sep 17 00:00:00 2001 From: "Chang S. Bae" Date: Wed, 16 Sep 2026 22:59:39 +0000 Subject: [PATCH 0762/1417] x86/microcode/intel: Reject problematic loading on Granite Rapids systems Microcode updates can usually jump revisions. However, there is an erratum on Granite Rapids systems. If they "jump over" revision 0x1000405, they result in an #MC. Avoid it. Signed-off-by: Chang S. Bae Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Dave Hansen Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260916225939.1144524-1-chang.seok.bae@intel.com --- arch/x86/kernel/cpu/microcode/intel.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/arch/x86/kernel/cpu/microcode/intel.c b/arch/x86/kernel/cpu/microcode/intel.c index 1142183c950c0c..9f09d388ed2056 100644 --- a/arch/x86/kernel/cpu/microcode/intel.c +++ b/arch/x86/kernel/cpu/microcode/intel.c @@ -309,6 +309,26 @@ static void save_microcode_patch(struct microcode_intel *patch) pr_err("Unable to allocate microcode memory size: %u\n", size); } +static bool revision_is_safe(struct cpu_signature *sig, u32 rev) +{ + u32 vfm = IFM(x86_family(sig->sig), x86_model(sig->sig)); + + /* + * Erratum GNR98 can cause #MCs if "jumping over" revision 0x1000405. + * Avoid the jumps. + */ + if (vfm == INTEL_GRANITERAPIDS_X && + x86_stepping(sig->sig) == 1 && + sig->pf & 0x95 && + sig->rev < 0x1000405 && + rev > 0x1000405) { + pr_err_once("Erratum GNR98: skipping revision 0x%x.\n", rev); + return false; + } + + return true; +} + /* Scan blob for microcode matching the boot CPUs family, model, stepping */ static __init struct microcode_intel *scan_microcode(void *data, size_t size, struct ucode_cpu_info *uci, @@ -330,6 +350,9 @@ static __init struct microcode_intel *scan_microcode(void *data, size_t size, if (!intel_find_matching_signature(data, &uci->cpu_sig)) continue; + if (!revision_is_safe(&uci->cpu_sig, mc_header->rev)) + continue; + /* * For saving the early microcode, find the matching revision which * was loaded on the BSP. @@ -878,6 +901,9 @@ static enum ucode_state parse_microcode_blobs(int cpu, struct iov_iter *iter) if (!intel_find_matching_signature(mc, &uci->cpu_sig)) continue; + if (!revision_is_safe(&uci->cpu_sig, mc_header.rev)) + continue; + is_safe = ucode_validate_minrev(&mc_header); if (force_minrev && !is_safe) continue; From 0a5f5d9e94dead312d32c366b917c64e552b72f7 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 16 Sep 2026 06:01:14 -0400 Subject: [PATCH 0763/1417] net/sched: cls_u32: fix manual hash table handle IDR aliasing A u32 hash table created with an explicit handle ('tc filter add ... handle 801: u32 divisor N') keys its IDR entry on the raw handle, while the destroy paths free it under handle2id(handle). The two key domains disagree for handles in the 0x800..0xFFF htid range: handle2id() folds them back into the auto-allocated id space (1..0x7FF). A manual table therefore leaves its raw-keyed IDR entry unreachable on delete (a permanent leak), and its delete can drop the idr entry of an unrelated live auto table. A later auto allocation can then hand out a handle that aliases the live manual table; u32_lookup_ht() first-match routes lookups and TCA_U32_LINK for that htid to the wrong table. Key the divisor-path alloc on handle2id(handle) so allocation and removal share one key domain. A manual handle that maps onto an id already in use is rejected with -ENOSPC, and auto allocation skips ids held by live manual tables. Conditions to recreate: ip link add test0 type dummy tc qdisc add dev test0 clsact tc filter add dev test0 ingress protocol ip pref 1 \ handle 801: u32 divisor 16 tc filter add dev test0 ingress protocol ip pref 2 u32 divisor 16 tc -d filter show dev test0 ingress | grep 'fh 801:' # unpatched: two live tables with handle 0x80100000 (the pref 2 root # hnode is auto-allocated id 1); patched: the auto hnode takes id 2. Also tested with a poc with a live u32 table on the block, add/delete a manual table 'handle 901: u32 divisor 1' twice; unpatched, the re-add fails with -ENOSPC because the raw key leaked on the first delete. Fixes: 73af53d82076 ("net: sched: cls_u32: Fix u32's systematic failure to free IDR entries for hnodes.") Reported-by: Sashiko (gemini + nipa) Closes: https://sashiko.dev/#/patchset/20260822222049.114526-1-jhs@mojatatu.com Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-LQFE.v1.20260911041746.1@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/cls_u32.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c index a3e65c8cf29ef3..76ce2d124079d5 100644 --- a/net/sched/cls_u32.c +++ b/net/sched/cls_u32.c @@ -1003,8 +1003,16 @@ static int u32_change(struct net *net, struct sk_buff *in_skb, return -ENOMEM; } } else { - err = idr_alloc_u32(&tp_c->handle_idr, ht, &handle, - handle, GFP_KERNEL); + /* The IDR is keyed on the mapped id, and that is + * what the destroy paths remove. Ask for it here, + * so a manual handle colliding with the + * auto-allocated id space is rejected (-ENOSPC) + * instead of aliasing a future auto id. + */ + u32 id = handle2id(handle); + + err = idr_alloc_u32(&tp_c->handle_idr, ht, &id, id, + GFP_KERNEL); if (err) { kfree(ht); return err; From 960ab631f3d8789586db71b9914b361059ddcb6e Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 16 Sep 2026 06:01:15 -0400 Subject: [PATCH 0764/1417] selftests/tc-testing: add u32 manual table handle IDR tests 35fc: create a manual table with handle 801:, then add an auto-allocated table. Before the fix, the auto allocation reuses id 1 and hands out the same handle 0x80100000, aliasing the manual table; the test requires the manual 801: handle to keep exactly one entry in the dump. a6e8: with a live u32 table keeping the tc_u_common alive, add and delete a manual table with handle 901:, then re-add it. Unpatched, the delete leaks the raw-keyed IDR entry and the re-add fails with -ENOSPC; the test requires the re-add to succeed. Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-LQFE.v1.20260911041746.2@mojatatu.com Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/filters/u32.json | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json b/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json index e2b03f2b5e89fb..edc5148a8d97e2 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json +++ b/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json @@ -376,5 +376,53 @@ "teardown": [ "$TC qdisc del dev $DUMMY clsact" ] + }, + { + "id": "35fc", + "name": "u32 manual table then auto table: auto allocation must not alias a live manual handle", + "category": [ + "filter", + "u32" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 ingress protocol ip pref 1 handle 801: u32 divisor 16" + ], + "cmdUnderTest": "$TC filter add dev $DEV1 ingress protocol ip pref 2 u32 divisor 16", + "expExitCode": "0", + "verifyCmd": "$TC -d filter show dev $DEV1 ingress", + "matchPattern": "fh 801:", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "a6e8", + "name": "u32 manual table add/del does not leak its idr entry (re-adding the same handle succeeds)", + "category": [ + "filter", + "u32" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 ingress protocol ip pref 1 u32 divisor 16", + "$TC filter add dev $DEV1 ingress protocol ip pref 5 handle 901: u32 divisor 1", + "$TC filter del dev $DEV1 ingress protocol ip pref 5 handle 901: u32" + ], + "cmdUnderTest": "$TC filter add dev $DEV1 ingress protocol ip pref 6 handle 901: u32 divisor 1", + "expExitCode": "0", + "verifyCmd": "$TC -d filter show dev $DEV1 ingress", + "matchPattern": "fh 901: ht divisor 1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] } ] From daf677c2c6449011ee695d55b48b5b2977a36f88 Mon Sep 17 00:00:00 2001 From: Kyle Hendry Date: Tue, 15 Sep 2026 10:39:20 -0700 Subject: [PATCH 0765/1417] net: pcs: rzn1-miic: Fix config array initialization Fix memset parameters to initialize the entire DT value array Fixes: f39e968dc168a7bd ("net: pcs: rzn1-miic: Move configuration data to SoC-specific struct") Reviewed-by: Geert Uytterhoeven Signed-off-by: Kyle Hendry Reviewed-by: Lad Prabhakar Link: https://patch.msgid.link/20260915-rzn1-miic-fix-array-v5-1-b7173fd5b97d@reliablecontrols.com Signed-off-by: Jakub Kicinski --- drivers/net/pcs/pcs-rzn1-miic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/pcs/pcs-rzn1-miic.c b/drivers/net/pcs/pcs-rzn1-miic.c index 2b72fa98ddf13e..cb74861e823c8e 100644 --- a/drivers/net/pcs/pcs-rzn1-miic.c +++ b/drivers/net/pcs/pcs-rzn1-miic.c @@ -683,7 +683,8 @@ static int miic_parse_dt(struct miic *miic, u32 *mode_cfg) if (!dt_val) return -ENOMEM; - memset(dt_val, MIIC_MODCTRL_CONF_NONE, sizeof(*dt_val)); + memset(dt_val, MIIC_MODCTRL_CONF_NONE, + sizeof(*dt_val) * miic->of_data->conf_conv_count); if (of_property_read_u32(np, "renesas,miic-switch-portin", &conf) == 0) dt_val[0] = conf; From 39c6580765dad6477fb2637f6f616e0d276aae65 Mon Sep 17 00:00:00 2001 From: Heyang Tan Date: Mon, 14 Sep 2026 10:05:21 +0800 Subject: [PATCH 0766/1417] octeontx2-af: use seq_file for rsrc_alloc debugfs The rsrc_alloc debugfs reader writes rows directly to userspace without respecting the caller's read count. It also uses the current row length as the userspace stride, which can corrupt output when rows have different widths. Use seq_file to handle userspace buffer sizes, offsets, and partial reads, and write output columns directly to the seq_file buffer. Fixes: 23205e6d06d4 ("octeontx2-af: Dump current resource provisioning status") Signed-off-by: Heyang Tan Reviewed-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260914020521.146-1-thy15333007817@163.com Signed-off-by: Jakub Kicinski --- .../marvell/octeontx2/af/rvu_debugfs.c | 104 ++++++------------ 1 file changed, 33 insertions(+), 71 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c index 904374baae6f32..2927633465d96f 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c @@ -714,110 +714,72 @@ static int get_max_column_width(struct rvu *rvu) } /* Dumps current provisioning status of all RVU block LFs */ -static ssize_t rvu_dbg_rsrc_attach_status(struct file *filp, - char __user *buffer, - size_t count, loff_t *ppos) +static int rvu_dbg_rsrc_attach_status(struct seq_file *filp, void *unused) { - int index, off = 0, flag = 0, len = 0, i = 0; - struct rvu *rvu = filp->private_data; - int bytes_not_copied = 0; + struct rvu *rvu = filp->private; + int index, pf, vf, pcifunc; struct rvu_block block; - int pf, vf, pcifunc; - int buf_size = 2048; int lf_str_size; char *lfs; - char *buf; - - /* don't allow partial reads */ - if (*ppos != 0) - return 0; - - buf = kzalloc(buf_size, GFP_KERNEL); - if (!buf) - return -ENOMEM; - /* Get the maximum width of a column */ lf_str_size = get_max_column_width(rvu); + if (lf_str_size < 0) + return lf_str_size; lfs = kzalloc(lf_str_size, GFP_KERNEL); - if (!lfs) { - kfree(buf); + if (!lfs) return -ENOMEM; - } - off += scnprintf(&buf[off], buf_size - 1 - off, "%-*s", lf_str_size, - "pcifunc"); - for (index = 0; index < BLK_COUNT; index++) - if (strlen(rvu->hw->block[index].name)) { - off += scnprintf(&buf[off], buf_size - 1 - off, - "%-*s", lf_str_size, - rvu->hw->block[index].name); - } - off += scnprintf(&buf[off], buf_size - 1 - off, "\n"); - bytes_not_copied = copy_to_user(buffer + (i * off), buf, off); - if (bytes_not_copied) - goto out; + seq_printf(filp, "%-*s", lf_str_size, "pcifunc"); + for (index = 0; index < BLK_COUNT; index++) + if (strlen(rvu->hw->block[index].name)) + seq_printf(filp, "%-*s", lf_str_size, + rvu->hw->block[index].name); - i++; - *ppos += off; + seq_putc(filp, '\n'); for (pf = 0; pf < rvu->hw->total_pfs; pf++) { for (vf = 0; vf <= rvu->hw->total_vfs; vf++) { - off = 0; - flag = 0; pcifunc = rvu_make_pcifunc(rvu->pdev, pf, vf); if (!pcifunc) continue; - if (vf) { + for (index = 0; index < BLK_COUNT; index++) { + block = rvu->hw->block[index]; + if (!strlen(block.name)) + continue; + lfs[0] = '\0'; + get_lf_str_list(&block, pcifunc, lfs); + if (strlen(lfs)) + break; + } + if (index == BLK_COUNT) + continue; + + if (vf) sprintf(lfs, "PF%d:VF%d", pf, vf - 1); - off = scnprintf(&buf[off], - buf_size - 1 - off, - "%-*s", lf_str_size, lfs); - } else { + else sprintf(lfs, "PF%d", pf); - off = scnprintf(&buf[off], - buf_size - 1 - off, - "%-*s", lf_str_size, lfs); - } + seq_printf(filp, "%-*s", lf_str_size, lfs); for (index = 0; index < BLK_COUNT; index++) { block = rvu->hw->block[index]; if (!strlen(block.name)) continue; - len = 0; - lfs[len] = '\0'; - get_lf_str_list(&block, pcifunc, lfs); - if (strlen(lfs)) - flag = 1; - off += scnprintf(&buf[off], buf_size - 1 - off, - "%-*s", lf_str_size, lfs); - } - if (flag) { - off += scnprintf(&buf[off], - buf_size - 1 - off, "\n"); - bytes_not_copied = copy_to_user(buffer + - (i * off), - buf, off); - if (bytes_not_copied) - goto out; - - i++; - *ppos += off; + lfs[0] = '\0'; + get_lf_str_list(&block, pcifunc, lfs); + seq_printf(filp, "%-*s", lf_str_size, lfs); } + seq_putc(filp, '\n'); } } -out: kfree(lfs); - kfree(buf); - if (bytes_not_copied) - return -EFAULT; - return *ppos; + return 0; } -RVU_DEBUG_FOPS(rsrc_status, rsrc_attach_status, NULL); +RVU_DEBUG_SEQ_FOPS(rsrc_status, rsrc_attach_status, NULL); static int rvu_dbg_rvu_pf_cgx_map_display(struct seq_file *filp, void *unused) { From 1d653a183973f5283a3db5a38cd5e195eb152244 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 17 Sep 2026 22:24:07 +0100 Subject: [PATCH 0767/1417] fprobe: Terminate the fgraph_data list when the reservation is not filled fprobe_fgraph_entry() reserves shadow stack space for every fprobe with an exit handler, but only fills it for those whose entry handler returns 0. fgraph_reserve_data() does not clear the area, so fprobe_return() parses the unused tail as headers left over from an earlier call, and an exit handler can run twice or despite its entry handler asking to skip it. Write a zero word after the last entry to terminate the walk. A zeroed slot does not decode to a NULL fprobe on the arches that encode the header into one unsigned long, since arch_decode_fprobe_header_fp() ORs in FPROBE_HEADER_MSB_PATTERN, so make read_fprobe_header() return NULL for a zeroed slot. Link: https://lore.kernel.org/all/20260917212407.384468-1-devnexen@gmail.com/ Fixes: e0a384434ae1 ("tracing: fprobe: do not zero out unused fgraph_data") Cc: stable@vger.kernel.org Suggested-by: Masami Hiramatsu (Google) Signed-off-by: David Carlier Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/fprobe.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c index 1e9b00997ff274..9f2d98181779af 100644 --- a/kernel/trace/fprobe.c +++ b/kernel/trace/fprobe.c @@ -171,6 +171,11 @@ static inline bool write_fprobe_header(unsigned long *stack, static inline void read_fprobe_header(unsigned long *stack, struct fprobe **fp, unsigned int *size_words) { + if (!*stack) { + *fp = NULL; + *size_words = 0; + return; + } *fp = arch_decode_fprobe_header_fp(*stack); *size_words = arch_decode_fprobe_header_size(*stack); } @@ -203,6 +208,12 @@ static inline void read_fprobe_header(unsigned long *stack, { struct __fprobe_header *fph = (struct __fprobe_header *)stack; + if (!*stack) { + *fp = NULL; + *size_words = 0; + return; + } + *fp = fph->fp; *size_words = fph->size_words; } @@ -635,6 +646,10 @@ static int fprobe_fgraph_entry(struct ftrace_graph_ent *trace, struct fgraph_ops } } + /* Terminate the list, fgraph_reserve_data() does not clear it. */ + if (used && used < reserved_words) + fgraph_data[used] = 0; + /* If any exit_handler is set, data must be used. */ return used != 0; } From d09e8f64653c93da5793c16be19330968f2a32e6 Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Tue, 15 Sep 2026 14:34:57 +0300 Subject: [PATCH 0768/1417] net/mlx5: devcom, Base component size on linked devices mlx5_devcom_comp_get_size() returns the component's kref count. That kref is bumped in mlx5_devcom_register_component() under comp_list_lock, before the comp_dev is linked onto comp_dev_list_head under comp->sem. The event broadcast (mlx5_devcom_locked_send_event()) walks that list. Hence, a caller can read the expected size, but send_event won't be sent to all peers. In the SD group registration path, this lets a member broadcast its role-election event over an incomplete list, electing a primary that never completes the group, is never marked ready, and leaves the group with a stale primary. Track the number of linked comp_devs in a dedicated counter, maintained under comp->sem together with the list add/remove, and return it from mlx5_devcom_comp_get_size(). Fixes: 9bb1ac80738a ("net/mlx5: devcom, Add component size getter") Signed-off-by: Shay Drory Reviewed-by: Akiva Goldberger Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260915113459.3934760-2-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c index 64f92427602de4..75855481522b11 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c @@ -37,6 +37,7 @@ struct mlx5_devcom_comp { struct mlx5_devcom_key key; mlx5_devcom_event_handler_t handler; struct kref ref; + int nr_devs; bool ready; struct rw_semaphore sem; struct lock_class_key lock_key; @@ -170,6 +171,7 @@ devcom_alloc_comp_dev(struct mlx5_devcom_dev *devc, down_write(&comp->sem); list_add_tail(&devcom->list, &comp->comp_dev_list_head); + WRITE_ONCE(comp->nr_devs, comp->nr_devs + 1); up_write(&comp->sem); return devcom; @@ -182,6 +184,7 @@ devcom_free_comp_dev(struct mlx5_devcom_comp_dev *devcom) down_write(&comp->sem); list_del(&devcom->list); + WRITE_ONCE(comp->nr_devs, comp->nr_devs - 1); up_write(&comp->sem); kref_put(&devcom->devc->ref, mlx5_devcom_dev_release); @@ -284,7 +287,7 @@ int mlx5_devcom_comp_get_size(struct mlx5_devcom_comp_dev *devcom) { struct mlx5_devcom_comp *comp = devcom->comp; - return kref_read(&comp->ref); + return READ_ONCE(comp->nr_devs); } int mlx5_devcom_locked_send_event(struct mlx5_devcom_comp_dev *devcom, From e1e29ada2b938b13ba689a06a8bd8604564da2b3 Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Tue, 15 Sep 2026 14:34:58 +0300 Subject: [PATCH 0769/1417] net/mlx5: SD, unload reps on shared FDB create error path mlx5_lag_shared_fdb_create() sets sd_fdb_active on every group member before reloading the representors, so mlx5_lag_is_active() is already true and the guard in mlx5_esw_offloads_rep_load() does not skip the VF/SF reps. If the reload then fails, the error path clears sd_fdb_active and destroys the shared FDB, leaving the reps loaded while SD LAG is inactive - the state cited commit was written to prevent. Unload the reps in the error path as well. Fixes: 68c2dd59a6c7 ("net/mlx5: E-Switch, Tie rep load/unload to SD LAG state") Signed-off-by: Shay Drory Reviewed-by: Akiva Goldberger Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260915113459.3934760-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c index 6b4ad3c53f2f33..424040918fa374 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c @@ -270,6 +270,7 @@ int mlx5_lag_shared_fdb_create(struct mlx5_lag *ldev, pf->sd_fdb_active = false; } mlx5_lag_destroy_single_fdb_filter(ldev, group_id); + mlx5_lag_unload_reps_from_locked(ldev, filter); } err_add_devices: mlx5_lag_add_devices_filter(ldev, filter); From bae23d1ae62092c7f0ec6d5f7e1be5d164822638 Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Tue, 15 Sep 2026 14:34:59 +0300 Subject: [PATCH 0770/1417] net/mlx5: LAG, reload IB reps of LAG master before the rest In a shared-FDB LAG the master device creates the bond IB device; the other LAG members do not create their own, they populate a port inside the master's IB device. mlx5_lag_reload_ib_reps_unlocked() reloaded the members' IB reps in iteration order, with no guarantee the master is reloaded first. When a non-master member is reloaded before the master, it tries to populate its port in an IB device that has not been recreated yet. Hence, reload the master's IB reps first, then every other member. Fixes: 2b204cdb1206 ("net/mlx5: LAG, use xa_alloc to manage LAG device indices") Signed-off-by: Shay Drory Reviewed-by: Akiva Goldberger Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260915113459.3934760-4-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/lag/lag.c | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index c655f6e32e9b07..dd14cdc378de01 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -1266,25 +1266,45 @@ void mlx5_lag_remove_devices(struct mlx5_lag *ldev) mlx5_lag_remove_devices_filter(ldev, MLX5_LAG_FILTER_PORTS); } +static int mlx5_lag_reload_ib_reps_idx(struct mlx5_lag *ldev, int idx, + u32 flags) +{ + struct lag_func *pf = mlx5_lag_pf(ldev, idx); + struct mlx5_eswitch *esw; + int ret; + + if (pf->dev->priv.flags & flags) + return 0; + + esw = pf->dev->priv.eswitch; + mlx5_esw_reps_block(esw); + ret = mlx5_eswitch_reload_ib_reps(esw); + mlx5_esw_reps_unblock(esw); + + return ret; +} + static int mlx5_lag_reload_ib_reps_unlocked(struct mlx5_lag *ldev, u32 flags, u32 filter, bool cont_on_fail) { - struct lag_func *pf; + int master_idx = mlx5_lag_get_dev_index_by_seq_filter(ldev, MLX5_LAG_P1, + filter); int ret; int i; + if (master_idx < 0) + return -EINVAL; + + ret = mlx5_lag_reload_ib_reps_idx(ldev, master_idx, flags); + if (ret && !cont_on_fail) + return ret; + mlx5_lag_for_each(i, 0, ldev, filter) { - pf = mlx5_lag_pf(ldev, i); - if (!(pf->dev->priv.flags & flags)) { - struct mlx5_eswitch *esw; - - esw = pf->dev->priv.eswitch; - mlx5_esw_reps_block(esw); - ret = mlx5_eswitch_reload_ib_reps(esw); - mlx5_esw_reps_unblock(esw); - if (ret && !cont_on_fail) - return ret; - } + if (i == master_idx) + continue; + ret = mlx5_lag_reload_ib_reps_idx(ldev, i, flags); + if (ret && !cont_on_fail) + return ret; } return 0; From 261b61d3735b042ae25634f795c4540be0fc140c Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:09 +0200 Subject: [PATCH 0771/1417] bpf: Make post-verification instruction rewrites killable After do_check() returns, the verifier runs several instruction rewrite passes. Some of them patch or remove one instruction at a time. Each operation moves the remaining instruction and auxiliary-data arrays and adjusts all branch offsets, making the overall work quadratic in the program length. A privileged loader can submit 131072 unconditional jumps by zero followed by a valid return. Verification finishes quickly, but bpf_opt_remove_nops() then spends a long time removing each jump separately. Since this post-verification work neither checks for signals nor reschedules, a pending SIGKILL cannot terminate the task until the rewrite finishes. Make bpf_patch_insn_data() and verifier_remove_insns() common cancellation and rescheduling points. These helpers run from BPF_PROG_LOAD process context, and bpf_patch_insn_data() can already sleep while reallocating auxiliary data. Report interrupted constant blinding as -EINTR and propagate it through both JIT paths, including kernels that permit interpreter fallback. Other blinding failures retain the existing fallback behavior. This does not reduce the quadratic cost of the rewrite passes, but it makes the work preemptible and allows a killed loader to be torn down promptly. Fixes: 52875a04f4b2 ("bpf: verifier: remove dead code") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-2-memxor@gmail.com Signed-off-by: Eduard Zingerman --- kernel/bpf/core.c | 21 +++++++++++++++++---- kernel/bpf/fixups.c | 24 ++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 8b294dfc1ad4de..2e3bf8113ae9b0 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -1619,6 +1620,8 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp * fix it up here on error. */ bpf_jit_prog_release_other(prog, clone); + if (env && fatal_signal_pending(current)) + return ERR_PTR(-EINTR); return IS_ERR(tmp) ? tmp : ERR_PTR(-ENOMEM); } @@ -2636,11 +2639,14 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc orig_prog = prog; prog = bpf_jit_blind_constants(env, prog); /* - * If blinding was requested and we failed during blinding, we must fall - * back to the interpreter. + * Fall back to the interpreter after blinding failures, except when + * the loader was killed. */ - if (IS_ERR(prog)) + if (IS_ERR(prog)) { + if (PTR_ERR(prog) == -EINTR) + return prog; goto out_restore; + } prog = bpf_int_jit_compile(env, prog); if (prog->jited) { @@ -2659,6 +2665,8 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct bpf_prog *fp, int *err) { + struct bpf_prog *jit_prog; + /* In case of BPF to BPF calls, verifier did all the prep * work with regards to JITing, etc. */ @@ -2681,7 +2689,12 @@ struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct if (*err) return fp; - fp = bpf_prog_jit_compile(env, fp); + jit_prog = bpf_prog_jit_compile(env, fp); + if (IS_ERR(jit_prog)) { + *err = PTR_ERR(jit_prog); + return fp; + } + fp = jit_prog; bpf_prog_jit_attempt_done(fp); if (!fp->jited && jit_needed) { *err = -ENOTSUPP; diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c index 52d3cec336727f..d6f83521fc78e6 100644 --- a/kernel/bpf/fixups.c +++ b/kernel/bpf/fixups.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "disasm.h" @@ -306,12 +307,28 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len) } } +/* + * Some post-verification instruction rewriting passes require an + * O(prog->len) operation per instruction. Keep their shared primitives + * killable and preemptible. + */ +static bool bpf_rewrite_must_abort(void) +{ + if (fatal_signal_pending(current)) + return true; + cond_resched(); + return false; +} + struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, const struct bpf_insn *patch, u32 len) { struct bpf_prog *new_prog; struct bpf_insn_aux_data *new_data = NULL; + if (bpf_rewrite_must_abort()) + return NULL; + if (len > 1) { new_data = vrealloc(env->insn_aux_data, array_size(env->prog->len + len - 1, @@ -523,6 +540,9 @@ static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt) unsigned int orig_prog_len = env->prog->len; int err; + if (bpf_rewrite_must_abort()) + return -EINTR; + if (bpf_prog_is_offloaded(env->prog->aux)) bpf_prog_offload_remove_insns(env, off, cnt); @@ -1356,7 +1376,7 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env) } prog = bpf_jit_blind_constants(env, prog); if (IS_ERR(prog)) { - err = -ENOMEM; + err = PTR_ERR(prog); prog = orig_prog; goto out_restore; } @@ -1433,7 +1453,7 @@ int bpf_fixup_call_args(struct bpf_verifier_env *env) err = bpf_jit_subprogs(env); if (err == 0) return 0; - if (err == -EFAULT) + if (err == -EFAULT || err == -EINTR) return err; } #ifndef CONFIG_BPF_JIT_ALWAYS_ON From fd16449a9b3b31a8f18944c2f0e29e4e218ca2cf Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:10 +0200 Subject: [PATCH 0772/1417] bpf: Preserve packet pointer class displacement in regsafe() regsafe() maps packet pointer IDs between states and checks that each current register range is a subset of the corresponding explored register range. It does not, however, preserve the displacement between registers that share a packet pointer ID. This is unsound because packet range is shared by ID. A bounds check on one class member updates every member, and a later access can consume the range through another member. Commit 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") folded the fixed pointer offset into r64 and removed the old off equality check, so two individually narrower registers can prune even when their displacement has changed. The explored path can then license an out-of-bounds packet access on the pruned path. Require matching range bases for packet pointers with an ID. Together with the existing ID mapping, this preserves the displacement between members of each packet-pointer class without adding per-ID state. Packet pointers without an ID remain unaffected. Fixes: 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-3-memxor@gmail.com Signed-off-by: Eduard Zingerman --- kernel/bpf/states.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c index 66fb11b6c6a765..6c88ad95b63b76 100644 --- a/kernel/bpf/states.c +++ b/kernel/bpf/states.c @@ -635,6 +635,9 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, /* id relations must be preserved */ if (!check_ids(rold->id, rcur->id, idmap)) return false; + /* Preserve displacements between pointers sharing an ID. */ + if (rold->id && rold->r64.base != rcur->r64.base) + return false; /* new val must satisfy old val knowledge */ return range_within(rold, rcur) && tnum_in(rold->var_off, rcur->var_off); From 2059d9af54f0d66deb237aa75d2ed28648df05a1 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:11 +0200 Subject: [PATCH 0773/1417] selftests/bpf: Test packet pointer class displacement pruning Add two paths whose packet pointer ranges are individually compatible at a join but whose members have different relative displacements. The first path proves an eight-byte access through one member. On the second path, the same guard only proves that the access starts before data_end. An affected verifier prunes the second path and accepts the program. With packet pointer class displacement preserved, it explores that path and rejects the out-of-bounds access. Read the unknown offset and branch selector directly from XDP context fields, and force state checkpoints so the pruning attempt does not depend on the verifier checkpoint heuristics. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://patch.msgid.link/20260917233222.2542500-4-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../progs/verifier_xdp_direct_packet_access.c | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c index 0b86d95a413359..9866bc154194ab 100644 --- a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c +++ b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c @@ -1719,4 +1719,39 @@ l0_%=: r0 = 0; \ : __clobber_all); } +SEC("xdp") +__description("XDP pkt regsafe preserves packet pointer class displacement") +__failure __msg("R2 min value is outside of the allowed memory range") +__flag(BPF_F_ANY_ALIGNMENT) __flag(BPF_F_TEST_STATE_FREQ) +__naked void pkt_regsafe_class_displacement(void) +{ + asm volatile (" \ + r8 = *(u32 *)(r1 + %[xdp_md_data_end]); \ + r9 = *(u32 *)(r1 + %[xdp_md_data]); \ + r4 = *(u32 *)(r1 + %[xdp_md_rx_queue_index]); \ + r4 &= 15; \ + r0 = *(u32 *)(r1 + %[xdp_md_ingress_ifindex]); \ + if r0 != 0 goto l0_%=; \ + r2 = r9; \ + r2 += r4; \ + r3 = r2; \ + r3 += 8; \ + goto l1_%=; \ +l0_%=: r4 &= 3; \ + r4 += 8; \ + r2 = r9; \ + r2 += r4; \ + r3 = r2; \ +l1_%=: if r3 > r8 goto l2_%=; \ + r0 = *(u64 *)(r2 + 0); \ +l2_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(xdp_md_data, offsetof(struct xdp_md, data)), + __imm_const(xdp_md_data_end, offsetof(struct xdp_md, data_end)), + __imm_const(xdp_md_rx_queue_index, offsetof(struct xdp_md, rx_queue_index)), + __imm_const(xdp_md_ingress_ifindex, offsetof(struct xdp_md, ingress_ifindex)) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; From c26e97721b172163042b98572fead234797f2c3c Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:12 +0200 Subject: [PATCH 0774/1417] bpf: Apply CO-RE relocations before subprogram validation check_subprogs() verifies that each subprogram ends in an exit or an unconditional jump before in-kernel CO-RE relocations are applied. An unresolved relocation can then replace that terminal instruction with an invalid helper call. The resulting fall-through into another subprogram breaks the CFG invariant used by postorder and stack liveness analysis, which can write past their per-subprogram arrays. Apply CO-RE relocations immediately after preparing the program BTF, before subprogram discovery and validation. Keep func_info and line_info validation after subprogram discovery because those records depend on the complete subprogram layout. Reject an ldimm64 first slot at the end of the instruction stream before CO-RE can inspect its missing second slot. check_subprogs() previously rejected this form before relocation processing because it is not a valid subprogram terminator. Moving CO-RE ahead of check_subprogs() removes that implicit protection, so perform an explicit check before applying relocations. Include core_relo_cnt when deciding whether to prepare program BTF. A load that supplied only CO-RE relocation metadata previously skipped both BTF setup and relocation processing. Fixes: fbd94c7afcf9 ("bpf: Pass a set of bpf_core_relo-s to prog_load command.") Suggested-by: Andrii Nakryiko Suggested-by: Alexei Starovoitov Suggested-by: Eduard Zingerman Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-5-memxor@gmail.com Signed-off-by: Eduard Zingerman --- include/linux/bpf_verifier.h | 2 ++ kernel/bpf/check_btf.c | 12 ++++-------- kernel/bpf/verifier.c | 12 +++++++++++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 36b65797877d09..bba5a727c6516c 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -1197,6 +1197,8 @@ static inline void bpf_trampoline_unpack_key(u64 key, u32 *obj_id, u32 *btf_id) int bpf_prepare_btf_info(struct bpf_verifier_env *env, const union bpf_attr *attr, bpfptr_t uattr); +int bpf_check_core_relo(struct bpf_verifier_env *env, + const union bpf_attr *attr, bpfptr_t uattr); int bpf_check_btf_info(struct bpf_verifier_env *env, const union bpf_attr *attr, bpfptr_t uattr); diff --git a/kernel/bpf/check_btf.c b/kernel/bpf/check_btf.c index 0e8b3ccc7a5b94..4c1ed842f661fe 100644 --- a/kernel/bpf/check_btf.c +++ b/kernel/bpf/check_btf.c @@ -338,9 +338,9 @@ static int check_btf_line(struct bpf_verifier_env *env, #define MIN_CORE_RELO_SIZE sizeof(struct bpf_core_relo) #define MAX_CORE_RELO_SIZE MAX_FUNCINFO_REC_SIZE -static int check_core_relo(struct bpf_verifier_env *env, - const union bpf_attr *attr, - bpfptr_t uattr) +int bpf_check_core_relo(struct bpf_verifier_env *env, + const union bpf_attr *attr, + bpfptr_t uattr) { u32 i, nr_core_relo, ncopy, expected_size, rec_size; struct bpf_core_relo core_relo = {}; @@ -414,7 +414,7 @@ int bpf_prepare_btf_info(struct bpf_verifier_env *env, struct btf *btf; int err; - if (!attr->func_info_cnt && !attr->line_info_cnt) { + if (!attr->func_info_cnt && !attr->line_info_cnt && !attr->core_relo_cnt) { if (check_abnormal_return(env)) return -EINVAL; return 0; @@ -455,9 +455,5 @@ int bpf_check_btf_info(struct bpf_verifier_env *env, if (err) return err; - err = check_core_relo(env, attr, uattr); - if (err) - return err; - return 0; } diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 5d7080c260d84d..33161dc6456811 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -21199,6 +21199,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, ret = bpf_diag_init(env); if (ret) goto err_prep; + if (env->prog->insnsi[env->prog->len - 1].code == (BPF_LD | BPF_IMM | BPF_DW)) { + verbose(env, "invalid bpf_ld_imm64 insn\n"); + ret = -EINVAL; + goto err_prep; + } if (env->signature) { ret = bpf_prog_calc_tag(env->prog); if (ret < 0) @@ -21274,6 +21279,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, if (ret < 0) goto skip_full_check; + /* Apply CO-RE before validating the program's instruction layout. */ + ret = bpf_check_core_relo(env, attr, uattr); + if (ret < 0) + goto skip_full_check; + /* Discover all subprograms before validating their layout and BTF. */ ret = add_subprogs(env); if (ret < 0) @@ -21283,7 +21293,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, if (ret < 0) goto skip_full_check; - /* Validate BTF against the complete subprogram layout and apply CO-RE. */ + /* Validate BTF against the complete subprogram layout. */ ret = bpf_check_btf_info(env, attr, uattr); if (ret < 0) goto skip_full_check; From 968ee7c06b62f1ac4597c351a45c2219a678a458 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:13 +0200 Subject: [PATCH 0775/1417] selftests/bpf: Test early in-kernel CO-RE relocation Add a raw program load with CO-RE relocation metadata but no func_info or line_info. Place the relocation in dead code and require the poisoning log, proving that the kernel processes standalone CO-RE metadata instead of silently skipping it. Also give a subprogram a relocatable immediate as its terminal instruction. Require the relocation's poisoning log before check_subprogs() rejects the resulting fall-through. With the old ordering, check_subprogs() rejects the original terminal instruction before CO-RE can emit the substitution log, so the test continues to distinguish the ordering after relocation target validation is tightened. Submit a trailing ldimm64 first slot with CO-RE metadata and require the early structural diagnostic. This exercises the check that protects relocation processing instead of the later regular instruction validation. Load the standalone instruction stream without relocation metadata first to ensure that CO-RE processing causes its poisoning diagnostic. Encode the fixed BTF metadata directly with the selftest BTF helpers. Suggested-by: Eduard Zingerman Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-6-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../selftests/bpf/prog_tests/core_reloc_raw.c | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c index a18d3680fb1697..bb19e49dd87dea 100644 --- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c +++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c @@ -14,6 +14,138 @@ static char log[16 * 1024]; +static int load_core_relo_insns(int btf_fd, struct bpf_insn *insns, int insn_cnt, + struct bpf_func_info *funcs, int func_cnt, + int enum_id, int access_str_off, int insn_idx, + bool relocate) +{ + struct bpf_core_relo relo = { + .insn_off = insn_idx * sizeof(struct bpf_insn), + .type_id = enum_id, + .access_str_off = access_str_off, + .kind = BPF_CORE_ENUMVAL_VALUE, + }; + union bpf_attr attr = { + .prog_type = BPF_PROG_TYPE_SOCKET_FILTER, + .insn_cnt = insn_cnt, + .insns = (__u64)insns, + .license = (__u64)"GPL", + .log_buf = (__u64)log, + .log_size = sizeof(log), + .log_level = 2, + .prog_btf_fd = btf_fd, + .func_info_rec_size = sizeof(struct bpf_func_info), + .func_info = (__u64)funcs, + .func_info_cnt = func_cnt, + }; + + if (relocate) { + attr.core_relo_cnt = 1; + attr.core_relos = (__u64)&relo; + attr.core_relo_rec_size = sizeof(relo); + } + memset(log, 0, sizeof(log)); + return sys_bpf_prog_load(&attr, sizeof(attr), 1); +} + +static void test_early_core_relo(void) +{ + struct test_btf { + struct btf_header hdr; + __u32 types[18]; + char strings[64]; + } raw_btf = { + .hdr = { + .magic = BTF_MAGIC, + .version = BTF_VERSION, + .hdr_len = sizeof(struct btf_header), + .type_off = 0, + .type_len = sizeof(raw_btf.types), + .str_off = offsetof(struct test_btf, strings) - + offsetof(struct test_btf, types), + .str_len = sizeof(raw_btf.strings), + }, + .types = { + BTF_TYPE_INT_ENC(1, BTF_INT_SIGNED, 0, 32, 4), /* [1] int */ + BTF_FUNC_PROTO_ENC(1, 0), /* [2] int (*)(void) */ + BTF_FUNC_ENC(5, 2), /* [3] main_fn */ + BTF_FUNC_ENC(13, 2), /* [4] sub_fn */ + BTF_TYPE_ENC(20, BTF_INFO_ENC(BTF_KIND_ENUM, 0, 1), 4), /* [5] enum */ + BTF_ENUM_ENC(45, 0), /* value = 0 */ + }, + .strings = "\0int\0main_fn\0sub_fn\0core_relo_poison_missing\0value\0" "0", + }; + struct bpf_func_info funcs[] = { + { .insn_off = 0, .type_id = 3 }, + { .insn_off = 3, .type_id = 4 }, + }; + struct bpf_insn core_only[] = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + struct bpf_insn subprog[] = { + BPF_CALL_REL(2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + struct bpf_insn truncated_ldimm64[] = { + BPF_RAW_INSN(BPF_LD | BPF_IMM | BPF_DW, 0, 0, 0, 0), + }; + int access_str_off = 51; /* offset of "0" */ + int enum_id = 5; + int btf_fd, prog_fd = -1; + + btf_fd = bpf_btf_load(&raw_btf, sizeof(raw_btf), NULL); + if (!ASSERT_GE(btf_fd, 0, "btf_load")) + goto cleanup; + + if (test__start_subtest("without_func_info")) { + prog_fd = load_core_relo_insns(btf_fd, core_only, ARRAY_SIZE(core_only), NULL, 0, + enum_id, access_str_off, 2, false); + if (!ASSERT_GE(prog_fd, 0, "control_load")) + goto cleanup; + close(prog_fd); + prog_fd = load_core_relo_insns(btf_fd, core_only, ARRAY_SIZE(core_only), NULL, 0, + enum_id, access_str_off, 2, true); + if (!ASSERT_GE(prog_fd, 0, "poisoned_load")) + goto cleanup; + ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log"); + close(prog_fd); + prog_fd = -1; + } + + if (test__start_subtest("before_subprog_validation")) { + prog_fd = load_core_relo_insns(btf_fd, subprog, ARRAY_SIZE(subprog), funcs, 2, + enum_id, access_str_off, 2, true); + if (!ASSERT_LT(prog_fd, 0, "poisoned_load")) + goto cleanup; + ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log"); + ASSERT_HAS_SUBSTR(log, "last insn is not an exit or jmp", "poisoned_load_log"); + } + + if (test__start_subtest("truncated_ldimm64")) { + prog_fd = load_core_relo_insns(btf_fd, truncated_ldimm64, + ARRAY_SIZE(truncated_ldimm64), NULL, 0, + enum_id, access_str_off, 0, true); + if (!ASSERT_LT(prog_fd, 0, "truncated_load")) + goto cleanup; + ASSERT_HAS_SUBSTR(log, "invalid bpf_ld_imm64 insn", "truncated_load_log"); + } + +cleanup: + if (env.verbosity > VERBOSE_NORMAL && log[0]) { + printf("-------- program load log start --------\n"); + printf("%s", log); + printf("-------- program load log end ----------\n"); + } + close(prog_fd); + close(btf_fd); +} + /* Check that verifier rejects BPF program containing relocation * pointing to non-existent BTF type. */ @@ -120,6 +252,7 @@ static void test_bad_local_id(void) void test_core_reloc_raw(void) { + test_early_core_relo(); if (test__start_subtest("bad_local_id")) test_bad_local_id(); } From 394ae398337c5f87e567f6cd63b937fc2b2f6ddc Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:14 +0200 Subject: [PATCH 0776/1417] bpf: Restrict CO-RE poisoning to relocatable instructions CO-RE relocation records can name any instruction offset. When a relocation cannot be resolved, bpf_core_patch_insn() currently poisons its target before checking whether that instruction is a valid relocation target. Malformed metadata can therefore replace jumps, calls, exits, register-source arithmetic, or non-immediate loads instead of failing at the relocation step. Handle poisoning only after the instruction has passed the same class and operand-form checks used for a resolved relocation. Route invalid forms through the existing diagnostic and return a hard error. Keep poisoning supported instructions, including both halves of a plain ldimm64, so an unresolved relocation in dead code remains valid. Extend bpf_core_poison_insn() to poison both halves of ldimm64, and return its status directly from each validated instruction case. This avoids routing the success path through a common label and leaves the helper free to report errors. The shared relocation code applies this restriction to both libbpf and in-kernel CO-RE. Fixes: d7a252708dbc ("libbpf: Improve handling of failed CO-RE relocations") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-7-memxor@gmail.com Signed-off-by: Eduard Zingerman --- tools/lib/bpf/relo_core.c | 58 +++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/tools/lib/bpf/relo_core.c b/tools/lib/bpf/relo_core.c index 8ad2715721cfd3..2672623a419890 100644 --- a/tools/lib/bpf/relo_core.c +++ b/tools/lib/bpf/relo_core.c @@ -980,23 +980,30 @@ static int bpf_core_calc_relo(const char *prog_name, } /* - * Turn instruction for which CO_RE relocation failed into invalid one with + * Turn instruction for which CO-RE relocation failed into invalid one with * distinct signature. */ -static void bpf_core_poison_insn(const char *prog_name, int relo_idx, - int insn_idx, struct bpf_insn *insn) +static int bpf_core_poison_insn(const char *prog_name, int relo_idx, + struct bpf_insn *insn, int insn_idx) { - pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n", - prog_name, relo_idx, insn_idx); - insn->code = BPF_JMP | BPF_CALL; - insn->dst_reg = 0; - insn->src_reg = 0; - insn->off = 0; - /* if this instruction is reachable (not a dead code), - * verifier will complain with the following message: - * invalid func unknown#195896080 - */ - insn->imm = 195896080; /* => 0xbad2310 => "bad relo" */ + int insn_cnt = is_ldimm64_insn(insn) ? 2 : 1; + int i; + + for (i = 0; i < insn_cnt; i++) { + pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n", + prog_name, relo_idx, insn_idx + i); + insn[i].code = BPF_JMP | BPF_CALL; + insn[i].dst_reg = 0; + insn[i].src_reg = 0; + insn[i].off = 0; + /* + * If this instruction is reachable (not dead code), the verifier + * will complain with "invalid func unknown#195896080". + */ + insn[i].imm = 195896080; /* => 0xbad2310 => "bad relo" */ + } + + return 0; } static int insn_bpf_size_to_bytes(struct bpf_insn *insn) @@ -1047,17 +1054,6 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, class = BPF_CLASS(insn->code); - if (res->poison) { -poison: - /* poison second part of ldimm64 to avoid confusing error from - * verifier about "unknown opcode 00" - */ - if (is_ldimm64_insn(insn)) - bpf_core_poison_insn(prog_name, relo_idx, insn_idx + 1, insn + 1); - bpf_core_poison_insn(prog_name, relo_idx, insn_idx, insn); - return 0; - } - orig_val = res->orig_val; new_val = res->new_val; @@ -1065,7 +1061,9 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, case BPF_ALU: case BPF_ALU64: if (BPF_SRC(insn->code) != BPF_K) - return -EINVAL; + goto bad_insn; + if (res->poison) + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); if (res->validate && insn->imm != orig_val) { pr_warn("prog '%s': relo #%d: unexpected insn #%d (ALU/ALU64) value: got %d, exp %llu -> %llu\n", prog_name, relo_idx, @@ -1082,6 +1080,8 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, case BPF_LDX: case BPF_ST: case BPF_STX: + if (res->poison) + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); if (res->validate && insn->off != orig_val) { pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDX/ST/STX) value: got %d, exp %llu -> %llu\n", prog_name, relo_idx, insn_idx, insn->off, (unsigned long long)orig_val, @@ -1097,7 +1097,7 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, pr_warn("prog '%s': relo #%d: insn #%d (LDX/ST/STX) accesses field incorrectly. " "Make sure you are accessing pointers, unsigned integers, or fields of matching type and size.\n", prog_name, relo_idx, insn_idx); - goto poison; + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); } orig_val = insn->off; @@ -1140,6 +1140,9 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, return -EINVAL; } + if (res->poison) + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); + imm = (__u32)insn[0].imm | ((__u64)insn[1].imm << 32); if (res->validate && imm != orig_val) { pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDIMM64) value: got %llu, exp %llu -> %llu\n", @@ -1157,6 +1160,7 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, break; } default: +bad_insn: pr_warn("prog '%s': relo #%d: trying to relocate unrecognized insn #%d, code:0x%x, src:0x%x, dst:0x%x, off:0x%x, imm:0x%x\n", prog_name, relo_idx, insn_idx, insn->code, (unsigned)insn->src_reg, (unsigned)insn->dst_reg, (unsigned)insn->off, (unsigned)insn->imm); From 3440505aca926e726a26c0fd30356454334322bd Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:15 +0200 Subject: [PATCH 0777/1417] selftests/bpf: Test CO-RE instruction poisoning restrictions Add raw CO-RE relocations that fail to resolve their target enum value. Place each supported and unsupported instruction form in dead code. Unsupported targets must fail relocation with a diagnostic even when they are unreachable. Supported ALU immediates, memory accesses, and ldimm64 instructions must still be poisoned and removed as dead code, allowing the program to load. Check that both halves of ldimm64 are poisoned. Load every instruction stream without relocations first to ensure that rejection is caused by the relocation rather than the original program. Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-8-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../selftests/bpf/prog_tests/core_reloc_raw.c | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c index bb19e49dd87dea..51f42b02a267fe 100644 --- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c +++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c @@ -50,6 +50,28 @@ static int load_core_relo_insns(int btf_fd, struct bpf_insn *insns, int insn_cnt static void test_early_core_relo(void) { + static const char unrecognized[] = "trying to relocate unrecognized insn #2"; + static const struct { + const char *name; + struct bpf_insn insns[2]; + const char *err_msg; + } tests[] = { + { "poison_exit", { BPF_EXIT_INSN() }, unrecognized }, + { "poison_ja", { BPF_JMP_A(1) }, unrecognized }, + { "poison_jmp", { BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1) }, unrecognized }, + { "poison_jmp32", { BPF_JMP32_IMM(BPF_JEQ, BPF_REG_0, 0, 1) }, unrecognized }, + { "poison_call", { BPF_EMIT_CALL(BPF_FUNC_get_prandom_u32) }, unrecognized }, + { "poison_alu_reg", { BPF_MOV32_REG(BPF_REG_0, BPF_REG_1) }, unrecognized }, + { "poison_alu64_reg", { BPF_MOV64_REG(BPF_REG_0, BPF_REG_1) }, unrecognized }, + { "poison_ld_abs", { BPF_LD_ABS(BPF_W, 0) }, + "insn #2 (LDIMM64) has unexpected form" }, + { "poison_alu_imm", { BPF_MOV32_IMM(BPF_REG_0, 0) } }, + { "poison_alu64_imm", { BPF_MOV64_IMM(BPF_REG_0, 0) } }, + { "poison_ldx", { BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0) } }, + { "poison_st", { BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0) } }, + { "poison_stx", { BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4) } }, + { "poison_ldimm64", { BPF_LD_IMM64(BPF_REG_0, 0) } }, + }; struct test_btf { struct btf_header hdr; __u32 types[18]; @@ -97,7 +119,7 @@ static void test_early_core_relo(void) }; int access_str_off = 51; /* offset of "0" */ int enum_id = 5; - int btf_fd, prog_fd = -1; + int btf_fd, prog_fd = -1, i; btf_fd = bpf_btf_load(&raw_btf, sizeof(raw_btf), NULL); if (!ASSERT_GE(btf_fd, 0, "btf_load")) @@ -136,6 +158,43 @@ static void test_early_core_relo(void) ASSERT_HAS_SUBSTR(log, "invalid bpf_ld_imm64 insn", "truncated_load_log"); } + for (i = 0; i < ARRAY_SIZE(tests); i++) { + struct bpf_insn insns[] = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1), + tests[i].insns[0], + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + bool is_ldimm64 = insns[2].code == (BPF_LD | BPF_DW | BPF_IMM); + + if (!test__start_subtest(tests[i].name)) + continue; + if (is_ldimm64) { + insns[1].off = 2; + insns[3] = tests[i].insns[1]; + } + prog_fd = load_core_relo_insns(btf_fd, insns, ARRAY_SIZE(insns), funcs, 1, + enum_id, access_str_off, 2, false); + if (!ASSERT_GE(prog_fd, 0, "control_load")) + goto cleanup; + close(prog_fd); + prog_fd = load_core_relo_insns(btf_fd, insns, ARRAY_SIZE(insns), funcs, 1, + enum_id, access_str_off, 2, true); + if (!tests[i].err_msg) { + ASSERT_GE(prog_fd, 0, "dead_poison_load"); + ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log"); + if (is_ldimm64) + ASSERT_HAS_SUBSTR(log, "substituting insn #3", "poison_ldimm64_log"); + } else { + ASSERT_LT(prog_fd, 0, "invalid_poison_load"); + ASSERT_HAS_SUBSTR(log, tests[i].err_msg, "invalid_poison_log"); + ASSERT_NULL(strstr(log, "substituting insn"), "invalid_poison_substitution"); + } + close(prog_fd); + prog_fd = -1; + } + cleanup: if (env.verbosity > VERBOSE_NORMAL && log[0]) { printf("-------- program load log start --------\n"); From 71919742c83c32afcccf06a7a28e28f3f55f21a2 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:16 +0200 Subject: [PATCH 0778/1417] bpf: Assign lock identity to callback map values A nested bpf_for_each_map_elem() callback can unlock a different element of the same map: static long inner(void *map, int *key, struct value *v, struct value **outer_value) { bpf_spin_lock(&v->lock); bpf_spin_unlock(&(*outer_value)->lock); return 0; } static long outer(void *map, int *key, struct value *v, void *ctx) { bpf_for_each_map_elem(map, inner, &v, 0); return 0; } Both callback values currently have ID zero and the same map_ptr. process_spin_lock() compares those two fields, so it accepts the unlock even though the two callbacks can receive different map elements. Assign a fresh ID to every callback map value in the for-each, timer/workqueue, and task-work constructors. Copies of one callback argument retain its ID, so locking and unlocking through that argument continues to work. Distinct callbacks also get distinct IDs for single-element arrays, including inner arrays sharing inner_map_meta. Preserve map_uid for every inner-map lookup and compare it through check_ids() during state pruning. This preserves relationships between maps, keys, and values while allowing equivalent states with different lookup IDs to match. It avoids field-specific rules for when an inner map needs an identity. Move map_uid out of the metadata union and next to the other IDs, so register comparisons can use the existing memcmp() ranges and remap the IDs separately. Clear it when resetting a register or converting a map lookup result to a socket pointer. Shrink frameno to u8, which is enough for MAX_CALL_FRAMES, to make room without growing bpf_reg_state. Fixes: d0d78c1df9b1 ("bpf: Allow locking bpf_spin_lock global variables") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-9-memxor@gmail.com Signed-off-by: Eduard Zingerman --- include/linux/bpf_verifier.h | 25 +++++++++++++------------ kernel/bpf/states.c | 6 ++++-- kernel/bpf/verifier.c | 13 +++++++++---- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index bba5a727c6516c..a7202b44ab1052 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -46,17 +46,11 @@ struct bpf_reg_state { /* valid when type == PTR_TO_PACKET */ int range; - /* valid when type == CONST_PTR_TO_MAP | PTR_TO_MAP_VALUE | - * PTR_TO_MAP_VALUE_OR_NULL + /* + * For CONST_PTR_TO_MAP, PTR_TO_MAP_KEY, PTR_TO_MAP_VALUE and + * PTR_TO_INSN. */ - struct { - struct bpf_map *map_ptr; - /* To distinguish map lookups from outer map - * the map_uid is non-zero for registers - * pointing to inner maps. - */ - u32 map_uid; - }; + struct bpf_map *map_ptr; /* for PTR_TO_BTF_ID */ struct { @@ -155,13 +149,20 @@ struct bpf_reg_state { * gets parent_id set to the dynptr's id. */ u32 parent_id; - /* Inside the callee two registers can be both PTR_TO_STACK like + /* + * Distinguishes inner-map lookups and their keys and values. Zero for + * other registers. Kept outside the metadata union for ID remapping + * during state comparisons. + */ + u32 map_uid; + /* + * Inside the callee two registers can be both PTR_TO_STACK like * R1=fp-8 and R2=fp-8, but one of them points to this function stack * while another to the caller's stack. To differentiate them 'frameno' * is used which is an index in bpf_verifier_state->frame[] array * pointing to bpf_func_state. */ - u32 frameno; + u8 frameno; /* if (!precise && SCALAR_VALUE) min/max/tnum don't affect safety */ bool precise; }; diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c index 6c88ad95b63b76..e4ec007f7fa61e 100644 --- a/kernel/bpf/states.c +++ b/kernel/bpf/states.c @@ -491,7 +491,8 @@ static bool regs_exact(const struct bpf_reg_state *rold, { return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 && check_ids(rold->id, rcur->id, idmap) && - check_ids(rold->parent_id, rcur->parent_id, idmap); + check_ids(rold->parent_id, rcur->parent_id, idmap) && + check_ids(rold->map_uid, rcur->map_uid, idmap); } enum exact_level { @@ -616,7 +617,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, range_within(rold, rcur) && tnum_in(rold->var_off, rcur->var_off) && check_ids(rold->id, rcur->id, idmap) && - check_ids(rold->parent_id, rcur->parent_id, idmap); + check_ids(rold->parent_id, rcur->parent_id, idmap) && + check_ids(rold->map_uid, rcur->map_uid, idmap); case PTR_TO_PACKET_META: case PTR_TO_PACKET: /* We must have at least as much range as the old ptr diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 33161dc6456811..02be326f235c15 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -1864,6 +1864,7 @@ static void __mark_reg_known(struct bpf_reg_state *reg, u64 imm) offsetof(struct bpf_reg_state, var_off) - sizeof(reg->type)); reg->id = 0; reg->parent_id = 0; + reg->map_uid = 0; ___mark_reg_known(reg, imm); } @@ -1925,17 +1926,18 @@ static void refine_map_lookup_value(struct bpf_reg_state *reg) if (map->inner_map_meta) { reg->type = CONST_PTR_TO_MAP | maybe_null; reg->map_ptr = map->inner_map_meta; - /* transfer reg's id which is unique for every map_lookup_elem + /* + * transfer reg's id which is unique for every map_lookup_elem * as UID of the inner map. */ - if (btf_record_has_field(map->inner_map_meta->record, - BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK)) - reg->map_uid = reg->id; + reg->map_uid = reg->id; } else if (map->map_type == BPF_MAP_TYPE_XSKMAP) { reg->type = PTR_TO_XDP_SOCK | maybe_null; + reg->map_uid = 0; } else if (map->map_type == BPF_MAP_TYPE_SOCKMAP || map->map_type == BPF_MAP_TYPE_SOCKHASH) { reg->type = PTR_TO_SOCKET | maybe_null; + reg->map_uid = 0; } } @@ -10048,6 +10050,7 @@ int map_set_for_each_callback_args(struct bpf_verifier_env *env, __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = caller->regs[BPF_REG_1].map_ptr; callee->regs[BPF_REG_3].map_uid = caller->regs[BPF_REG_1].map_uid; + callee->regs[BPF_REG_3].id = ++env->id_gen; /* pointer to stack or null */ callee->regs[BPF_REG_4] = caller->regs[BPF_REG_3]; @@ -10144,6 +10147,7 @@ static int set_timer_callback_state(struct bpf_verifier_env *env, __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = map_ptr; callee->regs[BPF_REG_3].map_uid = map_uid; + callee->regs[BPF_REG_3].id = ++env->id_gen; /* unused */ bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]); @@ -10262,6 +10266,7 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env, __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = map_ptr; callee->regs[BPF_REG_3].map_uid = map_uid; + callee->regs[BPF_REG_3].id = ++env->id_gen; /* unused */ bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]); From 04ae4ffc57a6b7a8215779f0e9c536cacda9f761 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:17 +0200 Subject: [PATCH 0779/1417] selftests/bpf: Check callback map value lock identity Add a verifier test which retains a map value from an outer callback and then acquires a lock through an inner callback value before attempting to release the outer callback value. Both values can denote different elements, so the verifier must reject the mismatched unlock. Also exercise callbacks reached through two inner-map lookups. The lookup results share inner_map_meta but may refer to different one-element arrays, so their callback values must retain distinct lock identities. Extend the existing spin_lock failure table and reuse its array and inner-map fixtures to keep these cases alongside the other lock identity tests. Update the nested callback reference-leak expectation for the extra callback value ID. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://patch.msgid.link/20260917233222.2542500-10-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../selftests/bpf/prog_tests/cb_refs.c | 2 +- .../selftests/bpf/prog_tests/spin_lock.c | 2 + .../selftests/bpf/progs/test_spin_lock_fail.c | 67 ++++++++++++++++++- 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/cb_refs.c b/tools/testing/selftests/bpf/prog_tests/cb_refs.c index 78566b817fd70a..490e15e7126d97 100644 --- a/tools/testing/selftests/bpf/prog_tests/cb_refs.c +++ b/tools/testing/selftests/bpf/prog_tests/cb_refs.c @@ -13,7 +13,7 @@ struct { } cb_refs_tests[] = { { "underflow_prog", "release kfunc bpf_kfunc_call_test_release expects referenced PTR_TO_BTF_ID passed to R1" }, { "leak_prog", "Possibly NULL pointer passed to helper R2" }, - { "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */ + { "nested_cb", "Unreleased reference id=5 alloc_insn=2" }, /* alloc_insn=2{4,5} */ { "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */ }; diff --git a/tools/testing/selftests/bpf/prog_tests/spin_lock.c b/tools/testing/selftests/bpf/prog_tests/spin_lock.c index 5c3579438427d3..e368370262c8e3 100644 --- a/tools/testing/selftests/bpf/prog_tests/spin_lock.c +++ b/tools/testing/selftests/bpf/prog_tests/spin_lock.c @@ -54,6 +54,8 @@ static struct { { "lock_global_sleepable_helper_subprog", "global function calls are not allowed while holding a lock" }, { "lock_global_sleepable_kfunc_subprog", "global function calls are not allowed while holding a lock" }, { "lock_global_sleepable_subprog_indirect", "global function calls are not allowed while holding a lock" }, + { "callback_value_lock_identity", "bpf_spin_unlock of different lock" }, + { "callback_inner_map_value_lock_identity", "bpf_spin_unlock of different lock" }, }; static int match_regex(const char *pattern, const char *string) diff --git a/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c b/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c index f678ee6bd7eafb..55282f20fa3262 100644 --- a/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c +++ b/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c @@ -14,17 +14,18 @@ struct array_map { __type(key, int); __type(value, struct foo); __uint(max_entries, 1); -} array_map SEC(".maps"); +} array_map SEC(".maps"), array_map_b SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS); - __uint(max_entries, 1); + __uint(max_entries, 2); __type(key, int); __type(value, int); __array(values, struct array_map); } map_of_maps SEC(".maps") = { .values = { [0] = &array_map, + [1] = &array_map_b, }, }; @@ -314,4 +315,66 @@ int lock_global_sleepable_subprog_indirect(struct __sk_buff *ctx) return ret; } +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 2); + __type(key, int); + __type(value, struct foo); +} callback_array_map SEC(".maps"); + +struct callback_ctx { + struct foo *value; +}; + +static long lock_different_value(struct bpf_map *map, int *key, + struct foo *value, struct callback_ctx *ctx) +{ + bpf_spin_lock(&value->lock); + bpf_spin_unlock(&ctx->value->lock); + return 0; +} + +static long nest_lock_different_value(struct bpf_map *map, int *key, + struct foo *value, void *data) +{ + struct callback_ctx ctx = { .value = value }; + + bpf_for_each_map_elem(&callback_array_map, lock_different_value, &ctx, 0); + return 0; +} + +SEC("?tc") +int callback_value_lock_identity(void *ctx) +{ + bpf_for_each_map_elem(&callback_array_map, nest_lock_different_value, NULL, 0); + return 0; +} + +static long nest_lock_different_inner_value(struct bpf_map *map, int *key, + struct foo *value, void *data) +{ + struct callback_ctx ctx = { .value = value }; + int inner_key = 1; + void *inner_map; + + inner_map = bpf_map_lookup_elem(&map_of_maps, &inner_key); + if (!inner_map) + return 0; + bpf_for_each_map_elem(inner_map, lock_different_value, &ctx, 0); + return 0; +} + +SEC("?tc") +int callback_inner_map_value_lock_identity(void *ctx) +{ + int inner_key = 0; + void *inner_map; + + inner_map = bpf_map_lookup_elem(&map_of_maps, &inner_key); + if (!inner_map) + return 0; + bpf_for_each_map_elem(inner_map, nest_lock_different_inner_value, NULL, 0); + return 0; +} + char _license[] SEC("license") = "GPL"; From b4e875d397da451fb4e9c573ff4b86db53caba05 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:18 +0200 Subject: [PATCH 0780/1417] libbpf: Reject truncated ldimm64 CO-RE relocations CO-RE relocation of an ldimm64 instruction operates on two instruction slots. A malformed BPF ELF can end a function after the first slot and attach a CO-RE relocation to it. libbpf allocates the instruction array according to the function symbol size, so the shared relocation code would then access beyond the allocation. Reject a terminal ldimm64 in libbpf's relocation loop, where the program length is available, before resolving or applying the relocation. Both resolved and unresolved relocations validate the absent second slot, and unresolved relocation poisoning would additionally write past the array. The in-kernel caller is protected by the verifier's early instruction-stream check before it applies CO-RE relocations. Fixes: eacaaed784e2 ("libbpf: Implement enum value-based CO-RE relocations") Reported-by: Sashiko Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/20260914140852.03DA21F0089B@smtp.kernel.org Link: https://patch.msgid.link/20260917233222.2542500-11-memxor@gmail.com Signed-off-by: Eduard Zingerman --- tools/lib/bpf/libbpf.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index b749c01742ee05..bfa64ae6c94df5 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -6206,6 +6206,13 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path) return -EINVAL; insn = &prog->insns[insn_idx]; + if (is_ldimm64_insn(insn) && (size_t)insn_idx + 1 >= prog->insns_cnt) { + pr_warn("prog '%s': relo #%d: insn #%d (LDIMM64) is truncated\n", + prog->name, i, insn_idx); + err = -EINVAL; + goto out; + } + err = record_relo_core(prog, rec, insn_idx); if (err) { pr_warn("prog '%s': relo #%d: failed to record relocation: %s\n", From 2ec28c09b320ba241bea8a70ee5cb9ccf4a099e8 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 14:30:50 -0300 Subject: [PATCH 0781/1417] vsock: ignore empty child namespace mode writes __vsock_net_mode_string() returns success without updating new_mode when the transfer length is zero. Its caller then reads the uninitialized enum and may permanently store a stack-derived value in the write-once child mode. Return before calling __vsock_net_mode_string() when *lenp is zero so that the helper is never invoked with nothing to parse and new_mode is never read uninitialized. This also prevents an empty write from locking the current mode. Fixes: eafb64f40ca4 ("vsock: add netns to vsock core") Cc: stable@vger.kernel.org Reviewed-by: Luigi Leonardi Signed-off-by: Aldo Ariel Panzardo Reviewed-by: Stefano Garzarella Reviewed-by: Bobby Eshleman Link: https://patch.msgid.link/20260915173050.3176344-1-qwe.aldo@gmail.com Signed-off-by: Jakub Kicinski --- net/vmw_vsock/af_vsock.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index f840498b58afbc..9b71479a2b2959 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -2889,6 +2889,9 @@ static int vsock_net_child_mode_string(const struct ctl_table *table, int write, net = container_of(table->data, struct net, vsock.child_ns_mode); + if (!*lenp) + return 0; + ret = __vsock_net_mode_string(table, write, buffer, lenp, ppos, vsock_net_child_mode(net), &new_mode); if (ret) From 651010592bdce7005c1179498327e51bfc4fe1a5 Mon Sep 17 00:00:00 2001 From: Zhang Yunfei Date: Fri, 11 Sep 2026 17:11:23 +0800 Subject: [PATCH 0782/1417] net: txgbe: fix FDIR filter restore for VF rules txgbe_fdir_filter_restore() reprograms every filter from txgbe->fdir_filter_list after a reset. It extracts the ring part of filter->action with ethtool_get_flow_spec_ring() and maps it onto a PF rx ring, silently dropping the VF part of the cookie that txgbe_add_ethtool_fdir_entry() stores there (input->action = fsp->ring_cookie). For a rule directed at a VF, restore therefore reprograms the filter to the PF queue with the same ring index: after any down/up or txgbe_reinit_locked(), traffic matching the rule is steered to the PF instead of the VF. Handle VF rules the same way txgbe_add_ethtool_fdir_entry() does: validate vf against wx->num_vfs and ring against wx->num_rx_queues_per_pool, and map the ring onto the absolute queue index ((vf - 1) * wx->num_rx_queues_per_pool) + ring. Fixes: 7a91722e0dd4 ("net: txgbe: Support the FDIR rules assigned to VFs") Cc: stable@vger.kernel.org Signed-off-by: Zhang Yunfei Link: https://patch.msgid.link/20260911091123.798931-1-zhangyunfei1@kylinos.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c index a840108285517f..59a47532618c89 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c @@ -591,15 +591,24 @@ static void txgbe_fdir_filter_restore(struct wx *wx) queue = TXGBE_RDB_FDIR_DROP_QUEUE; } else { u32 ring = ethtool_get_flow_spec_ring(filter->action); + u8 vf = ethtool_get_flow_spec_ring_vf(filter->action); - if (ring >= wx->num_rx_queues) { + if (!vf && ring >= wx->num_rx_queues) { wx_err(wx, "FDIR restore failed, ring:%u\n", ring); continue; + } else if (vf && (vf > wx->num_vfs || + ring >= wx->num_rx_queues_per_pool)) { + wx_err(wx, "FDIR restore failed, vf:%u, ring:%u\n", + vf, ring); + continue; } /* Map the ring onto the absolute queue index */ - queue = wx->rx_ring[ring]->reg_idx; + if (!vf) + queue = wx->rx_ring[ring]->reg_idx; + else + queue = ((vf - 1) * wx->num_rx_queues_per_pool) + ring; } ret = txgbe_fdir_write_perfect_filter(wx, From 46bc52d13594848023e681860df8700c8db14354 Mon Sep 17 00:00:00 2001 From: Linkui Xiao Date: Wed, 16 Sep 2026 20:53:16 +0800 Subject: [PATCH 0783/1417] ipv4: fib: fix data-race and stale genid check around nh->nh_saddr fib_select_multipath() compares nexthop_nh->nh_saddr against the flow source address with no lock held, while fib_info_update_nhc_saddr() stores a new value from another CPU as soon as the preferred source address of the egress device changes. Commit 195374d89368 ("ipv4: fib: annotate races around nh->nh_saddr_genid and nh->nh_saddr") added WRITE_ONCE() on the store side and READ_ONCE() in fib_result_prefsrc() after syzbot reported BUG: KCSAN: data-race in fib_select_path / fib_select_path but it only covered that reader. fib_select_multipath(), reached from fib_select_path(), is a second lockless reader of nh->nh_saddr and was left bare. Moreover, nh_saddr is only meaningful when nh_saddr_genid matches dev_addr_genid, as established by commit 436c3b66ec98 ("ipv4: Invalidate nexthop cache nh_saddr more correctly."). fib_select_multipath() skips that validation, so it can score a nexthop using a stale source address and skew the ECMP selection. Annotate both reads with READ_ONCE() and refresh the cached source address via fib_info_update_nhc_saddr() when the genid does not match, mirroring fib_result_prefsrc(). Fixes: 32607a332cfe ("ipv4: prefer multipath nexthop that matches source address") Signed-off-by: Linkui Xiao Reviewed-by: Ido Schimmel Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260916125316.988044-1-xiaolinkui@126.com Signed-off-by: Jakub Kicinski --- net/ipv4/fib_semantics.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c index 7a362f2e2c2bd0..5c9021ea3a7995 100644 --- a/net/ipv4/fib_semantics.c +++ b/net/ipv4/fib_semantics.c @@ -2176,6 +2176,15 @@ static bool fib_good_nh(const struct fib_nh *nh) return !!(state & NUD_VALID); } +static __be32 fib_nh_saddr(struct net *net, const struct fib_info *fi, + struct fib_nh *nh, int genid) +{ + if (READ_ONCE(nh->nh_saddr_genid) == genid) + return READ_ONCE(nh->nh_saddr); + + return fib_info_update_nhc_saddr(net, &nh->nh_common, fi->fib_scope); +} + void fib_select_multipath(struct fib_result *res, int hash, const struct flowi4 *fl4) { @@ -2184,6 +2193,7 @@ void fib_select_multipath(struct fib_result *res, int hash, bool use_neigh; int score = -1; __be32 saddr; + int genid; if (unlikely(res->fi->nh)) { nexthop_path_fib_result(res, hash); @@ -2192,6 +2202,7 @@ void fib_select_multipath(struct fib_result *res, int hash, use_neigh = READ_ONCE(net->ipv4.sysctl_fib_multipath_use_neigh); saddr = fl4 ? fl4->saddr : 0; + genid = saddr ? atomic_read(&net->ipv4.dev_addr_genid) : 0; change_nexthops(fi) { int nh_upper_bound, nh_score = 0; @@ -2204,7 +2215,7 @@ void fib_select_multipath(struct fib_result *res, int hash, (use_neigh && !fib_good_nh(nexthop_nh))) continue; - if (saddr && nexthop_nh->nh_saddr == saddr) + if (saddr && fib_nh_saddr(net, fi, nexthop_nh, genid) == saddr) nh_score += 2; if (hash <= nh_upper_bound) nh_score++; From a363c62a653cc8b3e21da9545fa4e028ef50f9c3 Mon Sep 17 00:00:00 2001 From: Longlong Xia Date: Sun, 23 Aug 2026 12:40:51 +0800 Subject: [PATCH 0784/1417] mm/hugetlb: do not dissolve gigantic pages without runtime support dissolve_free_hugetlb_folio() doesn't check hstate_is_gigantic_no_runtime(h) though remove_hugetlb_folio()/ update_and_free_hugetlb_folio() silently bail for such folios, so it frees a still-listed folio and, on vmemmap restore failure, the add_hugetlb_folio() rollback corrupts the free list. Link: https://lore.kernel.org/20260823044118.1097121-2-xialonglong2025@163.com Fixes: 6eb4e88a6d27 ("hugetlb: create remove_hugetlb_page() to separate functionality") Signed-off-by: Longlong Xia Signed-off-by: Andrew Morton Assisted-by: Codex:gpt-5.6-sol Acked-by: Muchun Song Cc: David Hildenbrand Cc: Miaohe Lin Cc: Michal Hocko Cc: Oscar Salvador Cc: --- mm/hugetlb.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 4f6f58bf3db6c1..d28972cd33f582 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -1967,6 +1967,15 @@ int dissolve_free_hugetlb_folio(struct folio *folio) struct hstate *h = folio_hstate(folio); bool adjust_surplus = false; + /* + * remove_hugetlb_folio()/update_and_free_hugetlb_folio() bail + * for gigantic hstates without runtime support, so dissolving one + * here would leave it on the free list and, on vmemmap restore + * failure, the add_hugetlb_folio() rollback corrupts that list. + */ + if (hstate_is_gigantic_no_runtime(h)) + goto out; + if (!available_huge_pages(h)) goto out; From 5179241521401ef364294128bb43cdbce7252457 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 17 Sep 2026 13:20:25 +0200 Subject: [PATCH 0785/1417] fs: don't create the private nullfs mount under namespace_sem init_mount_tree() mounts the mutable rootfs on top of nullfs via LOCK_MOUNT_EXACT(). That declares a pinned mountpoint with a cleanup attribute in the scope of the whole function so the nullfs root inode lock and namespace_sem are only dropped when init_mount_tree() returns. This became a problem when the private nullfs instance for kthreads was added. kern_mount() allocates a new superblock and alloc_super() takes the new s_umount with SINGLE_DEPTH_NESTING and then shrinker_mutex via shrinker_alloc(). Doing that with namespace_sem held teaches lockdep the dependency namespace_sem -> s_umount/1 -> shrinker_mutex With CONFIG_SHRINKER_DEBUG shrinker_debugfs_rename() takes the debugfs directory inode lock under shrinker_mutex every time a block device is mounted and lock_mount_exact() takes namespace_sem under the inode lock of the mountpoint for every mount. So mounting anything on debugfs, e.g. the tracefs automount on /sys/kernel/debug/tracing, closes the cycle: WARNING: possible circular locking dependency detected 7.3.0-rc3+ #17 Not tainted ------------------------------------------------------ rasdaemon/4449 is trying to acquire lock: (namespace_sem){++++}-{4:4}, at: lock_mount_exact+0x4c/0x308 but task is already holding lock: (&sb->s_type->i_mutex_key#17){++++}-{4:4}, at: lock_mount_exact+0x3c/0x308 which lock already depends on the new lock. ... Chain exists of: namespace_sem --> shrinker_mutex --> &sb->s_type->i_mutex_key#17 This can't actually deadlock. init_mount_tree() runs single-threaded during early boot before any other task exists and nothing allocates a superblock under namespace_sem after that. But lockdep can't know that and disables itself for the rest of the boot. Move mounting the rootfs on top of nullfs into a helper so the locks are dropped when it returns. Fixes: 32750c77e811 ("fs: start all kthreads in nullfs") Reported-by: Zenghui Yu Closes: https://lore.kernel.org/15174353-3f4a-a1ca-5bd1-ea2a4c77828e@huawei.com Link: https://patch.msgid.link/20260917-atemtechnik-bleichen-befassen-9a57db01baf0@brauner Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index ae5dc64f8b457e..5e41021eaa6305 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -6184,6 +6184,21 @@ struct mnt_namespace init_mnt_ns = { .poll = __WAIT_QUEUE_HEAD_INITIALIZER(init_mnt_ns.poll), }; +static void __init mount_rootfs_on_nullfs(struct vfsmount *mnt, + struct vfsmount *nullfs_mnt) +{ + struct path root = { + .mnt = nullfs_mnt, + .dentry = nullfs_mnt->mnt_root, + }; + + LOCK_MOUNT_EXACT(mp, &root); + if (unlikely(IS_ERR(mp.parent))) + panic("VFS: Failed to mount rootfs on nullfs"); + scoped_guard(mount_writer) + attach_mnt(real_mount(mnt), mp.parent, mp.mp); +} + static void __init init_mount_tree(void) { struct vfsmount *mnt, *nullfs_mnt; @@ -6215,15 +6230,7 @@ static void __init init_mount_tree(void) mnt_root = real_mount(nullfs_mnt); init_mnt_ns.root = mnt_root; - /* Mount mutable rootfs on top of nullfs. */ - root.mnt = nullfs_mnt; - root.dentry = nullfs_mnt->mnt_root; - - LOCK_MOUNT_EXACT(mp, &root); - if (unlikely(IS_ERR(mp.parent))) - panic("VFS: Failed to mount rootfs on nullfs"); - scoped_guard(mount_writer) - attach_mnt(real_mount(mnt), mp.parent, mp.mp); + mount_rootfs_on_nullfs(mnt, nullfs_mnt); pr_info("VFS: Finished mounting rootfs on nullfs\n"); From d54a489c8c4b627775445d54a6cbd32f209bda8f Mon Sep 17 00:00:00 2001 From: Frank Wunderlich Date: Thu, 17 Sep 2026 17:37:11 +0200 Subject: [PATCH 0786/1417] gpiolib: use of_node_name if line-name is missing Until v7.0, GPIO hogs inherited the DT node name when no line-name property was specified. This was implemented as a fallback in of_parse_own_gpio(). Commit d1d564ec4992 ("gpio: move hogs into GPIO core") moved hog parsing into the GPIO core and removed this fallback. Consequently, GPIO hogs without a line-name property are now displayed with a ? in /sys/kernel/debug/gpio. Restore the old fallback. Fixes: d1d564ec4992 ("gpio: move hogs into GPIO core") Signed-off-by: Frank Wunderlich Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260917153712.134367-1-linux@fw-web.de Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index ef8ccaf17c9cee..28f7265c5d5697 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1029,6 +1029,13 @@ int gpiochip_add_hog(struct gpio_chip *gc, struct fwnode_handle *fwnode) ret = of_gpiochip_get_lflags(gc, &gpiospec, &lflags); if (ret) return ret; + + /* + * If no line-name property is present, fall back to the OF + * node name as in the previous implementation. + */ + if (!name) + name = to_of_node(fwnode)->name; } else { /* * GPIO_ACTIVE_LOW is currently the only lookup flag From d644b23afe1ef509c9961a6d84a093c2587edf02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Tue, 18 Aug 2026 20:00:15 +0000 Subject: [PATCH 0787/1417] netfilter: flowtable: publish HW_DEAD after worker is done MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit flow_offload_work_del() sets NF_FLOW_HW_DEAD before the work handler clears NF_FLOW_HW_PENDING. Once a flow is both HW_DYING and HW_DEAD, a concurrent garbage collection pass can remove it and schedule it for RCU freeing. The offload worker holds neither an RCU read lock nor a reference to the flow. If it is preempted after publishing HW_DEAD, the RCU callback can free the flow before the worker resumes and clears HW_PENDING, resulting in a use-after-free. Move HW_DEAD publication to the common worker epilogue after the pending bit is cleared, making it the final flow access by destroy work. Order all preceding flow accesses before publishing the bit that allows garbage collection to free the object. Fixes: 2c8897953f3b ("netfilter: flowtable: Add pending bit for offload work") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_offload.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c index 801a3dd9ceea3d..6757fd89c1f1a6 100644 --- a/net/netfilter/nf_flow_table_offload.c +++ b/net/netfilter/nf_flow_table_offload.c @@ -995,7 +995,6 @@ static void flow_offload_work_del(struct flow_offload_work *offload) flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_ORIGINAL); if (test_bit(NF_FLOW_HW_BIDIRECTIONAL, &offload->flow->flags)) flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_REPLY); - set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags); } static void flow_offload_tuple_stats(struct flow_offload_work *offload, @@ -1059,6 +1058,12 @@ static void flow_offload_work_handler(struct work_struct *work) } clear_bit(NF_FLOW_HW_PENDING, &offload->flow->flags); + if (offload->cmd == FLOW_CLS_DESTROY) { + /* Publish after the worker's last flow access. */ + smp_mb__before_atomic(); + set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags); + } + kfree(offload); } From 9461613afc59acef44a0071b0dd5075f6e993ffe Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 3 Sep 2026 02:41:46 +0200 Subject: [PATCH 0788/1417] netfilter: nfnetlink_queue: hold nfnl mutex in event notifier We must serialize the release notifier and the config netlink function. A concurrent thread can issue close() which can call the release function while unrelated socket processes UNBIND request for same portid: Oops: general protection fault, [..] RIP: 0010:__instance_destroy+0x60/0x210 [nfnetlink_queue] Call Trace: nfqnl_recv_config+0x9b0/0xdc0 [nfnetlink_queue] nfnetlink_rcv_msg+0x7c2/0xeb0 ? __pfx_nfnetlink_rcv_msg+0x10/0x10 After this, parallel UNBIND and URELEASE events are impossible. This change isn't nice, but its the shortest fix given instances are not refcounted and the nfnetlink config callback drops the rcu read lock early due to need for sleeping allocations. Fixes: 7af4cc3fa158 ("[NETFILTER]: Add "nfnetlink_queue" netfilter queue handler over nfnetlink") Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nfnetlink_queue.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index c727668b0c5be7..a3bc0028005109 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -1593,6 +1593,7 @@ nfqnl_rcv_nl_event(struct notifier_block *this, if (event == NETLINK_URELEASE && n->protocol == NETLINK_NETFILTER) { int i; + nfnl_lock(NFNL_SUBSYS_QUEUE); /* destroy all instances for this portid */ spin_lock(&q->instances_lock); for (i = 0; i < INSTANCE_BUCKETS; i++) { @@ -1606,6 +1607,7 @@ nfqnl_rcv_nl_event(struct notifier_block *this, } } spin_unlock(&q->instances_lock); + nfnl_unlock(NFNL_SUBSYS_QUEUE); } return NOTIFY_DONE; } @@ -1925,9 +1927,9 @@ static int nfqnl_recv_config(struct sk_buff *skb, const struct nfnl_info *info, /* Lookup queue under RCU. After peer_portid check (or for new queue * in BIND case), the queue is owned by the socket sending this message. - * A socket cannot simultaneously send a message and close, so while - * processing this CONFIG message, nfqnl_rcv_nl_event() (triggered by - * socket close) cannot destroy this queue. Safe to use without RCU. + * nfqnl_rcv_nl_event() will block on the nfnl subsys mutex that is + * held by the caller, so the queue cannot be destroyed in parallel, + * even after we drop the RCU read lock. */ rcu_read_lock(); queue = instance_lookup(q, queue_num); From 1b9b5323725e458906c7620a3bc10398b51ad954 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 6 Sep 2026 16:44:10 +0800 Subject: [PATCH 0789/1417] netfilter: ip6t_rpfilter: reject routes without inet6_dev ip6_route_lookup() can return an error-free route whose rt6i_idev is NULL. Lowering an external nexthop device's MTU below IPV6_MIN_MTU tears down its inet6_dev while fib6_ifdown() leaves routes using nexthop objects in the FIB. An unprivileged user can construct this state with rtnetlink in a private user and network namespace, then trigger a NULL dereference through an IPv6 rpfilter lookup: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000 KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: rpfilter_mt (net/ipv6/netfilter/ip6t_rpfilter.c:75) Call Trace: ip6t_do_table (net/ipv6/netfilter/ip6_tables.c:316) nf_hook_slow (net/netfilter/core.c:619) ipv6_rcv (net/ipv6/ip6_input.c:351) __netif_receive_skb_one_core (net/core/dev.c:6216) process_backlog (net/core/dev.c:6680) __napi_poll (net/core/dev.c:7739) net_rx_action (net/core/dev.c:7959) handle_softirqs (kernel/softirq.c:622) do_softirq.part.0 (kernel/softirq.c:523) __local_bh_enable_ip (kernel/softirq.c:450) __dev_queue_xmit (net/core/dev.c:4913) packet_sendmsg (net/packet/af_packet.c:3139) __sys_sendto (net/socket.c:2252) __x64_sys_sendto (net/socket.c:2259) do_syscall_64 (arch/x86/entry/syscall_64.c:94) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Kernel panic - not syncing: Fatal exception in interrupt Reject routes without an inet6_dev immediately after lookup. Such routes are not eligible for reverse-path filtering, and the check protects all later rt6i_idev dereferences. Fixes: e26f9a480fb6 ("netfilter: add ipv6 reverse path filter match") Reported-by: co+459f67f4d8af8ce6@bugs.sh Closes: https://lore.kernel.org/all/VtWUkE8QzJt5CroTj2V2v3ZQ0gwbXZ7nq7I3@bugs.sh/ Suggested-by: Florian Westphal Assisted-by: Claude:gpt-5 Cc: stable@vger.kernel.org Signed-off-by: Weiming Shi Signed-off-by: Pablo Neira Ayuso --- net/ipv6/netfilter/ip6t_rpfilter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/netfilter/ip6t_rpfilter.c b/net/ipv6/netfilter/ip6t_rpfilter.c index 67c87a88cde4f8..b5def30c3127e5 100644 --- a/net/ipv6/netfilter/ip6t_rpfilter.c +++ b/net/ipv6/netfilter/ip6t_rpfilter.c @@ -61,7 +61,7 @@ static bool rpfilter_lookup_reverse6(struct net *net, const struct sk_buff *skb, fl6.flowi6_oif = dev->ifindex; rt = (void *)ip6_route_lookup(net, &fl6, skb, lookup_flags); - if (rt->dst.error) + if (rt->dst.error || !rt->rt6i_idev) goto out; if (rt->rt6i_flags & (RTF_REJECT|RTF_ANYCAST)) From 82313c169eddc02b1bf5ba6b427803e272d3ec42 Mon Sep 17 00:00:00 2001 From: Luxiao Xu Date: Sun, 6 Sep 2026 21:29:55 +0800 Subject: [PATCH 0790/1417] netfilter: ip6t_rt: fix zero-address non-strict match out-of-bounds read rt_mt6_check() permits rules to be configured with rtinfo->addrnr == 0 even when address matching (IP6T_RT_FST_MASK) is requested. In the IP6T_RT_FST_NSTRICT path, rt_mt6() evaluates packet routing addresses against rtinfo->addrs[i] and terminates backwards at the bottom of the loop: if (ipv6_addr_equal(ap, &rtinfo->addrs[i])) { i++; } if (i == rtinfo->addrnr) break; When addrnr is 0, if the first packet address matches rtinfo->addrs[0], i is incremented to 1. Because i is now strictly greater than addrnr (0), the loop termination condition (i == rtinfo->addrnr) is bypassed and will never be satisfied. If a crafted IPv6 packet contains matching routing addresses, i will advance past IP6T_RT_HOPS (16). The subsequent call to ipv6_addr_equal() reads beyond struct ip6t_rt, triggering UBSAN/KASAN out-of-bounds warnings or kernel panics. Fix this by: 1. Rejecting rules in rt_mt6_check() where IP6T_RT_FST_MASK is set but rtinfo->addrnr is zero. 2. In rt_mt6(), moving the termination condition (i < rtinfo->addrnr) into the for-loop header condition and removing the backwards break at the end of the loop body. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega Suggested-by: Florian Westphal Assisted-by: LLM Signed-off-by: Luxiao Xu Signed-off-by: Ren Wei Signed-off-by: Pablo Neira Ayuso --- net/ipv6/netfilter/ip6t_rt.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/ipv6/netfilter/ip6t_rt.c b/net/ipv6/netfilter/ip6t_rt.c index 8051425213ddae..9880faf3cc7dd7 100644 --- a/net/ipv6/netfilter/ip6t_rt.c +++ b/net/ipv6/netfilter/ip6t_rt.c @@ -96,7 +96,8 @@ static bool rt_mt6(const struct sk_buff *skb, struct xt_action_param *par) unsigned int i = 0; for (temp = 0; - temp < (unsigned int)((hdrlen - 8) / 16); + temp < (unsigned int)((hdrlen - 8) / 16) && + i < rtinfo->addrnr; temp++) { ap = skb_header_pointer(skb, ptr @@ -112,8 +113,6 @@ static bool rt_mt6(const struct sk_buff *skb, struct xt_action_param *par) if (ipv6_addr_equal(ap, &rtinfo->addrs[i])) i++; - if (i == rtinfo->addrnr) - break; } if (i == rtinfo->addrnr) return ret; @@ -162,6 +161,12 @@ static int rt_mt6_check(const struct xt_mtchk_param *par) pr_info_ratelimited("too many addresses specified\n"); return -EINVAL; } + + if ((rtinfo->flags & IP6T_RT_FST_MASK) && !rtinfo->addrnr) { + pr_info_ratelimited("address list match requested but addrnr is 0\n"); + return -EINVAL; + } + if ((rtinfo->flags & (IP6T_RT_RES | IP6T_RT_FST_MASK)) && (!(rtinfo->flags & IP6T_RT_TYP) || (rtinfo->rt_type != 0) || From a311a898172743558b82f6035ef2aa8c310a4223 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 22:02:28 +0200 Subject: [PATCH 0791/1417] netfilter: nft_synproxy: use the family-aware checksum helper nft_synproxy_do_eval() verifies the TCP checksum before it switches on skb->protocol. It uses nf_ip_checksum(), which constructs an IPv4 pseudo header and relies on the IPv4 header checksum when folding the whole skb. Neither operation is valid for an IPv6 packet. A correctly checksummed IPv6 segment can therefore fail verification when it reaches the hook as CHECKSUM_NONE or, at NF_INET_LOCAL_IN, CHECKSUM_COMPLETE. nft_synproxy_do_eval() returns NF_DROP before nft_synproxy_eval_v6() can send a SYN-ACK. nft_synproxy_validate() deliberately admits NFPROTO_IPV6 and NFPROTO_INET, and the xtables counterpart ip6t_SYNPROXY.c already calls nf_ip6_checksum(). Use nf_checksum() with nft_pf() so the checksum helper dispatches to the packet family's implementation. Fixes: ad49d86e07a4 ("netfilter: nf_tables: Add synproxy support") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nft_synproxy.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nft_synproxy.c b/net/netfilter/nft_synproxy.c index 9ed288c9d16880..554a96a000f403 100644 --- a/net/netfilter/nft_synproxy.c +++ b/net/netfilter/nft_synproxy.c @@ -118,7 +118,8 @@ static void nft_synproxy_do_eval(const struct nft_synproxy *priv, return; } - if (nf_ip_checksum(skb, nft_hook(pkt), thoff, IPPROTO_TCP)) { + if (nf_checksum(skb, nft_hook(pkt), thoff, IPPROTO_TCP, + nft_pf(pkt))) { regs->verdict.code = NF_DROP; return; } From e290145564886d6a3038810c621f738c1fe9fa51 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Fri, 11 Sep 2026 14:43:15 +0300 Subject: [PATCH 0792/1417] ipvs: revalidate ihl before icmp_send While the outer IP header is already pulled into the skb head, we must be careful and revalidate the embedded headers after reading them from the skb frags to prevent possible out-of-bounds access. One such place reported by Sashiko is ip_vs_in_icmp() where local process can change the ihl field and after pskb_may_pull() we can see larger value. Even if icmp_send() has checks to prevent out-of-bounds access, play safe and add check to drop the packet if the ihl field is changed. As the outer headers are pulled, make sure the transport header is updated too, it was used before commit 7fcc2fe39fed ("net: icmp: avoid invalid transport header access in icmp_send tracepoint") Fixes: f2edb9f7706d ("ipvs: implement passive PMTUD for IPIP packets") Link: https://sashiko.dev/#/patchset/20260806105211.34622-1-ja%40ssi.bg Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_core.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index ba0957798bad06..fd503f0efb5783 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -1960,6 +1960,12 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* Ensure the IP header is present in headroom */ if (!pskb_may_pull(skb, hlen_orig)) goto ignore_tunnel; + skb_set_transport_header(skb, hlen_orig); + /* Before now we may used ihl from skb frag, revalidate it after + * copying it into skb head to prevent out-of-bounds access + */ + if (ip_hdr(skb)->ihl * 4 != hlen_orig) + goto ignore_tunnel; IP_VS_DBG(12, "Sending ICMP for %pI4->%pI4: t=%u, c=%u, i=%u\n", &ip_hdr(skb)->saddr, &ip_hdr(skb)->daddr, type, code, ntohl(info)); From 207d591c353201f3bd3e0c89bb7d44a849c8fd59 Mon Sep 17 00:00:00 2001 From: Naman Gulati Date: Sat, 12 Sep 2026 01:10:51 +0000 Subject: [PATCH 0793/1417] netfilter: ctnetlink: fix suspicious RCU usage in expect_iter_name expect_iter_name() is invoked by nf_ct_expect_iterate_net() under spin_lock_bh(&nf_conntrack_expect_lock). It does not hold rcu_read_lock(). When accessing exp->helper with rcu_dereference() in syzbot's report, lockdep warns: ============================= WARNING: suspicious RCU usage syzkaller #0 Not tainted ----------------------------- net/netfilter/nf_conntrack_netlink.c:3393 suspicious rcu_dereference_check() usage! locks held by syz-executor381/5628: 2, last CPU#1: #0: ffffffff9aee42a0 (nfnl_subsys_ctnetlink_exp){+.+.}-{4:4}, at: nfnetlink_rcv_msg+0xa69/0x12b0 #1: ffffffff8ea74d58 (nf_conntrack_expect_lock){+...}-{3:3}, at: nf_ct_expect_iterate_net+0x38/0x180 Call Trace: dump_stack_lvl+0xe8/0x150 lockdep_rcu_suspicious+0x140/0x1d0 expect_iter_name+0xfb/0x100 nf_ct_expect_iterate_net+0xf2/0x180 ctnetlink_del_expect+0x45d/0x640 nfnetlink_rcv_msg+0xcc2/0x12b0 netlink_rcv_skb+0x226/0x4a0 nfnetlink_rcv+0x2b9/0x28c0 netlink_unicast+0x7bd/0x940 netlink_sendmsg+0x813/0xb40 ____sys_sendmsg+0x54e/0x850 ___sys_sendmsg+0x2a5/0x360 __sys_sendmsg+0x2a5/0x360 do_syscall_64+0x166/0x520 entry_SYSCALL_64_after_hwframe+0x77/0x7f Use rcu_dereference_protected() with lockdep_is_held() on nf_conntrack_expect_lock instead, similar to expect_iter_me() in nf_conntrack_helper.c. Fixes: f01794106042 ("netfilter: nf_conntrack_expect: use expect->helper") Reported-by: syzbot+4bd730aede2791e40bdf@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6aa4a377.f81106d8.2ab401.0024.GAE@google.com/T/#u Signed-off-by: Naman Gulati Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conntrack_netlink.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index 579ada063b1bb0..4e5d7c70143683 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -3392,7 +3392,8 @@ static bool expect_iter_name(struct nf_conntrack_expect *exp, void *data) struct nf_conntrack_helper *helper; const char *name = data; - helper = rcu_dereference(exp->helper); + helper = rcu_dereference_protected(exp->helper, + lockdep_is_held(&nf_conntrack_expect_lock)); if (!helper) return false; From 4f948b5949d2e418b4506752e7c514fe91bd408b Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Fri, 18 Sep 2026 11:19:59 +0200 Subject: [PATCH 0794/1417] binfmt_misc: fix OOB read in bpf_binprm_select_interp() bpf_binprm_select_interp() checks the name its load program passes with strnlen(name, name__sz) and then hands the same buffer to binfmt_misc_find_interp(), which compares it with an unbounded strcmp(). The buffer can be a BPF map value that another CPU rewrites between the two reads. If the terminating NUL is overwritten in that window, strcmp() reads past the name__sz bytes the verifier checked. That is an out-of-bounds read of up to 31 bytes of whatever follows the checked name__sz bytes. The verifier checks the name and name__sz pair with BPF_READ | BPF_WRITE, so a writable array map value is an accepted argument. bpf(BPF_MAP_UPDATE_ELEM) on an array map copies the new value over the old one in place and takes no lock. The NUL that strnlen() finds can be overwritten before strcmp() reads the buffer again: CPU0 CPU1 bpf_binprm_select_interp() strnlen(name, name__sz) finds the NUL inside name__sz bpf(BPF_MAP_UPDATE_ELEM) array_map_update_elem() copy_map_value() overwrites the NUL binfmt_misc_find_interp() strcmp(interp->name, name) reads past name__sz strnlen() proves that a NUL lies inside name__sz only at the moment it runs. The map update on CPU1 takes no lock, so it can store over the NUL right after. The lookup on CPU0 then walks the live buffer again, once per bound interpreter: fs/binfmt_misc.c:binfmt_misc_find_interp list_for_each_entry(interp, interps, list) if (!strcmp(interp->name, name)) return interp; strcmp() stops at the first mismatch or at the end of interp->name. bm_entry_add_interp() caps a bound name at BINFMT_MISC_INTERP_NAME_MAX (32) bytes, so strcmp() reads at most 33 bytes of name. The smallest name__sz the kfunc accepts is 2, which leaves up to 31 bytes read beyond the checked extent. The handler's own load program has to pass a writable map value, and something has to store into it while the kfunc runs. The window between strnlen() and strcmp() is short, but with a BPF_F_MMAPABLE array the store is a plain user space write into the mapped value, so a loop can hit it without a single bpf() call. Copy the name into a stack buffer of BINFMT_MISC_INTERP_NAME_MAX + 1 bytes, terminate it, and look up the copy. The memcpy() length is below name__sz, so the copy stays inside the extent the verifier checked, and the BPF buffer is not read again afterwards. Return -ENOENT first for a name longer than BINFMT_MISC_INTERP_NAME_MAX. bm_entry_add_interp() rejects a longer name, and the only other binding site attaches the empty name. No entry can bind such a name, so that lookup already ended in -ENOENT and no result changes. Check the first byte of the copy and return -EINVAL if it is NUL, as the existing "!len" test does for an empty name. Only an 'F' entry binds the empty name and a 'B' entry cannot carry 'F', so without that check a racing store of NUL to byte 0 would look up a name no entry binds and end in -ENOENT rather than -EINVAL. A NUL stored further into the name only shortens it to another name the program could have passed anyway. binfmt_misc_find_interp() itself is left alone: entry_attach_interpreter() calls it with a kernel string, and this kfunc now calls it with a private copy. Fixes: 6ec7c96bee30 ("binfmt_misc: let a 'B' entry bind its interpreters") Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260918-work-binfmt_misc-fixes-v1-1-647b24bc1c46@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc_bpf.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c index 91576ff05911d0..ce1bc78e851119 100644 --- a/fs/binfmt_misc_bpf.c +++ b/fs/binfmt_misc_bpf.c @@ -176,6 +176,7 @@ __bpf_kfunc int bpf_binprm_select_interp(struct linux_binprm *bprm, const char *name, size_t name__sz) { const struct binfmt_misc_interp *interp; + char buf[BINFMT_MISC_INTERP_NAME_MAX + 1]; size_t len; char *path; @@ -184,8 +185,20 @@ __bpf_kfunc int bpf_binprm_select_interp(struct linux_binprm *bprm, len = strnlen(name, name__sz); if (len == name__sz || !len) return -EINVAL; + /* No entry binds a longer name, so it cannot be found. */ + if (len > BINFMT_MISC_INTERP_NAME_MAX) + return -ENOENT; + + /* + * The program may pass memory that is written to while this runs, + * so look the name up in a private copy and check that instead. + */ + memcpy(buf, name, len); + buf[len] = '\0'; + if (!buf[0]) + return -EINVAL; - interp = binfmt_misc_find_interp(bprm->bpf_interps, name); + interp = binfmt_misc_find_interp(bprm->bpf_interps, buf); if (!interp) return -ENOENT; From 1970fc4ecb52dabce2e57f9be721964b936a052d Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Fri, 18 Sep 2026 11:20:00 +0200 Subject: [PATCH 0795/1417] binfmt_misc: fix racy checks in bpf set_interp kfuncs bpf_binprm_set_interp() tests path[0] != '/' on the buffer its load program passes and then reads the same buffer again to copy it with kmemdup_nul(). The buffer can be a BPF map value that another CPU rewrites between the two reads. If byte 0 is overwritten in that window, the kfunc stages a relative or empty interpreter path. The staged path is not checked again, so open_exec() resolves a relative path against the working directory of the task doing the exec. bpf_binprm_set_interp_arg() has the same pattern for its "!len" test and can stage an empty argument, which the interpreter then receives as an empty argv entry. The verifier checks the path and path__sz pair with BPF_READ | BPF_WRITE, so a writable array map value is an accepted argument. bpf(BPF_MAP_UPDATE_ELEM) on an array map copies the new value over the old one in place and takes no lock. Both kfuncs are KF_SLEEPABLE and allocate with GFP_KERNEL between the test and the copy, so the task can sleep inside the window: load program bpf(BPF_MAP_UPDATE_ELEM) bpf_binprm_set_interp() strnlen(path, path__sz) path[0] != '/' is false kmemdup_nul(path, len, GFP_KERNEL) allocation may sleep array_map_update_elem() copy_map_value() rewrites byte 0 copy reads path again bm_bpf_stage_selection() The test in the load program's column proves what byte 0 held only at the moment the test ran. The map update takes no lock, so it can store to byte 0 right after. kmemdup_nul() then copies the rewritten bytes, and bm_bpf_stage_selection() publishes them as bprm->bpf_interp. The staged path is not checked again on its way to open_exec(): load_misc_binary() entry_select_interpreter() returns bprm->bpf_interp unchanged build_interp_argv() copy_string_kernel() copies it as argv[0] bprm_change_interp() kstrdup() entry_open_interpreter() open_exec() unless a bound file is staged or the entry is an 'F' entry None of these functions tests the first byte, and load_misc_binary() hands the pointer to nothing else. In bpf_binprm_set_interp_arg(), strnlen() finds a non-zero len, a NUL is then stored to byte 0, and build_interp_argv() later copies the empty bprm->bpf_interp_arg with copy_string_kernel(). The handler's own load program has to pass a writable map value, and something has to store into it while the kfunc runs. The allocation can sleep inside the window, and with a BPF_F_MMAPABLE array the store is a plain user space write into the mapped value, so a loop can hit it without a single bpf() call. Check the private copy in both kfuncs, so that the string that gets staged is the string that was checked. bpf_binprm_select_interp() already looks its name up in a private copy for the same reason. The remaining tests work on path__sz, arg__sz or the local len, and the copy length is len, so the copy stays inside the extent the verifier checked. Results of bpf_binprm_set_interp() with the check on the copy: - A NUL stored to byte 0 fails interp[0] != '/' and gets -EINVAL. - For len == 0, kmemdup_nul() returns an empty string, so an empty path still gets -EINVAL. - A NUL stored further into the string only shortens it to another absolute path, or another non-empty argument, that the program could have passed anyway. - A path that both lacks the leading '/' and is PATH_MAX or longer now gets -ENAMETOOLONG instead of -EINVAL. - A path that is empty or lacks the leading '/' is now rejected after the copy rather than before it, so such a call makes an allocation and returns -ENOMEM instead of -EINVAL if that allocation fails. bpf_binprm_set_interp_arg() still rejects an empty argument before allocating, so its results are unchanged apart from the raced case fixed here. Both new checks run before the previously staged string is freed or replaced. A failing call frees only its own allocation and leaves the earlier selection in place, as the -ENOMEM path already does. Fixes: b4bfe2f6b011 ("binfmt_misc: add binfmt_misc_ops bpf struct_ops") Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260918-work-binfmt_misc-fixes-v1-2-647b24bc1c46@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc_bpf.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c index ce1bc78e851119..a3e26e8a402702 100644 --- a/fs/binfmt_misc_bpf.c +++ b/fs/binfmt_misc_bpf.c @@ -141,8 +141,6 @@ __bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm, len = strnlen(path, path__sz); if (len == path__sz) return -EINVAL; - if (path[0] != '/') - return -EINVAL; if (len >= PATH_MAX) return -ENAMETOOLONG; @@ -150,6 +148,15 @@ __bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm, if (!interp) return -ENOMEM; + /* + * The program may pass memory that is written to while this runs, + * so check the private copy and not the buffer it was made from. + */ + if (interp[0] != '/') { + kfree(interp); + return -EINVAL; + } + bm_bpf_stage_selection(bprm, interp, NULL); return 0; } @@ -241,6 +248,15 @@ __bpf_kfunc int bpf_binprm_set_interp_arg(struct linux_binprm *bprm, if (!val) return -ENOMEM; + /* + * The program may pass memory that is written to while this runs, + * so check the private copy and not the buffer it was made from. + */ + if (!val[0]) { + kfree(val); + return -EINVAL; + } + kfree(bprm->bpf_interp_arg); bprm->bpf_interp_arg = val; return 0; From 70194dc37670bd08e44b471389861cc01bd3a3c9 Mon Sep 17 00:00:00 2001 From: Aohan Mei Date: Mon, 14 Sep 2026 19:51:47 +0800 Subject: [PATCH 0796/1417] netfilter: nf_tables: skip expired catchall elements on insert and delete nft_setelem_catchall_insert() looks up duplicates with nft_set_elem_active() only, while nft_set_catchall_lookup() and the dump path additionally skip expired elements. Once a catchall element with a timeout expires, this predicate drift makes it invisible to userspace dumps, yet it still blocks re-insertion: with NLM_F_EXCL the request fails with -EEXIST, and without it the request reports success but silently inserts nothing. The stale entry only goes away when the (user-tunable) gc interval elapses, so the catchall rule may silently stop matching for an arbitrarily long time after its first expiration. The delete path shows the same drift: nft_setelem_catchall_deactivate() picks the first active-next entry in the catchall list, so with an expired entry still pending GC it retires the stale entry instead of the fresh one, and it deactivates an element that userspace no longer sees instead of failing with -ENOENT. Align both walks with the lookup and dump predicates: only an element that is active and not expired counts as a duplicate or delete candidate, using the per-netns timestamp taken at transaction start, in line with the set backend .insert/.deactivate and catchall GC sync paths. Reported-by: TencentOS Corvus AI Cc: stable@vger.kernel.org Fixes: aaa31047a6d2 ("netfilter: nftables: add catch-all set element support") Assisted-by: CodeBuddy:Kimi-K3 Signed-off-by: Aohan Mei Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_tables_api.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index c0b754a2d45b0a..b59628e6240c1b 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -6995,11 +6995,14 @@ static int nft_setelem_catchall_insert(const struct net *net, { struct nft_set_elem_catchall *catchall; u8 genmask = nft_genmask_next(net); + u64 tstamp = nft_net_tstamp(net); struct nft_set_ext *ext; list_for_each_entry(catchall, &set->catchall_list, list) { ext = nft_set_elem_ext(set, catchall->elem); - if (nft_set_elem_active(ext, genmask)) { + if (nft_set_elem_active(ext, genmask) && + !__nft_set_elem_expired(ext, tstamp) && + !nft_set_elem_is_dead(ext)) { *priv = catchall->elem; return -EEXIST; } @@ -7092,11 +7095,14 @@ static int nft_setelem_catchall_deactivate(const struct net *net, struct nft_set_elem *elem) { struct nft_set_elem_catchall *catchall; + u64 tstamp = nft_net_tstamp(net); struct nft_set_ext *ext; list_for_each_entry(catchall, &set->catchall_list, list) { ext = nft_set_elem_ext(set, catchall->elem); - if (!nft_is_active_next(net, ext)) + if (!nft_is_active_next(net, ext) || + __nft_set_elem_expired(ext, tstamp) || + nft_set_elem_is_dead(ext)) continue; kfree(elem->priv); From 88aed0422f39b22406f35f1e758cea25e7bbcfb5 Mon Sep 17 00:00:00 2001 From: Vinay Belgaumkar Date: Fri, 4 Sep 2026 11:16:24 -0700 Subject: [PATCH 0797/1417] perf: Fix null pointer access in is_include_guest_event() A typical module unload occurring event when there is an active perf connection leads to freeing of the pmu pointer. The call log is something like: .. __pmu_detach_event pmu_detach_event pmu_detach_events perf_pmu_unregister .. __pmu_detach_event() sets event->pmu to null. When the perf connection finally is closed, the following stack trace is observed: Oops: general protection fault, kernel NULL pointer dereference ... RIP: 0010:_free_event+0x3e/0x370 ... Call Trace: ... perf_event_release_kernel+0x260/0x2d0 perf_release+0x12/0x20 A call to mediated_pmu_unaccount_event() inside _free_event() is the root cause of this crash. Adding a check inside is_include_guest_event() ensures we don't accidentally access a null pmu ptr. In addition to this, we will now call mediated_pmu_unaccount_event() before clearing the pmu ptr so that nr_include_guest_events counts are maintained correctly. Fixes: eff95e170275 ("perf: Add APIs to create/release mediated guest vPMUs") Assisted-by: Claude:Claude-Sonnet-5 Signed-off-by: Vinay Belgaumkar Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260904181625.1394082-1-vinay.belgaumkar@intel.com --- kernel/events/core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/events/core.c b/kernel/events/core.c index fe33fe15689d07..db7b76d6b68aa5 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -6350,6 +6350,9 @@ static DEFINE_MUTEX(perf_mediated_pmu_mutex); /* !exclude_guest event of PMU with PERF_PMU_CAP_MEDIATED_VPMU */ static inline bool is_include_guest_event(struct perf_event *event) { + if (!event->pmu) + return false; + if ((event->pmu->capabilities & PERF_PMU_CAP_MEDIATED_VPMU) && !event->attr.exclude_guest) return true; @@ -13002,6 +13005,7 @@ static void __pmu_detach_event(struct pmu *pmu, struct perf_event *event, exclusive_event_destroy(event); module_put(pmu->module); + mediated_pmu_unaccount_event(event); event->pmu = NULL; /* force fault instead of UAF */ } From fe3c73d7bc769e7afc252f867a3421fe168b898d Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 15 Sep 2026 20:41:01 +0200 Subject: [PATCH 0798/1417] sched/core: Avoid false migration warning for proxy donors Proxy execution can move a blocked donor's scheduling context to the lock owner's CPU even when the donor is migration-disabled. The donor does not execute there, and its original execution CPU remains recorded in wake_cpu. set_task_cpu() warns unconditionally for migration-disabled tasks, so a subsequent proxy migration or the wakeup path returning the donor home triggers a false positive: moving a blocked scheduling context does not violate the migration-disabled execution context. For example, creating a mutex owner on CPU1 and a migration-disabled waiter on CPU0 can trigger the following warning: proxy_migrate_repro: donor blocking on CPU0 with migration disabled proxy_migrate_repro: donor moved from CPU0 to CPU1 WARNING: kernel/sched/core.c:3389 at set_task_cpu+0x1d3/0x280 ... Call Trace: try_to_wake_up+0x43f/0x780 __mutex_unlock_slowpath+0x330/0x540 owner_fn+0x9f/0xc0 [proxy_migrate_repro] ... proxy_migrate_repro: donor woke on CPU0, task_cpu=0 proxy_migrate_repro: completed Exclude blocked proxy donors from the warning. The proxy wakeup path restores an executable placement before clearing the blocked state. Fixes: b049b81bdff6 ("sched: Handle blocked-waiter migration (and return migration)") Signed-off-by: Andrea Righi Signed-off-by: Peter Zijlstra (Intel) Acked-by: John Stultz Link: https://patch.msgid.link/20260915184101.2621252-1-arighi@nvidia.com --- kernel/sched/core.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 7885ff76e69f28..0b846a13c62874 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -3351,6 +3351,8 @@ void relax_compatible_cpus_allowed_ptr(struct task_struct *p) void set_task_cpu(struct task_struct *p, unsigned int new_cpu) { unsigned int state = READ_ONCE(p->__state); + bool proxy_migrated = sched_proxy_exec() && p->is_blocked && + task_cpu(p) != p->wake_cpu; /* * We should never call set_task_cpu() on a blocked task, @@ -3386,7 +3388,12 @@ void set_task_cpu(struct task_struct *p, unsigned int new_cpu) */ WARN_ON_ONCE(!cpu_online(new_cpu)); - WARN_ON_ONCE(is_migration_disabled(p)); + /* + * Proxy execution can move a blocked task's scheduling context to any + * CPU without moving its migration-disabled execution context. The + * wakeup path will return the task to a CPU where it can execute. + */ + WARN_ON_ONCE(is_migration_disabled(p) && !proxy_migrated); trace_sched_migrate_task(p, new_cpu); From 93f53499d0b945e8ae447f497faf743d60069f61 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Thu, 17 Sep 2026 16:09:06 -0700 Subject: [PATCH 0799/1417] x86/fred: Reconstruct the #GP context for rejected INT instructions FRED event delivery does not use the IDT, so the gate DPL check that rejects a user INT n falls to software (Intel FRED specification [1], section 8.3). fred_intx() rejects the same vectors as IDT delivery, but reports a zero error code and the IP after the INT. This breaks the signal ABI. Wine uses the error code to recognize INT 0x2d, so the changed context turns a handled breakpoint into an access violation in Elden Ring. Rewind IP using the instruction length in the augmented SS and synthesize the IDT selector error code, (vector << 3) | 2. Set RF in the saved flags, as the CPU does for a #GP fault. Section 5.2.1 defines the saved vector, instruction length and RF state. The supplied length handles prefixes without reading user memory. Limit the changes to already-rejected software interrupts, preserving the accepted INT3, INT4 and enabled INT80 paths and hardware exceptions. With IA32 emulation disabled, INT 0x80 now reports the same #GP as the DPL 0 gate IDT installs there. The rewound IP also stops fixup_iopl_exception() from inspecting the byte after the INT. Also clear the software event flag. Section 6.2.3 specifies that ERETU with this flag and TF set traps before executing any user instruction. A tracer that suppresses SIGSEGV and resumes with TF set expects the next instruction to run first, as after IRET. The sigreturn path clears the same flag for this reason in prevent_single_step_upon_eretu(). [1] Intel Flexible Return and Event Delivery (FRED) Specification, revision 9.0 (346446-009US), sections 5.2.1, 6.2.3 and 8.3. Fixes: 14619d912b65 ("x86/fred: FRED entry/exit and dispatch code") Closes: https://gitlab.freedesktop.org/mesa/mesa/-/work_items/15745 Closes: https://gitlab.freedesktop.org/mesa/mesa/-/work_items/16132 Reported-by: Paul Gofman Signed-off-by: Matthew Schwartz Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: H. Peter Anvin Link: https://cdrdv2.intel.com/v1/dl/getContent/678938 # [1] Link: https://patch.msgid.link/20260917230907.2080792-2-matthew.schwartz@linux.dev --- arch/x86/entry/entry_fred.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/x86/entry/entry_fred.c b/arch/x86/entry/entry_fred.c index fb3594ddf731f5..854899bfec5d9d 100644 --- a/arch/x86/entry/entry_fred.c +++ b/arch/x86/entry/entry_fred.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -71,7 +72,15 @@ static noinstr void fred_intx(struct pt_regs *regs) #endif default: - return exc_general_protection(regs, 0); + /* + * Reconstruct the #GP fault state that IDT delivery would produce. + * Clear the software event flag so ERETU with TF set does not trap + * before the resumed instruction. See prevent_single_step_upon_eretu(). + */ + regs->ip -= regs->fred_ss.insnlen; + regs->flags |= X86_EFLAGS_RF; + regs->fred_ss.swevent = 0; + return exc_general_protection(regs, (regs->fred_ss.vector << 3) | 2); } } From 96443a53bc3ef4b67dab0c497878fc8d56f799f3 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Thu, 17 Sep 2026 16:09:07 -0700 Subject: [PATCH 0800/1417] selftests/x86: Check signal state for rejected software interrupts Add a test of the signal ABI for INT instructions in both 32-bit and 64-bit processes. Check the signal number, trap number, error code, si_code, si_addr, instruction pointer and RF/TF state against legacy IDT behavior. Include a 15-byte prefixed INT to check that IP uses the hardware instruction length. Exercise both INT3 encodings, INT4, UD2 and HLT to cover the unchanged trap and fault paths. Run each instruction with TF clear and set. Resume at a known NOP after handling the signal and check that single-stepping traps after the NOP. Also drive INT 0x2d under ptrace, which resumes through the fault frame rather than sigreturn and so exposes a stale FRED software event flag. Start from an INT3 stop, whose FRED frame has no software event flag, instead of the syscall frame of raise(SIGSTOP). Single-step into the INT and check that the fault reports its address. Then suppress SIGSEGV and resume at the NOP, once with PTRACE_SINGLESTEP and once with PTRACE_CONT and TF set. Section 6.2.3 of the Intel FRED specification [1] specifies the immediate single-step trap caused by returning with both that flag and TF set. Check that each trap occurs after the NOP, rather than at its address. Report whether the CPU supports FRED, since a pass looks the same on either entry path. INT 0x80 with IA32 emulation disabled and a 64-bit tracer of a 32-bit tracee are not covered. Both variants pass all 29 checks on a non-FRED AMD host and on Panther Lake with FRED enabled and the fix applied. With the same binaries on unpatched Panther Lake, 16 signal-context checks fail and the first ptrace check reports the IP after the INT. The two dependent ptrace resume checks are not reached. [1] Intel Flexible Return and Event Delivery (FRED) Specification, revision 9.0 (346446-009US), section 6.2.3. Signed-off-by: Matthew Schwartz Signed-off-by: Peter Zijlstra (Intel) Link: https://cdrdv2.intel.com/v1/dl/getContent/678938 # [1] Link: https://patch.msgid.link/20260917230907.2080792-3-matthew.schwartz@linux.dev --- tools/testing/selftests/x86/Makefile | 2 +- tools/testing/selftests/x86/int_signal.c | 311 +++++++++++++++++++++++ 2 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/x86/int_signal.c diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile index 434065215d127a..d478b13cc8d5d9 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile @@ -13,7 +13,7 @@ CAN_BUILD_WITH_NOPIE := $(shell ./check_cc.sh "$(CC)" trivial_program.c -no-pie) TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt test_mremap_vdso \ check_initial_reg_state sigreturn iopl ioperm \ test_vsyscall mov_ss_trap sigtrap_loop \ - syscall_arg_fault fsgsbase_restore sigaltstack + syscall_arg_fault fsgsbase_restore sigaltstack int_signal TARGETS_C_BOTHBITS += nx_stack TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \ test_FCMOV test_FCOMI test_FISTTP \ diff --git a/tools/testing/selftests/x86/int_signal.c b/tools/testing/selftests/x86/int_signal.c new file mode 100644 index 00000000000000..22676dac72b59d --- /dev/null +++ b/tools/testing/selftests/x86/int_signal.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Check the signal context for INT instructions with IDT and FRED entry. */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "helpers.h" + +#ifdef __x86_64__ +#define REG_IP REG_RIP +#define USER_IP rip +#define STACK_PTR "%rsp" +#else +#define REG_IP REG_EIP +#define USER_IP eip +#define STACK_PTR "%esp" +#endif + +/* + * Each instruction has normal and single-step entry points. Resume at the + * NOP after handling its signal, then expect a trace trap after that NOP + * when TF is set. Explicit labels avoid assuming the kernel's saved IP. + */ +#define PROBE(name, insn) \ + extern void name(void); \ + extern void name##_tf(void); \ + extern const char name##_end[], name##_step[]; \ + asm(".pushsection .text\n" \ + ".globl " #name "_tf\n" \ + ".type " #name "_tf, @function\n" \ + #name "_tf:\n" \ + "pushf\n" \ + "orl $0x100, (" STACK_PTR ")\n" \ + "popf\n" \ + ".globl " #name "\n" \ + ".type " #name ", @function\n" \ + #name ":\n" insn "\n" \ + ".globl " #name "_end\n" \ + #name "_end:\nnop\n" \ + ".globl " #name "_step\n" \ + #name "_step:\nret\n" \ + ".size " #name ", .-" #name "\n" \ + ".size " #name "_tf, .-" #name "_tf\n" \ + ".popsection\n") + +PROBE(int1, ".byte 0xcd, 0x01"); +PROBE(int29, ".byte 0xcd, 0x29"); +PROBE(int2c, ".byte 0xcd, 0x2c"); +PROBE(int2d, ".byte 0xcd, 0x2d"); +PROBE(prefixed_int2d, ".byte 0x66, 0xcd, 0x2d"); +PROBE(long_int2d, ".fill 13, 1, 0x2e\n.byte 0xcd, 0x2d"); +PROBE(int81, ".byte 0xcd, 0x81"); +PROBE(intff, ".byte 0xcd, 0xff"); +PROBE(short_int3, ".byte 0xcc"); +PROBE(long_int3, ".byte 0xcd, 0x03"); +PROBE(int4, ".byte 0xcd, 0x04"); +PROBE(ud2, ".byte 0x0f, 0x0b"); +PROBE(hlt, ".byte 0xf4"); + +struct test { + const char *name; + void (*run)(void); + void (*run_tf)(void); + const char *end, *step; + int signo, trap, error, ip_offset, flags, code; +}; + +#define TEST(name, sig, trap, error, offset, flags, code) \ + { #name, name, name##_tf, name##_end, name##_step, \ + sig, trap, error, offset, flags, code } + +#define GP(name, error) \ + TEST(name, SIGSEGV, 13, error, 0, X86_EFLAGS_RF, SI_KERNEL) + +static const struct test tests[] = { + GP(int1, 0x00a), + GP(int29, 0x14a), + GP(int2c, 0x162), + GP(int2d, 0x16a), + GP(prefixed_int2d, 0x16a), + GP(long_int2d, 0x16a), + GP(int81, 0x40a), + GP(intff, 0x7fa), + GP(hlt, 0), + TEST(short_int3, SIGTRAP, 3, 0, 1, 0, SI_KERNEL), + TEST(long_int3, SIGTRAP, 3, 0, 2, 0, SI_KERNEL), + TEST(int4, SIGSEGV, 4, 0, 2, 0, SI_KERNEL), + TEST(ud2, SIGILL, 6, 0, 0, X86_EFLAGS_RF, ILL_ILLOPN), +}; + +static const struct test *active; +static volatile sig_atomic_t seen, signo, trap, error, ip_offset, flags; +static volatile sig_atomic_t code, addr_ok, single_step, stepped, step_ok; + +static void handler(int sig, siginfo_t *info, void *context) +{ + ucontext_t *uc = context; + uintptr_t ip = uc->uc_mcontext.gregs[REG_IP]; + uintptr_t start = (uintptr_t)active->run; + uintptr_t end = (uintptr_t)active->end; + + if (seen && single_step && sig == SIGTRAP) { + if (stepped++) { + ksft_print_msg("%s: second trace trap at %#lx\n", + active->name, (unsigned long)ip); + _exit(KSFT_FAIL); + } + step_ok = ip == (uintptr_t)active->step && + uc->uc_mcontext.gregs[REG_TRAPNO] == 1 && + info->si_code == TRAP_TRACE; + uc->uc_mcontext.gregs[REG_EFL] &= ~X86_EFLAGS_TF; + return; + } + + if (seen || ip < start || ip > end) { + ksft_print_msg("%s: unexpected signal %d at %#lx\n", + active->name, sig, (unsigned long)ip); + _exit(KSFT_FAIL); + } + + signo = sig; + trap = uc->uc_mcontext.gregs[REG_TRAPNO]; + error = uc->uc_mcontext.gregs[REG_ERR]; + ip_offset = ip - start; + flags = uc->uc_mcontext.gregs[REG_EFL] & (X86_EFLAGS_RF | X86_EFLAGS_TF); + code = info->si_code; + /* force_sig() reports no address, force_sig_fault() reports the IP. */ + addr_ok = info->si_addr == (code == SI_KERNEL ? NULL : (void *)ip); + seen = 1; + uc->uc_mcontext.gregs[REG_IP] = end; +} + +static void wait_for_child(pid_t child, int *status) +{ + pid_t ret; + + do { + ret = waitpid(child, status, 0); + } while (ret < 0 && errno == EINTR); + if (ret != child) + ksft_exit_fail_perror("waitpid"); +} + +/* Resume the tracee and check where the next stop lands. */ +static bool resume_to(pid_t child, int *status, int request, int sig, + const void *ip, const char *what) +{ + struct user_regs_struct regs; + + if (ptrace(request, child, 0, 0)) + return false; + wait_for_child(child, status); + if (!WIFSTOPPED(*status)) { + ksft_print_msg("%s: tracee did not stop\n", what); + return false; + } + if (WSTOPSIG(*status) != sig) { + ksft_print_msg("%s: stopped with signal %d, expected %d\n", + what, WSTOPSIG(*status), sig); + return false; + } + if (ptrace(PTRACE_GETREGS, child, 0, ®s)) + return false; + if ((unsigned long)regs.USER_IP != (unsigned long)ip) { + ksft_print_msg("%s: stopped at %#lx, expected %#lx\n", what, + (unsigned long)regs.USER_IP, (unsigned long)ip); + return false; + } + return true; +} + +static bool set_ip(pid_t child, const void *ip, bool tf) +{ + struct user_regs_struct regs; + + if (ptrace(PTRACE_GETREGS, child, 0, ®s)) + return false; + regs.USER_IP = (unsigned long)ip; + if (tf) + regs.eflags |= X86_EFLAGS_TF; + return !ptrace(PTRACE_SETREGS, child, 0, ®s); +} + +/* + * Exercise the tracer paths that resume through the fault frame rather than + * sigreturn. A stale FRED software event flag on that frame traps before the + * NOP executes instead of after it. + */ +static void test_ptrace(void) +{ + bool into = false, step = false, cont = false; + pid_t child; + int status; + + child = fork(); + if (child < 0) + ksft_exit_fail_perror("fork"); + if (!child) { + if (ptrace(PTRACE_TRACEME, 0, 0, 0)) + _exit(KSFT_FAIL); + /* Start from a breakpoint frame, not the syscall frame of raise(). */ + asm volatile("int3"); + _exit(KSFT_FAIL); + } + + wait_for_child(child, &status); + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) + goto out; + if (ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_EXITKILL)) + goto out; + + /* Single-step into the INT. The fault must report the INT's address. */ + if (!set_ip(child, int2d, false)) + goto out; + into = resume_to(child, &status, PTRACE_SINGLESTEP, SIGSEGV, int2d, + "single-step into INT"); + if (!into) + goto out; + + /* Suppress SIGSEGV and single-step the NOP. */ + if (!set_ip(child, int2d_end, false)) + goto out; + step = resume_to(child, &status, PTRACE_SINGLESTEP, SIGTRAP, int2d_step, + "single-step after INT"); + if (!step) + goto out; + + /* Fault again, then suppress SIGSEGV and continue with TF set. */ + if (!set_ip(child, int2d, false)) + goto out; + if (!resume_to(child, &status, PTRACE_CONT, SIGSEGV, int2d, + "continue to INT")) + goto out; + if (!set_ip(child, int2d_end, true)) + goto out; + cont = resume_to(child, &status, PTRACE_CONT, SIGTRAP, int2d_step, + "continue with TF after INT"); +out: + if (WIFSTOPPED(status)) { + kill(child, SIGKILL); + wait_for_child(child, &status); + } + ksft_test_result(into, "ptrace single-step into INT faults at the INT\n"); + ksft_test_result(step, "ptrace single-step after suppressing SIGSEGV\n"); + ksft_test_result(cont, "ptrace continue with TF after suppressing SIGSEGV\n"); +} + +static bool cpu_has_fred(void) +{ + unsigned int eax, ebx, ecx, edx; + + if (__get_cpuid_max(0, NULL) < 7) + return false; + __cpuid_count(7, 1, eax, ebx, ecx, edx); + return eax & (1 << 17); +} + +int main(void) +{ + unsigned int i, tf; + int expected_flags, ok; + + ksft_print_header(); + ksft_set_plan(2 * ARRAY_SIZE(tests) + 3); + ksft_print_msg("CPU %s FRED\n", cpu_has_fred() ? "supports" : "lacks"); + sethandler(SIGSEGV, handler, 0); + sethandler(SIGTRAP, handler, 0); + sethandler(SIGILL, handler, 0); + + for (tf = 0; tf < 2; tf++) { + for (i = 0; i < ARRAY_SIZE(tests); i++) { + active = &tests[i]; + single_step = tf; + seen = signo = trap = error = ip_offset = flags = 0; + code = addr_ok = stepped = step_ok = 0; + expected_flags = active->flags | (tf ? X86_EFLAGS_TF : 0); + if (tf) + active->run_tf(); + else + active->run(); + + ok = seen && signo == active->signo && trap == active->trap && + error == active->error && ip_offset == active->ip_offset && + flags == expected_flags && code == active->code && addr_ok && + (!tf || (stepped && step_ok)); + ksft_test_result(ok, "%s%s\n", active->name, tf ? " with TF" : ""); + if (!ok) { + ksft_print_msg("got signal=%d trap=%d error=%#x ip=%d\n", + signo, trap, error, ip_offset); + ksft_print_msg("got flags=%#x code=%d addr_ok=%d step_ok=%d\n", + flags, code, addr_ok, step_ok); + ksft_print_msg("expected signal=%d trap=%d error=%#x ip=%d\n", + active->signo, active->trap, active->error, + active->ip_offset); + ksft_print_msg("expected flags=%#x code=%d\n", + expected_flags, active->code); + } + } + } + test_ptrace(); + ksft_finished(); +} From 406aa2b186d3f13a35bc1ad6aff4274917851bc4 Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Tue, 15 Sep 2026 13:24:22 +0100 Subject: [PATCH 0801/1417] perf/arm-cmn: Fix multi-filter encoding The current special-case for EVICT_STATE_SEL filtering effectively assigns the "filter" and "filter2" controls in the opposite order from how the CMN S3 r2 TRM states "Filtering is programmed in pmu_hbt_lbt_sel and pmu_evict_state_sel". On reflection, not only does this seem unnecessarily non-obvious to users, but it's also likely to be a problem for scaling to a full multi-filter abstraction in future. There is a logical order to filters based on their bitfield positions in the pmu_event_sel register, which the TRM descriptions allude to, and the cmn_filter_select enum already (almost) follows, so let's fix the UABI to follow suit while it's still unreleased. Fixes: 09178f536bb9 ("perf/arm-cmn: Plumb in new filter types") Signed-off-by: Robin Murphy Reviewed-by: Ilkka Koskinen Reviewed-by: Leo Yan Signed-off-by: Will Deacon --- drivers/perf/arm-cmn.c | 58 +++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c index 33ee2be9b38642..5378fba916cf53 100644 --- a/drivers/perf/arm-cmn.c +++ b/drivers/perf/arm-cmn.c @@ -305,9 +305,9 @@ enum cmn_filter_select { SEL_NONE, SEL_OCCUP1_ID, SEL_CLASS_OCCUP_ID, - SEL_CBUSY_SNTHROTTLE_SEL, SEL_HBT_LBT_SEL, SEL_SN_HOME_SEL, + SEL_CBUSY_SNTHROTTLE_SEL, SEL_SNP_VC_SEL, SEL_ENHANCED_HBT_LBT_SEL, SEL_EVICT_STATE_SEL, @@ -978,10 +978,14 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj, _CMN_EVENT_HNS(_model, _name##_all, _event, _sel, 0), \ _CMN_EVENT_HNS(_model, _name##_hbt, _event, _sel, 1), \ _CMN_EVENT_HNS(_model, _name##_lbt, _event, _sel, 2) -#define _CMN_EVENT_HNS_HBT2(_model, _name, _event, _fsel1, f1) \ - _CMN_EVENT_HNS(_model, _name##_all, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 0), \ - _CMN_EVENT_HNS(_model, _name##_hbt, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 1), \ - _CMN_EVENT_HNS(_model, _name##_lbt, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 2) +#define _CMN_EVENT_HNS_EVICT(_model, _name, _event, _fsel1, f1) \ + _CMN_EVENT_HNS(_model, _name##_all, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 0), \ + _CMN_EVENT_HNS(_model, _name##_eu, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 1), \ + _CMN_EVENT_HNS(_model, _name##_en, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 2), \ + _CMN_EVENT_HNS(_model, _name##_su, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 3), \ + _CMN_EVENT_HNS(_model, _name##_sn, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 4), \ + _CMN_EVENT_HNS(_model, _name##_mu, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 5), \ + _CMN_EVENT_HNS(_model, _name##_mn, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 6) #define CMN_EVENT_HNS_OCC(_model, _name, _event) \ CMN_EVENT_HN_OCC(_model, hns_##_name, CMN_TYPE_HNS, _event), \ @@ -1014,13 +1018,9 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj, _CMN_EVENT_HNS(CMNS3R2, _name##_ccglcn, _event, SEL_ENHANCED_HBT_LBT_SEL, 5), \ _CMN_EVENT_HNS(CMNS3R2, _name##_ccgrn, _event, SEL_ENHANCED_HBT_LBT_SEL, 6) #define CMN_EVENT_HNS_EVICT(_model, _name, _event) \ - _CMN_EVENT_HNS_HBT2(_model, _name##_all, _event, SEL_EVICT_STATE_SEL, 0), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_eu, _event, SEL_EVICT_STATE_SEL, 1), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_en, _event, SEL_EVICT_STATE_SEL, 2), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_su, _event, SEL_EVICT_STATE_SEL, 3), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_sn, _event, SEL_EVICT_STATE_SEL, 4), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_mu, _event, SEL_EVICT_STATE_SEL, 5), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_mn, _event, SEL_EVICT_STATE_SEL, 6) + _CMN_EVENT_HNS_EVICT(_model, _name##_all, _event, SEL_HBT_LBT_SEL, 0), \ + _CMN_EVENT_HNS_EVICT(_model, _name##_hbt, _event, SEL_HBT_LBT_SEL, 1), \ + _CMN_EVENT_HNS_EVICT(_model, _name##_lbt, _event, SEL_HBT_LBT_SEL, 2) #define CMN_EVENT_HNSR0_HBT(_name, _event) \ _CMN_EVENT_HNS_HBT(CMN700 | CMNS3R01, _name, _event, SEL_HBT_LBT_SEL) @@ -1860,10 +1860,12 @@ static void arm_cmn_val_add_event(struct arm_cmn *cmn, struct arm_cmn_val *val, val->dtm_count[dtm]++; + if (sel == SEL_EVICT_STATE_SEL) { + val->filter[dtm][sel] = CMN_EVENT_FILTER2(event) + 1; + sel = SEL_HBT_LBT_SEL; + } if (sel) val->filter[dtm][sel] = CMN_EVENT_FILTER(event) + 1; - if (sel == SEL_EVICT_STATE_SEL) - val->filter[dtm][SEL_HBT_LBT_SEL] = CMN_EVENT_FILTER2(event) + 1; if (type != CMN_TYPE_WP) continue; @@ -1914,14 +1916,17 @@ static int arm_cmn_validate_group(struct arm_cmn *cmn, struct perf_event *event) if (val->dtm_count[dtm] == CMN_DTM_NUM_COUNTERS) goto done; + if (sel == SEL_EVICT_STATE_SEL) { + if (val->filter[dtm][sel] && + val->filter[dtm][sel] != CMN_EVENT_FILTER2(event) + 1) + goto done; + sel = SEL_HBT_LBT_SEL; + } + if (sel && val->filter[dtm][sel] && val->filter[dtm][sel] != CMN_EVENT_FILTER(event) + 1) goto done; - if (sel == SEL_EVICT_STATE_SEL && val->filter[dtm][SEL_HBT_LBT_SEL] && - val->filter[dtm][SEL_HBT_LBT_SEL] != CMN_EVENT_FILTER2(event) + 1) - goto done; - if (type != CMN_TYPE_WP) continue; @@ -1950,7 +1955,7 @@ static enum cmn_filter_select arm_cmn_event_filter(const struct arm_cmn *cmn, for (int i = 0; i < ARRAY_SIZE(arm_cmn_event_attrs) - 1; i++) { e = container_of(arm_cmn_event_attrs[i], typeof(*e), attr.attr); if (e->model & model && e->type == type && e->eventid == eventid) - return e->filter[0].sel; + return e->filter[1].sel ?: e->filter[0].sel; } return SEL_NONE; } @@ -2081,16 +2086,21 @@ static void arm_cmn_event_clear(struct arm_cmn *cmn, struct perf_event *event, static int arm_cmn_set_event_filter(struct arm_cmn_node *dn, struct perf_event *event) { enum cmn_filter_select fsel = to_cmn_hw(event)->filter_sel; + bool evict_state = fsel == SEL_EVICT_STATE_SEL; int ret = 0; + if (evict_state) { + ret = arm_cmn_set_event_sel_hi(dn, fsel, CMN_EVENT_FILTER2(event)); + if (ret) + return ret; + fsel = SEL_HBT_LBT_SEL; + } if (fsel) ret = arm_cmn_set_event_sel_hi(dn, fsel, CMN_EVENT_FILTER(event)); - if (fsel == SEL_EVICT_STATE_SEL && !ret) { - ret = arm_cmn_set_event_sel_hi(dn, SEL_HBT_LBT_SEL, CMN_EVENT_FILTER2(event)); - if (ret) - dn->filter[fsel].count--; - } + if (ret && evict_state) + dn->filter[SEL_EVICT_STATE_SEL].count--; + return ret; } From bb756b11ad63832ebee58caf9e8f9381eaecff9f Mon Sep 17 00:00:00 2001 From: Zeng Heng Date: Fri, 11 Sep 2026 09:58:59 +0800 Subject: [PATCH 0802/1417] arm64: io: Reject non-user protection in ioremap_prot() Mapping a stack-top page via /dev/mem with PROT_NONE and then reading that process's /proc//cmdline triggers a spurious WARN in ioremap_prot() through generic_access_phys(): WARNING: ./arch/arm64/include/asm/io.h:275 at generic_access_phys Call trace: generic_access_phys+0x1c8/0x228 (P) __access_remote_vm+0x2b4/0x398 access_remote_vm+0x14/0x30 get_mm_cmdline+0xf8/0x2a0 proc_pid_cmdline_read+0x68/0x120 generic_access_phys() passes the protection derived from the user PTE to ioremap_prot(). On arm64, a PROT_NONE mapping is represented by a present-invalid PTE, so pte_present() still returns true and the protection reaches ioremap_prot(). A PROT_NONE mapping does not have PTE_USER, causing the existing WARN_ON_ONCE() in ioremap_prot() to fire even though this is a valid user mapping. Execute-only mappings have the same issue and must not be readable through this path either. ioremap_prot() should therefore reject protection values without PTE_USER without warning. This makes the access fail cleanly for PROT_NONE and execute-only mappings while retaining the existing user-protection contract. Fixes: 8f098037139b ("arm64: io: Extract user memory type in ioremap_prot()") Signed-off-by: Zeng Heng Reviewed-by: Catalin Marinas Signed-off-by: Will Deacon --- arch/arm64/include/asm/io.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h index 49a7002661a928..9aa0bb08ab60cd 100644 --- a/arch/arm64/include/asm/io.h +++ b/arch/arm64/include/asm/io.h @@ -280,7 +280,8 @@ static inline void __iomem *ioremap_prot(phys_addr_t phys, size_t size, pgprot_t prot; ptval_t user_prot_val = pgprot_val(user_prot); - if (WARN_ON_ONCE(!(user_prot_val & PTE_USER))) + /* Reject PROT_NONE and exec-only */ + if (!(user_prot_val & PTE_USER)) return NULL; prot = __pgprot_modify(PAGE_KERNEL, PTE_ATTRINDX_MASK, From baaa9b126b875b40ffd9356da52c7c871917e5bb Mon Sep 17 00:00:00 2001 From: Yureka Lilian Date: Fri, 11 Sep 2026 19:02:10 +0200 Subject: [PATCH 0803/1417] arm64: Add override for WFxT Add an override for WFxT support within ID_AA64ISAR2_EL1 to allow it to be disabled using the new arm64.nowfxt command line parameter. This accompanies the idle=nop param introduced in a previous patch series [1] in dealing with misbehaving WFI and WFIT instructions on Apple Silicon SoCs, and eases debugging of other quirky WFxT implementations. Link[1]: https://lore.kernel.org/all/20260804-arm64-idle-param-v3-1-d10f8159062b@cyberchaos.dev/ Suggested-by: Will Deacon Signed-off-by: Yureka Lilian Signed-off-by: Will Deacon --- Documentation/admin-guide/kernel-parameters.txt | 3 +++ arch/arm64/kernel/pi/idreg-override.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 68647ff4bdd24b..0fe9e36ce61abb 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -589,6 +589,9 @@ Kernel parameters arm64.nosve [ARM64] Unconditionally disable Scalable Vector Extension support + arm64.nowfxt [ARM64] Unconditionally disable Wait For Event with + Timeout and Wait For Interrupt with Timeout support + ataflop= [HW,M68k] atarimouse= [HW,MOUSE] Atari Mouse diff --git a/arch/arm64/kernel/pi/idreg-override.c b/arch/arm64/kernel/pi/idreg-override.c index 274bc72ba98d0f..acaf56234973b9 100644 --- a/arch/arm64/kernel/pi/idreg-override.c +++ b/arch/arm64/kernel/pi/idreg-override.c @@ -186,6 +186,7 @@ static const struct ftr_set_desc isar2 __prel64_initconst = { .name = "id_aa64isar2", .override = &id_aa64isar2_override, .fields = { + FIELD("wfxt", ID_AA64ISAR2_EL1_WFxT_SHIFT, NULL), FIELD("gpa3", ID_AA64ISAR2_EL1_GPA3_SHIFT, NULL), FIELD("apa3", ID_AA64ISAR2_EL1_APA3_SHIFT, NULL), FIELD("mops", ID_AA64ISAR2_EL1_MOPS_SHIFT, NULL), @@ -254,6 +255,7 @@ static const struct { "id_aa64isar1.api=0 id_aa64isar1.apa=0 " "id_aa64isar2.gpa3=0 id_aa64isar2.apa3=0" }, { "arm64.nomops", "id_aa64isar2.mops=0" }, + { "arm64.nowfxt", "id_aa64isar2.wfxt=0" }, { "arm64.nomte", "id_aa64pfr1.mte=0" }, { "nokaslr", "arm64_sw.nokaslr=1" }, { "rodata=off", "arm64_sw.rodataoff=1" }, From b7403afb7a5f85073243df10238b3483958ad69e Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 6 Sep 2026 13:14:16 +0100 Subject: [PATCH 0804/1417] arm64: errata: match the target implementation CPU's own MIDR __is_affected_midr_range() is handed the MIDR and REVIDR of one target implementation CPU, but tests the erratum's range with is_midr_in_range(), which re-scans all of target_impl_cpus[] and ignores the @midr argument. The range test is thus constant across the per-CPU loop in is_affected_midr_range() and only answers "is any target CPU in range". Since just the fixed_revs REVIDR check uses the iteration's own registers, an out-of-range target CPU can decide whether a MIDR_FIXED() exemption applies. A VM then enables a workaround whose only in-range CPU is fixed silicon, e.g. erratum 2658417 on a Cortex-A510 r1p1 with REVIDR_EL1[25] set. Factor the range test into __is_midr_in_range(), which takes an explicit MIDR, and use it in __is_affected_midr_range(). Fixes: 86edf6bdcf05 ("smccc/kvm_guest: Enable errata based on implementation CPUs") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: David Carlier Signed-off-by: Will Deacon --- arch/arm64/kernel/cpu_errata.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c index b33dccfafaf8c3..8ec47d89b45bb3 100644 --- a/arch/arm64/kernel/cpu_errata.c +++ b/arch/arm64/kernel/cpu_errata.c @@ -28,18 +28,21 @@ bool cpu_errata_set_target_impl(u64 num, void *impl_cpus) return true; } +static inline bool __is_midr_in_range(u32 midr, struct midr_range const *range) +{ + return midr_is_cpu_model_range(midr, range->model, + range->rv_min, range->rv_max); +} + static inline bool is_midr_in_range(struct midr_range const *range) { int i; if (!target_impl_cpu_num) - return midr_is_cpu_model_range(read_cpuid_id(), range->model, - range->rv_min, range->rv_max); + return __is_midr_in_range(read_cpuid_id(), range); for (i = 0; i < target_impl_cpu_num; i++) { - if (midr_is_cpu_model_range(target_impl_cpus[i].midr, - range->model, - range->rv_min, range->rv_max)) + if (__is_midr_in_range(target_impl_cpus[i].midr, range)) return true; } return false; @@ -59,7 +62,7 @@ __is_affected_midr_range(const struct arm64_cpu_capabilities *entry, u32 midr, u32 revidr) { const struct arm64_midr_revidr *fix; - if (!is_midr_in_range(&entry->midr_range)) + if (!__is_midr_in_range(midr, &entry->midr_range)) return false; midr &= MIDR_REVISION_MASK | MIDR_VARIANT_MASK; From 4485a01f4df1c9683d8ffe3e4ade6c33c9572d3c Mon Sep 17 00:00:00 2001 From: Sean Young Date: Sun, 13 Sep 2026 15:12:27 +0100 Subject: [PATCH 0805/1417] parisc: unwind: Replace open-coded binary search with bsearch() There is a bug in the binary search where hi can underflow. If addr is less than the first entry, then "hi = mid - 1" will underflow to ULONG_MAX. Then we have an out-of-bounds read. Replace the open-coded binary search with bsearch(). Issue found by an LLM. Signed-off-by: Sean Young Signed-off-by: Helge Deller --- arch/parisc/kernel/unwind.c | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/arch/parisc/kernel/unwind.c b/arch/parisc/kernel/unwind.c index 32103a270a8ea1..fab9ae22191af6 100644 --- a/arch/parisc/kernel/unwind.c +++ b/arch/parisc/kernel/unwind.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -49,27 +50,23 @@ static DEFINE_SPINLOCK(unwind_lock); static struct unwind_table kernel_unwind_table __ro_after_init; static LIST_HEAD(unwind_tables); -static inline const struct unwind_table_entry * -find_unwind_entry_in_table(const struct unwind_table *table, unsigned long addr) +static int cmp_unwind_entry(const void *key, const void *elt) { - const struct unwind_table_entry *e = NULL; - unsigned long lo, hi, mid; + unsigned long addr = (unsigned long)key; + const struct unwind_table_entry *e = elt; - lo = 0; - hi = table->length - 1; - - while (lo <= hi) { - mid = (hi - lo) / 2 + lo; - e = &table->table[mid]; - if (addr < e->region_start) - hi = mid - 1; - else if (addr > e->region_end) - lo = mid + 1; - else - return e; - } + if (addr < e->region_start) + return -1; + if (addr > e->region_end) + return 1; + return 0; +} - return NULL; +static inline const struct unwind_table_entry * +find_unwind_entry_in_table(const struct unwind_table *table, unsigned long addr) +{ + return bsearch((void *)addr, table->table, table->length, + sizeof(*table->table), cmp_unwind_entry); } static const struct unwind_table_entry * From cb917b1e1c23f6f9b73802cd576b48bd606458c0 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Thu, 17 Sep 2026 20:04:14 -0700 Subject: [PATCH 0806/1417] parisc: remove unused header The last use of arch/parisc/include/asm/compat_ucontext.h was removed in commit d313d4e72df3 ("parisc: remove unused compat_rt_sigframe.h header") but it was left behind. Remove it. Signed-off-by: Ethan Nelson-Moore Signed-off-by: Helge Deller --- arch/parisc/include/asm/compat_ucontext.h | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 arch/parisc/include/asm/compat_ucontext.h diff --git a/arch/parisc/include/asm/compat_ucontext.h b/arch/parisc/include/asm/compat_ucontext.h deleted file mode 100644 index c606f1bc891df7..00000000000000 --- a/arch/parisc/include/asm/compat_ucontext.h +++ /dev/null @@ -1,18 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _ASM_PARISC_COMPAT_UCONTEXT_H -#define _ASM_PARISC_COMPAT_UCONTEXT_H - -#include - -/* 32-bit ucontext as seen from an 64-bit kernel */ -struct compat_ucontext { - compat_uint_t uc_flags; - compat_uptr_t uc_link; - compat_stack_t uc_stack; /* struct compat_sigaltstack (12 bytes)*/ - /* FIXME: Pad out to get uc_mcontext to start at an 8-byte aligned boundary */ - compat_uint_t pad[1]; - struct compat_sigcontext uc_mcontext; - compat_sigset_t uc_sigmask; /* mask last for extensibility */ -}; - -#endif /* !_ASM_PARISC_COMPAT_UCONTEXT_H */ From 289e99e7a263c6fd6a5d07d6d8f2156b70f3a9d5 Mon Sep 17 00:00:00 2001 From: Zhenghui Hao Date: Fri, 18 Sep 2026 13:46:49 +0800 Subject: [PATCH 0807/1417] parisc: parse early parameters in setup_arch() parisc is one of the few architectures that does not call parse_early_param() from setup_arch(). That was mostly harmless until commit d49004c5f0c1 ("arch, mm: consolidate initialization of nodes, zones and memory map") moved the consumer of several hugetlb command line parameters into mm_core_init_early(), which runs before the generic parse_early_param() call in start_kernel(). As a result hugepages=, hugepagesz=, default_hugepagesz=, hugetlb_cma= and hugetlb_free_vmemmap= are recorded after they have already been consumed and are silently dropped on parisc. Call parse_early_param() from setup_arch(), after the command line has been set up and the memory inventory has been taken. jump_label_init() must be called first because early parameter handlers may enable or disable static keys. Both functions are safe to call more than once: the generic calls in start_kernel() remain in place and turn into no-ops. Suggested-by: Mike Rapoport (Microsoft) Fixes: d49004c5f0c1 ("arch, mm: consolidate initialization of nodes, zones and memory map") Cc: Signed-off-by: Zhenghui Hao Tested-by: Helge Deller Signed-off-by: Helge Deller --- arch/parisc/kernel/setup.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/arch/parisc/kernel/setup.c b/arch/parisc/kernel/setup.c index d3e17a7a89016a..4d3015a411d6f2 100644 --- a/arch/parisc/kernel/setup.c +++ b/arch/parisc/kernel/setup.c @@ -132,6 +132,18 @@ void __init setup_arch(char **cmdline_p) parisc_cache_init(); paging_init(); + /* + * Parse early parameters before mm_core_init_early() runs. + * Several early_param() handlers only record data that is consumed + * from there - for example hugepages=, hugepagesz=, + * default_hugepagesz=, hugetlb_cma= and hugetlb_free_vmemmap= - so + * the generic parse_early_param() call in start_kernel() is too late + * for them. jump_label_init() must come first, since early param + * handlers may enable or disable static keys. + */ + jump_label_init(); + parse_early_param(); + #ifdef CONFIG_PA11 dma_ops_init(); #endif From 4dd1999783d7d12434006289338373e49492dc96 Mon Sep 17 00:00:00 2001 From: Alexey Klimov Date: Thu, 17 Sep 2026 10:16:42 +0200 Subject: [PATCH 0808/1417] soc: samsung: exynos-pmu: fix use-after-free of interrupt generator node The setup_cpuhp_and_cpuidle() parses the device tree node for the interrupt generation block via of_parse_phandle() and decrements its reference count using of_node_put() immediately after fetching the resource address. However, later the intr_gen_node pointer is passed into of_syscon_register_regmap(). Fix this by declaring intr_gen_node with __free() and removing of_node_put(). Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260513-exynos850-cpuhotplug-v4-0-54fec5f65362@linaro.org?part=3 Fixes: 78b72897a5c8 ("soc: samsung: exynos-pmu: Enable CPU Idle for gs101") Cc: stable@vger.kernel.org Signed-off-by: Alexey Klimov Link: https://patch.msgid.link/20260828-exynos-pmu-cpuhp-idle-fixes-v2-1-06bce6107bd6@linaro.org Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260917081641.72291-2-krzk@kernel.org Signed-off-by: Arnd Bergmann --- drivers/soc/samsung/exynos-pmu.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/soc/samsung/exynos-pmu.c b/drivers/soc/samsung/exynos-pmu.c index f5fcdde9750e27..efccdd63e40ea6 100644 --- a/drivers/soc/samsung/exynos-pmu.c +++ b/drivers/soc/samsung/exynos-pmu.c @@ -409,13 +409,12 @@ static struct notifier_block exynos_cpupm_reboot_nb = { static int setup_cpuhp_and_cpuidle(struct device *dev) { - struct device_node *intr_gen_node; + struct device_node *intr_gen_node __free(device_node) = + of_parse_phandle(dev->of_node, "google,pmu-intr-gen-syscon", 0); struct resource intrgen_res; void __iomem *virt_addr; int ret, cpu; - intr_gen_node = of_parse_phandle(dev->of_node, - "google,pmu-intr-gen-syscon", 0); if (!intr_gen_node) { /* * To maintain support for older DTs that didn't specify syscon @@ -431,8 +430,6 @@ static int setup_cpuhp_and_cpuidle(struct device *dev) * syscon provided regmap. */ ret = of_address_to_resource(intr_gen_node, 0, &intrgen_res); - of_node_put(intr_gen_node); - virt_addr = devm_ioremap(dev, intrgen_res.start, resource_size(&intrgen_res)); if (!virt_addr) From 7cb575b71ab98194d2e040bded3a7281e089c5ed Mon Sep 17 00:00:00 2001 From: Li Jun Date: Thu, 17 Sep 2026 09:37:10 +0800 Subject: [PATCH 0809/1417] watchdog: da9063: fix suspend/resume handling of HW_RUNNING watchdog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit da9063_wdt_suspend() and da9063_wdt_resume() only check watchdog_active(), when the watchdog is left running by the driver sets WDOG_HW_RUNNING in da9063_wdt_probe() but userspace never opens the device, so WDOG_ACTIVE remains cleared, the wdt_disable() will not be executed in da9063_wdt_suspend. In this case, the suspend callback is a no-op and the watchdog keeps counting during system suspend, leading to an unexpected system reset. Check WDOG_HW_RUNNING and wdd,can fix this issue. Fixes: a7ceca4398bc8 ("watchdog: da9063: optionally disable watchdog during suspend") Cc: stable@vger.kernel.org Signed-off-by: Li Jun Link: https://patch.msgid.link/20260917013710.2754679-1-lijun01@kylinos.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/da9063_wdt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/da9063_wdt.c b/drivers/watchdog/da9063_wdt.c index 92e1b78ff48107..3703110e82fcc5 100644 --- a/drivers/watchdog/da9063_wdt.c +++ b/drivers/watchdog/da9063_wdt.c @@ -271,7 +271,7 @@ static int da9063_wdt_suspend(struct device *dev) if (!da9063->use_sw_pm) return 0; - if (watchdog_active(wdd)) + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) return da9063_wdt_stop(wdd); return 0; @@ -285,7 +285,7 @@ static int da9063_wdt_resume(struct device *dev) if (!da9063->use_sw_pm) return 0; - if (watchdog_active(wdd)) + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) return da9063_wdt_start(wdd); return 0; From 8c7fdc0b4c64d6583fff3e8f7696237a16ff7c29 Mon Sep 17 00:00:00 2001 From: Joshua Hahn Date: Wed, 2 Sep 2026 12:45:20 -0700 Subject: [PATCH 0810/1417] selftests/cgroup: account for zswap shrinker writeback The test_no_invasive_cgroup_shrink selftest checks that when a cgroup has zswapped out more memory than memory.zswap.max, it does not trigger writeback for other cgroups. To do this, it compares the writeback count in a control cgroup and makes sure that it is 0, and then checks the writeback count in an aggressor cgroup who does expect to see writeback. However, when the zswap shrinker is enabled, the victim cgroup can see legitimate writebacks not triggered by the aggressor. In some Meta CI tests, we have seen this failure mode happen. Instead of checking that the victim cgroup has 0 writeback, compare the writeback values before and after the aggressor runs and check that the victim cgroup did not perform any additional writeback. Note that this can still lead to probabilistic failures if writebacks take longer than 5 seconds, but this should fix the systematic failure case and make "not ok test_no_invasive_cgroup_shrink" less likely. Link: https://lore.kernel.org/20260902194521.3652178-1-joshua.hahnjy@gmail.com Fixes: b5ba474f3f51 ("zswap: shrink zswap pool based on memory pressure") Signed-off-by: Joshua Hahn Signed-off-by: Andrew Morton Reported-by: Krush Chavan Suggested-by: Nhat Pham Cc: --- tools/testing/selftests/cgroup/test_zswap.c | 28 +++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/cgroup/test_zswap.c b/tools/testing/selftests/cgroup/test_zswap.c index 609c48f3852410..8df54b59513a8c 100644 --- a/tools/testing/selftests/cgroup/test_zswap.c +++ b/tools/testing/selftests/cgroup/test_zswap.c @@ -20,6 +20,7 @@ static int page_size; #define PATH_ZSWAP "/sys/module/zswap" #define PATH_ZSWAP_ENABLED "/sys/module/zswap/parameters/enabled" +#define PATH_ZSWAP_SHRINKER_ENABLED "/sys/module/zswap/parameters/shrinker_enabled" #define PATH_ZSWAP_STORED_PAGES "/sys/kernel/debug/zswap/stored_pages" static int read_int(const char *path, size_t *value) @@ -444,6 +445,16 @@ static int test_zswap_writeback_disabled(const char *root) return test_zswap_writeback(root, false); } +static bool zswap_shrinker_enabled(void) +{ + char value[2]; + + if (read_text(PATH_ZSWAP_SHRINKER_ENABLED, value, sizeof(value)) <= 0) + return 0; + + return value[0] == 'Y'; +} + /* * When trying to store a memcg page in zswap, if the memcg hits its memory * limit in zswap, writeback should affect only the zswapped pages of that @@ -453,6 +464,7 @@ static int test_no_invasive_cgroup_shrink(const char *root) { int ret = KSFT_FAIL; unsigned int off; + long zswpwb_before, zswpwb_after, zswpwb_target; size_t allocation_size = page_size * 1024; unsigned int nr_pages = allocation_size / page_size; char zswap_max_buf[32], mem_max_buf[32]; @@ -488,6 +500,14 @@ static int test_no_invasive_cgroup_shrink(const char *root) if (cg_read_key_long(zw_group, "memory.stat", "zswapped") < 1) goto out; + /* If the shrinker is enabled, try to let the writebacks finish first */ + if (zswap_shrinker_enabled()) + sleep(5); + + zswpwb_before = get_cg_wb_count(zw_group); + if (zswpwb_before < 0) + goto out; + /* Push wb_group memory into zswap with hard-to-compress data to trigger wb */ if (cg_enter_current(wb_group)) goto out; @@ -500,9 +520,13 @@ static int test_no_invasive_cgroup_shrink(const char *root) getrandom(&wb_allocation[off], page_size/4, 0); } - /* Verify that only zswapped memory from gwb_group has been written back */ - if (wait_for_writeback(wb_group, 5000) > 0 && get_cg_wb_count(zw_group) == 0) + /* Verify that only zswapped memory from wb_group has been written back */ + zswpwb_target = wait_for_writeback(wb_group, 5000); + zswpwb_after = get_cg_wb_count(zw_group); + + if (zswpwb_target > 0 && zswpwb_before == zswpwb_after) ret = KSFT_PASS; + out: cg_enter_current(root); if (zw_group) { From 8d50c2f37bcc766cd5ecde9bbb14c9c27c609f33 Mon Sep 17 00:00:00 2001 From: Haowen Bai Date: Thu, 3 Sep 2026 21:28:54 +0800 Subject: [PATCH 0811/1417] mailmap: update Haowen Bai's email address Map Haowen Bai's former Meizu and current Ugreen addresses to baihaowen88@gmail.com as the canonical public address. Link: https://lore.kernel.org/20260903132854.1930923-1-calvin.bai@ugreen.com Signed-off-by: Haowen Bai Signed-off-by: Andrew Morton --- .mailmap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index 90ff4831f59299..59518b814b4f7a 100644 --- a/.mailmap +++ b/.mailmap @@ -355,6 +355,8 @@ Hans Verkuil Hans Verkuil Hans Verkuil Hao Ge +Haowen Bai +Haowen Bai Harry Yoo <42.hyeyoo@gmail.com> Harry Yoo Heiko Carstens From 525c0edc032b3297d0c1056cf1fa20cf1f9e6184 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Fri, 4 Sep 2026 10:37:51 +0800 Subject: [PATCH 0812/1417] ocfs2: make ocfs2_calc_xattr_init() return void ocfs2_calc_xattr_init() used to read the default ACL off the parent inode itself, so it could return an error from ocfs2_xattr_get_nolock(). Commit bd7c05fb4a47 ("ocfs2: fix circular locking dependency in ocfs2_init_acl()") moved that lookup before the transaction starts and deleted the error path, but left the now vestigial 'int ret = 0' declaration and both 'return ret' statements behind, along with an unreachable error branch in ocfs2_mknod(). Drop the leftover variable and convert the return type to void, so the callee states that it always succeeds and the caller no longer carries a check that can never trigger. No functional change. Link: https://lore.kernel.org/20260904023751.3703334-1-joseph.qi@linux.alibaba.com Fixes: bd7c05fb4a47 ("ocfs2: fix circular locking dependency in ocfs2_init_acl()") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609040247.8B3lmoqX-lkp@intel.com/ Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao --- fs/ocfs2/namei.c | 9 ++------- fs/ocfs2/xattr.c | 13 +++++-------- fs/ocfs2/xattr.h | 8 ++++---- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index e9c7774ccf9153..58c6061ed983ce 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -336,13 +336,8 @@ static int ocfs2_mknod(struct mnt_idmap *idmap, goto leave; /* calculate meta data/clusters for setting security and acl xattr */ - status = ocfs2_calc_xattr_init(dir, mode, &si, &want_clusters, - &xattr_credits, &want_meta, - &acl_state); - if (status < 0) { - mlog_errno(status); - goto leave; - } + ocfs2_calc_xattr_init(dir, mode, &si, &want_clusters, &xattr_credits, + &want_meta, &acl_state); /* Reserve a cluster if creating an extent based directory. */ if (S_ISDIR(mode) && !ocfs2_supports_inline_data(osb)) { diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 35bcbb0ff607b2..bfafe059bedff0 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -635,12 +635,11 @@ int ocfs2_calc_security_init(struct inode *dir, return ret; } -int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, - struct ocfs2_security_xattr_info *si, - int *want_clusters, int *xattr_credits, - int *want_meta, struct ocfs2_acl_state *acl_state) +void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, + struct ocfs2_security_xattr_info *si, + int *want_clusters, int *xattr_credits, + int *want_meta, struct ocfs2_acl_state *acl_state) { - int ret = 0; struct ocfs2_super *osb = OCFS2_SB(dir->i_sb); int s_size = 0, a_size = 0, acl_len = 0, new_clusters; @@ -662,7 +661,7 @@ int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, } if (!(s_size + a_size)) - return ret; + return; /* * The max space of security xattr taken inline is @@ -728,8 +727,6 @@ int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, } } } - - return ret; } static int ocfs2_xattr_extend_allocation(struct inode *inode, diff --git a/fs/ocfs2/xattr.h b/fs/ocfs2/xattr.h index 5e18513277f18b..887cc1a18b1aff 100644 --- a/fs/ocfs2/xattr.h +++ b/fs/ocfs2/xattr.h @@ -59,10 +59,10 @@ int ocfs2_calc_security_init(struct inode *, int *, int *, struct ocfs2_alloc_context **); struct ocfs2_acl_state; -int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, - struct ocfs2_security_xattr_info *si, - int *want_clusters, int *xattr_credits, - int *want_meta, struct ocfs2_acl_state *acl_state); +void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, + struct ocfs2_security_xattr_info *si, + int *want_clusters, int *xattr_credits, + int *want_meta, struct ocfs2_acl_state *acl_state); /* * xattrs can live inside an inode, as part of an external xattr block, From f166586f74dd5d9cbadaabf86ef81c8ddf6cafa7 Mon Sep 17 00:00:00 2001 From: Nathan Gao Date: Thu, 3 Sep 2026 17:28:27 -0700 Subject: [PATCH 0813/1417] mm/damon/ops-common: use a page-aligned address in damon_ptep_mkold() __damon_va_prepare_access_check() picks a random byte address within the region and stores it in r->sampling_addr. damon_va_mkold() passes it into a page table walk, which hands it to damon_ptep_mkold() as the address of the page to sample: damon_va_mkold(mm, r->sampling_addr) damon_va_walk_page_range(mm, addr, addr + 1) damon_mkold_pmd_entry() damon_ptep_mkold(pte, vma, addr) ptep_test_and_clear_young(vma, addr, pte) mmu_notifier_clear_young(mm, addr, addr + PAGE_SIZE) For arm64, before commit 6f0e1142173a ("arm64: mm: support batch clearing of the young flag for large folios"), the contpte helper walked exactly CONT_PTES entries from the aligned-down page table pointer and used @addr only to pass down to each entry, so an unaligned value was harmless: ptep = contpte_align_down(ptep); addr = ALIGN_DOWN(addr, CONT_PTE_SIZE); for (i = 0; i < CONT_PTES; i++, ptep++, addr += PAGE_SIZE) Now the range to walk is derived from @addr instead: end = addr + nr * PAGE_SIZE, rounded up to CONT_PTE_SIZE. For a sample in the last page of a contpte block, the sub-page offset puts end just past the block boundary, so the round-up lands a whole block further and the walk clears PTE_AF in CONT_PTES entries beyond the sampled block. For the last block in a page table page, those entries are past the end of that page, so the walk writes into the page that follows. Triggered by the full 7.1/7.2 kernel selftest suite on arm64 (EC2 c/m6g.4xlarge). The kernel sometimes crashes at or shortly after the DAMON test. What the overrun does depends on the page that happens to follow the page table, so there is no single signature. If that page is read-only, the write faults in the sampling path itself: Unable to handle kernel write to read-only memory at virtual address ffff0003c5d2d000 FSC = 0x0f: level 3 permission fault CM = 0, WnR = 1, TnD = 0, TagAccess = 0 CPU: 10 UID: 0 PID: 3487 Comm: kdamond.2 pc : contpte_test_and_clear_young_ptes+0x70/0xc0 lr : damon_ptep_mkold+0x1e8/0x1f8 Call trace: contpte_test_and_clear_young_ptes+0x70/0xc0 (P) damon_mkold_pmd_entry+0x150/0x170 walk_pmd_range+0x110/0x2b0 walk_pud_range+0x10c/0x208 walk_pgd_range+0x134/0x258 __walk_page_range+0x98/0x1b0 walk_page_range_vma_unsafe+0x90/0x148 walk_page_range_vma+0x28/0x40 damon_va_walk_page_range+0x114/0x2b8 damon_va_prepare_access_checks+0xec/0x1a8 kdamond_fn+0x534/0x770 kthread+0x128/0x138 ret_from_fork+0x10/0x20 Otherwise the page is writable, the PTE_AF clearing succeeds silently and the damage only surfaces later, in whatever happened to own the page, so the backtrace is unrelated to DAMON and differs between runs. Pass a page-aligned address to the ptep_test_and_clear_young() call in damon_ptep_mkold(), which is the only place DAMON can reach contpte_test_and_clear_young_ptes() from. Nothing else sees the aligned address, and r->sampling_addr itself is left as is, so the sampling and region bookkeeping semantics are unchanged. Link: https://lore.kernel.org/20260904002829.116381-1-sj@kernel.org Fixes: 6f0e1142173a ("arm64: mm: support batch clearing of the young flag for large folios") Signed-off-by: Nathan Gao Signed-off-by: SJ Park Signed-off-by: Andrew Morton Reviewed-by: SJ Park Reviewed-by: Baolin Wang Cc: Baolin Wang Cc: David Hildenbrand (Arm) Cc: Ryan Roberts Cc: --- mm/damon/ops-common.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mm/damon/ops-common.c b/mm/damon/ops-common.c index fbda70d8ea4d05..8fc61d06d35859 100644 --- a/mm/damon/ops-common.c +++ b/mm/damon/ops-common.c @@ -61,7 +61,12 @@ void damon_ptep_mkold(pte_t *pte, struct vm_area_struct *vma, unsigned long addr * device aspects. */ if (likely(pte_present(pteval))) - young |= ptep_test_and_clear_young(vma, addr, pte); + /* + * Arch implementation of ptep_test_and_clear_young() may + * require aligned @addr + */ + young |= ptep_test_and_clear_young(vma, PAGE_ALIGN_DOWN(addr), + pte); young |= mmu_notifier_clear_young(vma->vm_mm, addr, addr + PAGE_SIZE); if (young) folio_set_young(folio); From 90179da203ba8b708c84a12a07cd44be0f346334 Mon Sep 17 00:00:00 2001 From: Liew Rui Yan Date: Tue, 8 Sep 2026 06:54:11 -0700 Subject: [PATCH 0814/1417] mm/damon/core: allow esz to be set to zero When the temporal quota goal tuner determines that the goal has been achieved (score >= 10000), it sets esz_bp to zero so that the esz becomes zero. However, damos_set_effective_quota() clamps the esz to min_region_sz when quota->ms is set. This is a minor issue, the main problem is that it doesn't match the description in the documentation, which state that if the goal has already been [over-]achieved, the quota will be set to zero. Fix this by set quota (esz) as minimum as possible. Link: https://lore.kernel.org/20260908135413.97570-1-sj@kernel.org Fixes: 8bbde987c2b8 ("mm/damon/core: disallow time-quota setting zero esz") Signed-off-by: SJ Park Signed-off-by: Liew Rui Yan Signed-off-by: Andrew Morton Reviewed-by: SJ Park Cc: # v7.1.x --- mm/damon/core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 644daf5a165606..2b294fb4664801 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3091,6 +3091,7 @@ static void damos_set_effective_quota(struct damon_ctx *ctx, struct damos *s) struct damos_quota *quota = &s->quota; unsigned long throughput; unsigned long esz = ULONG_MAX; + unsigned long esz_time; if (!quota->ms && list_empty("a->goals)) { quota->esz = quota->sz; @@ -3111,8 +3112,8 @@ static void damos_set_effective_quota(struct damon_ctx *ctx, struct damos *s) 1000000, quota->total_charged_ns); else throughput = PAGE_SIZE * 1024; - esz = min(throughput * quota->ms, esz); - esz = max(ctx->min_region_sz, esz); + esz_time = max(throughput * quota->ms, ctx->min_region_sz); + esz = min(esz_time, esz); } if (quota->sz && quota->sz < esz) From 39c0ceedd54557bdc1542de08d22b2ed33e534e4 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 7 Sep 2026 10:03:56 -0700 Subject: [PATCH 0815/1417] mm/damon/vaddr: avoid hw-driven pte updates during damon_hugetlb_mkold() damon_hugetlb_mkold() reads the page table entry into a local variable, unsets the accessed bit in the variable, and updates the page table entry with the updated variable value. If hardware updates the same page table entry in parallel, the hw updates could be lost. For example, hardware-updated dirty bits might be lost. Avoid the parallel updates by clearing the page table entry when reading it together, using huge_ptep_get_and_clear(). If a parallel write to the memory is made after the clearing, the hw will see the page table entry is cleared, trigger page fault and wait until it is handled. The page fault handling will wait for damon_hugetlb_mkold() due to the page table lock. Because hugetlbfs is an in-memory file system and hugetlb pages cannot be reclaimed, no critical issue is expected to my best knowledge. But definitely this is a nasty bug that should be fixed sooner rather than later. The issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260907170358.100168-1-sj@kernel.org Link: https://lore.kernel.org/20260830160545.98969-1-sj@kernel.org [1] Fixes: 49f4203aae06 ("mm/damon: add access checking for hugetlb pages") Signed-off-by: SJ Park Signed-off-by: Andrew Morton Cc: Baolin Wang Cc: # 5.17.x --- mm/damon/vaddr.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c index 0648400b2d65b4..04ee2a2c6a4d65 100644 --- a/mm/damon/vaddr.c +++ b/mm/damon/vaddr.c @@ -293,22 +293,29 @@ static int damon_mkold_pmd_entry(pmd_t *pmd, unsigned long addr, } #ifdef CONFIG_HUGETLB_PAGE +static bool damon_hugetlb_ptep_mkold(pte_t *pte, struct mm_struct *mm, + struct vm_area_struct *vma, unsigned long addr, pte_t *entry) +{ + unsigned long psize = huge_page_size(hstate_vma(vma)); + + if (!pte_young(*entry)) + return false; + *entry = huge_ptep_get_and_clear(mm, addr, pte, psize); + *entry = pte_mkold(*entry); + set_huge_pte_at(mm, addr, pte, *entry, psize); + return true; +} + static void damon_hugetlb_mkold(pte_t *pte, struct mm_struct *mm, struct vm_area_struct *vma, unsigned long addr) { bool referenced = false; pte_t entry = huge_ptep_get(mm, addr, pte); struct folio *folio = pfn_folio(pte_pfn(entry)); - unsigned long psize = huge_page_size(hstate_vma(vma)); folio_get(folio); - if (pte_young(entry)) { - referenced = true; - entry = pte_mkold(entry); - set_huge_pte_at(mm, addr, pte, entry, psize); - } - + referenced = damon_hugetlb_ptep_mkold(pte, mm, vma, addr, &entry); if (mmu_notifier_clear_young(mm, addr, addr + huge_page_size(hstate_vma(vma)))) referenced = true; From b3723b596b548c837a766aae3553c14a7b15af2b Mon Sep 17 00:00:00 2001 From: Liew Rui Yan Date: Tue, 8 Sep 2026 06:47:38 -0700 Subject: [PATCH 0816/1417] mm/damon/core: fix unconditionally skip last region Once quota set, the charge_{target,addr}_from unconditionally skips and resets at the last region of the tracked target, so the last region can be skipped even when it has not been processed. Example: 1. Target has 2 regions: R1 (0-100 bytes) and R2 (100-200 bytes). 2. Quota is configured to process only 100 bytes per window. 3. Window 1: Processes R1 (0-100). Quota is full. charge_{target, addr}_from is saved at (Target, 100). 4. Window 2: The loop reaches R2. Because R2 is damon_last_region(t), the old code unconditionally returns true, skipping R2 entirely and resetting the charge_{target,addr}_from. Result: R2 is permanently skipped even though it has never been processed. However, it is important to note that this is a very minor issue. This is because it is triggered only when the previous window saved/kept charge_{target,addr}_from, and in the next window, all regions except the last region were skipped by damos_skip_charged_region(). Fix this by only resetting the charge_{target,addr}_from when last region is reached, only skipping when it is applied or cannot split. Link: https://lore.kernel.org/20260908134739.96919-1-sj@kernel.org Fixes: 50585192bc2e ("mm/damon/schemes: skip already charged targets and regions") Signed-off-by: Liew Rui Yan Reviewed-by: SJ Park Signed-off-by: SJ Park Signed-off-by: Andrew Morton Cc: # v5.16.x --- mm/damon/core.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 2b294fb4664801..06a253df8d4baa 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -2342,36 +2342,39 @@ static bool damos_skip_charged_region(struct damon_target *t, { struct damos_quota *quota = &s->quota; unsigned long sz_to_skip; + bool skip = false; /* Skip previously charged regions */ if (quota->charge_target_from) { if (t != quota->charge_target_from) return true; - if (r == damon_last_region(t)) { - quota->charge_target_from = NULL; - quota->charge_addr_from = 0; - return true; - } if (quota->charge_addr_from && - r->ar.end <= quota->charge_addr_from) - return true; + r->ar.end <= quota->charge_addr_from) { + skip = true; + goto out; + } if (quota->charge_addr_from && r->ar.start < quota->charge_addr_from) { sz_to_skip = ALIGN_DOWN(quota->charge_addr_from - r->ar.start, min_region_sz); if (!sz_to_skip) { - if (damon_sz_region(r) <= min_region_sz) - return true; + if (damon_sz_region(r) <= min_region_sz) { + skip = true; + goto out; + } sz_to_skip = min_region_sz; } damon_split_region_at(t, r, sz_to_skip); - return true; + skip = true; } + } +out: + if (r == damon_last_region(t)) { quota->charge_target_from = NULL; quota->charge_addr_from = 0; } - return false; + return skip; } static void damos_update_stat(struct damos *s, From 9bdad082d44bdcf93716973dcba6be77e8a06e7b Mon Sep 17 00:00:00 2001 From: Jaewook You Date: Mon, 14 Sep 2026 22:23:52 +0900 Subject: [PATCH 0817/1417] mm/hugetlb: preserve mremap address delta when skipping page tables move_hugetlb_page_tables() optimizes mremap() by advancing to the last entry in the page table when the source page table does not exist, either initially or after unsharing a PMD table. The common loop increment then steps to the first entry in the next page table. However, the code advances both the source and destination addresses to the last entries in their respective page tables, which is wrong. The destination address must be advanced only by the same amount as the source address. If the source and destination offsets within their page tables differ, the destination address can be advanced too far, causing follow-up issues. Fix this by advancing the destination address by the source advance distance. With a reproducer, we were able to trigger a kernel panic on x86-64. With this fix in place, we can no longer reproduce the issue. Link: https://lore.kernel.org/20260914132352.472-1-jaewook376@gmail.com Fixes: e95a9851787b ("hugetlb: skip to end of PT page mapping when pte not present") Fixes: 4ddb4d91b82f ("hugetlb: do not update address in huge_pmd_unshare") Signed-off-by: Jaewook You Signed-off-by: Andrew Morton Acked-by: David Hildenbrand (Arm) Cc: Johan Hovold Cc: Muchun Song Cc: Oscar Salvador Cc: Assisted-by: LLM --- mm/hugetlb.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index d28972cd33f582..cea25773a6c953 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -5170,18 +5170,21 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma, hugetlb_vma_lock_write(vma); i_mmap_lock_write(mapping); for (; old_addr < old_end; old_addr += sz, new_addr += sz) { + const unsigned long offset_to_last_entry = + (old_addr | last_addr_mask) - old_addr; + src_pte = hugetlb_walk(vma, old_addr, sz); if (!src_pte) { - old_addr |= last_addr_mask; - new_addr |= last_addr_mask; + old_addr += offset_to_last_entry; + new_addr += offset_to_last_entry; continue; } if (huge_pte_none(huge_ptep_get(mm, old_addr, src_pte))) continue; if (huge_pmd_unshare(&tlb, vma, old_addr, src_pte)) { - old_addr |= last_addr_mask; - new_addr |= last_addr_mask; + old_addr += offset_to_last_entry; + new_addr += offset_to_last_entry; continue; } From b6ac0b3f6013c168f22cad97e79967accacb08e1 Mon Sep 17 00:00:00 2001 From: Jinjiang Tu Date: Tue, 8 Sep 2026 20:29:24 +0800 Subject: [PATCH 0818/1417] mm/rmap: fix missing barrier between anon_vma init and vma->anon_vma publish On arm64 server, we find that a task trying to grab the anon_vma lock triggers hungtask. INFO: task main:2354726 blocked for more than 120 seconds. Tainted: G E 5.10.0-0021.aarch64 #1 "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. task:main state:D stack: 0 pid:2354726 ppid:2350673 flags:0x00000a01 Call trace: __switch_to+0x7c/0xbc __schedule+0x3b4/0x8a0 schedule+0x50/0xe0 rwsem_down_write_slowpath+0x3cc/0x6cc down_write+0x60/0x260 __anon_vma_prepare+0x6c/0x210 do_anonymous_page+0x258/0x660 handle_pte_fault+0x188/0x214 __handle_mm_fault+0x1b0/0x380 handle_mm_fault+0xf4/0x284 do_page_fault+0x19c/0x494 do_translation_fault+0xcc/0xf8 do_mem_abort+0x48/0xac el0_da+0x44/0x80 el0_sync_handler+0x88/0xb4 el0_sync+0x160/0x180 After analyzing the vmcore, we found the anon_vma->root->rwsem.count is -1. There is another anon_vma whose anon_vma->root->rwsem.count is 1, the anon_vma->root->rwsem.owner shows the lock is held, but the stack of the task shows the task doesn't hold the anon_vma lock. After adding more debugging info, we found __anon_vma_prepare() reuses anon_vma and triggers the UAF of anon_vma->root due to missing memory barrier, leading to locking and unlocking two different anon_vma->root, thus leading to an anon_vma will never be unlocked, and another anon_vma couldn't be locked anymore. This race requires two adjacent VMAs that are not merged but are anon_vma-compatible (e.g., they differ in VMA_ACCESS_FLAGS that can be changed by mprotect()). Two threads fault on each VMA concurrently, both calling __anon_vma_prepare() with only mmap_lock held for reading. THREAD A THREAD B __anon_vma_prepare __anon_vma_prepare find_mergeable_anon_vma() -> NULL anon_vma = anon_vma_alloc(); anon_vma->root = anon_vma; // the two stores may be reordered vma->anon_vma = anon_vma; // finds A's anon_vma anon_vma = find_mergeable_anon_vma(vma); anon_vma_lock_write(anon_vma); // may still see the old root down_write(&anon_vma->root->rwsem); anon_vma_unlock_write(anon_vma); // see the new root, never unlock old up_write(&anon_vma->root->rwsem); thread A triggers page fault and calls __anon_vma_prepare() to prepare anon_vma for the faulting vma. __anon_vma_prepare() allocates and initializes a new anon_vma, and then publishes it to the vma with a plain store. anon_vma_prepare() only requires the mmap_lock to be held for reading, so two threads can fault on adjacent VMAs at the same time. While thread A publishes a new anon_vma, thread B could find the anon_vma via find_mergeable_anon_vma() and then locks anon_vma->root->rwsem. The store to anon_vma->root in anon_vma_alloc() and the store to vma->anon_vma can be reordered. The anon_vma_lock_write() and spin_lock() only provide acquire semantics, which do not prevent prior stores from being reordered after them. The release semantics of the corresponding spin_unlock() and anon_vma_unlock_write() come too late, the store to vma->anon_vma is already published before they take effect. As a result, thread B can observe the following order: vma->anon_vma = anon_vma; anon_vma->root = anon_vma; The anon_vma slab is SLAB_TYPESAFE_BY_RCU, so a newly allocated anon_vma may reuse memory from a previously freed one. The constructor (anon_vma_ctor) does not reset anon_vma->root, and __put_anon_vma() doesn't clear it either, so the old root value persists until anon_vma_alloc() overwrites it. If that store isn't visible, thread B reads a root that points to the old anon_vma and locks it. As a result, thread B can call anon_vma_lock_write() with the old root, and call anon_vma_unlock_write() with the new root, leading to an anon_vma will never be unlocked, and another anon_vma couldn't be locked anymore (its count is dropped from 0 to -1 due to wrong unlock). To fix it, change the plain store `vma->anon_vma = anon_vma` to store release, so that the fields of anon_vma are visible before anon_vma is published to vma->anon_vma. At read side, the load of anon_vma and anon_vma->root have address dependency. According to Documentation/memory-barriers.txt and some investigations, only Alpha needs address-dependency barriers and it has been handled by READ_ONCE() in reusable_anon_vma(). We reproduced this issue in v5.10 with KSM enabled. The kernel doesn't merge commit cf7e7a3503df ("mm: prevent KSM from breaking VMA merging for new VMAs"), so there are many adjacent VMAs that aren't merged but are compatible for anon_vma. Without this fix, our production environment could reproduce this issue about 2-5 times each month. After adding a smp_mb() before anon_vma_lock_write(anon_vma) in __anon_vma_prepare(), which is different to this patch, this issue hasn't been reproduced for one month. Link: https://lore.kernel.org/20260908122924.554373-1-tujinjiang@huawei.com Fixes: 5c341ee1dfc8 ("mm: track the root (oldest) anon_vma") Signed-off-by: Jinjiang Tu Signed-off-by: Andrew Morton Reviewed-by: Lance Yang Reviewed-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Acked-by: Vlastimil Babka (SUSE) Cc: Minchan Kim Cc: Harry Yoo Cc: Hiroyouki Kamezawa Cc: Jann Horn Cc: Jinjiang Tu Cc: Kefeng Wang Cc: Larry Woodman Cc: Liam R. Howlett Cc: Nanyong Sun Cc: Rik van Riel Cc: --- mm/rmap.c | 6 +++++- mm/vma.c | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mm/rmap.c b/mm/rmap.c index d1819fd6993800..f3b21aaa34ee98 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -209,7 +209,11 @@ int __anon_vma_prepare(struct vm_area_struct *vma) /* page_table_lock to protect against threads */ spin_lock(&mm->page_table_lock); if (likely(!vma->anon_vma)) { - vma->anon_vma = anon_vma; + /* + * Make anon_vma fields visible before anon_vma is published. + * Paired with an address dependency in reusable_anon_vma(). + */ + smp_store_release(&vma->anon_vma, anon_vma); anon_vma_chain_assign(vma, avc, anon_vma); anon_rmap_tree_insert(avc, anon_vma); anon_vma->num_active_vmas++; diff --git a/mm/vma.c b/mm/vma.c index f29abb30956bb9..9f0a0acf694aa7 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -2094,6 +2094,13 @@ static int anon_vma_compatible(struct vm_area_struct *a, struct vm_area_struct * * acceptable for merging, so we can do all of this optimistically. But * we do that READ_ONCE() to make sure that we never re-load the pointer. * + * The READ_ONCE() establishes an address dependency between anon_vma and + * any access to its fields, which pairs with the assignment to + * vma->anon_vma performed with release semantics in __anon_vma_prepare(). + * + * This is especially important as anon_vma's are SLAB_TYPESAFE_BY_RCU so + * accessing an uninitialised anon_vma's fields may result in a UAF. + * * IOW: that the "list_is_singular()" test on the anon_vma_chain only * matters for the 'stable anon_vma' case (ie the thing we want to avoid * is to return an anon_vma that is "complex" due to having gone through @@ -2108,6 +2115,7 @@ static struct anon_vma *reusable_anon_vma(struct vm_area_struct *old, struct vm_area_struct *b) { if (anon_vma_compatible(a, b)) { + /* Paired with a memory barrier in __anon_vma_prepare(). */ struct anon_vma *anon_vma = READ_ONCE(old->anon_vma); if (anon_vma && list_is_singular(&old->anon_vma_chain)) From 44fcc0bfb0874a95cec2c1672f14d426f86dc68f Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Tue, 8 Sep 2026 09:42:12 +0800 Subject: [PATCH 0819/1417] MAINTAINERS: add Baoquan and Baolin as MGLRU reviewers Baoquan and I have been contributing MGLRU patches and helping review MGLRU related patches for some time. We will continue to follow MGLRU changes, so we'd like to be CC'd on MGLRU related patches. Link: https://lore.kernel.org/06e20ef4f603a4ffeafcdbf623ce2936281457bf.1788831480.git.baolin.wang@linux.alibaba.com Signed-off-by: Baolin Wang Signed-off-by: Andrew Morton Acked-by: Barry Song Acked-by: Qi Zheng Acked-by: Baoquan He Acked-by: Kairui Song Cc: Axel Rasmussen Cc: Shakeel Butt Cc: Wei Xu Cc: Yuanchu Xie --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index c9f866debdf0b1..a6c0e4bcbc78c3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17240,6 +17240,8 @@ R: Barry Song R: Axel Rasmussen R: Yuanchu Xie R: Wei Xu +R: Baoquan He +R: Baolin Wang L: linux-mm@kvack.org S: Maintained W: http://www.linux-mm.org From eb64948249781bda35de04feab5a0acc36aa9051 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Thu, 10 Sep 2026 07:28:45 -0700 Subject: [PATCH 0820/1417] mm/damon/core: reset invalid quota->charge_target_from DAMOS can suddenly stop working if a target process that the quota is just fully charged on is terminated. Fix by catching and processing the corner case. When DAMOS quota is fully charged, the target and the region to continue applying the action in the next round is saved in damos_quota->charge_{target,addr}_from. In the next round, DAMOS iterates targets and regions from the beginning. It skips applying the action to the regions until it visits and skips the saved target/region. Virtual address space targets become invalid if the process is terminated. Trying to apply the scheme to invalid target is just a waste of time. Hence commit 6e4930e33329 ("mm/damon/core: fix wasteful CPU calls by skipping non-existent targets") made the logic to skip invalid targets. However, it does skip before the charged target/region skipping/updating. Let's suppose the user runs DAMOS for multiple virtual address spaces with a quota. The quota exceeded in the middle of a virtual address space. And the process of the address space is terminated. Then the charge_target_from points to the invalid target. The pointer update logic is skipped for the invalid target, so the charge_target_from is never updated. DAMOS action to every target/region is skipped. From the user's perspective, it would look like suddenly DAMOS has stopped working. No critical leak or crash can happen. The user could reinstall the scheme. But this makes use of DAMOS under certain setups quite unreliable. When the invalid target is found, further check the corner case and reset the pointer. This issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260910142846.172957-1-sj@kernel.org Link: https://lore.kernel.org/20260830064708.40CA61F000E9@smtp.kernel.org [1] Fixes: 6e4930e33329 ("mm/damon/core: fix wasteful CPU calls by skipping non-existent targets") Signed-off-by: SJ Park Signed-off-by: Andrew Morton Cc: Enze Li Cc: # 7.0.x --- mm/damon/core.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 06a253df8d4baa..1764620903f2d3 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3245,8 +3245,15 @@ static void kdamond_apply_schemes(struct damon_ctx *c) max_region_sz = damon_region_sz_limit(c); mutex_lock(&c->walk_control_lock); damon_for_each_target(t, c) { - if (c->ops.target_valid && c->ops.target_valid(t) == false) + if (c->ops.target_valid && c->ops.target_valid(t) == false) { + damon_for_each_scheme(s, c) { + if (s->quota.charge_target_from != t) + continue; + s->quota.charge_target_from = NULL; + s->quota.charge_addr_from = 0; + } continue; + } damos_apply_target(c, t, max_region_sz); } From 407a5d205179a4ab186571b0e16ec42725dc77bc Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 9 Sep 2026 18:01:07 +0000 Subject: [PATCH 0821/1417] writeback: report a Tasks-RCU quiescent state per cgwb drain pass cleanup_offline_cgwbs_workfn() drains a dying cgwb by calling cleanup_offline_cgwb() until it returns false, with a cond_resched() between passes. On a CONFIG_PREEMPTION kernel that cond_resched() does nothing: _cond_resched() is a plain "return 0", and under PREEMPT_DYNAMIC the full and lazy modes disable it. Since commit 7dadeaa6e851 ("sched: Further restrict the preemption modes") those are the only two models on the architectures with PREEMPT_LAZY support, arm64 and x86 among them, so the drain loop never reports a Tasks-RCU quiescent state. A worker draining a cgwb with millions of attached inodes runs for minutes. On a 6.18 arm64 host in lazy mode the cgwb worker drained one dying cgroup's writeback domain for over 11 minutes. A BPF program unlink (bpf_trampoline_unlink_prog -> bpf_trampoline_update -> unregister_ftrace_direct -> ftrace_shutdown -> synchronize_rcu_tasks()) waited on that grace period while holding the trampoline mutex, 42 tasks queued behind it in D state, and the hung task detector fired at 614 s and panicked the host. Any BPF or ftrace detach during a long drain inherits the drain's length. Fix this by calling cond_resched_tasks_rcu_qs() so we do not stall out anybody who calls sycnrhonize_rcu_tasks(). We put this in a do { } while loop because if we have many small cgroups cleanup_offline_cgwb() will return false and we will never call cond_resched_tasks_rcu_qs(), creating the same problem. Link: https://lore.kernel.org/20260909-cgwb-tasks-rcu-qs-v1-1-967a7754771f@toxicpanda.com Fixes: c22d70a162d3 ("writeback, cgroup: release dying cgwbs by switching attached inodes") Signed-off-by: Josef Bacik Signed-off-by: Andrew Morton Link: https://lore.kernel.org/bpf/9d444098-7c03-4163-af12-bd0a79a51443@paulmck-laptop/ Assisted-by: LLM Acked-by: Tejun Heo Reviewed-by: Roman Gushchin Reviewed-by: Jan Kara Acked-by: Lorenzo Stoakes (ARM) Cc: David Hildenbrand Cc: Dennis Zhou Cc: Liam R. Howlett Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Mike Rapoport Cc: "Paul E . McKenney" Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: --- mm/backing-dev.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mm/backing-dev.c b/mm/backing-dev.c index cecbcf9060a653..18e999053bae0b 100644 --- a/mm/backing-dev.c +++ b/mm/backing-dev.c @@ -910,8 +910,9 @@ static void cleanup_offline_cgwbs_workfn(struct work_struct *work) continue; spin_unlock_irq(&cgwb_lock); - while (cleanup_offline_cgwb(wb)) - cond_resched(); + do { + cond_resched_tasks_rcu_qs(); + } while (cleanup_offline_cgwb(wb)); spin_lock_irq(&cgwb_lock); wb_put(wb); From 692dd08e03f4a5523650e055f033f846bee43a0c Mon Sep 17 00:00:00 2001 From: Xu Xin Date: Mon, 7 Sep 2026 14:53:42 +0800 Subject: [PATCH 0822/1417] MAINTAINERS: update Xu Xin's email Now I'm moving to the @linux.dev account, so map my old email addresses and update them to my new address. Link: https://lore.kernel.org/20260907145342529uOGtNYWTAzrSCFnghfgCb@zte.com.cn Signed-off-by: Xu Xin Signed-off-by: Andrew Morton --- .mailmap | 2 ++ MAINTAINERS | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 59518b814b4f7a..2447811f773ae9 100644 --- a/.mailmap +++ b/.mailmap @@ -980,6 +980,8 @@ Wesley Cheng Will Deacon Wolfram Sang Wolfram Sang +Xu Xin +Xu Xin xu xin Yakir Yang Yanteng Si Ying Huang diff --git a/MAINTAINERS b/MAINTAINERS index a6c0e4bcbc78c3..7d12dfac0149b4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17192,7 +17192,7 @@ F: tools/testing/selftests/mm/gup_test.c MEMORY MANAGEMENT - KSM (Kernel Samepage Merging) M: Andrew Morton M: David Hildenbrand -R: Xu Xin +R: Xu Xin R: Chengming Zhou L: linux-mm@kvack.org S: Maintained From 8633243a494bb78ed92cdd0339bb67ed3d08c020 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:14:04 -0700 Subject: [PATCH 0823/1417] ASoC: rockchip: rk3399_gru_sound: Allocate dai_link with ARRAY_SIZE() In preparation for making the devm_kmalloc family of allocators type aware, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (Before, the allocator would always return "void *", which can be implicitly cast to any pointer type.) This is allocating a copy of rockchip_dais, which is an array of struct snd_soc_dai_link, but the size was taken from the whole array, which would make the allocation type a pointer to the array rather than the "struct snd_soc_dai_link *" being assigned. Allocate ARRAY_SIZE-many entries instead. The resulting allocation size is the same. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: sound/soc/rockchip/rk3399_gru_sound.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Link: https://patch.msgid.link/20260917211403.i.984-kees@kernel.org Signed-off-by: Mark Brown --- sound/soc/rockchip/rk3399_gru_sound.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/rockchip/rk3399_gru_sound.c b/sound/soc/rockchip/rk3399_gru_sound.c index b80acb221d244b..04e7bed8275d01 100644 --- a/sound/soc/rockchip/rk3399_gru_sound.c +++ b/sound/soc/rockchip/rk3399_gru_sound.c @@ -505,8 +505,8 @@ static int rockchip_sound_of_parse_dais(struct device *dev, int i, index; int num_routes; - card->dai_link = devm_kzalloc(dev, sizeof(rockchip_dais), - GFP_KERNEL); + card->dai_link = devm_kcalloc(dev, ARRAY_SIZE(rockchip_dais), + sizeof(*card->dai_link), GFP_KERNEL); if (!card->dai_link) return -ENOMEM; From 13b4ab33642b145a74ee55dc64110900e077f6be Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:10:43 -0700 Subject: [PATCH 0824/1417] ASoC: codecs: wm8904: Add const to drc_texts allocation type In preparation for converting the kmalloc family of allocators to the type-aware kmalloc_obj family, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (The kmalloc family returns "void *", which can be implicitly cast to any pointer type.) The assigned type is "const char **", but the converted allocation type would be "char **", which is the same type without the const qualifier. As there is no general way to safely add const qualifiers, take the size from the assignment target instead. No change in allocation size results. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: sound/soc/codecs/wm8904.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260917211042.i.810-kees@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/wm8904.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index 153ce2c1a0f602..96d2e54a2c6a57 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -2204,7 +2204,7 @@ static void wm8904_handle_pdata(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8904->drc_texts = kmalloc_array(pdata->num_drc_cfgs, - sizeof(char *), + sizeof(*wm8904->drc_texts), GFP_KERNEL); if (!wm8904->drc_texts) return; From d0d2b9576f447ca842132e94f56c488f9fd85d33 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:11:03 -0700 Subject: [PATCH 0825/1417] ASoC: codecs: wm8958: Add const to enum texts allocation types In preparation for converting the kmalloc family of allocators to the type-aware kmalloc_obj family, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (The kmalloc family returns "void *", which can be implicitly cast to any pointer type.) The assigned types of mbc_texts, vss_texts, vss_hpf_texts, and enh_eq_texts are "const char **", but the converted allocation types would be "char **", which is the same type without the const qualifier. As there is no general way to safely add const qualifiers, take the sizes from the assignment targets instead. No change in allocation size results. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: sound/soc/codecs/wm8958-dsp2.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Reviewed-by: Cezary Rojewski Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260917211102.i.029-kees@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/wm8958-dsp2.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c index f75a6dc9d2bbf2..b26c36ca824997 100644 --- a/sound/soc/codecs/wm8958-dsp2.c +++ b/sound/soc/codecs/wm8958-dsp2.c @@ -928,7 +928,7 @@ void wm8958_dsp2_init(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8994->mbc_texts = kmalloc_array(pdata->num_mbc_cfgs, - sizeof(char *), + sizeof(*wm8994->mbc_texts), GFP_KERNEL); if (!wm8994->mbc_texts) return; @@ -954,7 +954,7 @@ void wm8958_dsp2_init(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8994->vss_texts = kmalloc_array(pdata->num_vss_cfgs, - sizeof(char *), + sizeof(*wm8994->vss_texts), GFP_KERNEL); if (!wm8994->vss_texts) return; @@ -981,7 +981,7 @@ void wm8958_dsp2_init(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8994->vss_hpf_texts = kmalloc_array(pdata->num_vss_hpf_cfgs, - sizeof(char *), + sizeof(*wm8994->vss_hpf_texts), GFP_KERNEL); if (!wm8994->vss_hpf_texts) return; @@ -1009,7 +1009,7 @@ void wm8958_dsp2_init(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8994->enh_eq_texts = kmalloc_array(pdata->num_enh_eq_cfgs, - sizeof(char *), + sizeof(*wm8994->enh_eq_texts), GFP_KERNEL); if (!wm8994->enh_eq_texts) return; From cbcb58ad7a194149c019b37d01ffaa8a65055edc Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:11:18 -0700 Subject: [PATCH 0826/1417] ASoC: SOF: topology: Add const to tplg_files allocation type In preparation for converting the kmalloc family of allocators to the type-aware kmalloc_obj family, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (The kmalloc family returns "void *", which can be implicitly cast to any pointer type.) The assigned type is "const char **", but the converted allocation type would be "char **", which is the same type without the const qualifier. As there is no general way to safely add const qualifiers, take the size from the assignment target instead. No change in allocation size results. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: sound/soc/sof/topology.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Link: https://patch.msgid.link/20260917211117.i.991-kees@kernel.org Signed-off-by: Mark Brown --- sound/soc/sof/topology.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 8338133899aba3..145bad4721188a 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -2517,7 +2517,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) int i; const char **tplg_files __free(kfree) = - kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); + kcalloc(scomp->card->num_links, sizeof(*tplg_files), + GFP_KERNEL); if (!tplg_files) return -ENOMEM; From d0814ef719032e34ff1ca5fb1be44c15bcfd54a0 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:15:30 +0200 Subject: [PATCH 0827/1417] ASoC: codecs: rt5514: Fix the pm_ops constant name To improve readability. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260918111530.3589280-1-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5514.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c index 8dad8ba0fe0ff5..753143e2d11ffe 100644 --- a/sound/soc/codecs/rt5514.c +++ b/sound/soc/codecs/rt5514.c @@ -1332,7 +1332,7 @@ static int rt5514_i2c_probe(struct i2c_client *i2c) rt5514_dai, ARRAY_SIZE(rt5514_dai)); } -static const struct dev_pm_ops rt5514_i2_pm_ops = { +static const struct dev_pm_ops rt5514_i2c_pm_ops = { SYSTEM_SLEEP_PM_OPS(NULL, rt5514_i2c_resume) }; @@ -1341,7 +1341,7 @@ static struct i2c_driver rt5514_i2c_driver = { .name = "rt5514", .acpi_match_table = ACPI_PTR(rt5514_acpi_match), .of_match_table = of_match_ptr(rt5514_of_match), - .pm = pm_ptr(&rt5514_i2_pm_ops), + .pm = pm_ptr(&rt5514_i2c_pm_ops), }, .probe = rt5514_i2c_probe, .id_table = rt5514_i2c_id, From b0be01f133aa36d2fd12d71c916cb8a47826b4ed Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Sun, 30 Aug 2026 11:02:01 +0800 Subject: [PATCH 0828/1417] pinctrl: qcom: nord: Split QUP1 SE2/SE3 into lane-pair functions QUP1 SE2 and SE3 pack all four of their lanes pair-wise onto only two pins each: lanes 0/1 (I2C SDA/SCL) at mux value 2 and lanes 2/3 (UART TX/RX) at mux value 1, on gpio127/gpio128 and gpio129/gpio130 respectively. Both mux values were named "qup1_se2" (respectively "qup1_se3"), so the two distinct lane pairs became indistinguishable. msm_pinmux_set_mux() stops at the first entry matching the requested function, which means mux value 1 was always selected and the I2C lanes could never be muxed out. In practice i2c9 and i2c10 got the UART lanes and did not work, while uart9 and uart10 happened to be muxed correctly. Give each lane pair its own function, following the _01/_23 naming already used for the same hardware arrangement by the shikra, eliza, hawi and maili TLMM drivers. Both functions still cover the full pin pair, so a single pinctrl state per protocol remains sufficient. Drop gpio129/gpio130 from the SE2 group list, since those pins belong to SE3 and were never reachable through the SE2 function. Also rename QUP1 SE2/SE3 functions in the binding doc accordingly. While at it, add missing "gpio", "qup3_se0_mira" and "qup3_se0_mirb" to the binding function enum to get the list complete. Fixes: c24dd0826f06 ("pinctrl: qcom: add the TLMM driver for the Nord platforms") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Shawn Guo Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260830030201.135637-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Linus Walleij --- .../bindings/pinctrl/qcom,nord-tlmm.yaml | 7 ++-- drivers/pinctrl/qcom/pinctrl-nord.c | 34 +++++++++++++------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml b/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml index 4bb511719f3130..56758a85ead8d0 100644 --- a/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml +++ b/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml @@ -67,7 +67,7 @@ $defs: Specify the alternative function to be configured for the specified pins. - enum: [ aoss_cti, atest_char, atest_usb20, atest_usb21, + enum: [ gpio, aoss_cti, atest_char, atest_usb20, atest_usb21, aud_intfc0_clk, aud_intfc0_data, aud_intfc0_ws, aud_intfc10_clk, aud_intfc10_data, aud_intfc10_ws, aud_intfc1_clk, aud_intfc1_data, aud_intfc1_ws, @@ -98,9 +98,10 @@ $defs: pcie3_clk_req_n, phase_flag, pll_bist_sync, pll_clk_aux, prng_rosc0, prng_rosc1, pwrbrk_i_n, qdss, qdss_cti, qspi, qup0_se0, qup0_se1, qup0_se2, qup0_se3, qup0_se4, qup0_se5, - qup1_se0, qup1_se1, qup1_se3, qup1_se2, qup1_se4, qup1_se5, + qup1_se0, qup1_se1, qup1_se2_01, qup1_se2_23, qup1_se3_01, + qup1_se3_23, qup1_se4, qup1_se5, qup1_se6, qup2_se0, qup2_se1, qup2_se2, qup2_se3, qup2_se4, - qup2_se5, qup2_se6, + qup2_se5, qup2_se6, qup3_se0_mira, qup3_se0_mirb, sailss_ospi, sdc4_clk, sdc4_cmd, sdc4_data, smb_alert, smb_alert_n, smb_clk, smb_dat, tb_trig_sdc4, tmess_prng0, tmess_prng1, tsc_timer, tsense_pwm, usb0_hs, diff --git a/drivers/pinctrl/qcom/pinctrl-nord.c b/drivers/pinctrl/qcom/pinctrl-nord.c index 7c21306e77ff72..7f37f8e819bab2 100644 --- a/drivers/pinctrl/qcom/pinctrl-nord.c +++ b/drivers/pinctrl/qcom/pinctrl-nord.c @@ -570,8 +570,10 @@ enum nord_functions { msm_mux_qup0_se5, msm_mux_qup1_se0, msm_mux_qup1_se1, - msm_mux_qup1_se2, - msm_mux_qup1_se3, + msm_mux_qup1_se2_01, + msm_mux_qup1_se2_23, + msm_mux_qup1_se3_01, + msm_mux_qup1_se3_23, msm_mux_qup1_se4, msm_mux_qup1_se5, msm_mux_qup1_se6, @@ -1152,11 +1154,19 @@ static const char *const qup1_se1_groups[] = { "gpio123", "gpio124", "gpio125", "gpio126", }; -static const char *const qup1_se2_groups[] = { - "gpio127", "gpio128", "gpio129", "gpio130", +static const char *const qup1_se2_01_groups[] = { + "gpio127", "gpio128", }; -static const char *const qup1_se3_groups[] = { +static const char *const qup1_se2_23_groups[] = { + "gpio127", "gpio128", +}; + +static const char *const qup1_se3_01_groups[] = { + "gpio129", "gpio130", +}; + +static const char *const qup1_se3_23_groups[] = { "gpio129", "gpio130", }; @@ -1428,8 +1438,10 @@ static const struct pinfunction nord_functions[] = { MSM_PIN_FUNCTION(qup0_se5), MSM_PIN_FUNCTION(qup1_se0), MSM_PIN_FUNCTION(qup1_se1), - MSM_PIN_FUNCTION(qup1_se2), - MSM_PIN_FUNCTION(qup1_se3), + MSM_PIN_FUNCTION(qup1_se2_01), + MSM_PIN_FUNCTION(qup1_se2_23), + MSM_PIN_FUNCTION(qup1_se3_01), + MSM_PIN_FUNCTION(qup1_se3_23), MSM_PIN_FUNCTION(qup1_se4), MSM_PIN_FUNCTION(qup1_se5), MSM_PIN_FUNCTION(qup1_se6), @@ -1633,13 +1645,13 @@ static const struct msm_pingroup nord_groups[] = { _, _, _, _, _, _, _), [126] = PINGROUP(126, qup1_se1, qup1_se0, ccu_i2c_scl, mdp1_vsync_out, _, atest_usb20, ddr_pxi, _, _, _, _), - [127] = PINGROUP(127, qup1_se2, qup1_se2, _, atest_usb21, ddr_pxi, + [127] = PINGROUP(127, qup1_se2_23, qup1_se2_01, _, atest_usb21, ddr_pxi, _, _, _, _, _, _), - [128] = PINGROUP(128, qup1_se2, qup1_se2, _, atest_usb20, ddr_pxi, + [128] = PINGROUP(128, qup1_se2_23, qup1_se2_01, _, atest_usb20, ddr_pxi, _, _, _, _, _, _), - [129] = PINGROUP(129, qup1_se3, qup1_se3, ccu_i2c_sda, mdp1_vsync_out, + [129] = PINGROUP(129, qup1_se3_23, qup1_se3_01, ccu_i2c_sda, mdp1_vsync_out, _, atest_usb21, ddr_pxi, _, _, _, _), - [130] = PINGROUP(130, qup1_se3, qup1_se3, ccu_i2c_scl, mdp1_vsync_out, + [130] = PINGROUP(130, qup1_se3_23, qup1_se3_01, ccu_i2c_scl, mdp1_vsync_out, _, atest_usb20, ddr_pxi, _, _, _, _), [131] = PINGROUP(131, qup1_se4, qup1_se6, ccu_i2c_sda, mdp1_vsync_out, _, atest_usb21, ddr_pxi, _, _, _, _), From 447dcff90557748a5ac384aaf5d70b2c7e3b961d Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:43:40 +0800 Subject: [PATCH 0829/1417] pinctrl: qcom: ipq5210: Publish the OF module alias The IPQ5210 TLMM platform driver can be a module and matches through ipq5210_tlmm_of_match. This table is not published for OF modalias matching. Publish the existing table, preserving arch_initcall ordering and the shared MSM pinctrl probe. Fixes: a549fe22376f ("pinctrl: qcom: Introduce IPQ5210 TLMM driver") Signed-off-by: hpp.iscas Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260905134340.67477-1-hppiscas@163.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Linus Walleij --- drivers/pinctrl/qcom/pinctrl-ipq5210.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pinctrl/qcom/pinctrl-ipq5210.c b/drivers/pinctrl/qcom/pinctrl-ipq5210.c index 827a4ad07a6b00..d5b4eb3e734352 100644 --- a/drivers/pinctrl/qcom/pinctrl-ipq5210.c +++ b/drivers/pinctrl/qcom/pinctrl-ipq5210.c @@ -867,6 +867,7 @@ static const struct of_device_id ipq5210_tlmm_of_match[] = { { .compatible = "qcom,ipq5210-tlmm", }, { }, }; +MODULE_DEVICE_TABLE(of, ipq5210_tlmm_of_match); static int ipq5210_tlmm_probe(struct platform_device *pdev) { From 5ea495bccfd746b063bc6702064f167467717b41 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 16:58:51 +0000 Subject: [PATCH 0830/1417] ASoC: audio-graph-card2: Fix use-after-free in audio_graph2_link_c2c() of_graph_get_next_port() consumes the reference of its prev argument. port0 is only borrowed from the caller's iterator, so the call drops a reference the function does not own, and port0 is dereferenced again afterwards. Pass an extra reference, as graph_count_c2c() does, so the reference the callee consumes is the one taken here. Fixes: 5f281c3e82b1 ("ASoC: audio-graph-card2: use __free(device_node) for device node") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Acked-by: Kuninori Morimoto Link: https://patch.msgid.link/20260917165851.2163548-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/generic/audio-graph-card2.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c index e11bc8135cb0ae..503170578cff81 100644 --- a/sound/soc/generic/audio-graph-card2.c +++ b/sound/soc/generic/audio-graph-card2.c @@ -969,7 +969,8 @@ int audio_graph2_link_c2c(struct simple_util_priv *priv, struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); struct device_node *port0 = lnk; struct device_node *ports __free(device_node) = port_to_ports(port0); - struct device_node *port1 __free(device_node) = of_graph_get_next_port(ports, port0); + struct device_node *port1 __free(device_node) = + of_graph_get_next_port(ports, of_node_get(port0)); u32 val = 0; int ret = -EINVAL; From b2c0e5bf750e33b0e1dcdfbb2df8103a06c7caab Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 16:51:30 +0000 Subject: [PATCH 0831/1417] ASoC: codecs: wm8994: Fix runtime PM leak in wm8994_mic_detect() wm8994_mic_detect() resumes the component before configuring the MICBIAS pins, but the default case of the micbias switch returns -EINVAL right after taking that reference, skipping the pm_runtime_put() on the success path. This leaks a runtime PM reference on every call with an invalid micbias. Release the reference before returning -EINVAL. Fixes: f5a2cda4f1db ("ASoC: wm8994: Ensure the device is resumed in wm89xx_mic_detect functions") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260917165130.2163278-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/wm8994.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 96d3aae3686272..b8881b260a4bea 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -3552,6 +3552,7 @@ int wm8994_mic_detect(struct snd_soc_component *component, struct snd_soc_jack * break; default: dev_warn(component->dev, "Invalid MICBIAS %d\n", micbias); + pm_runtime_put(component->dev); return -EINVAL; } From cc47a544d4f7e4d5603af2e0d5c18fbdac5fe257 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 18 Sep 2026 00:50:59 +0200 Subject: [PATCH 0832/1417] ASoC: dt-bindings: sound: Prepare Ux500 audio graph links Audio graph card drivers parse TDM slot data and continuous clock selection from endpoint nodes. The audio graph endpoint schema describes slot count and width directly but does not cover sparse slot masks or continuous clocking. Reference the common TDM slot schema so TX and RX masks are documented, and add the continuous-clock flag. Replace the incomplete Ux500 MSP text binding with a DT schema that describes the clocks, reset, DMA channels and power supply consumed by the driver. Define the MSP as a zero-cell sound DAI provider and allow it to expose an audio graph port. Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260918-ux500-simple-sound-v2-1-88d29416e3c3@kernel.org Signed-off-by: Mark Brown --- .../bindings/sound/audio-graph-port.yaml | 10 +- .../sound/stericsson,ux500-msp-i2s.yaml | 96 +++++++++++++++++++ .../devicetree/bindings/sound/ux500-msp.txt | 42 -------- 3 files changed, 100 insertions(+), 48 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/stericsson,ux500-msp-i2s.yaml delete mode 100644 Documentation/devicetree/bindings/sound/ux500-msp.txt diff --git a/Documentation/devicetree/bindings/sound/audio-graph-port.yaml b/Documentation/devicetree/bindings/sound/audio-graph-port.yaml index d1cbfc5edd3ac3..565d723d039851 100644 --- a/Documentation/devicetree/bindings/sound/audio-graph-port.yaml +++ b/Documentation/devicetree/bindings/sound/audio-graph-port.yaml @@ -39,6 +39,7 @@ definitions: allOf: - $ref: /schemas/graph.yaml#/$defs/endpoint-base - $ref: /schemas/sound/dai-params.yaml# + - $ref: /schemas/sound/tdm-slot.yaml# properties: mclk-fs: $ref: simple-card.yaml#/definitions/mclk-fs @@ -48,6 +49,9 @@ definitions: bitclock-inversion: description: dai-link uses bit clock inversion $ref: /schemas/types.yaml#/definitions/flag + continuous-clock: + description: dai-link bit clock runs continuously rather than gated + $ref: /schemas/types.yaml#/definitions/flag frame-master: description: Indicates dai-link frame master. oneOf: @@ -82,12 +86,6 @@ definitions: - msb - lsb - dai-tdm-slot-num: - description: Number of slots in use. - $ref: /schemas/types.yaml#/definitions/uint32 - dai-tdm-slot-width: - description: Width in bits for each slot. - $ref: /schemas/types.yaml#/definitions/uint32 dai-tdm-slot-width-map: description: Mapping of sample widths to slot widths. For hardware that cannot support a fixed slot width or a slot width always diff --git a/Documentation/devicetree/bindings/sound/stericsson,ux500-msp-i2s.yaml b/Documentation/devicetree/bindings/sound/stericsson,ux500-msp-i2s.yaml new file mode 100644 index 00000000000000..bdfcd5376cfe25 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/stericsson,ux500-msp-i2s.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/stericsson,ux500-msp-i2s.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ST-Ericsson Ux500 Multichannel Serial Port + +maintainers: + - Linus Walleij + +description: + The Ux500 Multichannel Serial Port (MSP) is a synchronous serial audio + interface supporting I2S and PCM/TDM protocols. Data is transferred to and + from memory using the Ux500 DMA controller. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: stericsson,ux500-msp-i2s + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + v-ape-supply: + description: Analog power engine supply + + dmas: + minItems: 1 + maxItems: 2 + + dma-names: + minItems: 1 + maxItems: 2 + items: + enum: [ rx, tx ] + + clocks: + items: + - description: MSP functional clock + - description: MSP APB bus clock + + clock-names: + items: + - const: msp + - const: apb_pclk + + resets: + maxItems: 1 + + '#sound-dai-cells': + const: 0 + + port: + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + - v-ape-supply + - dmas + - dma-names + - clocks + - clock-names + - resets + - '#sound-dai-cells' + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + msp@80123000 { + compatible = "stericsson,ux500-msp-i2s"; + reg = <0x80123000 0x1000>; + interrupts = ; + v-ape-supply = <&db8500_vape_reg>; + dmas = <&dma 31 0 0x12>, <&dma 31 0 0x10>; + dma-names = "rx", "tx"; + clocks = <&prcc_kclk 1 3>, <&prcc_pclk 1 3>; + clock-names = "msp", "apb_pclk"; + resets = <&prcc_reset DB8500_PRCC_1 DB8500_PRCC_1_RESET_MSP0>; + #sound-dai-cells = <0>; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/ux500-msp.txt b/Documentation/devicetree/bindings/sound/ux500-msp.txt deleted file mode 100644 index 7dd1b96160f597..00000000000000 --- a/Documentation/devicetree/bindings/sound/ux500-msp.txt +++ /dev/null @@ -1,42 +0,0 @@ -* ux500 MSP (CPU-side Digital Audio Interface) - -Required properties: - - compatible :"stericsson,ux500-msp-i2s" - - reg : Physical base address and length of the device's registers. - -Optional properties: - - interrupts : The interrupt output from the device. - - -supply : Phandle to the regulator supply - -Example: - - sound { - compatible = "stericsson,snd-soc-mop500"; - - stericsson,platform-pcm-dma = <&pcm>; - stericsson,cpu-dai = <&msp1 &msp3>; - stericsson,audio-codec = <&codec>; - }; - - pcm: ux500-pcm { - compatible = "stericsson,ux500-pcm"; - }; - - msp1: msp@80124000 { - compatible = "stericsson,ux500-msp-i2s"; - reg = <0x80124000 0x1000>; - interrupts = <0 62 0x4>; - v-ape-supply = <&db8500_vape_reg>; - }; - - msp3: msp@80125000 { - compatible = "stericsson,ux500-msp-i2s"; - reg = <0x80125000 0x1000>; - interrupts = <0 62 0x4>; - v-ape-supply = <&db8500_vape_reg>; - }; - - codec: ab8500-codec { - compatible = "stericsson,ab8500-codec"; - stericsson,earpeice-cmv = <950>; /* Units in mV. */ - }; From 661b5aa8109337509e12525ee53a30f90a7a5b01 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 18 Sep 2026 00:51:01 +0200 Subject: [PATCH 0833/1417] ASoC: dt-bindings: ux500: Remove MOP500 sound card binding The Ux500 device trees now use audio-graph-card2 and no longer contain the MOP500-specific compatible or properties. Remove the obsolete text binding rather than retaining an unused legacy ABI. Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260918-ux500-simple-sound-v2-3-88d29416e3c3@kernel.org Signed-off-by: Mark Brown --- .../bindings/sound/ux500-mop500.txt | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/ux500-mop500.txt diff --git a/Documentation/devicetree/bindings/sound/ux500-mop500.txt b/Documentation/devicetree/bindings/sound/ux500-mop500.txt deleted file mode 100644 index 48e071c96b465e..00000000000000 --- a/Documentation/devicetree/bindings/sound/ux500-mop500.txt +++ /dev/null @@ -1,39 +0,0 @@ -* MOP500 Audio Machine Driver - -This node is responsible for linking together all ux500 Audio Driver components. - -Required properties: - - compatible : "stericsson,snd-soc-mop500" - -Non-standard properties: - - stericsson,cpu-dai : Phandle to the CPU-side DAI - - stericsson,audio-codec : Phandle to the Audio CODEC - - stericsson,card-name : Over-ride default card name - -Example: - - sound { - compatible = "stericsson,snd-soc-mop500"; - - stericsson,cpu-dai = <&msp1 &msp3>; - stericsson,audio-codec = <&codec>; - }; - - msp1: msp@80124000 { - compatible = "stericsson,ux500-msp-i2s"; - reg = <0x80124000 0x1000>; - interrupts = <0 62 0x4>; - v-ape-supply = <&db8500_vape_reg>; - }; - - msp3: msp@80125000 { - compatible = "stericsson,ux500-msp-i2s"; - reg = <0x80125000 0x1000>; - interrupts = <0 62 0x4>; - v-ape-supply = <&db8500_vape_reg>; - }; - - codec: ab8500-codec { - compatible = "stericsson,ab8500-codec"; - stericsson,earpeice-cmv = <950>; /* Units in mV. */ - }; From 0d46182a1be7ef12f85433d91315d4d7091dd8c8 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 18 Sep 2026 00:51:02 +0200 Subject: [PATCH 0834/1417] ASoC: ab8500: Skip digital microphone support on AB8505 The AB8505 does not have the digital microphone interface, its six input pins or the VDMIC regulator found in AB8500. The shared codec driver nevertheless creates the entire AB8500 DMIC DAPM graph. When an AB8505 sound card binds, the regulator widget requests the nonexistent V-DMIC supply and falls back to a dummy regulator. Split the digital microphone widgets and routes from the common codec graph and add them only when the parent MFD is an actual AB8500. This also removes the DMIC4-only AD_OUT4 path from AB8505. Fixes: 679d7abdc754 ("ASoC: codecs: Add AB8500 codec-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260918-ux500-simple-sound-v2-4-88d29416e3c3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 144 ++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 62 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index fc204889c95b8b..3e3ff9bfe00d1b 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -299,7 +299,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_REGULATOR_SUPPLY("V-AUD", 0, 0), SND_SOC_DAPM_REGULATOR_SUPPLY("V-AMIC1", 0, 0), SND_SOC_DAPM_REGULATOR_SUPPLY("V-AMIC2", 0, 0), - SND_SOC_DAPM_REGULATOR_SUPPLY("V-DMIC", 0, 0), /* Power */ SND_SOC_DAPM_SUPPLY("Audio Power", @@ -324,7 +323,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_AIF_OUT("AD_OUT1", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AD_OUT2", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AD_OUT3", NULL, 0, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_AIF_OUT("AD_OUT4", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AD_OUT57", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AD_OUT68", NULL, 0, SND_SOC_NOPM, 0, 0), @@ -584,36 +582,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("AD5768 Enable", AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD5768, 0, NULL, 0), - /* Digital Microphone path */ - - SND_SOC_DAPM_INPUT("DMic 1"), - SND_SOC_DAPM_INPUT("DMic 2"), - SND_SOC_DAPM_INPUT("DMic 3"), - SND_SOC_DAPM_INPUT("DMic 4"), - SND_SOC_DAPM_INPUT("DMic 5"), - SND_SOC_DAPM_INPUT("DMic 6"), - - SND_SOC_DAPM_MIXER("DMIC1", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC1, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC2", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC2, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC3", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC3, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC4", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC4, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC5", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC5, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC6", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC6, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("AD4 Channel Volume", - SND_SOC_NOPM, 0, 0, - NULL, 0), /* Acoustical Noise Cancellation path */ SND_SOC_DAPM_MUX("ANC Source", @@ -648,6 +616,40 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { NULL, 0), }; +static const struct snd_soc_dapm_widget ab8500_dmic_dapm_widgets[] = { + SND_SOC_DAPM_REGULATOR_SUPPLY("V-DMIC", 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT4", NULL, 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_INPUT("DMic 1"), + SND_SOC_DAPM_INPUT("DMic 2"), + SND_SOC_DAPM_INPUT("DMic 3"), + SND_SOC_DAPM_INPUT("DMic 4"), + SND_SOC_DAPM_INPUT("DMic 5"), + SND_SOC_DAPM_INPUT("DMic 6"), + + SND_SOC_DAPM_MIXER("DMIC1", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC1, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC2", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC2, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC3", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC3, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC4", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC4, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC5", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC5, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC6", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC6, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("AD4 Channel Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), +}; + /* * DAPM-routes */ @@ -853,35 +855,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"AD_OUT68", NULL, "Main Supply"}, {"AD_OUT68", NULL, "AD6 Channel Volume"}, - /* Digital Microphone path */ - - {"DMic 1", NULL, "V-DMIC"}, - {"DMic 2", NULL, "V-DMIC"}, - {"DMic 3", NULL, "V-DMIC"}, - {"DMic 4", NULL, "V-DMIC"}, - {"DMic 5", NULL, "V-DMIC"}, - {"DMic 6", NULL, "V-DMIC"}, - - {"DMIC1", NULL, "DMic 1"}, - {"DMIC2", NULL, "DMic 2"}, - {"DMIC3", NULL, "DMic 3"}, - {"DMIC4", NULL, "DMic 4"}, - {"DMIC5", NULL, "DMic 5"}, - {"DMIC6", NULL, "DMic 6"}, - - {"AD1 Source Select", "DMic 1", "DMIC1"}, - {"AD2 Source Select", "DMic 2", "DMIC2"}, - {"AD3 Source Select", "DMic 3", "DMIC3"}, - {"AD5 Source Select", "DMic 5", "DMIC5"}, - {"AD6 Source Select", "DMic 6", "DMIC6"}, - - {"AD4 Channel Volume", NULL, "DMIC4"}, - {"AD4 Channel Volume", NULL, "AD34 Enable"}, - - {"ab8500_0c", NULL, "AD_OUT4"}, - {"AD_OUT4", NULL, "Main Supply"}, - {"AD_OUT4", NULL, "AD4 Channel Volume"}, - /* LineIn Bypass path */ {"LINL to HSL Volume", NULL, "LINL Enable"}, @@ -911,7 +884,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"Sidetone Left Source", "Headset Left", "DA_IN1"}, {"Sidetone Right Source", "LineIn Right", "AD2 Channel Volume"}, {"Sidetone Right Source", "Mic 1", "AD3 Channel Volume"}, - {"Sidetone Right Source", "DMic 4", "AD4 Channel Volume"}, {"Sidetone Right Source", "Headset Right", "DA_IN2"}, {"STFIR1 Control", NULL, "Sidetone Left Source"}, @@ -924,6 +896,37 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"DA2 Enable", NULL, "STFIR2 Volume"}, }; +static const struct snd_soc_dapm_route ab8500_dmic_dapm_routes[] = { + {"DMic 1", NULL, "V-DMIC"}, + {"DMic 2", NULL, "V-DMIC"}, + {"DMic 3", NULL, "V-DMIC"}, + {"DMic 4", NULL, "V-DMIC"}, + {"DMic 5", NULL, "V-DMIC"}, + {"DMic 6", NULL, "V-DMIC"}, + + {"DMIC1", NULL, "DMic 1"}, + {"DMIC2", NULL, "DMic 2"}, + {"DMIC3", NULL, "DMic 3"}, + {"DMIC4", NULL, "DMic 4"}, + {"DMIC5", NULL, "DMic 5"}, + {"DMIC6", NULL, "DMic 6"}, + + {"AD1 Source Select", "DMic 1", "DMIC1"}, + {"AD2 Source Select", "DMic 2", "DMIC2"}, + {"AD3 Source Select", "DMic 3", "DMIC3"}, + {"AD5 Source Select", "DMic 5", "DMIC5"}, + {"AD6 Source Select", "DMic 6", "DMIC6"}, + + {"AD4 Channel Volume", NULL, "DMIC4"}, + {"AD4 Channel Volume", NULL, "AD34 Enable"}, + + {"ab8500_0c", NULL, "AD_OUT4"}, + {"AD_OUT4", NULL, "Main Supply"}, + {"AD_OUT4", NULL, "AD4 Channel Volume"}, + + {"Sidetone Right Source", "DMic 4", "AD4 Channel Volume"}, +}; + static const struct snd_soc_dapm_route ab8500_dapm_routes_mic1a_vamicx[] = { {"MIC1A V-AMICx Enable", NULL, "V-AMIC1"}, {"MIC1A V-AMICx Enable", NULL, "V-AMIC2"}, @@ -2056,13 +2059,30 @@ static int ab8500_codec_probe(struct snd_soc_component *component) { struct device *dev = component->dev; struct device_node *np = dev->of_node; + struct ab8500 *ab8500 = dev_get_drvdata(dev->parent); struct ab8500_codec_platform_data codec_pdata; + struct snd_soc_dapm_context *dapm; int status; dev_dbg(dev, "%s: Enter.\n", __func__); ab8500_codec_of_probe(dev, np, &codec_pdata); + if (is_ab8500(ab8500)) { + dapm = snd_soc_component_to_dapm(component); + status = snd_soc_dapm_new_controls(dapm, + ab8500_dmic_dapm_widgets, + ARRAY_SIZE(ab8500_dmic_dapm_widgets)); + if (status) + return status; + + status = snd_soc_dapm_add_routes(dapm, + ab8500_dmic_dapm_routes, + ARRAY_SIZE(ab8500_dmic_dapm_routes)); + if (status) + return status; + } + status = ab8500_audio_init_audioblock(component); if (status < 0) { dev_err(dev, "%s: failed to init audio-block (%d)!\n", From f25d142baa23a94a16e3da20715f69f1166826ea Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 18 Sep 2026 00:51:03 +0200 Subject: [PATCH 0835/1417] ASoC: ux500: Remove the MOP500 machine driver The Ux500 boards now use audio-graph-card2, so remove the machine driver and its obsolete binding. The generic card obtains the DAI format, clock roles, TDM layout and board routing from the device tree graphs. Make the codec, MSP and DMA platform components part of the Ux500 ASoC selection. These providers were previously selected indirectly by the machine driver. Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260918-ux500-simple-sound-v2-5-88d29416e3c3@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/Kconfig | 23 +- sound/soc/ux500/Makefile | 3 - sound/soc/ux500/mop500.c | 167 ------------ sound/soc/ux500/mop500_ab8500.c | 437 -------------------------------- sound/soc/ux500/mop500_ab8500.h | 17 -- 5 files changed, 7 insertions(+), 640 deletions(-) delete mode 100644 sound/soc/ux500/mop500.c delete mode 100644 sound/soc/ux500/mop500_ab8500.c delete mode 100644 sound/soc/ux500/mop500_ab8500.h diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig index 11305806035000..f3c7c216267c03 100644 --- a/sound/soc/ux500/Kconfig +++ b/sound/soc/ux500/Kconfig @@ -6,28 +6,19 @@ menuconfig SND_SOC_UX500 tristate "Ux500" depends on SND_SOC depends on MFD_DB8500_PRCMU + depends on AB8500_CORE && AB8500_GPADC + select SND_SOC_AB8500_CODEC + select SND_SOC_UX500_PLAT_MSP_I2S + select SND_SOC_UX500_PLAT_DMA help - Say Y if you want to enable ASoC-support for - any of the Ux500 platforms (e.g. U8500). + Say Y if you want to enable ASoC support for Ux500 platforms + using the AB8500 or AB8505 codec. config SND_SOC_UX500_PLAT_MSP_I2S tristate depends on SND_SOC_UX500 config SND_SOC_UX500_PLAT_DMA - tristate "Platform - DB8500 (DMA)" + tristate depends on SND_SOC_UX500 select SND_SOC_GENERIC_DMAENGINE_PCM - help - Say Y if you want to enable the Ux500 platform-driver. - -config SND_SOC_UX500_MACH_MOP500 - tristate "Machine - MOP500 (Ux500 + AB8500)" - depends on AB8500_CORE && AB8500_GPADC && SND_SOC_UX500 - select SND_SOC_AB8500_CODEC - select SND_SOC_UX500_PLAT_MSP_I2S - select SND_SOC_UX500_PLAT_DMA - help - Select this to enable the MOP500 machine-driver. - This will enable platform-drivers for: Ux500 - This will enable codec-drivers for: AB8500 diff --git a/sound/soc/ux500/Makefile b/sound/soc/ux500/Makefile index a63787d9d66435..83e7aeff70f86d 100644 --- a/sound/soc/ux500/Makefile +++ b/sound/soc/ux500/Makefile @@ -6,6 +6,3 @@ obj-$(CONFIG_SND_SOC_UX500_PLAT_MSP_I2S) += snd-soc-ux500-plat-msp-i2s.o snd-soc-ux500-plat-dma-y := ux500_pcm.o obj-$(CONFIG_SND_SOC_UX500_PLAT_DMA) += snd-soc-ux500-plat-dma.o - -snd-soc-ux500-mach-mop500-y := mop500.o mop500_ab8500.o -obj-$(CONFIG_SND_SOC_UX500_MACH_MOP500) += snd-soc-ux500-mach-mop500.o diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c deleted file mode 100644 index 6d196b4b880270..00000000000000 --- a/sound/soc/ux500/mop500.c +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Author: Ola Lilja (ola.o.lilja@stericsson.com) - * for ST-Ericsson. - */ - -#include - -#include -#include -#include -#include - -#include -#include - -#include "ux500_pcm.h" -#include "ux500_msp_dai.h" - -#include "mop500_ab8500.h" - -/* Define the whole MOP500 soundcard, linking platform to the codec-drivers */ -SND_SOC_DAILINK_DEFS(link1, - DAILINK_COMP_ARRAY(COMP_CPU("ux500-msp-i2s.1")), - DAILINK_COMP_ARRAY(COMP_CODEC("ab8500-codec.0", "ab8500-codec-dai.0")), - DAILINK_COMP_ARRAY(COMP_PLATFORM("ux500-msp-i2s.1"))); - -SND_SOC_DAILINK_DEFS(link2, - DAILINK_COMP_ARRAY(COMP_CPU("ux500-msp-i2s.3")), - DAILINK_COMP_ARRAY(COMP_CODEC("ab8500-codec.0", "ab8500-codec-dai.1")), - DAILINK_COMP_ARRAY(COMP_PLATFORM("ux500-msp-i2s.3"))); - -static struct snd_soc_dai_link mop500_dai_links[] = { - { - .name = "ab8500_0", - .stream_name = "ab8500_0", - .init = mop500_ab8500_machine_init, - .ops = mop500_ab8500_ops, - SND_SOC_DAILINK_REG(link1), - }, - { - .name = "ab8500_1", - .stream_name = "ab8500_1", - .init = NULL, - .ops = mop500_ab8500_ops, - SND_SOC_DAILINK_REG(link2), - }, -}; - -static struct snd_soc_card mop500_card = { - .name = "MOP500-card", - .owner = THIS_MODULE, - .probe = NULL, - .dai_link = mop500_dai_links, - .num_links = ARRAY_SIZE(mop500_dai_links), -}; - -static void mop500_of_node_put(void) -{ - int i; - - for (i = 0; i < 2; i++) - of_node_put(mop500_dai_links[i].cpus->of_node); - - /* Both links use the same codec, which is refcounted only once */ - of_node_put(mop500_dai_links[0].codecs->of_node); -} - -static int mop500_of_probe(struct snd_soc_card *card) -{ - struct device *dev = card->dev; - struct device_node *codec_np, *msp_np[2]; - struct device_node *np = dev->of_node; - int i; - - msp_np[0] = of_parse_phandle(np, "stericsson,cpu-dai", 0); - msp_np[1] = of_parse_phandle(np, "stericsson,cpu-dai", 1); - codec_np = of_parse_phandle(np, "stericsson,audio-codec", 0); - - if (!(msp_np[0] && msp_np[1] && codec_np)) { - dev_err(dev, "Phandle missing or invalid\n"); - for (i = 0; i < 2; i++) - of_node_put(msp_np[i]); - of_node_put(codec_np); - return -EINVAL; - } - - for (i = 0; i < 2; i++) { - mop500_dai_links[i].cpus->of_node = msp_np[i]; - mop500_dai_links[i].cpus->dai_name = NULL; - mop500_dai_links[i].platforms->of_node = msp_np[i]; - mop500_dai_links[i].platforms->name = NULL; - mop500_dai_links[i].codecs->of_node = codec_np; - mop500_dai_links[i].codecs->name = NULL; - } - - snd_soc_of_parse_card_name(card, "stericsson,card-name"); - - return 0; -} - -static int mop500_probe(struct platform_device *pdev) -{ - int ret; - - dev_dbg(&pdev->dev, "%s: Enter.\n", __func__); - - mop500_card.dev = &pdev->dev; - - ret = mop500_of_probe(&mop500_card); - if (ret) - return ret; - - dev_dbg(&pdev->dev, "%s: Card %s: Set platform drvdata.\n", - __func__, mop500_card.name); - - snd_soc_card_set_drvdata(&mop500_card, NULL); - - dev_dbg(&pdev->dev, "%s: Card %s: num_links = %d\n", - __func__, mop500_card.name, mop500_card.num_links); - dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: name = %s\n", - __func__, mop500_card.name, mop500_card.dai_link[0].name); - dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: stream_name = %s\n", - __func__, mop500_card.name, - mop500_card.dai_link[0].stream_name); - - ret = snd_soc_register_card(&mop500_card); - if (ret) - dev_err(&pdev->dev, - "Error: snd_soc_register_card failed (%d)!\n", ret); - - return ret; -} - -static void mop500_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - - pr_debug("%s: Enter.\n", __func__); - - snd_soc_unregister_card(card); - mop500_ab8500_remove(card); - mop500_of_node_put(); -} - -static const struct of_device_id snd_soc_mop500_match[] = { - { .compatible = "stericsson,snd-soc-mop500", }, - {}, -}; -MODULE_DEVICE_TABLE(of, snd_soc_mop500_match); - -static struct platform_driver snd_soc_mop500_driver = { - .driver = { - .name = "snd-soc-mop500", - .of_match_table = snd_soc_mop500_match, - }, - .probe = mop500_probe, - .remove = mop500_remove, -}; - -module_platform_driver(snd_soc_mop500_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("ASoC MOP500 board driver"); -MODULE_AUTHOR("Ola Lilja"); diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c deleted file mode 100644 index feb683c55d1134..00000000000000 --- a/sound/soc/ux500/mop500_ab8500.c +++ /dev/null @@ -1,437 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Author: Ola Lilja , - * Kristoffer Karlsson - * for ST-Ericsson. - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "ux500_pcm.h" -#include "ux500_msp_dai.h" -#include "mop500_ab8500.h" -#include "../codecs/ab8500-codec.h" - -#define TX_SLOT_MONO 0x0008 -#define TX_SLOT_STEREO 0x000a -#define RX_SLOT_MONO 0x0001 -#define RX_SLOT_STEREO 0x0003 -#define TX_SLOT_8CH 0x00FF -#define RX_SLOT_8CH 0x00FF - -#define DEF_TX_SLOTS TX_SLOT_STEREO -#define DEF_RX_SLOTS RX_SLOT_MONO - -#define DRIVERMODE_NORMAL 0 -#define DRIVERMODE_CODEC_ONLY 1 - -/* Slot configuration */ -static unsigned int tx_slots = DEF_TX_SLOTS; -static unsigned int rx_slots = DEF_RX_SLOTS; - -/* Configuration consistency parameters */ -static DEFINE_MUTEX(mop500_ab8500_params_lock); -static unsigned long mop500_ab8500_usage; -static int mop500_ab8500_rate; -static int mop500_ab8500_channels; - -/* Clocks */ -static const char * const enum_mclk[] = { - "SYSCLK", - "ULPCLK" -}; -enum mclk { - MCLK_SYSCLK, - MCLK_ULPCLK, -}; - -static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_mclk, enum_mclk); - -/* Private data for machine-part MOP500<->AB8500 */ -struct mop500_ab8500_drvdata { - /* Clocks */ - enum mclk mclk_sel; - struct clk *clk_ptr_intclk; - struct clk *clk_ptr_sysclk; - struct clk *clk_ptr_ulpclk; -}; - -static inline const char *get_mclk_str(enum mclk mclk_sel) -{ - switch (mclk_sel) { - case MCLK_SYSCLK: - return "SYSCLK"; - case MCLK_ULPCLK: - return "ULPCLK"; - default: - return "Unknown"; - } -} - -static int mop500_ab8500_set_mclk(struct device *dev, - struct mop500_ab8500_drvdata *drvdata) -{ - int status; - struct clk *clk_ptr; - - if (IS_ERR(drvdata->clk_ptr_intclk)) { - dev_err(dev, - "%s: ERROR: intclk not initialized!\n", __func__); - return -EIO; - } - - switch (drvdata->mclk_sel) { - case MCLK_SYSCLK: - clk_ptr = drvdata->clk_ptr_sysclk; - break; - case MCLK_ULPCLK: - clk_ptr = drvdata->clk_ptr_ulpclk; - break; - default: - return -EINVAL; - } - - if (IS_ERR(clk_ptr)) { - dev_err(dev, "%s: ERROR: %s not initialized!\n", __func__, - get_mclk_str(drvdata->mclk_sel)); - return -EIO; - } - - status = clk_set_parent(drvdata->clk_ptr_intclk, clk_ptr); - if (status) - dev_err(dev, - "%s: ERROR: Setting intclk parent to %s failed (ret = %d)!", - __func__, get_mclk_str(drvdata->mclk_sel), status); - else - dev_dbg(dev, - "%s: intclk parent changed to %s.\n", - __func__, get_mclk_str(drvdata->mclk_sel)); - - return status; -} - -/* - * Control-events - */ - -static int mclk_input_control_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); - struct mop500_ab8500_drvdata *drvdata = - snd_soc_card_get_drvdata(card); - - ucontrol->value.enumerated.item[0] = drvdata->mclk_sel; - - return 0; -} - -static int mclk_input_control_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); - struct mop500_ab8500_drvdata *drvdata = - snd_soc_card_get_drvdata(card); - unsigned int val = ucontrol->value.enumerated.item[0]; - - if (val > (unsigned int)MCLK_ULPCLK) - return -EINVAL; - if (drvdata->mclk_sel == val) - return 0; - - drvdata->mclk_sel = val; - - return 1; -} - -/* - * Controls - */ - -static struct snd_kcontrol_new mop500_ab8500_ctrls[] = { - SOC_ENUM_EXT("Master Clock Select", - soc_enum_mclk, - mclk_input_control_get, mclk_input_control_put), - SOC_DAPM_PIN_SWITCH("Headset Left"), - SOC_DAPM_PIN_SWITCH("Headset Right"), - SOC_DAPM_PIN_SWITCH("Earpiece"), - SOC_DAPM_PIN_SWITCH("Speaker Left"), - SOC_DAPM_PIN_SWITCH("Speaker Right"), - SOC_DAPM_PIN_SWITCH("LineOut Left"), - SOC_DAPM_PIN_SWITCH("LineOut Right"), - SOC_DAPM_PIN_SWITCH("Vibra 1"), - SOC_DAPM_PIN_SWITCH("Vibra 2"), - SOC_DAPM_PIN_SWITCH("Mic 1"), - SOC_DAPM_PIN_SWITCH("Mic 2"), - SOC_DAPM_PIN_SWITCH("LineIn Left"), - SOC_DAPM_PIN_SWITCH("LineIn Right"), - SOC_DAPM_PIN_SWITCH("DMic 1"), - SOC_DAPM_PIN_SWITCH("DMic 2"), - SOC_DAPM_PIN_SWITCH("DMic 3"), - SOC_DAPM_PIN_SWITCH("DMic 4"), - SOC_DAPM_PIN_SWITCH("DMic 5"), - SOC_DAPM_PIN_SWITCH("DMic 6"), -}; - -/* ASoC */ - -static int mop500_ab8500_startup(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - - /* Set audio-clock source */ - return mop500_ab8500_set_mclk(rtd->card->dev, - snd_soc_card_get_drvdata(rtd->card)); -} - -static void mop500_ab8500_shutdown(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - struct device *dev = rtd->card->dev; - - dev_dbg(dev, "%s: Enter\n", __func__); - - /* Reset slots configuration to default(s) */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - tx_slots = DEF_TX_SLOTS; - else - rx_slots = DEF_RX_SLOTS; -} - -static int mop500_ab8500_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); - struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); - struct device *dev = rtd->card->dev; - unsigned int fmt; - int channels, ret = 0, driver_mode, slots; - unsigned int sw_codec, sw_cpu; - bool is_playback; - - dev_dbg(dev, "%s: Enter\n", __func__); - - dev_dbg(dev, "%s: substream->pcm->name = %s\n" - "substream->pcm->id = %s.\n" - "substream->name = %s.\n" - "substream->number = %d.\n", - __func__, - substream->pcm->name, - substream->pcm->id, - substream->name, - substream->number); - - /* Ensure configuration consistency between DAIs */ - scoped_guard(mutex, &mop500_ab8500_params_lock) { - if (mop500_ab8500_usage) { - if (mop500_ab8500_rate != params_rate(params) || - mop500_ab8500_channels != params_channels(params)) { - return -EBUSY; - } - } else { - mop500_ab8500_rate = params_rate(params); - mop500_ab8500_channels = params_channels(params); - } - __set_bit(cpu_dai->id, &mop500_ab8500_usage); - } - - channels = params_channels(params); - - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S32_LE: - sw_cpu = 32; - break; - - case SNDRV_PCM_FORMAT_S16_LE: - sw_cpu = 16; - break; - - default: - return -EINVAL; - } - - /* Setup codec depending on driver-mode */ - if (channels == 8) - driver_mode = DRIVERMODE_CODEC_ONLY; - else - driver_mode = DRIVERMODE_NORMAL; - dev_dbg(dev, "%s: Driver-mode: %s.\n", __func__, - (driver_mode == DRIVERMODE_NORMAL) ? "NORMAL" : "CODEC_ONLY"); - - /* Setup format */ - - if (driver_mode == DRIVERMODE_NORMAL) { - fmt = SND_SOC_DAIFMT_DSP_A | - SND_SOC_DAIFMT_CBP_CFP | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CONT; - } else { - fmt = SND_SOC_DAIFMT_DSP_A | - SND_SOC_DAIFMT_CBP_CFP | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_GATED; - } - - ret = snd_soc_runtime_set_dai_fmt(rtd, fmt); - if (ret) - return ret; - - /* Setup TDM-slots */ - - is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); - switch (channels) { - case 1: - slots = 16; - tx_slots = (is_playback) ? TX_SLOT_MONO : 0; - rx_slots = (is_playback) ? 0 : RX_SLOT_MONO; - break; - case 2: - slots = 16; - tx_slots = (is_playback) ? TX_SLOT_STEREO : 0; - rx_slots = (is_playback) ? 0 : RX_SLOT_STEREO; - break; - case 8: - slots = 16; - tx_slots = (is_playback) ? TX_SLOT_8CH : 0; - rx_slots = (is_playback) ? 0 : RX_SLOT_8CH; - break; - default: - return -EINVAL; - } - - if (driver_mode == DRIVERMODE_NORMAL) - sw_codec = sw_cpu; - else - sw_codec = 20; - - dev_dbg(dev, "%s: CPU-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__, - tx_slots, rx_slots); - ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_slots, rx_slots, slots, - sw_cpu); - if (ret) - return ret; - - dev_dbg(dev, "%s: CODEC-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__, - tx_slots, rx_slots); - ret = snd_soc_dai_set_tdm_slot(codec_dai, tx_slots, rx_slots, slots, - sw_codec); - if (ret) - return ret; - - return 0; -} - -static int mop500_ab8500_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); - - guard(mutex)(&mop500_ab8500_params_lock); - __clear_bit(cpu_dai->id, &mop500_ab8500_usage); - - return 0; -} - -const struct snd_soc_ops mop500_ab8500_ops[] = { - { - .hw_params = mop500_ab8500_hw_params, - .hw_free = mop500_ab8500_hw_free, - .startup = mop500_ab8500_startup, - .shutdown = mop500_ab8500_shutdown, - } -}; - -int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(rtd->card); - struct device *dev = rtd->card->dev; - struct mop500_ab8500_drvdata *drvdata; - int ret; - - dev_dbg(dev, "%s Enter.\n", __func__); - - /* Create driver private-data struct */ - drvdata = devm_kzalloc(dev, sizeof(struct mop500_ab8500_drvdata), - GFP_KERNEL); - - if (!drvdata) - return -ENOMEM; - - snd_soc_card_set_drvdata(rtd->card, drvdata); - - /* Setup clocks */ - - drvdata->clk_ptr_sysclk = clk_get(dev, "sysclk"); - if (IS_ERR(drvdata->clk_ptr_sysclk)) - dev_warn(dev, "%s: WARNING: clk_get failed for 'sysclk'!\n", - __func__); - drvdata->clk_ptr_ulpclk = clk_get(dev, "ulpclk"); - if (IS_ERR(drvdata->clk_ptr_ulpclk)) - dev_warn(dev, "%s: WARNING: clk_get failed for 'ulpclk'!\n", - __func__); - drvdata->clk_ptr_intclk = clk_get(dev, "intclk"); - if (IS_ERR(drvdata->clk_ptr_intclk)) - dev_warn(dev, "%s: WARNING: clk_get failed for 'intclk'!\n", - __func__); - - /* Set intclk default parent to ulpclk */ - drvdata->mclk_sel = MCLK_ULPCLK; - ret = mop500_ab8500_set_mclk(dev, drvdata); - if (ret < 0) - dev_warn(dev, "%s: WARNING: mop500_ab8500_set_mclk!\n", - __func__); - - drvdata->mclk_sel = MCLK_ULPCLK; - - /* Add controls */ - ret = snd_soc_add_card_controls(rtd->card, mop500_ab8500_ctrls, - ARRAY_SIZE(mop500_ab8500_ctrls)); - if (ret < 0) { - pr_err("%s: Failed to add machine-controls (%d)!\n", - __func__, ret); - return ret; - } - - ret = snd_soc_dapm_disable_pin(dapm, "Earpiece"); - ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Left"); - ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Right"); - ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Left"); - ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Right"); - ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 1"); - ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 2"); - ret |= snd_soc_dapm_disable_pin(dapm, "Mic 1"); - ret |= snd_soc_dapm_disable_pin(dapm, "Mic 2"); - ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Left"); - ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Right"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 1"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 2"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 3"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 4"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 5"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 6"); - - return ret; -} - -void mop500_ab8500_remove(struct snd_soc_card *card) -{ - struct mop500_ab8500_drvdata *drvdata = snd_soc_card_get_drvdata(card); - - clk_put(drvdata->clk_ptr_sysclk); - clk_put(drvdata->clk_ptr_ulpclk); - clk_put(drvdata->clk_ptr_intclk); - - snd_soc_card_set_drvdata(card, NULL); -} diff --git a/sound/soc/ux500/mop500_ab8500.h b/sound/soc/ux500/mop500_ab8500.h deleted file mode 100644 index 98de80a9cc4fef..00000000000000 --- a/sound/soc/ux500/mop500_ab8500.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Author: Ola Lilja - * for ST-Ericsson. - */ - -#ifndef MOP500_AB8500_H -#define MOP500_AB8500_H - -extern const struct snd_soc_ops mop500_ab8500_ops[]; - -int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd); -void mop500_ab8500_remove(struct snd_soc_card *card); - -#endif From bcd61aa247c28b721ac21dda6e4135f75a2de29f Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Mon, 14 Sep 2026 18:44:56 +0800 Subject: [PATCH 0836/1417] ASoC: codecs: max98363: fix uninitialized stream_config->type stream_config is not initialized before being passed to sdw_stream_add_slave(). The type field may contain garbage and is later copied to stream->type by sdw_config_stream(). Zero-initialize stream_config so type defaults to SDW_STREAM_PCM. Fixes: 18c0af945fa3 ("ASoC: max98363: add soundwire amplifier driver") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Link: https://patch.msgid.link/20260914104456.376666-1-yijiangshan@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/codecs/max98363.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/max98363.c b/sound/soc/codecs/max98363.c index 0e32b7a94cb05d..4a6d7b163acf13 100644 --- a/sound/soc/codecs/max98363.c +++ b/sound/soc/codecs/max98363.c @@ -200,7 +200,7 @@ static int max98363_sdw_dai_hw_params(struct snd_pcm_substream *substream, struct max98363_priv *max98363 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; enum sdw_data_direction direction; struct sdw_stream_runtime *stream; From 174d160884199cad97413f33fe1b56027dc0d3e1 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Mon, 14 Sep 2026 18:45:51 +0800 Subject: [PATCH 0837/1417] ASoC: codecs: rt1017-sdca-sdw: fix uninitialized stream_config->type stream_config is not initialized before being passed to sdw_stream_add_slave(). The type field may contain garbage and is later copied to stream->type by sdw_config_stream(). Zero-initialize stream_config so type defaults to SDW_STREAM_PCM. While at it, use snd_sdw_params_to_config() helper instead of open-coding the same logic. Fixes: 2b7aecd58528 ("ASoC: rt1017: Add RT1017 SDCA amplifier driver") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Link: https://patch.msgid.link/20260914104551.377880-1-yijiangshan@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1017-sdca-sdw.c | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/sound/soc/codecs/rt1017-sdca-sdw.c b/sound/soc/codecs/rt1017-sdca-sdw.c index caf75e5657ef16..01cc2511a7d073 100644 --- a/sound/soc/codecs/rt1017-sdca-sdw.c +++ b/sound/soc/codecs/rt1017-sdca-sdw.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -573,11 +574,10 @@ static int rt1017_sdca_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt1017_sdca_priv *rt1017 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; - enum sdw_data_direction direction; struct sdw_stream_runtime *sdw_stream; - int retval, port, num_channels, ch_mask; + int retval, port; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s", __func__, dai->name); @@ -591,28 +591,17 @@ static int rt1017_sdca_pcm_hw_params(struct snd_pcm_substream *substream, /* SoundWire specific configuration */ /* port 1 for playback */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - direction = SDW_DATA_DIR_RX; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) port = 1; - } else { - direction = SDW_DATA_DIR_TX; + else port = 2; - } - - num_channels = params_channels(params); - ch_mask = (1 << num_channels) - 1; - - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = num_channels; - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = direction; - port_config.ch_mask = ch_mask; + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = port; dev_dbg(dai->dev, "frame_rate %d, ch_count %d, bps %d, direction %d, ch_mask %d, port: %d\n", - params_rate(params), num_channels, snd_pcm_format_width(params_format(params)), - direction, ch_mask, port); + stream_config.frame_rate, stream_config.ch_count, stream_config.bps, + stream_config.direction, port_config.ch_mask, port); retval = sdw_stream_add_slave(rt1017->sdw_slave, &stream_config, &port_config, 1, sdw_stream); From aff09d9e37e02dc60bde79035ac15b136d602259 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 18 Sep 2026 15:16:17 +0200 Subject: [PATCH 0838/1417] drm/nouveau/clk: fix list cursor use after loop in nvkm_clk_ustate_update If list_for_each_entry() exits without hitting a break then "pstate" is not a valid pstate pointer. Introduce a "found" variable instead. The check is reachable from userspace: nvkm_clk_ustate_update() takes the pstate id straight from the 'pstate' debugfs file, so requesting an id that is not in clk->states - or any id at all when the perf tables are broken and the list is empty - makes the pstate->pstate != req test dereference the list head cast to a struct nvkm_pstate, which is an out-of-bounds read. Fixes: 7c8565220697 ("drm/nouveau/clk: implement power state and engine clock control in core") Signed-off-by: Dan Carpenter [Francesco: rebased on drm-misc-next, expanded the commit message] Signed-off-by: Francesco Magazzu Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260918131620.405133-2-postadelmaga@gmail.com --- drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c index 572e638463159f..5da82db71dde2c 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c @@ -473,6 +473,7 @@ static int nvkm_clk_ustate_update(struct nvkm_clk *clk, int req) { struct nvkm_pstate *pstate; + bool found = false; int i = 0; if (!clk->allow_reclock) @@ -480,12 +481,14 @@ nvkm_clk_ustate_update(struct nvkm_clk *clk, int req) if (req != -1 && req != -2) { list_for_each_entry(pstate, &clk->states, head) { - if (pstate->pstate == req) + if (pstate->pstate == req) { + found = true; break; + } i++; } - if (pstate->pstate != req) + if (!found) return -EINVAL; req = i; } From 866dbd17c3d5f599b9d93ea9bd5b3d6859ff8350 Mon Sep 17 00:00:00 2001 From: Francesco Magazzu Date: Fri, 18 Sep 2026 15:16:18 +0200 Subject: [PATCH 0839/1417] drm/nouveau/clk: don't use the pstate cursor after the loop nvkm_pstate_prog() walks clk->states looking for the entry at index 'pstatei' and then keeps using the list_for_each_entry cursor after the loop. This is not triggerable today: every caller clamps the index against clk->state_nr before calling, so the loop always breaks on a real entry. It is safe by virtue of what the callers happen to do, not by anything the function itself checks. Should a caller ever pass an index that is not on the list, the cursor would point at the list head rather than at a pstate, and the pstate->base.domain[] and pstate->fanspeed accesses that follow would read past it. Rather than leave that trap in place for the next caller, track whether the entry was found and return -EINVAL if it was not. No functional change. Signed-off-by: Francesco Magazzu Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260918131620.405133-3-postadelmaga@gmail.com --- drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c index 5da82db71dde2c..a43246ae681fc3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c @@ -270,13 +270,19 @@ nvkm_pstate_prog(struct nvkm_clk *clk, int pstatei) struct nvkm_fb *fb = subdev->device->fb; struct nvkm_pci *pci = subdev->device->pci; struct nvkm_pstate *pstate; + bool found = false; int ret, idx = 0; list_for_each_entry(pstate, &clk->states, head) { - if (idx++ == pstatei) + if (idx++ == pstatei) { + found = true; break; + } } + if (!found) + return -EINVAL; + nvkm_debug(subdev, "setting performance state %d\n", pstatei); clk->pstate = pstatei; From 7ca7b8b5f2cc89ee843c2eab3272aac5aafb7b73 Mon Sep 17 00:00:00 2001 From: Francesco Magazzu Date: Fri, 18 Sep 2026 15:16:19 +0200 Subject: [PATCH 0840/1417] drm/nouveau/device: don't use the pstate cursor after the loop nvkm_control_mthd_pstate_attr() looks up the pstate at the index supplied by userspace by walking clk->states, and then keeps using the list_for_each_entry cursor after the loop. This is not triggerable today: the function already rejects args->v0.state >= clk->state_nr before the loop, and clk->state_nr is kept in sync with the number of entries on clk->states, so the lookup always breaks on a real entry. Should the loop ever run to completion, the cursor would point at the list head rather than at a pstate, and the pstate->base.domain[] read and the walk of pstate->list that follow would read past it. Rather than leave that trap in place, track whether the entry was found and return -EINVAL if it was not, like the other lookup failures in this function. No functional change. Signed-off-by: Francesco Magazzu Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260918131620.405133-4-postadelmaga@gmail.com --- drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c index f2e9a06263ce33..28702741a88b13 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c @@ -74,6 +74,7 @@ nvkm_control_mthd_pstate_attr(struct nvkm_control *ctrl, void *data, u32 size) const struct nvkm_domain *domain; struct nvkm_pstate *pstate; struct nvkm_cstate *cstate; + bool found = false; int i = 0, j = -1; u32 lo, hi; int ret = -ENOSYS; @@ -104,10 +105,15 @@ nvkm_control_mthd_pstate_attr(struct nvkm_control *ctrl, void *data, u32 size) if (args->v0.state != NVIF_CONTROL_PSTATE_ATTR_V0_STATE_CURRENT) { list_for_each_entry(pstate, &clk->states, head) { - if (i++ == args->v0.state) + if (i++ == args->v0.state) { + found = true; break; + } } + if (!found) + return -EINVAL; + lo = pstate->base.domain[domain->name]; hi = lo; list_for_each_entry(cstate, &pstate->list, head) { From e5cccdafc855cd5f96f4b51d38114a0360b075d7 Mon Sep 17 00:00:00 2001 From: Francesco Magazzu Date: Fri, 18 Sep 2026 15:16:20 +0200 Subject: [PATCH 0841/1417] drm/nouveau/clk: don't clobber reclock status when restoring volt/fan nvkm_cstate_prog() reuses 'ret' for the voltage and fan-speed restore calls it makes after reprogramming the clocks. Those calls almost always succeed, so the status of the reclock itself is overwritten and the function reports success even when clk->func->calc() or clk->func->prog() failed. The converse is also true: a successful reclock is reported as an error if the final restore call fails, even though that failure is only logged and otherwise ignored. The only consumer of the return value is the error message in nvkm_pstate_work(), so in practice a failing reclock is simply never reported. Nothing else changes, but a function that returns success on failure is a trap for the next caller. Keep the calc/prog status in 'ret' and use a separate local for the restore calls. Fixes: 3eca809b3c05 ("drm/nouveau/clk: cosmetic changes") Signed-off-by: Francesco Magazzu Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260918131620.405133-5-postadelmaga@gmail.com --- drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c index a43246ae681fc3..1cb83edc78dc4a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c @@ -199,16 +199,18 @@ nvkm_cstate_prog(struct nvkm_clk *clk, struct nvkm_pstate *pstate, int cstatei) } if (volt) { - ret = nvkm_volt_set_id(volt, cstate->voltage, - pstate->base.voltage, clk->temp, -1); - if (ret && ret != -ENODEV) - nvkm_error(subdev, "failed to lower voltage: %d\n", ret); + int err = nvkm_volt_set_id(volt, cstate->voltage, + pstate->base.voltage, clk->temp, -1); + + if (err && err != -ENODEV) + nvkm_error(subdev, "failed to lower voltage: %d\n", err); } if (therm) { - ret = nvkm_therm_cstate(therm, pstate->fanspeed, -1); - if (ret && ret != -ENODEV) - nvkm_error(subdev, "failed to lower fan speed: %d\n", ret); + int err = nvkm_therm_cstate(therm, pstate->fanspeed, -1); + + if (err && err != -ENODEV) + nvkm_error(subdev, "failed to lower fan speed: %d\n", err); } return ret; From 6a6870d3077faa501ca97760057ddca22b68418d Mon Sep 17 00:00:00 2001 From: Peiyang He Date: Fri, 18 Sep 2026 10:53:12 +0800 Subject: [PATCH 0842/1417] drm/nouveau: don't bump pin count on failed re-pin in nouveau_bo_pin_locked() nouveau_bo_pin_locked() checks whether an already pinned BO is in a memory domain compatible with a new pin request. When the domains are incompatible, it sets -EBUSY but still calls ttm_bo_pin() before returning. Callers treat a failed nouveau_bo_pin() as not having acquired a new pin, so the extra pin count is never decreased by a matching unpin. This triggers the warning in ttm_bo_release(): WARN_ON_ONCE(bo->pin_count); Found when fuzzing the nouveau driver with a modified Syzkaller: WARNING: drivers/gpu/drm/ttm/ttm_bo.c:256 at ttm_bo_release+0x827/0x9e0 drivers/gpu/drm/ttm/ttm_bo.c:256, CPU#1: syz.3.24/2212 Modules linked in: CPU: 1 UID: 0 PID: 2212 Comm: syz.3.24 Not tainted 7.2.0 #24 PREEMPT(lazy) nouveau 0000:01:00.0: gsp:msg fn:103 len:0x40/0x20 res:0x19 resp:0x19 Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 RIP: 0010:ttm_bo_release+0x827/0x9e0 drivers/gpu/drm/ttm/ttm_bo.c:256 Code: 02 00 0f 85 51 01 00 00 48 8b 7b 08 e8 d2 20 01 00 e9 80 fd ff ff e8 d8 15 c0 fe 90 0f 0b 90 e9 e1 f8 ff ff e8 ca 15 c0 fe 90 <0f> 0b 90 e9 a4 f8 ff ff e8 bc 15 c0 fe be 03 00 00 00 4c 89 e7 e8 msg: 00000000: 05 00 d0 c1 04 00 f0 f1 01 30 00 00 2d 90 00 00 .........0..-... RSP: 0018:ffffc9000f5cf710 EFLAGS: 00010293 RAX: 0000000000000000 RBX: ffff888018e5d2a8 RCX: ffffffff82bb1b36 RDX: ffff888017b68000 RSI: 0000000000000004 RDI: ffff888018e5d2a8 msg: 00000010: 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ RBP: ffff88801261c720 R08: 0000000000000001 R09: ffffed10031cba55 R10: ffff888018e5d2ab R11: 00000000000000f3 R12: ffff888018e5d290 R13: ffff888018e5d2d4 R14: ffff88801b219c18 R15: dffffc0000000000 FS: 0000000000000000(0000) GS:ffff8880e0f6f000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000001b31223ffc CR3: 0000000028e00005 CR4: 0000000000770ef0 PKRU: 80000000 Call Trace: kref_put include/linux/kref.h:65 [inline] ttm_bo_put drivers/gpu/drm/ttm/ttm_bo.c:325 [inline] ttm_bo_fini+0x55/0x80 drivers/gpu/drm/ttm/ttm_bo.c:330 nouveau_gem_object_del+0xb2/0x1b0 drivers/gpu/drm/nouveau/nouveau_gem.c:90 drm_gem_object_free+0x5f/0x90 drivers/gpu/drm/drm_gem.c:1165 kref_put include/linux/kref.h:65 [inline] __drm_gem_object_put include/drm/drm_gem.h:562 [inline] drm_gem_object_put include/drm/drm_gem.h:575 [inline] nouveau_abi16_chan_fini.constprop.0+0x44f/0x5a0 drivers/gpu/drm/nouveau/nouveau_abi16.c:195 nouveau 0000:01:00.0: syz.2.23[2209]: Unknown handle 0x00000000 nouveau_abi16_fini+0x1d0/0x340 drivers/gpu/drm/nouveau/nouveau_abi16.c:225 nouveau_drm_postclose+0x18b/0x3e0 drivers/gpu/drm/nouveau/nouveau_drm.c:1284 nouveau 0000:01:00.0: syz.2.23[2209]: validate_init drm_file_free.part.0+0x6d6/0xb60 drivers/gpu/drm/drm_file.c:267 drm_file_free drivers/gpu/drm/drm_file.c:237 [inline] drm_close_helper.isra.0+0x11a/0x160 drivers/gpu/drm/drm_file.c:290 drm_release+0x1ab/0x330 drivers/gpu/drm/drm_file.c:438 __fput+0x39c/0xa60 fs/file_table.c:512 nouveau 0000:01:00.0: syz.2.23[2209]: validate: -2 task_work_run+0x15a/0x230 kernel/task_work.c:233 exit_task_work include/linux/task_work.h:40 [inline] do_exit+0x82b/0x25a0 kernel/exit.c:1009 do_group_exit+0xc2/0x280 kernel/exit.c:1152 get_signal+0x1d6e/0x1f30 kernel/signal.c:3046 arch_do_signal_or_restart+0x7d/0x6e0 arch/x86/kernel/signal.c:337 __exit_to_user_mode_loop kernel/entry/common.c:66 [inline] exit_to_user_mode_loop+0xdf/0x440 kernel/entry/common.c:101 __exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline] syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:230 [inline] syscall_exit_to_user_mode include/linux/entry-common.h:318 [inline] do_syscall_64+0x4f8/0x690 arch/x86/entry/syscall_64.c:100 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f12bac8594d Code: Unable to access opcode bytes at 0x7f12bac85923. RSP: 002b:00007f12b96e70d8 EFLAGS: 00000246 ORIG_RAX: 00000000000000ca RAX: 0000000000000001 RBX: 00007f12baf15fa8 RCX: 00007f12bac8594d RDX: 00000000000f4240 RSI: 0000000000000081 RDI: 00007f12baf15fac RBP: 00007f12baf15fa0 R08: 00007f12baee8000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007f12baf16038 R14: 0000000000000006 R15: 00007ffe2ed394b0 irq event stamp: 47867 hardirqs last enabled at (47883): [] __up_console_sem+0x66/0x70 kernel/printk/printk.c:347 hardirqs last disabled at (47892): [] __up_console_sem+0x4b/0x70 kernel/printk/printk.c:345 softirqs last enabled at (47880): [] __do_softirq kernel/softirq.c:656 [inline] softirqs last enabled at (47880): [] invoke_softirq kernel/softirq.c:496 [inline] softirqs last enabled at (47880): [] __irq_exit_rcu+0x137/0x1c0 kernel/softirq.c:735 softirqs last disabled at (47875): [] __do_softirq kernel/softirq.c:656 [inline] softirqs last disabled at (47875): [] invoke_softirq kernel/softirq.c:496 [inline] softirqs last disabled at (47875): [] __irq_exit_rcu+0x137/0x1c0 kernel/softirq.c:735 Fix by calling ttm_bo_pin() only when the existing placement is compatible with the new pin request. This matches the correct behavior in other DRM drivers such as amdgpu_bo_pin() in amdgpu. Cc: stable@vger.kernel.org Fixes: ad76b3f7c7a0 ("drm/nouveau: teach nouveau_bo_pin() how to force a contig vram allocation") Signed-off-by: Peiyang He Assisted-by: LLM Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/EACEF2F4E098413F+20260918025312.2814889-1-peiyang_he@smail.nju.edu.cn --- drivers/gpu/drm/nouveau/nouveau_bo.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c index 0e8de6d4b36f7c..6dcb92575eb466 100644 --- a/drivers/gpu/drm/nouveau/nouveau_bo.c +++ b/drivers/gpu/drm/nouveau/nouveau_bo.c @@ -578,8 +578,9 @@ int nouveau_bo_pin_locked(struct nouveau_bo *nvbo, uint32_t domain, bool contig) "0x%08x vs 0x%08x\n", bo, bo->resource->mem_type, domain); ret = -EBUSY; + } else { + ttm_bo_pin(&nvbo->bo); } - ttm_bo_pin(&nvbo->bo); goto out; } From 1717fcc5be575d4768279148ae9465a8b13d4339 Mon Sep 17 00:00:00 2001 From: Giuseppe Ranieri Date: Thu, 17 Sep 2026 21:51:14 +0000 Subject: [PATCH 0843/1417] drm/nouveau/disp: don't reject HDMI config on cards without SCDC nv50_hdmi_enable() passes the sink's SCDC capability from its EDID straight through to nvif_outp_hdmi(). On pre-Maxwell-2 cards there is no hdmi->scdc callback, so nvkm_uoutp_mthd_hdmi() rejects the whole configuration with -EINVAL, and nv50_hdmi_enable() returns before hdmi->ctrl() runs and before the AVI and VSI infoframes are sent. The result on such a card driving an SCDC-capable HDMI 2.0 sink is that HDMI audio silently stops working. Video is unaffected, and nothing is logged, which makes the failure hard to attribute. SCDC is optional, and the hdmi->scdc() call further down is already guarded against a missing callback. Requesting it on a card that cannot do it need not invalidate the rest of the HDMI configuration, so drop that term from the condition and let the existing guard skip SCDC alone. Fixes: 6c6abab20b99 ("drm/nouveau/disp: add output hdmi config method") Signed-off-by: Giuseppe Ranieri Co-Authored-By: Tano Dzhinski Signed-off-by: Tano Dzhinski Tested-by: Tano Dzhinski Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260917215114.1136715-1-tano.dzhinski@gmail.com --- drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c index 377d0e0cef8481..9887b3898505b0 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c @@ -253,8 +253,7 @@ nvkm_uoutp_mthd_hdmi(struct nvkm_outp *outp, void *argv, u32 argc) if (!ior->func->hdmi || args->v0.max_ac_packet > 0x1f || - args->v0.rekey > 0x7f || - (args->v0.scdc && !ior->func->hdmi->scdc)) + args->v0.rekey > 0x7f) return -EINVAL; if (!args->v0.enable) { From d58384c22739848efe14b34e9586e4f1242f33c0 Mon Sep 17 00:00:00 2001 From: Liz Fong-Jones Date: Fri, 18 Sep 2026 03:56:33 +0000 Subject: [PATCH 0844/1417] PCI: Fix BAR resize for devices on a root bus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pci_do_resource_release_and_resize() releases device BARs that share a bridge window with the BAR being resized, but when the device sits directly on a root bus (pdev->bus->self == NULL) it then skips resource assignment entirely and returns success, leaving the BARs it just released unassigned (IORESOURCE_UNSET). Skipping pbus_reassign_bridge_resources() is correct in that case -- there is no bridge window to adjust -- but the device BARs still have to be reassigned. Before the BAR release was consolidated into the PCI core, this case worked for amdgpu because the driver released the BARs itself and then called pci_assign_unassigned_bus_resources() unconditionally after the resize, which assigns unassigned device BARs also on a root bus. Commit db92e3fef53e ("drm/amdgpu: Remove driver side BAR release before resize") removed that call, so nothing assigns the released BARs anymore. This breaks amdgpu completely on the SolidRun HoneyComb LX2K (NXP LX2160A, arm64, ACPI), where ACPI doesn't expose the Root Port so the GPU endpoint appears directly on a "root bus" of its segment: amdgpu 0004:01:00.0: BAR 0 [mem 0xa400000000-0xa40fffffff 64bit pref]: releasing amdgpu 0004:01:00.0: BAR 2 [mem 0xa410000000-0xa4101fffff 64bit pref]: releasing amdgpu 0004:01:00.0: sw_init of IP block failed -19 amdgpu 0004:01:00.0: amdgpu_device_ip_init failed amdgpu 0004:01:00.0: Fatal error during GPU init No error is logged because the resize path reports success; amdgpu then finds BAR 0 IORESOURCE_UNSET and bails out with -ENODEV. When there is no upstream bridge, call pci_bus_assign_resources() on the root bus to place the BARs released above, using the same alignment-sorted algorithm as normal enumeration instead of a manual per-BAR loop. This also walks the rest of the hierarchy under the root bus, as pci_assign_unassigned_bus_resources() used to for amdgpu before commit db92e3fef53e ("drm/amdgpu: Remove driver side BAR release before resize") removed that call -- the core-side fix that commit asked for ("such a problem should be fixed inside pci_resize_resource() instead"). pci_bus_assign_resources() returns void, so failure is detected by checking whether the released BARs are still assigned afterward; if not, roll back as in the bridged case. This is stricter than the bridged path -- it fails on any unplaced resource, not just required ones -- since a root bus typically has one shared window, and failing loudly seemed better than leaving something silently unassigned. The root bus path also had a locking bug that any fix here necessarily touches: the old "goto out" jumped to up_read(&pci_bus_sem) without a matching down_read() (as does the "goto restore" taken when pci_dev_res_add_to_list() fails in the release loop). Take pci_bus_sem before the BAR release loop so every path through the function holds it exactly once. Fixes: 337b1b566db0 ("PCI: Fix restoring BARs on BAR resize rollback path") Link: https://bugs.launchpad.net/ubuntu/+source/linux-hwe-7.0/+bug/2159596 Suggested-by: Ilpo Järvinen Assisted-by: Claude:claude-fable-5 checkpatch Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Liz Fong-Jones [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Reviewed-by: Ilpo Järvinen Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260918035633.566823-1-lizf@honeycomb.io --- drivers/pci/setup-bus.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index e8c94aa1d3c129..ed16ef7c26fa71 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -2380,6 +2380,7 @@ int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size struct resource *res = pci_resource_n(pdev, resno); struct pci_dev_resource *dev_res; struct pci_bus *bus = pdev->bus; + struct pci_dev *bridge = pci_upstream_bridge(pdev); struct resource *b_win, *r; LIST_HEAD(saved); unsigned int i; @@ -2397,6 +2398,8 @@ int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size if (ret) return ret; + down_read(&pci_bus_sem); + pci_dev_for_each_resource(pdev, r, i) { if (i >= PCI_BRIDGE_RESOURCES) break; @@ -2415,13 +2418,21 @@ int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size pci_resize_resource_set_size(pdev, resno, size); - if (!bus->self) - goto out; + if (bridge) { + ret = pbus_reassign_bridge_resources(bus, res, &saved); + if (ret) + goto restore; + } else { + /* No bridge window to adjust; let the core reassign the bus. */ + pci_bus_assign_resources(bus); - down_read(&pci_bus_sem); - ret = pbus_reassign_bridge_resources(bus, res, &saved); - if (ret) - goto restore; + list_for_each_entry(dev_res, &saved, list) { + if (!resource_assigned(dev_res->res)) { + ret = -ENOSPC; + goto restore; + } + } + } out: up_read(&pci_bus_sem); From 8805840aad73df7146778be243a196d48b4f6430 Mon Sep 17 00:00:00 2001 From: Angel J Date: Fri, 18 Sep 2026 14:55:40 -0500 Subject: [PATCH 0845/1417] PCI: of_property: Omit bus properties without a subordinate bus A bridge (a device with a Type 1 header) may not have a secondary bus allocated (pdev->subordinate), e.g., if there are no available bus numbers or the bridge secondary/subordinate bus numbers are not writable. The dynamic OF helpers of_pci_prop_bus_range() and of_pci_prop_intr_map() dereference pdev->subordinate without checking it. When CONFIG_PCI_DYNAMIC_OF_NODES is enabled, this can cause a NULL pointer dereference and early boot hang. Generate 'bus-range' and 'interrupt-map' properties only when a subordinate bus exists. Keep the node and its remaining properties for bridges without one. The problem was latent since 407d1a51921e ("PCI: Create device tree node for bridge"), but wasn't reachable until 1f340724419e ("PCI: of: Create device tree PCI host bridge node"), which appeared in v6.15. Before 1f340724419e, of_pci_make_dev_node() returned early because the parent OF node was missing. Fixes: 407d1a51921e ("PCI: Create device tree node for bridge") Signed-off-by: Angel J [bhelgaas: move pdev->subordinate test to callees, commit log] Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org # v6.6+ Link: https://patch.msgid.link/20260918195540.GA1187209@bhelgaas --- drivers/pci/of_property.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/pci/of_property.c b/drivers/pci/of_property.c index 75a358f73e6940..1500740cc55d25 100644 --- a/drivers/pci/of_property.c +++ b/drivers/pci/of_property.c @@ -95,9 +95,13 @@ static int of_pci_prop_bus_range(struct pci_dev *pdev, struct of_changeset *ocs, struct device_node *np) { - u32 bus_range[] = { pdev->subordinate->busn_res.start, - pdev->subordinate->busn_res.end }; + u32 bus_range[2]; + if (!pdev->subordinate) + return 0; + + bus_range[0] = pdev->subordinate->busn_res.start; + bus_range[1] = pdev->subordinate->busn_res.end; return of_changeset_add_prop_u32_array(ocs, np, "bus-range", bus_range, ARRAY_SIZE(bus_range)); } @@ -220,6 +224,9 @@ static int of_pci_prop_intr_map(struct pci_dev *pdev, struct of_changeset *ocs, int ret; u8 pin; + if (!pdev->subordinate) + return 0; + pnode = pci_device_to_OF_node(pdev->bus->self); if (!pnode) pnode = pci_bus_to_OF_node(pdev->bus); From beb56d1d3de522885b30bb97fee83825041d65c2 Mon Sep 17 00:00:00 2001 From: Karl Asseily Date: Fri, 18 Sep 2026 21:23:14 +0300 Subject: [PATCH 0846/1417] dt-bindings: vendor-prefixes: add ESS Technology Add the vendor prefix for ESS Technology, Inc., maker of the SABRE family of audio DACs. The prefix follows the company's domain, esstech.com. Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Asseily Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260918182317.143223-2-karl@asseily.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index ba200296937343..ff4a85f4cbd4eb 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -559,6 +559,8 @@ patternProperties: description: Seiko Epson Corp. "^esp,.*": description: Espressif Systems Co. Ltd. + "^esstech,.*": + description: ESS Technology, Inc. "^est,.*": description: ESTeem Wireless Modems "^eswin,.*": From ab1a02163fcdb2dbe5da603f97aceebe0c582708 Mon Sep 17 00:00:00 2001 From: Karl Asseily Date: Fri, 18 Sep 2026 21:23:15 +0300 Subject: [PATCH 0847/1417] ASoC: dt-bindings: add ESS Technology ES9039Q2M Add a binding for the ES9039Q2M, a 32-bit two-channel audio DAC with an asynchronous sample rate converter. The part selects between two control personalities with its MODE pin: hardware mode, strapped by HW0/HW1/HW2 with no control bus at all, and software mode over I2C or SPI. This binding describes software mode over I2C, which MODE = GND selects. The four supply inputs are described and required. DVDD is not among them: the part's 1.2V digital rail is generated on chip and the pin takes only a decoupling capacitor. The datasheet asks for the supplies in a set order - AVDD, then VCCA about 200us later, then the two output-stage references, and the reverse on the way down - which the description records, so a board with separately switchable rails has it to hand. Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Asseily Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260918182317.143223-3-karl@asseily.com Signed-off-by: Mark Brown --- .../bindings/sound/esstech,es9039q2m.yaml | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml diff --git a/Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml b/Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml new file mode 100644 index 00000000000000..15db30af4b6c5c --- /dev/null +++ b/Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/esstech,es9039q2m.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ESS Technology ES9039Q2M audio DAC + +maintainers: + - Karl Asseily + +description: + The ES9039Q2M is a 32-bit two-channel audio DAC supporting PCM to 768 kHz, + DSD64 to DSD1024, DoP and S/PDIF. It is controlled over I2C or SPI when the + MODE pin selects software mode; this binding covers the I2C interface. The + part contains an asynchronous sample rate converter, so the system clock need + not be synchronous with the audio bit or frame clocks. + + The four supplies are all nominally 3.3V and the datasheet requires a set + order - AVDD first, then VCCA about 200us later, then AVCC_DAC1 and + AVCC_DAC2, and the reverse on the way down. DVDD, the part's 1.2V rail, is + on-chip and needs only its decoupling capacitor - it is not a supply input + and has no property here. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: esstech,es9039q2m + + reg: + description: + I2C address, selected by the ADDR0 and ADDR1 pins - 0x48, 0x49, 0x4a or + 0x4b for GND/GND, GND/AVDD, AVDD/GND and AVDD/AVDD respectively. + enum: [ 0x48, 0x49, 0x4a, 0x4b ] + + clocks: + maxItems: 1 + + clock-names: + const: mclk + + avdd-supply: + description: + 3.3V supply for the part's internal digital regulator, pin AVDD. It is + the first rail up and the last down, per the ordering described above. + + vcca-supply: + description: 3.3V analogue supply, pin VCCA. + + avcc-dac1-supply: + description: + 3.3V reference supply for the channel 1 analogue output stage, pin + AVCC_DAC1. The datasheet allows this rail to move by up to 15% where a + calibrating regulator tracks the DAC's output impedance. + + avcc-dac2-supply: + description: + 3.3V reference supply for the channel 2 analogue output stage, pin + AVCC_DAC2, with the same 15% allowance as channel 1. + + "#sound-dai-cells": + const: 0 + +required: + - compatible + - reg + - avdd-supply + - vcca-supply + - avcc-dac1-supply + - avcc-dac2-supply + - "#sound-dai-cells" + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + audio-codec@48 { + compatible = "esstech,es9039q2m"; + reg = <0x48>; + #sound-dai-cells = <0>; + clocks = <&dac_mclk>; + clock-names = "mclk"; + avdd-supply = <&dac_3v3>; + vcca-supply = <&dac_3v3>; + avcc-dac1-supply = <&dac_avcc1>; + avcc-dac2-supply = <&dac_avcc2>; + }; + }; From 004e260a54758de2cf813cb7fc63e45fe251c7fd Mon Sep 17 00:00:00 2001 From: Karl Asseily Date: Fri, 18 Sep 2026 21:23:16 +0300 Subject: [PATCH 0848/1417] ASoC: es9039q2m: add ESS Technology ES9039Q2M codec driver The ES9039Q2M is a 32-bit two-channel audio DAC with an asynchronous sample rate converter. It selects between two control personalities with its MODE pin: hardware mode, strapped by HW0/HW1/HW2 with no control bus at all, and software mode over I2C or SPI. This driver implements software mode over I2C, which MODE = GND selects. Three properties of the part shape the driver: - The ASRC in front of the DAC means MCLK need not be synchronous with BCLK or LRCK, so a board can feed it a fixed oscillator and never touch the clock again. The driver derives which case it is in rather than assuming one: the part runs synchronously when it drives the bus clocks itself, or when the MCLK it was given can be set to a multiple of the sample rate, and asynchronously only when a fixed oscillator feeds it as a clock consumer. The datasheet's MCLK floor differs between the two - 128 x Fs synchronous, 130 x Fs asynchronous - and where the clock is fixed a startup constraint derives the maximum rate from the MCLK actually present rather than advertising rates the hardware cannot honour. - Selecting an input format takes two registers, not one. Register 57 INPUT_SEL chooses which port to listen to; register 1 SYS MODE CONFIG enables the corresponding decoder, and at reset only ENABLE_TDM_DECODE is set. Selecting DoP without also enabling ENABLE_DOP_DECODE leaves the part hunting for a marker with the marker decoder switched off, so it finds no valid DoP and mutes. The driver sets both. - Several registers have non-zero reserved defaults - register 88 reads 0xb8 at reset - so every write is read-modify-write. The part has four supply inputs and the datasheet asks for them in a set order: AVDD, then VCCA about 200us later, then the two output-stage references AVCC_DAC1 and AVCC_DAC2, with power-down exactly reversed. The driver enables them one at a time for that reason rather than calling regulator_bulk_enable(), which makes no ordering guarantee; the bulk API still acquires them, where order does not matter. DVDD is not among them: the part's 1.2V digital rail is generated on chip and that pin takes only a decoupling capacitor. No settling time is inserted before the first register access because the datasheet specifies none. The datapath is off at reset and the driver turns it on at component probe. SYSTEM_CONFIG[1] is clear when the part comes out of reset, so in software mode nothing enables it at power-up and the analogue output stays dead until the driver writes that bit. It is written after the mute and never before it: bringing the datapath up into an unmuted part whose volume registers still sit at their 0 dB reset value is the thump the mute is there to prevent. Note when testing that a warm reboot does not reset this part - registers and coefficient RAM both survive one, and only removing power clears them - so the previous boot's value can hide whether the write happened at all. DoP is detected by the part rather than announced by the host: AUTO_INPUT_SEL is enabled and both the TDM and DoP decoders run, so a player can simply send DoP-encoded PCM. That is the only way DoP works in practice, because no player can select a DAC mode through a kcontrol first, and material sent to a part that is not looking for the marker renders as the near-silent hiss the DoP design deliberately degrades to. The programmable FIR coefficient controls are write-only by design. The part has a PROG_COEFF_OUT register described as "Programmable FIR coefficient readback", but it is not a RAM read port: it returns the last coefficient written, whatever address is selected in PROG_COEFF_ADDR. Measured by writing two different coefficients to addresses 0 and 1 and reading them back five ways - plain, with a settle delay, with PROG_COEFF_EN set, with the address written twice, and with a write-enable pulse after the address. All ten reads returned the value written to address 1. A get() built on that register would return something with the shape of data and none of its meaning, so there is none. Every control was verified against the silicon rather than against the driver's own read-back: written through ALSA, then read from the chip over raw I2C. That covers the eight filter shapes, the modulator bandwidth, the IIR bandwidth, the four signed 16-bit THD compensation coefficients at both extremes of their range, and the automute enable, time, level and off-level fields including the neighbouring MUTE_RAMP_TO_GND bit that shares a register with the 11-bit time field. The coefficient upload, whose RAM cannot be read back, was verified from the I2C bus itself. System suspend gates the part's internal clock tree. Where the part is clocked from the SoC, system suspend stops its clock as a side effect and there is nothing for a driver to do; where the board feeds it a free-running oscillator - which the ASRC makes an attractive design, since MCLK then need not track BCLK or LRCK - nothing in the system can gate that clock and the part stays fully clocked for the whole of suspend. Clearing ENABLE_DAC_CLK is the only part of its consumption software can reach; the analogue supplies are the board's business. Measured on an RK3588 board with a fixed 24.576 MHz oscillator, system suspend power fell from 1.98 W to 1.95 W and followed the register in both directions across an A/B. Resume mutes the part before regcache_sync(), because the sync walks registers in ascending address order. A board that keeps the supplies up needs nothing, but one that removes them brings the part back at reset defaults - DAC_MODE clear, both channels unmuted, both volume registers at 0 dB - and the sync would set DAC_MODE in register 0 and only restore the real mute in register 86, lighting the datapath up at full scale in between. Resume also re-uploads the programmable FIR coefficient RAM. regcache_sync() cannot restore it - the RAM is write-only, so nothing caches it - but the sync does restore the filter selection, so a board that removed the supplies would come back with the programmable filter selected and the RAM holding whatever it powered up with. The driver keeps a shadow of each stage it uploaded and replays it after the sync. Stages never written have no shadow and keep the part's own defaults. Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Asseily Link: https://patch.msgid.link/20260918182317.143223-4-karl@asseily.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 14 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/es9039q2m.c | 2008 ++++++++++++++++++++++++++++++++++ 3 files changed, 2024 insertions(+) create mode 100644 sound/soc/codecs/es9039q2m.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index d3730c4da51beb..6352f6b5acfbe6 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -123,6 +123,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_ES8328_I2C imply SND_SOC_ES8375 imply SND_SOC_ES8389 + imply SND_SOC_ES9039Q2M imply SND_SOC_ES9356 imply SND_SOC_ES7134 imply SND_SOC_ES7241 @@ -1317,6 +1318,19 @@ config SND_SOC_ES8389 tristate "Everest Semi ES8389 CODEC" depends on I2C +config SND_SOC_ES9039Q2M + tristate "ESS Technology ES9039Q2M CODEC" + depends on I2C + select REGMAP_I2C + help + Enable support for the ESS Technology ES9039Q2M, a 32-bit + two-channel audio DAC with an asynchronous sample rate converter, + supporting PCM, DSD, DoP and S/PDIF input. This driver covers the + I2C control interface, which the MODE pin selects. + + To compile this driver as a module, choose M here: the module + will be called snd-soc-es9039q2m. + config SND_SOC_ES9356 tristate "Everest Semi ES9356 CODEC SDW" depends on SND_SOC_SDCA diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 9287a602d41e8e..1679c3ffc63434 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -140,6 +140,7 @@ snd-soc-es8328-i2c-y := es8328-i2c.o snd-soc-es8328-spi-y := es8328-spi.o snd-soc-es8375-y := es8375.o snd-soc-es8389-y := es8389.o +snd-soc-es9039q2m-y := es9039q2m.o snd-soc-es9356-y := es9356.o snd-soc-framer-y := framer-codec.o snd-soc-fs-amp-lib-y := fs-amp-lib.o @@ -586,6 +587,7 @@ obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o obj-$(CONFIG_SND_SOC_ES8375) += snd-soc-es8375.o obj-$(CONFIG_SND_SOC_ES8389) += snd-soc-es8389.o +obj-$(CONFIG_SND_SOC_ES9039Q2M) += snd-soc-es9039q2m.o obj-$(CONFIG_SND_SOC_ES9356) += snd-soc-es9356.o obj-$(CONFIG_SND_SOC_FRAMER) += snd-soc-framer.o obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+= snd-soc-fs-amp-lib.o diff --git a/sound/soc/codecs/es9039q2m.c b/sound/soc/codecs/es9039q2m.c new file mode 100644 index 00000000000000..4e9f86cb786063 --- /dev/null +++ b/sound/soc/codecs/es9039q2m.c @@ -0,0 +1,2008 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// ESS Technology ES9039Q2M 32-bit 2-channel audio DAC +// +// Copyright (C) 2026 Karl Asseily +// +// Every register number, bit field and default in this file was taken from +// ES9039Q2M datasheet v0.2.3 and then verified by reading the defaults back off +// a live part over I2C. +// +// The part has two control personalities selected by the MODE pin: hardware +// mode (strapped by HW0/HW1/HW2, no bus at all) and software mode (I2C or SPI). +// This driver implements software mode over I2C, which MODE = GND selects. +// +// Three properties shape the driver: +// +// - There is an ASRC in front of the DAC, so MCLK need not be synchronous with +// BCLK or LRCK. A board may feed it a fixed oscillator, or may run it +// synchronously and let the part generate BCLK and WS; both are supported. +// +// - INPUT_SEL chooses PCM / DSD / DoP / S/PDIF. It does NOT choose I2S vs +// left-justified - that is TDM_LJ_MODE in register 60, and there is no +// right-justified mode to map onto at all. +// +// - Several registers have non-zero reserved defaults (register 88 reads +// 0xb8 at reset), so every write here is read-modify-write. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* ------------------------------------------------- read/write registers ---- */ + +#define ES9039_SYSTEM_CONFIG 0x00 /* reg 0 */ +#define ES9039_DAC_MODE BIT(1) /* CLEAR at reset - datapath off */ +#define ES9039_64FS_MODE BIT(6) + +/* + * Register 1 selects which DECODERS are running, and it is separate from + * INPUT_SEL in register 57, which only says which port to listen to. Both are + * needed: on reset only ENABLE_TDM_DECODE is set, so selecting DoP as the input + * while leaving bit 2 clear leaves the part hunting for a marker with the + * marker decoder switched off. It then finds no valid DoP and mutes - silence, + * DOP_VALID reading 0, and nothing anywhere saying why. Measured on hardware. + */ +#define ES9039_SYS_MODE 0x01 /* reg 1, reset 0xb1 */ +#define ES9039_ENABLE_TDM_DECODE BIT(0) /* set at reset */ +#define ES9039_ENABLE_DSD_DECODE BIT(1) +#define ES9039_ENABLE_DOP_DECODE BIT(2) +#define ES9039_ENABLE_SPDIF_DECODE BIT(3) +#define ES9039_SYNC_MODE BIT(6) /* 0 = ASYNC */ +#define ES9039_ENABLE_DAC_CLK BIT(7) /* set at reset */ +/* + * Every decoder the part has. hw_params() enables one of these and clears the + * rest, so leaving S/PDIF out of the mask would have left its decoder running + * alongside the selected one. + */ +#define ES9039_DECODE_MASK (ES9039_ENABLE_TDM_DECODE | \ + ES9039_ENABLE_DSD_DECODE | \ + ES9039_ENABLE_DOP_DECODE | \ + ES9039_ENABLE_SPDIF_DECODE) + +#define ES9039_AUTO_FS_DETECT 0x03 /* reg 3 */ +#define ES9039_AUTO_FS_DETECT_EN BIT(7) + +#define ES9039_CLOCK_CONFIG 0x04 /* reg 4, MASTER_BCK_DIV */ + +#define ES9039_INPUT_SEL 0x39 /* reg 57 */ +#define ES9039_AUTO_INPUT_SEL BIT(0) +#define ES9039_INPUT_SEL_MASK GENMASK(2, 1) +#define ES9039_INPUT_PCM 0x0 +#define ES9039_INPUT_DSD 0x1 +#define ES9039_INPUT_DOP 0x2 +#define ES9039_INPUT_SPDIF 0x3 +#define ES9039_PCM_MASTER_MODE BIT(4) +#define ES9039_DSD_MASTER_MODE BIT(5) +#define ES9039_DSD_FAULT_DETECT BIT(6) /* set at reset */ + +#define ES9039_MASTER_ENC 0x3a /* reg 58 */ +#define ES9039_BCK_INV BIT(6) + +#define ES9039_TDM_CH_NUM 0x3b /* reg 59, slots = value + 1 */ +#define ES9039_TDM_CH_NUM_MASK GENMASK(4, 0) + +#define ES9039_TDM_CONFIG1 0x3c /* reg 60 */ +#define ES9039_TDM_VALID_EDGE BIT(6) +#define ES9039_TDM_LJ_MODE BIT(7) /* 0 = standard I2S */ + +#define ES9039_TDM_CONFIG2 0x3d /* reg 61 */ +#define ES9039_TDM_BIT_WIDTH_MASK GENMASK(6, 5) +#define ES9039_WIDTH_32 0x0 +#define ES9039_WIDTH_24 0x1 +#define ES9039_WIDTH_16 0x2 + +#define ES9039_MONITOR_CFG 0x3e /* reg 62 */ +#define ES9039_DISABLE_PCM_DC BIT(3) +#define ES9039_ENABLE_BCK_MONITOR BIT(4) /* set at reset */ +#define ES9039_ENABLE_WS_MONITOR BIT(5) /* set at reset */ +#define ES9039_DISABLE_DSD_MUTE BIT(6) +#define ES9039_DISABLE_DSD_DC BIT(7) + +#define ES9039_VOLUME_CH1 0x4a /* reg 74, 0x00 = 0 dB */ +#define ES9039_VOLUME_CH2 0x4b /* reg 75, 0xff = -127.5 dB */ +#define ES9039_VOL_MAX 0xff + +#define ES9039_VOL_RATE_UP 0x52 /* reg 82 */ +#define ES9039_VOL_RATE_DOWN 0x53 /* reg 83 */ + +#define ES9039_DAC_MUTE 0x56 /* reg 86, 1 = muted */ +#define ES9039_MUTE_CH1 BIT(0) +#define ES9039_MUTE_CH2 BIT(1) +#define ES9039_MUTE_BOTH (ES9039_MUTE_CH1 | ES9039_MUTE_CH2) + +#define ES9039_DAC_INVERT 0x57 /* reg 87 */ + +#define ES9039_FILTER_SHAPE 0x58 /* reg 88, [7:3] reset to 10111 */ +#define ES9039_FILTER_SHAPE_MASK GENMASK(2, 0) +#define ES9039_FILTER_APODIZING 1 /* linear phase apodizing fast */ + +#define ES9039_IIR_SPDIF 0x59 /* reg 89 */ +#define ES9039_IIR_BW_MASK GENMASK(2, 0) +#define ES9039_VOLUME_HOLD BIT(3) +#define ES9039_SPDIF_SEL_MASK GENMASK(7, 4) + +#define ES9039_DAC_PATH 0x5a /* reg 90 */ +#define ES9039_BYPASS_FIR2X BIT(0) +#define ES9039_BYPASS_FIR4X BIT(1) +#define ES9039_BYPASS_IIR BIT(2) + +#define ES9039_THD_C2 0x5b /* regs 91-94: CH1 lo, CH2 hi */ +#define ES9039_THD_C3 0x6b /* regs 107-110 */ + +#define ES9039_AUTOMUTE_EN 0x7b /* reg 123, both set at reset */ +#define ES9039_AUTOMUTE_TIME 0x7c /* regs 124-125 */ +#define ES9039_AUTOMUTE_TIME_MASK GENMASK(10, 0) +#define ES9039_MUTE_RAMP_TO_GND BIT(11) /* set at reset */ +#define ES9039_AUTOMUTE_LEVEL 0x7e /* regs 126-127 */ +#define ES9039_AUTOMUTE_OFF_LEVEL 0x80 /* regs 128-129 */ + +#define ES9039_SOFT_RAMP 0x82 /* reg 130, valid 0..12 */ +#define ES9039_SOFT_RAMP_MASK GENMASK(4, 0) +#define ES9039_SOFT_RAMP_MAX 12 + +#define ES9039_NSMOD 0x83 /* reg 131 */ +#define ES9039_NSMOD_WIDE_BW_MASK GENMASK(4, 1) +#define ES9039_NSMOD_DEFAULT 0x4 +#define ES9039_NSMOD_WIDE 0xc + +#define ES9039_PROG_RAM_CTRL 0x87 /* reg 135 */ +#define ES9039_PROG_COEFF_EN BIT(0) +#define ES9039_PROG_COEFF_WE BIT(1) + +#define ES9039_PROG_RAM_ADDR 0x89 /* reg 137 */ +#define ES9039_PROG_ADDR_MASK GENMASK(6, 0) +#define ES9039_PROG_STAGE_4X BIT(7) + +#define ES9039_PROG_RAM_DATA 0x8a /* regs 138-140, 24-bit signed */ + +#define ES9039_LAST_RW 0x8e /* reg 145 */ + +/* ----------------------------------------------------- readback registers -- */ + +#define ES9039_READBACK_BASE 0xe0 /* reg 224 */ + +#define ES9039_CHIP_ID 0xe1 /* reg 225 */ +#define ES9039_CHIP_ID_ES9039Q2M 0x63 + +#define ES9039_IRQ_SOURCES 0xea /* regs 234-235, 16-bit */ +#define ES9039_SRC_VOL_MIN_MASK GENMASK(1, 0) +#define ES9039_SRC_AUTOMUTE_MASK GENMASK(3, 2) +#define ES9039_SRC_SS_RAMP_MASK GENMASK(5, 4) +#define ES9039_SRC_DOP_VALID BIT(6) +#define ES9039_SRC_BCK_WS_FAIL BIT(7) +#define ES9039_SRC_TDM_VALID BIT(11) + +#define ES9039_AUTO_FS_READ 0xef /* reg 239 */ +#define ES9039_FS_DIV_MASK GENMASK(5, 0) +#define ES9039_FS_HALF_DIV BIT(6) +#define ES9039_FS_DIV_VALID BIT(7) + +#define ES9039_AUTOMUTE_READ 0xf2 /* reg 242 */ + +#define ES9039_INPUT_STREAM_READ 0xf5 /* reg 245 */ +#define ES9039_RD_INPUT_SEL_MASK GENMASK(1, 0) +#define ES9039_RD_DOP_VALID BIT(2) +#define ES9039_RD_TDM_VALID BIT(3) +#define ES9039_RD_SPDIF_VALID BIT(4) + +#define ES9039_MAX_REGISTER 0xfb /* reg 251 */ + +/* Programmable oversampling FIR: 128 taps in the 2x stage, 32 in the 4x. */ +#define ES9039_FIR2X_TAPS 128 +#define ES9039_FIR4X_TAPS 32 +#define ES9039_COEFF_BYTES 3 +#define ES9039_FIR_STAGES 2 /* [0] = 2x stage, [1] = 4x */ + +/* In power-up order. Reversed on the way down. Datasheet figure 22. */ +#define ES9039_NUM_SUPPLIES 4 +#define ES9039_FIR_MAX_BYTES (ES9039_FIR2X_TAPS * ES9039_COEFF_BYTES) + +/* ------------------------------------------------------------------ private */ + +struct es9039q2m_priv { + struct regmap *regmap; + struct clk *mclk; + unsigned int mclk_rate; + unsigned int fmt; + unsigned int stream_rate; /* last rate from hw_params */ + unsigned int bclk_ratio; /* bit clocks per frame, 0 = unknown */ + + /* + * Serialises dop_auto against hw_params(), which reads it while + * deciding what to program. Without it a control write racing a + * stream start can leave the two DoP registers disagreeing. + */ + struct mutex lock; + bool dop_auto; /* let the part detect DoP itself */ + + /* + * ES9039_DAC_MUTE has two owners and neither can hold it alone: the + * "Master Playback Switch" control, and mute_stream() around stream + * start and stop. Both record what they want here and the register is + * written from the union, so a user unmute cannot un-mute a stopped + * stream and a stream start cannot override a user mute. + * + * mute_stream starts FALSE and only a real mute_stream(1) sets it. + * Starting it true seemed the safe choice - come up muted - but it is + * not: if a card never reaches the DAI mute callbacks, the flag never + * clears, and because the control now records intent rather than + * writing the register there is nothing the user can do about it. The + * DAC is then muted for ever and the mixer looks fine. Measured on + * hardware: silent with the switch on, off, stream or no stream. Pop + * suppression at probe comes from writing the register + * directly instead, which is what the driver did before this rework + * and is a state either owner may legitimately lift. + */ + unsigned int mute_user; /* ES9039_MUTE_CH* the user asked for */ + bool mute_stream; /* ASoC has the stream muted */ + + bool provider; /* part drives BCK and WS */ + bool mclk_fixed; /* MCLK rate cannot be changed, so ASRC it is */ + + /* + * The four supply inputs, held IN POWER-UP ORDER - the array order is + * the datasheet's sequence and the enable loop walks it forwards, the + * disable loop backwards. DVDD is not among them: the part's 1.2 V + * digital rail is generated on chip and that pin wants a decoupling + * capacitor, not a regulator. + */ + struct regulator_bulk_data supplies[ES9039_NUM_SUPPLIES]; + + /* + * Shadow of whatever was last uploaded to the programmable FIR RAM. + * + * That RAM cannot be read back - see the upload code below - so regmap + * cannot cache it and nothing else in the system knows its contents. + * A board that removes the part's supplies in system suspend therefore + * comes back with the RAM undefined while regcache_sync() restores the + * filter SELECTION from the cache, which would point the interpolator + * at whatever the RAM powered up holding. Keep a copy and re-upload. + */ + u8 fir_shadow[ES9039_FIR_STAGES][ES9039_FIR_MAX_BYTES]; + unsigned int fir_taps[ES9039_FIR_STAGES]; /* 0 = never uploaded */ +}; + +/* + * Multi-byte fields are little-endian across ascending register addresses: + * register N holds bits [7:0], N+1 holds [15:8], and so on. + */ +static int es9039_read_le(struct regmap *map, unsigned int reg, int n, u32 *out) +{ + u8 buf[4]; + int ret, i; + + ret = regmap_bulk_read(map, reg, buf, n); + if (ret) + return ret; + + *out = 0; + for (i = 0; i < n; i++) + *out |= (u32)buf[i] << (8 * i); + + return 0; +} + +static int es9039_write_le(struct regmap *map, unsigned int reg, int n, u32 val) +{ + u8 buf[4]; + int i; + + for (i = 0; i < n; i++) + buf[i] = (val >> (8 * i)) & 0xff; + + return regmap_bulk_write(map, reg, buf, n); +} + +/* ------------------------------------------------------------------ regmap */ + +static bool es9039q2m_writeable_reg(struct device *dev, unsigned int reg) +{ + return reg <= ES9039_LAST_RW; +} + +static bool es9039q2m_readable_reg(struct device *dev, unsigned int reg) +{ + return reg <= ES9039_LAST_RW || reg >= ES9039_READBACK_BASE; +} + +static bool es9039q2m_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg >= ES9039_READBACK_BASE; +} + +static const struct regmap_config es9039q2m_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ES9039_MAX_REGISTER, + .writeable_reg = es9039q2m_writeable_reg, + .readable_reg = es9039q2m_readable_reg, + .volatile_reg = es9039q2m_volatile_reg, + .cache_type = REGCACHE_MAPLE, +}; + +/* --------------------------------------------------- signed 16-bit controls */ + +struct es9039_s16_ctl { + unsigned int reg; + unsigned int shift; /* 0 for CH1, 16 for CH2 within the 32-bit pair */ +}; + +static int es9039_s16_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = S16_MIN; + uinfo->value.integer.max = S16_MAX; + return 0; +} + +static int es9039_s16_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_s16_ctl *p = (void *)kcontrol->private_value; + u32 v; + int ret; + + ret = es9039_read_le(priv->regmap, p->reg + (p->shift / 8), 2, &v); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = (s16)v; + return 0; +} + +static int es9039_s16_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_s16_ctl *p = (void *)kcontrol->private_value; + long v = ucontrol->value.integer.value[0]; + u32 old; + int ret; + + if (v < S16_MIN || v > S16_MAX) + return -EINVAL; + + ret = es9039_read_le(priv->regmap, p->reg + (p->shift / 8), 2, &old); + if (ret) + return ret; + + if ((s16)old == (s16)v) + return 0; + + ret = es9039_write_le(priv->regmap, p->reg + (p->shift / 8), 2, + (u16)v); + if (ret) + return ret; + + return 1; +} + +#define ES9039_S16(xname, xreg, xshift) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = es9039_s16_info, \ + .get = es9039_s16_get, \ + .put = es9039_s16_put, \ + .private_value = (unsigned long)&(struct es9039_s16_ctl) \ + { .reg = xreg, .shift = xshift }, \ +} + +/* ------------------------------------------------- multi-register integers */ + +struct es9039_wide_ctl { + unsigned int reg; + unsigned int bytes; + unsigned int mask; + unsigned int max; +}; + +static int es9039_wide_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct es9039_wide_ctl *p = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = p->max; + return 0; +} + +static int es9039_wide_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_wide_ctl *p = (void *)kcontrol->private_value; + u32 v; + int ret; + + ret = es9039_read_le(priv->regmap, p->reg, p->bytes, &v); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = v & p->mask; + return 0; +} + +static int es9039_wide_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_wide_ctl *p = (void *)kcontrol->private_value; + long v = ucontrol->value.integer.value[0]; + u32 old; + int ret, i; + + if (v < 0 || v > p->max) + return -EINVAL; + + ret = es9039_read_le(priv->regmap, p->reg, p->bytes, &old); + if (ret) + return ret; + + if ((old & p->mask) == (u32)v) + return 0; + + /* + * Write one register at a time through regmap_update_bits() rather + * than reading the whole field and writing it back. + * + * The bits outside the field are not ours: ES9039_AUTOMUTE_TIME spans + * registers 124-125 while MUTE_RAMP_TO_GND is bit 3 of register 125 + * and has a control of its own. A read-modify-write over the pair + * leaves a window in which that other control can write between our + * read and our write, and its change is then lost - the ALSA put path + * holds card->controls_rwsem for READ, so two controls really can run + * at once. Per-register update_bits closes the window inside regmap's + * own lock and never touches a bit outside p->mask. + */ + for (i = 0; i < p->bytes; i++) { + u8 mask = (p->mask >> (8 * i)) & 0xff; + + if (!mask) + continue; + + ret = regmap_update_bits(priv->regmap, p->reg + i, mask, + ((u32)v >> (8 * i)) & mask); + if (ret) + return ret; + } + + return 1; +} + +#define ES9039_WIDE(xname, xreg, xbytes, xmask, xmax) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = es9039_wide_info, \ + .get = es9039_wide_get, \ + .put = es9039_wide_put, \ + .private_value = (unsigned long)&(struct es9039_wide_ctl) \ + { .reg = xreg, .bytes = xbytes, \ + .mask = xmask, .max = xmax }, \ +} + +/* ------------------------------------------------ programmable FIR upload -- + * + * Write-only, deliberately. The chip has a PROG_COEFF_OUT register (248-246) + * described only as "Programmable FIR coefficient readback", but it is not a + * RAM read port: it returns the LAST COEFFICIENT WRITTEN, whatever address is + * selected in PROG_COEFF_ADDR. Measured over raw I2C with this driver out of + * the path - two different coefficients written to + * addresses 0 and 1, then read back with five different sequences (plain, with + * a settle delay, with PROG_COEFF_EN set, with the address written twice, and + * with a WE pulse after the address). All ten reads returned the value written + * to address 1. + * + * A get() built on that register would return something with the shape of data + * and none of its meaning, so there is no get(). If ESS documents a real + * readback sequence, add one. + */ + +/* + * Per-control data rides in our own struct with the soc_bytes_ext EMBEDDED, + * recovered by container_of. Not in soc_bytes_ext.dobj: that field belongs to + * the topology subsystem and only exists under CONFIG_SND_SOC_TOPOLOGY, so a + * driver stashing its own data there fails to build on any config without it. + */ +struct es9039_fir_ctl { + struct soc_bytes_ext be; + unsigned int taps; + bool stage_4x; +}; + +/* + * Push one stage's coefficients into the RAM. Callers hold priv->lock: both + * FIR controls drive the same address, data and strobe registers, so two + * uploads at once would interleave into each other's RAM, and resume re-uploads + * through this same path. + * + * PROG_COEFF_WE is a per-coefficient strobe, not a gate held open across the + * upload. The datasheet's sequence is address, data, raise WE, lower WE, once + * per coefficient. Holding it high for the whole loop also appears to work on + * ES9039Q2M silicon, but "appears to work" is not a specification. + */ +static int es9039_fir_upload(struct es9039q2m_priv *priv, const u8 *data, + unsigned int taps, bool stage_4x) +{ + unsigned int i; + int ret = 0; + + for (i = 0; i < taps; i++) { + ret = regmap_write(priv->regmap, ES9039_PROG_RAM_ADDR, + (stage_4x ? ES9039_PROG_STAGE_4X : 0) | + FIELD_PREP(ES9039_PROG_ADDR_MASK, i)); + if (ret) + goto out; + + ret = regmap_bulk_write(priv->regmap, ES9039_PROG_RAM_DATA, + &data[i * ES9039_COEFF_BYTES], + ES9039_COEFF_BYTES); + if (ret) + goto out; + + ret = regmap_update_bits(priv->regmap, ES9039_PROG_RAM_CTRL, + ES9039_PROG_COEFF_WE, + ES9039_PROG_COEFF_WE); + if (ret) + goto out; + + ret = regmap_update_bits(priv->regmap, ES9039_PROG_RAM_CTRL, + ES9039_PROG_COEFF_WE, 0); + if (ret) + goto out; + } + +out: + regmap_update_bits(priv->regmap, ES9039_PROG_RAM_CTRL, + ES9039_PROG_COEFF_WE, 0); + return ret; +} + +static int es9039_fir_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *bytes, unsigned int size) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct soc_bytes_ext *be = (void *)kcontrol->private_value; + struct es9039_fir_ctl *p = container_of(be, struct es9039_fir_ctl, be); + unsigned int idx = p->stage_4x ? 1 : 0; + u8 *buf; + int ret; + + if (size != p->taps * ES9039_COEFF_BYTES) + return -EINVAL; + + buf = memdup_user(bytes, size); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + scoped_guard(mutex, &priv->lock) { + ret = es9039_fir_upload(priv, buf, p->taps, p->stage_4x); + if (!ret) { + /* + * Keep a copy. The RAM cannot be read back, so this is + * the only record of what is in it, and resume has + * nothing else to restore from. + */ + memcpy(priv->fir_shadow[idx], buf, size); + priv->fir_taps[idx] = p->taps; + } + } + + kfree(buf); + if (ret) + return ret; + + return 1; +} + +static struct es9039_fir_ctl es9039_fir2x = { + .be = { .max = ES9039_FIR2X_TAPS * ES9039_COEFF_BYTES, + .put = es9039_fir_put }, + .taps = ES9039_FIR2X_TAPS, +}; + +static struct es9039_fir_ctl es9039_fir4x = { + .be = { .max = ES9039_FIR4X_TAPS * ES9039_COEFF_BYTES, + .put = es9039_fir_put }, + .taps = ES9039_FIR4X_TAPS, + .stage_4x = true, +}; + +#define ES9039_FIR(xname, xctl) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = snd_soc_bytes_info_ext, \ + .tlv.c = snd_soc_bytes_tlv_callback, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_WRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \ + .private_value = (unsigned long)&(xctl).be, \ +} + +/* ------------------------------------------------------- status (read-only) */ + +struct es9039_stat_ctl { + unsigned int reg; + unsigned int mask; + unsigned int max; +}; + +static int es9039_stat_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct es9039_stat_ctl *p = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = p->max; + return 0; +} + +static int es9039_stat_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_stat_ctl *p = (void *)kcontrol->private_value; + unsigned int v; + int ret; + + ret = regmap_read(priv->regmap, p->reg, &v); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = + (v & p->mask) >> (ffs(p->mask) - 1); + return 0; +} + +#define ES9039_STAT(xname, xreg, xmask, xmax) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | \ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = es9039_stat_info, \ + .get = es9039_stat_get, \ + .private_value = (unsigned long)&(struct es9039_stat_ctl) \ + { .reg = xreg, .mask = xmask, .max = xmax }, \ +} + +/* + * Detected sample rate. + * + * When the part's own rate detector has a valid ratio, use it - it is measured + * from the incoming frame clock and is the ground truth: + * + * FS = Y * SYS_CLK / ((X + 1) * (128 >> 64FS_MODE)) + * + * with X = IDAC_DIV_AUTO and Y = 2 when IDAC_HALF_DIV_AUTO reports a + * half-integer multiple. + * + * That detector is UNAVAILABLE on any board running the DAC asynchronously - + * register 3[7] AUTO_FS_DETECT carries the note "Cannot be used in ASYNC mode". + * A board feeding a free-running oscillator and letting the ASRC absorb the + * difference is precisely that case, and it is the preferable design, so the + * detector reading 0 there is expected rather than a fault. Fall back to the + * rate the stream was opened at, which is what a front panel wants to show. + * Reports 0 only when nothing is playing and the chip has no lock either. + */ +static int es9039_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1536000; + return 0; +} + +static int es9039_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + unsigned int fsreg, sysreg, div, y, den; + int ret; + + ucontrol->value.integer.value[0] = priv->stream_rate; + + if (!priv->mclk_rate) + return 0; + + ret = regmap_read(priv->regmap, ES9039_AUTO_FS_READ, &fsreg); + if (ret) + return ret; + + if (!(fsreg & ES9039_FS_DIV_VALID)) + return 0; /* async mode: keep the stream rate set above */ + + ret = regmap_read(priv->regmap, ES9039_SYSTEM_CONFIG, &sysreg); + if (ret) + return ret; + + div = FIELD_GET(ES9039_FS_DIV_MASK, fsreg) + 1; + y = (fsreg & ES9039_FS_HALF_DIV) ? 2 : 1; + den = div * ((sysreg & ES9039_64FS_MODE) ? 64 : 128); + + ucontrol->value.integer.value[0] = + DIV_ROUND_CLOSEST(priv->mclk_rate * y, den); + return 0; +} + +static const char * const es9039_stream_texts[] = { + "PCM", "DSD", "DoP", "S/PDIF", +}; + +static int es9039_stream_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(es9039_stream_texts), + es9039_stream_texts); +} + +static int es9039_stream_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + unsigned int v; + int ret; + + ret = regmap_read(priv->regmap, ES9039_INPUT_STREAM_READ, &v); + if (ret) + return ret; + + ucontrol->value.enumerated.item[0] = + FIELD_GET(ES9039_RD_INPUT_SEL_MASK, v); + return 0; +} + +/* ----------------------------------------------------------------- controls */ + +static const DECLARE_TLV_DB_SCALE(es9039_vol_tlv, -12750, 50, 1); + +static const char * const es9039_filter_texts[] = { + "Minimum Phase", + "Linear Phase Apodizing Fast Roll-Off", + "Linear Phase Fast Roll-Off", + "Linear Phase Fast Roll-Off Low Ripple", + "Linear Phase Slow Roll-Off", + "Minimum Phase Fast Roll-Off", + "Minimum Phase Slow Roll-Off", + "Minimum Phase Slow Roll-Off Low Dispersion", +}; + +static SOC_ENUM_SINGLE_DECL(es9039_filter_enum, ES9039_FILTER_SHAPE, 0, + es9039_filter_texts); + +/* IIR_BW is a multiple of the datapath bandwidth, not a frequency. */ +static const char * const es9039_iir_texts[] = { + "Reserved", "BW x8", "BW x4", "BW x2", "BW", "BW /2", "BW /4", "BW /8", +}; + +static SOC_ENUM_SINGLE_DECL(es9039_iir_enum, ES9039_IIR_SPDIF, 0, + es9039_iir_texts); + +static const char * const es9039_nsmod_texts[] = { + "Default", "Wide Bandwidth", +}; + +static const unsigned int es9039_nsmod_values[] = { + ES9039_NSMOD_DEFAULT, ES9039_NSMOD_WIDE, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(es9039_nsmod_enum, ES9039_NSMOD, 1, + GENMASK(3, 0), es9039_nsmod_texts, + es9039_nsmod_values); + +static int es9039_dop_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + + ucontrol->value.integer.value[0] = priv->dop_auto; + return 0; +} + +/* + * Automatic DoP detection is two bits in two registers, and they are one + * setting: AUTO_INPUT_SEL lets the part choose a decoder from the data, and + * ENABLE_DOP_DECODE is what gives it a DoP decoder to choose. Enabling the + * first without the second leaves the part hunting for a marker it has no + * decoder for, whereupon it finds no valid DoP and mutes. + * + * Everything that changes the setting goes through here, so the pair can + * never be written apart. Callers hold priv->lock. + */ +static int es9039_apply_dop(struct es9039q2m_priv *priv, bool on) +{ + int ret; + + ret = regmap_update_bits(priv->regmap, ES9039_SYS_MODE, + ES9039_ENABLE_DOP_DECODE, + on ? ES9039_ENABLE_DOP_DECODE : 0); + if (ret) + return ret; + + return regmap_update_bits(priv->regmap, ES9039_INPUT_SEL, + ES9039_AUTO_INPUT_SEL, + on ? ES9039_AUTO_INPUT_SEL : 0); +} + +/* Caller holds priv->lock. */ +static int es9039_apply_mute(struct es9039q2m_priv *priv) +{ + unsigned int val = priv->mute_user; + + if (priv->mute_stream) + val = ES9039_MUTE_BOTH; + + return regmap_update_bits(priv->regmap, ES9039_DAC_MUTE, + ES9039_MUTE_BOTH, val); +} + +/* + * The control is inverted - 1 means playing - so a zero here is a mute + * request. It reports priv->mute_user rather than the register, because the + * register also carries the stream mute and the user did not ask for that. + */ +static int es9039_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + + guard(mutex)(&priv->lock); + + ucontrol->value.integer.value[0] = !(priv->mute_user & ES9039_MUTE_CH1); + ucontrol->value.integer.value[1] = !(priv->mute_user & ES9039_MUTE_CH2); + + return 0; +} + +static int es9039_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + unsigned int val = 0, old; + int ret; + + if (!ucontrol->value.integer.value[0]) + val |= ES9039_MUTE_CH1; + if (!ucontrol->value.integer.value[1]) + val |= ES9039_MUTE_CH2; + + guard(mutex)(&priv->lock); + + if (val == priv->mute_user) + return 0; + + /* + * Commit to the register first. If the write fails and the shadow has + * already moved, get() reports a mute the part is not in and the + * val == mute_user test above turns a retry with the same value into a + * no-op, leaving the control permanently wrong. + */ + old = priv->mute_user; + priv->mute_user = val; + + ret = es9039_apply_mute(priv); + if (ret) { + priv->mute_user = old; + return ret; + } + + return 1; +} + +static int es9039_dop_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + bool on = !!ucontrol->value.integer.value[0], old; + int ret; + + guard(mutex)(&priv->lock); + + if (on == priv->dop_auto) + return 0; + + /* + * hw_params() programs the decoder and INPUT_SEL from dop_auto, so + * changing it under a running stream would apply half the setting now + * and the rest at the next open. Refuse instead of half-applying. + */ + if (priv->stream_rate) + return -EBUSY; + + /* Same reasoning as es9039_mute_put(): the shadow follows the write. */ + old = priv->dop_auto; + priv->dop_auto = on; + + ret = es9039_apply_dop(priv, on); + if (ret) { + priv->dop_auto = old; + return ret; + } + + return 1; +} + +/* + * The two channel volumes live in separate registers, so a stereo change is + * two I2C writes and the channels are briefly at different levels in between. + * VOLUME_HOLD exists for exactly this: while it is set the part accepts writes + * to registers 74-75 without applying them, and clearing it applies both at + * once. Raise it, let the generic handler do the writes, drop it again. + * + * It is dropped unconditionally, including on the error path, because a stuck + * VOLUME_HOLD would silently freeze the volume control. + */ +static int es9039_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + int ret; + + /* + * The hold has to cover the pair of channel writes, so it has to be + * serialised. Two callers interleaving would let the second write its + * volumes after the first had already dropped VOLUME_HOLD, which is + * exactly the torn stereo update the bit exists to prevent. + */ + guard(mutex)(&priv->lock); + + ret = regmap_update_bits(priv->regmap, ES9039_IIR_SPDIF, + ES9039_VOLUME_HOLD, ES9039_VOLUME_HOLD); + if (ret) + return ret; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + + regmap_update_bits(priv->regmap, ES9039_IIR_SPDIF, + ES9039_VOLUME_HOLD, 0); + + return ret; +} + +static const struct snd_kcontrol_new es9039q2m_controls[] = { + /* --- level --- */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_soc_info_volsw, + .get = snd_soc_get_volsw, + .put = es9039_vol_put, + .tlv.p = es9039_vol_tlv, + .private_value = SOC_DOUBLE_R_VALUE(ES9039_VOLUME_CH1, + ES9039_VOLUME_CH2, 0, 0, + ES9039_VOL_MAX, 1), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_soc_info_volsw, + .get = es9039_mute_get, + .put = es9039_mute_put, + .private_value = SOC_DOUBLE_VALUE(ES9039_DAC_MUTE, 0, 1, 0, 1, + 1, 0), + }, + SOC_DOUBLE("DAC Invert Switch", ES9039_DAC_INVERT, 0, 1, 1, 0), + SOC_SINGLE("Volume Ramp Up Rate", ES9039_VOL_RATE_UP, 0, 255, 0), + SOC_SINGLE("Volume Ramp Down Rate", ES9039_VOL_RATE_DOWN, 0, 255, 0), + SOC_SINGLE("Soft Ramp Time", ES9039_SOFT_RAMP, 0, + ES9039_SOFT_RAMP_MAX, 0), + + /* --- reconstruction filter --- */ + SOC_ENUM("Filter Shape", es9039_filter_enum), + SOC_ENUM("IIR Bandwidth", es9039_iir_enum), + SOC_ENUM("Modulator Bandwidth", es9039_nsmod_enum), + SOC_SINGLE("IIR Filter Bypass Switch", ES9039_DAC_PATH, 2, 1, 0), + SOC_SINGLE("FIR 2x Bypass Switch", ES9039_DAC_PATH, 0, 1, 0), + SOC_SINGLE("FIR 4x Bypass Switch", ES9039_DAC_PATH, 1, 1, 0), + SOC_SINGLE("Custom FIR Switch", ES9039_PROG_RAM_CTRL, 0, 1, 0), + ES9039_FIR("FIR 2x Coefficients", es9039_fir2x), + ES9039_FIR("FIR 4x Coefficients", es9039_fir4x), + + /* + * Not "... Volume", because these do not set a level. They are signed + * correction coefficients for the second and third harmonic: they + * cancel distortion the analogue stage adds, and turning one up does + * not make anything louder. Useful values come from measuring a given + * board's distortion on an analyser and solving for them; zero, the + * reset value, is the only honest default until someone has. + */ + /* --- distortion compensation --- */ + ES9039_S16("THD Compensation C2 CH1", ES9039_THD_C2, 0), + ES9039_S16("THD Compensation C2 CH2", ES9039_THD_C2, 16), + ES9039_S16("THD Compensation C3 CH1", ES9039_THD_C3, 0), + ES9039_S16("THD Compensation C3 CH2", ES9039_THD_C3, 16), + + /* --- automute --- */ + SOC_DOUBLE("Automute Switch", ES9039_AUTOMUTE_EN, 0, 1, 1, 0), + ES9039_WIDE("Automute Time", ES9039_AUTOMUTE_TIME, 2, + ES9039_AUTOMUTE_TIME_MASK, 2047), + ES9039_WIDE("Automute Level", ES9039_AUTOMUTE_LEVEL, 2, 0xffff, 65535), + ES9039_WIDE("Automute Off Level", ES9039_AUTOMUTE_OFF_LEVEL, 2, + 0xffff, 65535), + SOC_SINGLE("Mute Ramp To Ground Switch", ES9039_AUTOMUTE_TIME + 1, + 3, 1, 0), + SOC_SINGLE("DSD DC Automute Switch", ES9039_MONITOR_CFG, 7, 1, 1), + SOC_SINGLE("DSD Mute Pattern Switch", ES9039_MONITOR_CFG, 6, 1, 1), + SOC_SINGLE("PCM DC Automute Switch", ES9039_MONITOR_CFG, 3, 1, 1), + + /* --- stream --- */ + /* + * On by default. DoP is designed to be detected, not announced: the + * player just sends it and a DoP-aware DAC notices the marker, which + * is why a DAC that does not notice plays it as near-silence rather + * than noise. Players rely on that, and none of them can reach into + * ALSA to flip a mode first. + * + * Left switchable because automatic detection is a pattern match, and + * anyone worried about PCM material that happens to look like DoP can + * turn it off and get strictly PCM. + */ + SOC_SINGLE_BOOL_EXT("DoP Auto Detect Switch", 0, + es9039_dop_get, es9039_dop_put), + + /* --- status, read-only --- */ + ES9039_STAT("Automute Active CH1", ES9039_AUTOMUTE_READ, BIT(0), 1), + ES9039_STAT("Automute Active CH2", ES9039_AUTOMUTE_READ, BIT(1), 1), + ES9039_STAT("DoP Valid", ES9039_INPUT_STREAM_READ, + ES9039_RD_DOP_VALID, 1), + ES9039_STAT("TDM Data Valid", ES9039_INPUT_STREAM_READ, + ES9039_RD_TDM_VALID, 1), + ES9039_STAT("SPDIF Valid", ES9039_INPUT_STREAM_READ, + ES9039_RD_SPDIF_VALID, 1), + /* + * Register 235-234 is one 16-bit word and BCK_WS_FAIL_SOURCE is bit + * 7 of it, so it sits in the low byte at ES9039_IRQ_SOURCES. This + * read the byte above with the same mask, which is bit 15 - reserved + * - so the control could never have reported a fault. The named + * constant now carries the bit rather than a bare BIT(7). + * + * The flag is meaningful only with the BCK and WS monitors enabled, + * which they are at reset and the driver leaves alone. + */ + ES9039_STAT("Clock Fault", ES9039_IRQ_SOURCES, + ES9039_SRC_BCK_WS_FAIL, 1), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Detected Sample Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = es9039_rate_info, + .get = es9039_rate_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Detected Input Format", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = es9039_stream_info, + .get = es9039_stream_get, + }, +}; + +/* --------------------------------------------------------------------- DAPM */ + +static const struct snd_soc_dapm_widget es9039q2m_widgets[] = { + SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("AOUTL"), + SND_SOC_DAPM_OUTPUT("AOUTR"), +}; + +static const struct snd_soc_dapm_route es9039q2m_routes[] = { + { "DAC", NULL, "Playback" }, + { "AOUTL", NULL, "DAC" }, + { "AOUTR", NULL, "DAC" }, +}; + +/* ---------------------------------------------------------------------- DAI */ + +/* + * MCLK limits, all from the datasheet: + * + * - 50 MHz absolute maximum ("Max MCLK Frequency", electrical specification). + * + * - Table 7 note 1: MCLK >= 128 * FS synchronous, MCLK > 130 * FS + * asynchronous. The gap is small but not academic - with a 24.576 MHz + * clock, 128 * FS puts 192 kHz exactly at the limit while 130 * FS puts the + * ceiling at 189 kHz. Note that the asynchronous bound is strict. + * + * - Register 0[6] ENABLE_64FS_MODE runs the interpolation path at 64FS and is + * "used only for PCM high sample rates such as 768kHz with a 49.152MHz or + * 384kHz with 24.576MHz clock" - both of which are MCLK = 64 * FS. Since + * 128 * 768000 is 98.304 MHz, well over the ceiling, 64FS mode is the only + * way 705.6 and 768 kHz are reachable at all. + */ +#define ES9039_MAX_MCLK 50000000 +#define ES9039_64FS_MCLK_FS 64 +#define ES9039_SYNC_MIN_MCLK_FS 128 +#define ES9039_ASYNC_MIN_MCLK_FS 130 + +/* + * MCLK/FS ratios to try on a settable clock, highest first. 256 leaves the + * part inside the window its automatic clock gearing aims for - register 5[2]: + * "MCLK will be geared down until 128FS <= SYS_CLK < 256FS" - and 64 is 64FS + * mode, which only becomes the choice when nothing larger fits under 50 MHz. + */ +static const unsigned int es9039_mclk_ratios[] = { 512, 256, 128, 64 }; + +/* + * Synchronous means the frame clock and MCLK come from the same place. That is + * true when the part generates BCK and WS itself, and it is true when the MCLK + * we were given can be set to a multiple of the sample rate. Only a fixed + * oscillator feeding a consumer-mode part is genuinely asynchronous, and only + * then does the ASRC have anything to do. + */ +static bool es9039_is_sync(struct es9039q2m_priv *priv) +{ + return priv->provider || !priv->mclk_fixed; +} + +/* + * 64FS mode is an exact MCLK/FS ratio, not a rate threshold, and it only makes + * sense synchronously: at 64 * FS an asynchronous part would sit far below its + * own 130 * FS floor, so a fixed oscillator that happens to land on 64 * FS is + * a coincidence rather than a mode. + */ +static bool es9039_is_64fs(struct es9039q2m_priv *priv, unsigned int mclk, + unsigned int rate) +{ + return es9039_is_sync(priv) && rate && + mclk == ES9039_64FS_MCLK_FS * rate; +} + +/* The lowest MCLK this rate may run at outside 64FS mode. */ +static unsigned int es9039_min_mclk(struct es9039q2m_priv *priv, + unsigned int rate) +{ + if (es9039_is_sync(priv)) + return ES9039_SYNC_MIN_MCLK_FS * rate; + + /* Asynchronous is strictly greater than 130 * FS, not equal to it. */ + return ES9039_ASYNC_MIN_MCLK_FS * rate + 1; +} + +static int es9039q2m_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + unsigned int max_rate; + + /* + * A clock whose rate can be set imposes no ceiling here: hw_params() + * raises MCLK to suit the rate, and fails cleanly if it cannot. + * Constraining from the rate MCLK merely happens to be idling at would + * reject rates the board can carry perfectly well. + */ + if (!priv->mclk_rate || !priv->mclk_fixed) + return 0; + + if (!es9039_is_sync(priv)) { + max_rate = priv->mclk_rate / ES9039_ASYNC_MIN_MCLK_FS; + } else if (priv->mclk_rate % ES9039_64FS_MCLK_FS == 0) { + /* + * 64FS mode reaches exactly one rate above the ordinary + * ceiling - MCLK / 64 - so admit it here and leave + * hw_params() to reject anything in between, where neither + * 64 * FS nor 128 * FS is satisfied. + */ + max_rate = priv->mclk_rate / ES9039_64FS_MCLK_FS; + } else { + max_rate = priv->mclk_rate / ES9039_SYNC_MIN_MCLK_FS; + } + + return snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 8000, max_rate); +} + +/* + * Put the clocking where it needs to be for this rate, and tell the part which + * of its two timing worlds it is living in. + * + * With a settable MCLK the useful thing is to pull it to an exact multiple of + * the sample rate: the ASRC then has nothing to correct, which is the better + * arrangement whenever the board can manage it. With a fixed oscillator there + * is nothing to pull, and the ASRC earns its keep. + */ +static int es9039_setup_clocking(struct es9039q2m_priv *priv, + struct snd_soc_dai *dai, unsigned int rate) +{ + unsigned int ratio, min_mclk, div, i, target = 0; + bool sixtyfour; + int ret; + + if (!priv->mclk_rate) + return 0; + + ratio = priv->bclk_ratio ? priv->bclk_ratio : 64; + + if (!priv->mclk_fixed) { + /* + * Take the highest ratio that still fits under the 50 MHz + * ceiling rather than always asking for 256 * FS, which would + * put 384 kHz at 98.304 MHz and 768 kHz at 196.608 MHz. A + * settable clock is synchronous by definition here - see + * es9039_is_sync() - so only the synchronous floors apply. + */ + for (i = 0; i < ARRAY_SIZE(es9039_mclk_ratios); i++) { + unsigned int mult = es9039_mclk_ratios[i]; + + if (mult * rate > ES9039_MAX_MCLK) + continue; + if (mult < ES9039_SYNC_MIN_MCLK_FS && + mult != ES9039_64FS_MCLK_FS) + continue; + /* + * Master mode divides MCLK down to BCK by a whole + * number, so a ratio that is not a multiple of the + * frame size cannot produce the bit clock. + */ + if (priv->provider && mult % ratio) + continue; + + target = mult * rate; + break; + } + + if (!target) { + dev_err(dai->dev, + "no mclk ratio for %u Hz within %u Hz\n", + rate, ES9039_MAX_MCLK); + return -EINVAL; + } + + ret = clk_set_rate(priv->mclk, target); + if (ret) + return ret; + + /* Take what the clock settled on, not what was asked for. */ + priv->mclk_rate = clk_get_rate(priv->mclk); + } + + if (priv->mclk_rate > ES9039_MAX_MCLK) { + dev_err(dai->dev, "mclk %u Hz is above the %u Hz maximum\n", + priv->mclk_rate, ES9039_MAX_MCLK); + return -EINVAL; + } + + sixtyfour = es9039_is_64fs(priv, priv->mclk_rate, rate); + min_mclk = es9039_min_mclk(priv, rate); + + if (!sixtyfour && priv->mclk_rate < min_mclk) { + dev_err(dai->dev, + "mclk %u Hz is below the %u Hz %u Hz needs in this mode\n", + priv->mclk_rate, min_mclk, rate); + return -EINVAL; + } + + /* + * Register 0[6]. Decided from the MCLK actually in use rather than from + * the sample rate alone, because the part cares about the ratio: 384 kHz + * from 24.576 MHz needs 64FS mode exactly as 768 kHz from 49.152 MHz + * does. The mode also forces a minimum phase filter regardless of + * FILTER_SHAPE. + */ + ret = regmap_update_bits(priv->regmap, ES9039_SYSTEM_CONFIG, + ES9039_64FS_MODE, + sixtyfour ? ES9039_64FS_MODE : 0); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_SYS_MODE, + ES9039_SYNC_MODE, + es9039_is_sync(priv) ? ES9039_SYNC_MODE : 0); + if (ret) + return ret; + + if (!priv->provider) + return 0; + + /* + * Register 4: BCK = MCLK / (MASTER_BCK_DIV + 1), and WS follows from + * the frame length. An MCLK that is not a whole multiple of the bit + * clock cannot produce the requested rate at all, so say so rather + * than emitting something close. + */ + if (priv->mclk_rate % (ratio * rate)) { + dev_err(dai->dev, + "mclk %u Hz cannot produce %u * %u Hz bit clock\n", + priv->mclk_rate, ratio, rate); + return -EINVAL; + } + + div = priv->mclk_rate / (ratio * rate); + if (div < 1 || div > 256) { + dev_err(dai->dev, "master bck divider %u out of range\n", div); + return -EINVAL; + } + + return regmap_write(priv->regmap, ES9039_CLOCK_CONFIG, div - 1); +} + +static int es9039q2m_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + unsigned int cfg1 = 0, enc = 0; + int ret; + + /* + * Both directions are useful and which is right is a board decision, + * not a driver one. Driving BCK and WS from a good MCLK is the + * conventional arrangement and keeps everything synchronous; taking + * them from a consumer-mode host and letting the ASRC absorb the + * mismatch is what a board with a fixed oscillator wants. The part can + * do either, so the driver offers either. + */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + priv->provider = false; + break; + case SND_SOC_DAIFMT_CBP_CFP: + if (!priv->mclk) + return -EINVAL; + priv->provider = true; + break; + default: + /* The part cannot split them: it drives both or neither. */ + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, ES9039_INPUT_SEL, + ES9039_PCM_MASTER_MODE, + priv->provider ? ES9039_PCM_MASTER_MODE : 0); + if (ret) + return ret; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + cfg1 |= ES9039_TDM_LJ_MODE; + break; + default: + /* Register 60 offers I2S or LJ. There is no RJ mode. */ + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + enc |= ES9039_BCK_INV; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, ES9039_TDM_CONFIG1, + ES9039_TDM_LJ_MODE, cfg1); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_MASTER_ENC, + ES9039_BCK_INV, enc); + if (ret) + return ret; + + priv->fmt = fmt; + return 0; +} + +/* + * TDM_BIT_WIDTH describes the SLOT width on the wire, not the sample size. Those + * are routinely different: 16-bit samples are usually carried left-justified in + * 32-bit slots. Getting this wrong misaligns the channel boundaries and the + * result is one channel, or noise. + * + * ASoC does not hand the codec the bit clock ratio unless a machine driver sets + * it, so take it when offered and otherwise assume 32-bit slots - by far the + * most common arrangement, and what the RK3588 I2S does unconditionally + * (rockchip_i2s.c sets bclk_ratio = 64 at probe and never varies it with + * format). + */ +static int es9039q2m_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + + priv->bclk_ratio = ratio; + return 0; +} + +static int es9039q2m_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + unsigned int input_sel, decode, width, slot_bits, isel; + bool auto_sel; + int ret; + + /* + * Held for the whole call: dop_auto decides both INPUT_SEL and + * SYS_MODE below, and a kcontrol write landing between the two would + * leave automatic detection enabled with no DoP decoder behind it. + */ + guard(mutex)(&priv->lock); + + priv->stream_rate = params_rate(params); + + ret = es9039_setup_clocking(priv, dai, priv->stream_rate); + if (ret) + return ret; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + /* + * Forced, not auto-detected: the datasheet requires DSD data on + * DATA1 and DATA2 for AUTO_INPUT_SEL to identify it, which a + * two-channel I2S link does not provide. + */ + input_sel = ES9039_INPUT_DSD; + decode = ES9039_ENABLE_DSD_DECODE; + auto_sel = false; + break; + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_3LE: + case SNDRV_PCM_FORMAT_S32_LE: + /* + * DoP arrives inside ordinary PCM frames and is indistinguishable + * from PCM until the part finds the marker, so both decoders run + * and AUTO_INPUT_SEL picks between them. INPUT_SEL is programmed + * anyway as the fallback the part uses when auto-detection is + * switched off. + */ + input_sel = ES9039_INPUT_PCM; + decode = ES9039_ENABLE_TDM_DECODE | + (priv->dop_auto ? ES9039_ENABLE_DOP_DECODE : 0); + auto_sel = priv->dop_auto; + break; + default: + return -EINVAL; + } + + slot_bits = priv->bclk_ratio ? + priv->bclk_ratio / params_channels(params) : 32; + + switch (slot_bits) { + case 16: + width = ES9039_WIDTH_16; + break; + case 24: + width = ES9039_WIDTH_24; + break; + case 32: + width = ES9039_WIDTH_32; + break; + default: + dev_err(dai->dev, "unsupported slot width %u\n", slot_bits); + return -EINVAL; + } + + /* + * AUTO_INPUT_SEL belongs in the value as well as the mask. It was in + * the mask alone, so every hw_params quietly cleared it and undid what + * the component probe had set - which is why enabling auto-detection + * by hand mid-stream worked while enabling it in probe() did not. + * INPUT_SEL is still programmed underneath: it is what the part falls + * back to when auto-detection is switched off. + */ + isel = FIELD_PREP(ES9039_INPUT_SEL_MASK, input_sel); + if (auto_sel) + isel |= ES9039_AUTO_INPUT_SEL; + + ret = regmap_update_bits(priv->regmap, ES9039_INPUT_SEL, + ES9039_AUTO_INPUT_SEL | ES9039_INPUT_SEL_MASK, + isel); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_TDM_CONFIG2, + ES9039_TDM_BIT_WIDTH_MASK, + FIELD_PREP(ES9039_TDM_BIT_WIDTH_MASK, width)); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_TDM_CH_NUM, + ES9039_TDM_CH_NUM_MASK, + params_channels(params) - 1); + if (ret) + return ret; + + /* Switch the right decoder on for this stream, and the others off. */ + return regmap_update_bits(priv->regmap, ES9039_SYS_MODE, + ES9039_DECODE_MASK, decode); +} + +static int es9039q2m_mute_stream(struct snd_soc_dai *dai, int mute, int dir) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + bool old; + int ret; + + guard(mutex)(&priv->lock); + + old = priv->mute_stream; + priv->mute_stream = mute; + + ret = es9039_apply_mute(priv); + if (ret) + priv->mute_stream = old; + + return ret; +} + +/* + * stream_rate doubles as "a stream is open", which es9039_dop_put() needs in + * order to refuse a change it could only half-apply. + */ +static void es9039q2m_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + + guard(mutex)(&priv->lock); + + priv->stream_rate = 0; +} + +static const struct snd_soc_dai_ops es9039q2m_dai_ops = { + .startup = es9039q2m_startup, + .shutdown = es9039q2m_shutdown, + .set_fmt = es9039q2m_set_fmt, + .set_bclk_ratio = es9039q2m_set_bclk_ratio, + .hw_params = es9039q2m_hw_params, + .mute_stream = es9039q2m_mute_stream, + .no_capture_mute = 1, +}; + +#define ES9039_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_DSD_U8 | \ + SNDRV_PCM_FMTBIT_DSD_U16_LE | \ + SNDRV_PCM_FMTBIT_DSD_U32_LE) + +static struct snd_soc_dai_driver es9039q2m_dai = { + .name = "es9039q2m-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_768000, + .formats = ES9039_FORMATS, + }, + .ops = &es9039q2m_dai_ops, +}; + +/* ---------------------------------------------------------------- component */ + +static int es9039q2m_component_probe(struct snd_soc_component *component) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(component); + int ret; + + /* + * Come up muted. The volume registers default to 0 dB, and an unmuted + * DAC arriving into an already-powered analogue stage is how you get a + * thump. ASoC unmutes via mute_stream() when a stream starts. + */ + priv->mute_user = 0; + priv->mute_stream = false; + ret = regmap_update_bits(priv->regmap, ES9039_DAC_MUTE, + ES9039_MUTE_BOTH, ES9039_MUTE_BOTH); + if (ret) + return ret; + + /* + * Enable the datapath. SYSTEM_CONFIG[1] is clear at reset, so in + * software mode nothing turns the DAC on at power-up: the analogue + * output stays dead until this bit is set. + * + * After the mute above and never before it. Bringing the datapath up + * into an unmuted part whose volume registers still sit at their 0 dB + * reset value is exactly the thump the mute is there to prevent. + * + * Note when testing that a warm reboot does not reset this part - + * registers and coefficient RAM both survive one, and only removing + * power clears them - so the previous boot's value can hide whether + * this write happened at all. + */ + ret = regmap_update_bits(priv->regmap, ES9039_SYSTEM_CONFIG, + ES9039_DAC_MODE, ES9039_DAC_MODE); + if (ret) + return ret; + + /* + * VOLUME_HOLD set means "do not apply volume register writes", so its + * resting state has to be clear or the volume control would appear + * dead. es9039_vol_put() raises it around the pair of writes and drops + * it again, which is how both channels are made to move together. + */ + ret = regmap_update_bits(priv->regmap, ES9039_IIR_SPDIF, + ES9039_VOLUME_HOLD, 0); + if (ret) + return ret; + + /* + * Let the part identify DoP for itself. Register 57[0] AUTO_INPUT_SEL + * makes it choose between PCM and DoP from the data, which is the only + * way DoP can work in practice: players send DoP-encoded PCM and expect + * the DAC to notice, and none of them can flip an ALSA control first. + * Without this the part is told "PCM", never looks for the marker, and + * renders a DoP stream as the near-silent hiss the DoP marker design + * deliberately degrades to. Verified on hardware by enabling this + * mid-stream and watching reg 245 flip from PCM to DoP with DOP_VALID + * set. + * + * The datasheet's "data must be provided on the DATA2 pin" applies to + * identifying DSD, whose two channels arrive on separate data lines. + * DoP is ordinary stereo I2S on one line and detects correctly without + * it, which the same measurement establishes. + */ + priv->dop_auto = true; + scoped_guard(mutex, &priv->lock) + ret = es9039_apply_dop(priv, true); + if (ret) + return ret; + + /* + * Board defaults, applied once at probe so the part is deterministic + * from cold instead of inheriting whatever its reset value happens to + * be. Both stay user-settable through their kcontrols; these are + * defaults, not policy. + * + * Reconstruction filter: linear phase apodizing fast roll-off. It keeps + * the sharp cut and flat passband of the plain fast linear-phase filter + * while suppressing pre-ringing, and an apodizing response also + * suppresses pre-ringing already baked into the source material by the + * recording chain - which none of the other seven addresses. The cost + * is a little stopband rejection right at the band edge, well above + * where it can matter. Chosen this way because a blind A/B listening + * test found no audible difference between any of the eight, + * so the tie is broken on theory rather than on preference. + * + * Modulator: wide bandwidth, which is ESS's own recommendation. It + * moves the modulator's noise further out of band and improves + * linearity at high frequencies. + */ + ret = regmap_update_bits(priv->regmap, ES9039_FILTER_SHAPE, + ES9039_FILTER_SHAPE_MASK, + ES9039_FILTER_APODIZING); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_NSMOD, + ES9039_NSMOD_WIDE_BW_MASK, + FIELD_PREP(ES9039_NSMOD_WIDE_BW_MASK, + ES9039_NSMOD_WIDE)); + if (ret) + return ret; + + /* Let the part work out the incoming rate; the ASRC does the rest. */ + return regmap_update_bits(priv->regmap, ES9039_AUTO_FS_DETECT, + ES9039_AUTO_FS_DETECT_EN, + ES9039_AUTO_FS_DETECT_EN); +} + +/* + * System suspend. + * + * Where the part is clocked from the SoC, system suspend stops its clock as a + * side effect and there is nothing for a driver to do. Where the board feeds + * it a free-running oscillator instead - which the ASRC in front of the DAC + * makes an attractive design, since MCLK then need not track BCLK or LRCK - + * nothing in the system can gate that clock, and the part stays fully clocked + * for the whole of suspend. + * + * Clearing ENABLE_DAC_CLK gates the part's internal clock tree, which is the + * only part of its consumption software can reach. The analogue supplies are + * the board's business, and not every board is able to switch them. + * + * The write deliberately bypasses the cache. The cached value must keep + * ENABLE_DAC_CLK set, so that regcache_sync() on resume restores whatever + * state userspace last left the part in without this code having to remember + * anything itself. + */ +static int es9039q2m_suspend(struct snd_soc_component *component) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(component); + int ret; + + guard(mutex)(&priv->lock); + + regcache_cache_bypass(priv->regmap, true); + ret = regmap_update_bits(priv->regmap, ES9039_SYS_MODE, + ES9039_ENABLE_DAC_CLK, 0); + regcache_cache_bypass(priv->regmap, false); + + if (ret) + dev_warn(component->dev, + "could not gate the DAC clock for suspend: %d\n", ret); + + regcache_mark_dirty(priv->regmap); + regcache_cache_only(priv->regmap, true); + + return 0; +} + +static int es9039q2m_resume(struct snd_soc_component *component) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(component); + unsigned int i; + int ret; + + guard(mutex)(&priv->lock); + + regcache_cache_only(priv->regmap, false); + + /* + * Mute the part before anything else, and write it straight to the + * hardware rather than through the cache. + * + * Where the board kept the supplies up this is a no-op - the part still + * holds the mute state it went to sleep with. Where the board removed + * them it is not: the part comes back at reset defaults, which are + * DAC_MODE clear, both channels UNMUTED, and both volume registers at + * 0x00, their 0 dB setting. regcache_sync() walks registers in ascending + * address order, so it would set DAC_MODE in register 0 - lighting the + * datapath up unmuted at full scale - and only restore the real mute in + * register 86, long after. That is the thump the probe path mutes to + * avoid, arriving by the other door. + * + * The cache is bypassed so the cached mute state is untouched; the sync + * below puts it back, after the volume registers at 74 and 75. + */ + regcache_cache_bypass(priv->regmap, true); + ret = regmap_update_bits(priv->regmap, ES9039_DAC_MUTE, + ES9039_MUTE_BOTH, ES9039_MUTE_BOTH); + regcache_cache_bypass(priv->regmap, false); + + if (ret) + dev_warn(component->dev, + "could not mute before resync: %d\n", ret); + + ret = regcache_sync(priv->regmap); + if (ret) + dev_err(component->dev, + "failed to restore registers on resume: %d\n", ret); + + /* + * Re-upload the programmable FIR RAM. regcache_sync() cannot restore + * it - it is write-only, so there is no cache of it to sync - and a + * board that removed the part's supplies comes back with that RAM + * undefined while the sync has just restored the filter SELECTION from + * the cache. Selecting the programmable filter would then point the + * interpolator at whatever the RAM powered up holding. After the sync, + * so the selection is already in place. Stages never uploaded have + * fir_taps == 0 and keep the part's own defaults. + */ + for (i = 0; i < ES9039_FIR_STAGES; i++) { + int err; + + if (!priv->fir_taps[i]) + continue; + + err = es9039_fir_upload(priv, priv->fir_shadow[i], + priv->fir_taps[i], i == 1); + if (err) + dev_err(component->dev, + "failed to restore FIR stage %u on resume: %d\n", + i, err); + } + + return ret; +} + +static const struct snd_soc_component_driver es9039q2m_component = { + .probe = es9039q2m_component_probe, + .suspend = es9039q2m_suspend, + .resume = es9039q2m_resume, + .controls = es9039q2m_controls, + .num_controls = ARRAY_SIZE(es9039q2m_controls), + .dapm_widgets = es9039q2m_widgets, + .num_dapm_widgets = ARRAY_SIZE(es9039q2m_widgets), + .dapm_routes = es9039q2m_routes, + .num_dapm_routes = ARRAY_SIZE(es9039q2m_routes), + .idle_bias_on = 1, + .endianness = 1, +}; + +/* ------------------------------------------------------------------- power */ + +/* + * The part has four supply inputs and the datasheet is specific about the + * order: figure 22 brings AVDD up first, VCCA about 200 us later, then the two + * output-stage references, and only then is the part enabled. Power-down is + * the exact reverse. + * + * That ordering is the whole reason these are enabled one at a time rather + * than with regulator_bulk_enable(), which makes no ordering guarantee at all. + * The bulk API is still used to GET them, where order is irrelevant. On a + * board that ties all four to one always-on rail the sequence costs nothing; + * on one that switches them separately, bringing an output stage up before its + * reference is precisely what the sequence exists to prevent. + * + * The datasheet gives no settling time between the last supply and the first + * register access, so none is invented here. + */ +static const char * const es9039_supply_names[ES9039_NUM_SUPPLIES] = { + "avdd", "vcca", "avcc-dac1", "avcc-dac2", +}; + +static int es9039_power_on(struct es9039q2m_priv *priv) +{ + int i, ret; + + for (i = 0; i < ES9039_NUM_SUPPLIES; i++) { + ret = regulator_enable(priv->supplies[i].consumer); + if (ret) + goto err; + + /* AVDD leads VCCA. It is the only interval the part specifies. */ + if (i == 0) + fsleep(200); + } + + return 0; + +err: + while (--i >= 0) + regulator_disable(priv->supplies[i].consumer); + + return ret; +} + +static void es9039_power_off(void *data) +{ + struct es9039q2m_priv *priv = data; + int i; + + for (i = ES9039_NUM_SUPPLIES - 1; i >= 0; i--) + regulator_disable(priv->supplies[i].consumer); +} + +/* --------------------------------------------------------------------- I2C */ + +static int es9039q2m_i2c_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct es9039q2m_priv *priv; + unsigned int id; + int i, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = devm_regmap_init_i2c(i2c, &es9039q2m_regmap); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "failed to init regmap\n"); + + for (i = 0; i < ES9039_NUM_SUPPLIES; i++) + priv->supplies[i].supply = es9039_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ES9039_NUM_SUPPLIES, priv->supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to get the supplies\n"); + + ret = es9039_power_on(priv); + if (ret) + return dev_err_probe(dev, ret, "failed to enable the supplies\n"); + + ret = devm_add_action_or_reset(dev, es9039_power_off, priv); + if (ret) + return ret; + + priv->mclk = devm_clk_get_optional_enabled(dev, "mclk"); + if (IS_ERR(priv->mclk)) + return dev_err_probe(dev, PTR_ERR(priv->mclk), + "failed to get mclk\n"); + + if (priv->mclk) { + priv->mclk_rate = clk_get_rate(priv->mclk); + if (priv->mclk_rate > 50000000) + return dev_err_probe(dev, -EINVAL, + "mclk %u Hz exceeds the 50 MHz maximum\n", + priv->mclk_rate); + + /* + * A fixed-rate clock answers every rounding question with the + * one rate it has. That is the board saying "this is an + * oscillator, use the ASRC"; anything else can be pulled to + * suit the sample rate and run synchronously. + */ + priv->mclk_fixed = + clk_round_rate(priv->mclk, priv->mclk_rate / 2) == + priv->mclk_rate; + } + + ret = devm_mutex_init(dev, &priv->lock); + if (ret) + return ret; + + i2c_set_clientdata(i2c, priv); + + ret = regmap_read(priv->regmap, ES9039_CHIP_ID, &id); + if (ret) + return dev_err_probe(dev, ret, "no response at 0x%02x\n", + i2c->addr); + + if (id != ES9039_CHIP_ID_ES9039Q2M) + return dev_err_probe(dev, -ENODEV, + "unexpected chip id 0x%02x, want 0x%02x\n", + id, ES9039_CHIP_ID_ES9039Q2M); + + dev_info(dev, "ES9039Q2M at 0x%02x, mclk %u Hz\n", + i2c->addr, priv->mclk_rate); + + return devm_snd_soc_register_component(dev, &es9039q2m_component, + &es9039q2m_dai, 1); +} + +static const struct of_device_id es9039q2m_of_match[] = { + { .compatible = "esstech,es9039q2m" }, + { } +}; +MODULE_DEVICE_TABLE(of, es9039q2m_of_match); + +static const struct i2c_device_id es9039q2m_i2c_id[] = { + { "es9039q2m" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, es9039q2m_i2c_id); + +static struct i2c_driver es9039q2m_i2c_driver = { + .driver = { + .name = "es9039q2m", + .of_match_table = es9039q2m_of_match, + }, + .probe = es9039q2m_i2c_probe, + .id_table = es9039q2m_i2c_id, +}; +module_i2c_driver(es9039q2m_i2c_driver); + +MODULE_DESCRIPTION("ASoC ES9039Q2M driver"); +MODULE_AUTHOR("Karl Asseily "); +MODULE_LICENSE("GPL"); From 10d321c76b1e6605031b604bf89833d7ff2f8385 Mon Sep 17 00:00:00 2001 From: Karl Asseily Date: Fri, 18 Sep 2026 21:23:17 +0300 Subject: [PATCH 0849/1417] MAINTAINERS: add entry for the ES9039Q2M codec driver Add myself as maintainer of the ES9039Q2M codec driver and its binding. Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Asseily Link: https://patch.msgid.link/20260918182317.143223-5-karl@asseily.com Signed-off-by: Mark Brown --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 20d67cc0e7af91..1095476c1d9ffb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9699,6 +9699,13 @@ S: Maintained F: include/linux/errseq.h F: lib/errseq.c +ES9039Q2M AUDIO CODEC DRIVER +M: Karl Asseily +L: linux-sound@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml +F: sound/soc/codecs/es9039q2m.c + ESD CAN NETWORK DRIVERS M: Stefan Mätje R: socketcan@esd.eu From 78da571393daf0ece9067fe040018a2c02c081a7 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Tue, 8 Sep 2026 11:04:43 +0200 Subject: [PATCH 0850/1417] ASoC: dt-bindings: Add Airoha AN7581 AFE Sound card Add YAML schema for Airoha AN7581 AFE SoC sound card. The AFE handling on this SoC is very basic with clock always enabled and all the power saving feature not available. Signed-off-by: Christian Marangi Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260908090448.3332-2-ansuelsmth@gmail.com Signed-off-by: Mark Brown --- .../bindings/sound/airoha,an7581-afe.yaml | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml diff --git a/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml b/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml new file mode 100644 index 00000000000000..80d9e87f147044 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/airoha,an7581-afe.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Airoha AFE PCM controller for AN7581 + +maintainers: + - Christian Marangi + +properties: + compatible: + const: airoha,an7581-afe + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + #include + #include + + afe@1fbe2200 { + compatible = "airoha,an7581-afe"; + reg = <0x1fbe2200 0x9000>; + + interrupts = ; + }; + +... From 8fcf0497e6da049a3ff19e6baa958bbb146da996 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Tue, 8 Sep 2026 11:04:44 +0200 Subject: [PATCH 0851/1417] ASoC: dt-bindings: Add Airoha AN7581 AFE with WM8960 Codec schema Add DT schema for Airoha AN7581 Sound Card with the specific WM8960 i2c Codec. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260908090448.3332-3-ansuelsmth@gmail.com Signed-off-by: Mark Brown --- .../bindings/sound/airoha,an7581-wm8960.yaml | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml diff --git a/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml b/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml new file mode 100644 index 00000000000000..8886fbb6b43ccf --- /dev/null +++ b/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/airoha,an7581-wm8960.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Airoha AN7581 sound card with WM8960 codec + +maintainers: + - Christian Marangi + +allOf: + - $ref: sound-card-common.yaml# + +properties: + compatible: + const: airoha,an7581-wm8960-sound + + platform: + type: object + + additionalProperties: false + + properties: + sound-dai: + items: + - description: The phandle of AN7581 platform. + + required: + - sound-dai + + codec: + type: object + + additionalProperties: false + + properties: + sound-dai: + items: + - description: The phandle of WM8960 i2c codec. + + required: + - sound-dai + +required: + - compatible + - audio-routing + - platform + - codec + +unevaluatedProperties: false + +examples: + - | + sound { + compatible = "airoha,an7581-wm8960-sound"; + model = "an7581-wm8960"; + audio-routing = + "Headphone", "HP_L", + "Headphone", "HP_R", + "LINPUT1", "AMIC", + "RINPUT1", "AMIC"; + + platform { + sound-dai = <&afe>; + }; + + codec { + sound-dai = <&wm8960>; + }; + }; From b49799e403137b12fb9e57671901fe9d56f73d99 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Tue, 8 Sep 2026 11:04:45 +0200 Subject: [PATCH 0852/1417] ASoC: mediatek: common: permit to provide dedicated regmap for irq Some SoC might require dedicated regmap to configure some specific IRQ register. Add an extra entry in the base_afe_irq struct and use the specific regmap if defined. If not defined then the global AFE regmap is used instead. Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260908090448.3332-4-ansuelsmth@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/common/mtk-afe-fe-dai.c | 14 +++++++++----- sound/soc/mediatek/common/mtk-base-afe.h | 2 ++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c index 2a20fa5dba492e..948f606cbaf208 100644 --- a/sound/soc/mediatek/common/mtk-afe-fe-dai.c +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -204,11 +204,15 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, struct mtk_base_afe_irq *irqs = &afe->irqs[memif->irq_usage]; const struct mtk_base_irq_data *irq_data = irqs->irq_data; unsigned int counter = runtime->period_size; + struct regmap *regmap = afe->regmap; int fs; int ret; dev_dbg(afe->dev, "%s %s cmd=%d\n", __func__, memif->data->name, cmd); + if (irqs->regmap) + regmap = irqs->regmap; + switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: @@ -220,7 +224,7 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, } /* set irq counter */ - mtk_regmap_update_bits(afe->regmap, irq_data->irq_cnt_reg, + mtk_regmap_update_bits(regmap, irq_data->irq_cnt_reg, irq_data->irq_cnt_maskbit, counter, irq_data->irq_cnt_shift); @@ -230,12 +234,12 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, if (fs < 0) return -EINVAL; - mtk_regmap_update_bits(afe->regmap, irq_data->irq_fs_reg, + mtk_regmap_update_bits(regmap, irq_data->irq_fs_reg, irq_data->irq_fs_maskbit, fs, irq_data->irq_fs_shift); /* enable interrupt */ - mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, + mtk_regmap_update_bits(regmap, irq_data->irq_en_reg, 1, 1, irq_data->irq_en_shift); return 0; @@ -248,10 +252,10 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, } /* disable interrupt */ - mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, + mtk_regmap_update_bits(regmap, irq_data->irq_en_reg, 1, 0, irq_data->irq_en_shift); /* and clear pending IRQ */ - mtk_regmap_write(afe->regmap, irq_data->irq_clr_reg, + mtk_regmap_write(regmap, irq_data->irq_clr_reg, 1 << irq_data->irq_clr_shift); return ret; default: diff --git a/sound/soc/mediatek/common/mtk-base-afe.h b/sound/soc/mediatek/common/mtk-base-afe.h index a406f2e3e7a878..b5300c9ed415b6 100644 --- a/sound/soc/mediatek/common/mtk-base-afe.h +++ b/sound/soc/mediatek/common/mtk-base-afe.h @@ -153,6 +153,8 @@ struct mtk_base_afe_memif { struct mtk_base_afe_irq { const struct mtk_base_irq_data *irq_data; int irq_occupyed; + + struct regmap *regmap; }; struct mtk_base_afe_dai { From 4974ffa0d6c28dfaba838a29503c3af02080a8a1 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Tue, 8 Sep 2026 11:04:46 +0200 Subject: [PATCH 0853/1417] ASoC: airoha: Add AFE driver for Airoha AN7581 Add support for the Sound system present on Airoha AN7581 SoC. This is based on the Mediatek AFE drivers and adds the PCM and the ETDM driver. Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260908090448.3332-5-ansuelsmth@gmail.com Signed-off-by: Mark Brown --- MAINTAINERS | 8 + sound/soc/mediatek/Kconfig | 17 +- sound/soc/mediatek/Makefile | 1 + sound/soc/mediatek/an7581/Makefile | 8 + sound/soc/mediatek/an7581/an7581-afe-common.h | 49 ++ sound/soc/mediatek/an7581/an7581-afe-pcm.c | 515 ++++++++++++++++++ sound/soc/mediatek/an7581/an7581-dai-etdm.c | 433 +++++++++++++++ sound/soc/mediatek/an7581/an7581-reg.h | 114 ++++ 8 files changed, 1144 insertions(+), 1 deletion(-) create mode 100644 sound/soc/mediatek/an7581/Makefile create mode 100644 sound/soc/mediatek/an7581/an7581-afe-common.h create mode 100644 sound/soc/mediatek/an7581/an7581-afe-pcm.c create mode 100644 sound/soc/mediatek/an7581/an7581-dai-etdm.c create mode 100644 sound/soc/mediatek/an7581/an7581-reg.h diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c24..679da67ce63de3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -767,6 +767,14 @@ F: Documentation/devicetree/bindings/phy/airoha,en7581-pcie-phy.yaml F: drivers/phy/phy-airoha-pcie-regs.h F: drivers/phy/phy-airoha-pcie.c +AIROHA SOUND DRIVER +M: Christian Marangi +L: linux-sound@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml +F: Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml +F: sound/soc/mediatek/an7581/* + AIROHA SPI SNFI DRIVER M: Lorenzo Bianconi M: Ray Liu diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index 224746e7664fcf..eb6f2a443b23ca 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -1,10 +1,25 @@ # SPDX-License-Identifier: GPL-2.0-only -menu "Mediatek" config SND_SOC_MEDIATEK tristate select REGMAP_MMIO +menu "Airoha" + +config SND_SOC_AN7581 + tristate "ASoC support for Airoha AN7581 chip" + depends on ARCH_AIROHA || COMPILE_TEST + select SND_SOC_MEDIATEK + help + This adds ASoC platform driver support for Airoha AN7581 chip + that can be used with other codecs. + Select Y if you have such device. + If unsure select "N". + +endmenu + +menu "Mediatek" + config SND_SOC_MT2701 tristate "ASoC support for Mediatek MT2701 chip" depends on ARCH_MEDIATEK || COMPILE_TEST diff --git a/sound/soc/mediatek/Makefile b/sound/soc/mediatek/Makefile index a6815a3c598858..54522e1fa016be 100644 --- a/sound/soc/mediatek/Makefile +++ b/sound/soc/mediatek/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_SND_SOC_MEDIATEK) += common/ +obj-$(CONFIG_SND_SOC_AN7581) += an7581/ obj-$(CONFIG_SND_SOC_MT2701) += mt2701/ obj-$(CONFIG_SND_SOC_MT6797) += mt6797/ obj-$(CONFIG_SND_SOC_MT7986) += mt7986/ diff --git a/sound/soc/mediatek/an7581/Makefile b/sound/soc/mediatek/an7581/Makefile new file mode 100644 index 00000000000000..d6de5ee1668e9b --- /dev/null +++ b/sound/soc/mediatek/an7581/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +# platform driver +snd-soc-an7581-afe-y := \ + an7581-afe-pcm.o \ + an7581-dai-etdm.o + +obj-$(CONFIG_SND_SOC_AN7581) += snd-soc-an7581-afe.o diff --git a/sound/soc/mediatek/an7581/an7581-afe-common.h b/sound/soc/mediatek/an7581/an7581-afe-common.h new file mode 100644 index 00000000000000..ebc8e2f7fec48a --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-afe-common.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * an7581-afe-common.h -- Airoha AN7581 audio driver definitions + */ + +#ifndef _AN7581_AFE_COMMON_H_ +#define _AN7581_AFE_COMMON_H_ + +#include +#include +#include +#include + +#include "../../mediatek/common/mtk-base-afe.h" + +enum { + AN7581_MEMIF_DL1, + AN7581_MEMIF_UL1, + AN7581_MEMIF_NUM, + AN7581_DAI_ETDM = AN7581_MEMIF_NUM, + AN7581_DAI_NUM, +}; + +enum { + AN7581_IRQ_0, + AN7581_IRQ_1, + AN7581_IRQ_NUM, +}; + +struct an7581_memif_irq_desc { + u8 irq; + u32 status_bit; + u32 clear_reg; +}; + +struct an7581_afe_private { + unsigned int users; + /* protect concurrent ETDM user tracking */ + struct mutex user_lock; + + void *dai_priv[AN7581_DAI_NUM]; +}; + +unsigned int an7581_afe_rate_transform(struct device *dev, + unsigned int rate); + +int an7581_dai_etdm_register(struct mtk_base_afe *afe); + +#endif diff --git a/sound/soc/mediatek/an7581/an7581-afe-pcm.c b/sound/soc/mediatek/an7581/an7581-afe-pcm.c new file mode 100644 index 00000000000000..a1d742362538ea --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-afe-pcm.c @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha ALSA SoC AFE platform driver for AN7581 + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "an7581-afe-common.h" +#include "an7581-reg.h" +#include "../common/mtk-afe-platform-driver.h" +#include "../common/mtk-afe-fe-dai.h" + +enum { + ARH_AFE_RATE_8K = 0, + ARH_AFE_RATE_12K = 1, + ARH_AFE_RATE_16K = 2, + ARH_AFE_RATE_24K = 3, + ARH_AFE_RATE_32K = 4, + ARH_AFE_RATE_48K = 5, + ARH_AFE_RATE_96K = 6, + ARH_AFE_RATE_192K = 7, + ARH_AFE_RATE_384K = 8, + ARH_AFE_RATE_7K = 16, + ARH_AFE_RATE_11K = 17, + ARH_AFE_RATE_14K = 18, + ARH_AFE_RATE_22K = 19, + ARH_AFE_RATE_29K = 20, + ARH_AFE_RATE_44K = 21, + ARH_AFE_RATE_88K = 22, + ARH_AFE_RATE_176K = 23, + ARH_AFE_RATE_352K = 24, +}; + +unsigned int an7581_afe_rate_transform(struct device *dev, unsigned int rate) +{ + switch (rate) { + case 7350: + return ARH_AFE_RATE_7K; + case 8000: + return ARH_AFE_RATE_8K; + case 11025: + return ARH_AFE_RATE_11K; + case 12000: + return ARH_AFE_RATE_12K; + case 14700: + return ARH_AFE_RATE_14K; + case 16000: + return ARH_AFE_RATE_16K; + case 22050: + return ARH_AFE_RATE_22K; + case 24000: + return ARH_AFE_RATE_24K; + case 29400: + return ARH_AFE_RATE_29K; + case 32000: + return ARH_AFE_RATE_32K; + case 44100: + return ARH_AFE_RATE_44K; + case 48000: + return ARH_AFE_RATE_48K; + case 88200: + return ARH_AFE_RATE_88K; + case 96000: + return ARH_AFE_RATE_96K; + case 176400: + return ARH_AFE_RATE_176K; + case 192000: + return ARH_AFE_RATE_192K; + case 352800: + return ARH_AFE_RATE_352K; + case 384000: + return ARH_AFE_RATE_384K; + default: + dev_warn_ratelimited(dev, "%s(), rate %u invalid, using %d!\n", + __func__, rate, ARH_AFE_RATE_48K); + return ARH_AFE_RATE_48K; + } +} + +static const struct an7581_memif_irq_desc an7581_memif_irq_descs[AN7581_MEMIF_NUM] = { + [AN7581_MEMIF_DL1] = { + .irq = AN7581_IRQ_0, + .status_bit = AFE_IRQ_STS_PLAY, + .clear_reg = AFE_IRQ_CON0, + }, + [AN7581_MEMIF_UL1] = { + .irq = AN7581_IRQ_1, + .status_bit = AFE_IRQ_STS_RECORD, + .clear_reg = AFE_IRQ1_CON0, + }, +}; + +static const struct snd_pcm_hardware an7581_afe_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 512, + .period_bytes_max = 128 * 1024, + .periods_min = 2, + .periods_max = 256, + .buffer_bytes_max = 256 * 1024, +}; + +static int an7581_memif_irq_fs(struct snd_pcm_substream *substream, unsigned int rate) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); + + return an7581_afe_rate_transform(afe->dev, rate); +} + +static int an7581_afe_fe_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = mtk_afe_fe_startup(substream, dai); + if (ret < 0) + return ret; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + 0x2000, UINT_MAX); + if (ret < 0) + dev_err(afe->dev, "hw_constraint_minmax failed\n"); + } + + return ret; +} + +const struct snd_soc_dai_ops an7581_afe_fe_ops = { + .startup = an7581_afe_fe_startup, + .shutdown = mtk_afe_fe_shutdown, + .hw_params = mtk_afe_fe_hw_params, + .hw_free = mtk_afe_fe_hw_free, + .prepare = mtk_afe_fe_prepare, + .trigger = mtk_afe_fe_trigger, +}; + +#define ARH_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver an7581_memif_dai_driver[] = { + /* FE DAIs: memory intefaces to CPU */ + { + .name = "DL1", + .id = AN7581_MEMIF_DL1, + .playback = { + .stream_name = "DL1", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = ARH_PCM_FORMATS, + }, + .ops = &an7581_afe_fe_ops, + }, + { + .name = "UL1", + .id = AN7581_MEMIF_UL1, + .capture = { + .stream_name = "UL1", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = ARH_PCM_FORMATS, + }, + .ops = &an7581_afe_fe_ops, + }, +}; + +static const struct snd_soc_dapm_widget an7581_memif_widgets[] = { + /* DL */ + SND_SOC_DAPM_MIXER("I032", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I033", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* UL */ + SND_SOC_DAPM_MIXER("O018", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("O019", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route an7581_memif_routes[] = { + {"I032", NULL, "DL1"}, + {"I033", NULL, "DL1"}, + {"UL1", NULL, "O018"}, + {"UL1", NULL, "O019"}, + {"O018", NULL, "I150"}, + {"O019", NULL, "I151"}, +}; + +static const struct snd_soc_component_driver an7581_afe_pcm_dai_component = { + .name = "an7581-afe-pcm-dai", +}; + +static const struct mtk_base_memif_data memif_data[AN7581_MEMIF_NUM] = { + [AN7581_MEMIF_DL1] = { + .name = "DL1", + .id = AN7581_MEMIF_DL1, + .reg_ofs_base = AFE_DL1_BASE, + .reg_ofs_cur = AFE_DL1_CUR, + .reg_ofs_end = AFE_DL1_END, + .fs_reg = -1, + .fs_shift = -1, + .fs_maskbit = -1, + .mono_reg = -1, + .mono_shift = -1, + .hd_reg = AFE_DL1_CON0, + .hd_shift = AFE_HD_SHIFT, + .hd_align_reg = -1, + .hd_align_mshift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = AFE_DL1_ENABLE_SHIFT, + .msb_reg = -1, + .msb_shift = -1, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, + [AN7581_MEMIF_UL1] = { + .name = "UL1", + .id = AN7581_MEMIF_UL1, + .reg_ofs_base = AFE_UL1_BASE, + .reg_ofs_cur = AFE_UL1_CUR, + .reg_ofs_end = AFE_UL1_END, + .fs_reg = -1, + .fs_shift = -1, + .fs_maskbit = -1, + .mono_reg = -1, + .mono_shift = -1, + .hd_reg = AFE_UL1_CON0, + .hd_shift = AFE_HD_SHIFT, + .hd_align_reg = AFE_UL1_CON0, + .hd_align_mshift = AFE_HD_ALIGN_SHIFT, + .enable_reg = AFE_DAC_CON0, + .enable_shift = AFE_UL1_ENABLE_SHIFT, + .msb_reg = -1, + .msb_shift = -1, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, +}; + +static const struct mtk_base_irq_data irq_data[AN7581_IRQ_NUM] = { + [AN7581_IRQ_0] = { + .id = AN7581_IRQ_0, + .irq_cnt_reg = AFE_IRQ_CNT, + .irq_cnt_shift = AFE_IRQ_CNT_SHIFT, + .irq_cnt_maskbit = AFE_IRQ_CNT_MASK, + .irq_en_reg = AFE_IRQ_CON0, + .irq_en_shift = AFE_IRQ_ON_SHIFT, + .irq_fs_reg = -1, + .irq_fs_shift = -1, + .irq_fs_maskbit = -1, + .irq_clr_reg = -1, + .irq_clr_shift = -1, + }, + [AN7581_IRQ_1] = { + .id = AN7581_IRQ_1, + .irq_cnt_reg = AFE_IRQ1_CNT, + .irq_cnt_shift = AFE_IRQ_CNT_SHIFT, + .irq_cnt_maskbit = AFE_IRQ_CNT_MASK, + .irq_en_reg = AFE_IRQ1_CON0, + .irq_en_shift = AFE_IRQ_ON_SHIFT, + .irq_fs_reg = -1, + .irq_fs_shift = -1, + .irq_fs_maskbit = -1, + .irq_clr_reg = -1, + .irq_clr_shift = -1, + }, +}; + +static const struct regmap_config an7581_afe_regmap_config = { + .name = "afe", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AFE_MAX_REGISTER, +}; + +static const struct regmap_config an7581_afe_irq1_regmap_config = { + .name = "afe_irq1", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AFE_IRQ1_MAX_REGISTER, +}; + +static irqreturn_t an7581_afe_irq_handler(int irq_id, void *dev) +{ + const struct an7581_memif_irq_desc *irq_desc; + struct mtk_base_afe *afe = dev; + u32 status = 0; + int i; + + regmap_read(afe->regmap, AFE_IRQ_STS, &status); + + for (i = 0; i < AN7581_MEMIF_NUM; i++) { + struct mtk_base_afe_memif *memif = &afe->memif[i]; + + if (!memif->substream) + continue; + + if (memif->irq_usage < 0) + continue; + + irq_desc = &an7581_memif_irq_descs[i]; + if (status & irq_desc->status_bit) + snd_pcm_period_elapsed(memif->substream); + } + + for (i = 0; i < AN7581_MEMIF_NUM; i++) { + struct regmap *irq_regmap; + + irq_desc = &an7581_memif_irq_descs[i]; + if (!(status & irq_desc->status_bit)) + continue; + + irq_regmap = afe->irqs[irq_desc->irq].regmap; + regmap_set_bits(irq_regmap, irq_desc->clear_reg, + BIT(AFE_IRQ_CLR_SHIFT)); + regmap_clear_bits(irq_regmap, irq_desc->clear_reg, + BIT(AFE_IRQ_CLR_SHIFT)); + + regmap_set_bits(irq_regmap, irq_desc->clear_reg, + BIT(AFE_IRQ_MISS_FLG_CLR_SHIFT)); + regmap_clear_bits(irq_regmap, irq_desc->clear_reg, + BIT(AFE_IRQ_MISS_FLG_CLR_SHIFT)); + } + + return IRQ_HANDLED; +} + +static int an7581_dai_memif_register(struct mtk_base_afe *afe) +{ + struct mtk_base_afe_dai *dai; + + dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL); + if (!dai) + return -ENOMEM; + + list_add(&dai->list, &afe->sub_dais); + + dai->dai_drivers = an7581_memif_dai_driver; + dai->num_dai_drivers = ARRAY_SIZE(an7581_memif_dai_driver); + + dai->dapm_widgets = an7581_memif_widgets; + dai->num_dapm_widgets = ARRAY_SIZE(an7581_memif_widgets); + dai->dapm_routes = an7581_memif_routes; + dai->num_dapm_routes = ARRAY_SIZE(an7581_memif_routes); + + return 0; +} + +typedef int (*dai_register_cb)(struct mtk_base_afe *); +static const dai_register_cb dai_register_cbs[] = { + an7581_dai_etdm_register, + an7581_dai_memif_register, +}; + +static int an7581_afe_pcm_dev_probe(struct platform_device *pdev) +{ + struct an7581_afe_private *afe_priv; + struct device *dev = &pdev->dev; + struct reset_control *reset; + struct mtk_base_afe *afe; + int i, irq_id, ret; + void *base; + + afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + platform_set_drvdata(pdev, afe); + + afe->platform_priv = devm_kzalloc(dev, sizeof(*afe_priv), + GFP_KERNEL); + if (!afe->platform_priv) + return -ENOMEM; + + afe->irqs_size = AN7581_IRQ_NUM; + afe->irqs = devm_kcalloc(dev, afe->irqs_size, sizeof(*afe->irqs), + GFP_KERNEL); + if (!afe->irqs) + return -ENOMEM; + + afe->memif_size = AN7581_MEMIF_NUM; + afe->memif = devm_kcalloc(dev, afe->memif_size, sizeof(*afe->memif), + GFP_KERNEL); + if (!afe->memif) + return -ENOMEM; + + afe_priv = afe->platform_priv; + mutex_init(&afe_priv->user_lock); + afe->dev = &pdev->dev; + + reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(reset)) + return PTR_ERR(reset); + + /* Global reset I2S */ + reset_control_assert(reset); + usleep_range(10, 20); + reset_control_deassert(reset); + + afe->base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + + base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(base)) + return PTR_ERR(base); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr, + &an7581_afe_regmap_config); + if (IS_ERR(afe->regmap)) + return PTR_ERR(afe->regmap); + + mutex_init(&afe->irq_alloc_lock); + + /* irq initialize */ + for (i = 0; i < afe->irqs_size; i++) + afe->irqs[i].irq_data = &irq_data[i]; + + afe->irqs[AN7581_IRQ_0].regmap = afe->regmap; + afe->irqs[AN7581_IRQ_1].regmap = devm_regmap_init_mmio(&pdev->dev, base, + &an7581_afe_irq1_regmap_config); + if (IS_ERR(afe->irqs[AN7581_IRQ_1].regmap)) + return PTR_ERR(afe->irqs[AN7581_IRQ_1].regmap); + + /* request irq */ + irq_id = platform_get_irq(pdev, 0); + if (irq_id < 0) + return irq_id; + + /* init memif */ + for (i = 0; i < afe->memif_size; i++) { + int sel_irq = an7581_memif_irq_descs[i].irq; + + afe->memif[i].data = &memif_data[i]; + afe->memif[i].irq_usage = sel_irq; + afe->memif[i].const_irq = 1; + afe->irqs[sel_irq].irq_occupyed = true; + } + + /* init sub_dais */ + INIT_LIST_HEAD(&afe->sub_dais); + + ret = devm_request_irq(dev, irq_id, an7581_afe_irq_handler, + IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); + if (ret) + return dev_err_probe(dev, ret, "Failed to request irq for asys-isr\n"); + + for (i = 0; i < ARRAY_SIZE(dai_register_cbs); i++) { + ret = dai_register_cbs[i](afe); + if (ret) + return dev_err_probe(dev, ret, "DAI register failed, i: %d\n", i); + } + + /* init dai_driver and component_driver */ + ret = mtk_afe_combine_sub_dai(afe); + if (ret) + return dev_err_probe(dev, ret, "mtk_afe_combine_sub_dai fail\n"); + + afe->mtk_afe_hardware = &an7581_afe_hardware; + afe->memif_fs = an7581_memif_irq_fs; + afe->irq_fs = an7581_memif_irq_fs; + + /* register component */ + ret = devm_snd_soc_register_component(&pdev->dev, + &mtk_afe_pcm_platform, + NULL, 0); + if (ret) + return dev_err_probe(dev, ret, "Cannot register AFE component\n"); + + ret = devm_snd_soc_register_component(afe->dev, + &an7581_afe_pcm_dai_component, + afe->dai_drivers, + afe->num_dai_drivers); + if (ret) + return dev_err_probe(dev, ret, "Cannot register PCM DAI component\n"); + + return 0; +} + +static const struct of_device_id an7581_afe_pcm_dt_match[] = { + { .compatible = "airoha,an7581-afe" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, an7581_afe_pcm_dt_match); + +static struct platform_driver an7581_afe_pcm_driver = { + .driver = { + .name = "an7581-audio", + .of_match_table = an7581_afe_pcm_dt_match, + }, + .probe = an7581_afe_pcm_dev_probe, +}; +module_platform_driver(an7581_afe_pcm_driver); + +MODULE_DESCRIPTION("Airoha SoC AFE platform driver for ALSA AN7581"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mediatek/an7581/an7581-dai-etdm.c b/sound/soc/mediatek/an7581/an7581-dai-etdm.c new file mode 100644 index 00000000000000..7fbfd8fe18362b --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-dai-etdm.c @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha ALSA SoC Audio DAI eTDM Control + * + */ + +#include +#include +#include +#include +#include "an7581-afe-common.h" +#include "an7581-reg.h" + +#define HOPPING_CLK 0 +#define APLL_CLK 1 +#define MTK_DAI_ETDM_FORMAT_I2S 0 +#define MTK_DAI_ETDM_FORMAT_DSPA 4 +#define MTK_DAI_ETDM_FORMAT_DSPB 5 + +enum { + MTK_ETDM_RATE_8K = 0, + MTK_ETDM_RATE_12K = 1, + MTK_ETDM_RATE_16K = 2, + MTK_ETDM_RATE_24K = 3, + MTK_ETDM_RATE_32K = 4, + MTK_ETDM_RATE_48K = 5, + MTK_ETDM_RATE_96K = 6, + MTK_ETDM_RATE_192K = 7, + MTK_ETDM_RATE_384K = 8, + MTK_ETDM_RATE_7K = 16, + MTK_ETDM_RATE_11K = 17, + MTK_ETDM_RATE_14K = 18, + MTK_ETDM_RATE_22K = 19, + MTK_ETDM_RATE_29K = 20, + MTK_ETDM_RATE_44K = 21, + MTK_ETDM_RATE_88K = 22, + MTK_ETDM_RATE_176K = 23, + MTK_ETDM_RATE_352K = 24, +}; + +struct mtk_dai_etdm_priv { + bool bck_inv; + bool lrck_inv; + bool slave_mode; + unsigned int format; +}; + +static unsigned int an7581_etdm_rate_transform(struct device *dev, unsigned int rate) +{ + switch (rate) { + case 7350: + return MTK_ETDM_RATE_7K; + case 8000: + return MTK_ETDM_RATE_8K; + case 11025: + return MTK_ETDM_RATE_11K; + case 12000: + return MTK_ETDM_RATE_12K; + case 14700: + return MTK_ETDM_RATE_14K; + case 16000: + return MTK_ETDM_RATE_16K; + case 22050: + return MTK_ETDM_RATE_22K; + case 24000: + return MTK_ETDM_RATE_24K; + case 29400: + return MTK_ETDM_RATE_29K; + case 32000: + return MTK_ETDM_RATE_32K; + case 44100: + return MTK_ETDM_RATE_44K; + case 48000: + return MTK_ETDM_RATE_48K; + case 88200: + return MTK_ETDM_RATE_88K; + case 96000: + return MTK_ETDM_RATE_96K; + case 176400: + return MTK_ETDM_RATE_176K; + case 192000: + return MTK_ETDM_RATE_192K; + case 352800: + return MTK_ETDM_RATE_352K; + case 384000: + return MTK_ETDM_RATE_384K; + default: + dev_warn_ratelimited(dev, "%s(), rate %u invalid, using %d!\n", + __func__, rate, MTK_ETDM_RATE_48K); + return MTK_ETDM_RATE_48K; + } +} + +static int get_etdm_wlen(unsigned int bitwidth) +{ + return bitwidth <= 16 ? 16 : 32; +} + +static const struct snd_soc_dapm_widget mtk_dai_etdm_widgets[] = { + /* DL */ + SND_SOC_DAPM_MIXER("I150", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I151", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* UL */ + SND_SOC_DAPM_MIXER("O124", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("O125", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route mtk_dai_etdm_routes[] = { + {"I150", NULL, "ETDM Capture"}, + {"I151", NULL, "ETDM Capture"}, + {"ETDM Playback", NULL, "O124"}, + {"ETDM Playback", NULL, "O125"}, + {"O124", NULL, "I032"}, + {"O125", NULL, "I033"}, +}; + +/* dai ops */ +static int mtk_dai_etdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct an7581_afe_private *afe_priv = afe->platform_priv; + + guard(mutex)(&afe_priv->user_lock); + + if (!afe_priv->users) + regmap_set_bits(afe->regmap, AFE_DAC_CON0, + BIT(AFE_AFE_ENABLE_SHIFT)); + afe_priv->users++; + + return 0; +} + +static void mtk_dai_etdm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct an7581_afe_private *afe_priv = afe->platform_priv; + + guard(mutex)(&afe_priv->user_lock); + + afe_priv->users--; + if (!afe_priv->users) + regmap_clear_bits(afe->regmap, AFE_DAC_CON0, + BIT(AFE_AFE_ENABLE_SHIFT)); +} + +static unsigned int get_etdm_ch_fixup(unsigned int channels) +{ + if (channels > 16) + return 24; + + if (channels > 8) + return 16; + + if (channels > 4) + return 8; + + if (channels > 2) + return 4; + + return 2; +} + +static int mtk_dai_etdm_config(struct mtk_base_afe *afe, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, + int stream) +{ + struct an7581_afe_private *afe_priv = afe->platform_priv; + struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai->id]; + unsigned int rate = params_rate(params); + unsigned int etdm_rate = an7581_etdm_rate_transform(afe->dev, rate); + unsigned int bit_width = params_width(params); + unsigned int mask, mask1; + unsigned int val, val1; + + dev_dbg(afe->dev, "%s(), stream %d, rate %u, bitwidth %u\n", + __func__, stream, rate, params_width(params)); + + /* CON0 */ + mask = ETDM_SLAVE_MODE | ETDM_BIT_LEN | ETDM_WRD_LEN | + ETDM_FMT | ETDM_CH_NUM; + val = FIELD_PREP(ETDM_BIT_LEN, params_width(params) - 1) | + FIELD_PREP(ETDM_WRD_LEN, get_etdm_wlen(bit_width) - 1) | + FIELD_PREP(ETDM_FMT, etdm_data->format) | + FIELD_PREP(ETDM_CH_NUM, + get_etdm_ch_fixup(params_channels(params)) - 1); + if (etdm_data->slave_mode) + val |= ETDM_SLAVE_MODE; + + /* CON1 */ + mask1 = EDTM_LRCK_AUTO_MODE | EDTM_CKEN_SEL | EDTM_LRCK_AUTO_OFF | + EDTM_INITIAL_POINT | EDTM_INITIAL_COUNT; + val1 = EDTM_LRCK_AUTO_MODE | EDTM_CKEN_SEL | EDTM_LRCK_AUTO_OFF | + FIELD_PREP(EDTM_INITIAL_POINT, 14) | FIELD_PREP(EDTM_INITIAL_COUNT, 14); + + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + regmap_update_bits(afe->regmap, ETDM_OUT1_CON0, mask, val); + + mask1 |= EDTM_DIRECT_INPUT_MASTER_BCK; + val1 |= EDTM_DIRECT_INPUT_MASTER_BCK; + + regmap_update_bits(afe->regmap, ETDM_OUT1_CON1, mask1, val1); + + regmap_update_bits(afe->regmap, ETDM_OUT1_CON4, OUT_SEL_FS, + FIELD_PREP(OUT_SEL_FS, etdm_rate)); + + regmap_update_bits(afe->irqs[AN7581_IRQ_0].regmap, + afe->irqs[AN7581_IRQ_0].irq_data->irq_en_reg, + AFE_IRQ_EN_SEL, AFE_IRQ_EN_SEL_I2SOUT); + break; + case SNDRV_PCM_STREAM_CAPTURE: + regmap_update_bits(afe->regmap, ETDM_IN1_CON0, mask, val); + + regmap_update_bits(afe->regmap, ETDM_IN1_CON1, mask1, val1); + + regmap_update_bits(afe->regmap, ETDM_IN1_CON3, IN_SEL_FS, + FIELD_PREP(IN_SEL_FS, etdm_rate)); + + regmap_update_bits(afe->irqs[AN7581_IRQ_1].regmap, + afe->irqs[AN7581_IRQ_1].irq_data->irq_en_reg, + AFE_IRQ_EN_SEL, AFE_IRQ_EN_SEL_I2SIN); + break; + default: + break; + } + + return 0; +} + +static int mtk_dai_etdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + unsigned int rate = params_rate(params); + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + + regmap_update_bits(afe->regmap, ETDM_COWORK_CON0, + EDTM_IN1_SLAVE_SEL, + EDTM_IN1_SLAVE_FROM_ETDMIN1_SLAVE); + regmap_update_bits(afe->regmap, ETDM_COWORK_CON0, + EDTM_OUT1_SLAVE_SEL, + EDTM_OUT1_SLAVE_FROM_ETDMOUT1_SLAVE); + regmap_update_bits(afe->regmap, ETDM_COWORK_CON1, + EDTM_IN1_SDATA0_SEL, + EDTM_IN1_SDATA0_FROM_PAD); + + switch (rate) { + case 7350: + case 8000: + case 11025: + case 12000: + case 14700: + case 16000: + case 22050: + case 24000: + case 29400: + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + case 176400: + case 192000: + case 352800: + case 384000: + return mtk_dai_etdm_config(afe, params, dai, substream->stream); + default: + dev_err(afe->dev, "Sample rate %d invalid\n", rate); + return -EINVAL; + } +} + +static int mtk_dai_etdm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + + dev_dbg(afe->dev, "%s(), cmd %d, dai id %d\n", __func__, cmd, dai->id); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_set_bits(afe->regmap, ETDM_OUT1_CON0, + ETDM_OUT_EN); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + regmap_set_bits(afe->regmap, ETDM_IN1_CON0, + ETDM_IN_EN); + + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_clear_bits(afe->regmap, ETDM_OUT1_CON0, + ETDM_OUT_EN); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + regmap_clear_bits(afe->regmap, ETDM_IN1_CON0, + ETDM_IN_EN); + + break; + default: + break; + } + + return 0; +} + +static int mtk_dai_etdm_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct an7581_afe_private *afe_priv = afe->platform_priv; + struct mtk_dai_etdm_priv *etdm_data; + void *priv_data; + + priv_data = devm_kzalloc(afe->dev, sizeof(struct mtk_dai_etdm_priv), + GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + + afe_priv->dai_priv[dai->id] = priv_data; + etdm_data = afe_priv->dai_priv[dai->id]; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + etdm_data->format = MTK_DAI_ETDM_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + etdm_data->format = MTK_DAI_ETDM_FORMAT_DSPA; + break; + case SND_SOC_DAIFMT_DSP_B: + etdm_data->format = MTK_DAI_ETDM_FORMAT_DSPB; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + etdm_data->bck_inv = false; + etdm_data->lrck_inv = false; + break; + case SND_SOC_DAIFMT_NB_IF: + etdm_data->bck_inv = false; + etdm_data->lrck_inv = true; + break; + case SND_SOC_DAIFMT_IB_NF: + etdm_data->bck_inv = true; + etdm_data->lrck_inv = false; + break; + case SND_SOC_DAIFMT_IB_IF: + etdm_data->bck_inv = true; + etdm_data->lrck_inv = true; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BP_FP: + etdm_data->slave_mode = false; + break; + case SND_SOC_DAIFMT_BC_FC: + etdm_data->slave_mode = true; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops mtk_dai_etdm_ops = { + .startup = mtk_dai_etdm_startup, + .shutdown = mtk_dai_etdm_shutdown, + .hw_params = mtk_dai_etdm_hw_params, + .trigger = mtk_dai_etdm_trigger, + .set_fmt = mtk_dai_etdm_set_fmt, +}; + +/* dai driver */ +#define MTK_ETDM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver mtk_dai_etdm_driver[] = { + { + .name = "ETDM", + .id = AN7581_DAI_ETDM, + .capture = { + .stream_name = "ETDM Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = MTK_ETDM_FORMATS, + }, + .playback = { + .stream_name = "ETDM Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = MTK_ETDM_FORMATS, + }, + .ops = &mtk_dai_etdm_ops, + .symmetric_rate = 1, + .symmetric_sample_bits = 1, + }, +}; + +int an7581_dai_etdm_register(struct mtk_base_afe *afe) +{ + struct mtk_base_afe_dai *dai; + + dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL); + if (!dai) + return -ENOMEM; + + list_add(&dai->list, &afe->sub_dais); + + dai->dai_drivers = mtk_dai_etdm_driver; + dai->num_dai_drivers = ARRAY_SIZE(mtk_dai_etdm_driver); + + dai->dapm_widgets = mtk_dai_etdm_widgets; + dai->num_dapm_widgets = ARRAY_SIZE(mtk_dai_etdm_widgets); + dai->dapm_routes = mtk_dai_etdm_routes; + dai->num_dapm_routes = ARRAY_SIZE(mtk_dai_etdm_routes); + + return 0; +} diff --git a/sound/soc/mediatek/an7581/an7581-reg.h b/sound/soc/mediatek/an7581/an7581-reg.h new file mode 100644 index 00000000000000..a6ada3ca97ab19 --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-reg.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * an7581-reg.h -- Airoha AN7581 audio driver reg definition + */ + +#ifndef _AN7581_REG_H_ +#define _AN7581_REG_H_ + +#define AFE_DAC_CON0 0x0 +#define AFE_DL1_ENABLE_SHIFT 17 +#define AFE_UL1_ENABLE_SHIFT 1 +#define AFE_AFE_ENABLE_SHIFT 0 + +#define ETDM_COWORK_CON0 0x4c +#define EDTM_IN1_SLAVE_SEL GENMASK(27, 24) +#define EDTM_IN1_SLAVE_FROM_ETDMIN1_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x1) +#define EDTM_IN1_SLAVE_FROM_ETDMIN2_MASTER FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x2) +#define EDTM_IN1_SLAVE_FROM_ETDMIN2_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x3) +#define EDTM_IN1_SLAVE_FROM_ETDMOUT1_MASTER FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x8) +#define EDTM_IN1_SLAVE_FROM_ETDMOUT1_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x9) +#define EDTM_IN1_SLAVE_FROM_ETDMOUT2_MASTER FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0xa) +#define EDTM_IN1_SLAVE_FROM_ETDMOUT2_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0xb) +#define EDTM_OUT1_SLAVE_SEL GENMASK(11, 8) +#define EDTM_OUT1_SLAVE_FROM_ETDMIN1_MASTER FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x0) +#define EDTM_OUT1_SLAVE_FROM_ETDMIN1_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x1) +#define EDTM_OUT1_SLAVE_FROM_ETDMIN2_MASTER FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x2) +#define EDTM_OUT1_SLAVE_FROM_ETDMIN2_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x3) +#define EDTM_OUT1_SLAVE_FROM_ETDMOUT1_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x9) +#define EDTM_OUT1_SLAVE_FROM_ETDMOUT2_MASTER FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0xa) +#define EDTM_OUT1_SLAVE_FROM_ETDMOUT2_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0xb) +#define ETDM_COWORK_CON1 0x50 +#define EDTM_IN1_SDATA0_SEL GENMASK(3, 0) +#define EDTM_IN1_SDATA0_FROM_PAD FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0x0) +#define EDTM_IN1_SDATA0_FROM_ETDMIN2 FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0x2) +#define EDTM_IN1_SDATA0_FROM_ETDMOUT1 FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0x8) +#define EDTM_IN1_SDATA0_FROM_ETDMOUT2 FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0xa) +#define ETDM_IN1_CON0 0x5c +#define ETDM_CH_NUM GENMASK(26, 23) +#define ETDM_WRD_LEN GENMASK(20, 16) +#define ETDM_BIT_LEN GENMASK(15, 11) +#define ETDM_FMT GENMASK(8, 6) +#define ETDM_SLAVE_MODE BIT(5) +#define ETDM_IN_EN BIT(0) +#define ETDM_IN1_CON1 0x60 +#define EDTM_LRCK_AUTO_MODE BIT(29) +#define EDTM_DIRECT_INPUT_MASTER_BCK BIT(30) +#define EDTM_CKEN_SEL BIT(12) +#define EDTM_LRCK_AUTO_OFF BIT(10) +#define EDTM_INITIAL_POINT GENMASK(9, 5) +#define EDTM_INITIAL_COUNT GENMASK(4, 0) +#define ETDM_IN1_CON2 0x64 +#define IN_CLK_SRC GENMASK(12, 10) +#define ETDM_IN1_CON3 0x68 +#define IN_SEL_FS GENMASK(30, 26) +#define ETDM_IN1_CON4 0x6c +#define IN_RELATCH GENMASK(24, 20) +#define IN_CLK_INV BIT(18) +#define ETDM_IN1_CON5 0x70 +#define ETDM_IN1_CON6 0x74 +#define ETDM_OUT1_CON0 0x7c +#define ETDM_SYNC_MODE BIT(1) +#define ETDM_OUT_EN BIT(0) +#define ETDM_OUT1_CON1 0x80 +#define ETDM_OUT1_CON2 0x84 +#define ETDM_OUT1_CON3 0x88 +#define ETDM_OUT1_CON4 0x8c +#define OUT_RELATCH GENMASK(28, 24) +#define OUT_CLK_SRC GENMASK(8, 6) +#define OUT_SEL_FS GENMASK(4, 0) +#define ETDM_OUT1_CON5 0x90 +#define ETDM_CLK_DIV BIT(12) +#define OUT_CLK_INV BIT(9) +#define ETDM_OUT1_CON6 0x94 +#define ETDM_OUT1_CON7 0x98 + +#define AFE_DL1_BASE 0xa8 +#define AFE_DL1_END 0xb0 +#define AFE_DL1_CUR 0xac +#define AFE_DL1_CON0 0xb4 +#define AFE_PBUF_SIZE_SHIFT 16 +#define AFE_PBUF_SIZE_MASK GENMASK(1, 0) +#define AFE_MINLEN_SHIFT 8 +#define AFE_MINLEN_MASK GENMASK(3, 0) +#define AFE_HD_SHIFT 5 + +#define AFE_UL1_BASE 0xc4 +#define AFE_UL1_END 0xc8 +#define AFE_UL1_CUR 0xcc +#define AFE_UL1_CON0 0xd0 +#define AFE_HD_ALIGN_SHIFT 6 + +#define AFE_IRQ_CON0 0xe4 +#define AFE_IRQ_EN_SEL BIT(4) +#define AFE_IRQ_EN_SEL_I2SIN FIELD_PREP_CONST(AFE_IRQ_EN_SEL, 0x0) +#define AFE_IRQ_EN_SEL_I2SOUT FIELD_PREP_CONST(AFE_IRQ_EN_SEL, 0x1) +#define AFE_IRQ_MISS_FLG_CLR_SHIFT 3 +#define AFE_IRQ_CLR_SHIFT 2 +#define AFE_IRQ_ON_SHIFT 0 +#define AFE_IRQ_CNT 0xe8 +#define AFE_IRQ_CNT_SHIFT 0 +#define AFE_IRQ_CNT_MASK GENMASK(31, 0) + +#define AFE_IRQ_STS 0xf8 +#define AFE_IRQ_STS_PLAY BIT(1) +#define AFE_IRQ_STS_RECORD BIT(0) + +#define AFE_MAX_REGISTER AFE_IRQ_STS + +#define AFE_IRQ1_CON0 0x0 +#define AFE_IRQ1_CNT 0x4 + +#define AFE_IRQ1_MAX_REGISTER AFE_IRQ1_CNT + +#endif From 272d6e51bc1f7db9ee58bb4029c95118a587c1c9 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Tue, 8 Sep 2026 11:04:47 +0200 Subject: [PATCH 0854/1417] ASoC: airoha: Add machine driver for Airoha AN7581 Add support for the machine driver for Airoha AN7581 paired with the WM8960 i2c codec. This driver creates the sound card for the AFE driver. Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260908090448.3332-6-ansuelsmth@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/Kconfig | 10 ++ sound/soc/mediatek/an7581/Makefile | 1 + sound/soc/mediatek/an7581/an7581-wm8960.c | 161 ++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 sound/soc/mediatek/an7581/an7581-wm8960.c diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index eb6f2a443b23ca..ff2869641a35f6 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -16,6 +16,16 @@ config SND_SOC_AN7581 Select Y if you have such device. If unsure select "N". +config SND_SOC_AN7581_WM8960 + tristate "ASoc Audio driver for Airoha AN7581 with WM8960 codec" + depends on SND_SOC_AN7581 && I2C + select SND_SOC_WM8960 + help + This adds support for ASoC machine driver for Airoha AN7581 + boards with the WM8960 codecs. + Select Y if you have such device. + If unsure select "N". + endmenu menu "Mediatek" diff --git a/sound/soc/mediatek/an7581/Makefile b/sound/soc/mediatek/an7581/Makefile index d6de5ee1668e9b..f48cd0269beafa 100644 --- a/sound/soc/mediatek/an7581/Makefile +++ b/sound/soc/mediatek/an7581/Makefile @@ -6,3 +6,4 @@ snd-soc-an7581-afe-y := \ an7581-dai-etdm.o obj-$(CONFIG_SND_SOC_AN7581) += snd-soc-an7581-afe.o +obj-$(CONFIG_SND_SOC_AN7581_WM8960) += an7581-wm8960.o diff --git a/sound/soc/mediatek/an7581/an7581-wm8960.c b/sound/soc/mediatek/an7581/an7581-wm8960.c new file mode 100644 index 00000000000000..03ea5732563cfe --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-wm8960.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha ALSA SoC I2S platform driver for AN7581 + * + */ + +#include +#include + +#include "an7581-afe-common.h" + +SND_SOC_DAILINK_DEFS(playback, + DAILINK_COMP_ARRAY(COMP_CPU("DL1")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY(), COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(capture, + DAILINK_COMP_ARRAY(COMP_CPU("UL1")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY(), COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(codec, + DAILINK_COMP_ARRAY(COMP_CPU("ETDM")), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY(), COMP_EMPTY())); + +static struct snd_soc_dai_link an7581_wm8960_dai_links[] = { + /* FE */ + { + .name = "wm8960-playback", + .stream_name = "wm8960-playback", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .playback_only = 1, + SND_SOC_DAILINK_REG(playback), + }, + { + .name = "wm8960-capture", + .stream_name = "wm8960-capture", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .capture_only = 1, + SND_SOC_DAILINK_REG(capture), + }, + /* BE */ + { + .name = "wm8960-codec", + .no_pcm = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP | + SND_SOC_DAIFMT_GATED, + SND_SOC_DAILINK_REG(codec), + }, +}; + +static struct snd_soc_card an7581_wm8960_card = { + .name = "an7581-wm8960", + .owner = THIS_MODULE, + .dai_link = an7581_wm8960_dai_links, + .num_links = ARRAY_SIZE(an7581_wm8960_dai_links), +}; + +static int an7581_wm8960_machine_probe(struct platform_device *pdev) +{ + struct device_node *platform_dai_node, *codec_dai_node; + struct snd_soc_card *card = &an7581_wm8960_card; + struct device_node *of_platform, *codec; + struct snd_soc_dai_link *dai_link; + int i, d, ret; + + card->dev = &pdev->dev; + + of_platform = of_get_child_by_name(pdev->dev.of_node, "platform"); + + if (of_platform) { + platform_dai_node = of_parse_phandle(of_platform, "sound-dai", 0); + of_node_put(of_platform); + + if (!platform_dai_node) { + dev_err(&pdev->dev, "Failed to parse platform/sound-dai property\n"); + return -EINVAL; + } + } else { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for_each_card_prelinks(card, i, dai_link) { + struct snd_soc_dai_link_component *platform; + + dai_link->num_platforms = 2; + for_each_link_platforms(dai_link, d, platform) { + if (platform->name) + continue; + platform->of_node = platform_dai_node; + } + } + + codec = of_get_child_by_name(pdev->dev.of_node, "codec"); + + if (codec) { + codec_dai_node = of_parse_phandle(codec, "sound-dai", 0); + of_node_put(codec); + + if (!codec_dai_node) { + of_node_put(platform_dai_node); + dev_err(&pdev->dev, "Failed to parse codec/sound-dai property\n"); + return -EINVAL; + } + } else { + of_node_put(platform_dai_node); + dev_err(&pdev->dev, "Property 'codec' missing or invalid\n"); + return -EINVAL; + } + + for_each_card_prelinks(card, i, dai_link) { + if (dai_link->codecs->name) + continue; + dai_link->codecs->of_node = codec_dai_node; + } + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "Failed to parse audio-routing: %d\n", ret); + goto err_of_node_put; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err_probe(&pdev->dev, ret, "%s snd_soc_register_card fail\n", __func__); + goto err_of_node_put; + } + + return 0; + +err_of_node_put: + of_node_put(platform_dai_node); + of_node_put(codec_dai_node); + return ret; +} + +static const struct of_device_id an7581_wm8960_machine_dt_match[] = { + { .compatible = "airoha,an7581-wm8960-sound" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, an7581_wm8960_machine_dt_match); + +static struct platform_driver an7581_wm8960_driver = { + .driver = { + .name = "an7581-wm8960", + .of_match_table = an7581_wm8960_machine_dt_match, + }, + .probe = an7581_wm8960_machine_probe, +}; +module_platform_driver(an7581_wm8960_driver); + +MODULE_DESCRIPTION("Airoha SoC I2S platform driver for ALSA AN7581"); +MODULE_LICENSE("GPL"); From 99cc2a62e07a44a22254d7beca9ef1f8ad886d0d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 13 Sep 2026 04:42:33 +0000 Subject: [PATCH 0855/1417] tipc: reject invalid and unexpected GRP_ACK_MSG to prevent bc_ackers underflow Commit 48a5fe38772b ("tipc: fix bc_ackers underflow on duplicate GRP_ACK_MSG") rejected duplicate/stale ACKs in tipc_group_proto_rcv() by returning early when less_eq(acked, m->bc_acked). However, that check remains incomplete in two ways: 1. When grp->bc_ackers is zero (e.g. on a quiet group, when replicast ACKs were not requested, or after all expected members have already acknowledged), an unexpected GRP_ACK_MSG with acked > m->bc_acked passes less_eq() and unconditionally decrements grp->bc_ackers. Because bc_ackers is a u16, this wraps to 65535, causing tipc_group_bc_cong() to permanently report congestion and blocking all future group broadcasts on the socket. 2. During an active broadcast round (grp->bc_ackers > 0), the sender transmits packet S and advances grp->bc_snd_nxt to S + 1. Receivers increment their expected counter to S + 1 upon consuming packet S, so the only valid ACK value for the current round is strictly acked == grp->bc_snd_nxt. However, tipc_group_update_bc_members() initializes each member's m->bc_acked to prev = grp->bc_snd_nxt - 1 (S - 1 before increment). This leaves a 2-sequence gap (S - 1 to S + 1) in sequence space. An incoming ACK is therefore neither rejected as duplicate nor prevented from decrementing grp->bc_ackers if an unexpected or stale value (such as S) is received. A member sending acked = S followed by acked = S + 1 could decrement grp->bc_ackers twice in the same round, prematurely clearing bc_ackers or underflowing it. Fix this by: - Dropping GRP_ACK_MSG immediately if grp->bc_ackers is zero. - Requiring acked == grp->bc_snd_nxt and rejecting duplicates where m->bc_acked == acked. Because replicast broadcast rounds are strictly sequential, only grp->bc_snd_nxt can be acknowledged, and each member can acknowledge at most once per round. Note that a related pre-existing issue in tipc_group_delete_member() (where grp->bc_ackers decrementing to zero upon member departure does not restore *grp->open or trigger a socket wakeup) will be addressed in a separate patch. Fixes: 48a5fe38772b ("tipc: fix bc_ackers underflow on duplicate GRP_ACK_MSG") Fixes: 2f487712b893 ("tipc: guarantee that group broadcast doesn't bypass group unicast") Reported-by: James Burton Cc: stable@vger.kernel.org Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260913044233.193927-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/tipc/group.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/tipc/group.c b/net/tipc/group.c index 14e6732624e28e..74f6d3dac0784d 100644 --- a/net/tipc/group.c +++ b/net/tipc/group.c @@ -797,10 +797,10 @@ void tipc_group_proto_rcv(struct tipc_group *grp, bool *usr_wakeup, tipc_group_open(m, usr_wakeup); return; case GRP_ACK_MSG: - if (!m) + if (!m || !grp->bc_ackers) return; acked = msg_grp_bc_acked(hdr); - if (less_eq(acked, m->bc_acked)) + if (acked != grp->bc_snd_nxt || m->bc_acked == acked) return; m->bc_acked = acked; if (--grp->bc_ackers) From 47abe7a5c4eb53269aca3506446f851572a059a3 Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Tue, 15 Sep 2026 22:08:16 +0700 Subject: [PATCH 0856/1417] net/sched: act_ct: don't WARN on benign flow_offload_alloc() failure flow_offload_alloc() returns NULL when the conntrack entry is dying (e.g. raced with a conntrack flush) or when the GFP_ATOMIC allocation fails; both are expected under load and neither is a kernel bug. This path runs from softirq on every committed packet, so with panic_on_warn=1 an unprivileged user can panic the box just by racing a conntrack flush against a `tc ... action ct commit` classifier. Reproduced with a custom repro under QEMU: a small, fixed set of UDP flows through `tc filter ... action ct commit` on lo, raced against threads flooding bare ctnetlink CT_DELETE (flush) requests. Hits WARNING: net/sched/act_ct.c:437 (tcf_ct_flow_table_add(), inlined into tcf_ct_act() in this build) within ~15s on the unpatched kernel; same setup is clean on the patched kernel. The fix itself is behavior-preserving: both branches already did `goto err_alloc` before and after, only the WARN is removed. Fixes: 64ff70b80fd4 ("net/sched: act_ct: Offload established connections to flow table") Reported-by: syzbot+6cc37aba98dac721c415@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6cc37aba98dac721c415 Signed-off-by: Nguyen Ngoc Thang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260915150816.36487-1-ngocthang2710.1999@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/act_ct.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index 9080cb386c169b..55f3521edb4c9a 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -432,11 +432,10 @@ static void tcf_ct_flow_table_add(struct tcf_ct_flow_table *ct_ft, if (test_and_set_bit(IPS_OFFLOAD_BIT, &ct->status)) return; + /* NULL if ct is dying (raced flush) or the atomic alloc failed. */ entry = flow_offload_alloc(ct); - if (!entry) { - WARN_ON_ONCE(1); + if (!entry) goto err_alloc; - } if (tcp) { ct->proto.tcp.seen[0].flags |= IP_CT_TCP_FLAG_BE_LIBERAL; From 2566866fc30965d915d0b52b5c3323b362619f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Tue, 15 Sep 2026 12:48:07 +0000 Subject: [PATCH 0857/1417] net: gue: reject invalid REMCSUM offsets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The REMCSUM option carries an absolute checksum start and checksum field offset. gue_remcsum() passes them to skb_remcsum_process(), whose partial path stores offset - start in the u16 skb->csum_offset variable. If offset is less than start, this underflows. A forwarded packet can retain CHECKSUM_PARTIAL and reach a NETIF_F_HW_CSUM driver which trusts the metadata, leading skb_copy_and_csum_dev() to write two bytes about 64 KiB beyond the destination buffer. Reject reversed tuples in validate_gue_flags(), after the existing length validation, so all GUE parsers enforce the ordering in one place. Fixes: fe881ef11cf0 ("gue: Use checksum partial with remote checksum offload") Signed-off-by: Jérémy Jean Link: https://patch.msgid.link/20260915124806.2852293-2-Jeremy.Jean@oss.cyber.gouv.fr Signed-off-by: Jakub Kicinski --- include/net/gue.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/include/net/gue.h b/include/net/gue.h index caefd6da86939b..d377155fd0b318 100644 --- a/include/net/gue.h +++ b/include/net/gue.h @@ -84,8 +84,9 @@ static inline size_t guehdr_priv_flags_len(__be32 flags) } /* Validate standard and private flags. Returns non-zero (meaning invalid) - * if there is an unknown standard or private flags, or the options length for - * the flags exceeds the options length specific in hlen of the GUE header. + * if there is an unknown standard or private flags, if the options length for + * the flags exceeds the options length specified in hlen of the GUE header, or + * if a private option contains invalid data. */ static inline int validate_gue_flags(struct guehdr *guehdr, size_t optlen) { @@ -103,8 +104,8 @@ static inline int validate_gue_flags(struct guehdr *guehdr, size_t optlen) /* Private flags are last four bytes accounted in * guehdr_flags_len */ - __be32 pflags = *(__be32 *)((void *)&guehdr[1] + - len - GUE_LEN_PRIV); + void *data = (void *)&guehdr[1] + len; + __be32 pflags = *(__be32 *)(data - GUE_LEN_PRIV); if (pflags & ~GUE_PFLAGS_ALL) return 1; @@ -112,6 +113,16 @@ static inline int validate_gue_flags(struct guehdr *guehdr, size_t optlen) len += guehdr_priv_flags_len(pflags); if (len > optlen) return 1; + + if (pflags & GUE_PFLAG_REMCSUM) { + __be16 *pd = data; + + /* The field offset pd[1] must not be less + * than the start pd[0]. + */ + if (ntohs(pd[1]) < ntohs(pd[0])) + return 1; + } } return 0; From 310d1ac61a4d5a2ca8356a3a48d263acf54503ce Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 16 Sep 2026 15:30:13 +0200 Subject: [PATCH 0858/1417] net: ethernet: mtk_eth_soc: unregister net_devices in case of probe failure If register_netdev() fails for one of the MTK_MAX_DEVS devices in mtk_probe(), the error path jumps to err_deinit_ppe, skipping mtk_unreg_dev(). The previously registered net_devices are then freed by mtk_free_dev() while still in NETREG_REGISTERED state, hitting the BUG_ON(dev->reg_state != NETREG_UNREGISTERED). Route the register_netdev() failure to err_unreg_netdev so the net_devices registered so far are properly unregistered before being freed. Fixes: 8a8a9e89f801 ("net: ethernet: mediatek: cleanup error path inside mtk_hw_init") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260916-mtk_eth_soc-netdev-fix-v1-1-5dac50eb65b1@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mediatek/mtk_eth_soc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c index fd7a49ae88d015..2ea5dfe85539b8 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -4509,6 +4509,10 @@ static int mtk_unreg_dev(struct mtk_eth *eth) mac = netdev_priv(eth->netdev[i]); if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) unregister_netdevice_notifier(&mac->device_notifier); + + if (eth->netdev[i]->reg_state != NETREG_REGISTERED) + continue; + unregister_netdev(eth->netdev[i]); } @@ -5344,7 +5348,7 @@ static int mtk_probe(struct platform_device *pdev) err = register_netdev(eth->netdev[i]); if (err) { dev_err(eth->dev, "error bringing up device\n"); - goto err_deinit_ppe; + goto err_unreg_netdev; } else netif_info(eth, probe, eth->netdev[i], "mediatek frame engine at 0x%08lx, irq %d\n", From 24fedc7a569bce181728f0dd616504bef9f1513b Mon Sep 17 00:00:00 2001 From: Andy Moreton Date: Wed, 16 Sep 2026 13:56:41 +0100 Subject: [PATCH 0859/1417] sfc: add X4D PF support X4D is an X4 controller instance as an IP block in an SoC. It has the same feature set as X4. Signed-off-by: Andy Moreton Reviewed-by: Pieter Jansen van Vuuren Reviewed-by: Alejandro Lucero Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260916125641.12238-1-alucerop@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/sfc/efx.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/sfc/efx.c b/drivers/net/ethernet/sfc/efx.c index 3806cd3dd7f485..953f1d90b9b1a3 100644 --- a/drivers/net/ethernet/sfc/efx.c +++ b/drivers/net/ethernet/sfc/efx.c @@ -904,6 +904,10 @@ static const struct pci_device_id efx_pci_table[] = { .driver_data = (unsigned long)&efx_x4_nic_type}, {PCI_DEVICE(PCI_VENDOR_ID_SOLARFLARE, 0x2c03), /* X4 PF (FF only) */ .driver_data = (unsigned long)&efx_x4_nic_type}, + {PCI_DEVICE(PCI_VENDOR_ID_SOLARFLARE, 0x8c03), /* X4D PF (FF/LL) */ + .driver_data = (unsigned long)&efx_x4_nic_type}, + {PCI_DEVICE(PCI_VENDOR_ID_SOLARFLARE, 0xac03), /* X4D PF (FF only) */ + .driver_data = (unsigned long)&efx_x4_nic_type}, {0} /* end of list */ }; From ee319bd3a0e976af5087cbe59ebc50a66f31d202 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Wed, 16 Sep 2026 21:57:53 +0200 Subject: [PATCH 0860/1417] ipv6: do not let ipv6_find_hdr() return an offset past the packet end ipv6_find_hdr() walks the extension header chain, skipping each header by the length that header itself declares. ipv6_optlen() returns up to 2048, and the skip is never checked against skb->len, so the offset stored in *offset can point past the end of the packet. openvswitch installs that offset as the transport header, and update_ipv6_checksum() then reads and writes the transport checksum field out of bounds: BUG: KASAN: slab-use-after-free in inet_proto_csum_replace16+0x445/0x470 Read of size 2 at addr ffff88810b754b06 by task ovs_ipv6_oob/629 CPU: 4 UID: 1000 PID: 629 Comm: ovs_ipv6_oob Tainted: G N 7.3.0-rc3+ #348 Call Trace: inet_proto_csum_replace16+0x445/0x470 set_ipv6_addr+0x3dd/0x460 do_execute_actions+0x6a3d/0x7c40 ovs_execute_actions+0xfd/0x480 ovs_packet_cmd_execute+0xc38/0xf20 genl_rcv_msg+0x59e/0x870 netlink_rcv_skb+0x18b/0x450 genl_rcv+0x2d/0x40 netlink_unicast+0x6bc/0xa20 The buggy address belongs to the object at ffff88810b754980 which belongs to the cache skbuff_small_head of size 704 The buggy address is located 390 bytes inside of freed 704-byte region [ffff88810b754980, ffff88810b754c40) Other callers use that offset too, so bound it here rather than in one caller. Reject a header whose declared length does not fit in the packet. ipv6_find_hdr() already fails with -EBADMSG on a malformed chain, so this adds no new failure mode. Fixes: f8f626754ebe ("ipv6: Move ipv6_find_hdr() out of Netfilter code.") Suggested-by: Ilya Maximets Suggested-by: Eric Dumazet Cc: stable@vger.kernel.org Signed-off-by: Norbert Szetei Reviewed-by: Ido Schimmel Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/8F80BA1A-DDFD-432D-9075-242A3435FEB5@doyensec.com Signed-off-by: Jakub Kicinski --- net/ipv6/exthdrs_core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv6/exthdrs_core.c b/net/ipv6/exthdrs_core.c index 9d06d487e8b103..4a9748338cf400 100644 --- a/net/ipv6/exthdrs_core.c +++ b/net/ipv6/exthdrs_core.c @@ -278,6 +278,9 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset, hdrlen = ipv6_optlen(hp); if (!found) { + if (skb->len - start < hdrlen) + return -EBADMSG; + nexthdr = hp->nexthdr; start += hdrlen; } From dd47bcf279f1083f09bf5266890b26263361022b Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 16 Sep 2026 23:09:24 +0000 Subject: [PATCH 0861/1417] ip6_gre: Call ip6erspan_tunnel_unlink_md() in ip6erspan_changelink(). The cited commit accidentally added ip6gre_tunnel_unlink_md() in ip6erspan_changelink(). Let's correct it to ip6erspan_tunnel_unlink_md(). Fixes: b80d0b93b991 ("net: ip6_gre: fix tunnel metadata device sharing.") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Xuanqiang Luo Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260916230927.378957-1-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_gre.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index 8ebda0b6a78b22..e61cb10b50dc96 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -2279,7 +2279,7 @@ static int ip6erspan_changelink(struct net_device *dev, struct nlattr *tb[], return PTR_ERR(t); ip6erspan_set_version(data, &p); - ip6gre_tunnel_unlink_md(ign, t); + ip6erspan_tunnel_unlink_md(ign, t); ip6gre_tunnel_unlink(ign, t); ip6erspan_tnl_change(t, &p, !tb[IFLA_MTU]); ip6erspan_tunnel_link_md(ign, t); From 95c4d54ed02283e9a09e8cd7360e384daa67a741 Mon Sep 17 00:00:00 2001 From: Abhishek Ojha Date: Wed, 16 Sep 2026 19:19:28 -0400 Subject: [PATCH 0862/1417] net: phy: micrel: Advance register data pointer in write loop lanphy_write_reg_data() does not advance the data pointer while iterating over the register table. As a result, it writes the first entry num times and leaves the remaining errata registers unconfigured. Single-entry tables are unaffected, but tables with multiple entries leave every entry after the first unapplied. Advance the data pointer after each successful write so every table entry is applied in order. Fixes: c8732e933925 ("net: phy: micrel: lan8842 errata") Cc: stable@vger.kernel.org Signed-off-by: Abhishek Ojha Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260916231928.1336305-1-abhishek.ojha@savoirfairelinux.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/micrel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index ae830781824b26..5c8461db7b4b6e 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -6344,6 +6344,7 @@ static int lanphy_write_reg_data(struct phy_device *phydev, data->val); if (ret) break; + data++; } return ret; From 1b82958f3f035df5ccaab5430a2302f08a5d5351 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Mon, 14 Sep 2026 14:09:57 -0700 Subject: [PATCH 0863/1417] net: ethtool: keep rtnl_lock for the ioctl self test An offline self test that brings the interface down and back up with netif_close() / netif_open() requires rtnl_lock for both. Since the ethtool IOCTL path became rtnl-optional for ops-locked drivers, the ETHTOOL_TEST ioctl runs holding only the netdev instance lock, so on an ops-locked driver the self test now tears the device down without rtnl_lock. With lockdep this reproduces deterministically on every offline self test on such a driver; note the sole lock held is the instance lock, not rtnl: WARNING: suspicious RCU usage net/core/netpoll.c:207 suspicious rcu_dereference_protected() usage! 1 lock held by ethtool/107: #0: (&dev->lock){+.+.}, at: dev_ethtool Call Trace: netpoll_poll_disable __dev_close_many netif_close_many netif_close fbnic_self_test dev_ethtool_locked dev_ethtool dev_ioctl sock_ioctl __x64_sys_ioctl Without lockdep the same condition trips ASSERT_RTNL() in __dev_close_many() / __dev_open(); that check only samples the global rtnl state, so it can be masked by a concurrent rtnl holder, but the device is still being reconfigured without the lock it requires. The ethtool self_test is a legacy ioctl-only command, so an ETHTOOL_TEST case is only needed on the ioctl path. Add an opt-in bit for drivers whose self test needs rtnl_lock and set it on the ops-locked drivers whose offline self test tears the interface down and up: - fbnic (ops-locked via queue_mgmt_ops): fbnic_self_test() offline path uses netif_close() / netif_open(). - bnxt (ops-locked via queue_mgmt_ops): bnxt_self_test() offline path goes through bnxt_close_nic() / bnxt_half_open_nic() / bnxt_half_close_nic() / bnxt_open_nic(), which close and reopen the device. Fixes: f994752b1127 ("net: ethtool: optionally skip rtnl_lock on IOCTL path") Signed-off-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/178942019771.7700.338431553546884773.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 3 ++- drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c | 3 ++- include/linux/ethtool.h | 2 ++ net/ethtool/common.h | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c index 62bc9cae613c38..622e89587e5db4 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c @@ -5733,7 +5733,8 @@ const struct ethtool_ops bnxt_ethtool_ops = { .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | ETHTOOL_OP_NEEDS_RTNL_SCOALESCE | - ETHTOOL_OP_NEEDS_RTNL_RSS, + ETHTOOL_OP_NEEDS_RTNL_RSS | + ETHTOOL_OP_NEEDS_RTNL_TEST, .supported_coalesce_params = ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_MAX_FRAMES | ETHTOOL_COALESCE_USECS_IRQ | diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c index 0e47088ec44baf..423f179c9d4756 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c @@ -2025,7 +2025,8 @@ static const struct ethtool_ops fbnic_ethtool_ops = { ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM | ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | - ETHTOOL_OP_NEEDS_RTNL_GLINK, + ETHTOOL_OP_NEEDS_RTNL_GLINK | + ETHTOOL_OP_NEEDS_RTNL_TEST, .get_drvinfo = fbnic_get_drvinfo, .get_regs_len = fbnic_get_regs_len, .get_regs = fbnic_get_regs, diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 253600c0eccdf9..c4c9ce0386111b 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -944,6 +944,7 @@ struct kernel_ethtool_ts_info { #define ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM BIT(6) #define ETHTOOL_OP_NEEDS_RTNL_RSS BIT(7) #define ETHTOOL_OP_NEEDS_RTNL_GLINK BIT(8) +#define ETHTOOL_OP_NEEDS_RTNL_TEST BIT(9) /** * struct ethtool_ops - optional netdev operations @@ -981,6 +982,7 @@ struct kernel_ethtool_ts_info { * - netdev_update_features() * - netif_set_real_num_tx_queues() * - ethtool_op_get_link() (syncs link watch under rtnl_lock) + * - netif_open() / netif_close() (used by @self_test) * * @get_drvinfo: Report driver/device information. Modern drivers no * longer have to implement this callback. Most fields are diff --git a/net/ethtool/common.h b/net/ethtool/common.h index 4e5356e26f400a..ae32e7fdb563c1 100644 --- a/net/ethtool/common.h +++ b/net/ethtool/common.h @@ -163,6 +163,8 @@ ethtool_ioctl_needs_rtnl(const struct net_device *dev, u32 ethcmd) return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_RSS; case ETHTOOL_GLINK: return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GLINK; + case ETHTOOL_TEST: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_TEST; } return false; } From 1f4c73064a50f53d596c6f1d06d2d700f43c4b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20T=C3=B6pel?= Date: Mon, 14 Sep 2026 14:10:04 -0700 Subject: [PATCH 0864/1417] eth: fbnic: Handle maximum standalone channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standalone channels use one NAPI vector for each Tx and Rx queue. fbnic's allocation path excludes FBNIC_MAX_TXQS from that layout. A 64-Tx/64-Rx configuration therefore records 128 vectors but allocates only 64, leaving NULL entries that resource setup dereferences. Include the maximum vector count in standalone allocation. Fixes: bc6107771bb4 ("eth: fbnic: Allocate a netdevice and napi vectors with queues") Signed-off-by: Björn Töpel Reviewed-by: Simon Horman Link: https://patch.msgid.link/178942020457.7700.13129750616387075931.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index 661dee1661afeb..a30aa445084874 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -1791,7 +1791,7 @@ int fbnic_alloc_napi_vectors(struct fbnic_net *fbn) int err; /* Allocate 1 Tx queue per napi vector */ - if (num_napi < FBNIC_MAX_TXQS && num_napi == num_tx + num_rx) { + if (num_napi <= FBNIC_MAX_TXQS && num_napi == num_tx + num_rx) { while (num_tx) { err = fbnic_alloc_napi_vector(fbd, fbn, num_napi, v_idx, From b5d9e9d4d0c13bc8b60d8d97e7a07fb25fea639e Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Mon, 14 Sep 2026 14:10:11 -0700 Subject: [PATCH 0865/1417] eth: fbnic: use the Rx queue napi pointer to find the napi vector The queue management ndos pick the napi vector for an Rx queue with: nv = fbn->napi[idx % fbn->num_napi]; The issue is this is only correct in the cases where there are no standalone Tx vectors. In those cases we were allocating the Tx vectors first and then the Rx so the queues would be pointing to Tx NAPI vectors instead of the Rx ones. The mapping the ndos want is already recorded. fbnic_set_netif_napi() publishes it with netif_queue_set_napi(), which stores the napi pointer in netdev_rx_queue.napi, and fbnic_reset_netif_napi() clears it again. Both run under the netdev instance lock that the queue management ndos also hold, so the pointer can be read directly. Use it and drop the divide. The pointer is NULL exactly while the datapath is down, so fbnic_queue_mem_alloc() can reject that case rather than reaching into freed state: netdev_rx_queue_restart() calls it before it tests netif_running(), and fbnic_pm_suspend() leaves netif_running() true across a PCIe recovery that never completes, so a queue restart can arrive after fbnic_stop() has freed the rings and the vectors. fbnic_stop() clears the association in fbnic_reset_netif_queues() before fbnic_free_napi_vectors(), so the NULL is always published first. fbnic_queue_start() and fbnic_queue_stop() need no check of their own, as netdev_rx_queue_reconfig() only reaches them once fbnic_queue_mem_alloc() has succeeded under the same instance lock. Fixes: da43127a8edc ("eth: fbnic: support queue ops / zero-copy Rx") Signed-off-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/178942021136.7700.4391219358260544104.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 26 +++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index a30aa445084874..10caacffee0f0e 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -2853,6 +2854,17 @@ void fbnic_napi_depletion_check(struct net_device *netdev) fbnic_wrfl(fbd); } +/* Returns the napi vector servicing an Rx queue, or NULL if the datapath + * is torn down. The association is published by fbnic_set_netif_napi() + * and cleared by fbnic_reset_netif_napi(), both under the instance lock. + */ +static struct fbnic_napi_vector *fbnic_rxq_nv(struct net_device *dev, int idx) +{ + struct napi_struct *napi = __netif_get_rx_queue(dev, idx)->napi; + + return napi ? container_of(napi, struct fbnic_napi_vector, napi) : NULL; +} + static int fbnic_queue_mem_alloc(struct net_device *dev, struct netdev_queue_config *qcfg, void *qmem, int idx) @@ -2865,8 +2877,16 @@ static int fbnic_queue_mem_alloc(struct net_device *dev, if (!netif_running(dev)) return fbnic_alloc_qt_page_pools(fbn, qt, idx); + /* A failed PCIe recovery or resume can leave the datapath torn down + * while netif_running() is still true. This ndo runs before + * netdev_rx_queue_restart() checks netif_running(), so bail out + * rather than touching rings and vectors that are already freed. + */ + nv = fbnic_rxq_nv(dev, idx); + if (!nv) + return -ENETDOWN; + real = container_of(fbn->rx[idx], struct fbnic_q_triad, cmpl); - nv = fbn->napi[idx % fbn->num_napi]; fbnic_ring_init(&qt->sub0, real->sub0.doorbell, real->sub0.q_idx, real->sub0.flags); @@ -2917,7 +2937,7 @@ static int fbnic_queue_start(struct net_device *dev, struct fbnic_q_triad *real; real = container_of(fbn->rx[idx], struct fbnic_q_triad, cmpl); - nv = fbn->napi[idx % fbn->num_napi]; + nv = fbnic_rxq_nv(dev, idx); fbnic_aggregate_ring_bdq_counters(fbn, &real->sub0); fbnic_aggregate_ring_bdq_counters(fbn, &real->sub1); @@ -2939,7 +2959,7 @@ static int fbnic_queue_stop(struct net_device *dev, void *qmem, int idx) int err; real = container_of(fbn->rx[idx], struct fbnic_q_triad, cmpl); - nv = fbn->napi[idx % fbn->num_napi]; + nv = fbnic_rxq_nv(dev, idx); fbnic_dbg_nv_exit(nv); napi_disable_locked(&nv->napi); From 4bcc4a92c603fe7f062cea22e20da2e0ad6b12c3 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Mon, 14 Sep 2026 14:10:18 -0700 Subject: [PATCH 0866/1417] eth: fbnic: reset num_napi when the napi vectors are freed fbn->num_napi is the count of live napi vectors, each of which owns an IRQ. The PM path had freed them without clearing the count. fbnic_pm_suspend() tears the datapath down via ndo_stop() and frees the IRQs, but leaves netif_running() true so resume knows to re-open. Resume rebuilds the datapath in __fbnic_pm_resume() and fbnic_reset_queues() sets num_napi and __fbnic_open() re-allocates the vectors. When the datapath is torn down but never rebuilt, num_napi is left pointing at freed vectors under 2 different scenarios: - a PCIe error recovery that fails (fbnic_err_slot_reset() -> __fbnic_pm_resume() returns an error -> PCI_ERS_RESULT_DISCONNECT), so .resume never runs; or - an __fbnic_open() that fails partway on resume and unwinds, freeing the vectors after fbnic_reset_queues() has already set num_napi. The netdev is then running with num_napi > 0 but napi[] freed, and the eventual remove/unbind close re-enters fbnic_down() -> fbnic_dbg_down() and dereferences the freed vectors: BUG: kernel NULL pointer dereference, address: 0000000000000210 RIP: fbnic_dbg_down+0x28 Clear num_napi when the vectors are freed: in the suspend teardown (a good resume re-establishes it before __fbnic_open()) and on the resume open failure. A redundant ndo_stop() then walks an empty napi[]. The normal ndo_stop() down/up cycle is untouched and keeps num_napi for the next ndo_open(). Fixes: bc6107771bb4 ("eth: fbnic: Allocate a netdevice and napi vectors with queues") Signed-off-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/178942021809.7700.10804028989308077839.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_pci.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c index 8b9bc9e8ea56ca..c6698e3002a135 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c @@ -434,6 +434,7 @@ static int fbnic_pm_suspend(struct device *dev) { struct fbnic_dev *fbd = dev_get_drvdata(dev); struct net_device *netdev = fbd->netdev; + struct fbnic_net *fbn; if (fbnic_init_failure(fbd)) goto null_uc_addr; @@ -441,11 +442,16 @@ static int fbnic_pm_suspend(struct device *dev) rtnl_lock(); netdev_lock(netdev); + fbn = netdev_priv(netdev); + netif_device_detach(netdev); if (netif_running(netdev)) netdev->netdev_ops->ndo_stop(netdev); + /* The IRQs are about to be freed, so drop the napi vector count */ + fbn->num_napi = 0; + netdev_unlock(netdev); rtnl_unlock(); @@ -508,16 +514,20 @@ static int __fbnic_pm_resume(struct device *dev) if (fbnic_init_failure(fbd)) return 0; + rtnl_lock(); + netdev_lock(netdev); + fbn = netdev_priv(netdev); /* Reset the queues if needed */ fbnic_reset_queues(fbn, fbn->num_tx_queues, fbn->num_rx_queues); - rtnl_lock(); - netdev_lock(netdev); - - if (netif_running(netdev)) + if (netif_running(netdev)) { err = __fbnic_open(fbn); + /* On failure the vectors are freed, so drop the count */ + if (err) + fbn->num_napi = 0; + } netdev_unlock(netdev); rtnl_unlock(); From 8947f13e436a4ff5eed9f8f019b2865a07af4bb2 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Mon, 14 Sep 2026 14:10:25 -0700 Subject: [PATCH 0867/1417] eth: fbnic: Set AW_FLUSH_MODE alongside AW_FLUSH when flushing the mailbox When tearing down the FW mailbox Rx ring, fbnic_mbx_reset_desc_ring() writes AW_CFG with FLUSH set and everything else, BME included, cleared. Clearing BME halts the device's writes to the host but leaves the staged requests parked in the PUL write pipeline rather than draining them, so on the write path FLUSH alone never terminates the outstanding requests and the flush the firmware waits on never completes. Add the FLUSH_MODE definition and set both bits so the staged writes drain out of the pipeline on their own. BME stays cleared, so nothing lands on the host; it is restored later in fbnic_mbx_init_desc_ring() when the ring is rebuilt, once the outstanding writes are gone. The read path is unaffected. AR_CFG has no equivalent mode bit and AR_FLUSH terminates the outstanding reads by itself, so it is left as is. Both writes remain plain stores rather than read-modify-writes. That is deliberate: the matching write in fbnic_mbx_init_desc_ring() restores BME and the TLP attributes, and clears both flush bits as a side effect. Fixes: 3b12f00ddd08 ("fbnic: Gate AXI read/write enabling on FW mailbox") Signed-off-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/178942022583.7700.11050671998277309744.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_csr.h | 1 + drivers/net/ethernet/meta/fbnic/fbnic_fw.c | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h index 64b958df777443..14af30e189d6c2 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h @@ -974,6 +974,7 @@ enum { /* PUL User Registers */ #define FBNIC_CSR_START_PUL_USER 0x31000 /* CSR section delimiter */ #define FBNIC_PUL_OB_TLP_HDR_AW_CFG 0x3103d /* 0xc40f4 */ +#define FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH_MODE CSR_BIT(20) #define FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH CSR_BIT(19) #define FBNIC_PUL_OB_TLP_HDR_AW_CFG_BME CSR_BIT(18) #define FBNIC_PUL_OB_TLP_HDR_AW_CFG_RDE_ATTR CSR_GENMASK(17, 15) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c index 283d25fae79e78..59aa879798b9fe 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c @@ -60,8 +60,15 @@ static void fbnic_mbx_reset_desc_ring(struct fbnic_dev *fbd, int mbx_idx) */ switch (mbx_idx) { case FBNIC_IPC_MBX_RX_IDX: + /* Clearing BME blocks the device from writing to the host + * but leaves the requests parked in the write pipeline. The + * write path only clears outstanding requests when both FLUSH + * and FLUSH_MODE are set; FLUSH_MODE lets them drain without + * landing on the host. + */ wr32(fbd, FBNIC_PUL_OB_TLP_HDR_AW_CFG, - FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH); + FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH | + FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH_MODE); break; case FBNIC_IPC_MBX_TX_IDX: wr32(fbd, FBNIC_PUL_OB_TLP_HDR_AR_CFG, From 1b97a269a5bdde20d4e69511f27649c9cb82b7c7 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Mon, 14 Sep 2026 14:10:33 -0700 Subject: [PATCH 0868/1417] eth: fbnic: Handle FW mailbox completions flagged with an error The firmware can complete a mailbox descriptor while also setting FW_ERR to indicate it could not process the request, for example on a mailbox DMA error. The completion carries no valid data. The driver did not check FW_ERR. On the Rx mailbox it would sync and parse the stale page as a normal message, and on the Tx mailbox it silently freed the request. If the initial capabilities exchange in fbnic_mbx_poll_tx_ready() hit FW_ERR -- on the Tx request or on the Rx response descriptor -- no response was parsed and the poll spun until it timed out even though the ring was healthy. Check FW_ERR on both mailboxes. Count it per-mailbox in fbnic_fw_mbx.resp_error, which is also shown in debugfs, warn (rate limited, since the bit is firmware controlled), and drop the Rx page instead of parsing it. In fbnic_mbx_poll_tx_ready() re-issue the capabilities request when either the Tx or the Rx resp_error counter advances, so a FW_ERR on the request or on its response triggers a retry rather than a timeout. A valid capabilities response is honored before the retry check, so a response parsed in the same poll as an unrelated FW_ERR is not discarded. The counters are mailbox-wide rather than keyed to the capabilities request; that is sufficient here because the exchange runs during bring-up before any other mailbox traffic, and any spurious retry is bounded by the existing 10s timeout. Fixes: da3cde08209e ("eth: fbnic: Add FW communication mechanism") Signed-off-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/178942023343.7700.9423398932961964439.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_csr.h | 4 ++ .../net/ethernet/meta/fbnic/fbnic_debugfs.c | 4 +- drivers/net/ethernet/meta/fbnic/fbnic_fw.c | 38 ++++++++++++++++++- drivers/net/ethernet/meta/fbnic/fbnic_fw.h | 1 + 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h index 14af30e189d6c2..baba3471bf5a42 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h @@ -1216,6 +1216,10 @@ enum { #define FBNIC_IPC_MBX_DESC_LEN_MASK DESC_GENMASK(63, 48) #define FBNIC_IPC_MBX_DESC_EOM DESC_BIT(46) #define FBNIC_IPC_MBX_DESC_ADDR_MASK DESC_GENMASK(45, 3) +/* Set with FW_CMPL when the FW completed a descriptor without successfully + * processing it (e.g. a mailbox DMA error); the completion has no valid data. + */ +#define FBNIC_IPC_MBX_DESC_FW_ERR DESC_BIT(2) #define FBNIC_IPC_MBX_DESC_FW_CMPL DESC_BIT(1) #define FBNIC_IPC_MBX_DESC_HOST_CMPL DESC_BIT(0) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c b/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c index 3c4563c8f403fa..6edfa0aa69f111 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c @@ -539,8 +539,8 @@ static void fbnic_dbg_fw_mbx_display(struct seq_file *s, /* Generate header */ seq_puts(s, mbx_idx == FBNIC_IPC_MBX_RX_IDX ? "Rx\n" : "Tx\n"); - seq_printf(s, "Rdy: %d Head: %d Tail: %d\n", - mbx->ready, mbx->head, mbx->tail); + seq_printf(s, "Rdy: %d Head: %d Tail: %d resp_error: %llu\n", + mbx->ready, mbx->head, mbx->tail, mbx->resp_error); snprintf(hdr, sizeof(hdr), "%3s %-4s %s %-12s %s %-3s %-16s\n", "Idx", "Len", "E", "Addr", "F", "H", "Raw"); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c index 59aa879798b9fe..6d7eb8479edf4b 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c @@ -292,6 +292,12 @@ static void fbnic_mbx_process_tx_msgs(struct fbnic_dev *fbd) if (!(desc & FBNIC_IPC_MBX_DESC_FW_CMPL)) break; + if (desc & FBNIC_IPC_MBX_DESC_FW_ERR) { + tx_mbx->resp_error++; + dev_warn_ratelimited(fbd->dev, + "FW completed a Tx mailbox request with an error\n"); + } + fbnic_mbx_unmap_and_free_msg(fbd, FBNIC_IPC_MBX_TX_IDX, head); head++; @@ -1673,6 +1679,13 @@ static void fbnic_mbx_process_rx_msgs(struct fbnic_dev *fbd) if (!(desc & FBNIC_IPC_MBX_DESC_FW_CMPL)) break; + if (desc & FBNIC_IPC_MBX_DESC_FW_ERR) { + rx_mbx->resp_error++; + dev_warn_ratelimited(fbd->dev, + "FW reported an error on an Rx mailbox message; dropping\n"); + goto next_page; + } + dma_sync_single_for_cpu(fbd->dev, rx_mbx->buf_info[head].addr, FBNIC_RX_PAGE_SIZE, DMA_FROM_DEVICE); @@ -1740,7 +1753,9 @@ void fbnic_mbx_poll(struct fbnic_dev *fbd) int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd) { struct fbnic_fw_mbx *tx_mbx = &fbd->mbx[FBNIC_IPC_MBX_TX_IDX]; + struct fbnic_fw_mbx *rx_mbx = &fbd->mbx[FBNIC_IPC_MBX_RX_IDX]; unsigned long timeout = jiffies + 10 * HZ + 1; + u64 tx_resp_error, rx_resp_error; int err, i; do { @@ -1771,6 +1786,9 @@ int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd) * mgmt.version once we get the actual version from the firmware * in the capabilities request message. */ +send_cap_req: + tx_resp_error = tx_mbx->resp_error; + rx_resp_error = rx_mbx->resp_error; err = fbnic_fw_xmit_simple_msg(fbd, FBNIC_TLV_MSG_ID_HOST_CAP_REQ); if (err) goto clean_mbx; @@ -1788,9 +1806,27 @@ int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd) msleep(20); fbnic_mbx_poll(fbd); + /* A valid capabilities response ends the poll. Check it + * before the FW_ERR retry below so a response parsed in the + * same poll as an unrelated FW_ERR is not discarded. + */ + if (fbd->fw_cap.running.mgmt.version >= MIN_FW_VER_CODE) + break; + /* set err, but wait till mgmt.version check to report it */ - if (!time_is_after_jiffies(timeout)) + if (!time_is_after_jiffies(timeout)) { err = -ETIMEDOUT; + continue; + } + + /* The FW can flag our capabilities request (Tx) or its + * response (Rx) with FW_ERR, in which case it produced no + * usable response. The ring is not wedged, so re-issue the + * request instead of spinning until the timeout. + */ + if (tx_mbx->resp_error != tx_resp_error || + rx_mbx->resp_error != rx_resp_error) + goto send_cap_req; } return 0; diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.h b/drivers/net/ethernet/meta/fbnic/fbnic_fw.h index d84723e4cfa362..5f9969247e3059 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.h @@ -13,6 +13,7 @@ struct fbnic_tlv_msg; struct fbnic_fw_mbx { u8 ready, head, tail; + u64 resp_error; struct { struct fbnic_tlv_msg *msg; dma_addr_t addr; From d2c31b837406395e576afeb25958c98e9938f3f6 Mon Sep 17 00:00:00 2001 From: Yiqi Sun Date: Tue, 15 Sep 2026 17:50:17 +0800 Subject: [PATCH 0869/1417] sctp: avoid livelock while updating retransmit path sctp_assoc_update_retran_path() can loop forever when every remaining transport, including retran_path, is SCTP_UNCONFIRMED: the state check runs before the wraparound test, so the loop cannot observe that it has completed a full pass. Fix this by considering a transport only when it is not UNCONFIRMED, then checking whether the walk has returned to retran_path. This makes the full-pass termination independent of the transport state while preserving the existing fallback selection semantics. Also restore the NULL guard around the retran_path assignment. In the all-UNCONFIRMED case there is no eligible replacement transport, and installing NULL would leave later retransmit-path users and the debug print with a NULL path. Fixes: 4c47af4d5eb2 ("net: sctp: rework multihoming retransmission path selection to rfc4960") Signed-off-by: Yiqi Sun Acked-by: Xin Long Link: https://patch.msgid.link/20260915095017.942213-1-sunyiqixm@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/associola.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/net/sctp/associola.c b/net/sctp/associola.c index c0512c827d0f56..4521be3bd85aa5 100644 --- a/net/sctp/associola.c +++ b/net/sctp/associola.c @@ -1289,18 +1289,19 @@ void sctp_assoc_update_retran_path(struct sctp_association *asoc) /* Manually skip the head element. */ if (&trans->transports == &asoc->peer.transport_addr_list) continue; - if (trans->state == SCTP_UNCONFIRMED) - continue; - trans_next = sctp_trans_elect_best(trans, trans_next); - /* Active is good enough for immediate return. */ - if (trans_next->state == SCTP_ACTIVE) - break; + if (trans->state != SCTP_UNCONFIRMED) { + trans_next = sctp_trans_elect_best(trans, trans_next); + /* Active is good enough for immediate return. */ + if (trans_next->state == SCTP_ACTIVE) + break; + } /* We've reached the end, time to update path. */ if (trans == asoc->peer.retran_path) break; } - asoc->peer.retran_path = trans_next; + if (trans_next) + asoc->peer.retran_path = trans_next; pr_debug("%s: association:%p updated new path to addr:%pISpc\n", __func__, asoc, &asoc->peer.retran_path->ipaddr.sa); From 4581c3d2adc3c73a019bc38db64ca11f28bbd7fd Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Thu, 17 Sep 2026 14:27:23 +0200 Subject: [PATCH 0870/1417] net/mlx5e: advertise MACsec offload only when supported Commit 339ccec8d43d ("net/mlx5: Enable MACsec offload feature for VLAN interface") added NETIF_F_HW_MACSEC unconditionally to vlan_features so that VLAN devices could inherit MACsec offload support. mlx5e_build_nic_netdev subsequently copies vlan_features into hw_features and features. As a result, all mlx5e NIC netdevices advertise MACsec hardware offload, even when the firmware does not support it and the driver does not install macsec_ops. Set the MACsec feature bits in mlx5e_macsec_build_netdev, after device capabilities have been validated. This preserves MACsec-over-VLAN support and the ethtool feature control on capable devices, without advertising either on unsupported hardware. Fixes: 339ccec8d43d ("net/mlx5: Enable MACsec offload feature for VLAN interface") Cc: stable@vger.kernel.org Reviewed-by: Tariq Toukan Signed-off-by: Ralf Lici Link: https://patch.msgid.link/20260917122724.654639-1-ralf@mandelbit.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c | 2 ++ drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c index daff53ba7d09f9..38a3415acf7a38 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c @@ -1724,6 +1724,8 @@ void mlx5e_macsec_build_netdev(struct mlx5e_priv *priv) mlx5_core_dbg(priv->mdev, "mlx5e: MACsec acceleration enabled\n"); netdev->macsec_ops = &macsec_offload_ops; netdev->features |= NETIF_F_HW_MACSEC; + netdev->hw_features |= NETIF_F_HW_MACSEC; + netdev->vlan_features |= NETIF_F_HW_MACSEC; netif_keep_dst(netdev); } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index fc110a7d16e8da..4c6060d54fbed9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -5851,7 +5851,6 @@ static void mlx5e_build_nic_netdev(struct net_device *netdev) netdev->vlan_features |= NETIF_F_SG; netdev->vlan_features |= NETIF_F_HW_CSUM; - netdev->vlan_features |= NETIF_F_HW_MACSEC; netdev->vlan_features |= NETIF_F_GRO; netdev->vlan_features |= NETIF_F_TSO; netdev->vlan_features |= NETIF_F_TSO6; From 6c096bb08de97cdca051fecddad22cac6a1fd275 Mon Sep 17 00:00:00 2001 From: Quentin Armitage Date: Tue, 15 Sep 2026 22:33:21 +0100 Subject: [PATCH 0871/1417] net: allow IFLA_INET_CONF messages when NLA_F_NESTED unset Commit fa8fca88714c ("ipv4: validate IPV4_DEVCONF attributes properly") added validation of IFLA_INET_CONF attributes, and in the process changed the call of nla_for_each_nested() to nla_parse_nested(). A side effect of this change is that the IFLA_INET_CONF option is now tested for NLA_F_NESTED being set, and fails if it is not. Prior to the commit there was no check of NLA_F_NESTED. Change nla_parse_nested() to nla_parse(). This restores the previous functionality of not checking NLA_F_NESTED, thereby allowing code that (incorrectly) doesn't set NLA_F_NESTED to continue to work. This issue was identified because keepalived started logging errors when it was configuring macvlans that it created. Fixes: fa8fca88714c ("ipv4: validate IPV4_DEVCONF attributes properly") Signed-off-by: Quentin Armitage Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260915213320.1527029-2-quentin@armitage.org.uk Signed-off-by: Jakub Kicinski --- net/ipv4/devinet.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index a90be57c63be15..5b6b11c943e453 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -2117,9 +2117,10 @@ static int inet_validate_link_af(const struct net_device *dev, return err; if (tb[IFLA_INET_CONF]) { - err = nla_parse_nested(nested_tb, IPV4_DEVCONF_MAX, - tb[IFLA_INET_CONF], inet_devconf_policy, - extack); + err = nla_parse(nested_tb, IPV4_DEVCONF_MAX, + nla_data(tb[IFLA_INET_CONF]), + nla_len(tb[IFLA_INET_CONF]), + inet_devconf_policy, extack); if (err < 0) return err; From 10396a2d6d41d594975b6ece712278570c3c970c Mon Sep 17 00:00:00 2001 From: Holger Dengler Date: Fri, 14 Aug 2026 16:20:10 +0200 Subject: [PATCH 0872/1417] crypto: s390/hmac - Generate intermediate CV for API partial block handling The API partial block handling requires a intermediate chaining value (CV). The internal function hash_data() sets the function code correctly, so also call cpacf_kimd() instruction for intermediate CV generation, as cpacf_klmd() always generate the final hash value. Cc: stable@vger.kernel.org # 6.15+ Fixes: 08811169ac01 ("crypto: s390/hmac - Use API partial block handling") Signed-off-by: Holger Dengler Reviewed-by: Harald Freudenberger Signed-off-by: Herbert Xu --- arch/s390/crypto/hmac_s390.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/s390/crypto/hmac_s390.c b/arch/s390/crypto/hmac_s390.c index f8cd09f341d4b0..445fa7bbd95860 100644 --- a/arch/s390/crypto/hmac_s390.c +++ b/arch/s390/crypto/hmac_s390.c @@ -150,7 +150,10 @@ static int hash_data(const u8 *in, unsigned int inlen, #undef PARAM_INIT - cpacf_klmd(func, ¶m, in, inlen); + if (final) + cpacf_klmd(func, ¶m, in, inlen); + else + cpacf_kimd(func, ¶m, in, inlen); memcpy(digest, ¶m, digestsize); From 63edf5a009ae366369a1b484cd9ae4ee7c51946c Mon Sep 17 00:00:00 2001 From: "Chang S. Bae" Date: Wed, 16 Sep 2026 23:00:03 +0000 Subject: [PATCH 0873/1417] x86/build/64: Prevent native builds from generating EGPR use Omar reports that CONFIG_X86_NATIVE_CPU=y allows builds to opportunistically emit instructions using %r16-%r31 (EGPRs) when the build host supports APX since the commit: ea1dcca1de12 ("x86/kbuild/64: Add the CONFIG_X86_NATIVE_CPU option to locally optimize the kernel with '-march=native'") But the kernel is not yet prepared to use new registers internally. For example, there is no context-switch support for general in-kernel use. Explicitly disable EGPR use when building with -march=native. For C, since GCC 14 and Clang 18, both compilers support suppressing EGPR use with -mno-apx-features=egpr, whose availability can be detected via cc-option. For Rust, pass features=-apxf through the generated JSON to avoid unstable-feature warnings, see https://github.com/rust-lang/rust/issues/139284 Note Rust only accepts the option to disable APX instructions entirely or not. Support for this gating also depends on the Rust/LLVM combination. Rust 1.88 introduced the `apxf` feature option, but versions prior to 1.93 may emit an `apxf` attribute to the backend that only LLVM 23 or later can interpret. Restrict native Rust builds accordingly. Fixes: ea1dcca1de12 ("x86/kbuild/64: Add the CONFIG_X86_NATIVE_CPU option to locally optimize the kernel with '-march=native'") Reported-by: Omar Avelar Signed-off-by: Chang S. Bae Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Nathan Chancellor Acked-by: Miguel Ojeda Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260916230003.1144622-1-chang.seok.bae@intel.com --- arch/x86/Kconfig.cpu | 11 +++++++++++ arch/x86/Makefile | 5 +++++ scripts/generate_rust_target.rs | 5 +++++ 3 files changed, 21 insertions(+) diff --git a/arch/x86/Kconfig.cpu b/arch/x86/Kconfig.cpu index e4654388d794ba..6e7a366f079841 100644 --- a/arch/x86/Kconfig.cpu +++ b/arch/x86/Kconfig.cpu @@ -204,10 +204,21 @@ config CC_HAS_MARCH_NATIVE # usage warnings that only appear wth '-march=native'. depends on CC_IS_GCC || CLANG_VERSION >= 190100 +config RUSTC_HAS_APXF + # The kernel isn't ready for in-kernel APX instructions. Without + # explicit frontend gating of APX, the backend may emit those + # instructions in native builds. + # + # Rust 1.88 added the `apxf` feature option, but versions before 1.93 + # emit an `apxf` target attribute that only LLVM 23+ can interpret. + def_bool (RUSTC_VERSION >= 108800 && RUSTC_LLVM_MAJOR_VERSION >= 23) || \ + RUSTC_VERSION >= 109300 + config X86_NATIVE_CPU bool "Build and optimize for local/native CPU" depends on X86_64 depends on CC_HAS_MARCH_NATIVE + depends on !RUST || RUSTC_HAS_APXF help Optimize for the current CPU used to compile the kernel. Use this option if you intend to build the kernel for your diff --git a/arch/x86/Makefile b/arch/x86/Makefile index 598f178102ee4a..8af6b80cffdd8b 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile @@ -161,6 +161,11 @@ else ifdef CONFIG_X86_NATIVE_CPU KBUILD_CFLAGS += -march=native + # Prevent the compiler from generating EGPR use. The kernel is + # not yet prepared for general in-kernel use. + KBUILD_CFLAGS += $(call cc-option,-mno-apx-features=egpr) + + # generate_rust_target.rs handles Rust APX gating. KBUILD_RUSTFLAGS += -Ctarget-cpu=native else KBUILD_CFLAGS += -march=x86-64 -mtune=generic diff --git a/scripts/generate_rust_target.rs b/scripts/generate_rust_target.rs index 3bf296581a88ec..7687b0dd5474e5 100644 --- a/scripts/generate_rust_target.rs +++ b/scripts/generate_rust_target.rs @@ -224,6 +224,11 @@ fn main() { features += ",+harden-sls-ijmp"; features += ",+harden-sls-ret"; } + if cfg.has("X86_NATIVE_CPU") { + // Prevent the backend from generating APX instructions. The kernel is not yet prepared + // for general in-kernel EGPR use. + features += ",-apxf"; + } ts.push("features", features); ts.push("llvm-target", "x86_64-linux-gnu"); ts.push("supported-sanitizers", ["kcfi", "kernel-address"]); From 8901cee9316d53a5a97398f026c2d59a3043159d Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Sat, 19 Sep 2026 03:42:10 +0200 Subject: [PATCH 0874/1417] bpf: Compare stack frames in regs_exact() regs_exact() compares the register state up to id, followed by the ID mappings, but does not compare frameno. The PTR_TO_STACK case in regsafe() checks frameno separately, which is bypassed when exact comparison is requested. Consequently, infinite-loop detection can treat pointers to different stack frames as the same pointer and reject a finite loop. For example, initialize fp-8 to zero in the caller and to one in the callee, then pass the caller's fp-8 to the callee as r1: loop: r0 = *(u64 *)(r1 + 0); if r0 != 0 goto done; r1 = r10; r1 += -8; goto loop; done: exit; The loop terminates after reading the callee's slot on its second iteration. At the loop header, however, the only relevant difference is r1's frameno, so exact comparison incorrectly reports an infinite loop. The same problem occurs when the pointer is spilled to the stack. Move frameno into the type-specific metadata union, ahead of id, so the existing prefix comparison in regs_exact() covers it. Ordinary stack pointers do not use another union member. Iterator and IRQ stack-slot states use their dedicated union views and do not need a frame lookup. This also keeps bpf_reg_state at 80 bytes. Since frameno now shares storage with other pointer metadata, it is only meaningful for PTR_TO_STACK registers. Return NULL from bpf_func() for other register types. process_iter_arg(), get_constant_map_key() and is_dynptr_reg_valid_init() look up the frame before checking the register type and would otherwise index frame[] with a byte of the register's map or BTF pointer. They dereference the frame only after their type check. Move the states_maybe_looping() boundary from frameno to precise after the field relocation. Its prefix comparison continues to cover the complete value state and now includes frameno. Continue to ignore precise. Precision marks control whether pruning may ignore scalar ranges; they do not change the represented values, and exact comparison already compares those ranges unconditionally. Marks can also change through backtracking while an ancestor state is still being explored. Fixes: d5b892fd607a ("bpf: make infinite loop detection in is_state_visited() exact") Reported-by: Eduard Zingerman Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260919014213.1840880-2-memxor@gmail.com --- include/linux/bpf_verifier.h | 23 +++++++++++++++-------- kernel/bpf/states.c | 7 ++----- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index a7202b44ab1052..6fe8e5dc57aae8 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -45,6 +45,14 @@ struct bpf_reg_state { union { /* valid when type == PTR_TO_PACKET */ int range; + /* + * Valid when type == PTR_TO_STACK. Inside the callee two registers + * can be both PTR_TO_STACK like R1=fp-8 and R2=fp-8, but one of them + * points to this function stack while another to the caller's stack. + * To differentiate them 'frameno' is used which is an index in + * bpf_verifier_state->frame[] array pointing to bpf_func_state. + */ + u8 frameno; /* * For CONST_PTR_TO_MAP, PTR_TO_MAP_KEY, PTR_TO_MAP_VALUE and @@ -155,14 +163,6 @@ struct bpf_reg_state { * during state comparisons. */ u32 map_uid; - /* - * Inside the callee two registers can be both PTR_TO_STACK like - * R1=fp-8 and R2=fp-8, but one of them points to this function stack - * while another to the caller's stack. To differentiate them 'frameno' - * is used which is an index in bpf_verifier_state->frame[] array - * pointing to bpf_func_state. - */ - u8 frameno; /* if (!precise && SCALAR_VALUE) min/max/tnum don't affect safety */ bool precise; }; @@ -1239,11 +1239,18 @@ static inline int bpf_get_spi(s32 off) return (-off - 1) / BPF_REG_SIZE; } +/* + * Return the function state a stack pointer register refers to. frameno + * shares storage with other pointer metadata, so return NULL for any + * other register type instead of indexing frame[] with aliased bytes. + */ static inline struct bpf_func_state *bpf_func(struct bpf_verifier_env *env, const struct bpf_reg_state *reg) { struct bpf_verifier_state *cur = env->cur_state; + if (reg->type != PTR_TO_STACK) + return NULL; return cur->frame[reg->frameno]; } diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c index e4ec007f7fa61e..012b82513a3bc2 100644 --- a/kernel/bpf/states.c +++ b/kernel/bpf/states.c @@ -644,10 +644,7 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, return range_within(rold, rcur) && tnum_in(rold->var_off, rcur->var_off); case PTR_TO_STACK: - /* two stack pointers are equal only if they're pointing to - * the same stack frame, since fp-8 in foo != fp-8 in bar - */ - return regs_exact(rold, rcur, idmap) && rold->frameno == rcur->frameno; + return regs_exact(rold, rcur, idmap); case PTR_TO_ARENA: return true; case PTR_TO_INSN: @@ -1126,7 +1123,7 @@ static bool states_maybe_looping(struct bpf_verifier_state *old, fcur = cur->frame[fr]; for (i = 0; i < MAX_BPF_REG; i++) if (memcmp(&fold->regs[i], &fcur->regs[i], - offsetof(struct bpf_reg_state, frameno))) + offsetof(struct bpf_reg_state, precise))) return false; return true; } From 070587848985efcfcd45104c5266ba76a1165f55 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Sat, 19 Sep 2026 03:42:11 +0200 Subject: [PATCH 0875/1417] selftests/bpf: Cover frame changes in bounded loops Add a finite loop whose progress is represented only by changing the frame number of a stack pointer. The loop first reads zero from the caller's stack, switches to the same offset in the callee's stack, and exits after reading one on its next iteration. Force frequent checkpoints so the test exercises infinite-loop detection, and check that the program returns one when run. Without the frameno comparison in regs_exact(), the program is rejected with an "infinite loop detected" diagnostic instead of loading successfully. Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Tested-by: Eduard Zingerman Link: https://patch.msgid.link/20260919014213.1840880-3-memxor@gmail.com --- .../selftests/bpf/progs/verifier_loops1.c | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_loops1.c b/tools/testing/selftests/bpf/progs/verifier_loops1.c index d248ce877f14eb..48a966cda199b8 100644 --- a/tools/testing/selftests/bpf/progs/verifier_loops1.c +++ b/tools/testing/selftests/bpf/progs/verifier_loops1.c @@ -303,4 +303,40 @@ __naked void maybe_exit_scc_bug1(void) ::: __clobber_all); } +/* + * The loop reads zero from the caller's stack on its first iteration and + * one from the callee's stack on its second iteration. At the loop header, + * only the frame number of the pointer in r1 changes. + */ +static __naked __noinline __used +void loop_stack_frames_reg(void) +{ + asm volatile ( + "*(u64 *)(r10 - 8) = 1;" +"1:" + "r0 = *(u64 *)(r1 + 0);" + "if r0 != 0 goto 2f;" + "r1 = r10;" + "r1 += -8;" + "goto 1b;" +"2:" + "exit;" + ::: __clobber_all); +} + +SEC("xdp") +__description("bounded loop changing stack frame in a register") +__success __retval(1) +__flag(BPF_F_TEST_STATE_FREQ) +__naked void bounded_loop_stack_frames_reg(void) +{ + asm volatile ( + "*(u64 *)(r10 - 8) = 0;" + "r1 = r10;" + "r1 += -8;" + "call loop_stack_frames_reg;" + "exit;" + ::: __clobber_all); +} + char _license[] SEC("license") = "GPL"; From bfc888f04588f591851e95c974954cfca58e6c19 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 14 Sep 2026 15:24:42 +0200 Subject: [PATCH 0876/1417] bpf: Bound ownership depth through local kptrs and graph roots Program-allocated objects can own other local objects through referenced kptrs. bpf_obj_free_fields() follows those pointers through __bpf_obj_drop_impl() synchronously, before the object storage is freed through RCU. A self-referential local kptr type therefore permits arbitrarily deep object chains, and dropping the head can exhaust the kernel stack. Long acyclic type chains have the same problem. btf_check_and_fixup_fields() still assumes referenced kptrs only point to kernel types and checks ownership through list and rbtree roots only. Its existing rule is sufficient for graph-only cycles: the target of each graph edge must contain a node, so every type in a cycle has both a root and a node. The rule rejects such a type owning another root, breaking every cycle. It also limits graph-only chains to three types, or two if the first type contains a node, and conservatively rejects longer acyclic chains. The missing local-kptr edges, rather than a missed graph-only cycle, are the bug introduced by support for bpf_kptr_xchg() into local kptrs. Replace that restriction with one bounded ownership walk covering graph roots and local referenced kptrs. Run it after all BTF records have been fixed up, reject cycles and paths deeper than eight record-bearing types, and cache each type's suffix depth while checking it against the remaining budget. This also permits the longer acyclic graph-only layouts rejected by the old rule; update their existing BTF tests accordingly. Keep the bound independent of MAX_CALL_FRAMES because recursive destruction can run below a BPF call chain. A plain local pointee without special-field metadata adds only a final non-recursing drop. Non-owning kptrs and kernel-BTF kptrs do not recurse through local records and remain outside the walk. Include local percpu-kptr edges too, although allocation of percpu objects with special fields is currently forbidden, so that relaxing that restriction cannot bypass the ownership bound. btf_check_and_fixup_fields() continues to initialize graph_root.value_rec, including for separately allocated map records. The ownership relationships belong to immutable program BTF and only need validation at BTF load time. Fixes: b0966c724584 ("bpf: Support bpf_kptr_xchg into local kptr") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260914132444.2564218-2-memxor@gmail.com --- kernel/bpf/btf.c | 134 +++++++++++------- .../selftests/bpf/prog_tests/linked_list.c | 4 +- 2 files changed, 88 insertions(+), 50 deletions(-) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index d67a169ba2bb39..d870bc5e50bc86 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -4268,13 +4268,10 @@ int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec) { int i; - /* There are three types that signify ownership of some other type: - * kptr_ref, bpf_list_head, bpf_rb_root. - * kptr_ref only supports storing kernel types, which can't store - * references to program allocated local types. - * - * Hence we only need to ensure that bpf_{list_head,rb_root} ownership - * does not form cycles. + /* + * Check fields which require the complete BTF and initialize runtime + * metadata. Ownership relationships are validated after every record has + * been fixed up. */ if (IS_ERR_OR_NULL(rec) || !(rec->field_mask & (BPF_GRAPH_ROOT | BPF_UPTR))) return 0; @@ -4305,51 +4302,88 @@ int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec) if (!meta) return -EFAULT; rec->fields[i].graph_root.value_rec = meta->record; + } + return 0; +} - /* We need to set value_rec for all root types, but no need - * to check ownership cycle for a type unless it's also a - * node type. - */ - if (!(rec->field_mask & BPF_GRAPH_NODE)) +static int btf_owned_type_idx(const struct btf *btf, struct btf_struct_metas *tab, + const struct btf_field *field) +{ + struct btf_struct_meta *meta; + u32 btf_id; + + if (field->type & BPF_GRAPH_ROOT) { + btf_id = field->graph_root.value_btf_id; + } else if (field->type == BPF_KPTR_REF || field->type == BPF_KPTR_PERCPU) { + if (btf_is_kernel(field->kptr.btf)) + return -ENOENT; + btf_id = field->kptr.btf_id; + } else { + return -ENOENT; + } + + meta = btf_find_struct_meta(btf, btf_id); + if (!meta) + return field->type & BPF_GRAPH_ROOT ? -EFAULT : -ENOENT; + return meta - tab->types; +} + +/* + * Each ownership edge adds kernel frames through bpf_obj_free_fields() and + * __bpf_obj_drop_impl(). Keep the bound deliberately small because object + * destruction can itself run below a BPF call chain. A final pointee without + * special fields is not present in the struct metadata table and adds only a + * non-recursing drop. + */ +#define BTF_MAX_OWNERSHIP_DEPTH 8 + +static int btf_ownership_depth(const struct btf *btf, + struct btf_struct_metas *tab, u8 *depth, + int idx, int depth_left) +{ + const struct btf_record *rec = tab->types[idx].record; + int i, ret, max_depth = 0; + + if (!depth_left) + return -ELOOP; + if (depth[idx]) + goto done; + + for (i = 0; i < rec->cnt; i++) { + ret = btf_owned_type_idx(btf, tab, &rec->fields[i]); + if (ret == -ENOENT) continue; + if (ret < 0) + return ret; + ret = btf_ownership_depth(btf, tab, depth, ret, depth_left - 1); + if (ret < 0) + return ret; + max_depth = max(max_depth, ret); + } + depth[idx] = max_depth + 1; +done: + return depth[idx] > depth_left ? -ELOOP : depth[idx]; +} - /* We need to ensure ownership acyclicity among all types. The - * proper way to do it would be to topologically sort all BTF - * IDs based on the ownership edges, since there can be multiple - * bpf_{list_head,rb_node} in a type. Instead, we use the - * following resaoning: - * - * - A type can only be owned by another type in user BTF if it - * has a bpf_{list,rb}_node. Let's call these node types. - * - A type can only _own_ another type in user BTF if it has a - * bpf_{list_head,rb_root}. Let's call these root types. - * - * We ensure that if a type is both a root and node, its - * element types cannot be root types. - * - * To ensure acyclicity: - * - * When A is an root type but not a node, its ownership - * chain can be: - * A -> B -> C - * Where: - * - A is an root, e.g. has bpf_rb_root. - * - B is both a root and node, e.g. has bpf_rb_node and - * bpf_list_head. - * - C is only an root, e.g. has bpf_list_node - * - * When A is both a root and node, some other type already - * owns it in the BTF domain, hence it can not own - * another root type through any of the ownership edges. - * A -> B - * Where: - * - A is both an root and node. - * - B is only an node. - */ - if (meta->record->field_mask & BPF_GRAPH_ROOT) - return -ELOOP; +static int btf_check_ownership_depth(const struct btf *btf, + struct btf_struct_metas *tab) +{ + u8 *depth; + int i, ret = 0; + + depth = kvcalloc(tab->cnt, sizeof(*depth), GFP_KERNEL | __GFP_NOWARN); + if (!depth) + return -ENOMEM; + + for (i = 0; i < tab->cnt; i++) { + ret = btf_ownership_depth(btf, tab, depth, i, + BTF_MAX_OWNERSHIP_DEPTH); + if (ret < 0) + break; + ret = 0; } - return 0; + kvfree(depth); + return ret; } static void __btf_struct_show(const struct btf *btf, const struct btf_type *t, @@ -6044,6 +6078,10 @@ static struct btf *btf_parse(const union bpf_attr *attr, bpfptr_t uattr, if (err < 0) goto errout_meta; } + + err = btf_check_ownership_depth(btf, struct_meta_tab); + if (err < 0) + goto errout_meta; } err = bpf_log_attr_finalize(attr_log, &env->log); diff --git a/tools/testing/selftests/bpf/prog_tests/linked_list.c b/tools/testing/selftests/bpf/prog_tests/linked_list.c index c3d133c6a00d40..52fabbee3dd5af 100644 --- a/tools/testing/selftests/bpf/prog_tests/linked_list.c +++ b/tools/testing/selftests/bpf/prog_tests/linked_list.c @@ -714,7 +714,7 @@ static void test_btf(void) break; err = btf__load_into_kernel(btf); - ASSERT_EQ(err, -ELOOP, "check btf"); + ASSERT_EQ(err, 0, "check btf"); btf__free(btf); break; } @@ -773,7 +773,7 @@ static void test_btf(void) break; err = btf__load_into_kernel(btf); - ASSERT_EQ(err, -ELOOP, "check btf"); + ASSERT_EQ(err, 0, "check btf"); btf__free(btf); break; } From 0288ed67482b6e370eb3fa6b07720df435907970 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 14 Sep 2026 15:24:43 +0200 Subject: [PATCH 0877/1417] selftests/bpf: Check local object ownership depth Build raw program BTF records to exercise local object ownership without constructing a runtime chain deep enough to threaten the kernel stack. Cover referenced-kptr and percpu-kptr self-cycles, a two-type cycle, and a cycle mixing a graph root with a referenced kptr. The existing graph-only check accepts these local-kptr cycles and over-limit kptr chains; the fix rejects them with -ELOOP. Pin the eight-record depth boundary with a terminal plain object. Exercise both parent-first and child-first BTF orders, and a shared suffix reached first through a shorter path. These cases require cached suffix depths to be checked against the remaining depth budget on each path. Check list and rbtree chains of three, four, eight, and nine types. This covers the old graph-only depth boundary and the new explicit bound. The existing linked-list BTF tests still reject pure graph cycles and now accept the longer acyclic layouts previously rejected by the conservative rule. Although bpf_percpu_obj_new() currently rejects types with special fields, require the percpu cycle to fail at BTF load so future support cannot bypass the ownership bound. Keep a positive control for a non-owning kptr, which remains outside the ownership graph. Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260914132444.2564218-3-memxor@gmail.com --- .../bpf/prog_tests/local_kptr_ownership.c | 296 ++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c diff --git a/tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c b/tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c new file mode 100644 index 00000000000000..a487aa68f2eef7 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#include +#include +#include + +#define SPIN_LOCK 2 +#define LIST_HEAD 3 +#define LIST_NODE 4 +/* Keep in sync with BTF_MAX_OWNERSHIP_DEPTH. */ +#define MAX_OWNERSHIP_DEPTH 8 + +static struct btf *init_btf(void) +{ + struct btf *btf; + int id; + + btf = btf__new_empty(); + if (!ASSERT_OK_PTR(btf, "btf__new_empty")) + return NULL; + id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED); + if (!ASSERT_EQ(id, 1, "btf__add_int")) + goto err_out; + id = btf__add_struct(btf, "bpf_spin_lock", 4); + if (!ASSERT_EQ(id, SPIN_LOCK, "btf__add_struct bpf_spin_lock")) + goto err_out; + id = btf__add_struct(btf, "bpf_list_head", 16); + if (!ASSERT_EQ(id, LIST_HEAD, "btf__add_struct bpf_list_head")) + goto err_out; + id = btf__add_struct(btf, "bpf_list_node", 24); + if (!ASSERT_EQ(id, LIST_NODE, "btf__add_struct bpf_list_node")) + goto err_out; + return btf; + +err_out: + btf__free(btf); + return NULL; +} + +static int add_local_kptr(struct btf *btf, int pointee_id, const char *tag) +{ + int id; + + id = btf__add_type_tag(btf, tag, pointee_id); + if (!ASSERT_GT(id, 0, "btf__add_type_tag")) + return id; + id = btf__add_ptr(btf, id); + ASSERT_GT(id, 0, "btf__add_ptr"); + return id; +} + +static void test_self_cycle(const char *tag, int expected_err) +{ + struct btf *btf; + int id, err; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + id = add_local_kptr(btf, 7, tag); + if (id <= 0) + goto out; + id = btf__add_struct(btf, "self_cycle", 8); + if (!ASSERT_EQ(id, 7, "btf__add_struct self_cycle")) + goto out; + err = btf__add_field(btf, "next", 6, 0, 0); + if (!ASSERT_OK(err, "btf__add_field self_cycle::next")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, expected_err, "check btf"); +out: + btf__free(btf); +} + +static void test_aba_cycle(void) +{ + struct btf *btf; + int id, err; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + id = add_local_kptr(btf, 10, "kptr"); + if (id <= 0) + goto out; + id = add_local_kptr(btf, 9, "kptr"); + if (id <= 0) + goto out; + id = btf__add_struct(btf, "cycle_a", 8); + if (!ASSERT_EQ(id, 9, "btf__add_struct cycle_a")) + goto out; + err = btf__add_field(btf, "b", 6, 0, 0); + if (!ASSERT_OK(err, "btf__add_field cycle_a::b")) + goto out; + id = btf__add_struct(btf, "cycle_b", 8); + if (!ASSERT_EQ(id, 10, "btf__add_struct cycle_b")) + goto out; + err = btf__add_field(btf, "a", 8, 0, 0); + if (!ASSERT_OK(err, "btf__add_field cycle_b::a")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, -ELOOP, "check btf"); +out: + btf__free(btf); +} + +static void test_mixed_cycle(void) +{ + struct btf *btf; + int id, err; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + id = add_local_kptr(btf, 7, "kptr"); + if (id <= 0) + goto out; + id = btf__add_struct(btf, "mixed_owner", 20); + if (!ASSERT_EQ(id, 7, "btf__add_struct mixed_owner")) + goto out; + err = btf__add_field(btf, "root", LIST_HEAD, 0, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_owner::root")) + goto out; + err = btf__add_field(btf, "lock", SPIN_LOCK, 128, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_owner::lock")) + goto out; + id = btf__add_decl_tag(btf, "contains:mixed_node:node", 7, 0); + if (!ASSERT_EQ(id, 8, "btf__add_decl_tag mixed_owner")) + goto out; + id = btf__add_struct(btf, "mixed_node", 32); + if (!ASSERT_EQ(id, 9, "btf__add_struct mixed_node")) + goto out; + err = btf__add_field(btf, "node", LIST_NODE, 0, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_node::node")) + goto out; + err = btf__add_field(btf, "owner", 6, 192, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_node::owner")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, -ELOOP, "check btf"); +out: + btf__free(btf); +} + +static void test_acyclic_depth(int depth, bool child_first, bool shared_suffix, int expected_err) +{ + int ptr_id[MAX_OWNERSHIP_DEPTH + 1]; + int first_struct_id; + struct btf *btf; + int id, err, i, n, pointee_id; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + first_struct_id = 5 + 2 * depth; + for (i = 0; i < depth; i++) { + if (i == depth - 1) + pointee_id = first_struct_id + depth; + else + pointee_id = first_struct_id + (child_first ? depth - 2 - i : i + 1); + ptr_id[i] = add_local_kptr(btf, pointee_id, "kptr"); + if (ptr_id[i] <= 0) + goto out; + } + for (n = 0; n < depth; n++) { + char name[32]; + int offset = 0; + + i = child_first ? depth - 1 - n : n; + snprintf(name, sizeof(name), "owner_%d", i); + id = btf__add_struct(btf, name, shared_suffix && !i ? 16 : 8); + if (!ASSERT_EQ(id, first_struct_id + n, "btf__add_struct owner")) + goto out; + if (shared_suffix && !i) { + /* + * Visit the shared suffix through the shorter path before + * reaching it again with less remaining depth. + */ + err = btf__add_field(btf, "suffix", ptr_id[1], 0, 0); + if (!ASSERT_OK(err, "btf__add_field owner::suffix")) + goto out; + offset = 64; + } + err = btf__add_field(btf, "next", ptr_id[i], offset, 0); + if (!ASSERT_OK(err, "btf__add_field owner::next")) + goto out; + } + id = btf__add_struct(btf, "plain_leaf", 4); + if (!ASSERT_EQ(id, first_struct_id + depth, "btf__add_struct plain_leaf")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, expected_err, "check btf"); +out: + btf__free(btf); +} + +static void test_graph_depth(bool rbtree, int depth, int expected_err) +{ + int root_type = LIST_HEAD, node_type = LIST_NODE, node_size = 24; + int id, err, i, lock_off, root_off, size; + struct btf *btf; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + if (rbtree) { + root_type = btf__add_struct(btf, "bpf_rb_root", 16); + if (!ASSERT_GT(root_type, 0, "btf__add_struct bpf_rb_root")) + goto out; + node_type = btf__add_struct(btf, "bpf_rb_node", 32); + if (!ASSERT_GT(node_type, 0, "btf__add_struct bpf_rb_node")) + goto out; + node_size = 32; + } + + for (i = 0; i < depth; i++) { + char name[32], tag[64]; + + lock_off = i ? node_size : 0; + root_off = lock_off + 8; + size = i == depth - 1 ? node_size : root_off + 16; + snprintf(name, sizeof(name), "graph_owner_%d", i); + id = btf__add_struct(btf, name, size); + if (!ASSERT_GT(id, 0, "btf__add_struct graph_owner")) + goto out; + if (i) { + err = btf__add_field(btf, "node", node_type, 0, 0); + if (!ASSERT_OK(err, "btf__add_field graph_owner::node")) + goto out; + } + if (i == depth - 1) + continue; + err = btf__add_field(btf, "lock", SPIN_LOCK, lock_off * 8, 0); + if (!ASSERT_OK(err, "btf__add_field graph_owner::lock")) + goto out; + err = btf__add_field(btf, "root", root_type, root_off * 8, 0); + if (!ASSERT_OK(err, "btf__add_field graph_owner::root")) + goto out; + snprintf(tag, sizeof(tag), "contains:graph_owner_%d:node", i + 1); + err = btf__add_decl_tag(btf, tag, id, i ? 2 : 1); + if (!ASSERT_GT(err, 0, "btf__add_decl_tag graph_owner")) + goto out; + } + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, expected_err, "check btf"); +out: + btf__free(btf); +} + +void test_local_kptr_ownership(void) +{ + if (test__start_subtest("self_cycle")) + test_self_cycle("kptr", -ELOOP); + if (test__start_subtest("untrusted_self_cycle")) + test_self_cycle("kptr_untrusted", 0); + if (test__start_subtest("percpu_self_cycle")) + test_self_cycle("percpu_kptr", -ELOOP); + if (test__start_subtest("ABA_cycle")) + test_aba_cycle(); + if (test__start_subtest("mixed_graph_root_cycle")) + test_mixed_cycle(); + if (test__start_subtest("max_acyclic")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH, false, false, 0); + if (test__start_subtest("too_deep_acyclic")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, false, false, -ELOOP); + if (test__start_subtest("max_acyclic_child_first")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH, true, false, 0); + if (test__start_subtest("too_deep_acyclic_child_first")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, true, false, -ELOOP); + if (test__start_subtest("max_acyclic_shared_suffix")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH, false, true, 0); + if (test__start_subtest("too_deep_acyclic_shared_suffix")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, false, true, -ELOOP); + if (test__start_subtest("list_three_types")) + test_graph_depth(false, 3, 0); + if (test__start_subtest("list_four_types")) + test_graph_depth(false, 4, 0); + if (test__start_subtest("list_max_depth")) + test_graph_depth(false, MAX_OWNERSHIP_DEPTH, 0); + if (test__start_subtest("list_too_deep")) + test_graph_depth(false, MAX_OWNERSHIP_DEPTH + 1, -ELOOP); + if (test__start_subtest("rbtree_three_types")) + test_graph_depth(true, 3, 0); + if (test__start_subtest("rbtree_four_types")) + test_graph_depth(true, 4, 0); + if (test__start_subtest("rbtree_max_depth")) + test_graph_depth(true, MAX_OWNERSHIP_DEPTH, 0); + if (test__start_subtest("rbtree_too_deep")) + test_graph_depth(true, MAX_OWNERSHIP_DEPTH + 1, -ELOOP); +} From 3bd46666cfe51e2bd333fb46a0234694ca594230 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Fri, 18 Sep 2026 14:28:37 -1000 Subject: [PATCH 0878/1417] sched_ext: Pass the initial cmask to cid-form ops.enable() The cid-form API has an obvious hole. A task's cid mask is only visible through ops.set_cmask(), which fires on affinity changes and class switches but not when a task enters a scheduler through fork, sub-sched enable or re-home, and there is no p->cpus_ptr equivalent to fall back on. Schedulers work around it by seeding the mask in ops.init_task() from p->cpus_ptr cid by cid, which is subtly wrong: on sub-sched enable and re-home, an affinity change between init_task() and enable() is delivered to the sched the task is still on, and nothing corrects the new sched's copy afterwards. Fix it by adding struct scx_enable_args to cid-form ops.enable() carrying the task's cmask, built in the per-cpu scratch under the rq lock as the task enters the scheduler, and calling set_cmask() with the same mask right after enable(), ahead of set_weight(). A scheduler can then track affinity in set_cmask() alone, and scx_qmap drops its init_task() seed. set_cmask() no longer fires for a cid-form task before it is enabled, and the class-switch republish in switching_to_scx() is limited to the cpu form. This changes the cid-form ops.enable() signature, which is fine as the cid-form API is still considered unreleased. An args struct rather than a bare cmask argument leaves room for more initial state without another signature change, and the cmask travels as a plain arena address because BTF can't type arena struct members yet. v2: The cmask travels as a u64 arena address, cmask_arena_addr, instead of a kernel-typed pointer, with the typing limitation and the planned typed alias documented (Sashiko review). v3: The initial set_cmask() is delivered before set_weight() so that the mask is in place when weight-dependent state is derived (Andrea Righi). Selftest added. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 92 +++++++++++++++++++++++----------- kernel/sched/ext/internal.h | 56 ++++++++++++++++++--- tools/sched_ext/scx_qmap.bpf.c | 3 -- 3 files changed, 110 insertions(+), 41 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index f568fd9973f61f..ad391a8cbd05eb 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -447,37 +447,45 @@ static void switch_rq_lock(struct rq *from, struct rq *to) DEFINE_STATIC_KEY_FALSE(__scx_is_cid_type); /** - * scx_call_op_set_cpumask - invoke ops.set_cpumask / ops_cid.set_cmask for @task + * scx_fill_cmask_scratch - Build this cpu's arena cmask from @cpumask + * @sch: scx_sched whose scratch to fill + * @cpumask: cpus to translate into cids + * + * The scratch lives in BPF-writable arena memory and its header can't be + * trusted, so it is rewritten from kernel geometry rather than read. Caller + * must hold an rq lock so this cpu is the sole kernel writer for as long as the + * returned address is in use. + */ +static struct scx_cmask *scx_fill_cmask_scratch(struct scx_sched *sch, + const struct cpumask *cpumask) +{ + struct scx_cmask *kern_va = *this_cpu_ptr(sch->set_cmask_scratch); + struct scx_cmask_ref ref; + + scx_cmask_ref_init_kern(sch, kern_va, 0, num_possible_cpus(), &ref); + scx_cmask_ref_from_cpumask(&ref, cpumask); + return kern_va; +} + +/** + * scx_call_op_set_cpumask - Invoke the set_cpumask or set_cmask op for @task * @sch: scx_sched being invoked * @rq: rq to update as the currently-locked rq, or NULL * @task: task whose affinity is changing * @cpumask: new cpumask * - * For cid-form schedulers, translate @cpumask to a cmask via the per-cpu - * scratch in cid.c and dispatch through the ops_cid union view. Caller - * must hold @rq's rq lock so this_cpu_ptr is stable across the call. + * For cid-form schedulers, translate @cpumask to a cmask in the per-cpu scratch + * and dispatch through the ops_cid union view. Caller must hold @rq's rq lock. */ static inline void scx_call_op_set_cpumask(struct scx_sched *sch, struct rq *rq, struct task_struct *task, const struct cpumask *cpumask) { - if (scx_is_cid_type()) { - struct scx_cmask *kern_va = *this_cpu_ptr(sch->set_cmask_scratch); - struct scx_cmask_ref ref; - - /* - * Build the per-cpu arena cmask from kernel geometry via @ref, - * never reading its BPF-writable header. set_cmask()'s __arena - * argument takes the kernel address and the struct_ops - * trampoline rebases it into BPF's arena pointer form. The rq - * lock makes this cpu the sole kernel writer. - */ - scx_cmask_ref_init_kern(sch, kern_va, 0, num_possible_cpus(), &ref); - scx_cmask_ref_from_cpumask(&ref, cpumask); - SCX_CALL_CID_OP_TASK(sch, set_cmask, rq, task, kern_va); - } else { + if (scx_is_cid_type()) + SCX_CALL_CID_OP_TASK(sch, set_cmask, rq, task, + scx_fill_cmask_scratch(sch, cpumask)); + else SCX_CALL_OP_TASK(sch, set_cpumask, rq, task, cpumask); - } } enum scx_dsq_iter_flags { @@ -3634,8 +3642,12 @@ static void set_cpus_allowed_scx(struct task_struct *p, * * Fine-grained memory write control is enforced by BPF making the const * designation pointless. Cast it away when calling the operation. + * + * The cid form receives the initial mask when the task is enabled and + * hears about changes only afterwards, see struct scx_enable_args. */ - if (SCX_HAS_OP(sch, set_cpumask)) + if (SCX_HAS_OP(sch, set_cpumask) && + (!scx_is_cid_type() || scx_get_task_state(p) == SCX_TASK_ENABLED)) scx_call_op_set_cpumask(sch, task_rq(p), p, (struct cpumask *)p->cpus_ptr); } @@ -3944,8 +3956,27 @@ static void __scx_enable_task(struct scx_sched *sch, struct task_struct *p) p->scx.weight = sched_weight_to_cgroup(weight); - if (SCX_HAS_OP(sch, enable)) - SCX_CALL_OP_TASK(sch, enable, rq, p); + if (SCX_HAS_OP(sch, enable)) { + if (scx_is_cid_type()) { + struct scx_cmask *cmask = scx_fill_cmask_scratch(sch, p->cpus_ptr); + struct scx_enable_args args = { + .cmask_arena_addr = scx_kaddr_to_arena(sch, cmask), + }; + + SCX_CALL_CID_OP_TASK(sch, enable, rq, p, &args); + } else { + SCX_CALL_OP_TASK(sch, enable, rq, p); + } + } + + /* + * The initial mask also goes out through set_cmask() so a scheduler can + * track affinity there alone, and before set_weight() so that the mask + * is in place when weight-dependent state is derived, see struct + * scx_enable_args. + */ + if (scx_is_cid_type() && SCX_HAS_OP(sch, set_cpumask)) + scx_call_op_set_cpumask(sch, rq, p, p->cpus_ptr); if (SCX_HAS_OP(sch, set_weight)) SCX_CALL_OP_TASK(sch, set_weight, rq, p, p->scx.weight); @@ -4288,9 +4319,10 @@ static void switching_to_scx(struct rq *rq, struct task_struct *p) /* * set_cpus_allowed_scx() is not called while @p is associated with a - * different scheduler class. Keep the BPF scheduler up-to-date. + * different scheduler class. Keep the BPF scheduler up-to-date. The cid + * form gets its mask from scx_enable_task(). */ - if (SCX_HAS_OP(sch, set_cpumask)) + if (!scx_is_cid_type() && SCX_HAS_OP(sch, set_cpumask)) scx_call_op_set_cpumask(sch, rq, p, (struct cpumask *)p->cpus_ptr); } @@ -8374,10 +8406,11 @@ static struct bpf_struct_ops bpf_sched_ext_ops = { /* * cid-form cfi stubs. Stubs whose signatures match the cpu-form (param types * identical, only param names differ across structs) are reused. Some need - * fresh stubs, set_cmask due to an argument type difference and the sub-sched - * notifiers because no cpu-form stub exists to reuse. + * fresh stubs, set_cmask and enable due to argument differences and the + * sub-sched notifiers because no cpu-form stub exists to reuse. */ static void sched_ext_ops_cid__set_cmask(struct task_struct *p, const struct scx_cmask *cmask__arena) {} +static void sched_ext_ops_cid__enable(struct task_struct *p, struct scx_enable_args *args) {} static void sched_ext_ops__sub_caps_updated(const struct scx_cmask *cmask__arena, u64 caps) {} static void sched_ext_ops__sub_ecaps_updated(s32 cid, u64 before, u64 after) {} @@ -8398,7 +8431,7 @@ static struct sched_ext_ops_cid __bpf_ops_sched_ext_ops_cid = { .update_idle = sched_ext_ops__update_idle, .init_task = sched_ext_ops__init_task, .exit_task = sched_ext_ops__exit_task, - .enable = sched_ext_ops__enable, + .enable = sched_ext_ops_cid__enable, .disable = sched_ext_ops__disable, #ifdef CONFIG_EXT_GROUP_SCHED .cpuctl_init = sched_ext_ops__cgroup_init, @@ -10421,8 +10454,7 @@ __bpf_kfunc const void *scx_bpf_online_cmask(const struct bpf_prog_aux *aux) if (unlikely(!online)) return NULL; - /* BPF rebases by the low 32 bits, like __arena callback args */ - return (void *)((unsigned long)online - sch->arena_kern_base); + return (void *)scx_kaddr_to_arena(sch, online); } /** diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index d150de10a5c944..1df8f583b0eca5 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -250,6 +250,31 @@ struct scx_exit_task_args { bool cancelled; }; +/** + * struct scx_enable_args - Argument container for cid-form ops.enable() + * @cmask_arena_addr: BPF arena address of the cmask of cids the task may run on + * + * @cmask_arena_addr is the task's affinity as it enters the scheduler. + * set_cmask() delivers the same mask right after enable(), before set_weight() + * and the first enqueue, then every affinity change afterwards, and is never + * called before enable(). A scheduler may therefore track affinity in + * set_cmask() alone. + * + * The kernel builds the mask in the scheduler arena from its own geometry, so + * the header is valid regardless of what the scheduler last wrote there. The + * memory is per-cpu scratch reused once the callback returns: copy the bits + * out, don't keep the address. The set_cmask() argument follows the same rules. + * + * The address is a plain value rather than a typed pointer because BTF can't + * mark a struct member as an arena pointer yet and a pointer member would reach + * the program typed as a kernel pointer. Cast it to struct scx_cmask __arena * + * before use. Once arena members can be typed, a typed alias will join this + * field in an anonymous union at the same offset. + */ +struct scx_enable_args { + u64 cmask_arena_addr; +}; + /* argument container for ops.cgroup_init() */ struct scx_cgroup_init_args { /* the weight of the cgroup [1..10000] */ @@ -1037,6 +1062,7 @@ struct sched_ext_ops { * - dispatch -> dispatch (cpu arg is now cid) * - update_idle -> update_idle (cpu arg is now cid) * - set_cpumask -> set_cmask (cmask instead of cpumask) + * - enable -> enable (takes struct scx_enable_args) * - cpu_online -> cid_online * - cpu_offline -> cid_offline * - dump_cpu -> dump_cid @@ -1070,7 +1096,7 @@ struct sched_ext_ops_cid { struct scx_init_task_args *args); void (*exit_task)(struct task_struct *p, struct scx_exit_task_args *args); - void (*enable)(struct task_struct *p); + void (*enable)(struct task_struct *p, struct scx_enable_args *args); void (*disable)(struct task_struct *p); void (*dump)(struct scx_dump_ctx *ctx); void (*dump_cid)(struct scx_dump_ctx *ctx, s32 cid, bool idle); @@ -1533,7 +1559,8 @@ struct scx_sched { * by BUILD_BUG_ON in scx_init()). The anonymous union lets the kernel * access either view of the same storage without function-pointer * casts: use .ops for cpu-form and shared fields, .ops_cid for the - * cid-renamed callbacks (set_cmask, select_cid, cid_online, ...). + * callbacks whose cid-form signature differs (set_cmask, enable, + * select_cid, cid_online, ...). */ union { struct sched_ext_ops ops; @@ -1556,9 +1583,9 @@ struct scx_sched { uintptr_t arena_kern_base; /* - * Per-CPU arena cmask used by scx_call_op_set_cpumask() to hand a cmask - * to ops_cid.set_cmask(). The kernel writes through the stored kern_va - * and passes it to the callback's __arena argument. + * Per-CPU arena cmask the kernel fills from a task's cpumask and hands + * to ops_cid.enable() and ops_cid.set_cmask(). The stored pointers are + * the kernel addresses. */ struct scx_cmask * __percpu *set_cmask_scratch; struct scx_cmask *online_cmask; @@ -1669,6 +1696,19 @@ static inline void *scx_arena_to_kaddr(struct scx_sched *sch, const void *bpf_pt return (void *)(sch->arena_kern_base + (u32)(uintptr_t)bpf_ptr); } +/** + * scx_kaddr_to_arena - Translate a kernel arena address to the BPF form + * @sch: scheduler whose arena hosts @kaddr + * @kaddr: kernel address inside @sch's arena + * + * __arena callback arguments need no translation. Addresses handed to BPF any + * other way, such as struct fields and kfunc return values, go through this. + */ +static inline uintptr_t scx_kaddr_to_arena(struct scx_sched *sch, const void *kaddr) +{ + return (uintptr_t)kaddr - sch->arena_kern_base; +} + enum scx_wake_flags { /* expose select WF_* flags as enums */ SCX_WAKE_FORK = WF_FORK, @@ -2302,9 +2342,9 @@ do { \ } while (0) /* - * Dispatch a task op through the cid-form ops_cid table. Only set_cmask() needs - * this: it takes an arena cmask address instead of a cpumask, so it cannot be - * invoked via its cpu-form set_cpumask() slot. + * Dispatch a task op through the cid-form ops_cid table, for the ops whose + * cid-form signature differs from the cpu-form slot: set_cmask() takes an arena + * cmask instead of a cpumask and enable() takes scx_enable_args. */ #define SCX_CALL_CID_OP_TASK(sch, op, locked_rq, task, args...) \ __SCX_CALL_OP_TASK(sch, ops_cid, op, locked_rq, task, ##args) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index 67b7c01cae5505..2f36531998428b 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -961,9 +961,6 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init_task, struct task_struct *p, taskc->highpri = false; taskc->core_sched_seq = 0; cmask_init(&taskc->cpus_allowed, 0, scx_bpf_nr_cids()); - bpf_rcu_read_lock(); - cmask_from_cpumask(&taskc->cpus_allowed, p->cpus_ptr); - bpf_rcu_read_unlock(); v = bpf_task_storage_get(&task_ctx_stor, p, NULL, BPF_LOCAL_STORAGE_GET_F_CREATE); From d781d1b78acf547bafeb1592e8ba4020dce515c6 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 19 Sep 2026 04:08:20 -1000 Subject: [PATCH 0879/1417] selftests/sched_ext: Check the cmask cid-form ops.enable() receives cid-form ops.enable() now hands the task's cmask to the scheduler and set_cmask() repeats it right after. Add a cid-form selftest that checks both against p->cpus_ptr, that they match each other, that the initial set_cmask() lands before set_weight() and before the task first becomes runnable, and that set_cmask() never precedes enable(), across class-switch enables, fork-path enables and live affinity changes. v2: Mismatch details returned through a caller-local struct instead of globals, alloc_words validated in the header check, loop bounded by nr_cids directly (Andrea Righi). Signed-off-by: Tejun Heo --- tools/testing/selftests/sched_ext/Makefile | 1 + .../selftests/sched_ext/enable_cmask.bpf.c | 217 ++++++++++++++++++ .../selftests/sched_ext/enable_cmask.c | 138 +++++++++++ 3 files changed, 356 insertions(+) create mode 100644 tools/testing/selftests/sched_ext/enable_cmask.bpf.c create mode 100644 tools/testing/selftests/sched_ext/enable_cmask.c diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile index 49897727f535bf..4e06d0baaeec62 100644 --- a/tools/testing/selftests/sched_ext/Makefile +++ b/tools/testing/selftests/sched_ext/Makefile @@ -169,6 +169,7 @@ auto-test-targets := \ ddsp_bogus_dsq_fail \ ddsp_vtimelocal_fail \ dsp_local_on \ + enable_cmask \ enq_select_cpu \ exit \ hotplug \ diff --git a/tools/testing/selftests/sched_ext/enable_cmask.bpf.c b/tools/testing/selftests/sched_ext/enable_cmask.bpf.c new file mode 100644 index 00000000000000..0068f3c7ab3c33 --- /dev/null +++ b/tools/testing/selftests/sched_ext/enable_cmask.bpf.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A cid-form scheduler checking the cmask cid-form ops.enable() receives: the + * header, every cid bit against p->cpus_ptr, and that set_cmask() follows with + * the same mask before set_weight() and before the task first becomes runnable, + * and never runs before enable(). + * + * Copyright (c) 2026 Tejun Heo + */ +#include + +char _license[] SEC("license") = "GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_ARENA); + __uint(map_flags, BPF_F_MMAPABLE); + __uint(max_entries, 1 << 16); +} arena SEC(".maps"); + +struct task_ctx { + u64 enable_fp; /* fingerprint of the mask enable() received */ + bool enabled; + bool pending; /* enable() ran, the initial set_cmask() hasn't */ +}; + +struct { + __uint(type, BPF_MAP_TYPE_TASK_STORAGE); + __uint(map_flags, BPF_F_NO_PREALLOC); + __type(key, int); + __type(value, struct task_ctx); +} task_ctx_stor SEC(".maps"); + +/* details of a cid bit mismatch, filled by check_mask() */ +struct mask_mismatch { + s32 cid; + bool want; + bool got; +}; + +u64 nr_enable, nr_initial_set_cmask, nr_set_cmask, nr_set_weight; + +UEI_DEFINE(uei); + +static struct task_ctx *lookup_task_ctx(struct task_struct *p) +{ + struct task_ctx *tctx; + + tctx = bpf_task_storage_get(&task_ctx_stor, p, 0, 0); + if (!tctx) + scx_bpf_error("task_ctx lookup failed for %s[%d]", p->comm, p->pid); + return tctx; +} + +/* + * Verify @m's header and every cid bit against @p's cpumask and fingerprint the + * bits into @fp. Return 0 on success, -EINVAL on a bad header, -ENOENT on a cid + * without a cpu and -EIO on a bit mismatch with the details in @mm. + */ +static int check_mask(struct task_struct *p, const struct scx_cmask __arena *m, u64 *fp, + struct mask_mismatch *mm) +{ + u32 nr_cids = scx_bpf_nr_cids(); + u64 h = 0; + s32 cid; + + if (m->base || m->nr_cids != nr_cids || m->alloc_words != CMASK_NR_WORDS(nr_cids)) + return -EINVAL; + + bpf_for(cid, 0, nr_cids) { + bool want, got; + s32 cpu; + + cpu = scx_bpf_cid_to_cpu(cid); + if (cpu < 0) + return -ENOENT; + want = bpf_cpumask_test_cpu(cpu, p->cpus_ptr); + got = cmask_test(cid, m); + if (want != got) { + mm->cid = cid; + mm->want = want; + mm->got = got; + return -EIO; + } + h = h * 31 + got; + } + + *fp = h; + return 0; +} + +s32 BPF_STRUCT_OPS_SLEEPABLE(enable_cmask_init_task, struct task_struct *p, + struct scx_init_task_args *args) +{ + if (!bpf_task_storage_get(&task_ctx_stor, p, 0, BPF_LOCAL_STORAGE_GET_F_CREATE)) + return -ENOMEM; + return 0; +} + +void BPF_STRUCT_OPS(enable_cmask_enable, struct task_struct *p, struct scx_enable_args *args) +{ + struct scx_cmask __arena *m = (struct scx_cmask __arena *)args->cmask_arena_addr; + struct mask_mismatch mm = {}; + struct task_ctx *tctx; + int ret; + + asm volatile("" :: "r"(&arena)); + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + __sync_fetch_and_add(&nr_enable, 1); + if (tctx->enabled || tctx->pending) { + scx_bpf_error("enable: %s[%d] enabled twice", p->comm, p->pid); + return; + } + + ret = check_mask(p, m, &tctx->enable_fp, &mm); + if (ret) { + scx_bpf_error("enable: %s[%d] cmask check failed %d cid=%d want=%d got=%d", + p->comm, p->pid, ret, mm.cid, mm.want, mm.got); + return; + } + tctx->enabled = true; + tctx->pending = true; +} + +void BPF_STRUCT_OPS(enable_cmask_set_cmask, struct task_struct *p, + struct scx_cmask __arena *m) +{ + struct mask_mismatch mm = {}; + struct task_ctx *tctx; + u64 fp; + int ret; + + asm volatile("" :: "r"(&arena)); + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + __sync_fetch_and_add(&nr_set_cmask, 1); + if (!tctx->enabled) { + scx_bpf_error("set_cmask: %s[%d] not enabled", p->comm, p->pid); + return; + } + + ret = check_mask(p, m, &fp, &mm); + if (ret) { + scx_bpf_error("set_cmask: %s[%d] cmask check failed %d cid=%d want=%d got=%d", + p->comm, p->pid, ret, mm.cid, mm.want, mm.got); + return; + } + + if (tctx->pending) { + if (fp != tctx->enable_fp) { + scx_bpf_error("set_cmask: %s[%d] initial mask differs from enable()", + p->comm, p->pid); + return; + } + tctx->pending = false; + __sync_fetch_and_add(&nr_initial_set_cmask, 1); + } +} + +void BPF_STRUCT_OPS(enable_cmask_set_weight, struct task_struct *p, u32 weight) +{ + struct task_ctx *tctx; + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + __sync_fetch_and_add(&nr_set_weight, 1); + if (tctx->pending) + scx_bpf_error("set_weight: %s[%d] before the initial set_cmask()", p->comm, + p->pid); +} + +void BPF_STRUCT_OPS(enable_cmask_runnable, struct task_struct *p, u64 enq_flags) +{ + struct task_ctx *tctx; + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + if (tctx->pending) + scx_bpf_error("runnable: %s[%d] before the initial set_cmask()", p->comm, + p->pid); +} + +void BPF_STRUCT_OPS(enable_cmask_disable, struct task_struct *p) +{ + struct task_ctx *tctx; + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + tctx->enabled = false; + tctx->pending = false; +} + +void BPF_STRUCT_OPS(enable_cmask_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); +} + +SCX_OPS_CID_DEFINE(enable_cmask_ops, + .init_task = (void *)enable_cmask_init_task, + .enable = (void *)enable_cmask_enable, + .set_cmask = (void *)enable_cmask_set_cmask, + .set_weight = (void *)enable_cmask_set_weight, + .runnable = (void *)enable_cmask_runnable, + .disable = (void *)enable_cmask_disable, + .exit = (void *)enable_cmask_exit, + .flags = SCX_OPS_SWITCH_PARTIAL, + .name = "enable_cmask"); diff --git a/tools/testing/selftests/sched_ext/enable_cmask.c b/tools/testing/selftests/sched_ext/enable_cmask.c new file mode 100644 index 00000000000000..556bcad4431de3 --- /dev/null +++ b/tools/testing/selftests/sched_ext/enable_cmask.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Tejun Heo */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include "enable_cmask.bpf.skel.h" +#include "scx_test.h" + +#define SCHED_EXT 7 +#define NR_CHILDREN 8 +#define MAX_CPUS 1024 + +static int cpus[MAX_CPUS]; +static int nr_cpus; + +static void spin_ms(int ms) +{ + struct timespec start, now; + + clock_gettime(CLOCK_MONOTONIC, &start); + do { + clock_gettime(CLOCK_MONOTONIC, &now); + } while ((now.tv_sec - start.tv_sec) * 1000 + + (now.tv_nsec - start.tv_nsec) / 1000000 < ms); +} + +static int pin(pid_t pid, int idx) +{ + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(cpus[idx % nr_cpus], &set); + return sched_setaffinity(pid, sizeof(set), &set); +} + +/* + * Pin, switch to SCHED_EXT for a class-switch enable, fork a grandchild that + * inherits the policy for a fork-path enable, then change affinity a few times + * while running for set_cmask() on live tasks. + */ +static int child(int idx) +{ + struct sched_param param = {}; + int i, status; + pid_t pid; + + if (pin(0, idx) || sched_setscheduler(0, SCHED_EXT, ¶m)) + return 1; + + pid = fork(); + if (pid < 0) + return 1; + if (!pid) { + spin_ms(20); + return 0; + } + + for (i = 1; i <= 4; i++) { + if (pin(0, idx + i)) + return 1; + spin_ms(5); + } + + return waitpid(pid, &status, 0) == pid && !status ? 0 : 1; +} + +static enum scx_test_status run(void *ctx) +{ + struct enable_cmask *skel; + struct bpf_link *link; + pid_t pids[NR_CHILDREN]; + cpu_set_t set; + int i, status, failed = 0; + + if (!__COMPAT_struct_has_field("scx_enable_args", "cmask_arena_addr")) + return SCX_TEST_SKIP; + + SCX_FAIL_IF(sched_getaffinity(0, sizeof(set), &set), "Failed to read affinity"); + for (i = 0; i < MAX_CPUS && i < CPU_SETSIZE; i++) + if (CPU_ISSET(i, &set)) + cpus[nr_cpus++] = i; + if (nr_cpus < 2) + return SCX_TEST_SKIP; + + skel = enable_cmask__open(); + SCX_FAIL_IF(!skel, "Failed to open"); + SCX_ENUM_INIT(skel); + SCX_FAIL_IF(enable_cmask__load(skel), "Failed to load skel"); + + link = bpf_map__attach_struct_ops(skel->maps.enable_cmask_ops); + SCX_FAIL_IF(!link, "Failed to attach struct_ops"); + + for (i = 0; i < NR_CHILDREN; i++) { + pids[i] = fork(); + SCX_FAIL_IF(pids[i] < 0, "Failed to fork"); + if (!pids[i]) + exit(child(i)); + } + + /* affinity changes from the outside race with the children's own */ + for (i = 0; i < NR_CHILDREN; i++) + pin(pids[i], i + NR_CHILDREN); + + for (i = 0; i < NR_CHILDREN; i++) { + if (waitpid(pids[i], &status, 0) != pids[i] || status) + failed++; + } + + bpf_link__destroy(link); + + SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG)); + SCX_EQ(failed, 0); + SCX_GE(skel->bss->nr_enable, 2 * NR_CHILDREN); + SCX_EQ(skel->bss->nr_initial_set_cmask, skel->bss->nr_enable); + SCX_GT(skel->bss->nr_set_cmask, skel->bss->nr_initial_set_cmask); + SCX_GE(skel->bss->nr_set_weight, skel->bss->nr_enable); + printf("enable=%lu initial_set_cmask=%lu set_cmask=%lu set_weight=%lu\n", + (unsigned long)skel->bss->nr_enable, + (unsigned long)skel->bss->nr_initial_set_cmask, + (unsigned long)skel->bss->nr_set_cmask, + (unsigned long)skel->bss->nr_set_weight); + + enable_cmask__destroy(skel); + return SCX_TEST_PASS; +} + +struct scx_test enable_cmask = { + .name = "enable_cmask", + .description = "Check the cid-form ops.enable() cmask and the set_cmask() after it", + .run = run, +}; +REGISTER_SCX_TEST(&enable_cmask) From 6b1bca1b1ab77f60a62087337bfe6e2f0efb9e6d Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 10 Aug 2026 02:56:16 +0200 Subject: [PATCH 0880/1417] KVM: arm64: Fix AArch32 DBGBXVR handling The consolidation of the breakpoint and watchpoint register accessors switched DBGBXVR from trap_bvr() to trap_dbg_wb_reg(). The latter selects backing storage with demux_wb_reg(), which only handles Op2 values 4 through 7. Since DBGBXVR uses Op2 1, an AArch32 guest access hits KVM_BUG_ON() and marks the VM dead. DBGBXVR aliases DBGBVR_EL1[63:32], and its AA32(HI) descriptor already selects the upper half. Map Op2 1 to dbg_bvr[] alongside Op2 4, restoring the pre-regression behavior. Fixes: 3ce9f3357e9e ("KVM: arm64: Fold DBGxVR/DBGxCR accessors into common set") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Mehltretter Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260810005616.13227-1-kmehltretter@gmail.com Signed-off-by: Oliver Upton --- arch/arm64/kvm/sys_regs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c index 44aae52c473d7b..a2f4e769a42823 100644 --- a/arch/arm64/kvm/sys_regs.c +++ b/arch/arm64/kvm/sys_regs.c @@ -926,6 +926,7 @@ static u64 *demux_wb_reg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *rd) struct kvm_guest_debug_arch *dbg = &vcpu->arch.vcpu_debug_state; switch (rd->Op2) { + case 0b001: case 0b100: return &dbg->dbg_bvr[rd->CRm]; case 0b101: From c21eaa72f02fc6e85621cbe09d303d8fb8bd39cd Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Wed, 16 Sep 2026 20:48:30 +0200 Subject: [PATCH 0881/1417] posix-cpu-timers: Prevent freeing a timer which is queued on the expiry list Kijo analyzed another race in the POSIX CPU timer code: Commit bf635681c906 converted cpu_timer::firing from a tristate value to a boolean. This lost the distinction between "not owned by the firing list" and "still owned, but delivery was canceled". The resulting race is: expiry handler timer_settime() timer_delete() -------------- --------------- -------------- collect timer onto private firing list firing = true observes firing = true firing = false return TIMER_RETRY wait for handler observes firing = false finish deletion unhash and free timer resume list traversal read freed elist.next -> UAF The firing bit is clearly the wrong indicator since that commit. Check whether the timer is queued on the expiry list or not instead. If it is queued clear the firing bit to prevent signal delivery as before and return TIMER_RETRY so the caller unlocks the timer which allows the expiry code to make progress and remove it from the list. Fixes: bf635681c906 ("posix-cpu-timers: Cleanup the firing logic") Reported-by: Kijo Park Debugged-by: Kijo Park Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Frederic Weisbecker Cc: stable@vger.kernel.org --- kernel/time/posix-cpu-timers.c | 40 +++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index d73d31c7994fe1..0bf4fcd969c87e 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -408,6 +408,7 @@ static int posix_cpu_timer_create(struct k_itimer *new_timer) new_timer->kclock = &clock_posix_cpu; timerqueue_init(&new_timer->it.cpu.node); + INIT_LIST_HEAD(&new_timer->it.cpu.elist); new_timer->it.cpu.pid = get_pid(pid); rcu_read_unlock(); return 0; @@ -565,6 +566,24 @@ static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned l return NULL; } +/* + * If the timer is queued on the expiry list, then it cannot be dequeued because + * the firing list is not protected by sighand->lock. The delivery path is + * waiting for the timer lock. So go back, unlock and retry. + */ +static bool posix_cpu_timer_on_expiry_list(struct k_itimer *timer) +{ + if (list_empty(&timer->it.cpu.elist)) + return false; + + /* + * Prevent signal delivery as there is no point in delivering a signal + * which is made obsolete right away. + */ + timer->it.cpu.firing = false; + return true; +} + /* * Clean up a CPU-clock timer that is about to be destroyed. * This is called from timer deletion with the timer already locked. @@ -580,18 +599,10 @@ static int posix_cpu_timer_del(struct k_itimer *timer) p = timer_lock_sighand(timer, &flags); if (likely(p)) { - if (timer->it.cpu.firing) { - /* - * Prevent signal delivery. The timer cannot be dequeued - * because it is on the firing list which is not protected - * by sighand->lock. The delivery path is waiting for - * the timer lock. So go back, unlock and retry. - */ - timer->it.cpu.firing = false; + if (posix_cpu_timer_on_expiry_list(timer)) ret = TIMER_RETRY; - } else { + else disarm_timer(timer, p); - } unlock_task_sighand(p, &flags); } @@ -731,14 +742,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, /* Retrieve the current expiry time before disarming the timer */ old_expires = cpu_timer_getexpires(ctmr); - if (unlikely(timer->it.cpu.firing)) { - /* - * Prevent signal delivery. The timer cannot be dequeued - * because it is on the firing list which is not protected - * by sighand->lock. The delivery path is waiting for - * the timer lock. So go back, unlock and retry. - */ - timer->it.cpu.firing = false; + if (posix_cpu_timer_on_expiry_list(timer)) { ret = TIMER_RETRY; } else { cpu_timer_dequeue(ctmr); From c82b797abe668d0b668601a93ba2c0b071a63574 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Mon, 14 Sep 2026 14:51:23 +0800 Subject: [PATCH 0882/1417] net/sched: reject IDR error pointers when deleting actions tcf_action_delete() drops the reference held by its lookup before calling tcf_idr_delete_index() with the saved action index. An unlocked classifier can remove that action and reserve the same IDR slot with ERR_PTR(-EBUSY) in between. tcf_idr_delete_index() only checks the lookup result for NULL. It therefore treats the reservation as a tc_action and dereferences tcfa_bindcnt. A hardware execution breakpoint was used to schedule the interleaving without changing the kernel source. KASAN reported this decoded trace: BUG: KASAN: null-ptr-deref in tca_action_gd+0x5b9/0x1010 Read of size 4 at addr 0000000000000010 by task poc/150 Oops: general protection fault, probably for non-canonical address 0xdffffc0000000002 RIP: tca_action_gd+0x5c0/0x1010: arch_atomic_read at arch/x86/include/asm/atomic.h:23 raw_atomic_read at include/linux/atomic/atomic-arch-fallback.h:457 atomic_read at include/linux/atomic/atomic-instrumented.h:33 tcf_idr_delete_index at net/sched/act_api.c:766 tcf_action_delete at net/sched/act_api.c:1859 tcf_del_notify at net/sched/act_api.c:2014 tca_action_gd at net/sched/act_api.c:2064 R13: 0000000000000010 R15: fffffffffffffff0 Kernel panic - not syncing: Fatal exception R15 contains ERR_PTR(-EBUSY), and adding the tcfa_bindcnt offset produces the address in R13. With the guard applied, the same reproducer returned -ENOENT without a KASAN report or panic. Treat error pointers as absent and return -ENOENT. Fixes: 0190c1d452a9 ("net: sched: atomically check-allocate action") Cc: stable@vger.kernel.org Reported-by: Xiang Mei Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20260914065123.4109709-2-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 3f653721c45feb..e45a63be397ce1 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -758,7 +758,7 @@ static int tcf_idr_delete_index(struct tcf_idrinfo *idrinfo, u32 index) mutex_lock(&idrinfo->lock); p = idr_find(&idrinfo->action_idr, index); - if (!p) { + if (IS_ERR_OR_NULL(p)) { mutex_unlock(&idrinfo->lock); return -ENOENT; } From 9d565b6b72fe3f41fd43636e143072848105189f Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Tue, 15 Sep 2026 00:06:58 +0100 Subject: [PATCH 0883/1417] net: usb: catc: bound the RX packet length in catc_rx_done() catc_rx_done() walks a multi-packet URB, reading a two-byte length from each packet header. Its bound, pkt_len > urb->actual_length, ignores the header offset and compares against the whole transfer rather than the bytes left from pkt_start, so a crafted packet header makes skb_copy_to_linear_data() read past the buffer. A length below ETH_HLEN is also accepted, including zero, and eth_type_trans() then reads a MAC header from the uninitialised tailroom of a shorter skb. The is_f5u011 branch takes its length straight from the transfer, so a zero-length URB reaches the same path. Track the bytes remaining from the current packet, and reject a header that does not fit, a length past what is left, and a length below an Ethernet header. A transfer shorter than an Ethernet header, including a zero-length one, previously became a runt skb passed to netif_rx() and counted as received; it is now counted in rx_length_errors and ends the walk. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Aamir Ahmed Reviewed-by: Simon Horman Link: https://patch.msgid.link/AS8P251MB00015FD7716F38C345619B56C8BB2@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM Signed-off-by: Jakub Kicinski --- drivers/net/usb/catc.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/net/usb/catc.c b/drivers/net/usb/catc.c index 96e82f94edcf81..39b678f175dd95 100644 --- a/drivers/net/usb/catc.c +++ b/drivers/net/usb/catc.c @@ -233,17 +233,26 @@ static void catc_rx_done(struct urb *urb) } do { - if(!catc->is_f5u011) { - pkt_len = le16_to_cpup((__le16*)pkt_start); - if (pkt_len > urb->actual_length) { + int remaining = urb->actual_length - + (pkt_start - (u8 *)urb->transfer_buffer); + + if (!catc->is_f5u011) { + if (remaining < pkt_offset) { catc->netdev->stats.rx_length_errors++; catc->netdev->stats.rx_errors++; break; } + pkt_len = le16_to_cpup((__le16 *)pkt_start); } else { pkt_len = urb->actual_length; } + if (pkt_len < ETH_HLEN || pkt_len + pkt_offset > remaining) { + catc->netdev->stats.rx_length_errors++; + catc->netdev->stats.rx_errors++; + break; + } + if (!(skb = dev_alloc_skb(pkt_len))) return; From ab888242fce4f16f6c4d4c6ec53939ad36aa3b3a Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Tue, 15 Sep 2026 01:31:52 -0700 Subject: [PATCH 0884/1417] vlan: require the MAC header to be present in __vlan_insert_inner_tag() __vlan_insert_inner_tag() only guarantees head room via skb_cow_head(), never that mac_len bytes of MAC header are present. Its ETH_HLEN wrappers - __vlan_insert_tag() under skb_vlan_push(), and vlan_insert_tag() under validate_xmit_vlan() on the generic transmit path - therefore rewrite the first 16 bytes at skb->data: a 12-byte memmove plus two 2-byte stores at +12 and +14. No caller supplies the bound, while the pop helpers use skb_ensure_writable()/pskb_may_pull(). An IFF_TUN device has hard_header_len == 0, so packet_snd() accepts a one-byte AF_PACKET/SOCK_RAW frame. The first vlan push only sets a hwaccel tag; the next - clsact "action vlan push" or bpf_skb_vlan_push() - enters the helper with skb->len still 1. The head comes from skbuff_small_head without __GFP_ZERO, so each push drags bytes from beyond skb->tail into the frame. After three the one-byte send leaves as 13 bytes carrying 11 bytes of uninitialised slab: 0000: 5a b3 62 12 80 88 ff ff 00 b3 62 12 81 `------------------------------' only 0x5a was sent; the rest is slab, here the top 56 bits of a linear-map address Require the MAC header the helper rewrites to be present, so such a frame is dropped rather than transmitted. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: co+0ea1ac045375cf05@bugs.sh Signed-off-by: Xiang Mei Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260915083152.705309-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- include/linux/if_vlan.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h index 20cc16ea4e5abc..4846032bf4ffc8 100644 --- a/include/linux/if_vlan.h +++ b/include/linux/if_vlan.h @@ -365,6 +365,9 @@ static inline int __vlan_insert_inner_tag(struct sk_buff *skb, const u8 meta_len = mac_len > ETH_TLEN ? skb_metadata_len(skb) : 0; struct vlan_ethhdr *veth; + if (unlikely(!pskb_may_pull(skb, mac_len))) + return -EINVAL; + if (skb_cow_head(skb, meta_len + VLAN_HLEN) < 0) return -ENOMEM; From a11212910cf09b2fe8db9afa41ef60c4f81879c5 Mon Sep 17 00:00:00 2001 From: Yuqi Xu Date: Sat, 19 Sep 2026 16:45:02 +0800 Subject: [PATCH 0885/1417] bpf: Check params size before reading reserved fields bpf_crypto_ctx_create() is a kfunc whose second argument is declared with the __sz annotation, so the verifier only guarantees that params__sz bytes of params are valid. The function nevertheless reads params->reserved[0] and params->reserved[1] (offsets 14 and 15) before comparing params__sz against the size of struct bpf_crypto_params, so a BPF program can pass a shorter buffer and have the kernel read past the region that was validated for it. Move the size check in front of the reserved field reads. Fixes: 3e1c6f35409f ("bpf: make common crypto API for TC/XDP programs") Reported-by: Vega Signed-off-by: Yuqi Xu Signed-off-by: Alexei Starovoitov Reviewed-by: Ren Wei Link: https://patch.msgid.link/4f3ab4b03e79017e215521743996555439bf0bb3.1789802413.git.xuyuqiabc@gmail.com --- kernel/bpf/crypto.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/crypto.c b/kernel/bpf/crypto.c index 51f89cecefb4dc..3f3fe2450fc6cf 100644 --- a/kernel/bpf/crypto.c +++ b/kernel/bpf/crypto.c @@ -149,8 +149,9 @@ bpf_crypto_ctx_create(const struct bpf_crypto_params *params, u32 params__sz, const struct bpf_crypto_type *type; struct bpf_crypto_ctx *ctx; - if (!params || params->reserved[0] || params->reserved[1] || - params__sz != sizeof(struct bpf_crypto_params)) { + if (!params || + params__sz != sizeof(struct bpf_crypto_params) || + params->reserved[0] || params->reserved[1]) { *err = -EINVAL; return NULL; } From 8a60ade2277e1f0e0d0578d565354e52292fa46d Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Thu, 17 Sep 2026 06:57:32 -0400 Subject: [PATCH 0886/1417] net/sched: sch_hfsc: bound the classify inner-filter walk with a drift budget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hfsc_classify() applies the "filter may only point downwards" level check only when the filter result carries no bound class. A filter created with a flowid gets res.class set once at bind time, so the check never runs for it during classification. hfsc_adjust_levels() can later raise a class's level without revalidating existing bindings, leaving two binds that were each legal at bind time pointing at each other; the classify walk then bounces between two interior classes forever with the qdisc lock held and BH disabled — a soft lockup from a single packet. The stuck walk trips the watchdog: watchdog: BUG: soft lockup - CPU#3 stuck for 13s! [ping:444] RIP: 0010:u32_classify+0x542/0x17f0 ... tcf_classify+0x66/0xa0 hfsc_enqueue+0x166/0xdf0 Bound the traversal with a budget of non-descending hops, the only way a configured walk can move without descending the class tree once levels drift after bind time. The budget is cumulative over the whole walk and is deliberately not reset on a descending hop: a chain that alternates a descent with a lateral hop would return the budget every lap and never trip. Descending hops never decrement it, so legitimately deep trees are unaffected and a terminating lateral chain still classifies normally. Drop the packet with a rate-limited warning once the budget is exhausted, mirroring the merged HTB fix. This is a follow-up to commit 729c4896ab82 ("net/sched: sch_htb: limit htb_classify inner-class filter hops"), which bounded the same classify loop on the HTB side but left the HFSC walk unbounded. Conditions to recreate the bug: - CONFIG_NET_SCHED, CONFIG_NET_SCH_HFSC, CONFIG_NET_CLS_U32, CONFIG_LOCKUP_DETECTOR. - Build a cycle with two legal-at-bind-time flowid binds and a level drift: class X 1:1 (child of root) with leaf child 1:10; class Y 1:2 (sibling of X) with children 1:20 and 1:200; root u32 filter flowid 1:1; filter on X flowid 1:2 (legal when Y is a leaf); after Y's level rises to 2, filter on Y flowid 1:1 (legal then). Send one packet (ping on the device). Unfixed kernel: classify spins with the qdisc lock held; with softlockup_panic=1 it panics. - Reachable from unprivileged user via unshare -Urn (CAP_NET_ADMIN). Fixes: a2f79227138c ("net_sched: sch_hfsc: fix classification loops") Reported-by: Sashiko (gemini + nipa) Closes: https://lore.kernel.org/netdev/QDISC-CTUU.v2.20260913192614@mojatatu.com/ Link: https://sashiko.dev/#/patchset/QDISC-CTUU.v2.20260913192614@mojatatu.com Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/QDISC-CTUU.v2.20260913192614%40mojatatu.com Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-CTUU.v3.20260916184908@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/sch_hfsc.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c index e87f5021a19958..284490fd6ca91e 100644 --- a/net/sched/sch_hfsc.c +++ b/net/sched/sch_hfsc.c @@ -386,6 +386,15 @@ cftree_update(struct hfsc_class *cl) #define SM_MASK ((1ULL << SM_SHIFT) - 1) #define ISM_MASK ((1ULL << ISM_SHIFT) - 1) +/* + * Cap on the non-descending hops a classify walk may take before its + * filter chain is treated as misconfigured. A flowid binding that was + * legal at bind time can become lateral once hfsc_adjust_levels() + * raises a class level; a few such hops are legitimate, an unbounded + * run means the chain cycles. + */ +#define HFSC_CLASSIFY_MAX_DRIFT 8 + static inline u64 seg_x2y(u64 x, u64 sm) { @@ -1133,6 +1142,7 @@ hfsc_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) struct hfsc_class *head, *cl; struct tcf_result res; struct tcf_proto *tcf; + unsigned int drift; int result; if (TC_H_MAJ(skb->priority ^ sch->handle) == 0 && @@ -1142,6 +1152,7 @@ hfsc_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; head = &q->root; + drift = HFSC_CLASSIFY_MAX_DRIFT; tcf = rcu_dereference_bh(q->root.filter_list); while (tcf && (result = tcf_classify_qdisc(skb, tcf, &res, false)) >= 0) { #ifdef CONFIG_NET_CLS_ACT @@ -1167,6 +1178,17 @@ hfsc_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) if (cl->level == 0) return cl; /* hit leaf class */ + /* + * flowid binds skip the level check above (res.class is set + * at bind time and levels drift after), so a walk can follow + * lateral hops without descending; a bounded number of them + * is legal, more means the chain cycles. + */ + if (cl->level >= head->level && drift-- == 0) { + pr_warn_ratelimited("hfsc: classify hop budget exhausted, dropping packet\n"); + return NULL; + } + /* apply inner filter chain */ tcf = rcu_dereference_bh(cl->filter_list); head = cl; From 1e24c4f2ee44be0eee94092b5d13cbdb4bdf0d60 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Thu, 17 Sep 2026 06:57:33 -0400 Subject: [PATCH 0887/1417] selftests: tc-testing: add a lateral-drift hfsc classify-walk test The classify-loop fix bounds a walk's non-descending hops, so the guard must not misfire on a legal walk that reaches its leaf through a level-drift lateral chain. Add a case that builds exactly that chain and asserts traffic still reaches the chain's own leaf. A lateral hop can only exist because a bind was legal when it was made and a later class add raised the target's level, so the setup binds each hop while the target is still a leaf and only then deepens it: bind 1:1 -> 1:2 while 1:2 is a leaf, add 1:20 under 1:2, add 1:3 and bind 1:2 -> 1:3 while 1:3 is a leaf, then add 1:30 and 1:31 under 1:3 and bind 1:3 -> 1:31. The walk root -> 1:1 -> 1:2 -> 1:3 -> 1:31 then takes two lateral hops and must reach leaf 1:31. The default class is 1:30, distinct from the asserted leaf, and the verify pattern is anchored to the 1:31 stats line, so neither a fall-through to the default nor a nonzero count on another class can satisfy the check. On the patched kernel the test passes; with the bound forced to zero the walk falls to the default and 1:31 stays idle, so the test fails. Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-CTUU.v3.20260916184908@mojatatu.com.2 Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/qdiscs/hfsc.json | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json index c98c339424d4ea..4f6bbb8b57f932 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json @@ -169,5 +169,39 @@ "teardown": [ "$TC qdisc del dev $DUMMY handle 1: root" ] + }, + { + "id": "8c39", + "name": "HFSC classify walk still reaches leaf after lateral drift", + "category": [ + "qdisc", + "hfsc" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "ip link set lo up", + "$TC qdisc add dev lo handle 1: root hfsc default 30", + "$TC class add dev lo parent 1: classid 1:1 hfsc rt m2 100kbit", + "$TC class add dev lo parent 1:1 classid 1:10 hfsc rt m2 50kbit", + "$TC class add dev lo parent 1: classid 1:2 hfsc rt m2 100kbit", + "$TC filter add dev lo parent 1: protocol ip prio 1 u32 match u8 0 0 at 0 flowid 1:1", + "$TC filter add dev lo parent 1:1 protocol ip prio 1 u32 match u8 0 0 at 0 flowid 1:2", + "$TC class add dev lo parent 1:2 classid 1:20 hfsc rt m2 10kbit", + "$TC class add dev lo parent 1: classid 1:3 hfsc rt m2 100kbit", + "$TC filter add dev lo parent 1:2 protocol ip prio 1 u32 match u8 0 0 at 0 flowid 1:3", + "$TC class add dev lo parent 1:3 classid 1:30 hfsc rt m2 10kbit", + "$TC class add dev lo parent 1:3 classid 1:31 hfsc rt m2 100kbit", + "$TC filter add dev lo parent 1:3 protocol ip prio 1 u32 match u8 0 0 at 0 flowid 1:31" + ], + "cmdUnderTest": "ping -n -c 10 -W 1 127.0.0.1", + "expExitCode": "0", + "verifyCmd": "$TC -s class show dev lo", + "matchPattern": "class hfsc 1:31 parent 1:3 rt[^\\n]*\\n Sent [0-9]+ bytes [1-9][0-9]* pkt", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev lo handle 1: root" + ] } ] From 8f6f8a48399f82f4a83f7b9f25b9707e0062a4f9 Mon Sep 17 00:00:00 2001 From: Adarsh Das Date: Sat, 19 Sep 2026 16:27:32 +0530 Subject: [PATCH 0888/1417] smb: client: delete compound mids on send failure before unlock When sending a compound request fails, smb_send_rqst() kicks off a reconnect. compound_send_recv() still has those mids on pending_mid_q, but it unlocks the server without removing them first. During reconnect, cifs_abort_connection() walks pending_mid_q and runs each mid callback. With no response yet, those callbacks return credits and drop in_flight. Then compound_send_recv()'s send-error path returns the same credits again. in_flight ends up decremented twice and smb2_add_credits() WARNs. syzbot hits this during SMB2_negotiate when the socket send fails. cifs_call_async() already calls delete_mid() before unlock on send failure. Do the same for compound chains and set cancelled_mid[] so the out: path does not delete them again. Fixes: ee258d79159a ("CIFS: Move credit processing to mid callbacks for SMB3") Reported-by: syzbot+eeb58d2197d88720a228@syzkaller.appspotmail.com Closes: https://lore.kernel.org/r/6a727d22.40259c87.584f4.04ce.GAE@google.com Tested-by: syzbot+eeb58d2197d88720a228@syzkaller.appspotmail.com Assisted-by: LLM Cc: stable@vger.kernel.org Signed-off-by: Adarsh Das Signed-off-by: Paulo Alcantara --- fs/smb/client/transport.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index e266859818a441..7df5b3447aea78 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -965,6 +965,10 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, if (rc < 0) { revert_current_mid(server, num_rqst); server->sequence_number -= 2; + for (i = 0; i < num_rqst; i++) { + delete_mid(server, mid[i]); + cancelled_mid[i] = true; + } } cifs_server_unlock(server); From f7eeb1af8537b05953fb1c88ab8b59d94059a381 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Thu, 27 Aug 2026 23:43:01 +0800 Subject: [PATCH 0889/1417] i2c: at91: release DMA channels on remove and probe error at91_twi_configure_dma() requests exclusive tx/rx DMA channels, but nothing ever releases them on driver detach, and the probe error path after the channels are acquired (i2c_add_numbered_adapter() failure) returns without releasing them either, because the remove callback is not invoked after a failed probe. Move the release into a helper, call it from the existing configure-failure path, the adapter-registration failure path, and at91_twi_remove(). Fixes: 60937b2cdbf9 ("i2c: at91: add dma support") Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Cc: # v3.8+ Acked-by: Mukesh Kumar Savaliya Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260827-i2c-dma-channel-leak-v1-1-271d4adc03a0@cherr.cc --- drivers/i2c/busses/i2c-at91-core.c | 3 +++ drivers/i2c/busses/i2c-at91-master.c | 12 +++++++++++- drivers/i2c/busses/i2c-at91.h | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-at91-core.c b/drivers/i2c/busses/i2c-at91-core.c index b64adef778d4b8..8ca4556d966491 100644 --- a/drivers/i2c/busses/i2c-at91-core.c +++ b/drivers/i2c/busses/i2c-at91-core.c @@ -255,6 +255,7 @@ static int at91_twi_probe(struct platform_device *pdev) if (rc) { pm_runtime_disable(dev->dev); pm_runtime_set_suspended(dev->dev); + at91_twi_dma_release(dev); return rc; } @@ -270,6 +271,8 @@ static void at91_twi_remove(struct platform_device *pdev) i2c_del_adapter(&dev->adapter); + at91_twi_dma_release(dev); + pm_runtime_disable(dev->dev); pm_runtime_set_suspended(dev->dev); } diff --git a/drivers/i2c/busses/i2c-at91-master.c b/drivers/i2c/busses/i2c-at91-master.c index 894cedbca99f57..68238cc8aee0d1 100644 --- a/drivers/i2c/busses/i2c-at91-master.c +++ b/drivers/i2c/busses/i2c-at91-master.c @@ -817,11 +817,21 @@ static int at91_twi_configure_dma(struct at91_twi_dev *dev, u32 phy_addr) error: if (ret != -EPROBE_DEFER) dev_info(dev->dev, "can't get DMA channel, continue without DMA support\n"); + at91_twi_dma_release(dev); + return ret; +} + +void at91_twi_dma_release(struct at91_twi_dev *dev) +{ + struct at91_twi_dma *dma = &dev->dma; + if (dma->chan_rx) dma_release_channel(dma->chan_rx); if (dma->chan_tx) dma_release_channel(dma->chan_tx); - return ret; + dma->chan_rx = NULL; + dma->chan_tx = NULL; + dev->use_dma = false; } static int at91_init_twi_recovery_gpio(struct platform_device *pdev, diff --git a/drivers/i2c/busses/i2c-at91.h b/drivers/i2c/busses/i2c-at91.h index 942e9c3973bb57..d68fcbbc3e0f5d 100644 --- a/drivers/i2c/busses/i2c-at91.h +++ b/drivers/i2c/busses/i2c-at91.h @@ -172,6 +172,7 @@ void at91_twi_irq_restore(struct at91_twi_dev *dev); void at91_init_twi_bus(struct at91_twi_dev *dev); void at91_init_twi_bus_master(struct at91_twi_dev *dev); +void at91_twi_dma_release(struct at91_twi_dev *dev); int at91_twi_probe_master(struct platform_device *pdev, u32 phy_addr, struct at91_twi_dev *dev); From e9f03b9625e2eeaca357b065c92d5b14064a1583 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Thu, 27 Aug 2026 23:43:02 +0800 Subject: [PATCH 0890/1417] i2c: imx: release DMA channels on probe error i2c_imx_dma_request() acquires exclusive tx/rx DMA channels and is optional: on errors other than -EPROBE_DEFER the driver falls back to PIO mode and probe continues. If i2c_add_numbered_adapter() then fails, probe returns through clk_notifier_unregister without releasing the channels, because the remove callback is not invoked after a failed probe. Release the channels on the probe error path, mirroring i2c_imx_remove(). Fixes: ce1a78840ff7 ("i2c: imx: add DMA support for freescale i2c driver") Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Cc: # v3.19+ Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260827-i2c-dma-channel-leak-v1-2-271d4adc03a0@cherr.cc --- drivers/i2c/busses/i2c-imx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index 4dc3df6a41a4f4..ff5a91158df98d 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -1880,6 +1880,8 @@ static int i2c_imx_probe(struct platform_device *pdev) clk_notifier_unregister: clk_notifier_unregister(i2c_imx->clk, &i2c_imx->clk_change_nb); + if (i2c_imx->dma) + i2c_imx_dma_free(i2c_imx); free_irq(irq, i2c_imx); rpm_disable: pm_runtime_put_noidle(&pdev->dev); From 268aacb2e2a5c94d08e23194961234d0710c8407 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Thu, 27 Aug 2026 23:43:03 +0800 Subject: [PATCH 0891/1417] i2c: qcom-geni: release DMA channels on probe error geni_i2c_init() grabs exclusive GPI tx/rx DMA channels when the serial engine runs in GPI mode. If i2c_add_adapter() subsequently fails, probe returns without releasing the channels, because the remove callback is not invoked after a failed probe. The adapter-registration failure path used to release the channels via its err_dma label; that release was dropped when the probe tail was restructured into geni_i2c_init(). Release the channels on the adapter-registration failure path, mirroring geni_i2c_remove(). Fixes: d8d3bb127ad1 ("i2c: qcom-geni: Isolate serial engine setup") Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Reviewed-by: Konrad Dybcio Reviewed-by: Mukesh Kumar Savaliya Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260827-i2c-dma-channel-leak-v1-3-271d4adc03a0@cherr.cc --- drivers/i2c/busses/i2c-qcom-geni.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 00013b41a6f597..e9e41a174c204a 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -1189,8 +1189,10 @@ static int geni_i2c_probe(struct platform_device *pdev) return ret; ret = i2c_add_adapter(&gi2c->adap); - if (ret) + if (ret) { + release_gpi_dma(gi2c); return dev_err_probe(dev, ret, "Error adding i2c adapter\n"); + } dev_dbg(dev, "Geni-I2C adaptor successfully added\n"); From 7362a1553eb09a8cdf8be7e509bd5309a8342486 Mon Sep 17 00:00:00 2001 From: Liu Zhenlong Date: Wed, 19 Aug 2026 01:57:50 +0800 Subject: [PATCH 0892/1417] i2c: qcom-cci: fix device_node refcount leak in cci_probe()/cci_remove() The of_node_put() matching of_node_get() runs after i2c_del_adapter(), whose trailing memset() zeroes adap->dev and thus adap->dev.of_node, making the put a no-op and leaking the node on every adapter removal and error cleanup. Use a devm action: the pointer is captured at registration, out of reach of that memset(), and devres runs the put once on probe failure and detach, replacing the three manual of_node_put() calls. The setup loop uses the scoped iterator form so the child node is released automatically if devm_add_action_or_reset() fails mid-loop. Suggested-by: Konrad Dybcio Fixes: 02a4a69667a2 ("i2c: qcom-cci: don't put a device tree node before i2c_add_adapter()") Assisted-by: Claude:claude-opus-5 Signed-off-by: Liu Zhenlong Cc: # v5.17+ Reviewed-by: Vladimir Zapolskiy Reviewed-by: Konrad Dybcio Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260818175750.4205-1-dragonliu2018@gmail.com --- drivers/i2c/busses/i2c-qcom-cci.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-cci.c b/drivers/i2c/busses/i2c-qcom-cci.c index 25b6e4e9e3fa31..873e901a23d7a2 100644 --- a/drivers/i2c/busses/i2c-qcom-cci.c +++ b/drivers/i2c/busses/i2c-qcom-cci.c @@ -497,10 +497,14 @@ static const struct dev_pm_ops qcom_cci_pm = { SET_RUNTIME_PM_OPS(cci_suspend_runtime, cci_resume_runtime, NULL) }; +static void cci_put_of_node(void *data) +{ + of_node_put(data); +} + static int cci_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *child; struct resource *r; struct cci *cci; int ret, i; @@ -516,7 +520,7 @@ static int cci_probe(struct platform_device *pdev) if (!cci->data) return -ENOENT; - for_each_available_child_of_node(dev->of_node, child) { + for_each_available_child_of_node_scoped(dev->of_node, child) { struct cci_master *master; u32 idx; @@ -537,6 +541,9 @@ static int cci_probe(struct platform_device *pdev) master->adap.algo = &cci_algo; master->adap.dev.parent = dev; master->adap.dev.of_node = of_node_get(child); + ret = devm_add_action_or_reset(dev, cci_put_of_node, child); + if (ret) + return ret; master->master = idx; master->cci = cci; @@ -606,10 +613,8 @@ static int cci_probe(struct platform_device *pdev) continue; ret = i2c_add_adapter(&cci->master[i].adap); - if (ret < 0) { - of_node_put(cci->master[i].adap.dev.of_node); + if (ret < 0) goto error_i2c; - } } return 0; @@ -617,10 +622,8 @@ static int cci_probe(struct platform_device *pdev) error_i2c: for (--i ; i >= 0; i--) { - if (cci->master[i].cci) { + if (cci->master[i].cci) i2c_del_adapter(&cci->master[i].adap); - of_node_put(cci->master[i].adap.dev.of_node); - } } disable_clocks: cci_disable_clocks(cci); @@ -636,7 +639,6 @@ static void cci_remove(struct platform_device *pdev) for (i = 0; i < cci->data->num_masters; i++) { if (cci->master[i].cci) { i2c_del_adapter(&cci->master[i].adap); - of_node_put(cci->master[i].adap.dev.of_node); cci_halt(cci, i); } } From c4e941bb7654bcbdfb0b6f3341dc2acfdf235c8d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 15 Sep 2026 22:10:30 +0200 Subject: [PATCH 0893/1417] landlock: Work around gcc-16 -Wuninitialized warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gcc has a bug with -ftrivial-auto-var-init=pattern that produces a warning for correct code that uses sparse bitfields: security/landlock/fs.c: In function 'is_access_to_paths_allowed.isra': security/landlock/fs.c:767:28: error: '_layer_masks_child1' is used uninitialized [-Werror=uninitialized] 767 | struct layer_masks _layer_masks_child1, _layer_masks_child2; | ^~~~~~~~~~~~~~~~~~~ security/landlock/fs.c:767:28: note: '_layer_masks_child1' declared here 767 | struct layer_masks _layer_masks_child1, _layer_masks_child2; | ^~~~~~~~~~~~~~~~~~~ security/landlock/fs.c: In function 'hook_unix_find': security/landlock/fs.c:1649:28: error: 'layer_masks' is used uninitialized [-Werror=uninitialized] 1649 | struct layer_masks layer_masks; | ^~~~~~~~~~~ security/landlock/fs.c:1649:28: note: 'layer_masks' declared here 1649 | struct layer_masks layer_masks; | ^~~~~~~~~~~ To work around this, change the definition of struct layer_mask to use an explictit padding field. Link: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110743 Link: https://lore.kernel.org/all/20260619082133.3504146-1-arnd@kernel.org/ Fixes: a260c0055665 ("landlock: Add a place for flags to layer rules") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260915201036.3527935-1-arnd@kernel.org [mic: Use BITS_PER_TYPE(), fix kdoc warnings, fix commit message according to v2 changes] Cc: stable@vger.kernel.org Signed-off-by: Mickaël Salaün --- security/landlock/access.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/security/landlock/access.h b/security/landlock/access.h index bbbb41f41147cf..f843835851d014 100644 --- a/security/landlock/access.h +++ b/security/landlock/access.h @@ -61,6 +61,10 @@ union access_masks_all { static_assert(sizeof(typeof_member(union access_masks_all, masks)) == sizeof(typeof_member(union access_masks_all, all))); +#define _LANDLOCK_LAYER_MASK_PADDING \ + (BITS_PER_TYPE(access_mask_t) - LANDLOCK_NUM_ACCESS_MAX - \ + IS_ENABLED(CONFIG_SECURITY_LANDLOCK_LOG)) + /** * struct layer_mask - The access rights and rule flags for a layer. * @@ -81,6 +85,10 @@ struct layer_mask { */ access_mask_t quiet : 1; #endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + /** + * @__pad: Padding for the compiler's bitfield initialization. + */ + access_mask_t __pad : _LANDLOCK_LAYER_MASK_PADDING; } __packed __aligned(sizeof(access_mask_t)); /* From f71ecaece401cef287cdca12aad785fec809cb9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:24 +0200 Subject: [PATCH 0894/1417] landlock: Fix tracepoint fixed-width type names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new Landlock tracepoints use UAPI-prefixed __u32 and __u64 names for callback arguments and record fields, including internal IDs that are not Landlock UAPI values. Typed BPF consumers see callback typedef names through BTF. Use the kernel u32 and u64 aliases before release so the tracepoint contract does not present internal values as Landlock UAPI types. This changes BTF-visible typedef spelling but not integer widths, calling conventions, tracefs formats, or record layouts. Cc: Günther Noack Cc: Steven Rostedt Link: https://patch.msgid.link/20260918185036.608651-2-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 68 ++++++++++++++++----------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index d05253afaf5909..4984f80923ed9d 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -243,8 +243,8 @@ static inline const char *__trace_landlock_print_layers( * Field encoding * ~~~~~~~~~~~~~~ * - * Fields that mirror the Landlock UAPI use the same C types and endianness - * (e.g. network ports are __u64 in host endianness, like + * Fields that mirror the Landlock UAPI preserve their widths and endianness + * (e.g. network ports are u64 in host endianness, like * landlock_net_port_attr.port). Per-event details, such as where a value * is byte-swapped, live in the field's own kdoc. * @@ -319,8 +319,8 @@ TRACE_EVENT(landlock_create_ruleset, TP_ARGS(ruleset), TP_STRUCT__entry( - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, ruleset_id ) + __field( u32, ruleset_version ) __field( access_mask_t, handled_fs ) __field( access_mask_t, handled_net ) __field( access_mask_t, scoped ) @@ -359,8 +359,8 @@ TRACE_EVENT(landlock_free_ruleset, TP_ARGS(ruleset), TP_STRUCT__entry( - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, ruleset_id ) + __field( u32, ruleset_version ) ), TP_fast_assign( @@ -396,8 +396,8 @@ TRACE_EVENT(landlock_add_rule_fs, TP_ARGS(ruleset, access_rights, path, pathname), TP_STRUCT__entry( - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, ruleset_id ) + __field( u32, ruleset_version ) __field( access_mask_t, access_rights ) __field( dev_t, dev ) __field( ino_t, ino ) @@ -443,15 +443,15 @@ TRACE_EVENT(landlock_add_rule_fs, TRACE_EVENT(landlock_add_rule_net, TP_PROTO(const struct landlock_ruleset *ruleset, - access_mask_t access_rights, __u64 port), + access_mask_t access_rights, u64 port), TP_ARGS(ruleset, access_rights, port), TP_STRUCT__entry( - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, ruleset_id ) + __field( u32, ruleset_version ) __field( access_mask_t, access_rights ) - __field( __u64, port ) + __field( u64, port ) ), TP_fast_assign( @@ -495,10 +495,10 @@ TRACE_EVENT(landlock_create_domain, TP_ARGS(domain, ruleset), TP_STRUCT__entry( - __field( __u64, domain_id ) - __field( __u64, parent_id ) - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, domain_id ) + __field( u64, parent_id ) + __field( u64, ruleset_id ) + __field( u32, ruleset_version ) ), TP_fast_assign( @@ -557,7 +557,7 @@ TRACE_EVENT(landlock_enforce_domain, TP_ARGS(domain, complete, process_wide, no_new_privs), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, complete ) __field( bool, process_wide ) __field( bool, no_new_privs ) @@ -595,8 +595,8 @@ TRACE_EVENT(landlock_free_domain, TP_ARGS(hierarchy), TP_STRUCT__entry( - __field( __u64, domain_id ) - __field( __u64, denials ) + __field( u64, domain_id ) + __field( u64, denials ) ), TP_fast_assign( @@ -631,7 +631,7 @@ TRACE_EVENT(landlock_check_rule_fs, TP_ARGS(domain, rule, access_request, dentry), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( access_mask_t, access_request ) __field( dev_t, dev ) __field( ino_t, ino ) @@ -675,14 +675,14 @@ TRACE_EVENT(landlock_check_rule_net, TP_PROTO(const struct landlock_domain *domain, const struct landlock_rule *rule, - access_mask_t access_request, __u64 port), + access_mask_t access_request, u64 port), TP_ARGS(domain, rule, access_request, port), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( access_mask_t, access_request ) - __field( __u64, port ) + __field( u64, port ) __dynamic_array(access_mask_t, grants, domain->num_layers) ), @@ -729,7 +729,7 @@ TRACE_EVENT(landlock_deny_access_fs, TP_ARGS(hierarchy, same_exec, logged, blockers, path, pathname), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) __field( access_mask_t, blockers ) @@ -791,17 +791,17 @@ TRACE_EVENT(landlock_deny_access_net, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, bool logged, access_mask_t blockers, const struct sock *sk, - __u64 sport, __u64 dport), + u64 sport, u64 dport), TP_ARGS(hierarchy, same_exec, logged, blockers, sk, sport, dport), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) __field( access_mask_t, blockers ) - __field( __u64, sport ) - __field( __u64, dport ) + __field( u64, sport ) + __field( u64, dport ) ), TP_fast_assign( @@ -842,10 +842,10 @@ TRACE_EVENT(landlock_deny_ptrace, TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( __u64, tracee_domain_id) + __field( u64, tracee_domain_id) __field( pid_t, tracee_pid ) __string( tracee_comm, tracee->comm ) ), @@ -891,10 +891,10 @@ TRACE_EVENT(landlock_deny_scope_signal, TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( __u64, target_domain_id) + __field( u64, target_domain_id) __field( pid_t, target_pid ) __string( target_comm, target->comm ) ), @@ -940,10 +940,10 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket, TP_ARGS(hierarchy, same_exec, logged, peer_domain_id, peer), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( __u64, peer_domain_id ) + __field( u64, peer_domain_id ) __field( pid_t, peer_pid ) /* * Abstract socket names are untrusted binary data from From 0de33ca344fbf983d380d78db6eeb6fe312d5a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:25 +0200 Subject: [PATCH 0895/1417] landlock: Fix filesystem denial blocker reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filesystem topology denials are rendered with an empty blockers value because their blocker is identified by the request type instead of an access mask. Introduce the private struct landlock_blockers to carry the request type and final missing access mask to filesystem and network denial tracepoints. Copy both members into named trace-record fields, then use the type to print change_topology for topology denials while preserving symbolic access masks for ordinary denials. The request type lets typed BPF consumers distinguish topology denials from access denials. Keeping the native access mask in a pointer-reached field also lets CO-RE adjust existing programs' load width if access_mask_t grows. Cc: Günther Noack Cc: Steven Rostedt Fixes: 01ce260f5ccf ("landlock: Add landlock_deny_access_fs and landlock_deny_access_net") Link: https://patch.msgid.link/20260918185036.608651-3-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 51 ++++++++++++++++++++++++--------- security/landlock/log.h | 5 ++++ security/landlock/trace.c | 19 +++++++++--- 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index 4984f80923ed9d..4e304cab1dabf3 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -17,7 +17,9 @@ #include #include +enum landlock_request_type; struct dentry; +struct landlock_blockers; struct landlock_domain; struct landlock_hierarchy; struct landlock_rule; @@ -26,6 +28,10 @@ struct path; struct sock; struct task_struct; +TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY); +TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_ACCESS); +TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NET_ACCESS); + #ifdef CREATE_TRACE_POINTS /* About 6 KiB, leaving about 2 KiB for sibling helpers and fixed fields. */ @@ -182,6 +188,9 @@ static inline const char *__trace_landlock_print_layers( /* Maps a shared _LANDLOCK_*_NAMES entry to a __print_flags() pair. */ #define _LANDLOCK_NAME_ENTRY(mask, name) { mask, name } +#define _LANDLOCK_FS_BLOCKER_TYPE_NAMES \ + { LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, "change_topology" } + /** * DOC: Landlock trace events * @@ -281,6 +290,13 @@ static inline const char *__trace_landlock_print_layers( * the two parties without kernel-internal state. The ID is a scalar * snapshot, not a live domain pointer that could dangle: an optional * relational referent is a scalar (0 sentinel), not a nullable pointer. + * + * Blocker fields + * ~~~~~~~~~~~~~~ + * + * The filesystem and network blocker arguments identify the request type + * and carry its final missing access subset when applicable. The type + * determines how to interpret the access value. */ /* @@ -712,8 +728,7 @@ TRACE_EVENT(landlock_check_rule_net, * domain field. * @same_exec: Whether the current task entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. - * @blockers: Access mask that was blocked (zero for a mount-topology - * change, whose only blocker is the operation itself). + * @blockers: Request type and final missing access subset (never NULL). * @path: Filesystem path that was denied (never NULL). * @pathname: Resolved path string (never NULL; an error placeholder on * resolution failure). @@ -723,8 +738,8 @@ TRACE_EVENT(landlock_check_rule_net, TRACE_EVENT(landlock_deny_access_fs, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, - bool logged, access_mask_t blockers, const struct path *path, - const char *pathname), + bool logged, const struct landlock_blockers *blockers, + const struct path *path, const char *pathname), TP_ARGS(hierarchy, same_exec, logged, blockers, path, pathname), @@ -732,7 +747,8 @@ TRACE_EVENT(landlock_deny_access_fs, __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( access_mask_t, blockers ) + __field( enum landlock_request_type, blockers_type ) + __field( access_mask_t, blockers_access ) __field( dev_t, dev ) __field( ino_t, ino ) __string( pathname, pathname ) @@ -744,7 +760,8 @@ TRACE_EVENT(landlock_deny_access_fs, __entry->domain_id = hierarchy->id; __entry->same_exec = same_exec; __entry->logged = logged; - __entry->blockers = blockers; + __entry->blockers_type = blockers->type; + __entry->blockers_access = blockers->access; __entry->dev = path->dentry->d_sb->s_dev; /* * A negative dentry has no backing inode, so mirror the @@ -756,7 +773,10 @@ TRACE_EVENT(landlock_deny_access_fs, TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s dev=%u:%u ino=%lu path=%s", __entry->domain_id, __entry->same_exec, __entry->logged, - __print_flags(__entry->blockers, "|", _LANDLOCK_ACCESS_FS_NAMES), + __entry->blockers_type == LANDLOCK_REQUEST_FS_ACCESS ? + __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_FS_NAMES) : + __print_symbolic(__entry->blockers_type, + _LANDLOCK_FS_BLOCKER_TYPE_NAMES), MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __trace_print_untrusted_str(p, __get_str(pathname), __get_dynamic_array_len(pathname) - 1)) @@ -769,7 +789,7 @@ TRACE_EVENT(landlock_deny_access_fs, * domain field. * @same_exec: Whether the current task entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. - * @blockers: Access mask that was blocked. + * @blockers: Request type and final missing access subset (never NULL). * @sk: Socket object (never NULL), read without a socket lock, so its * fields are a best-effort snapshot. The denied endpoint is not * available: the hook runs before :manpage:`bind(2)` / @@ -790,8 +810,8 @@ TRACE_EVENT(landlock_deny_access_fs, TRACE_EVENT(landlock_deny_access_net, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, - bool logged, access_mask_t blockers, const struct sock *sk, - u64 sport, u64 dport), + bool logged, const struct landlock_blockers *blockers, + const struct sock *sk, u64 sport, u64 dport), TP_ARGS(hierarchy, same_exec, logged, blockers, sk, sport, dport), @@ -799,7 +819,8 @@ TRACE_EVENT(landlock_deny_access_net, __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( access_mask_t, blockers ) + __field( enum landlock_request_type, blockers_type ) + __field( access_mask_t, blockers_access ) __field( u64, sport ) __field( u64, dport ) ), @@ -808,14 +829,17 @@ TRACE_EVENT(landlock_deny_access_net, __entry->domain_id = hierarchy->id; __entry->same_exec = same_exec; __entry->logged = logged; - __entry->blockers = blockers; + __entry->blockers_type = blockers->type; + __entry->blockers_access = blockers->access; __entry->sport = sport; __entry->dport = dport; ), TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s sport=%llu dport=%llu", __entry->domain_id, __entry->same_exec, __entry->logged, - __print_flags(__entry->blockers, "|", _LANDLOCK_ACCESS_NET_NAMES), + __entry->blockers_type == LANDLOCK_REQUEST_NET_ACCESS ? + __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_NET_NAMES) : + "unknown", __entry->sport, __entry->dport) ); @@ -991,6 +1015,7 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket, __get_dynamic_array_len(sun_path) - 1)) ); +#undef _LANDLOCK_FS_BLOCKER_TYPE_NAMES #undef _LANDLOCK_NAME_ENTRY #endif /* _TRACE_LANDLOCK_H */ diff --git a/security/landlock/log.h b/security/landlock/log.h index e0a6e44f3ddd32..04f3e241e76576 100644 --- a/security/landlock/log.h +++ b/security/landlock/log.h @@ -25,6 +25,11 @@ enum landlock_request_type { LANDLOCK_REQUEST_SCOPE_SIGNAL, }; +struct landlock_blockers { + access_mask_t access; + enum landlock_request_type type; +}; + /* * We should be careful to only use a variable of this type for * landlock_log_denial(). This way, the compiler can remove it entirely if diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 8c21e5de6f0de2..58276cc32d3f4c 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -61,7 +61,7 @@ void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy * * @request: Detail of the user space request. * @youngest_denied: The youngest hierarchy node that denied the access. - * @missing: The set of denied access rights. + * @missing: The final missing access subset, when applicable. * @same_exec: Whether the current task is the same executable that called * landlock_restrict_self() for the denying domain, as computed * by landlock_log_denial(). @@ -83,6 +83,10 @@ void landlock_trace_denial( case LANDLOCK_REQUEST_FS_ACCESS: case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: if (trace_landlock_deny_access_fs_enabled()) { + const struct landlock_blockers blockers = { + .access = missing, + .type = request->type, + }; char *buf __free(__putname) = __getname(); struct path dentry_path; const char *pathname; @@ -147,16 +151,23 @@ void landlock_trace_denial( trace_landlock_deny_access_fs(youngest_denied, same_exec, logged, - missing, path, pathname); + &blockers, path, + pathname); } break; case LANDLOCK_REQUEST_NET_ACCESS: - if (trace_landlock_deny_access_net_enabled()) + if (trace_landlock_deny_access_net_enabled()) { + const struct landlock_blockers blockers = { + .access = missing, + .type = request->type, + }; + trace_landlock_deny_access_net( - youngest_denied, same_exec, logged, missing, + youngest_denied, same_exec, logged, &blockers, request->audit.u.net->sk, ntohs(request->audit.u.net->sport), ntohs(request->audit.u.net->dport)); + } break; case LANDLOCK_REQUEST_PTRACE: if (trace_landlock_deny_ptrace_enabled()) From 1a985d3890ed8caa428390a5682e957503f14060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:26 +0200 Subject: [PATCH 0896/1417] landlock: Fix rule tracepoint context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Name each event after the identity it reports. Add-rule events describe UAPI rule insertion, so rename them after LANDLOCK_RULE_PATH_BENEATH and LANDLOCK_RULE_NET_PORT. Check-rule events describe matches in internal rule trees, so rename them after LANDLOCK_KEY_INODE and LANDLOCK_KEY_NET_PORT. This remains accurate if multiple UAPI rule types share one lookup and stored rule. Keep denial event names based on filesystem and network families because they describe final access decisions. Use u64 for growable access masks passed by value to add-rule and check-rule typed BTF callbacks. CO-RE can relocate pointer-reached fields, but it cannot widen a scalar callback slot declared by a BPF program. Keep native access_mask_t for internal state and trace records. For add-rule callbacks, report the normalized per-call contribution passed to landlock_insert_rule() and expose the complete validated flags value. Put the ruleset and flags first as a common invocation prefix. This distinguishes duplicate and effective-zero additions without recovering arguments from saved syscall registers. Cc: Günther Noack Cc: Steven Rostedt Fixes: 63747c94774d ("landlock: Add landlock_add_rule_fs and landlock_add_rule_net tracepoints") Fixes: 3f1f106e4c14 ("landlock: Add tracepoints for rule checking") Link: https://patch.msgid.link/20260918185036.608651-4-mic@digikod.net Signed-off-by: Mickaël Salaün --- Documentation/trace/events-landlock.rst | 22 +++---- include/trace/events/landlock.h | 62 +++++++++++-------- security/landlock/fs.c | 10 +-- security/landlock/net.c | 5 +- tools/testing/selftests/landlock/fs_test.c | 28 ++++----- tools/testing/selftests/landlock/net_test.c | 11 ++-- tools/testing/selftests/landlock/trace.h | 56 ++++++++--------- .../selftests/landlock/trace_fs_test.c | 54 +++++++++------- tools/testing/selftests/landlock/trace_test.c | 51 ++++++++------- 9 files changed, 162 insertions(+), 137 deletions(-) diff --git a/Documentation/trace/events-landlock.rst b/Documentation/trace/events-landlock.rst index af9267cca47d9d..9bb81a5c676e73 100644 --- a/Documentation/trace/events-landlock.rst +++ b/Documentation/trace/events-landlock.rst @@ -6,7 +6,7 @@ Landlock Trace Events ===================== :Author: Mickaël Salaün -:Date: August 2026 +:Date: September 2026 Landlock emits trace events for sandbox lifecycle operations and access denials. These events can be consumed by ftrace (for human-readable @@ -31,8 +31,8 @@ Landlock trace events are organized in four categories: **Syscall events** are emitted during Landlock system calls: - ``landlock_create_ruleset``: a new ruleset is created -- ``landlock_add_rule_fs``: a filesystem rule is added to a ruleset -- ``landlock_add_rule_net``: a network port rule is added to a ruleset +- ``landlock_add_rule_path_beneath``: a filesystem rule is added to a ruleset +- ``landlock_add_rule_net_port``: a network port rule is added to a ruleset - ``landlock_create_domain``: a new domain is created from a ruleset - ``landlock_enforce_domain``: a domain is enforced on a thread @@ -47,8 +47,8 @@ Landlock trace events are organized in four categories: **Rule evaluation events** are emitted during rule matching: -- ``landlock_check_rule_fs``: a filesystem rule is evaluated -- ``landlock_check_rule_net``: a network port rule is evaluated +- ``landlock_check_rule_inode``: an inode-keyed rule is evaluated +- ``landlock_check_rule_net_port``: a network-port-keyed rule is evaluated **Lifecycle events**: @@ -189,7 +189,7 @@ rather than the caller's, so correlate those to the syscall by domain ID. Interpreting check_rule events ============================== -The ``check_rule_fs`` and ``check_rule_net`` events expose the per-layer +The ``check_rule_inode`` and ``check_rule_net_port`` events expose the per-layer rule evaluation, which is useful for understanding *why* a specific access is allowed or denied. @@ -244,22 +244,22 @@ check. For example, a program sandboxed with read and execute access to the whole filesystem reads ``/etc/passwd``; both the ``execve()`` and the -read match the rule covering ``/`` (inode 2), so ``check_rule_fs`` fires +read match the rule covering ``/`` (inode 2), so ``check_rule_inode`` fires with the requested rights intersected against what that rule grants. The ``access_request=`` mask includes ``truncate`` because the file-open hook evaluates that optional right alongside the required access, but the rule does not grant it, so ``truncate`` never appears in ``grants=``:: - cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file} - cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file} + cat-127 [...] landlock_check_rule_inode: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file} + cat-127 [...] landlock_check_rule_inode: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file} The ``[...]`` replaces the ftrace CPU, flags, and timestamp columns. A single ``grants=`` group means the enforcing domain has one layer. With two nested sandboxes that each grant the same rights, the rule spans both layers, so ``grants=`` has one group per layer:: - cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file} - cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file} + cat-128 [...] landlock_check_rule_inode: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file} + cat-128 [...] landlock_check_rule_inode: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file} eBPF access =========== diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index 4e304cab1dabf3..b7e6a66388227b 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -28,6 +28,8 @@ struct path; struct sock; struct task_struct; +static_assert(sizeof(access_mask_t) <= sizeof(u64)); + TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY); TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_ACCESS); TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NET_ACCESS); @@ -114,7 +116,7 @@ __trace_print_untrusted_str(struct trace_seq *p, const char *src, size_t len) * Fills the dense per-domain-layer array layers (one access mask per layer, * indexed by level - 1) from rule's sparse layer stack, keeping only the * requested rights (access_request). Layers with no matching rule entry get - * a zero mask. Shared by the check_rule_fs and check_rule_net events. + * a zero mask. Shared by the check_rule_inode and check_rule_net_port events. * * rule->layers is sorted by ascending level, with levels in the domain's * [1, num_layers] range (see landlock_merge_ruleset()), so every entry maps @@ -389,12 +391,14 @@ TRACE_EVENT(landlock_free_ruleset, ); /** - * landlock_add_rule_fs - Filesystem rule added to a ruleset + * landlock_add_rule_path_beneath - Path-beneath rule added to a ruleset * * @ruleset: Source ruleset (never NULL). - * @access_rights: Effective access mask stored in the rule, not the raw - * sys_landlock_add_rule() argument (unhandled rights - * added). + * @flags: Complete validated landlock_add_rule_flags value supplied by this + * successful call, not the rule's accumulated quiet state. + * @access_rights: Canonical per-call access mask passed to + * landlock_insert_rule() after normalization, not the raw + * sys_landlock_add_rule() argument or accumulated rule. * @path: Filesystem path for the rule (never NULL). * @pathname: Resolved absolute path string (never NULL; error placeholder * on resolution failure). @@ -403,13 +407,13 @@ TRACE_EVENT(landlock_free_ruleset, * the reported ruleset is a stable snapshot that no concurrent writer can * change. */ -TRACE_EVENT(landlock_add_rule_fs, +TRACE_EVENT(landlock_add_rule_path_beneath, - TP_PROTO(const struct landlock_ruleset *ruleset, - access_mask_t access_rights, const struct path *path, + TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags, + u64 access_rights, const struct path *path, const char *pathname), - TP_ARGS(ruleset, access_rights, path, pathname), + TP_ARGS(ruleset, flags, access_rights, path, pathname), TP_STRUCT__entry( __field( u64, ruleset_id ) @@ -443,25 +447,27 @@ TRACE_EVENT(landlock_add_rule_fs, ); /** - * landlock_add_rule_net - Network port rule added to a ruleset + * landlock_add_rule_net_port - Network-port rule added to a ruleset * * @ruleset: Source ruleset (never NULL). - * @access_rights: Effective access mask stored in the rule, not the raw - * sys_landlock_add_rule() argument (unhandled rights - * added). - * @port: Network port, the landlock_net_port_attr.port UAPI value - * forwarded directly. + * @flags: Complete validated landlock_add_rule_flags value supplied by this + * successful call, not the rule's accumulated quiet state. + * @access_rights: Canonical per-call access mask passed to + * landlock_insert_rule() after normalization, not the raw + * sys_landlock_add_rule() argument or accumulated rule. + * @port: Network port in host endianness, forwarded directly from + * &landlock_net_port_attr.port. * * Emitted by sys_landlock_add_rule() under the modified ruleset's lock, so * the reported ruleset is a stable snapshot that no concurrent writer can * change. */ -TRACE_EVENT(landlock_add_rule_net, +TRACE_EVENT(landlock_add_rule_net_port, - TP_PROTO(const struct landlock_ruleset *ruleset, - access_mask_t access_rights, u64 port), + TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags, + u64 access_rights, u64 port), - TP_ARGS(ruleset, access_rights, port), + TP_ARGS(ruleset, flags, access_rights, port), TP_STRUCT__entry( __field( u64, ruleset_id ) @@ -625,7 +631,7 @@ TRACE_EVENT(landlock_free_domain, ); /** - * landlock_check_rule_fs - Filesystem rule evaluated during access check + * landlock_check_rule_inode - Inode rule evaluated during access check * * @domain: Enforcing domain (never NULL). * @rule: Matching rule with per-layer access masks (never NULL). @@ -638,11 +644,11 @@ TRACE_EVENT(landlock_free_domain, * domain layer. See Documentation/trace/events-landlock.rst for how to * interpret it. */ -TRACE_EVENT(landlock_check_rule_fs, +TRACE_EVENT(landlock_check_rule_inode, TP_PROTO(const struct landlock_domain *domain, const struct landlock_rule *rule, - access_mask_t access_request, const struct dentry *dentry), + u64 access_request, const struct dentry *dentry), TP_ARGS(domain, rule, access_request, dentry), @@ -664,7 +670,8 @@ TRACE_EVENT(landlock_check_rule_fs, __trace_landlock_fill_layers(__get_dynamic_array(grants), __get_dynamic_array_len(grants) / sizeof(access_mask_t), - rule, access_request); + rule, + (access_mask_t)access_request); ), TP_printk("domain=%llx access_request=%s dev=%u:%u ino=%lu grants=%s", @@ -675,7 +682,7 @@ TRACE_EVENT(landlock_check_rule_fs, ); /** - * landlock_check_rule_net - Network port rule evaluated during access check + * landlock_check_rule_net_port - Network port rule evaluated * * @domain: Enforcing domain (never NULL). * @rule: Matching rule with per-layer access masks (never NULL). @@ -687,11 +694,11 @@ TRACE_EVENT(landlock_check_rule_fs, * layer. See Documentation/trace/events-landlock.rst for how to * interpret it. */ -TRACE_EVENT(landlock_check_rule_net, +TRACE_EVENT(landlock_check_rule_net_port, TP_PROTO(const struct landlock_domain *domain, const struct landlock_rule *rule, - access_mask_t access_request, u64 port), + u64 access_request, u64 port), TP_ARGS(domain, rule, access_request, port), @@ -711,7 +718,8 @@ TRACE_EVENT(landlock_check_rule_net, __trace_landlock_fill_layers(__get_dynamic_array(grants), __get_dynamic_array_len(grants) / sizeof(access_mask_t), - rule, access_request); + rule, + (access_mask_t)access_request); ), TP_printk("domain=%llx access_request=%s port=%llu grants=%s", diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 330a1871bf94a6..cab43892ec2f0d 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -356,14 +356,14 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, * held for BTF consistency (enforced by lockdep_assert_held in * TP_fast_assign). */ - if (!err && trace_landlock_add_rule_fs_enabled()) { + if (!err && trace_landlock_add_rule_path_beneath_enabled()) { char *buffer __free(__putname) = __getname(); const char *pathname = buffer ? resolve_path_for_trace(path, buffer) : ""; - trace_landlock_add_rule_fs(ruleset, access_rights, path, - pathname); + trace_landlock_add_rule_path_beneath( + ruleset, flags, access_rights, path, pathname); } mutex_unlock(&ruleset->lock); @@ -423,8 +423,8 @@ static bool unmask_layers_fs(const struct landlock_domain *const domain, ret = landlock_unmask_layers(domain, id, masks, &rule); if (rule) - trace_landlock_check_rule_fs(domain, rule, access_request, - dentry); + trace_landlock_check_rule_inode(domain, rule, access_request, + dentry); return ret; } diff --git a/security/landlock/net.c b/security/landlock/net.c index 8f2aaac54b3347..5552c60388f888 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -47,7 +47,8 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset, * TP_fast_assign). */ if (!err) - trace_landlock_add_rule_net(ruleset, access_rights, port); + trace_landlock_add_rule_net_port(ruleset, flags, access_rights, + port); mutex_unlock(&ruleset->lock); return err; @@ -63,7 +64,7 @@ static bool unmask_layers_net(const struct landlock_domain *const domain, ret = landlock_unmask_layers(domain, id, masks, &rule); if (rule) - trace_landlock_check_rule_net( + trace_landlock_check_rule_net_port( domain, rule, access_request, ntohs((__force __be16)id.key.data)); return ret; diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 18dbdb99aebab4..6e979cef884d15 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -10493,9 +10493,9 @@ FIXTURE_TEARDOWN_PARENT(trace_layout1) } /* - * Verifies that check_rule_fs events include correct field values: domain, dev, - * ino, access_request, and grants. All values are verified against stat() of - * the rule path on a deterministic tmpfs layout. + * Verifies that check_rule_inode events include correct field values: domain, + * dev, ino, access_request, and grants. All values are verified against stat() + * of the rule path on a deterministic tmpfs layout. */ TEST_F(trace_layout1, check_rule_fs_fields) { @@ -10529,7 +10529,7 @@ TEST_F(trace_layout1, check_rule_fs_fields) EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK))) { - TH_LOG("Expected 1 check_rule_fs event\n%s", buf); + TH_LOG("Expected 1 check_rule_inode event\n%s", buf); } ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK), @@ -10570,8 +10570,8 @@ TEST_F(trace_layout1, check_rule_fs_fields) } /* - * Verifies check_rule_fs behavior with multiple rules. With rules at s1d1 and - * s1d2 (a child of s1d1), accessing s1d2 produces only 1 event because the + * Verifies check_rule_inode behavior with multiple rules. With rules at s1d1 + * and s1d2 (a child of s1d1), accessing s1d2 produces only 1 event because the * pathwalk short-circuits after the first rule fully unmasks the single layer. */ TEST_F(trace_layout1, check_rule_fs_multiple_rules) @@ -10643,14 +10643,14 @@ TEST_F(trace_layout1, check_rule_fs_multiple_rules) ASSERT_NE(NULL, buf); /* - * Only 1 check_rule_fs event: the rule on dir_s1d2 fully unmasked the - * single layer, so the pathwalk short-circuits before reaching the + * Only one check_rule_inode event: the rule on dir_s1d2 fully unmasks + * the single layer, so the pathwalk short-circuits before reaching the * dir_s1d1 rule. */ count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); EXPECT_EQ(1, count) { - TH_LOG("Expected 1 check_rule_fs event, got %d\n%s", count, + TH_LOG("Expected 1 check_rule_inode event, got %d\n%s", count, buf); } @@ -10777,7 +10777,7 @@ TEST_F(trace_layout1, check_rule_fs_optional_access) count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); EXPECT_EQ(1, count) { - TH_LOG("Expected 1 check_rule_fs event, got %d\n%s", count, + TH_LOG("Expected 1 check_rule_inode event, got %d\n%s", count, buf); } @@ -10796,7 +10796,7 @@ TEST_F(trace_layout1, check_rule_fs_optional_access) } /* - * Verifies that check_rule_fs fires for a rule that matches the inode even when + * Verifies that check_rule_inode fires for a rule matching the inode even when * it grants none of the requested rights, so the grants set is empty. Landlock * cannot know a rule ignores the request before reading it, so the event is * still emitted (grants={}), which lets a tracer see that the rule matched. @@ -10884,7 +10884,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant) count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); EXPECT_EQ(2, count) { - TH_LOG("Expected 2 check_rule_fs events, got %d\n%s", count, + TH_LOG("Expected 2 check_rule_inode events, got %d\n%s", count, buf); } @@ -10894,7 +10894,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant) tracefs_count_matches( buf, TRACE_PREFIX( - TRACE_TASK) "landlock_check_rule_fs: domain=[0-9a-f]\\+ " + TRACE_TASK) "landlock_check_rule_inode: domain=[0-9a-f]\\+ " "access_request=read_dir " "dev=[0-9]\\+:[0-9]\\+ ino=[0-9]\\+ " "grants={}$")) @@ -10908,7 +10908,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant) tracefs_count_matches( buf, TRACE_PREFIX( - TRACE_TASK) "landlock_check_rule_fs: domain=[0-9a-f]\\+ " + TRACE_TASK) "landlock_check_rule_inode: domain=[0-9a-f]\\+ " "access_request=read_dir " "dev=[0-9]\\+:[0-9]\\+ ino=[0-9]\\+ " "grants={read_dir}$")) diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index a18761e0fd82f4..4fb705e1596d00 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -3712,7 +3712,7 @@ TEST_F(trace_net_connect, deny_access_net) free(buf); } -/* Field verification for the check_rule_net event on an allowed access. */ +/* Field verification for the check_rule_net_port event on an allowed access. */ /* clang-format off */ FIXTURE(trace_net_check_rule) { @@ -3757,10 +3757,11 @@ FIXTURE_TEARDOWN(trace_net_check_rule) /* * Verifies that an allowed bind matching a net-port rule emits exactly one - * landlock_check_rule_net event with the enforcing domain, the requested + * landlock_check_rule_net_port event with the enforcing domain, the requested * access, the checked port (host endianness), and the per-layer grants. The - * whole event is anchored to exact values so a revert of the check_rule_net - * emit (or a byte-order or field-plumbing regression) fails the test. + * whole event is anchored to exact values so removing the check_rule_net_port + * emission or introducing a byte-order or field-plumbing regression fails the + * test. */ TEST_F(trace_net_check_rule, check_rule_net_fields) { @@ -3832,7 +3833,7 @@ TEST_F(trace_net_check_rule, check_rule_net_fields) EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_CHECK_RULE_NET(TRACE_TASK))) { - TH_LOG("Expected 1 check_rule_net event\n%s", buf); + TH_LOG("Expected 1 check_rule_net_port event\n%s", buf); } ASSERT_EQ(0, diff --git a/tools/testing/selftests/landlock/trace.h b/tools/testing/selftests/landlock/trace.h index ba0c5e92001f1c..e6873853376c7f 100644 --- a/tools/testing/selftests/landlock/trace.h +++ b/tools/testing/selftests/landlock/trace.h @@ -27,14 +27,14 @@ TRACEFS_LANDLOCK_DIR "/landlock_create_domain/enable" #define TRACEFS_ENFORCE_DOMAIN_ENABLE \ TRACEFS_LANDLOCK_DIR "/landlock_enforce_domain/enable" -#define TRACEFS_ADD_RULE_FS_ENABLE \ - TRACEFS_LANDLOCK_DIR "/landlock_add_rule_fs/enable" -#define TRACEFS_ADD_RULE_NET_ENABLE \ - TRACEFS_LANDLOCK_DIR "/landlock_add_rule_net/enable" +#define TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE \ + TRACEFS_LANDLOCK_DIR "/landlock_add_rule_path_beneath/enable" +#define TRACEFS_ADD_RULE_NET_PORT_ENABLE \ + TRACEFS_LANDLOCK_DIR "/landlock_add_rule_net_port/enable" #define TRACEFS_CHECK_RULE_FS_ENABLE \ - TRACEFS_LANDLOCK_DIR "/landlock_check_rule_fs/enable" + TRACEFS_LANDLOCK_DIR "/landlock_check_rule_inode/enable" #define TRACEFS_CHECK_RULE_NET_ENABLE \ - TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net/enable" + TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net_port/enable" #define TRACEFS_DENY_ACCESS_FS_ENABLE \ TRACEFS_LANDLOCK_DIR "/landlock_deny_access_fs/enable" #define TRACEFS_DENY_ACCESS_NET_ENABLE \ @@ -79,18 +79,18 @@ */ #define KWORKER_TASK "kworker/[0-9]\\+:[0-9]\\+" -#define REGEX_ADD_RULE_FS(task) \ - TRACE_PREFIX(task) \ - "landlock_add_rule_fs: " \ - "ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \ - "access_rights=[a-z_|]* " \ - "dev=[0-9]\\+:[0-9]\\+ " \ - "ino=[0-9]\\+ " \ +#define REGEX_ADD_RULE_PATH_BENEATH(task) \ + TRACE_PREFIX(task) \ + "landlock_add_rule_path_beneath: " \ + "ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \ + "access_rights=[a-z_|]* " \ + "dev=[0-9]\\+:[0-9]\\+ " \ + "ino=[0-9]\\+ " \ "path=[^ ]\\+$" -#define REGEX_ADD_RULE_NET(task) \ +#define REGEX_ADD_RULE_NET_PORT(task) \ TRACE_PREFIX(task) \ - "landlock_add_rule_net: " \ + "landlock_add_rule_net_port: " \ "ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \ "access_rights=[a-z_|]* " \ "port=[0-9]\\+$" @@ -110,21 +110,21 @@ "parent=[0-9a-f]\\+ " \ "ruleset=[0-9a-f]\\+\\.[0-9]\\+$" -#define REGEX_CHECK_RULE_FS(task) \ - TRACE_PREFIX(task) \ - "landlock_check_rule_fs: " \ - "domain=[0-9a-f]\\+ " \ - "access_request=[a-z_|]* " \ - "dev=[0-9]\\+:[0-9]\\+ " \ - "ino=[0-9]\\+ " \ +#define REGEX_CHECK_RULE_FS(task) \ + TRACE_PREFIX(task) \ + "landlock_check_rule_inode: " \ + "domain=[0-9a-f]\\+ " \ + "access_request=[a-z_|]* " \ + "dev=[0-9]\\+:[0-9]\\+ " \ + "ino=[0-9]\\+ " \ "grants={[a-z_|,]*}$" -#define REGEX_CHECK_RULE_NET(task) \ - TRACE_PREFIX(task) \ - "landlock_check_rule_net: " \ - "domain=[0-9a-f]\\+ " \ - "access_request=[a-z_|]* " \ - "port=[0-9]\\+ " \ +#define REGEX_CHECK_RULE_NET(task) \ + TRACE_PREFIX(task) \ + "landlock_check_rule_net_port: " \ + "domain=[0-9a-f]\\+ " \ + "access_request=[a-z_|]* " \ + "port=[0-9]\\+ " \ "grants={[a-z_|,]*}$" #define REGEX_DENY_ACCESS_FS(task) \ diff --git a/tools/testing/selftests/landlock/trace_fs_test.c b/tools/testing/selftests/landlock/trace_fs_test.c index 4543a25c1f5591..6666d4746cb11a 100644 --- a/tools/testing/selftests/landlock/trace_fs_test.c +++ b/tools/testing/selftests/landlock/trace_fs_test.c @@ -121,7 +121,8 @@ FIXTURE_SETUP(trace_fs) } self->tracefs_ok = 1; - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true)); + ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, + true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true)); ASSERT_EQ(0, tracefs_clear()); @@ -134,7 +135,7 @@ FIXTURE_TEARDOWN(trace_fs) return; set_cap(_metadata, CAP_SYS_ADMIN); - tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false); + tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false); tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false); tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false); tracefs_fixture_teardown(); @@ -183,11 +184,11 @@ TEST_F(trace_fs, unsandboxed) } /* - * Verifies that adding a filesystem rule emits a landlock_add_rule_fs trace - * event with the expected path and field values: ruleset ID is non-zero, - * access_rights is non-zero, and path matches. + * Verifies that adding a filesystem rule emits a landlock_add_rule_path_beneath + * event with the expected path and field values: the ruleset ID and + * access_rights are non-zero, and the path matches. */ -TEST_F(trace_fs, add_rule_fs) +TEST_F(trace_fs, add_rule_path_beneath) { struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE | @@ -215,28 +216,30 @@ TEST_F(trace_fs, add_rule_fs) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + count = tracefs_count_matches(buf, + REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)); EXPECT_EQ(1, count) { - TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf); + TH_LOG("Expected 1 add_rule_path_beneath event, got %d\n%s", + count, buf); } /* Ruleset ID should be non-zero. */ - ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), - "ruleset", field_buf, - sizeof(field_buf))); + ASSERT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK), + "ruleset", field_buf, sizeof(field_buf))); EXPECT_STRNE("0", field_buf); /* Access rights should be non-zero. */ - ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), - "access_rights", field_buf, - sizeof(field_buf))); + ASSERT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK), + "access_rights", field_buf, sizeof(field_buf))); EXPECT_STRNE("", field_buf); /* Path should be /usr. */ - ASSERT_EQ(0, - tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), - "path", field_buf, sizeof(field_buf))); + ASSERT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK), + "path", field_buf, sizeof(field_buf))); EXPECT_STREQ("/usr", field_buf); free(buf); @@ -246,7 +249,7 @@ TEST_F(trace_fs, add_rule_fs) * Verifies that a path whose escaping exceeds the trace scratch sequence does * not corrupt a sibling symbolic field. */ -TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) +TEST_F(trace_fs, add_rule_path_beneath_escaped_path_overflow) { static const char access_prefix[] = "execute|write_file|read_file|"; static const char access_suffix[] = "|ioctl_dev|resolve_unix"; @@ -277,10 +280,12 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + count = tracefs_count_matches(buf, + REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)); EXPECT_EQ(1, count) { - TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf); + TH_LOG("Expected 1 add_rule_path_beneath event, got %d\n%s", + count, buf); } /* @@ -288,9 +293,9 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) * field also catches scratch-sequence poisoning when the compiler * evaluates the overflowing path first, as GCC currently does. */ - ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), - "access_rights", field_buf, - sizeof(field_buf))); + ASSERT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK), + "access_rights", field_buf, sizeof(field_buf))); EXPECT_EQ(0, strncmp(field_buf, access_prefix, sizeof(access_prefix) - 1)); EXPECT_EQ(NULL, strstr(field_buf, "|refer|")); @@ -298,7 +303,8 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) ASSERT_LE(sizeof(access_suffix) - 1, field_len); EXPECT_STREQ(access_suffix, field_buf + field_len - (sizeof(access_suffix) - 1)); - expect_truncated_path(_metadata, buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + expect_truncated_path(_metadata, buf, + REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)); free(buf); } diff --git a/tools/testing/selftests/landlock/trace_test.c b/tools/testing/selftests/landlock/trace_test.c index afdaf8511b3a13..f9b293a9dd565b 100644 --- a/tools/testing/selftests/landlock/trace_test.c +++ b/tools/testing/selftests/landlock/trace_test.c @@ -49,8 +49,10 @@ FIXTURE_SETUP(trace) ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true)); - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true)); - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, true)); + ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, + true)); + ASSERT_EQ(0, + tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true)); @@ -72,8 +74,8 @@ FIXTURE_TEARDOWN(trace) tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false); tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false); tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false); - tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false); - tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false); + tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false); + tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, false); tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false); tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false); tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false); @@ -103,8 +105,10 @@ TEST_F(trace, no_trace_when_disabled) ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false)); - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false)); - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false)); + ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, + false)); + ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, + false)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false)); @@ -265,10 +269,11 @@ TEST_F(trace, ruleset_version) ASSERT_NE(0, !!dot); EXPECT_STREQ("0", dot + 1); - /* Verify 2 add_rule_fs events were emitted. */ - EXPECT_EQ(2, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK))) + /* Verify two add_rule_path_beneath events were emitted. */ + EXPECT_EQ(2, tracefs_count_matches( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK))) { - TH_LOG("Expected 2 add_rule_fs events\n%s", buf); + TH_LOG("Expected 2 add_rule_path_beneath events\n%s", buf); } /* @@ -373,7 +378,7 @@ TEST_F(trace, create_domain) tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); ASSERT_LE(1, check_count) { - TH_LOG("Expected check_rule_fs events\n%s", buf); + TH_LOG("Expected check_rule_inode events\n%s", buf); } EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK), @@ -508,9 +513,11 @@ TEST_F(trace, add_rule_invalid_fd) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - EXPECT_EQ(0, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK))) + EXPECT_EQ(0, tracefs_count_matches( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK))) { - TH_LOG("No add_rule_fs event expected on invalid fd\n%s", buf); + TH_LOG("No add_rule_path_beneath event expected on invalid fd\n%s", + buf); } free(buf); @@ -902,10 +909,10 @@ TEST_F(trace, non_audit_visible_denial_counting) } /* - * Verifies that landlock_add_rule_net emits a trace event with the correct port - * and allowed access mask fields. + * Verifies that landlock_add_rule_net_port emits a trace event with the correct + * port and allowed access mask fields. */ -TEST_F(trace, add_rule_net_fields) +TEST_F(trace, add_rule_net_port_fields) { struct landlock_ruleset_attr ruleset_attr = { .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, @@ -931,9 +938,10 @@ TEST_F(trace, add_rule_net_fields) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_ADD_RULE_NET(TRACE_TASK))) + EXPECT_EQ(1, tracefs_count_matches(buf, + REGEX_ADD_RULE_NET_PORT(TRACE_TASK))) { - TH_LOG("Expected 1 add_rule_net event\n%s", buf); + TH_LOG("Expected 1 add_rule_net_port event\n%s", buf); } /* @@ -941,7 +949,8 @@ TEST_F(trace, add_rule_net_fields) * (landlock_net_port_attr.port). On little-endian, htons(8080) is * 36895, so this comparison catches byte-order bugs. */ - EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK), + EXPECT_EQ(0, tracefs_extract_field(buf, + REGEX_ADD_RULE_NET_PORT(TRACE_TASK), "port", field, sizeof(field))); EXPECT_STREQ("8080", field); /* @@ -950,9 +959,9 @@ TEST_F(trace, add_rule_net_fields) * net access bits are unhandled because the ruleset only handles * BIND_TCP). */ - EXPECT_EQ(0, - tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK), - "access_rights", field, sizeof(field))); + EXPECT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_NET_PORT(TRACE_TASK), + "access_rights", field, sizeof(field))); EXPECT_STREQ("bind_tcp|connect_tcp|bind_udp|connect_send_udp", field); free(buf); From 98b04ab00f0e738d69bd718228db55483a911b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:27 +0200 Subject: [PATCH 0897/1417] landlock: Fix network denial trace context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Network denial events report source and destination ports reconstructed from audit data. Their zero values are ambiguous, and neither identifies the complete endpoint that Landlock checked. Carry the checked sockaddr and its signed length in a private trace-only context. For an enabled event, validate the length and copy only the initialized prefix into zeroed local storage. This prevents a typed BPF program from reading uninitialized bytes while exposing the socket family, socket, address, and length. Replace the source and destination trace-record fields with one signed port derived from the checked address. A value of -1 means that no port was checked, zero is a valid port, and positive values use host endianness. Bind blockers select the bind address; connect and send blockers select the destination. Cc: Günther Noack Cc: Steven Rostedt Fixes: 01ce260f5ccf ("landlock: Add landlock_deny_access_fs and landlock_deny_access_net") Link: https://patch.msgid.link/20260918185036.608651-5-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 73 +++++++++++++-------- security/landlock/log.h | 32 +++++++-- security/landlock/net.c | 37 ++++++++--- security/landlock/trace.c | 20 +++++- tools/testing/selftests/landlock/net_test.c | 49 +++++--------- tools/testing/selftests/landlock/trace.h | 3 +- 6 files changed, 135 insertions(+), 79 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index b7e6a66388227b..a982a7cfa881fb 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -10,7 +10,10 @@ #if !defined(_TRACE_LANDLOCK_H) || defined(TRACE_HEADER_MULTI_READ) #define _TRACE_LANDLOCK_H +#include +#include #include +#include #include #include #include @@ -790,6 +793,11 @@ TRACE_EVENT(landlock_deny_access_fs, __get_dynamic_array_len(pathname) - 1)) ); +static_assert(offsetof(struct sockaddr_in, sin_port) == + offsetof(struct sockaddr_in6, sin6_port)); +static_assert(sizeof_field(struct sockaddr_in, sin_port) == + sizeof_field(struct sockaddr_in6, sin6_port)); + /** * landlock_deny_access_net - Network access denied * @@ -798,30 +806,31 @@ TRACE_EVENT(landlock_deny_access_fs, * @same_exec: Whether the current task entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. * @blockers: Request type and final missing access subset (never NULL). - * @sk: Socket object (never NULL), read without a socket lock, so its - * fields are a best-effort snapshot. The denied endpoint is not - * available: the hook runs before :manpage:`bind(2)` / - * :manpage:`connect(2)` sets the socket addresses. - * @sport: Source port in host endianness, set for bind denials (zero for - * an autobind/ephemeral port); zero for connect and send denials. - * @dport: Destination port in host endianness, set for connect and send - * denials; zero for bind denials, and also zero for a UDP send to - * an AF_UNSPEC address on an IPv6 socket (indistinguishable from a - * real destination port 0). The bind-vs-connect direction is - * given by @blockers, not by which port is set. - * - * Emitted when a Landlock domain denies a network operation. + * @sk: Socket object (never NULL), read without a socket lock, so its fields + * are a best-effort snapshot. + * @socket_family: Socket-family snapshot used by the verdict. + * @address: Authoritative address checked by the verdict (never NULL). + * The producer copies @addrlen bytes from the checked address and + * zeroes the remaining storage before emission. The + * &sockaddr_in.sin_port or &sockaddr_in6.sin6_port member, when + * present, remains in network endianness. + * @addrlen: Validated signed length of @address. * - * The port fields are converted from the socket's network byte order to - * host endianness before emitting. + * Emitted when a Landlock domain denies a network operation. The blocker + * identifies whether the address is a bind or connect/send policy object. + * The flattened port field is converted from the checked address to host + * endianness, or is -1 when no port was checked. Zero is a valid checked + * port. */ TRACE_EVENT(landlock_deny_access_net, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, bool logged, const struct landlock_blockers *blockers, - const struct sock *sk, u64 sport, u64 dport), + const struct sock *sk, u16 socket_family, + const struct sockaddr_storage *address, int addrlen), - TP_ARGS(hierarchy, same_exec, logged, blockers, sk, sport, dport), + TP_ARGS(hierarchy, same_exec, logged, blockers, sk, socket_family, + address, addrlen), TP_STRUCT__entry( __field( u64, domain_id ) @@ -829,26 +838,36 @@ TRACE_EVENT(landlock_deny_access_net, __field( bool, logged ) __field( enum landlock_request_type, blockers_type ) __field( access_mask_t, blockers_access ) - __field( u64, sport ) - __field( u64, dport ) + __field( s64, port ) ), TP_fast_assign( + const struct sockaddr *const addr = + (const struct sockaddr *)address; + const bool has_port = + addrlen >= (int)offsetofend(struct sockaddr_in, sin_port) && + (addr->sa_family == AF_INET || + addr->sa_family == AF_INET6 || + (addr->sa_family == AF_UNSPEC && + socket_family == AF_INET)); + __entry->domain_id = hierarchy->id; __entry->same_exec = same_exec; __entry->logged = logged; __entry->blockers_type = blockers->type; __entry->blockers_access = blockers->access; - __entry->sport = sport; - __entry->dport = dport; + __entry->port = + has_port ? + ntohs(((const struct sockaddr_in *)addr)->sin_port) : + -1; ), - TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s sport=%llu dport=%llu", - __entry->domain_id, __entry->same_exec, __entry->logged, - __entry->blockers_type == LANDLOCK_REQUEST_NET_ACCESS ? - __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_NET_NAMES) : - "unknown", - __entry->sport, __entry->dport) + TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s port=%lld", + __entry->domain_id, __entry->same_exec, __entry->logged, + __entry->blockers_type == LANDLOCK_REQUEST_NET_ACCESS ? + __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_NET_NAMES) : + "unknown", + __entry->port) ); /** diff --git a/security/landlock/log.h b/security/landlock/log.h index 04f3e241e76576..4587c2b1566d37 100644 --- a/security/landlock/log.h +++ b/security/landlock/log.h @@ -15,6 +15,7 @@ struct landlock_cred_security; struct landlock_hierarchy; +struct sockaddr; enum landlock_request_type { LANDLOCK_REQUEST_PTRACE = 1, @@ -30,6 +31,16 @@ struct landlock_blockers { enum landlock_request_type type; }; +#ifdef CONFIG_TRACEPOINTS + +struct landlock_net_trace { + const struct sockaddr *address; + int addrlen; + u16 socket_family; +}; + +#endif /* CONFIG_TRACEPOINTS */ + /* * We should be careful to only use a variable of this type for * landlock_log_denial(). This way, the compiler can remove it entirely if @@ -57,13 +68,20 @@ struct landlock_request { deny_masks_t deny_masks; optional_access_t quiet_optional_accesses; - /* - * Other-party domain ID for a relational (scope/ptrace) denial, or 0 if - * that party is unsandboxed. An ID, not a pointer: the other task can - * replace its credential and free the domain it referenced. Trace path - * only; audit ignores it. - */ - u64 other_domain_id; + union { + /* + * Other-party domain ID for a relational (scope/ptrace) denial, + * or 0 if that party is unsandboxed. Store an ID, not a + * pointer: the other task can replace its credential and free + * the domain it referenced. Trace-only; audit ignores it. + */ + u64 other_domain_id; + +#ifdef CONFIG_TRACEPOINTS + /* Synchronous context for a network denial. */ + const struct landlock_net_trace *trace_net; +#endif /* CONFIG_TRACEPOINTS */ + }; }; #ifdef CONFIG_SECURITY_LANDLOCK_LOG diff --git a/security/landlock/net.c b/security/landlock/net.c index 5552c60388f888..6fe0dbde3b787d 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -93,7 +93,7 @@ static int current_check_access_socket(struct socket *const sock, return 0; /* Checks for minimal header length to safely read sa_family. */ - if (addrlen < offsetofend(typeof(*address), sa_family)) + if (addrlen < (int)offsetofend(typeof(*address), sa_family)) return -EINVAL; /* @@ -145,6 +145,17 @@ static int current_check_access_socket(struct socket *const sock, .audit.u.net = &audit_net, .access = access_request, .layer_masks = &layer_masks, +#ifdef CONFIG_TRACEPOINTS + .trace_net = + &(struct landlock_net_trace){ + .address = + address, + .addrlen = + addrlen, + .socket_family = + sock_family, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return -EACCES; } @@ -276,14 +287,22 @@ static int current_check_access_socket(struct socket *const sock, audit_net.family = address->sa_family; audit_net.sk = sock->sk; - landlock_log_denial(subject, - &(struct landlock_request){ - .type = LANDLOCK_REQUEST_NET_ACCESS, - .audit.type = LSM_AUDIT_DATA_NET, - .audit.u.net = &audit_net, - .access = access_request, - .layer_masks = &layer_masks, - }); + landlock_log_denial( + subject, &(struct landlock_request){ + .type = LANDLOCK_REQUEST_NET_ACCESS, + .audit.type = LSM_AUDIT_DATA_NET, + .audit.u.net = &audit_net, + .access = access_request, + .layer_masks = &layer_masks, +#ifdef CONFIG_TRACEPOINTS + .trace_net = + &(struct landlock_net_trace){ + .address = address, + .addrlen = addrlen, + .socket_family = sock_family, + }, +#endif /* CONFIG_TRACEPOINTS */ + }); return -EACCES; } diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 58276cc32d3f4c..9be86638f90554 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "access.h" @@ -157,16 +158,31 @@ void landlock_trace_denial( break; case LANDLOCK_REQUEST_NET_ACCESS: if (trace_landlock_deny_access_net_enabled()) { + const struct landlock_net_trace *const trace_net = + request->trace_net; const struct landlock_blockers blockers = { .access = missing, .type = request->type, }; + struct sockaddr_storage address = {}; + if (WARN_ON_ONCE(!trace_net || !trace_net->address)) + return; + + if (WARN_ON_ONCE( + trace_net->addrlen < + (int)offsetofend(struct sockaddr, + sa_family) || + trace_net->addrlen > (int)sizeof(address))) + return; + + memcpy(&address, trace_net->address, + trace_net->addrlen); trace_landlock_deny_access_net( youngest_denied, same_exec, logged, &blockers, request->audit.u.net->sk, - ntohs(request->audit.u.net->sport), - ntohs(request->audit.u.net->dport)); + trace_net->socket_family, &address, + trace_net->addrlen); } break; case LANDLOCK_REQUEST_PTRACE: diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 4fb705e1596d00..28942438e154ca 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -3482,8 +3482,8 @@ TEST_F(trace_net, deny_access_net_bind) /* * Anchors the denial fields shared by every deny_access_net event so a field - * test proves more than sport/dport: the denying domain, the same-exec bit, the - * audit-logging verdict, and the blocked access all stay populated. + * test proves more than the checked endpoint: the denying domain, the same-exec + * bit, the audit-logging verdict, and the blocked access all stay populated. */ static void expect_net_deny_common_fields(struct __test_metadata *const _metadata, @@ -3569,38 +3569,38 @@ FIXTURE_VARIANT(trace_net_connect) { bool deny_connect; }; +/* Denied connect() to the next IPv4 port. */ /* clang-format off */ - -/* Denied connect(): sport=0, dport=. */ FIXTURE_VARIANT_ADD(trace_net_connect, connect_denied) { + /* clang-format on */ .handled = LANDLOCK_ACCESS_NET_CONNECT_TCP, .bind_base_first = false, .deny_connect = true, }; -/* Denied bind(): sport=, dport=0. */ +/* Denied bind() to the next IPv4 port. */ +/* clang-format off */ FIXTURE_VARIANT_ADD(trace_net_connect, bind_fields) { + /* clang-format on */ .handled = LANDLOCK_ACCESS_NET_BIND_TCP, .bind_base_first = false, .deny_connect = false, }; -/* Denied connect() after an allowed bind(): the connect fields (sport=0). */ +/* Denied connect() after an allowed bind() uses the checked destination. */ +/* clang-format off */ FIXTURE_VARIANT_ADD(trace_net_connect, connect_after_bind) { - .handled = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP, + /* clang-format on */ + .handled = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, .bind_base_first = true, .deny_connect = true, }; -/* clang-format on */ - /* - * A denied TCP bind(2) or connect(2) emits one deny_access_net event. The port - * is reported in the field matching the denied operation, in host endianness - * (the UAPI landlock_net_port_attr.port convention): a connect denial reports - * sport=0 dport=, a bind denial reports sport= dport=0, so a - * byte-order or field-swap bug is caught. A prior allowed bind - * (connect_after_bind) does not change the connect denial's fields. + * A denied TCP bind(2) or connect(2) emits one deny_access_net event with the + * checked IPv4 port in host endianness (the UAPI landlock_net_port_attr.port + * convention). A prior allowed bind does not change a connect denial's port. */ TEST_F(trace_net_connect, deny_access_net) { @@ -3693,21 +3693,13 @@ TEST_F(trace_net_connect, deny_access_net) expect_net_deny_common_fields(_metadata, buf); - /* - * The denied operation's port field carries the port; the other is 0. - */ snprintf(expected, sizeof(expected), "%llu", (unsigned long long)(sock_port_start + 1)); ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK), - "sport", field, sizeof(field))); - EXPECT_STREQ(variant->deny_connect ? "0" : expected, field); - - ASSERT_EQ(0, - tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK), - "dport", field, sizeof(field))); - EXPECT_STREQ(variant->deny_connect ? expected : "0", field); + "port", field, sizeof(field))); + EXPECT_STREQ(expected, field); free(buf); } @@ -3867,11 +3859,4 @@ TEST_F(trace_net_check_rule, check_rule_net_fields) free(buf); } -/* - * IPv6 network trace tests are intentionally elided. IPv6 hook dispatch uses - * the same current_check_access_socket() code path as IPv4, validated by the - * audit tests in this file. The trace events use the same blockers/sport/dport - * fields regardless of address family. - */ - TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/landlock/trace.h b/tools/testing/selftests/landlock/trace.h index e6873853376c7f..2ec86336217329 100644 --- a/tools/testing/selftests/landlock/trace.h +++ b/tools/testing/selftests/landlock/trace.h @@ -145,8 +145,7 @@ "same_exec=[01] " \ "logged=[01] " \ "blockers=[a-z_|]* " \ - "sport=[0-9]\\+ " \ - "dport=[0-9]\\+$" + "port=-\\?[0-9]\\+$" #define REGEX_DENY_PTRACE(task) \ TRACE_PREFIX(task) \ From 7ad69ac63315506e2091bf46d5c96c49f6ce439e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:28 +0200 Subject: [PATCH 0898/1417] landlock: Report the actual ptrace tracer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ptrace denial callback identifies only the tracee. Current is the tracer during hook_ptrace_access_check(), but it is the tracee during PTRACE_TRACEME, where the parent is the actual tracer. A consumer therefore cannot infer both parties from the existing arguments. Append the actual tracer task to the typed-BPF callback: current for hook_ptrace_access_check() and parent for hook_ptrace_traceme(). Carry it with the tracee domain ID in a private ptrace context. Both hooks keep the selected tasks alive through synchronous dispatch, so no extra task reference is needed. Keep the tracefs record unchanged. The new context is available only to typed BPF, while same_exec continues to describe the tracer that owns the denying policy. Cc: Günther Noack Cc: Steven Rostedt Fixes: bb91730f16c0 ("landlock: Add tracepoints for ptrace and scope denials") Link: https://patch.msgid.link/20260918185036.608651-6-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 11 ++++++++--- security/landlock/log.h | 16 ++++++++++++---- security/landlock/task.c | 28 +++++++++++++++++----------- security/landlock/trace.c | 20 ++++++++++++++------ 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index a982a7cfa881fb..8c6ebf958d6679 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -295,6 +295,8 @@ static inline const char *__trace_landlock_print_layers( * the two parties without kernel-internal state. The ID is a scalar * snapshot, not a live domain pointer that could dangle: an optional * relational referent is a scalar (0 sentinel), not a nullable pointer. + * For ptrace, same_exec instead describes the tracer, even for + * PTRACE_TRACEME, and may differ from the current task. * * Blocker fields * ~~~~~~~~~~~~~~ @@ -875,12 +877,14 @@ TRACE_EVENT(landlock_deny_access_net, * * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the * domain field. - * @same_exec: Whether the current task entered the denying domain itself. + * @same_exec: Whether the tracer entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. * @tracee_domain_id: The tracee's Landlock domain ID, or 0 if the tracee * is unsandboxed. * @tracee: The target task ptrace acted on (never NULL). tracee_pid is * the init-namespace TGID (like audit's opid). + * @tracer: The tracer or proposed tracer (never NULL); for PTRACE_TRACEME + * this is the parent, not the syscall caller. * * Emitted when a Landlock domain denies a ptrace operation. */ @@ -888,9 +892,10 @@ TRACE_EVENT(landlock_deny_ptrace, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, bool logged, u64 tracee_domain_id, - const struct task_struct *tracee), + const struct task_struct *tracee, + const struct task_struct *tracer), - TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee), + TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee, tracer), TP_STRUCT__entry( __field( u64, domain_id ) diff --git a/security/landlock/log.h b/security/landlock/log.h index 4587c2b1566d37..821df6f711c689 100644 --- a/security/landlock/log.h +++ b/security/landlock/log.h @@ -16,6 +16,7 @@ struct landlock_cred_security; struct landlock_hierarchy; struct sockaddr; +struct task_struct; enum landlock_request_type { LANDLOCK_REQUEST_PTRACE = 1, @@ -39,6 +40,11 @@ struct landlock_net_trace { u16 socket_family; }; +struct landlock_ptrace_trace { + u64 tracee_domain_id; + const struct task_struct *tracer; +}; + #endif /* CONFIG_TRACEPOINTS */ /* @@ -70,16 +76,18 @@ struct landlock_request { union { /* - * Other-party domain ID for a relational (scope/ptrace) denial, - * or 0 if that party is unsandboxed. Store an ID, not a - * pointer: the other task can replace its credential and free - * the domain it referenced. Trace-only; audit ignores it. + * Other-party domain ID for a scope denial, or 0 if that party + * is unsandboxed. Store an ID, not a pointer: the other task + * can replace its credential and free the domain it referenced. + * Audit ignores this trace-only field. */ u64 other_domain_id; #ifdef CONFIG_TRACEPOINTS /* Synchronous context for a network denial. */ const struct landlock_net_trace *trace_net; + /* Consumed only by the synchronous trace dispatcher. */ + const struct landlock_ptrace_trace *trace_ptrace; #endif /* CONFIG_TRACEPOINTS */ }; }; diff --git a/security/landlock/task.c b/security/landlock/task.c index 4491ce31ae04ac..445f6b921a872f 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -88,7 +88,9 @@ static int hook_ptrace_access_check(struct task_struct *const child, const unsigned int mode) { const struct landlock_cred_security *parent_subject; +#ifdef CONFIG_TRACEPOINTS u64 tracee_domain_id = 0; +#endif /* CONFIG_TRACEPOINTS */ int err; /* Quick return for non-landlocked tasks. */ @@ -100,10 +102,10 @@ static int hook_ptrace_access_check(struct task_struct *const child, const struct landlock_domain *const child_dom = landlock_get_task_domain(child); err = domain_ptrace(parent_subject->domain, child_dom); -#ifdef CONFIG_SECURITY_LANDLOCK_LOG +#ifdef CONFIG_TRACEPOINTS if (child_dom) tracee_domain_id = child_dom->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#endif /* CONFIG_TRACEPOINTS */ } if (!err) @@ -121,7 +123,12 @@ static int hook_ptrace_access_check(struct task_struct *const child, .u.tsk = child, }, .layer_plus_one = parent_subject->domain->num_layers, - .other_domain_id = tracee_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_ptrace = &(struct landlock_ptrace_trace) { + .tracee_domain_id = tracee_domain_id, + .tracer = current, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return err; @@ -142,7 +149,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent) { const struct landlock_cred_security *parent_subject; const struct landlock_domain *child_dom; - u64 tracee_domain_id = 0; int err; child_dom = landlock_get_current_domain(); @@ -154,12 +160,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent) if (!err) return 0; -#ifdef CONFIG_SECURITY_LANDLOCK_LOG - /* The tracee is the current task; its domain is stable here. */ - if (child_dom) - tracee_domain_id = child_dom->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ - /* * For the ptrace_traceme case, we log the domain which is the cause of * the denial, which means the parent domain instead of the current @@ -174,7 +174,13 @@ static int hook_ptrace_traceme(struct task_struct *const parent) .u.tsk = current, }, .layer_plus_one = parent_subject->domain->num_layers, - .other_domain_id = tracee_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_ptrace = &(struct landlock_ptrace_trace) { + /* The current task's domain is stable here. */ + .tracee_domain_id = child_dom ? child_dom->hierarchy->id : 0, + .tracer = parent, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return err; } diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 9be86638f90554..43091c052f7776 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -63,7 +63,7 @@ void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy * @request: Detail of the user space request. * @youngest_denied: The youngest hierarchy node that denied the access. * @missing: The final missing access subset, when applicable. - * @same_exec: Whether the current task is the same executable that called + * @same_exec: Whether the policy subject is the same executable that called * landlock_restrict_self() for the denying domain, as computed * by landlock_log_denial(). * @logged: Whether the domain's policy selects this denial for logging, as @@ -186,11 +186,19 @@ void landlock_trace_denial( } break; case LANDLOCK_REQUEST_PTRACE: - if (trace_landlock_deny_ptrace_enabled()) - trace_landlock_deny_ptrace(youngest_denied, same_exec, - logged, - request->other_domain_id, - request->audit.u.tsk); + if (trace_landlock_deny_ptrace_enabled()) { + const struct landlock_ptrace_trace *const trace_ptrace = + request->trace_ptrace; + + if (WARN_ON_ONCE(!trace_ptrace || + !trace_ptrace->tracer)) + return; + + trace_landlock_deny_ptrace( + youngest_denied, same_exec, logged, + trace_ptrace->tracee_domain_id, + request->audit.u.tsk, trace_ptrace->tracer); + } break; case LANDLOCK_REQUEST_SCOPE_SIGNAL: if (trace_landlock_deny_scope_signal_enabled()) From 0889db596a25ecde210e66fa0db70bc6a6d91f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:29 +0200 Subject: [PATCH 0899/1417] landlock: Report the effective signal number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The signal-scope denial callback identifies its target but not the effective signal. This loses permission-probe signal zero and makes the file-owner hook's zero sentinel ambiguous. Append an int signal argument to the typed-BPF callback. Preserve sig, including zero, in hook_task_kill(). In hook_file_send_sigiotask(), translate signum zero to SIGIO at the producer, where its meaning is known. Carry the effective signal and target domain ID in a private, stack-backed context consumed synchronously. This requires no allocation or task reference in the interrupt-capable file-owner path. Gate this context and the remaining scope-only domain IDs with CONFIG_TRACEPOINTS. Keep the tracefs record and audit output unchanged. Cc: Günther Noack Cc: Steven Rostedt Fixes: bb91730f16c0 ("landlock: Add tracepoints for ptrace and scope denials") Link: https://patch.msgid.link/20260918185036.608651-7-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 8 +++-- security/landlock/log.h | 21 ++++++++---- security/landlock/task.c | 59 +++++++++++++++++++++++---------- security/landlock/trace.c | 12 +++++-- 4 files changed, 71 insertions(+), 29 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index 8c6ebf958d6679..5da0f12ed2c3c6 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -928,12 +928,14 @@ TRACE_EVENT(landlock_deny_ptrace, * * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the * domain field. - * @same_exec: Whether the current task entered the denying domain itself. + * @same_exec: Whether the policy subject entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. * @target_domain_id: The target's Landlock domain ID, or 0 if the target * is unsandboxed. * @target: The task the signal was aimed at (never NULL). target_pid is * the init-namespace TGID (like audit's opid). + * @signal: The signal selected by the denied check. Zero is a permission + * probe, not an absent value. * * Emitted when a Landlock domain denies signal delivery to a scoped-out * target. @@ -942,9 +944,9 @@ TRACE_EVENT(landlock_deny_scope_signal, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, bool logged, u64 target_domain_id, - const struct task_struct *target), + const struct task_struct *target, int signal), - TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target), + TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target, signal), TP_STRUCT__entry( __field( u64, domain_id ) diff --git a/security/landlock/log.h b/security/landlock/log.h index 821df6f711c689..faa30e26e42a38 100644 --- a/security/landlock/log.h +++ b/security/landlock/log.h @@ -45,6 +45,11 @@ struct landlock_ptrace_trace { const struct task_struct *tracer; }; +struct landlock_signal_trace { + u64 target_domain_id; + int signal; +}; + #endif /* CONFIG_TRACEPOINTS */ /* @@ -74,22 +79,24 @@ struct landlock_request { deny_masks_t deny_masks; optional_access_t quiet_optional_accesses; +#ifdef CONFIG_TRACEPOINTS union { /* - * Other-party domain ID for a scope denial, or 0 if that party - * is unsandboxed. Store an ID, not a pointer: the other task - * can replace its credential and free the domain it referenced. - * Audit ignores this trace-only field. + * Other-party domain ID for an abstract UNIX socket scope + * denial, or 0 if that party is unsandboxed. Store an ID, not + * a pointer: the other task can replace its credential and free + * the domain it referenced. */ u64 other_domain_id; -#ifdef CONFIG_TRACEPOINTS /* Synchronous context for a network denial. */ const struct landlock_net_trace *trace_net; - /* Consumed only by the synchronous trace dispatcher. */ + /* Synchronous context for a ptrace denial. */ const struct landlock_ptrace_trace *trace_ptrace; -#endif /* CONFIG_TRACEPOINTS */ + /* Synchronous context for a signal denial. */ + const struct landlock_signal_trace *trace_signal; }; +#endif /* CONFIG_TRACEPOINTS */ }; #ifdef CONFIG_SECURITY_LANDLOCK_LOG diff --git a/security/landlock/task.c b/security/landlock/task.c index 445f6b921a872f..d9eae86fc5529c 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -256,8 +256,7 @@ static bool domain_is_scoped(const struct landlock_domain *const client, } static bool sock_is_scoped(struct sock *const other, - const struct landlock_domain *const domain, - u64 *const peer_domain_id) + const struct landlock_domain *const domain) { const struct landlock_domain *dom_other; @@ -275,13 +274,23 @@ static bool sock_is_scoped(struct sock *const other, return false; dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain; -#ifdef CONFIG_SECURITY_LANDLOCK_LOG - *peer_domain_id = dom_other ? dom_other->hierarchy->id : 0; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ return domain_is_scoped(domain, dom_other, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); } +#ifdef CONFIG_TRACEPOINTS + +static u64 get_socket_domain_id(const struct sock *const other) +{ + const struct landlock_domain *domain; + + lockdep_assert_held(&unix_sk(other)->lock); + domain = landlock_cred(other->sk_socket->file->f_cred)->domain; + return domain ? domain->hierarchy->id : 0; +} + +#endif /* CONFIG_TRACEPOINTS */ + static bool is_abstract_socket(struct sock *const sock) { struct unix_address *addr = unix_sk(sock)->addr; @@ -305,7 +314,6 @@ static int hook_unix_stream_connect(struct sock *const sock, struct sock *const newsk) { size_t handle_layer; - u64 peer_domain_id = 0; const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), unix_scope, &handle_layer); @@ -317,7 +325,7 @@ static int hook_unix_stream_connect(struct sock *const sock, if (!is_abstract_socket(other)) return 0; - if (!sock_is_scoped(other, subject->domain, &peer_domain_id)) + if (!sock_is_scoped(other, subject->domain)) return 0; landlock_log_denial(subject, &(struct landlock_request) { @@ -329,7 +337,9 @@ static int hook_unix_stream_connect(struct sock *const sock, }, }, .layer_plus_one = handle_layer + 1, - .other_domain_id = peer_domain_id, +#ifdef CONFIG_TRACEPOINTS + .other_domain_id = get_socket_domain_id(other), +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } @@ -338,7 +348,6 @@ static int hook_unix_may_send(struct socket *const sock, struct socket *const other) { size_t handle_layer; - u64 peer_domain_id = 0; const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), unix_scope, &handle_layer); @@ -356,7 +365,7 @@ static int hook_unix_may_send(struct socket *const sock, if (!is_abstract_socket(other->sk)) return 0; - if (!sock_is_scoped(other->sk, subject->domain, &peer_domain_id)) + if (!sock_is_scoped(other->sk, subject->domain)) return 0; landlock_log_denial(subject, &(struct landlock_request) { @@ -368,7 +377,9 @@ static int hook_unix_may_send(struct socket *const sock, }, }, .layer_plus_one = handle_layer + 1, - .other_domain_id = peer_domain_id, +#ifdef CONFIG_TRACEPOINTS + .other_domain_id = get_socket_domain_id(other->sk), +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } @@ -383,7 +394,9 @@ static int hook_task_kill(struct task_struct *const p, { bool is_scoped; size_t handle_layer; +#ifdef CONFIG_TRACEPOINTS u64 target_domain_id = 0; +#endif /* CONFIG_TRACEPOINTS */ const struct landlock_cred_security *subject; if (!cred) { @@ -415,10 +428,10 @@ static int hook_task_kill(struct task_struct *const p, is_scoped = domain_is_scoped(subject->domain, other, signal_scope.scope); -#ifdef CONFIG_SECURITY_LANDLOCK_LOG +#ifdef CONFIG_TRACEPOINTS if (other) target_domain_id = other->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#endif /* CONFIG_TRACEPOINTS */ } if (!is_scoped) @@ -431,7 +444,12 @@ static int hook_task_kill(struct task_struct *const p, .u.tsk = p, }, .layer_plus_one = handle_layer + 1, - .other_domain_id = target_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_signal = &(struct landlock_signal_trace) { + .target_domain_id = target_domain_id, + .signal = sig, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } @@ -441,7 +459,9 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, { const struct landlock_cred_security *subject; bool is_scoped = false; +#ifdef CONFIG_TRACEPOINTS u64 target_domain_id = 0; +#endif /* CONFIG_TRACEPOINTS */ /* Lock already held by send_sigio() and send_sigurg(). */ lockdep_assert_held(&fown->lock); @@ -474,10 +494,10 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, is_scoped = domain_is_scoped(subject->domain, other, signal_scope.scope); -#ifdef CONFIG_SECURITY_LANDLOCK_LOG +#ifdef CONFIG_TRACEPOINTS if (other) target_domain_id = other->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#endif /* CONFIG_TRACEPOINTS */ } if (!is_scoped) @@ -492,7 +512,12 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, #ifdef CONFIG_SECURITY_LANDLOCK_LOG .layer_plus_one = landlock_file(fown->file)->fown_layer + 1, #endif /* CONFIG_SECURITY_LANDLOCK_LOG */ - .other_domain_id = target_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_signal = &(struct landlock_signal_trace) { + .target_domain_id = target_domain_id, + .signal = signum ? signum : SIGIO, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 43091c052f7776..225dbf37bab095 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -201,10 +201,18 @@ void landlock_trace_denial( } break; case LANDLOCK_REQUEST_SCOPE_SIGNAL: - if (trace_landlock_deny_scope_signal_enabled()) + if (trace_landlock_deny_scope_signal_enabled()) { + const struct landlock_signal_trace *const trace_signal = + request->trace_signal; + + if (WARN_ON_ONCE(!trace_signal)) + return; + trace_landlock_deny_scope_signal( youngest_denied, same_exec, logged, - request->other_domain_id, request->audit.u.tsk); + trace_signal->target_domain_id, + request->audit.u.tsk, trace_signal->signal); + } break; case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: if (trace_landlock_deny_scope_abstract_unix_socket_enabled()) From c6dea91d846f192eb6ddbd44f5fd873035b8b285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:30 +0200 Subject: [PATCH 0900/1417] selftests/landlock: Test filesystem denial blockers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filesystem denial traces identify the policy change needed to allow a request, so require exact blocker values rather than merely nonempty output. Pin a READ_DIR denial to exactly one event with blockers=read_dir. Pin a REFER-only mount denial to EPERM and exactly one event with blockers=change_topology. The mount child retains CAP_SYS_ADMIN so Landlock is the only expected source of EPERM. This prevents a later capability failure from masking a Landlock regression; the trace-collecting parent remains unsandboxed. Cc: Günther Noack Cc: Steven Rostedt Link: https://patch.msgid.link/20260918185036.608651-8-mic@digikod.net Signed-off-by: Mickaël Salaün --- .../selftests/landlock/trace_fs_test.c | 76 ++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/landlock/trace_fs_test.c b/tools/testing/selftests/landlock/trace_fs_test.c index 6666d4746cb11a..64014ade3a0e96 100644 --- a/tools/testing/selftests/landlock/trace_fs_test.c +++ b/tools/testing/selftests/landlock/trace_fs_test.c @@ -548,7 +548,8 @@ TEST_F(trace_fs, check_rule_nested) */ TEST_F(trace_fs, deny_access_fs_denied) { - char *buf; + const char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK); + char *buf, blockers[64]; int count; ASSERT_EQ(0, tracefs_clear_buf()); @@ -564,8 +565,77 @@ TEST_F(trace_fs, deny_access_fs_denied) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - count = tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)); - EXPECT_LE(1, count); + count = tracefs_count_matches(buf, event_regex); + EXPECT_EQ(1, count) + { + TH_LOG("Expected 1 access denial, got %d\n%s", count, buf); + } + ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "blockers", + blockers, sizeof(blockers))); + EXPECT_STREQ("read_dir", blockers); + + free(buf); +} + +/* + * Verifies that a denied mount reports the singleton topology blocker rather + * than an empty access mask. + */ +TEST_F(trace_fs, deny_change_topology) +{ + const char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK); + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = LANDLOCK_ACCESS_FS_REFER, + }; + char *buf, blockers[64]; + int count, ruleset_fd, status; + pid_t pid; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + ASSERT_EQ(0, tracefs_clear_buf()); + + /* Ensure that Landlock is the only expected mount denial. */ + set_cap(_metadata, CAP_SYS_ADMIN); + pid = fork(); + ASSERT_LE(0, pid); + if (pid == 0) { + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + close(ruleset_fd); + _exit(1); + } + if (landlock_restrict_self(ruleset_fd, 0)) { + close(ruleset_fd); + _exit(2); + } + close(ruleset_fd); + + if (mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL) != -1) + _exit(3); + + if (errno != EPERM) + _exit(4); + + _exit(0); + } + close(ruleset_fd); + clear_cap(_metadata, CAP_SYS_ADMIN); + + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + buf = tracefs_read_buf(); + ASSERT_NE(NULL, buf); + count = tracefs_count_matches(buf, event_regex); + EXPECT_EQ(1, count) + { + TH_LOG("Expected 1 topology denial, got %d\n%s", count, buf); + } + ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "blockers", + blockers, sizeof(blockers))); + EXPECT_STREQ("change_topology", blockers); free(buf); } From c8dcb17205a689e96e2b8e46f22575a39b5b6320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:31 +0200 Subject: [PATCH 0901/1417] selftests/landlock: Test network denial context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Network denial events now report the port from their one authoritative validated address through one signed field. Verify this value directly without inferring a source or destination role. Exercise a nonzero IPv4 TCP bind, an explicit-zero IPv4 UDP bind, a synthetic-zero IPv6 UDP autobind, and a family-only AF_UNSPEC UDP send. Require exactly one event with the exact policy blocker for each shape. The value -1 distinguishes a family-only address with no validated port from the two valid port-zero cases. Test coverage for security/landlock is 91.6% of 2625 lines according to LLVM 22. Cc: Günther Noack Cc: Steven Rostedt Link: https://patch.msgid.link/20260918185036.608651-9-mic@digikod.net [mic: Add test coverage] Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/net_test.c | 259 +++++++++++--------- 1 file changed, 147 insertions(+), 112 deletions(-) diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 28942438e154ca..16afbfdf06bb22 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -3481,13 +3481,14 @@ TEST_F(trace_net, deny_access_net_bind) } /* - * Anchors the denial fields shared by every deny_access_net event so a field - * test proves more than the checked endpoint: the denying domain, the same-exec - * bit, the audit-logging verdict, and the blocked access all stay populated. + * Anchors the denial fields shared by every deny_access_net event so a port + * test also proves the denying domain, execution status, logging verdict, and + * exact blocked access. */ static void expect_net_deny_common_fields(struct __test_metadata *const _metadata, - const char *const buf) + const char *const buf, + const char *const expected_blockers) { char field[64]; @@ -3511,18 +3512,21 @@ expect_net_deny_common_fields(struct __test_metadata *const _metadata, ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK), "blockers", field, sizeof(field))); - EXPECT_STRNE("", field); + EXPECT_STREQ(expected_blockers, field); } -/* Connect and field-check tests use a separate fixture without variants. */ +enum trace_net_operation { + TRACE_NET_BIND, + TRACE_NET_SEND, +}; /* clang-format off */ -FIXTURE(trace_net_connect) { +FIXTURE(trace_net_address) { /* clang-format on */ int tracefs_ok; }; -FIXTURE_SETUP(trace_net_connect) +FIXTURE_SETUP(trace_net_address) { int ret; @@ -3547,7 +3551,7 @@ FIXTURE_SETUP(trace_net_connect) clear_cap(_metadata, CAP_SYS_ADMIN); } -FIXTURE_TEARDOWN(trace_net_connect) +FIXTURE_TEARDOWN(trace_net_address) { if (!self->tracefs_ok) return; @@ -3559,146 +3563,177 @@ FIXTURE_TEARDOWN(trace_net_connect) } /* clang-format off */ -FIXTURE_VARIANT(trace_net_connect) { +FIXTURE_VARIANT(trace_net_address) { /* clang-format on */ - /* handled_access_net, also the access allowed on the base port. */ - __u64 handled; - /* Bind the allowed base port before the denied operation. */ - bool bind_base_first; - /* Denied operation on the next port: connect (true) or bind (false). */ - bool deny_connect; + int socket_family; + int socket_type; + enum trace_net_operation operation; + int address_family; + socklen_t addrlen; + __u64 handled_access; + const char *expected_blockers; + bool address_port_zero; + bool expected_address_port; + int expected_port; }; -/* Denied connect() to the next IPv4 port. */ /* clang-format off */ -FIXTURE_VARIANT_ADD(trace_net_connect, connect_denied) { +FIXTURE_VARIANT_ADD(trace_net_address, ipv4_tcp_bind) { /* clang-format on */ - .handled = LANDLOCK_ACCESS_NET_CONNECT_TCP, - .bind_base_first = false, - .deny_connect = true, + .socket_family = AF_INET, + .socket_type = SOCK_STREAM, + .operation = TRACE_NET_BIND, + .address_family = AF_INET, + .addrlen = sizeof(struct sockaddr_in), + .handled_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .expected_blockers = "bind_tcp", + .expected_address_port = true, }; -/* Denied bind() to the next IPv4 port. */ +/* Explicit bind(0) has a checked zero port. */ /* clang-format off */ -FIXTURE_VARIANT_ADD(trace_net_connect, bind_fields) { +FIXTURE_VARIANT_ADD(trace_net_address, ipv4_udp_bind_zero) { /* clang-format on */ - .handled = LANDLOCK_ACCESS_NET_BIND_TCP, - .bind_base_first = false, - .deny_connect = false, + .socket_family = AF_INET, + .socket_type = SOCK_DGRAM, + .operation = TRACE_NET_BIND, + .address_family = AF_INET, + .addrlen = sizeof(struct sockaddr_in), + .handled_access = LANDLOCK_ACCESS_NET_BIND_UDP, + .expected_blockers = "bind_udp", + .address_port_zero = true, + .expected_port = 0, }; -/* Denied connect() after an allowed bind() uses the checked destination. */ +/* A UDP send can deny its synthetic unspecified bind endpoint. */ /* clang-format off */ -FIXTURE_VARIANT_ADD(trace_net_connect, connect_after_bind) { +FIXTURE_VARIANT_ADD(trace_net_address, ipv6_udp_autobind) { /* clang-format on */ - .handled = LANDLOCK_ACCESS_NET_BIND_TCP | - LANDLOCK_ACCESS_NET_CONNECT_TCP, - .bind_base_first = true, - .deny_connect = true, + .socket_family = AF_INET6, + .socket_type = SOCK_DGRAM, + .operation = TRACE_NET_SEND, + .address_family = AF_INET6, + .addrlen = sizeof(struct sockaddr_in6), + .handled_access = LANDLOCK_ACCESS_NET_BIND_UDP, + .expected_blockers = "bind_udp", + .expected_port = 0, }; -/* - * A denied TCP bind(2) or connect(2) emits one deny_access_net event with the - * checked IPv4 port in host endianness (the UAPI landlock_net_port_attr.port - * convention). A prior allowed bind does not change a connect denial's port. - */ -TEST_F(trace_net_connect, deny_access_net) +/* A family-only address has no checked port. */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(trace_net_address, ipv6_unspec_udp_send_min) { + /* clang-format on */ + .socket_family = AF_INET6, + .socket_type = SOCK_DGRAM, + .operation = TRACE_NET_SEND, + .address_family = AF_UNSPEC, + .addrlen = sizeof(sa_family_t), + .handled_access = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP, + .expected_blockers = "connect_send_udp", + .expected_port = -1, +}; + +static void set_trace_net_address(struct sockaddr_storage *const storage, + const int socket_family, + const int address_family, + const unsigned short port) { - pid_t child; - int status; - char *buf; + memset(storage, 0, sizeof(*storage)); + + if (socket_family == AF_INET) { + struct sockaddr_in *const addr4 = (struct sockaddr_in *)storage; + + addr4->sin_family = address_family; + addr4->sin_port = htons(port); + addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } else { + struct sockaddr_in6 *const addr6 = + (struct sockaddr_in6 *)storage; + + addr6->sin6_family = address_family; + addr6->sin6_port = htons(port); + addr6->sin6_addr = in6addr_loopback; + } +} + +/* Verifies the actionable signed port for representative checked shapes. */ +TEST_F(trace_net_address, deny_access_net) +{ + const char *const event_regex = REGEX_DENY_ACCESS_NET(TRACE_TASK); + const unsigned short address_port = + variant->address_port_zero ? 0 : sock_port_start + 1; + const int expected_port = variant->expected_address_port ? + address_port : + variant->expected_port; + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = variant->handled_access, + }; + struct sockaddr_storage address; char field[64], expected[16]; + char *buf; + int count, ret, ruleset_fd, socket_fd, status; + pid_t child; if (!self->tracefs_ok) SKIP(return, "tracefs not available"); + set_trace_net_address(&address, variant->socket_family, + variant->address_family, address_port); + socket_fd = socket(variant->socket_family, + variant->socket_type | SOCK_CLOEXEC, 0); + ASSERT_LE(0, socket_fd); + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + ASSERT_EQ(0, tracefs_clear_buf()); + child = fork(); ASSERT_LE(0, child); - if (child == 0) { - struct landlock_ruleset_attr ruleset_attr = { - .handled_access_net = variant->handled, - }; - struct landlock_net_port_attr port_attr = { - .allowed_access = variant->handled, - .port = sock_port_start, - }; - struct sockaddr_in addr = { - .sin_family = AF_INET, - .sin_addr.s_addr = htonl(INADDR_LOOPBACK), - }; - int ruleset_fd, sock_fd, optval = 1, ret; - - ruleset_fd = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); - if (ruleset_fd < 0) + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) _exit(1); - if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, - &port_attr, 0)) { - close(ruleset_fd); - _exit(1); - } - prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); - if (landlock_restrict_self(ruleset_fd, 0)) { - close(ruleset_fd); - _exit(1); - } + if (landlock_restrict_self(ruleset_fd, 0)) + _exit(2); close(ruleset_fd); - sock_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); - if (sock_fd < 0) - _exit(1); - - /* Bind the allowed base port first (succeeds, no event). */ - if (variant->bind_base_first) { - setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, - sizeof(optval)); - addr.sin_port = htons(sock_port_start); - if (bind(sock_fd, (struct sockaddr *)&addr, - sizeof(addr))) { - close(sock_fd); - _exit(1); - } - } - - /* Denied operation on the next port. */ - addr.sin_port = htons(sock_port_start + 1); - if (variant->deny_connect) - ret = connect(sock_fd, (struct sockaddr *)&addr, - sizeof(addr)); - else - ret = bind(sock_fd, (struct sockaddr *)&addr, - sizeof(addr)); - if (ret == 0) { - close(sock_fd); - _exit(2); - } - if (errno != EACCES) { - close(sock_fd); + switch (variant->operation) { + case TRACE_NET_BIND: + ret = bind(socket_fd, (const struct sockaddr *)&address, + variant->addrlen); + break; + case TRACE_NET_SEND: + ret = sendto(socket_fd, "A", 1, MSG_NOSIGNAL, + (const struct sockaddr *)&address, + variant->addrlen); + break; + default: _exit(3); } - close(sock_fd); + if (ret >= 0 || errno != EACCES) + _exit(4); + close(socket_fd); + _exit(0); } + close(ruleset_fd); + close(socket_fd); ASSERT_EQ(child, waitpid(child, &status, 0)); ASSERT_TRUE(WIFEXITED(status)); - EXPECT_EQ(0, WEXITSTATUS(status)); + ASSERT_EQ(0, WEXITSTATUS(status)); buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - - EXPECT_EQ(1, tracefs_count_matches(buf, - REGEX_DENY_ACCESS_NET(TRACE_TASK))); - - expect_net_deny_common_fields(_metadata, buf); - - snprintf(expected, sizeof(expected), "%llu", - (unsigned long long)(sock_port_start + 1)); - - ASSERT_EQ(0, - tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK), - "port", field, sizeof(field))); + count = tracefs_count_matches(buf, event_regex); + if (count != 1) + TH_LOG("Expected 1 denial event, got %d\n%s", count, buf); + ASSERT_EQ(1, count); + expect_net_deny_common_fields(_metadata, buf, + variant->expected_blockers); + + ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "port", field, + sizeof(field))); + snprintf(expected, sizeof(expected), "%d", expected_port); EXPECT_STREQ(expected, field); free(buf); From 3fa5aa398edf94653926ad5e68b89f69490481b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:32 +0200 Subject: [PATCH 0902/1417] landlock: Fix tracepoint contract documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tracepoint documentation claims that denial and lifecycle events expose every input needed to reproduce a verdict. Instead document how denial, ruleset, and domain events identify the denying policy, checked operation and object, and reason for denial. Direct consumers to generic tracepoints for additional operational context. State the reconstruction limits: IDs are boot-local, rule checks have no request ID, and exported records may be lost or cross-CPU reordered. Also replace the incorrect BPF_RAW_TRACEPOINT guidance with libbpf SEC("tp_btf/...") attachment and refer consumers to the event prototypes for callback argument layouts. Cc: Günther Noack Cc: Steven Rostedt Link: https://patch.msgid.link/20260918185036.608651-10-mic@digikod.net Signed-off-by: Mickaël Salaün --- Documentation/trace/events-landlock.rst | 30 ++++++++++++--------- include/trace/events/landlock.h | 36 ++++++++++++------------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/Documentation/trace/events-landlock.rst b/Documentation/trace/events-landlock.rst index 9bb81a5c676e73..304a8d06273ee3 100644 --- a/Documentation/trace/events-landlock.rst +++ b/Documentation/trace/events-landlock.rst @@ -141,10 +141,10 @@ in some field formats: treated as untrusted input and escaped in the trace text output so it cannot inject field separators or control characters. -- **Other party's domain**: A scope or ptrace denial compares the - subject's denying domain (``domain=``, always the enforcing domain and - never the current task) with the other party's domain, so these - tracepoints also report the other party's domain as a scalar ID: +- **Other party's domain**: A scope or ptrace denial compares the subject's + denying domain (``domain=``), which is the enforcing domain and not + necessarily the current task's domain, with the other party's domain. + These tracepoints also report the other party's domain as a scalar ID: ``tracee_domain=`` (ptrace), ``target_domain=`` (signal), and ``peer_domain=`` (abstract unix socket). It is ``0`` when the other party is unsandboxed, and otherwise a domain ID that a consumer resolves @@ -231,9 +231,10 @@ contribution, not the final decision: through another matching rule, or the right is denied and appears in the ``blockers=`` field of the corresponding ``deny_access`` event. -To reconstruct the decision for an object, aggregate the ``grants=`` -groups of all ``check_rule`` events emitted for that object during the -check. +For an access check that a consumer can delimit, aggregate the ``grants=`` +groups of all matching ``check_rule`` events. These events do not carry a +request ID; use their execution context and generic tracepoints to separate +concurrent or successive checks of the same object. .. note:: @@ -264,9 +265,11 @@ layers, so ``grants=`` has one group per layer:: eBPF access =========== -eBPF programs attached via ``BPF_RAW_TRACEPOINT`` can access the -tracepoint arguments directly through BTF. The arguments include both -standard kernel objects and Landlock-internal objects: +BTF-enabled raw tracepoint programs attached through libbpf +``SEC("tp_btf/...")`` sections receive typed callback arguments. The +event prototypes in `Event reference`_ document their argument layouts. +The arguments include both standard kernel objects and Landlock-internal +objects: - Standard kernel objects (``struct task_struct``, ``struct sock``, ``struct path``, ``struct dentry``) can be used with existing BPF @@ -277,7 +280,7 @@ standard kernel objects and Landlock-internal objects: Internal struct layouts may change between kernel versions; use CO-RE for field relocation. -A stateful eBPF program observes the full event stream and maintains +A stateful eBPF program attached before sandbox construction can maintain per-domain state in BPF maps: 1. On ``landlock_create_domain``: record the domain ID and parent (the @@ -293,7 +296,10 @@ per-domain state in BPF maps: final statistics. This approach requires no kernel modification and no Landlock-specific -BPF helpers. The Landlock IDs serve as correlation keys across events. +BPF helpers. Landlock IDs serve as correlation keys within one boot. +Records exported through tracing or BPF buffers can be lost, and records +from different CPUs are not globally ordered, so consumers must detect and +reconcile incomplete state. Audit filtering equivalence =========================== diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index 5da0f12ed2c3c6..523ba5ea987000 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -206,22 +206,22 @@ static inline const char *__trace_landlock_print_layers( * Decision context * ~~~~~~~~~~~~~~~~ * - * A denial event, together with the lifecycle events, exposes the full - * set of inputs the verdict consumed, so a consumer that tracked domain - * creation (landlock_create_ruleset, landlock_create_domain) can verify - * or reproduce the Landlock decision rather than merely observe it - * happened. In who/what/why terms: who is the denying domain (the domain - * field, always the subject that enforced the policy, never the current - * task), what is the operation and its object, and why is every other - * input the verdict weighed. + * A denial event identifies the domain whose policy denied the request, the + * Landlock operation and policy object that were checked, and the blocker or + * domain relationship responsible for the denial. When tracing starts with + * sandbox construction, ruleset and domain events provide the policy history + * needed to interpret these identifiers. The denying domain is the subject + * that enforced the policy, not necessarily current. Generic tracepoints can + * provide additional operational context. * * Lifecycle consistency * ~~~~~~~~~~~~~~~~~~~~~~ * - * Lifecycle events are balanced: a creation event always has a matching - * deallocation event and vice versa, so an eBPF program can model object - * lifetimes from the trace stream without reconciliation logic. A creation - * event fires while the object is still private to the calling thread + * Lifecycle emission is balanced: a creation event always has a matching + * deallocation event and vice versa. A consumer that observes an object's + * complete lifetime can model it from this pair; one that attaches late or + * loses exported records must reconcile incomplete state. A creation event + * fires while the object is still private to the calling thread * (landlock_create_ruleset fires before the ruleset's file descriptor is * installed, so it cannot race a concurrent :manpage:`close(2)`); if fd * installation later fails and the ruleset is freed, free_ruleset still @@ -295,7 +295,8 @@ static inline const char *__trace_landlock_print_layers( * the two parties without kernel-internal state. The ID is a scalar * snapshot, not a live domain pointer that could dangle: an optional * relational referent is a scalar (0 sentinel), not a nullable pointer. - * For ptrace, same_exec instead describes the tracer, even for + * Nonzero IDs are unique within one boot. For ptrace, same_exec instead + * describes the tracer, even for * PTRACE_TRACEME, and may differ from the current task. * * Blocker fields @@ -1031,11 +1032,10 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket, __entry->logged = logged; __entry->peer_domain_id = peer_domain_id; /* - * Best-effort (0 for a datagram peer). sk_peer_pid is - * canonically guarded by sk->sk_peer_lock, but the target - * peer's peercred is set once and not updated concurrently in - * these hooks, so this READ_ONCE() is safe; sun_path is the - * reliable identifier. + * Best-effort (0 for a datagram peer). The caller holds the + * peer's AF_UNIX state lock, serializing published peercred + * updates. The peer socket keeps a reference to sk_peer_pid + * through pid_nr(); sun_path is the reliable identifier. */ peer_pid = READ_ONCE(peer->sk_peer_pid); __entry->peer_pid = peer_pid ? pid_nr(peer_pid) : 0; From b74aad23d99b279bb34d135795f39a6d8ecdc075 Mon Sep 17 00:00:00 2001 From: Peiyang He Date: Tue, 8 Sep 2026 20:13:23 +0800 Subject: [PATCH 0903/1417] drm/virtio: fix memory leak of fence event on execbuffer failure virtio_gpu_execbuffer_ioctl() reserves a DRM event with drm_event_reserve_init() when VIRTGPU_EXECBUF_RING_IDX selects a ring that userspace has enabled polling for. virtio_gpu_init_submit() does this before the BO handles, the command buffer, the syncobj arrays and the in-fence are processed, so every later error path runs with the event already pending, including plain argument validation failures such as an invalid bo_handle or an in-syncobj that carries no fence. On those paths, virtio_gpu_cleanup_submit() drops the out-fence without cancelling the event. The fence is freed without ever having been emitted, taking the only driver-side pointer to the event with it. Closing the DRM file does not help. drm_events_release() unlinks pending events but deliberately leaves the freeing to the driver's later drm_send_event(), which never runs for an orphaned event, so the allocation is leaked permanently. Found when fuzzing the virtio driver with Syzkaller: BUG: memory leak unreferenced object 0xffff88802c176e80 (size 96): comm "syz.1.367", pid 10561, jiffies 4294960122 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ c8 6e 17 2c 80 88 ff ff 00 00 00 00 00 00 00 00 .n.,............ backtrace (crc e1973c6b): kmemleak_alloc_recursive include/linux/kmemleak.h:44 [inline] slab_post_alloc_hook mm/slub.c:4597 [inline] slab_alloc_node mm/slub.c:4917 [inline] __kmalloc_cache_noprof+0x49d/0x6f0 mm/slub.c:5485 _kmalloc_noprof include/linux/slab.h:988 [inline] _kzalloc_noprof include/linux/slab.h:1309 [inline] virtio_gpu_fence_event_create drivers/gpu/drm/virtio/virtgpu_submit.c:282 [inline] virtio_gpu_init_submit drivers/gpu/drm/virtio/virtgpu_submit.c:398 [inline] virtio_gpu_execbuffer_ioctl+0xbbf/0x1aa0 drivers/gpu/drm/virtio/virtgpu_submit.c:505 drm_ioctl_kernel+0x1f4/0x3e0 drivers/gpu/drm/drm_ioctl.c:817 drm_ioctl+0x5f4/0xc70 drivers/gpu/drm/drm_ioctl.c:914 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x116/0x800 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fix by cancelling and freeing the DRM event on the execbuffer error path before dropping the fence. Clear the fence's event pointer after cancellation so it does not retain a dangling pointer. Fixes: cd7f5ca33585 ("drm/virtio: implement context init: add virtio_gpu_fence_event") Cc: stable@vger.kernel.org Signed-off-by: Peiyang He Assisted-by: Codex:gpt-5.6-luna Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/D320EAB5680C1411+20260908121323.2405044-1-peiyang_he@smail.nju.edu.cn --- drivers/gpu/drm/virtio/virtgpu_submit.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_submit.c b/drivers/gpu/drm/virtio/virtgpu_submit.c index 32cb1e4aa4258f..734b1e976a75c6 100644 --- a/drivers/gpu/drm/virtio/virtgpu_submit.c +++ b/drivers/gpu/drm/virtio/virtgpu_submit.c @@ -538,6 +538,10 @@ int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data, virtio_gpu_process_post_deps(&submit); virtio_gpu_complete_submit(&submit); cleanup: + if (ret && submit.out_fence && submit.out_fence->e) { + drm_event_cancel_free(dev, &submit.out_fence->e->base); + submit.out_fence->e = NULL; + } virtio_gpu_cleanup_submit(&submit); return ret; From 36570ef2244cc4d7563b1f0157bc0f032498638c Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Tue, 15 Sep 2026 15:39:10 +0800 Subject: [PATCH 0904/1417] drm/virtio: fix object leak when drm_gem_handle_create() fails virtio_gpu_gem_create() owns the reference taken by virtio_gpu_object_create(). On the drm_gem_handle_create() error path it calls drm_gem_object_release() instead of dropping that reference. drm_gem_object_release() is the inverse of drm_gem_object_init() and does not touch the reference count or call obj->funcs->free(), so it is only correct as the last step of a destructor, as in virtio_gpu_cleanup_object(). Using it here leaves the bo at refcount 1 with no remaining reference, so virtio_gpu_free_object() never runs and the shmem pages, sg table and virtio_gpu_object are leaked. Since virtio_gpu_object_create() has already set bo->created, VIRTIO_GPU_CMD_RESOURCE_UNREF is not queued either, leaking the host-side resource and the resource id. drm_gem_handle_create_tail() drops the handle reference on all of its internal error paths, so the caller only has to drop its own. Use drm_gem_object_put(), matching the success path below. Fixes: dc5698e80cf7 ("Add virtio gpu driver.") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Signed-off-by: Junrui Luo Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260915-fixes-v2-1-a0d799e4db66@outlook.com --- drivers/gpu/drm/virtio/virtgpu_gem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_gem.c b/drivers/gpu/drm/virtio/virtgpu_gem.c index 66c3f6f74e9c69..d2f0b8a3f172be 100644 --- a/drivers/gpu/drm/virtio/virtgpu_gem.c +++ b/drivers/gpu/drm/virtio/virtgpu_gem.c @@ -45,7 +45,7 @@ static int virtio_gpu_gem_create(struct drm_file *file, ret = drm_gem_handle_create(file, &obj->base.base, &handle); if (ret) { - drm_gem_object_release(&obj->base.base); + drm_gem_object_put(&obj->base.base); return ret; } From 477bc3068fc3777b9d8ffd79e265b0dfdf2d3a6b Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Tue, 15 Sep 2026 15:39:11 +0800 Subject: [PATCH 0905/1417] drm/virtio: fix object leak in virtio_gpu_resource_create_ioctl() virtio_gpu_resource_create_ioctl() calls drm_gem_object_release() on the drm_gem_handle_create() error path instead of dropping the reference it owns, so obj->funcs->free() never runs and the virtio_gpu_object, its pages and sg table, the resource id and the host-side resource are leaked. Use drm_gem_object_put() instead. Fixes: 62fb7a5e1096 ("virtio-gpu: add 3d/virgl support") Assisted-by: Claude:claude-opus-5 Signed-off-by: Junrui Luo Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260915-fixes-v2-2-a0d799e4db66@outlook.com --- drivers/gpu/drm/virtio/virtgpu_ioctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_ioctl.c b/drivers/gpu/drm/virtio/virtgpu_ioctl.c index 3d8e4ccdb7c1f8..d16f07abb266a2 100644 --- a/drivers/gpu/drm/virtio/virtgpu_ioctl.c +++ b/drivers/gpu/drm/virtio/virtgpu_ioctl.c @@ -185,7 +185,7 @@ static int virtio_gpu_resource_create_ioctl(struct drm_device *dev, void *data, ret = drm_gem_handle_create(file, obj, &handle); if (ret) { - drm_gem_object_release(obj); + drm_gem_object_put(obj); return ret; } From 24b6d5c7641412c9ebef0d4c8b888d49a0e6b880 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Tue, 15 Sep 2026 15:39:12 +0800 Subject: [PATCH 0906/1417] drm/virtio: fix object leaks in virtio_gpu_resource_create_blob_ioctl() virtio_gpu_resource_create_blob_ioctl() calls drm_gem_object_release() on both the virtio_gpu_resource_assign_uuid() and drm_gem_handle_create() error paths instead of dropping the reference it owns, so obj->funcs->free() never runs and the virtio_gpu_object, the resource id and the host-side resource are leaked. Use drm_gem_object_put() instead. Fixes: 897b4d1acaf5 ("drm/virtio: implement blob resources: resource create blob ioctl") Assisted-by: Claude:claude-opus-5 Signed-off-by: Junrui Luo Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260915-fixes-v2-3-a0d799e4db66@outlook.com --- drivers/gpu/drm/virtio/virtgpu_ioctl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_ioctl.c b/drivers/gpu/drm/virtio/virtgpu_ioctl.c index d16f07abb266a2..fcdb07a37972c1 100644 --- a/drivers/gpu/drm/virtio/virtgpu_ioctl.c +++ b/drivers/gpu/drm/virtio/virtgpu_ioctl.c @@ -557,14 +557,14 @@ static int virtio_gpu_resource_create_blob_ioctl(struct drm_device *dev, if (params.blob_flags & VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE) { ret = virtio_gpu_resource_assign_uuid(vgdev, bo); if (ret) { - drm_gem_object_release(obj); + drm_gem_object_put(obj); return ret; } } ret = drm_gem_handle_create(file, obj, &handle); if (ret) { - drm_gem_object_release(obj); + drm_gem_object_put(obj); return ret; } From 036d28db1818af2f9d80db771f5405da84d7732d Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Tue, 15 Sep 2026 15:39:13 +0800 Subject: [PATCH 0907/1417] drm/virtio: release the GEM object on virtio_gpu_vram_create() errors virtio_gpu_vram_create() frees the object with a bare kfree(vram) on both error paths after drm_gem_private_object_init() has run, and on the second one after drm_gem_create_mmap_offset() has linked obj->vma_node into the device's VMA offset manager. The freed object stays in that interval tree, so a later lookup or insertion walks freed memory, and the dma_resv and gpuva lock are never destroyed. Call drm_gem_object_release() before kfree() on both paths. Fixes: 16845c5d5409 ("drm/virtio: implement blob resources: implement vram object") Assisted-by: Claude:claude-opus-5 Signed-off-by: Junrui Luo Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260915-fixes-v2-4-a0d799e4db66@outlook.com --- drivers/gpu/drm/virtio/virtgpu_vram.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_vram.c b/drivers/gpu/drm/virtio/virtgpu_vram.c index 5b4a3ab81cd5b4..01241ce4d07cdc 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vram.c +++ b/drivers/gpu/drm/virtio/virtgpu_vram.c @@ -215,16 +215,12 @@ int virtio_gpu_vram_create(struct virtio_gpu_device *vgdev, /* Create fake offset */ ret = drm_gem_create_mmap_offset(obj); - if (ret) { - kfree(vram); - return ret; - } + if (ret) + goto err_release_obj; ret = virtio_gpu_resource_id_get(vgdev, &vram->base.hw_res_handle); - if (ret) { - kfree(vram); - return ret; - } + if (ret) + goto err_release_obj; virtio_gpu_cmd_resource_create_blob(vgdev, &vram->base, params, NULL, 0); @@ -240,6 +236,11 @@ int virtio_gpu_vram_create(struct virtio_gpu_device *vgdev, *bo_ptr = &vram->base; return 0; + +err_release_obj: + drm_gem_object_release(obj); + kfree(vram); + return ret; } void virtio_gpu_vram_map_deferred(struct virtio_gpu_object_vram *vram) From 1e3b08de63274d0b009e99ef51cd6a9c0c6bf08c Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Fri, 11 Sep 2026 17:42:03 +0300 Subject: [PATCH 0908/1417] Revert "drm/virtio: Allow importing prime buffers when 3D is enabled" Guest userspace may import udmabuf to vrend. Vrend doesn't support guest blobs, and thus, further 3d operations with the imported blob are failing. Typical scenario of the problem shown with mouse cursor RGBA image imported into virtio-gpu, which previously was rejected by virtio-gpu driver. Revert enabling guest blobs importing into vrend to fix the regression. Link: https://gitlab.freedesktop.org/virgl/virglrenderer/-/work_items/674 Fixes: df4dc947c46b ("drm/virtio: Allow importing prime buffers when 3D is enabled") Signed-off-by: Dmitry Osipenko Reviewed-by: Val Packett Link: https://patch.msgid.link/20260911144204.2089401-1-dmitry.osipenko@collabora.com --- drivers/gpu/drm/virtio/virtgpu_prime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_prime.c b/drivers/gpu/drm/virtio/virtgpu_prime.c index 149e6bcb5878c3..ebf471044d06c0 100644 --- a/drivers/gpu/drm/virtio/virtgpu_prime.c +++ b/drivers/gpu/drm/virtio/virtgpu_prime.c @@ -349,7 +349,7 @@ struct drm_gem_object *virtgpu_gem_prime_import(struct drm_device *dev, } } - if (!vgdev->has_resource_blob) + if (!vgdev->has_resource_blob || vgdev->has_virgl_3d) return drm_gem_prime_import(dev, buf); bo = kzalloc_obj(*bo); From 846b3c64fe3e77d9db20a7e3e62dbbb637c773e1 Mon Sep 17 00:00:00 2001 From: Peiyang He Date: Wed, 9 Sep 2026 17:11:14 +0800 Subject: [PATCH 0909/1417] drm/virtio: fix NULL pointer dereference on fence allocation failure virtio_gpu_fence_alloc() can fail due to memory pressure and return NULL, but its caller like virtio_gpu_init_submit() never checks it. Later, virtio_gpu_init_submit() passes the NULL fence to virtio_gpu_fence_event_create(), which unconditionally dereferences it. Found when fuzzing the virtio driver with Syzkaller: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000012: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000090-0x0000000000000097] CPU: 1 UID: 0 PID: 9991 Comm: syz.0.121 Not tainted 7.2.0 #4 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org 04/01/2014 RIP: 0010:virtio_gpu_fence_event_create drivers/gpu/drm/virtio/virtgpu_submit.c:295 [inline] RIP: 0010:virtio_gpu_init_submit drivers/gpu/drm/virtio/virtgpu_submit.c:398 [inline] RIP: 0010:virtio_gpu_execbuffer_ioctl+0xc78/0x1aa0 drivers/gpu/drm/virtio/virtgpu_submit.c:505 Code: 85 ed 0f 85 21 09 00 00 e8 05 5a c9 fb 48 8b 44 24 10 48 8d b8 90 00 00 00 48 b8 00 00 00 00 00 fc ff df 48 89 fa 48 c1 ea 03 <80> 3c 02 00 0f 85 9a 0d 00 00 48 8b 44 24 10 4c 89 b0 90 00 00 00 RSP: 0018:ffffc900039dfad0 EFLAGS: 00010216 RAX: dffffc0000000000 RBX: ffffc900039dfdd8 RCX: ffffffff85f6fd3d RDX: 0000000000000012 RSI: ffffffff85f6fd4b RDI: 0000000000000090 RBP: 0000000000000000 R0virtio_gpu_virgl_process_cmd: ctrl 0x102, error 0x1203 R10: 0000000000000000 R11: 0000000000000000 R12: ffff8880132c4000 R13: 0000000000000000 R14: ffff888073b6c700 R15: 000000000000003b FS: 00007fab480b96c0(0000) GS:ffff8880eb6e9000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007effbf5e55a8 CR3: 0000000048d19000 CR4: 0000000000350ef0 Call Trace: drm_ioctl_kernel+0x1f4/0x3e0 drivers/gpu/drm/drm_ioctl.c:817 drm_ioctl+0x5f4/0xc70 drivers/gpu/drm/drm_ioctl.c:914 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x116/0x800 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7fab471a82bd Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b0 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007fab480b9018 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 00007fab47435fa0 RCX: 00007fab471a82bd RDX: 00002000000000c0 RSI: 00000000c0406442 RDI: 0000000000000003 RBP: 00007fab480b9080 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001 R13: 00007fab47436038 R14: 00007fab47435fa0 R15: 00007ffe85ab0740 Modules linked in: ---[ end trace 0000000000000000 ]--- RIP: 0010:virtio_gpu_fence_event_create drivers/gpu/drm/virtio/virtgpu_submit.c:295 [inline] RIP: 0010:virtio_gpu_init_submit drivers/gpu/drm/virtio/virtgpu_submit.c:398 [inline] RIP: 0010:virtio_gpu_execbuffer_ioctl+0xc78/0x1aa0 drivers/gpu/drm/virtio/virtgpu_submit.c:505 Code: 85 ed 0f 85 21 09 00 00 e8 05 5a c9 fb 48 8b 44 24 10 48 8d b8 90 00 00 00 48 b8 00 00 00 00 00 fc ff df 48 89 fa 48 c1 ea 03 <80> 3c 02 00 0f 85 9a 0d 00 00 48 8b 44 24 10 4c 89 b0 90 00 00 00 RSP: 0018:ffffc900039dfad0 EFLAGS: 00010216 RAX: dffffc0000000000 RBX: ffffc900039dfdd8 RCX: ffffffff85f6fd3d RDX: 0000000000000012 RSI: ffffffff85f6fd4b RDI: 0000000000000090 RBP: 0000000000000000 R08: 0000000000000005 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000000 R12: ffff8880132c4000 R13: 0000000000000000 R14: ffff888073b6c700 R15: 000000000000003b FS: 00007fab480b96c0(0000) GS:ffff888098ae9000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f24c3759000 CR3: 0000000048d19000 CR4: 0000000000350ef0 ---------------- Code disassembly (best guess): 0: 85 ed test %ebp,%ebp 2: 0f 85 21 09 00 00 jne 0x929 8: e8 05 5a c9 fb call 0xfbc95a12 d: 48 8b 44 24 10 mov 0x10(%rsp),%rax 12: 48 8d b8 90 00 00 00 lea 0x90(%rax),%rdi 19: 48 b8 00 00 00 00 00 movabs $0xdffffc0000000000,%rax 20: fc ff df 23: 48 89 fa mov %rdi,%rdx 26: 48 c1 ea 03 shr $0x3,%rdx * 2a: 80 3c 02 00 cmpb $0x0,(%rdx,%rax,1) <-- trapping instruction 2e: 0f 85 9a 0d 00 00 jne 0xdce 34: 48 8b 44 24 10 mov 0x10(%rsp),%rax 39: 4c 89 b0 90 00 00 00 mov %r14,0x90(%rax) Fix by checking virtio_gpu_fence_alloc() in virtio_gpu_init_submit() and returning -ENOMEM before any later code can dereference the NULL fence. Fixes: 70d1ace56db6 ("drm/virtio: Conditionally allocate virtio_gpu_fence") Cc: stable@vger.kernel.org Signed-off-by: Peiyang He Assisted-by: Codex:gpt-5.5 Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/00EFE4BA92889B14+20260909091114.2622550-1-peiyang_he@smail.nju.edu.cn --- drivers/gpu/drm/virtio/virtgpu_submit.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_submit.c b/drivers/gpu/drm/virtio/virtgpu_submit.c index 734b1e976a75c6..3d35326dd90406 100644 --- a/drivers/gpu/drm/virtio/virtgpu_submit.c +++ b/drivers/gpu/drm/virtio/virtgpu_submit.c @@ -389,10 +389,13 @@ static int virtio_gpu_init_submit(struct virtio_gpu_submit *submit, if ((exbuf->flags & VIRTGPU_EXECBUF_FENCE_FD_OUT) || exbuf->num_out_syncobjs || exbuf->num_bo_handles || - drm_fence_event) + drm_fence_event) { out_fence = virtio_gpu_fence_alloc(vgdev, fence_ctx, ring_idx); - else + if (!out_fence) + return -ENOMEM; + } else { out_fence = NULL; + } if (drm_fence_event) { err = virtio_gpu_fence_event_create(dev, file, out_fence, ring_idx); From 6947b78df4d25bd1e86b26870b614ea54c625b1e Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Fri, 28 Aug 2026 17:01:23 +0800 Subject: [PATCH 0910/1417] drm/virtio: Add pixel blend mode property to cursor plane The cursor plane exposes a format with an alpha channel (DRM_FORMAT_ARGB8888) without a pixel blend mode property. Since commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") this triggers a warning during drm_mode_config_validate(): [ 0.649020] ------------[ cut here ]------------ [ 0.649040] [PLANE:36:plane-1] pixel format with alpha exposed but blend mode not setup [ 0.649081] WARNING: drivers/gpu/drm/drm_mode_config.c:872 at drm_mode_config_validate ...... [ 0.649761] Call trace: [ 0.649764] drm_mode_config_validate+0x398/0x558 [drm] (P) [ 0.649912] drm_dev_register+0x1cc/0x2a0 [drm] [ 0.650058] virtio_gpu_probe+0xd4/0x1c0 [virtio_gpu] [ 0.650088] virtio_dev_probe+0x1c8/0x310 ...... [ 0.650261] ---[ end trace 0000000000000000 ]--- Create the property with the only supported blend mode, DRM_MODE_BLEND_PREMULTI, which is also the property's default and matches what userspace had to assume before the property existed. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Reported-by: Ye Liu Signed-off-by: Shixiong Ou Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260828090123.784944-1-oushixiong1025@163.com --- drivers/gpu/drm/virtio/virtgpu_plane.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_plane.c b/drivers/gpu/drm/virtio/virtgpu_plane.c index 640815af409801..b422eba42a5f93 100644 --- a/drivers/gpu/drm/virtio/virtgpu_plane.c +++ b/drivers/gpu/drm/virtio/virtgpu_plane.c @@ -589,6 +589,7 @@ struct drm_plane *virtio_gpu_plane_init(struct virtio_gpu_device *vgdev, struct drm_plane *plane; const uint32_t *formats; int nformats; + int ret; if (type == DRM_PLANE_TYPE_CURSOR) { formats = virtio_gpu_cursor_formats; @@ -614,5 +615,17 @@ struct drm_plane *virtio_gpu_plane_init(struct virtio_gpu_device *vgdev, drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_PREMULTI)); + if (type == DRM_PLANE_TYPE_CURSOR) { + /* + * The cursor plane exposes a format with an alpha channel, + * which requires a blend mode property. The host blends + * premultiplied alpha, matching the property's default. + */ + ret = drm_plane_create_blend_mode_property(plane, + BIT(DRM_MODE_BLEND_PREMULTI)); + if (ret) + return ERR_PTR(ret); + } + return plane; } From 598c1c3e895590f845e04455d5580ea28ffde666 Mon Sep 17 00:00:00 2001 From: Benjamin Leggett Date: Fri, 14 Aug 2026 18:20:11 -0400 Subject: [PATCH 0911/1417] drm/virtio: sync shmem backing on guest-bound transfers virtio_gpu_cmd_transfer_to_host_{2d,3d}() sync the shmem backing for the device before the transfer, but nothing syncs for the CPU when a transfer runs the other way. That breaks two ways. Where the DMA layer bounces, the device writes into the bounce buffer while the guest keeps reading the original pages. Where DMA is not coherent, the device writes memory while the CPU keeps stale cache lines, because nothing reaches arch_sync_dma_for_cpu(). Either way DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST hands back stale data. Sashiko originally found this in https://lore.kernel.org/dri-devel/20260806231002.27B4D1F000E9@smtp.kernel.org but the suggestion there to fix this with dma_sync_sgtable_for_cpu() isn't a sufficient fix, for two reasons. - The transfer is asynchronous. virtio_gpu_cmd_transfer_from_host_3d() only queues the command, so a sync there would run before the device had written anything. It belongs on completion, and ahead of any fence signalling. A waiter woken by the fence would otherwise race the sync and read the backing pages regardless. It needs its own pass over the reclaim list rather than a step inside the existing one, because virtio_gpu_fence_event_process() also signals every earlier fence in the same context, so any entry in that loop may signal an earlier entry's fence. - The transfer is also partial, carrying an offset, a level and a box. Where the mapping bounces, a sync for the CPU copies the whole mapping back, so unless the mapping is primed first the regions the device did not write come back holding whatever the bounce buffer contained, discarding data the guest owned. So the fix: Prime the mapping before queueing, tag the vbuffer, and sync for the CPU on completion before the fence is signalled. A second transfer must not snapshot the mapping while an earlier one is still in flight, or the snapshot would predate whatever the CPU wrote once the earlier fence signalled and the later sync would discard it. To mitigate this, wait for outstanding fences under the reservation before priming. Neither sync copies anything unless the mapping genuinely bounces: swiotlb_sync_single_for_cpu() and its Xen counterpart look the address up in the bounce pool and return early when it is absent. On a platform with non-coherent DMA they still perform the necessary cache maintenance. The range cannot be narrowed to the box, since for a non-blob resource virtio_gpu_transfer_from_host_ioctl() rejects a caller-supplied stride and layer_stride, leaving the layout to the host and the guest with no way to work out which bytes the device writes. A host3d guest blob does carry both, so its extent could be bounded, but the sync is left whole there too rather than special-cased: priming makes the untouched regions round-trip unchanged either way. Behaviour changes worth noting: - TRANSFER_FROM_HOST can now block, where before it returned as soon as the command was queued. Repeated readbacks of one resource serialise, and a readback can wait behind an earlier queued command that touched it, since virtio_gpu_array_add_fence() tags uploads, execbufs and plane flushes alike with DMA_RESV_USAGE_WRITE. -ERESTARTSYS was already possible here via dma_resv_lock_interruptible(). - A CPU write racing an in-flight transfer to the same resource is now lost, where before it survived and the transfer was lost instead. Priming captures the pages as of queueing, so a write landing before completion is overwritten by the sync. - TRANSFER_TO_HOST can also block now, but only while a guest-bound transfer on the same resource is outstanding, which happens only for callers that issue both without waiting. - Where a batch of completions contains a guest-bound transfer, the sync pass delays fence signalling for the whole batch. Only bounced pages are copied and the swiotlb pool bounds it. A batch with no such transfer is unaffected. Tested under QEMU on x86 with swiotlb=force and virtio-vga-gl iommu_platform=on, which forces both preconditions required to hit the original bug. Fixes: a3b815f09bb8 ("drm/virtio: add iommu support.") Reported-by: Sashiko AI review Closes: https://lore.kernel.org/dri-devel/20260806231002.27B4D1F000E9@smtp.kernel.org/ Signed-off-by: Benjamin Leggett Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260814-virtgpu-from-host-sync-v4-1-64dd736b1779@edera.io --- drivers/gpu/drm/virtio/virtgpu_drv.h | 5 +++ drivers/gpu/drm/virtio/virtgpu_ioctl.c | 43 ++++++++++++++++++++++++ drivers/gpu/drm/virtio/virtgpu_vq.c | 46 ++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index 9df4c7117341e7..f3bbbe4468bf1f 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -114,6 +114,8 @@ struct virtio_gpu_object { bool dumb; bool created; bool attached; + /* a guest-bound transfer is queued and its mapping not yet synced */ + bool from_host_pending; bool host3d_blob, guest_blob; uint32_t blob_mem, blob_flags; @@ -196,6 +198,9 @@ struct virtio_gpu_vbuffer { struct list_head list; uint32_t seqno; + + /* guest-bound transfer whose shmem backing needs a CPU sync */ + bool sync_for_cpu; }; struct virtio_gpu_output { diff --git a/drivers/gpu/drm/virtio/virtgpu_ioctl.c b/drivers/gpu/drm/virtio/virtgpu_ioctl.c index fcdb07a37972c1..81e70a12b35692 100644 --- a/drivers/gpu/drm/virtio/virtgpu_ioctl.c +++ b/drivers/gpu/drm/virtio/virtgpu_ioctl.c @@ -261,6 +261,27 @@ static int virtio_gpu_transfer_from_host_ioctl(struct drm_device *dev, if (ret != 0) goto err_put_free; + if (virtio_gpu_is_shmem(bo) && virtio_gpu_use_dma_api(vgdev->vdev)) { + /* + * The sync on completion restores the whole mapping, so an + * earlier transfer has to be done before this one snapshots it. + * Otherwise the snapshot predates anything the CPU wrote once + * that transfer's fence signalled, and the later sync would + * discard it. Nothing can add a fence behind our back here, + * since doing so takes the reservation we already hold. + * This writes the pages, so it waits as a writer does. READ + * usage covers existing readers. + */ + long wait = dma_resv_wait_timeout(objs->objs[0]->resv, + DMA_RESV_USAGE_READ, true, + MAX_SCHEDULE_TIMEOUT); + + if (wait < 0) { + ret = wait; + goto err_unlock; + } + } + fence = virtio_gpu_fence_alloc(vgdev, vgdev->fence_drv.context, 0); if (!fence) { ret = -ENOMEM; @@ -320,6 +341,28 @@ static int virtio_gpu_transfer_to_host_ioctl(struct drm_device *dev, void *data, if (ret != 0) goto err_put_free; + /* + * A transfer the other way may have queued without yet syncing + * its mapping. Pushing the guest pages into it now would + * discard what the device wrote there, so wait for that sync: + * it runs before the fence it belongs to is signalled. The + * flag is only set under this reservation, so it cannot appear + * behind our back, and the acquire pairs with the release in + * that sync, so finding it clear means the pages it wrote are + * visible here too. + */ + if (smp_load_acquire(&bo->from_host_pending)) { + long wait = dma_resv_wait_timeout(objs->objs[0]->resv, + DMA_RESV_USAGE_WRITE, + true, + MAX_SCHEDULE_TIMEOUT); + + if (wait < 0) { + ret = wait; + goto err_unlock; + } + } + ret = -ENOMEM; fence = virtio_gpu_fence_alloc(vgdev, vgdev->fence_drv.context, 0); diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c index c02c03c10d92e3..d99fb9e326e81e 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vq.c +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c @@ -256,6 +256,33 @@ void virtio_gpu_dequeue_ctrl_func(struct work_struct *work) } while (!virtqueue_enable_cb(vgdev->ctrlq.vq)); spin_unlock(&vgdev->ctrlq.qlock); + /* + * Sync guest-bound transfers before signalling anything, so that a + * waiter cannot read the backing pages while what the device wrote is + * still in a bounce buffer. This cannot be folded into the loop below: + * virtio_gpu_fence_event_process() also signals every earlier fence in + * the same context, so any entry there may signal this entry's fence. + */ + list_for_each_entry(entry, &reclaim_list, list) { + if (entry->sync_for_cpu) { + struct virtio_gpu_object *bo = + gem_to_virtio_gpu_obj(entry->objs->objs[0]); + + dma_sync_sgtable_for_cpu(vgdev->vdev->dev.parent, + bo->base.sgt, DMA_FROM_DEVICE); + /* + * Release, so a transfer the other way that skips its + * wait on the strength of this cannot go on to read + * the backing pages before the sync above is visible. + * Nothing orders the two otherwise: where the mapping + * bounces on a coherent device the sync is a plain + * copy, and dma_direct_sync_sg_for_cpu() emits its + * barrier only for the non-coherent case. + */ + smp_store_release(&bo->from_host_pending, false); + } + } + list_for_each_entry(entry, &reclaim_list, list) { resp = (struct virtio_gpu_ctrl_hdr *)entry->resp_buf; @@ -1278,12 +1305,31 @@ void virtio_gpu_cmd_transfer_from_host_3d(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(objs->objs[0]); struct virtio_gpu_transfer_host_3d *cmd_p; struct virtio_gpu_vbuffer *vbuf; + bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev); cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p)); memset(cmd_p, 0, sizeof(*cmd_p)); vbuf->objs = objs; + if (virtio_gpu_is_shmem(bo) && use_dma_api) { + /* + * The device writes only the requested box, so prime the + * mapping with the current contents: otherwise the sync on + * completion would hand back whatever a bounce buffer held for + * the regions the device does not touch. + */ + dma_sync_sgtable_for_device(vgdev->vdev->dev.parent, + bo->base.sgt, DMA_TO_DEVICE); + vbuf->sync_for_cpu = true; + /* + * Set under the reservation the caller holds, so a transfer + * the other way cannot miss it and push the guest pages into + * the mapping while the device still owns it. + */ + WRITE_ONCE(bo->from_host_pending, true); + } + cmd_p->hdr.type = cpu_to_le32(VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D); cmd_p->hdr.ctx_id = cpu_to_le32(ctx_id); cmd_p->resource_id = cpu_to_le32(bo->hw_res_handle); From 6a5719cc3ef2e4d9857cc4ae18e6db09d59a8cc9 Mon Sep 17 00:00:00 2001 From: Daniel J Blueman Date: Sun, 20 Sep 2026 16:16:08 +0200 Subject: [PATCH 0912/1417] net: qrtr: resend HELLO on MHI resume Since the MHI HELLO exchange was relocated, it is sent only at device registration. During a suspend-resume cycle, the firmware in WiFi cards such as WCN7850 indefinitely waits for another HELLO, triggering: ath12k_wifi7_pci 0004:01:00.0: timeout while waiting for restart complete ath12k_wifi7_pci 0004:01:00.0: failed to resume core: -110 Fix this by triggering the handshake from resume_early in the MHI transport. Validated on Qualcomm X1E-801800 on Lenovo Slim 7x across 10 suspend-resume cycles. Fixes: 544d85de4dc2 ("net: qrtr: Send HELLO message on endpoint register") Signed-off-by: Daniel J Blueman Reviewed-by: Manivannan Sadhasivam Reported-by: Jeff Johnson Link: https://lore.kernel.org/all/6257c447-788d-4362-851e-0d552bcf7c56@oss.qualcomm.com/ Tested-by: Jeff Johnson Reported-by: Vlastimil Babka (SUSE) Link: https://lore.kernel.org/all/ab1491bb-cca5-4145-ac7d-31c966abf7b4@suse.com/ Tested-by: Vlastimil Babka (SUSE) Reported-by: Takashi Iwai Link: https://lore.kernel.org/all/87a4plsg4w.wl-tiwai@suse.de/ Tested-by: Takashi Iwai Signed-off-by: Thorsten Leemhuis Signed-off-by: Linus Torvalds --- net/qrtr/af_qrtr.c | 13 +++++++++++++ net/qrtr/mhi.c | 9 ++++++++- net/qrtr/qrtr.h | 2 ++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/net/qrtr/af_qrtr.c b/net/qrtr/af_qrtr.c index 78347c937af76b..e7b3647424b8f1 100644 --- a/net/qrtr/af_qrtr.c +++ b/net/qrtr/af_qrtr.c @@ -623,6 +623,19 @@ static void qrtr_hello_work(struct work_struct *work) qrtr_port_put(ctrl); } +/* Trigger the HELLO handshake after the remote has been reset, eg on resume */ +void qrtr_endpoint_hello(struct qrtr_endpoint *ep) +{ + struct qrtr_node *node = ep->node; + + mutex_lock(&node->ep_lock); + node->hello_sent = false; + mutex_unlock(&node->ep_lock); + + schedule_delayed_work(&node->say_hello, 0); +} +EXPORT_SYMBOL_GPL(qrtr_endpoint_hello); + /** * qrtr_endpoint_register() - register a new endpoint * @ep: endpoint to register diff --git a/net/qrtr/mhi.c b/net/qrtr/mhi.c index 3990da1a65dc4f..e9a4bb92ce76ff 100644 --- a/net/qrtr/mhi.c +++ b/net/qrtr/mhi.c @@ -183,6 +183,7 @@ static int __maybe_unused qcom_mhi_qrtr_pm_suspend_late(struct device *dev) static int __maybe_unused qcom_mhi_qrtr_pm_resume_early(struct device *dev) { struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev); + struct qrtr_mhi_dev *qdev = dev_get_drvdata(dev); enum mhi_state state; int rc; @@ -201,7 +202,13 @@ static int __maybe_unused qcom_mhi_qrtr_pm_resume_early(struct device *dev) return rc; } - return qcom_mhi_qrtr_queue_dl_buffers(mhi_dev); + rc = qcom_mhi_qrtr_queue_dl_buffers(mhi_dev); + if (rc) + return rc; + + qrtr_endpoint_hello(&qdev->ep); + + return 0; } static const struct dev_pm_ops qcom_mhi_qrtr_pm_ops = { diff --git a/net/qrtr/qrtr.h b/net/qrtr/qrtr.h index 3f2d28696062a5..de2de69a619936 100644 --- a/net/qrtr/qrtr.h +++ b/net/qrtr/qrtr.h @@ -27,6 +27,8 @@ int qrtr_endpoint_register(struct qrtr_endpoint *ep, unsigned int nid); void qrtr_endpoint_unregister(struct qrtr_endpoint *ep); +void qrtr_endpoint_hello(struct qrtr_endpoint *ep); + int qrtr_endpoint_post(struct qrtr_endpoint *ep, const void *data, size_t len); int qrtr_ns_init(void); From 93f51579e7df248780214094418f205253383cc5 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 20 Sep 2026 13:48:15 -0700 Subject: [PATCH 0913/1417] Linux 7.3-rc4 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0f1b80100b4798..751a08643bf85c 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc3 +EXTRAVERSION = -rc4 NAME = Baby Opossum Posse # *DOCUMENTATION* From ae2c5bf969573708cd5b6bb6393631255d83c3fe Mon Sep 17 00:00:00 2001 From: Prathamesh Shete Date: Wed, 16 Sep 2026 06:51:32 +0000 Subject: [PATCH 0914/1417] pinctrl: tegra238: Fix register bank for AON pin groups The AON pin controller has a single register region and therefore, the bank defined in the tegra238_functions[] and tegra238_aon_groups[] for the AON pin groups must be 0. However, commit 25cac7292d49 ("pinctrl: tegra: Add Tegra238 pinmux driver") incorrectly specified the bank for these pins as 1 and not 0. This means that in the tegra_pinctrl_probe() function we use an invalid index when accessing the pmx->regs[] array which causes an incorrect address to be used for accessing the pinmux registers. Fix this by correcting the bank for the AON pin groups. Fixes: 25cac7292d49 ("pinctrl: tegra: Add Tegra238 pinmux driver") Signed-off-by: Prathamesh Shete Signed-off-by: Linus Walleij --- drivers/pinctrl/tegra/pinctrl-tegra238.c | 204 +++++++++++------------ 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/drivers/pinctrl/tegra/pinctrl-tegra238.c b/drivers/pinctrl/tegra/pinctrl-tegra238.c index ec482365f14f2c..40aba285944eb8 100644 --- a/drivers/pinctrl/tegra/pinctrl-tegra238.c +++ b/drivers/pinctrl/tegra/pinctrl-tegra238.c @@ -1744,57 +1744,57 @@ static const char * const tegra238_functions[] = { #define drive_sdmmc1_dat0_pu2 DRV_PINGROUP_ENTRY_Y(0x8034, 28, 2, 30, 2, -1, -1, -1, -1, 0) #define drive_ufs0_rst_n_pv1 DRV_PINGROUP_ENTRY_Y(0x11004, 12, 5, 24, 5, -1, -1, -1, -1, 0) #define drive_ufs0_ref_clk_pv0 DRV_PINGROUP_ENTRY_Y(0x1100c, 12, 5, 24, 5, -1, -1, -1, -1, 0) -#define drive_batt_oc_paa4 DRV_PINGROUP_ENTRY_Y(0x1024, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_bootv_ctl_n_paa0 DRV_PINGROUP_ENTRY_Y(0x102c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_vcomp_alert_paa2 DRV_PINGROUP_ENTRY_Y(0x105c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_hdmi_cec_pbb0 DRV_PINGROUP_ENTRY_Y(0x1064, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_touch_clk_pdd3 DRV_PINGROUP_ENTRY_Y(0x106c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart3_rx_pcc6 DRV_PINGROUP_ENTRY_Y(0x1074, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart3_tx_pcc5 DRV_PINGROUP_ENTRY_Y(0x107c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen8_i2c_sda_pdd2 DRV_PINGROUP_ENTRY_Y(0x1084, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen8_i2c_scl_pdd1 DRV_PINGROUP_ENTRY_Y(0x108c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_mosi_pcc2 DRV_PINGROUP_ENTRY_Y(0x1094, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen2_i2c_scl_pcc7 DRV_PINGROUP_ENTRY_Y(0x109c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_cs0_pcc3 DRV_PINGROUP_ENTRY_Y(0x10a4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen2_i2c_sda_pdd0 DRV_PINGROUP_ENTRY_Y(0x10ac, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_sck_pcc0 DRV_PINGROUP_ENTRY_Y(0x10b4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_miso_pcc1 DRV_PINGROUP_ENTRY_Y(0x10bc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio49_pee2 DRV_PINGROUP_ENTRY_Y(0x10c4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio50_pee4 DRV_PINGROUP_ENTRY_Y(0x10cc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio82_pee3 DRV_PINGROUP_ENTRY_Y(0x10d4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio71_pff2 DRV_PINGROUP_ENTRY_Y(0x10dc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio76_pff7 DRV_PINGROUP_ENTRY_Y(0x10e4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio74_pff5 DRV_PINGROUP_ENTRY_Y(0x10ec, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio00_paa1 DRV_PINGROUP_ENTRY_Y(0x10f4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio19_pdd6 DRV_PINGROUP_ENTRY_Y(0x10fc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio86_phh3 DRV_PINGROUP_ENTRY_Y(0x1104, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio72_pff3 DRV_PINGROUP_ENTRY_Y(0x110c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio77_pgg0 DRV_PINGROUP_ENTRY_Y(0x1114, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio80_pff6 DRV_PINGROUP_ENTRY_Y(0x111c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio84_pgg1 DRV_PINGROUP_ENTRY_Y(0x1124, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio83_pee5 DRV_PINGROUP_ENTRY_Y(0x112c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio73_pff4 DRV_PINGROUP_ENTRY_Y(0x1134, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio70_pff1 DRV_PINGROUP_ENTRY_Y(0x113c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio04_paa5 DRV_PINGROUP_ENTRY_Y(0x1144, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio85_pgg6 DRV_PINGROUP_ENTRY_Y(0x114c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio69_pff0 DRV_PINGROUP_ENTRY_Y(0x1154, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio25_paa6 DRV_PINGROUP_ENTRY_Y(0x115c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio26_paa7 DRV_PINGROUP_ENTRY_Y(0x1164, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_tx_pgg7 DRV_PINGROUP_ENTRY_Y(0x116c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_rx_phh0 DRV_PINGROUP_ENTRY_Y(0x1174, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_tx_pgg2 DRV_PINGROUP_ENTRY_Y(0x117c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_rx_pgg3 DRV_PINGROUP_ENTRY_Y(0x1184, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_cts_pgg5 DRV_PINGROUP_ENTRY_Y(0x118c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_rts_pgg4 DRV_PINGROUP_ENTRY_Y(0x1194, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_cts_phh2 DRV_PINGROUP_ENTRY_Y(0x119c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_rts_phh1 DRV_PINGROUP_ENTRY_Y(0x11a4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm7_pee1 DRV_PINGROUP_ENTRY_Y(0x11ac, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm2_pdd7 DRV_PINGROUP_ENTRY_Y(0x11b4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm3_pee0 DRV_PINGROUP_ENTRY_Y(0x11bc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm1_paa3 DRV_PINGROUP_ENTRY_Y(0x11c4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_cs1_pcc4 DRV_PINGROUP_ENTRY_Y(0x11cc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_dmic1_clk_pdd4 DRV_PINGROUP_ENTRY_Y(0x11d4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_dmic1_dat_pdd5 DRV_PINGROUP_ENTRY_Y(0x11dc, 12, 5, 20, 5, -1, -1, -1, -1, 1) +#define drive_batt_oc_paa4 DRV_PINGROUP_ENTRY_Y(0x1024, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_bootv_ctl_n_paa0 DRV_PINGROUP_ENTRY_Y(0x102c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_vcomp_alert_paa2 DRV_PINGROUP_ENTRY_Y(0x105c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_hdmi_cec_pbb0 DRV_PINGROUP_ENTRY_Y(0x1064, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_touch_clk_pdd3 DRV_PINGROUP_ENTRY_Y(0x106c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart3_rx_pcc6 DRV_PINGROUP_ENTRY_Y(0x1074, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart3_tx_pcc5 DRV_PINGROUP_ENTRY_Y(0x107c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen8_i2c_sda_pdd2 DRV_PINGROUP_ENTRY_Y(0x1084, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen8_i2c_scl_pdd1 DRV_PINGROUP_ENTRY_Y(0x108c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_mosi_pcc2 DRV_PINGROUP_ENTRY_Y(0x1094, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen2_i2c_scl_pcc7 DRV_PINGROUP_ENTRY_Y(0x109c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_cs0_pcc3 DRV_PINGROUP_ENTRY_Y(0x10a4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen2_i2c_sda_pdd0 DRV_PINGROUP_ENTRY_Y(0x10ac, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_sck_pcc0 DRV_PINGROUP_ENTRY_Y(0x10b4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_miso_pcc1 DRV_PINGROUP_ENTRY_Y(0x10bc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio49_pee2 DRV_PINGROUP_ENTRY_Y(0x10c4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio50_pee4 DRV_PINGROUP_ENTRY_Y(0x10cc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio82_pee3 DRV_PINGROUP_ENTRY_Y(0x10d4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio71_pff2 DRV_PINGROUP_ENTRY_Y(0x10dc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio76_pff7 DRV_PINGROUP_ENTRY_Y(0x10e4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio74_pff5 DRV_PINGROUP_ENTRY_Y(0x10ec, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio00_paa1 DRV_PINGROUP_ENTRY_Y(0x10f4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio19_pdd6 DRV_PINGROUP_ENTRY_Y(0x10fc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio86_phh3 DRV_PINGROUP_ENTRY_Y(0x1104, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio72_pff3 DRV_PINGROUP_ENTRY_Y(0x110c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio77_pgg0 DRV_PINGROUP_ENTRY_Y(0x1114, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio80_pff6 DRV_PINGROUP_ENTRY_Y(0x111c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio84_pgg1 DRV_PINGROUP_ENTRY_Y(0x1124, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio83_pee5 DRV_PINGROUP_ENTRY_Y(0x112c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio73_pff4 DRV_PINGROUP_ENTRY_Y(0x1134, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio70_pff1 DRV_PINGROUP_ENTRY_Y(0x113c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio04_paa5 DRV_PINGROUP_ENTRY_Y(0x1144, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio85_pgg6 DRV_PINGROUP_ENTRY_Y(0x114c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio69_pff0 DRV_PINGROUP_ENTRY_Y(0x1154, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio25_paa6 DRV_PINGROUP_ENTRY_Y(0x115c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio26_paa7 DRV_PINGROUP_ENTRY_Y(0x1164, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_tx_pgg7 DRV_PINGROUP_ENTRY_Y(0x116c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_rx_phh0 DRV_PINGROUP_ENTRY_Y(0x1174, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_tx_pgg2 DRV_PINGROUP_ENTRY_Y(0x117c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_rx_pgg3 DRV_PINGROUP_ENTRY_Y(0x1184, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_cts_pgg5 DRV_PINGROUP_ENTRY_Y(0x118c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_rts_pgg4 DRV_PINGROUP_ENTRY_Y(0x1194, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_cts_phh2 DRV_PINGROUP_ENTRY_Y(0x119c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_rts_phh1 DRV_PINGROUP_ENTRY_Y(0x11a4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm7_pee1 DRV_PINGROUP_ENTRY_Y(0x11ac, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm2_pdd7 DRV_PINGROUP_ENTRY_Y(0x11b4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm3_pee0 DRV_PINGROUP_ENTRY_Y(0x11bc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm1_paa3 DRV_PINGROUP_ENTRY_Y(0x11c4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_cs1_pcc4 DRV_PINGROUP_ENTRY_Y(0x11cc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_dmic1_clk_pdd4 DRV_PINGROUP_ENTRY_Y(0x11d4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_dmic1_dat_pdd5 DRV_PINGROUP_ENTRY_Y(0x11dc, 12, 5, 20, 5, -1, -1, -1, -1, 0) #define drive_sdmmc1_comp DRV_PINGROUP_ENTRY_N @@ -1961,57 +1961,57 @@ static const struct tegra_pingroup tegra238_groups[] = { }; static const struct tegra_pingroup tegra238_aon_groups[] = { - PINGROUP(bootv_ctl_n_paa0, RSVD0, RSVD1, RSVD2, RSVD3, 0x1028, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio00_paa1, RSVD0, RSVD1, RSVD2, RSVD3, 0x10f0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(vcomp_alert_paa2, SOC_THERM_OC1, RSVD1, RSVD2, RSVD3, 0x1058, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm1_paa3, GP_PWM1, RSVD1, RSVD2, RSVD3, 0x11c0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(batt_oc_paa4, SOC_THERM_OC2, RSVD1, RSVD2, RSVD3, 0x1020, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio04_paa5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1140, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio25_paa6, RSVD0, RSVD1, RSVD2, RSVD3, 0x1158, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio26_paa7, RSVD0, SOC_THERM_OC3, RSVD2, RSVD3, 0x1160, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(hdmi_cec_pbb0, HDMI_CEC, RSVD1, RSVD2, RSVD3, 0x1060, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_sck_pcc0, SPI2_SCK, RSVD1, RSVD2, RSVD3, 0x10b0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_miso_pcc1, SPI2_DIN, RSVD1, RSVD2, RSVD3, 0x10b8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_mosi_pcc2, SPI2_DOUT, RSVD1, RSVD2, RSVD3, 0x1090, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_cs0_pcc3, SPI2_CS0, RSVD1, RSVD2, RSVD3, 0x10a0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_cs1_pcc4, SPI2_CS1, RSVD1, RSVD2, RSVD3, 0x11c8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(uart3_tx_pcc5, UARTC_TXD, RSVD1, RSVD2, RSVD3, 0x1078, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart3_rx_pcc6, UARTC_RXD, RSVD1, RSVD2, RSVD3, 0x1070, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen2_i2c_scl_pcc7, I2C2_CLK, RSVD1, RSVD2, RSVD3, 0x1098, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen2_i2c_sda_pdd0, I2C2_DAT, RSVD1, RSVD2, RSVD3, 0x10a8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen8_i2c_scl_pdd1, I2C8_CLK, RSVD1, RSVD2, RSVD3, 0x1088, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen8_i2c_sda_pdd2, I2C8_DAT, RSVD1, RSVD2, RSVD3, 0x1080, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(touch_clk_pdd3, GP_PWM4, TOUCH_CLK, RSVD2, RSVD3, 0x1068, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(dmic1_clk_pdd4, DMIC1_CLK, RSVD1, DMIC5_CLK, RSVD3, 0x11d0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(dmic1_dat_pdd5, DMIC1_DAT, RSVD1, DMIC5_DAT, RSVD3, 0x11d8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio19_pdd6, RSVD0, WDT_RESET_OUTB, RSVD2, RSVD3, 0x10f8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio49_pee2, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio50_pee4, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio82_pee3, RSVD0, RSVD1, RSVD2, RSVD3, 0x10d0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio71_pff2, PPC_MODE_1, RSVD1, RSVD2, RSVD3, 0x10d8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio76_pff7, RSVD0, RSVD1, TSC_EDGE_OUT0, TSC_EDGE_OUT0A, 0x10e0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio74_pff5, PPC_READY, PPC_I2C_DAT, RSVD2, RSVD3, 0x10e8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio86_phh3, RSVD0, SPI5_CS1, TSC_EDGE_OUT3, TSC_EDGE_OUT0D, 0x1100, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio72_pff3, PPC_MODE_2, RSVD1, RSVD2, RSVD3, 0x1108, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio77_pgg0, RSVD0, RSVD1, TSC_EDGE_OUT1, TSC_EDGE_OUT0B, 0x1110, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio80_pff6, RSVD0, PPC_RST_N, RSVD2, RSVD3, 0x1118, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio84_pgg1, RSVD0, RSVD1, TSC_EDGE_OUT2, TSC_EDGE_OUT0C, 0x1120, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio83_pee5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1128, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio73_pff4, PPC_CC, PPC_I2C_CLK, RSVD2, RSVD3, 0x1130, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio70_pff1, PPC_MODE_0, RSVD1, RSVD2, RSVD3, 0x1138, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio85_pgg6, RSVD0, SPI4_CS1, RSVD2, RSVD3, 0x1148, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio69_pff0, PPC_INT_N, RSVD1, RSVD2, RSVD3, 0x1150, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_tx_pgg7, UARTE_TXD, SPI5_SCK, RSVD2, RSVD3, 0x1168, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_rx_phh0, UARTE_RXD, SPI5_MISO, RSVD2, RSVD3, 0x1170, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_tx_pgg2, UARTB_TXD, SPI4_SCK, RSVD2, RSVD3, 0x1178, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_rx_pgg3, UARTB_RXD, SPI4_MISO, RSVD2, RSVD3, 0x1180, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_cts_pgg5, UARTB_CTS, SPI4_CS0, RSVD2, RSVD3, 0x1188, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_rts_pgg4, UARTB_RTS, SPI4_MOSI, RSVD2, RSVD3, 0x1190, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_cts_phh2, UARTE_CTS, SPI5_CS0, RSVD2, RSVD3, 0x1198, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_rts_phh1, UARTE_RTS, SPI5_MOSI, RSVD2, RSVD3, 0x11a0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm2_pdd7, GP_PWM2, LED_BLINK, RSVD2, RSVD3, 0x11b0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm3_pee0, GP_PWM3, RSVD1, RSVD2, RSVD3, 0x11b8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm7_pee1, GP_PWM7, RSVD1, RSVD2, RSVD3, 0x11a8, 1, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(bootv_ctl_n_paa0, RSVD0, RSVD1, RSVD2, RSVD3, 0x1028, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio00_paa1, RSVD0, RSVD1, RSVD2, RSVD3, 0x10f0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(vcomp_alert_paa2, SOC_THERM_OC1, RSVD1, RSVD2, RSVD3, 0x1058, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm1_paa3, GP_PWM1, RSVD1, RSVD2, RSVD3, 0x11c0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(batt_oc_paa4, SOC_THERM_OC2, RSVD1, RSVD2, RSVD3, 0x1020, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio04_paa5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1140, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio25_paa6, RSVD0, RSVD1, RSVD2, RSVD3, 0x1158, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio26_paa7, RSVD0, SOC_THERM_OC3, RSVD2, RSVD3, 0x1160, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(hdmi_cec_pbb0, HDMI_CEC, RSVD1, RSVD2, RSVD3, 0x1060, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_sck_pcc0, SPI2_SCK, RSVD1, RSVD2, RSVD3, 0x10b0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_miso_pcc1, SPI2_DIN, RSVD1, RSVD2, RSVD3, 0x10b8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_mosi_pcc2, SPI2_DOUT, RSVD1, RSVD2, RSVD3, 0x1090, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_cs0_pcc3, SPI2_CS0, RSVD1, RSVD2, RSVD3, 0x10a0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_cs1_pcc4, SPI2_CS1, RSVD1, RSVD2, RSVD3, 0x11c8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(uart3_tx_pcc5, UARTC_TXD, RSVD1, RSVD2, RSVD3, 0x1078, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart3_rx_pcc6, UARTC_RXD, RSVD1, RSVD2, RSVD3, 0x1070, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen2_i2c_scl_pcc7, I2C2_CLK, RSVD1, RSVD2, RSVD3, 0x1098, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen2_i2c_sda_pdd0, I2C2_DAT, RSVD1, RSVD2, RSVD3, 0x10a8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen8_i2c_scl_pdd1, I2C8_CLK, RSVD1, RSVD2, RSVD3, 0x1088, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen8_i2c_sda_pdd2, I2C8_DAT, RSVD1, RSVD2, RSVD3, 0x1080, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(touch_clk_pdd3, GP_PWM4, TOUCH_CLK, RSVD2, RSVD3, 0x1068, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(dmic1_clk_pdd4, DMIC1_CLK, RSVD1, DMIC5_CLK, RSVD3, 0x11d0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(dmic1_dat_pdd5, DMIC1_DAT, RSVD1, DMIC5_DAT, RSVD3, 0x11d8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio19_pdd6, RSVD0, WDT_RESET_OUTB, RSVD2, RSVD3, 0x10f8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio49_pee2, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio50_pee4, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio82_pee3, RSVD0, RSVD1, RSVD2, RSVD3, 0x10d0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio71_pff2, PPC_MODE_1, RSVD1, RSVD2, RSVD3, 0x10d8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio76_pff7, RSVD0, RSVD1, TSC_EDGE_OUT0, TSC_EDGE_OUT0A, 0x10e0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio74_pff5, PPC_READY, PPC_I2C_DAT, RSVD2, RSVD3, 0x10e8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio86_phh3, RSVD0, SPI5_CS1, TSC_EDGE_OUT3, TSC_EDGE_OUT0D, 0x1100, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio72_pff3, PPC_MODE_2, RSVD1, RSVD2, RSVD3, 0x1108, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio77_pgg0, RSVD0, RSVD1, TSC_EDGE_OUT1, TSC_EDGE_OUT0B, 0x1110, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio80_pff6, RSVD0, PPC_RST_N, RSVD2, RSVD3, 0x1118, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio84_pgg1, RSVD0, RSVD1, TSC_EDGE_OUT2, TSC_EDGE_OUT0C, 0x1120, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio83_pee5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1128, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio73_pff4, PPC_CC, PPC_I2C_CLK, RSVD2, RSVD3, 0x1130, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio70_pff1, PPC_MODE_0, RSVD1, RSVD2, RSVD3, 0x1138, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio85_pgg6, RSVD0, SPI4_CS1, RSVD2, RSVD3, 0x1148, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio69_pff0, PPC_INT_N, RSVD1, RSVD2, RSVD3, 0x1150, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_tx_pgg7, UARTE_TXD, SPI5_SCK, RSVD2, RSVD3, 0x1168, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_rx_phh0, UARTE_RXD, SPI5_MISO, RSVD2, RSVD3, 0x1170, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_tx_pgg2, UARTB_TXD, SPI4_SCK, RSVD2, RSVD3, 0x1178, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_rx_pgg3, UARTB_RXD, SPI4_MISO, RSVD2, RSVD3, 0x1180, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_cts_pgg5, UARTB_CTS, SPI4_CS0, RSVD2, RSVD3, 0x1188, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_rts_pgg4, UARTB_RTS, SPI4_MOSI, RSVD2, RSVD3, 0x1190, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_cts_phh2, UARTE_CTS, SPI5_CS0, RSVD2, RSVD3, 0x1198, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_rts_phh1, UARTE_RTS, SPI5_MOSI, RSVD2, RSVD3, 0x11a0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm2_pdd7, GP_PWM2, LED_BLINK, RSVD2, RSVD3, 0x11b0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm3_pee0, GP_PWM3, RSVD1, RSVD2, RSVD3, 0x11b8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm7_pee1, GP_PWM7, RSVD1, RSVD2, RSVD3, 0x11a8, 0, Y, 5, 7, 6, 8, -1, 10, 12), }; static const struct tegra_pinctrl_soc_data tegra238_pinctrl_aon = { From ada667890773e033d2f40dc94176e3beb930b516 Mon Sep 17 00:00:00 2001 From: Li Youhong Date: Fri, 4 Sep 2026 09:49:58 +0800 Subject: [PATCH 0915/1417] drm/bridge: samsung-dsim: fix TE GPIO lifetime for host attach When the Exynos DSI driver was generalized into samsung-dsim, the TE GPIO acquisition was switched from gpiod_get_optional() to devm_gpiod_get_optional() while keeping the matching gpiod_put() calls. That combination is wrong for a managed descriptor. However, dropping the puts and keeping the managed get is also wrong: samsung_dsim_register_te_irq() runs from the DSI host attach callback, and host detach/reattach can happen without destroying the device that owns the managed action. A second attach would then request the GPIO again without having released it. Switch back to a non-managed gpiod_get_optional() and keep the explicit gpiod_put() on the request_irq() error path and in samsung_dsim_unregister_te_irq(). Fixes: e7447128ca4a ("drm: bridge: Generalize Exynos-DSI driver into a Samsung DSIM bridge") Suggested-by: Luca Ceresoli Signed-off-by: Li Youhong Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260904014958.1572918-1-dayou5941@163.com [Luca: remove unnecessary comment] Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/samsung-dsim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c index e2fc69fc51b60d..4694241f4d2295 100644 --- a/drivers/gpu/drm/bridge/samsung-dsim.c +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -1862,7 +1862,7 @@ static int samsung_dsim_register_te_irq(struct samsung_dsim *dsi, struct device int te_gpio_irq; int ret; - dsi->te_gpio = devm_gpiod_get_optional(dev, "te", GPIOD_IN); + dsi->te_gpio = gpiod_get_optional(dev, "te", GPIOD_IN); if (!dsi->te_gpio) return 0; else if (IS_ERR(dsi->te_gpio)) From d395e1b62d9694f2e4add9122f8d473eacd374aa Mon Sep 17 00:00:00 2001 From: Daniel Ortiz Date: Sat, 19 Sep 2026 20:19:20 -0500 Subject: [PATCH 0916/1417] ASoC: amd: yc: Add DMI quirk for Lenovo V15 G6 ARP The Lenovo V15 G6 ARP (product name 83UU) uses the ACP6x PDM interface for its internal digital microphone array. Its firmware exposes neither the AcpDmicConnected _DSD property nor the _WOV method, so acp6x_probe() falls through to the DMI check and returns -ENODEV, leaving acp_yc_mach.0 unbound and no capture device registered. Add a DMI quirk entry for this model. With it, the machine driver binds and the DMIC capture device is created: acp_yc_mach acp_yc_mach.0: Enabling ACP DMIC support via DMI card 2: acp6x [acp6x], device 0: DMIC capture dmic-hifi-0 Capture was verified working with arecord and PipeWire. Signed-off-by: Daniel Ortiz Link: https://patch.msgid.link/20260920011920.2482786-1-letargopausado@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index ea173bdab1d55b..e798bf6a9cd065 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -388,6 +388,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "83Q3"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83UU"), + } + }, { .driver_data = &acp6x_card, .matches = { From 7a6d08ee0f0e30023d18779bb314db8fd9a3b6d4 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 16:50:22 +0200 Subject: [PATCH 0917/1417] ovpn: preserve IPv6 scope id for netlink peer endpoints ovpn accepts OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID and reports bind->remote.in6.sin6_scope_id in peer dumps, but the netlink endpoint parser never copied the attribute into the sockaddr_in6 used to create or update the peer bind. As a result, an IPv6 link-local remote endpoint configured through netlink loses its interface scope, unlike on the peer float path where ipv6_iface_scope_id populates the field. The UDPv6 output path then builds a flow with flowi6_oif set to zero and route lookup can fail or select the wrong interface. Copy the scope id when parsing non-v4-mapped IPv6 remote endpoints. The existing precheck already rejects the scope-id attribute for IPv4 and v4-mapped IPv6 remotes. Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/netlink.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 4dad852941982e..2ba762082acc1b 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -100,6 +100,8 @@ static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs, struct sockaddr_in6 *sin6; struct sockaddr_in *sin; struct in6_addr *in6; + struct nlattr *scope; + u32 scope_id = 0; __be16 port = 0; __be32 *in; @@ -114,6 +116,9 @@ static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs, } else if (attrs[OVPN_A_PEER_REMOTE_IPV6]) { ss->ss_family = AF_INET6; in6 = nla_data(attrs[OVPN_A_PEER_REMOTE_IPV6]); + scope = attrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]; + if (scope) + scope_id = nla_get_u32(scope); } else { return false; } @@ -126,6 +131,7 @@ static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs, if (!ipv6_addr_v4mapped(in6)) { sin6 = (struct sockaddr_in6 *)ss; sin6->sin6_port = port; + sin6->sin6_scope_id = scope_id; memcpy(&sin6->sin6_addr, in6, sizeof(*in6)); break; } From 77393b4d72dfeb764b2af2b848acc659f6fcfd0a Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 16:50:23 +0200 Subject: [PATCH 0918/1417] ovpn: skip UDP source validation for unspecified addresses ovpn validates the cached local UDP source address before reusing or refreshing a peer dst cache. This is only meaningful when a concrete source address is selected. For IPv6, calling ipv6_chk_addr with :: checks whether the unspecified address itself is configured on the host. A peer may legitimately have bind->local.ipv6 set to :: when no local endpoint was configured or after a stale learned address was cleared. In that case the source should be left unspecified and selected by ip6_dst_lookup_flow(). For IPv4, inet_confirm_addr(..., local = 0, ...) asks for local address autoselection rather than validating a chosen source. Skip the precheck there as well and let ip_route_output_flow select or reject the source. Only validate non-zero/non-any source addresses. Fixes: 08857b5ec5d9 ("ovpn: implement basic TX path (UDP)") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/udp.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index 7f69e8890b5b5c..df4750dabd1e19 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -161,8 +161,8 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, if (rt) goto transmit; - if (unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0, fl.saddr, - RT_SCOPE_HOST))) { + if (fl.saddr && unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0, + fl.saddr, RT_SCOPE_HOST))) { /* we may end up here when the cached address is not usable * anymore. In this case we reset address/cache and perform a * new look up @@ -238,7 +238,8 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, if (dst) goto transmit; - if (unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) { + if (!ipv6_addr_any(&fl.saddr) && + unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) { /* we may end up here when the cached address is not usable * anymore. In this case we reset address/cache and perform a * new look up From 7c66b7a4ae80a9309e6dc1d24b7b6b897e6348eb Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 16:50:24 +0200 Subject: [PATCH 0919/1417] ovpn: track UDP socket route key for peer dst cache ovpn stores the route used to transmit UDP packets in a per-peer dst cache. A cached dst is only valid for the route lookup inputs used when it was resolved. Some of those inputs are mutable while userspace still owns the UDP socket. In particular, changes to the socket mark or UDP source port do not invalidate ovpn's peer dst cache, so ovpn can keep using a route selected with an old socket route key. Replace the cached mark with a route key containing the socket-owned lookup inputs currently used by ovpn, and reset the peer dst cache when the key changes. Before storing a newly looked-up dst, recheck the route key under the peer lock so a dst resolved for stale socket state is not published. Fixes: 08857b5ec5d9 ("ovpn: implement basic TX path (UDP)") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 1 + drivers/net/ovpn/peer.h | 19 ++++++++- drivers/net/ovpn/udp.c | 90 +++++++++++++++++++++++++++++++++++------ 3 files changed, 95 insertions(+), 15 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index c95656ca7c3571..b400783c2efabf 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -113,6 +113,7 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id) RCU_INIT_POINTER(peer->bind, NULL); ovpn_crypto_state_init(&peer->crypto); spin_lock_init(&peer->lock); + seqcount_spinlock_init(&peer->route_key_seq, &peer->lock); kref_init(&peer->refcount); ovpn_peer_stats_init(&peer->vpn_stats); ovpn_peer_stats_init(&peer->link_stats); diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index dfa5c0037e02b0..063535699ecd85 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -10,6 +10,7 @@ #ifndef _NET_OVPN_OVPNPEER_H_ #define _NET_OVPN_OVPNPEER_H_ +#include #include #include @@ -17,6 +18,16 @@ #include "socket.h" #include "stats.h" +/** + * struct ovpn_route_key - route key used for the peer dst cache + * @mark: fwmark used for route lookup + * @sport: UDP source port used for route lookup + */ +struct ovpn_route_key { + u32 mark; + __be16 sport; +}; + /** * struct ovpn_peer - the main remote peer object * @ovpn: main openvpn instance this peer belongs to @@ -45,6 +56,8 @@ * @tcp.sk_cb.ops: pointer to the original prot_ops object (TCP only) * @crypto: the crypto configuration (ciphers, keys, etc..) * @dst_cache: cache for dst_entry used to send to peer + * @route_key: route key matching the current dst cache contents + * @route_key_seq: seqcount protecting lockless route_key reads * @bind: remote peer binding * @keepalive_interval: seconds after which a new keepalive should be sent * @keepalive_xmit_exp: future timestamp when next keepalive should be sent @@ -55,7 +68,7 @@ * @vpn_stats: per-peer in-VPN TX/RX stats * @link_stats: per-peer link/transport TX/RX stats * @delete_reason: why peer was deleted (i.e. timeout, transport error, ..) - * @lock: protects binding to peer (bind) and keepalive* fields + * @lock: protects binding to peer (bind), route_key and keepalive* fields * @refcount: reference counter * @rcu: used to free peer in an RCU safe way * @release_entry: entry for the socket release list @@ -99,6 +112,8 @@ struct ovpn_peer { } tcp; struct ovpn_crypto_state crypto; struct dst_cache dst_cache; + struct ovpn_route_key route_key; + seqcount_spinlock_t route_key_seq; struct ovpn_bind __rcu *bind; unsigned long keepalive_interval; unsigned long keepalive_xmit_exp; @@ -109,7 +124,7 @@ struct ovpn_peer { struct ovpn_peer_stats vpn_stats; struct ovpn_peer_stats link_stats; enum ovpn_del_peer_reason delete_reason; - spinlock_t lock; /* protects bind and keepalive* */ + spinlock_t lock; /* protects bind, route_key and keepalive* */ struct kref refcount; struct rcu_head rcu; struct llist_node release_entry; diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index df4750dabd1e19..c6d591cb7ff456 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -131,6 +131,48 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb) return 0; } +static bool ovpn_route_key_equal(const struct ovpn_route_key *a, + const struct ovpn_route_key *b) +{ + return a->mark == b->mark && a->sport == b->sport; +} + +/** + * ovpn_dst_cache_check_key - reset peer dst cache after key changes + * @peer: the peer owning the dst cache + * @cache: the cache that might need to be reset + * @key: the route key for the packet being transmitted + * + * Reset the peer dst cache if it was populated for a different route key. + */ +static void ovpn_dst_cache_check_key(struct ovpn_peer *peer, + struct dst_cache *cache, + const struct ovpn_route_key *key) +{ + struct ovpn_route_key old_key; + unsigned int seq; + + /* snapshot the saved key before deciding whether the cache matches */ + do { + seq = read_seqcount_begin(&peer->route_key_seq); + old_key = peer->route_key; + } while (read_seqcount_retry(&peer->route_key_seq, seq)); + + /* nothing changed: the current cache can be reused */ + if (likely(ovpn_route_key_equal(&old_key, key))) + return; + + /* recheck under lock because another path may have updated the key */ + spin_lock_bh(&peer->lock); + if (!ovpn_route_key_equal(&peer->route_key, key)) { + write_seqcount_begin(&peer->route_key_seq); + peer->route_key = *key; + dst_cache_reset(cache); + write_seqcount_end(&peer->route_key_seq); + } + spin_unlock_bh(&peer->lock); +} + /** * ovpn_udp4_output - send IPv4 packet over udp socket * @peer: the destination peer @@ -138,21 +180,23 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb) * @cache: dst cache * @sk: the socket to send the packet over * @skb: the packet to send + * @key: the route key snapshot used for cache validation and flow lookup * * Return: 0 on success or a negative error code otherwise */ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct dst_cache *cache, struct sock *sk, - struct sk_buff *skb) + struct sk_buff *skb, + const struct ovpn_route_key *key) { struct rtable *rt; struct flowi4 fl = { .saddr = bind->local.ipv4.s_addr, .daddr = bind->remote.in4.sin_addr.s_addr, - .fl4_sport = inet_sk(sk)->inet_sport, + .fl4_sport = key->sport, .fl4_dport = bind->remote.in4.sin_port, .flowi4_proto = sk->sk_protocol, - .flowi4_mark = sk->sk_mark, + .flowi4_mark = key->mark, }; int ret; @@ -193,7 +237,12 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, ret); goto err; } - dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + + /* avoid storing a stale cache */ + spin_lock_bh(&peer->lock); + if (likely(ovpn_route_key_equal(key, &peer->route_key))) + dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + spin_unlock_bh(&peer->lock); transmit: udp_tunnel_xmit_skb(rt, sk, skb, fl.saddr, fl.daddr, 0, @@ -213,12 +262,14 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, * @cache: dst cache * @sk: the socket to send the packet over * @skb: the packet to send + * @key: the route key snapshot used for cache validation and flow lookup * * Return: 0 on success or a negative error code otherwise */ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct dst_cache *cache, struct sock *sk, - struct sk_buff *skb) + struct sk_buff *skb, + const struct ovpn_route_key *key) { struct dst_entry *dst; int ret; @@ -226,10 +277,10 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct flowi6 fl = { .saddr = bind->local.ipv6, .daddr = bind->remote.in6.sin6_addr, - .fl6_sport = inet_sk(sk)->inet_sport, + .fl6_sport = key->sport, .fl6_dport = bind->remote.in6.sin6_port, .flowi6_proto = sk->sk_protocol, - .flowi6_mark = sk->sk_mark, + .flowi6_mark = key->mark, .flowi6_oif = bind->remote.in6.sin6_scope_id, }; @@ -259,7 +310,12 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, &bind->remote.in6, ret); goto err; } - dst_cache_set_ip6(cache, dst, &fl.saddr); + + /* avoid storing a stale cache */ + spin_lock_bh(&peer->lock); + if (likely(ovpn_route_key_equal(key, &peer->route_key))) + dst_cache_set_ip6(cache, dst, &fl.saddr); + spin_unlock_bh(&peer->lock); transmit: /* user IPv6 packets may be larger than the transport interface @@ -288,6 +344,7 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, * @cache: dst cache * @sk: the socket to send the packet over * @skb: the packet to send + * @key: route key snapshot used for cache validation and flow lookup * * rcu_read_lock should be held on entry. * On return, the skb is consumed. @@ -295,7 +352,8 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, * Return: 0 on success or a negative error code otherwise */ static int ovpn_udp_output(struct ovpn_peer *peer, struct dst_cache *cache, - struct sock *sk, struct sk_buff *skb) + struct sock *sk, struct sk_buff *skb, + struct ovpn_route_key *key) { struct ovpn_bind *bind; int ret; @@ -315,11 +373,11 @@ static int ovpn_udp_output(struct ovpn_peer *peer, struct dst_cache *cache, switch (bind->remote.in4.sin_family) { case AF_INET: - ret = ovpn_udp4_output(peer, bind, cache, sk, skb); + ret = ovpn_udp4_output(peer, bind, cache, sk, skb, key); break; #if IS_ENABLED(CONFIG_IPV6) case AF_INET6: - ret = ovpn_udp6_output(peer, bind, cache, sk, skb); + ret = ovpn_udp6_output(peer, bind, cache, sk, skb, key); break; #endif default: @@ -341,15 +399,21 @@ static int ovpn_udp_output(struct ovpn_peer *peer, struct dst_cache *cache, void ovpn_udp_send_skb(struct ovpn_peer *peer, struct sock *sk, struct sk_buff *skb) { + struct ovpn_route_key key = { + .mark = READ_ONCE(sk->sk_mark), + .sport = READ_ONCE(inet_sk(sk)->inet_sport), + }; int ret; skb->dev = peer->ovpn->dev; - skb->mark = READ_ONCE(sk->sk_mark); + skb->mark = key.mark; /* no checksum performed at this layer */ skb->ip_summed = CHECKSUM_NONE; + ovpn_dst_cache_check_key(peer, &peer->dst_cache, &key); + /* crypto layer -> transport (UDP) */ - ret = ovpn_udp_output(peer, &peer->dst_cache, sk, skb); + ret = ovpn_udp_output(peer, &peer->dst_cache, sk, skb, &key); if (unlikely(ret < 0)) kfree_skb(skb); } From fa603710bdb9aea33c0d9cc2c05ed24d84f58753 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 16:50:25 +0200 Subject: [PATCH 0920/1417] ovpn: validate peer state before caching UDP dst UDP route lookup runs without peer->lock while the bind is protected by RCU. The route key is snapshotted separately. Either can change while the lookup is in progress. The TX path currently checks only the route key before publishing the looked-up dst. If the bind changes but the route key does not, a dst resolved from the old endpoint can be installed in the cache after the bind replacement. Compare both the bind pointer and the route key under peer->lock before updating the cache. The RCU read-side critical section keeps the old bind alive throughout the lookup, so pointer identity is sufficient to detect a replacement. Fixes: f0281c1d3732 ("ovpn: add support for updating local or remote UDP endpoint") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/udp.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index c6d591cb7ff456..eeef4a7229f5b5 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -173,6 +173,35 @@ static void ovpn_dst_cache_check_key(struct ovpn_peer *peer, spin_unlock_bh(&peer->lock); } +/** + * ovpn_dst_cache_current - check whether a route lookup matches peer state + * @peer: the peer owning the bind and dst cache + * @bind: the RCU bind used for the route lookup + * @key: the route key used for the route lookup + * + * Check that @bind is still the current peer bind and that @key still matches + * the peer route key. The caller must hold @peer->lock. The TX path keeps + * @bind inside an RCU read-side critical section, so pointer identity is enough + * to detect whether the bind was replaced while the route lookup was running. + * + * Return: true if the lookup result still matches the current peer state and + * may update the dst cache. + */ +static bool ovpn_dst_cache_current(const struct ovpn_peer *peer, + const struct ovpn_bind *bind, + const struct ovpn_route_key *key) +{ + const struct ovpn_bind *curr_bind; + + lockdep_assert_held(&peer->lock); + + curr_bind = rcu_dereference_protected(peer->bind, + lockdep_is_held(&peer->lock)); + + return curr_bind == bind && + ovpn_route_key_equal(key, &peer->route_key); +} + /** * ovpn_udp4_output - send IPv4 packet over udp socket * @peer: the destination peer @@ -240,7 +269,7 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, /* avoid storing a stale cache */ spin_lock_bh(&peer->lock); - if (likely(ovpn_route_key_equal(key, &peer->route_key))) + if (likely(ovpn_dst_cache_current(peer, bind, key))) dst_cache_set_ip4(cache, &rt->dst, fl.saddr); spin_unlock_bh(&peer->lock); @@ -313,7 +342,7 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, /* avoid storing a stale cache */ spin_lock_bh(&peer->lock); - if (likely(ovpn_route_key_equal(key, &peer->route_key))) + if (likely(ovpn_dst_cache_current(peer, bind, key))) dst_cache_set_ip6(cache, dst, &fl.saddr); spin_unlock_bh(&peer->lock); From aea934a221ec6a867221e5b765f65f1857befd53 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 16:50:26 +0200 Subject: [PATCH 0921/1417] ovpn: replace bind when learning local endpoint struct ovpn_bind is published through peer->bind with RCU, but local endpoint learning updates bind->local in place under peer->lock. UDP TX reads the field without that lock. In particular, a concurrent IPv6 update can therefore result in a torn address read. Use ovpn_peer_reset_sockaddr to publish a replacement bind when learning a new local endpoint, just as a remote endpoint change does. Preserve the current remote address and reset the dst cache only after the new bind has been published successfully. Track remote endpoint changes separately so that float notification and transport-address rehashing remain limited to actual peer floats. Fixes: f0281c1d3732 ("ovpn: add support for updating local or remote UDP endpoint") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 43 ++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index b400783c2efabf..430c6cd48db874 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -200,13 +200,12 @@ static void __ovpn_peer_hash_transp_addr(struct ovpn_peer *peer, */ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) { + const void *local_ip = NULL; struct sockaddr_storage ss; struct sockaddr_in6 *sa6; - bool reset_cache = false; struct sockaddr_in *sa; struct ovpn_bind *bind; - const void *local_ip; - size_t salen = 0; + bool floated = false; spin_lock_bh(&peer->lock); bind = rcu_dereference_protected(peer->bind, @@ -233,8 +232,7 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) .sin_addr.s_addr = ip_hdr(skb)->saddr, .sin_port = udp_hdr(skb)->source, }; - salen = sizeof(*sa); - reset_cache = true; + floated = true; break; } @@ -246,10 +244,12 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) netdev_name(peer->ovpn->dev), peer->id, &bind->local.ipv4.s_addr, &ip_hdr(skb)->daddr); - bind->local.ipv4.s_addr = ip_hdr(skb)->daddr; - reset_cache = true; + local_ip = &ip_hdr(skb)->daddr; + memcpy(&ss, &bind->remote, sizeof(struct sockaddr_in)); + break; } - break; + /* nothing changed */ + goto unlock; case htons(ETH_P_IPV6): /* float check */ if (unlikely(!ovpn_bind_skb_src_match(bind, skb))) { @@ -271,8 +271,7 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) ipv6_iface_scope_id(&ipv6_hdr(skb)->saddr, skb->skb_iif), }; - salen = sizeof(*sa6); - reset_cache = true; + floated = true; break; } @@ -285,26 +284,30 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) netdev_name(peer->ovpn->dev), peer->id, &bind->local.ipv6, &ipv6_hdr(skb)->daddr); - bind->local.ipv6 = ipv6_hdr(skb)->daddr; - reset_cache = true; + local_ip = &ipv6_hdr(skb)->daddr; + memcpy(&ss, &bind->remote, sizeof(struct sockaddr_in6)); + break; } - break; + /* nothing changed */ + goto unlock; default: goto unlock; } - if (unlikely(reset_cache)) - dst_cache_reset(&peer->dst_cache); - - /* if the peer did not float, we can bail out now */ - if (likely(!salen)) - goto unlock; - if (unlikely(ovpn_peer_reset_sockaddr(peer, (struct sockaddr_storage *)&ss, local_ip) < 0)) goto unlock; + /* reset the cache only after a successful bind update to avoid useless + * cache misses on concurrent TX + */ + dst_cache_reset(&peer->dst_cache); + + /* if only the local address changed, bail out now */ + if (!floated) + goto unlock; + net_dbg_ratelimited("%s: peer %d floated to %pIScp", netdev_name(peer->ovpn->dev), peer->id, &ss); From 7d8104988f423572df1f3347ce578037b1043f34 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 16:50:27 +0200 Subject: [PATCH 0922/1417] ovpn: replace bind when clearing stale local source The UDP output fallback clears bind->local in place when the remembered source address is no longer usable. The bind is RCU-published and read locklessly by concurrent TX, so an IPv6 reader can observe a torn address. Retry the route lookup with source address autoselection without modifying the bind. After a successful lookup, revalidate the bind and route key under peer->lock, reset the dst cache, and best-effort publish a replacement bind with a wildcard local address. Do not cache the resolved dst when clearing the local source. Replacing the source invalidates all per-CPU cache entries, while dst_cache_set_ip4 and dst_cache_set_ip6 update only the current CPU slot. The current packet can still use the resolved route; if bind allocation fails, a later cache miss retries the repair. Fixes: 08857b5ec5d9 ("ovpn: implement basic TX path (UDP)") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/udp.c | 81 +++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index eeef4a7229f5b5..055cdb1bee13b0 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -185,7 +185,7 @@ static void ovpn_dst_cache_check_key(struct ovpn_peer *peer, * to detect whether the bind was replaced while the route lookup was running. * * Return: true if the lookup result still matches the current peer state and - * may update the dst cache. + * may update the dst cache or replace the bind. */ static bool ovpn_dst_cache_current(const struct ovpn_peer *peer, const struct ovpn_bind *bind, @@ -218,6 +218,9 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct sk_buff *skb, const struct ovpn_route_key *key) { + struct sockaddr_storage remote; + struct in_addr local = {}; + bool reset_local = false; struct rtable *rt; struct flowi4 fl = { .saddr = bind->local.ipv4.s_addr, @@ -236,24 +239,17 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, if (fl.saddr && unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0, fl.saddr, RT_SCOPE_HOST))) { - /* we may end up here when the cached address is not usable - * anymore. In this case we reset address/cache and perform a - * new look up + /* The learned local address is not usable anymore. + * Retry with source address autoselection. */ fl.saddr = 0; - spin_lock_bh(&peer->lock); - bind->local.ipv4.s_addr = 0; - spin_unlock_bh(&peer->lock); - dst_cache_reset(cache); + reset_local = true; } rt = ip_route_output_flow(sock_net(sk), &fl, sk); if (IS_ERR(rt) && PTR_ERR(rt) == -EINVAL) { fl.saddr = 0; - spin_lock_bh(&peer->lock); - bind->local.ipv4.s_addr = 0; - spin_unlock_bh(&peer->lock); - dst_cache_reset(cache); + reset_local = true; rt = ip_route_output_flow(sock_net(sk), &fl, sk); } @@ -267,10 +263,28 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, goto err; } - /* avoid storing a stale cache */ + /* avoid storing a stale cache or local address */ spin_lock_bh(&peer->lock); - if (likely(ovpn_dst_cache_current(peer, bind, key))) - dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + if (likely(ovpn_dst_cache_current(peer, bind, key))) { + if (!reset_local) { + dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + spin_unlock_bh(&peer->lock); + goto transmit; + } + + /* invalidate per-CPU dst entries that may still carry + * the stale source + */ + dst_cache_reset(cache); + + /* preserve the current remote */ + memcpy(&remote, &bind->remote, sizeof(struct sockaddr_in)); + /* The current packet already has a valid wildcard-source route. + * If replacing the bind fails, leave the stale local in place; + * a later cache miss will retry the repair. + */ + ovpn_peer_reset_sockaddr(peer, &remote, &local); + } spin_unlock_bh(&peer->lock); transmit: @@ -300,6 +314,9 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct sk_buff *skb, const struct ovpn_route_key *key) { + struct in6_addr local = in6addr_any; + struct sockaddr_storage remote; + bool reset_local = false; struct dst_entry *dst; int ret; @@ -320,15 +337,11 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, if (!ipv6_addr_any(&fl.saddr) && unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) { - /* we may end up here when the cached address is not usable - * anymore. In this case we reset address/cache and perform a - * new look up + /* The learned local address is not usable anymore. + * Retry with source address autoselection. */ fl.saddr = in6addr_any; - spin_lock_bh(&peer->lock); - bind->local.ipv6 = in6addr_any; - spin_unlock_bh(&peer->lock); - dst_cache_reset(cache); + reset_local = true; } dst = ip6_dst_lookup_flow(sock_net(sk), sk, &fl, NULL); @@ -340,10 +353,28 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, goto err; } - /* avoid storing a stale cache */ + /* avoid storing a stale cache or local address */ spin_lock_bh(&peer->lock); - if (likely(ovpn_dst_cache_current(peer, bind, key))) - dst_cache_set_ip6(cache, dst, &fl.saddr); + if (likely(ovpn_dst_cache_current(peer, bind, key))) { + if (!reset_local) { + dst_cache_set_ip6(cache, dst, &fl.saddr); + spin_unlock_bh(&peer->lock); + goto transmit; + } + + /* invalidate per-CPU dst entries that may still carry + * the stale source + */ + dst_cache_reset(cache); + + /* preserve the current remote */ + memcpy(&remote, &bind->remote, sizeof(struct sockaddr_in6)); + /* The current packet already has a valid wildcard-source route. + * If replacing the bind fails, leave the stale local in place; + * a later cache miss will retry the repair. + */ + ovpn_peer_reset_sockaddr(peer, &remote, &local); + } spin_unlock_bh(&peer->lock); transmit: From b43beccb3713fafada57814b0a652f4a876eb75f Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 15:00:06 +0200 Subject: [PATCH 0923/1417] ovpn: always unhash old VPN addresses before rehashing ovpn_peer_hash_vpn_ip updates the per-peer VPN address hash entries after userspace changes a peer VPN address. The current code removes an old hash entry only when the new address for that family is not the unspecified address. When an address is cleared to 0.0.0.0 or ::, its hash node therefore remains linked in the bucket selected by the old address. The address comparison performed during lookup prevents the old address from matching, but the table retains a stale entry until the peer is removed or another address is configured for that family. Always remove both old VPN address hash entries before conditionally adding the currently configured addresses back. This ensures that a cleared address leaves its hash node unhashed. Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 430c6cd48db874..bbd9e17fb0bfa1 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -994,10 +994,11 @@ void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer) if (hlist_unhashed(&peer->hash_entry_id)) return; - if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) { - /* remove potential old hashing */ - hlist_nulls_del_init_rcu(&peer->hash_entry_addr4); + /* remove potential old hashing */ + hlist_nulls_del_init_rcu(&peer->hash_entry_addr4); + hlist_nulls_del_init_rcu(&peer->hash_entry_addr6); + if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) { nhead = ovpn_get_hash_head(peer->ovpn->peers->by_vpn_addr4, &peer->vpn_addrs.ipv4, sizeof(peer->vpn_addrs.ipv4)); @@ -1005,9 +1006,6 @@ void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer) } if (!ipv6_addr_any(&peer->vpn_addrs.ipv6)) { - /* remove potential old hashing */ - hlist_nulls_del_init_rcu(&peer->hash_entry_addr6); - nhead = ovpn_get_hash_head(peer->ovpn->peers->by_vpn_addr6, &peer->vpn_addrs.ipv6, sizeof(peer->vpn_addrs.ipv6)); From d25e885b31a0f2808d936f95c9a558a8a792669b Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 15:00:07 +0200 Subject: [PATCH 0924/1417] ovpn: reject duplicate peer VPN addresses In MP mode, ovpn uses the peer VPN addresses as lookup keys for selecting the peer that should receive an outgoing tunnel packet. However, the netlink peer configuration path does not currently reject duplicate VPN addresses. If two peers are configured with the same VPN address, both can be inserted in the VPN address hash table and lookups return whichever peer is found first. This makes peer selection ambiguous and dependent on hash insertion order. Reject peer creation or update when the resulting VPN address is already assigned to another peer. Ignore unspecified addresses because those are not inserted in the VPN address hash tables. This changes such configurations from being accepted to being rejected, but they have never worked reliably because peer selection is ambiguous. Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/netlink.c | 37 ++++++++++++++++----- drivers/net/ovpn/peer.c | 67 +++++++++++++++++++++++++++++++++++++- drivers/net/ovpn/peer.h | 6 ++++ 3 files changed, 101 insertions(+), 9 deletions(-) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 2ba762082acc1b..e23f7d1f49e011 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -480,8 +480,10 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; struct ovpn_priv *ovpn = info->user_ptr[0]; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct in6_addr vpn_addr6; + struct in_addr vpn_addr4; struct ovpn_socket *sock; struct ovpn_peer *peer; u32 peer_id; @@ -528,28 +530,47 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) rcu_read_unlock(); spin_lock_bh(&ovpn->lock); - ret = ovpn_nl_peer_modify(peer, info, attrs); - if (ret < 0) { - spin_unlock_bh(&ovpn->lock); - ovpn_peer_put(peer); - return ret; + + /* reject peer with conflicting VPN address */ + if (attrs[OVPN_A_PEER_VPN_IPV4]) { + vpn_addr4.s_addr = nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]); + if (ovpn_peer_vpn_addr_conflict4(ovpn, peer, &vpn_addr4)) + goto addr_conflict; } + if (attrs[OVPN_A_PEER_VPN_IPV6]) { + vpn_addr6 = nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]); + if (ovpn_peer_vpn_addr_conflict6(ovpn, peer, &vpn_addr6)) + goto addr_conflict; + } + + ret = ovpn_nl_peer_modify(peer, info, attrs); + if (ret < 0) + goto unlock; /* ret == 1 means that VPN IPv4/6 has been modified and rehashing * is required */ - if (ret > 0) + if (ret > 0) { ovpn_peer_hash_vpn_ip(peer); + ret = 0; + } /* if the remote endpoint was updated, the by_transp_addr hash bucket * also needs to be refreshed, otherwise incoming packets from the new * remote address would fail the lockless lookup */ if (attrs[OVPN_A_PEER_REMOTE_IPV4] || attrs[OVPN_A_PEER_REMOTE_IPV6]) ovpn_peer_hash_transp_addr(peer); + +unlock: spin_unlock_bh(&ovpn->lock); ovpn_peer_put(peer); - return 0; + return ret; +addr_conflict: + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "VPN IP is already assigned to another peer"); + ret = -EADDRINUSE; + goto unlock; } static int ovpn_nl_send_peer(struct sk_buff *skb, const struct genl_info *info, diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index bbd9e17fb0bfa1..2067825bb5b614 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -488,7 +488,7 @@ static struct ovpn_peer *ovpn_peer_get_by_vpn_addr4(struct ovpn_priv *ovpn, * Return: the peer if found or NULL otherwise */ static struct ovpn_peer *ovpn_peer_get_by_vpn_addr6(struct ovpn_priv *ovpn, - struct in6_addr *addr) + const struct in6_addr *addr) { struct hlist_nulls_head *nhead; struct hlist_nulls_node *ntmp; @@ -513,6 +513,64 @@ static struct ovpn_peer *ovpn_peer_get_by_vpn_addr6(struct ovpn_priv *ovpn, return NULL; } +/** + * ovpn_peer_vpn_addr_conflict4 - check if the VPN v4 address is already in use + * @ovpn: the openvpn instance to search + * @peer: peer being added or updated, or NULL + * @addr: VPN IPv4 address to check + * + * Check whether @addr is already assigned to another peer. @peer is ignored + * when found, allowing peer updates that keep an existing address. + * Unspecified addresses are ignored. + * + * Note: the caller must hold @ovpn->lock. + * + * Return: true on conflict, false otherwise. + */ +bool ovpn_peer_vpn_addr_conflict4(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in_addr *addr) +{ + struct ovpn_peer *tmp = NULL; + + lockdep_assert_held(&ovpn->lock); + + /* we don't hash INADDR_ANY, no conflict in that case */ + if (addr->s_addr != htonl(INADDR_ANY)) + tmp = ovpn_peer_get_by_vpn_addr4(ovpn, addr->s_addr); + + return tmp && tmp != peer; +} + +/** + * ovpn_peer_vpn_addr_conflict6 - check if the VPN v6 address is already in use + * @ovpn: the openvpn instance to search + * @peer: peer being added or updated, or NULL + * @addr: VPN IPv6 address to check + * + * Check whether @addr is already assigned to another peer. @peer is ignored + * when found, allowing peer updates that keep an existing address. + * Unspecified addresses are ignored. + * + * Note: the caller must hold @ovpn->lock. + * + * Return: true on conflict, false otherwise. + */ +bool ovpn_peer_vpn_addr_conflict6(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in6_addr *addr) +{ + struct ovpn_peer *tmp = NULL; + + lockdep_assert_held(&ovpn->lock); + + /* we don't hash ::, no conflict in that case */ + if (!ipv6_addr_any(addr)) + tmp = ovpn_peer_get_by_vpn_addr6(ovpn, addr); + + return tmp && tmp != peer; +} + /** * ovpn_peer_transp_match - check if sockaddr and peer binding match * @peer: the peer to get the binding from @@ -1040,6 +1098,13 @@ static int ovpn_peer_add_mp(struct ovpn_priv *ovpn, struct ovpn_peer *peer) goto out; } + /* reject peer with conflicting VPN address */ + if (ovpn_peer_vpn_addr_conflict4(ovpn, NULL, &peer->vpn_addrs.ipv4) || + ovpn_peer_vpn_addr_conflict6(ovpn, NULL, &peer->vpn_addrs.ipv6)) { + ret = -EADDRINUSE; + goto out; + } + bind = rcu_dereference_protected(peer->bind, true); /* peers connected via TCP have bind == NULL */ if (bind) { diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 063535699ecd85..1879bfb76992d1 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -164,6 +164,12 @@ struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id); struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb); +bool ovpn_peer_vpn_addr_conflict4(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in_addr *addr); +bool ovpn_peer_vpn_addr_conflict6(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in6_addr *addr); void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer); void ovpn_peer_hash_transp_addr(struct ovpn_peer *peer); bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb, From 025af3a0a892514f9f27f186338ba3d44365547a Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 15:00:08 +0200 Subject: [PATCH 0925/1417] ovpn: reject multipeer peers without VPN addresses In MP mode, ovpn uses the peer VPN addresses to select the peer for outgoing tunnel packets. Peer creation currently requires a VPN IPv4 or IPv6 attribute, but it only checks for the presence of the attribute and not for a usable address value. This allows userspace to create an MP peer with only unspecified VPN addresses, or to update an existing peer so that both VPN address families become unspecified. Such a peer cannot be selected through the VPN address hash tables. Reject MP peer creation or update when the resulting peer would not have at least one VPN address configured. This changes such configurations from being accepted to being rejected, but they have never been usable because the peer cannot be selected through the VPN address hash tables. Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/netlink.c | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index e23f7d1f49e011..e9e0f75e04433c 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -352,8 +352,10 @@ static int ovpn_nl_peer_modify(struct ovpn_peer *peer, struct genl_info *info, int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct in_addr vpn_addr4 = { .s_addr = htonl(INADDR_ANY) }; + struct in6_addr vpn_addr6 = IN6ADDR_ANY_INIT; struct ovpn_priv *ovpn = info->user_ptr[0]; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; struct ovpn_socket *ovpn_sock; struct socket *sock = NULL; struct ovpn_peer *peer; @@ -377,11 +379,20 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) return -EINVAL; /* in MP mode VPN IPs are required for selecting the right peer */ - if (ovpn->mode == OVPN_MODE_MP && !attrs[OVPN_A_PEER_VPN_IPV4] && - !attrs[OVPN_A_PEER_VPN_IPV6]) { - NL_SET_ERR_MSG_FMT_MOD(info->extack, - "VPN IP must be provided in MP mode"); - return -EINVAL; + if (ovpn->mode == OVPN_MODE_MP) { + if (attrs[OVPN_A_PEER_VPN_IPV4]) + vpn_addr4.s_addr = + nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]); + if (attrs[OVPN_A_PEER_VPN_IPV6]) + vpn_addr6 = + nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]); + + if (vpn_addr4.s_addr == htonl(INADDR_ANY) && + ipv6_addr_any(&vpn_addr6)) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "at least one VPN IP must be configured in MP mode"); + return -EINVAL; + } } peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]); @@ -531,6 +542,9 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) spin_lock_bh(&ovpn->lock); + vpn_addr4 = peer->vpn_addrs.ipv4; + vpn_addr6 = peer->vpn_addrs.ipv6; + /* reject peer with conflicting VPN address */ if (attrs[OVPN_A_PEER_VPN_IPV4]) { vpn_addr4.s_addr = nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]); @@ -543,6 +557,16 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) goto addr_conflict; } + /* in MP mode VPN IPs are required for selecting the right peer */ + if (ovpn->mode == OVPN_MODE_MP && + vpn_addr4.s_addr == htonl(INADDR_ANY) && + ipv6_addr_any(&vpn_addr6)) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "at least one VPN IP must be configured in MP mode"); + ret = -EINVAL; + goto unlock; + } + ret = ovpn_nl_peer_modify(peer, info, attrs); if (ret < 0) goto unlock; From 5940f3407b78062442cb01f541ef6eed709fc380 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 15:00:09 +0200 Subject: [PATCH 0926/1417] ovpn: reject invalid peer VPN addresses In MP mode, ovpn uses peer VPN addresses as lookup keys for selecting the peer that should receive outgoing tunnel packets. The netlink configuration path currently accepts address values that cannot sensibly identify a VPN peer, such as multicast, broadcast or loopback addresses. Reject invalid peer VPN addresses when creating or updating an MP peer. Keep accepting the unspecified address as the internal unset value, provided that at least one VPN address family remains configured. Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/netlink.c | 55 +++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index e9e0f75e04433c..5432bc2eb8e80c 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -185,6 +185,39 @@ static sa_family_t ovpn_nl_family_get(struct nlattr *addr4, return AF_UNSPEC; } +static int ovpn_nl_peer_check_vpn_addrs(const struct in_addr *addr4, + const struct in6_addr *addr6, + struct genl_info *info) +{ + int addr6_type; + + if (addr4->s_addr == htonl(INADDR_ANY) && ipv6_addr_any(addr6)) { + NL_SET_ERR_MSG_MOD(info->extack, + "at least one VPN IP must be configured in MP mode"); + return -EINVAL; + } + + if (ipv4_is_multicast(addr4->s_addr) || ipv4_is_lbcast(addr4->s_addr) || + ipv4_is_loopback(addr4->s_addr)) { + NL_SET_ERR_MSG_MOD(info->extack, + "VPN IPv4 address must be valid unicast or any"); + return -EADDRNOTAVAIL; + } + + if (!ipv6_addr_any(addr6)) { + addr6_type = ipv6_addr_type(addr6); + + if (!(addr6_type & IPV6_ADDR_UNICAST) || + (addr6_type & (IPV6_ADDR_LOOPBACK | IPV6_ADDR_COMPATv4))) { + NL_SET_ERR_MSG_MOD(info->extack, + "VPN IPv6 address must be valid unicast or any"); + return -EADDRNOTAVAIL; + } + } + + return 0; +} + static int ovpn_nl_peer_precheck(struct ovpn_priv *ovpn, struct genl_info *info, struct nlattr **attrs) @@ -387,12 +420,10 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) vpn_addr6 = nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]); - if (vpn_addr4.s_addr == htonl(INADDR_ANY) && - ipv6_addr_any(&vpn_addr6)) { - NL_SET_ERR_MSG_FMT_MOD(info->extack, - "at least one VPN IP must be configured in MP mode"); - return -EINVAL; - } + ret = ovpn_nl_peer_check_vpn_addrs(&vpn_addr4, &vpn_addr6, + info); + if (ret < 0) + return ret; } peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]); @@ -558,13 +589,11 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) } /* in MP mode VPN IPs are required for selecting the right peer */ - if (ovpn->mode == OVPN_MODE_MP && - vpn_addr4.s_addr == htonl(INADDR_ANY) && - ipv6_addr_any(&vpn_addr6)) { - NL_SET_ERR_MSG_FMT_MOD(info->extack, - "at least one VPN IP must be configured in MP mode"); - ret = -EINVAL; - goto unlock; + if (ovpn->mode == OVPN_MODE_MP) { + ret = ovpn_nl_peer_check_vpn_addrs(&vpn_addr4, &vpn_addr6, + info); + if (ret < 0) + goto unlock; } ret = ovpn_nl_peer_modify(peer, info, attrs); From 006208026819d5e9e5ec07b3e73d95960a059327 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 15:00:10 +0200 Subject: [PATCH 0927/1417] selftests: ovpn: validate peer VPN addresses Exercise peer VPN address validation through both peer creation and update. Check missing, unspecified, duplicate, multicast, broadcast, loopback, IPv4-compatible and IPv4-mapped addresses. Temporarily configure a peer with both address families to verify that either family can be cleared while the other remains configured, then restore the original addresses before running the existing traffic tests. Extend ovpn-cli peer updates with an optional VPN address and preserve peer creation errors so the negative tests can observe rejected netlink requests. Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- tools/testing/selftests/net/ovpn/common.sh | 13 ++++ tools/testing/selftests/net/ovpn/ovpn-cli.c | 54 ++++++++++----- tools/testing/selftests/net/ovpn/test.sh | 75 ++++++++++++++++++++- 3 files changed, 123 insertions(+), 19 deletions(-) diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh index 2d844eb3aa6e36..5e9c81e885e667 100644 --- a/tools/testing/selftests/net/ovpn/common.sh +++ b/tools/testing/selftests/net/ovpn/common.sh @@ -136,6 +136,19 @@ ovpn_create_ns() { ip netns add "ovpn_peer${1}" } +ovpn_peer_vpn_addr() { + local peer="$1" + local file + + if [ "${OVPN_PROTO}" == "UDP" ]; then + file="${OVPN_UDP_PEERS_FILE}" + else + file="${OVPN_TCP_PEERS_FILE}" + fi + + awk -v peer="${peer}" '$1 == peer {print $NF; exit}' "${file}" +} + ovpn_setup_ns() { local peer="ovpn_peer${1}" local server_ns="ovpn_peer0" diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index f4effa7580c0f9..3b612a8a18fe70 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -650,6 +650,26 @@ static int ovpn_connect(struct ovpn_ctx *ovpn) return ret; } +static int ovpn_nl_put_vpn_addr(struct nl_msg *msg, + const struct ovpn_ctx *ovpn) +{ + if (!ovpn->peer_ip_set) + return 0; + + switch (ovpn->peer_ip.in4.sin_family) { + case AF_INET: + return nla_put_u32(msg, OVPN_A_PEER_VPN_IPV4, + ovpn->peer_ip.in4.sin_addr.s_addr); + case AF_INET6: + return nla_put(msg, OVPN_A_PEER_VPN_IPV6, + sizeof(struct in6_addr), + &ovpn->peer_ip.in6.sin6_addr); + default: + fprintf(stderr, "Invalid family for peer address\n"); + return -EAFNOSUPPORT; + } +} + static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp) { struct nlattr *attr; @@ -691,22 +711,9 @@ static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp) } } - if (ovpn->peer_ip_set) { - switch (ovpn->peer_ip.in4.sin_family) { - case AF_INET: - NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_VPN_IPV4, - ovpn->peer_ip.in4.sin_addr.s_addr); - break; - case AF_INET6: - NLA_PUT(ctx->nl_msg, OVPN_A_PEER_VPN_IPV6, - sizeof(struct in6_addr), - &ovpn->peer_ip.in6.sin6_addr); - break; - default: - fprintf(stderr, "Invalid family for peer address\n"); - goto nla_put_failure; - } - } + ret = ovpn_nl_put_vpn_addr(ctx->nl_msg, ovpn); + if (ret) + goto nla_put_failure; nla_nest_end(ctx->nl_msg, attr); @@ -732,6 +739,10 @@ static int ovpn_set_peer(struct ovpn_ctx *ovpn) ovpn->keepalive_interval); NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_TIMEOUT, ovpn->keepalive_timeout); + + ret = ovpn_nl_put_vpn_addr(ctx->nl_msg, ovpn); + if (ret) + goto nla_put_failure; nla_nest_end(ctx->nl_msg, attr); ret = ovpn_nl_msg_send(ctx, NULL); @@ -1730,13 +1741,14 @@ static void usage(const char *cmd) fprintf(stderr, "\tmark: socket FW mark value\n"); fprintf(stderr, - "* set_peer : set peer attributes\n"); + "* set_peer [vpnaddr]: set peer attributes\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); fprintf(stderr, "\tpeer_id: peer ID of the peer to modify\n"); fprintf(stderr, "\tkeepalive_interval: interval for sending ping messages\n"); fprintf(stderr, "\tkeepalive_timeout: time after which a peer is timed out\n"); + fprintf(stderr, "\tvpnaddr: peer VPN IP\n"); fprintf(stderr, "* del_peer : delete peer\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); @@ -2090,6 +2102,8 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn) return ret; ret = ovpn_new_peer(ovpn, false); + if (ret < 0) + return ret; ovpn_waitbg(); break; case CMD_NEW_MULTI_PEER: @@ -2331,6 +2345,12 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) "keepalive interval value out of range\n"); return -1; } + + if (argc > 6) { + ret = ovpn_parse_remote(ovpn, NULL, NULL, argv[6]); + if (ret < 0) + return -1; + } break; case CMD_DEL_PEER: if (argc < 4) diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh index 9b5610837032f0..392109d5e14e21 100755 --- a/tools/testing/selftests/net/ovpn/test.sh +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -56,6 +56,76 @@ ovpn_prepare_network() { done } +ovpn_new_test_peer() { + local peer_id="$1" + + shift + ip netns exec ovpn_peer0 "${OVPN_CLI}" new_peer tun0 \ + "${peer_id}" none 65000 10.10.1.2 1 "$@" +} + +ovpn_set_peer_vpn_addr() { + ip netns exec ovpn_peer0 "${OVPN_CLI}" set_peer tun0 \ + "$1" 60 120 "$2" +} + +ovpn_run_vpn_addr_validation() { + local addr + local peer1_addr4 + local test_peer_id=$((OVPN_NUM_PEERS + 1)) + local test_peer_addr6="2001:db8::2" + # Do not include 0.0.0.0 or :: here. They are invalid on creation, but + # clear one address family on update and are valid if the other remains. + local -a invalid_addrs=( + "127.0.0.1" + "224.0.0.1" + "255.255.255.255" + "::1" + "::192.0.2.1" + "::ffff:192.0.2.1" + "ff02::1" + ) + + peer1_addr4=$(ovpn_peer_vpn_addr 1) + + ovpn_cmd_fail "reject peer without VPN address" \ + ovpn_new_test_peer "${test_peer_id}" + + for addr in "0.0.0.0" "::" "${invalid_addrs[@]}"; do + ovpn_cmd_fail "reject new peer VPN address ${addr}" \ + ovpn_new_test_peer "${test_peer_id}" "${addr}" + done + + ovpn_cmd_fail "reject duplicate IPv4 address on peer creation" \ + ovpn_new_test_peer "${test_peer_id}" "${peer1_addr4}" + ovpn_cmd_fail "reject clearing the last peer VPN address" \ + ovpn_set_peer_vpn_addr 1 0.0.0.0 + + for addr in "${invalid_addrs[@]}"; do + ovpn_cmd_fail "reject updated peer VPN address ${addr}" \ + ovpn_set_peer_vpn_addr 1 "${addr}" + done + + ovpn_cmd_fail "reject duplicate IPv4 address on peer update" \ + ovpn_set_peer_vpn_addr 2 "${peer1_addr4}" + + ovpn_cmd_ok "add peer IPv6 address" \ + ovpn_set_peer_vpn_addr 1 "${test_peer_addr6}" + ovpn_cmd_fail "reject duplicate IPv6 address on peer creation" \ + ovpn_new_test_peer "${test_peer_id}" "${test_peer_addr6}" + ovpn_cmd_fail "reject duplicate IPv6 address on peer update" \ + ovpn_set_peer_vpn_addr 2 "${test_peer_addr6}" + + ovpn_cmd_ok "clear peer IPv4 address" \ + ovpn_set_peer_vpn_addr 1 0.0.0.0 + ovpn_cmd_fail "reject clearing the remaining peer IPv6 address" \ + ovpn_set_peer_vpn_addr 1 :: + ovpn_cmd_ok "restore peer IPv4 address" \ + ovpn_set_peer_vpn_addr 1 "${peer1_addr4}" + ovpn_cmd_ok "clear peer IPv6 address" \ + ovpn_set_peer_vpn_addr 1 :: +} + ovpn_run_basic_traffic() { local p local header1 @@ -293,15 +363,16 @@ trap ovpn_stage_err ERR ktap_print_header if [ "${OVPN_FLOAT}" == "1" ]; then - ktap_set_plan 13 + ktap_set_plan 14 else - ktap_set_plan 12 + ktap_set_plan 13 fi ovpn_cleanup modprobe -q ovpn || true ovpn_run_stage "setup network topology" ovpn_prepare_network +ovpn_run_stage "validate peer VPN addresses" ovpn_run_vpn_addr_validation ovpn_run_stage "run baseline data traffic" ovpn_run_basic_traffic ovpn_run_stage "run LAN traffic behind peer1" ovpn_run_lan_traffic [ "${OVPN_FLOAT}" == "1" ] && ovpn_run_stage "run floating peer checks" \ From e31ae4aeb049983fe3be0cf1e12c8074811a7685 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sun, 20 Sep 2026 22:53:27 +0900 Subject: [PATCH 0928/1417] ASoC: wm8903: Move the DRC QR threshold to the register that holds it The DRC_THRESH_QR field is bits 7 and 6 of DRC_1, but the control sits on DRC_0. Its shift and max are right for the field, only the register is wrong. Those bits of DRC_0 belong to DRC_STARTUP_GAIN, which "DRC Startup Volume" claims as bits 10 to 6. So the two controls share bits 7 and 6. Writing either one moves the other, and the quick release threshold is never reached at all. The six fields of DRC_1 tile it exactly, so there is nowhere else the threshold could live. Point the control at DRC_1. Found by a sweep for two controls claiming the same bits of one register. No board with this codec was to hand, the register map is the driver's own wm8903.h. Fixes: f1c0a02f32f8 ("ALSA: ASoC: Add WM8903 CODEC driver") Signed-off-by: HyeongJun An Assisted-by: Claude:claude-opus-5 Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260920135327.3628704-1-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8903.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 156e1e24a38854..fbf388cf966dd7 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -676,7 +676,7 @@ SOC_ENUM("DRC Decay Rate", drc_decay), SOC_ENUM("DRC FF Delay", drc_ff_delay), SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0), SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0), -SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max), +SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_1, 6, 3, 0, drc_tlv_max), SOC_ENUM("DRC QR Decay Rate", drc_qr_decay), SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0), SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0), From 4454102d6565afedfcde64b222595f067a5b7e93 Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Thu, 17 Sep 2026 11:33:59 +0100 Subject: [PATCH 0929/1417] ASoC: SDCA: Improve scanning the SWFT during FDL Walking through SWFT is now improved to verify if it is not reading past the end of the table. Introduce file length checks that are done for SWFs from disk to files from SWFT too. Fixes: 71f7990a34cd ("ASoC: SDCA: Add FDL library for XU entities") Signed-off-by: Maciej Strozek Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260917103400.2032913-1-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_fdl.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c index 150e36ed24bcc7..e08a553e0eab05 100644 --- a/sound/soc/sdca/sdca_fdl.c +++ b/sound/soc/sdca/sdca_fdl.c @@ -199,7 +199,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, struct sdca_fdl_file *fdl_file; char *disk_filename; int ret; - int i; if (!set) { dev_err(dev, "request to load SWF with no set\n"); @@ -209,9 +208,18 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, fdl_file = &set->files[file_index]; if (fdl_data->swft) { - tmp = fdl_data->swft->files; - for (i = 0; i < fdl_data->swft->header.length; i += tmp->file_length, - tmp = ACPI_ADD_PTR(struct acpi_sw_file, tmp, tmp->file_length)) { + struct acpi_sw_file *table_end, *next; + + table_end = ACPI_ADD_PTR(struct acpi_sw_file, fdl_data->swft, + fdl_data->swft->header.length); + for (tmp = fdl_data->swft->files; tmp + 1 <= table_end; tmp = next) { + next = ACPI_ADD_PTR(struct acpi_sw_file, tmp, tmp->file_length); + + if (tmp->file_length < sizeof(*tmp) || next > table_end) { + dev_err(dev, "bad file length in SWFT: %u\n", tmp->file_length); + break; + } + if (tmp->vendor_id == fdl_file->vendor_id && tmp->file_id == fdl_file->file_id) { dev_dbg(dev, "located SWF in ACPI: %x-%x-%x\n", From dfd8940277c9a07193c2d168760e19d054f5047c Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Thu, 17 Sep 2026 11:34:00 +0100 Subject: [PATCH 0930/1417] ASoC: SDCA: Check return value on a missing FDL_Set_Index Range In case of a missing FDL_Set_Index control the return value can be NULL, ensure the functions returns before passing it on to range search. Fixes: 71f7990a34cd ("ASoC: SDCA: Add FDL library for XU entities") Signed-off-by: Maciej Strozek Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260917103400.2032913-2-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_fdl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c index e08a553e0eab05..7fc809707944d0 100644 --- a/sound/soc/sdca/sdca_fdl.c +++ b/sound/soc/sdca/sdca_fdl.c @@ -301,6 +301,8 @@ static struct sdca_fdl_set *fdl_get_set(struct sdca_interrupt *interrupt) range = sdca_selector_find_range(dev, xu, SDCA_CTL_XU_FDL_SET_INDEX, SDCA_FDL_SET_INDEX_NCOLS, 0); + if (!range) + return NULL; val = sdca_range_search(range, SDCA_FDL_SET_INDEX_SET_NUMBER, val, SDCA_FDL_SET_INDEX_FILE_SET_ID); From e6bae5034ef4a61f3f062b18140d4f58c8b9a149 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 18 Sep 2026 14:40:32 +0200 Subject: [PATCH 0931/1417] ata: libata-core: Extend Samsung LPM quirk to AMD controllers A Samsung SSD 870 QVO 8TB connected to an AMD 600 Series chipset SATA controller is reported to time out on STANDBY IMMEDIATE during system suspend with med_power_with_dipm enabled. The command completes when using max_performance instead. The existing Samsung LPM quirk only matches ATI controllers, leaving AMD controllers unaffected. Rename it to ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD and extend the vendor check to AMD for the same Samsung SSD model patterns. Keep LPM behavior unchanged for other controller vendors, including Intel. Leave ATA_QUIRK_NO_NCQ_ON_ATI restricted to ATI, since the reported AMD issue concerns LPM rather than NCQ. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221986 Reviewed-by: Damien Le Moal Reviewed-by: Mario Limonciello (AMD) > --- Link: https://lore.kernel.org/r/20260918124030.1962773-5-cassel@kernel.org Signed-off-by: Niklas Cassel --- drivers/ata/libata-core.c | 15 ++++++++------- include/linux/libata.h | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index f482c0a6d7e986..92233fb25051a5 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -2972,9 +2972,10 @@ static void ata_dev_config_lpm(struct ata_device *dev) (dev->id[ATA_ID_SATA_CAPABILITY] & 0xe) == 0x2) dev->quirks |= ATA_QUIRK_NOLPM; - /* ATI specific quirk */ - if ((dev->quirks & ATA_QUIRK_NO_LPM_ON_ATI) && - ata_dev_check_adapter(dev, PCI_VENDOR_ID_ATI)) + /* ATI and AMD specific quirk */ + if ((dev->quirks & ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD) && + (ata_dev_check_adapter(dev, PCI_VENDOR_ID_ATI) || + ata_dev_check_adapter(dev, PCI_VENDOR_ID_AMD))) dev->quirks |= ATA_QUIRK_NOLPM; } @@ -4136,7 +4137,7 @@ static const char * const ata_quirk_names[] = { [__ATA_QUIRK_MAX_SEC] = "maxsec", [__ATA_QUIRK_MAX_TRIM_128M] = "maxtrim128m", [__ATA_QUIRK_NO_NCQ_ON_ATI] = "noncqonati", - [__ATA_QUIRK_NO_LPM_ON_ATI] = "nolpmonati", + [__ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD] = "nolpmonatiandamd", [__ATA_QUIRK_NO_ID_DEV_LOG] = "noiddevlog", [__ATA_QUIRK_NO_LOG_DIR] = "nologdir", [__ATA_QUIRK_NO_FUA] = "nofua", @@ -4420,15 +4421,15 @@ static const struct ata_dev_quirks_entry __ata_dev_quirks[] = { { "Samsung SSD 860*", NULL, ATA_QUIRK_NO_NCQ_TRIM | ATA_QUIRK_ZERO_AFTER_TRIM | ATA_QUIRK_NO_NCQ_ON_ATI | - ATA_QUIRK_NO_LPM_ON_ATI }, + ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD }, { "Samsung SSD 870*", NULL, ATA_QUIRK_NO_NCQ_TRIM | ATA_QUIRK_ZERO_AFTER_TRIM | ATA_QUIRK_NO_NCQ_ON_ATI | - ATA_QUIRK_NO_LPM_ON_ATI }, + ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD }, { "SAMSUNG*MZ7LH*", NULL, ATA_QUIRK_NO_NCQ_TRIM | ATA_QUIRK_ZERO_AFTER_TRIM | ATA_QUIRK_NO_NCQ_ON_ATI | - ATA_QUIRK_NO_LPM_ON_ATI }, + ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD }, { "FCCT*M500*", NULL, ATA_QUIRK_NO_NCQ_TRIM | ATA_QUIRK_ZERO_AFTER_TRIM }, diff --git a/include/linux/libata.h b/include/linux/libata.h index 313e96173b1921..48bde275968d09 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -76,7 +76,7 @@ enum ata_quirks { __ATA_QUIRK_MAX_SEC, /* Limit max sectors */ __ATA_QUIRK_MAX_TRIM_128M, /* Limit max trim size to 128M */ __ATA_QUIRK_NO_NCQ_ON_ATI, /* Disable NCQ on ATI chipset */ - __ATA_QUIRK_NO_LPM_ON_ATI, /* Disable LPM on ATI chipset */ + __ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD, /* Disable LPM on ATI and AMD chipsets */ __ATA_QUIRK_NO_ID_DEV_LOG, /* Identify device log missing */ __ATA_QUIRK_NO_LOG_DIR, /* Do not read log directory */ __ATA_QUIRK_NO_FUA, /* Do not use FUA */ @@ -115,7 +115,7 @@ enum { ATA_QUIRK_MAX_SEC = BIT_ULL(__ATA_QUIRK_MAX_SEC), ATA_QUIRK_MAX_TRIM_128M = BIT_ULL(__ATA_QUIRK_MAX_TRIM_128M), ATA_QUIRK_NO_NCQ_ON_ATI = BIT_ULL(__ATA_QUIRK_NO_NCQ_ON_ATI), - ATA_QUIRK_NO_LPM_ON_ATI = BIT_ULL(__ATA_QUIRK_NO_LPM_ON_ATI), + ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD = BIT_ULL(__ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD), ATA_QUIRK_NO_ID_DEV_LOG = BIT_ULL(__ATA_QUIRK_NO_ID_DEV_LOG), ATA_QUIRK_NO_LOG_DIR = BIT_ULL(__ATA_QUIRK_NO_LOG_DIR), ATA_QUIRK_NO_FUA = BIT_ULL(__ATA_QUIRK_NO_FUA), From 88a0474d92ba102fff860db3cae9da87a0964fbf Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 18 Sep 2026 14:40:33 +0200 Subject: [PATCH 0932/1417] ata: libata: Correct libata.force parameter documentation Align the documented libata.force options with their implementation. The force table accepts PIO modes 0 through 6, not mode 7, and ncqati controls NCQ generally rather than only queued TRIM. The max_sec_1024 and max_sec_lba48 options only set transfer size limits. Remove the misleading claim that they can also clear them. Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20260918124030.1962773-6-cassel@kernel.org Signed-off-by: Niklas Cassel --- Documentation/admin-guide/kernel-parameters.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 33cd30996e47ef..24459f7ff8105f 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -3491,7 +3491,7 @@ Kernel parameters * SATA link speed limit: 1.5Gbps or 3.0Gbps. - * Transfer mode: pio[0-7], mwdma[0-4] and udma[0-7]. + * Transfer mode: pio[0-6], mwdma[0-4] and udma[0-7]. udma[/][16,25,33,44,66,100,133] notation is also allowed. @@ -3509,7 +3509,7 @@ Kernel parameters * [no]ncqtrim: Enable or disable queued DSM TRIM. - * [no]ncqati: Enable or disable NCQ trim on ATI chipset. + * [no]ncqati: Enable or disable NCQ on ATI chipsets. * [no]trim: Enable or disable (unqueued) TRIM. @@ -3540,11 +3540,9 @@ Kernel parameters * max_sec_128: Set transfer size limit to 128 sectors. - * max_sec_1024: Set or clear transfer size limit to - 1024 sectors. + * max_sec_1024: Set transfer size limit to 1024 sectors. - * max_sec_lba48: Set or clear transfer size limit to - 65535 sectors. + * max_sec_lba48: Set transfer size limit to 65535 sectors. * external: Mark port as external (hotplug-capable). From a389c4dd55d5568571b57dfd7200fd0fe24a55d4 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:05 +0200 Subject: [PATCH 0933/1417] ASoC: stm32: spdifrx: Drop mention of the deprecated and unused call The error message was not updated while driver moved from the deprecated dma_request_slave_channel() to the dma_request_chan(). Update the message by replacing it with the correct function. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260918113727.1898998-2-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/stm/stm32_spdifrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c index e0fef47a227ee0..0ece547407739e 100644 --- a/sound/soc/stm/stm32_spdifrx.c +++ b/sound/soc/stm/stm32_spdifrx.c @@ -400,7 +400,7 @@ static int stm32_spdifrx_dma_ctrl_register(struct device *dev, spdifrx->ctrl_chan = dma_request_chan(dev, "rx-ctrl"); if (IS_ERR(spdifrx->ctrl_chan)) return dev_err_probe(dev, PTR_ERR(spdifrx->ctrl_chan), - "dma_request_slave_channel error\n"); + "dma_request_chan error\n"); spdifrx->dmab = devm_kzalloc(dev, sizeof(struct snd_dma_buffer), GFP_KERNEL); From 08d77e31df21fd16a2430277c09a84296972c86c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:06 +0200 Subject: [PATCH 0934/1417] ASoC: sprd: Replace dma_request_slave_channel() by dma_request_chan() Replace dma_request_slave_channel() by dma_request_chan() as suggested since the former is deprecated. With this, propagate all possible errors to the caller. Reviewed-by: Baolin Wang Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260918113727.1898998-3-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sprd/sprd-pcm-compress.c | 9 +++++---- sound/soc/sprd/sprd-pcm-dma.c | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/sound/soc/sprd/sprd-pcm-compress.c b/sound/soc/sprd/sprd-pcm-compress.c index e5249924b54d91..f394121d417a3f 100644 --- a/sound/soc/sprd/sprd-pcm-compress.c +++ b/sound/soc/sprd/sprd-pcm-compress.c @@ -145,6 +145,7 @@ static int sprd_platform_compr_dma_config(struct snd_soc_component *component, enum dma_slave_buswidth bus_width; int period, period_cnt, sg_num = 2; dma_addr_t src_addr, dst_addr; + struct dma_chan *chan; unsigned long flags; int ret, j; @@ -153,12 +154,12 @@ static int sprd_platform_compr_dma_config(struct snd_soc_component *component, return -EINVAL; } - dma->chan = dma_request_slave_channel(dev, - dma_params->chan_name[channel]); - if (!dma->chan) { + chan = dma_request_chan(dev, dma_params->chan_name[channel]); + if (IS_ERR(chan)) { dev_err(dev, "failed to request dma channel\n"); - return -ENODEV; + return PTR_ERR(chan); } + dma->chan = chan; sgt = sg = kzalloc_objs(*sg, sg_num); if (!sg) { diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c index f509a4601de25b..62e84496942ac9 100644 --- a/sound/soc/sprd/sprd-pcm-dma.c +++ b/sound/soc/sprd/sprd-pcm-dma.c @@ -170,15 +170,16 @@ static int sprd_pcm_request_dma_channel(struct snd_soc_component *component, for (i = 0; i < channels; i++) { struct sprd_pcm_dma_data *data = &dma_private->data[i]; + struct dma_chan *chan; - data->chan = dma_request_slave_channel(dev, - dma_params->chan_name[i]); - if (!data->chan) { + chan = dma_request_chan(dev, dma_params->chan_name[i]); + if (IS_ERR(chan)) { dev_err(dev, "failed to request dma channel:%s\n", dma_params->chan_name[i]); sprd_pcm_release_dma_channel(substream); - return -ENODEV; + return PTR_ERR(chan); } + data->chan = chan; } return 0; From 77593bbdb8aa546ee1b99c2a7c806a00784c68cf Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:07 +0200 Subject: [PATCH 0935/1417] ASoC: soc-generic-dmaengine-pcm: Replace dma_request_slave_channel() by dma_request_chan() Replace dma_request_slave_channel() by dma_request_chan() as suggested since the former is deprecated. Reviewed-by: Cezary Rojewski Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260918113727.1898998-4-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/soc-generic-dmaengine-pcm.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index 65b7ec52a05403..6d1dbee8f6c14f 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -242,9 +242,11 @@ static int dmaengine_pcm_new(struct snd_soc_component *component, if (!substream) continue; - if (!pcm->chan[i] && config->chan_names[i]) - pcm->chan[i] = dma_request_slave_channel(dev, - config->chan_names[i]); + if (!pcm->chan[i] && config->chan_names[i]) { + pcm->chan[i] = dma_request_chan(dev, config->chan_names[i]); + if (IS_ERR(pcm->chan[i])) + pcm->chan[i] = NULL; + } if (!pcm->chan[i] && (pcm->flags & SND_DMAENGINE_PCM_FLAG_COMPAT)) { pcm->chan[i] = dmaengine_pcm_compat_request_channel( From ca11bc272ab4075afbcbf7d56116244243e1f2e1 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:08 +0200 Subject: [PATCH 0936/1417] ASoC: pxa: Replace dma_request_slave_channel() by dma_request_chan() Replace dma_request_slave_channel() by dma_request_chan() as suggested since the former is deprecated. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260918113727.1898998-5-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/pxa/pxa2xx-pcm-lib.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sound/soc/pxa/pxa2xx-pcm-lib.c b/sound/soc/pxa/pxa2xx-pcm-lib.c index 88a9d322630212..4b5b07f0c44415 100644 --- a/sound/soc/pxa/pxa2xx-pcm-lib.c +++ b/sound/soc/pxa/pxa2xx-pcm-lib.c @@ -79,6 +79,7 @@ static int pxa2xx_pcm_open(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dmaengine_dai_dma_data *dma_params; + struct dma_chan *chan; int ret; runtime->hw = pxa2xx_pcm_hardware; @@ -107,9 +108,11 @@ static int pxa2xx_pcm_open(struct snd_pcm_substream *substream) if (ret < 0) return ret; - return snd_dmaengine_pcm_open( - substream, dma_request_slave_channel(snd_soc_rtd_to_cpu(rtd, 0)->dev, - dma_params->chan_name)); + chan = dma_request_chan(snd_soc_rtd_to_cpu(rtd, 0)->dev, dma_params->chan_name); + if (IS_ERR(chan)) + return -ENXIO; + + return snd_dmaengine_pcm_open(substream, chan); } static int pxa2xx_pcm_close(struct snd_pcm_substream *substream) From b045248399d0cd8b6188ce1a5b9d722709ad3ddc Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:09 +0200 Subject: [PATCH 0937/1417] ASoC: fsl_asrc: Replace dma_request_slave_channel() by dma_request_chan() Replace dma_request_slave_channel() by dma_request_chan() as suggested since the former is deprecated. With this, propagate all possible errors to the caller. Signed-off-by: Andy Shevchenko Reviewed-by: Frank Li Link: https://patch.msgid.link/20260918113727.1898998-6-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc.c | 2 +- sound/soc/fsl/fsl_asrc_dma.c | 19 +++++++++++-------- sound/soc/fsl/fsl_asrc_m2m.c | 15 +++++++++------ sound/soc/fsl/fsl_easrc.c | 2 +- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index b24737add001b0..ae682bf450f6d5 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -638,7 +638,7 @@ static struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, sprintf(name, "%cx%c", dir == IN ? 'r' : 't', index + 'a'); - return dma_request_slave_channel(&asrc->pdev->dev, name); + return dma_request_chan(&asrc->pdev->dev, name); } static int fsl_asrc_dai_startup(struct snd_pcm_substream *substream, diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index 2f662bdf14d075..86489f70e2625e 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -136,8 +136,8 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, struct snd_dmaengine_dai_dma_data *dma_params_be = NULL; struct snd_pcm_runtime *runtime = substream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; - struct dma_chan *tmp_chan = NULL, *be_chan = NULL; struct snd_soc_component *component_be = NULL; + struct dma_chan *tmp_chan, *be_chan = NULL; struct fsl_asrc *asrc = pair->asrc; struct dma_slave_config config_fe = {}, config_be = {}; struct sdma_peripheral_config audio_config; @@ -190,11 +190,12 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, dma_params_fe->addr = asrc->paddr + asrc->get_fifo_addr(!dir, index); dma_params_fe->maxburst = dma_params_be->maxburst; - pair->dma_chan[!dir] = asrc->get_dma_channel(pair, !dir); - if (!pair->dma_chan[!dir]) { + tmp_chan = asrc->get_dma_channel(pair, !dir); + if (IS_ERR(tmp_chan)) { dev_err(dev, "failed to request DMA channel\n"); - return -EINVAL; + return PTR_ERR(tmp_chan); } + pair->dma_chan[!dir] = tmp_chan; ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, &config_fe); if (ret) { @@ -223,6 +224,8 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, be_chan = pcm->chan[substream->stream]; tmp_chan = be_chan; + } else { + tmp_chan = NULL; } if (!tmp_chan) { tmp_chan = dma_request_chan(dev_be, tx ? "tx" : "rx"); @@ -404,9 +407,9 @@ static int fsl_asrc_dma_startup(struct snd_soc_component *component, /* Request a dummy dma channel, which will be released later. */ tmp_chan = asrc->get_dma_channel(pair, dir); - if (!tmp_chan) { + ret = PTR_ERR_OR_ZERO(tmp_chan); + if (ret) { dev_err(dev, "failed to get dma channel\n"); - ret = -EINVAL; goto dma_chan_err; } @@ -497,9 +500,9 @@ static int fsl_asrc_dma_pcm_new(struct snd_soc_component *component, /* Request a dma channel, which will be released later. */ chan = asrc->get_dma_channel(pair, IN); - if (!chan) { + ret = PTR_ERR_OR_ZERO(chan); + if (ret) { dev_err(dev, "failed to get dma channel\n"); - ret = -EINVAL; goto dma_chan_err; } diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index 4bc40f328f587a..e7b91196d35b97 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -466,6 +466,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, struct snd_compr_runtime *runtime = stream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; struct device *dev = &asrc->pdev->dev; + struct dma_chan *tmp_chan; int ret; exp_info_in.ops = &fsl_asrc_m2m_dma_buf_ops; @@ -502,19 +503,21 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, } /* Request dma channels */ - pair->dma_chan[IN] = asrc->get_dma_channel(pair, IN); - if (!pair->dma_chan[IN]) { + tmp_chan = asrc->get_dma_channel(pair, IN); + ret = PTR_ERR_OR_ZERO(tmp_chan); + if (ret) { dev_err(dev, "[ctx%d] failed to get input DMA channel\n", pair->index); - ret = -EBUSY; goto err_dma_channel_in; } + pair->dma_chan[IN] = tmp_chan; - pair->dma_chan[OUT] = asrc->get_dma_channel(pair, OUT); - if (!pair->dma_chan[OUT]) { + tmp_chan = asrc->get_dma_channel(pair, OUT); + ret = PTR_ERR_OR_ZERO(tmp_chan); + if (ret) { dev_err(dev, "[ctx%d] failed to get output DMA channel\n", pair->index); - ret = -EBUSY; goto err_dma_channel_out; } + pair->dma_chan[OUT] = tmp_chan; return 0; diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index aced16d1228ace..d30cc3e9021563 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -1433,7 +1433,7 @@ static struct dma_chan *fsl_easrc_get_dma_channel(struct fsl_asrc_pair *ctx, /* Example of dma name: ctx0_rx */ sprintf(name, "ctx%c_%cx", index + '0', dir == IN ? 'r' : 't'); - return dma_request_slave_channel(&easrc->pdev->dev, name); + return dma_request_chan(&easrc->pdev->dev, name); }; static const unsigned int easrc_rates[] = { From 4f846b8b69fd0a114ebba076493ddb62efc9ce51 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:10 +0200 Subject: [PATCH 0938/1417] gpib: fmh_gpib: Replace dma_request_slave_channel() by dma_request_chan() Replace dma_request_slave_channel() by dma_request_chan() as suggested since the former is deprecated. With this, propagate all possible errors to the caller. Reviewed-by: Dave Penkler Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260918113727.1898998-7-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/gpib/fmh_gpib/fmh_gpib.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpib/fmh_gpib/fmh_gpib.c b/drivers/gpib/fmh_gpib/fmh_gpib.c index 5e10e9353fedd6..4ab9b0a317fdb8 100644 --- a/drivers/gpib/fmh_gpib/fmh_gpib.c +++ b/drivers/gpib/fmh_gpib/fmh_gpib.c @@ -1458,11 +1458,15 @@ static int fmh_gpib_attach_impl(struct gpib_board *board, const struct gpib_boar e_priv->irq = irq; if (acquire_dma) { - e_priv->dma_channel = dma_request_slave_channel(board->dev, "rxtx"); - if (!e_priv->dma_channel) { + struct dma_chan *tmp_chan; + + tmp_chan = dma_request_chan(board->dev, "rxtx"); + retval = PTR_ERR_OR_ZERO(tmp_chan); + if (retval) { dev_err(board->dev, "failed to acquire dma channel \"rxtx\".\n"); - return -EIO; + return retval; } + e_priv->dma_channel = tmp_chan; } /* * in the future we might want to know the half-fifo size From ae710b66c715933fb3e9aa53d4a024641921772e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Sep 2026 13:34:11 +0200 Subject: [PATCH 0939/1417] dmaengine: Remove deprecated dma_request_slave_channel() No more users. Reviewed-by: Frank Li Signed-off-by: Andy Shevchenko Acked-by: Vinod Koul Link: https://patch.msgid.link/20260918113727.1898998-8-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- include/linux/dmaengine.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h index fe33a20abc6146..f322e501ff6205 100644 --- a/include/linux/dmaengine.h +++ b/include/linux/dmaengine.h @@ -1760,15 +1760,6 @@ void dma_run_dependencies(struct dma_async_tx_descriptor *tx); #define dma_request_channel(mask, x, y) \ __dma_request_channel(&(mask), x, y, NULL) -/* Deprecated, please use dma_request_chan() directly */ -static inline struct dma_chan * __deprecated -dma_request_slave_channel(struct device *dev, const char *name) -{ - struct dma_chan *ch = dma_request_chan(dev, name); - - return IS_ERR(ch) ? NULL : ch; -} - static inline struct dma_chan *dma_request_slave_channel_compat(const dma_cap_mask_t mask, dma_filter_fn fn, void *fn_param, From f42fbd560cd834c7d10118743683def979c68739 Mon Sep 17 00:00:00 2001 From: Baojun Xu Date: Mon, 21 Sep 2026 19:19:30 +0800 Subject: [PATCH 0940/1417] ASoC: tas2783-sdw: add firmware download status check Currently, firmware download during system resume from suspend introduces significant wake-up latency. However, since PRAM content in the AMP persists across resets and is not erased, this step can be optimized. By verifying that the firmware version read from registers matches the expected value, we can confirm that memory content was retained. Consequently, the PRAM download can be skipped, significantly reducing resume latency. Signed-off-by: Baojun Xu Link: https://patch.msgid.link/20260921111930.1040-1-baojun.xu@ti.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2783-sdw.c | 70 ++++++++++++++++++++-------------- sound/soc/codecs/tas2783.h | 4 +- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index 85ab3fd83c7bb9..a57230e01f7553 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -97,6 +97,7 @@ struct tas2783_prv { u8 rca_binaryname[64]; u8 dev_name[32]; bool hw_init; + unsigned int fw_version; /* wq for firmware download */ wait_queue_head_t fw_wait; bool fw_dl_task_done; @@ -315,8 +316,10 @@ static int tas2783_sdca_mbq_size(struct device *dev, u32 reg) case 0x300 ... 0x340: /* Data port 3. */ case 0x400 ... 0x440: /* Data port 4. */ case 0x500 ... 0x540: /* Data port 5. */ - case 0x800000 ... 0x803fff: /* Page 0 ~ 127. */ - case 0x807e80 ... 0x807eff: /* Page 253. */ + case TASDEV_REG_SDW(0, 0, 0) ... TASDEV_REG_SDW(0x00, 0x01, 0x80): + case TASDEV_REG_SDW(0, 0xfd, 0) ... TASDEV_REG_SDW(0, 0xfd, 0x80): + case PRAM_ADDR_START ... PRAM_ADDR_END: + case YRAM_ADDR_START ... YRAM_ADDR_END: case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_UDMPU23, TAS2783_SDCA_CTL_UDMPU_CLUSTER, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU21, TAS2783_SDCA_CTL_FU_MUTE, @@ -517,7 +520,7 @@ static const struct regmap_config tas_regmap = { .volatile_reg = tas2783_volatile_register, .reg_defaults = tas2783_reg_default, .num_reg_defaults = ARRAY_SIZE(tas2783_reg_default), - .max_register = 0x41008000 + TASDEV_REG_SDW(0xa1, 0x60, 0x7f), + .max_register = 0x41000000 + PRAM_ADDR_END, .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, @@ -745,6 +748,7 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) const u8 *buf = NULL; s32 img_sz, ret = 0, cur_file = 0; s32 offset = 0; + u32 val[4], fw_version; struct tas_fw_hdr *hdr __free(kfree) = kzalloc_obj(*hdr); struct tas_fw_file *file __free(kfree) = kzalloc_obj(*file); @@ -786,6 +790,11 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) } mutex_lock(&tas_dev->pde_lock); + ret = regmap_bulk_read(tas_dev->regmap, TAS2783_FW_VERSION, &val, 4); + fw_version = (val[0] << 24) | (val[1] << 16) | (val[2] << 8) | val[3]; + dev_dbg(tas_dev->dev, "Get Firmware version: %08x == %08x?, err=%d", + fw_version, tas_dev->fw_version, ret); + while (offset < (img_sz - FW_FL_HDR)) { offset += tas_fw_get_next_file(&buf[offset], file); dev_dbg(tas_dev->dev, @@ -794,6 +803,13 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) file->version, file->length, file->dest_addr, file->fw_data); + if (tas_dev->fw_version == fw_version && + file->dest_addr >= PRAM_ADDR_START && + (file->dest_addr + file->length) <= PRAM_ADDR_END) { + cur_file++; + dev_dbg(tas_dev->dev, "Ignore PRAM block"); + continue; + } ret = sdw_nwrite_no_pm(tas_dev->sdw_peripheral, file->dest_addr, file->length, @@ -801,17 +817,34 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) if (ret < 0) { dev_err(tas_dev->dev, "FW download failed: %d", ret); - break; + /* + * We do retry here for some special case of download + * failed after Power-On. + */ + ret = sdw_nwrite_no_pm(tas_dev->sdw_peripheral, + file->dest_addr, + file->length, + file->fw_data); + if (ret < 0) { + dev_err(tas_dev->dev, + "FW download failed again: %d", ret); + break; + } } cur_file++; } mutex_unlock(&tas_dev->pde_lock); + regcache_drop_region(tas_dev->regmap, 0, UINT_MAX); if (cur_file == 0) { dev_err(tas_dev->dev, "fw with no files"); ret = -EINVAL; } else { tas2783_update_calibdata(tas_dev); + ret = regmap_bulk_read(tas_dev->regmap, TAS2783_FW_VERSION, + &val, 4); + tas_dev->fw_version = (val[0] << 24) | (val[1] << 16) | + (val[2] << 8) | val[3]; } out: @@ -962,30 +995,6 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, snd_sdw_params_to_config(substream, params, &stream_config, &port_config); - /* - * The two mono amps each render one channel of the stereo stream: - * snd_sdw_params_to_config() hands every codec the full mask for - * playback, which leaves the pair in mirror mode and one channel - * unreproduced. Claim a single channel instead, keyed off the - * machine-assigned component prefix rather than the SoundWire - * address, which is board-specific: soc_sdw_ti_amp.c names the amps - * tas2783-1..4. - * - * Which side an amp then renders does not follow from the bit that - * is set - sdw_compute_slave_ports() advances the payload offset by - * the popcount of ch_mask and never looks at which bit it is - but - * from the amp's position in the codec order of the DAI link, which - * on these boards matches the prefix numbering. - */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && - params_channels(params) == 2 && component->name_prefix) { - const char *idx_str = strrchr(component->name_prefix, '-'); - unsigned long idx; - - if (idx_str && !kstrtoul(idx_str + 1, 10, &idx) && idx) - port_config.ch_mask = (idx & 1) ? BIT(0) : BIT(1); - } - /* port 1 for playback */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) port_config.num = 1; @@ -1074,7 +1083,7 @@ static const struct snd_soc_component_driver soc_codec_driver_tasdevice = { .num_dapm_widgets = ARRAY_SIZE(tas_dapm_widgets), .dapm_routes = tas_audio_map, .num_dapm_routes = ARRAY_SIZE(tas_audio_map), - .idle_bias_on = 1, + .idle_bias_on = 0, .endianness = 1, }; @@ -1234,6 +1243,8 @@ static s32 tas_io_init(struct device *dev, struct sdw_slave *slave) ret = regmap_multi_reg_write(tas_dev->regmap, tas2783_init_seq, ARRAY_SIZE(tas2783_init_seq)); + /* Re-active AMP after resume. */ + regmap_write(tas_dev->regmap, TASDEV_REG_SDW(0, 0, 2), 0); if (ret) dev_err(tas_dev->dev, "init writes failed, err=%d", ret); @@ -1413,6 +1424,7 @@ static s32 tas_sdw_probe(struct sdw_slave *peripheral, tas_dev->dev = dev; tas_dev->sdw_peripheral = peripheral; tas_dev->hw_init = false; + tas_dev->fw_version = 0; mutex_init(&tas_dev->calib_lock); mutex_init(&tas_dev->pde_lock); diff --git a/sound/soc/codecs/tas2783.h b/sound/soc/codecs/tas2783.h index d5996c73526c5e..2f034617e3563f 100644 --- a/sound/soc/codecs/tas2783.h +++ b/sound/soc/codecs/tas2783.h @@ -35,10 +35,12 @@ #define TAS2783_AMP_LEVEL_MASK GENMASK(5, 1) #define PRAM_ADDR_START TASDEV_REG_SDW(0x8c, 0x01, 0x8) -#define PRAM_ADDR_END TASDEV_REG_SDW(0x8c, 0xff, 0x7f) +#define PRAM_ADDR_END TASDEV_REG_SDW(0x8c, 0xff, 0x80) #define YRAM_ADDR_START TASDEV_REG_SDW(0x00, 0x02, 0x8) #define YRAM_ADDR_END TASDEV_REG_SDW(0x00, 0x37, 0x7f) +#define TAS2783_FW_VERSION TASDEV_REG_SDW(0x00, 0x20, 0x3c) + /* Calibration data */ #define TAS2783_CAL_R0 TASDEV_REG_SDW(0, 0x16, 0x4C) #define TAS2783_CAL_INVR0 TASDEV_REG_SDW(0, 0x16, 0x5C) From ea4debcd8016f73c5dee3a29250a3d7977f015ef Mon Sep 17 00:00:00 2001 From: Sk Anirban Date: Wed, 9 Sep 2026 17:19:32 +0530 Subject: [PATCH 0941/1417] drm/xe/gt_throttle: Report power brake as a throttle reason on CRI CRI defines bit 5 of the perf limit reasons register as a power brake (PWRBRK) indicator. Add PWRBRK_MASK and a reason_pwrbrk sysfs attribute for CRI in place of reason_ratl. Signed-off-by: Sk Anirban Fixes: 8578e6d0546c ("drm/xe/gt_throttle: Drop individual show functions") Reviewed-by: Raag Jadav Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260909114931.1039331-2-sk.anirban@intel.com (cherry picked from commit e199c851c0ab608a0ca89e7be1756a1461b41a2c) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/regs/xe_gt_regs.h | 1 + drivers/gpu/drm/xe/xe_gt_throttle.c | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/regs/xe_gt_regs.h b/drivers/gpu/drm/xe/regs/xe_gt_regs.h index 08251c7a1a4b6b..247a736a54aaa2 100644 --- a/drivers/gpu/drm/xe/regs/xe_gt_regs.h +++ b/drivers/gpu/drm/xe/regs/xe_gt_regs.h @@ -651,6 +651,7 @@ #define MEM_THERMAL_MASK REG_BIT(2) #define VR_THERMAL_MASK REG_BIT(3) #define ICCMAX_MASK REG_BIT(4) +#define PWRBRK_MASK REG_BIT(5) #define SOC_AVG_THERMAL_MASK REG_BIT(6) #define FASTVMODE_MASK REG_BIT(7) #define PSYS_PL1_MASK REG_BIT(12) diff --git a/drivers/gpu/drm/xe/xe_gt_throttle.c b/drivers/gpu/drm/xe/xe_gt_throttle.c index 1e7e3a31aa6982..c0af5484611d1f 100644 --- a/drivers/gpu/drm/xe/xe_gt_throttle.c +++ b/drivers/gpu/drm/xe/xe_gt_throttle.c @@ -39,7 +39,7 @@ * - ``reason_mem_thermal``: Memory thermal * - ``reason_vr_thermal``: VR thermal * - ``reason_iccmax``: ICCMAX - * - ``reason_ratl``: RATL thermal algorithm + * - ``reason_pwrbrk``: Power brake * - ``reason_soc_avg_thermal``: SoC average temp * - ``reason_fastvmode``: VR is hitting FastVMode * - ``reason_psys_pl1``: PSYS PL1 @@ -200,6 +200,7 @@ static THROTTLE_ATTR_RO(reason_psys_pl1, PSYS_PL1_MASK); static THROTTLE_ATTR_RO(reason_psys_pl2, PSYS_PL2_MASK); static THROTTLE_ATTR_RO(reason_p0_freq, P0_FREQ_MASK); static THROTTLE_ATTR_RO(reason_psys_crit, PSYS_CRIT_MASK); +static THROTTLE_ATTR_RO(reason_pwrbrk, PWRBRK_MASK); static struct attribute *cri_throttle_attrs[] = { /* Common */ @@ -209,12 +210,12 @@ static struct attribute *cri_throttle_attrs[] = { &attr_reason_pl2.attr.attr, &attr_reason_pl4.attr.attr, &attr_reason_prochot.attr.attr, - &attr_reason_ratl.attr.attr, /* CRI */ &attr_reason_vr_thermal.attr.attr, &attr_reason_soc_thermal.attr.attr, &attr_reason_mem_thermal.attr.attr, &attr_reason_iccmax.attr.attr, + &attr_reason_pwrbrk.attr.attr, &attr_reason_soc_avg_thermal.attr.attr, &attr_reason_fastvmode.attr.attr, &attr_reason_psys_pl1.attr.attr, From c7a925c84704ec431598f9411dd89c0e16ae34ae Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Mon, 14 Sep 2026 21:53:18 +0000 Subject: [PATCH 0942/1417] drm/xe/tlb_inval: Treat wedged-device invalidations as complete A TLB invalidation issued on a wedged device fails with -ENOTRECOVERABLE. xe_tlb_inval_issue() squashes only -ECANCELED, so the error reaches callers that treat it as unexpected and WARN, tainting the kernel on a wedge that was deliberately caused: ggtt_invalidate_gt_tlb() drivers/gpu/drm/xe/xe_ggtt.c xe_svm_invalidate() drivers/gpu/drm/xe/xe_svm.c xe_bo_trigger_rebind() drivers/gpu/drm/xe/xe_bo.c xe_vma_userptr_do_inval() drivers/gpu/drm/xe/xe_userptr.c igt@xe_exec_reset@gt-reset-fault-injection hits the GGTT one, turning an otherwise passing run into an abort: *ERROR* SIGID=102 FATAL (-EIO) WEDGED: Device declared wedged! Tile0: GT1: Failed to invalidate GGTT (-ENOTRECOVERABLE) WARNING: drivers/gpu/drm/xe/xe_ggtt.c:588 at ggtt_invalidate_gt_tlb Workqueue: xe-guc-destroy-wq __guc_exec_queue_destroy_async [xe] ggtt_node_remove+0xe3/0x100 [xe] xe_ggtt_remove_bo+0x89/0x2c0 [xe] xe_ttm_bo_destroy+0xcb/0x330 [xe] ... xe_lrc_destroy+0x74/0x90 [xe] xe_exec_queue_fini+0x2d/0x60 [xe] -ECANCELED and -ENOTRECOVERABLE mean the same thing at this layer: the message was dropped rather than delivered, and the fence has already been signalled before the error is returned, so there is nothing left to wait for. Squash both. A wedged device is only recovered by a fresh initialisation, so the error return in xe_bo_trigger_rebind() becomes unreachable by design. v2: fix all invalidation paths. (Sashiko) Fixes: 50fa9acac26f ("drm/xe/guc: distinguish wedged from recoverable cancellation") Assisted-by: Claude:claude-opus-5 Cc: Sk Anirban Cc: Matthew Brost Signed-off-by: Shuicheng Lin Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260914215318.200603-1-shuicheng.lin@intel.com (cherry picked from commit af14e3705345cb57c53b237169873233052a5c16) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_tlb_inval.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_tlb_inval.c b/drivers/gpu/drm/xe/xe_tlb_inval.c index bbd21d393062fd..67b205bf129171 100644 --- a/drivers/gpu/drm/xe/xe_tlb_inval.c +++ b/drivers/gpu/drm/xe/xe_tlb_inval.c @@ -280,7 +280,8 @@ static void xe_tlb_inval_fence_prep(struct xe_tlb_inval_fence *fence) xe_tlb_inval_fence_signal_unlocked((__fence)); \ mutex_unlock(&(__tlb_inval)->seqno_lock); \ \ - __ret == -ECANCELED ? 0 : __ret; \ + /* Undelivered: fence already signalled, report done */ \ + (__ret == -ECANCELED || __ret == -ENOTRECOVERABLE) ? 0 : __ret; \ }) /** From be1df8badae513e01d9575398438716cfae18655 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Thu, 17 Sep 2026 13:31:58 -0700 Subject: [PATCH 0943/1417] drm/xe: Keep walking on SVM eviction failure The desired behavior for SVM eviction failures, which can occur due to various uncontrollable races, is for TTM to continue walking the LRU list and look for another eviction candidate. This is expressed by returning -ENOSPC from the ->move() callback. Adjust the SVM eviction failure path because of races in ->move() to return -ENOSPC so that TTM continues searching for another buffer to evict. Fixes: 3ca608dc7561 ("drm/xe: Basic SVM BO eviction") Cc: stable@vger.kernel.org Signed-off-by: Matthew Brost Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260917203158.292823-1-matthew.brost@intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 36a86c23588b8f57c9d20feb4cf5a2ab27e3baba) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_bo.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index dde309821237b4..b4921a627ff370 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -1037,6 +1037,13 @@ static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict, } else { drm_dbg(&xe->drm, "Evict system allocator BO failed=%pe\n", ERR_PTR(ret)); + /* + * The semantic we want upon SVM eviction failure + * because of racing access is keep walking for + * eviction, which is -ENOSPC. + */ + if (ret == -EBUSY) + ret = -ENOSPC; } goto out; From 24a22fb3c731474b68e986af6804db450fb88617 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Fri, 18 Sep 2026 14:10:35 +0100 Subject: [PATCH 0944/1417] drm/xe/vm: nuke PTs only after unlinking contested VMAs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In xe_vm_close_and_put(), external-BO VMAs are queued on the contested list for deferred destruction via xe_vma_destroy_unlocked(). However, xe_vm_pt_destroy() was previously invoked before processing contested VMAs, destroying vm->pt_root while those VMAs were still linked to their respective buffer objects (vm_bo->list.gpuva). If a concurrent thread evicts one of those shared buffer objects, xe_bo_trigger_rebind() holding only bo->resv walks the BO's VMAs and, in fault mode, calls xe_vm_invalidate_vma() -> xe_pt_zap_ptes(). Because vm->pt_root[tile->id] is already NULL, dereferencing pt->level causes a NULL ptr deref. Fix this by deferring xe_vm_free_scratch() and xe_vm_pt_destroy() until after all contested VMAs have been unlinked and destroyed. User is reporting hitting a NULL ptr deref in xe_pt_zap_ptes(), which could be explained by this race. Assisted-by: LLM Fixes: b06d47be7c83 ("drm/xe: Port Xe to GPUVA") Link: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/9290 Signed-off-by: Matthew Auld Cc: Thomas Hellström Cc: Matthew Brost Cc: # v6.12+ Reviewed-by: Thomas Hellström Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260918131034.598078-2-matthew.auld@intel.com (cherry picked from commit c2863648959489767f08892fd6e90577d2ea0b6a) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_vm.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index 23952ad8951e28..ef20e205a734c7 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -1947,21 +1947,13 @@ void xe_vm_close_and_put(struct xe_vm *vm) vma->gpuva.flags |= XE_VMA_DESTROYED; } - /* - * All vm operations will add shared fences to resv. - * The only exception is eviction for a shared object, - * but even so, the unbind when evicted would still - * install a fence to resv. Hence it's safe to - * destroy the pagetables immediately. - */ - xe_vm_free_scratch(vm); - xe_vm_pt_destroy(vm); xe_vm_unlock(vm); /* - * VM is now dead, cannot re-add nodes to vm->vmas if it's NULL - * Since we hold a refcount to the bo, we can remove and free - * the members safely without locking. + * Unlink and destroy all contested external-BO VMAs before destroying + * the page tables. Otherwise, concurrent eviction holding only bo->resv + * can walk the BO's VMAs and attempt to invalidate/zap page tables that + * have already been freed. */ list_for_each_entry_safe(vma, next_vma, &contested, combined_links.destroy) { @@ -1969,6 +1961,11 @@ void xe_vm_close_and_put(struct xe_vm *vm) xe_vma_destroy_unlocked(vma); } + xe_vm_lock(vm, false); + xe_vm_free_scratch(vm); + xe_vm_pt_destroy(vm); + xe_vm_unlock(vm); + xe_svm_fini(vm); up_write(&vm->lock); From 75fc8a3ee5aee72f2fea3b6436170fae175f7f19 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Mon, 21 Sep 2026 08:57:02 +0000 Subject: [PATCH 0945/1417] ASoC: tegra: Fix ASRC Stream6 input threshold control The Stream6 Input Threshold control points at stream index 4, which is already used by Stream5. The threshold get and put callbacks derive the lane ID from the register offset, so the duplicate index makes Stream6 operate on lane 4 and leaves lane 5 inaccessible from userspace. Use stream index 5 for Stream6. Fixes: a2df8c2d5b36 ("ASoC: tegra: Add Tegra186 based ASRC driver") Signed-off-by: Sheetal Reviewed-by: Thierry Reding Link: https://patch.msgid.link/20260921085704.1248920-2-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra186_asrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c index 7f360dfaf8b1ae..d8ae5d997615f3 100644 --- a/sound/soc/tegra/tegra186_asrc.c +++ b/sound/soc/tegra/tegra186_asrc.c @@ -828,7 +828,7 @@ static const struct snd_kcontrol_new tegra186_asrc_controls[] = { tegra186_asrc_put_input_threshold), SOC_SINGLE_EXT("Stream6 Input Threshold", - ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 4), 0, 3, 0, + ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 5), 0, 3, 0, tegra186_asrc_get_input_threshold, tegra186_asrc_put_input_threshold), From f0ca020cbb9bb7f3f4ea8ba1dfcf30a282aec91e Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 22:17:38 +0000 Subject: [PATCH 0946/1417] Bluetooth: bnep: fix out-of-bounds reads on short RX/TX frames and control fallthrough Fix multiple out-of-bounds reads in Bluetooth BNEP frame processing: 1. In bnep_rx_frame() and bnep_ctrl_frame() (net/bluetooth/bnep/core.c), use pskb_may_pull() to verify the BNEP header, control type byte, filter count, and extension headers exist before reading them, and return 0 after handling BNEP_CONTROL instead of falling through to Ethernet frame submission when no extension headers follow. 2. In bnep_net_xmit() (net/bluetooth/bnep/netdev.c), verify skb->len >= ETH_HLEN with pskb_may_pull() before reading the 14-byte Ethernet header to prevent an out-of-bounds heap read and infoleak on short AF_PACKET TX frames. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Assisted-by: LLM Signed-off-by: Hui Peng Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/bnep/core.c | 17 ++++++++++++++++- net/bluetooth/bnep/netdev.c | 8 +++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/bnep/core.c b/net/bluetooth/bnep/core.c index f7d88c33e23e4f..ad24d2486665a9 100644 --- a/net/bluetooth/bnep/core.c +++ b/net/bluetooth/bnep/core.c @@ -270,9 +270,14 @@ static int bnep_rx_extension(struct bnep_session *s, struct sk_buff *skb) BT_DBG("type 0x%x len %u", h->type, h->len); + if (skb->len < h->len) { + err = -EILSEQ; + break; + } + switch (h->type & BNEP_TYPE_MASK) { case BNEP_EXT_CONTROL: - bnep_rx_control(s, skb->data, skb->len); + bnep_rx_control(s, skb->data, h->len); break; default: @@ -373,6 +378,11 @@ static int bnep_rx_frame(struct bnep_session *s, struct sk_buff *skb) goto badframe; } + if ((type & BNEP_TYPE_MASK) == BNEP_CONTROL) { + kfree_skb(skb); + return 0; + } + /* Strip 802.1p header */ if (ntohs(s->eh.h_proto) == ETH_P_8021Q) { if (!skb_pull(skb, 4)) @@ -451,6 +461,11 @@ static int bnep_tx_frame(struct bnep_session *s, struct sk_buff *skb) goto send; } + if (skb->len < ETH_HLEN) { + kfree_skb(skb); + return 0; + } + iv[il++] = (struct kvec) { &type, 1 }; len++; diff --git a/net/bluetooth/bnep/netdev.c b/net/bluetooth/bnep/netdev.c index ee1e39a3daffb8..b451ef457741f2 100644 --- a/net/bluetooth/bnep/netdev.c +++ b/net/bluetooth/bnep/netdev.c @@ -166,6 +166,12 @@ static netdev_tx_t bnep_net_xmit(struct sk_buff *skb, BT_DBG("skb %p, dev %p", skb, dev); + if (!pskb_may_pull(skb, ETH_HLEN)) { + dev->stats.tx_dropped++; + kfree_skb(skb); + return NETDEV_TX_OK; + } + #ifdef CONFIG_BT_BNEP_MC_FILTER if (bnep_net_mc_filter(skb, s)) { kfree_skb(skb); @@ -218,7 +224,7 @@ void bnep_net_setup(struct net_device *dev) dev->addr_len = ETH_ALEN; ether_setup(dev); - dev->min_mtu = 0; + dev->min_mtu = ETH_MIN_MTU; dev->max_mtu = ETH_MAX_MTU; dev->priv_flags &= ~IFF_TX_SKB_SHARING; dev->netdev_ops = &bnep_netdev_ops; From 37a11129345337efd6eef8e62b03b6348cd0dd8b Mon Sep 17 00:00:00 2001 From: Ravindra Date: Tue, 15 Sep 2026 10:42:15 +0530 Subject: [PATCH 0947/1417] Bluetooth: btintel_pcie: validate device-supplied DMA indices In btintel_pcie_msix_rx_handle(), the driver processes RX completion descriptors (urbd1) written by the PCIe device into DMA-coherent memory. urbd1->frbd_tag (a 16-bit field fully controlled by the device firmware via DMA) is used directly as an array index into rxq->bufs[] without any bounds check. rxq->bufs[] has only BTINTEL_PCIE_RX_DESCS_COUNT (64) entries, while frbd_tag can be any value 0-65535. A malicious or malfunctioning device can write an out-of-range frbd_tag, causing the driver to dereference an out-of-bounds data_buf pointer. Additionally, cr_hia is read from a DMA-shared index array also writable by the device; if the device sets cr_hia >= rxq->count, the while-loop never terminates because cr_tia is wrapped via modulo rxq->count and can never equal an out-of-range cr_hia. Add bounds validation for cr_hia and frbd_tag in the RX path, and cr_hia in the TX path. Log invalid values with bt_dev_err before returning. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Signed-off-by: Ravindra Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 6e6e2b19815ca9..2819e001797b67 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1099,6 +1099,11 @@ static void btintel_pcie_msix_tx_handle(struct btintel_pcie_data *data) txq = &data->txq; + if (cr_hia >= txq->count) { + bt_dev_err(data->hdev, "TXQ: invalid cr_hia %u", cr_hia); + return; + } + while (cr_tia != cr_hia) { data->tx_wait_done = true; wake_up(&data->tx_wait_q); @@ -1650,6 +1655,11 @@ static void btintel_pcie_msix_rx_handle(struct btintel_pcie_data *data) rxq = &data->rxq; + if (cr_hia >= rxq->count) { + bt_dev_err(hdev, "RXQ: invalid cr_hia %u", cr_hia); + return; + } + /* The firmware sends multiple CD in a single MSI-X and it needs to * process all received CDs in this interrupt. */ @@ -1657,6 +1667,12 @@ static void btintel_pcie_msix_rx_handle(struct btintel_pcie_data *data) urbd1 = &rxq->urbd1s[cr_tia]; ipc_print_urbd1(data->hdev, urbd1, cr_tia); + if (urbd1->frbd_tag >= rxq->count) { + bt_dev_err(hdev, "RXQ: invalid frbd_tag %u", + urbd1->frbd_tag); + return; + } + buf = &rxq->bufs[urbd1->frbd_tag]; if (!buf) { bt_dev_err(hdev, "RXQ: failed to get the DMA buffer for %d", From 46f8ffd0a1f1eb6cbc94946a92c11ef601e228a1 Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 11:25:18 +0000 Subject: [PATCH 0948/1417] Bluetooth: RFCOMM: fix NULL dereference of dlc->session in RFCOMM_CONNINFO The RFCOMM_CONNINFO getsockopt handler accepts a socket that is not connected as long as deferred setup is enabled: if (sk->sk_state != BT_CONNECTED && !rfcomm_pi(sk)->dlc->defer_setup) { err = -ENOTCONN; break; } l2cap_sk = rfcomm_pi(sk)->dlc->session->sock->sk; dlc->defer_setup is set in rfcomm_sock_init() when rfcomm_connect_ind() creates a child socket for an incoming connection on a listening socket that has BT_DEFER_SETUP enabled. It is never cleared afterwards. The session, however, can go away underneath it. rfcomm_recv_disc() forces the dlc state before tearing it down: d->state = BT_CLOSED; __rfcomm_dlc_close(d, err); The RFCOMM_DEFER_SETUP early return in __rfcomm_dlc_close() only covers BT_CONNECT, BT_CONFIG, BT_OPEN and BT_CONNECT2, so with the state already BT_CLOSED that switch does not match and the function falls through to rfcomm_dlc_unlink(), which sets d->session = NULL, while d->defer_setup stays 1. A getsockopt(SOL_RFCOMM, RFCOMM_CONNINFO) on the accepted socket after that point therefore skips the -ENOTCONN path -- sk->sk_state is BT_CLOSED, but dlc->defer_setup is still set -- and dereferences the NULL session. No race is needed: once the DISC has been processed, the dereference is unconditional. Reproduced on a KASAN kernel under QEMU with a BR/EDR peer emulated over /dev/vhci: the peer brings up an ACL link, opens L2CAP on the RFCOMM PSM, starts a session and sends SABM for a channel bound with BT_DEFER_SETUP, and sends DISC for that dlci after the socket has been accepted. getsockopt(SOL_RFCOMM, RFCOMM_CONNINFO) on the accepted socket then hits: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000002: 0000 [#1] SMP KASAN PTI KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017] CPU: 1 UID: 0 PID: 150 Comm: init Tainted: G B 7.3.0-rc3-g5dd1818b15d9 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996) RIP: 0010:rfcomm_sock_getsockopt+0x529/0x780 Call Trace: do_sock_getsockopt+0x3ad/0x7d0 __sys_getsockopt+0x10e/0x1b0 __x64_sys_getsockopt+0xc2/0x160 do_syscall_64+0xda/0x4b0 entry_SYSCALL_64_after_hwframe+0x77/0x7f 0x10 is the offset of sock in struct rfcomm_session; rfcomm_sock_getsockopt_old() is inlined into rfcomm_sock_getsockopt(). Commit 43a556b2fd43 ("Bluetooth: RFCOMM: take rfcomm_mutex for the deferred setup accept") fixed the same "a remote DISC clears the session while deferred setup is still flagged" problem in rfcomm_dlc_accept(); this is the remaining instance of it, in the getsockopt path. Deferred setup only leaves a socket usable here once it has reached BT_CONNECT2, so restrict the exception to that state and check that a session is actually present before following it. Fixes: bb23c0ab8246 ("Bluetooth: Add support for deferring RFCOMM connection setup") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/sock.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index e2486bc11cbce0..fb924d0e34ec3c 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -786,8 +786,10 @@ static int rfcomm_sock_getsockopt_old(struct socket *sock, int optname, break; case RFCOMM_CONNINFO: - if (sk->sk_state != BT_CONNECTED && - !rfcomm_pi(sk)->dlc->defer_setup) { + if ((sk->sk_state != BT_CONNECTED && + !(sk->sk_state == BT_CONNECT2 && + rfcomm_pi(sk)->dlc->defer_setup)) || + !rfcomm_pi(sk)->dlc->session) { err = -ENOTCONN; break; } From 6d91041bb38b97e2feb625123cc0529d7b83a0e1 Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 11:25:14 +0000 Subject: [PATCH 0949/1417] Bluetooth: RFCOMM: Reject short EA=0 frames in rfcomm_recv_frame() While rfcomm_recv_frame() verifies that skb->len is at least sizeof(*hdr) + 1 (4 bytes: 3-byte header + 1-byte FCS), an RFCOMM frame with an extended 2-byte length field (!__test_ea(hdr->len)) has a 4-byte header plus a 1-byte FCS (5 bytes minimum, sizeof(*hdr) + 2). When a 4-byte RFCOMM frame with EA == 0 arrives: 1. The initial skb->len < sizeof(*hdr) + 1 check passes (4 < 4 is false). 2. Trimming the FCS byte decrements skb->len to 3. 3. If __check_fcs() succeeds, skb_pull(skb, 4) fails (4 > 3) and returns NULL without advancing skb->data. 4. Because the return value of skb_pull() is ignored, the un-pulled 3-byte struct rfcomm_hdr remains at skb->data and is either queued as application payload via rfcomm_recv_data() or parsed as a multiplexer control command via rfcomm_recv_mcc() on DLCI 0. Fix this by extending the length check in rfcomm_recv_frame() to also require skb->len >= sizeof(*hdr) + 2 when !__test_ea(hdr->len). Fixes: b230e5bf501c ("Bluetooth: RFCOMM: validate skb length in rfcomm_recv_frame") Assisted-by: LLM Signed-off-by: Hui Peng Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index f7463f09228329..d91e2a6ee26c90 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -1817,7 +1817,8 @@ static struct rfcomm_session *rfcomm_recv_frame(struct rfcomm_session *s, return s; } - if (skb->len < sizeof(*hdr) + 1) { + if (skb->len < sizeof(*hdr) + 1 || + (!__test_ea(hdr->len) && skb->len < sizeof(*hdr) + 2)) { kfree_skb(skb); return s; } From 94b7e3a7e871ae27d4935c76959dfc61829f27f9 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Sat, 19 Sep 2026 22:09:20 +0200 Subject: [PATCH 0950/1417] parisc: Increase kernel stack size to 32kb For 64-bit Linux kernels, increase the default kernel stack size (THREAD_SIZE_ORDER) to 32 kB, in order to avoid kernel crashes which have been triggered recently when building the debian vtk9 package with gcc 17: stackcheck: kworker/u128:0 will most likely overflow kernel stack (sp:179a83af0, stk bottom-top:179a80000-179a84000) Kernel panic - not syncing: low stack detected by irq handler - check messages CPU: 2 UID: 0 PID: 30760 Comm: kworker/u128:0 Tainted: G W 6.18.46-dirty #1 NONE Tainted: [W]=WARN Hardware name: 9000/800/rp3440 Workqueue: writeback wb_workfn (flush-259:0) Backtrace: [<000000004022f050>] show_stack+0x70/0x90 [<000000004022378c>] dump_stack_lvl+0x124/0x190 [<000000004022382c>] dump_stack+0x34/0x48 [<000000004020212c>] vpanic+0x204/0x648 [<00000000402025c4>] panic+0x54/0x58 [<0000000040232230>] do_cpu_irq_mask+0x3f8/0x440 [<0000000040227070>] intr_return+0x0/0xc Signed-off-by: Helge Deller Reported-by: John David Anglin Cc: stable@vger.kernel.org # v6.18+ --- arch/parisc/include/asm/thread_info.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/parisc/include/asm/thread_info.h b/arch/parisc/include/asm/thread_info.h index b283738bb6dab3..249370706242c5 100644 --- a/arch/parisc/include/asm/thread_info.h +++ b/arch/parisc/include/asm/thread_info.h @@ -24,7 +24,7 @@ struct thread_info { /* thread information allocation */ -#ifdef CONFIG_IRQSTACKS +#if defined(CONFIG_IRQSTACKS) && !defined(CONFIG_64BIT) #define THREAD_SIZE_ORDER 2 /* PA-RISC requires at least 16k stack */ #else #define THREAD_SIZE_ORDER 3 /* PA-RISC requires at least 32k stack */ From 79a9172f3ab4ad8392c5e5c8944b9b7710ade620 Mon Sep 17 00:00:00 2001 From: Xu Yunxiang Date: Mon, 21 Sep 2026 05:04:21 +0800 Subject: [PATCH 0951/1417] bpf: Reject non-negative offsets in stack_slot_obj_get_spi() bpf_get_spi() computes (-off - 1) / BPF_REG_SIZE using C division, which truncates toward zero. For off == 0, this produces spi 0, the same index used by the valid stack slot at fp-8. stack_slot_obj_get_spi() currently checks alignment and the resulting spi bounds, but does not reject the non-negative offset itself. It can therefore validate a PTR_TO_STACK register holding fp+0 against an iterator stored at fp-8 even though the runtime receives the actual fp+0 pointer. An effectful iterator kfunc can then interpret memory outside the BPF stack as iterator state. Reject non-negative offsets before converting the offset to an spi. All valid stack objects begin at a negative offset from the frame pointer. Fixes: 06accc8779c1 ("bpf: add support for open-coded iterator loops") Signed-off-by: Xu Yunxiang Signed-off-by: Andrii Nakryiko Reviewed-by: Sun Jian Link: https://lore.kernel.org/bpf/20260920210423.345636-2-xyx2021@mail.ustc.edu.cn --- kernel/bpf/verifier.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 02be326f235c15..dd8bb179d39eb9 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -567,7 +567,7 @@ static int stack_slot_obj_get_spi(struct bpf_verifier_env *env, struct bpf_reg_s } off = reg->var_off.value; - if (off % BPF_REG_SIZE) { + if (off >= 0 || off % BPF_REG_SIZE) { verbose(env, "cannot pass in %s at an offset=%d\n", obj_kind, off); return -EINVAL; } From 8244668cbbffc8e242b2c5204a2dea20bfca096c Mon Sep 17 00:00:00 2001 From: Xu Yunxiang Date: Mon, 21 Sep 2026 05:04:22 +0800 Subject: [PATCH 0952/1417] selftests/bpf: Reject iterator destruction through fp+0 Add a verifier regression test that initializes a numeric iterator at fp-8 and attempts to destroy it through fp+0. The verifier must reject the non-negative offset instead of treating it as the initialized stack slot. Check the offset diagnostic to ensure rejection happens at the stack object address check. The numeric iterator destroy operation is a no-op; this test checks verifier rejection and does not run the program. Signed-off-by: Xu Yunxiang Signed-off-by: Andrii Nakryiko Reviewed-by: Sun Jian Link: https://lore.kernel.org/bpf/20260920210423.345636-3-xyx2021@mail.ustc.edu.cn --- .../selftests/bpf/progs/iters_state_safety.c | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/iters_state_safety.c b/tools/testing/selftests/bpf/progs/iters_state_safety.c index 646026430e9b5f..e5bb9fe6d5e531 100644 --- a/tools/testing/selftests/bpf/progs/iters_state_safety.c +++ b/tools/testing/selftests/bpf/progs/iters_state_safety.c @@ -52,6 +52,28 @@ int create_and_destroy(void *ctx) return 0; } +/* fp+0 is not a stack slot. bpf_get_spi(0) used to alias spi 0 (fp-8). */ +SEC("?raw_tp") +__failure __msg("cannot pass in iter at an offset=0") +int destroy_fp0_fail(void *ctx) +{ + struct bpf_iter_num iter; + + asm volatile ("r1 = %[iter];" + "r2 = 0;" + "r3 = 1000;" + "call %[bpf_iter_num_new];" + /* r10 is fp+0, one byte above the top of the BPF stack */ + "r1 = r10;" + "call %[bpf_iter_num_destroy];" + : + : __imm_ptr(iter), ITER_HELPERS + : __clobber_common + ); + + return 0; +} + SEC("?raw_tp") __failure __msg("Unreleased reference id=1") int create_and_forget_to_destroy_fail(void *ctx) From d06f2ebf67ff2962fe00d687e4f0d4703eb41a12 Mon Sep 17 00:00:00 2001 From: Ratheesh Kannoth Date: Wed, 16 Sep 2026 07:51:11 +0530 Subject: [PATCH 0953/1417] octeontx2-af: Fix memory scaling limitation in SR-IOV mode The original code used DMA_ATTR_FORCE_CONTIGUOUS, which could exhaust the CMA pool when a large number of VFs were requested. Fix this by switching to the DMA streaming API. This is equivalent on Octeon platforms, which provide full I/O coherency via the SMMU. Cc: Leon Romanovsky Fixes: 73d33dbc0723 ("octeontx2-af: Use DMA_ATTR_FORCE_CONTIGUOUS attribute in DMA alloc") Signed-off-by: Ratheesh Kannoth Reviewed-by: Leon Romanovsky Link: https://patch.msgid.link/20260916022111.1083017-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- .../ethernet/marvell/octeontx2/af/common.h | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/common.h b/drivers/net/ethernet/marvell/octeontx2/af/common.h index 779413a383b743..78e42549d9908d 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/common.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/common.h @@ -7,6 +7,10 @@ #ifndef COMMON_H #define COMMON_H +#include +#include +#include + #include "rvu_struct.h" #define OTX2_ALIGN 128 /* Align to cacheline */ @@ -44,6 +48,33 @@ struct qmem { u32 qsize; }; +static inline void *otx2_dma_alloc_coherent(struct device *dev, size_t size, + dma_addr_t *dma_handle) +{ + dma_addr_t dma_addr; + void *vaddr; + + vaddr = kzalloc(size, GFP_KERNEL); + if (!vaddr) + return NULL; + + dma_addr = dma_map_single(dev, vaddr, size, DMA_BIDIRECTIONAL); + if (dma_mapping_error(dev, dma_addr)) { + kfree(vaddr); + return NULL; + } + + *dma_handle = dma_addr; + return vaddr; +} + +static inline void otx2_dma_free_coherent(struct device *dev, size_t size, + void *vaddr, dma_addr_t dma_handle) +{ + dma_unmap_single(dev, dma_handle, size, DMA_BIDIRECTIONAL); + kfree(vaddr); +} + static inline int qmem_alloc(struct device *dev, struct qmem **q, int qsize, int entry_sz) { @@ -60,8 +91,11 @@ static inline int qmem_alloc(struct device *dev, struct qmem **q, qmem->entry_sz = entry_sz; qmem->alloc_sz = (qsize * entry_sz) + OTX2_ALIGN; - qmem->base = dma_alloc_attrs(dev, qmem->alloc_sz, &qmem->iova, - GFP_KERNEL, DMA_ATTR_FORCE_CONTIGUOUS); + + if (get_order(PAGE_ALIGN(qmem->alloc_sz)) > MAX_PAGE_ORDER) + return -ENOMEM; + + qmem->base = otx2_dma_alloc_coherent(dev, qmem->alloc_sz, &qmem->iova); if (!qmem->base) return -ENOMEM; @@ -80,10 +114,9 @@ static inline void qmem_free(struct device *dev, struct qmem *qmem) return; if (qmem->base) - dma_free_attrs(dev, qmem->alloc_sz, - qmem->base - qmem->align, - qmem->iova - qmem->align, - DMA_ATTR_FORCE_CONTIGUOUS); + otx2_dma_free_coherent(dev, qmem->alloc_sz, + qmem->base - qmem->align, + qmem->iova - qmem->align); devm_kfree(dev, qmem); } From 0346ec2f080b40d95ed05b853bb9226289e75212 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 18 Sep 2026 08:22:05 +0000 Subject: [PATCH 0954/1417] ipv6: Prevent rt6_insert_exception() for dying fib6_info. Before the cited commit, fib6_nh_flush_exceptions() always set from->exception_bucket_flushed = 1 under rt6_exception_lock to prevent rt6_insert_exception() from inserting a new exception for a dying fib6_info. The flag was replaced with the FIB6_EXCEPTION_BUCKET_FLUSHED bit stored in nh->rt6i_exception_bucket. The problem is that now the bit is only set when the bucket is not NULL and fib6_nh_flush_exceptions() is called from fib6_nh_release() after fib6_ref has already reached zero. If rt6_insert_exception() is called while the target fib6_info is being removed via fib6_purge_rt(), a new exception could be created successfully because rt6_flush_exceptions() no longer sets the bit. This creates a reference cycle between the fib6_info and the exception route, leaking the fib6_info, its nexthop device, and all per-CPU routes in fib6_nh->rt6i_pcpu, which stalls netdev unregistration. [ 34.680602] unregister_netdevice: waiting for gre6 to become free. Usage count = 68 [ 44.920675] unregister_netdevice: waiting for gre6 to become free. Usage count = 68 [ 55.176582] unregister_netdevice: waiting for gre6 to become free. Usage count = 68 Let's call fib6_drop_pcpu_from() before rt6_flush_exceptions(), to set fib6_destroying before rt6_exception_lock, and check f6i->fib6_destroying in rt6_insert_exception(). Note that FIB6_EXCEPTION_BUCKET_FLUSHED logic is dead and we can clean it up in net-next. Fixes: cc5c073a693f ("ipv6: Move exception bucket to fib6_nh") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260918082209.2853582-1-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_fib.c | 2 +- net/ipv6/route.c | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 9ea75703b38d4a..9ff761962b45cf 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -1043,8 +1043,8 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn, struct fib6_table *table = rt->fib6_table; /* Flush all cached dst in exception table */ - rt6_flush_exceptions(rt); fib6_drop_pcpu_from(rt); + rt6_flush_exceptions(rt); if (rt->nh) { spin_lock(&rt->nh->lock); diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 08bd68f1b5bb42..884d9ab0d50d62 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -1729,6 +1729,11 @@ static int rt6_insert_exception(struct rt6_info *nrt, spin_lock_bh(&rt6_exception_lock); + if (f6i->fib6_destroying) { + err = -ENOENT; + goto out; + } + bucket = rcu_dereference_protected(nh->rt6i_exception_bucket, lockdep_is_held(&rt6_exception_lock)); if (!bucket) { From 31995571219c8ac30913d9c0dccad033fbb0b3da Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Fri, 18 Sep 2026 11:55:40 +0200 Subject: [PATCH 0955/1417] net: don't require the hwtstamp NDOs when a PHY provides timestamping Removing the legacy ioctl fallback made both hwtstamp NDOs mandatory. A device that only timestamps in its PHY implements neither, so SIOCSHWTSTAMP fails with EOPNOTSUPP before anything looks at the PHY and PTP stops working there. The check only ever picked the legacy path. That path is gone, so drop it and test where the NDOs are actually called. SIOCGHWTSTAMP is new here, not restored. The old path went through phy_mii_ioctl(), which only handled SIOCSHWTSTAMP. Such a device now returns -ENODEV while absent instead of -EOPNOTSUPP, like the ones that do implement the NDOs. Fixes: 5062245a5a7f ("net: remove legacy way to get/set HW timestamp config") Signed-off-by: Nicolai Buchwitz Reviewed-by: Kory Maincent Link: https://patch.msgid.link/20260918095540.34286-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- net/core/dev_ioctl.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c index a320e264eaaf09..164643140a5234 100644 --- a/net/core/dev_ioctl.c +++ b/net/core/dev_ioctl.c @@ -276,19 +276,18 @@ int dev_get_hwtstamp_phylib(struct net_device *dev, if (phy_is_default_hwtstamp(dev->phydev)) return phy_hwtstamp_get(dev->phydev, cfg); + if (!dev->netdev_ops->ndo_hwtstamp_get) + return -EOPNOTSUPP; + return dev->netdev_ops->ndo_hwtstamp_get(dev, cfg); } static int dev_get_hwtstamp(struct net_device *dev, struct ifreq *ifr) { - const struct net_device_ops *ops = dev->netdev_ops; struct kernel_hwtstamp_config kernel_cfg = {}; struct hwtstamp_config cfg; int err; - if (!ops->ndo_hwtstamp_get) - return -EOPNOTSUPP; - if (!netif_device_present(dev)) return -ENODEV; @@ -359,12 +358,18 @@ int dev_set_hwtstamp_phylib(struct net_device *dev, cfg->source = phy_ts ? HWTSTAMP_SOURCE_PHYLIB : HWTSTAMP_SOURCE_NETDEV; if (phy_ts && dev->see_all_hwtstamp_requests) { + if (!ops->ndo_hwtstamp_get) + return -EOPNOTSUPP; + err = ops->ndo_hwtstamp_get(dev, &old_cfg); if (err) return err; } if (!phy_ts || dev->see_all_hwtstamp_requests) { + if (!ops->ndo_hwtstamp_set) + return -EOPNOTSUPP; + err = ops->ndo_hwtstamp_set(dev, cfg, extack); if (err) { if (extack->_msg) @@ -390,7 +395,6 @@ int dev_set_hwtstamp_phylib(struct net_device *dev, static int dev_set_hwtstamp(struct net_device *dev, struct ifreq *ifr) { - const struct net_device_ops *ops = dev->netdev_ops; struct kernel_hwtstamp_config kernel_cfg = {}; struct netlink_ext_ack extack = {}; struct hwtstamp_config cfg; @@ -413,9 +417,6 @@ static int dev_set_hwtstamp(struct net_device *dev, struct ifreq *ifr) return err; } - if (!ops->ndo_hwtstamp_set) - return -EOPNOTSUPP; - if (!netif_device_present(dev)) return -ENODEV; @@ -441,15 +442,11 @@ static int dev_set_hwtstamp(struct net_device *dev, struct ifreq *ifr) int generic_hwtstamp_get_lower(struct net_device *dev, struct kernel_hwtstamp_config *kernel_cfg) { - const struct net_device_ops *ops = dev->netdev_ops; int err; if (!netif_device_present(dev)) return -ENODEV; - if (!ops->ndo_hwtstamp_get) - return -EOPNOTSUPP; - netdev_lock_ops(dev); err = dev_get_hwtstamp_phylib(dev, kernel_cfg); netdev_unlock_ops(dev); @@ -462,15 +459,11 @@ int generic_hwtstamp_set_lower(struct net_device *dev, struct kernel_hwtstamp_config *kernel_cfg, struct netlink_ext_ack *extack) { - const struct net_device_ops *ops = dev->netdev_ops; int err; if (!netif_device_present(dev)) return -ENODEV; - if (!ops->ndo_hwtstamp_set) - return -EOPNOTSUPP; - netdev_lock_ops(dev); err = dev_set_hwtstamp_phylib(dev, kernel_cfg, extack); netdev_unlock_ops(dev); From 7cce782d8327b7291334c4a304cf3fd909a74d9d Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Thu, 17 Sep 2026 16:37:36 +0200 Subject: [PATCH 0956/1417] dpll: use exact lookup for reference sync pin id dpll_pin_ref_sync_state_set() looks up the reference sync pin in the pin->ref_sync_pins xarray, which is keyed by the sync pin's id (see dpll_pin_ref_sync_pair_add() using xa_insert() with ref_sync_pin->id). The pin id to operate on is supplied by userspace via DPLL_A_PIN_ID. The lookup however used xa_find() with a ULONG_MAX limit, which returns the first present entry with an index greater than or equal to the requested id, not the entry stored exactly at that id. If userspace passes an id that is not paired as a reference sync pin, but another pin with a higher id is present in the xarray, xa_find() silently returns that wrong pin and the subsequent ref_sync_set() operates on it. The request only fails when the given id is larger than every present key. Use xa_load() for an exact-key lookup instead, mirroring the deletion path in dpll_pin_ref_sync_pair_del(). Fixes: 58256a26bfb3 ("dpll: add reference sync get/set") Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260917143736.526221-1-ivecera@redhat.com Signed-off-by: Jakub Kicinski --- drivers/dpll/dpll_netlink.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 45365214fbefa2..fb24fd53f2e19d 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -1210,8 +1210,7 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, struct dpll_device *dpll; int ret; - ref_sync_pin = xa_find(&pin->ref_sync_pins, &ref_sync_pin_idx, - ULONG_MAX, XA_PRESENT); + ref_sync_pin = xa_load(&pin->ref_sync_pins, ref_sync_pin_idx); if (!ref_sync_pin) { NL_SET_ERR_MSG(extack, "reference sync pin not found"); return -EINVAL; From c06bde80ae7a7b595732f7cabcb92cf08db9d56a Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 20 Sep 2026 11:47:45 +0800 Subject: [PATCH 0957/1417] net: usb: sr9700: include receive overhead in the length check The receive fixup subtracts the Ethernet CRC from the reported packet length, but compares that payload length against the whole remaining receive buffer. The following copy starts after the three-byte header, and the cursor advance consumes both that header and the four-byte CRC. Require the payload to fit after SR_RX_OVERHEAD before copying it or advancing to the next packet. The loop already ensures that the remaining buffer is larger than the overhead, so the subtraction is safe. The issue was found by our static-analysis tool. Fixes: c9b37458e956 ("USB2NET : SR9700 : One chip USB 1.1 USB2NET SR9700Device Driver Support") Reviewed-by: Ethan Nelson-Moore Tested-by: Ethan Nelson-Moore Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260920034745.18468-1-hppiscas@163.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/sr9700.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/usb/sr9700.c b/drivers/net/usb/sr9700.c index 937e6fef3ac6d2..50981a28376a53 100644 --- a/drivers/net/usb/sr9700.c +++ b/drivers/net/usb/sr9700.c @@ -355,7 +355,8 @@ static int sr9700_rx_fixup(struct usbnet *dev, struct sk_buff *skb) /* ignore the CRC length */ len = (skb->data[1] | (skb->data[2] << 8)) - 4; - if (len > ETH_FRAME_LEN || len > skb->len || len < 0) + if (len > ETH_FRAME_LEN || len < 0 || + len > skb->len - SR_RX_OVERHEAD) return 0; /* the last packet of current skb */ From be581d6635579489eadff9cea4caee142ec6d8bc Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Fri, 18 Sep 2026 22:49:30 +0900 Subject: [PATCH 0958/1417] selftests: net: fix CONFIG_SYSCTL sort order in configs Commit 8d75c338f0bc ("sysctl: remove CONFIG_PROC_SYSCTL, it just mirrors CONFIG_SYSCTL") renamed CONFIG_PROC_SYSCTL to CONFIG_SYSCTL in place, which left the entry out of alphabetical order in the net and packetdrill configs. The netdev CI check for sorted selftest configs now fails for every patch that touches either file. Fixes: 8d75c338f0bc ("sysctl: remove CONFIG_PROC_SYSCTL, it just mirrors CONFIG_SYSCTL") Signed-off-by: Yuya Kusakabe Reviewed-by: Joel Granados Link: https://patch.msgid.link/20260918-selftests-net-config-sort-v1-1-968ea6e8c1b7@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/config | 2 +- tools/testing/selftests/net/packetdrill/config | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config index 30d5fcb09a8319..737e7e6327b379 100644 --- a/tools/testing/selftests/net/config +++ b/tools/testing/selftests/net/config @@ -118,10 +118,10 @@ CONFIG_NFT_NAT=m CONFIG_NUMA=y CONFIG_OPENVSWITCH=m CONFIG_PAGE_POOL_STATS=y -CONFIG_SYSCTL=y CONFIG_PSAMPLE=m CONFIG_RPS=y CONFIG_SYN_COOKIES=y +CONFIG_SYSCTL=y CONFIG_SYSFS=y CONFIG_TAP=m CONFIG_TCP_CONG_DCTCP=y diff --git a/tools/testing/selftests/net/packetdrill/config b/tools/testing/selftests/net/packetdrill/config index 83dde525c53c99..b7df8bf3092004 100644 --- a/tools/testing/selftests/net/packetdrill/config +++ b/tools/testing/selftests/net/packetdrill/config @@ -4,8 +4,8 @@ CONFIG_IPV6=y CONFIG_NET_NS=y CONFIG_NET_SCH_FIFO=y CONFIG_NET_SCH_FQ=y -CONFIG_SYSCTL=y CONFIG_SYN_COOKIES=y +CONFIG_SYSCTL=y CONFIG_TCP_CONG_CUBIC=y CONFIG_TCP_MD5SIG=y CONFIG_TUN=y From 10de7ed8ef4840da9ca21de4c29578657ac367db Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Thu, 17 Sep 2026 13:55:42 +0200 Subject: [PATCH 0959/1417] net/mlx5e: fix swapped IPv6 IPsec policy masks IPv6 XFRM policies may use different source and destination prefix lengths. mlx5e_ipsec_policy_mask() builds the corresponding masks independently, but setup_fte_addr6() installs each mask in the opposite address field. When the prefix lengths differ, this makes the source match use the destination prefix and the destination match use the source prefix. The resulting hardware rule can both miss traffic covered by the policy and match traffic outside it. Install each mask in its corresponding match field. Fixes: ca7992f52c2c ("net/mlx5e: Properly match IPsec subnet addresses") Cc: stable@vger.kernel.org Signed-off-by: Andrea Parri Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260917115542.177675-1-parri.andrea@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c index 329608c59313bc..8ffa8068e90ac9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c @@ -1564,14 +1564,14 @@ static void setup_fte_addr6(struct mlx5_flow_spec *spec, memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value, outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6), saddr, 16); memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_criteria, - outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6), dmask, 16); + outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6), smask, 16); } if (!addr6_all_zero(daddr)) { memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value, outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6), daddr, 16); memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_criteria, - outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6), smask, 16); + outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6), dmask, 16); } } From 67f4c1c6a1b51e203d986779299824d1c2c590a6 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 16 Sep 2026 15:29:24 +0000 Subject: [PATCH 0960/1417] smb: client: fix create context out-of-bounds reads smb2_parse_contexts() validates the complete create-context area but does not limit each record to its Next field before dispatching it. A malformed chain can therefore expose bytes beyond the current context to a handler. The QFid handler also used a full response-structure cast although it only reads DiskFileId. The SMB2/SMB3 lease parsers made the same layout assumption: they read LeaseState and LeaseFlags at canonical offsets rather than at DataOffset. A valid non-canonical DataOffset could therefore yield unrelated in-bounds data, while a short DataLength was still accepted. Limit each context to its Next value, reject offsets before the context header, and reject malformed chains. Bound the name range by the current context and do not dispatch a known handler when DataLength is zero. Read the QFid DiskFileId only when the context data covers that field. Parse the lease context from DataOffset and require DataLength to match the v1 or v2 lease_context size used by ksmbd. A size mismatch skips lease parsing without failing the open. Fixes: b8c32dbb0deb ("CIFS: Request SMB2.1 leases") Fixes: f047390a097e ("CIFS: Add create lease v2 context for SMB3") Fixes: 89a5bfa350fa ("smb3: optimize open to not send query file internal info") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Tested-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 28 ++++++++++++++++++-------- fs/smb/client/smb2pdu.c | 44 +++++++++++++++++++++++++++++++---------- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 3464470d329775..aa142420dae2e6 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4572,25 +4572,37 @@ smb3_create_lease_buf(u8 *lease_key, u8 oplock, u8 *parent_lease_key, __le32 fla static __u8 smb2_parse_lease_buf(void *buf, __u16 *epoch, char *lease_key) { - struct create_lease *lc = (struct create_lease *)buf; + struct create_context *cc = buf; + struct lease_context lc; *epoch = 0; /* not used */ - if (lc->lcontext.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE) + if (le32_to_cpu(cc->DataLength) != sizeof(lc)) + return 0; + + memcpy(&lc, (u8 *)cc + le16_to_cpu(cc->DataOffset), sizeof(lc)); + if (lc.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE) return SMB2_OPLOCK_LEVEL_NOCHANGE; - return le32_to_cpu(lc->lcontext.LeaseState); + return le32_to_cpu(lc.LeaseState); } static __u8 smb3_parse_lease_buf(void *buf, __u16 *epoch, char *lease_key) { - struct create_lease_v2 *lc = (struct create_lease_v2 *)buf; + struct create_context *cc = buf; + struct lease_context_v2 lc; + + if (le32_to_cpu(cc->DataLength) != sizeof(lc)) { + *epoch = 0; + return 0; + } - *epoch = le16_to_cpu(lc->lcontext.Epoch); - if (lc->lcontext.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE) + memcpy(&lc, (u8 *)cc + le16_to_cpu(cc->DataOffset), sizeof(lc)); + *epoch = le16_to_cpu(lc.Epoch); + if (lc.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE) return SMB2_OPLOCK_LEVEL_NOCHANGE; if (lease_key) - memcpy(lease_key, &lc->lcontext.LeaseKey, SMB2_LEASE_KEY_SIZE); - return le32_to_cpu(lc->lcontext.LeaseState); + memcpy(lease_key, lc.LeaseKey, SMB2_LEASE_KEY_SIZE); + return le32_to_cpu(lc.LeaseState); } static unsigned int diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 880ce12f50c481..4046500dbe93b3 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -2379,11 +2379,17 @@ create_reconnect_durable_buf(struct cifs_fid *fid) static void parse_query_id_ctxt(struct create_context *cc, struct smb2_file_all_info *buf) { - struct create_disk_id_rsp *pdisk_id = (struct create_disk_id_rsp *)cc; + u16 doff = le16_to_cpu(cc->DataOffset); + u32 dlen = le32_to_cpu(cc->DataLength); + u8 *beg; - cifs_dbg(FYI, "parse query id context 0x%llx 0x%llx\n", - pdisk_id->DiskFileId, pdisk_id->VolumeId); - buf->IndexNumber = pdisk_id->DiskFileId; + if (dlen < sizeof(__le64)) + return; + + beg = (u8 *)cc + doff; + memcpy(&buf->IndexNumber, beg, sizeof(__le64)); + cifs_dbg(FYI, "parse query id context 0x%llx\n", + le64_to_cpu(buf->IndexNumber)); } static void @@ -2431,6 +2437,7 @@ int smb2_parse_contexts(struct TCP_Server_Info *server, struct smb2_create_rsp *rsp = rsp_iov->iov_base; struct create_context *cc; size_t rem, off, len; + size_t cc_len; size_t doff, dlen; size_t noff, nlen; char *name; @@ -2453,29 +2460,41 @@ int smb2_parse_contexts(struct TCP_Server_Info *server, buf->IndexNumber = 0; while (rem >= sizeof(*cc)) { + off = le32_to_cpu(cc->Next); + if (off) { + if ((off & 0x7) || off >= rem || off < sizeof(*cc)) + return -EINVAL; + cc_len = off; + } else { + cc_len = rem; + } + doff = le16_to_cpu(cc->DataOffset); dlen = le32_to_cpu(cc->DataLength); - if (check_add_overflow(doff, dlen, &len) || len > rem) + if (doff < sizeof(*cc) || + check_add_overflow(doff, dlen, &len) || len > cc_len) return -EINVAL; noff = le16_to_cpu(cc->NameOffset); nlen = le16_to_cpu(cc->NameLength); - if (noff + nlen > doff) + if (noff < sizeof(*cc) || + check_add_overflow(noff, nlen, &len) || len > cc_len || + (dlen && len > doff)) return -EINVAL; name = (char *)cc + noff; switch (nlen) { case 4: - if (!strncmp(name, SMB2_CREATE_REQUEST_LEASE, 4)) { + if (dlen && !strncmp(name, SMB2_CREATE_REQUEST_LEASE, 4)) { *oplock = server->ops->parse_lease_buf(cc, epoch, lease_key); - } else if (buf && + } else if (dlen && buf && !strncmp(name, SMB2_CREATE_QUERY_ON_DISK_ID, 4)) { parse_query_id_ctxt(cc, buf); } break; case 16: - if (posix && !memcmp(name, smb3_create_tag_posix, 16)) + if (dlen && posix && !memcmp(name, smb3_create_tag_posix, 16)) parse_posix_ctxt(cc, buf, posix); break; default: @@ -2487,13 +2506,18 @@ int smb2_parse_contexts(struct TCP_Server_Info *server, } off = le32_to_cpu(cc->Next); - if (!off) + if (!off) { + rem = 0; break; + } if (check_sub_overflow(rem, off, &rem)) return -EINVAL; cc = (struct create_context *)((u8 *)cc + off); } + if (rem) + return -EINVAL; + if (rsp->OplockLevel != SMB2_OPLOCK_LEVEL_LEASE) *oplock = rsp->OplockLevel; From fa2e9900dd2a3f5a1e7ef5a8c5e8d435feedbfcc Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 16 Sep 2026 15:29:25 +0000 Subject: [PATCH 0961/1417] smb: client: validate POSIX create context length parse_posix_ctxt() reads the fixed nlink, reparse_tag, and mode fields before checking that the POSIX create context contains them. A short context can pass the generic checks and still make these fixed-width reads run past its declared data. The current in-tree smb2_open_file() path passes a NULL posix pointer, so this handler is not reached on the ordinary open path. Still require the POSIX data to cover all three fields before reading them because the helper performs those unguarded reads. Keep the existing soft-failure behavior so malformed optional metadata does not fail the open. Fixes: 69dda3059e7a ("cifs: add SMB2_open() arg to return POSIX data") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Tested-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2pdu.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 4046500dbe93b3..538d708b04047e 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -2396,12 +2396,15 @@ static void parse_posix_ctxt(struct create_context *cc, struct smb2_file_all_info *info, struct create_posix_rsp *posix) { - int sid_len; u8 *beg = (u8 *)cc + le16_to_cpu(cc->DataOffset); - u8 *end = beg + le32_to_cpu(cc->DataLength); + u32 dlen = le32_to_cpu(cc->DataLength); + u8 *end = beg + dlen; + int sid_len; u8 *sid; memset(posix, 0, sizeof(*posix)); + if (dlen < 3 * sizeof(__le32)) + return; posix->nlink = get_unaligned_le32(beg); posix->reparse_tag = get_unaligned_le32(beg + 4); From 566820af017e81497fb5e9d3ad6e7ffe2828bc8b Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 16 Sep 2026 15:29:26 +0000 Subject: [PATCH 0962/1417] smb: client: close handle after create-context parsing failure SMB2_open() accounts a successful CREATE response as a remote open before parsing its create contexts. If smb2_parse_contexts() rejects malformed context data, SMB2_open() returns without closing the handle, leaving the server-side handle open and num_remote_opens elevated. Close the handle after a post-CREATE context parsing failure so the error path releases the remote resource and balances the open count. Fixes: af1689a9b770 ("smb: client: fix potential OOBs in smb2_parse_contexts()") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Tested-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2pdu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 538d708b04047e..3d7ead36d1a0e6 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -3416,6 +3416,9 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path, rc = smb2_parse_contexts(server, &rsp_iov, &oparms->fid->epoch, oparms->fid->lease_key, oplock, file_info, posix); + if (rc) + SMB2_close(xid, tcon, oparms->fid->persistent_fid, + oparms->fid->volatile_fid); trace_smb3_open_done(xid, rsp->PersistentFileId, tcon->tid, ses->Suid, oparms->create_options, oparms->desired_access, From d2ff5fb93ea83034025850266b5eed391f96b825 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 16 Sep 2026 15:29:27 +0000 Subject: [PATCH 0963/1417] smb: client: clean up failed cached directory opens open_cached_dir() sends CREATE and QUERY_INFO as a compound request. If the CREATE succeeds but a later command returns an error, the function must retain the CREATE FID so common cleanup can issue SMB2_close(). It also must not treat a response error as a valid CREATE. Validate the CREATE response before using its fields, record the FIDs, and mark the handle open before handling errors from later compound commands. Move the -EREMCHG reconnect handling before response validation so a missing response does not hide the reconnect request. Count the handle when it is marked open; confirmed close responses decrement the counter, while existing close retry behavior remains best effort on transport failures. Fixes: b0f6df737a1c ("cifs: cache FILE_ALL_INFO for the shared root handle") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Tested-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/cached_dir.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index 88d5e9a32f28b3..647fa26da4d24c 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -8,6 +8,7 @@ #include #include "cifsglob.h" #include "cifsproto.h" +#include "../common/smb2status.h" #include "cifs_debug.h" #include "smb2proto.h" #include "cached_dir.h" @@ -323,25 +324,37 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, rc = compound_send_recv(xid, ses, server, flags, 2, rqst, resp_buftype, rsp_iov); - if (rc) { - if (rc == -EREMCHG) { - tcon->need_reconnect = true; - pr_warn_once("server share %s deleted\n", - tcon->tree_name); - } - goto oshr_free; + if (rc == -EREMCHG) { + tcon->need_reconnect = true; + pr_warn_once("server share %s deleted\n", + tcon->tree_name); } - cfid->is_open = true; - spin_lock(&cfids->cfid_list_lock); + if (!rsp_iov[0].iov_base || rsp_iov[0].iov_len < sizeof(*o_rsp)) { + if (!rc) + rc = -EIO; + goto oshr_free; + } o_rsp = (struct smb2_create_rsp *)rsp_iov[0].iov_base; + if (o_rsp->hdr.Status != STATUS_SUCCESS) { + if (!rc) + rc = -EIO; + goto oshr_free; + } + oparms.fid->persistent_fid = o_rsp->PersistentFileId; oparms.fid->volatile_fid = o_rsp->VolatileFileId; #ifdef CONFIG_CIFS_DEBUG2 oparms.fid->mid = le64_to_cpu(o_rsp->hdr.MessageId); #endif /* CIFS_DEBUG2 */ + cfid->is_open = true; + atomic_inc(&tcon->num_remote_opens); + if (rc) + goto oshr_free; + + spin_lock(&cfids->cfid_list_lock); if (o_rsp->OplockLevel != SMB2_OPLOCK_LEVEL_LEASE) { spin_unlock(&cfids->cfid_list_lock); @@ -408,7 +421,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, close_cached_dir(cfid); } else { *ret_cfid = cfid; - atomic_inc(&tcon->num_remote_opens); } kfree(utf16_path); From 6c5c547f037bc18f0b8d0b5db5a648f8f630ce85 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 16 Sep 2026 15:29:28 +0000 Subject: [PATCH 0964/1417] smb: client: close completed creates on compound wait errors compound_send_recv() waits for responses in order. If a later wait is interrupted, or if a later MID fails during response synchronization, an earlier CREATE may already have opened a remote handle. The earlier mid is then released without invoking handle_cancelled_mid(), leaving the remote handle open because no FID was copied to the caller. Mark completed earlier mids as cancelled when a compound wait or MID synchronization aborts. Keep their response buffers attached while the MIDs are synchronized, and transfer them only after synchronization of the processed responses, so the release path can inspect successful CREATE responses and queue SMB2_close() after a later failure. Account for a remote open only after the close work is allocated and before it is queued, since the caller has not yet updated num_remote_opens. Mark the create+close compound used by smb2_unlink() so it is not closed again. Non-CREATE responses and compounds that already include a close keep their existing behavior. Fixes: e0bba0b85481 ("cifs: add compound_send_recv()") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Tested-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2inode.c | 2 +- fs/smb/client/smb2misc.c | 9 ++++-- fs/smb/client/transport.c | 67 +++++++++++++++++++++++++++++++-------- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 13fe8e3b48f319..ecd7a65cab0848 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -1121,7 +1121,7 @@ smb2_unlink(const unsigned int xid, struct cifs_tcon *tcon, const char *name, struct kvec close_iov; int resp_buftype[2]; struct cifs_fid fid; - int flags = 0; + int flags = CIFS_CP_CREATE_CLOSE_OP; __u8 oplock; int rc; diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c index 0cfe60ae42c380..5e5cf92d1eb340 100644 --- a/fs/smb/client/smb2misc.c +++ b/fs/smb/client/smb2misc.c @@ -834,7 +834,8 @@ smb2_cancelled_close_fid(struct work_struct *work) */ static int __smb2_handle_cancelled_cmd(struct cifs_tcon *tcon, __u16 cmd, __u64 mid, - __u64 persistent_fid, __u64 volatile_fid) + __u64 persistent_fid, __u64 volatile_fid, + bool account_remote_open) { struct close_cancelled_open *cancelled; @@ -848,6 +849,8 @@ __smb2_handle_cancelled_cmd(struct cifs_tcon *tcon, __u16 cmd, __u64 mid, cancelled->cmd = cmd; cancelled->mid = mid; INIT_WORK(&cancelled->work, smb2_cancelled_close_fid); + if (account_remote_open) + atomic_inc(&tcon->num_remote_opens); WARN_ON(queue_work(cifsiod_wq, &cancelled->work) == false); return 0; @@ -884,7 +887,7 @@ smb2_handle_cancelled_close(struct cifs_tcon *tcon, __u64 persistent_fid, spin_unlock(&tcon->tc_lock); rc = __smb2_handle_cancelled_cmd(tcon, SMB2_CLOSE_HE, 0, - persistent_fid, volatile_fid); + persistent_fid, volatile_fid, false); if (rc) cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_close); @@ -912,7 +915,7 @@ smb2_handle_cancelled_mid(struct mid_q_entry *mid, struct TCP_Server_Info *serve le16_to_cpu(hdr->Command), le64_to_cpu(hdr->MessageId), rsp->PersistentFileId, - rsp->VolatileFileId); + rsp->VolatileFileId, true); if (rc) cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_mid); diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index 7df5b3447aea78..6e21b5f8754a1e 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -805,6 +805,18 @@ cifs_cancelled_callback(struct TCP_Server_Info *server, struct mid_q_entry *mid) release_mid(server, mid); } +static void +cifs_mark_compound_mids_cancelled(struct mid_q_entry **mid, int count) +{ + int i; + + for (i = 0; i < count; i++) { + spin_lock(&mid[i]->mid_lock); + mid[i]->wait_cancelled = true; + spin_unlock(&mid[i]->mid_lock); + } +} + /* * cifs_pick_channel - pick an eligible channel for network operations * @@ -865,6 +877,7 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, int *resp_buf_type, struct kvec *resp_iov) { int i, j, optype, rc = 0; + int num_processed = 0; struct mid_q_entry *mid[MAX_COMPOUND]; bool cancelled_mid[MAX_COMPOUND] = {false}; struct cifs_credits credits[MAX_COMPOUND] = { @@ -1015,6 +1028,14 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, break; } if (rc != 0) { + /* + * A completed CREATE earlier in the compound chain may have + * opened a remote handle even though a later wait was + * interrupted. Mark it cancelled so __release_mid() invokes + * the existing unmatched-open cleanup. + */ + cifs_mark_compound_mids_cancelled(mid, i); + for (; i < num_rqst; i++) { cifs_server_dbg(FYI, "Cancelling wait for mid %llu cmd: %d\n", mid[i]->mid, le16_to_cpu(mid[i]->command)); @@ -1037,6 +1058,14 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, rc = cifs_sync_mid_result(mid[i], server); if (rc != 0) { + /* + * A previous CREATE may have completed before this + * response failed. Mark it cancelled so its remote + * handle is closed when the mid is released. + */ + cifs_mark_compound_mids_cancelled(mid, i); + /* Keep their response buffers for cancelled-mid cleanup. */ + num_processed = 0; /* mark this mid as cancelled to not free it below */ cancelled_mid[i] = true; goto out; @@ -1046,13 +1075,24 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, mid[i]->mid_state != MID_RESPONSE_READY) { rc = smb_EIO1(smb_eio_trace_rx_mid_unready, mid[i]->mid_state); cifs_dbg(FYI, "Bad MID state?\n"); + cifs_mark_compound_mids_cancelled(mid, i); + num_processed = 0; goto out; } rc = server->ops->check_receive(mid[i], server, flags & CIFS_LOG_ERROR); + num_processed = i + 1; + } - if (resp_iov) { +out: + /* + * Delay moving response buffers out of their mids until response + * synchronization completes. This lets cancelled-mid cleanup inspect + * an earlier CREATE response if a later MID fails. + */ + if (resp_iov) { + for (i = 0; i < num_processed; i++) { buf = (char *)mid[i]->resp_buf; resp_iov[i].iov_base = buf; resp_iov[i].iov_len = mid[i]->resp_buf_size; @@ -1071,21 +1111,22 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, /* * Compounding is never used during session establish. */ - spin_lock(&ses->ses_lock); - if ((ses->ses_status == SES_NEW) || (optype & CIFS_NEG_OP) || (optype & CIFS_SESS_OP)) { - struct kvec iov = { - .iov_base = resp_iov[0].iov_base, - .iov_len = resp_iov[0].iov_len - }; - spin_unlock(&ses->ses_lock); - cifs_server_lock(server); - smb311_update_preauth_hash(ses, server, &iov, 1); - cifs_server_unlock(server); + if (num_processed == num_rqst) { spin_lock(&ses->ses_lock); + if ((ses->ses_status == SES_NEW) || (optype & CIFS_NEG_OP) || (optype & CIFS_SESS_OP)) { + struct kvec iov = { + .iov_base = resp_iov[0].iov_base, + .iov_len = resp_iov[0].iov_len + }; + spin_unlock(&ses->ses_lock); + cifs_server_lock(server); + smb311_update_preauth_hash(ses, server, &iov, 1); + cifs_server_unlock(server); + spin_lock(&ses->ses_lock); + } + spin_unlock(&ses->ses_lock); } - spin_unlock(&ses->ses_lock); -out: /* * This will dequeue all mids. After this it is important that the * demultiplex_thread will not process any of these mids any further. From 2e828035d5d736d904c238ae7ec3f77c0d6270bf Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 16 Sep 2026 15:29:29 +0000 Subject: [PATCH 0965/1417] smb: client: preserve create-context parsing errors smb2_compound_op() saves the result from compound_send_recv() in tmp_rc. For SMB2_OP_OPEN_QUERY it then parses the CREATE contexts, but the final assignment of rc from tmp_rc discards a parsing error. A malformed create-context response can therefore be reported as successful to smb2_query_path_info(). Keep a create-context parsing error in tmp_rc so it survives per-command response processing and is returned to the caller. Fixes: b07687edee99 ("cifs: Improve SMB2+ stat() to work also without FILE_READ_ATTRIBUTES") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Tested-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2inode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index ecd7a65cab0848..f46a62eae65912 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -598,8 +598,10 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, /* smb2_parse_contexts() fills idata->fi.IndexNumber */ rc = smb2_parse_contexts(server, &rsp_iov[0], &oparms->fid->epoch, oparms->fid->lease_key, &oplock, &idata->fi, NULL); - if (rc) + if (rc) { cifs_dbg(VFS, "rc: %d parsing context of compound op\n", rc); + tmp_rc = rc; + } } for (i = 0; i < num_cmds; i++) { From be31fe6333f534155e6b408f1ef6d77974bb41aa Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sun, 20 Sep 2026 19:14:32 +0000 Subject: [PATCH 0966/1417] ipv6: Fix dst leak for uncached routes. ip6_route_output_flags(), ip6_rt_put_flags(), and ip6_dst_check() detect an uncached route by list_empty(&rt->dst.rt_uncached), which replaced the static DST_NOCACHE flag check in commit a4c2fd7f7891 ("net: remove DST_NOCACHE flag"). When a device is unregistered, rt6_uncached_list_flush_dev() unlinks uncached routes tied to the device from rt6_uncached_list. Previously, they were moved to another list with list_move() (__list_del_entry() + list_add()), and since commit 98aa546af5e4 ("inet: remove (struct uncached_list)->quarantine"), the routes are just unlinked with list_del_init(). If list_del_init() runs concurrently, list_empty() evaluates to true; ip6_route_output_flags() calls dst_hold_safe() incorrectly and ip6_rt_put_flags() skips ip6_rt_put(), leaking dst, and thus dev tied via rt->from as well. The same race is partially fixed by commit 9a6f0c4d5796 ("dst: fix races in rt6_uncached_list_del() and rt_del_uncached_list()"). Let's check rt6->dst.rt_uncached_list instead. Note that IPv4 does not have the same issue. Fixes: 98aa546af5e4 ("inet: remove (struct uncached_list)->quarantine") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Hangbin Liu Reviewed-by: Xuanqiang Luo Reviewed-by: Ido Schimmel Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260920191558.2990636-1-kuniyu@google.com Signed-off-by: Jakub Kicinski --- include/net/ip6_route.h | 4 ++-- net/ipv6/route.c | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index b9e8d2b759e9b7..0f9b7a260d2537 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -101,12 +101,12 @@ static inline struct dst_entry *ip6_route_output(struct net *net, } /* Only conditionally release dst if flags indicates - * !RT6_LOOKUP_F_DST_NOREF or dst is in uncached_list. + * !RT6_LOOKUP_F_DST_NOREF or dst is uncached. */ static inline void ip6_rt_put_flags(struct rt6_info *rt, int flags) { if (!(flags & RT6_LOOKUP_F_DST_NOREF) || - !list_empty(&rt->dst.rt_uncached)) + rt->dst.rt_uncached_list) ip6_rt_put(rt); } diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 884d9ab0d50d62..153ce16628c1e1 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -139,6 +139,7 @@ void rt6_uncached_list_add(struct rt6_info *rt) { struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list); + /* Set once and never cleared: non-NULL marks an uncached route. */ rt->dst.rt_uncached_list = ul; spin_lock_bh(&ul->lock); @@ -2726,8 +2727,8 @@ struct dst_entry *ip6_route_output_flags(struct net *net, rcu_read_lock(); dst = ip6_route_output_flags_noref(net, sk, fl6, flags); rt6 = dst_rt6_info(dst); - /* For dst cached in uncached_list, refcnt is already taken. */ - if (list_empty(&rt6->dst.rt_uncached) && !dst_hold_safe(dst)) { + /* For an uncached dst, refcnt is already taken. */ + if (!rt6->dst.rt_uncached_list && !dst_hold_safe(dst)) { dst = &net->ipv6.ip6_null_entry->dst; dst_hold(dst); } @@ -2836,7 +2837,7 @@ INDIRECT_CALLABLE_SCOPE struct dst_entry *ip6_dst_check(struct dst_entry *dst, from = rcu_dereference(rt->from); if (from && (rt->rt6i_flags & RTF_PCPU || - unlikely(!list_empty(&rt->dst.rt_uncached)))) + unlikely(rt->dst.rt_uncached_list))) dst_ret = rt6_dst_from_check(rt, from, cookie); else dst_ret = rt6_check(rt, from, cookie); From 6c43c72748fffd29dec15cd1f31e9a32949bc437 Mon Sep 17 00:00:00 2001 From: Melody Wang Date: Mon, 14 Sep 2026 00:57:17 +0000 Subject: [PATCH 0967/1417] x86/sev: Make vTPM SVSM calls preemption-safe Two functions in the SVSM vTPM guest implementation do not disable preemption when fetching the SVSM Calling Area Address (CAA). The SVSM CAA is a per-CPU structure. When a thread is preempted and migrated to a different CPU after fetching the per-CPU CAA, the SVSM call will execute on the new CPU with the original CPU's CAA. Which is wrong. Move the CAA fetching operation inside svsm_perform_call_protocol() which disables interrupts around the SVSM call and thus runs preemption-safe. Fixes: 770de678bc28 ("x86/sev: Add SVSM vTPM probe/send_command functions") Signed-off-by: Melody Wang Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Stefano Garzarella Cc: stable@vger.kernel.org Link: https://patch.msgid.link/a5bc0d4a2c462a0089109e145c21626b244b2ff0.1789345277.git.huibo.wang@amd.com --- arch/x86/coco/sev/svsm.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/arch/x86/coco/sev/svsm.c b/arch/x86/coco/sev/svsm.c index 916d62cd17dc70..2d493d55ff2e86 100644 --- a/arch/x86/coco/sev/svsm.c +++ b/arch/x86/coco/sev/svsm.c @@ -74,6 +74,14 @@ int svsm_perform_call_protocol(struct svsm_call *call) flags = native_local_irq_save(); + /* + * 'caa' is a per-CPU variable. To avoid using a stale or incorrect + * 'caa' if the task is preempted or migrated to another CPU after it + * is fetched, always fetch 'caa' and then issue the SVSM call with + * interrupts disabled. This ensures the correct 'caa' is used. + */ + call->caa = svsm_get_caa(); + ghcb = __sev_get_ghcb(&state); do { @@ -321,7 +329,6 @@ int snp_svsm_vtpm_send_command(u8 *buffer) { struct svsm_call call = {}; - call.caa = svsm_get_caa(); call.rax = SVSM_VTPM_CALL(SVSM_VTPM_CMD); call.rcx = __pa(buffer); @@ -345,7 +352,6 @@ bool snp_svsm_vtpm_probe(void) if (!snp_vmpl) return false; - call.caa = svsm_get_caa(); call.rax = SVSM_VTPM_CALL(SVSM_VTPM_QUERY); if (svsm_perform_call_protocol(&call)) From 72b782097e534ab48152dd25aaba40dda5f25f9e Mon Sep 17 00:00:00 2001 From: Jakub Pawlak Date: Mon, 14 Sep 2026 10:43:01 +0200 Subject: [PATCH 0968/1417] accel/ivpu: Use separate flag for job timeout Use separate flag to mark a job timeout as a reason of starting context_abort_work. This allows to distinguish engine reset reason and clearly adjust reset procedure flow. The flag is cleared in ivpu_prepare_for_reset(), which every recovery and suspend path already funnels through, so that the state is clean after recovery. Cc: stable@vger.kernel.org # v7.1+ Fixes: ade00a6c903f ("accel/ivpu: Perform engine reset instead of device recovery on TDR") Signed-off-by: Jakub Pawlak Reviewed-by: Dawid Osuchowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260914084301.894028-1-karol.wachowski@linux.intel.com --- drivers/accel/ivpu/ivpu_drv.c | 3 ++- drivers/accel/ivpu/ivpu_drv.h | 2 +- drivers/accel/ivpu/ivpu_job.c | 7 +++---- drivers/accel/ivpu/ivpu_mmu.c | 1 - drivers/accel/ivpu/ivpu_pm.c | 1 + 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_drv.c b/drivers/accel/ivpu/ivpu_drv.c index 95120957f42a58..8c1c87e69f91fb 100644 --- a/drivers/accel/ivpu/ivpu_drv.c +++ b/drivers/accel/ivpu/ivpu_drv.c @@ -515,6 +515,7 @@ void ivpu_prepare_for_reset(struct ivpu_device *vdev) { ivpu_hw_irq_disable(vdev); disable_irq(vdev->irq); + atomic_set(&vdev->job_timeout_detected, 0); flush_work(&vdev->irq_dct_work); flush_work(&vdev->context_abort_work); flush_work(&vdev->job_destroy_work); @@ -710,7 +711,7 @@ static int ivpu_dev_init(struct ivpu_device *vdev) vdev->context_xa_limit.max = IVPU_USER_CONTEXT_MAX_SSID; atomic64_set(&vdev->unique_id_counter, 0); atomic_set(&vdev->job_timeout_counter, 0); - atomic_set(&vdev->faults_detected, 0); + atomic_set(&vdev->job_timeout_detected, 0); xa_init_flags(&vdev->context_xa, XA_FLAGS_ALLOC | XA_FLAGS_LOCK_IRQ); xa_init_flags(&vdev->submitted_jobs_xa, XA_FLAGS_ALLOC1); xa_init_flags(&vdev->db_xa, XA_FLAGS_ALLOC1); diff --git a/drivers/accel/ivpu/ivpu_drv.h b/drivers/accel/ivpu/ivpu_drv.h index 86d7c9966cacbf..6f401292647892 100644 --- a/drivers/accel/ivpu/ivpu_drv.h +++ b/drivers/accel/ivpu/ivpu_drv.h @@ -171,7 +171,7 @@ struct ivpu_device { struct xarray submitted_jobs_xa; struct ivpu_ipc_consumer job_done_consumer; atomic_t job_timeout_counter; - atomic_t faults_detected; + atomic_t job_timeout_detected; atomic64_t unique_id_counter; diff --git a/drivers/accel/ivpu/ivpu_job.c b/drivers/accel/ivpu/ivpu_job.c index ebb2c865b09a08..4689b8ab519d75 100644 --- a/drivers/accel/ivpu/ivpu_job.c +++ b/drivers/accel/ivpu/ivpu_job.c @@ -621,7 +621,6 @@ bool ivpu_job_handle_engine_error(struct ivpu_device *vdev, u32 job_id, u32 job_ * status and ensure both are handled in the same way */ job->file_priv->has_mmu_faults = true; - atomic_set(&vdev->faults_detected, 1); queue_work(system_percpu_wq, &vdev->context_abort_work); return true; } @@ -1175,10 +1174,10 @@ static int reset_engine_and_mark_faulty_contexts(struct ivpu_device *vdev) return ret; /* - * If faults are detected, ignore guilty contexts from engine reset as NPU may not be stuck - * and could return currently running good context and faulty contexts are already marked + * If job timeout is detected, read guilty context from engine reset, for other reasons + * faulty context is already known */ - if (atomic_cmpxchg(&vdev->faults_detected, 1, 0) == 1) + if (atomic_cmpxchg(&vdev->job_timeout_detected, 1, 0) == 0) return 0; num_impacted_contexts = resp.payload.engine_reset_done.num_impacted_contexts; diff --git a/drivers/accel/ivpu/ivpu_mmu.c b/drivers/accel/ivpu/ivpu_mmu.c index 41efd8985fa67f..b2025274f91de3 100644 --- a/drivers/accel/ivpu/ivpu_mmu.c +++ b/drivers/accel/ivpu/ivpu_mmu.c @@ -964,7 +964,6 @@ void ivpu_mmu_irq_evtq_handler(struct ivpu_device *vdev) file_priv = xa_load(&vdev->context_xa, ssid); if (file_priv) { if (!READ_ONCE(file_priv->has_mmu_faults)) { - atomic_set(&vdev->faults_detected, 1); ivpu_mmu_dump_event(vdev, event); WRITE_ONCE(file_priv->has_mmu_faults, true); } diff --git a/drivers/accel/ivpu/ivpu_pm.c b/drivers/accel/ivpu/ivpu_pm.c index c1ce8329790e00..de0becbfdffb2a 100644 --- a/drivers/accel/ivpu/ivpu_pm.c +++ b/drivers/accel/ivpu/ivpu_pm.c @@ -229,6 +229,7 @@ static void ivpu_job_timeout_work(struct work_struct *work) ivpu_jsm_state_dump(vdev); ivpu_dev_coredump(vdev); + atomic_set(&vdev->job_timeout_detected, 1); queue_work(system_percpu_wq, &vdev->context_abort_work); } From 0d6526f82c3cdefcca47f73f5fc08dc6f335eac6 Mon Sep 17 00:00:00 2001 From: Tim Chen Date: Mon, 21 Sep 2026 17:37:22 -0700 Subject: [PATCH 0969/1417] sched/cache: Keep nr_pref_llc_running in the runnable domain, to fix LLC mis-scheduling bug alb_break_llc() decides whether to break LLC preference during active load balance. It does so by testing that every runnable fair task on the source rq prefers its LLC: env->src_rq->nr_pref_llc_running == env->src_rq->cfs.h_nr_runnable But the two counters cover different sets. nr_pref_llc_running is updated in account_llc_enqueue()/account_llc_dequeue(), next to cfs_rq->nr_queued, so it follows queued tasks. h_nr_runnable is updated in set_delayed()/ clear_delayed() and drops delay-dequeued tasks. So under DELAY_DEQUEUE, a preferring task that goes to sleep stays counted in nr_pref_llc_running while h_nr_runnable falls. The equality then breaks, alb_break_llc() returns false, and active balance is free to pull a task off its preferred LLC. Active balance only moves runnable tasks, and this is the only LLC check it consults: once the stopper runs, LBF_ACTIVE_LB skips the per-task test in can_migrate_task(). The runnable set is the one we want. Fix it on the counter side. A task should be counted in nr_pref_llc_running exactly while it is both queued on its preferred LLC (pref_llc_queued) and runnable (!sched_delayed). Define that membership once in task_pref_llc_runnable(), and adjust the counter only through pref_llc_running_inc()/pref_llc_running_dec() from the four sites that change either input: account_llc_enqueue(), account_llc_dequeue(), set_delayed() and clear_delayed(). Gating every update on the same predicate keeps the delay, wake and dequeue paths from double-counting or underflowing; see the comments at those sites for the ordering. nr_llc_running and sd->llc_counts are not touched and stay on queued semantics. Fixes: 714059f79ff0 ("sched/cache: Handle moving single tasks to/from their preferred LLC") Closes: https://lore.kernel.org/lkml/20260827135000.735138-1-zhanxusheng@xiaomi.com/ Reported-by: Zhan Xusheng Suggested-by: Chen Yu Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Kayra Cizmeci Cc: # v7.2.x Link: https://patch.msgid.link/06af61afedac32e6477f57feb4d658f6c411c3af.1790035273.git.tim.c.chen@linux.intel.com --- kernel/sched/fair.c | 63 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 7455a83a6a9907..de3d589fa8eb68 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1546,6 +1546,28 @@ static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p, (scale * per_cpu(sd_llc_size, cpu))); } +/* + * A task counts in nr_pref_llc_running while it is queued on its preferred + * LLC (pref_llc_queued) and runnable (!sched_delayed), keeping the counter in + * the runnable domain so alb_break_llc() can compare it with h_nr_runnable. + */ +static bool task_pref_llc_runnable(struct task_struct *p) +{ + return p->pref_llc_queued && !p->se.sched_delayed; +} + +static void pref_llc_running_inc(struct rq *rq, struct task_struct *p) +{ + if (task_pref_llc_runnable(p)) + rq->nr_pref_llc_running++; +} + +static void pref_llc_running_dec(struct rq *rq, struct task_struct *p) +{ + if (task_pref_llc_runnable(p)) + rq->nr_pref_llc_running--; +} + static void account_llc_enqueue(struct rq *rq, struct task_struct *p) { int pref_llc, pref_llc_queued; @@ -1557,7 +1579,6 @@ static void account_llc_enqueue(struct rq *rq, struct task_struct *p) pref_llc_queued = (pref_llc == task_llc(p)); rq->nr_llc_running++; - rq->nr_pref_llc_running += pref_llc_queued; /* * Record whether p is enqueued on its preferred @@ -1575,6 +1596,9 @@ static void account_llc_enqueue(struct rq *rq, struct task_struct *p) */ p->pref_llc_queued = pref_llc_queued; + /* Skipped while delayed; clear_delayed() adds it back on wake. */ + pref_llc_running_inc(rq, p); + sd = rcu_dereference_all(rq->sd); if (sd && (unsigned int)pref_llc < sd->llc_max) sd->llc_counts[pref_llc]++; @@ -1591,7 +1615,12 @@ static void account_llc_dequeue(struct rq *rq, struct task_struct *p) rq->nr_llc_running--; if (p->pref_llc_queued) { - rq->nr_pref_llc_running--; + /* + * Skipped if still delayed (set_delayed() already removed it); + * clearing pref_llc_queued below also stops clear_delayed() + * from re-adding it. + */ + pref_llc_running_dec(rq, p); /* * Update the status in case * other logic might query @@ -1995,6 +2024,7 @@ void init_sched_mm(struct task_struct *p) * polluting account_llc_enqueue(). */ p->preferred_llc = -1; + p->pref_llc_queued = 0; } #else /* CONFIG_SCHED_CACHE */ @@ -2016,6 +2046,10 @@ static void account_llc_enqueue(struct rq *rq, struct task_struct *p) {} static void account_llc_dequeue(struct rq *rq, struct task_struct *p) {} +static void pref_llc_running_inc(struct rq *rq, struct task_struct *p) {} + +static void pref_llc_running_dec(struct rq *rq, struct task_struct *p) {} + #endif /* CONFIG_SCHED_CACHE */ /* @@ -6390,15 +6424,27 @@ static __always_inline void return_cfs_rq_runtime(struct cfs_rq *cfs_rq); static void set_delayed(struct sched_entity *se) { - se->sched_delayed = 1; - /* * Delayed se of cfs_rq have no tasks queued on them. * Do not adjust h_nr_runnable since __dequeue_task() * will account it for blocked tasks. + * + * This check can be removed because when flat pick + * patches get merged as only task can get delayed, + * same for clear_delayed(). */ - if (!entity_is_task(se)) + if (!entity_is_task(se)) { + se->sched_delayed = 1; return; + } + + /* + * Drop a task leaving the runnable set. + * Needs to be called before sched_delayed is set. + * clear_delayed() mirrors this after clearing the flag. + */ + pref_llc_running_dec(rq_of(cfs_rq_of(se)), task_of(se)); + se->sched_delayed = 1; for_each_sched_entity(se) { struct cfs_rq *cfs_rq = cfs_rq_of(se); @@ -6420,6 +6466,13 @@ static void clear_delayed(struct sched_entity *se) if (!entity_is_task(se)) return; + /* + * Re-add on wake, after sched_delayed is cleared. On a final delayed + * dequeue account_llc_dequeue() already cleared pref_llc_queued, so + * this does nothing. + */ + pref_llc_running_inc(rq_of(cfs_rq_of(se)), task_of(se)); + for_each_sched_entity(se) { struct cfs_rq *cfs_rq = cfs_rq_of(se); From d6013e2465d98d524b030a81c1223882a1bb7e4c Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Mon, 21 Sep 2026 17:37:23 -0700 Subject: [PATCH 0970/1417] sched/cache: Honor migrate_llc_task semantics in active load balance, to fix LLC mis-scheduling bug Cache aware scheduling introduced the migrate_llc_task migration type to direct tasks toward their preferred LLC, but its semantics can be lost when passive load balance falls back to active load balance (ALB). This may allow ALB to select a candidate whose preferred LLC does not match the destination, moving it away from its preferred LLC. Example scenario: src_rq has two runnable tasks, p1 and p2. p1 prefers dst_rq (dst_llc), while p2 prefers src_rq (src_llc). In this case, migrate_llc_task is set because src_rq has at least one task, p1, that wants to migrate to dst_rq. In ALB, can_migrate_task() finds p2 and returns true for it, thus moving p2 out of its preferred LLC. Solution: The CPU stopper in ALB constructs a fresh lb_env that does not inherit migration_type from the passive load-balance pass. Two approaches are possible: (a) Add a new member to struct rq so ALB can inherit migrate_llc_task from the passive LB that triggered it. (b) Define a new flag LBF_ACTIVE_LB_LLC and select the stopper callback at kick time to preserve the migration semantics across the asynchronous boundary. We choose (b) because it avoids passing migration_type through the stopper, which would affect the meaning of migration_type for delayed-dequeue tasks. Fixes: e4c9a4cb244a ("sched/cache: Add migrate_llc_task migration type for cache-aware balancing") Suggested-by: Chen Yu Signed-off-by: Lu Wang Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Tim Chen Reviewed-by: Chen Yu Cc: # v7.2.x Link: https://patch.msgid.link/cb39f64a17fc2b76097264aaec74a2d6dfff4315.1790035273.git.tim.c.chen@linux.intel.com --- kernel/sched/fair.c | 57 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index de3d589fa8eb68..514bd54ccd56bc 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -10448,6 +10448,7 @@ enum migration_type { #define LBF_SOME_PINNED 0x08 #define LBF_ACTIVE_LB 0x10 #define LBF_LLC_PINNED 0x20 +#define LBF_ACTIVE_LB_LLC 0x40 struct lb_env { struct sched_domain *sd; @@ -10866,6 +10867,21 @@ alb_break_llc(struct lb_env *env) return false; } +/* + * Returns true if p's preferred LLC does not match the destination CPU + * under migrate_llc_task semantics. Passive LB passes migrate_llc_task + * in env->migration_type, while active LB carries LBF_ACTIVE_LB_LLC in + * env->flags to avoid overwriting env->migration_type. + */ +static inline bool +migrate_llc_task_wrong_dst(struct task_struct *p, struct lb_env *env) +{ + return sched_cache_enabled() && + (env->migration_type == migrate_llc_task || + env->flags & LBF_ACTIVE_LB_LLC) && + READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu); +} + /* * Check if migrating task p from env->src_cpu to * env->dst_cpu breaks LLC localiy. @@ -10894,8 +10910,7 @@ static bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) * run on env->dst_cpu, skip the tasks do not prefer * env->dst_cpu, and find the one that prefers. */ - if (env->migration_type == migrate_llc_task && - READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu)) + if (migrate_llc_task_wrong_dst(p, env)) return true; if (can_migrate_llc_task(env, p) != mig_forbid) @@ -10917,6 +10932,12 @@ alb_break_llc(struct lb_env *env) return false; } +static inline bool +migrate_llc_task_wrong_dst(struct task_struct *p, struct lb_env *env) +{ + return false; +} + static inline bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) { @@ -11016,7 +11037,7 @@ int can_migrate_task(struct task_struct *p, struct lb_env *env) * 4) too many balance attempts have failed. */ if (env->flags & LBF_ACTIVE_LB) - return 1; + return !migrate_llc_task_wrong_dst(p, env); degrades = migrate_degrades_locality(p, env); if (!degrades) { @@ -13415,6 +13436,20 @@ static int need_active_balance(struct lb_env *env) } static int active_load_balance_cpu_stop(void *data); +static int active_load_balance_llc_cpu_stop(void *data); + +/* + * migration_type is checked elsewhere to decide migration policy, so + * it shouldn't be repurposed just to flag an LLC-directed active + * balance across the stopper. Pick the callback here instead. + */ +static inline cpu_stop_fn_t alb_stop_fn(struct lb_env *env) +{ + if (env->migration_type == migrate_llc_task) + return active_load_balance_llc_cpu_stop; + + return active_load_balance_cpu_stop; +} static int should_we_balance(struct lb_env *env) { @@ -13760,7 +13795,7 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq, } if (active_balance) { stop_one_cpu_nowait(cpu_of(busiest), - active_load_balance_cpu_stop, busiest, + alb_stop_fn(&env), busiest, &busiest->active_balance_work); } preempt_enable(); @@ -13865,7 +13900,7 @@ update_next_balance(struct sched_domain *sd, unsigned long *next_balance) * least 1 task to be running on each physical CPU where possible, and * avoids physical / logical imbalances. */ -static int active_load_balance_cpu_stop(void *data) +static int __active_load_balance_cpu_stop(void *data, unsigned int lb_flags) { struct rq *busiest_rq = data; int busiest_cpu = cpu_of(busiest_rq); @@ -13915,7 +13950,7 @@ static int active_load_balance_cpu_stop(void *data) .src_cpu = busiest_rq->cpu, .src_rq = busiest_rq, .idle = CPU_IDLE, - .flags = LBF_ACTIVE_LB, + .flags = LBF_ACTIVE_LB | lb_flags, }; schedstat_inc(sd->alb_count); @@ -13943,6 +13978,16 @@ static int active_load_balance_cpu_stop(void *data) return 0; } +static int active_load_balance_cpu_stop(void *data) +{ + return __active_load_balance_cpu_stop(data, 0); +} + +static int active_load_balance_llc_cpu_stop(void *data) +{ + return __active_load_balance_cpu_stop(data, LBF_ACTIVE_LB_LLC); +} + /* * Scale the max sched_balance_rq interval with the number of CPUs in the system. * This trades load-balance latency on larger machines for less cross talk. From 28f9c0e0a0b94c5d3e1b634db545f6e1f94858c5 Mon Sep 17 00:00:00 2001 From: Tim Chen Date: Mon, 21 Sep 2026 17:37:24 -0700 Subject: [PATCH 0971/1417] sched/cache: Decouple sched_cache_group from mm to fix UAF Currently the sched cache grouping is by mm and the scheduling statistics sched_cache_stat lives in the mm structure. This ties the life cycle of scheduling stats with mm. In account_mm_sched(), the scheduling stats are accessed by task->mm->sc_stat. However, a task may be switching mm on one CPU when another CPU is running account_mm_sched(), and possibly accessing the old mm that was freed. This problem was found when running tests with KASAN by Hyunwoo: https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/ Instead of serializing the mm access by introducing extra acquisition of rq lock in the mm free path, extract sched_cache_stat from mm_struct, rename it as sched_cache_group and manage its life cycle apart from mm_struct with its own ref counting. This allows us in the next patch access sched_cache_group directly from task, and add a refcount on sched_cache_group when a task links to it. This prevents the use after free issue when accessing stale and released old mm and its sched cache stat a task switches to a new mm while account_mm_sched() is done elsewhere. The other benefit of this restructure is in the future, the grouping of tasks to a LLC would have the flexibility to be associated with a user defined grouping, or cgroup, cookie group, numa_group or others instead of just with a single mm address space. Rename sched_cache_stat to sched_cache_group and turn it into a refcounted object allocated from mm_struct. The mm_struct now holds a pointer (sched_cache_grp) to this object instead of embedding it. Fixes: df0d98475954 ("sched/cache: Introduce infrastructure for cache-aware load balancing") Closes: https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/ Closes: https://lore.kernel.org/all/343a7e07-7fad-4979-9c9b-82ec038c293c@linux.dev/ Reported-by: Hyunwoo Kim Reported-by: Zenghui Yu (Huawei) Co-developed-by: Chen Yu Signed-off-by: Chen Yu Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: #7.2.x Link: https://patch.msgid.link/91fd1e3266707c865bc9abecfb3e17bc676712df.1790035273.git.tim.c.chen@linux.intel.com --- include/linux/mm_types.h | 15 ++-- include/linux/sched.h | 6 +- kernel/exit.c | 11 ++- kernel/sched/fair.c | 173 ++++++++++++++++++++++++++++----------- 4 files changed, 144 insertions(+), 61 deletions(-) diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index 6d815f6440c94e..f3e5a2fadbe5b7 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -1226,7 +1226,7 @@ struct mm_struct { struct mm_mm_cid mm_cid; /* sched_cache related statistics */ - struct sched_cache_stat sc_stat; + struct sched_cache_group *sched_cache_grp; #ifdef CONFIG_MMU atomic_long_t pgtables_bytes; /* size of all page tables */ #endif @@ -1624,8 +1624,9 @@ static inline unsigned int mm_cid_size(void) #endif /* CONFIG_SCHED_MM_CID */ #ifdef CONFIG_SCHED_CACHE -void mm_init_sched(struct mm_struct *mm, - struct sched_cache_time __percpu *pcpu_sched); +int mm_init_sched(struct mm_struct *mm, + struct sched_cache_time __percpu *pcpu_sched); +void mm_destroy_sched(struct mm_struct *mm); static inline int mm_alloc_sched_noprof(struct mm_struct *mm) { @@ -1635,17 +1636,11 @@ static inline int mm_alloc_sched_noprof(struct mm_struct *mm) if (!pcpu_sched) return -ENOMEM; - mm_init_sched(mm, pcpu_sched); - return 0; + return mm_init_sched(mm, pcpu_sched); } #define mm_alloc_sched(...) alloc_hooks(mm_alloc_sched_noprof(__VA_ARGS__)) -static inline void mm_destroy_sched(struct mm_struct *mm) -{ - free_percpu(mm->sc_stat.pcpu_sched); - mm->sc_stat.pcpu_sched = NULL; -} #else /* !CONFIG_SCHED_CACHE */ static inline int mm_alloc_sched(struct mm_struct *mm) { return 0; } diff --git a/include/linux/sched.h b/include/linux/sched.h index 705970d07614f7..e14ad43522c81a 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -2405,7 +2405,7 @@ struct sched_cache_time { unsigned long epoch; }; -struct sched_cache_stat { +struct sched_cache_group { struct sched_cache_time __percpu *pcpu_sched; raw_spinlock_t lock; unsigned long epoch; @@ -2413,11 +2413,13 @@ struct sched_cache_stat { unsigned long next_scan; unsigned long footprint; int cpu; + refcount_t refcnt; + struct rcu_head rcu; } ____cacheline_aligned_in_smp; #else -struct sched_cache_stat { }; +struct sched_cache_group { }; #endif diff --git a/kernel/exit.c b/kernel/exit.c index 424c44a42a4d71..024350e9b48c0f 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -558,18 +558,23 @@ void mm_update_next_owner(struct mm_struct *mm) */ static void exit_mm_sched_cache(struct mm_struct *mm) { + struct sched_cache_group *grp; unsigned long fp, sub; if (!current->total_numa_faults) return; /* * No lock protection due to performance considerations. - * Make sure mm->sc_stat.footprint does not become + * Make sure the group footprint does not become * negative. */ - fp = READ_ONCE(mm->sc_stat.footprint); + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp) + return; + + fp = READ_ONCE(grp->footprint); sub = min(fp, current->total_numa_faults); - WRITE_ONCE(mm->sc_stat.footprint, fp - sub); + WRITE_ONCE(grp->footprint, fp - sub); } #else static inline void exit_mm_sched_cache(struct mm_struct *mm) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 514bd54ccd56bc..f0a9586332fd93 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1492,12 +1492,17 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) return true; if (static_branch_likely(&sched_numa_balancing)) { + struct sched_cache_group *grp = READ_ONCE(mm->sched_cache_grp); + + if (!grp) + return true; + /* * TBD: RDT exclusive LLC ways reserved should be * excluded. */ llc = sd->llc_bytes; - footprint = READ_ONCE(mm->sc_stat.footprint); + footprint = READ_ONCE(grp->footprint); /* * Scale the LLC size by 256*llc_aggr_tolerance @@ -1529,6 +1534,7 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p, int cpu) { + struct sched_cache_group *grp; int scale; if (get_nr_threads(p) <= 1) @@ -1542,7 +1548,11 @@ static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p, if (scale == INT_MAX) return false; - return !fits_capacity((mm->sc_stat.nr_running_avg * cpu_smt_num_threads), + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp) + return true; + + return !fits_capacity((READ_ONCE(grp->nr_running_avg) * cpu_smt_num_threads), (scale * per_cpu(sd_llc_size, cpu))); } @@ -1648,12 +1658,20 @@ static void account_llc_dequeue(struct rq *rq, struct task_struct *p) } } -void mm_init_sched(struct mm_struct *mm, - struct sched_cache_time __percpu *_pcpu_sched) +int mm_init_sched(struct mm_struct *mm, + struct sched_cache_time __percpu *_pcpu_sched) { + struct sched_cache_group *grp; unsigned long epoch = 0; int i; + grp = kzalloc_obj(*grp); + if (!grp) { + free_percpu(_pcpu_sched); + mm->sched_cache_grp = NULL; + return -ENOMEM; + } + for_each_possible_cpu(i) { struct sched_cache_time *pcpu_sched = per_cpu_ptr(_pcpu_sched, i); struct rq *rq = cpu_rq(i); @@ -1664,18 +1682,51 @@ void mm_init_sched(struct mm_struct *mm, epoch = rq->cpu_epoch; } - raw_spin_lock_init(&mm->sc_stat.lock); - mm->sc_stat.epoch = epoch; - mm->sc_stat.cpu = -1; - mm->sc_stat.next_scan = jiffies; - mm->sc_stat.nr_running_avg = 0; - mm->sc_stat.footprint = 0; + raw_spin_lock_init(&grp->lock); + grp->epoch = epoch; + grp->cpu = -1; + grp->next_scan = jiffies; + grp->nr_running_avg = 0; + grp->footprint = 0; + refcount_set(&grp->refcnt, 1); /* - * The update to mm->sc_stat should not be reordered - * before initialization to mm's other fields, in case + * The update to grp->pcpu_sched should not be reordered + * before initialization to grp's other fields, in case * the readers may get invalid mm_sched_epoch, etc. */ - smp_store_release(&mm->sc_stat.pcpu_sched, _pcpu_sched); + smp_store_release(&grp->pcpu_sched, _pcpu_sched); + /* + * Publish the group last. Not every reader qualifies it by + * grp->pcpu_sched - can_migrate_llc_task() only checks that the + * pointer is non-NULL before reading grp->footprint and + * grp->nr_running_avg - so a reachable group must already be + * fully initialized. + */ + smp_store_release(&mm->sched_cache_grp, grp); + return 0; +} + +static void sched_cache_group_free_rcu(struct rcu_head *rcu) +{ + struct sched_cache_group *grp = + container_of(rcu, struct sched_cache_group, rcu); + + free_percpu(grp->pcpu_sched); + kfree(grp); +} + +static void sched_cache_group_put(struct sched_cache_group *grp) +{ + if (!grp || !refcount_dec_and_test(&grp->refcnt)) + return; + + call_rcu(&grp->rcu, sched_cache_group_free_rcu); +} + +void mm_destroy_sched(struct mm_struct *mm) +{ + sched_cache_group_put(mm->sched_cache_grp); + mm->sched_cache_grp = NULL; } /* because why would C be fully specified */ @@ -1729,11 +1780,16 @@ static unsigned long fraction_mm_sched(struct rq *rq, static int get_pref_llc(struct task_struct *p, struct mm_struct *mm) { int mm_sched_llc = -1, mm_sched_cpu; + struct sched_cache_group *grp; if (!mm) return -1; - mm_sched_cpu = READ_ONCE(mm->sc_stat.cpu); + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp) + return -1; + + mm_sched_cpu = READ_ONCE(grp->cpu); if (mm_sched_cpu != -1) { mm_sched_llc = llc_id(mm_sched_cpu); @@ -1764,6 +1820,7 @@ static inline void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) { struct sched_cache_time *pcpu_sched; + struct sched_cache_group *grp; struct mm_struct *mm = p->mm; int mm_sched_llc = -1; unsigned long epoch; @@ -1777,10 +1834,14 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) * init_task, kthreads and user thread created * by user_mode_thread() don't have mm. */ - if (!mm || !mm->sc_stat.pcpu_sched) + if (!mm) + return; + + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp || !grp->pcpu_sched) return; - pcpu_sched = per_cpu_ptr(mm->sc_stat.pcpu_sched, cpu_of(rq)); + pcpu_sched = per_cpu_ptr(grp->pcpu_sched, cpu_of(rq)); scoped_guard (raw_spinlock, &rq->cpu_epoch_lock) { __update_mm_sched(rq, pcpu_sched); @@ -1793,11 +1854,11 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) * If this process hasn't hit task_cache_work() for a while invalidate * its preferred state. */ - if ((long)(epoch - READ_ONCE(mm->sc_stat.epoch)) > llc_epoch_affinity_timeout || + if ((long)(epoch - READ_ONCE(grp->epoch)) > llc_epoch_affinity_timeout || invalid_llc_nr(mm, p, cpu_of(rq)) || exceed_llc_capacity(mm, cpu_of(rq))) { - if (READ_ONCE(mm->sc_stat.cpu) != -1) - WRITE_ONCE(mm->sc_stat.cpu, -1); + if (READ_ONCE(grp->cpu) != -1) + WRITE_ONCE(grp->cpu, -1); } mm_sched_llc = get_pref_llc(p, mm); @@ -1814,30 +1875,35 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) static void task_tick_cache(struct rq *rq, struct task_struct *p) { struct callback_head *work = &p->cache_work; + struct sched_cache_group *grp; struct mm_struct *mm = p->mm; unsigned long epoch; if (!sched_cache_enabled()) return; - if (!mm || p->flags & PF_KTHREAD || - !mm->sc_stat.pcpu_sched) + if (!mm || p->flags & PF_KTHREAD) + return; + + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp || !grp->pcpu_sched) return; epoch = rq->cpu_epoch; /* avoid moving backwards */ - if (time_after_eq(mm->sc_stat.epoch, epoch)) + if (time_after_eq(grp->epoch, epoch)) return; - guard(raw_spinlock)(&mm->sc_stat.lock); + guard(raw_spinlock)(&grp->lock); if (work->next == work) { task_work_add(p, work, TWA_RESUME); - WRITE_ONCE(mm->sc_stat.epoch, epoch); + WRITE_ONCE(grp->epoch, epoch); } } -static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p) +static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p, + struct sched_cache_group *grp) { #ifdef CONFIG_NUMA_BALANCING int cpu, curr_cpu, nid, pref_nid; @@ -1845,7 +1911,7 @@ static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p) if (!static_branch_likely(&sched_numa_balancing)) goto out; - cpu = READ_ONCE(p->mm->sc_stat.cpu); + cpu = READ_ONCE(grp->cpu); if (cpu != -1) nid = cpu_to_node(cpu); curr_cpu = task_cpu(p); @@ -1906,6 +1972,7 @@ static void task_cache_work(struct callback_head *work) unsigned long next_scan, now = jiffies; struct task_struct *p = current, *cur; unsigned long curr_m_a_occ = 0; + struct sched_cache_group *grp; struct mm_struct *mm = p->mm; unsigned long m_a_occ = 0; cpumask_var_t cpus; @@ -1917,12 +1984,16 @@ static void task_cache_work(struct callback_head *work) if (p->flags & PF_EXITING) return; - next_scan = READ_ONCE(mm->sc_stat.next_scan); + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp) + return; + + next_scan = READ_ONCE(grp->next_scan); if (time_before(now, next_scan)) return; /* only 1 thread is allowed to scan */ - if (!try_cmpxchg(&mm->sc_stat.next_scan, &next_scan, + if (!try_cmpxchg(&grp->next_scan, &next_scan, now + max_t(unsigned long, READ_ONCE(llc_epoch_period), 1))) return; @@ -1930,8 +2001,8 @@ static void task_cache_work(struct callback_head *work) curr_cpu = task_cpu(p); if (invalid_llc_nr(mm, p, curr_cpu) || exceed_llc_capacity(mm, curr_cpu)) { - if (READ_ONCE(mm->sc_stat.cpu) != -1) - WRITE_ONCE(mm->sc_stat.cpu, -1); + if (READ_ONCE(grp->cpu) != -1) + WRITE_ONCE(grp->cpu, -1); return; } @@ -1942,7 +2013,7 @@ static void task_cache_work(struct callback_head *work) scoped_guard (cpus_read_lock) { guard(rcu)(); - get_scan_cpumasks(cpus, p); + get_scan_cpumasks(cpus, p, grp); for_each_cpu(cpu, cpus) { /* XXX sched_cluster_active */ @@ -1955,7 +2026,7 @@ static void task_cache_work(struct callback_head *work) for_each_cpu(i, sched_domain_span(sd)) { occ = fraction_mm_sched(cpu_rq(i), - per_cpu_ptr(mm->sc_stat.pcpu_sched, i)); + per_cpu_ptr(grp->pcpu_sched, i)); a_occ += occ; if (occ > m_occ) { m_occ = occ; @@ -1988,7 +2059,7 @@ static void task_cache_work(struct callback_head *work) m_a_cpu = m_cpu; } - if (llc_id(cpu) == llc_id(READ_ONCE(mm->sc_stat.cpu))) + if (llc_id(cpu) == llc_id(READ_ONCE(grp->cpu))) curr_m_a_occ = a_occ; cpumask_andnot(cpus, cpus, sched_domain_span(sd)); @@ -1997,7 +2068,7 @@ static void task_cache_work(struct callback_head *work) if (m_a_occ > (2 * curr_m_a_occ)) { /* - * Avoid switching sc_stat.cpu too fast. + * Avoid switching sched_cache_grp->cpu too fast. * The reason to choose 2X is because: * 1. It is better to keep the preferred LLC stable, * rather than changing it frequently and cause migrations @@ -2006,10 +2077,10 @@ static void task_cache_work(struct callback_head *work) * 3. 2X is chosen based on test results, as it delivers * the optimal performance gain so far. */ - WRITE_ONCE(mm->sc_stat.cpu, m_a_cpu); + WRITE_ONCE(grp->cpu, m_a_cpu); } - update_avg_scale(&mm->sc_stat.nr_running_avg, nr_running); + update_avg_scale(&grp->nr_running_avg, nr_running); free_cpumask_var(cpus); } @@ -3726,6 +3797,7 @@ static int preferred_group_nid(struct task_struct *p, int nid) static void task_numa_placement(struct task_struct *p) __context_unsafe(/* conditional locking */) { + struct sched_cache_group __maybe_unused *grp; int seq, nid, max_nid = NUMA_NO_NODE; unsigned long max_faults = 0; unsigned long fault_types[2] = { 0, 0 }; @@ -3818,19 +3890,23 @@ static void task_numa_placement(struct task_struct *p) * heuristic and occasional lost updates are tolerable. * * If a task exits, its corresponding footprint must - * be subtracted from the mm->sc_stat.footprint, otherwise - * the mm->sc_stat.footprint will not converge: - * the exiting thread's footprint remains unchanged/undecayed - * in mm->sc_stat.footprint. See exit_mm(). + * be subtracted from the mm->sched_cache_grp->footprint, + * otherwise the mm->sched_cache_grp->footprint will not + * converge: the exiting thread's footprint remains + * unchanged/undecayed in mm->sched_cache_grp->footprint. + * See exit_mm(). * * Lost updates and unsynchronized subtraction * in exit_mm() can cause footprint + diff to * go negative. Clamp to zero to prevent the * unsigned footprint from wrapping. */ - new_fp = (long)READ_ONCE(p->mm->sc_stat.footprint) + diff; - WRITE_ONCE(p->mm->sc_stat.footprint, - max(new_fp, 0L)); + grp = READ_ONCE(p->mm->sched_cache_grp); + if (!grp) + continue; + + new_fp = (long)READ_ONCE(grp->footprint) + diff; + WRITE_ONCE(grp->footprint, max(new_fp, 0L)); #endif } @@ -10778,6 +10854,7 @@ static inline bool task_misfits_asym_cpu(struct lb_env *env, struct task_struct static enum llc_mig can_migrate_llc_task(struct lb_env *env, struct task_struct *p) { + struct sched_cache_group *grp; struct mm_struct *mm; bool to_pref; int cpu, src_cpu, dst_cpu; @@ -10791,15 +10868,19 @@ static enum llc_mig can_migrate_llc_task(struct lb_env *env, if (!mm) return mig_unrestricted; - cpu = READ_ONCE(mm->sc_stat.cpu); + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp) + return mig_unrestricted; + + cpu = READ_ONCE(grp->cpu); if (cpu < 0 || cpus_share_cache(src_cpu, dst_cpu)) return mig_unrestricted; /* skip cache aware load balance for too many threads */ if (invalid_llc_nr(mm, p, dst_cpu) || exceed_llc_capacity(mm, dst_cpu)) { - if (READ_ONCE(mm->sc_stat.cpu) != -1) - WRITE_ONCE(mm->sc_stat.cpu, -1); + if (READ_ONCE(grp->cpu) != -1) + WRITE_ONCE(grp->cpu, -1); return mig_unrestricted; } From b636fef85bda7d1bab9c0a45067ab1508d79d946 Mon Sep 17 00:00:00 2001 From: Tim Chen Date: Mon, 21 Sep 2026 17:37:25 -0700 Subject: [PATCH 0972/1417] sched/cache: Introduce task_struct->sched_cache_grp to fix UAF Add a sched_cache_grp pointer to task_struct so that scheduler code can access the cache group directly via the task, without going through mm->sched_cache_grp. This decouples the scheduler's hot-path accesses from the mm_struct. Each task holds its own refcount on the sched_cache_group, separate from the reference held by its mm_struct. The reference is acquired in copy_mm() (fork) and exec_mmap() (exec), and released in exit_mm(). This fixes the use-after-free when account_mm_sched() reaches the group through a task whose mm is being switched, as reported by Hyunwoo: https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/ Convert all scheduler code in fair.c and exit.c to use p->sched_cache_grp instead of p->mm->sched_cache_grp. Keep the fork/exec/exit reference management out of the generic mm paths: add sched_cache_fork(), sched_cache_fork_cleanup(), sched_cache_exec_mmap() and sched_cache_exit_mm() in kernel/sched/cache_sched.c (with empty stubs for !CONFIG_SCHED_CACHE), so fs/exec.c, kernel/fork.c and kernel/exit.c each call one helper instead of open-coding the refcounting under #ifdef. Also add sched_cache_group_get() and task_cache_group_get(). Fixes: df0d98475954 ("sched/cache: Introduce infrastructure for cache-aware load balancing") Closes: https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/ Closes: https://lore.kernel.org/all/343a7e07-7fad-4979-9c9b-82ec038c293c@linux.dev/ Reported-by: Hyunwoo Kim Reported-by: Zenghui Yu (Huawei) Co-developed-by: Chen Yu Signed-off-by: Chen Yu Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: #7.2.x Link: https://patch.msgid.link/ae7081dc54736bf115215f9867abb2711a7403fb.1790035273.git.tim.c.chen@linux.intel.com --- fs/exec.c | 1 + include/linux/sched.h | 14 +++ kernel/exit.c | 33 +------ kernel/fork.c | 2 + kernel/sched/fair.c | 196 +++++++++++++++++++++++++++++------------- 5 files changed, 154 insertions(+), 92 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 819643408e6df8..a5269b5e00df1c 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -882,6 +882,7 @@ static int exec_mmap(struct linux_binprm *bprm) active_mm = tsk->active_mm; tsk->active_mm = mm; tsk->mm = mm; + sched_cache_exec_mmap(tsk, mm); mm_init_cid(mm, tsk); exec_state = task_exec_state_replace(tsk, exec_state); /* diff --git a/include/linux/sched.h b/include/linux/sched.h index e14ad43522c81a..d35ae49a991f7d 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1433,6 +1433,7 @@ struct task_struct { #ifdef CONFIG_SCHED_CACHE struct callback_head cache_work; + struct sched_cache_group __rcu *sched_cache_grp; int preferred_llc; /* 1: task was enqueued to its preferred LLC, 0 otherwise */ int pref_llc_queued; @@ -2417,10 +2418,23 @@ struct sched_cache_group { struct rcu_head rcu; } ____cacheline_aligned_in_smp; +struct sched_cache_group *sched_cache_group_get(struct sched_cache_group *grp); +struct sched_cache_group *task_cache_group_get(struct task_struct *p); + +void sched_cache_fork(struct task_struct *p); +void sched_cache_fork_cleanup(struct task_struct *p); +void sched_cache_exec_mmap(struct task_struct *p, struct mm_struct *mm); +void sched_cache_exit_mm(struct task_struct *p); + #else struct sched_cache_group { }; +static inline void sched_cache_fork(struct task_struct *p) { } +static inline void sched_cache_fork_cleanup(struct task_struct *p) { } +static inline void sched_cache_exec_mmap(struct task_struct *p, struct mm_struct *mm) { } +static inline void sched_cache_exit_mm(struct task_struct *p) { } + #endif #ifndef MODULE diff --git a/kernel/exit.c b/kernel/exit.c index 024350e9b48c0f..282328d2b4cf83 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -551,37 +551,6 @@ void mm_update_next_owner(struct mm_struct *mm) } #endif /* CONFIG_MEMCG */ -#if defined(CONFIG_SCHED_CACHE) && defined(CONFIG_NUMA_BALANCING) -/* - * Subtract the memory footprint of the current task from - * mm. - */ -static void exit_mm_sched_cache(struct mm_struct *mm) -{ - struct sched_cache_group *grp; - unsigned long fp, sub; - - if (!current->total_numa_faults) - return; - /* - * No lock protection due to performance considerations. - * Make sure the group footprint does not become - * negative. - */ - grp = READ_ONCE(mm->sched_cache_grp); - if (!grp) - return; - - fp = READ_ONCE(grp->footprint); - sub = min(fp, current->total_numa_faults); - WRITE_ONCE(grp->footprint, fp - sub); -} -#else -static inline void exit_mm_sched_cache(struct mm_struct *mm) -{ -} -#endif /* CONFIG_SCHED_CACHE CONFIG_NUMA_BALANCING */ - /* * Turn us into a lazy TLB process if we * aren't already.. @@ -594,7 +563,7 @@ static void exit_mm(void) if (!mm) return; - exit_mm_sched_cache(mm); + sched_cache_exit_mm(current); mmap_read_lock(mm); mmgrab_lazy_tlb(mm); diff --git a/kernel/fork.c b/kernel/fork.c index 5ef413368912e1..10f2d05d816a5f 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1599,6 +1599,7 @@ static int copy_mm(u64 clone_flags, struct task_struct *tsk) tsk->mm = mm; tsk->active_mm = mm; + sched_cache_fork(tsk); return 0; } @@ -2602,6 +2603,7 @@ __latent_entropy struct task_struct *copy_process( bad_fork_cleanup_namespaces: exit_nsproxy_namespaces(p); bad_fork_cleanup_mm: + sched_cache_fork_cleanup(p); if (p->mm) { mm_clear_owner(p->mm, p); mmput(p->mm); diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index f0a9586332fd93..974a7dfe321558 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1478,7 +1478,7 @@ static inline int get_sched_cache_scale(int mul) return (1 + (tol - 1) * mul); } -static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) +static bool exceed_llc_capacity(struct sched_cache_group *grp, int cpu) { #ifdef CONFIG_NUMA_BALANCING unsigned long llc, footprint; @@ -1492,11 +1492,6 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) return true; if (static_branch_likely(&sched_numa_balancing)) { - struct sched_cache_group *grp = READ_ONCE(mm->sched_cache_grp); - - if (!grp) - return true; - /* * TBD: RDT exclusive LLC ways reserved should be * excluded. @@ -1531,10 +1526,9 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) return false; } -static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p, +static bool invalid_llc_nr(struct sched_cache_group *grp, struct task_struct *p, int cpu) { - struct sched_cache_group *grp; int scale; if (get_nr_threads(p) <= 1) @@ -1548,10 +1542,6 @@ static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p, if (scale == INT_MAX) return false; - grp = READ_ONCE(mm->sched_cache_grp); - if (!grp) - return true; - return !fits_capacity((READ_ONCE(grp->nr_running_avg) * cpu_smt_num_threads), (scale * per_cpu(sd_llc_size, cpu))); } @@ -1723,6 +1713,96 @@ static void sched_cache_group_put(struct sched_cache_group *grp) call_rcu(&grp->rcu, sched_cache_group_free_rcu); } +DEFINE_FREE(sched_cache_group_put, struct sched_cache_group *, + sched_cache_group_put(_T)); + +#define rcu_deref_sched_cache_grp(tsk) \ + rcu_dereference_check((tsk)->sched_cache_grp, (tsk) == current) + +static struct sched_cache_group *sched_cache_replace_grp(struct task_struct *p, + struct sched_cache_group *new) +{ + struct sched_cache_group *old; + + old = rcu_deref_sched_cache_grp(p); + rcu_assign_pointer(p->sched_cache_grp, new); + + return old; +} + +struct sched_cache_group *sched_cache_group_get(struct sched_cache_group *grp) +{ + /* + * refcount_inc_not_zero() is the acquire primitive for lockless + * (RCU) lookups; plain refcount_inc() would scribble the count if + * it already reached zero. Return NULL in that case. + */ + if (grp && !refcount_inc_not_zero(&grp->refcnt)) + grp = NULL; + + return grp; +} + +struct sched_cache_group *task_cache_group_get(struct task_struct *p) +{ + guard(rcu)(); + return sched_cache_group_get(rcu_dereference(p->sched_cache_grp)); +} + +void sched_cache_fork(struct task_struct *p) +{ + /* + * The child takes its own reference on the mm's cache group, separate + * from the reference held by the mm. @p is not yet visible to readers, + * so a plain initializing store is enough. + */ + RCU_INIT_POINTER(p->sched_cache_grp, + sched_cache_group_get(p->mm->sched_cache_grp)); +} + +void sched_cache_fork_cleanup(struct task_struct *p) +{ + /* + * A fork that fails after sched_cache_fork() never reaches exit_mm(), + * so drop the reference here. @p never became visible, so there are no + * concurrent readers and the reference we hold keeps the group alive. + */ + sched_cache_group_put(rcu_access_pointer(p->sched_cache_grp)); + RCU_INIT_POINTER(p->sched_cache_grp, NULL); +} + +void sched_cache_exec_mmap(struct task_struct *p, struct mm_struct *mm) +{ + struct sched_cache_group *old; + + /* + * Acquire the new reference before publishing the pointer, then drop + * the old one. @p is current and the only writer of its own pointer. + */ + old = sched_cache_replace_grp(p, sched_cache_group_get(mm->sched_cache_grp)); + sched_cache_group_put(old); +} + +void sched_cache_exit_mm(struct task_struct *p) +{ + struct sched_cache_group *grp = sched_cache_replace_grp(p, NULL); + +#ifdef CONFIG_NUMA_BALANCING + /* + * Subtract this task's footprint from the group before dropping the + * reference, so the group footprint converges as its threads exit. + * Unlocked for performance; clamp to avoid underflow. + */ + if (grp && p->total_numa_faults) { + unsigned long fp = READ_ONCE(grp->footprint); + unsigned long sub = min(fp, p->total_numa_faults); + + WRITE_ONCE(grp->footprint, fp - sub); + } +#endif + sched_cache_group_put(grp); +} + void mm_destroy_sched(struct mm_struct *mm) { sched_cache_group_put(mm->sched_cache_grp); @@ -1777,15 +1857,10 @@ static unsigned long fraction_mm_sched(struct rq *rq, return div64_u64(NICE_0_LOAD * pcpu_sched->runtime, rq->cpu_runtime + 1); } -static int get_pref_llc(struct task_struct *p, struct mm_struct *mm) +static int get_pref_llc(struct task_struct *p, struct sched_cache_group *grp) { int mm_sched_llc = -1, mm_sched_cpu; - struct sched_cache_group *grp; - if (!mm) - return -1; - - grp = READ_ONCE(mm->sched_cache_grp); if (!grp) return -1; @@ -1819,9 +1894,8 @@ static unsigned int task_running_on_cpu(int cpu, struct task_struct *p); static inline void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) { + struct sched_cache_group *grp = rcu_dereference_all(p->sched_cache_grp); struct sched_cache_time *pcpu_sched; - struct sched_cache_group *grp; - struct mm_struct *mm = p->mm; int mm_sched_llc = -1; unsigned long epoch; @@ -1832,12 +1906,8 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) return; /* * init_task, kthreads and user thread created - * by user_mode_thread() don't have mm. + * by user_mode_thread() don't have a cache group. */ - if (!mm) - return; - - grp = READ_ONCE(mm->sched_cache_grp); if (!grp || !grp->pcpu_sched) return; @@ -1855,13 +1925,13 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) * its preferred state. */ if ((long)(epoch - READ_ONCE(grp->epoch)) > llc_epoch_affinity_timeout || - invalid_llc_nr(mm, p, cpu_of(rq)) || - exceed_llc_capacity(mm, cpu_of(rq))) { + invalid_llc_nr(grp, p, cpu_of(rq)) || + exceed_llc_capacity(grp, cpu_of(rq))) { if (READ_ONCE(grp->cpu) != -1) WRITE_ONCE(grp->cpu, -1); } - mm_sched_llc = get_pref_llc(p, mm); + mm_sched_llc = get_pref_llc(p, grp); /* task not on rq accounted later in account_entity_enqueue() */ if (task_running_on_cpu(rq->cpu, p) && @@ -1874,19 +1944,15 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) static void task_tick_cache(struct rq *rq, struct task_struct *p) { + struct sched_cache_group *grp = rcu_dereference_all(p->sched_cache_grp); struct callback_head *work = &p->cache_work; - struct sched_cache_group *grp; - struct mm_struct *mm = p->mm; unsigned long epoch; if (!sched_cache_enabled()) return; - if (!mm || p->flags & PF_KTHREAD) - return; - - grp = READ_ONCE(mm->sched_cache_grp); - if (!grp || !grp->pcpu_sched) + if (!grp || p->flags & PF_KTHREAD || + !grp->pcpu_sched) return; epoch = rq->cpu_epoch; @@ -1968,14 +2034,13 @@ static inline void update_avg_scale(u64 *avg, u64 sample) static void task_cache_work(struct callback_head *work) { + struct sched_cache_group *grp __free(sched_cache_group_put) = NULL; + cpumask_var_t cpus __free(free_cpumask_var) = CPUMASK_VAR_NULL; int cpu, m_a_cpu = -1, nr_running = 0, curr_cpu; unsigned long next_scan, now = jiffies; struct task_struct *p = current, *cur; unsigned long curr_m_a_occ = 0; - struct sched_cache_group *grp; - struct mm_struct *mm = p->mm; unsigned long m_a_occ = 0; - cpumask_var_t cpus; WARN_ON_ONCE(work != &p->cache_work); @@ -1984,7 +2049,12 @@ static void task_cache_work(struct callback_head *work) if (p->flags & PF_EXITING) return; - grp = READ_ONCE(mm->sched_cache_grp); + /* + * A reference makes sure grp is not released by others. The rcu + * lock can not be held till after zalloc_cpumask_var() below, + * because the latter might sleep. + */ + grp = task_cache_group_get(p); if (!grp) return; @@ -1999,8 +2069,8 @@ static void task_cache_work(struct callback_head *work) return; curr_cpu = task_cpu(p); - if (invalid_llc_nr(mm, p, curr_cpu) || - exceed_llc_capacity(mm, curr_cpu)) { + if (invalid_llc_nr(grp, p, curr_cpu) || + exceed_llc_capacity(grp, curr_cpu)) { if (READ_ONCE(grp->cpu) != -1) WRITE_ONCE(grp->cpu, -1); @@ -2033,9 +2103,13 @@ static void task_cache_work(struct callback_head *work) m_cpu = i; } + /* + * rcu_access_pointer() is used because the + * pointer is only compared, never dereferenced. + */ cur = rcu_dereference_all(cpu_rq(i)->curr); if (cur && !(cur->flags & (PF_EXITING | PF_KTHREAD)) && - cur->mm == mm) + rcu_access_pointer(cur->sched_cache_grp) == grp) nr_running++; } @@ -2081,7 +2155,6 @@ static void task_cache_work(struct callback_head *work) } update_avg_scale(&grp->nr_running_avg, nr_running); - free_cpumask_var(cpus); } void init_sched_mm(struct task_struct *p) @@ -2090,6 +2163,13 @@ void init_sched_mm(struct task_struct *p) init_task_work(work, task_cache_work); work->next = work; + /* + * dup_task_struct() copies the parent's task_struct, including its + * sched_cache_grp, for which the child holds no reference. Clear it + * here - before copy_mm() runs - so the child never carries a + * borrowed pointer that the fork error path would put. + */ + RCU_INIT_POINTER(p->sched_cache_grp, NULL); /* * Reset new task's preference to avoid * polluting account_llc_enqueue(). @@ -3890,10 +3970,9 @@ static void task_numa_placement(struct task_struct *p) * heuristic and occasional lost updates are tolerable. * * If a task exits, its corresponding footprint must - * be subtracted from the mm->sched_cache_grp->footprint, - * otherwise the mm->sched_cache_grp->footprint will not - * converge: the exiting thread's footprint remains - * unchanged/undecayed in mm->sched_cache_grp->footprint. + * be subtracted from p->sched_cache_grp->footprint, + * otherwise the footprint will not converge: the + * exiting thread's footprint remains unchanged/undecayed. * See exit_mm(). * * Lost updates and unsynchronized subtraction @@ -3901,12 +3980,14 @@ static void task_numa_placement(struct task_struct *p) * go negative. Clamp to zero to prevent the * unsigned footprint from wrapping. */ - grp = READ_ONCE(p->mm->sched_cache_grp); - if (!grp) - continue; + scoped_guard(rcu) { + grp = rcu_dereference(p->sched_cache_grp); - new_fp = (long)READ_ONCE(grp->footprint) + diff; - WRITE_ONCE(grp->footprint, max(new_fp, 0L)); + if (grp) { + new_fp = (long)READ_ONCE(grp->footprint) + diff; + WRITE_ONCE(grp->footprint, max(new_fp, 0L)); + } + } #endif } @@ -10855,7 +10936,6 @@ static enum llc_mig can_migrate_llc_task(struct lb_env *env, struct task_struct *p) { struct sched_cache_group *grp; - struct mm_struct *mm; bool to_pref; int cpu, src_cpu, dst_cpu; @@ -10864,11 +10944,7 @@ static enum llc_mig can_migrate_llc_task(struct lb_env *env, src_cpu = env->src_cpu; dst_cpu = env->dst_cpu; - mm = p->mm; - if (!mm) - return mig_unrestricted; - - grp = READ_ONCE(mm->sched_cache_grp); + grp = rcu_dereference_all(p->sched_cache_grp); if (!grp) return mig_unrestricted; @@ -10877,8 +10953,8 @@ static enum llc_mig can_migrate_llc_task(struct lb_env *env, return mig_unrestricted; /* skip cache aware load balance for too many threads */ - if (invalid_llc_nr(mm, p, dst_cpu) || - exceed_llc_capacity(mm, dst_cpu)) { + if (invalid_llc_nr(grp, p, dst_cpu) || + exceed_llc_capacity(grp, dst_cpu)) { if (READ_ONCE(grp->cpu) != -1) WRITE_ONCE(grp->cpu, -1); return mig_unrestricted; From 65efcccddc83d6a19e8a2e2a6117811e39e589bf Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Mon, 21 Sep 2026 17:37:26 -0700 Subject: [PATCH 0973/1417] sched/cache: Skip kernel threads for cache aware scheduling to rubustify the code Kernel thread should not be covered by cache aware scheduling as it borrows the statistics from the user space thread. Filter the kernel thread in account_mm_sched(). In theory a kernel thread does not have any valid cache group, so !grp should gate the kernel thread. Add the PF_KTHREAD check explicitly here for safety reasons, to guard against future modifications and to pair with task_tick_cache(). Fixes: df0d98475954 ("sched/cache: Introduce infrastructure for cache-aware load balancing") Signed-off-by: Chen Yu Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # 7.2.x Link: https://patch.msgid.link/058f0c6ea7b991c177a17de347fa3157f25489a7.1790035273.git.tim.c.chen@linux.intel.com --- kernel/sched/fair.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 974a7dfe321558..57360f5cdde4fa 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1907,8 +1907,14 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) /* * init_task, kthreads and user thread created * by user_mode_thread() don't have a cache group. - */ - if (!grp || !grp->pcpu_sched) + * In theory a kernel thread does not have any valid + * cache group, because sched_cache_fork() is not + * invoked for a kernel thread - !grp should gate the + * kernel thread. Use the PF_KTHREAD check explicitly + * here for safety reasons, to guard against future + * modifications and to pair with task_tick_cache(). + */ + if (p->flags & PF_KTHREAD || !grp || !grp->pcpu_sched) return; pcpu_sched = per_cpu_ptr(grp->pcpu_sched, cpu_of(rq)); From 3cb0243767fd033bdce95f4f1b5882172a2f8119 Mon Sep 17 00:00:00 2001 From: Davi Chaves Azevedo Date: Mon, 21 Sep 2026 17:37:27 -0700 Subject: [PATCH 0974/1417] sched/cache: Refresh LLC capacity across CPU hotplug, to fix capacity underestimation bug The scheduler scales LLC capacity by the fraction of cache-sharing CPUs covered by a domain: llc_bytes = cache_size * span_weight / shared_weight During CPU teardown, sched_cpu_deactivate() rebuilds scheduler domains before cacheinfo_cpu_pre_down() removes the CPU from shared_cpu_map. The new domains therefore use the old sharing weight. The later call to sched_update_llc_bytes() looks up the departing CPU's sd_llc, which has already been detached, and returns without correcting the surviving CPUs. On a Ryzen 5 7535U with twelve logical CPUs sharing a 16 MiB LLC, offlining one SMT sibling left the remaining CPUs with: llc_bytes = floor(16777216 * 11 / 12) = 15379114 bytes The correct capacity is still 16777216 bytes. On systems with active cache-aware scheduling, an underestimated capacity can cause exceed_llc_capacity() to reject aggregation for a process whose footprint would fit. Unchanged cpuset partitions sharing the physical cache can also retain stale capacity when a CPU comes online in another partition. Pass the cache-sharing mask already retained by cacheinfo to the scheduler update. Refresh every surviving CPU using its own LLC domain so that each partition receives the correct share. This also preserves the correction needed as cache-sharing maps grow during boot. Keep the existing CPU-hotplug and scheduler-domain synchronization. The update remains on the hotplug path; no steady-state scheduling operation or persistent allocation is added. Fixes: 7030513a0877 ("sched/cache: Calculate the LLC size and store it in sched_domain") Signed-off-by: Davi Chaves Azevedo Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Chen Yu Reviewed-by: Tim Chen Reviewed-by: K Prateek Nayak Tested-by: Chen Yu Tested-by: K Prateek Nayak Cc: # v7.2.x Link: https://patch.msgid.link/6751d93e15889e624796c74db0bfe66603d60b1b.1790035273.git.tim.c.chen@linux.intel.com --- drivers/base/cacheinfo.c | 11 ++++++----- include/linux/sched/topology.h | 4 ++-- kernel/sched/topology.c | 22 +++++++++++++--------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c index 9f9c72727a0590..7a47a392568a0d 100644 --- a/drivers/base/cacheinfo.c +++ b/drivers/base/cacheinfo.c @@ -1040,9 +1040,10 @@ static int cacheinfo_cpu_online(unsigned int cpu) rc = cache_add_dev(cpu); if (rc) goto err; - if (cpu_map_shared_cache(true, cpu, &cpu_map)) + if (cpu_map_shared_cache(true, cpu, &cpu_map)) { update_per_cpu_data_slice_size(true, cpu, cpu_map); - sched_update_llc_bytes(cpu); + sched_update_llc_bytes(cpu_map); + } return 0; err: free_cache_attributes(cpu); @@ -1059,10 +1060,10 @@ static int cacheinfo_cpu_pre_down(unsigned int cpu) cpu_cache_sysfs_exit(cpu); free_cache_attributes(cpu); - if (nr_shared > 1) + if (nr_shared > 1) { update_per_cpu_data_slice_size(false, cpu, cpu_map); - - sched_update_llc_bytes(cpu); + sched_update_llc_bytes(cpu_map); + } return 0; } diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h index b5d9d7c2b8add7..f96812d71c515c 100644 --- a/include/linux/sched/topology.h +++ b/include/linux/sched/topology.h @@ -281,9 +281,9 @@ static inline int task_node(const struct task_struct *p) } #ifdef CONFIG_SCHED_CACHE -extern void sched_update_llc_bytes(unsigned int cpu); +extern void sched_update_llc_bytes(const struct cpumask *cpus); #else -static inline void sched_update_llc_bytes(unsigned int cpu) { } +static inline void sched_update_llc_bytes(const struct cpumask *cpus) { } #endif #endif /* _LINUX_SCHED_TOPOLOGY_H */ diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c index 0248227d983a71..3dab0253976fb4 100644 --- a/kernel/sched/topology.c +++ b/kernel/sched/topology.c @@ -985,8 +985,8 @@ void sched_cache_active_set(void) } /* - * Update the bottom sched_domain's llc_bytes for @cpu and all its - * LLC siblings. Called from cacheinfo_cpu_online() or + * Update the bottom sched_domain's llc_bytes for @cpus sharing a physical + * LLC. Called from cacheinfo_cpu_online() or * cacheinfo_cpu_pre_down() with cpu hotplug lock held. * * Note: get_effective_llc_bytes() returns 0 on PowerPC. @@ -996,17 +996,13 @@ void sched_cache_active_set(void) * and does not populates the per-CPU struct cpu_cacheinfo array * that get_cpu_cacheinfo_llc() reads. */ -void sched_update_llc_bytes(unsigned int cpu) +void sched_update_llc_bytes(const struct cpumask *cpus) { struct sched_domain *sd, *sdp; unsigned int i; sched_domains_mutex_lock(); - sdp = rcu_dereference_sched_domain(per_cpu(sd_llc, cpu)); - if (!sdp) - goto unlock; - /* * ci->shared_cpu_map is built incrementally as CPUs come * online, so the first CPU in an LLC initially sees @@ -1014,14 +1010,22 @@ void sched_update_llc_bytes(unsigned int cpu) * get_effective_llc_bytes(). Re-evaluating every LLC * sibling on each online event corrects this once the full * shared_cpu_map is known. + * + * The departing CPU's domains have already been detached when + * cacheinfo removes it. Use the surviving cache siblings instead. + * They may belong to different cpuset partitions, so use each CPU's + * own LLC domain to scale its share of the physical cache. */ - for_each_cpu(i, sched_domain_span(sdp)) { + for_each_cpu(i, cpus) { + sdp = rcu_dereference_sched_domain(per_cpu(sd_llc, i)); + if (!sdp) + continue; + sd = rcu_dereference_sched_domain(cpu_rq(i)->sd); if (sd) sd->llc_bytes = get_effective_llc_bytes(i, sdp); } -unlock: sched_domains_mutex_unlock(); } From 5fd0783b99d4af98f65cd58b56ec203d1d426104 Mon Sep 17 00:00:00 2001 From: Shardul Bankar Date: Thu, 17 Sep 2026 14:55:32 +0530 Subject: [PATCH 0975/1417] udp: relocate a connected socket in the 4-tuple hash table on re-connect A connected UDP socket that connects again to a different peer is not re-filed in the 4-tuple hash table: sk binds to 127.0.0.1:21001 sk connects to 127.0.0.2:20001 // filed under hash(sk, peer1) sk connects to 127.0.0.3:20002 // still filed under hash(sk, peer1) packet from 127.0.0.3:20002 // hash(sk, peer2) misses, so the // lookup falls back to scoring the // hash2 chain for this address // and port udp_lib_hash4() returns early when the socket is already hashed, assuming ->rehash() relocates it. ->rehash() runs from __ip{4,6}_datagram_connect() only while the receive address is unset, which a second connect never is: the first connect assigns it, whether the socket was bound to a specific address or to the wildcard. commit 644f9108f3a5 ("udp: Make rehash4 independent in udp_lib_rehash()") added that early return and named connect(AF_UNSPEC) as the way around it. That workaround does not help a socket with both SOCK_BINDADDR_LOCK and SOCK_BINDPORT_LOCK set, because __udp_disconnect() skips ->rehash() for the first and ->unhash() for the second. Delivery is correct either way. Relocate the socket when the hash it is filed under differs from the one requested, which is what commit 78c91ae2c6de ("ipv4/udp: Add 4-tuple hash for connected socket") did before the early return became unconditional. It is done here under hslot->lock, which that version did not take, to match udp_lib_rehash() and udp_lib_unhash(). hslot2 is unchanged, so hash4_cnt needs no adjustment, as in udp_lib_rehash(). A first connect is unaffected, and IPv6 shares the code. With 500 sockets on the port, a re-connected socket measured 522,553 pps without this change and 2,055,078 with it. The UDP side was noted as remaining work in [1]. Link: https://lore.kernel.org/netdev/apnHqmYZQ4yzOP4N@v4bel/ [1] Fixes: 644f9108f3a5 ("udp: Make rehash4 independent in udp_lib_rehash()") Assisted-by: LLM Signed-off-by: Shardul Bankar Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260917-udp_hash4_fix_v1-v1-1-718891af0d7a@mpiricsoftware.com Signed-off-by: Paolo Abeni --- net/ipv4/udp.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index bb8cfc62cb0043..0fa3cdbdcc21af 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -617,14 +617,23 @@ void udp_lib_hash4(struct sock *sk, u16 hash) struct net *net = sock_net(sk); struct udp_table *udptable; - /* Connected udp socket can re-connect to another remote address, which - * will be handled by rehash. Thus no need to redo hash4 here. + udptable = net->ipv4.udp_table; + hslot = udp_hashslot(udptable, net, udp_sk(sk)->udp_port_hash); + + /* A connected socket can re-connect to another address. rehash() + * relocates it, but only runs when the local address changes, so a + * socket bound to a specific address would stay filed under the + * previous peer's hash. Move it here. */ - if (udp_hashed4(sk)) + if (udp_hashed4(sk)) { + if (udp_sk(sk)->udp_lrpa_hash != hash) { + spin_lock_bh(&hslot->lock); + udp_rehash4(udptable, sk, hash); + spin_unlock_bh(&hslot->lock); + } return; + } - udptable = net->ipv4.udp_table; - hslot = udp_hashslot(udptable, net, udp_sk(sk)->udp_port_hash); hslot2 = udp_hashslot2(udptable, udp_sk(sk)->udp_portaddr_hash); hslot4 = udp_hashslot4(udptable, hash); udp_sk(sk)->udp_lrpa_hash = hash; From 9e95b1a94c9c49b4ba722251bbca2759b9c51737 Mon Sep 17 00:00:00 2001 From: Shardul Bankar Date: Thu, 17 Sep 2026 14:55:33 +0530 Subject: [PATCH 0976/1417] udp: remove a disconnected socket from the 4-tuple hash table A UDP socket bound to a specific address and port keeps its entry in the 4-tuple hash table after it is disconnected: sk binds to 127.0.0.1:21001 sk connects to 127.0.0.2:20001 // filed in the 4-tuple table sk disconnects, connect(AF_UNSPEC) // still filed, peer now 0.0.0.0:0 __udp_disconnect() takes a socket out of that table only as a side effect of ->rehash() or ->unhash(), and it skips ->rehash() when SOCK_BINDADDR_LOCK is set and ->unhash() when SOCK_BINDPORT_LOCK is set. commit 6996a2d2d0a6 ("udp: Unhash auto-bound connected sk from 4-tuple hash table when disconnected.") fixed the same end state for a wildcard-bound socket, by a path this one does not take. The entry is counted whether or not anything hits it. hash4_cnt on the hash2 slot stays raised for as long as the socket lives, so udp_has_hash4() keeps sending every packet for that address and port through the 4-tuple lookup first. On IPv6 it can also be hit. __udp_disconnect() does not clear sk_v6_daddr, so udp_v6_rehash() files the entry under the peer the socket was connected to with a zero dport, and inet6_match() compares that same field: a datagram from the former peer with a zero source port matches, and source port zero is accepted on receive. On IPv4 the peer is cleared, so a match would need a zero source address as well, which the routing layer rejects as martian. The stale sk_v6_daddr is a separate defect, not addressed here; removing the entry closes this path either way. The entry can also be relocated. __udp_disconnect() clears sk_bound_dev_if, so a subsequent SO_BINDTODEVICE calls ->rehash(), and because the receive address is still specific udp_lib_rehash() moves the entry instead of removing it, into the bucket that (rcv_saddr, num, 0, 0) hashes to -- a pure function of the address and port, so every socket reaching this state on one address and port collects in one bucket. The bucket cannot be chosen from outside, as udp_ehashfn() is seeded with a per-boot secret. This last one became reachable only with commit 644f9108f3a5 ("udp: Make rehash4 independent in udp_lib_rehash()"), which moved the hash4 handling out of a branch a disconnected socket does not take; the stale entry itself dates from the commit in Fixes. Take the socket out of the table before __udp_disconnect() runs, while it still matches how it was filed. This also reaches the wildcard case ahead of udp_lib_rehash()'s udp_unhash4() branch, leaving that branch unreachable from udp_disconnect(); removing it belongs in net-next. udp_disconnect() and udp_abort() are the only UDP entries into __udp_disconnect(), which is shared with raw, ping and l2tp sockets that are not struct udp_sock: ping_prot.obj_size is sizeof(struct inet_sock), so udp_hashed4() on one would read past the allocation. Fixes: 78c91ae2c6de ("ipv4/udp: Add 4-tuple hash for connected socket") Assisted-by: LLM Signed-off-by: Shardul Bankar Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260917-udp_hash4_fix_v1-v1-2-718891af0d7a@mpiricsoftware.com Signed-off-by: Paolo Abeni --- net/ipv4/udp.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 0fa3cdbdcc21af..b090bd1f59e86c 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -2206,9 +2206,31 @@ int __udp_disconnect(struct sock *sk, int flags) } EXPORT_SYMBOL(__udp_disconnect); +/* __udp_disconnect() takes a socket out of the 4-tuple hash table only via + * ->rehash() or ->unhash(), and neither runs for a socket bound to a + * specific address and port. Remove it here, before its peer is cleared. + */ +static void udp_unhash4_on_disconnect(struct sock *sk) +{ + struct net *net = sock_net(sk); + struct udp_table *udptable; + struct udp_hslot *hslot; + + if (!udp_hashed4(sk)) + return; + + udptable = net->ipv4.udp_table; + hslot = udp_hashslot(udptable, net, udp_sk(sk)->udp_port_hash); + + spin_lock_bh(&hslot->lock); + udp_unhash4(udptable, sk); + spin_unlock_bh(&hslot->lock); +} + int udp_disconnect(struct sock *sk, int flags) { lock_sock(sk); + udp_unhash4_on_disconnect(sk); __udp_disconnect(sk, flags); release_sock(sk); return 0; @@ -3140,6 +3162,7 @@ int udp_abort(struct sock *sk, int err) sk->sk_err = err; sk_error_report(sk); + udp_unhash4_on_disconnect(sk); __udp_disconnect(sk, 0); out: From cec38d5c098a350dcf084d345025136ade7e6d1e Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:09 -0700 Subject: [PATCH 0977/1417] perf/x86/intel: Ensure KVM guest PEBS path doesn't set unwanted PERF_GLOBAL_CTRL bits When reinstating PEBS counters into PERF_GLOBAL_CTRL for a KVM guest, mask the value with perf's desired/original PERF_GLOBAL_CTRL value to ensure KVM doesn't unintentionally set reserved bits in PERF_GLOBAL_CTRL. E.g. if the guest's PEBS_ENABLE value had bit 63, "Enable Precise Store", set, then using the raw guest PEBS value would propagate bit 63 to the guest's PERF_GLOBAL_CTRL value (which thankfully would be a failed VM-Entry, not a VMX Abort). The only reason this bug isn't reachable is because KVM doesn't support "Enable Precise Store" (which is probably a KVM bug?), i.e. bit 63 can't be set in kvm_pmu->pebs_enable and thus not in arr[pebs_enable].guest. In other words, this _should_ be a glorified NOP in the current code base. Fixes: c59a1f106f5c ("KVM: x86/pmu: Add IA32_PEBS_ENABLE MSR emulation for extended PEBS") Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-2-seanjc@google.com --- arch/x86/events/intel/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 1ac2ca35db5375..0400b5111db99d 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -5359,7 +5359,7 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) arr[pebs_enable].guest &= ~kvm_pmu->host_cross_mapped_mask; arr[global_ctrl].guest &= ~kvm_pmu->host_cross_mapped_mask; /* Set hw GLOBAL_CTRL bits for PEBS counter when it runs for guest */ - arr[global_ctrl].guest |= arr[pebs_enable].guest; + arr[global_ctrl].guest |= intel_ctrl & arr[pebs_enable].guest; } return arr; From 4b64dbdc5861477f148e13d1ed127e7fe7182e4f Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:10 -0700 Subject: [PATCH 0978/1417] perf/x86/intel: Don't write PEBS_ENABLED on host<=>guest xfers if CPU has PEBS isolation, to fix stuck PEBS_ENABLED When filling the list of MSRs to be loaded by KVM on VM-Enter and VM-Exit, *never* insert an entry for PEBS_ENABLED if the CPU properly isolates PEBS events, in which case disabling counters via PERF_GLOBAL_CTRL is sufficient to prevent unwanted PEBS events in the guest (or host). Because perf loads PEBS_ENABLE with the unfiltered cpu_hw_events.pebs_enabled, i.e. with both host and guest masks, there is no need to load different values for the guest versus host, perf+KVM can and should simply control which counters are enabled/disabled via PERF_GLOBAL_CTRL. Avoiding touching PEBS_ENABLED "fixes" a bug where PEBS_ENABLED can end up with "stuck" bits if a PEBS event is throttled between generating the list and actually entering the guest (Intel CPUs can't arbtitrarily block NMIs). Fixes in quotes because leaving PEBS_ENABLED as-is doesn't fix the underlying problem of perf (via PMIs) being able to modify state after the perf<=>KVM handoff. But not writing PEBS_ENABLED is desirable no matter what, as stating the obvious, leaving PEBS_ENABLED as-is avoids three MSR writes on every VMX transition: one each on entry/exit, and one more explicit WRMSR to zero PEBS_ENABLED before VM-Entry (KVM assumes the only reason PEBS_ENABLED is in the load list is if the CPU lacks PEBS isolation and thus needs a quiescent period). Opportunistically add comments to (better) explain the rules for generating the set of PEBS counters that will be active while the guest is running, along with a FIXME for the suspected hack-a-fix where perf disables guest PEBS if _any_ PEBS event is configured to count in the host (commit 854250329c02 ("KVM: x86/pmu: Disable guest PEBS temporarily in two rare situations") doesn't explain the motivation, at all). Fixes: c59a1f106f5c ("KVM: x86/pmu: Add IA32_PEBS_ENABLE MSR emulation for extended PEBS") Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-3-seanjc@google.com --- arch/x86/events/intel/core.c | 55 ++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 0400b5111db99d..5ac8fe31871bd6 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -5292,12 +5292,15 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) struct kvm_pmu *kvm_pmu = (struct kvm_pmu *)data; u64 intel_ctrl = hybrid(cpuc->pmu, intel_ctrl); u64 pebs_mask = cpuc->pebs_enabled & x86_pmu.pebs_capable; - int global_ctrl, pebs_enable; + u64 guest_pebs_mask; + int global_ctrl; /* * In addition to obeying exclude_guest/exclude_host, remove bits being * used for PEBS when running a guest, because PEBS writes to virtual - * addresses (not physical addresses). + * addresses (not physical addresses). If the guest wants to utilize + * PEBS, and PEBS can be safely enabled in the guest, bits for the guest's + * PEBS-enabled counters will be OR'd back in as appropriate. */ *nr = 0; global_ctrl = (*nr)++; @@ -5344,24 +5347,40 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) }; } - pebs_enable = (*nr)++; - arr[pebs_enable] = (struct perf_guest_switch_msr){ - .msr = MSR_IA32_PEBS_ENABLE, - .host = cpuc->pebs_enabled & ~cpuc->intel_ctrl_guest_mask, - .guest = pebs_mask & ~cpuc->intel_ctrl_host_mask & kvm_pmu->pebs_enable, - }; + /* + * Restrict guest PEBS events to counters that (a) perf supports, (b) + * the guest wants to use for PEBS, (c) are not excluded from counting + * in the guest, and (d) _are_ excluded from counting in the host. + */ + guest_pebs_mask = pebs_mask & intel_ctrl & kvm_pmu->pebs_enable & + ~cpuc->intel_ctrl_host_mask & + cpuc->intel_ctrl_guest_mask; - if (arr[pebs_enable].host) { - /* Disable guest PEBS if host PEBS is enabled. */ - arr[pebs_enable].guest = 0; - } else { - /* Disable guest PEBS thoroughly for cross-mapped PEBS counters. */ - arr[pebs_enable].guest &= ~kvm_pmu->host_cross_mapped_mask; - arr[global_ctrl].guest &= ~kvm_pmu->host_cross_mapped_mask; - /* Set hw GLOBAL_CTRL bits for PEBS counter when it runs for guest */ - arr[global_ctrl].guest |= intel_ctrl & arr[pebs_enable].guest; - } + /* + * Disable counters where the guest PMC is different than the host PMC + * being used on behalf of the guest, as the PEBS record includes + * PERF_GLOBAL_STATUS, i.e. the guest will see overflow status for the + * wrong counter(s). + */ + guest_pebs_mask &= ~kvm_pmu->host_cross_mapped_mask; + + /* + * FIXME: Allow guest and host usage of PEBS events to co-exist instead + * of disabling guest PEBS entirely if the host is using PEBS. + * What exactly goes wrong if guest and host are using PEBS is + * unknown. + */ + if (pebs_mask & ~cpuc->intel_ctrl_guest_mask) + guest_pebs_mask = 0; + /* + * Do NOT mess with PEBS_ENABLED. As above, disabling counters via + * PERF_GLOBAL_CTRL is sufficient, and loading a stale PEBS_ENABLED, + * e.g. on VM-Exit, can put the system in a bad state. Simply enable + * counters in PERF_GLOBAL_CTRL, as perf load PEBS_ENABLED with the + * full value, i.e. perf *also* relies on PERF_GLOBAL_CTRL. + */ + arr[global_ctrl].guest |= guest_pebs_mask; return arr; } From d06260e99eb93d2942b7af4ccd789eb8a6c829d3 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:11 -0700 Subject: [PATCH 0979/1417] perf/x86/intel: Don't pointlessly context switch DS_AREA (and PEBS config) if PEBS is unused When filling the list of MSRs to be loaded by KVM on VM-Enter and VM-Exit, load the guest values for DS_AREA and (conditionally) MSR_PEBS_DATA_CFG if and only if PEBS will be active in the guest, i.e. only if a PEBS record may be generated while running the guest. As shown by the !pebs_ept path, it's perfectly safe to run with the host's DS_AREA, so long as PEBS-enabled counters are disabled via PERF_GLOBAL_CTRL. Omitting DS_AREA and MSR_PEBS_DATA_CFG when PEBS is unused saves two MSR writes per MSR on each VMX transition, i.e. eliminates two/four pointless MSR writes on each VMX roundtrip when PEBS isn't being used by the guest. Fixes: c59a1f106f5c ("KVM: x86/pmu: Add IA32_PEBS_ENABLE MSR emulation for extended PEBS") Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Jim Mattson Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-4-seanjc@google.com --- arch/x86/events/intel/core.c | 39 +++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 5ac8fe31871bd6..75c74e8fe2f4a2 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -5330,23 +5330,14 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) return arr; } + /* + * If the guest won't use PEBS or the CPU doesn't support PEBS in the + * guest, then there's nothing more to do as disabling PMCs via + * PERF_GLOBAL_CTRL is sufficient on CPUs with guest/host isolation. + */ if (!kvm_pmu || !x86_pmu.pebs_ept) return arr; - arr[(*nr)++] = (struct perf_guest_switch_msr){ - .msr = MSR_IA32_DS_AREA, - .host = (unsigned long)cpuc->ds, - .guest = kvm_pmu->ds_area, - }; - - if (x86_pmu.intel_cap.pebs_baseline) { - arr[(*nr)++] = (struct perf_guest_switch_msr){ - .msr = MSR_PEBS_DATA_CFG, - .host = cpuc->active_pebs_data_cfg, - .guest = kvm_pmu->pebs_data_cfg, - }; - } - /* * Restrict guest PEBS events to counters that (a) perf supports, (b) * the guest wants to use for PEBS, (c) are not excluded from counting @@ -5373,6 +5364,26 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) if (pebs_mask & ~cpuc->intel_ctrl_guest_mask) guest_pebs_mask = 0; + /* + * Context switch DS_AREA and PEBS_DATA_CFG if and only if PEBS will be + * active in the guest; if no records will be generated while the guest + * is running, then simply keep the host values resident in hardware. + */ + arr[(*nr)++] = (struct perf_guest_switch_msr){ + .msr = MSR_IA32_DS_AREA, + .host = (unsigned long)cpuc->ds, + .guest = guest_pebs_mask ? kvm_pmu->ds_area : (unsigned long)cpuc->ds, + }; + + if (x86_pmu.intel_cap.pebs_baseline) { + arr[(*nr)++] = (struct perf_guest_switch_msr){ + .msr = MSR_PEBS_DATA_CFG, + .host = cpuc->active_pebs_data_cfg, + .guest = guest_pebs_mask ? kvm_pmu->pebs_data_cfg : + cpuc->active_pebs_data_cfg, + }; + } + /* * Do NOT mess with PEBS_ENABLED. As above, disabling counters via * PERF_GLOBAL_CTRL is sufficient, and loading a stale PEBS_ENABLED, From a391618e1d563f099e4c2a704f45d08329ccdf7c Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:12 -0700 Subject: [PATCH 0980/1417] perf/x86/intel: Make @data a mandatory param for intel_guest_get_msrs() Drop "support" for passing a NULL @data/@kvm_pmu param when getting guest MSRs. KVM, the only in-tree user, unconditionally passes a non-NULL pointer, and carrying code that suggests @data may be NULL is confusing, e.g. incorrectly implies that there are scenarios where KVM doesn't pass a PMU context. Fixes: 8183a538cd95 ("KVM: x86/pmu: Add IA32_DS_AREA MSR emulation to support guest DS") Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Jim Mattson Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-5-seanjc@google.com --- arch/x86/events/intel/core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 75c74e8fe2f4a2..757d4082de601f 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -5331,11 +5331,11 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) } /* - * If the guest won't use PEBS or the CPU doesn't support PEBS in the - * guest, then there's nothing more to do as disabling PMCs via - * PERF_GLOBAL_CTRL is sufficient on CPUs with guest/host isolation. + * If the CPU doesn't support PEBS in the guest, then there's nothing + * more to do as disabling PMCs via PERF_GLOBAL_CTRL is sufficient on + * CPUs with guest/host isolation. */ - if (!kvm_pmu || !x86_pmu.pebs_ept) + if (!x86_pmu.pebs_ept) return arr; /* From a92e1a412c53dc0d9ad639e7abf8b3fc70a5b6ad Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Thu, 17 Sep 2026 14:33:36 -0400 Subject: [PATCH 0981/1417] tg3: clean up PHYLIB resources on probe failure tg3_get_invariants() can register an MDIO bus and connect a PHY for USE_PHYLIB devices. If tg3_init_one() later fails, its common error path releases the mappings and netdev without undoing those PHYLIB resources. Disconnect the PHY and unregister the MDIO bus before the remaining teardown. Guard PHY cleanup with USE_PHYLIB to match tg3_phy_init(), and call tg3_mdio_fini() unconditionally to match tg3_mdio_init(). The existing IS_CONNECTED and MDIOBUS_INITED flags make both helpers safe when initialization only completed partially. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 158d7abdae85 ("tg3: Add mdio bus registration") Assisted-by: OpenAI:GPT-5.6 Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260917183336.36239-1-mhun512@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/tg3.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c index 73a4b569b03e31..caa7a6caa6e2fa 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c @@ -18047,6 +18047,10 @@ static int tg3_init_one(struct pci_dev *pdev, return 0; err_out_apeunmap: + if (tg3_flag(tp, USE_PHYLIB)) + tg3_phy_fini(tp); + tg3_mdio_fini(tp); + if (tp->aperegs) { iounmap(tp->aperegs); tp->aperegs = NULL; From 89dc568e8c0be60e05e5fcd0b528c79077d7b84b Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:23 +0800 Subject: [PATCH 0982/1417] perf/x86/intel: Fix GRT PEBS load/store direction for latency events, to fix sample classification On Gracemont, intel_grt_pebs_event_constraints[] applies LAT_CONSTRAINT constraints to MEM_UOPS_RETIRED.{LOAD,STORE}_LATENCY, but does not set explicit LOAD/STORE flags for those events. The PEBS latency path (pebs_latency_data(), via __grt_latency_data()) uses the event flags to determine memory operation direction. Without an explicit STORE flag, samples from MEM_UOPS_RETIRED.STORE_LATENCY can be misclassified as LOADs. Set explicit LOAD/STORE flags in intel_grt_pebs_event_constraints[] for: - MEM_UOPS_RETIRED.LOAD_LATENCY - MEM_UOPS_RETIRED.STORE_LATENCY Also update __grt_latency_data() to explicitly interpret these flags when assigning the sampled memory operation direction. This fixes incorrect STORE sample classification. Fixes: 39a41278f041 ("perf/x86/intel: Fix PEBS memory access info encoding for ADL") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # v7.2+ Link: https://patch.msgid.link/20260917015234.981153-2-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index b98029b4405218..fff98f457576a7 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -455,6 +455,7 @@ static inline void pebs_set_tlb_lock(u64 *val, bool tlb, bool lock) static u64 __grt_latency_data(struct perf_event *event, u64 status, u8 dse, bool tlb, bool lock, bool blk) { + union perf_mem_data_src src; u64 val; WARN_ON_ONCE(is_hybrid() && @@ -470,7 +471,16 @@ static u64 __grt_latency_data(struct perf_event *event, u64 status, else val |= P(BLK, NA); - return val; + src.val = val; + + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_LDLAT | PERF_X86_EVENT_PEBS_LD_HSW)) + src.mem_op = P(OP, LOAD); + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_STLAT | PERF_X86_EVENT_PEBS_ST_HSW)) + src.mem_op = P(OP, STORE); + + return src.val; } u64 grt_latency_data(struct perf_event *event, u64 status) @@ -1291,8 +1301,8 @@ struct event_constraint intel_glm_pebs_event_constraints[] = { struct event_constraint intel_grt_pebs_event_constraints[] = { /* Allow all events as PEBS with no flags */ - INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0x3), - INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0x3f), + INTEL_HYBRID_LDLAT_CONSTRAINT(0x5d0, 0x3), + INTEL_HYBRID_STLAT_CONSTRAINT(0x6d0, 0x3f), EVENT_CONSTRAINT_END }; From e961d6db42d1f1b66c81438969a648f08573bd9c Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:24 +0800 Subject: [PATCH 0983/1417] perf/x86/intel: Fix CMT PEBS load/store direction for latency events, to fix sample classification The same bug exists on Crestmont as on Gracemont: intel_cmt_pebs_event_constraints[] applies LAT_CONSTRAINT constraints to MEM_UOPS_RETIRED.{LOAD,STORE}_LATENCY, but does not set explicit LOAD/STORE flags for those events. The PEBS latency path (pebs_latency_data(), via cmt_latency_data) uses the event flags to determine memory operation direction. Without an explicit STORE flag, samples from MEM_UOPS_RETIRED.STORE_LATENCY can be misclassified as LOADs. Set explicit LOAD/STORE flags in intel_cmt_pebs_event_constraints[] for: - MEM_UOPS_RETIRED.LOAD_LATENCY - MEM_UOPS_RETIRED.STORE_LATENCY This fixes incorrect STORE sample classification. Fixes: e99fb45436ea ("perf/x86/intel: Update event constraints and cache_extra_regsfor MTL") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # v7.2+ Link: https://patch.msgid.link/20260917015234.981153-3-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index fff98f457576a7..fa7e0268b4f29e 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1308,8 +1308,8 @@ struct event_constraint intel_grt_pebs_event_constraints[] = { struct event_constraint intel_cmt_pebs_event_constraints[] = { /* Allow all events as PEBS with no flags */ - INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0x3), - INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0xff), + INTEL_HYBRID_LDLAT_CONSTRAINT(0x5d0, 0x3), + INTEL_HYBRID_STLAT_CONSTRAINT(0x6d0, 0xff), EVENT_CONSTRAINT_END }; From 2777ec9852277a06ae68fee0c4f1a32783e4a999 Mon Sep 17 00:00:00 2001 From: Nemesa Garg Date: Wed, 9 Sep 2026 16:33:32 +0530 Subject: [PATCH 0984/1417] drm/i915/psr: Clear stale sel fetch enable bits on sel fetch disable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selective fetch is dropped while pipe CRC is active, and the planes keep their SEL_FETCH_PLANE_CTL / SEL_FETCH_CUR_CTL enable bit set in hardware over that. A plane disabled while selective fetch is off never gets the bit cleared, as the disable path is guarded by enable_psr2_sel_fetch. Once selective fetch comes back the hardware resumes fetching for a plane that is no longer enabled and keeps its DDB range reserved. Clear the bits as selective fetch is turned off instead. Atomic check has both the old and the new crtc state, so record the transition there and let the plane and cursor arm paths write the registers to 0 for that commit. v2: Drop the old_crtc_state->hw.active check. [Jouni] Fixes: b1f5279b5981 ("drm/i915/psr: Move plane sel fetch configuration into plane source files") Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/8739 Assisted-by: Copilot:Claude-Opus-5 Signed-off-by: Nemesa Garg Reviewed-by: Jouni Högander Signed-off-by: Suraj Kandpal Link: https://patch.msgid.link/20260909110332.3528029-3-nemesa.garg@intel.com (cherry picked from commit a4c0e7f80429eda6990960971aebd4e4b9533cc6) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_cursor.c | 7 +++++-- .../gpu/drm/i915/display/intel_display_types.h | 2 ++ drivers/gpu/drm/i915/display/intel_psr.c | 15 +++++++++++++++ .../gpu/drm/i915/display/skl_universal_plane.c | 9 ++++----- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cursor.c b/drivers/gpu/drm/i915/display/intel_cursor.c index 0673f16f6fd0dc..824ffeef0103cd 100644 --- a/drivers/gpu/drm/i915/display/intel_cursor.c +++ b/drivers/gpu/drm/i915/display/intel_cursor.c @@ -536,7 +536,8 @@ static void i9xx_cursor_disable_sel_fetch_arm(struct intel_dsb *dsb, struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) + if (!crtc_state->enable_psr2_sel_fetch && + !crtc_state->clear_psr2_sel_fetch) return; intel_de_write_dsb(display, dsb, SEL_FETCH_CUR_CTL(pipe), 0); @@ -569,8 +570,10 @@ static void i9xx_cursor_update_sel_fetch_arm(struct intel_dsb *dsb, struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) + if (!crtc_state->enable_psr2_sel_fetch) { + i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); return; + } if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) { if (crtc_state->enable_psr2_su_region_et) { diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index 43d53a98dae755..f6a9b0de1ade11 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -1187,6 +1187,8 @@ struct intel_crtc_state { bool has_sel_update; bool enable_psr2_sel_fetch; bool enable_psr2_su_region_et; + /* Drop the stale selective fetch enable bits as selective fetch is turned off */ + bool clear_psr2_sel_fetch; bool req_psr2_sdp_prior_scanline; bool has_panel_replay; bool link_off_after_as_sdp_when_pr_active; diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c index 40e3d709599651..c28bcb1a018411 100644 --- a/drivers/gpu/drm/i915/display/intel_psr.c +++ b/drivers/gpu/drm/i915/display/intel_psr.c @@ -2883,6 +2883,8 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, struct intel_crtc *crtc) { struct intel_display *display = to_intel_display(state); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); struct intel_crtc_state *crtc_state = intel_atomic_get_new_crtc_state(state, crtc); struct intel_plane_state *new_plane_state, *old_plane_state; struct intel_plane *plane; @@ -2895,6 +2897,19 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, bool full_update = false, su_area_changed; int i, ret; + /* + * Selective fetch is not always usable, for instance it is dropped + * while pipe CRC is active. The planes keep their selective fetch + * enable bit set in hardware over that, and a plane disabled while + * selective fetch is off never gets the bit cleared. Once selective + * fetch comes back the hardware would resume fetching for a plane that + * is no longer enabled and keep its DDB range reserved, so have the + * plane update drop the bit for every plane of the pipe as selective + * fetch is turned off. + */ + crtc_state->clear_psr2_sel_fetch = old_crtc_state->enable_psr2_sel_fetch && + !crtc_state->enable_psr2_sel_fetch; + if (!crtc_state->enable_psr2_sel_fetch) return 0; diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c index 07a68329335219..eb5ed981b40f6a 100644 --- a/drivers/gpu/drm/i915/display/skl_universal_plane.c +++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c @@ -885,7 +885,8 @@ static void icl_plane_disable_sel_fetch_arm(struct intel_dsb *dsb, struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) + if (!crtc_state->enable_psr2_sel_fetch && + !crtc_state->clear_psr2_sel_fetch) return; intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), 0); @@ -1634,10 +1635,8 @@ static void icl_plane_update_sel_fetch_arm(struct intel_dsb *dsb, struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) - return; - - if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) + if (crtc_state->enable_psr2_sel_fetch && + drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), SEL_FETCH_PLANE_CTL_ENABLE); else From 5271d81f99dd01d983d439930eb056952485e15e Mon Sep 17 00:00:00 2001 From: Ankit Nautiyal Date: Mon, 7 Sep 2026 09:15:55 +0530 Subject: [PATCH 0985/1417] drm/i915/quirks: Limit eDP rate to HBR2 on HP Pavilion Plus 14-ew1 The eDP panel on the HP Pavilion Plus Laptop 14-ew1xxx advertises HBR3 while leaving the TPS4 support bit clear. The output however flickers, once link is trained with HBR3. Until commit 8c9006283e4b ("Revert "drm/i915/dp: Reject HBR3 when sink doesn't support TPS4"") such sinks were capped at HBR2 by the TPS4 check which incidentally kept this panel stable. That check was reverted because other panels legitimately need HBR3 without advertising TPS4, and the per-machine QUIRK_EDP_LIMIT_RATE_HBR2 was introduced to handle the affected machines instead. Add the machine to the list of devices that need the QUIRK_EDP_LIMIT_RATE_HBR2. Fixes: 8c9006283e4b ("Revert "drm/i915/dp: Reject HBR3 when sink doesn't support TPS4"") Reported-by: Annoy Cc Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16743 Cc: # v6.18+ Tested-by: Annoy Cc Signed-off-by: Ankit Nautiyal Reviewed-by: Nemesa Garg Link: https://patch.msgid.link/20260907034555.2753846-1-ankit.k.nautiyal@intel.com (cherry picked from commit 550b703fdbb2a2022faa75b4b11ab135241afbd9) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c index 33245f44c0d501..7d7db774d8c7b9 100644 --- a/drivers/gpu/drm/i915/display/intel_quirks.c +++ b/drivers/gpu/drm/i915/display/intel_quirks.c @@ -257,6 +257,9 @@ static struct intel_quirk intel_quirks[] = { /* Dell XPS 13 7390 2-in-1 */ { 0x8a52, 0x1028, 0x08b0, quirk_edp_limit_rate_hbr2 }, + /* HP Pavilion Plus Laptop 14-ew1xxx */ + { 0x7d55, 0x103c, 0x8c31, quirk_edp_limit_rate_hbr2 }, + /* Xiaomi Book Pro 14 2026 */ { 0xb081, 0x1d72, 0x2424, quirk_disable_psr2 }, }; From 8302c5f475fa5a4ed36a7d32c7b965023d5d7066 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:25 +0800 Subject: [PATCH 0986/1417] perf/x86/intel: Fix DKT PEBS load/store direction for latency events, to fix sample classification Same bug exists on Darkmont as on Gracemont: intel_dkt_pebs_event_constraints[] applies LAT_CONSTRAINT constraints to MEM_UOPS_RETIRED.{LOAD,STORE}_LATENCY, but does not set explicit LOAD/STORE flags for those events. The PEBS latency path (pebs_latency_data(), via cmt_latency_data) uses the event flags to determine memory operation direction. Without an explicit STORE flag, samples from MEM_UOPS_RETIRED.STORE_LATENCY can be misclassified as LOADs. Set explicit LOAD/STORE flags in intel_dkt_pebs_event_constraints[] for: - MEM_UOPS_RETIRED.LOAD_LATENCY - MEM_UOPS_RETIRED.STORE_LATENCY This fixes incorrect STORE sample classification. Additionally remove INTEL_HYBRID_LAT_CONSTRAINT() since no one uses it anymore. Fixes: 65fd435095bb ("perf/x86/intel: Update event constraints for PTL") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # v7.2+ Link: https://patch.msgid.link/20260917015234.981153-4-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 4 ++-- arch/x86/events/perf_event.h | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index fa7e0268b4f29e..ea5b331c58d19e 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1315,8 +1315,8 @@ struct event_constraint intel_cmt_pebs_event_constraints[] = { struct event_constraint intel_dkt_pebs_event_constraints[] = { /* Allow all events as PEBS with no flags */ - INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0xff), - INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0xff), + INTEL_HYBRID_LDLAT_CONSTRAINT(0x5d0, 0xff), + INTEL_HYBRID_STLAT_CONSTRAINT(0x6d0, 0xff), EVENT_CONSTRAINT_END }; diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 4680cba9134062..fab9da78a5c758 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -517,10 +517,6 @@ struct cpu_hw_events { __EVENT_CONSTRAINT(c, n, INTEL_ARCH_EVENT_MASK|X86_ALL_EVENT_FLAGS, \ HWEIGHT(n), 0, PERF_X86_EVENT_PEBS_ST) -#define INTEL_HYBRID_LAT_CONSTRAINT(c, n) \ - __EVENT_CONSTRAINT(c, n, INTEL_ARCH_EVENT_MASK|X86_ALL_EVENT_FLAGS, \ - HWEIGHT(n), 0, PERF_X86_EVENT_PEBS_LAT_HYBRID) - #define INTEL_HYBRID_LDLAT_CONSTRAINT(c, n) \ __EVENT_CONSTRAINT(c, n, INTEL_ARCH_EVENT_MASK|X86_ALL_EVENT_FLAGS, \ HWEIGHT(n), 0, PERF_X86_EVENT_PEBS_LAT_HYBRID|PERF_X86_EVENT_PEBS_LD_HSW) From acbe9a3b60b9a6ace8ef11fe898f586251c84592 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Mon, 7 Sep 2026 20:44:12 +0300 Subject: [PATCH 0987/1417] drm/i915/dp_mst: Fix configuring FEC for a disconnected stream During an atomic commit after all the MST stream CRTC state is computed the driver ensures that the FEC is configured the same way (enabled or disabled) for all the streams on a given MST topology's link. drm_dp_mst_port_downstream_of_parent() used to determine if a stream is downstream of an MST port will return false if the whole topology is disconnected, since in that case it can't verify that the port/ parent_port passed to it is in the given MST topology. This is a problem during the above FEC configuration check, since intel_dp_mst_check_dsc_change()->get_pipes_downstream_of_mst_ports() will not return all the stream CRTCs/pipes for the topology as expected. Since passing parent_port==NULL to get_pipes_downstream_of_mst_port() is meant to return all the streams for the given topology (i.e. mst_mgr) skip checking if an MST port is downstream of a parent port in this case. This fixes a problem where the FEC configuration check explained above failed to ensure that all streams' FEC is configured the same way if the topology was disconnected, leading to a FEC state mismatch error. Cc: stable@vger.kernel.org # v6.10+ Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16073 Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16384 Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260907174413.741851-1-imre.deak@intel.com (cherry picked from commit 270681fbffbba2b6ccf5b7e3c34b8b563b36167f) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_dp_mst.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index 57daed0b0b36b5..9e65b4a1c71d9b 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -852,7 +852,8 @@ static u8 get_pipes_downstream_of_mst_port(struct intel_atomic_state *state, if (&connector->mst.dp->mst.mgr != mst_mgr) continue; - if (connector->mst.port != parent_port && + if (parent_port && + connector->mst.port != parent_port && !drm_dp_mst_port_downstream_of_parent(mst_mgr, connector->mst.port, parent_port)) From a443e0b8d647c1401b110d9f919d8c6cb8607260 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Mon, 7 Sep 2026 20:44:13 +0300 Subject: [PATCH 0988/1417] drm/i915/dp_mst: Fix configuring TUs for a disconnected stream During an atomic commit after all the MST stream CRTC state is computed the driver ensures that the sum of TUs of all the streams on a given MST topology link is within limits (63 for 8b10 and 64 for 128b132b). For a disconnected stream the DRM MST core's BW verification doesn't ensure this, because the topology state it uses for this is destroyed as soon as the stream (i.e. MST connector/port) is disconnected. The driver should keep the link state valid even for such disconnected streams, as userspace may disable them one-by-one only in a deferred way. Ensure the link's sum of TUs stays within limits in this case by simply reusing the maximum link BPP limit from the stream's (i.e. CRTC's) old state. The disconnection can happen either via the whole topology getting disconnected or via only the given stream's port getting disconnected. Check for both of these conditions separately, as a connector gets unregistered after a link disconnect event only in a deferred way. Cc: stable@vger.kernel.org # v6.10+ Link: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16073 Link: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16384 Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260907174413.741851-2-imre.deak@intel.com (cherry picked from commit ee00f8fbb2b202002ab90834e02e9ba372773a36) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_dp_mst.c | 21 ++++++++++++++++++++ drivers/gpu/drm/i915/display/intel_dp_mst.h | 2 ++ drivers/gpu/drm/i915/display/intel_link_bw.c | 3 ++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index 9e65b4a1c71d9b..fb3942f56b1fa7 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -2168,6 +2168,27 @@ bool intel_dp_mst_crtc_needs_modeset(struct intel_atomic_state *state, return false; } +bool intel_dp_mst_stream_disconnected(struct intel_atomic_state *state, + const struct intel_crtc *crtc) +{ + struct intel_connector *connector; + + connector = get_connector_in_state_for_crtc(state, crtc); + if (!connector) + return false; + + if (!connector->mst.dp) + return false; + + if (!connector->mst.dp->mst.mgr.mst_state) + return true; + + if (drm_connector_is_unregistered(&connector->base)) + return true; + + return false; +} + /** * intel_dp_mst_prepare_probe - Prepare an MST link for topology probing * @intel_dp: DP port object diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.h b/drivers/gpu/drm/i915/display/intel_dp_mst.h index ab09b487c6bb5e..8ce89242c05c95 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.h +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.h @@ -28,6 +28,8 @@ int intel_dp_mst_atomic_check_link(struct intel_atomic_state *state, struct intel_link_bw_limits *limits); bool intel_dp_mst_crtc_needs_modeset(struct intel_atomic_state *state, struct intel_crtc *crtc); +bool intel_dp_mst_stream_disconnected(struct intel_atomic_state *state, + const struct intel_crtc *crtc); void intel_dp_mst_prepare_probe(struct intel_dp *intel_dp); bool intel_dp_mst_verify_dpcd_state(struct intel_dp *intel_dp); diff --git a/drivers/gpu/drm/i915/display/intel_link_bw.c b/drivers/gpu/drm/i915/display/intel_link_bw.c index b47474a3e9fec5..e71e76d6fd3e06 100644 --- a/drivers/gpu/drm/i915/display/intel_link_bw.c +++ b/drivers/gpu/drm/i915/display/intel_link_bw.c @@ -64,7 +64,8 @@ void intel_link_bw_init_limits(struct intel_atomic_state *state, intel_atomic_get_new_crtc_state(state, crtc); int forced_bpp_x16 = get_forced_link_bpp_x16(state, crtc); - if (state->base.duplicated && crtc_state) { + if ((state->base.duplicated && crtc_state) || + intel_dp_mst_stream_disconnected(state, crtc)) { limits->max_bpp_x16[pipe] = crtc_state->max_link_bpp_x16; if (intel_dsc_enabled_on_link(crtc_state)) limits->link_dsc_pipes |= BIT(pipe); From bb2635be7646a6a9e40a27becb936fe3cdcccf8d Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Tue, 15 Sep 2026 19:06:20 +0300 Subject: [PATCH 0989/1417] drm/i915/dp: use EXPORT_SYMBOL_IF_KUNIT() for kunit helpers Use EXPORT_SYMBOL_IF_KUNIT() instead of the regular EXPORT_SYMBOL() to export the symbols to the kunit namespace. Otherwise, the symbols get exported for all the kernel to see, and the corresponding MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING") in the tests is meaningless. Fixes: 2eb9982ff179 ("drm/i915/kunit: Export link training and caps funcs for testing") Cc: Imre Deak Reviewed-by: Imre Deak Link: https://patch.msgid.link/20260915160620.779372-1-jani.nikula@intel.com Signed-off-by: Jani Nikula (cherry picked from commit 4ffdb772716e4279d63dfaaadf965da73e799aeb) --- drivers/gpu/drm/i915/display/intel_dp_link_caps.c | 6 ++++-- drivers/gpu/drm/i915/display/intel_dp_link_training.c | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 98657aa4d3d580..abec3e2519ca67 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -3,6 +3,8 @@ * Copyright © 2026 Intel Corporation */ +#include + #include #include #include @@ -1302,14 +1304,14 @@ void intel_dp_link_caps_cleanup(struct intel_dp_link_caps *link_caps) const struct intel_dp_link_caps_test_ops i915_display_dp_link_caps_test_ops = { INTEL_DP_LINK_CAPS_TEST_OPS_INIT }; -EXPORT_SYMBOL(i915_display_dp_link_caps_test_ops); +EXPORT_SYMBOL_IF_KUNIT(i915_display_dp_link_caps_test_ops); #else const struct intel_dp_link_caps_test_ops intel_display_dp_link_caps_test_ops = { INTEL_DP_LINK_CAPS_TEST_OPS_INIT }; -EXPORT_SYMBOL(intel_display_dp_link_caps_test_ops); +EXPORT_SYMBOL_IF_KUNIT(intel_display_dp_link_caps_test_ops); #endif /* I915 */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c index cb92cff906146c..9a692f4fdfee68 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c @@ -2825,14 +2825,14 @@ void intel_dp_link_training_cleanup(struct intel_dp_link_training *link_training const struct intel_dp_link_training_test_ops i915_display_dp_link_training_test_ops = { INTEL_DP_LINK_TRAINING_TEST_OPS_INIT }; -EXPORT_SYMBOL(i915_display_dp_link_training_test_ops); +EXPORT_SYMBOL_IF_KUNIT(i915_display_dp_link_training_test_ops); #else const struct intel_dp_link_training_test_ops intel_display_dp_link_training_test_ops = { INTEL_DP_LINK_TRAINING_TEST_OPS_INIT }; -EXPORT_SYMBOL(intel_display_dp_link_training_test_ops); +EXPORT_SYMBOL_IF_KUNIT(intel_display_dp_link_training_test_ops); #endif /* I915 */ From d2da6696e0c4e60414706e607029d0bb0330c67e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Thu, 3 Sep 2026 13:36:21 +0200 Subject: [PATCH 0990/1417] drm/i915: fix incorrect RCU teardown order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i915_gem_busy_ioctl uses dma_resv_for_each_fence_unlocked() to iterate over the fences in an GEM object without holding a reference but only the RCU read side lock. What can happen here is that the GEM object is destroyed concurrently while i915_gem_busy_ioctl is still running. This won't free the GEM objects memory, but still drops all the dma_fence references. Now when dma_resv_for_each_fence_unlocked() sees a destroyed dma_fence it assumes that a new fence list was installed and re-starts the loop. But in the case of a destroyed GEM object a new fence list is never installed, only the old one freed and therefore the iteration never finishes resulting in an endless loop. The solution is to drop the fence references only after the RCU grace period. The fixes tag is not necessary the patch introducing the problem, but the one making it so worse that we need to address it. This problem was pointed out by Sashiko-bot. Signed-off-by: Christian König Fixes: 912ff2ebd695 ("drm/i915: use the new iterator in i915_gem_busy_ioctl v2") CC: stable@vger.kernel.org Reviewed-by: Tvrtko Ursulin Signed-off-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20260903113621.54660-1-christian.koenig@amd.com (cherry picked from commit 5113479556025093bf8133bb2dcaa33be2d50921) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/gem/i915_gem_object.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object.c b/drivers/gpu/drm/i915/gem/i915_gem_object.c index 5172d398265480..9e01f8b2079aba 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_object.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_object.c @@ -89,6 +89,7 @@ struct drm_i915_gem_object *i915_gem_object_alloc(void) void i915_gem_object_free(struct drm_i915_gem_object *obj) { + dma_resv_fini(&obj->base._resv); return kmem_cache_free(slab_objects, obj); } @@ -144,7 +145,6 @@ void __i915_gem_object_fini(struct drm_i915_gem_object *obj) { mutex_destroy(&obj->mm.get_page.lock); mutex_destroy(&obj->mm.get_dma_page.lock); - dma_resv_fini(&obj->base._resv); } /** From 335b0642812ee0f2b7798b634ce507b8b7c9fe0c Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:26 +0800 Subject: [PATCH 0991/1417] perf/x86/intel: Update arw_latency_data() mem-op direction handling Align arw_latency_data() with other *_latency_data() helpers by explicitly decoding LOAD/STORE event flags when setting the sampled memory operation direction. This keeps the latency data path behavior consistent across platforms and avoids relying on implicit direction inference. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260917015234.981153-5-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index ea5b331c58d19e..c0004b08b08784 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -538,7 +538,11 @@ static u64 arw_latency_data(struct perf_event *event, u64 status) val |= P(BLK, NA); src.val = val; - if (event->hw.flags & PERF_X86_EVENT_PEBS_ST_HSW) + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_LDLAT | PERF_X86_EVENT_PEBS_LD_HSW)) + src.mem_op = P(OP, LOAD); + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_STLAT | PERF_X86_EVENT_PEBS_ST_HSW)) src.mem_op = P(OP, STORE); return src.val; From 7c944595cc43a664019edc22505b6d6f6039be5e Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:27 +0800 Subject: [PATCH 0992/1417] perf/x86/intel: Remove incorrect LionCove PEBS data-source constraints On Lion Cove, PEBS data source is valid only for these events: - MEM_TRANS_RETIRED.LOAD_LATENCY (0x1cd) - MEM_TRANS_RETIRED.STORE_SAMPLE (0x2cd) The perfmon database (https://github.com/intel/perfmon) previously tagged additional memory events such as MEM_INST_RETIRED.STLB_MISS_LOADS with L1_Hit_Indication, implying PEBS data-source support, which is incorrect. The database has since been fixed, but intel_lnc_pebs_event_constraints[] still follows the old definition and marks those events as data-source capable. As a result, get_data_src() may decode data-source information for events that do not provide valid PEBS data-source data and mislead users. Remove those non-data-source memory events from the Lion Cove PEBS constraint table so matching falls back to the regular non-PEBS constraints, which already provide the same counter constraints. Also update lnc_latency_data() to decode LOAD/STORE flags explicitly when setting memory operation direction, for consistency with other *_latency_data() helpers. Fixes: a932aa0e868f ("perf/x86: Add Lunar Lake and Arrow Lake support") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: Link: https://patch.msgid.link/20260917015234.981153-6-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index c0004b08b08784..cbcc0b36536bda 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -577,7 +577,11 @@ static u64 lnc_latency_data(struct perf_event *event, u64 status) val |= P(BLK, NA); src.val = val; - if (event->hw.flags & PERF_X86_EVENT_PEBS_ST_HSW) + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_LDLAT | PERF_X86_EVENT_PEBS_LD_HSW)) + src.mem_op = P(OP, LOAD); + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_STLAT | PERF_X86_EVENT_PEBS_ST_HSW)) src.mem_op = P(OP, STORE); return src.val; @@ -1510,24 +1514,8 @@ struct event_constraint intel_lnc_pebs_event_constraints[] = { INTEL_FLAGS_UEVENT_CONSTRAINT(0x012a, 0x1), /* OCR.* events */ INTEL_FLAGS_UEVENT_CONSTRAINT(0x012b, 0x1), /* OCR.* events */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x04a4, 0x1), /* TOPDOWN.BAD_SPEC_SLOTS */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x08a4, 0x1), /* TOPDOWN.BR_MISPREDICT_SLOTS */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x10a4, 0x8), /* TOPDOWN.MEMORY_BOUND_SLOTS */ - INTEL_HYBRID_LDLAT_CONSTRAINT(0x1cd, 0x3fc), INTEL_HYBRID_STLAT_CONSTRAINT(0x2cd, 0x3), - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x11d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x12d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x21d0, 0xf), /* MEM_INST_RETIRED.LOCK_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x41d0, 0xf), /* MEM_INST_RETIRED.SPLIT_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x42d0, 0xf), /* MEM_INST_RETIRED.SPLIT_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x81d0, 0xf), /* MEM_INST_RETIRED.ALL_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x82d0, 0xf), /* MEM_INST_RETIRED.ALL_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x87d0, 0x3ff), /* MEM_INST_RETIRED.ANY */ - - INTEL_FLAGS_EVENT_CONSTRAINT_DATALA_LD_RANGE(0xd1, 0xd4, 0xf), - - INTEL_FLAGS_EVENT_CONSTRAINT(0xd0, 0xf), /* * Everything else is handled by PMU_FL_PEBS_ALL, because we From 9f93d33ad65af9853974c1ec4ba1f937e8ba1376 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:28 +0800 Subject: [PATCH 0993/1417] perf/x86/intel: Remove incorrect Panther Cove PEBS data-source constraints Same issue exists on Panther Cove, PEBS data source is valid only for these events: - MEM_TRANS_RETIRED.LOAD_LATENCY (0x1cd) - MEM_TRANS_RETIRED.STORE_SAMPLE (0x2cd) The perfmon database (https://github.com/intel/perfmon) previously tagged additional memory events such as MEM_INST_RETIRED.STLB_MISS_LOADS with L1_Hit_Indication, implying PEBS data-source support, which is incorrect. The database has since been fixed, but intel_pnc_pebs_event_constraints[] still follows the old definition and marks those events as data-source capable. As a result, get_data_src() may decode data-source information for events that do not provide valid PEBS data-source data and mislead users. Remove those non-data-source memory events from the Pather Cove PEBS constraint table so matching falls back to the regular non-PEBS constraints, which already provide the same counter constraints. Also update pnc_latency_data() to decode LOAD/STORE flags explicitly when setting memory operation direction, for consistency with other *_latency_data() helpers. Fixes: d345b6bb8860 ("perf/x86/intel: Add core PMU support for DMR") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # v7.0+ Link: https://patch.msgid.link/20260917015234.981153-7-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index cbcc0b36536bda..72179bcf6d0d2b 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -639,7 +639,11 @@ u64 pnc_latency_data(struct perf_event *event, u64 status) val |= P(BLK, NA); src.val = val; - if (event->hw.flags & PERF_X86_EVENT_PEBS_ST_HSW) + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_LDLAT | PERF_X86_EVENT_PEBS_LD_HSW)) + src.mem_op = P(OP, LOAD); + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_STLAT | PERF_X86_EVENT_PEBS_ST_HSW)) src.mem_op = P(OP, STORE); return src.val; @@ -1528,18 +1532,6 @@ struct event_constraint intel_lnc_pebs_event_constraints[] = { struct event_constraint intel_pnc_pebs_event_constraints[] = { INTEL_HYBRID_LDLAT_CONSTRAINT(0x1cd, 0xfc), INTEL_HYBRID_STLAT_CONSTRAINT(0x2cd, 0x3), - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x11d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x12d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x21d0, 0xf), /* MEM_INST_RETIRED.LOCK_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x41d0, 0xf), /* MEM_INST_RETIRED.SPLIT_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x42d0, 0xf), /* MEM_INST_RETIRED.SPLIT_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x81d0, 0xf), /* MEM_INST_RETIRED.ALL_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x82d0, 0xf), /* MEM_INST_RETIRED.ALL_STORES */ - - INTEL_FLAGS_EVENT_CONSTRAINT_DATALA_LD_RANGE(0xd1, 0xd4, 0xf), - - INTEL_FLAGS_EVENT_CONSTRAINT(0xd0, 0xf), - INTEL_FLAGS_EVENT_CONSTRAINT(0xd6, 0xf), /* * Everything else is handled by PMU_FL_PEBS_ALL, because we From 0ac5d6ca2c3b7d047963b67dccef17902dc6c017 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:29 +0800 Subject: [PATCH 0994/1417] perf/x86/intel: Fix Panther Cove PEBS data-source snoop states For Panther Cove, the snoop states for the data source encodings "Prefetch Promotion" and "Cross Core Prefetch Promotion" should be SNOOP_NONE instead of SNOOP_MISS. Fix the incorrect snooping states for Panther Cove. Fixes: d2bdcde9626c ("perf/x86/intel: Add support for PEBS memory auxiliary info field in DMR") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # v7.0+ Link: https://patch.msgid.link/20260917015234.981153-8-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 72179bcf6d0d2b..d0329a7eb8a56b 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -277,8 +277,8 @@ static u64 pnc_pebs_l2_hit_data_source[PNC_PEBS_DATA_SOURCE_MAX] = { 0, /* 0x06: Reserved */ OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, HIT), /* 0x07: L2 Hit Snoop HIT */ OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, HITM), /* 0x08: L2 Hit Snoop Hit Modified */ - OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, MISS), /* 0x09: Prefetch Promotion */ - OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, MISS), /* 0x0a: Cross Core Prefetch Promotion */ + OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, NONE), /* 0x09: Prefetch Promotion */ + OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, NONE), /* 0x0a: Cross Core Prefetch Promotion */ 0, /* 0x0b: Reserved */ 0, /* 0x0c: Reserved */ 0, /* 0x0d: Reserved */ From 858b37ca19d3f695f7fa94cd14be5a856fd8e7d7 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:30 +0800 Subject: [PATCH 0995/1417] perf/x86/intel: Delete dead NVL PEBS data-source initcall Nova Lake now uses the OMR data-source table for PEBS data-source decoding and no longer depends on the legacy static pebs_data_source[] mapping. Remove the dead intel_pmu_pebs_data_source_lnl() initialization call for NVL. Fixes: c847a208f43b ("perf/x86/intel: Add core PMU support for Novalake") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260917015234.981153-9-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 757d4082de601f..a7dbbed8f94e7e 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -8824,8 +8824,6 @@ __init int intel_pmu_init(void) /* Initialize Atom core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_ATOM_IDX]; intel_pmu_init_arw(&pmu->pmu); - - intel_pmu_pebs_data_source_lnl(); break; default: From 04a7ef3b7aa3af34202285043e3423a2e3983595 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:31 +0800 Subject: [PATCH 0996/1417] perf/x86/intel: Constrain Panther Cove UOPS_DISPATCHED events to PMCs 0-3 Per the latest Panther Cove event definitions, the following events are only supported on PMCs 0-3: - UOPS_DISPATCHED.INT_EU_ALL (0x1b2) - UOPS_DISPATCHED.ALU (0x2b2) Add explicit event constraints for these two events so scheduling does not place them on unsupported counters. Fixes: d345b6bb8860 ("perf/x86/intel: Add core PMU support for DMR") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # v7.0+ Link: https://patch.msgid.link/20260917015234.981153-10-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index a7dbbed8f94e7e..ecf6d6325dfe88 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -506,6 +506,8 @@ static struct event_constraint intel_pnc_event_constraints[] = { INTEL_EVENT_CONSTRAINT(0xce, 0x1), INTEL_UEVENT_CONSTRAINT(0x01b1, 0x8), + INTEL_UEVENT_CONSTRAINT(0x01b2, 0xf), + INTEL_UEVENT_CONSTRAINT(0x02b2, 0xf), INTEL_UEVENT_CONSTRAINT(0x0847, 0xf), INTEL_UEVENT_CONSTRAINT(0x0446, 0xf), INTEL_UEVENT_CONSTRAINT(0x0846, 0xf), From ff1621adfdd7cfb73f2ccdd3856cd3462ac98ac5 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:32 +0800 Subject: [PATCH 0997/1417] perf/x86/intel: Fix precise OMR event scheduling for DMR/NVL The latest perfmon event database introduces below precise OMR event support for DMR/NVL: - MEM_LOAD_L2_MISS_RETIRED.* (event 0xd6) - MEM_STORE_L2_MISS_RETIRED.* (event 0x4f) These events use the same OMR MSRs as the existing OMR events, but they are not listed in intel_pnc_extra_regs[]. As a result, perf cannot assign the required OMR extra registers when scheduling them. Add the new precise OMR events to intel_pnc_extra_regs[] so they can be scheduled with the correct OMR MSRs. MEM_LOAD_L2_MISS_RETIRED.* remains limited to GP counters 0-3, while MEM_STORE_L2_MISS_RETIRED.* is available on all GP counters. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260917015234.981153-11-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index ecf6d6325dfe88..22715602f7f8cf 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -522,6 +522,14 @@ static struct extra_reg intel_pnc_extra_regs[] __read_mostly = { INTEL_UEVENT_EXTRA_REG(0x022a, MSR_OMR_1, 0x40ffffff0000ffffull, OMR_1), INTEL_UEVENT_EXTRA_REG(0x042a, MSR_OMR_2, 0x40ffffff0000ffffull, OMR_2), INTEL_UEVENT_EXTRA_REG(0x082a, MSR_OMR_3, 0x40ffffff0000ffffull, OMR_3), + INTEL_UEVENT_EXTRA_REG(0x014f, MSR_OMR_0, 0x40ffffff0000ffffull, OMR_0), + INTEL_UEVENT_EXTRA_REG(0x024f, MSR_OMR_1, 0x40ffffff0000ffffull, OMR_1), + INTEL_UEVENT_EXTRA_REG(0x044f, MSR_OMR_2, 0x40ffffff0000ffffull, OMR_2), + INTEL_UEVENT_EXTRA_REG(0x084f, MSR_OMR_3, 0x40ffffff0000ffffull, OMR_3), + INTEL_UEVENT_EXTRA_REG(0x01d6, MSR_OMR_0, 0x40ffffff0000ffffull, OMR_0), + INTEL_UEVENT_EXTRA_REG(0x02d6, MSR_OMR_1, 0x40ffffff0000ffffull, OMR_1), + INTEL_UEVENT_EXTRA_REG(0x04d6, MSR_OMR_2, 0x40ffffff0000ffffull, OMR_2), + INTEL_UEVENT_EXTRA_REG(0x08d6, MSR_OMR_3, 0x40ffffff0000ffffull, OMR_3), INTEL_UEVENT_PEBS_LDLAT_EXTRA_REG(0x01cd), INTEL_UEVENT_EXTRA_REG(0x02c6, MSR_PEBS_FRONTEND, 0x9, FE), INTEL_UEVENT_EXTRA_REG(0x03c6, MSR_PEBS_FRONTEND, 0x7fff1f, FE), From 0102c8c7fdfc4bd700971daff2b94470f4eafae4 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:33 +0800 Subject: [PATCH 0998/1417] perf/x86/intel: Rename DMR offcore_rsp attribute to offmodule_rsp DMR introduces Offmodule Response events in place of the legacy Offcore Response events, but it still exposes the inherited offcore_rsp PMU attribute for programming the corresponding MSR data. Rename the DMR PMU attribute to offmodule_rsp so the sysfs interface matches the underlying event name and avoids user & tooling confusion. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260917015234.981153-12-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 22715602f7f8cf..3843dbe530a503 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -6601,6 +6601,8 @@ static void intel_pmu_filter(struct pmu *pmu, int cpu, bool *ret) PMU_FORMAT_ATTR(offcore_rsp, "config1:0-63"); +PMU_FORMAT_ATTR(offmodule_rsp, "config1:0-63"); + PMU_FORMAT_ATTR(ldlat, "config1:0-15"); PMU_FORMAT_ATTR(frontend, "config1:0-23"); @@ -6649,6 +6651,20 @@ static struct attribute *skl_format_attr[] = { NULL, }; +static struct attribute *pnc_format_attr_rtm[] = { + &format_attr_in_tx.attr, + &format_attr_in_tx_cp.attr, + &format_attr_offmodule_rsp.attr, + &format_attr_ldlat.attr, + NULL +}; + +static struct attribute *pnc_format_attr[] = { + &format_attr_offmodule_rsp.attr, + &format_attr_ldlat.attr, + NULL +}; + static __initconst const struct x86_pmu core_pmu = { .name = "core", .handle_irq = x86_pmu_handle_irq, @@ -8589,6 +8605,8 @@ __init int intel_pmu_init(void) case INTEL_DIAMONDRAPIDS_X: intel_pmu_init_pnc(NULL); x86_pmu.pebs_latency_data = pnc_latency_data; + extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? + pnc_format_attr_rtm : pnc_format_attr; pr_cont("Panthercove events, "); name = "panthercove"; @@ -8597,13 +8615,12 @@ __init int intel_pmu_init(void) glc_common: intel_pmu_init_glc(NULL); intel_pmu_pebs_data_source_skl(true); - + extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? + hsw_format_attr : nhm_format_attr; glc_base: x86_pmu.pebs_ept = 1; x86_pmu.hw_config = hsw_hw_config; x86_pmu.get_event_constraints = glc_get_event_constraints; - extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? - hsw_format_attr : nhm_format_attr; extra_skl_attr = skl_format_attr; mem_attr = glc_events_attrs; td_attr = glc_td_events_attrs; From d4d9ccbad527af115b8e83a7a2b74785c0e8dfea Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:34 +0800 Subject: [PATCH 0999/1417] perf/x86/intel: Rename NVL offcore_rsp attribute to offmodule_rsp NVL introduces Offmodule Response events in place of the legacy Offcore Response events, but it still exposes the inherited offcore_rsp PMU attribute for programming the corresponding MSR data. Rename the NVL PMU attribute to offmodule_rsp so the sysfs interface matches the underlying event name and avoids user & tooling confusion. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260917015234.981153-13-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 3843dbe530a503..3ef80882843ebc 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -7530,6 +7530,7 @@ static struct attribute *adl_hybrid_tsx_attrs[] = { FORMAT_ATTR_HYBRID(in_tx, hybrid_big); FORMAT_ATTR_HYBRID(in_tx_cp, hybrid_big); FORMAT_ATTR_HYBRID(offcore_rsp, hybrid_big_small_tiny); +FORMAT_ATTR_HYBRID(offmodule_rsp, hybrid_big_small_tiny); FORMAT_ATTR_HYBRID(ldlat, hybrid_big_small_tiny); FORMAT_ATTR_HYBRID(frontend, hybrid_big); @@ -7568,6 +7569,23 @@ static struct attribute *mtl_hybrid_extra_attr[] = { NULL }; +static struct attribute *nvl_hybrid_extra_attr_rtm[] = { + ADL_HYBRID_RTM_FORMAT_ATTR, + FORMAT_HYBRID_PTR(offmodule_rsp), + FORMAT_HYBRID_PTR(ldlat), + FORMAT_HYBRID_PTR(frontend), + FORMAT_HYBRID_PTR(snoop_rsp), + NULL +}; + +static struct attribute *nvl_hybrid_extra_attr[] = { + FORMAT_HYBRID_PTR(offmodule_rsp), + FORMAT_HYBRID_PTR(ldlat), + FORMAT_HYBRID_PTR(frontend), + FORMAT_HYBRID_PTR(snoop_rsp), + NULL +}; + static bool is_attr_for_this_pmu(struct kobject *kobj, struct attribute *attr) { struct device *dev = kobj_to_dev(kobj); @@ -8842,7 +8860,7 @@ __init int intel_pmu_init(void) mem_attr = mtl_hybrid_mem_attrs; tsx_attr = adl_hybrid_tsx_attrs; extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? - mtl_hybrid_extra_attr_rtm : mtl_hybrid_extra_attr; + nvl_hybrid_extra_attr_rtm : nvl_hybrid_extra_attr; /* Initialize big core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; From 09b7040a1b79f4f61cdad6d9af972e045bb7c498 Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Wed, 16 Sep 2026 17:14:10 +0200 Subject: [PATCH 1000/1417] s390/pci: Fix leak of struct pci_dev reference in zpci_report_status() In zpci_report_status(), a reference to the pdev associated with the zdev being reported about is acquired using pci_get_slot(). This reference needs to be dropped with pci_dev_put(), but this call is missing, thus leaking the reference. On subsequent hot unplug, this will cause the struct pci_dev to not be released, leaking memory and preventing reattach. At the same time, the only existing caller already holds a pdev reference. So instead of reacquiring and then dropping another reference, simply pass the existing pdev pointer to zpci_report_status(). This gets rid of the need for pci_get_slot() as well as the zdev->zbus check. Cc: stable@vger.kernel.org Fixes: 4ec6054e7321 ("s390/pci: Report PCI error recovery results via SCLP") Signed-off-by: Niklas Schnelle Reviewed-by: Benjamin Block Reviewed-by: Farhan Ali Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_event.c | 2 +- arch/s390/pci/pci_report.c | 11 +++++------ arch/s390/pci/pci_report.h | 4 +++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c index f317a1465dad8c..3b4941b65840e5 100644 --- a/arch/s390/pci/pci_event.c +++ b/arch/s390/pci/pci_event.c @@ -298,7 +298,7 @@ static pci_ers_result_t zpci_event_attempt_error_recovery(struct pci_dev *pdev, pci_uevent_ers(pdev, PCI_ERS_RESULT_RECOVERED); out_unlock: device_unlock(&pdev->dev); - zpci_report_status(zdev, "recovery", status_str); + zpci_report_status(zdev, pdev, "recovery", status_str); return ers_res; } diff --git a/arch/s390/pci/pci_report.c b/arch/s390/pci/pci_report.c index 7030f7052926ae..86741977921978 100644 --- a/arch/s390/pci/pci_report.c +++ b/arch/s390/pci/pci_report.c @@ -89,7 +89,8 @@ static struct debug_view debug_log_view = { /** * zpci_report_status - Report the status of operations on a PCI device - * @zdev: The PCI device for which to report status + * @zdev: The zPCI device for which to report status + * @pdev: The PCI device associated with the zdev if any, NULL otherwise * @operation: A string representing the operation reported * @status: A string representing the status of the operation * @@ -103,15 +104,15 @@ static struct debug_view debug_log_view = { * * Return: 0 on success an error code < 0 otherwise. */ -int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char *status) +int zpci_report_status(struct zpci_dev *zdev, struct pci_dev *pdev, + const char *operation, const char *status) { struct zpci_report_error *report; struct pci_driver *driver = NULL; - struct pci_dev *pdev = NULL; char *buf, *end; int ret; - if (!zdev || !zdev->zbus) + if (!zdev) return -ENODEV; /* Protected virtualization hosts get nothing from us */ @@ -121,8 +122,6 @@ int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char report = (void *)get_zeroed_page(GFP_KERNEL); if (!report) return -ENOMEM; - if (zdev->zbus->bus) - pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn); if (pdev) driver = to_pci_driver(pdev->dev.driver); diff --git a/arch/s390/pci/pci_report.h b/arch/s390/pci/pci_report.h index e08003d51a9727..dd7b0b05001c19 100644 --- a/arch/s390/pci/pci_report.h +++ b/arch/s390/pci/pci_report.h @@ -8,9 +8,11 @@ */ #ifndef __S390_PCI_REPORT_H #define __S390_PCI_REPORT_H +#include struct zpci_dev; -int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char *status); +int zpci_report_status(struct zpci_dev *zdev, struct pci_dev *pdev, + const char *operation, const char *status); #endif /* __S390_PCI_REPORT_H */ From 0261aef4b15efcee2860ab857e5cb05e9bfa47b0 Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Wed, 16 Sep 2026 17:14:11 +0200 Subject: [PATCH 1001/1417] s390/pci: Fix missing device lock in zpci_report_status() When pdev is non-NULL, zpci_report_status() accesses the device's driver. To get a consistent state matching the recovery, the device lock needs to be held. Do so by expanding the existing device lock critical section. The lock only needs to be held when the pdev is non-NULL, so extract the pdev-specific reporting into a helper function which also adds a lockdep assertion to detect calls without the device lock held. Cc: stable@vger.kernel.org Fixes: 4ec6054e7321 ("s390/pci: Report PCI error recovery results via SCLP") Signed-off-by: Niklas Schnelle Reviewed-by: Benjamin Block Reviewed-by: Farhan Ali Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_event.c | 2 +- arch/s390/pci/pci_report.c | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c index 3b4941b65840e5..ec93f34b6e1978 100644 --- a/arch/s390/pci/pci_event.c +++ b/arch/s390/pci/pci_event.c @@ -297,8 +297,8 @@ static pci_ers_result_t zpci_event_attempt_error_recovery(struct pci_dev *pdev, driver->err_handler->resume(pdev); pci_uevent_ers(pdev, PCI_ERS_RESULT_RECOVERED); out_unlock: - device_unlock(&pdev->dev); zpci_report_status(zdev, pdev, "recovery", status_str); + device_unlock(&pdev->dev); return ers_res; } diff --git a/arch/s390/pci/pci_report.c b/arch/s390/pci/pci_report.c index 86741977921978..72ecabf7003c4c 100644 --- a/arch/s390/pci/pci_report.c +++ b/arch/s390/pci/pci_report.c @@ -87,6 +87,19 @@ static struct debug_view debug_log_view = { NULL }; +static ssize_t zpci_report_pdev(struct pci_dev *pdev, char *buf, size_t size) +{ + struct pci_driver *driver; + const char *start = buf; + char *end = buf + size; + + device_lock_assert(&pdev->dev); + buf += scnprintf(buf, end - buf, "state: %s\n", zpci_state_str(pdev->error_state)); + driver = to_pci_driver(pdev->dev.driver); + buf += scnprintf(buf, end - buf, "driver: %s\n", (driver) ? driver->name : "n/a"); + return buf - start; +} + /** * zpci_report_status - Report the status of operations on a PCI device * @zdev: The zPCI device for which to report status @@ -108,7 +121,6 @@ int zpci_report_status(struct zpci_dev *zdev, struct pci_dev *pdev, const char *operation, const char *status) { struct zpci_report_error *report; - struct pci_driver *driver = NULL; char *buf, *end; int ret; @@ -122,16 +134,13 @@ int zpci_report_status(struct zpci_dev *zdev, struct pci_dev *pdev, report = (void *)get_zeroed_page(GFP_KERNEL); if (!report) return -ENOMEM; - if (pdev) - driver = to_pci_driver(pdev->dev.driver); buf = report->data.log_data; end = report->data.log_data + ZPCI_REPORT_DATA_SIZE; buf += scnprintf(buf, end - buf, "report: %s\n", operation); buf += scnprintf(buf, end - buf, "status: %s\n", status); - buf += scnprintf(buf, end - buf, "state: %s\n", - (pdev) ? zpci_state_str(pdev->error_state) : "n/a"); - buf += scnprintf(buf, end - buf, "driver: %s\n", (driver) ? driver->name : "n/a"); + if (pdev) + buf += zpci_report_pdev(pdev, buf, end - buf); ret = debug_dump(pci_debug_msg_id, &debug_log_view, buf, end - buf, true); if (ret < 0) pr_err("Reading PCI debug messages failed with code %d\n", ret); From a1120bea9bc8ea9d9ab2f9904a63b9a228bf2ffc Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Wed, 16 Sep 2026 17:14:12 +0200 Subject: [PATCH 1002/1417] s390/pci: Report SCLP status on error events when no pdev is associated With commit 4ec6054e7321 ("s390/pci: Report PCI error recovery results via SCLP") SCLP reports are generated when recovery is performed in response to an error event. If such an error event arrives but no pdev is currently associated with the zdev, e.g. because it was removed or not yet probed, no report is generated. Fix this by generating a report specific to an error event for a zdev without an associated pdev. Cc: stable@vger.kernel.org Fixes: 4ec6054e7321 ("s390/pci: Report PCI error recovery results via SCLP") Signed-off-by: Niklas Schnelle Reviewed-by: Benjamin Block Reviewed-by: Farhan Ali Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_event.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c index ec93f34b6e1978..7b538c6ae11b46 100644 --- a/arch/s390/pci/pci_event.c +++ b/arch/s390/pci/pci_event.c @@ -361,8 +361,10 @@ static void __zpci_event_error(struct zpci_ccdf_err *ccdf) __zpci_event_print_error(pdev, ccdf); - if (!pdev) + if (!pdev) { + zpci_report_status(zdev, NULL, "error event", "no pdev bound"); goto no_pdev; + } switch (ccdf->pec) { case 0x002a: /* Error event concerns FMB */ From 3a43be7a1fd06a35cf9e621b88283b3b6e7d281c Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Wed, 16 Sep 2026 17:14:13 +0200 Subject: [PATCH 1003/1417] s390/pci: Don't report recovery success on skipped recovery When a PCI device is already in the permanent failure state, recovery is skipped, but the SCLP recovery report still shows success. Fix this by changing the status string to explicitly state that recovery was skipped due to permanent failure. Cc: stable@vger.kernel.org Fixes: 4ec6054e7321 ("s390/pci: Report PCI error recovery results via SCLP") Signed-off-by: Niklas Schnelle Reviewed-by: Benjamin Block Reviewed-by: Farhan Ali Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_event.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c index 7b538c6ae11b46..d6af4015223e79 100644 --- a/arch/s390/pci/pci_event.c +++ b/arch/s390/pci/pci_event.c @@ -226,6 +226,7 @@ static pci_ers_result_t zpci_event_attempt_error_recovery(struct pci_dev *pdev, device_lock(&pdev->dev); if (pdev->error_state == pci_channel_io_perm_failure) { ers_res = PCI_ERS_RESULT_DISCONNECT; + status_str = "skipped (permanent failure)"; goto out_unlock; } pdev->error_state = pci_channel_io_frozen; From f4d04425e66af2ecee9d1a49ae0484436f3c2fd1 Mon Sep 17 00:00:00 2001 From: Peter Oberparleiter Date: Mon, 21 Sep 2026 15:16:48 +0200 Subject: [PATCH 1004/1417] s390/cmf: Fix virtual vs physical address confusion The measurement block address is an absolute address. Define the associated schib_config and schib fields as dma64_t to enable automatic detection of incorrect assignments. Also add the missing virt_to_dma64() translation. Without this fix, a wrong address will be used by firmware when storing extended format channel measurement data on kernels built with CONFIG_RANDOMIZE_IDENTITY_BASE=y. Fixes: 14edd0d73bfe ("s390/cmf: fix virtual vs physical address confusion") Cc: stable@vger.kernel.org Signed-off-by: Peter Oberparleiter Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens --- drivers/s390/cio/cio.h | 5 +++-- drivers/s390/cio/cmf.c | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/s390/cio/cio.h b/drivers/s390/cio/cio.h index bad142c536e1e6..6d28a62bc67a37 100644 --- a/drivers/s390/cio/cio.h +++ b/drivers/s390/cio/cio.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -49,7 +50,7 @@ struct pmcw { /* Target SCHIB configuration. */ struct schib_config { - u64 mba; + dma64_t mba; u32 intparm; u16 mbi; u32 isc:3; @@ -66,7 +67,7 @@ struct schib_config { struct schib { struct pmcw pmcw; /* path management control word */ union scsw scsw; /* subchannel status word */ - __u64 mba; /* measurement block address */ + dma64_t mba; /* measurement block address */ __u8 mda[4]; /* model dependent area */ } __attribute__ ((packed,aligned(4))); diff --git a/drivers/s390/cio/cmf.c b/drivers/s390/cio/cmf.c index 92ab3d546fe47c..66b14fedbd1872 100644 --- a/drivers/s390/cio/cmf.c +++ b/drivers/s390/cio/cmf.c @@ -183,7 +183,7 @@ static int set_schib(struct ccw_device *cdev, u32 mme, int mbfc, sch->config.mbfc = mbfc; /* address can be either a block address or a block index */ if (mbfc) - sch->config.mba = address; + sch->config.mba = address ? virt_to_dma64((void *)address) : 0; else sch->config.mbi = address; From 4467df89dbca6a3e9dbc343a315324bb192603d6 Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Wed, 16 Sep 2026 18:53:30 +0200 Subject: [PATCH 1005/1417] s390/debug: Reject NULL debug info in debug_dump() debug_dump() passes id on to debug_info_copy(), which dereferences in->name unchecked. debug_unregister(), debug_set_size() and debug_register_view() guard against a NULL id but debug_dump() does not. Nothing reaches this today, but add the check for consistency. Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260910110147.96E851F000FF@smtp.kernel.org/ Signed-off-by: Mikhail Zaslonko Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens --- arch/s390/kernel/debug.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index b5bf8284dbfc8c..354ce78fc0cfaa 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -823,6 +823,9 @@ ssize_t debug_dump(debug_info_t *id, struct debug_view *view, file_private_info_t *p_info; size_t size, offset = 0; + if (!id) + return -EINVAL; + /* Need space for '\0' byte */ if (buf_size < 1) return 0; From 28e29992b034acffc9342df216c06097825ce610 Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Fri, 18 Sep 2026 17:13:51 +0200 Subject: [PATCH 1006/1417] s390/debug: Do not register views for failed static debug areas __REGISTER_STATIC_DEBUG_INFO() calls debug_register_view() unconditionally, even when debug_register_static() has failed. In that case _debug_register() was never reached and id->debugfs_root_entry is still NULL, so debugfs_create_file() places the view file in the debugfs root directory. For sclp_err this leaves a /sys/kernel/debug/hex_ascii file with nothing to indicate which debug log it belongs to. debug_register_static() is not exported and the macro is its only caller, so let it return an error code and skip the view registration when it fails. No debugfs files are created for such an area then. Reproduce by booting with s390dbf=sclp_err::100000000. The sclp_err registration fails, no s390dbf/sclp_err/ directory is created, and a hex_ascii file appears in the debugfs root instead. Fixes: d72541f94512 ("s390/debug: add early tracing support") Signed-off-by: Mikhail Zaslonko Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens --- arch/s390/include/asm/debug.h | 8 ++++++-- arch/s390/kernel/debug.c | 12 +++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/arch/s390/include/asm/debug.h b/arch/s390/include/asm/debug.h index 39d484c597748c..ad438d6352c8e6 100644 --- a/arch/s390/include/asm/debug.h +++ b/arch/s390/include/asm/debug.h @@ -460,7 +460,11 @@ static int VNAME(var, active_entries)[EARLY_AREAS] __initdata #define __REGISTER_STATIC_DEBUG_INFO(var, name, pages, areas, view) \ static int __init VNAME(var, reg)(void) \ { \ - debug_register_static(&var, (pages), (areas)); \ + int rc; \ + \ + rc = debug_register_static(&var, (pages), (areas)); \ + if (rc) \ + return rc; \ debug_register_view(&var, (view)); \ return 0; \ } \ @@ -493,7 +497,7 @@ static debug_info_t __refdata var = \ static debug_info_t __used __section(".s390dbf_info") *VNAME(var, info) = &var; \ __REGISTER_STATIC_DEBUG_INFO(var, name, pages, nr_areas, view) -void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas); +int debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas); #endif /* MODULE */ diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index 354ce78fc0cfaa..142dc148718761 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -953,8 +953,12 @@ EXPORT_SYMBOL(debug_register); * * Note: This function is called automatically via an initcall generated by * DEFINE_STATIC_DEBUG_INFO. + * + * Return: + * - 0 on success + * - negative error code on failure */ -void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) +int debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) { unsigned long flags; debug_info_t *copy; @@ -962,7 +966,7 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) if (!initialized) { pr_err("Tried to register debug feature %s too early\n", id->name); - return; + return -EINVAL; } debug_get_param(id->name, &id->level, &pages_per_area, false); @@ -978,7 +982,7 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) id->active_entries = NULL; raw_spin_unlock_irqrestore(&id->lock, flags); - return; + return -ENOMEM; } /* Replace static trace area with dynamic copy. */ @@ -996,6 +1000,8 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) mutex_lock(&debug_mutex); _debug_register(id); mutex_unlock(&debug_mutex); + + return 0; } /* Remove debugfs entries. */ From e5db53237fe63e2e3529660632249598308add32 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:17:44 +0200 Subject: [PATCH 1007/1417] ASoC: codecs: rt286: Revert delay value for jack setup Fat fingered the change when reorganizing the jack-detect initialization. Equivalent changes for rt274 and rt298: Commit a43b4394bb35 ("ASoC: codecs: rt274: Always init jack_detect_work") Commit 1eb73102da28 ("ASoC: codecs: rt298: Reorganize jack detect handling") carry no update to the delay value. >From user perspective, no difference has been observed between 1250 and 50 values on Intel's SkyLake, KabyLake and AmberLake configurations. Fixes: 3082afe097cc ("ASoC: codecs: rt286: Reorganize jack detect handling") Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260918111745.3589393-1-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt286.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index 51b407b78e6d16..c60e81ba87f33d 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -953,7 +953,7 @@ static int rt286_probe(struct snd_soc_component *component) if (rt286->i2c->irq) schedule_delayed_work(&rt286->jack_detect_work, - msecs_to_jiffies(50)); + msecs_to_jiffies(1250)); return 0; } From 3997b7bdedb5ecbfbb1ede75320baa21656f57c7 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:17:45 +0200 Subject: [PATCH 1008/1417] ASoC: codecs: rt274: Fix format setting for 44100 rate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per specification, Sample Base Rate (bit 14) shall be set to 1 for 44.1 kHz. Fixes: c7e79b2b2d2d ("ASoC: rt274: add rt274 codec driver") Signed-off-by: Amadeusz Sławiński Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260918111745.3589393-2-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt274.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt274.c b/sound/soc/codecs/rt274.c index 62fc071d646a7b..5c67b1d210b27e 100644 --- a/sound/soc/codecs/rt274.c +++ b/sound/soc/codecs/rt274.c @@ -619,9 +619,11 @@ static int rt274_hw_params(struct snd_pcm_substream *substream, unsigned int val = 0; int d_len_code = 0, c_len_code = 0; + /* bit 14 Sample Rate Base, 0: 48kHz 1: 44.1kHz */ switch (params_rate(params)) { - /* bit 14 0:48K 1:44.1K */ case 44100: + val |= BIT(14); + break; case 48000: break; default: From d30fba93ebfc11e4604a7fcc73fdf28684191dde Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:17:44 +0200 Subject: [PATCH 1009/1417] ASoC: codecs: rt286: Revert delay value for jack setup Fat fingered the change when reorganizing the jack-detect initialization. Equivalent changes for rt274 and rt298: Commit a43b4394bb35 ("ASoC: codecs: rt274: Always init jack_detect_work") Commit 1eb73102da28 ("ASoC: codecs: rt298: Reorganize jack detect handling") carry no update to the delay value. >From user perspective, no difference has been observed between 1250 and 50 values on Intel's SkyLake, KabyLake and AmberLake configurations. Fixes: 3082afe097cc ("ASoC: codecs: rt286: Reorganize jack detect handling") Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260918111745.3589393-1-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt286.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index 4217467904d6fb..bcd9f5abcda8ac 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -953,7 +953,7 @@ static int rt286_probe(struct snd_soc_component *component) if (rt286->i2c->irq) schedule_delayed_work(&rt286->jack_detect_work, - msecs_to_jiffies(50)); + msecs_to_jiffies(1250)); return 0; } From 6eda8938a790f65bd24fcfaae7b40ad4613fb7a2 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:17:45 +0200 Subject: [PATCH 1010/1417] ASoC: codecs: rt274: Fix format setting for 44100 rate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per specification, Sample Base Rate (bit 14) shall be set to 1 for 44.1 kHz. Fixes: c7e79b2b2d2d ("ASoC: rt274: add rt274 codec driver") Signed-off-by: Amadeusz Sławiński Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260918111745.3589393-2-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt274.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt274.c b/sound/soc/codecs/rt274.c index f8c370106504c1..3c47f1ae8db99c 100644 --- a/sound/soc/codecs/rt274.c +++ b/sound/soc/codecs/rt274.c @@ -619,9 +619,11 @@ static int rt274_hw_params(struct snd_pcm_substream *substream, unsigned int val = 0; int d_len_code = 0, c_len_code = 0; + /* bit 14 Sample Rate Base, 0: 48kHz 1: 44.1kHz */ switch (params_rate(params)) { - /* bit 14 0:48K 1:44.1K */ case 44100: + val |= BIT(14); + break; case 48000: break; default: From a644f09b2090ad22a13fbcf9d141084f573108ef Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 11:01:35 +0000 Subject: [PATCH 1011/1417] fsl/fman: Fix clk reference leak in read_dts_node() of_clk_get() returns a clock with its reference count incremented, but read_dts_node() only uses it to read the rate and never calls clk_put(). The clock is not stored anywhere, so the reference cannot be released later either. Release the clock once its rate has been read, which also covers the error path taken when the rate is zero. Fixes: 414fd46e7762 ("fsl/fman: Add FMan support") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260917110135.2148068-1-vulab@iscas.ac.cn Signed-off-by: Paolo Abeni --- drivers/net/ethernet/freescale/fman/fman.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/freescale/fman/fman.c b/drivers/net/ethernet/freescale/fman/fman.c index 299bab043175b3..46cc28895e5653 100644 --- a/drivers/net/ethernet/freescale/fman/fman.c +++ b/drivers/net/ethernet/freescale/fman/fman.c @@ -2734,6 +2734,7 @@ static struct fman *read_dts_node(struct platform_device *of_dev) } clk_rate = clk_get_rate(clk); + clk_put(clk); if (!clk_rate) { err = -EINVAL; dev_err(&of_dev->dev, "%s: Failed to determine FM%d clock rate\n", From 012bfcd5a51082d5a65f096dfb9ca652b5267965 Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Wed, 16 Sep 2026 18:06:26 +0200 Subject: [PATCH 1012/1417] s390/debug: Fix NULL pointer dereference in debug_info_copy() When debug_register_static() fails, it clears areas, active_pages and active_entries but leaves the area bounds unchanged. Copying such an area, either by opening its view file or via debug_dump(), makes debug_info_copy() dereference the NULL pointers. Skip the copy loop when the source has no areas. Closes: https://lore.kernel.org/r/20260903132123.12F271F00A3F@smtp.kernel.org Fixes: d72541f94512 ("s390/debug: add early tracing support") Signed-off-by: Mikhail Zaslonko Reviewed-by: Peter Oberparleiter Acked-by: Heiko Carstens Signed-off-by: Heiko Carstens --- arch/s390/kernel/debug.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index 142dc148718761..3b68f5c58333e0 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -434,7 +434,8 @@ static debug_info_t *debug_info_copy(debug_info_t *in, int mode) debug_info_free(rc); } while (1); - if (mode == NO_AREAS) + /* debug_register_static() failure leaves areas NULL, bounds intact */ + if (mode == NO_AREAS || !in->areas) goto out; for (i = 0; i < in->nr_areas; i++) { From 7325b35a12d7bdab94b9712c07f226c9354c65b4 Mon Sep 17 00:00:00 2001 From: Oder Chiou Date: Thu, 17 Sep 2026 18:42:44 +0800 Subject: [PATCH 1013/1417] ASoC: rt721: Add support for the RT721_U and RT718 codec variants The RT721_U and RT718 variants share most of the register layout and driver behavior with RT721. Extend the existing RT721 driver to recognize these variants and apply variant-specific settings where required. Signed-off-by: Oder Chiou Link: https://patch.msgid.link/20260917104244.2512641-1-oder_chiou@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca-sdw.c | 3 + sound/soc/codecs/rt721-sdca.c | 199 +++++++++++++++++++++++++--- sound/soc/codecs/rt721-sdca.h | 12 ++ sound/soc/sdw_utils/soc_sdw_utils.c | 46 +++++++ 4 files changed, 242 insertions(+), 18 deletions(-) diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c index 0ebb5688f84837..a8c7b42898dbbf 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.c +++ b/sound/soc/codecs/rt721-sdca-sdw.c @@ -91,6 +91,7 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re case 0x5810037: case 0x5810038: case 0x5810039: + case 0x5810100: case 0x5b10018: case 0x5b10019: case 0x5f00045: @@ -167,6 +168,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x5810037: case 0x5810038: case 0x5810039: + case 0x5810100: case 0x5b10018: case 0x5b10019: case 0x6100006: @@ -448,6 +450,7 @@ static void rt721_sdca_sdw_remove(struct sdw_slave *slave) static const struct sdw_device_id rt721_sdca_id[] = { SDW_SLAVE_ENTRY_EXT(0x025d, 0x721, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x025d, 0x718, 0x3, 0x1, 0), {}, }; MODULE_DEVICE_TABLE(sdw, rt721_sdca_id); diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index dbf61fa382a292..ef003c93f533d0 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -169,8 +169,12 @@ static void rt721_sdca_dmic_preset(struct rt721_sdca_priv *rt721) RT721_ENT_FLOAT_CTL8, 0x1e00); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_ENT_FLOAT_CTL7, 0x1515); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_CH_FLOAT_CTL3, 0x0304); + if (rt721->wf_id == RT721_S) + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL3, 0x0304); + else + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL5, 0x0304); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_CH_FLOAT_CTL4, 0x0304); rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, @@ -183,7 +187,12 @@ static void rt721_sdca_dmic_preset(struct rt721_sdca_priv *rt721) RT721_RC_CALIB_CTRL0, 0x0b00); rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, RT721_RC_CALIB_CTRL0, 0x0b40); - regmap_write(rt721->regmap, 0x2f5c, 0x25); + if (rt721->wf_id == RT721_S) { + regmap_write(rt721->regmap, 0x2f5c, 0x25); + } else { + regmap_write(rt721->regmap, 0x2f5c, 0x01); + regmap_write(rt721->regmap, 0x2f52, 0x00); + } /* clear flag */ regmap_write(rt721->regmap, SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, @@ -208,9 +217,11 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) RT721_VREF1_HV_CTRL1, 0xe000); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, RT721_MISC_POWER_CTL31, 0x8007); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6420); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6421); - regmap_write(rt721->mbq_regmap, 0x5810000, 0xe421); + if (rt721->wf_id == RT721_S) { + regmap_write(rt721->mbq_regmap, 0x5810000, 0x6420); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x6421); + regmap_write(rt721->mbq_regmap, 0x5810000, 0xe421); + } rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, RT721_CH_FLOAT_CTL6, 0x5561); rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_REG, @@ -231,6 +242,8 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); regmap_write(rt721->regmap, 0x2f5d, 0x1); + if (rt721->wf_id == RT721_U) + regmap_write(rt721->regmap, 0x2f54, 0x00); /* clear flag */ regmap_write(rt721->regmap, SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), @@ -245,7 +258,7 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) regmap_read(rt721->regmap, SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), - &jack_func_status); + &jack_func_status); dev_dbg(dev, "%s jack func_status=0x%x\n", __func__, jack_func_status); if ((jack_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt721->first_hw_init)) { @@ -330,6 +343,113 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) } } +static void rt721u_sdca_jack_preset(struct rt721_sdca_priv *rt721) +{ + unsigned int jack_func_status, calibration_status, i; + int ret; + struct device *dev = &rt721->slave->dev; + + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + &jack_func_status); + dev_dbg(dev, "%s jack func_status=0x%x\n", __func__, jack_func_status); + + if ((jack_func_status & BIT(5)) || (!rt721->first_hw_init)) { + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF1_HV_CTRL1, 0xe000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8007); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + 0x10, 0xffb7); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + 0x20, 0xffb7); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + 0x30, 0xffb7); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6048); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_GE_REL_CTRL1, 0x8011); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL3, 0xcf00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL4, 0x000f); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL1, 0x1100); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL5, 0x0c12); + rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_CTRL2, 0xc004); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b40); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x333b); + regmap_write(rt721->mbq_regmap, 0x5810135, 0x8036); + regmap_write(rt721->mbq_regmap, 0x5810130, 0xee03); + regmap_write(rt721->mbq_regmap, 0x5810133, 0x6000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL1, 0x4140); + regmap_write(rt721->mbq_regmap, 0x5810100, 0x0006); + regmap_write(rt721->mbq_regmap, 0x5810100, 0x8006); + for (i = 0; i < 5; i++) { + ret = regmap_read(rt721->mbq_regmap, 0x5810100, + &calibration_status); + if (ret < 0) + dev_dbg(dev, "calibration failed!, ret=%d\n", + ret); + if (!(calibration_status & 0x8000)) + break; + } + if (rt721->hw_vid == RT721_U_VB) + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x2205); + else + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x2e05); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3b3b); + regmap_write(rt721->mbq_regmap, 0x310400, 0x3043); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3f33); + regmap_write(rt721->mbq_regmap, 0x310401, 0x3000); + regmap_write(rt721->mbq_regmap, 0x310402, 0x1b00); + regmap_write(rt721->mbq_regmap, 0x310300, 0x000f); + regmap_write(rt721->mbq_regmap, 0x310301, 0x3000); + regmap_write(rt721->mbq_regmap, 0x310302, 0x1b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON17, 0x0008); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL2, 0x1234); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL3, 0x3512); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL1, 0x4040); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL4, 0x1201); + regmap_write(rt721->regmap, 0x2f58, 0x01); + regmap_write(rt721->regmap, 0x2f59, 0x01); + regmap_write(rt721->regmap, 0x2f51, 0x00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6040); + regmap_write(rt721->mbq_regmap, 0x910001, 0x3256); + regmap_write(rt721->mbq_regmap, 0x910002, 0x0000); + regmap_write(rt721->mbq_regmap, 0x910003, 0x0000); + regmap_write(rt721->mbq_regmap, 0x900004, 0x7778); + regmap_write(rt721->mbq_regmap, 0x910202, 0x02e4); + regmap_write(rt721->mbq_regmap, 0x800002, 0xcc04); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL4, 0xa105); + rt_sdca_index_write(rt721->mbq_regmap, 0x0b, 0x01, 0x0000); + /* clear flag */ + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0), FUNCTION_NEEDS_INITIALIZATION); + } +} + static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) { guard(mutex)(&rt721->calibrate_mutex); @@ -733,19 +853,47 @@ static int rt721_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol, return changed; } -static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(out_vol_tlv_s, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(out_vol_tlv_u, -9225, 75, 0); static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -1725, 75, 0); static const DECLARE_TLV_DB_SCALE(boost_vol_tlv, 0, 1000, 0); static const DECLARE_TLV_DB_SCALE(mic2_boost_vol_tlv, -200, 200, 0); -static const struct snd_kcontrol_new rt721_sdca_controls[] = { +static const struct snd_kcontrol_new rt721s_sdca_controls[] = { /* Headphone playback settings */ SOC_DOUBLE_R_EXT_TLV("FU05 Playback Volume", SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_USER_FU05, RT721_SDCA_CTL_FU_VOLUME, CH_L), SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_USER_FU05, RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x57, 0, - rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv), + rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv_s), + /* AMP playback settings */ + SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume", + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, + RT721_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, + RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x57, 0, + rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv_s), +}; + +static const struct snd_kcontrol_new rt721u_sdca_controls[] = { + /* Headphone playback settings */ + SOC_DOUBLE_R_EXT_TLV("FU05 Playback Volume", + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_USER_FU05, + RT721_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_USER_FU05, + RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x7b, 0, + rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv_u), + /* AMP playback settings */ + SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume", + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, + RT721_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, + RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x7b, 0, + rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv_u), +}; + +static const struct snd_kcontrol_new rt721_sdca_controls[] = { /* Headset mic capture settings */ SOC_DOUBLE_EXT("FU0F Capture Switch", SND_SOC_NOPM, 0, 1, 1, 0, rt721_sdca_fu0f_capture_get, rt721_sdca_fu0f_capture_put), @@ -761,13 +909,6 @@ static const struct snd_kcontrol_new rt721_sdca_controls[] = { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_PLATFORM_FU44, RT721_SDCA_CTL_FU_CH_GAIN, CH_R), 1, 0x15, 0, rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, mic2_boost_vol_tlv), - /* AMP playback settings */ - SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume", - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, - RT721_SDCA_CTL_FU_VOLUME, CH_L), - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, - RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x57, 0, - rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv), /* DMIC capture settings */ RT_SDCA_FU_CTRL("FU1E Capture Switch", SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT_USER_FU1E, @@ -1276,6 +1417,15 @@ static int rt721_sdca_probe(struct snd_soc_component *component) if (ret < 0 && ret != -EACCES) return ret; + if (rt721->wf_id == RT721_S) + snd_soc_add_component_controls(component, + rt721s_sdca_controls, + ARRAY_SIZE(rt721s_sdca_controls)); + else + snd_soc_add_component_controls(component, + rt721u_sdca_controls, + ARRAY_SIZE(rt721u_sdca_controls)); + return 0; } @@ -1533,6 +1683,7 @@ int rt721_sdca_init(struct device *dev, struct regmap *regmap, rt721->fu0f_mixer_l_mute = rt721->fu0f_mixer_r_mute = true; rt721->fu1e_mixer_mute[0] = rt721->fu1e_mixer_mute[1] = rt721->fu1e_mixer_mute[2] = rt721->fu1e_mixer_mute[3] = true; + rt721->wf_id = -1; return devm_snd_soc_register_component(dev, &soc_sdca_dev_rt721, rt721_sdca_dai, ARRAY_SIZE(rt721_sdca_dai)); @@ -1550,6 +1701,7 @@ static void rt721_sdca_reset(struct rt721_sdca_priv *rt721) int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) { struct rt721_sdca_priv *rt721 = dev_get_drvdata(dev); + int val; rt721->disable_irq = false; @@ -1581,12 +1733,23 @@ int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) pm_runtime_get_noresume(&slave->dev); + if (rt721->wf_id < 0) { + rt_sdca_index_read(rt721->mbq_regmap, RT721_VENDOR_REG, + RT721_JD_PRODUCT_NUM, &val); + rt721->wf_id = (val >> 4) & 0xf; + rt721->hw_vid = val & 0xf; + dev_dbg(&slave->dev, "wf_id = %d hw_vid = %d\n", rt721->wf_id, rt721->hw_vid); + } + if (!rt721->first_hw_init) rt721_sdca_reset(rt721); rt721_sdca_dmic_preset(rt721); rt721_sdca_amp_preset(rt721); - rt721_sdca_jack_preset(rt721); + if (rt721->wf_id == RT721_S) + rt721_sdca_jack_preset(rt721); + else + rt721u_sdca_jack_preset(rt721); if (rt721->hs_jack && (!rt721->first_hw_init)) rt721_sdca_jack_init(rt721); diff --git a/sound/soc/codecs/rt721-sdca.h b/sound/soc/codecs/rt721-sdca.h index b8873b18a648c7..16cf40cd88de3f 100644 --- a/sound/soc/codecs/rt721-sdca.h +++ b/sound/soc/codecs/rt721-sdca.h @@ -40,6 +40,8 @@ struct rt721_sdca_priv { /* For DMIC */ bool fu1e_dapm_mute; bool fu1e_mixer_mute[4]; + int wf_id; + int hw_vid; }; struct rt721_sdca_dmic_kctrl_priv { @@ -273,6 +275,16 @@ enum { RT721_AIFS, }; +enum rt721_wf_id { + RT721_S, + RT721_U, +}; + +enum rt721u_hw_ver { + RT721_U_VB = 1, + RT721_U_VD = 3, +}; + int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave); int rt721_sdca_init(struct device *dev, struct regmap *regmap, struct regmap *mbq_regmap, struct sdw_slave *slave); diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 0d7182bebcdc58..f0f0822d8ed6c3 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -685,6 +685,52 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, .dai_num = 3, }, + { + .vendor_id = 0x025d, + .part_id = 0x718, + .name_prefix = "rt721", + .version_id = 3, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt721-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt721-sdca-aif2", + .component_name = "rt721", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + /* No feedback capability is provided by rt721-sdca codec driver*/ + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt721-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + .quirk = SOC_SDW_CODEC_MIC, + .quirk_exclude = true, + }, + }, + .dai_num = 3, + }, { .vendor_id = 0x025d, .part_id = 0x722, From 2d14720beb58870b15a52b236c3ab0be0e06e915 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Sun, 20 Sep 2026 00:19:37 +0500 Subject: [PATCH 1014/1417] net: spacemit: clear TX descriptor on fragment mapping failure emac_tx_mem_map() writes TX_DESC_0_OWN into the ring descriptor for every slot beyond old_head as soon as that slot's memset()'d local copy is committed with "*tx_desc_addr = tx_desc", i.e. before the buffers for that slot have necessarily all been mapped successfully. If emac_tx_map_frag() then fails on a later fragment, the err_free_skb path calls emac_free_tx_buf() to unmap and drop the skb, but leaves the already-written descriptor memory untouched, and tx_ring->head is never advanced past old_head (the "tx_ring->head = head" store is skipped by the goto). So a slot between old_head and the rolled-back head can be left with TX_DESC_0_OWN set and buffer_addr_{1,2} pointing at DMA mappings that emac_free_tx_buf() just tore down, while software considers that slot free again. The next successful emac_tx_mem_map() call only rebuilds old_head itself; if the DMA engine auto-advances into the following descriptor once it finishes old_head's packet, it will fetch that stale, already-unmapped address. emac_tx_clean_desc() already treats emac_free_tx_buf() and clearing the descriptor as a pair when reclaiming completed descriptors; do the same in the mapping failure path. Fixes: bfec6d7f2001 ("net: spacemit: Add K1 Ethernet MAC") Signed-off-by: Muhammad Bilal Reviewed-by: Vivian Wang Reviewed-by: Troy Mitchell Link: https://patch.msgid.link/20260919191937.271202-1-meatuni001@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/spacemit/k1_emac.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/spacemit/k1_emac.c b/drivers/net/ethernet/spacemit/k1_emac.c index f7f16397a2c2ac..d641ac26a1e863 100644 --- a/drivers/net/ethernet/spacemit/k1_emac.c +++ b/drivers/net/ethernet/spacemit/k1_emac.c @@ -803,6 +803,9 @@ static void emac_tx_mem_map(struct emac_priv *priv, struct sk_buff *skb) while (i != head) { emac_free_tx_buf(priv, i); + tx_desc_addr = &((struct emac_desc *)tx_ring->desc_addr)[i]; + memset(tx_desc_addr, 0, sizeof(*tx_desc_addr)); + if (++i == tx_ring->total_cnt) i = 0; } From ac4334522e4ba4a3b6710dd5d4cc98092824b8ca Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 11:28:04 +0700 Subject: [PATCH 1015/1417] net: ethernet: ti: netcp: fix pm_runtime usage counter leak on error pm_runtime_get_sync() leaves the runtime PM usage counter incremented even when it fails, but the error path in netcp_probe() does not call pm_runtime_put_noidle() to balance it, leaking a reference each time resume fails. Use pm_runtime_resume_and_get() instead, which automatically drops the usage counter on failure, fixing the leak. Fixes: 84640e27f230 ("net: netcp: Add Keystone NetCP core ethernet driver") Signed-off-by: bui duc phuc Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260918042804.13101-1-phucduc.bui@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/ti/netcp_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/ti/netcp_core.c b/drivers/net/ethernet/ti/netcp_core.c index eb8fc2ed05f45b..4f9e20468bbbfe 100644 --- a/drivers/net/ethernet/ti/netcp_core.c +++ b/drivers/net/ethernet/ti/netcp_core.c @@ -2225,7 +2225,7 @@ static int netcp_probe(struct platform_device *pdev) return -ENOMEM; pm_runtime_enable(&pdev->dev); - ret = pm_runtime_get_sync(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0) { dev_err(dev, "Failed to enable NETCP power-domain\n"); pm_runtime_disable(&pdev->dev); From 999e8295bc41d6ce45b8e54f88150efa96f3f01e Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 11:08:28 +0000 Subject: [PATCH 1016/1417] net: hisilicon: hns_dsaf_mac: fix mdio device leak in hns_mac_register_phy() hns_dsaf_find_platform_device() returns the mdio platform device with its reference count incremented. hns_mac_register_phy() never drops that reference, so the mdio device can not be released. Release the reference on both the deferred probe and the normal path. Fixes: 1d1afa2ebf82 ("net: hns: register phy device in each mac initial sequence") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260917110828.2148390-1-vulab@iscas.ac.cn Signed-off-by: Paolo Abeni --- drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c b/drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c index bc6b269be29959..f1cb6d56e4b9f7 100644 --- a/drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c +++ b/drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c @@ -793,6 +793,7 @@ static int hns_mac_register_phy(struct hns_mac_cb *mac_cb) dev_err(mac_cb->dev, "mac%d mdio is NULL, dsaf will probe again later\n", mac_cb->mac_id); + put_device(&pdev->dev); return -EPROBE_DEFER; } @@ -801,6 +802,8 @@ static int hns_mac_register_phy(struct hns_mac_cb *mac_cb) dev_dbg(mac_cb->dev, "mac%d register phy addr:%d\n", mac_cb->mac_id, addr); + put_device(&pdev->dev); + return rc; } From 23d42b9a3bcd55b17d3b371544fedc708c2397e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Fri, 18 Sep 2026 21:53:52 +0200 Subject: [PATCH 1017/1417] net: macb: fix dma_alloc_coherent() leak on macb_alloc() error paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix 3 leaks in macb_alloc() error paths: - Tx buffer allocated but crossing a 4G boundary: Tx leaked. - Rx buffer allocation fails: Tx leaked. - Rx buffer allocated but crossing a 4G boundary: Tx & Rx leaked. This is because our error handling calls macb_free(bp) which in turn frees the buffers stored in bp->queues[0], but nothing has been stored in there. Fix by storing allocated buffers into bp->queues[0] ASAP. Fixes: 78d901897b3c ("net: macb: single dma_alloc_coherent() for DMA descriptors") Cc: stable@vger.kernel.org Signed-off-by: Théo Lebrun Reviewed-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260918-macb-alloc-leak-v1-1-aba9a3d4f6e3@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cadence/macb_main.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index b8234ac4b60212..8e5c034dc3a487 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2749,14 +2749,24 @@ static int macb_alloc(struct macb *bp) size = bp->num_queues * macb_tx_ring_size_per_queue(bp); tx = dma_alloc_coherent(dev, size, &tx_dma, GFP_KERNEL); - if (!tx || upper_32_bits(tx_dma) != upper_32_bits(tx_dma + size - 1)) + if (!tx) + goto out_err; + /* Record the buffer so that the error path frees it. */ + bp->queues[0].tx_ring = tx; + bp->queues[0].tx_ring_dma = tx_dma; + if (upper_32_bits(tx_dma) != upper_32_bits(tx_dma + size - 1)) goto out_err; netdev_dbg(bp->netdev, "Allocated %zu bytes for %u TX rings at %08lx (mapped %p)\n", size, bp->num_queues, (unsigned long)tx_dma, tx); size = bp->num_queues * macb_rx_ring_size_per_queue(bp); rx = dma_alloc_coherent(dev, size, &rx_dma, GFP_KERNEL); - if (!rx || upper_32_bits(rx_dma) != upper_32_bits(rx_dma + size - 1)) + if (!rx) + goto out_err; + /* Record the buffer so that the error path frees it. */ + bp->queues[0].rx_ring = rx; + bp->queues[0].rx_ring_dma = rx_dma; + if (upper_32_bits(rx_dma) != upper_32_bits(rx_dma + size - 1)) goto out_err; netdev_dbg(bp->netdev, "Allocated %zu bytes for %u RX rings at %08lx (mapped %p)\n", size, bp->num_queues, (unsigned long)rx_dma, rx); From 6d3400863baea26232cb1a85948887fa2b7df700 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:10 +0800 Subject: [PATCH 1018/1417] ASoC: SOF: get nhlt from all topologies The existing code assumes there is only one NHLT blob either from BIOS or topology. As function topologies are used and each function topology contains a NHLT blob section, we need to search the matching NHLT blob from all the NHLT blobs. The commit suggests adding a list of NHLTs and search the NHLTs from the list. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-2-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/apl.c | 1 + sound/soc/sof/intel/cnl.c | 1 + sound/soc/sof/intel/hda-dai.c | 26 +++++++++++++-- sound/soc/sof/intel/icl.c | 1 + sound/soc/sof/intel/mtl.c | 1 + sound/soc/sof/intel/skl.c | 1 + sound/soc/sof/intel/tgl.c | 1 + sound/soc/sof/ipc4-priv.h | 10 ++++-- sound/soc/sof/ipc4-topology.c | 63 ++++++++++++++++++++++++----------- 9 files changed, 80 insertions(+), 25 deletions(-) diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index b0072601181efc..753da09301fa17 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -59,6 +59,7 @@ int sof_apl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_5; diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 660c1475e5a43f..ec33cd18e669e2 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -406,6 +406,7 @@ int sof_cnl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_8; diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index bb44d4f8a4da04..c8729b6d82bd4b 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -760,8 +760,20 @@ void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4 && !hda_use_tplg_nhlt) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct snd_ipc4_nhlt *entry; - ipc4_data->nhlt = intel_nhlt_init(sdev->dev); + entry = devm_kzalloc(sdev->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return; + + entry->nhlt = intel_nhlt_init(sdev->dev); + if (!entry->nhlt) { + devm_kfree(sdev->dev, entry); + return; + } + + entry->from_acpi = true; + list_add(&entry->list, &ipc4_data->nhlt_list); } } EXPORT_SYMBOL_NS(hda_set_dai_drv_ops, "SND_SOC_SOF_INTEL_HDA_COMMON"); @@ -770,9 +782,17 @@ void hda_ops_free(struct snd_sof_dev *sdev) { if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct snd_ipc4_nhlt *entry; + struct snd_ipc4_nhlt *tmp; - if (!hda_use_tplg_nhlt) - intel_nhlt_free(ipc4_data->nhlt); + if (!hda_use_tplg_nhlt) { + list_for_each_entry_safe(entry, tmp, &ipc4_data->nhlt_list, list) { + if (entry->from_acpi && entry->nhlt) + intel_nhlt_free(entry->nhlt); + + list_del(&entry->list); + } + } kfree(sdev->private); sdev->private = NULL; diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c index 549bb4ca73e114..196caba14be0ee 100644 --- a/sound/soc/sof/intel/icl.c +++ b/sound/soc/sof/intel/icl.c @@ -131,6 +131,7 @@ int sof_icl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2; diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index 3d67d6777f1b5c..2506506f813aee 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -762,6 +762,7 @@ int sof_mtl_set_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *dsp_ops) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2; diff --git a/sound/soc/sof/intel/skl.c b/sound/soc/sof/intel/skl.c index 90519ebd316810..f831143a7afc28 100644 --- a/sound/soc/sof/intel/skl.c +++ b/sound/soc/sof/intel/skl.c @@ -67,6 +67,7 @@ int sof_skl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET_CAVS_1_5; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_5; diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index 7936361e2e39f5..09a8a4a2b017aa 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -95,6 +95,7 @@ int sof_tgl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2; diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index a8cdf9bc750b4d..61dd48282dd392 100644 --- a/sound/soc/sof/ipc4-priv.h +++ b/sound/soc/sof/ipc4-priv.h @@ -58,13 +58,18 @@ struct sof_ipc4_fw_library { struct sof_ipc4_fw_module *modules; }; +struct snd_ipc4_nhlt { + struct list_head list; + void *nhlt; + bool from_acpi; +}; + /** * struct sof_ipc4_fw_data - IPC4-specific data * @manifest_fw_hdr_offset: FW header offset in the manifest * @fw_lib_xa: XArray for firmware libraries, including basefw (ID = 0) * Used to store the FW libraries and to manage the unique IDs of the * libraries. - * @nhlt: NHLT table either from the BIOS or the topology manifest * @mtrace_type: mtrace type supported on the booted platform * @mtrace_log_bytes: log bytes as reported by the firmware via fw_config reply * @num_playback_streams: max number of playback DMAs, needed for CHAIN_DMA offset @@ -74,6 +79,7 @@ struct sof_ipc4_fw_library { * base firmware * @fw_context_save: Firmware supports full context save and restore * @libraries_restored: The libraries have been retained during firmware boot + * @nhlt_list: The NHLT tables from the BIOS and the topology manifest * * @load_library: Callback function for platform dependent library loading * @pipeline_state_mutex: Mutex to protect pipeline triggers, ref counts, states and deletion @@ -81,7 +87,6 @@ struct sof_ipc4_fw_library { struct sof_ipc4_fw_data { u32 manifest_fw_hdr_offset; struct xarray fw_lib_xa; - void *nhlt; enum sof_ipc4_mtrace_type mtrace_type; u32 mtrace_log_bytes; int num_playback_streams; @@ -90,6 +95,7 @@ struct sof_ipc4_fw_data { u32 max_libs_count; bool fw_context_save; bool libraries_restored; + struct list_head nhlt_list; int (*load_library)(struct snd_sof_dev *sdev, struct sof_ipc4_fw_library *fw_lib, bool reload); diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 24a1175dd69095..f6617dcbf789ea 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1844,12 +1844,13 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai u32 linktype, u8 dir, u32 **dst, u32 *len) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; - struct nhlt_specific_cfg *cfg; + struct nhlt_specific_cfg *cfg = NULL; + struct snd_ipc4_nhlt *entry = NULL; int sample_rate, channel_count; bool format_change = false; int bit_depth, ret; u32 nhlt_type; - int dev_type = 0; + int dev_type = -EINVAL; /* convert to NHLT type */ switch (linktype) { @@ -1880,10 +1881,17 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai * Query the type for the port and then pass that information back * to the blob lookup function. */ - dev_type = intel_nhlt_ssp_device_type(sdev->dev, ipc4_data->nhlt, - dai_index); - if (dev_type < 0) + list_for_each_entry(entry, &ipc4_data->nhlt_list, list) { + dev_type = intel_nhlt_ssp_device_type(sdev->dev, entry->nhlt, + dai_index); + if (dev_type >= 0) + break; + } + if (dev_type < 0) { + dev_err(sdev->dev, "%s: No match for SSP%d in NHLT table\n", + __func__, dai_index); return dev_type; + } break; default: return 0; @@ -1893,9 +1901,14 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai dai_index, nhlt_type, dir, dev_type); /* find NHLT blob with matching params */ - cfg = intel_nhlt_get_endpoint_blob(sdev->dev, ipc4_data->nhlt, dai_index, nhlt_type, - bit_depth, bit_depth, channel_count, sample_rate, - dir, dev_type); + list_for_each_entry(entry, &ipc4_data->nhlt_list, list) { + cfg = intel_nhlt_get_endpoint_blob(sdev->dev, entry->nhlt, dai_index, + nhlt_type, bit_depth, bit_depth, + channel_count, sample_rate, dir, + dev_type); + if (cfg) + break; + } if (!cfg) { bool get_new_blob = false; @@ -1929,13 +1942,15 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai } if (get_new_blob) { - cfg = intel_nhlt_get_endpoint_blob(sdev->dev, ipc4_data->nhlt, - dai_index, nhlt_type, - bit_depth, bit_depth, - channel_count, sample_rate, - dir, dev_type); - if (cfg) - goto out; + list_for_each_entry(entry, &ipc4_data->nhlt_list, list) { + cfg = intel_nhlt_get_endpoint_blob(sdev->dev, entry->nhlt, + dai_index, nhlt_type, + bit_depth, bit_depth, + channel_count, sample_rate, + dir, dev_type); + if (cfg) + goto out; + } } dev_err(sdev->dev, @@ -4069,6 +4084,7 @@ static int sof_ipc4_parse_manifest(struct snd_soc_component *scomp, int index, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct sof_manifest_tlv *manifest_tlv; + struct snd_ipc4_nhlt *tplg_nhlt; struct sof_manifest *manifest; u32 size = le32_to_cpu(man->priv.size); u8 *man_ptr = man->priv.data; @@ -4104,13 +4120,20 @@ static int sof_ipc4_parse_manifest(struct snd_soc_component *scomp, int index, switch (le32_to_cpu(manifest_tlv->type)) { case SOF_MANIFEST_DATA_TYPE_NHLT: - /* no NHLT in BIOS, so use the one from topology manifest */ - if (ipc4_data->nhlt) - break; - ipc4_data->nhlt = devm_kmemdup(sdev->dev, manifest_tlv->data, + /* Get the nhlt from topology manifest */ + tplg_nhlt = devm_kzalloc(sdev->dev, sizeof(*tplg_nhlt), GFP_KERNEL); + if (!tplg_nhlt) + return -ENOMEM; + + tplg_nhlt->nhlt = devm_kmemdup(sdev->dev, manifest_tlv->data, le32_to_cpu(manifest_tlv->size), GFP_KERNEL); - if (!ipc4_data->nhlt) + if (!tplg_nhlt->nhlt) return -ENOMEM; + + tplg_nhlt->from_acpi = false; + + list_add(&tplg_nhlt->list, &ipc4_data->nhlt_list); + break; default: dev_warn(scomp->dev, "Skipping unknown manifest data type %d\n", From 3313c59b1888cf2916a8ee4f5a3f4e9ffbc02e1c Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:11 +0800 Subject: [PATCH 1019/1417] ALSA: intel-nhlt: lower intel_nhlt_ssp_device_type log level To support multiple topology fragments per function, this function could be called multiple types with different NHLT blobs from different fragments. Downgrade the error message to debug level since the SSP device type could be found from one of the nhlt. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-3-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/hda/core/intel-nhlt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/hda/core/intel-nhlt.c b/sound/hda/core/intel-nhlt.c index 6d72a871bda0b5..af4d469dbd764b 100644 --- a/sound/hda/core/intel-nhlt.c +++ b/sound/hda/core/intel-nhlt.c @@ -351,7 +351,7 @@ int intel_nhlt_ssp_device_type(struct device *dev, struct nhlt_acpi_table *nhlt, int i; if (!nhlt) { - dev_err(dev, "%s: NHLT table is missing (query for SSP%d)\n", + dev_dbg(dev, "%s: NHLT table is missing (query for SSP%d)\n", __func__, virtual_bus_id); return -EINVAL; } @@ -369,7 +369,7 @@ int intel_nhlt_ssp_device_type(struct device *dev, struct nhlt_acpi_table *nhlt, epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); } - dev_err(dev, "%s: No match for SSP%d in NHLT table\n", __func__, + dev_dbg(dev, "%s: No match for SSP%d in NHLT table\n", __func__, virtual_bus_id); dev_dbg(dev, "Available endpoints:\n"); From 5a0601364c323f5820a3aeac2dd40a07e2ad2daf Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:12 +0800 Subject: [PATCH 1020/1417] ASoC: Intel: sof-function-topology-lib: create common helpers The existing code supports get_function_tplg_files callback for SoundWire machine driver only. Some common sections can be used to extend the support to other machines. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-4-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- .../intel/common/sof-function-topology-lib.c | 139 ++++++++++++------ 1 file changed, 90 insertions(+), 49 deletions(-) diff --git a/sound/soc/intel/common/sof-function-topology-lib.c b/sound/soc/intel/common/sof-function-topology-lib.c index b6e5a40b78cc60..657c7b8e06e807 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.c +++ b/sound/soc/intel/common/sof-function-topology-lib.c @@ -28,6 +28,86 @@ enum tplg_device_id { #define SOF_INTEL_PLATFORM_NAME_MAX 4 +static int get_platform_name(struct snd_soc_card *card, + const struct snd_soc_acpi_mach *mach, char *platform) +{ + int ret; + + ret = sscanf(mach->sof_tplg_filename, "sof-%3s-*.tplg", platform); + if (ret != 1) { + dev_err(card->dev, "Invalid platform name of tplg %s\n", + mach->sof_tplg_filename); + return -EINVAL; + } + + return 0; +} + +static bool tplg_files_exist(struct device *dev, const char *tplg_files) +{ + const struct firmware *fw; + int ret; + + ret = firmware_request_nowarn(&fw, tplg_files, dev); + if (!ret) { + release_firmware(fw); + return true; + } + + dev_warn(dev, + "Failed to open topology file: %s, you might need to\n", + tplg_files); + dev_warn(dev, + "download it from https://github.com/thesofproject/sof-bin/\n"); + return false; +} + +static char *get_tplg_filename(struct device *dev, const char *prefix, + const char *platform, const char *tplg_dev_name, + int dai_link_id, int tplg_dev) +{ + char *filename = NULL; + + /* + * The tplg file naming rule is sof---id.tplg + * where is only required for the devices that need NHLT blob like DMIC + * as the nhlt blob is platform dependent. + */ + switch (tplg_dev) { + case TPLG_DEVICE_INTEL_PCH_DMIC: + filename = devm_kasprintf(dev, GFP_KERNEL, "%s/sof-%s-%s-id%d.tplg", + prefix, platform, tplg_dev_name, dai_link_id); + break; + default: + filename = devm_kasprintf(dev, GFP_KERNEL, "%s/sof-%s-id%d.tplg", + prefix, tplg_dev_name, dai_link_id); + break; + } + + return filename; +} + +static int get_dmic_tplg_dev(struct device *dev, int dmic_num, + int *tplg_dev, char **tplg_dev_name) +{ + switch (dmic_num) { + case 2: + *tplg_dev_name = "dmic-2ch"; + break; + case 4: + *tplg_dev_name = "dmic-4ch"; + break; + default: + dev_warn(dev, + "unsupported number of dmics: %d\n", + dmic_num); + return -EINVAL; + } + *tplg_dev = TPLG_DEVICE_INTEL_PCH_DMIC; + + return 0; +} + int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, const char *prefix, const char ***tplg_files, bool best_effort) { @@ -38,7 +118,6 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ */ struct snd_soc_acpi_mach_params mach_params = card_mach->mach_params; struct snd_soc_dai_link *dai_link; - const struct firmware *fw; char platform[SOF_INTEL_PLATFORM_NAME_MAX]; unsigned long tplg_mask = 0; int tplg_num = 0; @@ -46,12 +125,9 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ int ret; int i; - ret = sscanf(mach->sof_tplg_filename, "sof-%3s-*.tplg", platform); - if (ret != 1) { - dev_err(card->dev, "Invalid platform name %s of tplg %s\n", - platform, mach->sof_tplg_filename); - return -EINVAL; - } + ret = get_platform_name(card, mach, platform); + if (ret < 0) + return ret; for_each_card_prelinks(card, i, dai_link) { char *tplg_dev_name; @@ -70,20 +146,9 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ tplg_dev = TPLG_DEVICE_SDCA_MIC; tplg_dev_name = "sdca-mic"; } else if (strstr(dai_link->name, "dmic")) { - switch (mach_params.dmic_num) { - case 2: - tplg_dev_name = "dmic-2ch"; - break; - case 4: - tplg_dev_name = "dmic-4ch"; - break; - default: - dev_warn(card->dev, - "unsupported number of dmics: %d\n", - mach_params.dmic_num); + if (get_dmic_tplg_dev(card->dev, mach_params.dmic_num, + &tplg_dev, &tplg_dev_name) < 0) continue; - } - tplg_dev = TPLG_DEVICE_INTEL_PCH_DMIC; } else if (strstr(dai_link->name, "iDisp")) { tplg_dev = TPLG_DEVICE_HDMI; tplg_dev_name = "hdmi-pcm5"; @@ -111,25 +176,9 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ tplg_mask |= BIT(tplg_dev); - /* - * The tplg file naming rule is sof---id.tplg - * where is only required for the DMIC function as the nhlt blob - * is platform dependent. - */ - switch (tplg_dev) { - case TPLG_DEVICE_INTEL_PCH_DMIC: - (*tplg_files)[tplg_num] = devm_kasprintf(card->dev, GFP_KERNEL, - "%s/sof-%s-%s-id%d.tplg", - prefix, platform, - tplg_dev_name, dai_link->id); - break; - default: - (*tplg_files)[tplg_num] = devm_kasprintf(card->dev, GFP_KERNEL, - "%s/sof-%s-id%d.tplg", - prefix, tplg_dev_name, - dai_link->id); - break; - } + (*tplg_files)[tplg_num] = get_tplg_filename(card->dev, prefix, platform, + tplg_dev_name, dai_link->id, + tplg_dev); if (!(*tplg_files)[tplg_num]) return -ENOMEM; tplg_num++; @@ -139,17 +188,9 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ /* Check presence of sub-topologies */ for (i = 0; i < tplg_num; i++) { - ret = firmware_request_nowarn(&fw, (*tplg_files)[i], card->dev); - if (!ret) { - release_firmware(fw); - } else { - dev_warn(card->dev, - "Failed to open topology file: %s, you might need to\n", - (*tplg_files)[i]); - dev_warn(card->dev, - "download it from https://github.com/thesofproject/sof-bin/\n"); + if (!tplg_files_exist(card->dev, (*tplg_files)[i])) + /* return 0 to use monolithic topology */ return 0; - } } return tplg_num; From aa436d8744afa45cfea2488122e01d9257201aa6 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:13 +0800 Subject: [PATCH 1021/1417] ASoC: Intel: sof-function-topology-lib: skip non-exist file in best_effort mode Currently we will fallback to the monolithic topology if any function topology is missing. But the monolithic topology may not exist if best_effort is set. We should load the existing function topologies in the case. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-5-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- .../intel/common/sof-function-topology-lib.c | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/sound/soc/intel/common/sof-function-topology-lib.c b/sound/soc/intel/common/sof-function-topology-lib.c index 657c7b8e06e807..e1049bb85a1343 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.c +++ b/sound/soc/intel/common/sof-function-topology-lib.c @@ -121,6 +121,7 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ char platform[SOF_INTEL_PLATFORM_NAME_MAX]; unsigned long tplg_mask = 0; int tplg_num = 0; + char *tplg_file; int tplg_dev; int ret; int i; @@ -174,25 +175,28 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ if (tplg_mask & BIT(tplg_dev)) continue; + tplg_file = get_tplg_filename(card->dev, prefix, platform, tplg_dev_name, + dai_link->id, tplg_dev); + if (!tplg_file) + return -ENOMEM; + + /* Check presence of sub-topologies */ + if (!tplg_files_exist(card->dev, tplg_file)) { + devm_kfree(card->dev, tplg_file); + if (best_effort) + continue; + + return 0; + } + tplg_mask |= BIT(tplg_dev); - (*tplg_files)[tplg_num] = get_tplg_filename(card->dev, prefix, platform, - tplg_dev_name, dai_link->id, - tplg_dev); - if (!(*tplg_files)[tplg_num]) - return -ENOMEM; + (*tplg_files)[tplg_num] = tplg_file; tplg_num++; } dev_dbg(card->dev, "tplg_mask %#lx tplg_num %d\n", tplg_mask, tplg_num); - /* Check presence of sub-topologies */ - for (i = 0; i < tplg_num; i++) { - if (!tplg_files_exist(card->dev, (*tplg_files)[i])) - /* return 0 to use monolithic topology */ - return 0; - } - return tplg_num; } EXPORT_SYMBOL_GPL(sof_sdw_get_tplg_files); From 6736426f31e807409e97ede9cfd2713cfcbc587a Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:14 +0800 Subject: [PATCH 1022/1417] ASoC: Intel: sof-function-topology-lib: add I2S support for sof_sdw_get_tplg_files The Intel SOF SDW machine drive also supports I2S interface. Add related supports for the sof_sdw_get_tplg_files() callback. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-6-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- .../intel/common/sof-function-topology-lib.c | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/sound/soc/intel/common/sof-function-topology-lib.c b/sound/soc/intel/common/sof-function-topology-lib.c index e1049bb85a1343..eddccbebb5f4e0 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.c +++ b/sound/soc/intel/common/sof-function-topology-lib.c @@ -19,6 +19,10 @@ enum tplg_device_id { TPLG_DEVICE_SDCA_MIC, TPLG_DEVICE_INTEL_PCH_DMIC, TPLG_DEVICE_HDMI, + TPLG_DEVICE_SSP_JACK, + TPLG_DEVICE_SSP_AMP, + TPLG_DEVICE_SSP_BT, + TPLG_DEVICE_SSP_HDMI_IN, TPLG_DEVICE_LOOPBACK_VIRTUAL, TPLG_DEVICE_MAX }; @@ -70,11 +74,15 @@ static char *get_tplg_filename(struct device *dev, const char *prefix, /* * The tplg file naming rule is sof---id.tplg - * where is only required for the devices that need NHLT blob like DMIC + * where is required for functions that depend on NHLT blobs (e.g. DMIC/SSP) * as the nhlt blob is platform dependent. */ switch (tplg_dev) { case TPLG_DEVICE_INTEL_PCH_DMIC: + case TPLG_DEVICE_SSP_JACK: + case TPLG_DEVICE_SSP_AMP: + case TPLG_DEVICE_SSP_BT: + case TPLG_DEVICE_SSP_HDMI_IN: filename = devm_kasprintf(dev, GFP_KERNEL, "%s/sof-%s-%s-id%d.tplg", prefix, platform, tplg_dev_name, dai_link_id); break; @@ -108,6 +116,53 @@ static int get_dmic_tplg_dev(struct device *dev, int dmic_num, return 0; } +static int get_ssp_tplg_dev(struct device *dev, struct snd_soc_dai_link *dai_link, + u16 *hdmi_in_mask, int *tplg_dev, char **tplg_dev_name) +{ + unsigned int ssp_port; + + if (sscanf(dai_link->name, "SSP%d", &ssp_port) != 1) { + dev_err(dev, "Can't get SSP port from dai_link->name %s\n", dai_link->name); + return -EINVAL; + } + if (strstr(dai_link->name, "Codec")) { + /* + * Assume DAI link 0 is jack which is true in all existing + * machine drivers + */ + if (dai_link->id == 0) { + *tplg_dev = TPLG_DEVICE_SSP_JACK; + *tplg_dev_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%d-jack", ssp_port); + } else { + *tplg_dev = TPLG_DEVICE_SSP_AMP; + *tplg_dev_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%d-amp", ssp_port); + } + } else if (strstr(dai_link->name, "BT")) { + *tplg_dev = TPLG_DEVICE_SSP_BT; + *tplg_dev_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%d-bt", ssp_port); + } else if (strstr(dai_link->name, "HDMI")) { + *hdmi_in_mask |= BIT(ssp_port); + /* The number of HDMI in dai link is always 2 right now */ + if (hweight16(*hdmi_in_mask) != 2) + return -EINVAL; + + *tplg_dev = TPLG_DEVICE_SSP_HDMI_IN; + *tplg_dev_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%x-hdmiin", *hdmi_in_mask); + } else { + dev_warn(dev, + "unsupported SSP link %s\n", dai_link->name); + return -EINVAL; + } + if (!*tplg_dev_name) + return -ENOMEM; + + return 0; +} + int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, const char *prefix, const char ***tplg_files, bool best_effort) { @@ -120,6 +175,7 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ struct snd_soc_dai_link *dai_link; char platform[SOF_INTEL_PLATFORM_NAME_MAX]; unsigned long tplg_mask = 0; + u16 hdmi_in_mask = 0; int tplg_num = 0; char *tplg_file; int tplg_dev; @@ -153,6 +209,10 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ } else if (strstr(dai_link->name, "iDisp")) { tplg_dev = TPLG_DEVICE_HDMI; tplg_dev_name = "hdmi-pcm5"; + } else if (strstr(dai_link->name, "SSP")) { + if (get_ssp_tplg_dev(card->dev, dai_link, &hdmi_in_mask, + &tplg_dev, &tplg_dev_name) < 0) + continue; } else if (strstr(dai_link->name, "Loopback_Virtual")) { tplg_dev = TPLG_DEVICE_LOOPBACK_VIRTUAL; /* From 21465d34877730ea032c0469b57213bc8da6db04 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:15 +0800 Subject: [PATCH 1023/1417] ASoC: Intel: sof-function-topology-lib: add get_function_topology for I2S machines Add sof_i2s_get_tplg_files() callback for Intel SOF I2S machines. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-7-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- .../intel/common/sof-function-topology-lib.c | 72 +++++++++++++++++++ .../intel/common/sof-function-topology-lib.h | 3 + 2 files changed, 75 insertions(+) diff --git a/sound/soc/intel/common/sof-function-topology-lib.c b/sound/soc/intel/common/sof-function-topology-lib.c index eddccbebb5f4e0..8f4fdf82d00f21 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.c +++ b/sound/soc/intel/common/sof-function-topology-lib.c @@ -260,3 +260,75 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ return tplg_num; } EXPORT_SYMBOL_GPL(sof_sdw_get_tplg_files); + +int sof_i2s_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, + const char *prefix, const char ***tplg_files, bool best_effort) +{ + struct snd_soc_acpi_mach_params mach_params = mach->mach_params; + struct snd_soc_dai_link *dai_link; + char platform[SOF_INTEL_PLATFORM_NAME_MAX]; + unsigned long tplg_mask = 0; + u16 hdmi_in_mask = 0; + int tplg_num = 0; + char *tplg_file; + int tplg_dev; + int ret; + int i; + + ret = get_platform_name(card, mach, platform); + if (ret < 0) + return ret; + + for_each_card_prelinks(card, i, dai_link) { + char *tplg_dev_name; + + dev_dbg(card->dev, "dai_link %s id %d\n", dai_link->name, dai_link->id); + if (strstr(dai_link->name, "SSP")) { + if (get_ssp_tplg_dev(card->dev, dai_link, &hdmi_in_mask, + &tplg_dev, &tplg_dev_name) < 0) + continue; + } else if (strstr(dai_link->name, "dmic")) { + if (get_dmic_tplg_dev(card->dev, mach_params.dmic_num, + &tplg_dev, &tplg_dev_name) < 0) + continue; + } else if (strstr(dai_link->name, "iDisp")) { + tplg_dev = TPLG_DEVICE_HDMI; + tplg_dev_name = "hdmi-pcm5"; + } else { + /* The dai link is not supported by separated tplg yet */ + dev_dbg(card->dev, + "dai_link %s is not supported by separated tplg yet\n", + dai_link->name); + if (best_effort) + continue; + + return 0; + } + if (tplg_mask & BIT(tplg_dev)) + continue; + + tplg_file = get_tplg_filename(card->dev, prefix, platform, tplg_dev_name, + dai_link->id, tplg_dev); + if (!tplg_file) + return -ENOMEM; + + /* Check presence of sub-topologies */ + if (!tplg_files_exist(card->dev, tplg_file)) { + devm_kfree(card->dev, tplg_file); + if (best_effort) + continue; + + return 0; + } + + tplg_mask |= BIT(tplg_dev); + + (*tplg_files)[tplg_num] = tplg_file; + tplg_num++; + } + + dev_dbg(card->dev, "tplg_mask %#lx tplg_num %d\n", tplg_mask, tplg_num); + + return tplg_num; +} +EXPORT_SYMBOL_GPL(sof_i2s_get_tplg_files); diff --git a/sound/soc/intel/common/sof-function-topology-lib.h b/sound/soc/intel/common/sof-function-topology-lib.h index f358f8c52d7854..9755e97709685e 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.h +++ b/sound/soc/intel/common/sof-function-topology-lib.h @@ -12,4 +12,7 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, const char *prefix, const char ***tplg_files, bool best_effort); +int sof_i2s_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, + const char *prefix, const char ***tplg_files, bool best_effort); + #endif From 7f1040813b63f0674ecc5bcf98a66b3411b15fb4 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 22 Sep 2026 17:23:16 +0800 Subject: [PATCH 1024/1417] ASoC: soc-acpi-intel-ptl-match: I2S machines: use function topology Use sof_i2s_get_tplg_files() for SOF es83x6 machines. Signed-off-by: Bard Liao Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20260922092316.2296039-8-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/common/soc-acpi-intel-ptl-match.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c index dd84e97836c98d..0ce51c44bfe65a 100644 --- a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c @@ -51,12 +51,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_machines[] = { .drv_name = "ptl_es83x6_c1_h02", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &ptl_lt6911_hdmi, + .get_function_tplg_files = sof_i2s_get_tplg_files, .sof_tplg_filename = "sof-ptl-es83x6-ssp1-hdmi-ssp02.tplg", }, { .comp_ids = &ptl_essx_83x6, .drv_name = "sof-essx8336", .sof_tplg_filename = "sof-ptl-es8336", /* the tplg suffix is added at run time */ + .get_function_tplg_files = sof_i2s_get_tplg_files, .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, @@ -65,6 +67,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_machines[] = { { .id = "INTC10B0", .drv_name = "ptl_lt6911_hdmi_ssp", + .get_function_tplg_files = sof_i2s_get_tplg_files, .sof_tplg_filename = "sof-ptl-hdmi-ssp02.tplg", }, {}, From 261e8a37ecbaf462cdf9c336d2b2f5056088401a Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Fri, 18 Sep 2026 15:29:48 -0700 Subject: [PATCH 1025/1417] genetlink: report the real command id for dump-only ops in policy dumps The op-to-policy map a CTRL_CMD_GETPOLICY dump returns is the only way for userspace to find out which policy index belongs to which command. ctrl_dumppolicy_put_op() tags the nest with doit->cmd, but an op which only has a dumpit has no doit and every path which fills the split ops in zeroes it out, so those entries all claim to be command 0. nlctrl's own CTRL_CMD_GETPOLICY and NETDEV_CMD_QSTATS_GET are both in that group: [{'family-id': 16, 'op-policy': {'do': 0, 'dump': 0, 'op-id': 3}}, {'family-id': 16, 'op-policy': {'dump': 1, 'op-id': 0}}, ctrl_fill_info() gets this right - it uses the iterator's cmd for CTRL_ATTR_OP_ID - so the two introspection interfaces of the same family contradict each other today. Pass the command in rather than reconstructing it from doit->cmd | dumpit->cmd inside the helper, both callers already have it. Fixes: 26588edbef60 ("genetlink: support split policies in ctrl_dumppolicy_put_op()") Signed-off-by: Jakub Kicinski Link: https://patch.msgid.link/20260918222949.4190284-1-kuba@kernel.org Signed-off-by: Paolo Abeni --- net/netlink/genetlink.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c index 41d37442f18681..5cc1037d4917e0 100644 --- a/net/netlink/genetlink.c +++ b/net/netlink/genetlink.c @@ -1656,7 +1656,7 @@ static void *ctrl_dumppolicy_prep(struct sk_buff *skb, } static int ctrl_dumppolicy_put_op(struct sk_buff *skb, - struct netlink_callback *cb, + struct netlink_callback *cb, u32 cmd, struct genl_split_ops *doit, struct genl_split_ops *dumpit) { @@ -1677,7 +1677,7 @@ static int ctrl_dumppolicy_put_op(struct sk_buff *skb, if (!nest_pol) goto err; - nest_op = nla_nest_start(skb, doit->cmd); + nest_op = nla_nest_start(skb, cmd); if (!nest_op) goto err; @@ -1721,7 +1721,8 @@ static int ctrl_dumppolicy(struct sk_buff *skb, struct netlink_callback *cb) &doit, &dumpit))) return -ENOENT; - if (ctrl_dumppolicy_put_op(skb, cb, &doit, &dumpit)) + if (ctrl_dumppolicy_put_op(skb, cb, ctx->op, + &doit, &dumpit)) return skb->len; /* done with the per-op policy index list */ @@ -1730,6 +1731,7 @@ static int ctrl_dumppolicy(struct sk_buff *skb, struct netlink_callback *cb) while (ctx->dump_map) { if (ctrl_dumppolicy_put_op(skb, cb, + ctx->op_iter->cmd, &ctx->op_iter->doit, &ctx->op_iter->dumpit)) return skb->len; From a87529034b9c3c3f3bb71c33e2d6d94658e06383 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Fri, 18 Sep 2026 15:29:49 -0700 Subject: [PATCH 1026/1417] selftests: net: nl_nlctrl: check the op ids in the policy map Validate that the op map in the policy dump is correct. Signed-off-by: Jakub Kicinski Link: https://patch.msgid.link/20260918222949.4190284-2-kuba@kernel.org Signed-off-by: Paolo Abeni --- tools/testing/selftests/net/nl_nlctrl.py | 116 +++++++++++++++++++---- 1 file changed, 99 insertions(+), 17 deletions(-) diff --git a/tools/testing/selftests/net/nl_nlctrl.py b/tools/testing/selftests/net/nl_nlctrl.py index fe1f66dc943505..237b3d27326029 100755 --- a/tools/testing/selftests/net/nl_nlctrl.py +++ b/tools/testing/selftests/net/nl_nlctrl.py @@ -9,40 +9,86 @@ from lib.py import ksft_eq, ksft_ge, ksft_true, ksft_in, ksft_not_in from lib.py import NetdevFamily, EthtoolFamily, NlctrlFamily +# Families we can expect to always be around, and which between them +# cover ops with a do, with a dump, and with both. +FAMILIES = ('nlctrl', 'netdev') -def getfamily_do(ctrl) -> None: - """Query a single family by name and validate its ops.""" - fam = ctrl.getfamily({'family-name': 'netdev'}) - ksft_eq(fam['family-name'], 'netdev') + +def _get_ops(ctrl, name): + """Get the ops of a family, keyed by command id.""" + fam = ctrl.getfamily({'family-name': name}) + ksft_eq(fam['family-name'], name) ksft_true(fam['family-id'] > 0) # The format of ops is quite odd, [{$idx: {"id"...}}, {$idx: {"id"...}}] # Discard the indices and re-key by command id. ops_by_id = {v['id']: v for op in fam['ops'] for v in op.values()} - ksft_eq(len(ops_by_id), len(fam['ops'])) + ksft_eq(len(ops_by_id), len(fam['ops']), + comment=f"{name} lists a command twice") + return ops_by_id + + +def _get_policy_map(ctrl, req): + """ + The policy map in the Netlink replies looks like this: + + [{'family-id': 16, 'op-policy': {'do': 0, 'dump': 0, 'op-id': 3}}, + {'family-id': 16, 'op-policy': {'dump': 1, 'op-id': 4}}, ...] + + Return the mapping: + + {3:{'do','dump'}, 4:{'dump'}} + + The policy itself is discarded here, only return which command has policy. + """ + pol_map = {} + for msg in ctrl.getpolicy(req, dump=True): + if 'op-policy' not in msg: + continue + modes = dict(msg['op-policy']) + cmd = modes.pop('op-id') + ksft_not_in(cmd, pol_map, comment=f"command {cmd} reported twice") + pol_map[cmd] = set(modes.keys()) + return pol_map + + +def getfamily_do(ctrl) -> None: + """Query single families by name and validate their ops.""" + ops = {name: _get_ops(ctrl, name) for name in FAMILIES} + + for name, ops_by_id in ops.items(): + for op in ops_by_id.values(): + # All ops in nlctrl and netdev have a policy + ksft_in('cmd-cap-haspol', op['flags'], + comment=f"{name} op {op['id']} missing haspol") + ksft_true(op['flags'] & {'cmd-cap-do', 'cmd-cap-dump'}, + comment=f"{name} op {op['id']} has no handler") - # All ops should have a policy (either do or dump has one) - for op in ops_by_id.values(): - ksft_in('cmd-cap-haspol', op['flags'], - comment=f"op {op['id']} missing haspol") + # nlctrl getfamily (id 3) does both, getpolicy (id 10) is dump-only + ksft_in('cmd-cap-do', ops['nlctrl'][3]['flags']) + ksft_in('cmd-cap-dump', ops['nlctrl'][3]['flags']) + ksft_not_in('cmd-cap-do', ops['nlctrl'][10]['flags']) + ksft_in('cmd-cap-dump', ops['nlctrl'][10]['flags']) + + netdev = ops['netdev'] # dev-get (id 1) should support both do and dump - ksft_in('cmd-cap-do', ops_by_id[1]['flags']) - ksft_in('cmd-cap-dump', ops_by_id[1]['flags']) + ksft_in('cmd-cap-do', netdev[1]['flags']) + ksft_in('cmd-cap-dump', netdev[1]['flags']) # qstats-get (id 12) is dump-only - ksft_not_in('cmd-cap-do', ops_by_id[12]['flags']) - ksft_in('cmd-cap-dump', ops_by_id[12]['flags']) + ksft_not_in('cmd-cap-do', netdev[12]['flags']) + ksft_in('cmd-cap-dump', netdev[12]['flags']) # napi-set (id 14) is do-only and requires admin - ksft_in('cmd-cap-do', ops_by_id[14]['flags']) - ksft_not_in('cmd-cap-dump', ops_by_id[14]['flags']) - ksft_in('admin-perm', ops_by_id[14]['flags']) + ksft_in('cmd-cap-do', netdev[14]['flags']) + ksft_not_in('cmd-cap-dump', netdev[14]['flags']) + ksft_in('admin-perm', netdev[14]['flags']) # Notification-only commands (dev-add/del/change-ntf etc.) must # not appear in the ops list since they have no do/dump handlers. for ntf_id in [2, 3, 4, 6, 7, 8]: - ksft_not_in(ntf_id, ops_by_id, + ksft_not_in(ntf_id, netdev, comment=f"ntf-only cmd {ntf_id} should not be in ops") @@ -103,6 +149,41 @@ def getpolicy_dump(_ctrl) -> None: comment="linkinfo-set should not have a dump policy") +def getpolicy_op_map(ctrl) -> None: + """Check the op-to-policy map consistency. Each op with 'haspol' flag + has to have a policy. The policy back-references must name only + real ops that exist, have given modes (do vs dump) and have 'haspol'. + """ + for name in FAMILIES: + ops_by_id = _get_ops(ctrl, name) + haspol = {cmd for cmd, op in ops_by_id.items() + if 'cmd-cap-haspol' in op['flags']} + + pol_map = _get_policy_map(ctrl, {'family-name': name}) + ksft_eq(set(pol_map), haspol, + comment=f"{name} policy map does not match the op list") + + # Walk the op list rather than the map, the map may be missing + # the very op we are after. Asking for a command the family does + # not have is an error, so it must not come from the map either. + for cmd in sorted(haspol): + modes = pol_map.get(cmd, set()) + + # The kernel only reports a mode the op actually has. + if 'do' in modes: + ksft_in('cmd-cap-do', ops_by_id[cmd]['flags'], + comment=f"{name} cmd {cmd} has no do") + if 'dump' in modes: + ksft_in('cmd-cap-dump', ops_by_id[cmd]['flags'], + comment=f"{name} cmd {cmd} has no dump") + + # Asking for one op builds the map in a different place in + # the kernel, it has to report what the full dump did. + single = _get_policy_map(ctrl, {'family-name': name, 'op': cmd}) + ksft_eq(single, {cmd: modes}, + comment=f"{name} cmd {cmd} policy differs from the dump") + + def getpolicy_by_op(_ctrl) -> None: """Query policy for specific ops, check attr names are resolved.""" ndev = NetdevFamily() @@ -122,6 +203,7 @@ def main() -> None: ksft_run([getfamily_do, getfamily_dump, getpolicy_dump, + getpolicy_op_map, getpolicy_by_op], args=(ctrl, )) ksft_exit() From 5d35f9be9b54a1b8f7df85ec5261557e43a999bb Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Tue, 22 Sep 2026 20:30:29 +0800 Subject: [PATCH 1027/1417] ASoC: amd: acp: Add DMI quirk for Lenovo Yoga Pro 7 14ASP9 The Lenovo Yoga Pro 7 14ASP9 (83HN) has no digital microphone on the ACP PDM interface; all internal mics are wired to the Realtek ALC287 codec. Add 83HN to acp70_acpi_flag_override_table to prevent the legacy ACP driver from binding and exposing a non-functional PDM capture device, consistent with the sibling 83W5/83V9 entries. Reported-by: Mikail Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221886 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260922123029.534152-1-zhangheng@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index 2d50be8190c10e..4d8f86aa3d1635 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -51,6 +51,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "83V9"), }, }, + { + /* Lenovo Yoga Pro 7 14ASP9 (Strix Point, ACP 7.0) */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83HN"), + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC"), From 9892d71cf0ce3ff3d4fed2d9a3968fd4feb1c918 Mon Sep 17 00:00:00 2001 From: Coia Prant Date: Sun, 20 Sep 2026 01:20:21 +0800 Subject: [PATCH 1028/1417] net: pcs: xpcs: fix clock reference leak on xpcs_init_clks failure xpcs_init_clks() takes references with clk_bulk_get_optional() and then enables them with clk_bulk_prepare_enable(). If the enable step fails, the function returns without dropping the references. xpcs_create() handles the failure through out_free_data, which calls xpcs_free_data() but never xpcs_clear_clks(), so the clk references are leaked. Add the missing clk_bulk_put() on the enable failure path. The prepare/enable side is already rolled back by clk_bulk_prepare_enable() itself. Fixes: f6bb3e9d98c2 ("net: pcs: xpcs: Add Synopsys DW xPCS platform device driver") Signed-off-by: Coia Prant Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260919172021.2336748-1-coiaprant@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/pcs/pcs-xpcs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index 0337e2bcc01258..b415b93d77c154 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -1545,8 +1545,10 @@ static int xpcs_init_clks(struct dw_xpcs *xpcs) return dev_err_probe(dev, ret, "Failed to get clocks\n"); ret = clk_bulk_prepare_enable(DW_XPCS_NUM_CLKS, xpcs->clks); - if (ret) + if (ret) { + clk_bulk_put(DW_XPCS_NUM_CLKS, xpcs->clks); return dev_err_probe(dev, ret, "Failed to enable clocks\n"); + } return 0; } From e845b86c58e86ea4466ee72763faeff6646c12f4 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 17 Sep 2026 17:01:20 +0800 Subject: [PATCH 1029/1417] ASoC: rt712: add GPIOs for LED control Certain platforms use the RT712 GPIOs to control the microphone mute LED and speaker mute LED. This patch lets the LED subsystem to handle the GPIO control. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260917090120.2506160-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca-sdw.c | 1 + sound/soc/codecs/rt712-sdca-sdw.h | 3 + sound/soc/codecs/rt712-sdca.c | 136 ++++++++++++++++++++++++++++++ sound/soc/codecs/rt712-sdca.h | 6 ++ 4 files changed, 146 insertions(+) diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c index edba0367d9cec2..f2f91dd087bf72 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.c +++ b/sound/soc/codecs/rt712-sdca-sdw.c @@ -395,6 +395,7 @@ static void rt712_sdca_sdw_remove(struct sdw_slave *slave) mutex_destroy(&rt712->calibrate_mutex); mutex_destroy(&rt712->disable_irq_lock); + mutex_destroy(&rt712->gc_lock); } static const struct sdw_device_id rt712_sdca_id[] = { diff --git a/sound/soc/codecs/rt712-sdca-sdw.h b/sound/soc/codecs/rt712-sdca-sdw.h index 7ad25cc27f624d..162d62234b689d 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.h +++ b/sound/soc/codecs/rt712-sdca-sdw.h @@ -46,6 +46,9 @@ static const struct reg_default rt712_sdca_mbq_defaults[] = { { 0x5b00029, 0x3fff }, { 0x5b0002a, 0xf000 }, { 0x6100000, 0x04e4 }, + { 0x6100009, 0x0000 }, + { 0x610000a, 0x0000 }, + { 0x610000b, 0x0020 }, { 0x610000e, 0x0007 }, { 0x6100045, 0x0860 }, { 0x6100046, 0x0029 }, diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c index 38052cb19790e6..a98537e23c219b 100644 --- a/sound/soc/codecs/rt712-sdca.c +++ b/sound/soc/codecs/rt712-sdca.c @@ -113,6 +113,138 @@ static void rt712_sdca_clk_patch2(struct rt712_sdca_priv *rt712) rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); } +static int rt712_sdca_gpio_request(struct gpio_chip *chip, unsigned int offset) +{ + struct rt712_sdca_priv *rt712 = gpiochip_get_data(chip); + struct device *dev = &rt712->slave->dev; + unsigned int gpio_pin = offset + 3; + int ret; + + dev_dbg(&rt712->slave->dev, "%s: gpio_pin=%d\n", __func__, gpio_pin); + + mutex_lock(&rt712->gc_lock); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) + goto io_error; + + /* + * Only support GPIO3 and GPIO4 for now + */ + switch (gpio_pin) { + case 3: + break; + case 4: + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_LEGACY_CONFIG_CTL0, 0x000c, 0x0004); + if (ret < 0) + goto io_error; + break; + default: + ret = -EINVAL; + goto io_error; + } + + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_EN_CTL, (1 << gpio_pin), (1 << gpio_pin)); + if (ret < 0) + goto io_error; + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_DIRECTION_CTL, (1 << gpio_pin), (1 << gpio_pin)); + +io_error: + mutex_unlock(&rt712->gc_lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int rt712_sdca_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct rt712_sdca_priv *rt712 = gpiochip_get_data(chip); + struct device *dev = &rt712->slave->dev; + unsigned int gpio_pin = offset + 3; + int ret; + + dev_dbg(&rt712->slave->dev, "%s: gpio_pin=%d, value=%d\n", __func__, gpio_pin, value); + + mutex_lock(&rt712->gc_lock); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) + goto io_error; + + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_SET_CTL, (1 << gpio_pin), (!!value << gpio_pin)); + +io_error: + mutex_unlock(&rt712->gc_lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int rt712_sdca_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct rt712_sdca_priv *rt712 = gpiochip_get_data(chip); + struct device *dev = &rt712->slave->dev; + unsigned int gpio_pin = offset + 3; + int ret; + + dev_dbg(&rt712->slave->dev, "%s: gpio_pin=%d, value=%d\n", __func__, gpio_pin, value); + + mutex_lock(&rt712->gc_lock); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) + goto io_error; + + switch (gpio_pin) { + case 3: + case 4: + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_DIRECTION_CTL, (1 << gpio_pin), (1 << gpio_pin)); + if (ret < 0) + goto io_error; + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_SET_CTL, (1 << gpio_pin), (!!value << gpio_pin)); + break; + default: + ret = -EINVAL; + goto io_error; + } + +io_error: + mutex_unlock(&rt712->gc_lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static const struct gpio_chip rt712_sdca_template_chip = { + .label = "rt712-sdca", + .owner = THIS_MODULE, + .request = rt712_sdca_gpio_request, + .direction_output = rt712_sdca_gpio_direction_out, + .set = rt712_sdca_gpio_set, + .ngpio = 2, + .can_sleep = true, + .base = -1, +}; + +static void rt712_sdca_gpio_init(struct rt712_sdca_priv *rt712) +{ + int ret; + + mutex_init(&rt712->gc_lock); + rt712->gpio_chip = rt712_sdca_template_chip; + rt712->gpio_chip.parent = &rt712->slave->dev; + rt712->gpio_chip.fwnode = dev_fwnode(&rt712->slave->dev); + + ret = devm_gpiochip_add_data(&rt712->slave->dev, &rt712->gpio_chip, rt712); + if (ret != 0) + dev_err(&rt712->slave->dev, "Failed to add GPIOs: %d\n", ret); +} + static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) { unsigned int val, loop_rc = 0, loop_dc = 0; @@ -1724,6 +1856,10 @@ int rt712_sdca_init(struct device *dev, struct regmap *regmap, INIT_DELAYED_WORK(&rt712->jack_detect_work, rt712_sdca_jack_detect_handler); INIT_DELAYED_WORK(&rt712->jack_btn_check_work, rt712_sdca_btn_check_handler); + /* initialize GPIOs */ + if (IS_ENABLED(CONFIG_GPIOLIB)) + rt712_sdca_gpio_init(rt712); + /* * Mark hw_init to false * HW init will be performed when device reports present diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h index 6229fe341bb516..7e6ee21c8a172d 100644 --- a/sound/soc/codecs/rt712-sdca.h +++ b/sound/soc/codecs/rt712-sdca.h @@ -14,6 +14,7 @@ #include #include #include +#include struct rt712_sdca_priv { struct regmap *regmap; @@ -45,6 +46,8 @@ struct rt712_sdca_priv { bool fu05_dapm_mute; bool fu05_mixer_l_mute; bool fu05_mixer_r_mute; + struct gpio_chip gpio_chip; + struct mutex gc_lock; }; struct rt712_dmic_kctrl_priv { @@ -114,6 +117,9 @@ struct rt712_dmic_kctrl_priv { #define RT712_HDA_LEGACY_MUX_CTL0 0x00 #define RT712_HDA_LEGACY_CONFIG_CTL0 0x06 #define RT712_HDA_LEGACY_RESET_CTL 0x08 +#define RT712_HDA_GPIO_EN_CTL 0x09 +#define RT712_HDA_GPIO_DIRECTION_CTL 0x0a +#define RT712_HDA_GPIO_SET_CTL 0x0b #define RT712_HDA_LEGACY_GPIO_WAKE_EN_CTL 0x0e #define RT712_DMIC_ENT_FLOAT_CTL 0x10 #define RT712_DMIC_GAIN_ENT_FLOAT_CTL0 0x11 From ba593d662183de74d2b733b4c6e8cd26f04f0a65 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 07:31:36 +0000 Subject: [PATCH 1030/1417] ASoC: ti: davinci-mcasp: Fix runtime PM imbalance in __davinci_mcasp_set_clkdiv() In __davinci_mcasp_set_clkdiv(), pm_runtime_get_sync() is called at the entry of the function. If an invalid div_id is provided, the function hits the default switch case and directly returns -EINVAL without calling pm_runtime_put(), resulting in a runtime PM reference leak. Add pm_runtime_put() in the default case before returning -EINVAL. Fixes: 4ed8c9b737b6 ("ASoC: McASP: add support for clock dividers") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Tested-by: Sen Wang Link: https://patch.msgid.link/20260916073136.1971673-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/ti/davinci-mcasp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index d461ab4196552d..481d1042ddb6d1 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -799,6 +799,7 @@ static int __davinci_mcasp_set_clkdiv(struct davinci_mcasp *mcasp, int div_id, break; default: + pm_runtime_put(mcasp->dev); return -EINVAL; } From 17741334d00bf5ebd37f8c1c36bc9c146a351deb Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 11:58:11 +0000 Subject: [PATCH 1031/1417] net: usb: lan78xx: Fix URB reference leak in lan78xx_submit_deferred_urbs() usb_get_from_anchor() hands over a reference to the URB, which the caller must release. lan78xx_submit_deferred_urbs() never does, so every deferred Tx URB keeps an extra reference: the counter grows on each suspend/resume cycle and the URBs are never freed when the buffers are released. Drop the reference after submitting, and on the path that drops the packet instead of submitting it. Fixes: 5f4cc6e25148 ("lan78xx: Fix race conditions in suspend/resume handling") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260917115811.2150119-1-vulab@iscas.ac.cn Signed-off-by: Paolo Abeni --- drivers/net/usb/lan78xx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c index cb782d81d84f2a..5655941f147830 100644 --- a/drivers/net/usb/lan78xx.c +++ b/drivers/net/usb/lan78xx.c @@ -5239,10 +5239,12 @@ static bool lan78xx_submit_deferred_urbs(struct lan78xx_net *dev) !netif_carrier_ok(dev->net) || pipe_halted) { lan78xx_release_tx_buf(dev, skb); + usb_put_urb(urb); continue; } ret = usb_submit_urb(urb, GFP_ATOMIC); + usb_put_urb(urb); if (ret == 0) { netif_trans_update(dev->net); From 10180a277549339020b08000206092c07e0bff5a Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 14:16:07 -0700 Subject: [PATCH 1032/1417] KVM: x86: Re-pend GET_NESTED_STATE_PAGES if getting said pages fails Re-pend GET_NESTED_STATE_PAGES before exiting to userspace if getting the nested pages fails in the KVM_RUN path. If userspace re-runs the vCPU, and vmcs02 holds valid PFNs from the *previous* run of L2, then KVM could re-enter L2 with stale, unpinned PFNs mapped into e.g. the vAPIC page. Note, both SVM and VMX (as of commit 11722439fb20 ("KVM: nVMX: Ensure KVM_REQ_GET_NESTED_STATE_PAGES is cleared on VM-Exit") ensure the request is cleared on VM-Exit (including the "forced" case), i.e. there is no risk of double-mapping due to emulated VMLAUNCH/VMRESUME/VMRUN *and* the request trying to map the nested pages. Fixes: 671ddc700fd0 ("KVM: nVMX: Don't leak L1 MMIO regions to L2") Cc: stable@vger.kernel.org Reported-by: Jinwoo Lee Closes: https://lore.kernel.org/all/20260813043932.3214460-1-rkskek9254@gmail.com Reported-by: Stefan Teodorescu Link: https://patch.msgid.link/20260921211608.1030158-2-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/x86.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index a137dc6dd8c673..4ec17aaff41349 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -8072,6 +8072,7 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) if (kvm_check_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu)) { if (unlikely(!kvm_nested_call(get_nested_state_pages)(vcpu))) { + kvm_make_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu); r = 0; goto out; } From c1214f293d77c6e425a379f90d09bdb4815993a8 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 14:16:08 -0700 Subject: [PATCH 1033/1417] KVM: x86: Fill kvm_run exit fields in common get_nested_state_pages() error paths Fill kvm_run with "internal error, emulation" in the common error handling paths for getting nested state pages, as requiring each check to manually fill kvm_run is error prone and requires a non-trivial amount of copy+paste. Specifically, both SVM and VMX fail to fill kvm_run if load_pdptrs() fails, and SVM fails to fill kvm_run if kvm_hv_verify_vp_assist() fails. If those flows fail, the *best* case scenario is that KVM will exit to userspace with KVM_EXIT_UNKNOWN. The worst case scenario is that KVM exits with a stale exit_reason and confuses userspace. Note, SVM never exits to userspace if something goes sideways when dealing with vmcb12 assets while emulating VMRUN, i.e. lack of SVM-specific code is not a bug. Fixes: 0f85722341b0 ("KVM: nVMX: delay loading of PDPTRs to KVM_REQ_GET_NESTED_STATE_PAGES") Fixes: 232f75d3b4b5 ("KVM: nSVM: call nested_svm_load_cr3 on nested state load") Fixes: 3f4a812edf5c ("KVM: nSVM: hyper-v: Enable L2 TLB flush") Cc: stable@vger.kernel.org Reported-by: Jinwoo Lee Closes: https://lore.kernel.org/all/20260813043932.3214460-1-rkskek9254@gmail.com Reported-by: Stefan Teodorescu Link: https://patch.msgid.link/20260921211608.1030158-3-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/nested.c | 7 +------ arch/x86/kvm/vmx/nested.c | 15 +++++---------- arch/x86/kvm/x86.c | 3 +++ 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c index 73f37b050d0a0b..f9090b601efaaa 100644 --- a/arch/x86/kvm/svm/nested.c +++ b/arch/x86/kvm/svm/nested.c @@ -2125,13 +2125,8 @@ static bool svm_get_nested_state_pages(struct kvm_vcpu *vcpu) return false; } - if (!nested_svm_merge_msrpm(vcpu)) { - vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; - vcpu->run->internal.suberror = - KVM_INTERNAL_ERROR_EMULATION; - vcpu->run->internal.ndata = 0; + if (!nested_svm_merge_msrpm(vcpu)) return false; - } if (kvm_hv_verify_vp_assist(vcpu)) return false; diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c index 151873407abd36..40c1a5f6fa8a59 100644 --- a/arch/x86/kvm/vmx/nested.c +++ b/arch/x86/kvm/vmx/nested.c @@ -3465,10 +3465,6 @@ static bool nested_get_vmcs12_pages(struct kvm_vcpu *vcpu) } else { pr_debug_ratelimited("%s: no backing for APIC-access address in vmcs12\n", __func__); - vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; - vcpu->run->internal.suberror = - KVM_INTERNAL_ERROR_EMULATION; - vcpu->run->internal.ndata = 0; return false; } } @@ -3539,11 +3535,6 @@ static bool vmx_get_nested_state_pages(struct kvm_vcpu *vcpu) if (!nested_get_evmcs_page(vcpu)) { pr_debug_ratelimited("%s: enlightened vmptrld failed\n", __func__); - vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; - vcpu->run->internal.suberror = - KVM_INTERNAL_ERROR_EMULATION; - vcpu->run->internal.ndata = 0; - return false; } #endif @@ -3915,8 +3906,12 @@ static int nested_vmx_run(struct kvm_vcpu *vcpu, bool launch) vmentry_failed: vcpu->arch.nested_run_pending = 0; - if (status == NVMX_VMENTRY_KVM_INTERNAL_ERROR) + if (status == NVMX_VMENTRY_KVM_INTERNAL_ERROR) { + vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; + vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_EMULATION; + vcpu->run->internal.ndata = 0; return 0; + } if (status == NVMX_VMENTRY_VMEXIT) return 1; WARN_ON_ONCE(status != NVMX_VMENTRY_VMFAIL); diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 4ec17aaff41349..aad065d035fbb1 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -8072,6 +8072,9 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) if (kvm_check_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu)) { if (unlikely(!kvm_nested_call(get_nested_state_pages)(vcpu))) { + vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; + vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_EMULATION; + vcpu->run->internal.ndata = 0; kvm_make_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu); r = 0; goto out; From 277d3623d99a4fc2623bfb7d191b649ca380605d Mon Sep 17 00:00:00 2001 From: Zeng Chi Date: Mon, 21 Sep 2026 18:24:42 +0800 Subject: [PATCH 1034/1417] KVM: Don't treat reserved xarray entries as having memory attributes kvm_vm_set_mem_attributes() reserves an xarray entry for every gfn in the range before storing the new attributes, so that the store loop can't fail partway through. If one of the reservations fails, e.g. with -ENOMEM, the entries that were already reserved are left in the array. That is harmless as far as xa_reserve() is concerned, as the reserved entries read back as NULL via xa_load(), but it confuses the "does this range have no attributes at all" check: if (!attrs) return !xas_find(&xas, end - 1); A reserved entry is XA_ZERO_ENTRY, not NULL, and xas_find() returns it as present. So a leftover reservation makes KVM report that a fully shared range has attributes even though kvm_get_memory_attributes() returns none for every gfn in the range. On x86, the next time mixed-attribute tracking is recomputed for the range (memslot creation, or a later attribute change that straddles the 2MiB page), hugepage_has_attrs() treats a fully shared 2MiB range as mixed and refuses to map it with a hugepage, until userspace happens to set attributes on the range again. Drop the shortcut and handle the !attrs case in the per-index loop, using xas_next_entry() to find the next non-NULL entry. xas_next_entry() is essentially an optimized xas_find(), so the effective change is that the !attrs lookup now goes through xas_retry() like the attrs != 0 case, i.e. reserved entries are skipped and retry entries restart the walk. Don't check the index when no entry is found, as the xarray leaves the xas index in a bogus state in that case; no entry simply means the rest of the range has no attributes. KVM never stores a non-NULL entry with a value of zero (clearing stores NULL), but such an entry would be returned by xas_next_entry() and trip the index check, so WARN if one is ever seen. Fixes: 5a475554db1e ("KVM: Introduce per-page memory attributes") Cc: stable@vger.kernel.org Suggested-by: Sean Christopherson Cc: David Ballesteros Signed-off-by: Zeng Chi Link: https://patch.msgid.link/20260921102442.1232375-1-zeng_chi911@163.com [sean: expand comment to elaborate on xarray APIs, split optimization out] Signed-off-by: Sean Christopherson --- virt/kvm/kvm_main.c | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 65eb26a0520d88..2a046e95f95ea4 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -2447,14 +2447,36 @@ bool kvm_range_has_memory_attributes(struct kvm *kvm, gfn_t start, gfn_t end, return (kvm_get_memory_attributes(kvm, start) & mask) == attrs; guard(rcu)(); - if (!attrs) - return !xas_find(&xas, end - 1); + /* + * Lookup the entry for each index instead of iterating over the xarray + * as KVM deletes/nullifies entries to represent "no attributes", and + * the xas index is effectively invalid when no entry is found. I.e. + * matching non-zero attributes for *every* entry effectively requires + * a manually lookup for each index. + * + * Skip pre-allocated, reserved entries, or restart the lookup if the + * xarray was concurrently modified, via xas_retry() ("retry" means the + * entry holds an internal xarray value, i.e. is either invalid or NULL + * from the caller's perspective). + * + * Use xas_next() when looking for non-zero attributes to optimize for + * the case where the start of the range (or the entire range) doesn't + * have any attributes, as xas_next() returns literally the next entry, + * whereas xas_next_entry() returns the next non-NULL entry (bounded by + * a maximum index). + */ for (index = start; index < end; index++) { do { - entry = xas_next(&xas); + entry = attrs ? xas_next(&xas) : + xas_next_entry(&xas, end - 1); } while (xas_retry(&xas, entry)); + if (!entry) + return !attrs; + + WARN_ON_ONCE(!xa_to_value(entry)); + if (xas.xa_index != index || (xa_to_value(entry) & mask) != attrs) return false; From 382e5d514b6f35bdda2ab9044b4eed23d2ec4254 Mon Sep 17 00:00:00 2001 From: David Ballesteros Date: Tue, 15 Sep 2026 17:53:57 +0000 Subject: [PATCH 1035/1417] KVM: Ensure memory attributes xarray nodes are accounted to the caller's memcg Explicitly instantiate the memory attributes xarray with XA_FLAGS_ACCOUNT to ensure that all allocations are accounted to the memcg. Frustratingly, memory allocations done in the "fastpath" do not honor the passed in gfp, even for an explicit xa_reserve(). Only the rare, slow path __xas_nomem() honors the original gfp. E.g. xa_reserve(..., GFP_KERNEL_ACCOUNT) | -> ... | -> __xa_cmpxchg_raw() | -> xas_store() <== does not take @gfp | -> xas_create() | -> xas_alloc() The bug was confirmed by observing that a process in a cgroup limited to 256 MiB grew radix_tree_node slab by ~512 MiB while its memory.current stayed near 0. Fixes: 5a475554db1e ("KVM: Introduce per-page memory attributes") Cc: stable@vger.kernel.org Assisted-by: Claude-Code:claude-opus-5 Signed-off-by: David Ballesteros Link: https://patch.msgid.link/20260915175335.138547-4-davimaba.v@proton.me [sean: rewrite changelog, tag for stable] Signed-off-by: Sean Christopherson --- virt/kvm/kvm_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 2a046e95f95ea4..df643ec3ea75a6 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -1117,7 +1117,7 @@ static struct kvm *kvm_create_vm(unsigned long type, const char *fdname) rcuwait_init(&kvm->mn_memslots_update_rcuwait); xa_init(&kvm->vcpu_array); #ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES - xa_init(&kvm->mem_attr_array); + xa_init_flags(&kvm->mem_attr_array, XA_FLAGS_ACCOUNT); #endif INIT_LIST_HEAD(&kvm->gpc_list); From 119e9db233ad0f4cbd4cfb9f9385a7bca378b37d Mon Sep 17 00:00:00 2001 From: Zeng Chi Date: Mon, 21 Sep 2026 18:24:42 +0800 Subject: [PATCH 1036/1417] KVM: Don't pre-reserve xarray entries when storing empty/NULL attributes Skip the xarray reservation loop when clearing all memory attributes, as storing NULL only erases the entry and never needs to allocate, so no reservation (and no cleanup of a failed one) is required in that case. Suggested-by: Sean Christopherson Cc: David Ballesteros Signed-off-by: Zeng Chi Link: https://patch.msgid.link/20260921102442.1232375-1-zeng_chi911@163.com [sean: split to separate patch] Signed-off-by: Sean Christopherson --- virt/kvm/kvm_main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index df643ec3ea75a6..85f42289748dc6 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -2593,9 +2593,10 @@ static int kvm_vm_set_mem_attributes(struct kvm *kvm, gfn_t start, gfn_t end, /* * Reserve memory ahead of time to avoid having to deal with failures - * partway through setting the new attributes. + * partway through setting the new attributes. Storing NULL never + * allocates, so no reservations are needed when clearing. */ - for (i = start; i < end; i++) { + for (i = start; entry && i < end; i++) { r = xa_reserve(&kvm->mem_attr_array, i, GFP_KERNEL_ACCOUNT); if (r) goto out_unlock; From 141008dec73521ccf64878517460cec8b3297251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Aceda=C5=84ski?= Date: Wed, 16 Sep 2026 19:30:30 +0200 Subject: [PATCH 1037/1417] drm/xe: Limit sg segment size to PAGE_SIZE on Xen PV MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix display corruption on Xen PV dom0, where DMA buffers are not guaranteed machine-contiguous, in which case bounce buffering kicks in, breaking xe's memory coherency assumptions. Apply the same workaround i915 carries in i915_sg_segment_size() since commit 78a07fe777c4 ("drm/i915: stop abusing swiotlb_max_segment"). Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Reported-by: Marek Marczykowski-Górecki Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/8382 Link: https://lore.kernel.org/xen-devel/aYtznP_tT6xNPwf-@mail-itl/ Link: https://lore.kernel.org/all/20221020110308.1582518-1-hch@lst.de/ # i915 counterpart Cc: Christoph Hellwig Cc: Robert Beckett Cc: stable@vger.kernel.org # v6.8+ Signed-off-by: Szymon Acedański Reviewed-by: Thomas Hellström Signed-off-by: Thomas Hellström Link: https://patch.msgid.link/20260916173030.3223833-1-accek@invisiblethingslab.com (cherry picked from commit 77f704158f099b952681f207478a22d5b8218edb) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_bo.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h index e8081af5bfc18e..152bfcffe39959 100644 --- a/drivers/gpu/drm/xe/xe_bo.h +++ b/drivers/gpu/drm/xe/xe_bo.h @@ -9,6 +9,8 @@ #include #include +#include + #include "xe_bo_types.h" #include "xe_ggtt.h" #include "xe_macros.h" @@ -575,6 +577,23 @@ static inline unsigned int xe_sg_segment_size(struct device *dev) struct scatterlist __maybe_unused sg; size_t max = BIT_ULL(sizeof(sg.length) * 8) - 1; + /* + * For Xen PV guests pages aren't contiguous in DMA (machine) address + * space. The DMA API takes care of that both in dma_alloc_* (by + * calling into the hypervisor to make the pages contiguous) and in + * dma_map_* (by bounce buffering). But xe (like i915, see commit + * 78a07fe777c4) ignores the coherency aspects of the DMA API and thus + * can't cope with bounce buffering actually happening, so add a hack + * here to force small allocations and mappings when running in PV + * mode on Xen. + * + * Note this will still break if bounce buffering is required for other + * reasons, like confidential computing hypervisors or PCIe root ports + * with addressing limitations. + */ + if (xen_pv_domain()) + return PAGE_SIZE; + max = min_t(size_t, max, dma_max_mapping_size(dev)); /* From 90f467577ebc28c5bc4ba2f0f1b83a4419fb0740 Mon Sep 17 00:00:00 2001 From: Tangudu Tilak Tirumalesh Date: Wed, 16 Sep 2026 15:35:44 +0530 Subject: [PATCH 1038/1417] drm/xe: harden adjust_idledly() against divide-by-zero and overflow adjust_idledly() has several corner-case issues flagged during review: 1. If xe_gt_clock_init() failed to recognise the crystal clock, gt->info.timestamp_base is 0, which makes idledly_units_ps also 0. The subsequent DIV_ROUND_CLOSEST(..., idledly_units_ps) is then a divide-by-zero and panics the kernel. 2. The tick-to-ns conversions are done in u32: idledly * idledly_units_ps, (maxcnt - 1) * 1000 Both overflow u32 before DIV_ROUND_CLOSEST() sees them. 3. If IDLE_WAIT_TIME reads back as 0, maxcnt evaluates to 0 and the maxcnt - 1 clamp wraps to 0xFFFFFFFF in u32. 4. The register only stores whole ticks, so the clamped ns value has to be converted to ticks and back. DIV_ROUND_CLOSEST() can round that conversion up past maxcnt: maxcnt = 640 ns, one tick = 666664 ps clamp: maxcnt - 1 = 639 ns ns -> ticks: 639000 / 666664 = 0.958 -> rounds to 1 tick tick -> ns: 1 * 666664 / 1000 = 667 ns 667 ns is programmed into RING_IDLEDLY, but 667 >= maxcnt (640), so xe_gt_WARN_ON() fires again on every subsequent init. Return early if timestamp_base is 0 (the unknown-crystal path). Do the conversions in u64 via the *_ULL() helpers so they cannot wrap. Clamp with a floor (DIV_ROUND_DOWN_ULL) so the programmed delay stays strictly below maxcnt, and guard the maxcnt == 0 case with a zero delay while still writing RING_IDLEDLY so INHIBIT_SWITCH_UNTIL_PREEMPTED is cleared. v2: Drop the redundant warn on the timestamp_base == 0 path; xe_gt_clock_init() already warns on an unrecognised crystal clock. Keep the early return to avoid the divide-by-zero. - Vinay v3: Field-mask the RING_IDLEDLY write with REG_FIELD_PREP(IDLE_DELAY, ...) instead of writing the raw tick count, which could clobber INHIBIT_SWITCH_UNTIL_PREEMPTED and reserved bits. Split the inhibit-switch clear from the maxcnt clamp so a set inhibit bit no longer forces a needless delay overwrite when the delay itself is already valid. Use gt_to_xe(gt) instead of gt_to_xe(hwe->gt). Fixes: d2de4410a88f ("drm/xe: Apply Wa_16023105232") Cc: stable@vger.kernel.org Assisted-by: GitHub_Copilot:claude-opus-4.8 Signed-off-by: Tangudu Tilak Tirumalesh Reviewed-by: Vinay Belgaumkar Link: https://patch.msgid.link/20260916100545.779894-2-tilak.tirumalesh.tangudu@intel.com Signed-off-by: Matt Roper (cherry picked from commit d864065ea25e9d12897c175de9176ce46677e176) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_hw_engine.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_hw_engine.c b/drivers/gpu/drm/xe/xe_hw_engine.c index 010499766fce28..d868af8e330197 100644 --- a/drivers/gpu/drm/xe/xe_hw_engine.c +++ b/drivers/gpu/drm/xe/xe_hw_engine.c @@ -592,22 +592,37 @@ static void adjust_idledly(struct xe_hw_engine *hwe) u32 idledly_units_ps = 8 * gt->info.timestamp_base; u32 maxcnt_units_ns = 640; bool inhibit_switch = 0; + bool wa_applied = false; + + if (!IS_SRIOV_VF(gt_to_xe(gt)) && XE_GT_WA(gt, 16023105232)) { + /* xe_gt_clock_init() warns and zeroes timestamp_base on unknown crystal clock. */ + if (!idledly_units_ps) + return; - if (!IS_SRIOV_VF(gt_to_xe(hwe->gt)) && XE_GT_WA(gt, 16023105232)) { idledly = xe_mmio_read32(>->mmio, RING_IDLEDLY(hwe->mmio_base)); maxcnt = xe_mmio_read32(>->mmio, RING_PWRCTX_MAXCNT(hwe->mmio_base)); inhibit_switch = idledly & INHIBIT_SWITCH_UNTIL_PREEMPTED; idledly = REG_FIELD_GET(IDLE_DELAY, idledly); - idledly = DIV_ROUND_CLOSEST(idledly * idledly_units_ps, 1000); + idledly = DIV_ROUND_CLOSEST_ULL((u64)idledly * idledly_units_ps, 1000); maxcnt = REG_FIELD_GET(IDLE_WAIT_TIME, maxcnt); maxcnt *= maxcnt_units_ns; - if (xe_gt_WARN_ON(gt, idledly >= maxcnt || inhibit_switch)) { - idledly = DIV_ROUND_CLOSEST(((maxcnt - 1) * 1000), - idledly_units_ps); - xe_mmio_write32(>->mmio, RING_IDLEDLY(hwe->mmio_base), idledly); + /* Clear the inhibit switch without disturbing a valid delay. */ + if (inhibit_switch) + wa_applied = true; + + if (xe_gt_WARN_ON(gt, idledly >= maxcnt)) { + /* Floor below maxcnt; write 0 to still clear the inhibit bit. */ + idledly = maxcnt ? + DIV_ROUND_DOWN_ULL((u64)(maxcnt - 1) * 1000, + idledly_units_ps) : 0; + wa_applied = true; } + + if (wa_applied) + xe_mmio_write32(>->mmio, RING_IDLEDLY(hwe->mmio_base), + REG_FIELD_PREP(IDLE_DELAY, idledly)); } } From bd3a19800dd1ba1ba9a4c307d9dfe50eac443c01 Mon Sep 17 00:00:00 2001 From: Tingmao Wang Date: Sun, 8 Feb 2026 23:54:48 +0000 Subject: [PATCH 1039/1417] landlock: Add counted_by in landlock_domain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For a domain, this array stores the access masks for each layer (of which there are num_layers of them). This annotation serves as useful documentation. Signed-off-by: Tingmao Wang Reviewed-by: Günther Noack Link: https://patch.msgid.link/20260208235449.1124354-1-m@maowtm.org [mic: Rebase on the ruleset/domain split, and update commit message] Signed-off-by: Mickaël Salaün --- security/landlock/domain.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/landlock/domain.h b/security/landlock/domain.h index 5ce2f91488d5d0..caa3d19d2c4321 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -243,7 +243,8 @@ struct landlock_domain { * overlapping access rights. These layers are set once * and never changed for the lifetime of the domain. */ - struct access_masks handled_masks[]; + struct access_masks + handled_masks[] __counted_by(num_layers); }; }; }; From e7e0a54300a896e731dffd3e2e8dae5631d6243d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Tue, 22 Sep 2026 15:26:14 +0200 Subject: [PATCH 1040/1417] landlock: Widen ruleset versions to 64 bits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tracepoint consumers use a ruleset ID and version to identify the successful landlock_add_rule(2) call prefix used to create a domain. LANDLOCK_MAX_NUM_RULES bounds distinct stored rules, not successful calls: re-adding already-present rights for an object or port succeeds without increasing num_rules. Because every successful call increments the version, these calls can wrap the 32-bit counter and give different prefixes the same trace identity. Widen the counter and its trace fields to 64 bits so the counter cannot wrap in practice, while preserving the successful-call semantics. Saturating would alias all subsequent histories, while rejecting a call at the limit would change otherwise valid syscall behavior solely for trace metadata. Cc: Günther Noack Cc: Steven Rostedt Fixes: 63747c94774d ("landlock: Add landlock_add_rule_fs and landlock_add_rule_net tracepoints") Reviewed-by: Günther Noack Link: https://patch.msgid.link/20260922132615.1025945-1-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 20 ++++++++++---------- security/landlock/ruleset.h | 5 +++-- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index 523ba5ea987000..3a43638c9bc290 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -344,7 +344,7 @@ TRACE_EVENT(landlock_create_ruleset, TP_STRUCT__entry( __field( u64, ruleset_id ) - __field( u32, ruleset_version ) + __field( u64, ruleset_version ) __field( access_mask_t, handled_fs ) __field( access_mask_t, handled_net ) __field( access_mask_t, scoped ) @@ -358,7 +358,7 @@ TRACE_EVENT(landlock_create_ruleset, __entry->scoped = ruleset->handled_masks.scope; ), - TP_printk("ruleset=%llx.%u handled_fs=%s handled_net=%s scoped=%s", + TP_printk("ruleset=%llx.%llu handled_fs=%s handled_net=%s scoped=%s", __entry->ruleset_id, __entry->ruleset_version, __print_flags(__entry->handled_fs, "|", _LANDLOCK_ACCESS_FS_NAMES), __print_flags(__entry->handled_net, "|", _LANDLOCK_ACCESS_NET_NAMES), @@ -384,7 +384,7 @@ TRACE_EVENT(landlock_free_ruleset, TP_STRUCT__entry( __field( u64, ruleset_id ) - __field( u32, ruleset_version ) + __field( u64, ruleset_version ) ), TP_fast_assign( @@ -392,7 +392,7 @@ TRACE_EVENT(landlock_free_ruleset, __entry->ruleset_version = ruleset->version; ), - TP_printk("ruleset=%llx.%u", + TP_printk("ruleset=%llx.%llu", __entry->ruleset_id, __entry->ruleset_version) ); @@ -423,7 +423,7 @@ TRACE_EVENT(landlock_add_rule_path_beneath, TP_STRUCT__entry( __field( u64, ruleset_id ) - __field( u32, ruleset_version ) + __field( u64, ruleset_version ) __field( access_mask_t, access_rights ) __field( dev_t, dev ) __field( ino_t, ino ) @@ -444,7 +444,7 @@ TRACE_EVENT(landlock_add_rule_path_beneath, __assign_str(pathname); ), - TP_printk("ruleset=%llx.%u access_rights=%s dev=%u:%u ino=%lu path=%s", + TP_printk("ruleset=%llx.%llu access_rights=%s dev=%u:%u ino=%lu path=%s", __entry->ruleset_id, __entry->ruleset_version, __print_flags(__entry->access_rights, "|", _LANDLOCK_ACCESS_FS_NAMES), MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, @@ -477,7 +477,7 @@ TRACE_EVENT(landlock_add_rule_net_port, TP_STRUCT__entry( __field( u64, ruleset_id ) - __field( u32, ruleset_version ) + __field( u64, ruleset_version ) __field( access_mask_t, access_rights ) __field( u64, port ) ), @@ -490,7 +490,7 @@ TRACE_EVENT(landlock_add_rule_net_port, __entry->port = port; ), - TP_printk("ruleset=%llx.%u access_rights=%s port=%llu", + TP_printk("ruleset=%llx.%llu access_rights=%s port=%llu", __entry->ruleset_id, __entry->ruleset_version, __print_flags(__entry->access_rights, "|", _LANDLOCK_ACCESS_NET_NAMES), __entry->port) @@ -526,7 +526,7 @@ TRACE_EVENT(landlock_create_domain, __field( u64, domain_id ) __field( u64, parent_id ) __field( u64, ruleset_id ) - __field( u32, ruleset_version ) + __field( u64, ruleset_version ) ), TP_fast_assign( @@ -538,7 +538,7 @@ TRACE_EVENT(landlock_create_domain, __entry->ruleset_version = ruleset->version; ), - TP_printk("domain=%llx parent=%llx ruleset=%llx.%u", + TP_printk("domain=%llx parent=%llx ruleset=%llx.%llu", __entry->domain_id, __entry->parent_id, __entry->ruleset_id, __entry->ruleset_version) ); diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index b536fa0425b7a5..cf77f1806a9505 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -171,9 +171,10 @@ struct landlock_ruleset { * @version: Counter incremented on each successful * landlock_add_rule(2), including when it only extends an existing * rule's access rights. Used by tracepoints to correlate a domain with - * the exact ruleset state it was created from. Protected by @lock. + * the exact successful rule history it was created from. Protected by + * @lock. */ - u32 version; + u64 version; /** * @id: Unique identifier for this ruleset, used for tracing. */ From 41112a787f9182c7f2d27122817861e3f22ef928 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 20 Sep 2026 16:35:25 +0200 Subject: [PATCH 1041/1417] PM: hibernate: Freeze kernel threads after image preallocation Commit 783c81098445 ("PM: hibernate: call preallocate_image() after freeze prepare") moved hibernate_preallocate_memory() after dpm_prepare() so that device drivers have the opportunity to release pinned/unswappable memory during their ->prepare() callback before memory is preallocated for the snapshot image. However, that commit also placed hibernate_preallocate_memory() after freeze_kernel_threads(). While it was assumed during review that swap I/O submitted via submit_bio() is synchronous and would not depend on frozen kernel threads, this does not hold in practice. Calling hibernate_preallocate_memory() with kernel threads frozen leads to intermittent deadlocks during hibernation. Inside hibernate_preallocate_memory(), shrink_all_memory() is invoked with .may_writepage = 1 and .may_swap = 1 to aggressively reclaim and swap out pages. Any writeback or swap I/O that relies on freezable kernel threads, block device helpers, or WQ_FREEZABLE workqueues (such as those in storage drivers, device mapper, or filesystems) deadlocks waiting on tasks that are stuck in the refrigerator. Fix this by reordering hibernation_snapshot(): 1. Call dpm_prepare(PMSG_FREEZE) first, allowing device drivers to release pinned resources while kernel threads are still active. 2. Call hibernate_preallocate_memory() second, performing page reclaim and swapout while storage layers, workqueues, and kernel threads are alive. 3. Call freeze_kernel_threads() third, only after all memory preallocation and swap I/O have completed. Additionally, restore the call to swsusp_free() in the cleanup path so that preallocated image memory is properly freed if freeze_kernel_threads() fails or if TEST_FREEZER is enabled. Fixes: 783c81098445 ("PM: hibernate: call preallocate_image() after freeze prepare") Signed-off-by: Florian Schmaus Reviewed-by: Mario Limonciello (AMD) Tested-by: Matthew Leach Reviewed-by: Matthew Leach Link: https://patch.msgid.link/20260920-fix-hibernation-v1-1-f9940c2d7d7f@geekplace.eu Signed-off-by: Rafael J. Wysocki --- kernel/power/hibernate.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index d2479c69d71a4c..c13f68ab7f6e05 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -408,9 +408,18 @@ int hibernation_snapshot(int platform_mode) if (error) goto Close; + error = dpm_prepare(PMSG_FREEZE); + if (error) + goto Complete; + + /* Preallocate image memory before freezing kernel threads and shutting down devices. */ + error = hibernate_preallocate_memory(); + if (error) + goto Complete; + error = freeze_kernel_threads(); if (error) - goto Close; + goto Cleanup; if (hibernation_test(TEST_FREEZER)) { @@ -422,15 +431,6 @@ int hibernation_snapshot(int platform_mode) goto Thaw; } - error = dpm_prepare(PMSG_FREEZE); - if (error) - goto Complete; - - /* Preallocate image memory before shutting down devices. */ - error = hibernate_preallocate_memory(); - if (error) - goto Complete; - console_suspend_all(); pm_restrict_gfp_mask(); @@ -464,10 +464,12 @@ int hibernation_snapshot(int platform_mode) platform_end(platform_mode); return error; - Complete: - dpm_complete(PMSG_RECOVER); Thaw: thaw_kernel_threads(); + Cleanup: + swsusp_free(); + Complete: + dpm_complete(PMSG_RECOVER); goto Close; } From ec0d89150a9381d591344a9f6f5428655c227a7f Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Tue, 22 Sep 2026 17:49:02 +0530 Subject: [PATCH 1042/1417] thermal: gov_step_wise: Fix stale mitigation vote with non-zero lower bounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When two or more thermal zones bind to a common cooling device and one zone uses a non-zero instance->lower value, there is a bug where the instance holds a stale mitigation vote even after its trip is cleared. Problem scenario: - thermal-zone1: Trip at 50°C, cooling-map with lower=0 - thermal-zone2: Trip at 55°C, cooling-map with lower=2 - Both zones share the same cooling device (e.g., CPU) Issue flow: 1. Both trips trigger, zone1 requests state 5, zone2 also mitigates 2. Zone2 trip clears (temp < 53°C due to hysteresis) 3. When throttle=false and trend=THERMAL_TREND_DROPPING: - Current code checks: if (cur_state <= instance->lower) return THERMAL_NO_TARGET - Since cur_state (5) > instance->lower (2), it returns instance->lower (2) - This is the BUG where it returns instance->lower even though trip is cleared 4. Zone2's passive polling stops (tz->passive reaches 0) - no more updates for zone2 5. Zone2's stale vote of 2 persists indefinitely 6. Even when zone1 wants to reduce cooling to state, the cooling device cannot go below state 2 due to zone2's stale vote When a trip is cleared (throttle == false), always return THERMAL_NO_TARGET instead of instance->lower. Remove the unnecessary check comparing cur_state with instance->lower. Since passive polling is already deactivated when the trip is cleared, the instance should always be deactivated regardless of its current cooling state. This ensures that instances with non-zero lower bounds do not retain stale mitigation votes after their trips are cleared. Fixes: 042a3d80f118 ("thermal: core: Move passive polling management to the core") Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://patch.msgid.link/20260922-step_wise_multi_zone_stale_vote_fix-v1-1-789f68dab229@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/gov_step_wise.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/gov_step_wise.c b/drivers/thermal/gov_step_wise.c index ea277c466d8d2e..4fa4377f0d4286 100644 --- a/drivers/thermal/gov_step_wise.c +++ b/drivers/thermal/gov_step_wise.c @@ -65,14 +65,12 @@ static unsigned long get_target_state(struct thermal_instance *instance, min(instance->lower + 1, instance->upper), instance->upper); } else if (trend == THERMAL_TREND_DROPPING) { - if (cur_state <= instance->lower) - return THERMAL_NO_TARGET; - /* - * If 'throttle' is false, no mitigation is necessary, so - * request the lower state for this instance. + * If 'throttle' is false, no mitigation is necessary and + * passive polling is already deactivated, so clear this + * instance state by returning THERMAL_NO_TARGET. */ - return instance->lower; + return THERMAL_NO_TARGET; } return instance->target; From ed6eec97b534979dcf28b40c389cee57bd6561d4 Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:18 +0000 Subject: [PATCH 1043/1417] bpf: Fix bounds check for skb-backed dynptrs The skb_pointer_if_linear() function checks whether a memory region of length len starting at offset off into the skb is in the linear area, and returns a pointer to the region if so. The check currently subtracts between skb_headlen and offset of the check, and since skb_headlen is unsigned the subtraction can underflow. This causes the bounds check to spuriously pass and generate an arbitrary pointer of the form *(skb->data + off). The only user of this helper is currently skb-backed BPF dynptr code. Returning the wrong pointer leads to the dynptr erroneously being backed with invalid memory. Ensure the subtraction cannot underflow, and fail the check if it would. Use u64 arithmetic to also prevent overflow when calculating (skb_headlen(skb) - off) since off is unsigned. Fixes: 6f5a630d7c57 ("bpf, net: Introduce skb_pointer_if_linear().") Reported-by: Nicholas Carlini Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20260922172028.6269-2-emil@etsalapatis.com --- include/linux/skbuff.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 421f6fc454511e..c8e21903074c3a 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -4372,7 +4372,10 @@ skb_header_pointer_careful(const struct sk_buff *skb, int offset, static inline void * __must_check skb_pointer_if_linear(const struct sk_buff *skb, int offset, int len) { - if (likely(skb_headlen(skb) - offset >= len)) + unsigned int uoffset = (unsigned int)offset; + + if (likely(uoffset <= skb_headlen(skb) && + (unsigned int)len <= skb_headlen(skb) - uoffset)) return skb->data + offset; return NULL; } From 4fd72eb9f1c939ce33bb714c6f745a125ac5f40c Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:19 +0000 Subject: [PATCH 1044/1417] selftests/bpf: Test dynptr slices past end of skb Add a selftest to ensure dynptr slices cannot include past the end of the linear area of an skb. Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-3-emil@etsalapatis.com --- .../testing/selftests/bpf/prog_tests/dynptr.c | 10 ++++++++++ .../selftests/bpf/progs/dynptr_success.c | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/dynptr.c b/tools/testing/selftests/bpf/prog_tests/dynptr.c index 5fda1159070822..4396560365e88d 100644 --- a/tools/testing/selftests/bpf/prog_tests/dynptr.c +++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c @@ -9,6 +9,7 @@ enum test_setup_type { SETUP_SYSCALL_SLEEP, SETUP_SKB_PROG, + SETUP_SKB_PROG_NONLINEAR, SETUP_SKB_PROG_TP, SETUP_XDP_PROG, }; @@ -32,6 +33,7 @@ static struct { {"test_ringbuf", SETUP_SYSCALL_SLEEP}, {"test_skb_readonly", SETUP_SKB_PROG}, {"test_dynptr_skb_data", SETUP_SKB_PROG}, + {"test_dynptr_skb_slice_non_linear", SETUP_SKB_PROG_NONLINEAR}, {"test_dynptr_skb_meta_data", SETUP_SKB_PROG}, {"test_dynptr_skb_meta_flags", SETUP_SKB_PROG}, {"test_adjust", SETUP_SYSCALL_SLEEP}, @@ -94,7 +96,9 @@ static void verify_success(const char *prog_name, enum test_setup_type setup_typ bpf_link__destroy(link); break; case SETUP_SKB_PROG: + case SETUP_SKB_PROG_NONLINEAR: { + struct __sk_buff ctx = {}; int prog_fd; char buf[64]; @@ -106,6 +110,12 @@ static void verify_success(const char *prog_name, enum test_setup_type setup_typ .repeat = 1, ); + if (setup_type == SETUP_SKB_PROG_NONLINEAR) { + ctx.data_end = ETH_HLEN + sizeof(struct iphdr); + topts.ctx_in = &ctx; + topts.ctx_size_in = sizeof(ctx); + } + prog_fd = bpf_program__fd(prog); if (!ASSERT_GE(prog_fd, 0, "prog_fd")) goto cleanup; diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c index e0745b6e467ea9..b668ebd61fc793 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_success.c +++ b/tools/testing/selftests/bpf/progs/dynptr_success.c @@ -10,6 +10,7 @@ #include "errno.h" #define PAGE_SIZE_64K 65536 +#define TEST_SKB_LINEAR_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr)) char _license[] SEC("license") = "GPL"; @@ -211,6 +212,25 @@ int test_dynptr_skb_data(struct __sk_buff *skb) return 1; } +SEC("?tc") +int test_dynptr_skb_slice_non_linear(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr; + void *data; + + if (bpf_dynptr_from_skb(skb, 0, &ptr)) { + err = 1; + return 1; + } + + /* Ensure we cannot read past the end of the buffer. */ + data = bpf_dynptr_slice(&ptr, TEST_SKB_LINEAR_SIZE + 1, NULL, 1); + if (data) + err = 2; + + return 1; +} + SEC("?tc") int test_dynptr_skb_meta_data(struct __sk_buff *skb) { From 4a4852376e3a2727ea40e61143d6d7c22bb6dfad Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:20 +0000 Subject: [PATCH 1045/1417] bpf: Fix bpf_sock context code generation Currently, the ctx access code reads the rx_queue_mapping field with either a 4-byte or 2-byte load. The rest of the bits in the register are marked known zero by the verifier. However, the emitted ctx access code places in the register on certain the special value (-1) using BPF_MOV_IMM64, which gets sign-extended to turn on all the bits in the register. By shifting this value right, the program ends up with a value at runtime above what the verifier assumes is possible. Fix this by ensuring the read value is as wide as the assumed size. Use MOV32 instructions instead of MOV64 instructions to keep the upper bits zero as assumed by the verifier. Also properly report the size of the destination variable (the bpf_sock field, 4 bytes) instead of the source (the socket field, 2 bytes). Fixes: c3c16f2ea6d2 ("bpf: Add rx_queue_mapping to bpf_sock") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20260922172028.6269-4-emil@etsalapatis.com --- net/core/filter.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 532405988fd98b..70dc621672f2e9 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -10566,11 +10566,12 @@ u32 bpf_sock_convert_ctx_access(enum bpf_access_type type, target_size)); *insn++ = BPF_JMP_IMM(BPF_JNE, si->dst_reg, NO_QUEUE_MAPPING, 1); - *insn++ = BPF_MOV64_IMM(si->dst_reg, -1); + *insn++ = BPF_MOV32_IMM(si->dst_reg, -1); #else - *insn++ = BPF_MOV64_IMM(si->dst_reg, -1); - *target_size = 2; + *insn++ = BPF_MOV32_IMM(si->dst_reg, -1); #endif + *target_size = sizeof_field(struct bpf_sock, rx_queue_mapping); + break; } From dec0c209a6808fe6de2e4787538e02786a16a4ef Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:21 +0000 Subject: [PATCH 1046/1417] selftests/bpf: Add selftests for rx_queue_mapping context access Add tests to ensure the verifier properly tracks the 0 bit state and width of the rx_queue_mapping field read from struct sock. Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-5-emil@etsalapatis.com --- .../selftests/bpf/progs/verifier_sock.c | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_sock.c b/tools/testing/selftests/bpf/progs/verifier_sock.c index 4f2f3209eec818..bf9f6fb6582ca4 100644 --- a/tools/testing/selftests/bpf/progs/verifier_sock.c +++ b/tools/testing/selftests/bpf/progs/verifier_sock.c @@ -88,6 +88,44 @@ l0_%=: r0 = *(u32*)(r1 + %[bpf_sock_family]); \ : __clobber_all); } +SEC("socket") +__description("skb->sk: sk->rx_queue_mapping [no sign extension]") +__success __success_unpriv __retval(0) +__naked void sk_rx_queue_mapping_no_sign_ext(void) +{ + asm volatile (" \ + r1 = *(u64*)(r1 + %[__sk_buff_sk]); \ + if r1 != 0 goto l0_%=; \ + r0 = 0xdead; \ + exit; \ +l0_%=: r0 = *(u32*)(r1 + %[bpf_sock_rx_queue_mapping]); \ + r0 >>= 32; \ + exit; \ +" : + : __imm_const(__sk_buff_sk, offsetof(struct __sk_buff, sk)), + __imm_const(bpf_sock_rx_queue_mapping, offsetof(struct bpf_sock, rx_queue_mapping)) + : __clobber_all); +} + +SEC("socket") +__description("skb->sk: sk->rx_queue_mapping [narrow load mask]") +__success __success_unpriv __retval(0) +__naked void sk_rx_queue_mapping_narrow_load_mask(void) +{ + asm volatile (" \ + r1 = *(u64*)(r1 + %[__sk_buff_sk]); \ + if r1 != 0 goto l0_%=; \ + r0 = 0xdead; \ + exit; \ +l0_%=: r0 = *(u16*)(r1 + %[bpf_sock_rx_queue_mapping]); \ + r0 >>= 16; \ + exit; \ +" : + : __imm_const(__sk_buff_sk, offsetof(struct __sk_buff, sk)), + __imm_const(bpf_sock_rx_queue_mapping, offsetof(struct bpf_sock, rx_queue_mapping)) + : __clobber_all); +} + SEC("cgroup/skb") __description("skb->sk: sk->type [fullsock field]") __failure __msg("invalid sock_common access") From a6c1edfbe240e4377038a0e1d233ad81fde9a21b Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:22 +0000 Subject: [PATCH 1047/1417] bpf: Reject pkt arguments in mutating subprogs The verifier tracks changes in how PTR_TO_PACKET registers' bounds are modified across subprog boundaries. PTR_TO_PACKET registers are actually passed as PTR_TO_MEM, which is assumed valid for the entire call. This is not the case with packet memory, where a pskb_* call may invalidate its memory region. Reject BPF code that passes PTR_TO_PACKET pointers to subprogs that may mutate a packet. We cannot pass the pointer as a true PTR_TO_PACKET because we would also need to somehow pass the PTR_TO_PACKET_META or PTR_TO_PACKET_END to the subprog. Since we cannot avoid representing the pointer in the subprog as PTR_TO_MEM, only permit it if the subprog is guaranteed not to mutate the packet. Fixes: 80f281664f5a ("bpf: Support pointers in global func args") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-6-emil@etsalapatis.com --- kernel/bpf/verifier.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index dd8bb179d39eb9..7ffbb804184f68 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9750,6 +9750,16 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog, if (check_mem_reg(env, reg, argno, arg->mem_size, BPF_READ | BPF_WRITE, NULL, NULL)) return -EINVAL; + /* + * PTR_TO_PACKET get passed as PTR_TO_MEM, preventing + * us from adjusting bounds tracking info. + */ + if ((reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg)) && + sub->changes_pkt_data) { + bpf_log(log, "%s is a packet pointer, but func#%d may change packet data\n", + reg_arg_name(env, argno), subprog); + return -EINVAL; + } if (!(arg->arg_type & PTR_MAYBE_NULL) && (type_may_be_null(reg->type) || bpf_register_is_null(reg))) { bpf_log(log, "%s is expected to be non-NULL\n", From 1ed69a54d31848618131aaf3311a78adbe78ced0 Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:23 +0000 Subject: [PATCH 1048/1417] selftests/bpf: Test rejection of pkt args to mutating subprogs Add a selftests that ensures that PTR_TO_PACKET arguments can only be passed to subprogs that will never adjust the underlying packet memory, and are rejected otherwise. Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-7-emil@etsalapatis.com --- .../bpf/progs/verifier_global_ptr_args.c | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c index a3d2af8dc8396b..dcc2dd46751a49 100644 --- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c +++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c @@ -350,4 +350,57 @@ int anything_to_untrusted_mem(void *ctx) return 0; } +struct pkt_arg { + __u64 x; + __u8 pad[56]; +}; + +__weak int subprog_pkt_ptr_no_change(struct pkt_arg *p) +{ + if (!p) + return 0; + + return p->x; +} + +SEC("?tc") +__success +int pkt_ptr_to_global_mem_arg_no_change(struct __sk_buff *skb) +{ + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + struct pkt_arg *p = data; + + if ((void *)(p + 1) > data_end) + return 0; + + return subprog_pkt_ptr_no_change(p); +} + +__weak int subprog_pkt_ptr_changes_data(struct __sk_buff *skb __arg_ctx, + struct pkt_arg *p) +{ + if (!p) + return 0; + + bpf_skb_pull_data(skb, 0); + return p->x; +} + +SEC("?tc") +__failure __log_level(2) +__msg("R2 is a packet pointer, but func#{{[0-9]+}} may change packet data") +__msg("Caller passes invalid args into func#{{[0-9]+}} ('subprog_pkt_ptr_changes_data')") +int pkt_ptr_to_global_mem_arg_changes_data(struct __sk_buff *skb) +{ + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + struct pkt_arg *p = data; + + if ((void *)(p + 1) > data_end) + return 0; + + return subprog_pkt_ptr_changes_data(skb, p); +} + char _license[] SEC("license") = "GPL"; From f85f5917aa2fbd861c72ea71ecb14a2fa915c3f6 Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:24 +0000 Subject: [PATCH 1049/1417] bpf: Prevent variable arena/non-arena register contents The verifier marks ALU instructions that include at least one arena operand with needs_zext: These instructions are fixed up after verification to be ALU32 instructions to ensure that the result is a valid offset into an arena. However, different code paths may provide two non-arena 64-bit arguments to the same instruction. The result of the operation in that code path is wrong, since it is now unexpectedly truncated to 32 bits and zero-extended. Add logic to the verifier to ensure every instruction either always has at least one PTR_TO_ARENA argument, or never does. Since needs_zext already tracks the first scenario, add a prevent_zext field in bpf_insn_aux to track the latter. Reject instructions that use arena arguments and have prevent_zext set, or do not have arena arguments and have needs_zext set. Fixes: 6082b6c328b5 ("bpf: Recognize addr_space_cast instruction in the verifier.") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-8-emil@etsalapatis.com --- include/linux/bpf_verifier.h | 1 + kernel/bpf/verifier.c | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 6fe8e5dc57aae8..b83ec99f1a13d5 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -680,6 +680,7 @@ struct bpf_insn_aux_data { bool nospec_result; /* result is unsafe under speculation, nospec must follow */ bool zext_dst; /* this insn zero extends dst reg */ bool needs_zext; /* alu op needs to clear upper bits */ + bool prevent_zext; /* alu op cannot be zext (already used with 64-bit scalars) */ bool non_sleepable; /* helper/kfunc may be called from non-sleepable context */ bool is_iter_next; /* bpf_iter__next() kfunc call */ bool call_with_percpu_alloc_ptr; /* {this,per}_cpu_ptr() with prog percpu alloc */ diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 7ffbb804184f68..41b49c56e1237c 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -15741,6 +15741,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, struct bpf_reg_state *regs = state->regs, *dst_reg, *src_reg; struct bpf_reg_state *ptr_reg = NULL, off_reg = {0}; bool alu32 = (BPF_CLASS(insn->code) != BPF_ALU64); + struct bpf_insn_aux_data *aux = cur_aux(env); u8 opcode = BPF_OP(insn->code); int err; @@ -15752,12 +15753,23 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, /* Case where at least one operand is an arena. */ if (dst_reg->type == PTR_TO_ARENA || (src_reg && src_reg->type == PTR_TO_ARENA)) { - struct bpf_insn_aux_data *aux = cur_aux(env); if (dst_reg->type != PTR_TO_ARENA) *dst_reg = *src_reg; if (BPF_CLASS(insn->code) == BPF_ALU64) { + /* + * Only arena pointers set needs_zext, but doing so + * modifies the instruction at fixup time to an ALU32 + * and makes it unsuitable for 64-bit scalar args. We + * prevent zext from being set if the instruction has + * been previously called with non-arena registers. + */ + if (aux->prevent_zext) { + verbose(env, "same insn cannot be used with and without arena pointer\n"); + return -EINVAL; + } + /* * 32-bit operations zero upper bits automatically. * 64-bit operations need to be converted to 32. @@ -15770,6 +15782,16 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, return 0; } + /* Prevent the instruction from being used with arena pointers (see above). */ + if (env->prog->aux->arena && BPF_CLASS(insn->code) == BPF_ALU64) { + if (aux->needs_zext) { + verbose(env, "same insn cannot be used with and without arena pointer\n"); + return -EINVAL; + } + + aux->prevent_zext = true; + } + if (dst_reg->type != SCALAR_VALUE) ptr_reg = dst_reg; From a9e86dd9de4f933b69f5cca6293fd4c8919224ec Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:25 +0000 Subject: [PATCH 1050/1417] selftests/bpf: Test for mixed arena/nonarena code paths Add a selftest to confirm the verifier rejects ALU operations that return arena or non-arena results depending on code path. Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-9-emil@etsalapatis.com --- .../selftests/bpf/progs/verifier_arena.c | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_arena.c b/tools/testing/selftests/bpf/progs/verifier_arena.c index 815f342eb4b05f..3e33766547c08a 100644 --- a/tools/testing/selftests/bpf/progs/verifier_arena.c +++ b/tools/testing/selftests/bpf/progs/verifier_arena.c @@ -561,6 +561,55 @@ int arena_ptr_add_arena_ptr(void *ctx) return 0; } +SEC("syscall") +__failure __msg("same insn cannot be used with and without arena pointer") +int mixed_arena_scalar_alu64_scalar_first(void *ctx) +{ + volatile register __u64 reg asm("r3"); + __u32 pick_arena = bpf_get_prandom_u32(); + + reg = 1ULL << 32; + + if (pick_arena) { + asm volatile ( + "r9 = %[arena] ll;" + "%[reg] = 0;" + "%[reg] = addr_space_cast(%[reg], 0x0, 0x1);" + : [reg] "=r"(reg) + : __imm_addr(arena) + : "r9" + ); + } + + reg += 1; + + return 0; +} + +SEC("syscall") +__failure __msg("same insn cannot be used with and without arena pointer") +int mixed_arena_scalar_alu64_arena_first(void *ctx) +{ + volatile register __u64 reg asm("r3"); + __u32 pick_scalar = bpf_get_prandom_u32(); + + asm volatile ( + "r9 = %[arena] ll;" + "%[reg] = 0;" + "%[reg] = addr_space_cast(%[reg], 0x0, 0x1);" + : [reg] "=r"(reg) + : __imm_addr(arena) + : "r9" + ); + + if (pick_scalar) + reg = 1ULL << 32; + + reg += 1; + + return 0; +} + SEC("syscall") __success __retval(0) int scalar_xor_arena_ptr(void *ctx) From cc319238e3f6668f867beb381ce93727c69b7317 Mon Sep 17 00:00:00 2001 From: Tangudu Tilak Tirumalesh Date: Wed, 16 Sep 2026 15:35:45 +0530 Subject: [PATCH 1051/1417] drm/xe: Add wa_14025941587 to xe2, xe3 and xe3p platforms Avoid programming the IDLEDLY timer to less than 5 microseconds. Apply wa_14025941587 to Graphics Versions 20.01 to 35.11 and Media Versions 13.01 to 35.03 v2: Use xe_rtp_match_not_sriov_vf, move to local variable Remove warn and other knits - Matt R v3: Add verbose comment - Tejas v4: Restore IDLE_DLY register on engine reset. Add it to GUC save-restore list. -Vivek v5: Extend WA to Media Versions 13.01 to 35.03 - Vinay v6: Avoid clearing inhibit switch - Bala Refactor code accordingly by adding idle_reg_val. v7: Rebased with the divide-by-zero/overflow guards living in a separate hardening patch. v8: Preserve the Wa_16023105232 floor (DIV_ROUND_DOWN_ULL) and the maxcnt == 0 guard from the hardening patch. Round up (DIV_ROUND_UP_ULL) the Wa_14025941587 minimum conversion instead, so the tick-quantized delay cannot round back below 5 us. v9: Evaluate the Wa_16023105232 xe_gt_WARN_ON() against the value read from hardware instead of the Wa_14025941587-bumped value, so it no longer fires on the driver's own floor. Re-check the rounded-up tick value against maxcnt and floor it if tick quantization pushed it back to/above maxcnt, logging via xe_gt_dbg since this is the driver's own value, not a hardware anomaly. Assisted-by: GitHub_Copilot:claude-opus-4.8 Signed-off-by: Tangudu Tilak Tirumalesh Reviewed-by: Vinay Belgaumkar Link: https://patch.msgid.link/20260916100545.779894-3-tilak.tirumalesh.tangudu@intel.com Signed-off-by: Matt Roper (cherry picked from commit 9453c528fc909076468ff10df1c2e334ca5a9b00) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_guc_ads.c | 2 +- drivers/gpu/drm/xe/xe_hw_engine.c | 93 ++++++++++++++++++++++++------ drivers/gpu/drm/xe/xe_wa_oob.rules | 2 + 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c index ff8eee3831aa28..58a3f6293ce469 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.c +++ b/drivers/gpu/drm/xe/xe_guc_ads.c @@ -864,7 +864,7 @@ static unsigned int guc_mmio_regset_write(struct xe_guc_ads *ads, } } - if (XE_GT_WA(hwe->gt, 16023105232)) + if (XE_GT_WA(hwe->gt, 16023105232) || XE_GT_WA(hwe->gt, 14025941587)) guc_mmio_regset_write_one(ads, regset_map, RING_IDLEDLY(hwe->mmio_base), count++); diff --git a/drivers/gpu/drm/xe/xe_hw_engine.c b/drivers/gpu/drm/xe/xe_hw_engine.c index d868af8e330197..9680d2a5adec7e 100644 --- a/drivers/gpu/drm/xe/xe_hw_engine.c +++ b/drivers/gpu/drm/xe/xe_hw_engine.c @@ -585,44 +585,103 @@ static void hw_engine_init_early(struct xe_gt *gt, struct xe_hw_engine *hwe, xe_reg_whitelist_process_engine(hwe); } +static u32 idledly_floor_ticks(u32 idledly_ns, u32 idledly_units_ps) +{ + return DIV_ROUND_DOWN_ULL((u64)idledly_ns * 1000, idledly_units_ps); +} + static void adjust_idledly(struct xe_hw_engine *hwe) { struct xe_gt *gt = hwe->gt; - u32 idledly, maxcnt; + u32 idledly, idledly_hw, idledly_reg_val, maxcnt; u32 idledly_units_ps = 8 * gt->info.timestamp_base; u32 maxcnt_units_ns = 640; - bool inhibit_switch = 0; + bool inhibit_switch = false; bool wa_applied = false; + bool clamped_below_maxcnt = false; + + if ((!IS_SRIOV_VF(gt_to_xe(gt)) && XE_GT_WA(gt, 16023105232)) || + XE_GT_WA(gt, 14025941587)) { + u32 mincnt_idledly_ns = 5000; - if (!IS_SRIOV_VF(gt_to_xe(gt)) && XE_GT_WA(gt, 16023105232)) { /* xe_gt_clock_init() warns and zeroes timestamp_base on unknown crystal clock. */ if (!idledly_units_ps) return; - idledly = xe_mmio_read32(>->mmio, RING_IDLEDLY(hwe->mmio_base)); + idledly_reg_val = xe_mmio_read32(>->mmio, RING_IDLEDLY(hwe->mmio_base)); maxcnt = xe_mmio_read32(>->mmio, RING_PWRCTX_MAXCNT(hwe->mmio_base)); - inhibit_switch = idledly & INHIBIT_SWITCH_UNTIL_PREEMPTED; - idledly = REG_FIELD_GET(IDLE_DELAY, idledly); + inhibit_switch = idledly_reg_val & INHIBIT_SWITCH_UNTIL_PREEMPTED; + idledly = REG_FIELD_GET(IDLE_DELAY, idledly_reg_val); idledly = DIV_ROUND_CLOSEST_ULL((u64)idledly * idledly_units_ps, 1000); + idledly_hw = idledly; maxcnt = REG_FIELD_GET(IDLE_WAIT_TIME, maxcnt); maxcnt *= maxcnt_units_ns; - /* Clear the inhibit switch without disturbing a valid delay. */ - if (inhibit_switch) + /* + * Wa_14025941587 is applied before Wa_16023105232, which takes + * priority if the two ever conflict (not expected in practice). + */ + if (XE_GT_WA(gt, 14025941587) && + idledly < mincnt_idledly_ns) { + idledly = mincnt_idledly_ns; wa_applied = true; + } - if (xe_gt_WARN_ON(gt, idledly >= maxcnt)) { - /* Floor below maxcnt; write 0 to still clear the inhibit bit. */ - idledly = maxcnt ? - DIV_ROUND_DOWN_ULL((u64)(maxcnt - 1) * 1000, - idledly_units_ps) : 0; - wa_applied = true; + if (XE_GT_WA(gt, 16023105232)) { + /* Clear the inhibit switch without disturbing a valid delay. */ + if (inhibit_switch) { + idledly_reg_val &= ~INHIBIT_SWITCH_UNTIL_PREEMPTED; + wa_applied = true; + } + + /* Warn only on the value read from hardware. */ + xe_gt_WARN_ON(gt, idledly_hw >= maxcnt); + + if (idledly >= maxcnt) { + /* maxcnt may be 0 if IDLE_WAIT_TIME is unprogrammed. */ + idledly = maxcnt ? maxcnt - 1 : 0; + clamped_below_maxcnt = true; + wa_applied = true; + } } - if (wa_applied) - xe_mmio_write32(>->mmio, RING_IDLEDLY(hwe->mmio_base), - REG_FIELD_PREP(IDLE_DELAY, idledly)); + if (wa_applied) { + u32 idledly_ticks; + + /* + * Wa_16023105232 requires idledly < maxcnt, so floor + * that clamp; otherwise round up to guarantee the + * Wa_14025941587 minimum survives tick quantization. + */ + if (clamped_below_maxcnt) + idledly_ticks = idledly_floor_ticks(idledly, idledly_units_ps); + else + idledly_ticks = DIV_ROUND_UP_ULL((u64)idledly * 1000, + idledly_units_ps); + + /* + * Tick quantization can still push the rounded-up value + * to/above maxcnt; re-floor here so Wa_16023105232 keeps + * priority even in that case. + */ + if (!clamped_below_maxcnt && XE_GT_WA(gt, 16023105232) && + (u64)idledly_ticks * idledly_units_ps >= (u64)maxcnt * 1000) { + xe_gt_dbg(gt, "idledly %s: %u ticks would exceed maxcnt=%u, so flooring\n", + hwe->name, idledly_ticks, maxcnt); + idledly = maxcnt ? maxcnt - 1 : 0; + idledly_ticks = idledly_floor_ticks(idledly, idledly_units_ps); + } + + idledly_reg_val &= ~IDLE_DELAY; + idledly_reg_val |= REG_FIELD_PREP(IDLE_DELAY, idledly_ticks); + xe_gt_dbg(gt, "idledly %s: set %u max=%u inh=%u ts=%u\n", + hwe->name, idledly, maxcnt, + !!inhibit_switch, gt->info.timestamp_base); + xe_mmio_write32(>->mmio, + RING_IDLEDLY(hwe->mmio_base), + idledly_reg_val); + } } } diff --git a/drivers/gpu/drm/xe/xe_wa_oob.rules b/drivers/gpu/drm/xe/xe_wa_oob.rules index dd69ad07f7a9a7..3001155f8d09e4 100644 --- a/drivers/gpu/drm/xe/xe_wa_oob.rules +++ b/drivers/gpu/drm/xe/xe_wa_oob.rules @@ -72,3 +72,5 @@ 16029897822 MEDIA_VERSION(3500) GRAPHICS_VERSION(3510) 14027054324 GRAPHICS_VERSION(3511) +14025941587 GRAPHICS_VERSION_RANGE(2001, 3511), FUNC(xe_rtp_match_not_sriov_vf) + MEDIA_VERSION_RANGE(1301, 3503), FUNC(xe_rtp_match_not_sriov_vf) From 814a81c842bd88f6bd8a4ce550d560df071a5d03 Mon Sep 17 00:00:00 2001 From: Zhao Gongyi Date: Thu, 17 Sep 2026 20:10:16 +0800 Subject: [PATCH 1052/1417] bpf, sockmap: Reject max_entries > INT_MAX in sock_map_alloc sock_map_alloc() only rejects max_entries == 0 and otherwise allows any u32 value. sock_map_free() then walks the sks[] array with a signed int iterator: int i; for (i = 0; i < stab->map.max_entries; i++) struct sock **psk = &stab->sks[i]; When a SOCKMAP is created with max_entries = 0xffffffff (UINT_MAX), the allocation of 32 GiB can succeed on large-memory hosts. During free the counter reaches 0x80000000, wraps to INT_MIN, is sign-extended by movslq and turned into a ~16 GiB negative offset from stab->sks, pointing far below the allocation. The faulting access is an xchg() write in sock_map_free(). Without KASAN, the same out-of-bounds write can fault on an unmapped vmalloc page or corrupt an unrelated allocation if that vmalloc address is populated. On a KASAN kernel with CONFIG_KASAN_VMALLOC=y, the shadow check for that address hits an unmapped shadow page and oopses first: BUG: unable to handle page fault for address: fffff521b59c5a00 RIP: 0010:kasan_check_range+0x107/0x190 Call Trace: sock_map_free+0x93/0x190 map_create+0x68d/0xb30 __sys_bpf+0x21e/0x2e70 Vmcore confirmed stab->map.max_entries == 0xffffffff, stab->sks == 0xffffc911ace2d000, and the faulting address sks + (s64)INT_MIN * 8 exactly at 0xffffc90dace2d000. The same buggy path is reached on the normal close()/bpf_map_free_deferred() path whenever such a map is destroyed. sock_map_alloc() used to bound its allocation size through bpf_map_charge_init(), but the bound was dropped when rlimit-based memory accounting was removed. Reject max_entries > INT_MAX at creation time so the signed iterator in sock_map_free() never sees a value that would overflow. Triggered by syzkaller and reproduced on both a 6.6-based KASAN kernel and the upstream v7.3-rc2 kernel. Fixes: 0d2c4f964050 ("bpf: Eliminate rlimit-based memory accounting for sockmap and sockhash maps") Signed-off-by: Zhao Gongyi Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260917121016.48171-1-zhaogongyi@bytedance.com --- net/core/sock_map.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/core/sock_map.c b/net/core/sock_map.c index ca49bc7f8687ce..38df842843289a 100644 --- a/net/core/sock_map.c +++ b/net/core/sock_map.c @@ -41,6 +41,7 @@ static struct bpf_map *sock_map_alloc(union bpf_attr *attr) struct bpf_stab *stab; if (attr->max_entries == 0 || + attr->max_entries > INT_MAX || attr->key_size != 4 || (attr->value_size != sizeof(u32) && attr->value_size != sizeof(u64)) || From c6b51091cafff9ce6c03c1416aa13864d17ab97c Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Tue, 22 Sep 2026 22:26:39 +0900 Subject: [PATCH 1053/1417] firewire: cdev: fix back-transition for iso_resource_auto client resource The todo member of iso_resource_auto structure represents the state of the client resource and normally transitions in the following order: ISO_RES_AUTO_ALLOC -> ISO_RES_AUTO_REALLOC -> ISO_RES_AUTO_DEALLOC However, concurrent access from the work item and the file descriptor release function can cause the state to transition backwards from ISO_RES_AUTO_DEALLOC to ISO_RES_AUTO_REALLOC. Prevent the back-transition by checking the current state before updating it in the work item. Fixes: fcabbf40fae5 ("firewire: core: move allocation/reallocation paths into specific branch after isoc resource management in cdev") Link: https://lore.kernel.org/r/20260922132639.191593-1-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/core-cdev.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c index e49d8a58be09ed..664952a67a11ae 100644 --- a/drivers/firewire/core-cdev.c +++ b/drivers/firewire/core-cdev.c @@ -1397,8 +1397,10 @@ static void iso_resource_auto_work(struct work_struct *work) } else { // Transit from allocation to reallocation, except if the client requested // deallocation in the meantime. - scoped_guard(spinlock_irq, &client->lock) - r->todo = ISO_RES_AUTO_REALLOC; + scoped_guard(spinlock_irq, &client->lock) { + if (r->todo == ISO_RES_AUTO_ALLOC) + r->todo = ISO_RES_AUTO_REALLOC; + } if (channel >= 0) r->params.channels_mask = BIT_ULL(channel); From 35e6f970f553954d92ba20afa885139a8e7dd0d7 Mon Sep 17 00:00:00 2001 From: Bernardo Soares Date: Fri, 18 Sep 2026 10:59:30 +0100 Subject: [PATCH 1054/1417] net/mlx5: Bridge, don't fail switchdev events of sibling eswitch ports mlx5 registers the bridge offload switchdev notifiers once per eswitch instance, but the notifier chains are global, so every instance sees every event and must filter out the ones that aren't its own. The existing filter, mlx5_esw_bridge_dev_same_hw(), only checks that the event netdevice sits on the same HCA - intentional for merged eswitch, where one bridge can span representors of several eswitches on one HCA - but same-HCA doesn't mean the instance actually has that port: peer ports are only created reactively from NETDEV_CHANGEUPPER, so an instance brought up after a sibling PF's port was already enslaved has none. The port object and attribute handlers claim the event anyway once same-HW passes, then fail the port lookup and return -EINVAL, which gets reported to user space even though the owning instance already handled it (e.g. "bridge vlan add ... RTNETLINK answers: Invalid argument"). Fix by filtering on the tracked port instead. The same gap exists in the generic recursive lower-device walk used by attribute changes on a bridge with more than one representor enslaved directly: mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get() is entered with the bridge master netdevice, falls through to its generic netdev_for_each_lower_dev() loop, and returns as soon as the recursion into any one lower device yields a non-NULL rep - the underlying base case, mlx5_esw_bridge_rep_vport_num_vhca_id_get(), only checks mlx5_esw_bridge_dev_same_hw(), not ownership by the calling instance's br_offloads. mlx5_esw_bridge_lag_rep_get(), used for the LAG-master case, already filters on mlx5_esw_bridge_dev_same_esw() per candidate and so cannot select a sibling's rep; it is not the source of this bug. On a merged-eswitch HCA with a bridge spanning representors of more than one eswitch instance directly, the walk can return a sibling's rep instead of continuing to the one the calling instance actually owns, so the attribute change fails the same way as above. Fix by checking mlx5_esw_bridge_port_exists() at the point each rep is picked, same as the previous fix did for the notifier filter. Fixes: c358ea1741bc ("net/mlx5: Bridge, allow merged eswitch connectivity") Signed-off-by: Bernardo Soares Cc: Vlad Buslov Cc: Saeed Mahameed Reviewed-by: Mark Bloch Link: https://patch.msgid.link/20260918095931.29792-2-bsoares.it@gmail.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/en/rep/bridge.c | 45 +++++++++++++++---- .../ethernet/mellanox/mlx5/core/esw/bridge.c | 6 +++ .../ethernet/mellanox/mlx5/core/esw/bridge.h | 2 + 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/bridge.c b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/bridge.c index baac38bece14a7..4b7b0a0fc2b2ff 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/bridge.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/bridge.c @@ -85,9 +85,16 @@ mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get(struct net_device *dev, struct m struct net_device *lower_dev; struct list_head *iter; - if (netif_is_lag_master(dev) || mlx5e_eswitch_rep(dev)) - return mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, esw, vport_num, - esw_owner_vhca_id); + if (netif_is_lag_master(dev) || mlx5e_eswitch_rep(dev)) { + struct net_device *rep; + + rep = mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, esw, vport_num, + esw_owner_vhca_id); + if (rep && !mlx5_esw_bridge_port_exists(*vport_num, *esw_owner_vhca_id, + esw->br_offloads)) + return NULL; + return rep; + } netdev_for_each_lower_dev(dev, lower_dev, iter) { struct net_device *rep; @@ -104,6 +111,28 @@ mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get(struct net_device *dev, struct m return NULL; } +static bool mlx5_esw_bridge_rep_port_lookup(struct net_device *dev, + struct mlx5_esw_bridge_offloads *br_offloads, + u16 *vport_num, u16 *esw_owner_vhca_id) +{ + if (!mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, br_offloads->esw, vport_num, + esw_owner_vhca_id)) + return false; + + return mlx5_esw_bridge_port_exists(*vport_num, *esw_owner_vhca_id, br_offloads); +} + +static bool mlx5_esw_bridge_lower_rep_port_lookup(struct net_device *dev, + struct mlx5_esw_bridge_offloads *br_offloads, + u16 *vport_num, u16 *esw_owner_vhca_id) +{ + if (!mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get(dev, br_offloads->esw, vport_num, + esw_owner_vhca_id)) + return false; + + return mlx5_esw_bridge_port_exists(*vport_num, *esw_owner_vhca_id, br_offloads); +} + static bool mlx5_esw_bridge_is_local(struct net_device *dev, struct net_device *rep, struct mlx5_eswitch *esw) { @@ -218,8 +247,7 @@ mlx5_esw_bridge_port_obj_add(struct net_device *dev, u16 vport_num, esw_owner_vhca_id; int err; - if (!mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, br_offloads->esw, &vport_num, - &esw_owner_vhca_id)) + if (!mlx5_esw_bridge_rep_port_lookup(dev, br_offloads, &vport_num, &esw_owner_vhca_id)) return 0; port_obj_info->handled = true; @@ -251,8 +279,7 @@ mlx5_esw_bridge_port_obj_del(struct net_device *dev, const struct switchdev_obj_port_mdb *mdb; u16 vport_num, esw_owner_vhca_id; - if (!mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, br_offloads->esw, &vport_num, - &esw_owner_vhca_id)) + if (!mlx5_esw_bridge_rep_port_lookup(dev, br_offloads, &vport_num, &esw_owner_vhca_id)) return 0; port_obj_info->handled = true; @@ -283,8 +310,8 @@ mlx5_esw_bridge_port_obj_attr_set(struct net_device *dev, u16 vport_num, esw_owner_vhca_id; int err = 0; - if (!mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get(dev, br_offloads->esw, &vport_num, - &esw_owner_vhca_id)) + if (!mlx5_esw_bridge_lower_rep_port_lookup(dev, br_offloads, &vport_num, + &esw_owner_vhca_id)) return 0; port_attr_info->handled = true; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c index 87b5fd3495945f..ac90ccda127229 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c @@ -1686,6 +1686,12 @@ int mlx5_esw_bridge_vport_peer_unlink(struct net_device *br_netdev, u16 vport_nu extack); } +bool mlx5_esw_bridge_port_exists(u16 vport_num, u16 esw_owner_vhca_id, + struct mlx5_esw_bridge_offloads *br_offloads) +{ + return mlx5_esw_bridge_port_lookup(vport_num, esw_owner_vhca_id, br_offloads); +} + int mlx5_esw_bridge_port_vlan_add(u16 vport_num, u16 esw_owner_vhca_id, u16 vid, u16 flags, struct mlx5_esw_bridge_offloads *br_offloads, struct netlink_ext_ack *extack) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.h b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.h index d6f5391619930d..a4e59cc210894a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.h @@ -80,6 +80,8 @@ int mlx5_esw_bridge_vlan_proto_set(u16 vport_num, u16 esw_owner_vhca_id, u16 pro struct mlx5_esw_bridge_offloads *br_offloads); int mlx5_esw_bridge_mcast_set(u16 vport_num, u16 esw_owner_vhca_id, bool enable, struct mlx5_esw_bridge_offloads *br_offloads); +bool mlx5_esw_bridge_port_exists(u16 vport_num, u16 esw_owner_vhca_id, + struct mlx5_esw_bridge_offloads *br_offloads); int mlx5_esw_bridge_port_vlan_add(u16 vport_num, u16 esw_owner_vhca_id, u16 vid, u16 flags, struct mlx5_esw_bridge_offloads *br_offloads, struct netlink_ext_ack *extack); From 2e51097c982b7b22382fda3202ba29f0ea33e8c0 Mon Sep 17 00:00:00 2001 From: Bernardo Soares Date: Fri, 18 Sep 2026 10:59:31 +0100 Subject: [PATCH 1055/1417] net/mlx5: Bridge, don't fail unlink of untracked/unsupported peer ports mlx5_esw_bridge_vport_unlink() returns -EINVAL when the port isn't tracked by this instance's br_offloads. This is reachable on a sibling instance that registered its notifier after the port was already enslaved: it never saw the NETDEV_CHANGEUPPER link event, so peer_link() never created a peer port for it, but it does see the later unlink event and fails. Return 0 instead, and give mlx5_esw_bridge_vport_peer_unlink() the same merged_eswitch capability guard peer_link() already has, since without it peer_link() likewise never creates a port to unlink. This also matters beyond the -EINVAL itself: mlx5_esw_bridge_switchdev_port_event() runs on the per-netns netdev_chain, and notifier_from_errno(-EINVAL) sets NOTIFY_STOP_MASK, which call_netdevice_notifiers_info() checks to stop calling further listeners on that chain - so the old -EINVAL silently dropped the event for any listener registered later on the same chain, even though none of it was visible to user space since __netdev_upper_dev_unlink() discards the return value. Fixes: c358ea1741bc ("net/mlx5: Bridge, allow merged eswitch connectivity") Signed-off-by: Bernardo Soares Reviewed-by: Mark Bloch Link: https://patch.msgid.link/20260918095931.29792-3-bsoares.it@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c index ac90ccda127229..b4cf3c5ac0dde8 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c @@ -1649,10 +1649,8 @@ int mlx5_esw_bridge_vport_unlink(struct net_device *br_netdev, u16 vport_num, int err; port = mlx5_esw_bridge_port_lookup(vport_num, esw_owner_vhca_id, br_offloads); - if (!port) { - NL_SET_ERR_MSG_MOD(extack, "Port is not attached to any bridge"); - return -EINVAL; - } + if (!port) + return 0; if (port->bridge->ifindex != br_netdev->ifindex) { NL_SET_ERR_MSG_MOD(extack, "Port is attached to another bridge"); return -EINVAL; @@ -1682,6 +1680,9 @@ int mlx5_esw_bridge_vport_peer_unlink(struct net_device *br_netdev, u16 vport_nu struct mlx5_esw_bridge_offloads *br_offloads, struct netlink_ext_ack *extack) { + if (!MLX5_CAP_ESW(br_offloads->esw->dev, merged_eswitch)) + return 0; + return mlx5_esw_bridge_vport_unlink(br_netdev, vport_num, esw_owner_vhca_id, br_offloads, extack); } From 0bf6bb567f0edaa771e7dd208ef98da50e6a4485 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 11:31:31 +0000 Subject: [PATCH 1056/1417] net/mlx5: Fix rev_entry reference leak in mlx5_tc_ct_shared_counter_get() When the reverse entry is found but its counter is already being released, refcount_inc_not_zero() fails and the reference taken by mlx5_tc_ct_entry_get() is never dropped before falling through to create_counter. Drop it so the reverse entry is not kept alive forever by a shared counter lookup that did not use it. Fixes: 1edae2335adf ("net/mlx5e: CT: Use the same counter for both directions") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260917113131.2149024-1-vulab@iscas.ac.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c index 6c87a1c7db0959..c1841ce74d9ac8 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c @@ -1082,6 +1082,9 @@ mlx5_tc_ct_shared_counter_get(struct mlx5_tc_ct_priv *ct_priv, spin_unlock_bh(&ct_priv->ht_lock); + if (rev_entry) + mlx5_tc_ct_entry_put(rev_entry); + create_counter: shared_counter = mlx5_tc_ct_counter_create(ct_priv); From a1259e92e1e892f009bbebf23e1207b02e2d5708 Mon Sep 17 00:00:00 2001 From: ZhangGuoDong Date: Mon, 21 Sep 2026 09:41:28 +0800 Subject: [PATCH 1057/1417] smb: client: update POSIX extension specification references The POSIX extension specifications have now been published: https://smb3posix.org/ Suggested-by: Paulo Alcantara Reviewed-by: ChenXiaoSong Signed-off-by: ZhangGuoDong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2pdu.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/smb2pdu.h b/fs/smb/client/smb2pdu.h index ab6c667bebc0ae..b37a1e3941a1d9 100644 --- a/fs/smb/client/smb2pdu.h +++ b/fs/smb/client/smb2pdu.h @@ -224,8 +224,7 @@ struct smb2_file_id_extd_directory_info { extern char smb2_padding[7]; /* - * See POSIX-SMB2 2.2.14.2.16 - * Link: https://gitlab.com/samba-team/smb3-posix-spec/-/blob/master/smb3_posix_extensions.md + * See POSIX-SMB2 2.1.3.2.1 */ struct create_posix_rsp { u32 nlink; @@ -238,6 +237,7 @@ struct create_posix_rsp { #define SMB2_QUERY_DIRECTORY_IOV_SIZE 2 /* + * See POSIX-FSCC 2.2.1 * SMB2-only POSIX info level for query dir * * See posix_info_sid_size(), posix_info_extra_size() and @@ -256,13 +256,17 @@ struct smb2_posix_info { __le64 Inode; __le32 DeviceId; __le32 Zero; - /* beginning of POSIX Create Context Response */ + /* + * Beginning of POSIX Create Context Response + * See POSIX-SMB2 2.1.3.2.1 + */ __le32 HardLinks; __le32 ReparseTag; __le32 Mode; /* * var sized owner SID * var sized group SID + * End of POSIX Create Context Response * le32 filenamelength * u8 filename[] */ From 031fe051abb3c1f36c2ba6346931fcdb3cddaeae Mon Sep 17 00:00:00 2001 From: Fredric Cover Date: Tue, 22 Sep 2026 10:37:59 -0700 Subject: [PATCH 1058/1417] smb: client: use GFP_KERNEL in get_targets() Currently, get_targets() uses GFP_ATOMIC to allocate the cache target iterator and to duplicate t->name. The callers of get_targets() are in a sleepable context; therefore, switch to GFP_KERNEL to reduce risk of failure and reduce pressure on emergency pools in low-memory scenarios. Signed-off-by: Fredric Cover Signed-off-by: Paulo Alcantara --- fs/smb/client/dfs_cache.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c index 29dfd75959413c..26956e33037e7c 100644 --- a/fs/smb/client/dfs_cache.c +++ b/fs/smb/client/dfs_cache.c @@ -798,13 +798,13 @@ static int get_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl) INIT_LIST_HEAD(head); list_for_each_entry(t, &ce->tlist, list) { - it = kzalloc_obj(*it, GFP_ATOMIC); + it = kzalloc_obj(*it, GFP_KERNEL); if (!it) { rc = -ENOMEM; goto err_free_it; } - it->it_name = kstrdup(t->name, GFP_ATOMIC); + it->it_name = kstrdup(t->name, GFP_KERNEL); if (!it->it_name) { kfree(it); rc = -ENOMEM; From 4498467a8af06cfa3d71cb04bd7c4170dec8f449 Mon Sep 17 00:00:00 2001 From: Aohan Mei Date: Mon, 21 Sep 2026 17:37:04 +0800 Subject: [PATCH 1059/1417] sctp: discard the rest of the packet on a stale-cookie error When an association is in COOKIE-ECHOED state and the peer sends a bundled [ERROR(Stale Cookie)][DATA] packet from one of its non-primary addresses, processing the ERROR chunk takes the non-fatal stale-cookie retry path sctp_sf_do_5_2_6_stale(), which queues SCTP_CMD_DEL_NON_PRIMARY while keeping the association alive. sctp_cmd_del_non_primary() removes every non-primary transport - including the very transport this packet arrived on, which is still referenced by the receive lookup and shared by all chunks of the packet via chunk->transport. sctp_assoc_rm_peer() does redirect asoc->peer.last_data_from away from the removed transport, but right afterwards the bundled DATA chunk makes sctp_assoc_bh_rcv() re-register asoc->peer.last_data_from = chunk->transport unconditionally, undoing the redirection with the just-removed transport. Once the packet is done, the receive reference is dropped and the transport is RCU-freed, while the surviving association keeps the dangling last_data_from. A later FWD-TSN (or the delayed SACK timer) makes sctp_gen_sack() dereference it (->param_flags and friends), and sctp_make_sack()/sctp_outq_select_transport() may write to the freed object and link it into the live transport list. This is a use-after-free triggerable by any malicious SCTP peer (or a local unprivileged user acting as one) with no capabilities required: BUG: KASAN: slab-use-after-free in sctp_do_sm+0x498a/0x5660 Read of size 4 at addr ffff88800e1e356c by task poc/115 Call Trace: sctp_do_sm <- sctp_assoc_bh_rcv <- sctp_inq_push <- sctp_rcv <- ip_protocol_deliver_rcu <- ip_rcv Allocated: sctp_transport_new <- sctp_assoc_add_peer <- sctp_process_init (INIT-ACK processing) Freed: kfree <- sctp_transport_destroy_rcu <- rcu_core (call_rcu queued by sctp_transport_put at end of sctp_rcv) The buggy address is located 364 bytes inside of freed 1024-byte region [ffff88800e1e3400, ffff88800e1e3800), cache kmalloc-1k Note that commit 03a9d10ecf71 ("sctp: drop a chunk if its transport was removed") only covers the window between the receive lookup and the chunk processing (e.g. an ASCONF DEL-IP racing the socket backlog); here the transport is removed *while* the packet is being processed, by an earlier chunk of the same packet, so the drop in sctp_inq_push() does not reach this path. Verified with the bundled [ERROR(Stale Cookie)][DATA] + FWD-TSN reproducer: the KASAN report above still fires with that commit applied, and is gone with this patch on top. Fix it by discarding the rest of the packet on this path, as suggested by Xin. After the stale-cookie ERROR has sent the association back to COOKIE-WAIT and removed the non-primary transports, the remaining chunks of the packet can only run against the restarted handshake while referencing the removed arrival transport through chunk->transport: besides the last_data_from registration above, sctp_cmd_setup_t2() and the sctp_make_*() reply builders would also copy that pointer into association-lifetime state that sctp_assoc_rm_peer() has already sanitized. Let the peer retransmit them, in line with what sctp_inq_push() does for chunks whose transport was removed before processing. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Suggested-by: Xin Long Reported-by: TencentOS Corvus AI Cc: stable@vger.kernel.org Signed-off-by: Aohan Mei Acked-by: Xin Long Link: https://patch.msgid.link/20260921093707.1432184-1-ljp1205831794@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/sm_statefuns.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index c701cff6ea67cc..8dc65498763cc1 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -2654,6 +2654,8 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale( sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply)); + sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET, SCTP_NULL()); + return SCTP_DISPOSITION_CONSUME; nomem: From 07e1a9408b6c2f9d0cfb757b67dabb52da7a32b2 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Fri, 18 Sep 2026 20:47:28 -0400 Subject: [PATCH 1060/1417] virtio_net: copy zerocopy frags in start_xmit without NAPI Virtio-net without NAPI frees completed skbs lazily on the next start_xmit. Senders waiting for in-flight zerocopy buffers can deadlock if they cannot transmit more packets, as then no completed packets will be freed. When !use_napi, virtio-net already calls skb_orphan to avoid waiting up for transmitted skbs to be freed. For zerocopy packets that require deep copying on orphan (i.e. those that do not set SKBFL_DONT_ORPHAN, such as PACKET_TX_RING), call skb_orphan_frags before orphaning to release the buffers. This fixes the tpacket_snd slot reuse bug on skb_orphan for virtio-net, and prevents PACKET_TX_RING from running out of slots. This fix also touches vhost_net zerocopy packets, which also do not set SKBFL_DONT_ORPHAN. This is fine: vhost_net packets only encounter virtio-net in nested virtualization, and only if napi_tx is explicitly disabled (it has been default-enabled since Linux 4.12). In that rare case, copying the frags is desirable anyway to prevent holding guest descriptors pinned across unbounded intervals. This is a prerequisite for the next patch, which converts PACKET_TX_RING to standard zerocopy completion. Without this patch first, a bounded ring sender can stall indefinitely behind a virtio-net virtqueue that cannot reclaim. Fixes: 5cd8d46ea156 ("packet: copy user buffers before orphan or clone") Cc: stable@vger.kernel.org Cc: mst@redhat.com Cc: jasowangio@gmail.com Signed-off-by: Willem de Bruijn Link: https://patch.msgid.link/20260919004748.1463985-2-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/virtio_net.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index e34c52d059d398..bf82ef9874abb4 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -3349,6 +3349,14 @@ static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) else virtqueue_disable_cb(sq->vq); + if (!use_napi && + unlikely(skb_orphan_frags(skb, GFP_ATOMIC))) { + DEV_STATS_INC(dev, tx_dropped); + dev_kfree_skb_any(skb); + kick = !xmit_more || netif_xmit_stopped(txq); + goto kick_vq; + } + /* timestamp packet in software */ skb_tx_timestamp(skb); @@ -3381,6 +3389,7 @@ static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) kick = use_napi ? __netdev_tx_sent_queue(txq, skb->len, xmit_more) : !xmit_more || netif_xmit_stopped(txq); +kick_vq: if (kick) { if (virtqueue_kick_prepare(sq->vq) && virtqueue_notify(sq->vq)) { u64_stats_update_begin(&sq->stats.syncp); From 9518405613863d0bf0700927a21367f5942cf058 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Fri, 18 Sep 2026 20:47:29 -0400 Subject: [PATCH 1061/1417] packet: use ubuf_info completion for TX_RING packets tpacket_snd sends skbs with frags pointing into its ring slots. Slots are released when skb->destructor is called. A call to skb_orphan calls skb->destructor before the skb is freed. This can cause the slot to be reused while still linked into the skb. Switch to standard zerocopy completion (ubuf_info) so the slot is only released once all references to the payload are freed or copied. Restore skb->destructor to standard sock_wfree. The ubuf_info completion callback can be called with a NULL skb, but only from net_zcopy_put and related API, used by zerocopy implementations that hold their own reference on the uarg, such as MSG_ZEROCOPY. This uarg is only ever completed from skb_zcopy_clear, so skb is always set. To prevent userspace from aliasing in-flight state on shared ring slots, allocate tpacket_uarg per packet, rather than per slot. This adds a small allocation to the transmit path. Use standard kmalloc to allow backporting to stable kernels. The uarg holds an sk_wmem_alloc reference, rather than an sk_refcnt reference. packet_free_tx_ring waits on sk_wmem_alloc before freeing the ring pages. Always allocate vec->deferred for tx_ring so page-backed rings also wait on sk_wmem_alloc when skb_copy_ubufs drops page refs before calling tpacket_ubuf_complete. Drop the tx_ring.pg_vec test that tpacket_destruct_skb performed before accessing the slot. The sk_wmem_alloc reference now guarantees that the slot is valid. The test is also not sufficient by itself, as it reads pg_vec without pg_vec_lock, so it can race with packet_set_ring. As a result a slot is released when its payload is copied, which can be before transmission (e.g., in skb_orphan_frags_rx). If copied before skb_tx_timestamp() is called, no slot timestamp is recorded, similar to when skb_orphan() was called early in the datapath before this patch. Revert the now unused previous skb_zcopy_.._nouarg infra. Depends on commit 992cc9f94ca9 ("net/packet: defer vmalloc TX_RING free until skbs finish"). Reported-by: Katherine Leaver Reported-by: Bjoern Doebel Closes: https://lore.kernel.org/netdev/20260909085542.3370986-1-doebel@amazon.de/ Fixes: 5cd8d46ea156 ("packet: copy user buffers before orphan or clone") Cc: stable@vger.kernel.org Signed-off-by: Willem de Bruijn Link: https://patch.msgid.link/20260919004748.1463985-3-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- include/linux/skbuff.h | 19 +------- net/packet/af_packet.c | 102 ++++++++++++++++++++++++++--------------- 2 files changed, 65 insertions(+), 56 deletions(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 421f6fc454511e..b14d6be7370bad 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -1834,22 +1834,6 @@ static inline void skb_zcopy_set(struct sk_buff *skb, struct ubuf_info *uarg, } } -static inline void skb_zcopy_set_nouarg(struct sk_buff *skb, void *val) -{ - skb_shinfo(skb)->destructor_arg = (void *)((uintptr_t) val | 0x1UL); - skb_shinfo(skb)->flags |= SKBFL_ZEROCOPY_FRAG; -} - -static inline bool skb_zcopy_is_nouarg(struct sk_buff *skb) -{ - return (uintptr_t) skb_shinfo(skb)->destructor_arg & 0x1UL; -} - -static inline void *skb_zcopy_get_nouarg(struct sk_buff *skb) -{ - return (void *)((uintptr_t) skb_shinfo(skb)->destructor_arg & ~0x1UL); -} - static inline void net_zcopy_put(struct ubuf_info *uarg) { if (uarg) @@ -1872,8 +1856,7 @@ static inline void skb_zcopy_clear(struct sk_buff *skb, bool zerocopy_success) struct ubuf_info *uarg = skb_zcopy(skb); if (uarg) { - if (!skb_zcopy_is_nouarg(skb)) - uarg->ops->complete(skb, uarg, zerocopy_success); + uarg->ops->complete(skb, uarg, zerocopy_success); skb_shinfo(skb)->flags &= ~SKBFL_ALL_ZEROCOPY; } diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 50cae32ae26922..64b501db660a2d 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -2530,26 +2530,6 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev, goto drop_n_restore; } -static void tpacket_destruct_skb(struct sk_buff *skb) -{ - struct packet_sock *po = pkt_sk(skb->sk); - - if (likely(po->tx_ring.pg_vec)) { - void *ph; - __u32 ts; - - ph = skb_zcopy_get_nouarg(skb); - - ts = __packet_set_timestamp(po, ph, skb); - __packet_set_status(po, ph, TP_STATUS_AVAILABLE | ts); - - packet_dec_pending(&po->tx_ring); - complete(&po->skb_completion); - } - - sock_wfree(skb); -} - static int __packet_snd_vnet_parse(struct virtio_net_hdr *vnet_hdr, size_t len) { if ((vnet_hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) && @@ -2589,27 +2569,56 @@ static int packet_snd_vnet_parse(struct msghdr *msg, size_t *len, return 0; } +struct tpacket_uarg { + struct ubuf_info ubuf; + struct packet_sock *po; + void *ph; +}; + +static void tpacket_ubuf_complete(struct sk_buff *skb, struct ubuf_info *uarg, + bool success) +{ + struct tpacket_uarg *tu = container_of(uarg, struct tpacket_uarg, ubuf); + struct packet_sock *po = tu->po; + void *ph = tu->ph; + __u32 ts; + + DEBUG_NET_WARN_ON_ONCE(!skb); + + if (!refcount_dec_and_test(&uarg->refcnt)) + return; + + ts = __packet_set_timestamp(po, ph, skb); + __packet_set_status(po, ph, TP_STATUS_AVAILABLE | ts); + + packet_dec_pending(&po->tx_ring); + complete(&po->skb_completion); + + kfree(tu); + sk_free(&po->sk); +} + +static const struct ubuf_info_ops tpacket_ubuf_ops = { + .complete = tpacket_ubuf_complete, +}; + static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, - void *frame, struct net_device *dev, void *data, int tp_len, + struct net_device *dev, void *data, int tp_len, __be16 proto, unsigned char *addr, int hlen, int copylen, int hard_header_len, const struct sockcm_cookie *sockc) { - union tpacket_uhdr ph; int to_write, offset, len, nr_frags, len_max; struct socket *sock = po->sk.sk_socket; struct page *page; int err; - ph.raw = frame; - skb->protocol = proto; skb->dev = dev; skb->priority = sockc->priority; skb->mark = sockc->mark; skb_set_delivery_type_by_clockid(skb, sockc->transmit_time, po->sk.sk_clockid); skb_setup_tx_timestamp(skb, sockc); - skb_zcopy_set_nouarg(skb, ph.raw); skb_reserve(skb, hlen); skb_reset_network_header(skb); @@ -2749,6 +2758,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) struct virtio_net_hdr vnet_hdr; bool has_vnet_hdr = false; struct sockcm_cookie sockc; + struct tpacket_uarg *uarg; __be16 proto; int err, reserve = 0; void *ph; @@ -2876,7 +2886,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) err = len_sum; goto out_status; } - tp_len = tpacket_fill_skb(po, skb, ph, dev, data, tp_len, proto, + tp_len = tpacket_fill_skb(po, skb, dev, data, tp_len, proto, addr, hlen, copylen, hard_header_len, &sockc); if (likely(tp_len >= 0) && @@ -2908,7 +2918,24 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) virtio_net_hdr_set_proto(skb, &vnet_hdr); } - skb->destructor = tpacket_destruct_skb; + uarg = kmalloc(sizeof(*uarg), GFP_KERNEL); + if (unlikely(!uarg)) { + if (likely(len_sum > 0)) + err = len_sum; + else + err = -ENOMEM; + goto out_status; + } + uarg->po = po; + uarg->ph = ph; + uarg->ubuf.ops = &tpacket_ubuf_ops; + uarg->ubuf.flags = SKBFL_ZEROCOPY_FRAG; + refcount_set(&uarg->ubuf.refcnt, 1); + + /* Hold a sk_wmem_alloc reference until completion */ + refcount_inc(&po->sk.sk_wmem_alloc); + skb_zcopy_init(skb, &uarg->ubuf); + __packet_set_status(po, ph, TP_STATUS_SENDING); packet_inc_pending(&po->tx_ring); @@ -4486,21 +4513,20 @@ static struct pgv *alloc_pg_vec(struct tpacket_req *req, int order, bool tx_ring vec->len = block_nr; pg_vec = vec->pg_vec; + if (tx_ring) { + vec->deferred = kzalloc_obj(*vec->deferred, + GFP_KERNEL | __GFP_NOWARN); + if (!vec->deferred) + goto out_free_pgvec; + vec->deferred->vec = vec; + INIT_DELAYED_WORK(&vec->deferred->work, + packet_free_pg_vec_work); + } + for (i = 0; i < block_nr; i++) { pg_vec[i].buffer = alloc_one_pg_vec_page(order); if (unlikely(!pg_vec[i].buffer)) goto out_free_pgvec; - - if (tx_ring && !vec->deferred && - is_vmalloc_addr(pg_vec[i].buffer)) { - vec->deferred = kzalloc_obj(*vec->deferred, - GFP_KERNEL | __GFP_NOWARN); - if (!vec->deferred) - goto out_free_pgvec; - vec->deferred->vec = vec; - INIT_DELAYED_WORK(&vec->deferred->work, - packet_free_pg_vec_work); - } } out: From cae23ae3f7887a1cf8a75da38edcebeef695040f Mon Sep 17 00:00:00 2001 From: Xin Long Date: Mon, 21 Sep 2026 14:03:45 -0400 Subject: [PATCH 1062/1417] sctp: hold asoc or transport before mod_timer() in timer handlers Take the association or transport reference before rearming a timer in the timer handlers. The existing code calls mod_timer() before taking the reference needed by the rearmed timer without holding the sock lock. This creates a race with timer cleanup: if the timer is deleted after mod_timer() returns but before the reference is taken, the cleanup path can drop the timer's reference and destroy the transport or association. The timer handler then takes a reference on the already freed object and eventually drops it, causing a refcount underflow. Hold the object before mod_timer() and drop the reference if mod_timer() reports that the timer was already pending in timer handlers. Apply the same ordering to the proto-unreachable path, which can rearm a transport timer outside the timer handlers without holding the sock lock. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Tangxin Xie Signed-off-by: Xin Long Link: https://patch.msgid.link/c31b5e3ee2b7274e804f5eba2f21e2412e7eef7a.1790013825.git.lucien.xin@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/input.c | 7 ++++--- net/sctp/sm_sideeffect.c | 37 ++++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/net/sctp/input.c b/net/sctp/input.c index 864741fae4187e..9494cfa51106c1 100644 --- a/net/sctp/input.c +++ b/net/sctp/input.c @@ -436,9 +436,10 @@ void sctp_icmp_proto_unreachable(struct sock *sk, if (timer_pending(&t->proto_unreach_timer)) return; else { - if (!mod_timer(&t->proto_unreach_timer, - jiffies + (HZ/20))) - sctp_transport_hold(t); + sctp_transport_hold(t); + if (mod_timer(&t->proto_unreach_timer, + jiffies + (HZ / 20))) + sctp_transport_put(t); } } else { struct net *net = sock_net(sk); diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c index 0d99b7e8c082f0..35f540fb15fc14 100644 --- a/net/sctp/sm_sideeffect.c +++ b/net/sctp/sm_sideeffect.c @@ -244,8 +244,9 @@ void sctp_generate_t3_rtx_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->T3_rtx_timer, jiffies + (HZ/20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->T3_rtx_timer, jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } @@ -280,8 +281,9 @@ static void sctp_generate_timeout_event(struct sctp_association *asoc, timeout_type); /* Try again later. */ - if (!mod_timer(&asoc->timers[timeout_type], jiffies + (HZ/20))) - sctp_association_hold(asoc); + sctp_association_hold(asoc); + if (mod_timer(&asoc->timers[timeout_type], jiffies + (HZ / 20))) + sctp_association_put(asoc); goto out_unlock; } @@ -378,8 +380,9 @@ void sctp_generate_heartbeat_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->hb_timer, jiffies + (HZ/20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->hb_timer, jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } @@ -388,8 +391,9 @@ void sctp_generate_heartbeat_event(struct timer_list *t) timeout = sctp_transport_timeout(transport); if (elapsed < timeout) { elapsed = timeout - elapsed; - if (!mod_timer(&transport->hb_timer, jiffies + elapsed)) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->hb_timer, jiffies + elapsed)) + sctp_transport_put(transport); goto out_unlock; } @@ -422,9 +426,10 @@ void sctp_generate_proto_unreach_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->proto_unreach_timer, - jiffies + (HZ/20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->proto_unreach_timer, + jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } @@ -458,8 +463,9 @@ void sctp_generate_reconf_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->reconf_timer, jiffies + (HZ / 20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->reconf_timer, jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } @@ -495,8 +501,9 @@ void sctp_generate_probe_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->probe_timer, jiffies + (HZ / 20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->probe_timer, jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } From 73b3fc67a493e9b9a8e94a38839f6638d12fe7a6 Mon Sep 17 00:00:00 2001 From: Mina Almasry Date: Mon, 21 Sep 2026 19:54:12 +0000 Subject: [PATCH 1063/1417] net: devmem: document that bind-tx is unprivileged by design Unlike bind-rx, which configures shared NIC RX queues to steer incoming traffic into the caller's dmabuf and requires CAP_NET_ADMIN (uns-admin-perm), bind-tx only DMA-maps the caller's dmabuf so the caller can transmit from it on their own sockets without affecting other traffic or device configuration. Add a comment in netdev.yaml and above netdev_nl_bind_tx_doit() to make it explicit that NETDEV_CMD_BIND_TX is unprivileged by design. Signed-off-by: Mina Almasry Acked-by: Stanislav Fomichev Acked-by: Daniel Borkmann Link: https://patch.msgid.link/20260921195545.493253-1-almasrymina@google.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/netdev.yaml | 2 ++ net/core/netdev-genl.c | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml index 3e3f03bd5c29b8..0562ba1f567aa5 100644 --- a/Documentation/netlink/specs/netdev.yaml +++ b/Documentation/netlink/specs/netdev.yaml @@ -851,6 +851,8 @@ operations: name: bind-tx doc: Bind dmabuf to netdev for TX attribute-set: dmabuf + # Intentionally unprivileged (no admin-perm / uns-admin-perm); see + # comment above netdev_nl_bind_tx_doit(). do: request: attributes: diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index cb18db681640f0..33b9f4eb9565a9 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -1168,6 +1168,12 @@ netdev_find_netmem_tx_dev(struct net_device *dev) return NULL; } +/* Note: NETDEV_CMD_BIND_TX is intentionally unprivileged (no + * GENL_ADMIN_PERM / GENL_UNS_ADMIN_PERM). Unlike bind-rx, which configures + * shared NIC RX queues, bind-tx only DMA-maps the caller's dmabuf so they can + * transmit from it on their own sockets without affecting other traffic or + * device state. + */ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info) { struct net_devmem_dmabuf_binding *binding; From d68acbf93531abdb5b02b21994cd4c15a3c95b42 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:32 +0200 Subject: [PATCH 1064/1417] net: stmmac: selftests: Support running selftests on DSA conduits Most stmmac selftests rely on dev_add_pack() to add custom handlers, that validate the packets sent to ourselves through MAC loopback. However, when the stmmac-driven interface is a DSA CPU conduit, all frames that are received have ETH_P_XDSA as a protocol, even though they don't actually contain any tag as they come from the loopback and not the switch. This will prevent any incoming packet to match our packet handlers. Let's register a ETH_P_ALL packet handler when we detect that we're a DSA conduit, and use a proxy packet handler to filter the h_proto. As this allows external frames to be received through our .func(), the packet handler is added after the dev->addr field is populated in our selftest attributes. Note that we may still receive incoming packets from the switch, but these frames shouldn't interfere with the very specific frames used for selftests, and stmmac selftests in general aren't safe against external traffic interferences. This was validated on a WPQ864 devkit for IPQ8064, that has the SoC connected to a QCA8k switch. The ARP offload's packet handler is left alone, this feature is just not implemented in stmmac and due for removal. Fixes: 091810dbded9 ("net: stmmac: Introduce selftests support") Reviewed-by: Nicolai Buchwitz Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260917215339.2022523-2-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../stmicro/stmmac/stmmac_selftests.c | 89 +++++++++++++++---- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 6372ec7c3f3170..614b5995dec5f1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -237,6 +238,9 @@ struct stmmac_test_priv { struct stmmac_packet_attrs *packet; struct packet_type pt; struct completion comp; + __be16 packet_type; + int (*func)(struct sk_buff *skb, struct net_device *ndev, + struct packet_type *pt, struct net_device *orig_ndev); int double_vlan; int vlan_id; int ok; @@ -316,6 +320,50 @@ static int stmmac_test_loopback_validate(struct sk_buff *skb, return 0; } +static int stmmac_sft_filter(struct sk_buff *skb, struct net_device *ndev, + struct packet_type *pt, + struct net_device *orig_ndev) +{ + struct stmmac_test_priv *tpriv = pt->af_packet_priv; + struct ethhdr *hdr = eth_hdr(skb); + int ret = 0; + + if (hdr->h_proto == tpriv->packet_type) { + struct sk_buff *nskb = skb_clone(skb, GFP_ATOMIC); + + if (nskb) + ret = tpriv->func(nskb, ndev, pt, orig_ndev); + } + + kfree_skb(skb); + return ret; +} + +static void stmmac_sft_add_pack(struct packet_type *pt) +{ + struct stmmac_test_priv *tpriv = pt->af_packet_priv; + + if (netdev_uses_dsa(tpriv->pt.dev)) { + tpriv->packet_type = tpriv->pt.type; + tpriv->func = tpriv->pt.func; + + /* DSA conduit will report ETH_P_XDSA, so our packet handler + * won't match. Let's register a ETH_P_ALL match and filter + * manually in stmmac_sft_filter. + */ + tpriv->pt.type = htons(ETH_P_ALL); + tpriv->pt.func = stmmac_sft_filter; + tpriv->pt.ignore_outgoing = true; + } + + dev_add_pack(pt); +} + +static void stmmac_sft_remove_pack(struct packet_type *pt) +{ + dev_remove_pack(pt); +} + static int __stmmac_test_loopback(struct stmmac_priv *priv, struct stmmac_packet_attrs *attr) { @@ -337,7 +385,7 @@ static int __stmmac_test_loopback(struct stmmac_priv *priv, tpriv->packet = attr; if (!attr->dont_wait) - dev_add_pack(&tpriv->pt); + stmmac_sft_add_pack(&tpriv->pt); skb = stmmac_test_get_udp_skb(priv, attr); if (!skb) { @@ -360,7 +408,7 @@ static int __stmmac_test_loopback(struct stmmac_priv *priv, cleanup: if (!attr->dont_wait) - dev_remove_pack(&tpriv->pt); + stmmac_sft_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -767,7 +815,7 @@ static int stmmac_test_flowctrl(struct stmmac_priv *priv) tpriv->pt.func = stmmac_test_flowctrl_validate; tpriv->pt.dev = priv->dev; tpriv->pt.af_packet_priv = tpriv; - dev_add_pack(&tpriv->pt); + stmmac_sft_add_pack(&tpriv->pt); /* Compute minimum number of packets to make FIFO full */ pkt_count = rx_fifo_size; @@ -823,7 +871,7 @@ static int stmmac_test_flowctrl(struct stmmac_priv *priv) cleanup: dev_mc_del(priv->dev, paddr); dev_set_promiscuity(priv->dev, -1); - dev_remove_pack(&tpriv->pt); + stmmac_sft_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -928,18 +976,20 @@ static int __stmmac_test_vlanfilt(struct stmmac_priv *priv) * HASH values. */ tpriv->vlan_id = 0x123; - dev_add_pack(&tpriv->pt); ret = vlan_vid_add(priv->dev, htons(ETH_P_8021Q), tpriv->vlan_id); if (ret) goto cleanup; + attr.vlan = 1; + attr.dst = priv->dev->dev_addr; + attr.sport = 9; + attr.dport = 9; + + stmmac_sft_add_pack(&tpriv->pt); + for (i = 0; i < 4; i++) { - attr.vlan = 1; attr.vlan_id_out = tpriv->vlan_id + i; - attr.dst = priv->dev->dev_addr; - attr.sport = 9; - attr.dport = 9; skb = stmmac_test_get_udp_skb(priv, &attr); if (!skb) { @@ -966,9 +1016,9 @@ static int __stmmac_test_vlanfilt(struct stmmac_priv *priv) } vlan_del: + stmmac_sft_remove_pack(&tpriv->pt); vlan_vid_del(priv->dev, htons(ETH_P_8021Q), tpriv->vlan_id); cleanup: - dev_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -1022,18 +1072,20 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) * HASH values. */ tpriv->vlan_id = 0x123; - dev_add_pack(&tpriv->pt); ret = vlan_vid_add(priv->dev, htons(ETH_P_8021AD), tpriv->vlan_id); if (ret) goto cleanup; + attr.vlan = 2; + attr.dst = priv->dev->dev_addr; + attr.sport = 9; + attr.dport = 9; + + stmmac_sft_add_pack(&tpriv->pt); + for (i = 0; i < 4; i++) { - attr.vlan = 2; attr.vlan_id_out = tpriv->vlan_id + i; - attr.dst = priv->dev->dev_addr; - attr.sport = 9; - attr.dport = 9; skb = stmmac_test_get_udp_skb(priv, &attr); if (!skb) { @@ -1060,9 +1112,9 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) } vlan_del: + stmmac_sft_remove_pack(&tpriv->pt); vlan_vid_del(priv->dev, htons(ETH_P_8021AD), tpriv->vlan_id); cleanup: - dev_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -1293,7 +1345,6 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) tpriv->pt.af_packet_priv = tpriv; tpriv->packet = &attr; tpriv->vlan_id = 0x123; - dev_add_pack(&tpriv->pt); ret = vlan_vid_add(priv->dev, htons(proto), tpriv->vlan_id); if (ret) @@ -1301,6 +1352,8 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) attr.dst = priv->dev->dev_addr; + stmmac_sft_add_pack(&tpriv->pt); + skb = stmmac_test_get_udp_skb(priv, &attr); if (!skb) { ret = -ENOMEM; @@ -1318,9 +1371,9 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) ret = tpriv->ok ? 0 : -ETIMEDOUT; vlan_del: + stmmac_sft_remove_pack(&tpriv->pt); vlan_vid_del(priv->dev, htons(proto), tpriv->vlan_id); cleanup: - dev_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } From c8c1795aa8106293020a836d01e127e98f442925 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:33 +0200 Subject: [PATCH 1065/1417] net: stmmac: selftests: Validate EEE based on the actual LPI timer value The EEE selftest is a 2-step test : - It validates that we enter in LPI mode with the irq_tx_path_in_lpi_mode_n counter - It then validates that we exit LPI when sending a frame, with the irq_tx_path_exit_lpi_mode_n counter. The current state of the test lacks 2 main things : - We don't know exactly when was the previous frame sent (it's from the previous selftest) - The timeout is hardcoded, while the LPI is entered after a user-configurable delay. On top of that, the timeout loop uses a pre-decrement iterator (--retries) that actually only iterate nine times, so 900ms while the default LPI value is 1 second. Let's therefore make it more deterministic : - Send a frame at the beginning of the test - Wait for more than the lpi timer value, we timeout after about twice the value, - Then send another frame, and verify that we do go out of LPI, also with a timeout. As LPI timer can get pretty high, bail out if LPI timer is over 5 seconds. Note that the test's goal isn't to validate the LPI timer value itself, only that we enter/leave LPI mode. Fixes: 091810dbded9 ("net: stmmac: Introduce selftests support") Reviewed-by: Nicolai Buchwitz Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260917215339.2022523-3-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../stmicro/stmmac/stmmac_selftests.c | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 614b5995dec5f1..2f9f7746c40ae5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -30,6 +30,7 @@ struct stmmachdr { sizeof(struct stmmachdr)) #define STMMAC_TEST_PKT_MAGIC 0xdeadcafecafedeadULL #define STMMAC_LB_TIMEOUT msecs_to_jiffies(200) +#define STMMAC_SFT_MAX_LPI (5 * USEC_PER_SEC) struct stmmac_packet_attrs { int vlan; @@ -462,12 +463,16 @@ static int stmmac_test_mmc(struct stmmac_priv *priv) static int stmmac_test_eee(struct stmmac_priv *priv) { struct stmmac_extra_stats *initial, *final; - int retries = 10; + unsigned long timeout, max_duration; int ret; if (!priv->dma_cap.eee || !priv->eee_active) return -EOPNOTSUPP; + /* Bail out if the configured LPI timer is too long */ + if (priv->tx_lpi_timer > STMMAC_SFT_MAX_LPI) + return -EOPNOTSUPP; + initial = kzalloc_obj(*initial); if (!initial) return -ENOMEM; @@ -478,14 +483,21 @@ static int stmmac_test_eee(struct stmmac_priv *priv) goto out_free_initial; } + /* Snapshot stats, we want to count the in_lpi events. We may enter + * LPI just after the packet was sent. + */ memcpy(initial, &priv->xstats, sizeof(*initial)); + /* Send a frame, then wait to enter LPI */ ret = stmmac_test_mac_loopback(priv); if (ret) goto out_free_final; + max_duration = usecs_to_jiffies(2 * priv->tx_lpi_timer); + /* We have no traffic in the line so, sooner or later it will go LPI */ - while (--retries) { + timeout = jiffies + max_duration; + while (!time_after(jiffies, timeout)) { memcpy(final, &priv->xstats, sizeof(*final)); if (final->irq_tx_path_in_lpi_mode_n > @@ -494,20 +506,38 @@ static int stmmac_test_eee(struct stmmac_priv *priv) msleep(100); } - if (!retries) { + memcpy(final, &priv->xstats, sizeof(*final)); + if (final->irq_tx_path_in_lpi_mode_n <= + initial->irq_tx_path_in_lpi_mode_n) { ret = -ETIMEDOUT; goto out_free_final; } - if (final->irq_tx_path_in_lpi_mode_n <= - initial->irq_tx_path_in_lpi_mode_n) { - ret = -EINVAL; + /* Re-snapshot, as we want to measure exit_lpi events. We should be + * in LPI right now. + */ + memcpy(initial, &priv->xstats, sizeof(*initial)); + + /* TX something so we go out of LPI */ + ret = stmmac_test_mac_loopback(priv); + if (ret) goto out_free_final; + + /* Wait for the exit LPI interrupt */ + timeout = jiffies + max_duration; + while (!time_after(jiffies, timeout)) { + memcpy(final, &priv->xstats, sizeof(*final)); + + if (final->irq_tx_path_exit_lpi_mode_n > + initial->irq_tx_path_exit_lpi_mode_n) + break; + msleep(100); } + memcpy(final, &priv->xstats, sizeof(*final)); if (final->irq_tx_path_exit_lpi_mode_n <= initial->irq_tx_path_exit_lpi_mode_n) { - ret = -EINVAL; + ret = -ETIMEDOUT; goto out_free_final; } From ba804b23d76d278ee475b8427fa7c5623ce5e270 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:34 +0200 Subject: [PATCH 1066/1417] net: stmmac: selftests: Check the dev->features for S-TAG offload testing The S-TAG offload insertion incorrectly checks the dvlan (double vlan) DMA cap, which is different than S-TAG support. Use NETIF_F_HW_VLAN_STAG_TX to check if the feature is supported instead. Note that this flag isn't set in stmmac yet, but contrary to ARP offload, this is a feature that has a chance to get there eventually so let's leave the selftest here for now. It'll report -EOPNOTSUPP in the meantime. Fixes: 091810dbded9 ("net: stmmac: Introduce selftests support") Reviewed-by: Nicolai Buchwitz Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260917215339.2022523-4-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 2f9f7746c40ae5..de02c0da56dc3f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -1415,7 +1415,7 @@ static int stmmac_test_vlanoff(struct stmmac_priv *priv) static int stmmac_test_svlanoff(struct stmmac_priv *priv) { - if (!priv->dma_cap.dvlan) + if (!(priv->dev->features & NETIF_F_HW_VLAN_STAG_TX)) return -EOPNOTSUPP; return stmmac_test_vlanoff_common(priv, true); } From 960db6f65788c21249ea04a947d5c01e38d19294 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:35 +0200 Subject: [PATCH 1067/1417] net: stmmac: selftests: Capture all packets for vlan checks While we use vlan_vid_add to trigger the tag filtering machinery in the driver, there's no netdev associated to the VLAN. This causes the skb to arrive with empty skb->vlan_tci fields, as the packet is marked OTHERHOST in __netif_receive_skb_core(), and we fail our validation. Let's use the proxy mechanism introduced for DSA, that registers a ETH_P_ALL packet handler that runs earlier, before the vlan netdev lookup, then filters for the correct ethertype before passing an skb clone to our validation function. As we may receive external frames with the right tag from the outside, let's move the address check in the vlan validation function earlier. Fixes: 091810dbded9 ("net: stmmac: Introduce selftests support") Reviewed-by: Nicolai Buchwitz Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260917215339.2022523-5-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../stmicro/stmmac/stmmac_selftests.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index de02c0da56dc3f..43b8411c511208 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -242,6 +242,7 @@ struct stmmac_test_priv { __be16 packet_type; int (*func)(struct sk_buff *skb, struct net_device *ndev, struct packet_type *pt, struct net_device *orig_ndev); + bool capture_all; int double_vlan; int vlan_id; int ok; @@ -344,13 +345,15 @@ static void stmmac_sft_add_pack(struct packet_type *pt) { struct stmmac_test_priv *tpriv = pt->af_packet_priv; - if (netdev_uses_dsa(tpriv->pt.dev)) { + if (netdev_uses_dsa(tpriv->pt.dev) || tpriv->capture_all) { tpriv->packet_type = tpriv->pt.type; tpriv->func = tpriv->pt.func; /* DSA conduit will report ETH_P_XDSA, so our packet handler * won't match. Let's register a ETH_P_ALL match and filter - * manually in stmmac_sft_filter. + * manually in stmmac_sft_filter. This is also useful for + * VLAN tests, to capture packets otherwise marked as + * OTHERHOST. */ tpriv->pt.type = htons(ETH_P_ALL); tpriv->pt.func = stmmac_sft_filter; @@ -943,6 +946,11 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, goto out; if (skb_headlen(skb) < (STMMAC_TEST_PKT_SIZE - ETH_HLEN)) goto out; + + ehdr = (struct ethhdr *)skb_mac_header(skb); + if (!ether_addr_equal_unaligned(ehdr->h_dest, tpriv->packet->dst)) + goto out; + if (tpriv->vlan_id) { if (skb->vlan_proto != htons(proto)) goto out; @@ -954,10 +962,6 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, } } - ehdr = (struct ethhdr *)skb_mac_header(skb); - if (!ether_addr_equal_unaligned(ehdr->h_dest, tpriv->packet->dst)) - goto out; - ihdr = ip_hdr(skb); if (tpriv->double_vlan) ihdr = (struct iphdr *)(skb_network_header(skb) + 4); @@ -999,6 +1003,7 @@ static int __stmmac_test_vlanfilt(struct stmmac_priv *priv) tpriv->pt.dev = priv->dev; tpriv->pt.af_packet_priv = tpriv; tpriv->packet = &attr; + tpriv->capture_all = true; /* * As we use HASH filtering, false positives may appear. This is a @@ -1095,6 +1100,7 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) tpriv->pt.dev = priv->dev; tpriv->pt.af_packet_priv = tpriv; tpriv->packet = &attr; + tpriv->capture_all = true; /* * As we use HASH filtering, false positives may appear. This is a @@ -1375,6 +1381,7 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) tpriv->pt.af_packet_priv = tpriv; tpriv->packet = &attr; tpriv->vlan_id = 0x123; + tpriv->capture_all = true; ret = vlan_vid_add(priv->dev, htons(proto), tpriv->vlan_id); if (ret) From b42e7012773a0e81e97a2dda6ef907f5147a6658 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:36 +0200 Subject: [PATCH 1068/1417] net: stmmac: dwmac4: Use the correct bufzise when the len is exactly 8K DMA bufsize selection isn't made on the MTU but the actual frame length, so including the L2 header. On DWMAC4, if the len is exactly BUF_SIZE_8KiB, the next larger size is incorrectly selected. Lets fix the comparison and while at it, rename the parameter from len to mtu. Fixes: c3efed5ad1b0 ("net: stmmac: Enable dwmac4 jumbo frame more than 8KiB"). Signed-off-by: Maxime Chevallier Reviewed-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260917215339.2022523-6-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 4 ++-- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/ring_mode.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index 2994df41ec2c4e..c6a8f8d7350150 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -474,11 +474,11 @@ static void dwmac4_set_sarc(struct dma_desc *p, u32 sarc_type) sarc_type)); } -static int set_16kib_bfsize(int mtu) +static int set_16kib_bfsize(int len) { int ret = 0; - if (unlikely(mtu >= BUF_SIZE_8KiB)) + if (unlikely(len > BUF_SIZE_8KiB)) ret = BUF_SIZE_16KiB; return ret; } diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 9314bcb85c221d..857f7562c6c6dc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -540,7 +540,7 @@ struct stmmac_mode_ops { bool (*is_jumbo_frm)(unsigned int len, bool enh_desc); int (*jumbo_frm)(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, int csum); - int (*set_16kib_bfsize)(int mtu); + int (*set_16kib_bfsize)(int len); void (*init_desc3)(struct dma_desc *p); void (*refill_desc3)(struct stmmac_rx_queue *rx_q, struct dma_desc *p); void (*clean_desc3)(struct stmmac_tx_queue *tx_q, struct dma_desc *p); diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c index f7949419eb9fdc..d2f0c321661d99 100644 --- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c @@ -124,10 +124,10 @@ static void clean_desc3(struct stmmac_tx_queue *tx_q, struct dma_desc *p) p->des3 = 0; } -static int set_16kib_bfsize(int mtu) +static int set_16kib_bfsize(int len) { int ret = 0; - if (unlikely(mtu > BUF_SIZE_8KiB)) + if (unlikely(len > BUF_SIZE_8KiB)) ret = BUF_SIZE_16KiB; return ret; } From b8a26d46c0a4254f8bfe143681adb9d18d020299 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:37 +0200 Subject: [PATCH 1069/1417] net: stmmac: size the RX buffers from the frame length, not the MTU When picking the buffsize to use based on the MTU, we shouldn't check only the MTU value, but also : - ETH_HLEN for the L2 header, - up to 2 VLAN tags, - the FCS, The default bufsize is 1536 bytes, which is enough to contain all the above so this hasn't surfaced before, but the addition of NET_IP_ALIGN to the start of buffer address tripped the Jumbo selftest, leading to this discovery. With that, we don't need the '>=' checks on the buffer len, we can use more consistent comparison operators in stmmac_set_bfsize. Fixes: 286a83721720 ("stmmac: add CHAINED descriptor mode support (V4)") Reviewed-by: Nicolai Buchwitz Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260917215339.2022523-7-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 1fb5f804ea2345..d5a984ad864f23 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1536,17 +1536,17 @@ static unsigned int stmmac_rx_offset(struct stmmac_priv *priv) return NET_SKB_PAD + NET_IP_ALIGN; } -static int stmmac_set_bfsize(int mtu) +static int stmmac_set_bfsize(int len) { int ret; - if (mtu >= BUF_SIZE_8KiB) + if (len > BUF_SIZE_8KiB) ret = BUF_SIZE_16KiB; - else if (mtu >= BUF_SIZE_4KiB) + else if (len > BUF_SIZE_4KiB) ret = BUF_SIZE_8KiB; - else if (mtu >= BUF_SIZE_2KiB) + else if (len > BUF_SIZE_2KiB) ret = BUF_SIZE_4KiB; - else if (mtu > DEFAULT_BUFSIZE) + else if (len > DEFAULT_BUFSIZE) ret = BUF_SIZE_2KiB; else ret = DEFAULT_BUFSIZE; @@ -4063,7 +4063,7 @@ static struct stmmac_dma_conf * stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu) { struct stmmac_dma_conf *dma_conf; - int bfsize, ret; + int bfsize, len, ret; u8 chan; dma_conf = kzalloc_obj(*dma_conf); @@ -4073,13 +4073,15 @@ stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu) return ERR_PTR(-ENOMEM); } - /* Returns 0 or BUF_SIZE_16KiB if mtu > 8KiB and dwmac4 or ring mode */ - bfsize = stmmac_set_16kib_bfsize(priv, mtu); + len = mtu + ETH_HLEN + 2 * VLAN_HLEN + ETH_FCS_LEN; + + /* Returns 0 or BUF_SIZE_16KiB if len > 8KiB and dwmac4 or ring mode */ + bfsize = stmmac_set_16kib_bfsize(priv, len); if (bfsize < 0) bfsize = 0; if (bfsize < BUF_SIZE_16KiB) - bfsize = stmmac_set_bfsize(mtu); + bfsize = stmmac_set_bfsize(len); dma_conf->dma_buf_sz = bfsize; /* Chose the tx/rx size from the already defined one in the From c4ac6e94eb9423126bda907a7f2933284f7450ff Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:38 +0200 Subject: [PATCH 1070/1417] net: stmmac: selftests: Account for alignment shift on dwmac1000 for Jumbo test On dwmac1000, we currently only support single-descriptor frames. The Jumbo test started failing when NET_IP_ALIGN was added to align the IP header, as this tests tries to send the biggest possible frame. On dwmac1000 the DMA transfer is aligned on 4-bytes, so adding a 2-byte shift at the start-of-buffer address means it takes a whole extra 4-byte DMA burst to receive the Jumbo packet, causing it to spill over the next descriptor. This doesn't seem to happen on dwmac4 and xgmac that appear to correctly handle unaligned xfers (only tested on dwmac4) Let's account for that in the Jumbo test, reduce the size of our big packet by the align size. Fixes: 23680bf5f8c6 ("net: stmmac: restore NET_IP_ALIGN in the RX DMA offset") Reviewed-by: Nicolai Buchwitz Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260917215339.2022523-8-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 43b8411c511208..c25dc9f89270cc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -1789,6 +1789,9 @@ static int __stmmac_test_jumbo(struct stmmac_priv *priv, u16 queue) struct stmmac_packet_attrs attr = { }; int size = priv->dma_conf.dma_buf_sz; + if (!dwmac_is_xmac(priv->plat->core_type)) + size -= NET_IP_ALIGN; + attr.dst = priv->dev->dev_addr; attr.max_size = size - ETH_FCS_LEN; attr.queue_mapping = queue; From 0160953d8eec75c3c55562158c46442ff1fd410b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20T=C3=B6pel?= Date: Fri, 18 Sep 2026 13:46:40 +0200 Subject: [PATCH 1071/1417] eth: fbnic: Avoid rounding zero ring sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit roundup_pow_of_two() is undefined for zero. ethtool permits a zero ring size to reach the driver, where the minimum-size check should reject it. Leave zero unchanged while rounding nonzero ring sizes. The minimum-size check then rejects zero deterministically without changing the established behavior for other values. Fixes: 6cbf18a05c06 ("eth: fbnic: support ring size configuration") Reported-by: Sashiko Link: https://lore.kernel.org/netdev/178971206933.22033.236948278674126701@kernel.org/ Suggested-by: Alexander Duyck Signed-off-by: Björn Töpel Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260918114641.1281172-1-bjorn@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c index 423f179c9d4756..76e9a545bb1654 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c @@ -313,6 +313,11 @@ fbnic_get_ringparam(struct net_device *netdev, struct ethtool_ringparam *ring, kernel_ring->hds_thresh = fbn->hds_thresh; } +static u32 fbnic_ring_size_pow2(u32 size) +{ + return size ? roundup_pow_of_two(size) : 0; +} + static void fbnic_set_rings(struct fbnic_net *fbn, struct ethtool_ringparam *ring, struct kernel_ethtool_ringparam *kernel_ring) @@ -334,10 +339,10 @@ fbnic_set_ringparam(struct net_device *netdev, struct ethtool_ringparam *ring, struct fbnic_net *clone; int err; - ring->rx_pending = roundup_pow_of_two(ring->rx_pending); - ring->rx_mini_pending = roundup_pow_of_two(ring->rx_mini_pending); - ring->rx_jumbo_pending = roundup_pow_of_two(ring->rx_jumbo_pending); - ring->tx_pending = roundup_pow_of_two(ring->tx_pending); + ring->rx_pending = fbnic_ring_size_pow2(ring->rx_pending); + ring->rx_mini_pending = fbnic_ring_size_pow2(ring->rx_mini_pending); + ring->rx_jumbo_pending = fbnic_ring_size_pow2(ring->rx_jumbo_pending); + ring->tx_pending = fbnic_ring_size_pow2(ring->tx_pending); /* These are absolute minimums allowing the device and driver to operate * but not necessarily guarantee reasonable performance. Settings below From 9c572a83037a7dcd653ba3a9cc468c16b857d0c9 Mon Sep 17 00:00:00 2001 From: Bernard Ladenthin Date: Fri, 18 Sep 2026 15:39:37 +0200 Subject: [PATCH 1072/1417] net/sched: fix potential stack infoleak in em_text_dump() em_text_dump() allocates struct tcf_em_text on the stack without zeroing it. strscpy() writes the algorithm name and a NUL terminator into conf.algo[], leaving the remaining bytes uninitialised. nla_put_nohdr() then copies the full struct to the netlink response. KMSAN on Linux 7.2-rc6 reports two kernel-infoleak splats from this path, one triggered via "tc filter show" and one via a raw RTM_GETTFILTER dump: BUG: KMSAN: kernel-infoleak in _copy_to_iter+0x1c9/0x2620 nla_put_nohdr+0x83/0x130 em_text_dump+0x291/0x550 Local variable conf created at: em_text_dump+0x5d/0x550 Bytes 168-179 of 199 are uninitialized I am not certain whether this constitutes a real security problem in practice: the test was conducted in a controlled KMSAN environment and the leaked stack bytes may or may not carry sensitive data on actual production kernels. I am reporting it because KMSAN flagged it as a kernel-infoleak and the fix is straightforward. I can provide a userspace reproducer on request. The original code used strncpy() which zero-pads to the destination size. Commit b04202d6065c ("net/sched: replace strncpy with strscpy") replaced it with strscpy(), which does not pad, creating this condition. Zero-initialising the struct closes it. Fixes: b04202d6065c ("net/sched: replace strncpy with strscpy") Link: https://lore.kernel.org/netdev/20250327143733.187438-1-richard120310@gmail.com/ Assisted-by: Claude:claude-sonnet-4-6 [KMSAN] Signed-off-by: Bernard Ladenthin Acked-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260918133953.12494-1-bernard.ladenthin@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/em_text.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/em_text.c b/net/sched/em_text.c index 343f1aebeec2ae..4132f8c3c5fc95 100644 --- a/net/sched/em_text.c +++ b/net/sched/em_text.c @@ -113,7 +113,7 @@ static void em_text_destroy(struct tcf_ematch *m) static int em_text_dump(struct sk_buff *skb, struct tcf_ematch *m) { struct text_match *tm = EM_TEXT_PRIV(m); - struct tcf_em_text conf; + struct tcf_em_text conf = {}; strscpy(conf.algo, tm->config->ops->name); conf.from_offset = tm->from_offset; From 6db1ce73e9853f533eb7f413f14ba00f8ec6f80d Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 20 Sep 2026 21:23:04 +0800 Subject: [PATCH 1073/1417] bpf: Reject dev-bound-only programs on other devices __bpf_offload_dev_match() falls back to comparing offdev pointers after an exact netdev mismatch. Bound-only programs normally have NULL offdevs, so unrelated netdevs compare equal. A bound-only program on an offload-registered netdev can instead inherit a real offdev and match a sibling port. With CAP_BPF and CAP_NET_ADMIN, a caller can use bpf(BPF_LINK_CREATE) with a different target ifindex to run metadata kfuncs specialized for the bound driver on the target driver's xdp_buff. Running a veth-bound program on tun reads beyond tun's bare stack xdp_buff as a veth_xdp_buff. Oops: general protection fault, probably for non-canonical address KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017] RIP: 0010:veth_xdp_rx_timestamp (drivers/net/veth.c:1673) Call Trace: ... tun_build_skb (drivers/net/tun.c:1739) tun_get_user (drivers/net/tun.c:1856) tun_chr_write_iter (drivers/net/tun.c:2091) vfs_write (fs/read_write.c:595 fs/read_write.c:687) ksys_write (fs/read_write.c:739) do_syscall_64 (arch/x86/entry/syscall_64.c:84) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Kernel panic - not syncing: Fatal exception in interrupt Restrict non-offloaded programs to exact netdev matches and retain the shared-offdev fallback only for genuinely offloaded multi-port programs. Fixes: 2b3486bc2d23 ("bpf: Introduce device-bound XDP programs") Reported-by: Signed-off-by: Weiming Shi Signed-off-by: Alexei Starovoitov Link: https://lore.kernel.org/bpf/20260917161335.1020405-2-bestswngs@gmail.com/ Link: https://patch.msgid.link/20260920132303.4109240-3-bestswngs@gmail.com --- kernel/bpf/offload.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/bpf/offload.c b/kernel/bpf/offload.c index 0d6f5569588c35..d855399812eec3 100644 --- a/kernel/bpf/offload.c +++ b/kernel/bpf/offload.c @@ -698,6 +698,8 @@ static bool __bpf_offload_dev_match(struct bpf_prog *prog, return false; if (offload->netdev == netdev) return true; + if (!bpf_prog_is_offloaded(prog->aux)) + return false; ondev1 = bpf_offload_find_netdev(offload->netdev); ondev2 = bpf_offload_find_netdev(netdev); From 76ebb69da677d2a4c5e59bf758b6424d511f5684 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 17 Sep 2026 13:49:03 +0200 Subject: [PATCH 1074/1417] Revert "selftests/filesystems: add mntns cleanup test" This reverts commit 3452eecbcc954ef859b7d19309c121a78d6d0e31. The test checks that the mounts of a destroyed mount namespace stay connected. The commit that made them stay connected is reverted next because it leaks superblocks and loop devices, so drop the test with it. Link: https://patch.msgid.link/20260917-work-put_mnt_ns-revert-v1-1-34d9b8679e68@kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/Makefile | 1 - .../filesystems/mntns_cleanup/.gitignore | 2 - .../filesystems/mntns_cleanup/Makefile | 6 -- .../mntns_cleanup/mntns_cleanup_test.c | 58 ------------------- 4 files changed, 67 deletions(-) delete mode 100644 tools/testing/selftests/filesystems/mntns_cleanup/.gitignore delete mode 100644 tools/testing/selftests/filesystems/mntns_cleanup/Makefile delete mode 100644 tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 330d061f6366fc..273853937c2546 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -49,7 +49,6 @@ TARGETS += filesystems/move_mount TARGETS += filesystems/empty_mntns TARGETS += filesystems/fsmount_ns TARGETS += filesystems/fscontext_ns -TARGETS += filesystems/mntns_cleanup TARGETS += filesystems/xattr TARGETS += firmware TARGETS += fpu diff --git a/tools/testing/selftests/filesystems/mntns_cleanup/.gitignore b/tools/testing/selftests/filesystems/mntns_cleanup/.gitignore deleted file mode 100644 index 493fbcf8d9ec3c..00000000000000 --- a/tools/testing/selftests/filesystems/mntns_cleanup/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -mntns_cleanup_test diff --git a/tools/testing/selftests/filesystems/mntns_cleanup/Makefile b/tools/testing/selftests/filesystems/mntns_cleanup/Makefile deleted file mode 100644 index 0e09e7030a5cf2..00000000000000 --- a/tools/testing/selftests/filesystems/mntns_cleanup/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -TEST_GEN_PROGS := mntns_cleanup_test - -CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) - -include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c b/tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c deleted file mode 100644 index 5209712568b175..00000000000000 --- a/tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include - -#include "../../kselftest_harness.h" - -FIXTURE(mntns_cleanup) { -}; - -FIXTURE_SETUP(mntns_cleanup) -{ - if (geteuid() != 0) - SKIP(return, "test requires CAP_SYS_ADMIN"); - - ASSERT_EQ(unshare(CLONE_NEWNS), 0); - ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); - - rmdir("/mnt_dir"); - ASSERT_EQ(mkdir("/mnt_dir", 0755), 0); - ASSERT_EQ(mount("tmpfs", "/mnt_dir", "tmpfs", 0, NULL), 0); - ASSERT_EQ(mkdir("/mnt_dir/hidden", 0755), 0); - ASSERT_EQ(mkdir("/mnt_dir/hidden/secret", 0755), 0); - ASSERT_EQ(mount("tmpfs", "/mnt_dir/hidden", "tmpfs", 0, NULL), 0); -} - -FIXTURE_TEARDOWN(mntns_cleanup) -{ -} - -/* Mounts must stay connected when a mount namespace is cleaned up. */ -TEST_F(mntns_cleanup, keeps_mounts_connected) -{ - int fd, sfd, err; - - fd = open("/mnt_dir", O_PATH | O_DIRECTORY | O_CLOEXEC); - ASSERT_GE(fd, 0); - - /* Destroy the namespace; the fd keeps /mnt_dir alive. */ - ASSERT_EQ(unshare(CLONE_NEWNS), 0); - - sfd = openat(fd, "hidden/secret", O_RDONLY); - err = errno; - if (sfd >= 0) - close(sfd); - close(fd); - - ASSERT_LT(sfd, 0) - TH_LOG("mount namespace teardown revealed what the overmount covered"); - ASSERT_EQ(err, ENOENT); -} - -TEST_HARNESS_MAIN From 2e2142a35d809c3cc726d3b39e7aeee98d9ab71c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 17 Sep 2026 13:49:04 +0200 Subject: [PATCH 1075/1417] Revert "put_mnt_ns(): leave mounts connected" This reverts commit 0342482a4d15358fe6931606caf58968de5d1d38. Keeping every mount of a dying namespace attached to its parent changes who owns it. An attached but unmounted mount is owned by its parent: the parent's final mntput() is what drops the child's reference and takes its superblock down. Before that commit only the namespace root was in that position and every other mount dropped its own reference in namespace_unlock(). That ownership rule turns any reference from a child's superblock back to one of its ancestors into a cycle. The obvious case is a loop device: unshare -m sh -c 'mount -o loop /var/tmp/img /var/tmp/mp' mount(8) opens the image from inside the new namespace, so the loop device's backing file pins that namespace's copy of the root mount. When the namespace dies the loop mount stays attached to that copy and is owned by it. The copy can't reach zero because the backing file holds it. The backing file is only put once the ext4 superblock releases the block device and autoclear runs, and that needs the loop mount to go first. Nothing breaks the cycle. The superblock, the loop device and, since the root copy keeps all of its children. systemd-sysext sets up its loop devices in a private mount namespace and relies on autoclear when that namespace goes away, which is how this was found. Anything that holds a file on an ancestor from a mounted filesystem has the same problem. That includes ecryptfs lower paths, erofs file-backed mounts, fuse passthrough backing files. That's a bigger fix and not an -rc change. Revert. Fixes: 0342482a4d15 ("put_mnt_ns(): leave mounts connected") Reported-by: Michael Vogt Link: https://gist.github.com/mvo5/63ef46482349f3b1c3957d463a0c9c6f Link: https://patch.msgid.link/20260917-work-put_mnt_ns-revert-v1-2-34d9b8679e68@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/namespace.c b/fs/namespace.c index 5e41021eaa6305..580877e46b1acc 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -6301,7 +6301,7 @@ void put_mnt_ns(struct mnt_namespace *ns) guard(namespace_excl)(); emptied_ns = ns; guard(mount_writer)(); - umount_tree(ns->root, UMOUNT_CONNECTED); + umount_tree(ns->root, 0); } struct vfsmount *kern_mount(struct file_system_type *type) From 1feb5d39b05afd902ed9fc902ec5b15be03a4bdb Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 22 Sep 2026 10:52:28 +0200 Subject: [PATCH 1076/1417] gpio: cdev: fix kernel stack leak to user-space in error path If we fail to acquire the GPIO chip guard in gpio_desc_to_lineinfo(), we return immediately before zeroing the info struct we'll end up passing to the user-space later in lineinfo_get_v1(). This may leak the kernel stack contents. Make gpio_desc_to_lineinfo() return int so that the -ENODEV returned on failure to acquire the guard can be propagated to the callers. While not strictly necessary: move the memset() before trying to acquire the SRCU read lock too for good measure. Fixes: d83cee3d2bb1 ("gpio: protect the pointer to gpio_chip in gpio_device with SRCU") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260912123529.7951-1-tzungbi%40kernel.org?part=3 Reviewed-by: Kent Gibson Link: https://patch.msgid.link/20260922-gpio-cdev-stack-leak-fixes-v3-1-7a0c7a4299d5@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index d1105b7ae43777..5d53bfcdf72687 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2172,18 +2172,19 @@ static void gpio_v2_line_info_changed_to_v1( #endif /* CONFIG_GPIO_CDEV_V1 */ -static void gpio_desc_to_lineinfo(struct gpio_desc *desc, - struct gpio_v2_line_info *info, bool atomic) +static int gpio_desc_to_lineinfo(struct gpio_desc *desc, + struct gpio_v2_line_info *info, bool atomic) { u32 debounce_period_us; unsigned long dflags; const char *label; + memset(info, 0, sizeof(*info)); + CLASS(gpio_chip_guard, guard)(desc); if (!guard.gc) - return; + return -ENODEV; - memset(info, 0, sizeof(*info)); info->offset = gpiod_hwgpio(desc); if (desc->name) @@ -2258,6 +2259,8 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc, debounce_period_us; info->num_attrs++; } + + return 0; } struct gpio_chardev_data { @@ -2309,6 +2312,7 @@ static int lineinfo_get_v1(struct gpio_chardev_data *cdev, void __user *ip, struct gpio_desc *desc; struct gpioline_info lineinfo; struct gpio_v2_line_info lineinfo_v2; + int ret; if (copy_from_user(&lineinfo, ip, sizeof(lineinfo))) return -EFAULT; @@ -2326,7 +2330,10 @@ static int lineinfo_get_v1(struct gpio_chardev_data *cdev, void __user *ip, return -EBUSY; } - gpio_desc_to_lineinfo(desc, &lineinfo_v2, false); + ret = gpio_desc_to_lineinfo(desc, &lineinfo_v2, false); + if (ret) + return ret; + gpio_v2_line_info_to_v1(&lineinfo_v2, &lineinfo); if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) { @@ -2344,6 +2351,7 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip, { struct gpio_desc *desc; struct gpio_v2_line_info lineinfo; + int ret; if (copy_from_user(&lineinfo, ip, sizeof(lineinfo))) return -EFAULT; @@ -2363,7 +2371,10 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip, if (test_and_set_bit(lineinfo.offset, cdev->watched_lines)) return -EBUSY; } - gpio_desc_to_lineinfo(desc, &lineinfo, false); + + ret = gpio_desc_to_lineinfo(desc, &lineinfo, false); + if (ret) + return ret; if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) { if (watch) @@ -2489,6 +2500,7 @@ static int lineinfo_changed_notify(struct notifier_block *nb, struct lineinfo_changed_ctx *ctx; struct gpio_desc *desc = data; struct file *fp; + int ret; if (!test_bit(gpiod_hwgpio(desc), cdev->watched_lines)) return NOTIFY_DONE; @@ -2519,7 +2531,13 @@ static int lineinfo_changed_notify(struct notifier_block *nb, ctx->chg.event_type = action; ctx->chg.timestamp_ns = ktime_get_ns(); - gpio_desc_to_lineinfo(desc, &ctx->chg.info, true); + + ret = gpio_desc_to_lineinfo(desc, &ctx->chg.info, true); + if (ret) { + fput(fp); + return NOTIFY_DONE; + } + /* Keep the GPIO device alive until we emit the event. */ ctx->gdev = gpio_device_get(desc->gdev); ctx->cdev = cdev; From 2d2a2d7aa98741b58f54cacc99b52024e4d865f9 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 9 Sep 2026 21:30:34 +0200 Subject: [PATCH 1077/1417] super: make iterate_supers_type() deletion-safe iterate_supers_type() drops sb_lock while invoking the callback and keeps only a passive reference to the current superblock. That reference keeps the object allocated, but does not keep its s_instances node linked. After the iterator releases s_umount, final teardown can unlink the current s_instances node. The iterator then advances through a reinitialized node. With the current hlist it stops without visiting the remaining superblocks. The unlink moved from generic_shutdown_super() to kill_super_notify(), but the cursor lifetime has been unsafe since the helper was introduced. The CIFS DFS lookup can consequently miss a matching superblock and return -EINVAL. Move removal from fs_supers to put_super(), alongside removal from super_blocks, so a passive reference keeps both list nodes linked. Keep the filesystem module reference until then, since unlinking s_instances may touch type->fs_supers. Make sget_fc() skip SB_DEAD superblocks before invoking test(), and set SB_DEAD under sb_lock to serialize with those callbacks. This allows kernfs to free its private information after kill_anon_super() returns. Keep matching SB_DYING superblocks until SB_DEAD is set so concurrent mounts still wait for teardown before retrying. Fixes: 43e15cdbefea ("new helper: iterate_supers_type()") Reported-by: Karl Mehltretter Closes: https://lore.kernel.org/r/20260903013336.92081-1-kmehltretter@gmail.com Suggested-by: Jan Kara Cc: stable@vger.kernel.org Tested-by: Karl Mehltretter [kmehltretter: supplied the commit message] Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260909193034.7467-1-kmehltretter@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/mount.c | 4 ++-- fs/super.c | 39 +++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/fs/kernfs/mount.c b/fs/kernfs/mount.c index f183a96778b9a2..a57399021c8b05 100644 --- a/fs/kernfs/mount.c +++ b/fs/kernfs/mount.c @@ -434,8 +434,8 @@ void kernfs_kill_sb(struct super_block *sb) up_write(&root->kernfs_supers_rwsem); /* - * Remove the superblock from fs_supers/s_instances - * so we can't find it, before freeing kernfs_super_info. + * Mark the superblock dead so sget_fc() can't find it, + * before freeing kernfs_super_info. */ kill_anon_super(sb); kfree(info); diff --git a/fs/super.c b/fs/super.c index 01db6124e409dc..e41cae9e5c626e 100644 --- a/fs/super.c +++ b/fs/super.c @@ -433,15 +433,19 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags, void put_super(struct super_block *s) { if (refcount_dec_and_test(&s->s_passive)) { + struct file_system_type *type = s->s_type; spin_lock(&sb_lock); list_del_init(&s->s_list); + hlist_del_init(&s->s_instances); spin_unlock(&sb_lock); WARN_ON(s->s_dentry_lru.node); WARN_ON(s->s_inode_lru.node); WARN_ON(s->s_mounts); call_rcu(&s->rcu, destroy_super_rcu); + /* The unlink above may touch type->fs_supers, so drop it last. */ + put_filesystem(type); } } @@ -558,17 +562,6 @@ static void kill_super_notify(struct super_block *sb) if (sb->s_flags & SB_DEAD) return; - /* - * Remove it from @fs_supers so it isn't found by new - * sget_fc() walkers anymore. Any concurrent mounter still - * managing to grab a temporary reference is guaranteed to - * already see SB_DYING and will wait until we notify them about - * SB_DEAD. - */ - spin_lock(&sb_lock); - hlist_del_init(&sb->s_instances); - spin_unlock(&sb_lock); - /* Drop sget_fc()'s claim; a never-registered entry stays with the sb. */ if (sb->s_super_dev->sd_dev) { super_dev_put(sb->s_super_dev); @@ -577,11 +570,15 @@ static void kill_super_notify(struct super_block *sb) /* * Let concurrent mounts know that this thing is really dead. - * We don't need @sb->s_umount here as every concurrent caller - * will see SB_DYING and either discard the superblock or wait - * for SB_DEAD. + * sget_fc() skips SB_DEAD superblocks and calls test() under + * sb_lock, so set it under sb_lock: once we return no test() + * runs on this superblock anymore and none will start. Everyone + * else already saw SB_DYING and either discarded the superblock + * or waits for SB_DEAD. */ + spin_lock(&sb_lock); super_wake(sb, SB_DEAD); + spin_unlock(&sb_lock); } /** @@ -608,7 +605,6 @@ void deactivate_locked_super(struct super_block *s) list_lru_destroy(&s->s_dentry_lru); list_lru_destroy(&s->s_inode_lru); - put_filesystem(fs); put_super(s); } else { super_unlock_excl(s); @@ -795,12 +791,12 @@ void generic_shutdown_super(struct super_block *sb) } /* * Broadcast to everyone that grabbed a temporary reference to this - * superblock before we removed it from @fs_supers that the superblock - * is dying. Every walker of @fs_supers outside of sget_fc() will now - * discard this superblock and treat it as dead. + * superblock that it is dying. Every walker of @fs_supers outside + * of sget_fc() will now discard this superblock and treat it as + * dead. * - * We leave the superblock on @fs_supers so it can be found by - * sget_fc() until we passed sb->kill_sb(). + * sget_fc() keeps finding the superblock until SB_DEAD is set, so + * a concurrent mounter waits until we passed sb->kill_sb(). */ super_wake(sb, SB_DYING); super_unlock_excl(sb); @@ -879,6 +875,9 @@ struct super_block *sget_fc(struct fs_context *fc, spin_lock(&sb_lock); if (test) { hlist_for_each_entry(old, &fc->fs_type->fs_supers, s_instances) { + /* Only unlinked at the last passive reference. */ + if (super_flags(old, SB_DEAD)) + continue; if (test(old, fc)) goto share_extant_sb; } From e9438ab5328a177c9c0e5df87eb92a7162841e98 Mon Sep 17 00:00:00 2001 From: Ridham Khurana Date: Tue, 22 Sep 2026 09:20:58 +0000 Subject: [PATCH 1078/1417] gpio: zynq: fix runtime PM leak on request error path pm_runtime_get_sync() leaves the usage counter incremented even when it fails, and zynq_gpio_request() returns the error without dropping it. gpiolib does not call ->free() when ->request() fails, so zynq_gpio_free(), which holds the only matching pm_runtime_put(), never runs. The reference is leaked and the controller can no longer runtime-suspend, so its clock stays enabled. Switch to pm_runtime_resume_and_get(), which only increments the usage counter on success. Fixes: 3242ba117e9b ("gpio: Add driver for Zynq GPIO controller") Cc: stable@vger.kernel.org Signed-off-by: Ridham Khurana Link: https://patch.msgid.link/20260922092102.1053513-1-khurana.ridham222@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-zynq.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-zynq.c b/drivers/gpio/gpio-zynq.c index 15a79d9a2e9eb4..13e4c5f0e297ce 100644 --- a/drivers/gpio/gpio-zynq.c +++ b/drivers/gpio/gpio-zynq.c @@ -798,15 +798,7 @@ static int zynq_gpio_runtime_resume(struct device *dev) static int zynq_gpio_request(struct gpio_chip *chip, unsigned int offset) { - int ret; - - ret = pm_runtime_get_sync(chip->parent); - - /* - * If the device is already active pm_runtime_get() will return 1 on - * success, but gpio_request still needs to return 0. - */ - return ret < 0 ? ret : 0; + return pm_runtime_resume_and_get(chip->parent); } static void zynq_gpio_free(struct gpio_chip *chip, unsigned int offset) From cca4980630b3c7a85f53cb43c6018184ce5d4e37 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Sun, 20 Sep 2026 16:16:39 -0700 Subject: [PATCH 1079/1417] perf/core: Fix a refcount leak in attach_perf_ctx_data() The attach_perf_ctx_data() can race on global and !global cases. The global case is protected by global_ctx_data_rwsem and shares a single reference count using perf_ctx_data.global field. But when it races with !global case, it may miss to set the global field and result in a reference count leak. CPU1 CPU2 ---------------------------------------------------------------- attach_task_ctx_data(.global=1) attach_task_ctx_data(.global=0) cd1 = alloc_perf_ctx_data(); cd2 = alloc_perf_ctx_data(); // { .global = 0, .refcount = 1 }; try_cmpxchg(); // success, // task->perf_ctx_data = cd2 try_cmpxhg(); // fail; old = cd2 refcount_inc_not_zero(&old->refcount); // success // old.refcount = 2 free_perf_ctx_data(cd1); Then later detach_global_ctx_data() will see the data but it's not marked as global, so it won't call detach_task_ctx_data(). Fixes: 506e64e710ff ("perf: attach/detach PMU specific data") Assisted-by: Sashiko.dev:Gemini-3.1-pro Signed-off-by: Namhyung Kim Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260920231639.11910-1-namhyung@kernel.org --- kernel/events/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/events/core.c b/kernel/events/core.c index db7b76d6b68aa5..e180134bad5e0a 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -5454,6 +5454,8 @@ attach_task_ctx_data(struct task_struct *task, struct kmem_cache *ctx_cache, } if (refcount_inc_not_zero(&old->refcount)) { + if (global) + old->global = true; free_perf_ctx_data(cd); /* unused */ return 0; } From 36bb85cf36cab15fb611cb44b78a5df06e4e69a2 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 10 Aug 2026 06:35:34 -0700 Subject: [PATCH 1080/1417] perf/core: Fix NULL pmu_ctx passed to pmu->sched_task() perf_pmu_sched_task() returns early when cpuctx->task_ctx is set, and cpc->task_epc is only non-NULL while a task context is scheduled in on this CPU. __perf_pmu_sched_task() therefore always passes NULL: Unable to handle kernel NULL pointer dereference at virtual address 00 pc : armv8pmu_sched_task+0x14/0x50 Call trace: armv8pmu_sched_task+0x14/0x50 (P) perf_pmu_sched_task+0xac/0x108 __perf_event_task_sched_out+0x6c/0xe0 Pass &cpc->epc instead, the CPU-wide context for this PMU, which the function already dereferences a few lines up to find pmu. armv8pmu_sched_task() is the only in-tree implementation that dereferences the argument, and it only reads ->pmu, so the oops needs BRBE, added in v6.17. Fixes: bd2756811766 ("perf: Rewrite core context handling") Signed-off-by: Puranjay Mohan Signed-off-by: Peter Zijlstra (Intel) Tested-by: Yifan Wu Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260810133540.1947118-2-puranjay@kernel.org --- kernel/events/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index e180134bad5e0a..7ce72f366f6caa 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -3914,7 +3914,7 @@ static void __perf_pmu_sched_task(struct perf_cpu_pmu_context *cpc, perf_ctx_lock(cpuctx, cpuctx->task_ctx); perf_pmu_disable(pmu); - pmu->sched_task(cpc->task_epc, task, sched_in); + pmu->sched_task(&cpc->epc, task, sched_in); perf_pmu_enable(pmu); perf_ctx_unlock(cpuctx, cpuctx->task_ctx); From 3d8d74100954a3b17e5c5e37adfe14e16b1db103 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 10 Aug 2026 06:35:35 -0700 Subject: [PATCH 1081/1417] perf/core: Run sched_task() for PMUs with only CPU-wide events perf_pmu_sched_task() returns early when cpuctx->task_ctx is set and leaves the work to perf_ctx_sched_task_cb(), which only walks ctx->pmu_ctx_list. A PMU whose events are all CPU-wide is not on that list, so nothing calls its sched_task(). With perf record -b -e cycles -a -- ls armv8pmu_sched_task() is skipped on every switch to a task that has a perf context but no event on that PMU, and BRBE records leak across the task boundary. intel_pmu_lbr_add() calls perf_sched_cb_inc() unconditionally too, so LBR records leak the same way on x86. Drop the early return and skip only the CPCs that perf_ctx_sched_task_cb() handles. That one needs a gate of its own to make the split exact: it tests cpc->sched_cb_usage, which perf_sched_cb_inc() sets per CPU for every branch stack user, so a task with an event for that PMU pinned to another CPU would be handled twice. On x86 the second __intel_pmu_lbr_restore() finds lbr_stack_state == LBR_NONE and calls intel_pmu_lbr_reset(), throwing away the callstack the first one restored. cpc->task_epc is set only while a task context is scheduled in, and there is one epc per PMU on ctx->pmu_ctx_list, so the two gates are inverses. For the CPCs perf_pmu_sched_task() picks up, the callback now runs outside the perf_ctx_disable() and perf_ctx_enable() pair in perf_event_context_sched_in(). __perf_pmu_sched_task() disables the PMU around the call itself. Fixes: bd2756811766 ("perf: Rewrite core context handling") Signed-off-by: Puranjay Mohan Signed-off-by: Peter Zijlstra (Intel) Tested-by: Yifan Wu Link: https://patch.msgid.link/20260810133540.1947118-3-puranjay@kernel.org Cc: stable@vger.kernel.org --- kernel/events/core.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index 7ce72f366f6caa..634d2ccbab82d8 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -3764,6 +3764,9 @@ static void perf_ctx_sched_task_cb(struct perf_event_context *ctx, list_for_each_entry(pmu_ctx, &ctx->pmu_ctx_list, pmu_ctx_entry) { cpc = this_cpc(pmu_ctx->pmu); + if (cpc->task_epc != pmu_ctx) + continue; + if (cpc->sched_cb_usage && pmu_ctx->pmu->sched_task) pmu_ctx->pmu->sched_task(pmu_ctx, task, sched_in); } @@ -3924,15 +3927,17 @@ static void perf_pmu_sched_task(struct task_struct *prev, struct task_struct *next, bool sched_in) { - struct perf_cpu_context *cpuctx = this_cpu_ptr(&perf_cpu_context); struct perf_cpu_pmu_context *cpc, *cpc2; - /* cpuctx->task_ctx will be handled in perf_event_context_sched_in/out */ - if (prev == next || cpuctx->task_ctx) + if (prev == next) return; - list_for_each_entry_safe(cpc, cpc2, this_cpu_ptr(&sched_cb_list), sched_cb_entry) + list_for_each_entry_safe(cpc, cpc2, this_cpu_ptr(&sched_cb_list), sched_cb_entry) { + if (cpc->task_epc) + continue; + __perf_pmu_sched_task(cpc, sched_in ? next : prev, sched_in); + } } static void perf_event_switch(struct task_struct *task, From 24b620729e53d978b3e425f55bc66efd3bab1f59 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 10 Aug 2026 06:35:36 -0700 Subject: [PATCH 1082/1417] perf/core: Fill branch entries with a single assignment perf_clear_branch_entry_bitfields() clears the bitfields of struct perf_branch_entry one by one and leaves from/to alone, since callers overwrite those straight away. The list has to be kept in sync with the struct by hand and has already fallen behind: new_type and priv were added to perf_branch_entry and never added here. Only BRBE writes those two, and neither for every record. brbe_set_perf_entry_type() leaves new_type alone for a branch type it does not recognise, and priv is not set for source-only records. arm_pmuv3.c allocates the per-CPU branch stack with kmalloc(), so such a record reaches userspace with whatever the slot held: uninitialised kmalloc() data on the first pass over the buffer, the previous record's values after that. Nothing under arch/x86/events/ writes either field, so only arm64 is affected. Assign the whole entry at each site instead. Everything not named is then zero, and there is no list to keep in sync. The bitfields add up to exactly 64 bits, so the struct has no padding to leave undefined. perf_clear_branch_entry_bitfields() has no callers left, so remove it. perf_entry_from_brbe_regset() assigns an empty literal instead, since it fills from/to conditionally. PERF_BR_SPEC_NA is 0, so dropping the explicit spec assignment changes nothing. Fixes: b190bc4ac9e6 ("perf: Extend branch type classification") Fixes: 5402d25aa571 ("perf: Capture branch privilege information") Suggested-by: Peter Zijlstra Signed-off-by: Puranjay Mohan Signed-off-by: Peter Zijlstra (Intel) Tested-by: Yifan Wu Link: https://patch.msgid.link/20260810133540.1947118-4-puranjay@kernel.org --- arch/x86/events/amd/brs.c | 9 +++-- arch/x86/events/amd/lbr.c | 16 ++++----- arch/x86/events/intel/lbr.c | 65 ++++++++++++++++++++----------------- drivers/perf/arm_brbe.c | 2 +- include/linux/perf_event.h | 17 ---------- 5 files changed, 48 insertions(+), 61 deletions(-) diff --git a/arch/x86/events/amd/brs.c b/arch/x86/events/amd/brs.c index dc564688f3d73a..54b13faba116c8 100644 --- a/arch/x86/events/amd/brs.c +++ b/arch/x86/events/amd/brs.c @@ -343,11 +343,10 @@ void amd_brs_drain(void) if (!amd_brs_match_plm(event, from, to)) continue; - perf_clear_branch_entry_bitfields(br+nr); - - br[nr].from = from; - br[nr].to = to; - + br[nr] = (struct perf_branch_entry){ + .from = from, + .to = to, + }; nr++; } empty: diff --git a/arch/x86/events/amd/lbr.c b/arch/x86/events/amd/lbr.c index 9d9c961989d51c..a55646fcb8465c 100644 --- a/arch/x86/events/amd/lbr.c +++ b/arch/x86/events/amd/lbr.c @@ -184,13 +184,6 @@ void amd_pmu_lbr_read(void) entry.to.split.reserved) continue; - perf_clear_branch_entry_bitfields(br + out); - - br[out].from = sign_ext_branch_ip(entry.from.split.ip); - br[out].to = sign_ext_branch_ip(entry.to.split.ip); - br[out].mispred = entry.from.split.mispredict; - br[out].predicted = !br[out].mispred; - /* * Set branch speculation information using the status of * the valid and spec bits. @@ -208,7 +201,14 @@ void amd_pmu_lbr_read(void) * speculative and took the correct path */ idx = (entry.to.split.valid << 1) | entry.to.split.spec; - br[out].spec = lbr_spec_map[idx]; + + br[out] = (struct perf_branch_entry){ + .from = sign_ext_branch_ip(entry.from.split.ip), + .to = sign_ext_branch_ip(entry.to.split.ip), + .mispred = entry.from.split.mispredict, + .predicted = !entry.from.split.mispredict, + .spec = lbr_spec_map[idx], + }; out++; } diff --git a/arch/x86/events/intel/lbr.c b/arch/x86/events/intel/lbr.c index cbe5c762008d2d..22e2a06d5786cf 100644 --- a/arch/x86/events/intel/lbr.c +++ b/arch/x86/events/intel/lbr.c @@ -756,10 +756,10 @@ void intel_pmu_lbr_read_32(struct cpu_hw_events *cpuc) rdmsrq(x86_pmu.lbr_from + lbr_idx, msr_lastbranch.lbr); - perf_clear_branch_entry_bitfields(br); - - br->from = msr_lastbranch.from; - br->to = msr_lastbranch.to; + *br = (struct perf_branch_entry){ + .from = msr_lastbranch.from, + .to = msr_lastbranch.to, + }; br++; } cpuc->lbr_stack.nr = i; @@ -847,14 +847,15 @@ void intel_pmu_lbr_read_64(struct cpu_hw_events *cpuc) if (abort && x86_pmu.lbr_double_abort && out > 0) out--; - perf_clear_branch_entry_bitfields(br+out); - br[out].from = from; - br[out].to = to; - br[out].mispred = mis; - br[out].predicted = pred; - br[out].in_tx = in_tx; - br[out].abort = abort; - br[out].cycles = cycles; + br[out] = (struct perf_branch_entry){ + .from = from, + .to = to, + .mispred = mis, + .predicted = pred, + .in_tx = in_tx, + .abort = abort, + .cycles = cycles, + }; out++; } cpuc->lbr_stack.nr = out; @@ -905,6 +906,7 @@ static void intel_pmu_store_lbr(struct cpu_hw_events *cpuc, struct perf_branch_entry *e; struct lbr_entry *lbr; u64 from, to, info; + bool mispred; int i; for (i = 0; i < x86_pmu.lbr_nr; i++) { @@ -921,24 +923,27 @@ static void intel_pmu_store_lbr(struct cpu_hw_events *cpuc, to = rdlbr_to(i, lbr); info = rdlbr_info(i, lbr); - perf_clear_branch_entry_bitfields(e); - - e->from = from; - e->to = to; - e->mispred = get_lbr_mispred(info); - e->predicted = !e->mispred; - e->in_tx = !!(info & LBR_INFO_IN_TX); - e->abort = !!(info & LBR_INFO_ABORT); - e->cycles = get_lbr_cycles(info); - e->type = get_lbr_br_type(info); - - /* - * Leverage the reserved field of cpuc->lbr_entries[i] to - * temporarily store the branch counters information. - * The later code will decide what content can be disclosed - * to the perf tool. Pleae see intel_pmu_lbr_counters_reorder(). - */ - e->reserved = (info >> LBR_INFO_BR_CNTR_OFFSET) & LBR_INFO_BR_CNTR_FULL_MASK; + mispred = get_lbr_mispred(info); + + *e = (struct perf_branch_entry){ + .from = from, + .to = to, + .mispred = mispred, + .predicted = !mispred, + .in_tx = !!(info & LBR_INFO_IN_TX), + .abort = !!(info & LBR_INFO_ABORT), + .cycles = get_lbr_cycles(info), + .type = get_lbr_br_type(info), + /* + * Leverage the reserved field of + * cpuc->lbr_entries[i] to temporarily store the + * branch counters information. The later code will + * decide what content can be disclosed to the perf + * tool. Pleae see intel_pmu_lbr_counters_reorder(). + */ + .reserved = (info >> LBR_INFO_BR_CNTR_OFFSET) & + LBR_INFO_BR_CNTR_FULL_MASK, + }; } cpuc->lbr_stack.nr = i; diff --git a/drivers/perf/arm_brbe.c b/drivers/perf/arm_brbe.c index ba554e0c846c4f..254be4da8ae298 100644 --- a/drivers/perf/arm_brbe.c +++ b/drivers/perf/arm_brbe.c @@ -604,7 +604,7 @@ static bool perf_entry_from_brbe_regset(int index, struct perf_branch_entry *ent return false; brbinf = bregs.brbinf; - perf_clear_branch_entry_bitfields(entry); + *entry = (struct perf_branch_entry){ }; if (brbe_record_is_complete(brbinf)) { entry->from = bregs.brbsrc; entry->to = bregs.brbtgt; diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 5842552294c195..915c6fd3f08458 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1467,23 +1467,6 @@ static inline u32 perf_sample_data_size(struct perf_sample_data *data, return size; } -/* - * Clear all bitfields in the perf_branch_entry. - * The to and from fields are not cleared because they are - * systematically modified by caller. - */ -static inline void perf_clear_branch_entry_bitfields(struct perf_branch_entry *br) -{ - br->mispred = 0; - br->predicted = 0; - br->in_tx = 0; - br->abort = 0; - br->cycles = 0; - br->type = 0; - br->spec = PERF_BR_SPEC_NA; - br->reserved = 0; -} - extern void perf_output_sample(struct perf_output_handle *handle, struct perf_event_header *header, struct perf_sample_data *data, From 2a2d659b835c37c93d0200f50a549973ca03c67b Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Tue, 22 Sep 2026 13:31:20 -0500 Subject: [PATCH 1083/1417] ASoC: dt-bindings: mediatek: Fix MT7622 clock count MT7622 audio nodes have 33 clocks and clock names. The shared MT2701 binding listed the longer MT2701 clock set as the common minimum, so MT7622 DTs failed validation as too short even though the binding already capped MT7622 at a smaller list. Set the common minimum to 33 and make the MT7622 conditional require exactly 33 clocks and clock names. Assisted-by: LLM Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260922183121.289108-1-robh@kernel.org Signed-off-by: Mark Brown --- .../bindings/sound/mediatek,mt2701-audio.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt2701-audio.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt2701-audio.yaml index 30f331366566e1..10df1e7c7eb966 100644 --- a/Documentation/devicetree/bindings/sound/mediatek,mt2701-audio.yaml +++ b/Documentation/devicetree/bindings/sound/mediatek,mt2701-audio.yaml @@ -32,7 +32,7 @@ properties: maxItems: 1 clocks: - minItems: 34 + minItems: 33 items: - description: audio infra sys clock - description: top audio mux 1 @@ -74,7 +74,7 @@ properties: - description: audio APLL root pd clock-names: - minItems: 34 + minItems: 33 items: - const: infra_sys_audio_clk - const: top_audio_mux1_sel @@ -132,8 +132,10 @@ allOf: then: properties: clocks: - maxItems: 34 + minItems: 33 + maxItems: 33 clock-names: - maxItems: 34 + minItems: 33 + maxItems: 33 additionalProperties: false From 425733d0aea97c677fa335a3f37dc20057cd0e6f Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Tue, 22 Sep 2026 13:31:28 -0500 Subject: [PATCH 1084/1417] ASoC: dt-bindings: hi6210-i2s: Allow graph ports The HiKey DTS uses the HI6210 I2S controller with an audio graph ports node for the ADV7533 connection. Add the audio graph ports node so the converted schema matches the existing platform description. Assisted-by: LLM Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260922183128.289564-1-robh@kernel.org Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml b/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml index 5171f984630bec..b126982647241e 100644 --- a/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml +++ b/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml @@ -48,6 +48,10 @@ properties: The dai cell indexes reference the following interfaces: 0: S2 interface + ports: + $ref: audio-graph-port.yaml#/definitions/ports + unevaluatedProperties: false + required: - compatible - reg From 87dd248876d11621c4ed0e9fe865b39d40833535 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Tue, 22 Sep 2026 16:21:21 -0500 Subject: [PATCH 1085/1417] ASoC: dt-bindings: Fix TAS2563 address constraints Apply the TAS2781 register range only when the compatible is exactly ti,tas2781. This avoids applying the TAS2781 single-address rule to TAS2563 nodes with 0x4c-0x4f addresses. Assisted-by: LLM Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260922212122.1116270-1-robh@kernel.org Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/ti,tas2781.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/ti,tas2781.yaml b/Documentation/devicetree/bindings/sound/ti,tas2781.yaml index aa5a317b5a3618..50c7792b24f811 100644 --- a/Documentation/devicetree/bindings/sound/ti,tas2781.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tas2781.yaml @@ -219,9 +219,7 @@ allOf: - if: properties: compatible: - contains: - enum: - - ti,tas2781 + const: ti,tas2781 then: properties: reg: From 3713c74a1b0ad71b416345399d8a706c921cb47f Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:45 +0900 Subject: [PATCH 1086/1417] ASoC: codecs: cpcap: Fix devm resource leaks across card bind/unbind The ASoC component .probe/.remove are called on card bind/unbind, but component->dev is the underlying platform device. Resources requested with devm_*(component->dev, ...) or request_irq() in the component .probe are only released when the platform device is removed, not on card unbind, leaking on every bind/unbind cycle. The VAUDIO regulator is a pure hardware resource that only depends on the platform device, so acquire it with devm_regulator_get() in the platform probe where its devres lifetime matches the device. The headset/mic-button IRQs genuinely depend on the component, so keep them in cpcap_soc_probe() but request them with the non-devm request_irq() API and free them explicitly in cpcap_soc_remove(). Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-2-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/cpcap.c | 74 ++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index 0262900fbb7dcb..dda68165b8173c 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -1622,21 +1622,12 @@ static int cpcap_soc_probe(struct snd_soc_component *component) { struct platform_device *pdev = to_platform_device(component->dev); struct snd_soc_card *card = component->card; - struct cpcap_audio *cpcap; + struct cpcap_audio *cpcap = dev_get_drvdata(component->dev); int err; - cpcap = devm_kzalloc(component->dev, sizeof(*cpcap), GFP_KERNEL); - if (!cpcap) - return -ENOMEM; - snd_soc_component_set_drvdata(component, cpcap); cpcap->component = component; - cpcap->vaudio = devm_regulator_get(component->dev, "VAUDIO"); - if (IS_ERR(cpcap->vaudio)) - return dev_err_probe(component->dev, PTR_ERR(cpcap->vaudio), - "Cannot get VAUDIO regulator\n"); - err = snd_soc_card_jack_new(card, "Headphones", SND_JACK_HEADSET | SND_JACK_BTN_0, &cpcap->jack); @@ -1660,13 +1651,13 @@ static int cpcap_soc_probe(struct snd_soc_component *component) if (cpcap->hsirq < 0) return cpcap->hsirq; - err = devm_request_threaded_irq(component->dev, cpcap->hsirq, NULL, - cpcap_hs_irq_thread, - IRQF_TRIGGER_RISING | - IRQF_TRIGGER_FALLING | - IRQF_ONESHOT, - "cpcap-codec-hs", - component); + err = request_threaded_irq(cpcap->hsirq, NULL, + cpcap_hs_irq_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "cpcap-codec-hs", + component); if (err) { dev_warn(component->dev, "no HS irq%i: %i\n", cpcap->hsirq, err); @@ -1674,25 +1665,27 @@ static int cpcap_soc_probe(struct snd_soc_component *component) } cpcap->mb2irq = platform_get_irq_byname(pdev, "mb2"); - if (cpcap->mb2irq < 0) - return cpcap->mb2irq; - - err = devm_request_threaded_irq(component->dev, cpcap->mb2irq, NULL, - cpcap_mb2_irq_thread, - IRQF_TRIGGER_RISING | - IRQF_TRIGGER_FALLING | - IRQF_ONESHOT, - "cpcap-codec-mb2", - component); + if (cpcap->mb2irq < 0) { + err = cpcap->mb2irq; + goto err_free_hsirq; + } + + err = request_threaded_irq(cpcap->mb2irq, NULL, + cpcap_mb2_irq_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "cpcap-codec-mb2", + component); if (err) { dev_warn(component->dev, "no MB2 irq%i: %i\n", cpcap->mb2irq, err); - return err; + goto err_free_hsirq; } err = cpcap_audio_reset(component, false); if (err) - return err; + goto err_free_mb2irq; cpcap_hs_irq_thread(cpcap->hsirq, component); @@ -1700,6 +1693,13 @@ static int cpcap_soc_probe(struct snd_soc_component *component) enable_irq_wake(cpcap->mb2irq); return 0; + +err_free_mb2irq: + free_irq(cpcap->mb2irq, component); +err_free_hsirq: + free_irq(cpcap->hsirq, component); + + return err; } static void cpcap_soc_remove(struct snd_soc_component *component) @@ -1708,6 +1708,9 @@ static void cpcap_soc_remove(struct snd_soc_component *component) disable_irq_wake(cpcap->hsirq); disable_irq_wake(cpcap->mb2irq); + + free_irq(cpcap->mb2irq, component); + free_irq(cpcap->hsirq, component); } static int cpcap_set_bias_level(struct snd_soc_component *component, @@ -1754,11 +1757,24 @@ static int cpcap_codec_probe(struct platform_device *pdev) { struct device_node *codec_node = of_get_child_by_name(pdev->dev.parent->of_node, "audio-codec"); + struct cpcap_audio *cpcap; + if (!codec_node) return -ENODEV; pdev->dev.of_node = codec_node; + cpcap = devm_kzalloc(&pdev->dev, sizeof(*cpcap), GFP_KERNEL); + if (!cpcap) + return -ENOMEM; + + cpcap->vaudio = devm_regulator_get(&pdev->dev, "VAUDIO"); + if (IS_ERR(cpcap->vaudio)) + return dev_err_probe(&pdev->dev, PTR_ERR(cpcap->vaudio), + "Cannot get VAUDIO regulator\n"); + + platform_set_drvdata(pdev, cpcap); + return devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_cpcap, cpcap_dai, ARRAY_SIZE(cpcap_dai)); } From 116f1bd60ae33ed794c502f56152a0384a80a2d6 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:46 +0900 Subject: [PATCH 1087/1417] ASoC: codecs: da7218: Fix devm resource leaks across card bind/unbind component->dev is the underlying I2C device, so resources requested with devm_*(component->dev, ...) in the component .probe are only released when the I2C device is removed, not on card unbind. This leaks the mclk clock, the regulator bulk, the platform data parsed from the device tree and the IRQ on every card bind/unbind cycle. Move the mclk clock and the regulator supplies (pure hardware resources tied to the I2C device) to da7218_i2c_probe() using devm on &i2c->dev. Likewise move the device tree parsing (da7218_of_to_pdata()), which also allocates with devm, to da7218_i2c_probe() and store the result in da7218->pdata; the OF helpers now take a struct device so they no longer depend on the component. Keep the IRQ in the component .probe (it feeds the component) but request it with the non-devm API and free it in da7218_remove(). Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-3-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/da7218.c | 129 +++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 64 deletions(-) diff --git a/sound/soc/codecs/da7218.c b/sound/soc/codecs/da7218.c index 361daf14152e23..a538b3dd1635bc 100644 --- a/sound/soc/codecs/da7218.c +++ b/sound/soc/codecs/da7218.c @@ -2298,7 +2298,7 @@ static const struct of_device_id da7218_of_match[] = { MODULE_DEVICE_TABLE(of, da7218_of_match); static enum da7218_micbias_voltage - da7218_of_micbias_lvl(struct snd_soc_component *component, u32 val) + da7218_of_micbias_lvl(struct device *dev, u32 val) { switch (val) { case 1200: @@ -2320,13 +2320,13 @@ static enum da7218_micbias_voltage case 3000: return DA7218_MICBIAS_3_0V; default: - dev_warn(component->dev, "Invalid micbias level"); + dev_warn(dev, "Invalid micbias level"); return DA7218_MICBIAS_1_6V; } } static enum da7218_mic_amp_in_sel - da7218_of_mic_amp_in_sel(struct snd_soc_component *component, const char *str) + da7218_of_mic_amp_in_sel(struct device *dev, const char *str) { if (!strcmp(str, "diff")) { return DA7218_MIC_AMP_IN_SEL_DIFF; @@ -2335,39 +2335,39 @@ static enum da7218_mic_amp_in_sel } else if (!strcmp(str, "se_n")) { return DA7218_MIC_AMP_IN_SEL_SE_N; } else { - dev_warn(component->dev, "Invalid mic input type selection"); + dev_warn(dev, "Invalid mic input type selection"); return DA7218_MIC_AMP_IN_SEL_DIFF; } } static enum da7218_dmic_data_sel - da7218_of_dmic_data_sel(struct snd_soc_component *component, const char *str) + da7218_of_dmic_data_sel(struct device *dev, const char *str) { if (!strcmp(str, "lrise_rfall")) { return DA7218_DMIC_DATA_LRISE_RFALL; } else if (!strcmp(str, "lfall_rrise")) { return DA7218_DMIC_DATA_LFALL_RRISE; } else { - dev_warn(component->dev, "Invalid DMIC data type selection"); + dev_warn(dev, "Invalid DMIC data type selection"); return DA7218_DMIC_DATA_LRISE_RFALL; } } static enum da7218_dmic_samplephase - da7218_of_dmic_samplephase(struct snd_soc_component *component, const char *str) + da7218_of_dmic_samplephase(struct device *dev, const char *str) { if (!strcmp(str, "on_clkedge")) { return DA7218_DMIC_SAMPLE_ON_CLKEDGE; } else if (!strcmp(str, "between_clkedge")) { return DA7218_DMIC_SAMPLE_BETWEEN_CLKEDGE; } else { - dev_warn(component->dev, "Invalid DMIC sample phase"); + dev_warn(dev, "Invalid DMIC sample phase"); return DA7218_DMIC_SAMPLE_ON_CLKEDGE; } } static enum da7218_dmic_clk_rate - da7218_of_dmic_clkrate(struct snd_soc_component *component, u32 val) + da7218_of_dmic_clkrate(struct device *dev, u32 val) { switch (val) { case 1500000: @@ -2375,13 +2375,13 @@ static enum da7218_dmic_clk_rate case 3000000: return DA7218_DMIC_CLK_3_0MHZ; default: - dev_warn(component->dev, "Invalid DMIC clock rate"); + dev_warn(dev, "Invalid DMIC clock rate"); return DA7218_DMIC_CLK_3_0MHZ; } } static enum da7218_hpldet_jack_rate - da7218_of_jack_rate(struct snd_soc_component *component, u32 val) + da7218_of_jack_rate(struct device *dev, u32 val) { switch (val) { case 5: @@ -2401,13 +2401,13 @@ static enum da7218_hpldet_jack_rate case 640: return DA7218_HPLDET_JACK_RATE_640US; default: - dev_warn(component->dev, "Invalid jack detect rate"); + dev_warn(dev, "Invalid jack detect rate"); return DA7218_HPLDET_JACK_RATE_40US; } } static enum da7218_hpldet_jack_debounce - da7218_of_jack_debounce(struct snd_soc_component *component, u32 val) + da7218_of_jack_debounce(struct device *dev, u32 val) { switch (val) { case 0: @@ -2419,13 +2419,13 @@ static enum da7218_hpldet_jack_debounce case 4: return DA7218_HPLDET_JACK_DEBOUNCE_4; default: - dev_warn(component->dev, "Invalid jack debounce"); + dev_warn(dev, "Invalid jack debounce"); return DA7218_HPLDET_JACK_DEBOUNCE_2; } } static enum da7218_hpldet_jack_thr - da7218_of_jack_thr(struct snd_soc_component *component, u32 val) + da7218_of_jack_thr(struct device *dev, u32 val) { switch (val) { case 84: @@ -2437,76 +2437,76 @@ static enum da7218_hpldet_jack_thr case 96: return DA7218_HPLDET_JACK_THR_96PCT; default: - dev_warn(component->dev, "Invalid jack threshold level"); + dev_warn(dev, "Invalid jack threshold level"); return DA7218_HPLDET_JACK_THR_84PCT; } } -static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *component) +static struct da7218_pdata *da7218_of_to_pdata(struct device *dev, + struct da7218_priv *da7218) { - struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); - struct device_node *np = component->dev->of_node; + struct device_node *np = dev->of_node; struct device_node *hpldet_np; struct da7218_pdata *pdata; struct da7218_hpldet_pdata *hpldet_pdata; const char *of_str; u32 of_val32; - pdata = devm_kzalloc(component->dev, sizeof(*pdata), GFP_KERNEL); + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return NULL; if (of_property_read_u32(np, "dlg,micbias1-lvl-millivolt", &of_val32) >= 0) - pdata->micbias1_lvl = da7218_of_micbias_lvl(component, of_val32); + pdata->micbias1_lvl = da7218_of_micbias_lvl(dev, of_val32); else pdata->micbias1_lvl = DA7218_MICBIAS_1_6V; if (of_property_read_u32(np, "dlg,micbias2-lvl-millivolt", &of_val32) >= 0) - pdata->micbias2_lvl = da7218_of_micbias_lvl(component, of_val32); + pdata->micbias2_lvl = da7218_of_micbias_lvl(dev, of_val32); else pdata->micbias2_lvl = DA7218_MICBIAS_1_6V; if (!of_property_read_string(np, "dlg,mic1-amp-in-sel", &of_str)) pdata->mic1_amp_in_sel = - da7218_of_mic_amp_in_sel(component, of_str); + da7218_of_mic_amp_in_sel(dev, of_str); else pdata->mic1_amp_in_sel = DA7218_MIC_AMP_IN_SEL_DIFF; if (!of_property_read_string(np, "dlg,mic2-amp-in-sel", &of_str)) pdata->mic2_amp_in_sel = - da7218_of_mic_amp_in_sel(component, of_str); + da7218_of_mic_amp_in_sel(dev, of_str); else pdata->mic2_amp_in_sel = DA7218_MIC_AMP_IN_SEL_DIFF; if (!of_property_read_string(np, "dlg,dmic1-data-sel", &of_str)) - pdata->dmic1_data_sel = da7218_of_dmic_data_sel(component, of_str); + pdata->dmic1_data_sel = da7218_of_dmic_data_sel(dev, of_str); else pdata->dmic1_data_sel = DA7218_DMIC_DATA_LRISE_RFALL; if (!of_property_read_string(np, "dlg,dmic1-samplephase", &of_str)) pdata->dmic1_samplephase = - da7218_of_dmic_samplephase(component, of_str); + da7218_of_dmic_samplephase(dev, of_str); else pdata->dmic1_samplephase = DA7218_DMIC_SAMPLE_ON_CLKEDGE; if (of_property_read_u32(np, "dlg,dmic1-clkrate-hz", &of_val32) >= 0) - pdata->dmic1_clk_rate = da7218_of_dmic_clkrate(component, of_val32); + pdata->dmic1_clk_rate = da7218_of_dmic_clkrate(dev, of_val32); else pdata->dmic1_clk_rate = DA7218_DMIC_CLK_3_0MHZ; if (!of_property_read_string(np, "dlg,dmic2-data-sel", &of_str)) - pdata->dmic2_data_sel = da7218_of_dmic_data_sel(component, of_str); + pdata->dmic2_data_sel = da7218_of_dmic_data_sel(dev, of_str); else pdata->dmic2_data_sel = DA7218_DMIC_DATA_LRISE_RFALL; if (!of_property_read_string(np, "dlg,dmic2-samplephase", &of_str)) pdata->dmic2_samplephase = - da7218_of_dmic_samplephase(component, of_str); + da7218_of_dmic_samplephase(dev, of_str); else pdata->dmic2_samplephase = DA7218_DMIC_SAMPLE_ON_CLKEDGE; if (of_property_read_u32(np, "dlg,dmic2-clkrate-hz", &of_val32) >= 0) - pdata->dmic2_clk_rate = da7218_of_dmic_clkrate(component, of_val32); + pdata->dmic2_clk_rate = da7218_of_dmic_clkrate(dev, of_val32); else pdata->dmic2_clk_rate = DA7218_DMIC_CLK_3_0MHZ; @@ -2520,7 +2520,7 @@ static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *compone if (!hpldet_np) return pdata; - hpldet_pdata = devm_kzalloc(component->dev, sizeof(*hpldet_pdata), + hpldet_pdata = devm_kzalloc(dev, sizeof(*hpldet_pdata), GFP_KERNEL); if (!hpldet_pdata) { of_node_put(hpldet_np); @@ -2531,14 +2531,14 @@ static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *compone if (of_property_read_u32(hpldet_np, "dlg,jack-rate-us", &of_val32) >= 0) hpldet_pdata->jack_rate = - da7218_of_jack_rate(component, of_val32); + da7218_of_jack_rate(dev, of_val32); else hpldet_pdata->jack_rate = DA7218_HPLDET_JACK_RATE_40US; if (of_property_read_u32(hpldet_np, "dlg,jack-debounce", &of_val32) >= 0) hpldet_pdata->jack_debounce = - da7218_of_jack_debounce(component, of_val32); + da7218_of_jack_debounce(dev, of_val32); else hpldet_pdata->jack_debounce = DA7218_HPLDET_JACK_DEBOUNCE_2; @@ -2546,7 +2546,7 @@ static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *compone if (of_property_read_u32(hpldet_np, "dlg,jack-threshold-pct", &of_val32) >= 0) hpldet_pdata->jack_thr = - da7218_of_jack_thr(component, of_val32); + da7218_of_jack_thr(dev, of_val32); else hpldet_pdata->jack_thr = DA7218_HPLDET_JACK_THR_84PCT; @@ -2638,18 +2638,7 @@ static int da7218_handle_supplies(struct snd_soc_component *component) struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); struct regulator *vddio; u8 io_voltage_lvl = DA7218_IO_VOLTAGE_LEVEL_2_5V_3_6V; - int i, ret; - - /* Get required supplies */ - for (i = 0; i < DA7218_NUM_SUPPLIES; ++i) - da7218->supplies[i].supply = da7218_supply_names[i]; - - ret = devm_regulator_bulk_get(component->dev, DA7218_NUM_SUPPLIES, - da7218->supplies); - if (ret) { - dev_err(component->dev, "Failed to get supplies\n"); - return ret; - } + int ret; /* Determine VDDIO voltage provided */ vddio = da7218->supplies[DA7218_SUPPLY_VDDIO].consumer; @@ -2887,21 +2876,8 @@ static int da7218_probe(struct snd_soc_component *component) if (ret) return ret; - /* Handle DT/Platform data */ - if (component->dev->of_node) - da7218->pdata = da7218_of_to_pdata(component); - else - da7218->pdata = dev_get_platdata(component->dev); - da7218_handle_pdata(component); - /* Check if MCLK provided, if not the clock is NULL */ - da7218->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(da7218->mclk)) { - ret = PTR_ERR(da7218->mclk); - goto err_disable_reg; - } - /* Default PC to free-running */ snd_soc_component_write(component, DA7218_PC_COUNT, DA7218_PC_FREERUN_MASK); @@ -2965,10 +2941,10 @@ static int da7218_probe(struct snd_soc_component *component) } if (da7218->irq) { - ret = devm_request_threaded_irq(component->dev, da7218->irq, NULL, - da7218_irq_thread, - IRQF_TRIGGER_LOW | IRQF_ONESHOT, - "da7218", component); + ret = request_threaded_irq(da7218->irq, NULL, + da7218_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "da7218", component); if (ret != 0) { dev_err(component->dev, "Failed to request IRQ %d: %d\n", da7218->irq, ret); @@ -2989,6 +2965,9 @@ static void da7218_remove(struct snd_soc_component *component) { struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + if (da7218->irq) + free_irq(da7218->irq, component); + regulator_bulk_disable(DA7218_NUM_SUPPLIES, da7218->supplies); } @@ -3259,7 +3238,7 @@ static const struct regmap_config da7218_regmap_config = { static int da7218_i2c_probe(struct i2c_client *i2c) { struct da7218_priv *da7218; - int ret; + int i, ret; da7218 = devm_kzalloc(&i2c->dev, sizeof(*da7218), GFP_KERNEL); if (!da7218) @@ -3284,6 +3263,28 @@ static int da7218_i2c_probe(struct i2c_client *i2c) return ret; } + /* Get required supplies */ + for (i = 0; i < DA7218_NUM_SUPPLIES; ++i) + da7218->supplies[i].supply = da7218_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, DA7218_NUM_SUPPLIES, + da7218->supplies); + if (ret) { + dev_err(&i2c->dev, "Failed to get supplies\n"); + return ret; + } + + /* Check if MCLK provided, if not the clock is NULL */ + da7218->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(da7218->mclk)) + return PTR_ERR(da7218->mclk); + + /* Handle DT/Platform data */ + if (i2c->dev.of_node) + da7218->pdata = da7218_of_to_pdata(&i2c->dev, da7218); + else + da7218->pdata = dev_get_platdata(&i2c->dev); + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_da7218, &da7218_dai, 1); if (ret < 0) { From ef8caf5d2784dabe090814e01694a57209273ed5 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:47 +0900 Subject: [PATCH 1088/1417] ASoC: codecs: tlv320aic32x4: Fix clock leak from runtime callbacks component->dev is the underlying I2C/SPI device. The driver acquired its codec clocks with devm_clk_get()/devm_clk_bulk_get(component->dev, ...) from the DAI set_sysclk runtime callback, from set_bias_level and from the component .probe. Because devres on component->dev is only released when the platform device is removed, each card bind/unbind (and, for set_sysclk, each runtime invocation) leaked a fresh clock reference. Acquire all codec clocks once in the bus probe with a single devm_clk_bulk_get() into the private struct, and have the runtime callbacks and both component probes reuse those stored references. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-4-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic32x4.c | 94 +++++++++++++++----------------- 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index af4be5bee7237f..7fec94768afb81 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -48,8 +48,18 @@ struct aic32x4_priv { enum aic32x4_type type; unsigned int fmt; + + struct clk_bulk_data clocks[7]; }; +#define AIC32X4_CLK_CODEC_CLKIN 0 +#define AIC32X4_CLK_PLL 1 +#define AIC32X4_CLK_NADC 2 +#define AIC32X4_CLK_MADC 3 +#define AIC32X4_CLK_NDAC 4 +#define AIC32X4_CLK_MDAC 5 +#define AIC32X4_CLK_BDIV 6 + static int aic32x4_reset_adc(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -591,14 +601,10 @@ static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_component *component = codec_dai->component; + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); struct clk *mclk; - struct clk *pll; - - pll = devm_clk_get(component->dev, "pll"); - if (IS_ERR(pll)) - return PTR_ERR(pll); - mclk = clk_get_parent(pll); + mclk = clk_get_parent(aic32x4->clocks[AIC32X4_CLK_PLL].clk); return clk_set_rate(mclk, freq); } @@ -750,17 +756,7 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, unsigned long adc_clock_rate, dac_clock_rate; int ret; - struct clk_bulk_data clocks[] = { - { .id = "pll" }, - { .id = "nadc" }, - { .id = "madc" }, - { .id = "ndac" }, - { .id = "mdac" }, - { .id = "bdiv" }, - }; - ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); - if (ret) - return ret; + struct clk_bulk_data *clocks = &aic32x4->clocks[AIC32X4_CLK_PLL]; ret = aic32x4_configure_rate(component, sample_rate, &aosr, &adc_resource_class, &dac_resource_class, @@ -876,18 +872,15 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); int ret; struct clk_bulk_data clocks[] = { - { .id = "madc" }, - { .id = "mdac" }, - { .id = "bdiv" }, + aic32x4->clocks[AIC32X4_CLK_MADC], + aic32x4->clocks[AIC32X4_CLK_MDAC], + aic32x4->clocks[AIC32X4_CLK_BDIV], }; - ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); - if (ret) - return ret; - switch (level) { case SND_SOC_BIAS_ON: ret = clk_bulk_prepare_enable(ARRAY_SIZE(clocks), clocks); @@ -970,23 +963,16 @@ static int aic32x4_component_probe(struct snd_soc_component *component) { struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); u32 tmp_reg; - int ret; - struct clk_bulk_data clocks[] = { - { .id = "codec_clkin" }, - { .id = "pll" }, - { .id = "bdiv" }, - { .id = "mdac" }, - }; - - ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); - if (ret) - return ret; + struct clk *codec_clkin = aic32x4->clocks[AIC32X4_CLK_CODEC_CLKIN].clk; + struct clk *pll = aic32x4->clocks[AIC32X4_CLK_PLL].clk; + struct clk *bdiv = aic32x4->clocks[AIC32X4_CLK_BDIV].clk; + struct clk *mdac = aic32x4->clocks[AIC32X4_CLK_MDAC].clk; aic32x4_setup_gpios(component); - clk_set_parent(clocks[0].clk, clocks[1].clk); - clk_set_parent(clocks[2].clk, clocks[3].clk); + clk_set_parent(codec_clkin, pll); + clk_set_parent(bdiv, mdac); /* Power platform configuration */ if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { @@ -1127,23 +1113,16 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) { struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); u32 tmp_reg; - int ret; - - struct clk_bulk_data clocks[] = { - { .id = "codec_clkin" }, - { .id = "pll" }, - { .id = "bdiv" }, - { .id = "mdac" }, - }; - ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); - if (ret) - return ret; + struct clk *codec_clkin = aic32x4->clocks[AIC32X4_CLK_CODEC_CLKIN].clk; + struct clk *pll = aic32x4->clocks[AIC32X4_CLK_PLL].clk; + struct clk *bdiv = aic32x4->clocks[AIC32X4_CLK_BDIV].clk; + struct clk *mdac = aic32x4->clocks[AIC32X4_CLK_MDAC].clk; aic32x4_setup_gpios(component); - clk_set_parent(clocks[0].clk, clocks[1].clk); - clk_set_parent(clocks[2].clk, clocks[3].clk); + clk_set_parent(codec_clkin, pll); + clk_set_parent(bdiv, mdac); /* Power platform configuration */ if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) @@ -1362,6 +1341,21 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, if (ret) goto err_disable_regulators; + aic32x4->clocks[AIC32X4_CLK_CODEC_CLKIN].id = "codec_clkin"; + aic32x4->clocks[AIC32X4_CLK_PLL].id = "pll"; + aic32x4->clocks[AIC32X4_CLK_NADC].id = "nadc"; + aic32x4->clocks[AIC32X4_CLK_MADC].id = "madc"; + aic32x4->clocks[AIC32X4_CLK_NDAC].id = "ndac"; + aic32x4->clocks[AIC32X4_CLK_MDAC].id = "mdac"; + aic32x4->clocks[AIC32X4_CLK_BDIV].id = "bdiv"; + + ret = devm_clk_bulk_get(dev, ARRAY_SIZE(aic32x4->clocks), + aic32x4->clocks); + if (ret) { + dev_err(dev, "Failed to get clocks\n"); + goto err_disable_regulators; + } + switch (aic32x4->type) { case AIC32X4_TYPE_TAS2505: ret = devm_snd_soc_register_component(dev, From 99fff20db8ded3003e4d525d8db34f731316ea92 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:48 +0900 Subject: [PATCH 1089/1417] ASoC: codecs: twl4030: Acquire board params and hs_extmute GPIO in the platform probe The component .probe/.remove callbacks fire on ASoC card bind/unbind, but component->dev is the underlying platform device whose devres is only released on physical device removal. Resources allocated with devm_*(component->dev, ...) in the component probe are therefore never freed on card unbind, leaking one copy per bind/unbind cycle and leaking the hs_extmute GPIO descriptor (which can also fail to be re-acquired on re-bind). The board parameters and the hs_extmute GPIO are pure hardware/device level resources: they only depend on the physical device and the DT, not on the ASoC component. Acquire them (together with the driver context) in twl4030_codec_probe() using devm on the platform device, and hand the context to the component through drvdata. Their devres lifetime then correctly follows the device rather than the card bind/unbind, so no explicit component .remove is needed. The component probe keeps only the codec register initialisation that genuinely needs the component. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-5-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 63 +++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index b986ece55d5a4e..442624cf2a317d 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -212,21 +212,21 @@ twl4030_get_board_param_values(struct twl4030_board_params *board_params, } static struct twl4030_board_params* -twl4030_get_board_params(struct snd_soc_component *component) +twl4030_get_board_params(struct device *dev) { struct twl4030_board_params *board_params = NULL; struct device_node *twl4030_codec_node = NULL; - twl4030_codec_node = of_get_child_by_name(component->dev->parent->of_node, + twl4030_codec_node = of_get_child_by_name(dev->parent->of_node, "codec"); if (twl4030_codec_node) { - board_params = devm_kzalloc(component->dev, + board_params = devm_kzalloc(dev, sizeof(struct twl4030_board_params), GFP_KERNEL); if (!board_params) { of_node_put(twl4030_codec_node); - return NULL; + return ERR_PTR(-ENOMEM); } twl4030_get_board_param_values(board_params, twl4030_codec_node); of_node_put(twl4030_codec_node); @@ -235,21 +235,22 @@ twl4030_get_board_params(struct snd_soc_component *component) return board_params; } -static int twl4030_init_chip(struct snd_soc_component *component) +static int twl4030_get_hw_params(struct device *dev, + struct twl4030_priv *twl4030) { struct twl4030_board_params *board_params; - struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); - u8 reg, byte; - int i = 0; - board_params = twl4030_get_board_params(component); + board_params = twl4030_get_board_params(dev); + if (IS_ERR(board_params)) + return PTR_ERR(board_params); if (board_params && board_params->hs_extmute) { - board_params->hs_extmute_gpio = devm_gpiod_get_optional(component->dev, + board_params->hs_extmute_gpio = devm_gpiod_get_optional(dev, "ti,hs_extmute", GPIOD_OUT_LOW); if (IS_ERR(board_params->hs_extmute_gpio)) - return dev_err_probe(component->dev, PTR_ERR(board_params->hs_extmute_gpio), + return dev_err_probe(dev, + PTR_ERR(board_params->hs_extmute_gpio), "Failed to get hs_extmute GPIO\n"); if (board_params->hs_extmute_gpio) { @@ -257,7 +258,7 @@ static int twl4030_init_chip(struct snd_soc_component *component) } else { u8 pin_mux; - dev_info(component->dev, "use TWL4030 GPIO6\n"); + dev_info(dev, "use TWL4030 GPIO6\n"); /* Set TWL4030 GPIO6 as EXTMUTE signal */ twl_i2c_read_u8(TWL4030_MODULE_INTBR, &pin_mux, @@ -269,6 +270,18 @@ static int twl4030_init_chip(struct snd_soc_component *component) } } + twl4030->board_params = board_params; + + return 0; +} + +static int twl4030_init_chip(struct snd_soc_component *component) +{ + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + struct twl4030_board_params *board_params = twl4030->board_params; + u8 reg, byte; + int i = 0; + /* Initialize the local ctl register cache */ tw4030_init_ctl_cache(twl4030); @@ -288,8 +301,6 @@ static int twl4030_init_chip(struct snd_soc_component *component) if (!board_params) return 0; - twl4030->board_params = board_params; - reg = twl4030_read(component, TWL4030_REG_HS_POPN_SET); reg &= ~TWL4030_RAMP_DELAY; reg |= (board_params->ramp_delay_value << 2); @@ -2163,15 +2174,9 @@ static struct snd_soc_dai_driver twl4030_dai[] = { static int twl4030_soc_probe(struct snd_soc_component *component) { - struct twl4030_priv *twl4030; + struct twl4030_priv *twl4030 = dev_get_drvdata(component->dev); - twl4030 = devm_kzalloc(component->dev, sizeof(struct twl4030_priv), - GFP_KERNEL); - if (!twl4030) - return -ENOMEM; snd_soc_component_set_drvdata(component, twl4030); - /* Set the defaults, and power up the codec */ - twl4030->sysclk = twl4030_audio_get_mclk() / 1000; return twl4030_init_chip(component); } @@ -2193,6 +2198,22 @@ static const struct snd_soc_component_driver soc_component_dev_twl4030 = { static int twl4030_codec_probe(struct platform_device *pdev) { + struct twl4030_priv *twl4030; + int ret; + + twl4030 = devm_kzalloc(&pdev->dev, sizeof(*twl4030), GFP_KERNEL); + if (!twl4030) + return -ENOMEM; + + /* Set the defaults, and power up the codec */ + twl4030->sysclk = twl4030_audio_get_mclk() / 1000; + + ret = twl4030_get_hw_params(&pdev->dev, twl4030); + if (ret) + return ret; + + platform_set_drvdata(pdev, twl4030); + return devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_twl4030, twl4030_dai, ARRAY_SIZE(twl4030_dai)); From d009170e12addcaba9fb42b6be000ed4b901144e Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:49 +0900 Subject: [PATCH 1090/1417] ASoC: codecs: es8316: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the mclk with devm_clk_get_optional(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get_optional() to es8316_i2c_probe() so the clk reference is tied to the i2c device lifetime. Signed-off-by: Chancel Liu Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260923061305.4041197-6-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8316.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index 59399476b9d79b..5e7bf6f020e06e 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -771,14 +771,6 @@ static int es8316_probe(struct snd_soc_component *component) es8316->component = component; - es8316->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(es8316->mclk)) { - dev_err(component->dev, "unable to get mclk\n"); - return PTR_ERR(es8316->mclk); - } - if (!es8316->mclk) - dev_warn(component->dev, "assuming static mclk\n"); - ret = clk_prepare_enable(es8316->mclk); if (ret) { dev_err(component->dev, "unable to enable mclk\n"); @@ -883,6 +875,12 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client) i2c_set_clientdata(i2c_client, es8316); + es8316->mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(es8316->mclk)) + return dev_err_probe(dev, PTR_ERR(es8316->mclk), "unable to get mclk\n"); + if (!es8316->mclk) + dev_warn(dev, "assuming static mclk\n"); + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(es8316_supply_names), es8316_supply_names); if (ret) From be9bc47e2569ac387289ee1cbf3dd93330a663b7 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:50 +0900 Subject: [PATCH 1091/1417] ASoC: codecs: es8323: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the mclk with devm_clk_get_optional(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get_optional() to es8323_i2c_probe() so the clk reference is tied to the i2c device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-7-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8323.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/sound/soc/codecs/es8323.c b/sound/soc/codecs/es8323.c index 12fcfe017ab2e5..3f7fbbdb111e00 100644 --- a/sound/soc/codecs/es8323.c +++ b/sound/soc/codecs/es8323.c @@ -671,15 +671,6 @@ static int es8323_probe(struct snd_soc_component *component) struct es8323_priv *es8323 = snd_soc_component_get_drvdata(component); int ret; - es8323->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(es8323->mclk)) { - dev_err(component->dev, "unable to get mclk\n"); - return PTR_ERR(es8323->mclk); - } - - if (!es8323->mclk) - dev_warn(component->dev, "assuming static mclk\n"); - ret = clk_prepare_enable(es8323->mclk); if (ret) { dev_err(component->dev, "unable to enable mclk\n"); @@ -789,6 +780,12 @@ static int es8323_i2c_probe(struct i2c_client *i2c_client) i2c_set_clientdata(i2c_client, es8323); + es8323->mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(es8323->mclk)) + return dev_err_probe(dev, PTR_ERR(es8323->mclk), "unable to get mclk\n"); + if (!es8323->mclk) + dev_warn(dev, "assuming static mclk\n"); + es8323->regmap = devm_regmap_init_i2c(i2c_client, &es8323_regmap); if (IS_ERR(es8323->regmap)) return PTR_ERR(es8323->regmap); From 5cc3c8f7264ea83b2c597d3d71d1a4a4a515d65f Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:51 +0900 Subject: [PATCH 1092/1417] ASoC: codecs: es8311: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the mclk with devm_clk_get_optional(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get_optional() to es8311_i2c_probe() so the clk reference is tied to the i2c device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-8-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8311.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/es8311.c b/sound/soc/codecs/es8311.c index e46b85f11d57e1..ddbbfc2443b36b 100644 --- a/sound/soc/codecs/es8311.c +++ b/sound/soc/codecs/es8311.c @@ -906,12 +906,6 @@ static int es8311_component_probe(struct snd_soc_component *component) es8311 = snd_soc_component_get_drvdata(component); - es8311->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(es8311->mclk)) { - dev_err(component->dev, "invalid mclk\n"); - return PTR_ERR(es8311->mclk); - } - es8311->mclk_freq = clk_get_rate(es8311->mclk); if (es8311->mclk_freq > 0 && es8311->mclk_freq < ES8311_MCLK_MAX_FREQ) es8311_set_sysclk_constraints(es8311->mclk_freq, es8311); @@ -960,6 +954,10 @@ static int es8311_i2c_probe(struct i2c_client *i2c_client) if (es8311 == NULL) return -ENOMEM; + es8311->mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(es8311->mclk)) + return dev_err_probe(dev, PTR_ERR(es8311->mclk), "invalid mclk\n"); + es8311->regmap = devm_regmap_init_i2c(i2c_client, &es8311_regmap_config); if (IS_ERR(es8311->regmap)) From 10a45015665e8cb4f865106015246d472d7d7a3d Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:52 +0900 Subject: [PATCH 1093/1417] ASoC: codecs: es8328: Move clk acquisition to the bus probe component->dev is the underlying i2c/spi device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clock with devm_clk_get(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get() to es8328_probe() (the shared i2c/spi bus level probe) so the clk reference is tied to the physical device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-9-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8328.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c index 7d52b5d9d06155..37fda2f42b0cde 100644 --- a/sound/soc/codecs/es8328.c +++ b/sound/soc/codecs/es8328.c @@ -829,14 +829,6 @@ static int es8328_component_probe(struct snd_soc_component *component) return ret; } - /* Setup clocks */ - es8328->clk = devm_clk_get(component->dev, NULL); - if (IS_ERR(es8328->clk)) { - dev_err(component->dev, "codec clock missing or invalid\n"); - ret = PTR_ERR(es8328->clk); - goto clk_fail; - } - ret = clk_prepare_enable(es8328->clk); if (ret) { dev_err(component->dev, "unable to prepare codec clk\n"); @@ -906,6 +898,11 @@ int es8328_probe(struct device *dev, struct regmap *regmap) es8328->regmap = regmap; + es8328->clk = devm_clk_get(dev, NULL); + if (IS_ERR(es8328->clk)) + return dev_err_probe(dev, PTR_ERR(es8328->clk), + "codec clock missing or invalid\n"); + for (i = 0; i < ARRAY_SIZE(es8328->supplies); i++) es8328->supplies[i].supply = supply_names[i]; From e68e66353b8e3eb9e7d88c398a11af0cc4d2915d Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:53 +0900 Subject: [PATCH 1094/1417] ASoC: codecs: rt5640: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clock with devm_clk_get_optional(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get_optional() to rt5640_i2c_probe() so the clk reference is tied to the physical device lifetime. Signed-off-by: Chancel Liu Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260923061305.4041197-10-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5640.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c index 03d0ac3359f5e4..d6068bb5cb4c84 100644 --- a/sound/soc/codecs/rt5640.c +++ b/sound/soc/codecs/rt5640.c @@ -2667,11 +2667,6 @@ static int rt5640_probe(struct snd_soc_component *component) bool dmic_en = false; u32 val; - /* Check if MCLK provided */ - rt5640->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(rt5640->mclk)) - return PTR_ERR(rt5640->mclk); - rt5640->component = component; snd_soc_dapm_force_bias_level(dapm, SND_SOC_BIAS_OFF); @@ -3012,6 +3007,11 @@ static int rt5640_i2c_probe(struct i2c_client *i2c) return -ENOMEM; i2c_set_clientdata(i2c, rt5640); + /* Check if MCLK provided */ + rt5640->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5640->mclk)) + return PTR_ERR(rt5640->mclk); + rt5640->ldo1_en = devm_gpiod_get_optional(&i2c->dev, "realtek,ldo1-en", GPIOD_OUT_HIGH); From b5c9df40ca561ac61df68834c667400640a24f75 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:54 +0900 Subject: [PATCH 1095/1417] ASoC: codecs: rt5616: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clock with devm_clk_get_optional(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get_optional() to rt5616_i2c_probe() so the clk reference is tied to the physical device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-11-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5616.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/rt5616.c b/sound/soc/codecs/rt5616.c index 3f9d97d04d48a2..e30e3b563c9036 100644 --- a/sound/soc/codecs/rt5616.c +++ b/sound/soc/codecs/rt5616.c @@ -1222,11 +1222,6 @@ static int rt5616_probe(struct snd_soc_component *component) { struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); - /* Check if MCLK provided */ - rt5616->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(rt5616->mclk)) - return PTR_ERR(rt5616->mclk); - rt5616->component = component; return 0; @@ -1357,6 +1352,11 @@ static int rt5616_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, rt5616); + /* Check if MCLK provided */ + rt5616->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5616->mclk)) + return PTR_ERR(rt5616->mclk); + rt5616->regmap = devm_regmap_init_i2c(i2c, &rt5616_regmap); if (IS_ERR(rt5616->regmap)) { ret = PTR_ERR(rt5616->regmap); From cc2977e802181ce6d239734be1ce02bfdbea2380 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:55 +0900 Subject: [PATCH 1096/1417] ASoC: codecs: rt5514: Move clk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clocks with devm_clk_get*(component->dev, ...) in the component probe therefore leaks clk references on every card bind/unbind cycle. Move the mclk and dsp_calib_clk acquisition to rt5514_i2c_probe() so the clk references are tied to the physical device lifetime. Signed-off-by: Chancel Liu Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260923061305.4041197-12-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5514.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c index 753143e2d11ffe..a33fbc9fb04729 100644 --- a/sound/soc/codecs/rt5514.c +++ b/sound/soc/codecs/rt5514.c @@ -1099,18 +1099,6 @@ static int rt5514_set_bias_level(struct snd_soc_component *component, static int rt5514_probe(struct snd_soc_component *component) { struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); - struct platform_device *pdev = to_platform_device(component->dev); - - rt5514->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(rt5514->mclk)) - return PTR_ERR(rt5514->mclk); - - if (rt5514->pdata.dsp_calib_clk_name) { - rt5514->dsp_calib_clk = devm_clk_get(&pdev->dev, - rt5514->pdata.dsp_calib_clk_name); - if (PTR_ERR(rt5514->dsp_calib_clk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - } rt5514->component = component; rt5514->pll3_cal_value = 0x0078b000; @@ -1285,6 +1273,17 @@ static int rt5514_i2c_probe(struct i2c_client *i2c) else rt5514_parse_dp(rt5514, &i2c->dev); + rt5514->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5514->mclk)) + return PTR_ERR(rt5514->mclk); + + if (rt5514->pdata.dsp_calib_clk_name) { + rt5514->dsp_calib_clk = devm_clk_get(&i2c->dev, + rt5514->pdata.dsp_calib_clk_name); + if (PTR_ERR(rt5514->dsp_calib_clk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } + rt5514->i2c_regmap = devm_regmap_init_i2c(i2c, &rt5514_i2c_regmap); if (IS_ERR(rt5514->i2c_regmap)) { ret = PTR_ERR(rt5514->i2c_regmap); From c580cd416b684a4411a842010ce88bd114b75340 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:56 +0900 Subject: [PATCH 1097/1417] ASoC: codecs: rt5682s: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clock with devm_clk_get_optional(component->dev, ...) in the component probe chain therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get_optional() to rt5682s_i2c_probe() so the clk reference is tied to the physical device lifetime. Reviewed-by: Cezary Rojewski Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-13-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5682s.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c index 6dd0d36a71863e..0f80e4c32f573b 100644 --- a/sound/soc/codecs/rt5682s.c +++ b/sound/soc/codecs/rt5682s.c @@ -2846,11 +2846,6 @@ static int rt5682s_dai_probe_clks(struct snd_soc_component *component) struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); int ret; - /* Check if MCLK provided */ - rt5682s->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(rt5682s->mclk)) - return PTR_ERR(rt5682s->mclk); - /* Register CCF DAI clock control */ ret = rt5682s_register_dai_clks(component); if (ret) @@ -3152,6 +3147,13 @@ static int rt5682s_i2c_probe(struct i2c_client *i2c) return ret; } +#ifdef CONFIG_COMMON_CLK + /* Check if MCLK provided */ + rt5682s->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5682s->mclk)) + return PTR_ERR(rt5682s->mclk); +#endif + for (i = 0; i < ARRAY_SIZE(rt5682s->supplies); i++) rt5682s->supplies[i].supply = rt5682s_supply_names[i]; From bf4379a7195ea1133cb0672b94878c65419aa5b9 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:57 +0900 Subject: [PATCH 1098/1417] ASoC: codecs: max98090: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clock with devm_clk_get(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get() to max98090_i2c_probe() so the clk reference is tied to the physical device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-14-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/max98090.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c index c31f3d32fa4354..4bb5478f4642f9 100644 --- a/sound/soc/codecs/max98090.c +++ b/sound/soc/codecs/max98090.c @@ -2448,11 +2448,6 @@ static int max98090_probe(struct snd_soc_component *component) dev_dbg(component->dev, "max98090_probe\n"); - max98090->mclk = devm_clk_get(component->dev, "mclk"); - if (IS_ERR(max98090->mclk)) - if (PTR_ERR(max98090->mclk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - max98090->component = component; /* Reset the codec, the DSP core, and disable all interrupts */ @@ -2633,6 +2628,11 @@ static int max98090_i2c_probe(struct i2c_client *i2c) goto err_enable; } + max98090->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(max98090->mclk)) + if (PTR_ERR(max98090->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, max98090_interrupt, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "max98090_interrupt", max98090); From 8635b9033d039bdad19e65faf0450d738d34527f Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:58 +0900 Subject: [PATCH 1099/1417] ASoC: codecs: max98095: Move mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the codec clock with devm_clk_get(component->dev, ...) in the component probe therefore leaks a clk reference on every card bind/unbind cycle. Move the devm_clk_get() to max98095_i2c_probe() so the clk reference is tied to the physical device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-15-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/max98095.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c index a64a5ba00379b4..c827fd47a5b0e2 100644 --- a/sound/soc/codecs/max98095.c +++ b/sound/soc/codecs/max98095.c @@ -2005,11 +2005,6 @@ static int max98095_probe(struct snd_soc_component *component) struct i2c_client *client; int ret = 0; - max98095->mclk = devm_clk_get(component->dev, "mclk"); - if (IS_ERR(max98095->mclk)) - if (PTR_ERR(max98095->mclk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - /* reset the codec, the DSP core, and disable all interrupts */ max98095_reset(component); @@ -2153,6 +2148,11 @@ static int max98095_i2c_probe(struct i2c_client *i2c) return ret; } + max98095->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(max98095->mclk)) + if (PTR_ERR(max98095->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + max98095->devtype = (uintptr_t)i2c_get_match_data(i2c); i2c_set_clientdata(i2c, max98095); max98095->pdata = i2c->dev.platform_data; From fe15e876d9ee7a205aeafa3dbadd358954ffc901 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:12:59 +0900 Subject: [PATCH 1100/1417] ASoC: codecs: wm8985: Move regulator acquisition to the bus probe component->dev is the underlying i2c/spi device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the regulator supplies with devm_regulator_bulk_get(component->dev, ...) in the component probe therefore leaks the regulator references on every card bind/unbind cycle. Move the devm_regulator_bulk_get() into a helper called from the i2c and spi probes so the supplies are tied to the physical device lifetime. Signed-off-by: Chancel Liu Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260923061305.4041197-16-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8985.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/sound/soc/codecs/wm8985.c b/sound/soc/codecs/wm8985.c index 77ca9a0feb8a47..b5ca4af715c0d7 100644 --- a/sound/soc/codecs/wm8985.c +++ b/sound/soc/codecs/wm8985.c @@ -1024,23 +1024,30 @@ static int wm8985_set_bias_level(struct snd_soc_component *component, return 0; } -static int wm8985_probe(struct snd_soc_component *component) +static int wm8985_get_regulators(struct device *dev, + struct wm8985_priv *wm8985) { size_t i; - struct wm8985_priv *wm8985; int ret; - wm8985 = snd_soc_component_get_drvdata(component); - for (i = 0; i < ARRAY_SIZE(wm8985->supplies); i++) wm8985->supplies[i].supply = wm8985_supply_names[i]; - ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(wm8985->supplies), - wm8985->supplies); - if (ret) { - dev_err(component->dev, "Failed to request supplies: %d\n", ret); - return ret; - } + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(wm8985->supplies), + wm8985->supplies); + if (ret) + dev_err(dev, "Failed to request supplies: %d\n", ret); + + return ret; +} + +static int wm8985_probe(struct snd_soc_component *component) +{ + size_t i; + struct wm8985_priv *wm8985; + int ret; + + wm8985 = snd_soc_component_get_drvdata(component); ret = regulator_bulk_enable(ARRAY_SIZE(wm8985->supplies), wm8985->supplies); @@ -1171,6 +1178,10 @@ static int wm8985_spi_probe(struct spi_device *spi) return ret; } + ret = wm8985_get_regulators(&spi->dev, wm8985); + if (ret) + return ret; + ret = devm_snd_soc_register_component(&spi->dev, &soc_component_dev_wm8985, &wm8985_dai, 1); return ret; @@ -1207,6 +1218,10 @@ static int wm8985_i2c_probe(struct i2c_client *i2c) return ret; } + ret = wm8985_get_regulators(&i2c->dev, wm8985); + if (ret) + return ret; + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_wm8985, &wm8985_dai, 1); return ret; From 07bcfafa4719046cca821917aa01e74ed7afef4b Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:13:00 +0900 Subject: [PATCH 1101/1417] ASoC: codecs: wm8955: Move regulator acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the regulator supplies with devm_regulator_bulk_get(component->dev, ...) in the component probe therefore leaks the regulator references on every card bind/unbind cycle. Move the devm_regulator_bulk_get() to the i2c probe so the supplies are tied to the physical device lifetime. Reviewed-by: Charles Keepax Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-17-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8955.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c index 1d367a957f3ee9..017ec2e37d1e44 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -888,17 +888,7 @@ static int wm8955_probe(struct snd_soc_component *component) struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct wm8955_priv *wm8955 = snd_soc_component_get_drvdata(component); struct wm8955_pdata *pdata = dev_get_platdata(component->dev); - int ret, i; - - for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++) - wm8955->supplies[i].supply = wm8955_supply_names[i]; - - ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(wm8955->supplies), - wm8955->supplies); - if (ret != 0) { - dev_err(component->dev, "Failed to request supplies: %d\n", ret); - return ret; - } + int ret; ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies); @@ -990,7 +980,7 @@ static const struct regmap_config wm8955_regmap = { static int wm8955_i2c_probe(struct i2c_client *i2c) { struct wm8955_priv *wm8955; - int ret; + int i, ret; wm8955 = devm_kzalloc(&i2c->dev, sizeof(struct wm8955_priv), GFP_KERNEL); @@ -1007,6 +997,16 @@ static int wm8955_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, wm8955); + for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++) + wm8955->supplies[i].supply = wm8955_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8955->supplies), + wm8955->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_wm8955, &wm8955_dai, 1); From 5ef6e7ff52db85556ef1fcca393366dfdd6452f9 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:13:01 +0900 Subject: [PATCH 1102/1417] ASoC: codecs: es8389: Move regulator and mclk acquisition to the i2c probe component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. Getting the core regulator supplies and the mclk with devm_*(component->dev, ...) in the component probe therefore leaks those references on every card bind/unbind cycle. Move the devm_regulator_bulk_get() and devm_clk_get_optional() into the i2c probe so the resources are tied to the physical device lifetime. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-18-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8389.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c index fe341fe567601b..267806b72f8727 100644 --- a/sound/soc/codecs/es8389.c +++ b/sound/soc/codecs/es8389.c @@ -1067,7 +1067,7 @@ static int es8389_resume(struct snd_soc_component *component) static int es8389_probe(struct snd_soc_component *component) { - int ret, i; + int ret; struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ret = device_property_read_u8(component->dev, "everest,mclk-src", &es8389->mclk_src); @@ -1076,22 +1076,6 @@ static int es8389_probe(struct snd_soc_component *component) es8389->mclk_src = ES8389_MCLK_SOURCE; } - for (i = 0; i < ARRAY_SIZE(es8389_core_supplies); i++) - es8389->core_supply[i].supply = es8389_core_supplies[i]; - ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(es8389_core_supplies), es8389->core_supply); - if (ret) { - dev_err(component->dev, "Failed to request core supplies %d\n", ret); - return ret; - } - - es8389->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(es8389->mclk)) - return dev_err_probe(component->dev, PTR_ERR(es8389->mclk), - "ES8389 is unable to get mclk\n"); - - if (!es8389->mclk) - dev_err(component->dev, "%s, assuming static mclk\n", __func__); - ret = clk_prepare_enable(es8389->mclk); if (ret) { dev_err(component->dev, "%s, unable to enable mclk\n", __func__); @@ -1179,7 +1163,7 @@ static void es8389_i2c_shutdown(struct i2c_client *i2c) static int es8389_i2c_probe(struct i2c_client *i2c_client) { struct es8389_private *es8389; - int ret; + int ret, i; es8389 = devm_kzalloc(&i2c_client->dev, sizeof(*es8389), GFP_KERNEL); if (es8389 == NULL) @@ -1191,6 +1175,22 @@ static int es8389_i2c_probe(struct i2c_client *i2c_client) return dev_err_probe(&i2c_client->dev, PTR_ERR(es8389->regmap), "regmap_init() failed\n"); + for (i = 0; i < ARRAY_SIZE(es8389_core_supplies); i++) + es8389->core_supply[i].supply = es8389_core_supplies[i]; + ret = devm_regulator_bulk_get(&i2c_client->dev, ARRAY_SIZE(es8389_core_supplies), + es8389->core_supply); + if (ret) + return dev_err_probe(&i2c_client->dev, ret, + "Failed to request core supplies\n"); + + es8389->mclk = devm_clk_get_optional(&i2c_client->dev, "mclk"); + if (IS_ERR(es8389->mclk)) + return dev_err_probe(&i2c_client->dev, PTR_ERR(es8389->mclk), + "ES8389 is unable to get mclk\n"); + + if (!es8389->mclk) + dev_err(&i2c_client->dev, "%s, assuming static mclk\n", __func__); + ret = devm_snd_soc_register_component(&i2c_client->dev, &soc_codec_dev_es8389, &es8389_dai, From 57d1e55c7b5a66a8642e3c944d46e3d3bafb28c8 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:13:02 +0900 Subject: [PATCH 1103/1417] ASoC: codecs: rt5677-spi: Free the DSP context on component remove component->dev is the underlying SPI bus device, whose devres lifetime follows the physical devices probe/remove rather than the ASoC cards bind/unbind. The rt5677_dsp context allocated in the component probe with devm_kzalloc(component->dev, ...) therefore leaks on every card bind/unbind cycle, and the delayed work initialised there is never cancelled on unbind. Allocate the context with kzalloc() and add a component .remove callback that cancels the copy work, destroys the mutex and frees the context. The exported rt5677_spi_hotword_detected(), called from the rt5677 codec interrupt path, fetches the context via dev_get_drvdata(&g_spi->dev) and dereferences it. Freeing the context on remove would let that callback run concurrently and touch freed memory. Add a dsp_lock that serializes the fetch/use in rt5677_spi_hotword_detected() against clearing the drvdata in the remove callback, so once remove has cleared the pointer the callback either observes NULL and bails out or has already finished. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-19-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677-spi.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c index ebc527115ea50c..0b2166082437d4 100644 --- a/sound/soc/codecs/rt5677-spi.c +++ b/sound/soc/codecs/rt5677-spi.c @@ -58,6 +58,8 @@ static struct spi_device *g_spi; static DEFINE_MUTEX(spi_mutex); +/* Serializes access to the DSP context against the exported hotword callback */ +static DEFINE_MUTEX(dsp_lock); struct rt5677_dsp { struct device *dev; @@ -380,8 +382,7 @@ static int rt5677_spi_pcm_probe(struct snd_soc_component *component) { struct rt5677_dsp *rt5677_dsp; - rt5677_dsp = devm_kzalloc(component->dev, sizeof(*rt5677_dsp), - GFP_KERNEL); + rt5677_dsp = kzalloc_obj(*rt5677_dsp); if (!rt5677_dsp) return -ENOMEM; rt5677_dsp->dev = &g_spi->dev; @@ -392,9 +393,23 @@ static int rt5677_spi_pcm_probe(struct snd_soc_component *component) return 0; } +static void rt5677_spi_pcm_remove(struct snd_soc_component *component) +{ + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + + scoped_guard(mutex, &dsp_lock) + snd_soc_component_set_drvdata(component, NULL); + + cancel_delayed_work_sync(&rt5677_dsp->copy_work); + mutex_destroy(&rt5677_dsp->dma_lock); + kfree(rt5677_dsp); +} + static const struct snd_soc_component_driver rt5677_spi_dai_component = { .name = DRV_NAME, .probe = rt5677_spi_pcm_probe, + .remove = rt5677_spi_pcm_remove, .open = rt5677_spi_pcm_open, .close = rt5677_spi_pcm_close, .hw_params = rt5677_spi_hw_params, @@ -579,6 +594,8 @@ void rt5677_spi_hotword_detected(void) if (!g_spi) return; + guard(mutex)(&dsp_lock); + rt5677_dsp = dev_get_drvdata(&g_spi->dev); if (!rt5677_dsp) { dev_err(&g_spi->dev, "Can't get rt5677_dsp\n"); From 48f1dfbfe4e5f314ddc3e7cbe13f0bb60a1b4f7f Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:13:03 +0900 Subject: [PATCH 1104/1417] ASoC: codecs: rt1011: Free the bq/drc coefficient arrays on component remove component->dev is the underlying i2c device whose devres is only released on physical device removal, not on ASoC card unbind. The bq_drc_params arrays allocated in the component probe with devm_kcalloc(component->dev, ...) therefore leak on every card bind/unbind cycle. Allocate the arrays with kcalloc() and free them in the component remove callback. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-20-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1011.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/rt1011.c b/sound/soc/codecs/rt1011.c index d47b0370dd6b06..c140c079838d45 100644 --- a/sound/soc/codecs/rt1011.c +++ b/sound/soc/codecs/rt1011.c @@ -2053,29 +2053,45 @@ static int rt1011_probe(struct snd_soc_component *component) schedule_work(&rt1011->cali_work); rt1011->i2s_ref = 0; - rt1011->bq_drc_params = devm_kcalloc(component->dev, - RT1011_ADVMODE_NUM, sizeof(struct rt1011_bq_drc_params *), - GFP_KERNEL); + rt1011->bq_drc_params = kcalloc(RT1011_ADVMODE_NUM, + sizeof(struct rt1011_bq_drc_params *), + GFP_KERNEL); if (!rt1011->bq_drc_params) return -ENOMEM; for (i = 0; i < RT1011_ADVMODE_NUM; i++) { - rt1011->bq_drc_params[i] = devm_kcalloc(component->dev, - RT1011_BQ_DRC_NUM, sizeof(struct rt1011_bq_drc_params), - GFP_KERNEL); + rt1011->bq_drc_params[i] = kcalloc(RT1011_BQ_DRC_NUM, + sizeof(struct rt1011_bq_drc_params), + GFP_KERNEL); if (!rt1011->bq_drc_params[i]) - return -ENOMEM; + goto err; } return 0; + +err: + while (i--) + kfree(rt1011->bq_drc_params[i]); + kfree(rt1011->bq_drc_params); + rt1011->bq_drc_params = NULL; + + return -ENOMEM; } static void rt1011_remove(struct snd_soc_component *component) { struct rt1011_priv *rt1011 = snd_soc_component_get_drvdata(component); + int i; cancel_work_sync(&rt1011->cali_work); rt1011_reset(rt1011->regmap); + + if (rt1011->bq_drc_params) { + for (i = 0; i < RT1011_ADVMODE_NUM; i++) + kfree(rt1011->bq_drc_params[i]); + kfree(rt1011->bq_drc_params); + rt1011->bq_drc_params = NULL; + } } #ifdef CONFIG_PM From 6e7f4c5b30a3b0d47c44be2303d8370e0adfa361 Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:13:04 +0900 Subject: [PATCH 1105/1417] ASoC: codecs: rt5645: Free the hardware EQ parameters on component remove component->dev is the underlying I2C bus device, whose devres lifetime follows the physical device's probe/remove rather than the ASoC card's bind/unbind. The eq_param array was allocated in rt5645_probe() via devm_kcalloc(component->dev, ...) but never released on component remove, so it leaked one allocation on every card bind/unbind cycle. Allocate eq_param with plain kcalloc() in rt5645_probe() and free it explicitly in rt5645_remove() to keep its lifetime tied to the component. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-21-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5645.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index bb448254275f65..15d21097ed0c2e 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3487,10 +3487,8 @@ static int rt5645_probe(struct snd_soc_component *component) if (rt5645->pdata.long_name) component->card->long_name = rt5645->pdata.long_name; - rt5645->eq_param = devm_kcalloc(component->dev, - RT5645_HWEQ_NUM, sizeof(struct rt5645_eq_param_s), - GFP_KERNEL); - + rt5645->eq_param = kcalloc(RT5645_HWEQ_NUM, + sizeof(struct rt5645_eq_param_s), GFP_KERNEL); if (!rt5645->eq_param) return -ENOMEM; @@ -3503,7 +3501,12 @@ static int rt5645_probe(struct snd_soc_component *component) static void rt5645_remove(struct snd_soc_component *component) { + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + rt5645_reset(component); + + kfree(rt5645->eq_param); + rt5645->eq_param = NULL; } #ifdef CONFIG_PM From 3943b84e063f036ce919effa834a51d582812b9f Mon Sep 17 00:00:00 2001 From: Chancel Liu Date: Wed, 23 Sep 2026 15:13:05 +0900 Subject: [PATCH 1106/1417] ASoC: codecs: rt5514-spi: Free the DSP context on component remove component->dev is the underlying SPI bus device, whose devres lifetime follows the physical device's probe/remove rather than the ASoC card's bind/unbind. The rt5514_dsp context was allocated in rt5514_spi_pcm_probe() via devm_kzalloc(component->dev, ...) and its delayed work and wakeup source were never torn down on component remove, so the context leaked and the pending copy work / wakeup source were left dangling on every card bind/unbind cycle. Allocate the context with plain kzalloc() and add a component .remove callback that cancels the copy work, undoes device_init_wakeup(), destroys the mutex and frees the context. Signed-off-by: Chancel Liu Link: https://patch.msgid.link/20260923061305.4041197-22-chancel.liu@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5514-spi.c | 37 +++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c index 91290bfe8daac7..bcc2ff28b12531 100644 --- a/sound/soc/codecs/rt5514-spi.c +++ b/sound/soc/codecs/rt5514-spi.c @@ -43,6 +43,7 @@ struct rt5514_dsp { struct snd_pcm_substream *substream; unsigned int buf_base, buf_limit, buf_rp; size_t buf_size, get_size, dma_offset; + int irq; }; static const struct snd_pcm_hardware rt5514_spi_pcm_hardware = { @@ -257,8 +258,7 @@ static int rt5514_spi_pcm_probe(struct snd_soc_component *component) struct rt5514_dsp *rt5514_dsp; int ret; - rt5514_dsp = devm_kzalloc(component->dev, sizeof(*rt5514_dsp), - GFP_KERNEL); + rt5514_dsp = kzalloc_obj(*rt5514_dsp); if (!rt5514_dsp) return -ENOMEM; @@ -268,21 +268,41 @@ static int rt5514_spi_pcm_probe(struct snd_soc_component *component) snd_soc_component_set_drvdata(component, rt5514_dsp); if (rt5514_spi->irq) { - ret = devm_request_threaded_irq(&rt5514_spi->dev, - rt5514_spi->irq, NULL, rt5514_spi_irq, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, "rt5514-spi", - rt5514_dsp); - if (ret) + ret = request_threaded_irq(rt5514_spi->irq, NULL, + rt5514_spi_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "rt5514-spi", rt5514_dsp); + if (ret) { dev_err(&rt5514_spi->dev, "%s Failed to request IRQ: %d\n", __func__, ret); - else + } else { + rt5514_dsp->irq = rt5514_spi->irq; device_init_wakeup(rt5514_dsp->dev, true); + } } return 0; } +static void rt5514_spi_pcm_remove(struct snd_soc_component *component) +{ + struct rt5514_dsp *rt5514_dsp = + snd_soc_component_get_drvdata(component); + + snd_soc_component_set_drvdata(component, NULL); + + if (rt5514_dsp->irq) { + free_irq(rt5514_dsp->irq, rt5514_dsp); + device_init_wakeup(rt5514_dsp->dev, false); + } + + cancel_delayed_work_sync(&rt5514_dsp->copy_work); + + mutex_destroy(&rt5514_dsp->dma_lock); + kfree(rt5514_dsp); +} + static int rt5514_spi_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { @@ -294,6 +314,7 @@ static int rt5514_spi_pcm_new(struct snd_soc_component *component, static const struct snd_soc_component_driver rt5514_spi_component = { .name = DRV_NAME, .probe = rt5514_spi_pcm_probe, + .remove = rt5514_spi_pcm_remove, .open = rt5514_spi_pcm_open, .hw_params = rt5514_spi_hw_params, .hw_free = rt5514_spi_hw_free, From 2bc6b218717b9d08f466f88209251d54bc09b207 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 22 Sep 2026 19:14:30 +0100 Subject: [PATCH 1107/1417] arm64/boot: Disable trapping of PMZR_EL0 writes to EL2 __init_el2_fgt2() writes one mask to both HDFGRTR2_EL2 and HDFGWTR2_EL2. PMZR_EL0 is write-only, so its trap bit, nPMZR_EL0, exists only in HDFGWTR2_EL2 and is therefore never set: a PMZR_EL0 write from the host traps to EL2, where the nVHE hypervisor has no handler and BUG()s. The kernel never writes PMZR_EL0, but kernel.perf_user_access=1 has the PMU driver set PMUSERENR_EL0.UEN for a task with a user-read event, so a write from EL0 reaches the trap and takes the host down without a panic message. Accumulate the HDFGWTR2_EL2 bits separately, as __init_el2_fgt() already does for HDFGWTR_EL2, and set nPMZR_EL0 with the other FEAT_PMUv3p9 bits. Fixes: 858c7bfcb35e1 ("arm64/boot: Enable EL2 requirements for FEAT_PMUv3p9") Cc: stable@vger.kernel.org Signed-off-by: Fuad Tabba Reviewed-by: Anshuman Khandual Reviewed-by: Oliver Upton Signed-off-by: Will Deacon --- Documentation/arch/arm64/booting.rst | 1 + arch/arm64/include/asm/el2_setup.h | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Documentation/arch/arm64/booting.rst b/Documentation/arch/arm64/booting.rst index 13ef311dace83f..3fea4b14ef7c22 100644 --- a/Documentation/arch/arm64/booting.rst +++ b/Documentation/arch/arm64/booting.rst @@ -465,6 +465,7 @@ Before jumping into the kernel, the following conditions must be met: - HDFGWTR2_EL2.nPMICNTR_EL0 (bit 2) must be initialised to 0b1. - HDFGWTR2_EL2.nPMICFILTR_EL0 (bit 3) must be initialised to 0b1. - HDFGWTR2_EL2.nPMUACR_EL1 (bit 4) must be initialised to 0b1. + - HDFGWTR2_EL2.nPMZR_EL0 (bit 21) must be initialised to 0b1. For CPUs with SPE data source filtering (FEAT_SPE_FDS): diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h index aa8ec9df802436..87560d8b254e67 100644 --- a/arch/arm64/include/asm/el2_setup.h +++ b/arch/arm64/include/asm/el2_setup.h @@ -418,6 +418,7 @@ b.lt .Lskip_fgt2_\@ mov x0, xzr + mov x2, xzr mrs x1, id_aa64dfr0_el1 ubfx x1, x1, #ID_AA64DFR0_EL1_PMUVer_SHIFT, #4 cmp x1, #ID_AA64DFR0_EL1_PMUVer_V3P9 @@ -426,6 +427,11 @@ orr x0, x0, #HDFGRTR2_EL2_nPMICNTR_EL0 orr x0, x0, #HDFGRTR2_EL2_nPMICFILTR_EL0 orr x0, x0, #HDFGRTR2_EL2_nPMUACR_EL1 + orr x2, x2, #HDFGWTR2_EL2_nPMICNTR_EL0 + orr x2, x2, #HDFGWTR2_EL2_nPMICFILTR_EL0 + orr x2, x2, #HDFGWTR2_EL2_nPMUACR_EL1 + /* PMZR_EL0 is write-only, so it has no read trap to disable */ + orr x2, x2, #HDFGWTR2_EL2_nPMZR_EL0 .Lskip_pmuv3p9_\@: /* If SPE is implemented, */ __spe_vers_imp .Lskip_spefds_\@, ID_AA64DFR0_EL1_PMSVer_IMP, x1 @@ -436,10 +442,11 @@ cbz x1, .Lskip_spefds_\@ /* disable traps of PMSDSFR to EL2. */ orr x0, x0, #HDFGRTR2_EL2_nPMSDSFR_EL1 + orr x2, x2, #HDFGWTR2_EL2_nPMSDSFR_EL1 .Lskip_spefds_\@: msr_s SYS_HDFGRTR2_EL2, x0 - msr_s SYS_HDFGWTR2_EL2, x0 + msr_s SYS_HDFGWTR2_EL2, x2 msr_s SYS_HFGRTR2_EL2, xzr msr_s SYS_HFGWTR2_EL2, xzr msr_s SYS_HFGITR2_EL2, xzr From 83701fdb2d8d644c547773791da48d6ecb3a5e1d Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Wed, 23 Sep 2026 01:40:17 -0400 Subject: [PATCH 1108/1417] ASoC: fsl_xcvr: free IRQ before canceling reset work irq0_isr() schedules work_rst on a preamble error, and reset_rx_work() touches regmap. remove() cancels that work while the IRQ is still registered, so the handler can queue it again during teardown. The IRQ is also requested before INIT_WORK() and spin_lock_init(). Probe failure does not call remove(), so freeing the IRQ leaves queued work running, and an earlier interrupt schedules uninitialized work. Initialize the lock and devm_work_autocancel() before the IRQ. Failed probe then frees the IRQ and cancels the work. Unbind frees the IRQ before cancel_work_sync() and pm_runtime_disable(). Fixes: 1e5d0f106164 ("ASoC: fsl_xcvr: reset RX dpath after wrong preamble") Cc: stable@vger.kernel.org # 6.13+ Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20260923054017.93553-1-mhun512@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_xcvr.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c index 98282720435112..29929dfcce2a69 100644 --- a/sound/soc/fsl/fsl_xcvr.c +++ b/sound/soc/fsl/fsl_xcvr.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -57,6 +58,7 @@ struct fsl_xcvr { struct snd_aes_iec958 tx_iec958; u8 cap_ds[FSL_XCVR_CAPDS_SIZE]; struct work_struct work_rst; + int irq; spinlock_t lock; /* Protect hw_reset and trigger */ struct snd_pcm_hw_constraint_list spdif_constr_rates; u32 spdif_constr_rates_list[SPDIF_NUM_RATES]; @@ -1617,7 +1619,7 @@ static int fsl_xcvr_probe(struct platform_device *pdev) struct fsl_xcvr *xcvr; struct resource *rx_res, *tx_res; void __iomem *regs; - int ret, irq; + int ret; xcvr = devm_kzalloc(dev, sizeof(*xcvr), GFP_KERNEL); if (!xcvr) @@ -1705,12 +1707,22 @@ static int fsl_xcvr_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(xcvr->reset), "failed to get XCVR reset control\n"); + /* + * irq0_isr() schedules work_rst. Prepare the work and its lock + * before the IRQ, and register the cancel action first so a failed + * probe frees the IRQ and then cancels any queued work. + */ + spin_lock_init(&xcvr->lock); + ret = devm_work_autocancel(dev, &xcvr->work_rst, reset_rx_work); + if (ret) + return ret; + /* get IRQs */ - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + xcvr->irq = platform_get_irq(pdev, 0); + if (xcvr->irq < 0) + return xcvr->irq; - ret = devm_request_irq(dev, irq, irq0_isr, 0, pdev->name, xcvr); + ret = devm_request_irq(dev, xcvr->irq, irq0_isr, 0, pdev->name, xcvr); if (ret) return dev_err_probe(dev, ret, "failed to claim IRQ0\n"); @@ -1751,8 +1763,6 @@ static int fsl_xcvr_probe(struct platform_device *pdev) fsl_xcvr_comp.name); } - INIT_WORK(&xcvr->work_rst, reset_rx_work); - spin_lock_init(&xcvr->lock); return ret; } @@ -1760,6 +1770,8 @@ static void fsl_xcvr_remove(struct platform_device *pdev) { struct fsl_xcvr *xcvr = dev_get_drvdata(&pdev->dev); + /* Free the IRQ first so irq0_isr() cannot requeue work_rst. */ + devm_free_irq(&pdev->dev, xcvr->irq, xcvr); cancel_work_sync(&xcvr->work_rst); pm_runtime_disable(&pdev->dev); } From 4cbe530c0233c7413aaaeb029a4f32dd6aadacbb Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 19 Sep 2026 19:10:58 +0200 Subject: [PATCH 1109/1417] gpio: tps65219: Fix GPIO input value reads TPS65219_MFP_GPIO_STATUS_MASK is already BIT(4). Passing it to BIT() again tests bit 16, which cannot be set in the 8-bit MFP_CTRL register, so GPIO0 is always reported low when configured as an input. Test the register value with the mask directly. Fixes: 57e30e00bd5b ("gpio: tps65219: add GPIO support for TPS65219 PMIC") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Jonathan Cormier Link: https://patch.msgid.link/20260919171100.90430-2-kmehltretter@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tps65219.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-tps65219.c b/drivers/gpio/gpio-tps65219.c index 457fd8a589e892..b25c6f7277680a 100644 --- a/drivers/gpio/gpio-tps65219.c +++ b/drivers/gpio/gpio-tps65219.c @@ -79,7 +79,7 @@ static int tps65219_gpio_get(struct gpio_chip *gc, unsigned int offset) if (ret) return ret; - ret = !!(val & BIT(TPS65219_MFP_GPIO_STATUS_MASK)); + ret = !!(val & TPS65219_MFP_GPIO_STATUS_MASK); dev_warn(dev, "GPIO%d = %d, MULTI_DEVICE_ENABLE, not a standard GPIO\n", offset, ret); /* From 93cf8cedeaaa05714f709b539cfb976e0b80c830 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 19 Sep 2026 19:10:59 +0200 Subject: [PATCH 1110/1417] gpio: tps65219: Use the variant-specific direction callback The TPS65214 template installs its own get_direction callback because its direction bit is in GENERAL_CONFIG. The shared get and direction callbacks nevertheless call tps65219_gpio_get_direction() directly and interpret the unrelated TPS65219 MFP bit. On TPS65214 this can reject reads from an input and skip the change from input to output. Call the callback selected by the gpio_chip template instead. Fixes: 1b6ab07c0c80 ("gpio: tps65219: Add support for TI TPS65214 PMIC") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260919171100.90430-3-kmehltretter@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tps65219.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-tps65219.c b/drivers/gpio/gpio-tps65219.c index b25c6f7277680a..479a80ef765433 100644 --- a/drivers/gpio/gpio-tps65219.c +++ b/drivers/gpio/gpio-tps65219.c @@ -87,7 +87,7 @@ static int tps65219_gpio_get(struct gpio_chip *gc, unsigned int offset) * status bit. */ - if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) + if (gc->get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) return -ENOTSUPP; return ret; @@ -176,7 +176,7 @@ static int tps65219_gpio_direction_input(struct gpio_chip *gc, unsigned int offs return -ENOTSUPP; } - if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_IN) + if (gc->get_direction(gc, offset) == GPIO_LINE_DIRECTION_IN) return 0; return gpio->change_dir(gc, offset, GPIO_LINE_DIRECTION_IN); @@ -190,7 +190,7 @@ static int tps65219_gpio_direction_output(struct gpio_chip *gc, unsigned int off if (offset != TPS6521X_GPIO0_IDX) return 0; - if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) + if (gc->get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) return 0; return gpio->change_dir(gc, offset, GPIO_LINE_DIRECTION_OUT); From 270437f3fe62516f16482742a7762a075e7a9457 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 19 Sep 2026 19:11:00 +0200 Subject: [PATCH 1111/1417] gpio: tps65219: Fix TPS65214 GPIO direction programming GPIO_LINE_DIRECTION_OUT and GPIO_LINE_DIRECTION_IN have the values 0 and 1, respectively, while the TPS65214 GPIO_CONFIG field is BIT(1). regmap_update_bits() masks the supplied value, so passing either direction value clears the field and selects input mode. Translate the GPIO direction to the register encoding used by tps65214_gpio_get_direction(), setting GPIO_CONFIG for output and clearing it for input. Fixes: 1b6ab07c0c80 ("gpio: tps65219: Add support for TI TPS65214 PMIC") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260919171100.90430-4-kmehltretter@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tps65219.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-tps65219.c b/drivers/gpio/gpio-tps65219.c index 479a80ef765433..6958466455d04c 100644 --- a/drivers/gpio/gpio-tps65219.c +++ b/drivers/gpio/gpio-tps65219.c @@ -158,8 +158,10 @@ static int tps65214_gpio_change_direction(struct gpio_chip *gc, unsigned int off if (ret) dev_err(dev, "GPIO%d configured as VSEL, not GPIO\n", offset); + val = direction == GPIO_LINE_DIRECTION_OUT ? + TPS65214_GPIO0_DIR_MASK : 0; ret = regmap_update_bits(gpio->tps->regmap, TPS65219_REG_GENERAL_CONFIG, - TPS65214_GPIO0_DIR_MASK, direction); + TPS65214_GPIO0_DIR_MASK, val); if (ret) dev_err(dev, "Fail to change direction to %u for GPIO%d.\n", direction, offset); From 5b76268dac968612f7283d59b539036de955b7d9 Mon Sep 17 00:00:00 2001 From: Vineeth Vijayan Date: Tue, 22 Sep 2026 22:48:39 +0200 Subject: [PATCH 1112/1417] s390/cio: Fix NULL pointer dereference in ccw_device_get_util_str() The channel path registry entry associated with a CHPID may be removed while the subchannel's PMCW still references that CHPID. In this case, chpid_to_chp() can return NULL, leading to a NULL pointer dereference. Add the missing NULL check before dereferencing the returned pointer. Fixes: 199652309a4d ("s390/cio: add helper to query utility strings per given ccw device") Cc: stable@vger.kernel.org Signed-off-by: Vineeth Vijayan Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/device_ops.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c index f2f7f8cba410bc..1f7e83fd50872b 100644 --- a/drivers/s390/cio/device_ops.c +++ b/drivers/s390/cio/device_ops.c @@ -517,6 +517,8 @@ u8 *ccw_device_get_util_str(struct ccw_device *cdev, int chp_idx) chp_id_init(&chpid); chpid.id = sch->schib.pmcw.chpid[chp_idx]; chp = chpid_to_chp(chpid); + if (!chp) + return NULL; util_str = kmalloc(sizeof(chp->desc_fmt3.util_str), GFP_KERNEL); if (!util_str) From 883b776abe48fed8ef615f4ff7e91113a6c4dffb Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Tue, 22 Sep 2026 12:41:02 +0200 Subject: [PATCH 1113/1417] isofs: Fix handling of directories with tight blocks Thomas has reported that after commit b2eb2e288604 ("isofs: Drop support of directory entries straddling blocks") some isofs images of Fedora miss some entries in some directories. The problem is triggered when directory entries are packed in a block in such a way that they exactly fit the block (which BTW mkisofs doesn't do so I didn't catch this bug when testing with my images). Fix readdir and lookup code to properly transition to the next block when directory block is tightly packed. Link: https://bugzilla.redhat.com/show_bug.cgi?id=2535353 Reported-by: Thomas Schmitt Reported-by: Matthias Goergens Fixes: b2eb2e288604 ("isofs: Drop support of directory entries straddling blocks") Reviewed-by: Thomas Schmitt Signed-off-by: Jan Kara --- fs/isofs/dir.c | 20 ++++++++------------ fs/isofs/namei.c | 16 +++++++++------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/fs/isofs/dir.c b/fs/isofs/dir.c index c7ca7603e97a12..28741251d56e36 100644 --- a/fs/isofs/dir.c +++ b/fs/isofs/dir.c @@ -110,25 +110,21 @@ static int do_isofs_readdir(struct inode *inode, struct file *file, return 0; } - de = (struct iso_directory_record *) (bh->b_data + offset); - - de_len = *(unsigned char *)de; - + de = (struct iso_directory_record *)(bh->b_data + offset); /* - * If the length byte is zero, we should move on to the next - * CDROM sector. If we are at the end of the directory, we - * kick out of the while loop. + * If we are at the end of a block (or at its zero-padded + * tail), move on to the next CDROM sector. If we are at the + * end of the directory, we'll abort the while loop. */ - - if (de_len == 0) { + if (offset >= bufsize || de->length[0] == 0) { brelse(bh); bh = NULL; - ctx->pos = (ctx->pos + ISOFS_BLOCK_SIZE) & ~(ISOFS_BLOCK_SIZE - 1); - block = ctx->pos >> bufbits; + block++; + ctx->pos = (loff_t)block << bufbits; offset = 0; continue; } - + de_len = de->length[0]; block_saved = block; offset_saved = offset; offset += de_len; diff --git a/fs/isofs/namei.c b/fs/isofs/namei.c index 010682f5901a49..4fba1bf7f0167c 100644 --- a/fs/isofs/namei.c +++ b/fs/isofs/namei.c @@ -74,18 +74,20 @@ isofs_find_entry(struct inode *dir, struct dentry *dentry, return 0; } - de = (struct iso_directory_record *) (bh->b_data + offset); - - de_len = *(unsigned char *) de; - if (!de_len) { + de = (struct iso_directory_record *)(bh->b_data + offset); + /* + * If we are at the end of the block or at its zero-padded + * tail, move to the next block. + */ + if (offset >= bufsize || de->length[0] == 0) { brelse(bh); bh = NULL; - f_pos = (f_pos + ISOFS_BLOCK_SIZE) & ~(ISOFS_BLOCK_SIZE - 1); - block = f_pos >> bufbits; + block++; + f_pos = block << bufbits; offset = 0; continue; } - + de_len = de->length[0]; block_saved = bh->b_blocknr; offset_saved = offset; offset += de_len; From 3246de5077648a427eeb69ec970348774da5df68 Mon Sep 17 00:00:00 2001 From: Diogo Ivo Date: Wed, 23 Sep 2026 17:45:10 +0100 Subject: [PATCH 1114/1417] ASoC: dt-bindings: realtek,rt5677: Add MCLK1 clock property Add a clocks property so that boards can describe the clock feeding the codec's MCLK1 input. Signed-off-by: Diogo Ivo Link: https://patch.msgid.link/20260923-rt5677-mclk-v2-1-6ca31e8f68a4@tecnico.ulisboa.pt Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/realtek,rt5677.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/realtek,rt5677.yaml b/Documentation/devicetree/bindings/sound/realtek,rt5677.yaml index ae27ae78b1b221..f76aad0d30168b 100644 --- a/Documentation/devicetree/bindings/sound/realtek,rt5677.yaml +++ b/Documentation/devicetree/bindings/sound/realtek,rt5677.yaml @@ -44,6 +44,14 @@ properties: '#gpio-cells': const: 2 + clocks: + items: + - description: phandle and clock specifier for the codec MCLK1 input. + + clock-names: + items: + - const: mclk1 + realtek,pow-ldo2-gpio: maxItems: 1 description: CODEC's POW_LDO2 pin. @@ -127,6 +135,8 @@ examples: interrupts = <3 IRQ_TYPE_LEVEL_HIGH>; gpio-controller; #gpio-cells = <2>; + clocks = <&osc>; + clock-names = "mclk1"; realtek,pow-ldo2-gpio = <&gpio 3 GPIO_ACTIVE_HIGH>; realtek,reset-gpio = <&gpio 3 GPIO_ACTIVE_LOW>; realtek,in1-differential; From 4409a85735cdca5c4c400c0dad1feee091872eee Mon Sep 17 00:00:00 2001 From: Liang Luo Date: Wed, 23 Sep 2026 19:07:37 +0800 Subject: [PATCH 1115/1417] sched_ext: Count SCX_EV_SUB_BYPASS_DISPATCH in the dispatch fallback When a descendant scheduler enters bypass mode, its tasks are parked in the bypass DSQs of the nearest non-bypassing ancestor, which is then responsible for running them. On behalf of such a non-bypassing host, scx_dispatch_sched() consumes those bypass DSQs from two places: the attempt made every SCX_BYPASS_HOST_NTH dispatches, and the end-of-dispatch fallback that keeps the CPU from going idle while bypassed descendants still have tasks queued. The former increments SCX_EV_SUB_BYPASS_DISPATCH but the latter does not, even though both perform the same scx_consume_dispatch_q() on the same bypass DSQ. The descendant bypass dispatches done by the fallback are therefore missing from the counter exposed via sysfs, scx_dump_state() and the scx_bpf_events() kfunc, which under-reports the actual number of such dispatches. Add the missing __scx_add_event() so the fallback counts them too. When @sch itself is bypassing, scx_dispatch_sched() takes the earlier self-bypass branch and returns before reaching these host paths; that mode is accounted for by SCX_EV_BYPASS_DISPATCH at enqueue time and is intentionally left unchanged. Fixes: 025b1bd41965 ("sched_ext: Implement hierarchical bypass mode") Signed-off-by: Liang Luo Signed-off-by: Tejun Heo --- kernel/sched/ext/inlines.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/inlines.h b/kernel/sched/ext/inlines.h index ed423bcc26b87c..2ff5479334cba9 100644 --- a/kernel/sched/ext/inlines.h +++ b/kernel/sched/ext/inlines.h @@ -129,8 +129,10 @@ scx_dispatch_sched(struct scx_sched *sch, struct rq *rq, * scheduler's ops.dispatch() doesn't yield any tasks. */ if (scx_bypass_dsp_enabled(sch) && - scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), 0)) + scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), 0)) { + __scx_add_event(sch, SCX_EV_SUB_BYPASS_DISPATCH, 1); return SCX_DSP_LOCAL; + } return SCX_DSP_NONE; } From b8d1d5b63a8ef532038eebd9d97d406860385668 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Tue, 22 Sep 2026 13:24:55 +0900 Subject: [PATCH 1116/1417] x86/mce: Fix hardware debug register corruption on task migration In exc_machine_check_user(), local_db_save() and local_db_restore() are invoked in the outer entry stubs (DEFINE_IDTENTRY_MCE_USER, DEFINE_FREDENTRY_MCE, and DEFINE_IDTENTRY_RAW), surrounding exc_machine_check_user(). However, exc_machine_check_user() calls irqentry_exit_to_user_mode(), which handles pending thread work and may schedule() if TIF_NEED_RESCHED is set. If the task migrates to another CPU during schedule(), local_db_restore() runs on the new CPU with the dr7 state saved from the old CPU. This corrupts the new CPU's DR7 hardware debug register and leaves the old CPU's DR7 disabled. In short, local_db_save() and local_db_restore() pair must be run on the same CPU. To fix this, move local_db_save() and local_db_restore() inside exc_machine_check_user() and exc_machine_check_kernel(). In exc_machine_check_user(), DR7 is saved and restored strictly around do_machine_check() to avoid schedule() during migration. In exc_machine_check_kernel(), local_db_save() is called at the entry point to prevent early memory accesses from triggering nested #DB exceptions, and restored on all exits. Fixes: cd840e424f27 ("x86/entry, mce: Disallow #DB during #MC") Assisted-by: LLM Signed-off-by: Masami Hiramatsu (Google) Signed-off-by: Borislav Petkov (AMD) Acked-by: Peter Zijlstra (Intel) Cc: Link: https://patch.msgid.link/179005109564.388919.3937970081044095776.stgit@devnote2 --- arch/x86/kernel/cpu/mce/core.c | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c index ab469605fc893a..39f238952e1498 100644 --- a/arch/x86/kernel/cpu/mce/core.c +++ b/arch/x86/kernel/cpu/mce/core.c @@ -2108,6 +2108,9 @@ bool filter_mce(struct mce *m) static __always_inline void exc_machine_check_kernel(struct pt_regs *regs) { irqentry_state_t irq_state; + unsigned long dr7; + + dr7 = local_db_save(); WARN_ON_ONCE(user_mode(regs)); @@ -2116,20 +2119,26 @@ static __always_inline void exc_machine_check_kernel(struct pt_regs *regs) * mce_check_crashing_cpu() for details. */ if (mca_cfg.initialized && mce_check_crashing_cpu()) - return; + goto out; irq_state = irqentry_nmi_enter(regs); do_machine_check(regs); irqentry_nmi_exit(regs, irq_state); +out: + local_db_restore(dr7); } static __always_inline void exc_machine_check_user(struct pt_regs *regs) { + unsigned long dr7; + irqentry_enter_from_user_mode(regs); + dr7 = local_db_save(); do_machine_check(regs); + local_db_restore(dr7); irqentry_exit_to_user_mode(regs); } @@ -2138,21 +2147,13 @@ static __always_inline void exc_machine_check_user(struct pt_regs *regs) /* MCE hit kernel mode */ DEFINE_IDTENTRY_MCE(exc_machine_check) { - unsigned long dr7; - - dr7 = local_db_save(); exc_machine_check_kernel(regs); - local_db_restore(dr7); } /* The user mode variant. */ DEFINE_IDTENTRY_MCE_USER(exc_machine_check) { - unsigned long dr7; - - dr7 = local_db_save(); exc_machine_check_user(regs); - local_db_restore(dr7); } #ifdef CONFIG_X86_FRED @@ -2169,28 +2170,20 @@ DEFINE_IDTENTRY_MCE_USER(exc_machine_check) */ DEFINE_FREDENTRY_MCE(exc_machine_check) { - unsigned long dr7; - - dr7 = local_db_save(); if (user_mode(regs)) exc_machine_check_user(regs); else exc_machine_check_kernel(regs); - local_db_restore(dr7); } #endif #else /* 32bit unified entry point */ DEFINE_IDTENTRY_RAW(exc_machine_check) { - unsigned long dr7; - - dr7 = local_db_save(); if (user_mode(regs)) exc_machine_check_user(regs); else exc_machine_check_kernel(regs); - local_db_restore(dr7); } #endif From 4fde448225123442c5796f54b7a4400e2d3cbaf6 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 8 Sep 2026 14:05:59 -0500 Subject: [PATCH 1117/1417] x86/PCI: Disable enhanced atomics on AMD NBIO 7.7 and 7.11 Multiple users report data corruption during 64-bit DMA transfers on systems with AMD NBIO 7.7 and 7.11 controllers. This occurs when BIOS enables AMD "enhanced atomic operations" on PCIe Root Ports. When enhanced atomics are enabled, any 64-bit DMA access may be corrupted. Disable enhanced atomics using SMN for NBIO 7.7 and 7.11 based models. Reported-by: Mikael Etienne Closes: https://lore.kernel.org/178789300872.392066.15963676631650361573@gmail.com/ Reported-by: Arthur Husband Closes: https://lore.kernel.org/20260406222335.379935-1-artmoty@gmail.com/ Reported-by: Alvin Lim Closes: https://lore.kernel.org/20260621100844.1224301-1-alvinwylim@gmail.com/ Signed-off-by: Mario Limonciello [bhelgaas: commit log, s/IOVA/DMA/ in comment] Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org Cc: David Laight Cc: John Smith Cc: Lennert Buytenhek Cc: Niklas Cassel Cc: Roland Waltersson Link: https://patch.msgid.link/20260908190600.226485-2-mario.limonciello@amd.com --- arch/x86/pci/fixup.c | 99 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/arch/x86/pci/fixup.c b/arch/x86/pci/fixup.c index b301c6c8df7532..795d81c7a4de4b 100644 --- a/arch/x86/pci/fixup.c +++ b/arch/x86/pci/fixup.c @@ -886,6 +886,105 @@ static void quirk_clear_strap_no_soft_reset_dev2_f0(struct pci_dev *dev) } } DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x15b8, quirk_clear_strap_no_soft_reset_dev2_f0); + +/* + * Enhanced atomic operations can cause corruption with 64-bit DMA + * on these devices. + */ +#define RX_ENH_ATOMIC_EN BIT(8) + +static const u32 nbio_7_7_pcie_smn_addrs[] = { + 0x111401d0, + 0x111411d0, + 0x111421d0, + 0x111431d0, + 0x111441d0, + 0x112401d0, + 0x112411d0, + 0x112421d0, + 0x112431d0, + 0x112441d0, + 0x112451d0, + 0x113401d0, + 0x114401d0, +}; + +static const u32 nbio_7_11_pcie_smn_addrs[] = { + 0x112401d0, + 0x112411d0, + 0x112421d0, + 0x112431d0, + 0x112441d0, + 0x112451d0, + 0x113401d0, + 0x113411d0, + 0x113421d0, + 0x113431d0, + 0x113441d0, + 0x113451d0, +}; + +static void quirk_amd_nbio_enhanced_atomic(struct pci_dev *host_bridge, + const u32 *smn_addrs, + size_t nr_smn_addrs) +{ + bool changed = false; + size_t i; + u32 data; + int ret; + + for (i = 0; i < nr_smn_addrs; i++) { + ret = amd_smn_read(0, smn_addrs[i], &data); + if (ret) + continue; + if (!(data & RX_ENH_ATOMIC_EN)) + continue; + data = data & ~RX_ENH_ATOMIC_EN; + ret = amd_smn_write(0, smn_addrs[i], data); + if (ret) + continue; + if (changed) + continue; + ret = amd_smn_read(0, smn_addrs[i], &data); + if (ret) + continue; + if (data & RX_ENH_ATOMIC_EN) + continue; + changed = true; + } + + if (changed) + pci_info(host_bridge, "enhanced atomics disabled\n"); +} + +static void quirk_amd_nbio_7_7_disable_enhanced_atomic(struct pci_dev *dev) +{ + quirk_amd_nbio_enhanced_atomic(dev, nbio_7_7_pcie_smn_addrs, + ARRAY_SIZE(nbio_7_7_pcie_smn_addrs)); +} + +static void quirk_amd_nbio_7_11_disable_enhanced_atomic(struct pci_dev *dev) +{ + quirk_amd_nbio_enhanced_atomic(dev, nbio_7_11_pcie_smn_addrs, + ARRAY_SIZE(nbio_7_11_pcie_smn_addrs)); +} + +/* Phoenix, Hawk Point (NBIO 7.7) */ +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x14E8, + quirk_amd_nbio_7_7_disable_enhanced_atomic); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x14E8, + quirk_amd_nbio_7_7_disable_enhanced_atomic); + +/* Strix, Krackan, Strix Halo (NBIO 7.11) */ +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1507, + quirk_amd_nbio_7_11_disable_enhanced_atomic); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1507, + quirk_amd_nbio_7_11_disable_enhanced_atomic); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1122, + quirk_amd_nbio_7_11_disable_enhanced_atomic); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1122, + quirk_amd_nbio_7_11_disable_enhanced_atomic); + #endif /* From 80320b278fea07ffcda3f57b67b61658e0a4e1ca Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 24 Sep 2026 01:52:03 +0800 Subject: [PATCH 1118/1417] ata: libata-scsi: bound the ATA passthru sense descriptor writes When an ATA PASS-THROUGH command to an ATAPI device fails, the sense buffer holds the device's REQUEST SENSE reply, and ata_scsi_set_passthru_sense_fields() trusts its additional length byte, sb[7], when adding the ATA Status Return descriptor. A faulty or malicious device can use that to make the kernel read and write past the 96-byte buffer in three ways: - scsi_sense_desc_find() is passed sb[7] + 8 as the buffer length, so its clamp against sb[7] does nothing and the walk runs off the end. - A type-9 descriptor found near the end is filled in unchecked. - A new descriptor at sb[8 + len] needs len + 22 bytes, not len + 14, so len 75..82 writes up to 8 bytes past the end. Reproduced with KASAN under qemu, with the emulated ATAPI REQUEST SENSE reply patched: BUG: KASAN: slab-out-of-bounds in scsi_sense_desc_find+0x1a5/0x210 BUG: KASAN: slab-out-of-bounds in ata_scsi_qc_complete+0x1a15/0x1a50 Both are gone with this patch, and a valid descriptor is still filled in. Fixes: 97981926224a ("ata: libata-scsi: Do not overwrite valid sense data when CK_COND=1") Cc: stable@vger.kernel.org Reviewed-by: Damien Le Moal Signed-off-by: Matthias Goergens Link: https://lore.kernel.org/r/20260923175203.1576825-1-matthias.goergens@gmail.com Signed-off-by: Niklas Cassel --- drivers/ata/libata-scsi.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index 7e22bbc382384e..8f9aa97a519d11 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -261,12 +261,18 @@ static void ata_scsi_set_passthru_sense_fields(struct ata_queued_cmd *qc) /* descriptor format */ len = sb[7]; - desc = (char *)scsi_sense_desc_find(sb, len + 8, 9); + desc = (char *)scsi_sense_desc_find(sb, SCSI_SENSE_BUFFERSIZE, 9); if (!desc) { - if (SCSI_SENSE_BUFFERSIZE < len + 14) + /* + * The descriptor is written at sb[8 + len] and is 14 + * bytes long, so it needs len + 22 bytes of buffer. + */ + if (len + 22 > SCSI_SENSE_BUFFERSIZE) return; sb[7] = len + 14; desc = sb + 8 + len; + } else if (desc - sb > SCSI_SENSE_BUFFERSIZE - 14) { + return; } desc[0] = 9; desc[1] = 12; From c5fd4eaad50d620c7e09ac2082b2fb55ee54170e Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 09:49:56 +0000 Subject: [PATCH 1119/1417] drm/amd/display: Fix dc stream excess put in dm_update_crtc_state() In dm_update_crtc_state(), when a modeset is required the newly created stream is stored in dm_new_crtc_state->stream and an extra reference is taken with dc_stream_retain(). The reference returned by create_validate_stream_for_sink() is released as an extra reference at the skip_modeset label, leaving the stream owned by the new CRTC state. If amdgpu_dm_check_crtc_color_mgmt() fails afterwards, the code jumps to the fail label which releases new_stream again. Since the extra reference was already released at skip_modeset, this drops the reference owned by dm_new_crtc_state->stream and the stream is released while the atomic state still points to it, leading to a premature free of the dc stream. Set new_stream to NULL after releasing the extra reference at the skip_modeset label so that a later goto fail cannot release the reference owned by the new CRTC state. Fixes: 7cd4b70091a5 ("drm/amd/display: Rework CRTC color management") Signed-off-by: Wentao Liang Signed-off-by: Alex Deucher (cherry picked from commit 102a47065a62dc8f6bbbb47cf082a2934282eb08) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 36d2f86f000a97..91fdf3de7202dc 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -5583,8 +5583,10 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, skip_modeset: /* Release extra reference */ - if (new_stream) + if (new_stream) { dc_stream_release(new_stream); + new_stream = NULL; + } new_stream = NULL; /* From 3022bdfe3e6d776e9273d6892f7c193138ca0666 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Fri, 31 Jul 2026 11:44:37 +0800 Subject: [PATCH 1120/1417] drm/amdgpu: move userq fence wait out of signalling section The eviction fence suspend worker waits for every pending userq fence from inside a dma_fence_begin_signalling() critical section. Waiting on another DMA fence while responsible for signalling one violates the cross-driver fence contract and is reported by lockdep as a dma_fence_map dependency. Move the wait before dma_fence_begin_signalling(). Keep userq_mutex held so queue lifetime remains stable while inspecting last_fence. Fixes: fc61df151617 ("drm/amdgpu: annotate eviction fence signaling path") Signed-off-by: Prike Liang Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher (cherry picked from commit 3bd4fbc5ed89621340b5cd249869092691a9c81f) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c | 3 +++ drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 4 +--- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c index f6b7522c3c82ff..f8652fd0525df7 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c @@ -68,6 +68,9 @@ amdgpu_eviction_fence_suspend_worker(struct work_struct *work) mutex_lock(&uq_mgr->userq_mutex); + /* Fence waits are not allowed in a fence signalling critical section. */ + amdgpu_userq_wait_for_signal(uq_mgr); + /* * This is intentionally after taking the userq_mutex since we do * allocate memory while holding this lock, but only after ensuring that diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index e43bda0cab3fbb..280bdeb389709e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -1272,7 +1272,7 @@ amdgpu_userq_evict_all(struct amdgpu_userq_mgr *uq_mgr) return ret; } -static void +void amdgpu_userq_wait_for_signal(struct amdgpu_userq_mgr *uq_mgr) { struct amdgpu_usermode_queue *queue; @@ -1291,8 +1291,6 @@ amdgpu_userq_wait_for_signal(struct amdgpu_userq_mgr *uq_mgr) void amdgpu_userq_evict(struct amdgpu_userq_mgr *uq_mgr) { - /* Wait for any pending userqueue fence work to finish */ - amdgpu_userq_wait_for_signal(uq_mgr); amdgpu_userq_evict_all(uq_mgr); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 6412a7f7b6ef03..488dc21d7c81bb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -162,6 +162,7 @@ void amdgpu_userq_mgr_cancel_reset_work(struct amdgpu_device *adev); void amdgpu_userq_mgr_cancel_resume(struct amdgpu_userq_mgr *userq_mgr); void amdgpu_userq_mgr_fini(struct amdgpu_userq_mgr *userq_mgr); +void amdgpu_userq_wait_for_signal(struct amdgpu_userq_mgr *uq_mgr); void amdgpu_userq_evict(struct amdgpu_userq_mgr *uq_mgr); void amdgpu_userq_ensure_ev_fence(struct amdgpu_userq_mgr *userq_mgr, From cd195f1616b2bb5fb7765465326c4d5d64a620a0 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 17 Sep 2026 18:56:45 +0530 Subject: [PATCH 1121/1417] drm/amdgpu/userq: fix double jiffies conversion in hang detect timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Function amdgpu_userq_start_hang_detect_work() calls msecs_to_jiffies() on adev->gfx_timeout/compute_timeout/sdma_timeout before arming hang_detect_work. These timeout values already hold jiffies values from amdgpu_device_get_job_timeout_settings() at device init. This silently shrinks the real hang-detect deadline to (2 * HZ) ms instead of the intended timeout. e.g. 500ms instead of the 2000ms default on a CONFIG_HZ=250 kernel, only coincidentally correct at HZ=1000. The shortened window is easily exceeded by ordinary fence-completion latency, causing hang_detect_work to fire and trigger a per-queue or full GPU reset for queues that are not actually hung. Pass the jiffies value directly to queue_delayed_work() instead of converting it a second time. Fixes: fc3336be9c62 ("drm/amd/amdgpu: Add independent hang detect work for user queue fence") Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 13d44ca033cb74756c2aef0ade54a75cdf2f6271) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 280bdeb389709e..cc8e7af18834cd 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -184,27 +184,27 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) void amdgpu_userq_start_hang_detect_work(struct amdgpu_usermode_queue *queue) { struct amdgpu_device *adev; - unsigned long timeout_ms; + unsigned long timeout_jiffies; adev = queue->userq_mgr->adev; /* Determine timeout based on queue type */ switch (queue->queue_type) { case AMDGPU_RING_TYPE_GFX: - timeout_ms = adev->gfx_timeout; + timeout_jiffies = adev->gfx_timeout; break; case AMDGPU_RING_TYPE_COMPUTE: - timeout_ms = adev->compute_timeout; + timeout_jiffies = adev->compute_timeout; break; case AMDGPU_RING_TYPE_SDMA: - timeout_ms = adev->sdma_timeout; + timeout_jiffies = adev->sdma_timeout; break; default: - timeout_ms = adev->gfx_timeout; + timeout_jiffies = adev->gfx_timeout; break; } queue_delayed_work(adev->reset_domain->wq, &queue->hang_detect_work, - msecs_to_jiffies(timeout_ms)); + timeout_jiffies); } void amdgpu_userq_process_fence_irq(struct amdgpu_device *adev, u32 doorbell) From f952ed353a27b46c86c9525a39b7a642850b8139 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 17 Sep 2026 18:56:45 +0530 Subject: [PATCH 1122/1417] drm/amdgpu/vcn5.0.1: fix video_timeout unit mismatch in jpeg reset wait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vcn_v5_0_1_reset_jpeg_pre_helper() passes adev->video_timeout directly to amdgpu_fence_wait_polling(), whose timeout parameter is documented and implemented in usecs (busy-wait loop decrementing by udelay(2)). adev->video_timeout is set in jiffies by amdgpu_device_get_job_timeout_settings(), via msecs_to_jiffies(). Passing it unconverted means the intended ~2s wait for outstanding JPEG fences to complete before the JPEG queue is torn down actually lasts only a couple of microseconds (HZ jiffies interpreted as usecs), so pending jobs are almost never given a real chance to finish before the reset path forces completion in the following helper. Convert the jiffies value to usecs with jiffies_to_usecs() before passing it to amdgpu_fence_wait_polling(). Fixes: fab47d2db5ca ("drm/amdgpu/vcn5.0.1: rework reset handling") Cc: Jesse.Zhang Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit b8334fec8b90ebffcaa01001a23edca9f29a05e9) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c index 1a07c3bf44253d..011afc0fdc879b 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c @@ -1335,7 +1335,8 @@ static int vcn_v5_0_1_reset_jpeg_pre_helper(struct amdgpu_device *adev, int inst /* if Jobs are still pending after timeout, * We'll handle them in the bottom helper */ - amdgpu_fence_wait_polling(ring, wait_seq, adev->video_timeout); + amdgpu_fence_wait_polling(ring, wait_seq, + jiffies_to_usecs(adev->video_timeout)); } return 0; From 6b13ddbf5bb8deec337f9b4887f579097a953f6a Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 17 Sep 2026 18:56:45 +0530 Subject: [PATCH 1123/1417] drm/amdgpu/vcn4.0.3: fix video_timeout unit mismatch in jpeg reset wait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vcn_v4_0_3_reset_jpeg_pre_helper() passes adev->video_timeout directly to amdgpu_fence_wait_polling(), whose timeout parameter is documented and implemented in usecs (busy-wait loop decrementing by udelay(2)). adev->video_timeout is set in jiffies by amdgpu_device_get_job_timeout_settings(), via msecs_to_jiffies(). Passing it unconverted means the intended ~2s wait for outstanding JPEG fences to complete before the JPEG queue is torn down actually lasts only a couple of microseconds (HZ jiffies interpreted as usecs), so pending jobs are almost never given a real chance to finish before the reset path forces completion in the following helper. Convert the jiffies value to usecs with jiffies_to_usecs() before passing it to amdgpu_fence_wait_polling(). Fixes: d25c67fd9d6f ("drm/amdgpu/vcn4.0.3: rework reset handling") Cc: Jesse.Zhang Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 5feabbd673c10ebee22b880e4d812f08974d2ef7) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c index 179b892fb410eb..62e2e04314dca6 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c @@ -1689,7 +1689,8 @@ static int vcn_v4_0_3_reset_jpeg_pre_helper(struct amdgpu_device *adev, int inst /* if Jobs are still pending after timeout, * We'll handle them in the bottom helper */ - amdgpu_fence_wait_polling(ring, wait_seq, adev->video_timeout); + amdgpu_fence_wait_polling(ring, wait_seq, + jiffies_to_usecs(adev->video_timeout)); } return 0; From c3a31087b1c8df679b653c1a09d9abe5ff7ec8ef Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Fri, 28 Aug 2026 15:59:21 +0800 Subject: [PATCH 1124/1417] drm/amdkfd: fix use-after-free and multi-container gap in kfd_dev_mapping kfd_dev_mapping caches the address_space of the first /dev/kfd opener so that the GPU reset path can call unmap_mapping_range() to zap all userspace mappings of doorbell and MMIO ranges. This design has two bugs that both manifest under SRIOV with multiple containers: 1. Use-after-free / rwsem deadlock. The cached pointer refers to an inode owned by the first opener's container. When that container exits and its inode is released, kfd_dev_mapping becomes a dangling pointer. A subsequent GPU reset dereferences it inside unmap_mapping_range(), which takes i_mmap_rwsem on the freed inode, causing a hard hang observable as an uninterruptible rwsem wait. 2. Multi-container gap. Only the first opener's address_space is cached; VMAs created by later openers live in a different address_space and are never reached by unmap_mapping_range(). After a GPU reset those stale mappings keep doorbell and MMIO pages accessible to guest userspace with no GPU behind them, risking PCIe transaction timeouts and NMI panics. Fix both bugs with the same approach used by DRM core (drm_drv.c): create a private pseudo-filesystem at module init time and allocate one anonymous inode from it. In kfd_open() redirect every opener's file->f_mapping to that inode's address_space. The inode is module-owned, lives exactly as long as the amdgpu module, and collects VMAs from all openers in one address_space. A single unmap_mapping_range() call in the reset path then correctly reaches every container's mappings with no dangling pointer risk. The hang manifests as an NMI backtrace on the GPU reset workqueue stuck spinning in rwsem_down_read_slowpath() with a corrupted i_mmap_rwsem: Workqueue: amdgpu-reset-dev xgpu_ai_mailbox_flr_work [amdgpu] Call Trace: kvm_wait+0x1f/0x40 __pv_queued_spin_lock_slowpath+0x31d/0x3a0 _raw_spin_lock_irq+0x51/0x80 rwsem_down_read_slowpath+0xb3/0x550 down_read+0x48/0xd0 unmap_mapping_range+0x71/0x140 kfd_dev_unmap_mapping_range+0x5b/0x140 [amdgpu] amdgpu_amdkfd_clear_kfd_mapping+0xd8/0x190 [amdgpu] amdgpu_device_gpu_recover+0x232/0x450 [amdgpu] xgpu_ai_mailbox_flr_work+0xb5/0xc0 [amdgpu] process_one_work+0x18e/0x3e0 worker_thread+0x2e3/0x420 kthread+0x10a/0x230 Fixes: 70cadefcc616 ("drm/amdgpu: unmap all user mappings of framebuffer and doorbell before mode1 reset") Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher (cherry picked from commit 1128b4a52de1572e87431de837fd9850cb99542c) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 71 +++++++++++++++++++----- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 504a286368ebd1..344da6c0e96ad5 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -70,18 +71,54 @@ static const struct class kfd_class = { }; /* - * Cache the address space of the chardev on first open so that the reset - * path can drop all userspace mappings of doorbell and MMIO ranges via - * unmap_mapping_range(). + * Private pseudo-filesystem for KFD, Provides a stable, module-owned + * inode whose address_space is the unmap target for all /dev/kfd + * openers during GPU reset. */ -static struct address_space *kfd_dev_mapping; +static struct vfsmount *kfd_fs_mnt; +static int kfd_fs_cnt; -void kfd_dev_unmap_mapping_range(loff_t const holebegin, loff_t const holelen) +static int kfd_fs_init_fs_context(struct fs_context *fc) +{ + return init_pseudo(fc, 0x4b464400 /* "KFD" */) ? 0 : -ENOMEM; +} + +static struct file_system_type kfd_fs_type = { + .name = "kfd", + .init_fs_context = kfd_fs_init_fs_context, + .kill_sb = kill_anon_super, +}; + +static struct inode *kfd_fs_inode_new(void) { - struct address_space *mapping = READ_ONCE(kfd_dev_mapping); + struct inode *inode; + int r; + + r = simple_pin_fs(&kfd_fs_type, &kfd_fs_mnt, &kfd_fs_cnt); + if (r < 0) + return ERR_PTR(r); + + inode = alloc_anon_inode(kfd_fs_mnt->mnt_sb); + if (IS_ERR(inode)) + simple_release_fs(&kfd_fs_mnt, &kfd_fs_cnt); - if (mapping) - unmap_mapping_range(mapping, holebegin, holelen, 1); + return inode; +} + +static void kfd_fs_inode_free(struct inode *inode) +{ + if (inode) { + iput(inode); + simple_release_fs(&kfd_fs_mnt, &kfd_fs_cnt); + } +} + +static struct inode *kfd_anon_inode; + +void kfd_dev_unmap_mapping_range(loff_t const holebegin, loff_t const holelen) +{ + if (kfd_anon_inode) + unmap_mapping_range(kfd_anon_inode->i_mapping, holebegin, holelen, 1); } static inline struct kfd_process_device *kfd_lock_pdd_by_id(struct kfd_process *p, __u32 gpu_id) @@ -107,6 +144,13 @@ int kfd_chardev_init(void) { int err = 0; + kfd_anon_inode = kfd_fs_inode_new(); + if (IS_ERR(kfd_anon_inode)) { + err = PTR_ERR(kfd_anon_inode); + kfd_anon_inode = NULL; + return err; + } + kfd_char_dev_major = register_chrdev(0, kfd_dev_name, &kfd_fops); err = kfd_char_dev_major; if (err < 0) @@ -130,6 +174,8 @@ int kfd_chardev_init(void) err_class_create: unregister_chrdev(kfd_char_dev_major, kfd_dev_name); err_register_chrdev: + kfd_fs_inode_free(kfd_anon_inode); + kfd_anon_inode = NULL; return err; } @@ -138,6 +184,8 @@ void kfd_chardev_exit(void) device_destroy(&kfd_class, MKDEV(kfd_char_dev_major, 0)); class_unregister(&kfd_class); unregister_chrdev(kfd_char_dev_major, kfd_dev_name); + kfd_fs_inode_free(kfd_anon_inode); + kfd_anon_inode = NULL; kfd_device = NULL; } @@ -150,12 +198,7 @@ static int kfd_open(struct inode *inode, struct file *filep) if (iminor(inode) != 0) return -ENODEV; - /* - * /dev/kfd is a single chardev so all opens share one inode. Cache - * its address_space on the first open for use by the reset path. - */ - if (!READ_ONCE(kfd_dev_mapping)) - cmpxchg(&kfd_dev_mapping, NULL, inode->i_mapping); + filep->f_mapping = kfd_anon_inode->i_mapping; is_32bit_user_mode = in_compat_syscall(); From aea841bc62a76242396610d22d8ff40c13065f64 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 10:01:39 +0000 Subject: [PATCH 1125/1417] drm/amdgpu: Fix vmid_wait fence leak in amdgpu_ring_init() amdgpu_ring_init() initializes ring->vmid_wait with a reference to the stub fence taken via dma_fence_get_stub(). When a later step of the initialization fails, e.g. amdgpu_fence_driver_init_ring(), a writeback slot allocation or the ring buffer allocation, the function returns an error without releasing the stub fence reference and the reference is leaked if the ring is torn down without amdgpu_ring_fini(). Move the stub fence assignment to the end of the initialization, right before the ring is registered with the GPU scheduler, where no further failure is possible. The stub fence is only consumed by command submission handling in amdgpu_ids.c once the ring is up and running, so nothing reads it during the error-prone part of the initialization. Fixes: 48e9fbd1a284 ("drm/amdgpu: initialize the vmid_wait with the stub fence") Signed-off-by: Wentao Liang Signed-off-by: Alex Deucher (cherry picked from commit f2b96986851203e9c50ca0d13aaa3581ca3e8ebd) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c index 686c92e9602543..5922406d0a03ac 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c @@ -254,7 +254,6 @@ int amdgpu_ring_init(struct amdgpu_device *adev, struct amdgpu_ring *ring, ring->adev = adev; ring->num_hw_submission = sched_hw_submission; ring->sched_score = sched_score; - ring->vmid_wait = dma_fence_get_stub(); ring->idx = adev->num_rings++; adev->rings[ring->idx] = ring; @@ -374,6 +373,7 @@ int amdgpu_ring_init(struct amdgpu_device *adev, struct amdgpu_ring *ring, ring->max_dw = max_dw; ring->hw_prio = hw_prio; + ring->vmid_wait = dma_fence_get_stub(); if (!ring->no_scheduler && ring->funcs->type < AMDGPU_HW_IP_NUM) { hw_ip = ring->funcs->type; From a997baa61179b450bd55c4810c7ccfed3b753a54 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 09:54:32 +0000 Subject: [PATCH 1126/1417] drm/amdgpu: Fix acpi device leak in amdgpu_acpi_enumerate_xcc() amdgpu_acpi_enumerate_xcc() looks up each XCC ACPI device with acpi_dev_get_first_match_dev(), which takes a reference to the device. The reference is dropped with acpi_dev_put() after the XCC info is initialized, but if the kzalloc_obj() allocation of the XCC info fails the function returns -ENOMEM without releasing the reference, leaking the last reference to the ACPI device. Drop the ACPI device reference on the allocation failure path before returning. Fixes: 4d5275ab0b18 ("drm/amdgpu: Add parsing of acpi xcc objects") Reviewed-by: Lijo Lazar Signed-off-by: Wentao Liang Signed-off-by: Alex Deucher (cherry picked from commit 9211ef48b31ec66999cf55e04d0cbc60cd855fd5) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c index 7f5abb03be1b39..8b8acf98fdfe20 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c @@ -1167,8 +1167,10 @@ int amdgpu_acpi_enumerate_xcc(void) } xcc_info = kzalloc_obj(struct amdgpu_acpi_xcc_info); - if (!xcc_info) + if (!xcc_info) { + acpi_dev_put(acpi_dev); return -ENOMEM; + } INIT_LIST_HEAD(&xcc_info->list); xcc_info->handle = acpi_device_handle(acpi_dev); From b4f7b4459b1b155e4c4977a6482b5df2cf08758c Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 09:55:36 +0000 Subject: [PATCH 1127/1417] drm/amdgpu: Fix last_update fence leak in amdgpu_vm_init() amdgpu_vm_init() initializes vm->last_update, vm->last_unlocked and vm->last_tlb_flush with references to the stub fence taken via dma_fence_get_stub(). The error label at the end of the function releases the last_unlocked and last_tlb_flush references with dma_fence_put(), but the reference stored in vm->last_update is never dropped, so whenever the page table root creation, the reservation of the root BO or the PASID registration fails, the stub fence reference leaks. Drop the vm->last_update reference together with the other stub fence references on the error path. Fixes: 187916e6ed9d ("drm/amdgpu: install stub fence into potential unused fence pointers") Signed-off-by: Wentao Liang Signed-off-by: Alex Deucher (cherry picked from commit e7979c84fc05a176bdf855ee664871b1648404c9) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index bb04101b0fb507..4a63b472f68ec8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -2678,6 +2678,7 @@ int amdgpu_vm_init(struct amdgpu_device *adev, struct amdgpu_vm *vm, amdgpu_bo_unref(&root_bo); error_free_delayed: + dma_fence_put(vm->last_update); dma_fence_put(vm->last_tlb_flush); dma_fence_put(vm->last_unlocked); ttm_lru_bulk_move_fini(&adev->mman.bdev, &vm->lru_bulk_move); From 2b86ab1bd6673c525adda88819d7658ba9e784ec Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 09:58:05 +0000 Subject: [PATCH 1128/1417] drm/amdgpu: Fix runtime PM leak in amdgpu_debugfs_test_ib_show() amdgpu_debugfs_test_ib_show() resumes the device with pm_runtime_get_sync() before taking the reset domain semaphore with down_write_killable(). If the write lock acquisition is interrupted, the function returns without calling pm_runtime_put_autosuspend(), leaking the runtime PM reference acquired for the device and keeping the GPU awake. Drop the runtime PM reference on the interrupted down_write_killable() error path before returning. Fixes: 6049db43d6dd ("drm/amdgpu: change reset lock from mutex to rw_semaphore") Signed-off-by: Wentao Liang Signed-off-by: Alex Deucher (cherry picked from commit ec30a576c2d4c0364549e6c04218f50704ef56c8) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index 132d054900b5b4..aca1a8045afaba 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -1780,8 +1780,10 @@ static int amdgpu_debugfs_test_ib_show(struct seq_file *m, void *unused) /* Avoid accidently unparking the sched thread during GPU reset */ r = down_write_killable(&adev->reset_domain->sem); - if (r) + if (r) { + pm_runtime_put_autosuspend(dev->dev); return r; + } /* hold on the scheduler */ for (i = 0; i < AMDGPU_MAX_RINGS; i++) { From 0fd5e9ddf362b1253b3c94b858371f90400e5c4a Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 23 Sep 2026 11:31:18 -0600 Subject: [PATCH 1129/1417] drm/amd/display: Relax DML frame limit with UBSAN [WHY] UBSAN instrumentation adds checks and handler calls and increases stack usage in the large DML calculation functions, similar to KASAN and KCSAN. With UBSAN enabled these files exceed the default -Wframe-larger-than limit and fail to build when -Werror is in effect. Reproduced with LLVM (make LLVM=1, clang 19.1.1), CONFIG_UBSAN=y, CONFIG_GCOV_PROFILE_ALL=y and CONFIG_DRM_AMDGPU_WERROR=y on x86_64. [HOW] Include CONFIG_UBSAN in the sanitizer check that selects the higher per-file frame warning limit in the dml and dml2_0 Makefiles. Suggested-by: Leo Li Assisted-by: Copilot:Claude-Opus-5.5 Signed-off-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit ebf8b0fd8508b744f85a8eee82b745b1d3502dd0) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/dml/Makefile | 2 +- drivers/gpu/drm/amd/display/dc/dml2_0/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml/Makefile b/drivers/gpu/drm/amd/display/dc/dml/Makefile index 10d4ace04d4f19..096520bd27c64a 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml/Makefile @@ -29,7 +29,7 @@ dml_ccflags := $(CC_FLAGS_FPU) dml_rcflags := $(CC_FLAGS_NO_FPU) ifneq ($(CONFIG_FRAME_WARN),0) - ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)),y) + ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)$(CONFIG_UBSAN)),y) ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_COMPILE_TEST),yy) frame_warn_limit := 4096 else diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile index 5388bf094fbcd8..4d682e92df1d3c 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile @@ -28,7 +28,7 @@ dml2_ccflags := $(CC_FLAGS_FPU) dml2_rcflags := $(CC_FLAGS_NO_FPU) ifneq ($(CONFIG_FRAME_WARN),0) - ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)),y) + ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)$(CONFIG_UBSAN)),y) ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_COMPILE_TEST),yy) frame_warn_limit := 4096 else From 18779dd84515db093fedb4ebaf0998c9b165a5fb Mon Sep 17 00:00:00 2001 From: Ivan Lipski Date: Fri, 21 Aug 2026 00:04:53 -0400 Subject: [PATCH 1130/1417] drm/amd/display: Bump frame warning limit for clang builds of dml [Why&How] When building the DML files with clang without any sanitizer or LTO, the following -Wframe-larger-than errors break the build under CONFIG_WERROR: display_mode_vba_30.c: error: stack frame size (2512) exceeds limit (2048) in 'dml30_ModeSupportAndSystemConfigurationFull' display_mode_vba_31.c: error: stack frame size (2416) exceeds limit (2048) in 'dml31_ModeSupportAndSystemConfigurationFull' display_mode_vba_314.c: error: stack frame size (2392) exceeds limit (2048) in 'dml314_ModeSupportAndSystemConfigurationFull' Clang consistently spills more than gcc, pushing the frame past the 2048 byte limit. Apply an existing approach of increasing the warn stack size to the non-sanitizer path so plain clang builds use a 3072 byte limit. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5642 Signed-off-by: Ivan Lipski Signed-off-by: Alex Deucher (cherry picked from commit 21711b6e66bb7b41b1aec67b2d99aafe768c8fcb) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/dml/Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml/Makefile b/drivers/gpu/drm/amd/display/dc/dml/Makefile index 096520bd27c64a..79eeb3721b9967 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml/Makefile @@ -36,7 +36,11 @@ ifneq ($(CONFIG_FRAME_WARN),0) frame_warn_limit := 3072 endif else - frame_warn_limit := 2048 + ifeq ($(CONFIG_CC_IS_CLANG),y) + frame_warn_limit := 3072 + else + frame_warn_limit := 2048 + endif endif ifeq ($(call test-lt, $(CONFIG_FRAME_WARN), $(frame_warn_limit)),y) From 686f942332b1667f13f3b8d6a2f50bcfbf42e277 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 15 Jul 2026 16:43:25 +0800 Subject: [PATCH 1131/1417] nfc: nfcmrvl: validate helper command length before pull The firmware download receive path removes the NCI data header and reads the helper command before validating the remaining packet length. A short frame can therefore reach the data access before the malformed packet is rejected. Validate the complete helper command length before stripping the NCI data header. Fixes: 3194c6870158 ("NFC: nfcmrvl: add firmware download support") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260715084325.40276-1-pengpeng@iscas.ac.cn Signed-off-by: David Heidelberg --- drivers/nfc/nfcmrvl/fw_dnld.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/nfc/nfcmrvl/fw_dnld.c b/drivers/nfc/nfcmrvl/fw_dnld.c index 2b8f401d8fd7a6..8b9d5257320dcc 100644 --- a/drivers/nfc/nfcmrvl/fw_dnld.c +++ b/drivers/nfc/nfcmrvl/fw_dnld.c @@ -263,9 +263,14 @@ static int process_state_fw_dnld(struct nfcmrvl_private *priv, * B8..N: payload */ - /* Remove NCI HDR */ - skb_pull(skb, 3); - if (skb->data[0] != HELPER_CMD_PACKET_FORMAT || skb->len != 5) { + if (skb->len != NCI_DATA_HDR_SIZE + 5) { + nfc_err(priv->dev, "bad command"); + return -EINVAL; + } + + /* Remove NCI header */ + skb_pull(skb, NCI_DATA_HDR_SIZE); + if (skb->data[0] != HELPER_CMD_PACKET_FORMAT) { nfc_err(priv->dev, "bad command"); return -EINVAL; } From a653c01ce447f10c36b901646888c0330363af4f Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 15 Jul 2026 16:44:05 +0800 Subject: [PATCH 1132/1417] nfc: st21nfca: validate received frame size st21nfca_hci_i2c_repack() trims a received frame at its EOF marker before removing byte stuffing. It then assumes the truncated frame contains the LLC header and two CRC bytes, and it unconditionally reads the byte after an escape marker. A malformed frame can place EOF immediately after the start marker or can end its data portion with an escape marker. The former leaves too few bytes for check_crc(), while the latter makes the unstuffing loop read past the current skb length. Require the minimum framing bytes both before and after unstuffing. Use separate input and output cursors while removing byte stuffing, and reject an escape marker without its encoded byte. This keeps malformed frames within the received frame boundary before CRC processing. Fixes: 3096e25a3e40 ("NFC: st21nfca: Fix incorrect byte stuffing revocation") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260715084405.41546-1-pengpeng@iscas.ac.cn Signed-off-by: David Heidelberg --- drivers/nfc/st21nfca/i2c.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/drivers/nfc/st21nfca/i2c.c b/drivers/nfc/st21nfca/i2c.c index a4c93ff7c5b0c2..0f44c783bd0417 100644 --- a/drivers/nfc/st21nfca/i2c.c +++ b/drivers/nfc/st21nfca/i2c.c @@ -289,27 +289,36 @@ static int check_crc(u8 *buf, int buflen) */ static int st21nfca_hci_i2c_repack(struct sk_buff *skb) { - int i, j, r, size; + int read, write, r, size; - if (skb->len < 1 || (skb->len > 1 && skb->data[1] != 0)) + if (skb->len < ST21NFCA_FRAME_HEADROOM || + !IS_START_OF_FRAME(skb->data)) return -EBADMSG; size = get_frame_size(skb->data, skb->len); if (size > 0) { + if (size < ST21NFCA_FRAME_HEADROOM + 2) + return -EBADMSG; + skb_trim(skb, size); /* remove ST21NFCA byte stuffing for upper layer */ - for (i = 1, j = 0; i < skb->len; i++) { - if (skb->data[i + j] == + for (read = 1, write = 1; read < skb->len;) { + if (skb->data[read] == (u8) ST21NFCA_ESCAPE_BYTE_STUFFING) { - skb->data[i] = skb->data[i + j + 1] - | ST21NFCA_BYTE_STUFFING_MASK; - i++; - j++; + if (read + 1 == skb->len) + return -EBADMSG; + + skb->data[write++] = skb->data[read + 1] + | ST21NFCA_BYTE_STUFFING_MASK; + read += 2; + } else { + skb->data[write++] = skb->data[read++]; } - skb->data[i] = skb->data[i + j]; } /* remove byte stuffing useless byte */ - skb_trim(skb, i - j); + skb_trim(skb, write); + if (skb->len < ST21NFCA_FRAME_HEADROOM + 2) + return -EBADMSG; /* remove ST21NFCA_SOF_EOF from head */ skb_pull(skb, 1); From bf1460acdf8cf5a07c819f59785d40f20d113099 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Thu, 16 Jul 2026 20:26:57 -0300 Subject: [PATCH 1133/1417] nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm() nfc_llcp_recv_dm() handles DM(NOBOUND)/DM(REJ) for a socket that is still linked on local->connecting_sockets: it looks the socket up with nfc_llcp_connecting_sock_get(), sets sk->sk_state = LLCP_CLOSED and returns, without taking the socket lock and without unlinking the socket from the connecting_sockets list. llcp_sock_release() selects the list to unlink from by sk_state: a socket in LLCP_CONNECTING is unlinked from connecting_sockets, otherwise from the sockets list. Because recv_dm left the socket physically on connecting_sockets but in the LLCP_CLOSED state, release() takes the else branch and calls nfc_llcp_sock_unlink(&local->sockets, sk). That runs sk_del_node_init() while holding sockets.lock, i.e. it removes the socket from the connecting_sockets hlist under the wrong lock. A concurrent connect() linking another socket onto connecting_sockets under connecting_sockets.lock then mutates the same hlist unserialized, which corrupts the list and desyncs the sk_add_node()/sk_del_node_init() sock_hold()/__sock_put() pairing. An unprivileged local process holding LLCP sockets, with the DM supplied by the remote peer over an established LLCP link, can drive this to leak kernel sockets without bound (the mis-decrement goes through the non-freeing __sock_put() path, so the object is never released), leading to memory exhaustion / DoS. This is the same class of bug that was fixed in the sibling handler nfc_llcp_recv_cc() by commit b493ea2765cc ("nfc: llcp: Fix use-after-free race in nfc_llcp_recv_cc()"); recv_dm did not receive the equivalent fix. Fix it the same way: take lock_sock(), re-check that the socket is still hashed (release() may have won the race), and for the NOBOUND/REJ case unlink it from connecting_sockets before moving it to LLCP_CLOSED. The unlink drops the connecting_sockets membership reference via sk_del_node_init(), leaving the socket unhashed, so the later nfc_llcp_sock_unlink() in llcp_sock_release() becomes a no-op and no double put occurs. Fixes: a69f32af86e3 ("NFC: Socket linked list") Signed-off-by: Aldo Ariel Panzardo Link: https://patch.msgid.link/20260716232657.203145-1-qwe.aldo@gmail.com Signed-off-by: David Heidelberg --- net/nfc/llcp_core.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index cac1b5487064d0..bd6361e2efa4f1 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -1251,6 +1251,7 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, struct nfc_llcp_sock *llcp_sock; struct sock *sk; u8 dsap, ssap, reason; + bool connecting = false; dsap = nfc_llcp_dsap(skb); ssap = nfc_llcp_ssap(skb); @@ -1262,6 +1263,7 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, case LLCP_DM_NOBOUND: case LLCP_DM_REJ: llcp_sock = nfc_llcp_connecting_sock_get(local, dsap); + connecting = true; break; default: @@ -1276,10 +1278,33 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, sk = &llcp_sock->sk; + lock_sock(sk); + + /* Check if socket was destroyed whilst waiting for the lock */ + if (!sk_hashed(sk)) { + release_sock(sk); + nfc_llcp_sock_put(llcp_sock); + return; + } + + /* + * For DM(NOBOUND)/DM(REJ) the socket is still linked on the + * connecting_sockets list. Unlink it here, under the socket lock, + * before moving it to LLCP_CLOSED: llcp_sock_release() selects the + * list to unlink from by sk_state, so leaving a connecting socket + * in the CLOSED state would make it unlink from the wrong list and + * corrupt the connecting_sockets list / desync the socket refcount. + * This mirrors nfc_llcp_recv_cc(). + */ + if (connecting) + nfc_llcp_sock_unlink(&local->connecting_sockets, sk); + sk->sk_err = ENXIO; sk->sk_state = LLCP_CLOSED; sk->sk_state_change(sk); + release_sock(sk); + nfc_llcp_sock_put(llcp_sock); } From 092c6a605cbd6414ef499834c2e0da69c2c3388e Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Sat, 11 Jul 2026 14:36:51 +0200 Subject: [PATCH 1134/1417] nfc: port100: reject frames whose declared length exceeds the received data port100_recv_response() passes the URB transfer buffer to port100_rx_frame_is_valid(), which checksums le16_to_cpu(frame->datalen) bytes of frame->data. datalen is a 16-bit field supplied by the device and is never checked against the number of bytes actually received (urb->actual_length), so a device reporting a datalen larger than the received frame makes port100_data_checksum() read out of bounds past the transfer buffer. Reject a response whose declared frame size does not fit the received length before validating it. Found by 0sec (https://0sec.ai) using automated source analysis; the missing bound is evident from source. Compile-tested. Fixes: 562d4d59b8a1 ("NFC: Sony Port-100 Series driver") Cc: stable@vger.kernel.org Assisted-by: 0sec:claude-opus-4-8 Signed-off-by: Doruk Tan Ozturk Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260711123651.32595-1-doruk@0sec.ai Signed-off-by: David Heidelberg --- drivers/nfc/port100.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/nfc/port100.c b/drivers/nfc/port100.c index b613f5e2fd57a4..e769a30b8b5c7e 100644 --- a/drivers/nfc/port100.c +++ b/drivers/nfc/port100.c @@ -636,6 +636,13 @@ static void port100_recv_response(struct urb *urb) in_frame = dev->in_urb->transfer_buffer; + if (urb->actual_length < PORT100_FRAME_HEADER_LEN || + urb->actual_length < port100_rx_frame_size(in_frame)) { + nfc_err(&dev->interface->dev, "Received a truncated frame\n"); + cmd->status = -EIO; + goto sched_wq; + } + if (!port100_rx_frame_is_valid(in_frame)) { nfc_err(&dev->interface->dev, "Received an invalid frame\n"); cmd->status = -EIO; From 3d8afc5243ea2ee803d98e69eb4a01748167ac1b Mon Sep 17 00:00:00 2001 From: Lei Zhu Date: Wed, 29 Jul 2026 15:24:26 +0800 Subject: [PATCH 1135/1417] selftests: nci: Correct pthread_create return value check The pthread_create() functions returns 0 on success and a positive value on failure. Modify the return value check to correctly detect failure cases. Fixes: 72696bd8a09d ("selftests: nci: Extract the start/stop discovery function") Signed-off-by: Lei Zhu Link: https://patch.msgid.link/20260729072426.303484-1-zhulei_szu@163.com Signed-off-by: David Heidelberg --- tools/testing/selftests/nci/nci_dev.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/nci/nci_dev.c b/tools/testing/selftests/nci/nci_dev.c index 312f84ee0444fd..c053f5cf2745d7 100644 --- a/tools/testing/selftests/nci/nci_dev.c +++ b/tools/testing/selftests/nci/nci_dev.c @@ -438,7 +438,7 @@ FIXTURE_SETUP(NCI) else rc = pthread_create(&thread_t, NULL, virtual_dev_open, (void *)&self->virtual_nci_fd); - ASSERT_GT(rc, -1); + ASSERT_EQ(rc, 0); rc = send_cmd_with_idx(self->sd, self->fid, self->pid, NFC_CMD_DEV_UP, self->dev_idex); @@ -509,7 +509,7 @@ FIXTURE_TEARDOWN(NCI) rc = pthread_create(&thread_t, NULL, virtual_deinit, (void *)&self->virtual_nci_fd); - ASSERT_GT(rc, -1); + ASSERT_EQ(rc, 0); rc = send_cmd_with_idx(self->sd, self->fid, self->pid, NFC_CMD_DEV_DOWN, self->dev_idex); EXPECT_EQ(rc, 0); @@ -590,7 +590,7 @@ int start_polling(int dev_idx, int proto, int virtual_fd, int sd, int fid, int p rc = pthread_create(&thread_t, NULL, virtual_poll_start, (void *)&virtual_fd); - if (rc < 0) + if (rc) return rc; rc = send_cmd_mt_nla(sd, fid, pid, NFC_CMD_START_POLL, 2, nla_start_poll_type, @@ -610,7 +610,7 @@ int stop_polling(int dev_idx, int virtual_fd, int sd, int fid, int pid) rc = pthread_create(&thread_t, NULL, virtual_poll_stop, (void *)&virtual_fd); - if (rc < 0) + if (rc) return rc; rc = send_cmd_with_idx(sd, fid, pid, @@ -830,6 +830,8 @@ int disconnect_tag(int nfc_sock, int virtual_fd) status = pthread_create(&thread_t, NULL, virtual_deactivate_proc, (void *)&virtual_fd); + if (status) + return status; close(nfc_sock); pthread_join(thread_t, (void **)&status); @@ -874,7 +876,7 @@ TEST_F(NCI, deinit) else rc = pthread_create(&thread_t, NULL, virtual_deinit, (void *)&self->virtual_nci_fd); - ASSERT_GT(rc, -1); + ASSERT_EQ(rc, 0); rc = send_cmd_with_idx(self->sd, self->fid, self->pid, NFC_CMD_DEV_DOWN, self->dev_idex); From c3eef2f988a3db9690369d7cef9a3344dd9788d3 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 2 Sep 2026 12:30:31 +0000 Subject: [PATCH 1136/1417] nfc: llcp: Fix race condition in accept_queue lifecycle In nfc_llcp_socket_release(), sockets and listener accept queues are walked under the local sockets rwlock and bh_lock_sock(). However, bh_lock_sock() does not synchronise against process-context lock_sock() held by nfc_llcp_accept_dequeue() during accept(). Because socket_release() does not check sock_owned_by_user(), both paths can concurrently unlink and release the same child socket, resulting in use-after-free or a NULL pointer dereference of child->parent in nfc_llcp_accept_unlink(). Fix this synchronisation race by having nfc_llcp_socket_release() use process-context lock_sock() instead of bh_lock_sock(): 1. Pop sockets from the local sockets list under the write lock using nfc_llcp_sock_list_pop() so lock_sock() can be acquired without holding the rwlock. 2. Because lock_sock() can sleep, defer the final release of the nfc_llcp_local structure to a workqueue (release_work). This avoids a sleeping-in-atomic bug when the last local reference is dropped from softirq context. Additionally, hold a single device reference on local from registration until final destruction. 3. In nfc_llcp_local_get(), use kref_get_unless_zero() to prevent resurrecting a local object whose teardown has been scheduled. 4. In llcp_sock_accept(), verify that the listener socket state is still LLCP_LISTEN after waking from schedule_timeout() to prevent hangs if the listener is closed concurrently. 5. When unlinking unaccepted child sockets during listener release, unlink them from local->sockets, call sock_orphan(), and drop their initial sk_alloc creation reference via sock_put(). 6. Make nfc_llcp_accept_unlink() idempotent by guarding parent access with a NULL check. Fixes: 50b78b2a6500 ("NFC: Fix sleeping in atomic when releasing socket") Signed-off-by: Lee Jones Link: https://patch.msgid.link/20260902123033.1169067-1-lee@kernel.org Signed-off-by: David Heidelberg --- net/nfc/llcp.h | 1 + net/nfc/llcp_core.c | 125 +++++++++++++++++++++++++++----------------- net/nfc/llcp_sock.c | 49 ++++++++++++----- 3 files changed, 116 insertions(+), 59 deletions(-) diff --git a/net/nfc/llcp.h b/net/nfc/llcp.h index d8345ed57c9542..23ae7a0112d37d 100644 --- a/net/nfc/llcp.h +++ b/net/nfc/llcp.h @@ -91,6 +91,7 @@ struct nfc_llcp_local { struct hlist_head pending_sdreqs; struct timer_list sdreq_timer; struct work_struct sdreq_timeout_work; + struct work_struct release_work; u8 sdreq_next_tid; /* sockets array */ diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index bd6361e2efa4f1..23553e7426ec2b 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -20,6 +20,8 @@ static LIST_HEAD(llcp_devices); /* Protects llcp_devices list */ static DEFINE_SPINLOCK(llcp_devices_lock); +static struct workqueue_struct *llcp_wq; + static void nfc_llcp_rx_skb(struct nfc_llcp_local *local, struct sk_buff *skb); void nfc_llcp_sock_link(struct llcp_sock_list *l, struct sock *sk) @@ -63,21 +65,33 @@ static void nfc_llcp_socket_purge(struct nfc_llcp_sock *sock) } } +static struct sock *nfc_llcp_sock_list_pop(struct llcp_sock_list *l) +{ + struct sock *sk; + + write_lock(&l->lock); + sk = sk_head(&l->head); + if (sk) { + sock_hold(sk); + sk_del_node_init(sk); + } + write_unlock(&l->lock); + + return sk; +} + static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, int err) { struct sock *sk; - struct hlist_node *tmp; struct nfc_llcp_sock *llcp_sock; skb_queue_purge(&local->tx_queue); - write_lock(&local->sockets.lock); - - sk_for_each_safe(sk, tmp, &local->sockets.head) { + while ((sk = nfc_llcp_sock_list_pop(&local->sockets))) { llcp_sock = nfc_llcp_sock(sk); - bh_lock_sock(sk); + lock_sock(sk); nfc_llcp_socket_purge(llcp_sock); @@ -91,17 +105,27 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, list_for_each_entry_safe(lsk, n, &llcp_sock->accept_queue, accept_queue) { - accept_sk = &lsk->sk; - bh_lock_sock(accept_sk); - - nfc_llcp_accept_unlink(accept_sk); + bool put_creation = false; - if (err) - accept_sk->sk_err = err; - accept_sk->sk_state = LLCP_CLOSED; - accept_sk->sk_state_change(sk); + accept_sk = &lsk->sk; + lock_sock_nested(accept_sk, + SINGLE_DEPTH_NESTING); + + if (nfc_llcp_sock(accept_sk)->parent == sk) { + nfc_llcp_accept_unlink(accept_sk); + nfc_llcp_sock_unlink(&local->sockets, accept_sk); + + if (err) + accept_sk->sk_err = err; + accept_sk->sk_state = LLCP_CLOSED; + accept_sk->sk_state_change(accept_sk); + sock_orphan(accept_sk); + put_creation = true; + } - bh_unlock_sock(accept_sk); + release_sock(accept_sk); + if (put_creation) + sock_put(accept_sk); /* creation ref */ } } @@ -110,23 +134,18 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, sk->sk_state = LLCP_CLOSED; sk->sk_state_change(sk); - bh_unlock_sock(sk); - - sk_del_node_init(sk); + release_sock(sk); + sock_put(sk); } - write_unlock(&local->sockets.lock); - /* If we still have a device, we keep the RAW sockets alive */ if (device == true) return; - write_lock(&local->raw_sockets.lock); - - sk_for_each_safe(sk, tmp, &local->raw_sockets.head) { + while ((sk = nfc_llcp_sock_list_pop(&local->raw_sockets))) { llcp_sock = nfc_llcp_sock(sk); - bh_lock_sock(sk); + lock_sock(sk); nfc_llcp_socket_purge(llcp_sock); @@ -135,26 +154,20 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, sk->sk_state = LLCP_CLOSED; sk->sk_state_change(sk); - bh_unlock_sock(sk); - - sk_del_node_init(sk); + release_sock(sk); + sock_put(sk); } - - write_unlock(&local->raw_sockets.lock); } static struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local) { - /* Since using nfc_llcp_local may result in usage of nfc_dev, whenever - * we hold a reference to local, we also need to hold a reference to - * the device to avoid UAF. - */ - if (!nfc_get_device(local->dev->idx)) + if (!local) return NULL; - kref_get(&local->ref); + if (kref_get_unless_zero(&local->ref)) + return local; - return local; + return NULL; } static void local_cleanup(struct nfc_llcp_local *local) @@ -172,30 +185,34 @@ static void local_cleanup(struct nfc_llcp_local *local) nfc_llcp_free_sdp_tlv_list(&local->pending_sdreqs); } -static void local_release(struct kref *ref) +static void local_release_work(struct work_struct *work) { struct nfc_llcp_local *local; + struct nfc_dev *dev; - local = container_of(ref, struct nfc_llcp_local, ref); + local = container_of(work, struct nfc_llcp_local, release_work); + dev = local->dev; local_cleanup(local); kfree(local); + nfc_put_device(dev); } -int nfc_llcp_local_put(struct nfc_llcp_local *local) +static void local_release(struct kref *ref) { - struct nfc_dev *dev; - int ret; + struct nfc_llcp_local *local; - if (local == NULL) - return 0; + local = container_of(ref, struct nfc_llcp_local, ref); - dev = local->dev; + queue_work(llcp_wq, &local->release_work); +} - ret = kref_put(&local->ref, local_release); - nfc_put_device(dev); +int nfc_llcp_local_put(struct nfc_llcp_local *local) +{ + if (!local) + return 0; - return ret; + return kref_put(&local->ref, local_release); } static struct nfc_llcp_sock *nfc_llcp_sock_get(struct nfc_llcp_local *local, @@ -1705,6 +1722,7 @@ int nfc_llcp_register_device(struct nfc_dev *ndev) INIT_WORK(&local->rx_work, nfc_llcp_rx_work); INIT_WORK(&local->timeout_work, nfc_llcp_timeout_work); + INIT_WORK(&local->release_work, local_release_work); rwlock_init(&local->sockets.lock); rwlock_init(&local->connecting_sockets.lock); @@ -1748,10 +1766,23 @@ void nfc_llcp_unregister_device(struct nfc_dev *dev) int __init nfc_llcp_init(void) { - return nfc_llcp_sock_init(); + int ret; + + llcp_wq = alloc_workqueue("nfc_llcp_wq", WQ_UNBOUND, 0); + if (!llcp_wq) + return -ENOMEM; + + ret = nfc_llcp_sock_init(); + if (ret) { + destroy_workqueue(llcp_wq); + return ret; + } + + return 0; } void nfc_llcp_exit(void) { nfc_llcp_sock_exit(); + destroy_workqueue(llcp_wq); } diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c index 5558d8a4d48b36..ce6875eb58fbc9 100644 --- a/net/nfc/llcp_sock.c +++ b/net/nfc/llcp_sock.c @@ -392,11 +392,12 @@ void nfc_llcp_accept_unlink(struct sock *sk) pr_debug("state %d\n", sk->sk_state); - list_del_init(&llcp_sock->accept_queue); - sk_acceptq_removed(llcp_sock->parent); - llcp_sock->parent = NULL; - - sock_put(sk); + if (llcp_sock->parent) { + list_del_init(&llcp_sock->accept_queue); + sk_acceptq_removed(llcp_sock->parent); + llcp_sock->parent = NULL; + sock_put(sk); + } } void nfc_llcp_accept_enqueue(struct sock *parent, struct sock *sk) @@ -423,12 +424,20 @@ struct sock *nfc_llcp_accept_dequeue(struct sock *parent, list_for_each_entry_safe(lsk, n, &llcp_parent->accept_queue, accept_queue) { + struct nfc_llcp_local *local; + sk = &lsk->sk; - lock_sock(sk); + lock_sock_nested(sk, SINGLE_DEPTH_NESTING); if (sk->sk_state == LLCP_CLOSED) { - release_sock(sk); + local = nfc_llcp_sock(sk)->local; + nfc_llcp_accept_unlink(sk); + if (local) + nfc_llcp_sock_unlink(&local->sockets, sk); + sock_orphan(sk); + release_sock(sk); + sock_put(sk); continue; } @@ -464,7 +473,7 @@ static int llcp_sock_accept(struct socket *sock, struct socket *newsock, pr_debug("parent %p\n", sk); - lock_sock_nested(sk, SINGLE_DEPTH_NESTING); + lock_sock(sk); if (sk->sk_state != LLCP_LISTEN) { ret = -EBADFD; @@ -490,7 +499,12 @@ static int llcp_sock_accept(struct socket *sock, struct socket *newsock, release_sock(sk); timeo = schedule_timeout(timeo); - lock_sock_nested(sk, SINGLE_DEPTH_NESTING); + lock_sock(sk); + + if (sk->sk_state != LLCP_LISTEN) { + ret = -EBADFD; + break; + } } __set_current_state(TASK_RUNNING); remove_wait_queue(sk_sleep(sk), &wait); @@ -629,13 +643,24 @@ static int llcp_sock_release(struct socket *sock) list_for_each_entry_safe(lsk, n, &llcp_sock->accept_queue, accept_queue) { + bool put_creation = false; + accept_sk = &lsk->sk; - lock_sock(accept_sk); + lock_sock_nested(accept_sk, SINGLE_DEPTH_NESTING); - nfc_llcp_send_disconnect(lsk); - nfc_llcp_accept_unlink(accept_sk); + if (nfc_llcp_sock(accept_sk)->parent == sk) { + nfc_llcp_send_disconnect(lsk); + nfc_llcp_accept_unlink(accept_sk); + nfc_llcp_sock_unlink(&local->sockets, accept_sk); + + accept_sk->sk_state = LLCP_CLOSED; + sock_orphan(accept_sk); + put_creation = true; + } release_sock(accept_sk); + if (put_creation) + sock_put(accept_sk); /* creation ref */ } } From eda518d2cdb6074a0bcdfa06af291616bcb5c421 Mon Sep 17 00:00:00 2001 From: Chaithanya Lagisetty Date: Tue, 1 Sep 2026 07:06:18 +0000 Subject: [PATCH 1137/1417] selftests: nci: Fix uninitialized family ID on missing attribute get_family_id() walks the generic netlink CTRL_CMD_GETFAMILY reply looking for the CTRL_ATTR_FAMILY_ID attribute and returns the parsed value in the local variable "id". If the reply does not carry that attribute, the parsing loop never assigns "id" and the function returns an indeterminate stack value, which the caller stores in self->fid and uses for subsequent netlink requests. Initialize "id" to 0 so a missing attribute yields a deterministic (invalid) family ID instead of a garbage value. Fixes: f595cf1242f3 ("selftests: Add nci suite") Signed-off-by: Chaithanya Lagisetty Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260901070618.3299012-1-nagachaithanya9911@gmail.com Signed-off-by: David Heidelberg --- tools/testing/selftests/nci/nci_dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/nci/nci_dev.c b/tools/testing/selftests/nci/nci_dev.c index c053f5cf2745d7..23fd38acfcf42a 100644 --- a/tools/testing/selftests/nci/nci_dev.c +++ b/tools/testing/selftests/nci/nci_dev.c @@ -182,7 +182,7 @@ static int get_family_id(int sd, __u32 pid, __u32 *event_group) } ans; struct nlattr *na; int resp_len; - __u16 id; + __u16 id = 0; int len; int rc; From 6be581aeffc215bfc77939cd59902b0dbc4af23e Mon Sep 17 00:00:00 2001 From: Chris Gellermann Date: Fri, 4 Sep 2026 11:59:15 +0200 Subject: [PATCH 1138/1417] selftests/nci: Fix out-of-bounds store on thread join The NCI test collects the exit status of its helper threads by passing the address of an int to pthread_join(): int status; ... pthread_join(thread_t, (void **) &status); pthread_join() stores a void pointer to the memory location. On 64-bit systems, a void pointer is wider than an int, so the store overruns the 4 bytes of space allocated on the stack for the integer and corrupts the adjacent stack. On our CHERI system, this caused a fault due to a capability bounds violation. Fix this by introducing a helper that joins a thread through a void pointer and converts the result back to an integer, which is what the helper threads return. While here, also fix the logic in disconnect_tag() if the helper thread creation failed. Previously, it would have joined a thread that was never created when pthread_create() failed. Fixes: f595cf1242f3 ("selftests: Add nci suite") Signed-off-by: Chris Gellermann Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260904095915.3372241-1-christian.gellermann@codasip.com Signed-off-by: David Heidelberg --- tools/testing/selftests/nci/nci_dev.c | 31 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/tools/testing/selftests/nci/nci_dev.c b/tools/testing/selftests/nci/nci_dev.c index 23fd38acfcf42a..07427fa42888de 100644 --- a/tools/testing/selftests/nci/nci_dev.c +++ b/tools/testing/selftests/nci/nci_dev.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -87,6 +88,16 @@ struct msgtemplate { char buf[MAX_MSG_SIZE]; }; +static int join_thread_status(pthread_t thread) +{ + void *thread_ret = NULL; + + if (pthread_join(thread, &thread_ret)) + return -1; + + return (int)(intptr_t)thread_ret; +} + static int create_nl_socket(void) { int fd; @@ -444,7 +455,7 @@ FIXTURE_SETUP(NCI) NFC_CMD_DEV_UP, self->dev_idex); EXPECT_EQ(rc, 0); - pthread_join(thread_t, (void **)&status); + status = join_thread_status(thread_t); ASSERT_EQ(status, 0); self->open_state = true; } @@ -514,7 +525,7 @@ FIXTURE_TEARDOWN(NCI) NFC_CMD_DEV_DOWN, self->dev_idex); EXPECT_EQ(rc, 0); - pthread_join(thread_t, (void **)&status); + status = join_thread_status(thread_t); ASSERT_EQ(status, 0); } @@ -585,7 +596,6 @@ int start_polling(int dev_idx, int proto, int virtual_fd, int sd, int fid, int p void *nla_start_poll_data[2] = {&dev_idx, &proto}; int nla_start_poll_len[2] = {4, 4}; pthread_t thread_t; - int status; int rc; rc = pthread_create(&thread_t, NULL, virtual_poll_start, @@ -598,14 +608,12 @@ int start_polling(int dev_idx, int proto, int virtual_fd, int sd, int fid, int p if (rc != 0) return rc; - pthread_join(thread_t, (void **)&status); - return status; + return join_thread_status(thread_t); } int stop_polling(int dev_idx, int virtual_fd, int sd, int fid, int pid) { pthread_t thread_t; - int status; int rc; rc = pthread_create(&thread_t, NULL, virtual_poll_stop, @@ -618,8 +626,7 @@ int stop_polling(int dev_idx, int virtual_fd, int sd, int fid, int pid) if (rc != 0) return rc; - pthread_join(thread_t, (void **)&status); - return status; + return join_thread_status(thread_t); } TEST_F(NCI, start_poll) @@ -834,8 +841,10 @@ int disconnect_tag(int nfc_sock, int virtual_fd) return status; close(nfc_sock); - pthread_join(thread_t, (void **)&status); - return status; + if (status) + return -1; + + return join_thread_status(thread_t); } TEST_F(NCI, t4t_tag_read) @@ -882,7 +891,7 @@ TEST_F(NCI, deinit) NFC_CMD_DEV_DOWN, self->dev_idex); EXPECT_EQ(rc, 0); - pthread_join(thread_t, (void **)&status); + status = join_thread_status(thread_t); self->open_state = 0; ASSERT_EQ(status, 0); From 51814683e28fc64eceb415962376956c3cfc75a7 Mon Sep 17 00:00:00 2001 From: Chris Gellermann Date: Fri, 4 Sep 2026 18:42:52 +0200 Subject: [PATCH 1139/1417] nfc: virtual_ncidev: Add missing ioctl compat handler The compat handler for ioctls to the virtual nci device is missing. So, nci-specific ioctls of a compat task return with -1 and errno set to ENOTTY. Add a handler. The handling of an ioctl() call of a compat task to get the index of virtual nci device (IOCTL_GET_NCIDEV_IDX) lands in the default case of the ioctl compat handler (see fs/ioctl.c): COMPAT_SYSCALL_DEFINE3(ioctl, ...) { ... default: error = do_vfs_ioctl(fd_file(f), fd, cmd, ...); if (error != -ENOIOCTLCMD) break; if (fd_file(f)->f_op->compat_ioctl) error = fd_file(f)->f_op->compat_ioctl(fd_file(f), cmd, arg); if (error == -ENOIOCTLCMD) error = -ENOTTY; ... } There, do_vfs_ioctl() returns -ENOIOCTLCMD and compat_ioctl is not set for virtual_ncidev_fops, i.e. f_op->compat_ioctl == NULL. So, the ioctl() syscall returns with -1 and errno set to ENOTTY to the compat task. To fix this, use the compat_ptr_ioctl helper for compat handling here. It shall be used for ioctls that "either ignore the argument or pass a pointer to a compatible data type". The driver's sole ioctl takes a user void pointer and copies nfc_dev->idx to it, a 4-byte integer across all ABIs. This issue has been found by running the nci_dev kernel selftest as rv64 binary on top of a CHERI kernel, where the ioctl() ends up in the ioctl compat handler, similar to a 32-bit application on top of a 64-bit kernel. Fixes: e624e6c3e777 ("nfc: Add a virtual nci device driver") Signed-off-by: Chris Gellermann Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260904164252.18351-1-christian.gellermann@codasip.com Signed-off-by: David Heidelberg --- drivers/nfc/virtual_ncidev.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nfc/virtual_ncidev.c b/drivers/nfc/virtual_ncidev.c index 8eeb447ac96e9b..e51c647b27ebed 100644 --- a/drivers/nfc/virtual_ncidev.c +++ b/drivers/nfc/virtual_ncidev.c @@ -195,7 +195,8 @@ static const struct file_operations virtual_ncidev_fops = { .write = virtual_ncidev_write, .open = virtual_ncidev_open, .release = virtual_ncidev_close, - .unlocked_ioctl = virtual_ncidev_ioctl + .unlocked_ioctl = virtual_ncidev_ioctl, + .compat_ioctl = compat_ptr_ioctl, }; static struct miscdevice miscdev = { From 273f9d667cde649f8de9d72b1303cc2f4b658c50 Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Tue, 15 Sep 2026 19:54:27 +0100 Subject: [PATCH 1140/1417] nfc: llcp: drop truncated I/RR/RNR PDUs in nfc_llcp_recv_hdlc() nfc_llcp_recv_hdlc() reads the sequence byte skb->data[2], via nfc_llcp_ns()/nfc_llcp_nr(), before any length check. The receive path only guarantees the two-byte LLCP header -- __nfc_llcp_recv() checks it with pskb_may_pull() and nfc_llcp_recv_agf() admits two-byte inner PDUs -- so a two-byte I, RR or RNR PDU reads one byte of uninitialised skb tailroom. The byte becomes N(R)/N(S); a peer can already set those with a well-formed PDU, so this is acting on uninitialised memory, not new peer control. Guard the read with pskb_may_pull(), as commit 95674f506c63 ("nfc: llcp: reject PDUs shorter than the LLCP header") did for the two-byte header, so the sequence byte is present and linear before it is read. RR and RNR PDUs are LLCP_HEADER_SIZE + LLCP_SEQUENCE_SIZE bytes and an I PDU is longer, so no valid frame is rejected; a truncated PDU is malformed, so return without a DM reply. Fixes: d646960f7986 ("NFC: Initial LLCP support") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Aamir Ahmed Reviewed-by: Simon Horman Link: https://patch.msgid.link/AS8P251MB0001789BBF04B72745C7D96BC8BA2@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM Signed-off-by: David Heidelberg --- net/nfc/llcp_core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index 23553e7426ec2b..5017f6aa57a041 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -1091,6 +1091,9 @@ static void nfc_llcp_recv_hdlc(struct nfc_llcp_local *local, struct sock *sk; u8 dsap, ssap, ptype, ns, nr; + if (!pskb_may_pull(skb, LLCP_HEADER_SIZE + LLCP_SEQUENCE_SIZE)) + return; + ptype = nfc_llcp_ptype(skb); dsap = nfc_llcp_dsap(skb); ssap = nfc_llcp_ssap(skb); From 66f4300206b82b0b143ef0d9be90cd8d29f23a47 Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Mon, 14 Sep 2026 19:11:29 +0700 Subject: [PATCH 1141/1417] nfc: llcp: fix sdreq TLV list leak on parse/alloc/send failure nfc_genl_llc_sdreq() builds a list of TLV nodes while walking nested netlink attrs, but 3 error paths (nested-attr parse failure, TLV alloc ENOMEM, nfc_llcp_send_snl_sdreq() failure) all skip freeing what was already queued. Route them through a new free_list label, mirroring the SDRES path in the same file which already does this. Harmless on the success path too -- send_snl_sdreq() drains the list as it moves nodes, so it's already empty by the time free_list runs. Fixes: d9b8d8e19b07 ("NFC: llcp: Service Name Lookup netlink interface") Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260914121129.2098606-1-congnt264@gmail.com Signed-off-by: David Heidelberg --- net/nfc/netlink.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/nfc/netlink.c b/net/nfc/netlink.c index 0c58824cb150dd..224bdfa2dd0dc6 100644 --- a/net/nfc/netlink.c +++ b/net/nfc/netlink.c @@ -1181,7 +1181,7 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) if (rc != 0) { rc = -EINVAL; - goto put_local; + goto free_list; } if (!sdp_attrs[NFC_SDP_ATTR_URI]) @@ -1200,7 +1200,7 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) sdreq = nfc_llcp_build_sdreq_tlv(tid, uri, uri_len); if (sdreq == NULL) { rc = -ENOMEM; - goto put_local; + goto free_list; } tlvs_len += sdreq->tlv_len; @@ -1215,6 +1215,9 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) rc = nfc_llcp_send_snl_sdreq(local, &sdreq_list, tlvs_len); +free_list: + nfc_llcp_free_sdp_tlv_list(&sdreq_list); + put_local: nfc_llcp_local_put(local); From d2acbde7e67df44efa8f0963462d1192e7694ffc Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 00:26:25 -0400 Subject: [PATCH 1142/1417] nfc: trf7970a: power down on startup RX gain failure trf7970a_startup() powers up the device before applying the optional RX gain reduction. If the register read or write fails, it returns without undoing that power-up. Probe's unwind only drops the separate regulator references acquired by probe, leaving the additional VIN enable from startup unbalanced. The system resume caller also has no power-down on this error. Call trf7970a_power_down() before returning the RX gain error to deassert the enable GPIOs, release the startup VIN reference and restore the powered-off state. Runtime PM has not been enabled yet, so the full shutdown helper is not appropriate here. Preserve the original SPI error. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 5d69351820ea ("NFC: trf7970a: Create device-tree parameter for RX gain reduction") Cc: stable@vger.kernel.org Assisted-by: OpenAI:GPT-5.6 Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Paul Geurts Link: https://patch.msgid.link/20260913042625.31296-1-mhun512@gmail.com Signed-off-by: David Heidelberg --- drivers/nfc/trf7970a.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c index 60883001fa5d94..ddfc58c2996495 100644 --- a/drivers/nfc/trf7970a.c +++ b/drivers/nfc/trf7970a.c @@ -1997,8 +1997,10 @@ static int trf7970a_startup(struct trf7970a *trf) return ret; ret = trf7970a_update_rx_gain_reduction(trf); - if (ret) + if (ret) { + trf7970a_power_down(trf); return ret; + } pm_runtime_set_active(trf->dev); pm_runtime_enable(trf->dev); From dcab71a7011918f6fdba7adcec02d217dcb84b8d Mon Sep 17 00:00:00 2001 From: Luxiao Xu Date: Wed, 9 Sep 2026 13:19:24 +0800 Subject: [PATCH 1143/1417] nfc: fix use-after-free in nfc_get_local_general_bytes Commit 6709d4b7bc2e ("net: nfc: Fix use-after-free caused by nfc_llcp_find_local") attempted to fix a use-after-free (UAF) issue by invoking nfc_llcp_local_put(local) after accessing local->gb. However, if the reference count drops to zero, local is freed immediately, leading to a use-after-free when callers access the returned pointer. Alternative approaches using dynamic allocation (e.g. kmemdup) introduced memory leaks because callers consistently treat the returned pointer as borrowed memory. Fix this properly by refactoring nfc_llcp_general_bytes() and nfc_get_local_general_bytes() to accept a caller-provided output buffer (out_gb) and its maximum length (gb_max_len). The general bytes are safely copied into out_gb before calling nfc_llcp_local_put(local), ensuring safe lifetime management without ownership transfer complications. Update all callers across drivers (microread, pn533, pn544, st21nfca, digital_dep, and nci) to provide their own destination buffers and pass them to nfc_get_local_general_bytes(). Fixes: 6709d4b7bc2e ("net: nfc: Fix use-after-free caused by nfc_llcp_find_local") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Luxiao Xu Signed-off-by: Ren Wei Reviewed-by: Simon Horman Link: https://patch.msgid.link/3cbaac3bee23f8ff3a3284ed32d347696eb1d208.1788841683.git.rakukuip@gmail.com Signed-off-by: David Heidelberg --- drivers/nfc/microread/microread.c | 6 +++--- drivers/nfc/pn533/pn533.c | 14 ++++++++------ drivers/nfc/pn533/pn533.h | 4 +++- drivers/nfc/pn544/pn544.c | 7 +++---- drivers/nfc/st21nfca/core.c | 8 ++++---- include/net/nfc/hci.h | 2 +- include/net/nfc/nfc.h | 3 ++- net/nfc/core.c | 15 +++++++-------- net/nfc/digital_dep.c | 8 ++++---- net/nfc/llcp_core.c | 17 +++++++++++++---- net/nfc/nci/core.c | 10 +++++----- net/nfc/nfc.h | 3 ++- 12 files changed, 55 insertions(+), 42 deletions(-) diff --git a/drivers/nfc/microread/microread.c b/drivers/nfc/microread/microread.c index dfa2490db545c1..2bfafa94e83d6c 100644 --- a/drivers/nfc/microread/microread.c +++ b/drivers/nfc/microread/microread.c @@ -251,9 +251,9 @@ static int microread_start_poll(struct nfc_hci_dev *hdev, param[1] |= (1 << 1); if ((im_protocols | tm_protocols) & NFC_PROTO_NFC_DEP_MASK) { - hdev->gb = nfc_get_local_general_bytes(hdev->ndev, - &hdev->gb_len); - if (hdev->gb == NULL || hdev->gb_len == 0) { + nfc_get_local_general_bytes(hdev->ndev, hdev->gb, + sizeof(hdev->gb), &hdev->gb_len); + if (hdev->gb_len == 0) { im_protocols &= ~NFC_PROTO_NFC_DEP_MASK; tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK; } diff --git a/drivers/nfc/pn533/pn533.c b/drivers/nfc/pn533/pn533.c index f5a6a7c20d5af3..b0133e51dce963 100644 --- a/drivers/nfc/pn533/pn533.c +++ b/drivers/nfc/pn533/pn533.c @@ -1357,10 +1357,11 @@ static int pn533_poll_dep(struct nfc_dev *nfc_dev) u8 *next, nfcid3[NFC_NFCID3_MAXSIZE]; u8 passive_data[PASSIVE_DATA_LEN] = {0x00, 0xff, 0xff, 0x00, 0x3}; - if (!dev->gb) { - dev->gb = nfc_get_local_general_bytes(nfc_dev, &dev->gb_len); - - if (!dev->gb || !dev->gb_len) { + if (!dev->gb_len) { + nfc_get_local_general_bytes(nfc_dev, dev->gb, + sizeof(dev->gb), + &dev->gb_len); + if (!dev->gb_len) { dev->poll_dep = 0; queue_work(dev->wq, &dev->rf_work); } @@ -1658,8 +1659,9 @@ static int pn533_start_poll(struct nfc_dev *nfc_dev, } if (tm_protocols) { - dev->gb = nfc_get_local_general_bytes(nfc_dev, &dev->gb_len); - if (dev->gb == NULL) + nfc_get_local_general_bytes(nfc_dev, dev->gb, + sizeof(dev->gb), &dev->gb_len); + if (dev->gb_len == 0) tm_protocols = 0; } diff --git a/drivers/nfc/pn533/pn533.h b/drivers/nfc/pn533/pn533.h index 09e35b8693f5a6..5ab668e05121ae 100644 --- a/drivers/nfc/pn533/pn533.h +++ b/drivers/nfc/pn533/pn533.h @@ -6,6 +6,8 @@ * Copyright (C) 2012-2013 Tieto Poland */ +#include + #define PN533_DEVICE_STD 0x1 #define PN533_DEVICE_PASORI 0x2 #define PN533_DEVICE_ACR122U 0x3 @@ -166,7 +168,7 @@ struct pn533 { struct timer_list listen_timer; int cancel_listen; - u8 *gb; + u8 gb[NFC_MAX_GT_LEN]; size_t gb_len; u8 tgt_available_prots; diff --git a/drivers/nfc/pn544/pn544.c b/drivers/nfc/pn544/pn544.c index 9d0a16ac465e3d..c4fa70e45c14fb 100644 --- a/drivers/nfc/pn544/pn544.c +++ b/drivers/nfc/pn544/pn544.c @@ -377,10 +377,9 @@ static int pn544_hci_start_poll(struct nfc_hci_dev *hdev, return r; if ((im_protocols | tm_protocols) & NFC_PROTO_NFC_DEP_MASK) { - hdev->gb = nfc_get_local_general_bytes(hdev->ndev, - &hdev->gb_len); - pr_debug("generate local bytes %p\n", hdev->gb); - if (hdev->gb == NULL || hdev->gb_len == 0) { + nfc_get_local_general_bytes(hdev->ndev, hdev->gb, + sizeof(hdev->gb), &hdev->gb_len); + if (hdev->gb_len == 0) { im_protocols &= ~NFC_PROTO_NFC_DEP_MASK; tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK; } diff --git a/drivers/nfc/st21nfca/core.c b/drivers/nfc/st21nfca/core.c index fd39a05c962221..6bfeb8e7ed8995 100644 --- a/drivers/nfc/st21nfca/core.c +++ b/drivers/nfc/st21nfca/core.c @@ -351,10 +351,10 @@ static int st21nfca_hci_start_poll(struct nfc_hci_dev *hdev, if (r < 0) return r; } else { - hdev->gb = nfc_get_local_general_bytes(hdev->ndev, - &hdev->gb_len); - - if (hdev->gb == NULL || hdev->gb_len == 0) { + nfc_get_local_general_bytes(hdev->ndev, hdev->gb, + sizeof(hdev->gb), + &hdev->gb_len); + if (hdev->gb_len == 0) { im_protocols &= ~NFC_PROTO_NFC_DEP_MASK; tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK; } diff --git a/include/net/nfc/hci.h b/include/net/nfc/hci.h index 756c11084f65fe..86ed63e5d5334a 100644 --- a/include/net/nfc/hci.h +++ b/include/net/nfc/hci.h @@ -144,7 +144,7 @@ struct nfc_hci_dev { data_exchange_cb_t async_cb; void *async_cb_context; - u8 *gb; + u8 gb[NFC_MAX_GT_LEN]; size_t gb_len; unsigned long quirks; diff --git a/include/net/nfc/nfc.h b/include/net/nfc/nfc.h index c54df042db6be2..bcafab5c53e51a 100644 --- a/include/net/nfc/nfc.h +++ b/include/net/nfc/nfc.h @@ -273,7 +273,8 @@ struct sk_buff *nfc_alloc_recv_skb(unsigned int size, gfp_t gfp); int nfc_set_remote_general_bytes(struct nfc_dev *dev, const u8 *gt, u8 gt_len); -u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, size_t *gb_len); +u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, u8 *out_gb, + size_t gb_max_len, size_t *gb_len); int nfc_fw_download_done(struct nfc_dev *dev, const char *firmware_name, u32 result); diff --git a/net/nfc/core.c b/net/nfc/core.c index a92a6566e6a0d4..f521669293f05f 100644 --- a/net/nfc/core.c +++ b/net/nfc/core.c @@ -279,10 +279,10 @@ static struct nfc_target *nfc_find_target(struct nfc_dev *dev, u32 target_idx) int nfc_dep_link_up(struct nfc_dev *dev, int target_index, u8 comm_mode) { - int rc = 0; - u8 *gb; - size_t gb_len; struct nfc_target *target; + u8 gb[NFC_MAX_GT_LEN]; + size_t gb_len = 0; + int rc = 0; pr_debug("dev_name=%s comm %d\n", dev_name(&dev->dev), comm_mode); @@ -301,7 +301,7 @@ int nfc_dep_link_up(struct nfc_dev *dev, int target_index, u8 comm_mode) goto error; } - gb = nfc_llcp_general_bytes(dev, &gb_len); + nfc_get_local_general_bytes(dev, gb, sizeof(gb), &gb_len); if (gb_len > NFC_MAX_GT_LEN) { rc = -EINVAL; goto error; @@ -644,11 +644,10 @@ int nfc_set_remote_general_bytes(struct nfc_dev *dev, const u8 *gb, u8 gb_len) } EXPORT_SYMBOL(nfc_set_remote_general_bytes); -u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, size_t *gb_len) +u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, u8 *out_gb, + size_t gb_max_len, size_t *gb_len) { - pr_debug("dev_name=%s\n", dev_name(&dev->dev)); - - return nfc_llcp_general_bytes(dev, gb_len); + return nfc_llcp_general_bytes(dev, out_gb, gb_max_len, gb_len); } EXPORT_SYMBOL(nfc_get_local_general_bytes); diff --git a/net/nfc/digital_dep.c b/net/nfc/digital_dep.c index 3982fa084737f7..968547c306a5d8 100644 --- a/net/nfc/digital_dep.c +++ b/net/nfc/digital_dep.c @@ -1490,14 +1490,14 @@ static int digital_tg_send_atr_res(struct nfc_digital_dev *ddev, struct digital_atr_req *atr_req) { struct digital_atr_res *atr_res; + u8 gb[NFC_MAX_GT_LEN]; struct sk_buff *skb; - u8 *gb, payload_bits; + u8 payload_bits; size_t gb_len; int rc; - gb = nfc_get_local_general_bytes(ddev->nfc_dev, &gb_len); - if (!gb) - gb_len = 0; + nfc_get_local_general_bytes(ddev->nfc_dev, gb, sizeof(gb), + &gb_len); skb = digital_skb_alloc(ddev, sizeof(struct digital_atr_res) + gb_len); if (!skb) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index 5017f6aa57a041..5ce3ce64baf2c1 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -652,23 +652,32 @@ static int nfc_llcp_build_gb(struct nfc_llcp_local *local) return ret; } -u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, size_t *general_bytes_len) +u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, u8 *out_gb, size_t gb_max_len, + size_t *general_bytes_len) { struct nfc_llcp_local *local; + if (!out_gb || !general_bytes_len) + return NULL; + local = nfc_llcp_find_local(dev); - if (local == NULL) { + if (!local) { *general_bytes_len = 0; return NULL; } nfc_llcp_build_gb(local); - *general_bytes_len = local->gb_len; + if (local->gb_len) { + *general_bytes_len = min_t(size_t, local->gb_len, gb_max_len); + memcpy(out_gb, local->gb, *general_bytes_len); + } else { + *general_bytes_len = 0; + } nfc_llcp_local_put(local); - return local->gb; + return out_gb; } int nfc_llcp_set_remote_gb(struct nfc_dev *dev, const u8 *gb, u8 gb_len) diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c index 5f46c4b5720f6c..73e3a96470ace7 100644 --- a/net/nfc/nci/core.c +++ b/net/nfc/nci/core.c @@ -780,15 +780,15 @@ static int nci_set_local_general_bytes(struct nfc_dev *nfc_dev) { struct nci_dev *ndev = nfc_get_drvdata(nfc_dev); struct nci_set_config_param param; + u8 gb[NFC_MAX_GT_LEN]; int rc; - param.val = nfc_get_local_general_bytes(nfc_dev, ¶m.len); - if ((param.val == NULL) || (param.len == 0)) + nfc_get_local_general_bytes(nfc_dev, gb, sizeof(gb), + ¶m.len); + if (param.len == 0) return 0; - if (param.len > NFC_MAX_GT_LEN) - return -EINVAL; - + param.val = gb; param.id = NCI_PN_ATR_REQ_GEN_BYTES; rc = nci_request(ndev, nci_set_config_req, ¶m, diff --git a/net/nfc/nfc.h b/net/nfc/nfc.h index 0b1e6466f4fbf5..82c5dfdad10e24 100644 --- a/net/nfc/nfc.h +++ b/net/nfc/nfc.h @@ -49,7 +49,8 @@ void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx, int nfc_llcp_register_device(struct nfc_dev *dev); void nfc_llcp_unregister_device(struct nfc_dev *dev); int nfc_llcp_set_remote_gb(struct nfc_dev *dev, const u8 *gb, u8 gb_len); -u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, size_t *general_bytes_len); +u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, u8 *out_gb, size_t gb_max_len, + size_t *general_bytes_len); int nfc_llcp_data_received(struct nfc_dev *dev, struct sk_buff *skb); struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev); int nfc_llcp_local_put(struct nfc_llcp_local *local); From 7f2ea5ed588c03d481f0301e6c3d4240132383fb Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 21:29:58 +0800 Subject: [PATCH 1144/1417] nfc: st21nfca: validate ISO15693 inventory length The ISO15693 inventory helper removes a two-byte prefix without checking that it exists, then accepts a one-byte remainder before reading data[1] as the DSFID. Require the prefix and at least two remaining bytes before copying the UID data and reading the DSFID. Fixes: 7974728094d3 ("NFC: st21nfca: Add ISO15693 Reader/Writer support") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830132958.6397-1-pengpeng@iscas.ac.cn Signed-off-by: David Heidelberg --- drivers/nfc/st21nfca/core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/nfc/st21nfca/core.c b/drivers/nfc/st21nfca/core.c index 6bfeb8e7ed8995..b5c1ca3acfbe12 100644 --- a/drivers/nfc/st21nfca/core.c +++ b/drivers/nfc/st21nfca/core.c @@ -577,9 +577,7 @@ static int st21nfca_get_iso15693_inventory(struct nfc_hci_dev *hdev, if (r < 0) goto exit; - skb_pull(inventory_skb, 2); - - if (inventory_skb->len == 0 || + if (!skb_pull(inventory_skb, 2) || inventory_skb->len < 2 || inventory_skb->len > NFC_ISO15693_UID_MAXSIZE) { r = -EPROTO; goto exit; From c04981e42d94f39c1dba965cc462a046e946a6c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Mete=20Kaya?= Date: Wed, 9 Sep 2026 15:16:22 +0300 Subject: [PATCH 1145/1417] nfc: llcp: fix -ENOMEM on connect with zero-length service name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When service_name_len is 0, kmemdup() returns ZERO_SIZE_PTR which passes the NULL check, causing nfc_llcp_send_connect() to attempt building a zero-length service name TLV and fail with -ENOMEM. Fix by setting service_name to NULL directly when service_name_len is 0. Fixes: d646960f7986 ("NFC: Initial LLCP support") Signed-off-by: Ömer Mete Kaya Link: https://patch.msgid.link/20260909122029.34081-1-omermetekaya0@gmail.com Signed-off-by: David Heidelberg --- net/nfc/llcp_sock.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c index ce6875eb58fbc9..1e5ee4bcde684f 100644 --- a/net/nfc/llcp_sock.c +++ b/net/nfc/llcp_sock.c @@ -759,12 +759,16 @@ static int llcp_sock_connect(struct socket *sock, struct sockaddr_unsized *_addr llcp_sock->service_name_len = min_t(unsigned int, addr->service_name_len, NFC_LLCP_MAX_SERVICE_NAME); - llcp_sock->service_name = kmemdup(addr->service_name, - llcp_sock->service_name_len, - GFP_KERNEL); - if (!llcp_sock->service_name) { - ret = -ENOMEM; - goto sock_llcp_release; + if (llcp_sock->service_name_len == 0) { + llcp_sock->service_name = NULL; + } else { + llcp_sock->service_name = kmemdup(addr->service_name, + llcp_sock->service_name_len, + GFP_KERNEL); + if (!llcp_sock->service_name) { + ret = -ENOMEM; + goto sock_llcp_release; + } } nfc_llcp_sock_link(&local->connecting_sockets, sk); From 408cff6bd60636df201274d320edfdfde9ed41db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Mete=20Kaya?= Date: Wed, 9 Sep 2026 15:14:31 +0300 Subject: [PATCH 1146/1417] nfc: llcp: fix WKS SAP hijacking via prefix match in nfc_llcp_wks_sap() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nfc_llcp_wks_sap() compares only service_name_len bytes, so a short service_name like "u" matches longer WKS strings like "urn:nfc:sn:snep". Fix by requiring exact length match before strncmp(). Fixes: d646960f7986 ("NFC: Initial LLCP support") Signed-off-by: Ömer Mete Kaya Link: https://patch.msgid.link/20260909121437.33744-1-omermetekaya0@gmail.com Signed-off-by: David Heidelberg --- net/nfc/llcp_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index 5ce3ce64baf2c1..def712e1b2b2cc 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -369,7 +369,8 @@ static int nfc_llcp_wks_sap(const char *service_name, size_t service_name_len) if (wks[sap] == NULL) continue; - if (strncmp(wks[sap], service_name, service_name_len) == 0) + if (strlen(wks[sap]) == service_name_len && + !strncmp(wks[sap], service_name, service_name_len)) return sap; } From 7dcf371a35632f035baf77bcf2c129165f772ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Mete=20Kaya?= Date: Tue, 8 Sep 2026 19:18:01 +0300 Subject: [PATCH 1147/1417] nfc: llcp: fix slab-out-of-bounds reads when logging service names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nfc_llcp_wks_sap() and nfc_llcp_build_sdreq_tlv() pass non-null- terminated strings to pr_debug() using the %s format specifier. The buffers are allocated via kmemdup() or come from netlink attributes and are not guaranteed to be null-terminated, causing __dynamic_pr_debug() to read beyond the allocated region: KASAN: slab-out-of-bounds Read in __dynamic_pr_debug Fix both call sites by using %.*s with the explicit length to limit the output to the actual length of the string. Fixes: d9b8d8e19b07 ("NFC: llcp: Service Name Lookup netlink interface") Reported-by: syzbot+1e3df0852e82c21ca418@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1e3df0852e82c21ca418 Signed-off-by: Ömer Mete Kaya Link: https://patch.msgid.link/20260908161952.731468-1-omermetekaya0@gmail.com Signed-off-by: David Heidelberg --- net/nfc/llcp_commands.c | 2 +- net/nfc/llcp_core.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/nfc/llcp_commands.c b/net/nfc/llcp_commands.c index ca89fe967d6a27..80a00938c86965 100644 --- a/net/nfc/llcp_commands.c +++ b/net/nfc/llcp_commands.c @@ -135,7 +135,7 @@ struct nfc_llcp_sdp_tlv *nfc_llcp_build_sdreq_tlv(u8 tid, const char *uri, { struct nfc_llcp_sdp_tlv *sdreq; - pr_debug("uri: %s, len: %zu\n", uri, uri_len); + pr_debug("uri: %.*s, len: %zu\n", (int)uri_len, uri, uri_len); /* sdreq->tlv_len is u8, takes uri_len, + 3 for header, + 1 for NULL */ if (WARN_ON_ONCE(uri_len > U8_MAX - 4)) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index def712e1b2b2cc..74bf817007cf27 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -358,7 +358,7 @@ static int nfc_llcp_wks_sap(const char *service_name, size_t service_name_len) { int sap, num_wks; - pr_debug("%s\n", service_name); + pr_debug("%.*s\n", (int)service_name_len, service_name); if (service_name == NULL) return -EINVAL; From b61732f47316d45f27706db7812950145d3327b5 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Wed, 23 Sep 2026 09:26:27 +0530 Subject: [PATCH 1148/1417] nfc: pn533: fix OOB read in pn533_acr122_is_rx_frame_valid() frame->ccid.datalen is read directly from the USB response frame and used, unchecked, as an index into frame->data[]. A malicious or malfunctioning device can set this field to an arbitrary value, causing the driver to read far outside the received buffer. Bound ccid.datalen against the maximum possible ACR122 frame size before using it. This replaces the existing datalen == 0 check, since datalen < 2 already covers that case and additionally rejects datalen == 1, which would still underflow the "datalen - 2" offset used below. Fixes: 9815c7cf22da ("NFC: pn533: Separate physical layer from the core implementation") Reported-by: syzbot+1853daab1a47603d4678@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1853daab1a47603d4678 Tested-by: syzbot+1853daab1a47603d4678@syzkaller.appspotmail.com Assisted-by: LLM Signed-off-by: Deepanshu Kartikey Link: https://patch.msgid.link/20260923035627.6210-1-kartikey406@gmail.com Signed-off-by: David Heidelberg --- drivers/nfc/pn533/usb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nfc/pn533/usb.c b/drivers/nfc/pn533/usb.c index efb07f944fce29..972eaac09e5924 100644 --- a/drivers/nfc/pn533/usb.c +++ b/drivers/nfc/pn533/usb.c @@ -319,7 +319,9 @@ static bool pn533_acr122_is_rx_frame_valid(void *_frame, struct pn533 *dev) if (frame->ccid.type != 0x83) return false; - if (!frame->ccid.datalen) + if (frame->ccid.datalen < 2 || + frame->ccid.datalen > PN533_ACR122_FRAME_MAX_PAYLOAD_LEN + + PN533_ACR122_RX_FRAME_TAIL_LEN) return false; if (frame->data[frame->ccid.datalen - 2] == 0x63) From cfa165cbfbed9d0f4bbc22fef4309f595a3ab187 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Sun, 20 Sep 2026 14:07:01 -0300 Subject: [PATCH 1149/1417] net/sched: act_gate: budget the per-entry list in get_fill_size tcf_gate_get_fill_size returns only the TCA_GATE_PARMS size, but tcf_gate_dump also emits three 64-bit timestamps, the clock id, flags, priority and the variable-length TCA_GATE_ENTRY_LIST nest. The per-entry nest is unbounded: parse_gate_list places no cap on the number of sched-entries, so a gate with many entries can push the real dump well past the skb that tca_get_fill allocates from this size. RTM_NEWACTION then fails the add-notify with -EINVAL while the action is already committed to the IDR, and a subsequent RTM_GETACTION on the installed gate also returns -EINVAL because its dump no longer fits. Fix this by accounting for the missing fields in tcf_gate_get_fill_size along with all elements in the entries list. Note that sizing the reply from the action lets an oversized gate install cleanly for the first time: with the input unbounded by parse_gate_list, the sized skb can now grow well above NLMSG_GOODSIZE per netlink request (a transient GFP_KERNEL allocation reachable only with namespace-local CAP_NET_ADMIN). Overload from a malicious netns admin is hardening material, not net, per the discussion at https://lore.kernel.org/netdev/20260914191108.55a1a4f1@kernel.org/; a follow-up patch for net-next will cap the sched-entry count. Fixes: 4e76e75d6aba ("net sched actions: calculate add/delete event message size") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260824153903.4143642-1-victor@mojatatu.com Tested-by: hybris Co-developed-by: Jamal Hadi Salim Signed-off-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/QDISC-3BLH.v1.20260914203033@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/act_gate.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/net/sched/act_gate.c b/net/sched/act_gate.c index 5d228a4022040a..6d6d45e03c079c 100644 --- a/net/sched/act_gate.c +++ b/net/sched/act_gate.c @@ -681,7 +681,35 @@ static void tcf_gate_stats_update(struct tc_action *a, u64 bytes, u64 packets, static size_t tcf_gate_get_fill_size(const struct tc_action *act) { - return nla_total_size(sizeof(struct tc_gate)); + struct tcf_gate *gact = to_gate(act); + const struct tcf_gate_params *p; + struct tcfg_gate_entry *entry; + size_t size = nla_total_size(sizeof(struct tc_gate)) /* TCA_GATE_PARMS */ + + 3 * nla_total_size_64bit(sizeof(u64)) /* TCA_GATE_BASE_TIME + * TCA_GATE_CYCLE_TIME + * TCA_GATE_CYCLE_TIME_EXT + */ + + nla_total_size(sizeof(s32)) /* TCA_GATE_CLOCKID */ + + nla_total_size(sizeof(u32)) /* TCA_GATE_FLAGS */ + + nla_total_size(sizeof(s32)) /* TCA_GATE_PRIORITY */ + + nla_total_size(0); /* TCA_GATE_ENTRY_LIST */ + /* TCA_GATE_TM is budgeted by tcf_action_shared_attrs_size() */ + + rcu_read_lock(); + p = rcu_dereference(gact->param); + if (p) { + list_for_each_entry_rcu(entry, &p->entries, list) + /* TCA_GATE_ONE_ENTRY nest and its attributes */ + size += nla_total_size(0) + + nla_total_size(sizeof(u32)) /* TCA_GATE_ENTRY_INDEX */ + + nla_total_size(0) /* TCA_GATE_ENTRY_GATE */ + + nla_total_size(sizeof(u32)) /* TCA_GATE_ENTRY_INTERVAL */ + + nla_total_size(sizeof(s32)) /* TCA_GATE_ENTRY_MAX_OCTETS */ + + nla_total_size(sizeof(s32)); /* TCA_GATE_ENTRY_IPV */ + } + rcu_read_unlock(); + + return size; } static void tcf_gate_entry_destructor(void *priv) From ab1404ac81154a89fb61ac50ae9a04cd8d4834dc Mon Sep 17 00:00:00 2001 From: Fourie Zhang Date: Sun, 20 Sep 2026 19:08:43 +0800 Subject: [PATCH 1150/1417] net: bridge: mdb: restart port group walk after deletion br_mdb_flush_pgs() keeps a pointer-to-pointer cursor while walking mp->ports. br_multicast_del_pg() can re-enter the same MDB entry through br_multicast_sg_del_exclude_ports() and unlink other port groups. If the cursor points into one of those groups, the next iteration dereferences a stale cursor and can leave mp->ports pointing at freed memory. A following RTM_GETMDB exposes the dangling pointer: BUG: KASAN: slab-use-after-free in br_mdb_dump Read of size 8 br_mdb_dump rtnl_mdb_dump rtnl_dumpit netlink_dump Reset the cursor to mp->ports after every deletion. The deletion removes at least the selected group, so the restarted walk always makes progress. Fixes: a6acb535afb2 ("bridge: mdb: Add MDB bulk deletion support") Cc: stable@vger.kernel.org Signed-off-by: Fourie Zhang Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260920110852.60293-1-fouriezhang@tencent.com Signed-off-by: Jakub Kicinski --- net/bridge/br_mdb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c index e0c7020b12f5fc..a01bd280c722b6 100644 --- a/net/bridge/br_mdb.c +++ b/net/bridge/br_mdb.c @@ -1523,6 +1523,8 @@ static void br_mdb_flush_pgs(struct net_bridge *br, } br_multicast_del_pg(mp, p, pp); + /* br_multicast_del_pg() can remove other groups from this list. */ + pp = &mp->ports; } } From fdfec06ac1eb5cdbc18d556c70a7b26837208218 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 22 Sep 2026 09:31:40 +0200 Subject: [PATCH 1151/1417] MAINTAINERS: add Nicolai Buchwitz as GENET maintainer I have been contributing to and reviewing the GENET driver for a while now. Florian asked if I would like to formalize this commitment, so add myself as a maintainer. Signed-off-by: Nicolai Buchwitz Acked-by: Florian Fainelli Acked-by: Justin Chen Link: https://patch.msgid.link/20260922073140.1471858-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3b2eb2a7a89a88..7200905501b84a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5454,6 +5454,7 @@ F: include/linux/brcmphy.h BROADCOM GENET ETHERNET DRIVER M: Doug Berger M: Florian Fainelli +M: Nicolai Buchwitz R: Broadcom internal kernel review list L: netdev@vger.kernel.org S: Maintained From 7e87508b5c4d81210d0a736ed01962e52f5c4c56 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 22 Sep 2026 15:06:38 +0200 Subject: [PATCH 1152/1417] net: bcmgenet: stop Tx NAPI before disabling the queues bcmgenet_netif_stop() and the Wake-on-LAN branch of bcmgenet_suspend() both disable the Tx queues first and stop Tx NAPI several steps later. A completion in flight calls netif_tx_wake_queue() in between, and nothing stops the queue again, so a transmit can reach the rings after they have been freed. Close is safe because dev_deactivate_many() stops the qdisc first. bcmgenet_suspend() does not, so stop Tx NAPI before the queues on both paths. KASAN on a Raspberry Pi CM4, driven from an MTU change because suspend freezes user space before the callback runs: BUG: KASAN: use-after-free in bcmgenet_xmit+0x17f8/0x2258 Write of size 8 at addr ffffff8055844a68 by task ksoftirqd/0/14 bcmgenet_xmit+0x17f8/0x2258 dev_hard_start_xmit+0x13c/0x588 sch_direct_xmit+0x108/0x340 __dev_queue_xmit+0x1190/0x3848 Fixes: 254f3239dd07 ("net: bcmgenet: revise suspend/resume") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260922130639.1660797-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index b916080f4ff176..ca62041efecd70 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -3441,6 +3441,8 @@ static void bcmgenet_netif_stop(struct net_device *dev, bool stop_phy) { struct bcmgenet_priv *priv = netdev_priv(dev); + /* Stop completion polling before it can wake a stopped queue */ + bcmgenet_disable_tx_napi(priv); netif_tx_disable(dev); /* Disable MAC receive */ @@ -3455,7 +3457,6 @@ static void bcmgenet_netif_stop(struct net_device *dev, bool stop_phy) /* Disable MAC transmit. TX DMA disabled must be done before this */ umac_enable_set(priv, CMD_TX_EN, false); - bcmgenet_disable_tx_napi(priv); bcmgenet_disable_rx_napi(priv); bcmgenet_intr_disable(priv); @@ -4320,6 +4321,8 @@ static int bcmgenet_suspend(struct device *d) netif_device_detach(dev); if (device_may_wakeup(d) && priv->wolopts) { + /* Stop completion polling before it can wake a stopped queue */ + bcmgenet_disable_tx_napi(priv); netif_tx_disable(dev); /* Suspend non-wake Rx data flows */ @@ -4348,7 +4351,6 @@ static int bcmgenet_suspend(struct device *d) netdev_warn(priv->dev, "Timed out while disabling TX DMA\n"); - bcmgenet_disable_tx_napi(priv); bcmgenet_disable_rx_napi(priv); disable_irq(priv->irq1); bcmgenet_tx_reclaim_all(dev); From 7104a370714346b667712913dc16abf14bbc97ed Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 21 Sep 2026 16:18:56 -0700 Subject: [PATCH 1153/1417] veth: manage XDP program pointers during channel resize veth_set_channels() tears down XDP resources for removed RX queues without clearing rq->xdp_prog. If the program is then detached or replaced, those queues keep the old pointer after bpf_prog_put(). A later channel increase can re-enable NAPI and run the freed program. BUG: unable to handle page fault for address: ffffc90000256048 Oops: Oops: 0000 [#1] SMP KASAN NOPTI RIP: veth_xdp_rcv_skb (include/linux/filter.h:779 include/net/xdp.h:696 drivers/net/veth.c:820) Call Trace: veth_xdp_rcv (drivers/net/veth.c:941) veth_poll (drivers/net/veth.c:986) __napi_poll (net/core/dev.c:7787) net_rx_action (net/core/dev.c:7850 net/core/dev.c:8007) handle_softirqs (kernel/softirq.c:645) Kernel panic - not syncing: Fatal exception in interrupt Fixes: 4752eeb3d891 ("veth: implement support for set_channel ethtool op") Signed-off-by: Weiming Shi Acked-by: Stanislav Fomichev Reviewed-by: Jiayuan Chen Reviewed-by: Jason Xing Link: https://patch.msgid.link/20260921231856.1798630-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/veth.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 6ed3ee81153fbe..71227d0389aa5d 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -1054,6 +1054,7 @@ static int __veth_napi_enable_range(struct net_device *dev, int start, int end) for (i = start; i < end; i++) { struct veth_rq *rq = &priv->rq[i]; + rcu_assign_pointer(rq->xdp_prog, priv->_xdp_prog); napi_enable(&rq->xdp_napi); rcu_assign_pointer(priv->rq[i].napi, &priv->rq[i].xdp_napi); } @@ -1088,6 +1089,7 @@ static void veth_napi_del_range(struct net_device *dev, int start, int end) rcu_assign_pointer(priv->rq[i].napi, NULL); napi_disable(&rq->xdp_napi); + rcu_assign_pointer(rq->xdp_prog, NULL); __netif_napi_del(&rq->xdp_napi); } synchronize_net(); From 3b4e0b0c008a8c1b474730248cd5b873026c74bd Mon Sep 17 00:00:00 2001 From: Shihuang Liu Date: Sat, 19 Sep 2026 21:36:04 +0800 Subject: [PATCH 1154/1417] net: skbuff: fix pull-bound underflow in skb_checksum_setup_ipv6() skb_maybe_pull_tail() subtracts skb_headlen(skb) from the unsigned max argument and passes the result to __pskb_pull_tail() as a signed int. The function does not ensure that max is at least skb_headlen(skb). This can happen while parsing IPv6 extension headers when an skb already has a linear area larger than MAX_IPV6_HDR_LEN. Once the parser needs data beyond the linear area, max - skb_headlen(skb) wraps and is converted to a negative delta. __pskb_pull_tail() then passes that negative length to skb_copy_bits(), where it can become a very large copy length. Pass the requested length itself as the pull bound at the three extension-header call sites, so the delta can no longer go negative. Fixes: 1431fb31ecba ("xen-netback: fix fragment detection in checksum setup") Suggested-by: Eric Dumazet Signed-off-by: Shihuang Liu Link: https://patch.msgid.link/20260919133604.50948-1-shlomojune6@gmail.com Signed-off-by: Jakub Kicinski --- net/core/skbuff.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 609f2c7f4a47ad..b4edbd06655e2a 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -5977,7 +5977,8 @@ static int skb_checksum_setup_ipv6(struct sk_buff *skb, bool recalculate) err = skb_maybe_pull_tail(skb, off + sizeof(struct ipv6_opt_hdr), - MAX_IPV6_HDR_LEN); + off + + sizeof(struct ipv6_opt_hdr)); if (err < 0) goto out; @@ -5992,7 +5993,8 @@ static int skb_checksum_setup_ipv6(struct sk_buff *skb, bool recalculate) err = skb_maybe_pull_tail(skb, off + sizeof(struct ip_auth_hdr), - MAX_IPV6_HDR_LEN); + off + + sizeof(struct ip_auth_hdr)); if (err < 0) goto out; @@ -6007,7 +6009,8 @@ static int skb_checksum_setup_ipv6(struct sk_buff *skb, bool recalculate) err = skb_maybe_pull_tail(skb, off + sizeof(struct frag_hdr), - MAX_IPV6_HDR_LEN); + off + + sizeof(struct frag_hdr)); if (err < 0) goto out; From ab7aa05c06ae340e5c7530bb78fa8d23794e460b Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Tue, 22 Sep 2026 16:12:39 +0300 Subject: [PATCH 1155/1417] vrf: Stop corrupting skb->csum when capturing CHECKSUM_COMPLETE packets The VRF device is an Ethernet device but it can have non-Ethernet ports such as IP tunnels. Before the cited commit, capturing packets from such ports on the VRF device resulted in these packets being detected as malformed since they lack an Ethernet header. The cited commit fixed it by pushing a dummy Ethernet header to such packets before the capture and pulling it afterwards. In the case of CHECKSUM_COMPLETE packets it also updated skb->csum with the checksum of the dummy Ethernet header. This is wrong as skb->csum should not include the checksum of the Ethernet header ("checksum of the _whole_ packet as seen by netif_rx()"). This also means that L4 protocols receive a corrupted skb->csum and potentially drop the packet, as is the case with UDP packets whose checksum was completed by software. Fix by removing the unnecessary call to skb_postpush_rcsum(). Fixes: 048939088220 ("vrf: add mac header for tunneled packets when sniffer is attached") Reported-by: Stefano Sasso Closes: https://lore.kernel.org/netdev/CALtE316UtL3x7LL6uxfXzx8rW6AbzYPeDOb478hqJCr_-dj=Wg@mail.gmail.com/ Signed-off-by: Ido Schimmel Reviewed-by: David Ahern Reviewed-by: Eric Dumazet Reviewed-by: Andrea Mayer Link: https://patch.msgid.link/20260922131239.2509494-1-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/vrf.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c index a0557a3a70260a..d4dc6d690a751a 100644 --- a/drivers/net/vrf.c +++ b/drivers/net/vrf.c @@ -1175,8 +1175,6 @@ static int vrf_prepare_mac_header(struct sk_buff *skb, skb->protocol = eth->h_proto; skb->pkt_type = PACKET_HOST; - skb_postpush_rcsum(skb, skb->data, ETH_HLEN); - skb_pull_inline(skb, ETH_HLEN); return 0; From 2d959c75c27f90e9ec489d18ce5ee6b852ad4741 Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Mon, 21 Sep 2026 04:40:25 +0000 Subject: [PATCH 1156/1417] ipv6: sr: enforce exact attribute length for SEG6_ATTR_DST In seg6_genl_policy, SEG6_ATTR_DST is defined with .type = NLA_BINARY and .len = sizeof(struct in6_addr). For NLA_BINARY, .len only enforces the maximum payload length and permits shorter payloads (e.g., 0 bytes). When seg6_genl_set_tunsrc() copies sizeof(struct in6_addr) bytes via kmemdup(val, sizeof(*val), GFP_KERNEL), a short SEG6_ATTR_DST attribute triggers a 16-byte out-of-bounds read past skb->tail into uninitialized skb->head memory, which is stored in sdata->tun_src and leaked back to userspace via SEG6_CMD_GET_TUNSRC. Switch SEG6_ATTR_DST in seg6_genl_policy to NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)) so that generic netlink validation rejects any attribute whose length is not exactly sizeof(struct in6_addr) with -ERANGE. Tested in QEMU against Linux 7.3.0-rc3 by sending a SEG6_CMD_SET_TUNSRC Generic Netlink message with a 0-byte SEG6_ATTR_DST attribute followed by SEG6_CMD_GET_TUNSRC. On the unfixed kernel, SEG6_CMD_SET_TUNSRC succeeds (err = 0) and SEG6_CMD_GET_TUNSRC leaks 16 bytes of uninitialized kernel heap memory (tun_src = 836a61ecc4d25a1042a8d60411cfb378); with this patch applied, SEG6_CMD_SET_TUNSRC is rejected by netlink policy validation with -ERANGE (-34) and tun_src remains zeroed. Fixes: 915d7e5e5930 ("ipv6: sr: add code base for control plane support of SR-IPv6") Cc: stable@vger.kernel.org Signed-off-by: Hui Peng Reviewed-by: Hangbin Liu Reviewed-by: Justin Iurman Reviewed-by: Andrea Mayer Link: https://patch.msgid.link/20260921044025.1535982-1-benquike@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/seg6.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c index 62a7eb77920265..8c2b156c227a73 100644 --- a/net/ipv6/seg6.c +++ b/net/ipv6/seg6.c @@ -138,8 +138,8 @@ void seg6_icmp_srh(struct sk_buff *skb, struct inet6_skb_parm *opt) static struct genl_family seg6_genl_family; static const struct nla_policy seg6_genl_policy[SEG6_ATTR_MAX + 1] = { - [SEG6_ATTR_DST] = { .type = NLA_BINARY, - .len = sizeof(struct in6_addr) }, + [SEG6_ATTR_DST] = + NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), [SEG6_ATTR_DSTLEN] = { .type = NLA_S32, }, [SEG6_ATTR_HMACKEYID] = { .type = NLA_U32, }, [SEG6_ATTR_SECRET] = { .type = NLA_BINARY, }, From 6b491af01aa5c0633a580e3b11bc7277adc903b2 Mon Sep 17 00:00:00 2001 From: Nicolo Giuliani Date: Mon, 21 Sep 2026 05:56:29 +0200 Subject: [PATCH 1157/1417] net: dsa: mv88e6xxx: 88E6191X and 88E6193X have no PTP The 88E6191X and 88E6193X are 6393 family devices that share mv88e6393x_ops with the 88E6393X and are marked as ptp_support. Marvell's UMSD driver describes both as parts without AVB (88E6193X: "BGA package - No AVB, No Routing, No Cut-through"), and the register access confirms it on an 88E6193X: the whole indirect AVB register space behind Global 2 registers 0x16 and 0x17 reads zero, for every port, block and address, with the 6390 and with the 6352 command encoding. Writes to the TAI registers, including the clock period register and the TAI global configuration register, read back as zero. Since commit 7e3c18097a70 ("net: dsa: mv88e6xxx: read cycle counter period from hardware") the PTP setup reads the TAI clock period, so the switch fails to probe: mv88e6xxx ...: unexpected cycle counter period of 0 ps Add mv88e6191x_ops, a copy of mv88e6393x_ops without avb_ops and ptp_ops, use it for the 88E6191X and the 88E6193X and stop setting ptp_support for them. The 88E6393X is unchanged. Tested on an 88E6193X (Sophos XGS 107w): the switch probes and the ports work. I do not have an 88E6191X, it is changed because UMSD describes it the same way. Fixes: de776d0d316f ("net: dsa: mv88e6xxx: add support for mv88e6393x family") Suggested-by: Andrew Lunn Signed-off-by: Nicolo Giuliani Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260921-send-net-v2-1-031ad720f140@studio.unibo.it Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mv88e6xxx/chip.c | 68 ++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index 7f68a0c5580265..a4a8c7e11bf4fd 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -5639,6 +5639,68 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = { .pcs_ops = &mv88e6390_pcs_ops, }; +static const struct mv88e6xxx_ops mv88e6191x_ops = { + /* MV88E6XXX_FAMILY_6393 without AVB and PTP: 6191X and 6193X */ + .irl_init_all = mv88e6390_g2_irl_init_all, + .get_eeprom = mv88e6xxx_g2_get_eeprom8, + .set_eeprom = mv88e6xxx_g2_set_eeprom8, + .set_switch_mac = mv88e6xxx_g2_set_switch_mac, + .phy_read = mv88e6xxx_g2_smi_phy_read_c22, + .phy_write = mv88e6xxx_g2_smi_phy_write_c22, + .phy_read_c45 = mv88e6xxx_g2_smi_phy_read_c45, + .phy_write_c45 = mv88e6xxx_g2_smi_phy_write_c45, + .port_set_link = mv88e6xxx_port_set_link, + .port_sync_link = mv88e6xxx_port_sync_link, + .port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay, + .port_set_speed_duplex = mv88e6393x_port_set_speed_duplex, + .port_tag_remap = mv88e6390_port_tag_remap, + .port_set_policy = mv88e6393x_port_set_policy, + .port_set_frame_mode = mv88e6351_port_set_frame_mode, + .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, + .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, + .port_set_ether_type = mv88e6393x_port_set_ether_type, + .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, + .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, + .port_pause_limit = mv88e6390_port_pause_limit, + .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, + .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, + .port_get_cmode = mv88e6352_port_get_cmode, + .port_set_cmode = mv88e6393x_port_set_cmode, + .port_setup_message_port = mv88e6xxx_setup_message_port, + .port_set_upstream_port = mv88e6393x_port_set_upstream_port, + .port_enable_tcam = mv88e6xxx_port_enable_tcam, + .stats_snapshot = mv88e6390_g1_stats_snapshot, + .stats_set_histogram = mv88e6390_g1_stats_set_histogram, + .stats_get_sset_count = mv88e6320_stats_get_sset_count, + .stats_get_strings = mv88e6320_stats_get_strings, + .stats_get_stat = mv88e6390_stats_get_stat, + /* .set_cpu_port is missing because this family does not support a global + * CPU port, only per port CPU port which is set via + * .port_set_upstream_port method. + */ + .set_egress_port = mv88e6393x_set_egress_port, + .watchdog_ops = &mv88e6393x_watchdog_ops, + .mgmt_rsvd2cpu = mv88e6393x_port_mgmt_rsvd2cpu, + .pot_clear = mv88e6xxx_g2_pot_clear, + .hardware_reset_pre = mv88e6xxx_g2_eeprom_wait, + .hardware_reset_post = mv88e6xxx_g2_eeprom_wait, + .reset = mv88e6352_g1_reset, + .rmu_disable = mv88e6390_g1_rmu_disable, + .atu_get_hash = mv88e6165_g1_atu_get_hash, + .atu_set_hash = mv88e6165_g1_atu_set_hash, + .vtu_getnext = mv88e6390_g1_vtu_getnext, + .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, + .stu_getnext = mv88e6390_g1_stu_getnext, + .stu_loadpurge = mv88e6390_g1_stu_loadpurge, + .serdes_get_lane = mv88e6393x_serdes_get_lane, + .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, + /* TODO: serdes stats */ + .gpio_ops = &mv88e6352_gpio_ops, + .phylink_get_caps = mv88e6393x_phylink_get_caps, + .pcs_ops = &mv88e6393x_pcs_ops, + .tcam_ops = &mv88e6393_tcam_ops, +}; + static const struct mv88e6xxx_ops mv88e6393x_ops = { /* MV88E6XXX_FAMILY_6393 */ .irl_init_all = mv88e6390_g2_irl_init_all, @@ -6163,8 +6225,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, - .ptp_support = true, - .ops = &mv88e6393x_ops, + .ops = &mv88e6191x_ops, }, [MV88E6193X] = { @@ -6190,8 +6251,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, - .ptp_support = true, - .ops = &mv88e6393x_ops, + .ops = &mv88e6191x_ops, }, [MV88E6220] = { From d22609f3d13fc5baacd92c222731b03c593401db Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Mon, 21 Sep 2026 04:59:20 +0000 Subject: [PATCH 1158/1417] fou: reject omitted FOU_ATTR_IPPROTO on FOU_ENCAP_DIRECT Commit 7a9bc9e3f423 ("fou: Don't allow 0 for FOU_ATTR_IPPROTO.") added NLA_POLICY_MIN(NLA_U8, 1) to fou_nl_policy[FOU_ATTR_IPPROTO], which rejects an explicitly supplied FOU_ATTR_IPPROTO == 0 attribute with -ERANGE. However, FOU_ATTR_IPPROTO is an optional netlink attribute. When a user sends FOU_CMD_ADD with FOU_ATTR_TYPE set to FOU_ENCAP_DIRECT and omits FOU_ATTR_IPPROTO entirely, nla_policy validation succeeds and parse_nl_config() leaves cfg->protocol as 0 (from memset(cfg, 0, sizeof(*cfg))). fou_create() then creates a FOU_ENCAP_DIRECT socket with fou->protocol == 0. In fou_udp_recv(), returning -fou->protocol to udp_queue_rcv_one_skb() triggers IP protocol resubmission when fou->protocol > 0, whereas returning 0 tells the UDP tunnel layer that the skb was consumed without freeing it. When fou->protocol == 0, every packet received on the socket returns 0 from fou_udp_recv() and leaks the sk_buff. Reject FOU_ENCAP_DIRECT when !cfg->protocol in fou_create() so that creating a direct encapsulation port without FOU_ATTR_IPPROTO fails with -EINVAL while leaving FOU_CMD_DEL and FOU_CMD_GET (which share parse_nl_config()) unaffected. Tested in QEMU against Linux 7.3.0-rc3 by sending a FOU_CMD_ADD Generic Netlink request with FOU_ATTR_PORT = 5555 and FOU_ATTR_TYPE = FOU_ENCAP_DIRECT while omitting FOU_ATTR_IPPROTO. On the unfixed kernel, FOU_CMD_ADD succeeds (err = 0), FOU_CMD_GET reports fou->type = 1 and fou->protocol = 0, and sending 4000 UDP packets to 127.0.0.1:5555 leaks all 4000 sk_buffs (SUnreclaim in /proc/meminfo grows from 41456 kB to 59008 kB, +17552 kB); with this patch applied, FOU_CMD_ADD is rejected with -EINVAL (-22). Fixes: 23461551c006 ("fou: Support for foo-over-udp RX path") Fixes: 7a9bc9e3f423 ("fou: Don't allow 0 for FOU_ATTR_IPPROTO.") Cc: stable@vger.kernel.org Signed-off-by: Hui Peng Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260921045920.1613098-1-benquike@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/fou_core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/ipv4/fou_core.c b/net/ipv4/fou_core.c index 5e867f1b5c1d39..076fdca44f5180 100644 --- a/net/ipv4/fou_core.c +++ b/net/ipv4/fou_core.c @@ -600,6 +600,10 @@ static int fou_create(struct net *net, struct fou_cfg *cfg, /* Initial for fou type */ switch (cfg->type) { case FOU_ENCAP_DIRECT: + if (!cfg->protocol) { + err = -EINVAL; + goto error; + } tunnel_cfg.encap_rcv = fou_udp_recv; tunnel_cfg.gro_receive = fou_gro_receive; tunnel_cfg.gro_complete = fou_gro_complete; From 89a8a1eef2d441b7825a6c0116ce817235a7ffe6 Mon Sep 17 00:00:00 2001 From: Jonas Jelonek Date: Fri, 18 Sep 2026 21:19:55 +0000 Subject: [PATCH 1159/1417] net: mdio: realtek-rtl9300: fix RTL931x C22 extended page selection The RTL931x indirect access engine has a separate nine-bit extended page field. The driver leaves it at zero, and otto_emdio_run_cmd() therefore programs extended page zero for every Clause 22 transaction. This overrides page selection made through PHY register 30, causing accesses to private PHY pages to hit extended page zero instead. Set the field to its 0x1ff "do not change" value for RTL931x Clause 22 reads and writes. This preserves extended page selection made through PHY register 30 and restores access to its private register pages. Fixes: 5ebdcac59aff ("net: mdio: realtek-rtl9300: Add support for RTL931x") Signed-off-by: Jonas Jelonek Acked-by: Markus Stockhausen Link: https://patch.msgid.link/20260918211955.3955777-1-jonas@jonasjelonek.de Signed-off-by: Jakub Kicinski --- drivers/net/mdio/mdio-realtek-rtl9300.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c index afd52a1cd7f831..9ce2b78075320a 100644 --- a/drivers/net/mdio/mdio-realtek-rtl9300.c +++ b/drivers/net/mdio/mdio-realtek-rtl9300.c @@ -88,6 +88,8 @@ #define RTL9310_SMI_INDRT_ACCESS_BC_PHYID_CTRL 0x0c14 #define RTL9310_BC_PORT_ID GENMASK(10, 5) #define RTL9310_SMI_INDRT_ACCESS_CTRL_1 0x0c04 +#define RTL9310_SMI_INDRT_EXT_PAGE GENMASK(8, 0) +#define RTL9310_SMI_INDRT_EXT_PAGE_NO_CHANGE 0x1ff #define RTL9310_SMI_INDRT_ACCESS_CTRL_2_LOW 0x0c08 #define RTL9310_SMI_INDRT_ACCESS_CTRL_2_HIGH 0x0c0c #define RTL9310_SMI_INDRT_ACCESS_CTRL_3 0x0c10 /* I/O fields flipped */ @@ -325,6 +327,8 @@ static int otto_emdio_9310_read_c22(struct mii_bus *bus, int port, int regnum, u .broadcast = FIELD_PREP(RTL9310_BC_PORT_ID, port), .c22_data = FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) | FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)), + .ext_page = FIELD_PREP(RTL9310_SMI_INDRT_EXT_PAGE, + RTL9310_SMI_INDRT_EXT_PAGE_NO_CHANGE), }; return otto_emdio_read_cmd(bus, RTL9310_PHY_CTRL_TYPE_C22, &cmd_data, @@ -337,6 +341,8 @@ static int otto_emdio_9310_write_c22(struct mii_bus *bus, int port, int regnum, struct otto_emdio_cmd_regs cmd_data = { .c22_data = FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) | FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)), + .ext_page = FIELD_PREP(RTL9310_SMI_INDRT_EXT_PAGE, + RTL9310_SMI_INDRT_EXT_PAGE_NO_CHANGE), .io_data = FIELD_PREP(RTL9310_PHY_CTRL_INDATA, value), .port_mask_high = (u32)(BIT_ULL(port) >> 32), .port_mask_low = (u32)(BIT_ULL(port)), From db762fd96be225bd06161c9631160c755d891693 Mon Sep 17 00:00:00 2001 From: Johan Almbladh Date: Wed, 23 Sep 2026 12:51:57 +0200 Subject: [PATCH 1160/1417] bpf: Fix immediate JMP JEQ/JNE on MIPS32 An addu instruction was emitted instead of addiu, causing the immediate value 1 to be interpreted as register $at. This made the comparison result invalid when the immediate operand was negative. Note that $at is mapped to BPF_REG_AX, which is used for constant blinding. Fix the instruction to use the immediate form. Found with test_bpf on MIPS32r1 emulated by QEMU. Fixes: eb63cfcd2ee8 ("mips, bpf: Add eBPF JIT for 32-bit MIPS") Signed-off-by: Johan Almbladh Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260923105158.3514342-1-johan.almbladh@anyfinetworks.com --- arch/mips/net/bpf_jit_comp32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/mips/net/bpf_jit_comp32.c b/arch/mips/net/bpf_jit_comp32.c index 40a878b672f5d3..15a2a153dc8733 100644 --- a/arch/mips/net/bpf_jit_comp32.c +++ b/arch/mips/net/bpf_jit_comp32.c @@ -1111,7 +1111,7 @@ static void emit_jmp_i64(struct jit_context *ctx, emit(ctx, xor, tmp, lo(dst), tmp); } if (imm < 0) { /* Compare sign extension */ - emit(ctx, addu, MIPS_R_T9, hi(dst), 1); + emit(ctx, addiu, MIPS_R_T9, hi(dst), 1); emit(ctx, or, tmp, tmp, MIPS_R_T9); } else { /* Compare zero extension */ emit(ctx, or, tmp, tmp, hi(dst)); From 8110ba09777873443db286b3cbb89b0e6311c554 Mon Sep 17 00:00:00 2001 From: Johan Almbladh Date: Wed, 23 Sep 2026 12:51:58 +0200 Subject: [PATCH 1161/1417] bpf: Fix BSWAP 32 and 16 on MIPS64 The 16/32-bit byteswap implementations for MIPS64r1 and earlier do not have an explicit zero extension afterwards. The input is first sign-extended to 64 bits, and the byteswap sequence can then leave the result sign-extended depending on the value of the low bits. Add the missing zero-extension. Found with test_bpf on MIPS64r1 emulated by QEMU. Fixes: fbc802de6b10 ("mips, bpf: Add new eBPF JIT for 64-bit MIPS") Signed-off-by: Johan Almbladh Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260923105158.3514342-2-johan.almbladh@anyfinetworks.com --- arch/mips/net/bpf_jit_comp64.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/mips/net/bpf_jit_comp64.c b/arch/mips/net/bpf_jit_comp64.c index fa7e9aa37f498d..6681ccac9dd9eb 100644 --- a/arch/mips/net/bpf_jit_comp64.c +++ b/arch/mips/net/bpf_jit_comp64.c @@ -305,8 +305,7 @@ static void emit_bswap_r64(struct jit_context *ctx, u8 dst, u32 width) case 16: emit_sext(ctx, dst, dst); emit_bswap_r(ctx, dst, width); - if (cpu_has_mips64r2 || cpu_has_mips64r6) - emit_zext(ctx, dst); + emit_zext(ctx, dst); break; } clobber_reg(ctx, dst); From d8b6529e80bcb4fb8177121404cbb3377acaebd2 Mon Sep 17 00:00:00 2001 From: Zijie Huang Date: Mon, 21 Sep 2026 01:36:19 +0800 Subject: [PATCH 1162/1417] net: arp: terminate device name before lookup The ARP ioctl copies a user-provided struct arpreq into a stack object. Its arp_dev field may contain IFNAMSIZ bytes without a NUL terminator. Such input is passed to dev_get_by_name_rcu() or __dev_get_by_name(), where strcmp() can read past the end of the stack object when a matching alternative interface name exists. Terminate the field before the lookup to prevent the out-of-bounds read. Fixes: 36fbf1e52bd3 ("net: rtnetlink: add linkprop commands to add and delete alternative ifnames") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zijie Huang Signed-off-by: Ren Wei Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/fabf02a70787d17299e4b3153eadffaf20d154b3.1789910973.git.milkory@outlook.com Signed-off-by: Jakub Kicinski --- net/ipv4/arp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c index d409f606aec0e1..60009d92e07103 100644 --- a/net/ipv4/arp.c +++ b/net/ipv4/arp.c @@ -1278,6 +1278,7 @@ int arp_ioctl(struct net *net, unsigned int cmd, void __user *arg) err = copy_from_user(&r, arg, sizeof(struct arpreq)); if (err) return -EFAULT; + r.arp_dev[IFNAMSIZ - 1] = '\0'; break; default: return -EINVAL; From 4eb3f195ef08c5acaed87958297e41cc49588dde Mon Sep 17 00:00:00 2001 From: Ivan Delalande Date: Fri, 18 Sep 2026 15:47:15 -0700 Subject: [PATCH 1163/1417] tg3: use random MAC address when tg3_get_device_address fails Some of the tg3 NICs we use (BCM57762) reset the SRAM MAC address to the placeholder address on link flaps, tg3_chip_reset, etc. We've typically fixed it from userspace, but since e4c00ba7274b ("tg3: replace placeholder MAC address with device property") was merged, tg3 just fails probe as we don't have a way to get it through the generic device_get_mac_address infrastructure as fallback on our systems. Make the driver assign a random address in this condition instead of being fatal for probe. Set deferred_probe_reason through dev_warn_probe if the address isn't yet available from the provider. Fixes: e4c00ba7274b ("tg3: replace placeholder MAC address with device property") Suggested-by: Jakub Kicinski Link: https://lore.kernel.org/netdev/20260909191751.651aa5c4@kernel.org/ Signed-off-by: Ivan Delalande Link: https://patch.msgid.link/20260918224715.GA654128@visor Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/tg3.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c index caa7a6caa6e2fa..8b6806a79edff8 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c @@ -17915,11 +17915,14 @@ static int tg3_init_one(struct pci_dev *pdev, err = tg3_get_device_address(tp, addr); if (err) { - dev_err(&pdev->dev, - "Could not obtain valid ethernet address, aborting\n"); - goto err_out_apeunmap; + dev_warn_probe(&pdev->dev, err, + "Could not obtain a valid ethernet address\n"); + if (err == -EPROBE_DEFER) + goto err_out_apeunmap; + eth_hw_addr_random(dev); + } else { + eth_hw_addr_set(dev, addr); } - eth_hw_addr_set(dev, addr); intmbx = MAILBOX_INTERRUPT_0 + TG3_64BIT_REG_LOW; rcvmbx = MAILBOX_RCVRET_CON_IDX_0 + TG3_64BIT_REG_LOW; From 0a7822e34a0bfde31b194ac3da3253e5032b44cc Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 21 Sep 2026 16:46:18 +0200 Subject: [PATCH 1164/1417] net: stmmac: clear stale buf->page after recycling on skb build failure In stmmac_rx(), when napi_build_skb() fails the descriptor page is recycled back to the page pool with page_pool_recycle_direct(), but buf->page is left pointing at the recycled page, unlike every other consumption site in the function which clears the pointer after handing the page away. With the stale pointer stmmac_rx_refill() skips the replacement allocation and programs the already-recycled page back into the RX descriptor. Clear buf->page on the napi_build_skb() failure path to keep the buffer lifecycle consistent with the other consumption sites. Fixes: df542f669307 ("net: stmmac: Switch to zero-copy in non-XDP RX path") Signed-off-by: Lorenzo Bianconi Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260921-stmmac-fix-napi-build-skb-error-v1-1-3d54bf6d9bb6@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index d5a984ad864f23..3f34d491c95920 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -5889,6 +5889,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) if (!skb) { page_pool_recycle_direct(rx_q->page_pool, buf->page); + buf->page = NULL; rx_dropped++; count++; goto drain_data; From 87cd6b717e4069dca34e0866e57eaeb44d3b173e Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Tue, 22 Sep 2026 05:49:56 +0900 Subject: [PATCH 1165/1417] net: ipv6: keep room for the mac header in dst_dev_overhead() The seg6, ioam6 and rpl lwtunnels size their skb_cow_head() request as the length they are about to push plus dst_dev_overhead(), then push the new headers and rebuild the mac header below them with skb_mac_header_rebuild(). That rebuild needs skb->mac_len of headroom, but dst_dev_overhead() leaves LL_RESERVED_SPACE() of the egress device, 16 bytes for plain Ethernet. Where the mac header is longer than that, as it is on ingress through a VLAN device with reorder_hdr off, the rebuild runs out of room: skb_set_mac_header(skb, -skb->mac_len) computes a negative offset, stores it unchecked in the u16 skb->mac_header, and the memmove that follows writes skb->mac_len bytes about 64 KB past skb->head. Forwarding plain ping6 traffic through such a device reproduces it on all five seg6 encapsulation modes and on the rpl and ioam6 inline paths; skb->mac_header comes back as 65534 on a 704-byte head. Return the larger of the two. The helper already returns skb->mac_len when it has no dst, so this only makes the other branch agree, and it covers every caller rather than each call site in turn. Fixes: 40475b63761a ("net: ipv6: seg6_iptunnel: mitigate 2-realloc issue") Fixes: dce525185bc9 ("net: ipv6: ioam6_iptunnel: mitigate 2-realloc issue") Fixes: 985ec6f5e623 ("net: ipv6: rpl_iptunnel: mitigate 2-realloc issue") Suggested-by: Andrea Mayer Signed-off-by: Yuya Kusakabe Reviewed-by: Justin Iurman Reviewed-by: Gabriel Goller Reviewed-by: Eric Dumazet Reviewed-by: Andrea Mayer Link: https://patch.msgid.link/20260922-seg6-maclen-headroom-v3-1-7b2f982ef79d@gmail.com Signed-off-by: Jakub Kicinski --- include/net/dst.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/net/dst.h b/include/net/dst.h index 307073eae7f834..dbedfe72e1fd15 100644 --- a/include/net/dst.h +++ b/include/net/dst.h @@ -455,7 +455,8 @@ static inline unsigned int dst_dev_overhead(struct dst_entry *dst, struct sk_buff *skb) { if (likely(dst)) - return LL_RESERVED_SPACE(dst->dev); + return max_t(unsigned int, skb->mac_len, + LL_RESERVED_SPACE(dst->dev)); return skb->mac_len; } From 0e2bec77ea62895416600c90588f593516572bca Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 21 Sep 2026 15:00:17 -0700 Subject: [PATCH 1166/1417] net: bcmgenet: fix 64-bit RTNL stats reading in ethtool on 32-bit systems When bcmgenet was converted to 64-bit statistics, STAT_RTNL members were switched to point into struct rtnl_link_stats64, whose fields are 64-bit (__u64) regardless of architecture. However, bcmgenet_get_ethtool_stats() retained a legacy check: if (sizeof(unsigned long) != sizeof(u32) && s->stat_sizeof == sizeof(unsigned long)) On 32-bit systems, sizeof(unsigned long) == sizeof(u32), causing this condition to evaluate to false. As a result, 64-bit RTNL stats fields were read via *(u32 *)p. On 32-bit Big-Endian systems (such as MIPS BE), this reads the high 32 bits and returns 0 until the counter exceeds 4GB; on 32-bit Little-Endian systems (such as 32-bit ARM), the value is truncated to 32 bits. Fix this by checking if s->stat_sizeof == sizeof(u64) so 64-bit fields are always read as 64-bit values. Fixes: 59aa6e3072aa ("net: bcmgenet: switch to use 64bit statistics") Reviewed-by: Nicolai Buchwitz Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260921220021.281418-2-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index ca62041efecd70..47a6c073c8d945 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -1346,9 +1346,8 @@ static void bcmgenet_get_ethtool_stats(struct net_device *dev, p = (char *)&stats64; p += s->stat_offset; - if (sizeof(unsigned long) != sizeof(u32) && - s->stat_sizeof == sizeof(unsigned long)) - data[i] = *(unsigned long *)p; + if (s->stat_sizeof == sizeof(u64)) + data[i] = *(u64 *)p; else data[i] = *(u32 *)p; } From 3aeaa609fda19c09d5298c9fedaaa3b6229601b5 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 21 Sep 2026 15:00:18 -0700 Subject: [PATCH 1167/1417] net: bcmgenet: initialize u64 stats seq counter for all queues bcmgenet_gstrings_stats statically defines ethtool statistics for queues 0 through GENET_MAX_MQ_CNT (4). However, bcmgenet_probe() only initialized the u64_stats_sync seq counter up to priv->hw_params->rx_queues and priv->hw_params->tx_queues. Since priv->hw_params->rx_queues is 0 across all hardware versions (and priv->hw_params->tx_queues is 0 on GENET V1), rings 1..4 have uninitialized u64_stats_sync structures. When ethtool -S is run on 32-bit kernels, bcmgenet_get_ethtool_stats() reads stats from rx_rings[1..4], causing lockdep warnings due to the uninitialized sequence counters. Initialize the sequence counters for all GENET_MAX_MQ_CNT + 1 queues. Fixes: ffc2c8c4a714 ("net: bcmgenet: Initialize u64 stats seq counter") Reviewed-by: Nicolai Buchwitz Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260921220021.281418-3-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 47a6c073c8d945..2ab37bb031ed29 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -4135,10 +4135,10 @@ static int bcmgenet_probe(struct platform_device *pdev) priv->rx_rings[i].rx_max_coalesced_frames = 1; /* Initialize u64 stats seq counter for 32bit machines */ - for (i = 0; i <= priv->hw_params->rx_queues; i++) + for (i = 0; i <= GENET_MAX_MQ_CNT; i++) { u64_stats_init(&priv->rx_rings[i].stats64.syncp); - for (i = 0; i <= priv->hw_params->tx_queues; i++) u64_stats_init(&priv->tx_rings[i].stats64.syncp); + } /* libphy will determine the link state */ netif_carrier_off(dev); From cbbc1aee7776c7fa1d89e6cb963a23e58c495dca Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 21 Sep 2026 15:00:19 -0700 Subject: [PATCH 1168/1417] net: bcmgenet: do not skip WoL power up on GENET V1 bcmgenet_power_up() had an early check for bcmgenet_has_ext(priv) before dispatching by power mode. GENET V1 does not have the EXT block (unlike GENET V2+), which causes bcmgenet_power_up() to immediately return 0. As a consequence, when waking up from GENET_POWER_WOL_MAGIC on GENET V1, bcmgenet_wol_power_up_cfg() is never invoked to disable the WoL clock, clear wake event masks, and restore normal PHY and MAC operations. Move the bcmgenet_has_ext() checks to the GENET_POWER_PASSIVE and GENET_POWER_CABLE_SENSE cases where the EXT registers are actually accessed, allowing GENET_POWER_WOL_MAGIC cleanup to execute on all hardware versions. Fixes: c3ae64ae0c08 ("net: bcmgenet: handle GENET_POWER_WOL_MAGIC") Reviewed-by: Nicolai Buchwitz Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260921220021.281418-4-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 2ab37bb031ed29..07032e4193ea14 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -1762,13 +1762,12 @@ static int bcmgenet_power_up(struct bcmgenet_priv *priv, int ret = 0; u32 reg; - if (!bcmgenet_has_ext(priv)) - return ret; - - reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT); - switch (mode) { case GENET_POWER_PASSIVE: + if (!bcmgenet_has_ext(priv)) + break; + + reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT); reg &= ~(EXT_PWR_DOWN_DLL | EXT_PWR_DOWN_BIAS | EXT_ENERGY_DET_MASK); if (GENET_IS_V5(priv) && !bcmgenet_has_ephy_16nm(priv)) { @@ -1792,8 +1791,12 @@ static int bcmgenet_power_up(struct bcmgenet_priv *priv, break; case GENET_POWER_CABLE_SENSE: + if (!bcmgenet_has_ext(priv)) + break; + /* enable APD */ if (!GENET_IS_V5(priv)) { + reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT); reg |= EXT_PWR_DN_EN_LD; bcmgenet_ext_writel(priv, reg, EXT_EXT_PWR_MGMT); } From 273941c85fc2632cd3e56ddff737b9245de7697d Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 21 Sep 2026 15:00:20 -0700 Subject: [PATCH 1169/1417] net: bcmgenet: validate Ethernet address in bcmgenet_set_mac_addr bcmgenet_set_mac_addr() did not check whether the provided MAC address is a valid Ethernet address before applying it. Userspace could configure an invalid address (such as all zeroes or a multicast address) while the interface is down. Add a call to is_valid_ether_addr() and return -EADDRNOTAVAIL if the MAC address is not valid. Fixes: 1c1008c793fa ("net: bcmgenet: add main driver file") Reviewed-by: Nicolai Buchwitz Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260921220021.281418-5-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 07032e4193ea14..011376a1678a19 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -3635,6 +3635,9 @@ static int bcmgenet_set_mac_addr(struct net_device *dev, void *p) if (netif_running(dev)) return -EBUSY; + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + eth_hw_addr_set(dev, addr->sa_data); return 0; From d64e277b955be4506931802837499b62c8f3968a Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 21 Sep 2026 15:00:21 -0700 Subject: [PATCH 1170/1417] net: bcmgenet: mask DMA_TIMEOUT_MASK when reading DMA_RING0_TIMEOUT bcmgenet_get_coalesce() reads DMA_RING0_TIMEOUT to calculate rx_coalesce_usecs without masking out bits outside DMA_TIMEOUT_MASK (16 bits). If upper bits are non-zero or contain status/flags, the computed value of rx_coalesce_usecs returned to userspace via ethtool becomes corrupted. Mask the register read with DMA_TIMEOUT_MASK before computing the timeout in microseconds. Fixes: 4a29645bfe6c ("net: bcmgenet: Implement RX coalescing control knobs") Reviewed-by: Nicolai Buchwitz Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260921220021.281418-6-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 011376a1678a19..21668e41b6964a 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -852,7 +852,8 @@ static int bcmgenet_get_coalesce(struct net_device *dev, ec->rx_max_coalesced_frames = bcmgenet_rdma_ring_readl(priv, 0, DMA_MBUF_DONE_THRESH); ec->rx_coalesce_usecs = - bcmgenet_rdma_readl(priv, DMA_RING0_TIMEOUT) * 8192 / 1000; + (bcmgenet_rdma_readl(priv, DMA_RING0_TIMEOUT) & + DMA_TIMEOUT_MASK) * 8192 / 1000; for (i = 0; i <= priv->hw_params->rx_queues; i++) { ring = &priv->rx_rings[i]; From 4bdee8060d1e4581624e68fbd369b1afb14df4bc Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 21 Sep 2026 20:09:14 -0400 Subject: [PATCH 1171/1417] net: airoha: npu: cancel wdt_work after releasing the WDT IRQ airoha_npu_remove() calls cancel_work_sync() on each core's wdt_work, but the watchdog IRQ that queues it is requested with devm_request_irq() and is freed only after .remove() returns. airoha_npu_wdt_handler() can therefore schedule_work() again once the cancel has returned. struct airoha_npu, which contains the work, is devm_kzalloc()'d and is freed in that same unwind, so the late work dereferences freed memory. Register the work with devm_work_autocancel() before devm_request_irq() and drop .remove(). Devres runs in reverse order, so the IRQ is freed before cancel_work_sync(), including when probe fails. A cancel left in .remove() cannot get that order. Initializing the work first also stops a pending watchdog interrupt from queuing an uninitialized work item. Probe currently calls INIT_WORK() only after devm_request_irq(). This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 23290c7bc190 ("net: airoha: Introduce Airoha NPU support") Cc: stable@vger.kernel.org # 6.15+ Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260922000914.542068-1-mhun512@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/airoha/airoha_npu.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_npu.c b/drivers/net/ethernet/airoha/airoha_npu.c index 5bb4817a898d1c..4d3195eb00f7ae 100644 --- a/drivers/net/ethernet/airoha/airoha_npu.c +++ b/drivers/net/ethernet/airoha/airoha_npu.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -751,12 +752,15 @@ static int airoha_npu_probe(struct platform_device *pdev) if (irq < 0) return irq; + err = devm_work_autocancel(dev, &core->wdt_work, + airoha_npu_wdt_work); + if (err) + return err; + err = devm_request_irq(dev, irq, airoha_npu_wdt_handler, IRQF_SHARED, "airoha-npu-wdt", core); if (err) return err; - - INIT_WORK(&core->wdt_work, airoha_npu_wdt_work); } /* wlan IRQ lines */ @@ -803,18 +807,8 @@ static int airoha_npu_probe(struct platform_device *pdev) return 0; } -static void airoha_npu_remove(struct platform_device *pdev) -{ - struct airoha_npu *npu = platform_get_drvdata(pdev); - int i; - - for (i = 0; i < ARRAY_SIZE(npu->cores); i++) - cancel_work_sync(&npu->cores[i].wdt_work); -} - static struct platform_driver airoha_npu_driver = { .probe = airoha_npu_probe, - .remove = airoha_npu_remove, .driver = { .name = "airoha-npu", .of_match_table = of_airoha_npu_match, From a3f315be9d30eeb6938d11fa17fd4b32d52f7c42 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Mon, 21 Sep 2026 11:18:59 +0800 Subject: [PATCH 1172/1417] ip_gre: Reject enabling collect metadata through changelink ipgre_netlink_parms() can enable collect_md on an existing GRE, GRETAP or ERSPAN device. Unlike newlink, changelink does not enforce metadata tunnel uniqueness. Converting a non-metadata device can therefore replace the metadata receive entry for another device of the same type in the same netns. Deleting either device then clears the shared entry, breaking metadata receive lookup for the surviving device. If parameter validation fails after collect_md is set, deleting the modified device can also clear an entry it never owned. Reject enabling metadata mode in both changelink callbacks before any encapsulation or tunnel parameters are modified. Allow requests that repeat the metadata attribute on an existing metadata device. Fixes: 2e15ea390e6f ("ip_gre: Add support to collect tunnel metadata.") Signed-off-by: Xuanqiang Luo Reviewed-by: Ido Schimmel Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260921031859.9283-1-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- net/ipv4/ip_gre.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index 82309efd417e0f..e4878e9aa63675 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -1464,6 +1464,12 @@ static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[], if (!rtnl_dev_link_net_capable(dev, t->net)) return -EPERM; + if (data && data[IFLA_GRE_COLLECT_METADATA] && !t->collect_md) { + NL_SET_ERR_MSG(extack, + "Enabling collect_md on an existing device is not supported"); + return -EOPNOTSUPP; + } + err = ipgre_newlink_encap_setup(dev, data); if (err) return err; @@ -1496,6 +1502,12 @@ static int erspan_changelink(struct net_device *dev, struct nlattr *tb[], if (!rtnl_dev_link_net_capable(dev, t->net)) return -EPERM; + if (data && data[IFLA_GRE_COLLECT_METADATA] && !t->collect_md) { + NL_SET_ERR_MSG(extack, + "Enabling collect_md on an existing device is not supported"); + return -EOPNOTSUPP; + } + err = ipgre_newlink_encap_setup(dev, data); if (err) return err; From 0f2fd31f63c65c409bd336af3a42d478a9207c1f Mon Sep 17 00:00:00 2001 From: Gilberto Conde Date: Mon, 21 Sep 2026 10:34:26 +0100 Subject: [PATCH 1173/1417] net: usb: qmi_wwan: add Quectel EG120K-EA Add support for the Quectel EG120K-EA LTE Cat.12 module (USB ID 2c7c:030b). Its QMI interface (interface 4) uses class/subclass/protocol ff/ff/ff like the other recent Quectel modules, so match it the same way. The product ID is shared with the EM060K, which the option driver already knows and uses to claim the serial interfaces. Without a qmi_wwan entry the data interface is left unbound. Tested on a GL.iNet GL-X2000, where the module is soldered down and enumerates at SuperSpeed: cdc-wdm0 and wwan0 appear and ModemManager brings up a data session. T: Bus=02 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=5000 MxCh= 0 D: Ver= 3.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 9 #Cfgs= 1 P: Vendor=2c7c ProdID=030b Rev= 5.04 S: Manufacturer=Quectel S: Product=EG120K-EA S: SerialNumber=45546267 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 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=00 Prot=40 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=ff Driver=qmi_wwan 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#=12 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=70 Driver=(none) E: Ad=89(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms Signed-off-by: Gilberto Conde Link: https://patch.msgid.link/20260921093426.2870266-1-gilbertorconde@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/qmi_wwan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index f51cf9cb9421c3..0e2ab567dd4011 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -1086,6 +1086,7 @@ static const struct usb_device_id products[] = { {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0125)}, /* Quectel EC25, EC20 R2.0 Mini PCIe */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x013d)}, /* Quectel RG660QB */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0306)}, /* Quectel EP06/EG06/EM06 */ + {QMI_MATCH_FF_FF_FF(0x2c7c, 0x030b)}, /* Quectel EM060K/EG120K-EA */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0512)}, /* Quectel EG12/EM12 */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0620)}, /* Quectel EM160R-GL */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0800)}, /* Quectel RM500Q-GL */ From 481506a756dcd828ef42391cb08f38d8d96d38fc Mon Sep 17 00:00:00 2001 From: Sanghyun Park Date: Fri, 18 Sep 2026 12:26:58 +0900 Subject: [PATCH 1174/1417] vxlan: use one headroom snapshot for neighbour replies vxlan_na_create() samples LL_RESERVED_SPACE() to size the reply skb and then samples it again to reserve headroom. A concurrent vxlan_changelink() can update needed_headroom between the two reads, creating a TOCTOU race. The second value can exceed the allocation and make the Ethernet header write out of bounds. The race is reproducible on the unpatched kernel. It occurred when vxlan_na_create() generated a neighbour reply while vxlan_changelink() changed the link headroom. KASAN caught a four-byte write two bytes beyond a 704-byte skbuff_small_head allocation. Snapshot the headroom once and use that value for both allocation and reservation. Fixes: 4b29dba9c085 ("vxlan: fix nonfunctional neigh_reduce()") Signed-off-by: Sanghyun Park Link: https://patch.msgid.link/20260918032842.502409-2-sanghyun.park.cnu@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index c1d54339fa2b15..3390e341d1e585 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -1958,13 +1958,15 @@ static struct sk_buff *vxlan_na_create(struct sk_buff *request, struct ipv6hdr *pip6; u8 *daddr; int na_olen = 8; /* opt hdr + ETH_ALEN for target */ + int headroom; int ns_olen; int i, len; if (dev == NULL || !pskb_may_pull(request, request->len)) return NULL; - len = LL_RESERVED_SPACE(dev) + sizeof(struct ipv6hdr) + + headroom = LL_RESERVED_SPACE(dev); + len = headroom + sizeof(struct ipv6hdr) + sizeof(*na) + na_olen + dev->needed_tailroom; reply = alloc_skb(len, GFP_ATOMIC); if (reply == NULL) @@ -1972,7 +1974,7 @@ static struct sk_buff *vxlan_na_create(struct sk_buff *request, reply->protocol = htons(ETH_P_IPV6); reply->dev = dev; - skb_reserve(reply, LL_RESERVED_SPACE(request->dev)); + skb_reserve(reply, headroom); skb_push(reply, sizeof(struct ethhdr)); skb_reset_mac_header(reply); From 4da3b7b8b50f3e2fde54a4c18a82a8e3f6223910 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Mon, 21 Sep 2026 17:03:57 +0200 Subject: [PATCH 1175/1417] net: xps: reject an out of range traffic class Only the entries below dev->num_tc are valid in dev->tc_to_txq[], and dev->prio_tc_map[] may only name classes below it. netdev_set_num_tc() lowers dev->num_tc without touching either array. netdev_txq_to_tc() walks all TC_MAX_QUEUE slots and netdev_get_prio_tc_map() returns the entry as it stands, so a leftover entry is handed out as a traffic class >= dev->num_tc. Taking that class from netdev_txq_to_tc(), __netif_set_xps_queue() rejects only a negative one and indexes an XPS map sized for dev->num_tc classes: tci = j * num_tc + tc; RCU_INIT_POINTER(new_dev_maps->attr_map[tci], map); attr_map[] holds nr_ids * num_tc entries and j runs over the ids named in the mask, so a class that is not below num_tc pushes tci past the end of the map for the last ids and the store overruns it. Any caller that lowers num_tc leaves such entries behind, and mqprio_destroy() tears down with netdev_set_num_tc(dev, 0) rather than netdev_reset_tc(). After mqprio with 8 classes then 1, tc_to_txq[1..7] still describe txq 1..7. The splat is from an XPS write to txq 2 on a veth with 8 rx queues: attr_map[] has 8 * 1 entries, tci = j + 2, and j == 6 stores one past the end of the 88-byte map: BUG: KASAN: slab-out-of-bounds in __netif_set_xps_queue (net/core/dev.c:2954) Write of size 8 at addr ffff88813016bc58 by task xps_oob/634 __netif_set_xps_queue (net/core/dev.c:2954) xps_rxqs_store (net/core/net-sysfs.c:1880) netdev_queue_attr_store (net/core/net-sysfs.c:1390) Allocated by task 634: __kmalloc_noprof (mm/slub.c:5439) __netif_set_xps_queue (net/core/dev.c:2937) The buggy address is located 0 bytes to the right of allocated 88-byte region [ffff88813016bc00, ffff88813016bc58) Reject a class the map has no room for. Fixes: 184c449f91fe ("net: Add support for XPS with QoS via traffic classes") Signed-off-by: Norbert Szetei Link: https://patch.msgid.link/162DD16F-54C6-444A-9E09-0B8CB3D591F2@doyensec.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index c67900354fa64b..0292a16e16c2cb 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -2901,7 +2901,7 @@ int __netif_set_xps_queue(struct net_device *dev, const unsigned long *mask, dev = netdev_get_tx_queue(dev, index)->sb_dev ? : dev; tc = netdev_txq_to_tc(dev, index); - if (tc < 0) + if (tc < 0 || tc >= num_tc) return -EINVAL; } From a0bb6fac53fa7cf1cadb487b43d4c9276a6b82e3 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Fri, 18 Sep 2026 21:29:15 +0800 Subject: [PATCH 1176/1417] sched/core: Account PSI IRQ time to the execution context, not the scheduling context psi_account_irqtime() has two callers which share rq->psi_irq_time, and they disagree about the context: __schedule() passes the outgoing rq->curr, sched_tick() passes rq->donor. Under proxy execution the donor is blocked on a mutex while rq->curr burns the CPU. The tick charges PSI_IRQ_FULL to the donor's cgroup and advances the timestamp, so the call from __schedule() then finds delta <= 0 and charges nothing. The delta is not counted twice, it lands on the wrong cgroup. Pass rq->curr, which is what the call read before commit af0c8b2bf67b ("sched: Split scheduler and execution contexts") renamed 'curr' to 'donor' across sched_tick(). Without CONFIG_SCHED_PROXY_EXEC the two rq members are a union, so this only changes anything where that option is set, and it depends on EXPERT. Fixes: af0c8b2bf67b ("sched: Split scheduler and execution contexts") Signed-off-by: Zhan Xusheng Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260918132915.1236312-1-zhanxusheng@xiaomi.com --- kernel/sched/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 0b846a13c62874..1fe40de6ebe3cb 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -5798,7 +5798,7 @@ void sched_tick(void) curr = rq->curr; donor = rq->donor; - psi_account_irqtime(rq, donor, NULL); + psi_account_irqtime(rq, curr, NULL); update_rq_clock(rq); hw_pressure = arch_scale_hw_pressure(cpu_of(rq)); From e47a1958e12abc3a17b5231a4f21c8f1bf662e08 Mon Sep 17 00:00:00 2001 From: Yuqi Xu Date: Sat, 19 Sep 2026 16:45:27 +0800 Subject: [PATCH 1177/1417] net: ipconfig: bound DHCP option construction ic_dhcp_init_options() appends the hostname (option 12), vendor-class (option 60) and client-ID (option 61) options into the fixed 312-byte bootp_pkt.exten[] buffer. Only the client-ID branch checked the remaining space; the hostname and vendor-class writes were unbounded. A 64-byte hostname together with the maximum 252-byte dhcpclass= identifier needs 18 + (2 + 64) + (2 + 252) = 338 of the 312 available bytes even before the terminating END marker, so the vendor-class memcpy runs past the end of exten[]. With CONFIG_FORTIFY_SOURCE this is reported as a field-spanning write and, when the kernel is booted with panic_on_warn=1, aborts boot with a panic. Route the optional options through a common helper that makes sure the option, its 2-byte header and the END marker all fit and drops an option that would not. Configurations with short options keep sending exactly the same bytes as before. Fixes: 130c0f47fdf9 ("ipconfig: send host-name in DHCP requests") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Yuqi Xu Reviewed-by: Ren Wei Reviewed-by: Simon Horman Link: https://patch.msgid.link/7808dfbfa2162dfd0b19f59aff5742d6e0db2abb.1789798023.git.xuyuqiabc@gmail.com Signed-off-by: Paolo Abeni --- net/ipv4/ipconfig.c | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/net/ipv4/ipconfig.c b/net/ipv4/ipconfig.c index 155db067eaecbe..1b8585404a41db 100644 --- a/net/ipv4/ipconfig.c +++ b/net/ipv4/ipconfig.c @@ -676,6 +676,24 @@ static const u8 ic_bootp_cookie[4] = { 99, 130, 83, 99 }; #ifdef IPCONFIG_DHCP +static bool __init +ic_dhcp_add_option(u8 **options, const u8 *end, u8 type, const void *value, + int len) +{ + u8 *e = *options; + + /* leave room for the option header and the END marker */ + if (len > U8_MAX || end - e < len + 3) + return false; + + *e++ = type; + *e++ = len; + memcpy(e, value, len); + *options = e + len; + + return true; +} + static void __init ic_dhcp_init_options(u8 *options, struct ic_device *d) { @@ -691,6 +709,7 @@ ic_dhcp_init_options(u8 *options, struct ic_device *d) 42, /* NTP servers */ }; u8 mt = (ic_servaddr == NONE) ? DHCPDISCOVER : DHCPREQUEST; + u8 *end = options + sizeof(((struct bootp_pkt *)0)->exten); u8 *e = options; int len; @@ -721,31 +740,19 @@ ic_dhcp_init_options(u8 *options, struct ic_device *d) e += sizeof(ic_req_params); if (ic_host_name_set) { - *e++ = 12; /* host-name */ len = strlen(utsname()->nodename); - *e++ = len; - memcpy(e, utsname()->nodename, len); - e += len; + ic_dhcp_add_option(&e, end, 12, utsname()->nodename, len); } if (*vendor_class_identifier) { - pr_info("DHCP: sending class identifier \"%s\"\n", - vendor_class_identifier); - *e++ = 60; /* Class-identifier */ len = strlen(vendor_class_identifier); - *e++ = len; - memcpy(e, vendor_class_identifier, len); - e += len; + if (ic_dhcp_add_option(&e, end, 60, vendor_class_identifier, len)) + pr_info("DHCP: sending class identifier \"%s\"\n", + vendor_class_identifier); } len = strlen(dhcp_client_identifier + 1); - /* the minimum length of identifier is 2, include 1 byte type, - * and can not be larger than the length of options - */ - if (len >= 1 && len < 312 - (e - options) - 1) { - *e++ = 61; - *e++ = len + 1; - memcpy(e, dhcp_client_identifier, len + 1); - e += len + 1; - } + /* the minimum length of identifier is 2, include 1 byte type */ + if (len >= 1) + ic_dhcp_add_option(&e, end, 61, dhcp_client_identifier, len + 1); *e++ = 255; /* End of the list */ } From 7b824c293a6b56de8285a97984c507cba56bc4c4 Mon Sep 17 00:00:00 2001 From: Brajesh Gupta Date: Tue, 22 Sep 2026 09:56:55 +0530 Subject: [PATCH 1178/1417] drm/imagination: Propagate map failures correctly from pvr_mmu_map_sgl() Map failure from pvr_mmu_map_sgl() interface was not returned correctly to pvr_mmu_map() interface. This resulted in pvr_mmu_map() interface to continue instead of returning an error to caller. Fix it by returning a proper error code from pvr_mmu_map_sgl() interface. Call stack for crash: [ 1179.286237] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000008 [ 1179.295067] Mem abort info: [ 1179.297877] ESR = 0x0000000096000004 [ 1179.301656] EC = 0x25: DABT (current EL), IL = 32 bits [ 1179.306987] SET = 0, FnV = 0 [ 1179.310048] EA = 0, S1PTW = 0 [ 1179.313198] FSC = 0x04: level 0 translation fault [ 1179.318096] Data abort info: [ 1179.320993] ISV = 0, ISS = 0x00000004, ISS2 = 0x00000000 [ 1179.326483] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 1179.331546] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 1179.336895] user pgtable: 4k pages, 48-bit VAs, pgdp=000000009822a000 [ 1179.343402] [0000000000000008] pgd=0000000000000000, p4d=0000000000000000 [ 1179.350243] Internal error: Oops: 0000000096000004 [#2] SMP [ 1179.355908] Modules linked in: powervr gpu_sched drm_shmem_helper drm_gpuvm drm_exec xhci_plat_hcd xhci_hcd dwc3 usbcore usb_common snd_soc_simple_card snd_soc_simple_card_utils dwc3_am62 at24 sa2ul sha512 libsha512 sha256 authenc sch_fq_codel fuse dm_mod ipv6 [ 1179.378992] CPU: 1 UID: 1000 PID: 680 Comm: deqp-vk Tainted: G D 6.17.0 #1 PREEMPT [ 1179.388120] Tainted: [D]=DIE [ 1179.390994] Hardware name: Texas Instruments AM625 SK (DT) [ 1179.396467] pstate: 00000005 (nzcv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 1179.403415] pc : pvr_mmu_op_context_unmap_curr_page+0x6c/0x134 [powervr] [ 1179.410140] lr : pvr_mmu_op_context_unmap_curr_page+0x58/0x134 [powervr] [ 1179.416848] sp : ffff8000839ab8c0 [ 1179.420153] x29: ffff8000839ab8c0 x28: 0000000000000001 x27: 000000008f386000 [ 1179.427283] x26: ffff000016d1df98 x25: 0000000000247000 x24: 00000000000001e6 [ 1179.434413] x23: 0000000000000002 x22: 000000000000ffff x21: 0000000000000247 [ 1179.441540] x20: 0000000000000245 x19: ffff000016d1df60 x18: 0000000000000002 [ 1179.448668] x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000001 [ 1179.455793] x14: 0000000000060810 x13: ffff80007fffffff x12: ffff000004190480 [ 1179.462921] x11: ffff8000853f7000 x10: ffff8000811ae000 x9 : ffff0000041900b8 [ 1179.470051] x8 : 0000000000000000 x7 : 00000000990c4001 x6 : 0000000000000007 [ 1179.477177] x5 : ffff000016d1df60 x4 : 0000000000000000 x3 : ffff00000a7d8000 [ 1179.484306] x2 : 00000000000001ff x1 : 0000000000000000 x0 : 0000000000000000 [ 1179.491433] Call trace: [ 1179.493872] pvr_mmu_op_context_unmap_curr_page+0x6c/0x134 [powervr] (P) [ 1179.500582] pvr_mmu_map+0x31c/0x388 [powervr] [ 1179.505027] pvr_vm_gpuva_map+0x40/0x88 [powervr] [ 1179.509732] __drm_gpuvm_sm_map+0x250/0x44c [drm_gpuvm] [ 1179.514952] drm_gpuvm_sm_map+0x48/0x5c [drm_gpuvm] [ 1179.519822] pvr_vm_bind_op_exec+0x64/0x70 [powervr] [ 1179.524785] pvr_vm_map+0x1f8/0x2a8 [powervr] [ 1179.529142] pvr_ioctl_vm_map+0x12c/0x188 [powervr] [ 1179.534018] drm_ioctl_kernel+0xb8/0x128 [ 1179.537941] drm_ioctl+0x21c/0x4ec [ 1179.541337] __arm64_sys_ioctl+0xac/0x108 [ 1179.545344] invoke_syscall+0x44/0x100 [ 1179.549091] el0_svc_common.constprop.0+0x40/0xe0 [ 1179.553790] do_el0_svc+0x1c/0x28 [ 1179.557106] el0_svc+0x34/0xf0 [ 1179.560159] el0t_64_sync_handler+0xd0/0xe4 [ 1179.564334] el0t_64_sync+0x198/0x19c [ 1179.567996] Code: 54000300 35000360 f9402261 79409a62 (f9400421) [ 1179.574081] ---[ end trace 0000000000000000 ]--- Fixes: ff5f643de0bf ("drm/imagination: Add GEM and VM related code") Reviewed-by: Alexandru Dadu Reviewed-by: Alessio Belle Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260922-mmu_fix-v4-1-12f1a871456a@imgtec.com Signed-off-by: Brajesh Gupta --- drivers/gpu/drm/imagination/pvr_mmu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/imagination/pvr_mmu.c b/drivers/gpu/drm/imagination/pvr_mmu.c index 3cac482e10347c..23261d9ad3fddd 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.c +++ b/drivers/gpu/drm/imagination/pvr_mmu.c @@ -12,6 +12,7 @@ #include "pvr_rogue_mmu_defs.h" #include +#include #include #include #include @@ -2553,7 +2554,9 @@ pvr_mmu_map_sgl(struct pvr_mmu_op_context *op_ctx, struct scatterlist *sgl, err_destroy_pages: memcpy(&op_ctx->curr_page, &ptr_copy, sizeof(op_ctx->curr_page)); - err = pvr_mmu_op_context_unmap_curr_page(op_ctx, page); + if (pvr_mmu_op_context_unmap_curr_page(op_ctx, page)) + drm_err(from_pvr_device(op_ctx->mmu_ctx->pvr_dev), + "%s : Failure in unmapping pages\n", __func__); return err; } From 0a8224058a5835297dcf4a46bbcd16f77a9fe424 Mon Sep 17 00:00:00 2001 From: Brajesh Gupta Date: Tue, 22 Sep 2026 09:56:56 +0530 Subject: [PATCH 1179/1417] drm/imagination: Fix page count for page table for map() interface The GPU virtual start address wasn't included in the calculation for the amount of page tables required for mapping a BO object in map() interface. It resulted in map failure later due to not enough pages at L0/L1 level. Update pvr_mmu_op_context_create() interface to pass device address as well to allow correct calculation for page table memory. If L0 tables cover 2MB (0x200000), the range defined by device address 0x80001ff000 (general heap at 2MB - 4KB) and size 0x2000 (two 4KB pages) requires two L0 pages to be mapped, but without the base address a range of 0x2000 computes to a single L0 page which is not enough. Fixes: ff5f643de0bf ("drm/imagination: Add GEM and VM related code") Reviewed-by: Alexandru Dadu Reviewed-by: Alessio Belle Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260922-mmu_fix-v4-2-12f1a871456a@imgtec.com Signed-off-by: Brajesh Gupta --- drivers/gpu/drm/imagination/pvr_mmu.c | 14 ++++++++------ drivers/gpu/drm/imagination/pvr_mmu.h | 2 +- drivers/gpu/drm/imagination/pvr_vm.c | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_mmu.c b/drivers/gpu/drm/imagination/pvr_mmu.c index 23261d9ad3fddd..62eae7fcd5a26a 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.c +++ b/drivers/gpu/drm/imagination/pvr_mmu.c @@ -2336,6 +2336,7 @@ void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx) * pvr_mmu_op_context_create() - Create an MMU op context. * @ctx: MMU context associated with owning VM context. * @sgt: Scatter gather table containing pages pinned for use by this context. + * @device_addr: Virtual device address at the start of the requested mapping. * @sgt_offset: Start offset of the requested device-virtual memory mapping. * @size: Size in bytes of the requested device-virtual memory mapping. For an * unmapping, this should be zero so that no page tables are allocated. @@ -2347,8 +2348,9 @@ void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx) */ struct pvr_mmu_op_context * pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, - u64 sgt_offset, u64 size) + u64 device_addr, u64 sgt_offset, u64 size) { + u64 start_addr = device_addr + sgt_offset; int err; struct pvr_mmu_op_context *op_ctx = kzalloc_obj(*op_ctx); @@ -2364,16 +2366,16 @@ pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, if (size) { /* * The number of page table objects we need to prealloc is - * indicated by the mapping size, start offset and the sizes + * indicated by the mapping size, start address and the sizes * of the areas mapped per PT or PD. The range calculation is * identical to that for the index into a table for a device * address, so we reuse those functions here. */ - const u32 l1_start_idx = pvr_page_table_l2_idx(sgt_offset); - const u32 l1_end_idx = pvr_page_table_l2_idx(sgt_offset + size); + const u32 l1_start_idx = pvr_page_table_l2_idx(start_addr); + const u32 l1_end_idx = pvr_page_table_l2_idx(start_addr + size); const u32 l1_count = l1_end_idx - l1_start_idx + 1; - const u32 l0_start_idx = pvr_page_table_l1_idx(sgt_offset); - const u32 l0_end_idx = pvr_page_table_l1_idx(sgt_offset + size); + const u32 l0_start_idx = pvr_page_table_l1_idx(start_addr); + const u32 l0_end_idx = pvr_page_table_l1_idx(start_addr + size); const u32 l0_count = l0_end_idx - l0_start_idx + 1; /* diff --git a/drivers/gpu/drm/imagination/pvr_mmu.h b/drivers/gpu/drm/imagination/pvr_mmu.h index a8ecd460168dcd..2c02d61ba0a29f 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.h +++ b/drivers/gpu/drm/imagination/pvr_mmu.h @@ -99,7 +99,7 @@ dma_addr_t pvr_mmu_get_root_table_dma_addr(struct pvr_mmu_context *ctx); void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx); struct pvr_mmu_op_context * pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, - struct sg_table *sgt, u64 sgt_offset, u64 size); + struct sg_table *sgt, u64 device_addr, u64 sgt_offset, u64 size); int pvr_mmu_map(struct pvr_mmu_op_context *op_ctx, u64 size, u64 flags, u64 device_addr); diff --git a/drivers/gpu/drm/imagination/pvr_vm.c b/drivers/gpu/drm/imagination/pvr_vm.c index ceb78694cd9873..55cc999f3708b6 100644 --- a/drivers/gpu/drm/imagination/pvr_vm.c +++ b/drivers/gpu/drm/imagination/pvr_vm.c @@ -276,7 +276,7 @@ pvr_vm_bind_op_map_init(struct pvr_vm_bind_op *bind_op, goto err_bind_op_fini; bind_op->mmu_op_ctx = - pvr_mmu_op_context_create(vm_ctx->mmu_ctx, sgt, offset, size); + pvr_mmu_op_context_create(vm_ctx->mmu_ctx, sgt, device_addr, offset, size); err = PTR_ERR_OR_ZERO(bind_op->mmu_op_ctx); if (err) { bind_op->mmu_op_ctx = NULL; @@ -318,7 +318,7 @@ pvr_vm_bind_op_unmap_init(struct pvr_vm_bind_op *bind_op, } bind_op->mmu_op_ctx = - pvr_mmu_op_context_create(vm_ctx->mmu_ctx, NULL, 0, 0); + pvr_mmu_op_context_create(vm_ctx->mmu_ctx, NULL, device_addr, 0, 0); err = PTR_ERR_OR_ZERO(bind_op->mmu_op_ctx); if (err) { bind_op->mmu_op_ctx = NULL; From 45585c3aa285854face65293acc95eff73063d6d Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 20 Sep 2026 11:43:29 +0800 Subject: [PATCH 1180/1417] drm/imagination: clamp freelist reconstruction requests The firmware reconstruction count controls accesses to the request's fixed freelist ID array and the copy into the fixed response array. Neither access currently bounds the count to those protocol arrays. Clamp the count to the request capacity, which is shared by the response layout, and use that count consistently for reconstruction and response publication. Keep the firmware recovery exchange instead of dropping an oversized request without a response, as discussed with the firmware maintainer. The issue was found by our static-analysis tool. Fixes: 6eedddab733b ("drm/imagination: Implement free list and HWRT create and destroy ioctls") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Reviewed-by: Alessio Belle Link: https://patch.msgid.link/20260920034329.16614-1-hppiscas@163.com Signed-off-by: Brajesh Gupta --- drivers/gpu/drm/imagination/pvr_free_list.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_free_list.c b/drivers/gpu/drm/imagination/pvr_free_list.c index e85cac83834c63..faf5e586d8dc17 100644 --- a/drivers/gpu/drm/imagination/pvr_free_list.c +++ b/drivers/gpu/drm/imagination/pvr_free_list.c @@ -8,6 +8,7 @@ #include "pvr_vm.h" #include +#include #include #include #include @@ -612,13 +613,21 @@ pvr_free_list_process_reconstruct_req(struct pvr_device *pvr_dev, }; struct rogue_fwif_freelists_reconstruction_data *resp = &resp_cmd.cmd_data.free_lists_reconstruction_data; + u32 count = min_t(u32, req->freelist_count, + ARRAY_SIZE(req->freelist_ids)); - for (u32 i = 0; i < req->freelist_count; i++) + if (count != req->freelist_count) { + drm_warn_once(from_pvr_device(pvr_dev), + "Requested reconstruction of %u freelists, limiting to %u\n", + req->freelist_count, count); + } + + for (u32 i = 0; i < count; i++) pvr_free_list_reconstruct(pvr_dev, req->freelist_ids[i]); - resp->freelist_count = req->freelist_count; + resp->freelist_count = count; memcpy(resp->freelist_ids, req->freelist_ids, - req->freelist_count * sizeof(resp->freelist_ids[0])); + count * sizeof(resp->freelist_ids[0])); WARN_ON(pvr_kccb_send_cmd(pvr_dev, &resp_cmd, NULL)); } From 00efbbd40bd5fd92c67b7cf1aab8904fa59a96f6 Mon Sep 17 00:00:00 2001 From: David Dai Date: Fri, 18 Sep 2026 16:11:55 -0500 Subject: [PATCH 1181/1417] bonding: crypto offload enabled, non-offload slave failover, rekey failed Create a bonding device (i.e. bond0) in active-backup mode, 2 slaves. Active slave: offload capable interface (i.e. eth1), primary interface. Backup slave: non-offload capable interface(i.e. eth2). Configure strongswan service swantl.conf child SA "hw_offload = crypto" Start strongswan service IPSec Crytpo Offload is enabled on top of bond0. i.e. ip xfrm state |grep offload crypto offload parameters: dev bond0 dir out mode crypto crypto offload parameters: dev bond0 dir in mode crypto Active slave eth1 takes adavantage of IPSec Crypto Offload capability. If active slave eth1 is down for any reason (i.e. eth1 link down): ip link set down dev eth1 non-offload capable interface eth2 failover to becomes active slave. The existing SAs can continue use software IPsec after failover. Traffic still keeps going properly. However if eth1 link had not recovered yet, strongswan service does new child SA rekey, or uses swanctl command to do new child SA rekey, it will fail because active slave eth2 doesn't support crypto offload. In bond_ipsec_add_sa routine, it returns -EINVAL now, which is treated as fatal error by xfrm_dev_state_add routine in kernel xfrm. To make the non-offload active slave survive the child SA rekey, need to make bond_ipsec_add_sa routine returns -EOPNOTSUPP instead when active slave doesn't support IPsec Crypto offload, the xfrm will gracefully fallback to create new SA using Software IPsec. Network traffic can keep going. After offload capable interface eth1 link is up, becomes active slave, next time strongswan child SA rekey will create a new SA which enables crypto offload again. Fixes: 18cb261afd7b ("bonding: support hardware encryption offload to slaves") Signed-off-by: David Dai Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260918211155.1664493-1-zdai@linux.ibm.com Signed-off-by: Paolo Abeni --- drivers/net/bonding/bond_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index a9bff7663eecad..de2489c3d9bf28 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -490,7 +490,7 @@ static int bond_ipsec_add_sa(struct net_device *bond_dev, !real_dev->xfrmdev_ops->xdo_dev_state_add || netif_is_bond_master(real_dev)) { NL_SET_ERR_MSG_MOD(extack, "Slave does not support ipsec offload"); - err = -EINVAL; + err = -EOPNOTSUPP; goto out; } From 36c2009d90f2210ef92e6f4f2850e8b57b09e754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gajdos=20Tam=C3=A1s?= Date: Mon, 21 Sep 2026 11:13:32 +0200 Subject: [PATCH 1182/1417] net: atl1c: fix soft lockup on out-of-range tpd_cons read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hardware can report an out-of-range tpd_cons (seen as 0xffff) while the PCIe link/MAC is resetting. An out-of-range value can never be reached and the loop below would spin forever. To avoid a soft lockup treat it as "nothing new to clean" instead. Reproduced on two machines, same NIC (Qualcomm Atheros AR8151 v2.0, 4-port), triggered by rebooting a Mikrotik CCR2004 PCIe card that the ports are directly linked to: - Ubuntu 26.04.1 LTS, kernel 7.0.0-31-generic. The link-flap precursor, before the lockup was captured with a full trace elsewhere: atl1c 0000:05:00.0 enp5s0f0: NETDEV WATCHDOG: CPU: 4: transmit queue 2 timed out 489984 ms atl1c 0000:05:00.0: MAC state machine can't be idle since disabled for 10ms second atl1c 0000:05:00.0: atl1c: enp5s0f0 NIC Link is Up<65535 Mbps Full Duplex> 65535 (0xffff) here is the same value tpd_cons reads back once the loop below gets stuck. - Proxmox VE, kernel 7.0.14-11-pve. Same NIC/trigger, this time caught by the soft lockup watchdog with a full stack trace: watchdog: BUG: soft lockup - CPU#12 stuck for 354s! [napi/eth%d-0:329] CPU: 12 UID: 0 PID: 329 Comm: napi/eth%d-0 Tainted: P O L 7.0.14-11-pve #1 PREEMPT(lazy) RIP: 0010:atl1c_clean_tx+0x142/0x2d0 [atl1c] Call Trace: __napi_poll+0x32/0x1e0 napi_threaded_poll_loop+0x286/0x2e0 napi_threaded_poll+0xfd/0x140 kthread+0xf7/0x130 ret_from_fork+0x2da/0x3a0 ret_from_fork_asm+0x1a/0x30 Fixes: 43250ddd75a35d ("atl1c: Atheros L1C Gigabit Ethernet driver") Cc: stable@vger.kernel.org Signed-off-by: Gajdos Tamás Link: https://patch.msgid.link/20260921091334.3571525-2-tamas@rimpianto.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/atheros/atl1c/atl1c_main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c index 7efa3fc257b395..e58f1d2c26bdfd 100644 --- a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c +++ b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c @@ -1602,6 +1602,9 @@ static int atl1c_clean_tx(struct napi_struct *napi, int budget) AT_READ_REGW(&adapter->hw, atl1c_qregs[tpd_ring->num].tpd_cons, &hw_next_to_clean); + if (unlikely(hw_next_to_clean >= tpd_ring->count)) + hw_next_to_clean = next_to_clean; + while (next_to_clean != hw_next_to_clean) { buffer_info = &tpd_ring->buffer_info[next_to_clean]; if (buffer_info->skb) { From 374bf9e4b90f979e052332c4faca2d745c491a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gajdos=20Tam=C3=A1s?= Date: Mon, 21 Sep 2026 11:13:33 +0200 Subject: [PATCH 1183/1417] net: atl1e: fix soft lockup on out-of-range hw_next_to_clean read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same issue as atl1c (see the first commit in this series, "net: atl1c: fix soft lockup on out-of-range tpd_cons read"): the hardware can report an out-of-range hw_next_to_clean (seen as 0xffff) while the PCIe link/MAC is resetting. An out-of-range value can never be reached and the loop below would spin forever. Treat it as "nothing new to clean" instead. Fixes: a6a5325239c202 ("atl1e: Atheros L1E Gigabit Ethernet driver") Cc: stable@vger.kernel.org Signed-off-by: Gajdos Tamás Link: https://patch.msgid.link/20260921091334.3571525-3-tamas@rimpianto.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/atheros/atl1e/atl1e_main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/atheros/atl1e/atl1e_main.c b/drivers/net/ethernet/atheros/atl1e/atl1e_main.c index 40290028580ba3..437989edb78022 100644 --- a/drivers/net/ethernet/atheros/atl1e/atl1e_main.c +++ b/drivers/net/ethernet/atheros/atl1e/atl1e_main.c @@ -1234,6 +1234,9 @@ static bool atl1e_clean_tx_irq(struct atl1e_adapter *adapter) u16 hw_next_to_clean = AT_READ_REGW(&adapter->hw, REG_TPD_CONS_IDX); u16 next_to_clean = atomic_read(&tx_ring->next_to_clean); + if (unlikely(hw_next_to_clean >= tx_ring->count)) + hw_next_to_clean = next_to_clean; + while (next_to_clean != hw_next_to_clean) { tx_buffer = &tx_ring->tx_buffer[next_to_clean]; if (tx_buffer->dma) { From 43e746821f5f5afbbf68e388bf9fbe221e03bfca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gajdos=20Tam=C3=A1s?= Date: Mon, 21 Sep 2026 11:13:34 +0200 Subject: [PATCH 1184/1417] net: atl1: fix soft lockup on out-of-range cmb_tpd_next_to_clean read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same issue as atl1c (see the first commit in this series, "net: atl1c: fix soft lockup on out-of-range tpd_cons read"): the hardware can report an out-of-range cmb_tpd_next_to_clean (seen as 0xffff) while the PCIe link/MAC is resetting. An out-of-range value can never be reached and the loop below would spin forever. Treat it as "nothing new to clean" instead. Fixes: f3cc28c797604f ("Add Attansic L1 ethernet driver.") Cc: stable@vger.kernel.org Signed-off-by: Gajdos Tamás Link: https://patch.msgid.link/20260921091334.3571525-4-tamas@rimpianto.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/atheros/atlx/atl1.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/atheros/atlx/atl1.c b/drivers/net/ethernet/atheros/atlx/atl1.c index 98a4d089270e41..957d5598dda5ed 100644 --- a/drivers/net/ethernet/atheros/atlx/atl1.c +++ b/drivers/net/ethernet/atheros/atlx/atl1.c @@ -2066,6 +2066,9 @@ static int atl1_intr_tx(struct atl1_adapter *adapter) sw_tpd_next_to_clean = atomic_read(&tpd_ring->next_to_clean); cmb_tpd_next_to_clean = le16_to_cpu(adapter->cmb.cmb->tpd_cons_idx); + if (unlikely(cmb_tpd_next_to_clean >= tpd_ring->count)) + cmb_tpd_next_to_clean = sw_tpd_next_to_clean; + while (cmb_tpd_next_to_clean != sw_tpd_next_to_clean) { buffer_info = &tpd_ring->buffer_info[sw_tpd_next_to_clean]; if (buffer_info->dma) { From 77b1718e39e5c9f6956fb60807326af20baf889d Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 21 Sep 2026 21:46:05 -0400 Subject: [PATCH 1185/1417] bna: prevent IOC timer rearm during teardown bna: prevent IOC timer rearm during teardown bnad_pci_remove() and the probe disable_ioceth path call timer_delete_sync() for ioc_timer, sem_timer and hb_timer, but not for iocpf_timer. bnad_iocpf_timeout() then takes bnad->bna_lock after free_netdev() has freed the struct bnad. Deleting iocpf_timer last does not fix this. sem_timer and iocpf_timer rearm each other: bnad_iocpf_sem_timeout() can arm iocpf_timer, and bnad_iocpf_timeout() arms sem_timer from bfa_ioc_hw_sem_get() when the semaphore is busy. timer_delete_sync() only waits out its own callback. bnad_ioceth_disable() can time out and leave that callback live. Shut all four IOC timers down with timer_shutdown_sync() on both paths, so a later mod_timer() is ignored. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 1d32f7696286 ("bna: IOC failure auto recovery fix") Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260922014605.588040-1-mhun512@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/brocade/bna/bnad.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/brocade/bna/bnad.c b/drivers/net/ethernet/brocade/bna/bnad.c index 8b75004ba7c9d4..55dfd48967851e 100644 --- a/drivers/net/ethernet/brocade/bna/bnad.c +++ b/drivers/net/ethernet/brocade/bna/bnad.c @@ -2571,6 +2571,22 @@ bnad_ioceth_disable(struct bnad *bnad) return err; } +/* + * The IOC timers rearm one another, so deleting one cannot stop a + * sibling callback from arming it again. Shut them down so a later + * mod_timer() is ignored. + */ +static void +bnad_ioc_timers_shutdown(struct bnad *bnad) +{ + struct bfa_ioc *ioc = &bnad->bna.ioceth.ioc; + + timer_shutdown_sync(&ioc->ioc_timer); + timer_shutdown_sync(&ioc->sem_timer); + timer_shutdown_sync(&ioc->hb_timer); + timer_shutdown_sync(&ioc->iocpf_timer); +} + static int bnad_ioceth_enable(struct bnad *bnad) { @@ -3727,9 +3743,7 @@ bnad_pci_probe(struct pci_dev *pdev, bnad_res_free(bnad, &bnad->mod_res_info[0], BNA_MOD_RES_T_MAX); disable_ioceth: bnad_ioceth_disable(bnad); - timer_delete_sync(&bnad->bna.ioceth.ioc.ioc_timer); - timer_delete_sync(&bnad->bna.ioceth.ioc.sem_timer); - timer_delete_sync(&bnad->bna.ioceth.ioc.hb_timer); + bnad_ioc_timers_shutdown(bnad); spin_lock_irqsave(&bnad->bna_lock, flags); bna_uninit(bna); spin_unlock_irqrestore(&bnad->bna_lock, flags); @@ -3770,9 +3784,7 @@ bnad_pci_remove(struct pci_dev *pdev) mutex_lock(&bnad->conf_mutex); bnad_ioceth_disable(bnad); - timer_delete_sync(&bnad->bna.ioceth.ioc.ioc_timer); - timer_delete_sync(&bnad->bna.ioceth.ioc.sem_timer); - timer_delete_sync(&bnad->bna.ioceth.ioc.hb_timer); + bnad_ioc_timers_shutdown(bnad); spin_lock_irqsave(&bnad->bna_lock, flags); bna_uninit(bna); spin_unlock_irqrestore(&bnad->bna_lock, flags); From db1761980ccc3b792dbd6e106c237d06108ea897 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Thu, 24 Sep 2026 13:40:15 +0800 Subject: [PATCH 1186/1417] ASoC: Intel: sof-function-topology-lib: fix sscanf type Change %d to %u as ssp_port is unsigned int. Fixes: 6736426f31e8 ("ASoC: Intel: sof-function-topology-lib: add I2S support for sof_sdw_get_tplg_files") Signed-off-by: Bard Liao Link: https://patch.msgid.link/20260924054015.2600784-1-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/common/sof-function-topology-lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/intel/common/sof-function-topology-lib.c b/sound/soc/intel/common/sof-function-topology-lib.c index 8f4fdf82d00f21..57e089f8e721dc 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.c +++ b/sound/soc/intel/common/sof-function-topology-lib.c @@ -121,7 +121,7 @@ static int get_ssp_tplg_dev(struct device *dev, struct snd_soc_dai_link *dai_lin { unsigned int ssp_port; - if (sscanf(dai_link->name, "SSP%d", &ssp_port) != 1) { + if (sscanf(dai_link->name, "SSP%u", &ssp_port) != 1) { dev_err(dev, "Can't get SSP port from dai_link->name %s\n", dai_link->name); return -EINVAL; } From 58eb1b3325edac42dc6df72c80962bd53a3c8ca7 Mon Sep 17 00:00:00 2001 From: Dongliang Qin Date: Tue, 22 Sep 2026 11:15:45 +0800 Subject: [PATCH 1187/1417] rds: ib: Clear the sg list when mapping an MR fails rds_ib_map_frmr() stores the caller's scatterlist in the MR before DMA mapping and registration can fail. On failure, __rds_rdma_map() unpins the pages and frees the scatterlist, but rds_ib_free_frmr() can still return the MR to the pool with the stale pointer set. This leaves the pool with a dangling scatterlist and can lead to local privilege escalation. KASAN detects the resulting use-after-free when the MR is later torn down: BUG: KASAN: slab-use-after-free in __rds_ib_teardown_mr Read of size 8 Call Trace: __rds_ib_teardown_mr rds_ib_unreg_frmr rds_ib_flush_mr_pool rds_ib_flush_mrs rds_free_mr rds_setsockopt Store the scatterlist in the MR only after DMA mapping succeeds. If DMA mapping fails, return directly while the MR fields remain clear; the caller keeps ownership of the scatterlist and its pinned pages. If a later registration step fails, unmap the scatterlist and clear the MR fields before returning. Fixes: 1659185fb4d0 ("RDS: IB: Support Fastreg MR (FRMR) memory registration mode") Cc: stable@vger.kernel.org Signed-off-by: Dongliang Qin Reviewed-by: Allison Henderson Link: https://patch.msgid.link/20260922031546.3874605-1-cccccccccccc777777@gmail.com Signed-off-by: Paolo Abeni --- net/rds/ib_frmr.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/net/rds/ib_frmr.c b/net/rds/ib_frmr.c index bd861191157b54..8397aa4a17ac87 100644 --- a/net/rds/ib_frmr.c +++ b/net/rds/ib_frmr.c @@ -204,19 +204,16 @@ static int rds_ib_map_frmr(struct rds_ib_device *rds_ibdev, */ rds_ib_teardown_mr(ibmr); - ibmr->sg = sg; - ibmr->sg_len = sg_len; - ibmr->sg_dma_len = 0; frmr->sg_byte_len = 0; - WARN_ON(ibmr->sg_dma_len); - ibmr->sg_dma_len = ib_dma_map_sg(dev, ibmr->sg, ibmr->sg_len, + ibmr->sg_dma_len = ib_dma_map_sg(dev, sg, sg_len, DMA_BIDIRECTIONAL); if (unlikely(!ibmr->sg_dma_len)) { pr_warn("RDS/IB: %s failed!\n", __func__); return -EBUSY; } - frmr->sg_byte_len = 0; + ibmr->sg = sg; + ibmr->sg_len = sg_len; frmr->dma_npages = 0; len = 0; @@ -264,6 +261,8 @@ static int rds_ib_map_frmr(struct rds_ib_device *rds_ibdev, ib_dma_unmap_sg(rds_ibdev->dev, ibmr->sg, ibmr->sg_len, DMA_BIDIRECTIONAL); ibmr->sg_dma_len = 0; + ibmr->sg = NULL; + ibmr->sg_len = 0; return ret; } From b16610e3770ef22fc8fcce818db0cd955332abf4 Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Thu, 24 Sep 2026 10:54:57 +0100 Subject: [PATCH 1188/1417] ASoC: SDCA: Set suspended flag after resuming within system suspend drv->suspended path was executed before system suspension instead of after, move it lower to correct it. Fixes: 7a5214f769c7 ("ASoC: SDCA: Add basic system suspend support") Signed-off-by: Maciej Strozek Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260924095458.2683755-2-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class_function.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index cc7045dc26e6b4..5039230280ad27 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -490,8 +490,6 @@ static int class_function_suspend(struct device *dev) struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); int ret; - drv->suspended = true; - /* Ensure runtime resume runs on resume */ ret = pm_runtime_resume_and_get(dev); if (ret) { @@ -499,6 +497,8 @@ static int class_function_suspend(struct device *dev) return ret; } + drv->suspended = true; + sdca_irq_disable(drv->function, drv->core->irq_info); ret = pm_runtime_force_suspend(dev); From 91094f9c86216898ce92141c26c8fec7e97544c0 Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Thu, 24 Sep 2026 10:54:58 +0100 Subject: [PATCH 1189/1417] ASoC: SDCA: Add better pm_runtime error handling in func probe Add a common error path in probe that balances the runtime PM reference. Fixes: 3af1815a2f9c ("ASoC: SDCA: Add basic SDCA function driver") Signed-off-by: Maciej Strozek Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260924095458.2683755-3-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class_function.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index 5039230280ad27..610931ae5531f8 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -388,20 +388,27 @@ static int class_function_probe(struct auxiliary_device *auxdev, ret = devm_pm_runtime_enable(dev); if (ret) - return ret; + goto err_pm; ret = class_function_boot(drv); if (ret) - return ret; + goto err_pm; ret = devm_snd_soc_register_component(dev, cmp_drv, dais, num_dais); - if (ret) - return dev_err_probe(dev, ret, "failed to register component\n"); + if (ret) { + dev_err_probe(dev, ret, "failed to register component\n"); + goto err_pm; + } pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; + +err_pm: + pm_runtime_put_sync(dev); + + return ret; } static void class_function_remove(struct auxiliary_device *auxdev) From c2de369c5c5b8599ca10fd5ca8d11fcd845c1331 Mon Sep 17 00:00:00 2001 From: Haseeb Malik Date: Mon, 21 Sep 2026 16:40:30 -0400 Subject: [PATCH 1190/1417] macsec: initialize SecY before registering the netdevice Creating a MACsec device with MAC offload over an LRO-capable lower device triggers a warning in rtmsg_ifinfo_build_skb() when IPv4 forwarding is enabled by default. register_netdevice() invokes inetdev_init(), which disables LRO and emits a NETDEV_FEAT_CHANGE notification. This reaches macsec_fill_info() before macsec_add_dev() initializes the SecY. key_len is still zero, so macsec_fill_info() returns -EMSGSIZE and trips the WARN_ON in rtmsg_ifinfo_build_skb(), even though the skb has enough space. Even without the warning, notifications during registration can report uninitialized SecY attributes, including the SCI. This ordering has existed since the driver was introduced. Initialize the SecY and apply the new-link attributes before registration. Move MAC address inheritance into macsec_newlink() so the SCI can also be initialized before registration-time notifications report it. Move the per-CPU statistics and metadata destination allocation into ndo_init(), and release partial allocations on failure. Fixes: c09440f7dcb3 ("macsec: introduce IEEE 802.1AE driver") Reported-by: syzbot+f2f6312ad1b5a0bfe316@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f2f6312ad1b5a0bfe316 Suggested-by: Sabrina Dubroca Link: https://lists.openwall.net/linux-kernel/2026/08/19/552 Signed-off-by: Haseeb Malik Reviewed-by: Sabrina Dubroca Link: https://patch.msgid.link/20260921-fix-macsec-net-v3-1-accf94f93f5e@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/macsec.c | 84 +++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c index 6f9f3aceffaad3..78a19b1346321a 100644 --- a/drivers/net/macsec.c +++ b/drivers/net/macsec.c @@ -3539,6 +3539,22 @@ static int macsec_dev_init(struct net_device *dev) if (err) return err; + err = -ENOMEM; + macsec->stats = netdev_alloc_pcpu_stats(struct pcpu_secy_stats); + if (!macsec->stats) + goto destroy_gro_cells; + + macsec->secy.tx_sc.stats = + netdev_alloc_pcpu_stats(struct pcpu_tx_sc_stats); + if (!macsec->secy.tx_sc.stats) + goto free_secy_stats; + + macsec->secy.tx_sc.md_dst = metadata_dst_alloc(0, METADATA_MACSEC, + GFP_KERNEL); + if (!macsec->secy.tx_sc.md_dst) + goto free_tx_sc_stats; + macsec->secy.tx_sc.md_dst->u.macsec_info.sci = macsec->secy.sci; + macsec_inherit_tso_max(dev); dev->hw_features = real_dev->hw_features & MACSEC_OFFLOAD_FEATURES; @@ -3551,8 +3567,6 @@ static int macsec_dev_init(struct net_device *dev) macsec_set_head_tail_room(dev); - if (is_zero_ether_addr(dev->dev_addr)) - eth_hw_addr_inherit(dev, real_dev); if (is_zero_ether_addr(dev->broadcast)) memcpy(dev->broadcast, real_dev->broadcast, dev->addr_len); @@ -3560,6 +3574,14 @@ static int macsec_dev_init(struct net_device *dev) netdev_hold(real_dev, &macsec->dev_tracker, GFP_KERNEL); return 0; + +free_tx_sc_stats: + free_percpu(macsec->secy.tx_sc.stats); +free_secy_stats: + free_percpu(macsec->stats); +destroy_gro_cells: + gro_cells_destroy(&macsec->gro_cells); + return err; } static void macsec_dev_uninit(struct net_device *dev) @@ -4116,26 +4138,11 @@ static sci_t dev_to_sci(struct net_device *dev, __be16 port) return make_sci(dev->dev_addr, port); } -static int macsec_add_dev(struct net_device *dev, sci_t sci, u8 icv_len) +static void macsec_init_secy(struct net_device *dev, sci_t sci, u8 icv_len) { struct macsec_dev *macsec = macsec_priv(dev); struct macsec_secy *secy = &macsec->secy; - macsec->stats = netdev_alloc_pcpu_stats(struct pcpu_secy_stats); - if (!macsec->stats) - return -ENOMEM; - - secy->tx_sc.stats = netdev_alloc_pcpu_stats(struct pcpu_tx_sc_stats); - if (!secy->tx_sc.stats) - return -ENOMEM; - - secy->tx_sc.md_dst = metadata_dst_alloc(0, METADATA_MACSEC, GFP_KERNEL); - if (!secy->tx_sc.md_dst) - /* macsec and secy percpu stats will be freed when unregistering - * net_device in macsec_free_netdev() - */ - return -ENOMEM; - if (sci == MACSEC_UNDEF_SCI) sci = dev_to_sci(dev, MACSEC_PORT_ES); @@ -4149,15 +4156,12 @@ static int macsec_add_dev(struct net_device *dev, sci_t sci, u8 icv_len) secy->xpn = DEFAULT_XPN; secy->sci = sci; - secy->tx_sc.md_dst->u.macsec_info.sci = sci; secy->tx_sc.active = true; secy->tx_sc.encoding_sa = DEFAULT_ENCODING_SA; secy->tx_sc.encrypt = DEFAULT_ENCRYPT; secy->tx_sc.send_sci = DEFAULT_SEND_SCI; secy->tx_sc.end_station = false; secy->tx_sc.scb = false; - - return 0; } static struct lock_class_key macsec_netdev_addr_lock_key; @@ -4220,6 +4224,24 @@ static int macsec_newlink(struct net_device *dev, if (rx_handler && rx_handler != macsec_handle_frame) return -EBUSY; + if (is_zero_ether_addr(dev->dev_addr)) + eth_hw_addr_inherit(dev, real_dev); + + if (data && data[IFLA_MACSEC_SCI]) + sci = nla_get_sci(data[IFLA_MACSEC_SCI]); + else if (data && data[IFLA_MACSEC_PORT]) + sci = dev_to_sci(dev, nla_get_be16(data[IFLA_MACSEC_PORT])); + else + sci = dev_to_sci(dev, MACSEC_PORT_ES); + + /* Registration can notify listeners before returning. */ + macsec_init_secy(dev, sci, icv_len); + if (data) { + err = macsec_changelink_common(dev, data); + if (err) + return err; + } + err = register_netdevice(dev); if (err < 0) return err; @@ -4232,31 +4254,11 @@ static int macsec_newlink(struct net_device *dev, if (err < 0) goto unregister; - /* need to be already registered so that ->init has run and - * the MAC addr is set - */ - if (data && data[IFLA_MACSEC_SCI]) - sci = nla_get_sci(data[IFLA_MACSEC_SCI]); - else if (data && data[IFLA_MACSEC_PORT]) - sci = dev_to_sci(dev, nla_get_be16(data[IFLA_MACSEC_PORT])); - else - sci = dev_to_sci(dev, MACSEC_PORT_ES); - if (rx_handler && sci_exists(real_dev, sci)) { err = -EBUSY; goto unlink; } - err = macsec_add_dev(dev, sci, icv_len); - if (err) - goto unlink; - - if (data) { - err = macsec_changelink_common(dev, data); - if (err) - goto del_dev; - } - /* If h/w offloading is available, propagate to the device */ if (macsec_is_offloaded(macsec)) { const struct macsec_ops *ops; From 15814c01ac57643edf1662a076d2961332f277a5 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 23 Sep 2026 21:44:30 -0400 Subject: [PATCH 1191/1417] drm/amd/display: Bump frame warning limit for all builds of dml Some configs with gcc are also now affected. Signed-off-by: Alex Deucher (cherry picked from commit 76b9706e7fa1a6e374703170128b1be2f590cda7) --- drivers/gpu/drm/amd/display/dc/dml/Makefile | 6 +----- drivers/gpu/drm/amd/display/dc/dml2_0/Makefile | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml/Makefile b/drivers/gpu/drm/amd/display/dc/dml/Makefile index 79eeb3721b9967..91465ac05c9717 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml/Makefile @@ -36,11 +36,7 @@ ifneq ($(CONFIG_FRAME_WARN),0) frame_warn_limit := 3072 endif else - ifeq ($(CONFIG_CC_IS_CLANG),y) - frame_warn_limit := 3072 - else - frame_warn_limit := 2048 - endif + frame_warn_limit := 3072 endif ifeq ($(call test-lt, $(CONFIG_FRAME_WARN), $(frame_warn_limit)),y) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile index 4d682e92df1d3c..39ee2d1999dd1b 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile @@ -35,7 +35,7 @@ ifneq ($(CONFIG_FRAME_WARN),0) frame_warn_limit := 3072 endif else - frame_warn_limit := 2056 + frame_warn_limit := 3072 endif ifeq ($(call test-lt, $(CONFIG_FRAME_WARN), $(frame_warn_limit)),y) From d6ec384c87cc851cfd13bb18c99ce351ccee6192 Mon Sep 17 00:00:00 2001 From: Fang Xieyan Date: Mon, 21 Sep 2026 20:54:41 +0800 Subject: [PATCH 1192/1417] net/sched: act_ife: validate metadata length before decoding skbmark_decode(), skbprio_decode() and skbtcindex_decode() read fixed-size values from the TLV payload without validating its length. A malformed IFE frame can declare a shorter payload, causing the decoders to consume bytes beyond the declared metadata value: [TLV type=IFE_META_SKBMARK len=4] -> dlen == 0, but decode reads 4 bytes The decoder may therefore set skb metadata from unintended input. Validate the payload length before decoding and return -EINVAL for invalid lengths. Read the values with get_unaligned_be32() and get_unaligned_be16(), as TLV payloads are not guaranteed to be aligned. Teach tcf_ife_decode() to log a decoder error separately from an unknown metaid; both are counted as overlimits and decoding continues with the remaining metadata. The metadata length issue was found by an automated audit of the IFE decode path at v6.18-rc7 and reproduced with a userspace sanitizer model of the decode path. Compile-tested on x86_64 with defconfig and NET_ACT_IFE=y: act_ife.o and the three act_meta_*.o build warning-free. Fixes: 084e2f6566d2 ("Support to encoding decoding skb mark on IFE action") Fixes: 200e10f46936 ("Support to encoding decoding skb prio on IFE action") Fixes: 408fbc22ef1e ("net sched ife action: Introduce skb tcindex metadata encap decap") Assisted-by: Hawkeye:GLM-5.3-flash Assisted-by: Qoder:Qwen3.8-Max Signed-off-by: Fang Xieyan Link: https://patch.msgid.link/20260921125441.81459-1-fangxy@xiaopeng.com Signed-off-by: Paolo Abeni --- net/sched/act_ife.c | 17 ++++++++++++----- net/sched/act_meta_mark.c | 6 ++++-- net/sched/act_meta_skbprio.c | 6 ++++-- net/sched/act_meta_skbtcindex.c | 6 ++++-- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c index 9cea71fc1db3d4..2afd68983ece40 100644 --- a/net/sched/act_ife.c +++ b/net/sched/act_ife.c @@ -737,6 +737,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a, u8 *curr_data; u16 mtype; u16 dlen; + int ret; curr_data = ife_tlv_meta_decode(tlv_data, ifehdr_end, &mtype, &dlen, NULL); @@ -745,13 +746,19 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a, return TC_ACT_SHOT; } - if (find_decode_metaid(skb, p, mtype, dlen, curr_data)) { - /* abuse overlimits to count when we receive metadata - * but dont have an ops for it + ret = find_decode_metaid(skb, p, mtype, dlen, curr_data); + if (ret < 0) { + /* abuse overlimits to count metadata we cannot + * decode: no ops for it, or the decoder rejected it */ - pr_info_ratelimited("Unknown metaid %d dlen %d\n", - mtype, dlen); qstats_cpu_overlimit_inc(ife->common.cpu_qstats); + + if (ret == -ENOENT) + pr_info_ratelimited("Unknown metaid %d dlen %d\n", + mtype, dlen); + else + pr_info_ratelimited("Failed to decode metaid %d dlen %d err %d\n", + mtype, dlen, ret); } } diff --git a/net/sched/act_meta_mark.c b/net/sched/act_meta_mark.c index ea0573cb8b2d6c..e2f61b22bf0f87 100644 --- a/net/sched/act_meta_mark.c +++ b/net/sched/act_meta_mark.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -28,9 +29,10 @@ static int skbmark_encode(struct sk_buff *skb, void *skbdata, static int skbmark_decode(struct sk_buff *skb, void *data, u16 len) { - u32 ifemark = *(u32 *)data; + if (len != sizeof(u32)) + return -EINVAL; - skb->mark = ntohl(ifemark); + skb->mark = get_unaligned_be32(data); return 0; } diff --git a/net/sched/act_meta_skbprio.c b/net/sched/act_meta_skbprio.c index 2df3133ce5adc9..5cdb57931eab4b 100644 --- a/net/sched/act_meta_skbprio.c +++ b/net/sched/act_meta_skbprio.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -33,9 +34,10 @@ static int skbprio_encode(struct sk_buff *skb, void *skbdata, static int skbprio_decode(struct sk_buff *skb, void *data, u16 len) { - u32 ifeprio = *(u32 *)data; + if (len != sizeof(u32)) + return -EINVAL; - skb->priority = ntohl(ifeprio); + skb->priority = get_unaligned_be32(data); return 0; } diff --git a/net/sched/act_meta_skbtcindex.c b/net/sched/act_meta_skbtcindex.c index 44547caead4690..8803710c09058d 100644 --- a/net/sched/act_meta_skbtcindex.c +++ b/net/sched/act_meta_skbtcindex.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -28,9 +29,10 @@ static int skbtcindex_encode(struct sk_buff *skb, void *skbdata, static int skbtcindex_decode(struct sk_buff *skb, void *data, u16 len) { - u16 ifetc_index = *(u16 *)data; + if (len != sizeof(u16)) + return -EINVAL; - skb->tc_index = ntohs(ifetc_index); + skb->tc_index = get_unaligned_be16(data); return 0; } From 7c9f391ec89cb621d7af375ace2eb9a6248e5b9d Mon Sep 17 00:00:00 2001 From: Christian Lamparter Date: Mon, 21 Sep 2026 18:28:17 +0200 Subject: [PATCH 1193/1417] net: emac: move setting of netops to fix crash fixes the following crash on driver initialization: |BUG: Kernel NULL pointer dereference on read at 0x00000158 |Faulting instruction address: 0xc0566b40 |Oops: Kernel access of bad area, sig: 11 [#1] |BE PAGE_SIZE=4K PowerPC 44x Platform |Modules linked in: |CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Tainted: GW 7.3.0-rc3+ #1 |Tainted: [W]=WARN |Hardware name: MyBook Live APM821XX 0x12c41c83 PowerPC 44x Platform |NIP: c0566b40 LR: c05648f8 CTR: c04c1e9c |REGS: c1053a20 TRAP: 0300 Tainted: GW (7.3.0-rc3+) |MSR: 0002b000 CR: 24008808 XER: 00000000 |DEAR: 00000158 ESR: 00000000 |GPR00: c05648f8 c1053b10 c1063600 c1030000 c5ab3000 00000000 [...] |GPR08: 00000002 00000000 00000000 c1053b40 84002808 00000000 [...] |GPR16: cfffd210 00000002 c0beafcc cfffc960 00000000 c1030644 [...] |GPR24: c0beafbc c1037000 00000000 0000000a 00000000 c1030000 [...] |NIP [c0566b40] phy_link_topo_add_phy+0x2c/0x1d0 |LR [c05648f8] phy_attach_direct+0x1a4/0x368 |Call Trace: |[c1053b10] [c0811e04] klist_put+0x54/0xb4 (unreliable) |[c1053b40] [c05648f8] phy_attach_direct+0x1a4/0x368 |[c1053b70] [c0564ae8] phy_connect_direct+0x2c/0x60 |[c1053b90] [c056c7a8] of_phy_connect+0x50/0x74 |[c1053bc0] [c0572a88] emac_probe+0xd50/0x119c |[c1053c90] [c04cb770] platform_probe+0x74/0xa4 |[c1053cb0] [c04c8f08] really_probe+0x120/0x2b0 |[c1053cd0] [c04c9254] __driver_probe_device+0x1bc/0x1fc |[c1053d00] [c04c9334] driver_probe_device+0x38/0xa8 |[c1053d30] [c04c9560] __driver_attach+0xf4/0x10c |[c1053d50] [c04c6af8] bus_for_each_dev+0x68/0xd0 |[c1053d90] [c04c7c34] bus_add_driver+0xcc/0x1ec |[c1053dc0] [c04c9f6c] driver_register+0xcc/0x110 |[c1053de0] [c0aa3f40] emac_init+0x1c4/0x200 This bug showed up starting with v7.3-rc1. At the NIP in phy_link_topo_add_phy() is a netdev_need_ops_lock() check. This was added by the following commit ded86da4bbb7 ("net: ethtool: relax ethnl_req_get_phydev() locking assertion") The bug shows up because at the time of_phy_connect() was called, the netdev_ops were *not yet* determined. My fix is to move the code that sets netdev_ops+commac.ops+ethtool_ops further up as emac_init_config() derives that by looking at the device-tree and sets the required dev->phy_mode accordingly. During review, the Sashiko bot's AI stated that the commac assignment became a dead store. Great catch! To keep the original behavior as-is, one mentioned option "set dev->commac.ops = &emac_commac_ops only in the non-gige case?" sounded like a great plan. So the dev->commac.ops assignment for the non-gige-case moves into the else block. This patch was tested on a WD MyBook Live (RGMII). the device now works again. Fixes: ded86da4bbb7 ("net: ethtool: relax ethnl_req_get_phydev() locking assertion") Signed-off-by: Christian Lamparter Reviewed-by: Nicolai Buchwitz Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/49cd7343bc0e507c022071f3e2b5662b053dca73.1790007431.git.chunkeey@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/ibm/emac/core.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/ibm/emac/core.c b/drivers/net/ethernet/ibm/emac/core.c index 1d46cf6c2c127a..e7043523457c14 100644 --- a/drivers/net/ethernet/ibm/emac/core.c +++ b/drivers/net/ethernet/ibm/emac/core.c @@ -3044,6 +3044,15 @@ static int emac_probe(struct platform_device *ofdev) if (err) goto err_gone; + if (emac_phy_supports_gige(dev->phy_mode)) { + ndev->netdev_ops = &emac_gige_netdev_ops; + dev->commac.ops = &emac_commac_sg_ops; + } else { + ndev->netdev_ops = &emac_netdev_ops; + dev->commac.ops = &emac_commac_ops; + } + ndev->ethtool_ops = &emac_ethtool_ops; + dev->emacp = devm_platform_ioremap_resource(ofdev, 0); if (IS_ERR(dev->emacp)) { err = PTR_ERR(dev->emacp); @@ -3076,7 +3085,6 @@ static int emac_probe(struct platform_device *ofdev) dev->mdio_instance = platform_get_drvdata(dev->mdio_dev); /* Register with MAL */ - dev->commac.ops = &emac_commac_ops; dev->commac.dev = dev; dev->commac.tx_chan_mask = MAL_CHAN_MASK(dev->mal_tx_chan); dev->commac.rx_chan_mask = MAL_CHAN_MASK(dev->mal_rx_chan); @@ -3144,12 +3152,6 @@ static int emac_probe(struct platform_device *ofdev) ndev->features |= ndev->hw_features | NETIF_F_RXCSUM; } ndev->watchdog_timeo = 5 * HZ; - if (emac_phy_supports_gige(dev->phy_mode)) { - ndev->netdev_ops = &emac_gige_netdev_ops; - dev->commac.ops = &emac_commac_sg_ops; - } else - ndev->netdev_ops = &emac_netdev_ops; - ndev->ethtool_ops = &emac_ethtool_ops; /* MTU range: 46 - 1500 or whatever is in OF */ ndev->min_mtu = EMAC_MIN_MTU; From e66cf1625ec4a3fe68346119f371def713fd0a4d Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 23 Sep 2026 20:25:49 +0900 Subject: [PATCH 1194/1417] smb: client: use finish_no_open() for non-regular inodes An O_CREAT open can find an existing symlink or another non-regular inode. cifs_atomic_open() calls finish_open() on it and attaches a cifsFileInfo. Symlink inodes have no CIFS release operation, so the dentry reference held by cifsFileInfo is leaked. FMODE_OPENED also prevents the VFS from following the symlink. Track whether cifs_do_create() returned an open server handle. For non-regular inodes, close the handle if present, remove the pending open, and call finish_no_open() so the VFS can continue the lookup. Do not set FMODE_CREATED unless a regular file was opened. For O_NOFOLLOW with __O_REGULAR, return -ELOOP before the VFS's -EFTYPE check. Defer closing a legacy POSIX handle on a non-regular inode until after inode lookup. This avoids closing it again if lookup fails. Fixes: d2c127197dfc ("cifs: implement i_op->atomic_open()") Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: stable@vger.kernel.org --- fs/smb/client/dir.c | 52 +++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 6fa6d48fdfd30f..1a56fa4d0e8905 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -199,7 +199,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, struct tcon_link *tlink, unsigned int oflags, umode_t mode, __u32 *oplock, struct cifs_fid *fid, struct cifs_open_info_data *buf, - struct inode **inode) + struct inode **inode, bool *opened) { int rc = -ENOENT; int create_options = CREATE_NOT_DIR; @@ -216,6 +216,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, __le32 lease_flags = 0; *inode = NULL; + *opened = false; *oplock = 0; if (tcon->ses->server->oplocks) *oplock = REQ_OPLOCK; @@ -232,6 +233,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, oflags, oplock, &fid->netfid, xid); switch (rc) { case 0: + *opened = true; if (newinode == NULL) { /* query inode info */ goto cifs_create_get_file_info; @@ -253,11 +255,9 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, /* * The server may allow us to open things like * FIFOs, but the client isn't set up to deal - * with that. If it's not a regular file, just - * close it and proceed as if it were a normal - * lookup. + * with that. Keep the handle until the caller + * can finish the lookup. */ - CIFSSMBClose(xid, tcon, fid->netfid); goto cifs_create_get_file_info; } /* success, no need to query */ @@ -384,6 +384,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, } return rc; } + *opened = true; if (rdwr_for_fscache == 2) cifs_invalidate_cache(dir, FSCACHE_INVAL_DIO_WRITE); @@ -475,7 +476,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, return rc; out_err: - if (server->ops->close) + if (*opened && server->ops->close) server->ops->close(xid, tcon, fid); if (newinode) iput(newinode); @@ -487,7 +488,7 @@ static int cifs_do_create(struct inode *dir, struct dentry *direntry, unsigned int oflags, umode_t mode, __u32 *oplock, struct cifs_fid *fid, struct cifs_open_info_data *buf, - struct inode **inode) + struct inode **inode, bool *opened) { void *page = alloc_dentry_path(); const char *full_path; @@ -496,10 +497,11 @@ static int cifs_do_create(struct inode *dir, struct dentry *direntry, full_path = build_path_from_dentry(direntry, page); if (IS_ERR(full_path)) { rc = PTR_ERR(full_path); + *opened = false; } else { rc = __cifs_do_create(dir, direntry, full_path, xid, tlink, oflags, mode, oplock, - fid, buf, inode); + fid, buf, inode, opened); } free_dentry_path(page); return rc; @@ -529,6 +531,8 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry, struct inode *inode; unsigned int xid; __u32 oplock; + bool is_regular; + bool opened; int rc; if (unlikely(cifs_forced_shutdown(cifs_sb))) @@ -581,12 +585,26 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry, cifs_add_pending_open(&fid, tlink, &open); rc = cifs_do_create(dir, direntry, xid, tlink, oflags, mode, - &oplock, &fid, &buf, &inode); + &oplock, &fid, &buf, &inode, &opened); if (rc) { cifs_del_pending_open(&open); goto out; } + is_regular = S_ISREG(inode->i_mode); + if (!is_regular || !opened) { + if (opened && server->ops->close) + server->ops->close(xid, tcon, &fid); + cifs_del_pending_open(&open); + if (S_ISLNK(inode->i_mode) && + (oflags & (O_NOFOLLOW | __O_REGULAR)) == + (O_NOFOLLOW | __O_REGULAR) && !(oflags & O_EXCL)) { + iput(inode); + rc = -ELOOP; + goto out; + } + } + if (d_in_lookup(direntry)) { alias = d_splice_alias(inode, direntry); if (!IS_ERR_OR_NULL(alias)) @@ -595,9 +613,15 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry, d_instantiate(direntry, inode); } - if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) + if (is_regular && opened && + (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) file->f_mode |= FMODE_CREATED; + if (!is_regular || !opened) { + rc = finish_no_open(file, NULL); + goto out; + } + rc = finish_open(file, direntry, generic_file_open); if (rc) { if (server->ops->close) @@ -660,6 +684,7 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *dir, struct inode *inode; struct cifs_fid fid; __u32 oplock; + bool opened; struct cifs_open_info_data buf = {}; cifs_dbg(FYI, "cifs_create parent inode = 0x%p name is: %pd and dentry = 0x%p\n", @@ -682,10 +707,10 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *dir, server->ops->new_lease_key(&fid); rc = cifs_do_create(dir, direntry, xid, tlink, oflags, - mode, &oplock, &fid, &buf, &inode); + mode, &oplock, &fid, &buf, &inode, &opened); if (!rc) { d_instantiate(direntry, inode); - if (server->ops->close) + if (opened && server->ops->close) server->ops->close(xid, tcon, &fid); } @@ -1078,6 +1103,7 @@ int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, struct inode *inode; unsigned int xid; __u32 oplock; + bool opened; int namelen; int rc; @@ -1116,7 +1142,7 @@ int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, namelen = scnprintf(name, namesize, CIFS_TMPNAME_PREFIX "%x", atomic_inc_return(&cifs_tmpcounter)); rc = __cifs_do_create(dir, dentry, path, xid, tlink, oflags, - mode, &oplock, &fid, NULL, &inode); + mode, &oplock, &fid, NULL, &inode, &opened); if (!rc) { rc = d_mark_tmpfile_name(file, &QSTR_LEN(name, namelen)); if (rc) { From c3a66e5f5bab3912e9f84223c5a982bf4333d5a1 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Thu, 24 Sep 2026 19:23:20 +0900 Subject: [PATCH 1195/1417] bpf: Zero-fill other CPUs when BPF_F_CPU creates a per-cpu hash element pcpu_init_value() initializes the per-cpu area of a newly created [lru_]percpu_hash element. The area is recycled, so when the value comes from a BPF program (onallcpus == false) it writes the running CPU's slot and zeroes the rest. bpf_percpu_hash_update() passes onallcpus == true, which delegates to pcpu_copy_value(). pcpu_copy_value() writes only the CPU named in map_flags when BPF_F_CPU is set, so on the create path the other slots keep the recycled element's values: update(k1, 0xdeadc0de, BPF_F_ALL_CPUS) every CPU holds 0xdeadc0de delete(k1) element back on the freelist update(k2, 0xc0ffee, BPF_F_CPU | 0) creates, writes CPU 0 only lookup(k2) CPU 0 0xc0ffee, rest 0xdeadc0de Zero-fill the other CPUs on that arm too. Fixes: c6936161fd55 ("bpf: Add BPF_F_CPU and BPF_F_ALL_CPUS flags support for percpu_hash and lru_percpu_hash maps") Signed-off-by: Donggeun Yoo Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260924102321.2120434-2-donggeunyoo.kernel@gmail.com --- kernel/bpf/hashtab.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index 4f495dcbf670c3..f9464e566f1094 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -1054,14 +1054,17 @@ static void pcpu_init_value(struct bpf_htab *htab, void __percpu *pptr, /* When not setting the initial value on all cpus, zero-fill element * values for other cpus. Otherwise, bpf program has no way to ensure * known initial values for cpus other than current one - * (onallcpus=false always when coming from bpf prog). + * (onallcpus=false always when coming from bpf prog, + * map_flags & BPF_F_CPU when coming from syscall but setting + * only one cpu). */ - if (!onallcpus) { - int current_cpu = raw_smp_processor_id(); + if (!onallcpus || (map_flags & BPF_F_CPU)) { + int init_cpu = (map_flags & BPF_F_CPU) ? map_flags >> 32 : + raw_smp_processor_id(); int cpu; for_each_possible_cpu(cpu) { - if (cpu == current_cpu) + if (cpu == init_cpu) copy_map_value(&htab->map, per_cpu_ptr(pptr, cpu), value); else /* Since elem is preallocated, we cannot touch special fields */ zero_map_value(&htab->map, per_cpu_ptr(pptr, cpu)); From 3422808f4e959266ea7790101ac90b8341f48226 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Thu, 24 Sep 2026 19:23:21 +0900 Subject: [PATCH 1196/1417] selftests/bpf: Test per-cpu initialization of a BPF_F_CPU created element The existing cpu_flag subtests always prime a key with BPF_F_ALL_CPUS before any BPF_F_CPU write, so the create path is never covered. Add a subtest that creates the element with BPF_F_CPU on a map with max_entries 1, so the key can only reuse the element the previous key released, and check that the CPUs the update did not name read back zero. Run it for PERCPU_HASH preallocated and BPF_F_NO_PREALLOC, whose per-cpu areas come from different allocators, and for LRU_PERCPU_HASH. Under BPF_F_NO_PREALLOC the reuse is only guaranteed on the cpu that ran the delete, so pin the thread across the pair, and name a cpu other than that one in map_flags. Signed-off-by: Donggeun Yoo Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260924102321.2120434-3-donggeunyoo.kernel@gmail.com --- .../selftests/bpf/prog_tests/percpu_alloc.c | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c b/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c index a72ae0b29f6e96..7b4a1e24363b59 100644 --- a/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c +++ b/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c @@ -1,4 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include #include #include "cgroup_helpers.h" #include "percpu_alloc_array.skel.h" @@ -350,6 +352,103 @@ static void test_lru_percpu_hash_cpu_flag(void) test_percpu_map_cpu_flag(BPF_MAP_TYPE_LRU_PERCPU_HASH); } +/* + * A BPF_F_CPU update that creates an element must zero the value on the other + * cpus, rather than leave them holding whatever the recycled element last + * contained. max_entries is 1 so the second key can only reuse the element + * the first one released. + */ +static void test_percpu_map_cpu_flag_create(enum bpf_map_type map_type, __u32 map_flags) +{ + LIBBPF_OPTS(bpf_map_create_opts, opts, .map_flags = map_flags); + const u32 stale = 0xDEADC0DE, fresh = 0xC0FFEE; + int nr_cpus, cpu, map_fd, err, key; + int pinned_cpu, value_cpu; + cpu_set_t old_mask, new_mask; + bool restore_mask = false; + u32 value; + u64 flags; + + nr_cpus = libbpf_num_possible_cpus(); + if (!ASSERT_GT(nr_cpus, 0, "libbpf_num_possible_cpus")) + return; + + if (nr_cpus < 2) { + test__skip(); + return; + } + + map_fd = bpf_map_create(map_type, "cpu_flag_create", sizeof(key), sizeof(value), 1, &opts); + if (!ASSERT_GE(map_fd, 0, "bpf_map_create")) + return; + + /* NO_PREALLOC recycles per cpu, so keep the delete and the create on one cpu. */ + err = sched_getaffinity(0, sizeof(old_mask), &old_mask); + if (!ASSERT_OK(err, "sched_getaffinity")) + goto out; + + pinned_cpu = sched_getcpu(); + if (!ASSERT_GE(pinned_cpu, 0, "sched_getcpu")) + goto out; + + CPU_ZERO(&new_mask); + CPU_SET(pinned_cpu, &new_mask); + err = sched_setaffinity(0, sizeof(new_mask), &new_mask); + if (!ASSERT_OK(err, "sched_setaffinity")) + goto out; + restore_mask = true; + + value_cpu = pinned_cpu ? 0 : 1; + + key = 1; + value = stale; + err = bpf_map_update_elem(map_fd, &key, &value, BPF_F_ALL_CPUS); + if (!ASSERT_OK(err, "bpf_map_update_elem all_cpus")) + goto out; + + err = bpf_map_delete_elem(map_fd, &key); + if (!ASSERT_OK(err, "bpf_map_delete_elem")) + goto out; + + key = 2; + value = fresh; + flags = (u64)value_cpu << 32 | BPF_F_CPU; + err = bpf_map_update_elem(map_fd, &key, &value, flags); + if (!ASSERT_OK(err, "bpf_map_update_elem specified cpu")) + goto out; + + for (cpu = 0; cpu < nr_cpus; cpu++) { + value = 0; + flags = (u64)cpu << 32 | BPF_F_CPU; + err = bpf_map_lookup_elem_flags(map_fd, &key, &value, flags); + if (!ASSERT_OK(err, "bpf_map_lookup_elem_flags specified cpu")) + goto out; + if (!ASSERT_EQ(value, cpu == value_cpu ? fresh : 0, "value on specified cpu")) + goto out; + } + +out: + if (restore_mask) + sched_setaffinity(0, sizeof(old_mask), &old_mask); + close(map_fd); +} + +static void test_percpu_hash_cpu_flag_create(void) +{ + test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_PERCPU_HASH, 0); +} + +static void test_percpu_hash_cpu_flag_create_malloc(void) +{ + test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_PERCPU_HASH, BPF_F_NO_PREALLOC); +} + +static void test_lru_percpu_hash_cpu_flag_create(void) +{ + /* lru without prealloc is -ENOTSUPP, so there is no malloc variant */ + test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_LRU_PERCPU_HASH, 0); +} + static void test_percpu_cgroup_storage_cpu_flag(void) { struct percpu_alloc_array *skel = NULL; @@ -454,6 +553,12 @@ void test_percpu_alloc(void) test_percpu_hash_cpu_flag(); if (test__start_subtest("cpu_flag_lru_percpu_hash")) test_lru_percpu_hash_cpu_flag(); + if (test__start_subtest("cpu_flag_create_percpu_hash")) + test_percpu_hash_cpu_flag_create(); + if (test__start_subtest("cpu_flag_create_percpu_hash_malloc")) + test_percpu_hash_cpu_flag_create_malloc(); + if (test__start_subtest("cpu_flag_create_lru_percpu_hash")) + test_lru_percpu_hash_cpu_flag_create(); if (test__start_subtest("cpu_flag_percpu_cgroup_storage")) test_percpu_cgroup_storage_cpu_flag(); if (test__start_subtest("cpu_flag_array")) From c2cdef41e0b4d8ed23a5b41e6ad4e64594e055e4 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 18 Sep 2026 04:50:19 +0300 Subject: [PATCH 1197/1417] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621 The core and io supplies are only requested for ID_MT7530: both the devm_regulator_get() in probe and the regulator_enable() in mt7530_setup() are guarded by the switch id, but mt7530_remove() disables them unconditionally. On an MT7621 or an MT7531 both pointers are still NULL from devm_kzalloc(), so rmmod or a sysfs unbind calls regulator_disable() on NULL. Fixes: ddda1ac116c8 ("net: dsa: mt7530: support the 7530 switch on the Mediatek MT7621 SoC") Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260918015020.2518315-2-f@lex.la Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530-mdio.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/net/dsa/mt7530-mdio.c b/drivers/net/dsa/mt7530-mdio.c index 784dd58a71589f..de42f70afcfa12 100644 --- a/drivers/net/dsa/mt7530-mdio.c +++ b/drivers/net/dsa/mt7530-mdio.c @@ -227,15 +227,17 @@ mt7530_remove(struct mdio_device *mdiodev) if (!priv) return; - ret = regulator_disable(priv->core_pwr); - if (ret < 0) - dev_err(priv->dev, - "Failed to disable core power: %d\n", ret); - - ret = regulator_disable(priv->io_pwr); - if (ret < 0) - dev_err(priv->dev, "Failed to disable io pwr: %d\n", - ret); + if (priv->id == ID_MT7530) { + ret = regulator_disable(priv->core_pwr); + if (ret < 0) + dev_err(priv->dev, + "Failed to disable core power: %d\n", ret); + + ret = regulator_disable(priv->io_pwr); + if (ret < 0) + dev_err(priv->dev, "Failed to disable io pwr: %d\n", + ret); + } mt7530_remove_common(priv); From 0d80ba0a204c6a16bd7778b50de578dff107c0fe Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 18 Sep 2026 04:50:20 +0300 Subject: [PATCH 1198/1417] net: dsa: mt7530: leave the MDIO IRQ mappings to regmap-irq mt7530_remove_common() disposes the per-PHY interrupt mappings from .remove, but the regmap-irq chip that owns the domain is devm-registered, so its parent interrupt is only freed once .remove has returned. The switch's own regmap-irq thread can therefore still dispatch on a mapping that is already gone: irq_find_mapping() returns 0, irq_to_desc() returns NULL and handle_nested_irq() locks desc->lock without checking it. The attached PHYs have not given those interrupts back yet either, which the kernel warns about a moment before the fault. regmap_del_irq_chip() disposes the same mappings itself, after freeing the parent interrupt and before removing the domain, so there is nothing left for the driver to do here. Until it runs the descriptors stay alive, and a late dispatch on one of them is harmless: dsa_unregister_switch() has freed the PHY handlers by then, so handle_nested_irq() finds no action and returns. Fixes: 254f6b272e3b ("dsa: mt7530: Utilize REGMAP_IRQ for interrupt handling") Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260918015020.2518315-3-f@lex.la Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 3e61eb3c2b1e2d..96832852c65a55 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -3593,9 +3593,6 @@ EXPORT_SYMBOL_GPL(mt7530_probe_common); void mt7530_remove_common(struct mt7530_priv *priv) { - if (priv->irq_domain) - mt7530_free_mdio_irq(priv); - dsa_unregister_switch(priv->ds); mutex_destroy(&priv->reg_mutex); From 26cc0e69cce062cd3aa6fae33074684669c35a71 Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Mon, 21 Sep 2026 05:10:01 +0000 Subject: [PATCH 1199/1417] mctp: route: iterate socket tag list in mctp_lookup_prealloc_tag() When a socket transmits a packet with MCTP_TAG_PREALLOC set, mctp_lookup_prealloc_tag() iterates over the per-netns &mns->keys list and matches netid, req_tag, peer_addr, and manual_alloc, without checking whether tmp->sk == &msk->sk. This allows any MCTP socket in the same network namespace to use and consume another socket's preallocated tag. Iterate the socket's own tag list (&msk->keys via sklist) instead of the namespace-wide &mns->keys list in mctp_lookup_prealloc_tag(), ensuring that only tags allocated by msk are matched. Tested in QEMU against Linux 7.3.0-rc3 by allocating a manual tag (0x18) on socket A via SIOCMCTPALLOCTAG for peer EID 9 and sending a 4-byte message with MCTP_TAG_PREALLOC from socket B in the same network namespace. On the unfixed kernel, sendto(sock_b) using socket A's preallocated tag succeeds (ret = 4); with this patch applied, sendto(sock_b) fails with -ENOENT (errno = 2) while sendto(sock_a) succeeds (ret = 4). Fixes: 63ed1aab3d40 ("mctp: Add SIOCMCTP{ALLOC,DROP}TAG ioctls for tag control") Suggested-by: Jeremy Kerr Cc: stable@vger.kernel.org Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260921051002.1656692-1-benquike@gmail.com Signed-off-by: Jakub Kicinski --- net/mctp/route.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mctp/route.c b/net/mctp/route.c index b19c63a5691a87..e7c95eeacb486f 100644 --- a/net/mctp/route.c +++ b/net/mctp/route.c @@ -825,7 +825,7 @@ static struct mctp_sk_key *mctp_lookup_prealloc_tag(struct mctp_sock *msk, spin_lock_irqsave(&mns->keys_lock, flags); - hlist_for_each_entry(tmp, &mns->keys, hlist) { + hlist_for_each_entry(tmp, &msk->keys, sklist) { if (tmp->net != netid) continue; From fe99bbeee5c5dbd3abc30721a8079ced59649d97 Mon Sep 17 00:00:00 2001 From: Yilin Zhang Date: Thu, 24 Sep 2026 12:49:00 +0800 Subject: [PATCH 1200/1417] tcp: fix use-after-free of retransmit_skb_hint in tcp_send_synack() When tcp_send_synack() replaces the cloned SYN skb at the head of the retransmit queue with a copy, it frees the original with tcp_rtx_queue_unlink_and_free() and only repairs tp->highest_sack. tp->retransmit_skb_hint keeps pointing at the freed skbuff_fclone_cache object. The dangling hint is read in tcp_verify_retransmit_hint() and used as the root of the rbtree walk in tcp_xmit_retransmit_queue(). An unprivileged TFO client (sendmsg(MSG_FASTOPEN)) can arm the hint with an attacker-supplied ICMP fragmentation-needed message, after which a simultaneous open frees the armed SYN skb: BUG: KASAN: slab-use-after-free in tcp_mark_skb_lost (net/ipv4/tcp_input.c:1316) Read of size 4 at addr ffff88800604d928 by task swapper/1/0 Call Trace: tcp_mark_skb_lost (net/ipv4/tcp_input.c:1316) tcp_simple_retransmit (net/ipv4/tcp_input.c:3158) tcp_v4_err (net/ipv4/tcp_ipv4.c:587) Sync the hint to the copy. Fixes: c31b70c9968f ("tcp: Add logic to check for SYN w/ data in tcp_simple_retransmit") Reported-by: Kimi Security Team Tested-by: Weiming Shi Signed-off-by: Yilin Zhang Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/8a9dff4063a2745653b7e88ceb745d75efa16e68.1790224474.git.yilinzhang@moonshot.ai Signed-off-by: Jakub Kicinski --- net/ipv4/tcp_output.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index d960e3de7d50d0..e0c392e29de5a4 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -3886,6 +3886,7 @@ void tcp_send_active_reset(struct sock *sk, enum sk_rst_reason reason) */ int tcp_send_synack(struct sock *sk) { + struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *skb; skb = tcp_rtx_queue_head(sk); @@ -3903,6 +3904,8 @@ int tcp_send_synack(struct sock *sk) if (!nskb) return -ENOMEM; INIT_LIST_HEAD(&nskb->tcp_tsorted_anchor); + if (skb == tp->retransmit_skb_hint) + tp->retransmit_skb_hint = nskb; tcp_highest_sack_replace(sk, skb, nskb); tcp_rtx_queue_unlink_and_free(skb, sk); __skb_header_release(nskb); From 1765a153d985c231357145e26798f9408db10e42 Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Thu, 24 Sep 2026 17:15:36 +0800 Subject: [PATCH 1201/1417] cgroup/pids: Restore pids.events notifications in local mode A fork rejected by the pids controller increments the counter reported by pids.events. When local event accounting is selected, however, pids_event() returns after notifying only events_local_file, leaving pids.events pollers asleep. On legacy hierarchies, pids.events.local does not exist. With pids_localevents, pids.events reports the same local counter. In both cases, pids.events changes without generating a notification. This can be reproduced with a pids_localevents mount: mkdir /tmp/test mount -t cgroup2 -o pids_localevents none /tmp/test mkdir /tmp/test/t echo 1 > /tmp/test/t/pids.max cat /tmp/test/t/pids.events # max 0 timeout 3 inotifywait -e modify /tmp/test/t/pids.events & sh -c 'echo $$ > /tmp/test/t/cgroup.procs; (true &)' 2>/dev/null wait cat /tmp/test/t/pids.events # max 1 Without this patch, inotifywait times out without reporting an event. Notify pids.events before returning from the local event path. Fixes: 3f26a885a068 ("cgroup/pids: Add pids.events.local") Cc: stable@vger.kernel.org # v6.11+ Signed-off-by: Guopeng Zhang Signed-off-by: Tejun Heo --- kernel/cgroup/pids.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kernel/cgroup/pids.c b/kernel/cgroup/pids.c index ecbb839d2acbae..78cdc0558d0c54 100644 --- a/kernel/cgroup/pids.c +++ b/kernel/cgroup/pids.c @@ -253,6 +253,11 @@ static void pids_event(struct pids_cgroup *pids_forking, } if (!cgroup_subsys_on_dfl(pids_cgrp_subsys) || cgrp_dfl_root.flags & CGRP_ROOT_PIDS_LOCAL_EVENTS) { + /* + * pids.events reports the local counter on legacy hierarchies + * and when pids_localevents is enabled. + */ + cgroup_file_notify(&p->events_file); cgroup_file_notify(&p->events_local_file); return; } From f75f21ef36285e5f56ee0c428bd2909ee81165b9 Mon Sep 17 00:00:00 2001 From: Ming Wang Date: Sun, 20 Sep 2026 15:44:59 +0800 Subject: [PATCH 1202/1417] net: usb: cdc_mbim: add MeiG Smart SRM821 to ZLP whitelist The MeiG Smart SRM821 5G module (0x2dee:0x4d53) crashes and drops off the USB bus when it receives a Zero Length Packet (ZLP) after sending or receiving an NTB of exactly 16384 bytes (tx_max). According to the MBIM specification, devices do not require a ZLP if the NTB size is exactly dwNtbOutMaxSize. However, the cdc_mbim driver defaults to sending ZLPs for devices not explicitly whitelisted to accommodate non-conformant hardware. This default behavior breaks the strictly conformant MeiG SRM821 module. Add this device to the ZLP conformance whitelist (cdc_mbim_info) so the driver will pad the NTB to avoid sending ZLPs, preventing the device firmware from crashing. Cc: stable@vger.kernel.org Signed-off-by: Ming Wang Link: https://patch.msgid.link/20260920074500.826121-1-wangming01@loongson.cn Signed-off-by: Jakub Kicinski --- drivers/net/usb/cdc_mbim.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/usb/cdc_mbim.c b/drivers/net/usb/cdc_mbim.c index 877fb0ed7d3d71..a7010a0664c7d4 100644 --- a/drivers/net/usb/cdc_mbim.c +++ b/drivers/net/usb/cdc_mbim.c @@ -635,6 +635,11 @@ static const struct usb_device_id mbim_devs[] = { .driver_info = (unsigned long)&cdc_mbim_info, }, + /* MeiG Smart SRM821 ZLP conformance */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d53, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&cdc_mbim_info, + }, + /* Some Huawei devices, ME906s-158 (12d1:15c1) and E3372 * (12d1:157d), are known to fail unless the NDP is placed * after the IP packets. Applying the quirk to all Huawei From a940003f44e7e441c228151dd212642152700ec8 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 21 Sep 2026 01:20:44 +0300 Subject: [PATCH 1203/1417] net: phylink: record the PHY only once bringup cannot fail phylink_bringup_phy() stores the PHY in pl->phydev before its last fallible step: on a MAC whose phylink ops implement LPI, phy_eee_rx_clock_stop() can fail with a real MDIO error. The callers unwind with phy_detach(), which knows nothing about pl->phydev, so a pointer to a PHY that is no longer attached outlives the failed connect. What that costs depends on how the caller got here. phylink_connect_phy() goes through phylink_attach_phy(), which refuses to attach while pl->phydev is set, turning a transient MDIO error into a permanent -EBUSY. The SFP path is worse than that: sfp_sm_probe_phy() answers the failure with phy_device_remove() and phy_device_free(), and it assigns sfp->mod_phy only past that error return, so nothing clears pl->phydev and it is left pointing at a freed phy_device that phylink_resolve() and the ethtool helpers go on reading. phylink_fwnode_phy_connect() has no such check, so a later connect overwrites the stale pointer and hides the problem. A disconnect does not: phylink_disconnect_phy() hands that pointer to phy_disconnect(), and the second phy_detach() on the same PHY drops references the first one already released. Found while making a DSA port survive a PHY whose driver arrives after the switch probes: keeping the port across a failed connect and retrying is what makes this window reachable. Publish the pointer after the last call that can fail instead of unwinding it afterwards. Nothing between the two points reads pl->phydev, and the registration that follows cannot fail: phy_request_interrupt() falls back to polling on its own. The PHY-side state keeps the order it had, so no MDIO operation moves relative to another. Fixes: 03abf2a7c654 ("net: phylink: add EEE management") Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260920222044.1752860-1-f@lex.la Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index a1458da8111b6c..1bbcf46c835663 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -2129,7 +2129,6 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, mutex_lock(&pl->phydev_mutex); mutex_lock(&phy->lock); mutex_lock(&pl->state_mutex); - pl->phydev = phy; pl->phy_state.interface = interface; pl->phy_state.pause = MLO_PAUSE_NONE; pl->phy_state.speed = SPEED_UNKNOWN; @@ -2196,10 +2195,25 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, ret = 0; } - if (ret == 0 && phy_interrupt_is_valid(phy)) + if (ret) + return ret; + + /* Nothing below can fail, so the PHY can be recorded now. Doing it + * here rather than above keeps a failed bringup from leaving + * pl->phydev pointing at a PHY the caller is about to detach. + */ + mutex_lock(&pl->phydev_mutex); + mutex_lock(&phy->lock); + mutex_lock(&pl->state_mutex); + pl->phydev = phy; + mutex_unlock(&pl->state_mutex); + mutex_unlock(&phy->lock); + mutex_unlock(&pl->phydev_mutex); + + if (phy_interrupt_is_valid(phy)) phy_request_interrupt(phy); - return ret; + return 0; } static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy, From 3173cba1170131816972ed2b6185cb970ba8b747 Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Mon, 21 Sep 2026 15:15:49 +0800 Subject: [PATCH 1204/1417] net: libwx: fix races in Tx timestamp handling wx->ptp_tx_skb is shared between the Tx path, the PTP auxiliary worker and the timestamp cleanup paths. The WX_STATE_PTP_TX_IN_PROGRESS bit prevents multiple Tx paths from submitting timestamp requests, but does not serialize the worker against cleanup. As a result, wx_ptp_clear_tx_timestamp() can free an skb after wx_ptp_tx_hwtstamp_work() has obtained its pointer. The worker may then pass the freed skb to skb_tstamp_tx() and release the same reference again. The cleanup path may also clear the in-progress bit while the worker is still processing the old skb. This allows the Tx path to publish a new skb which the worker can subsequently overwrite with NULL, leaking its reference. Add a dedicated spinlock to protect publication and consumption of the Tx timestamp skb. Detach the skb and clear the in-progress bit while holding the lock, then deliver the timestamp and release the skb after dropping it. Use the same locked cleanup in the quiesce path, but keep the detach there free of register accesses: quiesce runs during PCIe error recovery, where MMIO is not reliable, and it deliberately did not touch the device before. The lock is taken with interrupts disabled, because netpoll can call ndo_start_xmit() with hard interrupts already off. When handling a Tx DMA mapping failure, keep the transmit path reference until after comparing the skb under the lock. This prevents skb address reuse from making the error path mistake a newer timestamp request for the failed one. Fixes: 06e75161b9d4 ("net: wangxun: Add support for PTP clock") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/6C7EC12D69217315%2B20260818074721.45536-1-jiawenwu%40trustnetic.com Signed-off-by: Jiawen Wu Link: https://patch.msgid.link/77431AF9A0E369F3+20260921071549.1141804-1-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/libwx/wx_hw.c | 1 + drivers/net/ethernet/wangxun/libwx/wx_lib.c | 47 ++++-- drivers/net/ethernet/wangxun/libwx/wx_ptp.c | 142 +++++++++++++------ drivers/net/ethernet/wangxun/libwx/wx_type.h | 2 + 4 files changed, 132 insertions(+), 60 deletions(-) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c b/drivers/net/ethernet/wangxun/libwx/wx_hw.c index 122c4952d203f0..113552586be704 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c @@ -2518,6 +2518,7 @@ int wx_sw_init(struct wx *wx) } spin_lock_init(&wx->hw_stats_lock); + spin_lock_init(&wx->ptp_tx_lock); mutex_init(&wx->reset_lock); bitmap_zero(wx->state, WX_STATE_NBITS); bitmap_zero(wx->flags, WX_PF_FLAGS_NBITS); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c index ed5aad7857bd9b..dcbf5811046e96 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c @@ -1200,9 +1200,11 @@ static int wx_tx_map(struct wx_ring *tx_ring, i--; } - dev_kfree_skb_any(first->skb); - first->skb = NULL; - + /* first->skb is released by the caller, which keeps a reference on it + * until the PTP cleanup has compared it against wx->ptp_tx_skb. That + * prevents the address from being reused by a newer request while the + * comparison is pending. + */ tx_ring->next_to_use = i; return -ENOMEM; @@ -1649,9 +1651,11 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb, if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && wx->ptp_clock) { + unsigned long flags; + + spin_lock_irqsave(&wx->ptp_tx_lock, flags); if (wx->tstamp_config.tx_type == HWTSTAMP_TX_ON && - !test_and_set_bit_lock(WX_STATE_PTP_TX_IN_PROGRESS, - wx->state)) { + !test_and_set_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state)) { skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; tx_flags |= WX_TX_FLAGS_TSTAMP; wx->ptp_tx_skb = skb_get(skb); @@ -1659,6 +1663,7 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb, } else { wx->tx_hwtstamp_skipped++; } + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); } /* record initial flags and protocol */ @@ -1677,19 +1682,35 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb, wx->atr(tx_ring, first, ptype); if (wx_tx_map(tx_ring, first, hdr_len)) - goto cleanup_tx_tstamp; + goto out_drop; return NETDEV_TX_OK; out_drop: - dev_kfree_skb_any(first->skb); - first->skb = NULL; -cleanup_tx_tstamp: + /* The frame never reached the hardware, so no timestamp will ever be + * reported for it and the request has to be cancelled. The slot is + * shared, though: wx_ptp_clear_tx_timestamp() or wx_ptp_tx_hang() may + * have dropped our request already, and a transmit on another queue + * can have claimed the slot since. Only cancel it while it is still + * ours, otherwise we would free somebody else's skb and release their + * in-progress bit. + */ if (unlikely(tx_flags & WX_TX_FLAGS_TSTAMP)) { - dev_kfree_skb_any(wx->ptp_tx_skb); - wx->ptp_tx_skb = NULL; - wx->tx_hwtstamp_errors++; - clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + struct sk_buff *ptp_tx_skb = NULL; + unsigned long flags; + + spin_lock_irqsave(&wx->ptp_tx_lock, flags); + if (wx->ptp_tx_skb == skb) { + ptp_tx_skb = wx->ptp_tx_skb; + wx->ptp_tx_skb = NULL; + clear_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + wx->tx_hwtstamp_errors++; + } + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + + dev_kfree_skb_any(ptp_tx_skb); } + dev_kfree_skb_any(first->skb); + first->skb = NULL; return NETDEV_TX_OK; } diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c index 4708e7f3958f75..65b8937f6e94c9 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c @@ -129,6 +129,34 @@ static int wx_ptp_settime64(struct ptp_clock_info *ptp, return 0; } +/** + * __wx_ptp_detach_tx_skb - detach the skb tracking the Tx timestamp request + * @wx: the private board structure + * + * Detach the skb of the outstanding request and release the in-progress bit, + * so that a new request can be submitted. + * + * This performs no register access. Callers that need a timestamp the hardware + * may have left latched must unlatch it themselves, while the device is known + * to be alive. wx_ptp_quiesce() runs during PCIe error recovery, where MMIO is + * not reliable, and therefore deliberately skips the unlatch. + * + * Context: Expects wx->ptp_tx_lock to be held by the caller. + * Return: the detached skb, or NULL if no request was outstanding. The caller + * owns the returned reference and must release it once the lock is dropped. + */ +static struct sk_buff *__wx_ptp_detach_tx_skb(struct wx *wx) +{ + struct sk_buff *skb = wx->ptp_tx_skb; + + lockdep_assert_held(&wx->ptp_tx_lock); + + wx->ptp_tx_skb = NULL; + clear_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + + return skb; +} + /** * wx_ptp_clear_tx_timestamp - utility function to clear Tx timestamp state * @wx: the private board structure @@ -139,12 +167,16 @@ static int wx_ptp_settime64(struct ptp_clock_info *ptp, */ static void wx_ptp_clear_tx_timestamp(struct wx *wx) { + struct sk_buff *skb; + unsigned long flags; + + spin_lock_irqsave(&wx->ptp_tx_lock, flags); + /* Unlatch a timestamp the hardware may have left pending. */ rd32ptp(wx, WX_TSC_1588_STMPH); - if (wx->ptp_tx_skb) { - dev_kfree_skb_any(wx->ptp_tx_skb); - wx->ptp_tx_skb = NULL; - } - clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + skb = __wx_ptp_detach_tx_skb(wx); + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + + dev_kfree_skb_any(skb); } /** @@ -175,49 +207,54 @@ static void wx_ptp_convert_to_hwtstamp(struct wx *wx, } /** - * wx_ptp_tx_hwtstamp - utility function which checks for TX time stamp + * wx_ptp_tx_hwtstamp_work - check for a pending Tx time stamp * @wx: the private board struct * - * if the timestamp is valid, we convert it into the timecounter ns - * value, then store that result into the shhwtstamps structure which - * is passed up the network stack + * If a Tx timestamp request is outstanding and the hardware has latched a + * valid value, we convert it into the timecounter ns value, then store that + * result into the shhwtstamps structure which is passed up the network stack. + * + * Return: 0 when there is nothing left to poll for, -1 when the timestamp is + * not available yet and the caller should poll again. */ -static void wx_ptp_tx_hwtstamp(struct wx *wx) +static int wx_ptp_tx_hwtstamp_work(struct wx *wx) { struct skb_shared_hwtstamps shhwtstamps; - struct sk_buff *skb = wx->ptp_tx_skb; + unsigned long flags; + struct sk_buff *skb; + u32 tsynctxctl; u64 regval = 0; - regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPL); - regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPH) << 32; - - wx_ptp_convert_to_hwtstamp(wx, &shhwtstamps, regval); - - wx->ptp_tx_skb = NULL; - clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); - skb_tstamp_tx(skb, &shhwtstamps); - dev_kfree_skb_any(skb); - wx->tx_hwtstamp_pkts++; -} - -static int wx_ptp_tx_hwtstamp_work(struct wx *wx) -{ - u32 tsynctxctl; + spin_lock_irqsave(&wx->ptp_tx_lock, flags); /* we have to have a valid skb to poll for a timestamp */ if (!wx->ptp_tx_skb) { - wx_ptp_clear_tx_timestamp(wx); + rd32ptp(wx, WX_TSC_1588_STMPH); + __wx_ptp_detach_tx_skb(wx); + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); return 0; } /* stop polling once we have a valid timestamp */ tsynctxctl = rd32ptp(wx, WX_TSC_1588_CTL); - if (tsynctxctl & WX_TSC_1588_CTL_VALID) { - wx_ptp_tx_hwtstamp(wx); - return 0; + if (!(tsynctxctl & WX_TSC_1588_CTL_VALID)) { + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + return -1; } - return -1; + regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPL); + regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPH) << 32; + skb = wx->ptp_tx_skb; + wx->ptp_tx_skb = NULL; + clear_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + + wx_ptp_convert_to_hwtstamp(wx, &shhwtstamps, regval); + skb_tstamp_tx(skb, &shhwtstamps); + dev_kfree_skb_any(skb); + wx->tx_hwtstamp_pkts++; + + return 0; } /** @@ -296,24 +333,29 @@ static void wx_ptp_rx_hang(struct wx *wx) */ static void wx_ptp_tx_hang(struct wx *wx) { - bool timeout = time_is_before_jiffies(wx->ptp_tx_start + - WX_PTP_TX_TIMEOUT); - - if (!wx->ptp_tx_skb) - return; + struct sk_buff *skb = NULL; + unsigned long flags; - if (!test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state)) - return; + spin_lock_irqsave(&wx->ptp_tx_lock, flags); /* If we haven't received a timestamp within the timeout, it is * reasonable to assume that it will never occur, so we can unlock the * timestamp bit when this occurs. */ - if (timeout) { - wx_ptp_clear_tx_timestamp(wx); - wx->tx_hwtstamp_timeouts++; - dev_warn(&wx->pdev->dev, "clearing Tx timestamp hang\n"); + if (wx->ptp_tx_skb && + test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state) && + time_is_before_jiffies(wx->ptp_tx_start + WX_PTP_TX_TIMEOUT)) { + rd32ptp(wx, WX_TSC_1588_STMPH); + skb = __wx_ptp_detach_tx_skb(wx); } + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + + if (!skb) + return; + + dev_kfree_skb_any(skb); + wx->tx_hwtstamp_timeouts++; + dev_warn(&wx->pdev->dev, "clearing Tx timestamp hang\n"); } static long wx_ptp_do_aux_work(struct ptp_clock_info *ptp) @@ -841,6 +883,9 @@ EXPORT_SYMBOL(wx_ptp_stop); void wx_ptp_quiesce(struct wx *wx) { + struct sk_buff *skb; + unsigned long flags; + if (!test_and_clear_bit(WX_STATE_PTP_RUNNING, wx->state)) return; @@ -849,11 +894,14 @@ void wx_ptp_quiesce(struct wx *wx) if (wx->ptp_clock) ptp_cancel_worker_sync(wx->ptp_clock); - if (wx->ptp_tx_skb) { - dev_kfree_skb_any(wx->ptp_tx_skb); - wx->ptp_tx_skb = NULL; - } - clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + /* Drop a pending Tx timestamp request. Do not touch the registers + * here: quiesce runs during PCIe error recovery, where the device may + * already be gone and MMIO is not reliable. + */ + spin_lock_irqsave(&wx->ptp_tx_lock, flags); + skb = __wx_ptp_detach_tx_skb(wx); + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + dev_kfree_skb_any(skb); if (wx->ptp_clock) { ptp_clock_unregister(wx->ptp_clock); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h index 9454e90258d8ed..afd980dbb79311 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h @@ -1430,6 +1430,8 @@ struct wx { unsigned long last_overflow_check; unsigned long last_rx_ptp_check; unsigned long ptp_tx_start; + /* protects ptp_tx_skb, ptp_tx_start and the in-progress state bit */ + spinlock_t ptp_tx_lock; seqlock_t hw_tc_lock; /* seqlock for ptp */ struct cyclecounter hw_cc; struct timecounter hw_tc; From 26b2bd70d22457556e2fa01cbf1192cb1a94d619 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 21 Sep 2026 16:55:43 +0200 Subject: [PATCH 1205/1417] net: openvswitch: conntrack: avoid modifying shared unconfirmed ct entry In a case where skb with an unconfirmed ct entry gets cloned, we may end up committing both but with different sets of extensions. The series of events: 1. The first clone wants to commit and runs the helpers wiring up the extension pointer into the expectation list. 2. Then it looses the confirmation keeping the entry unconfirmed. 3. Second clone now wants to commit labels and adds the new extension for that breaking the pointer in the expectation list causing UAF on the destruction path later. While this is possible to trigger, there should be no practical network pipeline where committing both clones without modifications into the same zone is needed. So, let's just reset the entry in case for some reason we got an skb with a shared one during commit. This doesn't affect any known use cases, but avoids any potential problems with sharing and modification of the unconfirmed ct entry. The fixes tag points to the introduction of helpers, since that's the main UAF trigger for the sharing. Fixes: cae3a2627520 ("openvswitch: Allow attaching helpers to ct action") Cc: stable@vger.kernel.org Reported-by: Axel Mierczuk Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260921145655.3167436-2-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- include/net/netfilter/nf_conntrack.h | 5 +++++ net/openvswitch/conntrack.c | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/include/net/netfilter/nf_conntrack.h b/include/net/netfilter/nf_conntrack.h index bc42dd0e10e659..c39425e54d87d3 100644 --- a/include/net/netfilter/nf_conntrack.h +++ b/include/net/netfilter/nf_conntrack.h @@ -185,6 +185,11 @@ static inline void nf_ct_put(struct nf_conn *ct) nf_ct_destroy(&ct->ct_general); } +static inline bool nf_ct_shared(const struct nf_conn *ct) +{ + return refcount_read(&ct->ct_general.use) > 1; +} + /* load module; enable/disable conntrack in this namespace */ int nf_ct_netns_get(struct net *net, u8 nfproto); void nf_ct_netns_put(struct net *net, u8 nfproto); diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index 0f433688e17b98..a733029c28dd0d 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -734,6 +734,18 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, enum ip_conntrack_info ctinfo; struct nf_conn *ct; + /* If the ct entry is not confirmed and shared with some other skb, + * e.g., a cloned one, we can't just modify it with the commit as we + * must not modify the extension set. Reset. + */ + if (cached && info->commit) { + ct = nf_ct_get(skb, &ctinfo); + if (ct && !nf_ct_is_confirmed(ct) && nf_ct_shared(ct)) { + nf_reset_ct(skb); + cached = false; + } + } + if (!cached) { struct nf_hook_state state = { .hook = NF_INET_PRE_ROUTING, From 5e6c14dd42a1c1fe938e573dc6c9098145b2b0c4 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 21 Sep 2026 16:55:44 +0200 Subject: [PATCH 1206/1417] net: openvswitch: conntrack: remove 'add_helper' dead code This variable can only become 'true' when the connection is not confirmed, but it is only checked when it is confirmed. So, it can be treated as being always false and just removed. Fixes: 3c1860543fcc ("openvswitch: add nf_ct_is_confirmed check before assigning the helper") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260921145655.3167436-3-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/openvswitch/conntrack.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index a733029c28dd0d..c20f096eef40e7 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -778,8 +778,6 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, ct = nf_ct_get(skb, &ctinfo); if (ct) { - bool add_helper = false; - /* Packets starting a new connection must be NATted before the * helper, so that the helper knows about the NAT. We enforce * this by delaying both NAT and helper calls for unconfirmed @@ -811,7 +809,6 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, GFP_ATOMIC); if (err) return err; - add_helper = true; /* helper installed, add seqadj if NAT is required */ if (info->nat && !nfct_seqadj(ct)) { @@ -821,13 +818,10 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, } /* Call the helper only if: - * - nf_conntrack_in() was executed above ("!cached") or a - * helper was just attached ("add_helper") for a confirmed - * connection, or + * - nf_conntrack_in() was executed above ("!cached"), or * - When committing an unconfirmed connection. */ - if ((nf_ct_is_confirmed(ct) ? !cached || add_helper : - info->commit)) { + if ((nf_ct_is_confirmed(ct) ? !cached : info->commit)) { int err = nf_ct_helper(skb, ct, ctinfo, info->family); err = verdict_to_errno(err); From 1a4151e6be57b098b7a5ebfbde58585e83200cdc Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 21 Sep 2026 16:55:45 +0200 Subject: [PATCH 1207/1417] net: openvswitch: conntrack: fix helper UAF due to extensions realloc While calling the helpers, a raw pointer to the extensions area is wired into expectations list: -> nf_ct_helper() -> helper->help() -> nf_ct_expect_related_report() -> nf_ct_expect_insert() -> hlist_add_head_rcu(&exp->lnode, &master_help->expectations) In case the connection is not confirmed yet, more extensions can be added afterwards with *_ext_add() calls reallocating the extension space and leaving the now invalid pointer in the expectations list that is later accessed while removing the expectation. Make sure that helpers are called at the end after all the other extensions are already added. Note that the helper rejection now leaves the mark and labels set, but that's not different from how the NAT was handled before or how the mark and the labels were handled on confirmation failure. And there are no atomicity guarantees provided by the API anyway. Fixes: cae3a2627520 ("openvswitch: Allow attaching helpers to ct action") Cc: stable@vger.kernel.org Reported-by: Axel Mierczuk Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260921145655.3167436-4-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/openvswitch/conntrack.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index c20f096eef40e7..d3326edcabf76f 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -817,11 +817,14 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, } } - /* Call the helper only if: - * - nf_conntrack_in() was executed above ("!cached"), or - * - When committing an unconfirmed connection. + /* Call the helper only if nf_conntrack_in() was executed + * above ("!cached"). + * + * For unconfirmed connections it will be called later during + * commit as we need to have all the other extensions allocated + * before the call. */ - if ((nf_ct_is_confirmed(ct) ? !cached : info->commit)) { + if (nf_ct_is_confirmed(ct) && !cached) { int err = nf_ct_helper(skb, ct, ctinfo, info->family); err = verdict_to_errno(err); @@ -1025,6 +1028,14 @@ static int ovs_ct_commit(struct net *net, struct sw_flow_key *key, return err; nf_conn_act_ct_ext_add(skb, ct, ctinfo); + + /* Call the helpers now. We couldn't do this before as + * all the extensions must be allocated before the call. + */ + err = nf_ct_helper(skb, ct, ctinfo, info->family); + err = verdict_to_errno(err); + if (err) + return err; } else if (IS_ENABLED(CONFIG_NF_CONNTRACK_LABELS) && labels_nonzero(&info->labels.mask)) { err = ovs_ct_set_labels(ct, key, &info->labels.value, From f85009dfcd65e5969526b0db7a49b5413746e630 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 21 Sep 2026 16:55:46 +0200 Subject: [PATCH 1208/1417] net/sched: act_ct: avoid modifying shared unconfirmed ct entry In a case where skb with an unconfirmed ct entry gets cloned, we may end up processing both again but with different sets of extensions. The series of events: 1. The first clone wants to commit and runs the helpers wiring up the extension pointer into the expectation list. 2. Then it looses the confirmation keeping the entry unconfirmed. 3. Second clone now wants to commit labels or run NAT and adds the new extension for that breaking the pointer in the expectation list causing UAF on the destruction path later. While this is possible to trigger, there should be no practical network pipeline where we need to process both clones without modifications in the same zone. So, let's just reset the entry in case for some reason we got an skb with a shared one. This doesn't affect any known use cases, but avoids any potential problems with sharing and modification of the unconfirmed ct entry. Unlike openvswitch module, act_ct allows for NAT without commit. Changing that would be a uAPI break. So, act_ct needs to reset on NAT regardless of the commit flag to avoid reallocation of the extension space. This, however, doesn't really change the picture for sensible networking cases as there should be no need to run the same packet twice (before and after the clone) through conntrack without packet header or zone changes and without commit. The fixes tag points to the introduction of helpers, since that's the main UAF trigger for the sharing. Fixes: a21b06e73191 ("net: sched: add helper support in act_ct") Cc: stable@vger.kernel.org Reported-by: Axel Mierczuk Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Reviewed-by: Xin Long Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260921145655.3167436-5-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/sched/act_ct.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index 55f3521edb4c9a..e72143d36b1195 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -979,11 +979,11 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, struct tcf_result *res) { struct net *net = dev_net(skb->dev); + bool cached, commit, clear, nat; enum ip_conntrack_info ctinfo; struct tcf_ct *c = to_ct(a); struct nf_conn *tmpl = NULL; struct nf_hook_state state; - bool cached, commit, clear; int nh_ofs, err, retval; struct tcf_ct_params *p; bool add_helper = false; @@ -998,6 +998,7 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, retval = p->action; commit = p->ct_action & TCA_CT_ACT_COMMIT; clear = p->ct_action & TCA_CT_ACT_CLEAR; + nat = p->ct_action & TCA_CT_ACT_NAT; tmpl = p->tmpl; tcf_lastuse_update(&c->tcf_tm); @@ -1046,6 +1047,19 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, * different zone. */ cached = tcf_ct_skb_nfct_cached(net, skb, p); + + /* If the ct entry is not confirmed and shared with some other skb, + * e.g., a cloned one, we can't just modify it with a commit or nat + * as we must not modify the extension set. Reset. + */ + if (cached && (commit || nat)) { + ct = nf_ct_get(skb, &ctinfo); + if (ct && !nf_ct_is_confirmed(ct) && nf_ct_shared(ct)) { + nf_reset_ct(skb); + cached = false; + } + } + if (!cached) { if (tcf_ct_flow_table_lookup(p, skb, family)) { skip_add = true; @@ -1083,7 +1097,7 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, if (err) goto drop; add_helper = true; - if (p->ct_action & TCA_CT_ACT_NAT && !nfct_seqadj(ct)) { + if (nat && !nfct_seqadj(ct)) { if (!nfct_seqadj_ext_add(ct)) goto drop; } From 00df72e39f306e2f7adb68528a5c92109da6a0a9 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 21 Sep 2026 16:55:47 +0200 Subject: [PATCH 1209/1417] net/sched: act_ct: remove 'add_helper' dead code This variable can only become 'true' when the connection is not confirmed, but it is only checked when it is confirmed. So, it can be treated as being always false and just removed. Fixes: a21b06e73191 ("net: sched: add helper support in act_ct") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Reviewed-by: Xin Long Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260921145655.3167436-6-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/sched/act_ct.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index e72143d36b1195..f62051ec9d57de 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -986,7 +986,6 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, struct nf_hook_state state; int nh_ofs, err, retval; struct tcf_ct_params *p; - bool add_helper = false; bool skb_is_ours = false; bool skip_add = false; bool defrag = false; @@ -1096,14 +1095,14 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, err = __nf_ct_try_assign_helper(ct, p->tmpl, GFP_ATOMIC); if (err) goto drop; - add_helper = true; + if (nat && !nfct_seqadj(ct)) { if (!nfct_seqadj_ext_add(ct)) goto drop; } } - if (nf_ct_is_confirmed(ct) ? ((!cached && !skip_add) || add_helper) : commit) { + if (nf_ct_is_confirmed(ct) ? (!cached && !skip_add) : commit) { err = nf_ct_helper(skb, ct, ctinfo, family); if (err != NF_ACCEPT) goto nf_error; From dad19b59da050cb60d3f7023dac2a042a84bf0bd Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 21 Sep 2026 16:55:48 +0200 Subject: [PATCH 1210/1417] net/sched: act_ct: fix helper UAF due to extensions realloc While calling the helpers, a raw pointer to the extensions area is wired into expectations list: -> nf_ct_helper() -> helper->help() -> nf_ct_expect_related_report() -> nf_ct_expect_insert() -> hlist_add_head_rcu(&exp->lnode, &master_help->expectations) In case the connection is not confirmed yet, more extensions can be added afterwards with *_ext_add() calls reallocating the extension space and leaving the now invalid pointer in the expectations list that is later accessed while removing the expectation. Make sure that helpers are called at the end after all the other extensions are already added. Note that the helper rejection now leaves the mark and labels set, but that's not different from how the NAT was handled before or how the mark and the labels were handled on confirmation failure. And there are no atomicity guarantees provided by the API anyway. Fixes: a21b06e73191 ("net: sched: add helper support in act_ct") Cc: stable@vger.kernel.org Reported-by: Axel Mierczuk Signed-off-by: Ilya Maximets Reviewed-by: Xin Long Reviewed-by: Jamal Hadi Salim Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260921145655.3167436-7-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/sched/act_ct.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index f62051ec9d57de..411e3dd92d072e 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -1102,19 +1102,25 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, } } - if (nf_ct_is_confirmed(ct) ? (!cached && !skip_add) : commit) { - err = nf_ct_helper(skb, ct, ctinfo, family); - if (err != NF_ACCEPT) - goto nf_error; - } - if (commit) { tcf_ct_act_set_mark(ct, p->mark, p->mark_mask); tcf_ct_act_set_labels(ct, p->labels, p->labels_mask); if (!nf_ct_is_confirmed(ct)) nf_conn_act_ct_ext_add(skb, ct, ctinfo); + } + /* Run helpers for the connection if nf_conntrack_in() was executed + * or if we're about to commit. This has to be done after all the + * extensions are already added. + */ + if (nf_ct_is_confirmed(ct) ? (!cached && !skip_add) : commit) { + err = nf_ct_helper(skb, ct, ctinfo, family); + if (err != NF_ACCEPT) + goto nf_error; + } + + if (commit) { /* This will take care of sending queued events * even if the connection is already confirmed. */ From 0958ea4355e2e9220ad4e13da3b7d94f365ed34f Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 21 Sep 2026 23:42:01 +0800 Subject: [PATCH 1211/1417] net: ena: fix PHC cleanup on probe failure ena_probe() initializes the PHC as part of ena_device_init(), but the probe failure path does not destroy it before freeing the PHC private data. The normal removal path calls ena_phc_destroy() through ena_destroy_device() before ena_phc_free(). However, if probe fails after ena_device_init() succeeds, the error path reaches ena_phc_free() without unregistering the PTP clock or destroying the device PHC resources. Call ena_phc_destroy() in the probe error path before freeing the PHC private data. This issue was found by manual code inspection. Cc: stable@vger.kernel.org tags and describe this as a consistency cleanup Fixes: e0ea34158ee8 ("net: ena: Add PHC support in the ENA driver") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Cc: stable Link: https://patch.msgid.link/20260921154202.471662-2-lgs201920130244@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amazon/ena/ena_netdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/amazon/ena/ena_netdev.c b/drivers/net/ethernet/amazon/ena/ena_netdev.c index ea89619039d89b..5f0864d16dd3c3 100644 --- a/drivers/net/ethernet/amazon/ena/ena_netdev.c +++ b/drivers/net/ethernet/amazon/ena/ena_netdev.c @@ -4122,6 +4122,7 @@ static int ena_probe(struct pci_dev *pdev, const struct pci_device_id *ent) err_device_destroy: ena_com_delete_host_info(ena_dev); ena_com_admin_destroy(ena_dev); + ena_phc_destroy(adapter); ena_devlink_destroy: ena_devlink_free(devlink); err_metrics_destroy: From 9476b4468862927297c94c440863cd8ed1e7cc83 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 21 Sep 2026 23:42:02 +0800 Subject: [PATCH 1212/1417] net: ena: fix MMIO read buffer leak on probe failure ena_device_init() initializes the MMIO read mechanism with ena_com_mmio_reg_read_request_init(), which allocates a coherent DMA buffer for MMIO read responses. The normal removal path releases this buffer through ena_com_mmio_reg_read_request_destroy(). However, if ena_probe() fails after ena_device_init() succeeds, the error path destroys the admin resources and eventually frees ena_dev without destroying the MMIO read request, leaving the coherent DMA buffer allocated. Call ena_com_mmio_reg_read_request_destroy() in the probe error path before releasing the remaining device resources. This issue was found by manual code inspection. Fixes: 1738cd3ed342 ("net: ena: Add a driver for Amazon Elastic Network Adapters (ENA)") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260921154202.471662-3-lgs201920130244@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amazon/ena/ena_netdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/amazon/ena/ena_netdev.c b/drivers/net/ethernet/amazon/ena/ena_netdev.c index 5f0864d16dd3c3..7eb6456ed0d556 100644 --- a/drivers/net/ethernet/amazon/ena/ena_netdev.c +++ b/drivers/net/ethernet/amazon/ena/ena_netdev.c @@ -4123,6 +4123,7 @@ static int ena_probe(struct pci_dev *pdev, const struct pci_device_id *ent) ena_com_delete_host_info(ena_dev); ena_com_admin_destroy(ena_dev); ena_phc_destroy(adapter); + ena_com_mmio_reg_read_request_destroy(ena_dev); ena_devlink_destroy: ena_devlink_free(devlink); err_metrics_destroy: From 1a983a4e14c635c40354be110cd9a1a5c94e01e6 Mon Sep 17 00:00:00 2001 From: Sang-Hoon Choi Date: Tue, 22 Sep 2026 03:15:59 +0900 Subject: [PATCH 1213/1417] nfp: hold IPsec RX state under the XArray lock nfp_net_ipsec_rx() drops the XArray lock before taking a reference to the xfrm_state it found. The delete path can erase the entry and drop the last state reference in that interval. RX can then try to increment a zero refcount after the state has been queued for destruction. The driver queues firmware invalidation asynchronously; the delete path does not wait for the command to complete or drain pending RX processing. The XFRM garbage collector waits for an RCU grace period before freeing the state. That delays reclamation but does not make acquiring a reference from zero valid. Take the xfrm_state reference before releasing the XArray lock so xa_erase() cannot run between lookup and reference acquisition. Fixes: 57f273adbcd4 ("nfp: add framework to support ipsec offloading") Reported-by: Changyul Lee Signed-off-by: Sang-Hoon Choi Link: https://patch.msgid.link/179001455912.44752.17153022439349797877.idr-bug-92@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/netronome/nfp/crypto/ipsec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/netronome/nfp/crypto/ipsec.c b/drivers/net/ethernet/netronome/nfp/crypto/ipsec.c index 9e7c285eaa6bca..960d7513aa8dd8 100644 --- a/drivers/net/ethernet/netronome/nfp/crypto/ipsec.c +++ b/drivers/net/ethernet/netronome/nfp/crypto/ipsec.c @@ -625,11 +625,12 @@ int nfp_net_ipsec_rx(struct nfp_meta_parsed *meta, struct sk_buff *skb) xa_lock(&nn->xa_ipsec); x = xa_load(&nn->xa_ipsec, saidx); + if (x) + xfrm_state_hold(x); xa_unlock(&nn->xa_ipsec); if (!x) return -EINVAL; - xfrm_state_hold(x); sp->xvec[sp->len++] = x; sp->olen++; xo = xfrm_offload(skb); From 92871abc1302b852b88d86114115f8fd6b50b9b9 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 16 Sep 2026 17:40:42 +0800 Subject: [PATCH 1214/1417] ASoC: fsl_asrc: use regmap_write_bits to update clock source registers After a warm reboot the REG_ASRCSR register may not be in its hardware reset state. regmap caches the register value as 0 (the assumed default), so when the desired clock source index is also 0, regmap_update_bits() skips the actual hardware write because it believes the register already holds the correct value. This leaves a stale clock source in the hardware. Replace regmap_update_bits() with regmap_write_bits() so that the masked fields are always written to hardware regardless of the cached value. Fixes: 3117bb3109dc ("ASoC: fsl_asrc: Add ASRC ASoC CPU DAI and platform drivers") Signed-off-by: Shengjiu Wang Reviewed-by: Chancel Liu Link: https://patch.msgid.link/20260916094043.1496709-1-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index ae682bf450f6d5..cc304abbc8bd95 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -521,10 +521,10 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair, bool use_ideal_rate) ASRCTR_USR(index)); /* Set the input and output clock sources */ - regmap_update_bits(asrc->regmap, REG_ASRCSR, - ASRCSR_AICSi_MASK(index) | ASRCSR_AOCSi_MASK(index), - ASRCSR_AICS(index, clk_index[IN]) | - ASRCSR_AOCS(index, clk_index[OUT])); + regmap_write_bits(asrc->regmap, REG_ASRCSR, + ASRCSR_AICSi_MASK(index) | ASRCSR_AOCSi_MASK(index), + ASRCSR_AICS(index, clk_index[IN]) | + ASRCSR_AOCS(index, clk_index[OUT])); /* Calculate the input clock divisors */ indiv = fsl_asrc_cal_asrck_divisor(pair, div[IN]); From ba3d1f480c7a3fba963e7867ad6cc557c197acbc Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Mon, 21 Sep 2026 14:50:27 -0700 Subject: [PATCH 1215/1417] net/rds: size a connection's path set by the transport it ends up with __rds_conn_create() computes npaths from the caller's transport before it decides whether a connection to one of the host's own addresses is to be handled by the loopback transport instead. That substitution is what an RDS/TCP socket sending to a local address gets, and after it the path init loop still runs for the TCP transport's RDS_MPATH_WORKERS paths and allocates an ordered workqueue for each, while rds_loop_conn_alloc() only ever provides transport data for path 0. rds_conn_destroy() sizes its teardown from c_trans, by then the loopback transport, so it visits path 0 only - and rds_conn_path_destroy() would skip the other paths anyway, since it returns before destroy_workqueue() for a path without transport data. kfree(c_path) then drops the last pointers to seven workqueues. That repeats for every such connection, on every netns teardown or module unload, and every distinct local destination address is a separate connection. Recompute npaths once the transport is final, so that creation and destruction agree on the set of paths. The c_path array stays sized for the caller's transport; the unused entries are freed with it. Fixes: 4716af3897e9 ("net/rds: Give each connection path its own workqueue") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260921215027.174657-1-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/connection.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/rds/connection.c b/net/rds/connection.c index b6c4beb50eaf0e..c752a8623cfca0 100644 --- a/net/rds/connection.c +++ b/net/rds/connection.c @@ -276,6 +276,12 @@ static struct rds_connection *__rds_conn_create(struct net *net, conn->c_trans = trans; + /* The transport may just have been swapped for loopback; size the + * set of paths - which is also what rds_conn_destroy() tears down + * again - by the transport the connection actually uses. + */ + npaths = (trans->t_mp_capable ? RDS_MPATH_WORKERS : 1); + init_waitqueue_head(&conn->c_hs_waitq); for (i = 0; i < npaths; i++) { __rds_conn_path_init(conn, &conn->c_path[i], From 61cb282fe97b3b0ba32ca09417a693162bf4ae3f Mon Sep 17 00:00:00 2001 From: Sidraya Jayagond Date: Tue, 22 Sep 2026 09:31:49 +0200 Subject: [PATCH 1216/1417] net/smc: fix UAF on lgr list traversal in smcr_port_err() smcr_port_err() traverses smc_lgr_list.list without holding smc_lgr_list.lock, allowing a concurrent smc_lgr_terminate_sched() to free an lgr while it is still being dereferenced. Hold smc_lgr_list.lock across the traversal. Update smc_ib_gid_check() to call smcr_port_err() after releasing the lock. Fixes: 541afa10c126 ("net/smc: add smcr_port_err() and smcr_link_down() processing") Reviewed-by: Mahanta Jambigi Signed-off-by: Sidraya Jayagond Reviewed-by: Dust Li Link: https://patch.msgid.link/20260922073149.474762-1-sidraya@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/smc/smc_core.c | 2 ++ net/smc/smc_ib.c | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c index 04aedd957543a5..9974149659c2f2 100644 --- a/net/smc/smc_core.c +++ b/net/smc/smc_core.c @@ -1849,6 +1849,7 @@ void smcr_port_err(struct smc_ib_device *smcibdev, u8 ibport) struct smc_link_group *lgr, *n; int i; + spin_lock_bh(&smc_lgr_list.lock); list_for_each_entry_safe(lgr, n, &smc_lgr_list.list, list) { if (strncmp(smcibdev->pnetid[ibport - 1], lgr->pnet_id, SMC_MAX_PNETID_LEN)) @@ -1863,6 +1864,7 @@ void smcr_port_err(struct smc_ib_device *smcibdev, u8 ibport) smcr_link_down_cond_sched(lnk); } } + spin_unlock_bh(&smc_lgr_list.lock); } static void smc_link_down_work(struct work_struct *work) diff --git a/net/smc/smc_ib.c b/net/smc/smc_ib.c index 9bb495707445eb..daaa8a72da90fe 100644 --- a/net/smc/smc_ib.c +++ b/net/smc/smc_ib.c @@ -333,6 +333,7 @@ static bool smc_ib_check_link_gid(u8 gid[SMC_GID_SIZE], bool smcrv2, static void smc_ib_gid_check(struct smc_ib_device *smcibdev, u8 ibport) { struct smc_link_group *lgr; + bool stale_gid = false; int i; spin_lock_bh(&smc_lgr_list.lock); @@ -348,11 +349,16 @@ static void smc_ib_gid_check(struct smc_ib_device *smcibdev, u8 ibport) continue; if (!smc_ib_check_link_gid(lgr->lnk[i].gid, lgr->smc_version == SMC_V2, - smcibdev, ibport)) - smcr_port_err(smcibdev, ibport); + smcibdev, ibport)) { + stale_gid = true; + goto out; + } } } +out: spin_unlock_bh(&smc_lgr_list.lock); + if (stale_gid) + smcr_port_err(smcibdev, ibport); } static int smc_ib_remember_port_attr(struct smc_ib_device *smcibdev, u8 ibport) From b94773dc4df7026a6f29b2c65e96e88d29cdb576 Mon Sep 17 00:00:00 2001 From: Alexander Sverdlin Date: Tue, 22 Sep 2026 09:52:46 +0200 Subject: [PATCH 1217/1417] net: phy: intel-xway: workaround 100BASE-TX Link-Up issue MaxLinear GSW12x/GSW14x Ethernet Switch Errata Sheet states: "An issue has been sporadically observed after device power-on on the first link-up attempt in 100BASE-TX mode resulting in either the link-up taking a long time, or failing to link-up altogether... Workaround: After power-on, enable Cable Diagnostic Mode for all ports and disable it..." Implement the proposed workaround unconditionally in the Intel XWAY driver (MaxLinear GSW1xx switches incorporate Intel XWAY PHYs) because the diagnostic bits have the same meaning even in older integral PHYs such as GPY111/PEF7071/PHY11G. So it's not clear how to distinguish the affected newer integrated PHYs, but the workaround should not hurt the older PHYs. Cc: stable@vger.kernel.org Fixes: 22335939ec90 ("net: dsa: add driver for MaxLinear GSW1xx switch family") Signed-off-by: Alexander Sverdlin Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260922075251.23386-1-alexander.sverdlin@siemens.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/intel-xway.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/intel-xway.c b/drivers/net/phy/intel-xway.c index afbcec71174433..3cee31bb931f23 100644 --- a/drivers/net/phy/intel-xway.c +++ b/drivers/net/phy/intel-xway.c @@ -16,6 +16,11 @@ #define XWAY_MDIO_ISTAT 0x1A /* interrupt status */ #define XWAY_MDIO_LED 0x1B /* led control */ +#define XWAY_MDIO_GCTRL_TM_MASK GENMASK(15, 13) +#define XWAY_MDIO_GCTRL_TM(mode) FIELD_PREP(XWAY_MDIO_GCTRL_TM_MASK, (mode)) +#define XWAY_MDIO_GCTRL_TM_NOP XWAY_MDIO_GCTRL_TM(0) /* Normal operation */ +#define XWAY_MDIO_GCTRL_TM_CDIAG XWAY_MDIO_GCTRL_TM(6) /* Cable diagnostics */ + #define XWAY_MDIO_ERRCNT_SEL GENMASK(11, 8) #define XWAY_MDIO_ERRCNT_COUNT GENMASK(7, 0) #define XWAY_MDIO_ERRCNT_SEL_RXERR 0 @@ -326,6 +331,28 @@ static int xway_gphy_probe(struct phy_device *phydev) return 0; } +static int xway_11g_int_config_init(struct phy_device *phydev) +{ + int err; + + /* An issue has been sporadically observed after device power-on on the + * first link-up attempt in 100BASE-TX mode resulting in either the + * link-up taking a long time, or failing to link-up altogether. + * + * Workaround: + * After power-on, enable Cable Diagnostic Mode for all ports and + * disable it. + */ + err = phy_modify(phydev, MII_CTRL1000, XWAY_MDIO_GCTRL_TM_MASK, XWAY_MDIO_GCTRL_TM_CDIAG); + if (err) + return err; + err = phy_modify(phydev, MII_CTRL1000, XWAY_MDIO_GCTRL_TM_MASK, XWAY_MDIO_GCTRL_TM_NOP); + if (err) + return err; + + return xway_gphy_config_init(phydev); +} + static int xway_gphy14_config_aneg(struct phy_device *phydev) { int reg, err; @@ -735,7 +762,7 @@ static struct phy_driver xway_gphy[] = { .phy_id_mask = 0xffffffff, .name = "Intel XWAY PHY11G (xRX v1.2 integrated)", /* PHY_GBIT_FEATURES */ - .config_init = xway_gphy_config_init, + .config_init = xway_11g_int_config_init, .probe = xway_gphy_probe, .handle_interrupt = xway_gphy_handle_interrupt, .config_intr = xway_gphy_config_intr, From 8e1937fed6738460554ec123c64839e2445e7d53 Mon Sep 17 00:00:00 2001 From: Ginger Li Date: Tue, 22 Sep 2026 16:09:09 +0800 Subject: [PATCH 1218/1417] tipc: Fix a data race on mon->peer_cnt in mon_timeout() mon_timeout() evaluates dom_size(mon->peer_cnt) before it takes mon->lock, while mon->peer_cnt is updated under that lock by tipc_mon_add_peer() and tipc_mon_remove_peer(). The value can therefore be stale, and the decision whether the local domain has to be recomputed can be based on an outdated member count. Read mon->peer_cnt inside the write_lock_bh(&mon->lock) protected region. Fixes: 35c55c9877f8 ("tipc: add neighbor monitoring framework") Signed-off-by: Ginger Li Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/20260922080909.21123-1-ginger.jzllee@gmail.com Signed-off-by: Jakub Kicinski --- net/tipc/monitor.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/tipc/monitor.c b/net/tipc/monitor.c index a94b9b36a70082..1a438e312d66b3 100644 --- a/net/tipc/monitor.c +++ b/net/tipc/monitor.c @@ -632,9 +632,10 @@ static void mon_timeout(struct timer_list *t) { struct tipc_monitor *mon = timer_container_of(mon, t, timer); struct tipc_peer *self; - int best_member_cnt = dom_size(mon->peer_cnt) - 1; + int best_member_cnt; write_lock_bh(&mon->lock); + best_member_cnt = dom_size(mon->peer_cnt) - 1; self = mon->self; if (self && (best_member_cnt != self->applied)) { mon_update_local_domain(mon); From 56d82862a0a243ac14ba11b6d7b57ddc2d064b95 Mon Sep 17 00:00:00 2001 From: Dairui Zhang Date: Wed, 23 Sep 2026 13:01:01 +0800 Subject: [PATCH 1219/1417] af_packet: fix integer overflow in prb_calc_retire_blk_tmo() prb_calc_retire_blk_tmo() computes in 32-bit int arithmetic: mbits = (blk_size_in_bytes * 8) / (1024 * 1024); If I'm reading the validation right, tp_block_size is user controlled and packet_set_ring() only rejects values that are <= 0 as int or not page aligned, so a 256MiB block goes right through (and alloc_one_pg_vec_page() even has a vzalloc fallback for it). 0x10000000 * 8 wraps to INT_MIN, and on a NIC reporting 1 Gbps (div == 1) the function ends up returning -2047. The condition is actually (8 * size) mod 2^32 >= 2^31 && div == 1, so the trigger set is [256,512), [768,1024), [1280,1536) and [1792,2048) MiB. Other sizes wrap to non-negative values and faster links divide the unsigned value back below 2^31, which is why this doesn't blow up for everyone. What makes it fatal is what happens next in init_prb_bdqc(): p1->interval_ktime = ms_to_ktime(prb_calc_retire_blk_tmo(...)); hrtimer_start(&p1->retire_blk_timer, p1->interval_ktime, HRTIMER_MODE_REL_SOFT); A negative relative timeout expires immediately. The callback unconditionally returns HRTIMER_RESTART, and hrtimer_forward() turns the negative interval into hrtimer_resolution: if (interval < hrtimer_resolution) interval = hrtimer_resolution; So the SOFT timer re-fires at the maximum rate forever, holding sk_receive_queue.lock each pass. One CPU spins in softirq until the socket is closed. Repeat with more rings and the machine is gone. The overflow itself is ancient - it was introduced together with TPACKET_V3 in f6fb8f100b80 ("af-packet: TPACKET_V3 flexible buffer implementation."). Its effect prior to f7460d2989fa ("net: af_packet: Use hrtimer to do the retire operation", v6.18) was not as clear-cut, though: the return value was stored into an unsigned short retire_blk_tov, so a negative result was truncated, and a 0-jiffy delay loop could be programmed as well. Neither is nearly as detrimental as the immediate maximum-rate spin the hrtimer conversion turned it into. (Unrelated to CVE-2019-20812 - that one was the ethtool failure path returning 0, which now returns DEFAULT_PRB_RETIRE_TOV.) Reproducer, needs CAP_NET_RAW (a --network host container has it by default) and a 1 Gbps NIC (QEMU e1000 works): int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); bind(fd, ...); int v = TPACKET_V3; setsockopt(fd, SOL_PACKET, PACKET_VERSION, &v, sizeof(v)); struct tpacket_req3 req = { .tp_block_size = 0x10000000, .tp_block_nr = 1, .tp_frame_size = 2048, .tp_frame_nr = 0x10000000 / 2048, .tp_retire_blk_tov = 0, }; setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req)); Compute in 64 bits instead. The operands are already bounded by the existing validation, so nothing else changes. If you'd prefer a different fix, just say so and I'll respin. Fixes: f6fb8f100b80 ("af-packet: TPACKET_V3 flexible buffer implementation.") Cc: stable@vger.kernel.org Signed-off-by: Dairui Zhang Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260923050101.1510064-1-zhangdairui@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 64b501db660a2d..7c83e01526edc5 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -617,7 +617,7 @@ static int prb_calc_retire_blk_tmo(struct packet_sock *po, return DEFAULT_PRB_RETIRE_TOV; div = ecmd.base.speed / 1000; - mbits = (blk_size_in_bytes * 8) / (1024 * 1024); + mbits = (u64)blk_size_in_bytes * 8 / (1024 * 1024); if (div) mbits /= div; From 8db67bb6a1fffa4df68fbbc22e39943aeeff9178 Mon Sep 17 00:00:00 2001 From: Coia Prant Date: Wed, 23 Sep 2026 20:37:13 +0800 Subject: [PATCH 1220/1417] net: ethernet: stmmac: dwmac-rk: fix bulk clock leak when the PHY clock fails gmac_clk_enable() enables the bulk clocks first and then the optional PHY clock. If clk_prepare_enable() on the PHY clock fails, the function returns without rolling back the bulk clocks, and bsp_priv->clk_enabled stays false, so the later gmac_clk_enable(bsp_priv, false) becomes a no-op and the bulk clock references are leaked. Add the missing clk_bulk_disable_unprepare() on that failure path. Fixes: ea449f7fa0bf ("net: ethernet: stmmac: dwmac-rk: rework optional clock handling") Reviewed-by: Maxime Chevallier Reviewed-by: Heiko Stuebner Acked-by: Lorenzo Bianconi Signed-off-by: Coia Prant Link: https://patch.msgid.link/20260923123713.3137146-1-coiaprant@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c index 8d7042e6892619..72bdbcb5e863a7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c @@ -1162,8 +1162,11 @@ static int gmac_clk_enable(struct rk_priv_data *bsp_priv, bool enable) return ret; ret = clk_prepare_enable(bsp_priv->clk_phy); - if (ret) + if (ret) { + clk_bulk_disable_unprepare(bsp_priv->num_clks, + bsp_priv->clks); return ret; + } rk_configure_io_clksel(bsp_priv); rk_ungate_rmii_clock(bsp_priv); From 06e3f54e8b22040ada01a28343badf1990b4dd7d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 23 Sep 2026 13:03:18 +0000 Subject: [PATCH 1221/1417] net: flush skb_defer_nodes in dev_cpu_dead() When a CPU goes offline, dev_cpu_dead() drains its softnet queues (completion_queue, output_queue, poll_list, process_queue, and input_pkt_queue), but leaves net_hotdata.skb_defer_nodes untouched. If oldcpu goes offline while holding pending skbs in its skb_defer_nodes lists (e.g. below the sysctl_skb_defer_max >> 1 IPI threshold, or if the IPI races with CPU teardown), those skbs remain stranded until oldcpu is brought back online. If any of these skbs hold page_pool fragments, page_pool_destroy() will stall indefinitely waiting for inflight pages to be returned when a netdev or driver is torn down while oldcpu is offline. Additionally, if smp_call_function_single_async() fails in kick_defer_list_purge() because the target CPU went offline, reset defer_ipi_scheduled to 0 so future IPI kicks are not blocked when the CPU comes back online. Also, if oldcpu was the last online CPU on its NUMA node, drain that node's slot across all CPUs so no skbs deferred from that node remain stranded on idle remote CPUs (or if the node itself is subsequently offlined). Finally, in skb_attempt_defer_free(), re-check cpu_online(cpu) and whether the caller migrated CPUs after llist_add(), flushing the node list if so, to close the preemption TOCTOU race against CPU/node teardown. Fixes: 68822bdf76f1 ("net: generalize skb freeing deferral to per-cpu lists") Fixes: 5628f3fe3b16 ("net: add NUMA awareness to skb_attempt_defer_free()") Closes: https://lore.kernel.org/netdev/20260916003430.3612956-1-kris.pan@intel.com/ Signed-off-by: Eric Dumazet Cc: Kris Pan Link: https://patch.msgid.link/20260923130318.607255-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 47 +++++++++++++++++++++++++++++++++++------------ net/core/dev.h | 2 ++ net/core/skbuff.c | 12 +++++++++--- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index 0292a16e16c2cb..f660fccfc0dbc5 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -5376,7 +5376,8 @@ void kick_defer_list_purge(unsigned int cpu) backlog_unlock_irq_restore(sd, flags); } else if (!cmpxchg(&sd->defer_ipi_scheduled, 0, 1)) { - smp_call_function_single_async(cpu, &sd->defer_csd); + if (smp_call_function_single_async(cpu, &sd->defer_csd)) + WRITE_ONCE(sd->defer_ipi_scheduled, 0); } } @@ -6900,25 +6901,35 @@ bool napi_complete_done(struct napi_struct *n, int work_done) } EXPORT_SYMBOL(napi_complete_done); -static void skb_defer_free_flush(void) +static void __skb_defer_free_flush(struct skb_defer_node *sdn, int budget) { struct llist_node *free_list; struct sk_buff *skb, *next; + + if (llist_empty(&sdn->defer_list)) + return; + atomic_long_set(&sdn->defer_count, 0); + free_list = llist_del_all(&sdn->defer_list); + + llist_for_each_entry_safe(skb, next, free_list, ll_node) { + prefetch(next); + napi_consume_skb(skb, budget); + } +} + +void skb_defer_node_flush(struct skb_defer_node *sdn) +{ + __skb_defer_free_flush(sdn, 0); +} + +static void skb_defer_free_flush(void) +{ struct skb_defer_node *sdn; int node; for_each_node(node) { sdn = this_cpu_ptr(net_hotdata.skb_defer_nodes) + node; - - if (llist_empty(&sdn->defer_list)) - continue; - atomic_long_set(&sdn->defer_count, 0); - free_list = llist_del_all(&sdn->defer_list); - - llist_for_each_entry_safe(skb, next, free_list, ll_node) { - prefetch(next); - napi_consume_skb(skb, 1); - } + __skb_defer_free_flush(sdn, 1); } } @@ -12897,6 +12908,7 @@ static int dev_cpu_dead(unsigned int oldcpu) struct sk_buff **list_skb; struct sk_buff *skb; unsigned int cpu; + int node; struct softnet_data *sd, *oldsd, *remsd = NULL; local_irq_disable(); @@ -12957,6 +12969,17 @@ static int dev_cpu_dead(unsigned int oldcpu) rps_input_queue_head_incr(oldsd); } + for_each_node(node) + skb_defer_node_flush(per_cpu_ptr(net_hotdata.skb_defer_nodes, + oldcpu) + node); + node = cpu_to_node(oldcpu); + if (node_possible(node) && + !cpumask_intersects(cpumask_of_node(node), cpu_online_mask)) { + for_each_possible_cpu(cpu) + skb_defer_node_flush(per_cpu_ptr(net_hotdata.skb_defer_nodes, + cpu) + node); + } + return 0; } diff --git a/net/core/dev.h b/net/core/dev.h index b757faead4d1a3..04fb0e9a571e03 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -399,6 +399,8 @@ static inline void napi_assert_will_not_race(const struct napi_struct *napi) WARN_ON(READ_ONCE(napi->list_owner) != -1); } +struct skb_defer_node; +void skb_defer_node_flush(struct skb_defer_node *sdn); void kick_defer_list_purge(unsigned int cpu); int dev_set_hwtstamp_phylib(struct net_device *dev, diff --git a/net/core/skbuff.c b/net/core/skbuff.c index b4edbd06655e2a..c3042d822afa7a 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -7360,8 +7360,8 @@ void skb_attempt_defer_free(struct sk_buff *skb) struct skb_defer_node *sdn; unsigned long defer_count; unsigned int defer_max; + int cpu, my_cpu; bool kick; - int cpu; if (static_branch_unlikely(&skb_defer_disable_key)) goto nodefer; @@ -7371,7 +7371,8 @@ void skb_attempt_defer_free(struct sk_buff *skb) goto nodefer; cpu = skb->alloc_cpu; - if (cpu == raw_smp_processor_id() || + my_cpu = raw_smp_processor_id(); + if (cpu == my_cpu || WARN_ON_ONCE(cpu >= nr_cpu_ids) || !cpu_online(cpu)) { nodefer: kfree_skb_napi_cache(skb); @@ -7382,7 +7383,7 @@ nodefer: kfree_skb_napi_cache(skb); DEBUG_NET_WARN_ON_ONCE(skb->destructor); DEBUG_NET_WARN_ON_ONCE(skb_nfct(skb)); - sdn = per_cpu_ptr(net_hotdata.skb_defer_nodes, cpu) + numa_node_id(); + sdn = per_cpu_ptr(net_hotdata.skb_defer_nodes, cpu) + cpu_to_node(my_cpu); defer_max = READ_ONCE(net_hotdata.sysctl_skb_defer_max); defer_count = atomic_long_inc_return(&sdn->defer_count); @@ -7392,6 +7393,11 @@ nodefer: kfree_skb_napi_cache(skb); llist_add(&skb->ll_node, &sdn->defer_list); + if (unlikely(!cpu_online(cpu) || my_cpu != raw_smp_processor_id())) { + skb_defer_node_flush(sdn); + return; + } + /* Send an IPI every time queue reaches half capacity. */ kick = (defer_count - 1) == (defer_max >> 1); From 83769c23fb1879edc916a526ba424285033baf2d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 23 Sep 2026 14:59:42 +0000 Subject: [PATCH 1222/1417] gve: DQO: fix header length used by gve_can_send_tso() for UDP GSO gve_can_send_tso() computes how many buffers each segment of a GSO packet would span, and for this it needs the length of the headers that the device replicates in front of every segment. It unconditionally uses skb_tcp_all_headers(), which reads the doff field of the TCP header. SKB_GSO_UDP_L4 packets have no TCP header: tcp_hdrlen() then reads one byte of the UDP payload, and header_len can be anything in [0, 60] instead of the transport offset plus the eight bytes of the UDP header that gve_prep_tso() programs into the TSO context descriptor. A wrong header length shifts all the segment boundaries computed in the loop, so the number of buffers per segment can be over or under estimated. In the first case, GSO is needlessly disabled for this packet by gve_features_check_dqo() and the stack has to segment it. In the second case, the driver hands the device a packet whose segments span more than GVE_TX_MAX_DATA_DESCS buffers. Use the UDP header length for SKB_GSO_UDP_L4 packets, matching what gve_prep_tso() does. Fixes: 014c607f86ab ("gve: add support for UDP GSO for DQO format") Closes: https://lore.kernel.org/netdev/CANn89i+MS4L60sFQ49=-f-mibeveUfcrpVkD5X+Qy6SOnEpd6w@mail.gmail.com/ Signed-off-by: Eric Dumazet Cc: Ankit Garg Cc: Harshitha Ramamurthy Cc: Joshua Washington Cc: Willem de Bruijn Reviewed-by: Ankit Garg Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260923145942.731365-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_tx_dqo.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/google/gve/gve_tx_dqo.c b/drivers/net/ethernet/google/gve/gve_tx_dqo.c index 80ab0a449ff54e..0f6f7c5dbb2e02 100644 --- a/drivers/net/ethernet/google/gve/gve_tx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_tx_dqo.c @@ -918,13 +918,19 @@ static bool gve_can_send_tso(const struct sk_buff *skb) { const int max_bufs_per_seg = GVE_TX_MAX_DATA_DESCS - 1; const struct skb_shared_info *shinfo = skb_shinfo(skb); - const int header_len = skb_tcp_all_headers(skb); const int gso_size = shinfo->gso_size; int cur_seg_num_bufs; int prev_frag_size; int cur_seg_size; + int header_len; int i; + /* Must match the header length programmed by gve_prep_tso(). */ + if (skb_is_gso_tcp(skb)) + header_len = skb_tcp_all_headers(skb); + else + header_len = skb_transport_offset(skb) + sizeof(struct udphdr); + cur_seg_size = skb_headlen(skb) - header_len; prev_frag_size = skb_headlen(skb); cur_seg_num_bufs = cur_seg_size > 0; From 3b430ea6234087957b0d3cd181e3116722b59819 Mon Sep 17 00:00:00 2001 From: Eddie Phillips Date: Thu, 24 Sep 2026 00:42:51 +0000 Subject: [PATCH 1223/1417] gve: fix TX drop when GSO MSS is too small for hw The device has a strict requirement that the minimum MSS (gso_size) for TSO/GSO packets must be at least 88 bytes. If a packet below this threshold is pushed to the hardware, it can cause hardware to silently drop the packet, leading to increased latency and retransmissions. Currently, this is validated too late in the transmit pipeline (gve_prep_tso), leading to silent drops. Fix this by moving the validation into the .ndo_features_check callback (gve_features_check_dqo). If we detect a GSO packet with a gso_size smaller than GVE_TX_MIN_TSO_MSS_DQO, we clear the GSO feature flags for this packet. Fixes: a57e5de476be ("gve: DQO: Add TX path") Signed-off-by: Eddie Phillips Signed-off-by: Eric Dumazet Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260924004252.1196328-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_tx_dqo.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_tx_dqo.c b/drivers/net/ethernet/google/gve/gve_tx_dqo.c index 0f6f7c5dbb2e02..e5fe17b0479810 100644 --- a/drivers/net/ethernet/google/gve/gve_tx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_tx_dqo.c @@ -577,17 +577,6 @@ static int gve_prep_tso(struct sk_buff *skb) int header_len; int err; - /* Note: HW requires MSS (gso_size) to be <= 9728 and the total length - * of the TSO to be <= 262143. - * - * However, we don't validate these because: - * - Hypervisor enforces a limit of 9K MTU - * - Kernel will not produce a TSO larger than 64k - */ - - if (unlikely(shinfo->gso_size < GVE_TX_MIN_TSO_MSS_DQO)) - return -1; - /* Needed because we will modify header. */ err = skb_cow_head(skb, 0); if (err < 0) @@ -925,6 +914,9 @@ static bool gve_can_send_tso(const struct sk_buff *skb) int header_len; int i; + if (unlikely(gso_size < GVE_TX_MIN_TSO_MSS_DQO)) + return false; + /* Must match the header length programmed by gve_prep_tso(). */ if (skb_is_gso_tcp(skb)) header_len = skb_tcp_all_headers(skb); From 296c83b5ccc808c080865eb20fd7a477b0355bb7 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 24 Sep 2026 00:42:52 +0000 Subject: [PATCH 1224/1417] gve: DQO: reject TSO packets with an out of range MSS gve_prep_tso() notes that the device requires the MSS to be <= 9728, but does not enforce it, assuming the 9K MTU enforced by the hypervisor and the 64KB limit on TSO sizes are enough. This does not hold for packets that were not generated locally. A guest behind a tap, or any packet socket user, can provide an arbitrary gso_size in virtio_net_hdr. Layer 2 forwarding does not check the MTU for GSO packets (is_skb_forwardable()), and gso_features_check() only bounds skb->len and gso_segs, never gso_size. Such a packet reaches gve_tx_fill_tso_ctx_desc(), which puts gso_size into the mss field of the TSO context descriptor. This field is 14 bits wide, so a gso_size of 16384 is silently turned into an MSS of zero. Drop these packets from gve_prep_tso(), and make sure that gve_features_check_dqo() leaves their GSO bits alone: skb_segment() splits at gso_size regardless of the MTU, so falling back to software segmentation would give the device non TSO packets bigger than the 9728 bytes it supports. Note that the device can still be given oversized non TSO packets when the stack segments in software for other reasons, for instance after TSO has been disabled with ethtool. This is a generic issue, because the MTU check is skipped for GSO packets in the forwarding path, and is addressed separately. Fixes: a57e5de476be ("gve: DQO: Add TX path") Signed-off-by: Eric Dumazet Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260924004252.1196328-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/google/gve/gve_desc_dqo.h | 5 ++++ drivers/net/ethernet/google/gve/gve_tx_dqo.c | 26 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/google/gve/gve_desc_dqo.h b/drivers/net/ethernet/google/gve/gve_desc_dqo.h index f7786b03c74447..d2c86c8eeae2a1 100644 --- a/drivers/net/ethernet/google/gve/gve_desc_dqo.h +++ b/drivers/net/ethernet/google/gve/gve_desc_dqo.h @@ -14,6 +14,11 @@ #define GVE_TX_MAX_HDR_SIZE_DQO 255 #define GVE_TX_MIN_TSO_MSS_DQO 88 +/* HW limit. This also has to fit in the 14 bits of the mss field of + * struct gve_tx_tso_context_desc_dqo. + */ +#define GVE_TX_MAX_TSO_MSS_DQO 9728 + #ifndef __LITTLE_ENDIAN_BITFIELD #error "Only little endian supported" #endif diff --git a/drivers/net/ethernet/google/gve/gve_tx_dqo.c b/drivers/net/ethernet/google/gve/gve_tx_dqo.c index e5fe17b0479810..616c1921aebeac 100644 --- a/drivers/net/ethernet/google/gve/gve_tx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_tx_dqo.c @@ -577,6 +577,20 @@ static int gve_prep_tso(struct sk_buff *skb) int header_len; int err; + /* Note: HW requires the total length of the TSO to be <= 262143, + * this is enforced by netif_set_tso_max_size(). + * + * MSS (gso_size) can not be trusted: packets forwarded from a tap or + * injected by a packet socket can carry an arbitrary value, while the + * mss field of the TSO context descriptor is only 14 bits wide. + * + * A too big MSS is dropped here instead of being rejected from + * gve_features_check_dqo(), because software segmentation would + * produce packets larger than the device can send. + */ + if (unlikely(shinfo->gso_size > GVE_TX_MAX_TSO_MSS_DQO)) + return -1; + /* Needed because we will modify header. */ err = skb_cow_head(skb, 0); if (err < 0) @@ -964,7 +978,17 @@ netdev_features_t gve_features_check_dqo(struct sk_buff *skb, struct net_device *dev, netdev_features_t features) { - if (skb_is_gso(skb) && !gve_can_send_tso(skb)) + if (!skb_is_gso(skb)) + return features; + + /* Keep the GSO bits for a too big MSS, so that gve_prep_tso() drops + * the packet: software segmentation would give packets larger than + * the device can send. + */ + if (skb_shinfo(skb)->gso_size > GVE_TX_MAX_TSO_MSS_DQO) + return features; + + if (!gve_can_send_tso(skb)) return features & ~NETIF_F_GSO_MASK; return features; From 72b5b9a28b996e09b8b5b944370c79851bb68f52 Mon Sep 17 00:00:00 2001 From: Zixuan Chai Date: Thu, 24 Sep 2026 09:26:05 +0800 Subject: [PATCH 1225/1417] llc: reserve device headroom for allocated frames llc_alloc_frame() reserves link-layer headroom using the device type. This is insufficient for stacked Ethernet devices such as VLAN devices, where vlan_dev_hard_header() pushes a VLAN header before the lower device's Ethernet header. An LLC response on such a device can therefore underflow skb headroom in eth_header(). Use LL_RESERVED_SPACE() to account for the device's actual required headroom while preserving the existing LLC device-type check. Fixes: bf9ae5386bca ("llc: use dev_hard_header") Cc: stable@vger.kernel.org Reported-by: VEGA Signed-off-by: Zixuan Chai Signed-off-by: Ren Wei Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260924012613.2533934-1-weir@nebusec.ai Signed-off-by: Jakub Kicinski --- net/llc/llc_sap.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/llc/llc_sap.c b/net/llc/llc_sap.c index 1bd446a21092f8..3904a1b4ba8445 100644 --- a/net/llc/llc_sap.c +++ b/net/llc/llc_sap.c @@ -19,12 +19,12 @@ #include #include -static int llc_mac_header_len(unsigned short devtype) +static int llc_mac_header_len(struct net_device *dev) { - switch (devtype) { + switch (dev->type) { case ARPHRD_ETHER: case ARPHRD_LOOPBACK: - return sizeof(struct ethhdr); + return LL_RESERVED_SPACE(dev); } return 0; } @@ -45,7 +45,7 @@ struct sk_buff *llc_alloc_frame(struct sock *sk, struct net_device *dev, int hlen = type == LLC_PDU_TYPE_U ? 3 : 4; struct sk_buff *skb; - hlen += llc_mac_header_len(dev->type); + hlen += llc_mac_header_len(dev); skb = alloc_skb(hlen + data_size, GFP_ATOMIC); if (skb) { From 72f9dd522f8d6c5a00be9695c7bb74631eb5069e Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 24 Sep 2026 08:29:48 +0000 Subject: [PATCH 1226/1417] llc: fix skb UAF and leaks on llc_mac_hdr_init() failure In llc_conn_ac_resend_i_xxx_x_set_0_or_send_rr(), if llc_mac_hdr_init() fails, kfree_skb(skb) is called instead of kfree_skb(nskb). This leaks the newly allocated nskb, reads from the freed skb via LLC_I_GET_NR(pdu), and double-frees skb when llc_conn_state_process() drops its reference. In llc_sap_action_send_xid_r() and llc_sap_action_send_test_r(), nskb is leaked if llc_mac_hdr_init() returns an error. Free nskb in all three error paths. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Closes: https://lore.kernel.org/netdev/179022851638.2160803.1808206741379444999@kernel.org/ Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260924082951.1599377-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/llc/llc_c_ac.c | 2 +- net/llc/llc_s_ac.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/net/llc/llc_c_ac.c b/net/llc/llc_c_ac.c index 724ecd741d4cf4..1aa7fe28acddb2 100644 --- a/net/llc/llc_c_ac.c +++ b/net/llc/llc_c_ac.c @@ -437,7 +437,7 @@ int llc_conn_ac_resend_i_xxx_x_set_0_or_send_rr(struct sock *sk, if (likely(!rc)) llc_conn_send_pdu(sk, nskb); else - kfree_skb(skb); + kfree_skb(nskb); } if (rc) { nr = LLC_I_GET_NR(pdu); diff --git a/net/llc/llc_s_ac.c b/net/llc/llc_s_ac.c index 98deee56037351..831998211b52ee 100644 --- a/net/llc/llc_s_ac.c +++ b/net/llc/llc_s_ac.c @@ -121,6 +121,8 @@ int llc_sap_action_send_xid_r(struct llc_sap *sap, struct sk_buff *skb) rc = llc_mac_hdr_init(nskb, mac_sa, mac_da); if (likely(!rc)) rc = dev_queue_xmit(nskb); + else + kfree_skb(nskb); out: return rc; } @@ -170,6 +172,8 @@ int llc_sap_action_send_test_r(struct llc_sap *sap, struct sk_buff *skb) rc = llc_mac_hdr_init(nskb, mac_sa, mac_da); if (likely(!rc)) rc = dev_queue_xmit(nskb); + else + kfree_skb(nskb); out: return rc; } From ac704ff08e511c87643799c385f55ecd69b85e03 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 24 Sep 2026 08:29:49 +0000 Subject: [PATCH 1227/1417] bridge: check llc_mac_hdr_init() return value in br_send_bpdu() If llc_mac_hdr_init() fails (for instance if the port device type does not support LLC or dev_hard_header() fails), br_send_bpdu() should drop the skb instead of resetting the mac header to the LLC payload and transmitting a malformed frame. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Closes: https://lore.kernel.org/netdev/179022851638.2160803.1808206741379444999@kernel.org/ Cc: Nikolay Aleksandrov Cc: Ido Schimmel Cc: bridge@lists.linux.dev Signed-off-by: Eric Dumazet Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260924082951.1599377-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/bridge/br_stp_bpdu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/bridge/br_stp_bpdu.c b/net/bridge/br_stp_bpdu.c index 74ec42ba1e7d08..21d092f5acbb81 100644 --- a/net/bridge/br_stp_bpdu.c +++ b/net/bridge/br_stp_bpdu.c @@ -52,7 +52,10 @@ static void br_send_bpdu(struct net_bridge_port *p, LLC_SAP_BSPAN, LLC_PDU_CMD); llc_pdu_init_as_ui_cmd(skb); - llc_mac_hdr_init(skb, p->dev->dev_addr, p->br->group_addr); + if (llc_mac_hdr_init(skb, p->dev->dev_addr, p->br->group_addr)) { + kfree_skb(skb); + return; + } skb_reset_mac_header(skb); From 907b978e82cb4c1c245fc2985bb27c5d5c88c8f6 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 24 Sep 2026 08:29:50 +0000 Subject: [PATCH 1228/1417] net/sched: sch_teql: fix shadowed err in __teql_resolve() __teql_resolve() declares an inner 'int err;' inside the 'if (neigh_event_send(n, skb_res) == 0)' block, shadowing the outer 'int err = 0;'. As a result, a negative return from dev_hard_header() is written to the inner variable and __teql_resolve() still returns 0. Remove the shadowed variable and set the outer err to -EINVAL when dev_hard_header() returns a negative error. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Closes: https://lore.kernel.org/netdev/179022851638.2160803.1808206741379444999@kernel.org/ Cc: Jamal Hadi Salim Cc: Jiri Pirko Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260924082951.1599377-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/sched/sch_teql.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c index 9e52afc2d9808c..409ce50cc0dbc7 100644 --- a/net/sched/sch_teql.c +++ b/net/sched/sch_teql.c @@ -265,14 +265,11 @@ __teql_resolve(struct sk_buff *skb, struct sk_buff *skb_res, } if (neigh_event_send(n, skb_res) == 0) { - int err; char haddr[MAX_ADDR_LEN]; neigh_ha_snapshot(haddr, n, dev); - err = dev_hard_header(skb, dev, ntohs(skb_protocol(skb, false)), - haddr, NULL, skb->len); - - if (err < 0) + if (dev_hard_header(skb, dev, ntohs(skb_protocol(skb, false)), + haddr, NULL, skb->len) < 0) err = -EINVAL; } else { err = (skb_res == NULL) ? -EAGAIN : 1; From cd5dd68267c4238795fadaf02b3575ca3f8a6500 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 24 Sep 2026 08:29:51 +0000 Subject: [PATCH 1229/1417] vlan: ensure sufficient headroom in vlan_dev_hard_header() Callers that only reserve ETH_HLEN or less (such as llc_alloc_frame()), or skbs allocated before dynamic device/headroom changes (e.g. toggling VLAN_FLAG_REORDER_HDR or bonding/team switching slaves), can reach vlan_dev_hard_header() with insufficient headroom and trigger skb_under_panic(). Use skb_cow_head() in vlan_dev_hard_header() when VLAN_FLAG_REORDER_HDR is not set to ensure sufficient headroom for the VLAN header(s) and the underlying device hard header. Use READ_ONCE() to read dev->hard_header_len and dev->needed_headroom as they can be updated concurrently under RTNL (e.g. in vlan_transfer_features()) while vlan_dev_hard_header() runs locklessly on the transmit path. Also avoid LL_RESERVED_SPACE(dev) here so that the extra HH_DATA_MOD alignment padding does not trigger unnecessary pskb_expand_head() reallocations on inner stacked VLAN devices after the outer VLAN header has been pushed. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Zixuan Chai Closes: https://lore.kernel.org/netdev/cover.1789987105.git.petalzu987@gmail.com/ Link: https://lore.kernel.org/netdev/179022851638.2160803.1808206741379444999@kernel.org/ Cc: Hangbin Liu Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260924082951.1599377-5-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/8021q/vlan_dev.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c index 2859cbac3f266b..c949c6a829456c 100644 --- a/net/8021q/vlan_dev.c +++ b/net/8021q/vlan_dev.c @@ -55,6 +55,11 @@ static int vlan_dev_hard_header(struct sk_buff *skb, struct net_device *dev, int rc; if (!(vlan->flags & VLAN_FLAG_REORDER_HDR)) { + unsigned int hlen = READ_ONCE(dev->hard_header_len) + + READ_ONCE(dev->needed_headroom); + + if (skb_cow_head(skb, hlen) < 0) + return -ENOMEM; vhdr = skb_push(skb, VLAN_HLEN); vlan_tci = vlan->vlan_id; From fc6d80eb504458d6416b75a94188b268c95c6533 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Thu, 24 Sep 2026 11:44:12 -0400 Subject: [PATCH 1230/1417] tcp: prevent collapsing skbs across boundary in rtx queue tcp_write_collapse_fence() sets TCP_SKB_CB(skb)->eor = 1 on tcp_write_queue_tail(sk) to prevent skbs queued after a switch to device encryption from being collapsed into earlier skbs. The fence is a no-op if all earlier data has already been transmitted when the switch happens: sk->sk_write_queue is empty. The not yet acknowledged earlier skbs wait in sk->tcp_rtx_queue with eor 0. On a subsequent retransmit or SACK shift, tcp_retrans_try_collapse() or tcp_shift_skb_data() can then merge an skb queued after the switch into one queued before it. Both users of the fence are affected: - psp: devices only encrypt skbs with skb->decrypted set. The merged skb keeps decrypted = 0 from the earlier skb, so merged data sent after psp_sock_assoc_set_tx() is retransmitted in cleartext. - tls device offload: the merged skb straddles the start marker set in tls_set_device_offload(). The software fallback (fill_sg_in() returns -EINVAL) and the mlx5, nfp and funeth drivers cannot handle such an skb and drop it. Every retransmit rebuilds the same skb, so the connection stalls. Fix this in two places, for defense in depth: 1. Fall back to tcp_rtx_queue_tail(sk) in tcp_write_collapse_fence() when tcp_write_queue_tail(sk) is NULL. 2. Check !skb_cmp_decrypted(to, from) in tcp_skb_can_collapse(), as tcp_skb_can_collapse_rx() does on receive. skb_shift(), which both collapse paths call, already has a DEBUG_NET_WARN_ON_ONCE() for this condition. Fixes: e8f69799810c ("net/tls: Add generic NIC offload infrastructure") Cc: stable@vger.kernel.org Signed-off-by: Willem de Bruijn Reviewed-by: Eric Dumazet Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/20260924154427.953800-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- include/net/tcp.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/net/tcp.h b/include/net/tcp.h index 436495ff2271de..4416cdf9bf30ef 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -1232,9 +1232,9 @@ static inline bool tcp_skb_can_collapse_to(const struct sk_buff *skb) static inline bool tcp_skb_can_collapse(const struct sk_buff *to, const struct sk_buff *from) { - /* skb_cmp_decrypted() not needed, use tcp_write_collapse_fence() */ return likely(tcp_skb_can_collapse_to(to) && mptcp_skb_can_collapse(to, from) && + !skb_cmp_decrypted(to, from) && skb_pure_zcopy_same(to, from) && skb_frags_readable(to) == skb_frags_readable(from)); } @@ -2327,7 +2327,7 @@ static inline void tcp_rtx_queue_unlink_and_free(struct sk_buff *skb, struct soc static inline void tcp_write_collapse_fence(struct sock *sk) { - struct sk_buff *skb = tcp_write_queue_tail(sk); + struct sk_buff *skb = tcp_write_queue_tail(sk) ?: tcp_rtx_queue_tail(sk); if (skb) TCP_SKB_CB(skb)->eor = 1; From eb169cc54b87fc2c633c2343c6060c5955efb69d Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Thu, 24 Sep 2026 10:05:10 +0800 Subject: [PATCH 1231/1417] ASoC: rt1320: make SRAM registers readable to stop being cached rt1320_volatile_register() marks the SRAM ranges 0x10000000-0x10008fff and 0x1000c000-0x1000dfff as volatile, but regmap only honors that flag for a readable register and these ranges were missing from rt1320_readable_register(). They were therefore cached at probe and replayed over SoundWire on every runtime resume, delaying the first PCM open after autosuspend by about a second. Make the ranges readable so the volatile flag takes effect and the SRAM is no longer cached. Exclude 0x1000d000-0x1000d7ff from both callbacks, since rt1320_rae_load() deliberately caches that tuning-tool window. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20260924020510.3551163-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1320-sdw.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index b13622e54ae067..fa89db09665149 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -890,8 +890,9 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) case 0xf01e: case 0xf717 ... 0xf719: case 0xf720 ... 0xf723: - case 0x1000cd91 ... 0x1000cd96: - case RT1321_PATCH_MAIN_VER ... RT1321_PATCH_BETA_VER: + case 0x10000000 ... 0x10008fff: + case 0x1000c000 ... 0x1000cfff: + case 0x1000d800 ... 0x1000dfff: case 0x1000f008: case 0x1000f021: case 0x20003000 ... 0x2000300f: @@ -996,7 +997,8 @@ static bool rt1320_volatile_register(struct device *dev, unsigned int reg) case 0xf717 ... 0xf719: case 0xf720 ... 0xf723: case 0x10000000 ... 0x10008fff: - case 0x1000c000 ... 0x1000dfff: + case 0x1000c000 ... 0x1000cfff: + case 0x1000d800 ... 0x1000dfff: case 0x1000f008: case 0x1000f021: case 0x2000300e ... 0x2000300f: From 7cb06353a20337598eff3e645b5454ee71d81937 Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Fri, 18 Sep 2026 19:01:12 +0530 Subject: [PATCH 1232/1417] ASoC: dt-bindings: qcom: add QAIF AIF MI2S and TDM dai ids Add new dai ids entries for Qualcomm Audio Interface (QAIF) AIF MI2S and TDM audio lines. Co-developed-by: Harendra Gautam Signed-off-by: Harendra Gautam Signed-off-by: Mohammad Rafi Shaik Reviewed-by: Prasad Kumpatla Tested-by: Prasad Kumpatla Link: https://patch.msgid.link/20260918-qaif_dai_id_support-v1-1-ed863c0e5e45@oss.qualcomm.com Signed-off-by: Mark Brown --- .../sound/qcom,q6dsp-lpass-ports.yaml | 16 ++++-- .../sound/qcom,q6dsp-lpass-ports.h | 52 +++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml b/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml index 3b03e2acd67e08..63f53837bd9fb2 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml @@ -108,10 +108,15 @@ patternProperties: properties: reg: contains: - # TDM DAI ID range from PRIMARY_TDM_RX_0 - QUINARY_TDM_TX_7 + # TDM DAI ID range from PRIMARY_TDM_RX_0 - QUINARY_TDM_TX_7 and + # AIF_TDM_RX_0 - AIF_TDM_TX_12 items: - minimum: 24 - maximum: 103 + oneOf: + - minimum: 24 + maximum: 103 + - minimum: 179 + maximum: 204 + then: required: - qcom,tdm-sync-mode @@ -127,7 +132,8 @@ patternProperties: contains: # MI2S DAI ID range PRIMARY_MI2S_RX - QUATERNARY_MI2S_TX and # QUINARY_MI2S_RX - QUINARY_MI2S_TX and - # LPI_MI2S_RX_0 - LPI_MI2S_TX_6 + # LPI_MI2S_RX_0 - LPI_MI2S_TX_6 and + # AIF_MI2S_RX_0 - AIF_MI2S_TX_12 items: oneOf: - minimum: 16 @@ -136,6 +142,8 @@ patternProperties: maximum: 128 - minimum: 137 maximum: 152 + - minimum: 153 + maximum: 178 then: required: - qcom,sd-lines diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h index 3a99703dbc4a0d..e6c792d9312fc5 100644 --- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h @@ -156,6 +156,58 @@ #define LPI_MI2S_TX_5 150 #define LPI_MI2S_RX_6 151 #define LPI_MI2S_TX_6 152 +#define AIF_MI2S_RX_0 153 +#define AIF_MI2S_TX_0 154 +#define AIF_MI2S_RX_1 155 +#define AIF_MI2S_TX_1 156 +#define AIF_MI2S_RX_2 157 +#define AIF_MI2S_TX_2 158 +#define AIF_MI2S_RX_3 159 +#define AIF_MI2S_TX_3 160 +#define AIF_MI2S_RX_4 161 +#define AIF_MI2S_TX_4 162 +#define AIF_MI2S_RX_5 163 +#define AIF_MI2S_TX_5 164 +#define AIF_MI2S_RX_6 165 +#define AIF_MI2S_TX_6 166 +#define AIF_MI2S_RX_7 167 +#define AIF_MI2S_TX_7 168 +#define AIF_MI2S_RX_8 169 +#define AIF_MI2S_TX_8 170 +#define AIF_MI2S_RX_9 171 +#define AIF_MI2S_TX_9 172 +#define AIF_MI2S_RX_10 173 +#define AIF_MI2S_TX_10 174 +#define AIF_MI2S_RX_11 175 +#define AIF_MI2S_TX_11 176 +#define AIF_MI2S_RX_12 177 +#define AIF_MI2S_TX_12 178 +#define AIF_TDM_RX_0 179 +#define AIF_TDM_TX_0 180 +#define AIF_TDM_RX_1 181 +#define AIF_TDM_TX_1 182 +#define AIF_TDM_RX_2 183 +#define AIF_TDM_TX_2 184 +#define AIF_TDM_RX_3 185 +#define AIF_TDM_TX_3 186 +#define AIF_TDM_RX_4 187 +#define AIF_TDM_TX_4 188 +#define AIF_TDM_RX_5 189 +#define AIF_TDM_TX_5 190 +#define AIF_TDM_RX_6 191 +#define AIF_TDM_TX_6 192 +#define AIF_TDM_RX_7 193 +#define AIF_TDM_TX_7 194 +#define AIF_TDM_RX_8 195 +#define AIF_TDM_TX_8 196 +#define AIF_TDM_RX_9 197 +#define AIF_TDM_TX_9 198 +#define AIF_TDM_RX_10 199 +#define AIF_TDM_TX_10 200 +#define AIF_TDM_RX_11 201 +#define AIF_TDM_TX_11 202 +#define AIF_TDM_RX_12 203 +#define AIF_TDM_TX_12 204 #define LPASS_CLK_ID_PRI_MI2S_IBIT 1 #define LPASS_CLK_ID_PRI_MI2S_EBIT 2 From 897be15f03b18d63ed5614b7b0ff1b379681e61a Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Fri, 18 Sep 2026 19:01:13 +0530 Subject: [PATCH 1233/1417] ASoC: qcom: qdsp6: lpass-ports: add support for QAIF AIF MI2S and TDM dais Add support for Qualcomm Audio Interface (QAIF) AIF MI2S and TDM dais in the dai-driver, these dais are used in Shikra, Hawi and Nord based Qualcomm platform devices. Signed-off-by: Mohammad Rafi Shaik Reviewed-by: Prasad Kumpatla Tested-by: Prasad Kumpatla Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260918-qaif_dai_id_support-v1-2-ed863c0e5e45@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-lpass-dais.c | 2 + sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 54 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index e204fd59e5129f..83f2711bceeaaf 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -386,6 +386,7 @@ static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai, switch (dai->id) { case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + case AIF_TDM_RX_0 ... AIF_TDM_TX_12: slot_mask = (dai->id & 0x1) ? tx_mask : rx_mask; if (slot_mask & ~cap_mask) { dev_err(dai->dev, "%s: invalid slot mask 0x%x for %d slots\n", @@ -474,6 +475,7 @@ static int of_q6apm_parse_dai_data(struct device *dev, case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: case SENARY_MI2S_RX ... SENARY_MI2S_TX: case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + case AIF_MI2S_RX_0 ... AIF_TDM_TX_12: priv = &data->priv[id]; priv->mclk = of_clk_get_by_name(node, "mclk"); if (IS_ERR(priv->mclk)) { diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c index c3d8116ad50352..f422346a50c694 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c @@ -548,6 +548,32 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { .id = SENARY_MI2S_TX, .name = "SEN_MI2S_TX", }, + Q6AFE_MI2S_RX_DAI("AIF RX0", AIF_MI2S_RX_0), + Q6AFE_MI2S_RX_DAI("AIF RX1", AIF_MI2S_RX_1), + Q6AFE_MI2S_RX_DAI("AIF RX2", AIF_MI2S_RX_2), + Q6AFE_MI2S_RX_DAI("AIF RX3", AIF_MI2S_RX_3), + Q6AFE_MI2S_RX_DAI("AIF RX4", AIF_MI2S_RX_4), + Q6AFE_MI2S_RX_DAI("AIF RX5", AIF_MI2S_RX_5), + Q6AFE_MI2S_RX_DAI("AIF RX6", AIF_MI2S_RX_6), + Q6AFE_MI2S_RX_DAI("AIF RX7", AIF_MI2S_RX_7), + Q6AFE_MI2S_RX_DAI("AIF RX8", AIF_MI2S_RX_8), + Q6AFE_MI2S_RX_DAI("AIF RX9", AIF_MI2S_RX_9), + Q6AFE_MI2S_RX_DAI("AIF RX10", AIF_MI2S_RX_10), + Q6AFE_MI2S_RX_DAI("AIF RX11", AIF_MI2S_RX_11), + Q6AFE_MI2S_RX_DAI("AIF RX12", AIF_MI2S_RX_12), + Q6AFE_MI2S_TX_DAI("AIF TX0", AIF_MI2S_TX_0), + Q6AFE_MI2S_TX_DAI("AIF TX1", AIF_MI2S_TX_1), + Q6AFE_MI2S_TX_DAI("AIF TX2", AIF_MI2S_TX_2), + Q6AFE_MI2S_TX_DAI("AIF TX3", AIF_MI2S_TX_3), + Q6AFE_MI2S_TX_DAI("AIF TX4", AIF_MI2S_TX_4), + Q6AFE_MI2S_TX_DAI("AIF TX5", AIF_MI2S_TX_5), + Q6AFE_MI2S_TX_DAI("AIF TX6", AIF_MI2S_TX_6), + Q6AFE_MI2S_TX_DAI("AIF TX7", AIF_MI2S_TX_7), + Q6AFE_MI2S_TX_DAI("AIF TX8", AIF_MI2S_TX_8), + Q6AFE_MI2S_TX_DAI("AIF TX9", AIF_MI2S_TX_9), + Q6AFE_MI2S_TX_DAI("AIF TX10", AIF_MI2S_TX_10), + Q6AFE_MI2S_TX_DAI("AIF TX11", AIF_MI2S_TX_11), + Q6AFE_MI2S_TX_DAI("AIF TX12", AIF_MI2S_TX_12), Q6AFE_MI2S_RX_DAI("LPI RX0", LPI_MI2S_RX_0), Q6AFE_MI2S_RX_DAI("LPI RX1", LPI_MI2S_RX_1), Q6AFE_MI2S_RX_DAI("LPI RX2", LPI_MI2S_RX_2), @@ -562,6 +588,32 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { Q6AFE_MI2S_TX_DAI("LPI TX4", LPI_MI2S_TX_4), Q6AFE_MI2S_TX_DAI("LPI TX5", LPI_MI2S_TX_5), Q6AFE_MI2S_TX_DAI("LPI TX6", LPI_MI2S_TX_6), + Q6AFE_TDM_PB_DAI("AIF RX0", 0, AIF_TDM_RX_0), + Q6AFE_TDM_PB_DAI("AIF RX1", 1, AIF_TDM_RX_1), + Q6AFE_TDM_PB_DAI("AIF RX2", 2, AIF_TDM_RX_2), + Q6AFE_TDM_PB_DAI("AIF RX3", 3, AIF_TDM_RX_3), + Q6AFE_TDM_PB_DAI("AIF RX4", 4, AIF_TDM_RX_4), + Q6AFE_TDM_PB_DAI("AIF RX5", 5, AIF_TDM_RX_5), + Q6AFE_TDM_PB_DAI("AIF RX6", 6, AIF_TDM_RX_6), + Q6AFE_TDM_PB_DAI("AIF RX7", 7, AIF_TDM_RX_7), + Q6AFE_TDM_PB_DAI("AIF RX8", 8, AIF_TDM_RX_8), + Q6AFE_TDM_PB_DAI("AIF RX9", 9, AIF_TDM_RX_9), + Q6AFE_TDM_PB_DAI("AIF RX10", 10, AIF_TDM_RX_10), + Q6AFE_TDM_PB_DAI("AIF RX11", 11, AIF_TDM_RX_11), + Q6AFE_TDM_PB_DAI("AIF RX12", 12, AIF_TDM_RX_12), + Q6AFE_TDM_CAP_DAI("AIF TX0", 0, AIF_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("AIF TX1", 1, AIF_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("AIF TX2", 2, AIF_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("AIF TX3", 3, AIF_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("AIF TX4", 4, AIF_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("AIF TX5", 5, AIF_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("AIF TX6", 6, AIF_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("AIF TX7", 7, AIF_TDM_TX_7), + Q6AFE_TDM_CAP_DAI("AIF TX8", 8, AIF_TDM_TX_8), + Q6AFE_TDM_CAP_DAI("AIF TX9", 9, AIF_TDM_TX_9), + Q6AFE_TDM_CAP_DAI("AIF TX10", 10, AIF_TDM_TX_10), + Q6AFE_TDM_CAP_DAI("AIF TX11", 11, AIF_TDM_TX_11), + Q6AFE_TDM_CAP_DAI("AIF TX12", 12, AIF_TDM_TX_12), Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0), Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1), Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2), @@ -717,9 +769,11 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + case AIF_MI2S_RX_0 ... AIF_MI2S_TX_12: q6dsp_audio_fe_dais[i].ops = cfg->q6i2s_ops; break; case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + case AIF_TDM_RX_0 ... AIF_TDM_TX_12: q6dsp_audio_fe_dais[i].ops = cfg->q6tdm_ops; break; case WSA_CODEC_DMA_RX_0 ... RX_CODEC_DMA_RX_7: From 654d53e6901f0610580239ce88730a406422981d Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Fri, 18 Sep 2026 19:01:14 +0530 Subject: [PATCH 1234/1417] ASoC: qcom: sc8280xp: Handle AIF MI2S and TDM interfaces Handle AIF MI2S and TDM DAI IDs in the SC8280XP machine driver and extend LPASS_MAX_PORT to accommodate the additional audio interfaces. Signed-off-by: Mohammad Rafi Shaik Reviewed-by: Prasad Kumpatla Tested-by: Prasad Kumpatla Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260918-qaif_dai_id_support-v1-3-ed863c0e5e45@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/qcom/common.h | 2 +- sound/soc/qcom/sc8280xp.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h index c1deac109f2435..18171656408c92 100644 --- a/sound/soc/qcom/common.h +++ b/sound/soc/qcom/common.h @@ -7,7 +7,7 @@ #include #include -#define LPASS_MAX_PORT (LPI_MI2S_TX_6 + 1) +#define LPASS_MAX_PORT (AIF_TDM_TX_12 + 1) struct qcom_snd_tdm_slot_cfg { unsigned int tx_mask; diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 4d48e1012cd4b1..0876b9be136d50 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -260,6 +260,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: case SENARY_MI2S_RX ... SENARY_MI2S_TX: case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: + case AIF_MI2S_RX_0 ... AIF_MI2S_TX_12: ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); if (ret && ret != -ENOTSUPP) return ret; @@ -296,6 +297,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, } break; case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + case AIF_TDM_RX_0 ... AIF_TDM_TX_12: return sc8280xp_tdm_hw_params(substream, params); default: break; From dcc36d766dc82bc57cd730915d484119befcfca8 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Thu, 24 Sep 2026 21:49:32 +0800 Subject: [PATCH 1235/1417] ASoC: Intel: atom: Fix platform device leak on probe failure sst_acpi_probe() registers platform devices for the SST platform and machine board using platform_device_register_data(), but does not unregister them when later probe steps fail. Fixes: caf94ed8629a ("ASoC: Intel: bytcr_rt5640: fixup DAI codec_name with HID") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260924134932.1590967-1-lgs201920130244@gmail.com Signed-off-by: Mark Brown --- sound/soc/intel/atom/sst/sst_acpi.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c index 73624e1b138a65..de1ad168008bda 100644 --- a/sound/soc/intel/atom/sst/sst_acpi.c +++ b/sound/soc/intel/atom/sst/sst_acpi.c @@ -254,6 +254,11 @@ static int sst_platform_get_resources(struct intel_sst_drv *ctx) return 0; } +static void sst_unregister_platform_device(void *data) +{ + platform_device_unregister(data); +} + static int sst_acpi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -342,7 +347,10 @@ static int sst_acpi_probe(struct platform_device *pdev) pdata->platform); return PTR_ERR(plat_dev); } - + ret = devm_add_action_or_reset(dev, sst_unregister_platform_device, + plat_dev); + if (ret) + return ret; /* * Create platform device for sst machine driver, * pass machine info as pdata @@ -355,6 +363,10 @@ static int sst_acpi_probe(struct platform_device *pdev) return PTR_ERR(mdev); } + ret = devm_add_action_or_reset(dev, sst_unregister_platform_device, + mdev); + if (ret) + return ret; /* Fill sst platform data */ ctx->pdata = pdata; strscpy(ctx->firmware_name, mach->fw_filename); From 8eb2b0c882866c4c3f5622366a57481bb2dc494c Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 24 Sep 2026 13:53:03 +0800 Subject: [PATCH 1236/1417] ASoC: sdw_utils: Add rt766/rt767 support This patch adds the codec_info and route-related settings. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260924-rt766-v2-2-e0b63606a5e8@realtek.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c | 6 + .../sdw_utils/soc_sdw_rt_sdca_jack_common.c | 8 + sound/soc/sdw_utils/soc_sdw_utils.c | 172 ++++++++++++++++++ 3 files changed, 186 insertions(+) diff --git a/sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c b/sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c index 5bf3627a97a00d..6e5fa93b5fc204 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c +++ b/sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c @@ -33,6 +33,11 @@ static const struct snd_soc_dapm_route rt722_spk_map[] = { { "Speaker", NULL, "rt722 SPK" }, }; +static const struct snd_soc_dapm_route rt766_spk_map[] = { + { "Speaker", NULL, "rt766 SPOL" }, + { "Speaker", NULL, "rt766 SPOR" }, +}; + /* Structure to map codec names to respective route arrays and sizes */ struct codec_route_map { const char *codec_name; @@ -45,6 +50,7 @@ static const struct codec_route_map codec_routes[] = { { "rt712", rt712_spk_map, ARRAY_SIZE(rt712_spk_map) }, { "rt721", rt721_spk_map, ARRAY_SIZE(rt721_spk_map) }, { "rt722", rt722_spk_map, ARRAY_SIZE(rt722_spk_map) }, + { "rt766", rt766_spk_map, ARRAY_SIZE(rt766_spk_map) }, }; static const struct codec_route_map *get_codec_route_map(const char *codec_name) diff --git a/sound/soc/sdw_utils/soc_sdw_rt_sdca_jack_common.c b/sound/soc/sdw_utils/soc_sdw_rt_sdca_jack_common.c index 2547b5b3fdd795..b4e427d551305c 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt_sdca_jack_common.c +++ b/sound/soc/sdw_utils/soc_sdw_rt_sdca_jack_common.c @@ -70,6 +70,11 @@ static const struct snd_soc_dapm_route rt722_sdca_map[] = { { "rt722 MIC2", NULL, "Headset Mic" }, }; +static const struct snd_soc_dapm_route rt766_sdca_map[] = { + { "Headphone", NULL, "rt766 HP" }, + { "rt766 MIC2", NULL, "Headset Mic" }, +}; + static struct snd_soc_jack_pin rt_sdca_jack_pins[] = { { .pin = "Headphone", @@ -133,6 +138,9 @@ int asoc_sdw_rt_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_s } else if (strstr(component->name_prefix, "rt722")) { ret = snd_soc_dapm_add_routes(dapm, rt722_sdca_map, ARRAY_SIZE(rt722_sdca_map)); + } else if (strstr(component->name_prefix, "rt766")) { + ret = snd_soc_dapm_add_routes(dapm, rt766_sdca_map, + ARRAY_SIZE(rt766_sdca_map)); } else { dev_err(card->dev, "%s is not supported\n", component->name_prefix); return -EINVAL; diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index f0f0822d8ed6c3..14aee10b40ba7c 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -779,6 +779,178 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, .dai_num = 3, }, + { + .vendor_id = 0x025d, + .part_id = 0x767, + .name_prefix = "rt766", + .version_id = 3, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt766-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt766-sdca-aif2", + .component_name = "rt766", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt766-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x025d, + .part_id = 0x767, + .name_prefix = "rt766", + .version_id = 4, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt766-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt766-sdca-aif2", + .component_name = "rt766", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt766-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x025d, + .part_id = 0x766, + .name_prefix = "rt766", + .version_id = 3, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt766-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt766-sdca-aif2", + .component_name = "rt766", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt766-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x025d, + .part_id = 0x766, + .name_prefix = "rt766", + .version_id = 4, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt766-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt766-sdca-aif2", + .component_name = "rt766", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt766-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, + }, + .dai_num = 3, + }, { .vendor_id = 0x019f, .part_id = 0x8373, From be190d999369fad147e94124cbab0b28583496e7 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 24 Sep 2026 13:53:04 +0800 Subject: [PATCH 1237/1417] ASoC: rt766: avoid re-initialize the blind write We only need to check the function_status to determine whether the system has undergone a cold or warm reboot. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260924-rt766-v2-3-e0b63606a5e8@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt766-sdca.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c index 5a1c1e10e6d7d3..28b4118e05c95d 100644 --- a/sound/soc/codecs/rt766-sdca.c +++ b/sound/soc/codecs/rt766-sdca.c @@ -1297,7 +1297,7 @@ static int rt766_func_initialize(struct rt766_sdca_priv *rt766, struct sdca_func regmap_read(rt766->regmap, func_status_reg, &func_status); dev_dbg(dev, "%s, %s func_status=0x%x\n", __func__, func_data->desc->name, func_status); - if ((func_status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) || (!rt766->first_hw_init)) { + if ((func_status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION)) { ret = sdca_regmap_write_init(dev, rt766->regmap, func_data); if (ret) { dev_err(dev, "%s initialization table update failed\n", func_data->desc->name); From d06607bafb36abc14ed34c8adfb1239bebcd83d1 Mon Sep 17 00:00:00 2001 From: Syed Saba Kareem Date: Thu, 24 Sep 2026 18:11:52 +0530 Subject: [PATCH 1238/1417] ASoC: amd: acp: fix TAS2783 SoundWire codec unmet dependencies SND_SOC_AMD_LEGACY_SDW_MACH unconditionally selects SND_SOC_TAS2783_SDW, but that codec depends on SND_SOC_SDCA and EFI in addition to SOUNDWIRE. An unconditional select force-enables the codec even when those dependencies are not met, which the kernel test robot reported as unmet direct dependencies when EFI=n: WARNING: unmet direct dependencies detected for SND_SOC_TAS2783_SDW Depends on [n]: SOUND [=y] && SND [=y] && SND_SOC [=y] && SOUNDWIRE [=y] && SND_SOC_SDCA [=y] && EFI [=n] Selected by [y]: - SND_SOC_AMD_LEGACY_SDW_MACH [=y] && ... The other SoundWire codecs selected by this machine driver only depend on SOUNDWIRE, which the driver already depends on, so they select cleanly. SND_SOC_TAS2783_SDW is the only one that additionally needs the SDCA core and EFI (it reads speaker calibration data from EFI variables). SND_SOC_SDCA cannot simply be selected here, as that introduces a recursive Kconfig dependency (SOUNDWIRE depends on SND_SOC_SDCA_OPTIONAL, whose default pulls in SND_SOC_SDCA), and EFI is a platform feature that must not be selected by a driver. Instead, guard the select with the missing dependencies so the codec is only (and always) built when they are satisfied: select SND_SOC_TAS2783_SDW if SND_SOC_SDCA && EFI This keeps the TAS2783 SoundWire codec built together with the machine driver on platforms that can use it, while avoiding the unmet dependency when SND_SOC_SDCA or EFI is disabled. Fixes: 28114992dd2f ("ASoC: amd: acp: enable TAS2783 and add RT712-VB SoundWire machine") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609182147.gfofWIXW-lkp@intel.com/ Signed-off-by: Syed Saba Kareem Link: https://patch.msgid.link/20260924124205.3376083-1-syed.sabakareem@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig index e880e44b9763fc..6433876430f046 100644 --- a/sound/soc/amd/acp/Kconfig +++ b/sound/soc/amd/acp/Kconfig @@ -181,7 +181,7 @@ config SND_SOC_AMD_LEGACY_SDW_MACH select SND_SOC_RT715_SDW select SND_SOC_RT715_SDCA_SDW select SND_SOC_RT722_SDCA_SDW - select SND_SOC_TAS2783_SDW + select SND_SOC_TAS2783_SDW if SND_SOC_SDCA && EFI help This option enables Legacy(No DSP) sound card support for SoundWire enabled AMD platforms along with ACP PDM controller. From 0bc42544a4a06e8d76eb412d2b1414db910a311d Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Sun, 20 Sep 2026 10:42:32 +0800 Subject: [PATCH 1239/1417] ASoC: rt712-sdca-dmic: fix drvdata type in the gain controls rt712_sdca_dmic_set_gain_get() and rt712_sdca_dmic_set_gain_put() take the component drvdata as struct rt712_sdca_priv, but this driver stores a struct rt712_sdca_dmic_priv. regmap and mbq_regmap are at the same offsets in both structs, so the register accesses work. slave is not: rt712_sdca_priv has dmic_component in front of it, so its slave overlays rt712_sdca_dmic_priv's params, which this driver never writes. When regmap_write() fails in the put handler, dev_err() is called with &NULL->dev (RSI below): rt712-sdca-dmic sdw:0:3:025d:1713:01: Defer on undeferrable control: 40800f13 BUG: kernel NULL pointer dereference, address: 0000000000000058 RIP: 0010:__dev_printk+0x10/0x70 RDX: ffffcdac45f83cd0 RSI: 0000000000000008 RDI: ffffffffa09cb5cb Call Trace: _dev_err+0x7f/0x99 rt712_sdca_dmic_set_gain_put.cold+0x20/0x25 [snd_soc_rt712_sdca_dmic] snd_ctl_elem_write+0x19a/0x1f0 [snd] snd_ctl_ioctl+0x658/0x8a0 [snd] Seen on a Dell Precision 5690 running 7.2.5-200.fc44, with alsactl writing the control during boot. Compile-tested only. Fixes: 63a511284c9e ("ASoC: rt712-sdca: Add RT712 SDCA driver for Mic topology") Closes: https://bugzilla.redhat.com/show_bug.cgi?id=2532834 Cc: stable@vger.kernel.org Signed-off-by: Junjie Cao Link: https://patch.msgid.link/20260920024232.710189-1-junjie.cao@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca-dmic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c index a9f3aa4e143ae7..882390890738e2 100644 --- a/sound/soc/codecs/rt712-sdca-dmic.c +++ b/sound/soc/codecs/rt712-sdca-dmic.c @@ -246,7 +246,7 @@ static int rt712_sdca_dmic_set_gain_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component); + struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component); struct rt712_sdca_dmic_kctrl_priv *p = (struct rt712_sdca_dmic_kctrl_priv *)kcontrol->private_value; unsigned int regvalue, ctl, i; @@ -277,7 +277,7 @@ static int rt712_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol, struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct rt712_sdca_dmic_kctrl_priv *p = (struct rt712_sdca_dmic_kctrl_priv *)kcontrol->private_value; - struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component); + struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component); unsigned int gain_val[4]; unsigned int i, adc_vol_flag = 0, changed = 0; unsigned int regvalue[4]; From 55c9c04e847fafbf319e7053993959eb3c3a9f13 Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Fri, 4 Sep 2026 18:02:25 +0800 Subject: [PATCH 1240/1417] ASoC: dt-bindings: es8316: Document jack detect inversion The ES8316 driver supports the everest,jack-detect-inverted property to account for boards which invert the codec jack-detect signal. However, the property is not described by the binding, so a valid user of the driver fails schema validation because the binding rejects unknown properties. Document the flag so boards can describe the jack-detect polarity used by their wiring. Acked-by: Krzysztof Kozlowski Signed-off-by: Hongyang Zhao Link: https://patch.msgid.link/20260904-rubikpi-next-20260605-v3-1-f49146d85af3@thundersoft.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/everest,es8316.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/everest,es8316.yaml b/Documentation/devicetree/bindings/sound/everest,es8316.yaml index f4ff23120c5b55..9060358a9080c6 100644 --- a/Documentation/devicetree/bindings/sound/everest,es8316.yaml +++ b/Documentation/devicetree/bindings/sound/everest,es8316.yaml @@ -84,6 +84,11 @@ properties: "#sound-dai-cells": const: 0 + everest,jack-detect-inverted: + $ref: /schemas/types.yaml#/definitions/flag + description: + Defined to invert the jack detection. + required: - compatible - reg From f6a447b32a3995c09ffca38a9b3adb9bf833cf50 Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Fri, 4 Sep 2026 18:02:26 +0800 Subject: [PATCH 1241/1417] ASoC: dt-bindings: qcom,sm8250: Add RubikPi 3 sound card The QCS6490-based Thundercomm RubikPi 3 routes primary MI2S to an external ES8316 headset codec and quaternary MI2S to the LT9611 HDMI bridge. This requires board-specific DAI clocking and jack setup in the sc8280xp machine driver. The existing QCM6490 and QCS6490 compatibles select machine data for boards using different codec and audio routing arrangements, so they cannot be used as compatible fallbacks for RubikPi 3. Add a dedicated compatible to select the RubikPi 3 machine data. Signed-off-by: Hongyang Zhao Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260904-rubikpi-next-20260605-v3-2-f49146d85af3@thundersoft.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/qcom,sm8250.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml index 1536fcd96d6873..fc7bccdace0f42 100644 --- a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml @@ -54,6 +54,7 @@ properties: - qcom,sm8250-sndcard - qcom,sm8450-sndcard - qcom,x1e80100-sndcard + - thundercomm,qcs6490-rubikpi3-sndcard audio-routing: $ref: /schemas/types.yaml#/definitions/non-unique-string-array From 7f8dd07370f8c17f8f98f0fa98691635828b0dac Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Fri, 4 Sep 2026 18:02:27 +0800 Subject: [PATCH 1242/1417] ASoC: qcom: common: Add generic headset jack helpers qcom_snd_wcd_jack_setup() combines creation of the card-level headset jack with the WCD-specific operation of attaching jack detection to codecs on the TX codec DMA links. External codecs connected over MI2S also provide component jack detection, but cannot use the WCD-specific DAI filtering. Factor the common jack allocation, DAPM pin registration and headset button mappings into a private initializer. Reuse it from the existing WCD path and add a generic setup helper which attaches the jack to every codec component in a runtime. Treat -ENOTSUPP as a no-op because snd_soc_component_set_jack() uses it for components which do not provide a set_jack callback. Add a matching cleanup helper so machine drivers can detach component jack detection when the DAI link exits. The WCD setup behavior remains unchanged. Signed-off-by: Hongyang Zhao Link: https://patch.msgid.link/20260904-rubikpi-next-20260605-v3-3-f49146d85af3@thundersoft.com Signed-off-by: Mark Brown --- sound/soc/qcom/common.c | 61 +++++++++++++++++++++++++++++++++++------ sound/soc/qcom/common.h | 3 ++ 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index d9f256d5197332..83b745f617a86a 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -339,13 +339,11 @@ static struct snd_soc_jack_pin qcom_headset_jack_pins[] = { }, }; -int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, - struct snd_soc_jack *jack, bool *jack_setup) +static int qcom_snd_headset_jack_init(struct snd_soc_card *card, + struct snd_soc_jack *jack, + bool *jack_setup) { - struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); - struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); - struct snd_soc_card *card = rtd->card; - int rval, i; + int rval; if (!*jack_setup) { rval = snd_soc_card_jack_new_pins(card, "Headset Jack", @@ -369,6 +367,55 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, *jack_setup = true; } + return 0; +} + +int qcom_snd_headset_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup) +{ + struct snd_soc_dai *codec_dai; + struct snd_soc_card *card = rtd->card; + int rval, i; + + rval = qcom_snd_headset_jack_init(card, jack, jack_setup); + if (rval) + return rval; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + rval = snd_soc_component_set_jack(codec_dai->component, jack, NULL); + if (rval != 0 && rval != -ENOTSUPP) { + dev_warn(card->dev, "Failed to set jack: %d\n", rval); + qcom_snd_headset_jack_cleanup(rtd); + return rval; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_snd_headset_jack_setup); + +void qcom_snd_headset_jack_cleanup(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai; + int i; + + for_each_rtd_codec_dais(rtd, i, codec_dai) + snd_soc_component_set_jack(codec_dai->component, NULL, NULL); +} +EXPORT_SYMBOL_GPL(qcom_snd_headset_jack_cleanup); + +int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup) +{ + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + int rval, i; + + rval = qcom_snd_headset_jack_init(card, jack, jack_setup); + if (rval) + return rval; + switch (cpu_dai->id) { case LPI_MI2S_RX_0: case TX_CODEC_DMA_TX_0: @@ -388,8 +435,6 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, default: break; } - - return 0; } EXPORT_SYMBOL_GPL(qcom_snd_wcd_jack_setup); diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h index 18171656408c92..37b185a4968f36 100644 --- a/sound/soc/qcom/common.h +++ b/sound/soc/qcom/common.h @@ -24,6 +24,9 @@ int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, const struct qcom_snd_tdm_slot_cfg *cpu_cfg, const struct qcom_snd_tdm_slot_cfg *codec_cfg); int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd); +int qcom_snd_headset_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup); +void qcom_snd_headset_jack_cleanup(struct snd_soc_pcm_runtime *rtd); int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, struct snd_soc_jack *jack, bool *jack_setup); int qcom_snd_dp_jack_setup(struct snd_soc_pcm_runtime *rtd, From ea6e98472c65a713421ac9bdaa13834073437844 Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Fri, 4 Sep 2026 18:02:28 +0800 Subject: [PATCH 1243/1417] ASoC: qcom: sc8280xp: Add per-DAI board configuration The board data currently applies codec sysclk, MI2S clock and jack setup policy to every backend DAI on a sound card. This cannot describe a card whose codecs have different clock requirements on different MI2S interfaces. Add an optional per-DAI configuration table indexed by CPU DAI ID. Each entry can override the MCLK rate, CPU MCLK and BCLK programming, codec sysclk setup and jack setup path. Keep the codec DAI format card-wide. Cards without a matching per-DAI entry continue to use the existing card-wide settings, keeping all current board data unchanged. Signed-off-by: Hongyang Zhao Link: https://patch.msgid.link/20260904-rubikpi-next-20260605-v3-4-f49146d85af3@thundersoft.com Signed-off-by: Mark Brown --- sound/soc/qcom/sc8280xp.c | 107 ++++++++++++++++++++++++++++++++++---- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 0876b9be136d50..58b4feb2ef3ec2 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -58,6 +58,25 @@ static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = { SND_SOC_DAPM_SPK("Speaker", NULL), }; +enum sc8280xp_jack_setup { + SC8280XP_JACK_SETUP_NONE, + SC8280XP_JACK_SETUP_CODEC, + SC8280XP_JACK_SETUP_WCD, +}; + +struct sc8280xp_dai_data { + unsigned int id; + unsigned int mclk_rate; + bool codec_sysclk_set; + bool mi2s_mclk_enable; + bool mi2s_bclk_enable; + enum sc8280xp_jack_setup jack_setup; +}; + +#define SC8280XP_DAI_DATA(...) \ + .dai_data = (const struct sc8280xp_dai_data[]) { __VA_ARGS__ }, \ + .num_dai_data = ARRAY_SIZE(((const struct sc8280xp_dai_data[]) { __VA_ARGS__ })) + struct qcom_snd_soc_common { const char *driver_name; const struct snd_soc_dapm_widget *dapm_widgets; @@ -71,6 +90,8 @@ struct qcom_snd_soc_common { bool mi2s_mclk_enable; bool mi2s_bclk_enable; bool wcd_jack; + const struct sc8280xp_dai_data *dai_data; + size_t num_dai_data; int (*snd_prepare)(struct snd_pcm_substream *substream); }; @@ -83,6 +104,20 @@ struct sc8280xp_snd_data { bool jack_setup; }; +static const struct sc8280xp_dai_data * +sc8280xp_get_dai_data(const struct qcom_snd_soc_common *common, + unsigned int id) +{ + size_t i; + + for (i = 0; i < common->num_dai_data; i++) { + if (common->dai_data[i].id == id) + return &common->dai_data[i]; + } + + return NULL; +} + static inline int sc8280xp_get_mclk_freq(struct snd_pcm_hw_params *params) { int rate = params_rate(params); @@ -112,13 +147,22 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + const struct sc8280xp_dai_data *dai_data; struct snd_soc_dai *codec_dai; struct qcom_snd_tdm_slot_cfg cpu_cfg; struct qcom_snd_tdm_slot_cfg codec_cfg; + bool codec_sysclk_set = data->priv->codec_sysclk_set; + bool mi2s_bclk_enable = data->priv->mi2s_bclk_enable; int bclk_freq; int ret; int i; + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + if (dai_data) { + codec_sysclk_set = dai_data->codec_sysclk_set; + mi2s_bclk_enable = dai_data->mi2s_bclk_enable; + } + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); if (ret) return ret == -ENOENT ? 0 : ret; @@ -147,7 +191,7 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, if (bclk_freq <= 0) return -EINVAL; - if (data->priv->mi2s_bclk_enable) { + if (mi2s_bclk_enable) { ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, SND_SOC_CLOCK_IN); if (ret && ret != -ENOTSUPP) { @@ -157,7 +201,7 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, } } - if (data->priv->codec_sysclk_set) { + if (codec_sysclk_set) { for_each_rtd_codec_dais(rtd, i, codec_dai) { ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, SND_SOC_CLOCK_IN); @@ -176,10 +220,13 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) { struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + const struct sc8280xp_dai_data *dai_data; struct snd_soc_card *card = rtd->card; struct snd_soc_jack *dp_jack = NULL; int dp_pcm_id = 0; + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + switch (cpu_dai->id) { case WSA_CODEC_DMA_RX_0: case WSA_CODEC_DMA_RX_1: @@ -209,10 +256,24 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) if (dp_jack) return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id); - if (data->priv->wcd_jack) - return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); + if (!dai_data) { + if (data->priv->wcd_jack) + return qcom_snd_wcd_jack_setup(rtd, &data->jack, + &data->jack_setup); - return 0; + return 0; + } + + switch (dai_data->jack_setup) { + case SC8280XP_JACK_SETUP_CODEC: + return qcom_snd_headset_jack_setup(rtd, &data->jack, + &data->jack_setup); + case SC8280XP_JACK_SETUP_WCD: + return qcom_snd_wcd_jack_setup(rtd, &data->jack, + &data->jack_setup); + default: + return 0; + } } static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, @@ -251,10 +312,24 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); - int mclk_freq = sc8280xp_get_mclk_freq(params); + const struct sc8280xp_dai_data *dai_data; + bool codec_sysclk_set = data->priv->codec_sysclk_set; + bool mi2s_mclk_enable = data->priv->mi2s_mclk_enable; + bool mi2s_bclk_enable = data->priv->mi2s_bclk_enable; + int mclk_freq; int bclk_freq = sc8280xp_get_bclk_freq(params); int ret; + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + mclk_freq = sc8280xp_get_mclk_freq(params); + if (dai_data) { + codec_sysclk_set = dai_data->codec_sysclk_set; + mi2s_mclk_enable = dai_data->mi2s_mclk_enable; + mi2s_bclk_enable = dai_data->mi2s_bclk_enable; + if (dai_data->mclk_rate) + mclk_freq = dai_data->mclk_rate; + } + switch (cpu_dai->id) { case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: @@ -272,7 +347,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, return ret; } - if (data->priv->mi2s_mclk_enable) { + if (mi2s_mclk_enable) { ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_MCLK, mclk_freq, SND_SOC_CLOCK_OUT); @@ -280,7 +355,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, return ret; } - if (data->priv->mi2s_bclk_enable) { + if (mi2s_bclk_enable) { ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, SND_SOC_CLOCK_OUT); @@ -288,7 +363,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, return ret; } - if (data->priv->codec_sysclk_set) { + if (codec_sysclk_set) { ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk_freq, SND_SOC_CLOCK_IN); @@ -370,6 +445,19 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream) return qcom_snd_sdw_hw_free(substream, &data->stream_prepared[cpu_dai->id]); } +static void sc8280xp_snd_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + const struct sc8280xp_dai_data *dai_data; + + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + if (dai_data && dai_data->jack_setup == SC8280XP_JACK_SETUP_CODEC) { + qcom_snd_headset_jack_cleanup(rtd); + data->jack_setup = false; + } +} + static const struct snd_soc_ops sc8280xp_be_ops = { .startup = qcom_snd_sdw_startup, .shutdown = qcom_snd_sdw_shutdown, @@ -386,6 +474,7 @@ static void sc8280xp_add_be_ops(struct snd_soc_card *card) for_each_card_prelinks(card, i, link) { if (link->no_pcm == 1) { link->init = sc8280xp_snd_init; + link->exit = sc8280xp_snd_exit; link->be_hw_params_fixup = sc8280xp_be_hw_params_fixup; link->ops = &sc8280xp_be_ops; } From c82a336b186409bb24c194b1c3626736d8b6f68b Mon Sep 17 00:00:00 2001 From: Hongyang Zhao Date: Fri, 4 Sep 2026 18:02:29 +0800 Subject: [PATCH 1244/1417] ASoC: qcom: sc8280xp: Add RubikPi 3 sound card support RubikPi 3 connects an ES8316 codec to primary MI2S for headset playback and capture, and an LT9611 bridge to quaternary MI2S for HDMI audio. Add per-DAI data for the primary playback and capture links. Configure the codecs for I2S with normal clock polarity and as bit and frame clock consumers. Program the primary MI2S BCLK, configure the ES8316 for its fixed 19.2 MHz MCLK and enable component jack detection on the playback link. The HDMI link inherits the card-wide I2S format but does not request codec sysclk programming because the LT9611 codec DAI does not implement set_sysclk(). Select this configuration through the RubikPi 3 sound-card compatible. Signed-off-by: Hongyang Zhao Link: https://patch.msgid.link/20260904-rubikpi-next-20260605-v3-5-f49146d85af3@thundersoft.com Signed-off-by: Mark Brown --- sound/soc/qcom/sc8280xp.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 58b4feb2ef3ec2..b5d367486d56ad 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -579,6 +579,28 @@ static const struct qcom_snd_soc_common qcs6490_priv_data = { .wcd_jack = true, }; +static const struct qcom_snd_soc_common rubikpi3_priv_data = { + .driver_name = "qcs6490", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .codec_dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_BC_FC, + SC8280XP_DAI_DATA({ + .id = PRIMARY_MI2S_RX, + .mclk_rate = 19200000, + .codec_sysclk_set = true, + .mi2s_bclk_enable = true, + .jack_setup = SC8280XP_JACK_SETUP_CODEC, + }, { + .id = PRIMARY_MI2S_TX, + .mclk_rate = 19200000, + .codec_sysclk_set = true, + .mi2s_bclk_enable = true, + } + ), +}; + static const struct qcom_snd_soc_common qcs8275_priv_data = { .driver_name = "qcs8300", .dapm_widgets = max98090_dapm_widgets, @@ -658,6 +680,7 @@ static const struct of_device_id snd_sc8280xp_dt_match[] = { { .compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data }, { .compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data }, { .compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data }, + { .compatible = "thundercomm,qcs6490-rubikpi3-sndcard", .data = &rubikpi3_priv_data }, { .compatible = "qcom,qcs8275-sndcard", .data = &qcs8275_priv_data }, { .compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data }, { .compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data }, From cb97bf3d4f91453b881acaf8e9f0cc47bb40b604 Mon Sep 17 00:00:00 2001 From: Viken Dadhaniya Date: Mon, 21 Sep 2026 17:17:01 +0530 Subject: [PATCH 1245/1417] i2c: qcom-geni: Fix hardcoded clock index in SE_GENI_CLK_SEL qcom_geni_i2c_conf() writes a hardcoded 0 to SE_GENI_CLK_SEL, which selects an index from the hardware clock performance table. This always picks the first table entry regardless of the actual source clock configuration. On platforms where the matching entry is not at index 0, the wrong source clock divider is active and the I2C bus runs at an incorrect frequency. Use geni_se_clk_freq_match() in geni_i2c_clk_map_idx() to find the performance table index for the source clock (32 MHz or 19.2 MHz). Store the resolved index in a new clk_idx field in geni_i2c_dev and write it to SE_GENI_CLK_SEL instead of the hardcoded 0. Fixes: 37692de5d523 ("i2c: i2c-qcom-geni: Add bus driver for the Qualcomm GENI I2C controller") Signed-off-by: Viken Dadhaniya Cc: # v4.19+ Reviewed-by: Mukesh Kumar Savaliya Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260921-i2c-fix-se-clk-conf-v2-1-8b5537ceff2d@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 38 ++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index e9e41a174c204a..6a39e21d5ed4fb 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -82,6 +82,9 @@ enum geni_i2c_err_code { #define XFER_TIMEOUT HZ #define RST_TIMEOUT HZ +#define GENI_SE_CLK_32MHZ (32 * HZ_PER_MHZ) +#define GENI_SE_CLK_19P2MHZ 19200000UL + struct geni_i2c_desc { bool no_dma_support; unsigned int tx_fifo_depth; @@ -127,6 +130,7 @@ struct geni_i2c_dev { spinlock_t lock; u32 clk_freq_out; const struct geni_i2c_clk_fld *clk_fld; + u32 clk_idx; void *dma_buf; size_t xfer_len; dma_addr_t dma_addr; @@ -197,19 +201,44 @@ static const struct geni_i2c_clk_fld geni_i2c_clk_map_32mhz[] = { static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c) { const struct geni_i2c_clk_fld *itr; + unsigned long res_freq; - if (clk_get_rate(gi2c->se.clk) == 32 * HZ_PER_MHZ) + /* + * Frequency counter tables are calibrated for a specific source + * clock frequency and are not valid for any multiple of it + * (e.g. 64 MHz, 128 MHz). + * Use exact=true and verify res_freq matches req_freq literally + * to reject harmonics: a 64 MHz clock that divides evenly to + * 32 MHz would pass exact matching but produce double the intended + * I2C frequency with these counter values. + */ + if (!geni_se_clk_freq_match(&gi2c->se, GENI_SE_CLK_32MHZ, + &gi2c->clk_idx, &res_freq, true) && + res_freq == GENI_SE_CLK_32MHZ) { itr = geni_i2c_clk_map_32mhz; - else + } else if (!geni_se_clk_freq_match(&gi2c->se, GENI_SE_CLK_19P2MHZ, + &gi2c->clk_idx, &res_freq, true) && + res_freq == GENI_SE_CLK_19P2MHZ) { itr = geni_i2c_clk_map_19p2mhz; + } else { + dev_err(gi2c->se.dev, + "Unsupported SE source clock: must be exactly 32 MHz or 19.2 MHz\n"); + return -EINVAL; + } while (itr->clk_freq_out != 0) { if (itr->clk_freq_out == gi2c->clk_freq_out) { gi2c->clk_fld = itr; + dev_dbg(gi2c->se.dev, + "I2C clk selected: freq: %u Hz, clk_idx: %u\n", + gi2c->clk_freq_out, gi2c->clk_idx); return 0; } itr++; } + + dev_err(gi2c->se.dev, "Unsupported I2C output frequency %u Hz\n", gi2c->clk_freq_out); + return -EINVAL; } @@ -219,7 +248,7 @@ static int qcom_geni_i2c_conf(struct geni_se *se, unsigned long freq) const struct geni_i2c_clk_fld *itr = gi2c->clk_fld; u32 val; - writel_relaxed(0, gi2c->se.base + SE_GENI_CLK_SEL); + writel_relaxed(gi2c->clk_idx, gi2c->se.base + SE_GENI_CLK_SEL); val = (itr->clk_div << CLK_DIV_SHFT) | SER_CLK_EN; writel_relaxed(val, gi2c->se.base + GENI_SER_M_CLK_CFG); @@ -1111,8 +1140,7 @@ static int geni_i2c_resources_init(struct geni_se *se) ret = geni_i2c_clk_map_idx(gi2c); if (ret) - return dev_err_probe(gi2c->se.dev, ret, "Invalid clk frequency %d Hz\n", - gi2c->clk_freq_out); + return ret; return geni_icc_set_bw_ab(&gi2c->se, GENI_DEFAULT_BW, GENI_DEFAULT_BW, Bps_to_icc(gi2c->clk_freq_out)); From b4136c0d69ead737197af741e20edd1c3574f6b4 Mon Sep 17 00:00:00 2001 From: Slavin Liu Date: Sun, 13 Sep 2026 20:51:47 +0800 Subject: [PATCH 1246/1417] ASoC: fsl_asrc: check the second front-end DMA channel The temporary Front-End DMA request can fail independently of the persistent channel acquired earlier in fsl_asrc_dma_hw_params(). The returned NULL pointer is immediately used to read its private data. Check the temporary channel and release the previously acquired persistent Front-End channel on failure. Clear its slot so a later hw_free cannot release it twice. ASoC marks a component's hw_params only after success and skips unmarked components during rollback, so returning an error alone would leak the earlier channel. Detected by static analysis and reviewed with AI-assisted source auditing. Fixes: 3117bb3109dc ("ASoC: fsl_asrc: Add ASRC ASoC CPU DAI and platform drivers") Assisted-by: LLM Signed-off-by: Slavin Liu Link: https://patch.msgid.link/20260913125147.109920-1-bolin.liu@seu.edu.cn Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc_dma.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index 86489f70e2625e..0aa5db8973d6f0 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -251,6 +251,11 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, /* Get DMA request of Front-End */ tmp_chan = asrc->get_dma_channel(pair, dir); + if (!tmp_chan) { + dma_release_channel(pair->dma_chan[!dir]); + pair->dma_chan[!dir] = NULL; + return -EINVAL; + } tmp_data = tmp_chan->private; pair->dma_data.dma_request2 = tmp_data->dma_request; pair->dma_data.peripheral_type = tmp_data->peripheral_type; From 8ca84987c11d11e0ba60f202e3199b3b80c87721 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:37 +0800 Subject: [PATCH 1247/1417] ASoC: adau17x1: return cache sync errors from resume adau17x1_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The optional switch-mode callback remains ordered before the cache replay. The issue was found by a tool we developed. Fixes: 4101866c743a ("ASoC: Add ADAU1X61 and ADAU1X81 CODECs common code") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-2-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/adau17x1.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c index 26d7eb437ad183..00efaff44e9270 100644 --- a/sound/soc/codecs/adau17x1.c +++ b/sound/soc/codecs/adau17x1.c @@ -989,9 +989,7 @@ int adau17x1_resume(struct snd_soc_component *component) if (adau->switch_mode) adau->switch_mode(component->dev); - regcache_sync(adau->regmap); - - return 0; + return regcache_sync(adau->regmap); } EXPORT_SYMBOL_GPL(adau17x1_resume); From 7142c34ed92fddca7038787b8695d22c661dc6ec Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:38 +0800 Subject: [PATCH 1248/1417] ASoC: ak4642: return cache sync errors from resume ak4642_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: a3a83d9a7cb0 ("ASoC: Add ak4642/ak4643 codec support") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-3-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/ak4642.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 08ec2035b2705e..37ac298835d64f 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -533,8 +533,7 @@ static int ak4642_resume(struct snd_soc_component *component) struct regmap *regmap = dev_get_regmap(component->dev, NULL); regcache_cache_only(regmap, false); - regcache_sync(regmap); - return 0; + return regcache_sync(regmap); } static int ak4642_probe(struct snd_soc_component *component) { From 243d0c47e57a6af89d95aa451ef5fbf453983b1c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:39 +0800 Subject: [PATCH 1249/1417] ASoC: es8316: return cache sync errors from resume es8316_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: 4bac47a7b2f9 ("ASoC: codecs: add suspend and resume for ES8316") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-4-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/es8316.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index 24ec8b211cdba5..4c33a243c1e1cd 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -810,9 +810,7 @@ static int es8316_resume(struct snd_soc_component *component) struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component); regcache_cache_only(es8316->regmap, false); - regcache_sync(es8316->regmap); - - return 0; + return regcache_sync(es8316->regmap); } static int es8316_suspend(struct snd_soc_component *component) From b7c3f95915a9e791fc8721ce1ecc037f7a55d58c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:40 +0800 Subject: [PATCH 1250/1417] ASoC: es8323: return cache sync errors from resume es8323_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: b97391a604b9 ("ASoC: codecs: Add support for ES8323") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-5-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/es8323.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/es8323.c b/sound/soc/codecs/es8323.c index b926340256be5d..03d516d70c435c 100644 --- a/sound/soc/codecs/es8323.c +++ b/sound/soc/codecs/es8323.c @@ -733,9 +733,7 @@ static int es8323_resume(struct snd_soc_component *component) struct es8323_priv *es8323 = snd_soc_component_get_drvdata(component); regcache_cache_only(es8323->regmap, false); - regcache_sync(es8323->regmap); - - return 0; + return regcache_sync(es8323->regmap); } static const struct snd_soc_component_driver soc_component_dev_es8323 = { From 0bf2b3411537e8e4d9c03702646a9fc87198d4b1 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:41 +0800 Subject: [PATCH 1251/1417] ASoC: nau8540: return cache sync errors from resume nau8540_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: c1644e3de45d ("ASoC: nau8540: new codec driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-6-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/nau8540.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/nau8540.c b/sound/soc/codecs/nau8540.c index fefbd5722c0041..6c421e2386aa78 100644 --- a/sound/soc/codecs/nau8540.c +++ b/sound/soc/codecs/nau8540.c @@ -897,9 +897,7 @@ static int __maybe_unused nau8540_resume(struct snd_soc_component *component) struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); regcache_cache_only(nau8540->regmap, false); - regcache_sync(nau8540->regmap); - - return 0; + return regcache_sync(nau8540->regmap); } static const struct snd_soc_component_driver nau8540_component_driver = { From 432734a00bfae17baeff1a83b61bfc9c2ab20c12 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:42 +0800 Subject: [PATCH 1252/1417] ASoC: rt1011: return cache sync errors from resume rt1011_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: d6e65bb7ff0d ("ASoC: rt1011: Add RT1011 amplifier driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-7-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1011.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt1011.c b/sound/soc/codecs/rt1011.c index 15488a0592837a..6e783c3d14aecf 100644 --- a/sound/soc/codecs/rt1011.c +++ b/sound/soc/codecs/rt1011.c @@ -2094,9 +2094,7 @@ static int rt1011_resume(struct snd_soc_component *component) struct rt1011_priv *rt1011 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1011->regmap, false); - regcache_sync(rt1011->regmap); - - return 0; + return regcache_sync(rt1011->regmap); } #else #define rt1011_suspend NULL From 98ace05793c7b80a9f49d92b78fcc88e0970f8e4 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:43 +0800 Subject: [PATCH 1253/1417] ASoC: rt1016: return cache sync errors from resume rt1016_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: 79a4b670b4b4 ("ASoC: rt1016: Add the rt1016 support") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-8-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1016.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt1016.c b/sound/soc/codecs/rt1016.c index 11c8c45574a131..12b97c04d033cf 100644 --- a/sound/soc/codecs/rt1016.c +++ b/sound/soc/codecs/rt1016.c @@ -570,9 +570,7 @@ static int rt1016_resume(struct snd_soc_component *component) struct rt1016_priv *rt1016 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1016->regmap, false); - regcache_sync(rt1016->regmap); - - return 0; + return regcache_sync(rt1016->regmap); } #else #define rt1016_suspend NULL From 30858325aa90380c6bcdde605214f1ebdbec568c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:44 +0800 Subject: [PATCH 1254/1417] ASoC: rt1305: return cache sync errors from resume rt1305_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: 29bc643ddd7e ("ASoC: rt1305: Add RT1305/RT1306 amplifier driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-9-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1305.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt1305.c b/sound/soc/codecs/rt1305.c index 2d5fad76cf5283..601cbf9eb1e2e4 100644 --- a/sound/soc/codecs/rt1305.c +++ b/sound/soc/codecs/rt1305.c @@ -896,9 +896,7 @@ static int rt1305_resume(struct snd_soc_component *component) struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1305->regmap, false); - regcache_sync(rt1305->regmap); - - return 0; + return regcache_sync(rt1305->regmap); } #else #define rt1305_suspend NULL From 3db3289178b94056753f7a8c6ee4ab4b232ee6a5 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:45 +0800 Subject: [PATCH 1255/1417] ASoC: rt1308: return cache sync errors from resume rt1308_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: 2b9def8c0d92 ("ASoC: rt1308: Add RT1308 amplifier driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-10-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1308.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt1308.c b/sound/soc/codecs/rt1308.c index 630946cd7cdf80..df95cfbd76d69b 100644 --- a/sound/soc/codecs/rt1308.c +++ b/sound/soc/codecs/rt1308.c @@ -715,9 +715,7 @@ static int rt1308_resume(struct snd_soc_component *component) struct rt1308_priv *rt1308 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1308->regmap, false); - regcache_sync(rt1308->regmap); - - return 0; + return regcache_sync(rt1308->regmap); } #else #define rt1308_suspend NULL From a5735b341c6e8cff5f5f9480295012a5ceebb665 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:46 +0800 Subject: [PATCH 1256/1417] ASoC: rt1318: return cache sync errors from resume rt1318_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: fe1ff61487ac ("ASoC: rt1318: Add RT1318 audio amplifier driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-11-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1318.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt1318.c b/sound/soc/codecs/rt1318.c index 55607f1b9a1ed0..3dcec7564b82d4 100644 --- a/sound/soc/codecs/rt1318.c +++ b/sound/soc/codecs/rt1318.c @@ -1075,8 +1075,7 @@ static int rt1318_resume(struct snd_soc_component *component) struct rt1318_priv *rt1318 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1318->regmap, false); - regcache_sync(rt1318->regmap); - return 0; + return regcache_sync(rt1318->regmap); } #else #define rt1318_suspend NULL From 8fc9d353d5371a946d9a9952278b96c09070374c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:47 +0800 Subject: [PATCH 1257/1417] ASoC: rt5616: return cache sync errors from resume rt5616_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: b1d15059957d ("ASoC: rt5616: add rt5616 codec driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-12-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt5616.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt5616.c b/sound/soc/codecs/rt5616.c index 46ee412dc81db7..73a3d1d008f410 100644 --- a/sound/soc/codecs/rt5616.c +++ b/sound/soc/codecs/rt5616.c @@ -1248,8 +1248,7 @@ static int rt5616_resume(struct snd_soc_component *component) struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt5616->regmap, false); - regcache_sync(rt5616->regmap); - return 0; + return regcache_sync(rt5616->regmap); } #else #define rt5616_suspend NULL From 3a9b5bce85a1baff95463a2bb021efac64bcf83f Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:48 +0800 Subject: [PATCH 1258/1417] ASoC: rt5659: return cache sync errors from resume rt5659_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: d3cb2de2479b ("ASoC: rt5659: add rt5659 codec driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-13-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt5659.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt5659.c b/sound/soc/codecs/rt5659.c index 3097ee6d4e89f9..915b6b4b2630a9 100644 --- a/sound/soc/codecs/rt5659.c +++ b/sound/soc/codecs/rt5659.c @@ -3703,9 +3703,7 @@ static int rt5659_resume(struct snd_soc_component *component) struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt5659->regmap, false); - regcache_sync(rt5659->regmap); - - return 0; + return regcache_sync(rt5659->regmap); } #else #define rt5659_suspend NULL From 70fe8997b03078ff8253d985a161fef310f7c346 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:49 +0800 Subject: [PATCH 1259/1417] ASoC: rt5660: return cache sync errors from resume rt5660_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The power-off resume delay remains ordered before the cache replay. The issue was found by a tool we developed. Fixes: 2b26dd4c1fc5 ("ASoC: rt5660: add rt5660 codec driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-14-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt5660.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt5660.c b/sound/soc/codecs/rt5660.c index edf2e3e346e4f9..830498c947e0d4 100644 --- a/sound/soc/codecs/rt5660.c +++ b/sound/soc/codecs/rt5660.c @@ -1148,9 +1148,7 @@ static int rt5660_resume(struct snd_soc_component *component) msleep(350); regcache_cache_only(rt5660->regmap, false); - regcache_sync(rt5660->regmap); - - return 0; + return regcache_sync(rt5660->regmap); } #else #define rt5660_suspend NULL From 9f2933d9a437302d5ae32eb9022efef049d8ab22 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 2 Sep 2026 09:32:50 +0800 Subject: [PATCH 1260/1417] ASoC: rt5665: return cache sync errors from resume rt5665_resume() restores cached registers during component resume but discards the regcache_sync() result and returns success. A failed replay can therefore leave the hardware register state stale without reaching the component error reporting path. Return the cache synchronization result. The ASoC component wrapper reports a negative callback result and retains the established best-effort resume state, so this adds observability without introducing rollback or retry semantics. The successful resume path is unchanged. The issue was found by a tool we developed. Fixes: 33ada14a26c8 ("ASoC: add rt5665 codec driver") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260902013256.93072-15-pengpeng@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt5665.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c index 1d987472ba42fd..6e4967ed12c349 100644 --- a/sound/soc/codecs/rt5665.c +++ b/sound/soc/codecs/rt5665.c @@ -4388,9 +4388,7 @@ static int rt5665_resume(struct snd_soc_component *component) struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt5665->regmap, false); - regcache_sync(rt5665->regmap); - - return 0; + return regcache_sync(rt5665->regmap); } #else #define rt5665_suspend NULL From 691c4aac5ae5a99537785659d25d49071e2c6502 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:04 +0700 Subject: [PATCH 1261/1417] ASoC: mediatek: mt8188: mt8188-afe-clk: Propagate clock initialization errors Use dev_err_probe() to handle clock lookup errors without printing redundant messages for deferred probe. Propagate the original error from tuner initialization instead of returning -EINVAL. Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-clk.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c index fc6cb3f0469e53..42878c8a652903 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c @@ -416,12 +416,9 @@ int mt8188_afe_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT8188_CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "%s(), devm_clk_get %s fail, ret %ld\n", - __func__, aud_clks[i], - PTR_ERR(afe_priv->clk[i])); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } /* initial tuner */ @@ -430,7 +427,7 @@ int mt8188_afe_init_clock(struct mtk_base_afe *afe) if (ret) { dev_info(afe->dev, "%s(), init apll_tuner%d failed", __func__, (i + 1)); - return -EINVAL; + return ret; } } From a2c3ee3349a7da91e563b9a21a355a28fa754bc7 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:05 +0700 Subject: [PATCH 1262/1417] ASoC: mediatek: mt8188: mt8188-afe-clk: Handle tuner clock enable errors Clock enable errors are currently ignored when enabling the APLL and tuner clocks. Check the return values and roll back the APLL clock if the tuner clock fails to enable. Fixes: f6b026479b13 ("ASoC: mediatek: mt8188: support audio clock control") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-clk.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c index 42878c8a652903..ecba13eda440c7 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c @@ -260,15 +260,28 @@ static int mt8188_afe_enable_tuner_clk(struct mtk_base_afe *afe, unsigned int id) { struct mt8188_afe_private *afe_priv = afe->platform_priv; + int ret; switch (id) { case MT8188_AUD_PLL1: - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL1_TUNER]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL]); + if (ret) + return ret; + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL1_TUNER]); + if (ret) { + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL]); + return ret; + } break; case MT8188_AUD_PLL2: - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2_TUNER]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2]); + if (ret) + return ret; + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2_TUNER]); + if (ret) { + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2]); + return ret; + } break; default: return -EINVAL; From 60a8112a088aee38d728457a14fa8bfb50f4c87e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:06 +0700 Subject: [PATCH 1263/1417] ASoC: mediatek: mt8188: mt8188-afe-clk: Propagate regmap update errors The return values from regmap_update_bits() are currently ignored by the clock and AFE control functions. Propagate the errors to allow callers to handle regmap update failures. Fixes: f6b026479b13 ("ASoC: mediatek: mt8188: support audio clock control") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-clk.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c index ecba13eda440c7..77511f6fba41d9 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c @@ -564,9 +564,7 @@ static int mt8188_afe_enable_top_cg(struct mtk_base_afe *afe, unsigned int cg_ty unsigned int mask = get_top_cg_mask(cg_type); unsigned int val = get_top_cg_on_val(cg_type); - regmap_update_bits(afe->regmap, reg, mask, val); - - return 0; + return regmap_update_bits(afe->regmap, reg, mask, val); } static int mt8188_afe_disable_top_cg(struct mtk_base_afe *afe, unsigned int cg_type) @@ -575,9 +573,7 @@ static int mt8188_afe_disable_top_cg(struct mtk_base_afe *afe, unsigned int cg_t unsigned int mask = get_top_cg_mask(cg_type); unsigned int val = get_top_cg_off_val(cg_type); - regmap_update_bits(afe->regmap, reg, mask, val); - - return 0; + return regmap_update_bits(afe->regmap, reg, mask, val); } int mt8188_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) @@ -617,14 +613,12 @@ int mt8188_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) static int mt8188_afe_enable_afe_on(struct mtk_base_afe *afe) { - regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x1); - return 0; + return regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x1); } static int mt8188_afe_disable_afe_on(struct mtk_base_afe *afe) { - regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x0); - return 0; + return regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x0); } static int mt8188_afe_enable_a1sys(struct mtk_base_afe *afe) From 1b65c13d1af99170e2a855ca9212a03daa3e7f04 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:07 +0700 Subject: [PATCH 1264/1417] ASoC: mediatek: mt8188: mt8188-afe-clk: Handle clock enable errors Clock enable errors are currently ignored by several AFE clock control functions. Check the return values and roll back previously enabled clocks when a subsequent clock enable fails. Fixes: f6b026479b13 ("ASoC: mediatek: mt8188: support audio clock control") Fixes: 9be0213a6858 ("ASoC: mediatek: mt8188: refine APLL control") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-clk.c | 69 ++++++++++++++++++---- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c index 77511f6fba41d9..8b6dca22c8edf5 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c @@ -579,22 +579,47 @@ static int mt8188_afe_disable_top_cg(struct mtk_base_afe *afe, unsigned int cg_t int mt8188_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) { struct mt8188_afe_private *afe_priv = afe->platform_priv; - + int ret; /* bus clock for AFE external access, like DRAM */ - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUDIO_LOCAL_BUS_SEL]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUDIO_LOCAL_BUS_SEL]); + if (ret) + goto err_local_bus; /* bus clock for AFE internal access, like AFE SRAM */ - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUD_INTBUS_SEL]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUD_INTBUS_SEL]); + if (ret) + goto err_intbus; /* audio 26m clock source */ - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_ADSP_AUDIO_26M]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_ADSP_AUDIO_26M]); + if (ret) + goto err_26m; /* AFE hw clock */ - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_AFE]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS_HP]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_AFE]); + if (ret) + goto err_afe; + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS_HP]); + if (ret) + goto err_a1sys_hp; + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); + if (ret) + goto err_a1sys; return 0; + +err_a1sys: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS_HP]); +err_a1sys_hp: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_AFE]); +err_afe: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_ADSP_AUDIO_26M]); +err_26m: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUD_INTBUS_SEL]); +err_intbus: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUDIO_LOCAL_BUS_SEL]); +err_local_bus: + return ret; } int mt8188_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) @@ -630,7 +655,13 @@ static int mt8188_afe_enable_a1sys(struct mtk_base_afe *afe) if (ret) return ret; - return mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A1SYS_TIMING); + ret = mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A1SYS_TIMING); + if (ret) { + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); + return ret; + } + + return 0; } static int mt8188_afe_disable_a1sys(struct mtk_base_afe *afe) @@ -651,7 +682,13 @@ static int mt8188_afe_enable_a2sys(struct mtk_base_afe *afe) if (ret) return ret; - return mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A2SYS_TIMING); + ret = mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A2SYS_TIMING); + if (ret) { + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A2SYS]); + return ret; + } + + return 0; } static int mt8188_afe_disable_a2sys(struct mtk_base_afe *afe) @@ -739,8 +776,18 @@ int mt8188_apll2_disable(struct mtk_base_afe *afe) int mt8188_afe_enable_main_clock(struct mtk_base_afe *afe) { - mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); - mt8188_afe_enable_afe_on(afe); + int ret; + + ret = mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); + if (ret) + return ret; + + ret = mt8188_afe_enable_afe_on(afe); + if (ret) { + mt8188_afe_disable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); + return ret; + } + return 0; } From f5f6941d92972ba66829a99999fa825e63da5162 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:08 +0700 Subject: [PATCH 1265/1417] ASoC: mediatek: mt8188: mt8188-afe-pcm: Handle runtime resume errors Errors from clock enable and regcache synchronization are currently ignored during runtime resume. Check the return values from mt8188_afe_enable_reg_rw_clk(), regcache_sync(), and mt8188_afe_enable_main_clock(), and abort the runtime resume if any of them fails. On failure, disable the previously enabled reg_rw clocks and restore the regmap to cache-only mode. Fixes: bf106bf09376 ("ASoC: mediatek: mt8188: add platform driver") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-pcm.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c index 7b1f5d05f4d615..f8cbe7bac36de9 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c @@ -3030,22 +3030,33 @@ static int mt8188_afe_runtime_resume(struct device *dev) struct mtk_base_afe *afe = dev_get_drvdata(dev); struct mt8188_afe_private *afe_priv = afe->platform_priv; struct arm_smccc_res res; + int ret; arm_smccc_smc(MTK_SIP_AUDIO_CONTROL, MTK_AUDIO_SMC_OP_DOMAIN_SIDEBANDS, 0, 0, 0, 0, 0, 0, &res); - mt8188_afe_enable_reg_rw_clk(afe); + ret = mt8188_afe_enable_reg_rw_clk(afe); + if (ret) + return ret; if (!afe->regmap || afe_priv->pm_runtime_bypass_reg_ctl) - goto skip_regmap; + return 0; regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) + goto err; + + ret = mt8188_afe_enable_main_clock(afe); + if (ret) + goto err; - mt8188_afe_enable_main_clock(afe); -skip_regmap: return 0; +err: + mt8188_afe_disable_reg_rw_clk(afe); + regcache_cache_only(afe->regmap, true); + return ret; } static int init_memif_priv_data(struct mtk_base_afe *afe) From a79f2432ee89cc825b83b92383cc47ea80683dd2 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:09 +0700 Subject: [PATCH 1266/1417] ASoC: mediatek: mt8188: mt8188-afe-pcm: Drop redundant probe error messages Several probe error paths print error messages that are already reported by the called functions. Return the original error directly for devm_platform_ioremap_resource(), mt8188_afe_init_clock(), platform_get_irq(), and devm_request_irq() instead of wrapping the errors with dev_err_probe(). Also remove the redundant "err_platform" warning from the component registration error path. Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-pcm.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c index f8cbe7bac36de9..efad0af977f410 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c @@ -3232,8 +3232,7 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) afe->base_addr = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(afe->base_addr)) - return dev_err_probe(dev, PTR_ERR(afe->base_addr), - "AFE base_addr not found\n"); + return PTR_ERR(afe->base_addr); infra_ao = syscon_regmap_lookup_by_phandle(dev->of_node, "mediatek,infracfg"); @@ -3269,7 +3268,7 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt8188_afe_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "init clock error"); + return ret; spin_lock_init(&afe_priv->afe_ctrl_lock); @@ -3302,12 +3301,12 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); if (irq_id < 0) - return dev_err_probe(dev, irq_id, "no irq found"); + return irq_id; ret = devm_request_irq(dev, irq_id, mt8188_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for asys-isr\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); @@ -3363,10 +3362,8 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) /* register component */ ret = devm_snd_soc_register_component(dev, &mtk_afe_pcm_platform, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_warn(dev, "err_platform\n"); + if (ret) goto err_pm_put; - } mt8188_afe_init_registers(afe); From 6cddf6e56d2539fd13c0ee9dd817c4e06a967173 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 7 Sep 2026 19:03:10 +0700 Subject: [PATCH 1267/1417] ASoC: mediatek: mt8188: fix clk leak on error in audsys_clk_register devm_add_action_or_reset() is called after the loop that registers gate clocks. If kzalloc() fails mid-loop, the function returns -ENOMEM before that call, so cleanup is never registered and all previously registered clocks leak permanently. Move devm_add_action_or_reset() before the loop so cleanup is always scheduled. The clock from the current (failing) iteration is not yet stored in afe_priv->lookup[i], so it still needs an explicit clk_unregister_gate() call. Fixes: fd67a7a1a22c ("ASoC: mediatek: mt8188: fix use-after-free in driver remove path") Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260907120310.135693-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-audsys-clk.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8188/mt8188-audsys-clk.c b/sound/soc/mediatek/mt8188/mt8188-audsys-clk.c index 972f097a13ca91..9f3b3a777577c9 100644 --- a/sound/soc/mediatek/mt8188/mt8188-audsys-clk.c +++ b/sound/soc/mediatek/mt8188/mt8188-audsys-clk.c @@ -170,7 +170,7 @@ int mt8188_audsys_clk_register(struct mtk_base_afe *afe) struct mt8188_afe_private *afe_priv = afe->platform_priv; struct clk *clk; struct clk_lookup *cl; - int i; + int i, ret; afe_priv->lookup = devm_kcalloc(afe->dev, CLK_AUD_NR_CLK, sizeof(*afe_priv->lookup), @@ -179,6 +179,10 @@ int mt8188_audsys_clk_register(struct mtk_base_afe *afe) if (!afe_priv->lookup) return -ENOMEM; + ret = devm_add_action_or_reset(afe->dev, mt8188_audsys_clk_unregister, afe); + if (ret) + return ret; + for (i = 0; i < ARRAY_SIZE(aud_clks); i++) { const struct afe_gate *gate = &aud_clks[i]; @@ -194,8 +198,10 @@ int mt8188_audsys_clk_register(struct mtk_base_afe *afe) /* add clk_lookup for devm_clk_get(SND_SOC_DAPM_CLOCK_SUPPLY) */ cl = kzalloc_obj(*cl); - if (!cl) + if (!cl) { + clk_unregister_gate(clk); return -ENOMEM; + } cl->clk = clk; cl->con_id = gate->name; @@ -206,5 +212,5 @@ int mt8188_audsys_clk_register(struct mtk_base_afe *afe) afe_priv->lookup[i] = cl; } - return devm_add_action_or_reset(afe->dev, mt8188_audsys_clk_unregister, afe); + return 0; } From 5626653a4bdf32812d66b970860677cd9d7a2190 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 17 Sep 2026 19:11:36 +0700 Subject: [PATCH 1268/1417] ASoC: fsl_asrc_m2m: fix pm_runtime usage counter leak on error pm_runtime_get_sync() leaves the runtime PM usage counter incremented even when it fails, but the error path in fsl_asrc_m2m_comp_open() does not call pm_runtime_put_noidle() to balance it, leaking a reference each time resume fails. Use pm_runtime_resume_and_get() instead, which automatically drops the usage counter on failure, fixing the leak. Fixes: 24a01710f627 ("ASoC: fsl_asrc_m2m: Add memory to memory function") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260917121136.32527-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc_m2m.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index e7b91196d35b97..a5e3670349f968 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -320,7 +320,7 @@ static int fsl_asrc_m2m_comp_open(struct snd_compr_stream *stream) if (ret) goto error_alloc_out_buf; - ret = pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0) { dev_err(dev, "Failed to power up asrc\n"); goto err_pm_runtime; From 286ece639c3753ff929b219452391134e611823a Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 16:34:12 +0800 Subject: [PATCH 1269/1417] ASoC: use regmap_assign_bits() for conditional set/clear Replace if/else blocks using regmap_set_bits()/regmap_clear_bits() with the simpler regmap_assign_bits() calls in the cs35l45 codec and the mediatek mt8188 ADDA and DMIC DAI drivers. Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260924083420.461715-1-peng.fan@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l45.c | 6 ++---- sound/soc/mediatek/mt8188/mt8188-dai-adda.c | 5 +---- sound/soc/mediatek/mt8188/mt8188-dai-dmic.c | 10 ++-------- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index 0ab76824f00f17..9eef7c8b1837e7 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -224,10 +224,8 @@ static int cs35l45_sync_en_put(struct snd_kcontrol *kcontrol, return 0; } - if ((bool)ucontrol->value.integer.value[0]) - regmap_set_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES2, CS35L45_SYNC_EN_MASK); - else - regmap_clear_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES2, CS35L45_SYNC_EN_MASK); + regmap_assign_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES2, CS35L45_SYNC_EN_MASK, + (bool)ucontrol->value.integer.value[0]); cs35l45->sync_en = (bool)ucontrol->value.integer.value[0]; diff --git a/sound/soc/mediatek/mt8188/mt8188-dai-adda.c b/sound/soc/mediatek/mt8188/mt8188-dai-adda.c index ac547fc864a6fe..395f4ab972f81c 100644 --- a/sound/soc/mediatek/mt8188/mt8188-dai-adda.c +++ b/sound/soc/mediatek/mt8188/mt8188-dai-adda.c @@ -123,10 +123,7 @@ static void mtk_adda_ul_mictype(struct mtk_base_afe *afe, bool dmic) UL_MODE_3P25M_CH2_CTL); /* turn on dmic, ch1, ch2 */ - if (dmic) - regmap_set_bits(afe->regmap, reg, val); - else - regmap_clear_bits(afe->regmap, reg, val); + regmap_assign_bits(afe->regmap, reg, val, dmic); } static int mtk_adda_ul_event(struct snd_soc_dapm_widget *w, diff --git a/sound/soc/mediatek/mt8188/mt8188-dai-dmic.c b/sound/soc/mediatek/mt8188/mt8188-dai-dmic.c index a9515d7fb70acb..7ac847a5925ca6 100644 --- a/sound/soc/mediatek/mt8188/mt8188-dai-dmic.c +++ b/sound/soc/mediatek/mt8188/mt8188-dai-dmic.c @@ -143,10 +143,7 @@ static void mtk_dai_dmic_hw_gain_bypass(struct mtk_base_afe *afe, return; } - if (bypass) - regmap_set_bits(afe->regmap, reg->bypass, msk); - else - regmap_clear_bits(afe->regmap, reg->bypass, msk); + regmap_assign_bits(afe->regmap, reg->bypass, msk, bypass); } static void mtk_dai_dmic_hw_gain_on(struct mtk_base_afe *afe, unsigned int id, @@ -157,10 +154,7 @@ static void mtk_dai_dmic_hw_gain_on(struct mtk_base_afe *afe, unsigned int id, if (!reg) return; - if (on) - regmap_set_bits(afe->regmap, reg->con0, DMIC_GAIN_CON0_GAIN_ON); - else - regmap_clear_bits(afe->regmap, reg->con0, DMIC_GAIN_CON0_GAIN_ON); + regmap_assign_bits(afe->regmap, reg->con0, DMIC_GAIN_CON0_GAIN_ON, on); } static const struct reg_sequence mtk_dai_dmic_iir_coeff_reg_defaults[] = { From 113dcdfadf30ea11fbbdfcd4f6ea87687655cc5b Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 25 Sep 2026 13:23:29 +0800 Subject: [PATCH 1270/1417] MAINTAINERS: name the libata/linux for-next branch The T: entry for LIBATA SUBSYSTEM (Serial and Parallel ATA drivers) names libata/linux without a branch. The repository's HEAD pointer points to branch master, which has no active development. Active development is on the for-next branch. Name the branch so the entry identifies where development happens. Documentation/process/submitting-patches.rst sends contributors to the T: entry to find the tree to prepare patches against, so a branch-less entry whose HEAD is already in mainline points them to the wrong branch. Reviewed-by: Damien Le Moal Signed-off-by: Matthias Goergens Link: https://lore.kernel.org/r/20260925052329.2683619-1-matthias.goergens@gmail.com Signed-off-by: Niklas Cassel --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c9d..3e7ede95c7cf5f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14911,7 +14911,7 @@ M: Damien Le Moal M: Niklas Cassel L: linux-ide@vger.kernel.org S: Maintained -T: git git://git.kernel.org/pub/scm/linux/kernel/git/libata/linux.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/libata/linux.git for-next F: Documentation/ABI/testing/sysfs-ata F: Documentation/devicetree/bindings/ata/ F: drivers/ata/ From 090991b292c69029570521fc97e97dc0c3bcda87 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Thu, 24 Sep 2026 15:24:06 +0200 Subject: [PATCH 1271/1417] ASoC: qcom: sdm845: Demystify TDM masks a bit Describe the mask with the bits used for each RX/TX. Signed-off-by: David Heidelberg Link: https://patch.msgid.link/20260924-pixel3-audio-v4-1-85b5d133aa5e@ixit.cz Signed-off-by: Mark Brown --- sound/soc/qcom/sdm845.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c index 6843ab8ba017e9..0ce8265ab1c1f7 100644 --- a/sound/soc/qcom/sdm845.c +++ b/sound/soc/qcom/sdm845.c @@ -23,9 +23,14 @@ #define DEFAULT_MCLK_RATE 24576000 #define TDM_BCLK_RATE 6144000 #define MI2S_BCLK_RATE 1536000 -#define LEFT_SPK_TDM_TX_MASK 0x30 -#define RIGHT_SPK_TDM_TX_MASK 0xC0 -#define SPK_TDM_RX_MASK 0x03 +#define LEFT_SPK_TDM_RX_MASK BIT(0) +#define RIGHT_SPK_TDM_RX_MASK BIT(1) +#define SPK_TDM_RX_MASK (LEFT_SPK_TDM_RX_MASK | RIGHT_SPK_TDM_RX_MASK) +#define MIC1_TDM_RX_MASK BIT(2) +#define MIC2_TDM_RX_MASK BIT(3) +#define MIC_TDM_RX_MASK (MIC1_TDM_RX_MASK | MIC2_TDM_RX_MASK) +#define LEFT_SPK_TDM_TX_MASK (BIT(4) | BIT(5)) +#define RIGHT_SPK_TDM_TX_MASK (BIT(6) | BIT(7)) #define NUM_TDM_SLOTS 8 #define SLIM_MAX_TX_PORTS 16 #define SLIM_MAX_RX_PORTS 13 @@ -112,8 +117,8 @@ static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, channels = params_channels(params); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0x3, - 8, slot_width); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, SPK_TDM_RX_MASK, + NUM_TDM_SLOTS, slot_width); if (ret < 0) { dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n", __func__, ret); @@ -128,8 +133,10 @@ static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, goto end; } } else { - ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0xf, 0, - 8, slot_width); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + SPK_TDM_RX_MASK | + MIC_TDM_RX_MASK, 0, + NUM_TDM_SLOTS, slot_width); if (ret < 0) { dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n", __func__, ret); From ed41c80271e6fc9b8d6aea01f2e6f3ced0f534b2 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Thu, 24 Sep 2026 15:24:07 +0200 Subject: [PATCH 1272/1417] ASoC: qcom: sdm845: use DSP_A format for TDM codec DAIs Before the DSP_B only worked because the only close-to-mainline consumer cs35l36 codec was patched to map both DSP_A and DSP_B to the same hardware register value (asp_fmt = 0), which is inherently DSP_A timing. Use the right codec (DSP_A) which works as expected. Signed-off-by: David Heidelberg Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260924-pixel3-audio-v4-2-85b5d133aa5e@ixit.cz Signed-off-by: Mark Brown --- sound/soc/qcom/sdm845.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c index 0ce8265ab1c1f7..d1cc825fc5cdce 100644 --- a/sound/soc/qcom/sdm845.c +++ b/sound/soc/qcom/sdm845.c @@ -387,7 +387,7 @@ static int sdm845_snd_startup(struct snd_pcm_substream *substream) TDM_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); } - codec_dai_fmt |= SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_DSP_B; + codec_dai_fmt |= SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_DSP_A; for_each_rtd_codec_dais(rtd, j, codec_dai) { From 84c3a8e05ce8c9d0c6678b33964cd31c4ab569a3 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Thu, 24 Sep 2026 15:24:08 +0200 Subject: [PATCH 1273/1417] ASoC: qcom: sdm845: Use per-speaker RX masks for TDM slot assignment Both Left and Right codec DAIs were passing the same SPK_TDM_RX_MASK, both speakers ended up on slot 0, breaking the one speaker in configuration such as on Pixel 3. Split SPK_TDM_RX_MASK into per-speaker masks so that the Left codec gets slot 0 (rx_mask=0x01) and the Right codec gets slot 1 (rx_mask=0x02). This commit is here, so later CS35L36 receives correct slot for right and left speakers. Assisted-by: Claude:claude-4.6-opus Signed-off-by: David Heidelberg Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260924-pixel3-audio-v4-3-85b5d133aa5e@ixit.cz Signed-off-by: Mark Brown --- sound/soc/qcom/sdm845.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c index d1cc825fc5cdce..59cfa3b26cd4dc 100644 --- a/sound/soc/qcom/sdm845.c +++ b/sound/soc/qcom/sdm845.c @@ -157,7 +157,7 @@ static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, if (!strcmp(codec_dai->component->name_prefix, "Left")) { ret = snd_soc_dai_set_tdm_slot( codec_dai, LEFT_SPK_TDM_TX_MASK, - SPK_TDM_RX_MASK, NUM_TDM_SLOTS, + LEFT_SPK_TDM_RX_MASK, NUM_TDM_SLOTS, slot_width); if (ret < 0) { dev_err(rtd->dev, @@ -169,7 +169,7 @@ static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, if (!strcmp(codec_dai->component->name_prefix, "Right")) { ret = snd_soc_dai_set_tdm_slot( codec_dai, RIGHT_SPK_TDM_TX_MASK, - SPK_TDM_RX_MASK, NUM_TDM_SLOTS, + RIGHT_SPK_TDM_RX_MASK, NUM_TDM_SLOTS, slot_width); if (ret < 0) { dev_err(rtd->dev, From 5e9f324509a76e377db813839d27012084081dfb Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Thu, 24 Sep 2026 15:24:09 +0200 Subject: [PATCH 1274/1417] ASoC: qcom: sdm845: Set codec dai and component sysclk during startup The cs35l36 codec needs the codec dai and component sysclk to be set during TDM startup. Set these for all codec DAIs on the QUATERNARY_TDM path, gracefully handling codecs that don't support sysclk by ignoring -ENOTSUPP returns. Based on work of Joel Selvaraj. Signed-off-by: David Heidelberg Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260924-pixel3-audio-v4-4-85b5d133aa5e@ixit.cz Signed-off-by: Mark Brown --- sound/soc/qcom/sdm845.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c index 59cfa3b26cd4dc..f0fbec9c8df257 100644 --- a/sound/soc/qcom/sdm845.c +++ b/sound/soc/qcom/sdm845.c @@ -412,6 +412,28 @@ static int sdm845_snd_startup(struct snd_pcm_substream *substream) return ret; } } + + /* Set codec sysclk needed by codecs like cs35l36. */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + TDM_BCLK_RATE, + SND_SOC_CLOCK_IN); + if (ret < 0 && ret != -ENOTSUPP) { + dev_err(codec_dai->dev, + "Failed to set codec dai sysclk: %d\n", + ret); + return ret; + } + + ret = snd_soc_component_set_sysclk(codec_dai->component, + 0, 0, + TDM_BCLK_RATE, + SND_SOC_CLOCK_IN); + if (ret < 0 && ret != -ENOTSUPP) { + dev_err(codec_dai->dev, + "Failed to set codec component sysclk: %d\n", + ret); + return ret; + } } break; case SLIMBUS_0_RX...SLIMBUS_6_TX: From f390794e6354de783a1443f2b935d9e2b4361354 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Thu, 24 Sep 2026 15:24:10 +0200 Subject: [PATCH 1275/1417] ASoC: cs35l36: Implement set_tdm_slot to program RX and TX slots Program the ASP RX and TX slot registers from the TDM masks passed by the machine driver. Each set bit in a mask names a slot; codec channels are assigned to those slots in order, ASPRX1 taking the first RX slot and ASPTX1..TX8 the first eight TX slots, with a warning if the mask names more slots than the device has channels. Passing slots == 0 or an empty mask restores the hardware defaults, ASPRX1 in slot 0 and ASPTX1..TX8 in slots 0..7. This lets a machine driver with several amplifiers on one bus, such as sdm845 with two CS35L36, put each amplifier on its own RX slot and keep their TX slots from colliding. Assisted-by: Claude:claude-4.6-opus Reviewed-by: Charles Keepax Signed-off-by: David Heidelberg Link: https://patch.msgid.link/20260924-pixel3-audio-v4-5-85b5d133aa5e@ixit.cz Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l36.c | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/sound/soc/codecs/cs35l36.c b/sound/soc/codecs/cs35l36.c index 170588be4dbe34..5fc8ec292e3c44 100644 --- a/sound/soc/codecs/cs35l36.c +++ b/sound/soc/codecs/cs35l36.c @@ -947,6 +947,49 @@ static const struct cs35l36_pll_config *cs35l36_get_clk_config( return NULL; } +static void cs35l36_mask_to_slots(struct cs35l36_private *cs35l36, + unsigned long mask, unsigned int base_reg, + unsigned int nchan) +{ + unsigned int chan = 0, shift; + int slot; + + /* Two 6-bit slot fields per register, at bits 0 and 16 */ + for_each_set_bit(slot, &mask, BITS_PER_TYPE(mask)) { + if (chan == nchan) { + dev_warn(cs35l36->dev, + "Too many slots in TDM mask: %lx\n", mask); + return; + } + + shift = (chan % 2) * CS35L36_ASP_TX2_SLOT_SHIFT; + regmap_update_bits(cs35l36->regmap, base_reg + (chan / 2) * 4, + CS35L36_ASP_RX1_SLOT_MASK << shift, + slot << shift); + chan++; + } +} + +static int cs35l36_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct cs35l36_private *cs35l36 = + snd_soc_component_get_drvdata(dai->component); + + /* Note: rx/tx is from point of view of the CPU end */ + if (!slots || !rx_mask) + rx_mask = BIT(0); /* ASPRX1 in slot 0 */ + + if (!slots || !tx_mask) + tx_mask = GENMASK(7, 0); /* ASPTX1..8 in slots 0..7 */ + + cs35l36_mask_to_slots(cs35l36, rx_mask, CS35L36_ASP_RX1_SLOT, 1); + cs35l36_mask_to_slots(cs35l36, tx_mask, CS35L36_ASP_TX1_TX2_SLOT, 8); + + return 0; +} + static const u64 cs35l36_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_I2S | SND_SOC_POSSIBLE_DAIFMT_DSP_A | @@ -963,6 +1006,7 @@ static const struct snd_soc_dai_ops cs35l36_ops = { .set_sysclk = cs35l36_dai_set_sysclk, .auto_selectable_formats = &cs35l36_selectable_formats, .num_auto_selectable_formats = 1, + .set_tdm_slot = cs35l36_set_tdm_slot, }; #define CS35L36_RATES ( \ From ae146bc1abdeb4607abf2975b858c053024e8ac1 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Mon, 21 Sep 2026 12:40:13 +0200 Subject: [PATCH 1276/1417] ovl: fix UAF in ovl_do_mkdir() debug print ovl_do_mkdir() prints the input dentry with %pd after vfs_mkdir(). Since commit fe497f0759e0 ("VFS: change vfs_mkdir() to unlock on failure."), vfs_mkdir() calls end_creating() on the input dentry on failure and may replace it on success, so the post-call %pd can use-after-free the dentry when CONFIG_OVERLAY_FS_DEBUG is enabled. Print the dentry before the call and only the result afterward. Reported-by: syzbot+ced26b784bf977d223dd@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ced26b784bf977d223dd Fixes: fe497f0759e0 ("VFS: change vfs_mkdir() to unlock on failure.") Signed-off-by: Amir Goldstein Link: https://patch.msgid.link/20260921104013.40475-1-amir73il@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/overlayfs.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index e0d8c6152e9fc5..7f3558372c5990 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -254,8 +254,10 @@ static inline struct dentry *ovl_do_mkdir(struct ovl_fs *ofs, { struct dentry *ret; + /* vfs_mkdir() drops @dentry on failure and may replace it on success */ + pr_debug("mkdir(%pd2, 0%o)\n", dentry, mode); ret = vfs_mkdir(ovl_upper_mnt_idmap(ofs), dir, dentry, mode, NULL); - pr_debug("mkdir(%pd2, 0%o) = %i\n", dentry, mode, PTR_ERR_OR_ZERO(ret)); + pr_debug("...mkdir = %i\n", PTR_ERR_OR_ZERO(ret)); return ret; } From 76d8e697242e7e4e30dd08eff7c278728f9dd8e2 Mon Sep 17 00:00:00 2001 From: Drif Abdelmalek Mohamed Said Date: Fri, 18 Sep 2026 23:42:04 +0100 Subject: [PATCH 1277/1417] dcache: unpoison the inline name buffer in __d_alloc() syzbot reported: BUG: KMSAN: uninit-value in dentry_string_cmp fs/dcache.c:291 [inline] BUG: KMSAN: uninit-value in dentry_cmp fs/dcache.c:322 [inline] BUG: KMSAN: uninit-value in __d_lookup_rcu+0x37d/0x5e0 fs/dcache.c:2522 dentry_string_cmp fs/dcache.c:291 [inline] dentry_cmp fs/dcache.c:322 [inline] __d_lookup_rcu+0x37d/0x5e0 fs/dcache.c:2522 lookup_fast+0x194/0xa40 fs/namei.c:1854 lookup_fast_for_open fs/namei.c:4545 [inline] open_last_lookups fs/namei.c:4579 [inline] path_openat+0x9ef/0x6540 fs/namei.c:4856 do_file_open+0x2aa/0x680 fs/namei.c:4888 do_sys_openat2+0x17c/0x390 fs/open.c:1395 do_sys_open fs/open.c:1401 [inline] __do_sys_openat fs/open.c:1417 [inline] __se_sys_openat fs/open.c:1412 [inline] __x64_sys_openat+0x240/0x300 fs/open.c:1412 x64_sys_call+0x2445/0x3ea0 arch/x86/include/generated/asm/syscalls_64.h:258 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x15d/0x3c0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Uninit was stored to memory at: copy_name fs/dcache.c:3031 [inline] __d_move+0xd29/0x21f0 fs/dcache.c:3099 d_move+0x71/0xf0 fs/dcache.c:3147 vfs_rename+0x2619/0x2770 fs/namei.c:6085 filename_renameat2+0xa59/0x1230 fs/namei.c:6188 __do_sys_rename fs/namei.c:6232 [inline] __se_sys_rename+0xc5/0x5c0 fs/namei.c:6228 __x64_sys_rename+0x78/0xb0 fs/namei.c:6228 x64_sys_call+0x329/0x3ea0 arch/x86/include/generated/asm/syscalls_64.h:83 do_syscall_x64 arch/x86/entry/syscall_64.c:63 do_syscall_64+0x15d/0x3c0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Uninit was created at: slab_post_alloc_hook mm/slub.c:4617 [inline] slab_alloc_node mm/slub.c:4939 [inline] kmem_cache_alloc_lru_noprof+0x376/0x1230 mm/s __d_alloc+0x52/0x9f0 fs/dcache.c:1902 d_alloc+0x57/0x300 fs/dcache.c:1981 lookup_one_qstr_excl+0x19d/0x7a0 fs/namei.c:1806 __start_renaming+0x341/0x850 fs/namei.c:3888 filename_renameat2+0x625/0x1230 fs/namei.c:6163 __do_sys_rename fs/namei.c:6232 [inline] __se_sys_rename+0xc5/0x5c0 fs/namei.c:6228 __x64_sys_rename+0x78/0xb0 fs/namei.c:6228 x64_sys_call+0x329/0x3ea0 arch/x86/include/generated/asm/syscalls_64.h:83 do_syscall_x64 arch/x86/entry/syscall_64.c:63 do_syscall_64+0x15d/0x3c0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f The race is between a concurrent open() and rename() of the same path. __d_alloc() only stores the name itself and its terminating NUL, so the rest of the inline buffer (d_shortname, DNAME_INLINE_LEN bytes) is left uninitialized. copy_name(), called from rename(), copies that buffer as a whole, so the uninitialized tail is propagated into the dentry that is being moved. Meanwhile __d_lookup_rcu(), called from open(), is an optimistic lockless lookup: it checks d_name.hash_len first and leaves the seqcount retry to its caller, so it can end up comparing against a dentry whose name a rename is rewriting in place, using a stale (longer) length. The comparison then runs past the terminating NUL and reads bytes of the uninitialized tail, which KMSAN reports. The read is harmless by design: it stays inside the buffer, the name is still NUL-terminated, and the result is thrown away by the seqcount retry. It is not specific to KMSAN either - with CONFIG_DCACHE_WORD_ACCESS enabled the very same bytes are read by read_word_at_a_time(), which is __no_sanitize_or_inline and therefore invisible to KMSAN. KMSAN builds only see the instrumented byte-at-a-time dentry_string_cmp() because CONFIG_DCACHE_WORD_ACCESS is disabled when KMSAN is enabled on x86: commit 7cf8f44a5a1c ("x86: fs: kmsan: disable CONFIG_DCACHE_WORD_ACCESS") Zeroing the inline buffer would hide the report, but it would add a memset() to a hot allocation path just to initialize bytes that are never used as part of a name. Instead, tell KMSAN the inline buffer is initialized: kmsan_unpoison_memory() compiles to nothing unless CONFIG_KMSAN is set, and doing it at allocation time is enough for every dentry, because copy_name() and swap_names() copy the whole buffer and thus propagate its shadow. Reported-by: syzbot+7ff3adde89dd795ad4c4@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7ff3adde89dd795ad4c4 Signed-off-by: Drif Abdelmalek Mohamed Said Changes in v3: - Annotate for KMSAN instead of zeroing, as suggested in review: the read is harmless, so unpoison the inline buffer in __d_alloc() with kmsan_unpoison_memory() (a no-op unless CONFIG_KMSAN) rather than adding a memset() to the dentry allocation path. - Document why only KMSAN builds report this at all: with CONFIG_DCACHE_WORD_ACCESS the same read goes through read_word_at_a_time(), which KMSAN does not instrument. - Rewrite the commit message; the previous one had several truncated lines. Link: https://patch.msgid.link/20260918224204.3056-1-drifabdelmalekmohamedsaid@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/dcache.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/dcache.c b/fs/dcache.c index 1b1a81f10da67c..a66be85f9d014e 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1916,6 +1916,10 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name) * be overwriting an internal NUL character */ dentry->d_shortname.string[DNAME_INLINE_LEN-1] = 0; + + /* Racy __d_lookup_rcu() walk may read past the NUL; harmless */ + kmsan_unpoison_memory(dentry->d_shortname.string, DNAME_INLINE_LEN); + if (unlikely(!name)) { name = &slash_name; dname = dentry->d_shortname.string; From 5bfa9f1a9dcb6ecb607adbc1c0226605c972935b Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Thu, 24 Sep 2026 11:21:39 +0200 Subject: [PATCH 1278/1417] kprobes: Fix permanent hang when flushing the kprobe optimizer Writing 0 to /proc/sys/debug/kprobes-optimization while a kprobe is jump-optimized never returns. The writer sleeps in D state forever with kprobe_sysctl_mutex held, so any later read or write of that sysctl hangs as well. For example, with vfs_read+9 as an optimizable address in this build: # cd /sys/kernel/tracing # echo 'p:myprobe vfs_read+9' >> kprobe_events # echo 1 > events/kprobes/myprobe/enable # # wait until /sys/kernel/debug/kprobes/list shows [OPTIMIZED] # echo 0 > /proc/sys/debug/kprobes-optimization INFO: task sh:246 blocked for more than 10 seconds. Call Trace: __schedule+0x1176/0x4f70 schedule+0xdc/0x2c0 schedule_timeout+0x17b/0x260 wait_for_completion+0x173/0x3c0 wait_for_kprobe_optimizer_locked+0xbc/0x130 proc_kprobes_optimization_handler+0x156/0x1b0 proc_sys_call_handler+0x324/0x490 vfs_write+0x52d/0xfe0 ksys_write+0xff/0x200 do_syscall_64+0x106/0x630 entry_SYSCALL_64_after_hwframe+0x77/0x7f ... INFO: task cat:265 is blocked on a mutex likely owned by task sh:246. wait_for_kprobe_optimizer_locked() reinitializes optimizer_completion, asks the optimizer thread to flush and sleeps in wait_for_completion(). The thread drains the (un)optimizing lists, but calls complete() only if completion_done() is true, i.e. if the completion is already done, which never happens while someone waits. disarm_all_kprobes() and kprobe_trace_self_tests_init() wait the same way. Calling complete() unconditionally would not be enough: the waiter drops kprobe_mutex while it sleeps, and nothing else serializes the sysctl handler against the debugfs "enabled" file. A second flusher that still finds the lists non-empty, e.g. because a disabled probe is queued for unoptimizing, reinitializes the completion under the first: sysctl write debugfs "enabled" write unoptimize_all_kprobes() wait_for_kprobe_optimizer_locked() init_completion(c) mutex_unlock(&kprobe_mutex) wait_for_completion(c) disarm_all_kprobes() wait_for_kprobe_optimizer_locked() init_completion(c) // c->wait is reset, the first // waiter is off the queue mutex_unlock(&kprobe_mutex) wait_for_completion(c) kprobe_optimizer() complete(c) // wakes the debugfs writer only where c is &optimizer_completion. Lining up the two writes during an optimizer pass loses the sysctl writer this way. Replace the completion with a counter of optimizer passes, bumped at the end of each pass and signalled with wake_up_var_locked(), both under kprobe_mutex. A flusher samples the count and waits with wait_var_event_mutex(), which drops kprobe_mutex only while sleeping, so a new count means a whole pass ran in the meantime. Nothing is reinitialized, so several flushers can sleep in the wait at once. Link: https://lore.kernel.org/all/20260924092142.199198-1-parri.andrea@gmail.com/ Fixes: 73c12f209462 ("kprobes: Use dedicated kthread for kprobe optimizer") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Andrea Parri Signed-off-by: Masami Hiramatsu (Google) --- kernel/kprobes.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/kernel/kprobes.c b/kernel/kprobes.c index 6337da5cab9e7e..4edd8ca5c65782 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -526,7 +527,8 @@ enum { OPTIMIZER_ST_FLUSHING = 2, }; -static DECLARE_COMPLETION(optimizer_completion); +/* Bumped at the end of each kprobe_optimizer() pass, under 'kprobe_mutex' */ +static unsigned long optimizer_passes; #define OPTIMIZE_DELAY 5 @@ -654,9 +656,9 @@ static void kprobe_optimizer(void) do_free_cleaned_kprobes(); } - /* Step 5: Kick optimizer again if needed. But if there is a flush requested, */ - if (completion_done(&optimizer_completion)) - complete(&optimizer_completion); + /* Step 5: Wake up flushers, and kick optimizer again if needed. */ + optimizer_passes++; + wake_up_var_locked(&optimizer_passes, &kprobe_mutex); if (!list_empty(&optimizing_list) || !list_empty(&unoptimizing_list)) kick_kprobe_optimizer(); /*normal kick*/ @@ -708,7 +710,8 @@ static void wait_for_kprobe_optimizer_locked(void) lockdep_assert_held(&kprobe_mutex); while (!list_empty(&optimizing_list) || !list_empty(&unoptimizing_list)) { - init_completion(&optimizer_completion); + unsigned long passes = optimizer_passes; + /* * Set state to OPTIMIZER_ST_FLUSHING and wake up the thread if it's * idle. If it's already kicked, it will see the state change. @@ -717,9 +720,12 @@ static void wait_for_kprobe_optimizer_locked(void) OPTIMIZER_ST_FLUSHING) != OPTIMIZER_ST_FLUSHING) wake_up(&kprobe_optimizer_wait); - mutex_unlock(&kprobe_mutex); - wait_for_completion(&optimizer_completion); - mutex_lock(&kprobe_mutex); + /* + * kprobe_optimizer() holds 'kprobe_mutex' for a whole pass, which + * this drops while sleeping, so a new count means a full pass ran. + */ + wait_var_event_mutex(&optimizer_passes, + optimizer_passes != passes, &kprobe_mutex); } } From 4d2b92b82ee82f0008179cd795b02256144064e1 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 25 Sep 2026 15:11:27 +0200 Subject: [PATCH 1279/1417] ASoC: amd: acp: add EFI dependency for selecting TAS2783 The TAS2783 codec requires EFI support, so it cannot be selected unless that is also enabled: WARNING: unmet direct dependencies detected for SND_SOC_TAS2783_SDW Depends on [n]: SOUND [=m] && SND [=m] && SND_SOC [=m] && SOUNDWIRE [=m] && SND_SOC_SDCA [=m] && EFI [=n] Selected by [m]: - SND_SOC_AMD_LEGACY_SDW_MACH [=m] && SOUND [=m] && SND [=m] && SND_SOC [=m] && SND_SOC_AMD_ACP_COMMON [=m] && X86 [=y] && PCI [=y] && ACPI [=y] && SOUNDWIRE [=m] Add this as a dependency for SND_SOC_AMD_LEGACY_SDW_MACH, assuming that this is only needed on modern x86 machines that require EFI support already. It would likely be possible to do something more complex to still allow SND_SOC_AMD_LEGACY_SDW_MACH to be used without TAS2783 when EFI is disabled, but I cannot see a usecase for that. Fixes: 28114992dd2f ("ASoC: amd: acp: enable TAS2783 and add RT712-VB SoundWire machine") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260925131137.146638-1-arnd@kernel.org Signed-off-by: Mark Brown --- sound/soc/amd/acp/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig index 6433876430f046..b7b7b4f4a55ca3 100644 --- a/sound/soc/amd/acp/Kconfig +++ b/sound/soc/amd/acp/Kconfig @@ -167,7 +167,7 @@ config SND_SOC_AMD_SOF_SDW_MACH config SND_SOC_AMD_LEGACY_SDW_MACH tristate "AMD Legacy(No DSP) Soundwire Machine Driver Support" - depends on X86 && PCI && ACPI + depends on X86 && PCI && ACPI && EFI depends on SOUNDWIRE select SND_SOC_AMD_SDW_MACH_COMMON select SND_SOC_SDW_UTILS From aa5e44b29ffe4eaa08cc2237fd65bc2596bc023e Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 20:48:08 +0000 Subject: [PATCH 1280/1417] autofs: fix sbi->pipe file reference leak in autofs_kill_sb() When autofs_fill_super() fails before clearing AUTOFS_SBI_CATATONIC (for example, when find_get_pid() fails on an invalid pgrp mount option, or when an fs_context is closed before mounting), deactivate_locked_super() invokes autofs_kill_sb() -> autofs_catatonic_mode(sbi). Because AUTOFS_SBI_CATATONIC is still set in sbi->flags, autofs_catatonic_mode() returns early without calling fput(sbi->pipe), permanently leaking the pipe struct file reference. Explicitly release sbi->pipe in autofs_kill_sb() if it is still non-NULL after autofs_catatonic_mode(). Fixes: ebc921ca9b92 ("autofs: copy autofs4 to autofs") Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260919204808.2812930-1-benquike@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/autofs/inode.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/autofs/inode.c b/fs/autofs/inode.c index 6b15a3717ba7e4..066c16f2ea5695 100644 --- a/fs/autofs/inode.c +++ b/fs/autofs/inode.c @@ -51,6 +51,10 @@ void autofs_kill_sb(struct super_block *sb) if (sbi) { /* Free wait queues, close pipe */ autofs_catatonic_mode(sbi); + if (sbi->pipe) { + fput(sbi->pipe); + sbi->pipe = NULL; + } put_pid(sbi->oz_pgrp); } From 35d442ed1f86465e49df3119fb898f985186db13 Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Tue, 22 Sep 2026 16:55:30 +0200 Subject: [PATCH 1281/1417] bpf: fs/xattr: don't assume the inode is locked in path_unlink/path_rmdir bpf_lsm_has_d_inode_locked() makes the verifier rewrite bpf_[set|remove]_dentry_xattr() to the _locked variants, which assume that the caller already holds the inode's i_rwsem. The path_unlink and path_rmdir hooks are listed, but security_path_unlink() and security_path_rmdir() run before vfs_unlink()/vfs_rmdir() take the victim inode's i_rwsem, so a sleepable BPF LSM program attached to either hook mutates the victim's xattrs without the lock held. Drop the two path hooks from d_inode_locked_hooks so that the verifier keeps the locking bpf_[set|remove]_dentry_xattr() variants, which take the lock themselves. Fixes: 56467292794b8 ("bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs") Cc: stable@vger.kernel.org Signed-off-by: Andrea Parri Link: https://patch.msgid.link/20260922145530.369775-1-parri.andrea@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/bpf_fs_kfuncs.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c index 6cb8772679782d..357a379ef92a5c 100644 --- a/fs/bpf_fs_kfuncs.c +++ b/fs/bpf_fs_kfuncs.c @@ -472,10 +472,6 @@ BTF_ID(func, bpf_lsm_inode_rmdir) BTF_ID(func, bpf_lsm_inode_setattr) BTF_ID(func, bpf_lsm_inode_setxattr) BTF_ID(func, bpf_lsm_inode_unlink) -#ifdef CONFIG_SECURITY_PATH -BTF_ID(func, bpf_lsm_path_unlink) -BTF_ID(func, bpf_lsm_path_rmdir) -#endif /* CONFIG_SECURITY_PATH */ BTF_SET_END(d_inode_locked_hooks) bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog) From b78b728e21c32ec4c330b299f657fb1eb02dffc2 Mon Sep 17 00:00:00 2001 From: Hao Ge Date: Wed, 23 Sep 2026 14:37:59 +0800 Subject: [PATCH 1282/1417] netfs: Fix missing alloc tagging of direct mempool allocations Commit 1d78d56c43ef ("netfs: Fix folio_queue ENOMEM in writeback by adding a mempool") added a mempool for the folio_queues and made the request, subrequest and folio_queue allocations distinguish between writeback and everything else. Writeback is part of memory reclaim and must not fail due to ENOMEM, so it allocates under GFP_NOFS through mempool_alloc(), which may dip into the pool's reserve and, if that runs empty, wait for elements to be returned. The GFP_KERNEL paths, which can return -ENOMEM to their callers, invoke the pool's ->alloc() callback directly instead. The direct call, however, skips the alloc_hooks() wrapper that the mempool_alloc() macro provides. The pool callbacks, mempool_alloc_slab() and mempool_kmalloc(), call kmem_cache_alloc_noprof() and kmalloc_noprof() and rely on current->alloc_tag having been set by the caller. With CONFIG_MEM_ALLOC_PROFILING_DEBUG=y this leads to current->alloc_tag not set WARNING: ./include/linux/alloc_tag.h:161 at __alloc_tagging_slab_alloc_hook alloc_tag was not set WARNING: ./include/linux/alloc_tag.h:166 at __alloc_tagging_slab_free_hook at allocation and free time respectively, as reported when reading files on a CIFS mount. The allocations are also missing from /proc/allocinfo. Wrap the direct ->alloc() invocations in alloc_hooks() with a new mempool_alloc_noreserve() helper in include/linux/mempool.h, next to the other alloc_hooks()-wrapped macros such as mempool_alloc(). The GFP_KERNEL paths keep their failable allocation semantics, they just get tagged now. Fixes: 1d78d56c43ef ("netfs: Fix folio_queue ENOMEM in writeback by adding a mempool") Reported-by: Erhard Furtner Closes: https://lore.kernel.org/all/0b004319-9ef7-437c-a4dd-174d6a9a83db@mailbox.org/ Tested-by: Erhard Furtner Suggested-by: Suren Baghdasaryan Cc: stable@vger.kernel.org Signed-off-by: Hao Ge Link: https://patch.msgid.link/20260923063759.34667-1-hao.ge@linux.dev Acked-by: Vlastimil Babka (SUSE) Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/objects.c | 4 ++-- fs/netfs/rolling_buffer.c | 2 +- include/linux/mempool.h | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index 7f6a3e912602ee..ad549daa9c794b 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -34,7 +34,7 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, rreq = mempool_alloc(mempool, gfp); } else { - rreq = mempool->alloc(gfp, mempool->pool_data); + rreq = mempool_alloc_noreserve(mempool, gfp); if (!rreq) return ERR_PTR(-ENOMEM); } @@ -214,7 +214,7 @@ struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq struct kmem_cache *cache = mempool->pool_data; if (rreq->gfp == GFP_KERNEL) - subreq = mempool->alloc(rreq->gfp, mempool->pool_data); + subreq = mempool_alloc_noreserve(mempool, rreq->gfp); else subreq = mempool_alloc(mempool, rreq->gfp); if (!subreq) diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c index 424e77a9a1098e..d30d5ef6d86eca 100644 --- a/fs/netfs/rolling_buffer.c +++ b/fs/netfs/rolling_buffer.c @@ -29,7 +29,7 @@ struct folio_queue *netfs_folioq_alloc(unsigned int rreq_id, gfp_t gfp, struct folio_queue *fq; if (gfp == GFP_KERNEL) - fq = netfs_folioq_pool.alloc(gfp, netfs_folioq_pool.pool_data); + fq = mempool_alloc_noreserve(&netfs_folioq_pool, gfp); else fq = mempool_alloc(&netfs_folioq_pool, gfp); if (fq) { diff --git a/include/linux/mempool.h b/include/linux/mempool.h index a0fa6d43e0dc4e..6da502aef2f718 100644 --- a/include/linux/mempool.h +++ b/include/linux/mempool.h @@ -70,6 +70,13 @@ int mempool_alloc_bulk_noprof(struct mempool *pool, void **elem, #define mempool_alloc_bulk(...) \ alloc_hooks(mempool_alloc_bulk_noprof(__VA_ARGS__)) +/* + * Allocate a new element without dipping into the pool's reserves or + * waiting. Returns NULL on failure. + */ +#define mempool_alloc_noreserve(_pool, _gfp) \ + alloc_hooks((_pool)->alloc(_gfp, (_pool)->pool_data)) + void *mempool_alloc_preallocated(struct mempool *pool) __malloc; void mempool_free(void *element, struct mempool *pool); unsigned int mempool_free_bulk(struct mempool *pool, void **elem, From c5a8fc2abe05cf6db71f57ff0177fddc59861a1a Mon Sep 17 00:00:00 2001 From: Jasper Firth Date: Fri, 25 Sep 2026 23:06:25 +1000 Subject: [PATCH 1283/1417] ASoC: Intel: bytcr_rt5651: Add quirk for Chuwi Hi8 with generic DMI strings Some Chuwi Hi8 tablets with model number CWI513 ship with a BIOS (American Megatrends 5.11, dated 05/21/2016) whose DMI sys_vendor and product_name are both "Default string". The tested unit is labelled just "Hi8", but its model number is the same as the existing "Chuwi Hi8 Pro (CWI513)" entry, and it needs the same quirks. That entry matches on sys_vendor "Hampoo" and product_name "X1D3_C806N", so it never applies to these units. Without its quirks the internal speaker, whose amplifier has a differential input wired across LOUT left and right, cancels to a faint distorted residual for any centred (L == R) content, and the headphone channels are swapped. Add an entry matching board_vendor "Hampoo" and board_name "Cherry Trail CR", plus the BIOS date since those two are too generic on their own. drivers/platform/x86/touchscreen_dmi.c already matches the Chuwi HiBook the same way. The quirks are those of the existing Hi8 Pro entry, except for the over-current detection scale factor: 1.0 instead of the 0.75 from BYT_RT5651_DEFAULT_QUIRKS. With 0.75 and a CTIA headset (inline mic and one button), loud speech into the mic, and headphone playback coupling onto the mic line, were reported as spurious KEY_PLAYPAUSE presses, one of them held for a whole 8 second speech window. With 1.0 the same test gave no spurious events, and 3 real button presses were reported as exactly 3 press/release pairs. Tested on a unit with the generic DMI strings, with a module built from this patch and no quirk module parameter. The quirks were applied via the new entry, the card components read "cfg-spk:1 cfg-mic:in2 cfg-hp:lrswap", and the following all work: speaker, headphone left/right order, jack and headset-mic detection, headset button, and the internal and headset mics. The problem was diagnosed with the help of an AI coding assistant, which also drafted this changelog. Assisted-by: Claude Opus 5.5 Signed-off-by: Jasper Firth Link: https://patch.msgid.link/20260925-hi8-quirk-v1-1-ffd00b0447f9@hotmail.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/bytcr_rt5651.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c index 62cb4856c7972d..c9b1053205de55 100644 --- a/sound/soc/intel/boards/bytcr_rt5651.c +++ b/sound/soc/intel/boards/bytcr_rt5651.c @@ -410,6 +410,23 @@ static const struct dmi_system_id byt_rt5651_quirk_table[] = { BYT_RT5651_HP_LR_SWAPPED | BYT_RT5651_MONO_SPEAKER), }, + { + /* Chuwi Hi8 (CWI513) with a BIOS using generic DMI strings */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), + DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), + /* Above matches are too generic, add bios-date match */ + DMI_MATCH(DMI_BIOS_DATE, "05/21/2016"), + }, + .driver_data = (void *)(BYT_RT5651_MCLK_EN | + BYT_RT5651_JD1_1 | + BYT_RT5651_OVCD_TH_2000UA | + BYT_RT5651_OVCD_SF_1P0 | + BYT_RT5651_IN2_MAP | + BYT_RT5651_HP_LR_SWAPPED | + BYT_RT5651_MONO_SPEAKER), + }, { /* Chuwi Vi8 Plus (CWI519) */ .callback = byt_rt5651_quirk_cb, From 7c7df141dcabaa5370b5e52a9d64e2c1ea13a338 Mon Sep 17 00:00:00 2001 From: David Cemin Date: Sat, 12 Sep 2026 11:21:31 -0700 Subject: [PATCH 1284/1417] ASoC: sdw_utils: clear stale RT711 device reference on exit asoc_sdw_rt711_exit() drops the reference held in ctx->headset_codec_dev but leaves the pointer populated. If the card cleanup path reaches the exit hook more than once after a failed or deferred probe, a later invocation reuses the stale pointer and calls put_device() again, underflowing the refcount and leading to oopses when later probe, driver bind, or suspend paths walk the SoundWire bus after the device reference was released. Observed as boot- and suspend-time oopses on an arm64 platform with an RT712 headset codec, reproducible deterministically by repeated machine-driver probe cycling with the codec drivers unloaded. Clear the pointer after put_device() so repeated cleanup becomes a no-op, which also makes the existing NULL check at the top of the exit hook effective. This matches what commit 046173b98de3 ("ASoC: sdw_utils: fix double put_device() on aggregated amps") did for the aggregated amp references. Fixes: 811648340707 ("ASoC: Intel: sof_sdw: avoid oops in error handling") Cc: stable@vger.kernel.org Signed-off-by: David Cemin Link: https://patch.msgid.link/20260912182131.1156475-1-dcemin@nvidia.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_rt711.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sdw_utils/soc_sdw_rt711.c b/sound/soc/sdw_utils/soc_sdw_rt711.c index 3a3a66b4b7372f..4707190e160c1b 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt711.c +++ b/sound/soc/sdw_utils/soc_sdw_rt711.c @@ -124,6 +124,7 @@ int asoc_sdw_rt711_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_ device_remove_software_node(ctx->headset_codec_dev); put_device(ctx->headset_codec_dev); + ctx->headset_codec_dev = NULL; return 0; } From cc466c77059ea6ce734f909ff4b156b036334cc3 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Fri, 25 Sep 2026 18:59:17 +0200 Subject: [PATCH 1285/1417] ASoC: airoha: Fix sparse warning for AN7581 AFE PCM Fix sparse warning reported by kernel bot: sparse warnings: (new ones prefixed by >>) >> sound/soc/mediatek/an7581/an7581-afe-pcm.c:144:30: sparse: sparse: symbol 'an7581_afe_fe_ops' was not declared. Should it be static? >> sound/soc/mediatek/an7581/an7581-afe-pcm.c:419:14: sparse: sparse: incorrect type in assignment (different address spaces) @@ expected void *base @@ got void [noderef] __iomem * @@ sound/soc/mediatek/an7581/an7581-afe-pcm.c:419:14: sparse: expected void *base sound/soc/mediatek/an7581/an7581-afe-pcm.c:419:14: sparse: got void [noderef] __iomem * >> sound/soc/mediatek/an7581/an7581-afe-pcm.c:439:42: sparse: sparse: incorrect type in argument 3 (different address spaces) @@ expected void [noderef] __iomem *regs @@ got void *base @@ sound/soc/mediatek/an7581/an7581-afe-pcm.c:439:42: sparse: expected void [noderef] __iomem *regs Make an7581_afe_fe_ops static and add __iomem for the base from devm_platform_ioremap_resource. Fixes: 4974ffa0d6c2 ("ASoC: airoha: Add AFE driver for Airoha AN7581") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609242059.t4FlJzOz-lkp@intel.com/ Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260925165919.5407-1-ansuelsmth@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/an7581/an7581-afe-pcm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/an7581/an7581-afe-pcm.c b/sound/soc/mediatek/an7581/an7581-afe-pcm.c index a1d742362538ea..d47edb63045224 100644 --- a/sound/soc/mediatek/an7581/an7581-afe-pcm.c +++ b/sound/soc/mediatek/an7581/an7581-afe-pcm.c @@ -141,7 +141,7 @@ static int an7581_afe_fe_startup(struct snd_pcm_substream *substream, struct snd return ret; } -const struct snd_soc_dai_ops an7581_afe_fe_ops = { +static const struct snd_soc_dai_ops an7581_afe_fe_ops = { .startup = an7581_afe_fe_startup, .shutdown = mtk_afe_fe_shutdown, .hw_params = mtk_afe_fe_hw_params, @@ -375,7 +375,7 @@ static int an7581_afe_pcm_dev_probe(struct platform_device *pdev) struct reset_control *reset; struct mtk_base_afe *afe; int i, irq_id, ret; - void *base; + void __iomem *base; afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); if (!afe) From f0cc352be29ba616e0682f66f551d3f3029886fd Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Mon, 14 Sep 2026 17:50:09 +0800 Subject: [PATCH 1286/1417] ASoC: wcd9335: Fix device reference leak in wcd9335_slim_status() wcd9335_slim_status() obtains the SLIM interface device with of_slim_get_device(), which uses device_find_child() internally and returns the device with a reference held. As the device_find_child() documentation says: you will need to drop the reference with put_device() after use. The reference is stored in wcd->slim_ifc_dev and never dropped, so the SLIM interface device cannot be released. Fix it by using devm_add_action_or_reset() or put_device() respectively. Found by code review. Fixes: 20aedafdf492 ("ASoC: wcd9335: add support to wcd9335 codec") Cc: stable@vger.kernel.org Signed-off-by: Ma Ke Link: https://patch.msgid.link/20260914095009.12049-1-make_ruc2021@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/wcd9335.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index 40de279a9750a7..9332082092c791 100644 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -5096,6 +5096,13 @@ static int wcd9335_slim_probe(struct slim_device *slim) return 0; } +static void wcd9335_put_device_action(void *data) +{ + struct device *dev = data; + + put_device(dev); +} + static int wcd9335_slim_status(struct slim_device *sdev, enum slim_device_status status) { @@ -5120,6 +5127,11 @@ static int wcd9335_slim_status(struct slim_device *sdev, return -EINVAL; } + ret = devm_add_action_or_reset(dev, wcd9335_put_device_action, + &wcd->slim_ifc_dev->dev); + if (ret) + return ret; + slim_get_logical_addr(wcd->slim_ifc_dev); wcd->regmap = regmap_init_slimbus(sdev, &wcd9335_regmap_config); From 7f05596bd4047e4623eeb1f3c6921d6f71481c3b Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 11:51:59 +0700 Subject: [PATCH 1287/1417] ASoC: mediatek: mt8183: Fix wrong clock cleanup on clk_set_parent() failure In mt8183_afe_enable_clock(), when clk_set_parent() fails, the current error path incorrectly cleans up the previously enabled clock instead of the clock used by clk_set_parent(). Fix the error path to clean up the correct clock when clk_set_parent() fails. Fixes: a94aec035a12 ("ASoC: mediatek: mt8183: add platform driver") Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903045203.175337-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8183/mt8183-afe-clk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c index cc4f8f4d3dab97..6ab0734ad13650 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c @@ -137,7 +137,7 @@ int mt8183_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s(), clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIO], aud_clks[CLK_CLK26M], ret); - goto CLK_MUX_AUDIO_ERR; + goto CLK_MUX_AUDIO_INTBUS_ERR; } ret = clk_prepare_enable(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); @@ -153,7 +153,7 @@ int mt8183_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s(), clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIOINTBUS], aud_clks[CLK_TOP_SYSPLL_D2_D4], ret); - goto CLK_MUX_AUDIO_INTBUS_ERR; + goto CLK_AFE_ERR; } ret = clk_prepare_enable(afe_priv->clk[CLK_AFE]); From 4a0f9566ae0c3ad32620254342b266f37f4757de Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 11:52:00 +0700 Subject: [PATCH 1288/1417] ASoC: mediatek: mt8183: Fix clock handling in mux disable path In the disable path of the appl*_mux_setting() functions, clk_disable_unprepare() is called after clk_set_parent(). If clk_set_parent() fails, clk_disable_unprepare() is skipped, potentially leaving one of the clocks enabled. Remove the goto exit paths so that all teardown steps are attempted and any errors are only reported. Fixes: a94aec035a12 ("ASoC: mediatek: mt8183: add platform driver") Suggested-by: Cezary Rojewski Signed-off-by: bui duc phuc Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20260903045203.175337-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8183/mt8183-afe-clk.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c index 6ab0734ad13650..dd1a5925af96d4 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c @@ -271,7 +271,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); @@ -281,7 +280,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); } @@ -298,7 +296,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) afe_priv->clk[CLK_CLK26M]); clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); ERR_ENABLE_CLK_TOP_MUX_AUD_1: -EXIT: return ret; } @@ -345,7 +342,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); @@ -355,7 +351,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); } @@ -372,7 +367,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) afe_priv->clk[CLK_CLK26M]); clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); ERR_ENABLE_CLK_TOP_MUX_AUD_2: -EXIT: return ret; } From 30cf48383006579d7cd35380cc6e766260b72bff Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 11:52:01 +0700 Subject: [PATCH 1289/1417] ASoC: mediatek: mt8183: Fix APLL enable error handling Currently, the mt8183_apll*_enable() functions call mux_setting(afe, true) but do not check its return value to handle failures. In addition, the cleanup paths of mt8183_apll*_enable() do not call mux_setting(afe, false) when the enable operation fails, while the mt8183_apll*_disable() functions do. Add error handling for apll*_mux_setting() and call mux_setting(afe, false) in the cleanup paths when mt8183_apll*_enable() fails. Fixes: a94aec035a12 ("ASoC: mediatek: mt8183: add platform driver") Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903045203.175337-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8183/mt8183-afe-clk.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c index dd1a5925af96d4..06556689976dbb 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c @@ -376,7 +376,9 @@ int mt8183_apll1_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll1_mux_setting(afe, true); + ret = apll1_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL22M]); if (ret) { @@ -405,6 +407,8 @@ int mt8183_apll1_enable(struct mtk_base_afe *afe) ERR_CLK_APLL1_TUNER: clk_disable_unprepare(afe_priv->clk[CLK_APLL22M]); ERR_CLK_APLL22M: + apll1_mux_setting(afe, false); + return ret; } @@ -430,7 +434,9 @@ int mt8183_apll2_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll2_mux_setting(afe, true); + ret = apll2_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL24M]); if (ret) { @@ -459,6 +465,8 @@ int mt8183_apll2_enable(struct mtk_base_afe *afe) ERR_CLK_APLL2_TUNER: clk_disable_unprepare(afe_priv->clk[CLK_APLL24M]); ERR_CLK_APLL24M: + apll2_mux_setting(afe, false); + return ret; } From 21cc0c50d395b8b8311c0b6c713038d9d9607063 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 11:52:02 +0700 Subject: [PATCH 1290/1417] ASoC: mediatek: mt8183: Use dev_err_probe() for error handling Replace dev_err() with dev_err_probe() to prevent log spam when probe returns -EPROBE_DEFER. Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903045203.175337-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8183/mt8183-afe-clk.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c index 06556689976dbb..40f9afcdaa16b8 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c @@ -101,12 +101,9 @@ int mt8183_init_clock(struct mtk_base_afe *afe) for (i = 0; i < CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "%s(), devm_clk_get %s fail, ret %ld\n", - __func__, aud_clks[i], - PTR_ERR(afe_priv->clk[i])); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } return 0; From f7bcb8c94ccb8a8b231a353c628b0104951ab6d0 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 11:52:03 +0700 Subject: [PATCH 1291/1417] ASoC: mediatek: mt8183: Drop redundant probe error messages The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() calls are redundant and can be removed. Reviewed-by: Cezary Rojewski Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903045203.175337-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8183/mt8183-afe-pcm.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c index 2634699534db72..46b7a2bb6aaa93 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c @@ -809,10 +809,8 @@ static int mt8183_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt8183_init_clock(afe); - if (ret) { - dev_err(dev, "init clock error\n"); + if (ret) return ret; - } pm_runtime_enable(dev); @@ -903,10 +901,8 @@ static int mt8183_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt8183_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); - if (ret) { - dev_err(dev, "could not request_irq for asys-isr\n"); + if (ret) goto err_pm_disable; - } /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); From b25a036348e945a1ddb106609fc9fa52205cc936 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 24 Sep 2026 18:48:12 +0200 Subject: [PATCH 1292/1417] ASoC: sprd: Shadow errors from dma_request_chan() For some reason the SPDR PCM support code requests DMA resources in the IOCTL. This might lead to the Linux error codes, such as EPROBE_DEFER, to be leaked to the user space. Prevent that from happening by shadowing errors from dma_request_chan() as it was done previously (see Fixes tag). Fixes: 08d77e31df21 ("ASoC: sprd: Replace dma_request_slave_channel() by dma_request_chan()") Reported-by: Sashiko Closes: https://lore.kernel.org/r/20260918115229.CC8AC1F000FF@smtp.kernel.org Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260924164812.816743-1-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sprd/sprd-pcm-compress.c | 2 +- sound/soc/sprd/sprd-pcm-dma.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/sprd/sprd-pcm-compress.c b/sound/soc/sprd/sprd-pcm-compress.c index f394121d417a3f..f790b959cd4e47 100644 --- a/sound/soc/sprd/sprd-pcm-compress.c +++ b/sound/soc/sprd/sprd-pcm-compress.c @@ -157,7 +157,7 @@ static int sprd_platform_compr_dma_config(struct snd_soc_component *component, chan = dma_request_chan(dev, dma_params->chan_name[channel]); if (IS_ERR(chan)) { dev_err(dev, "failed to request dma channel\n"); - return PTR_ERR(chan); + return -ENODEV; } dma->chan = chan; diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c index 2a89dc7616bf7c..bbd7516f81954e 100644 --- a/sound/soc/sprd/sprd-pcm-dma.c +++ b/sound/soc/sprd/sprd-pcm-dma.c @@ -177,7 +177,7 @@ static int sprd_pcm_request_dma_channel(struct snd_soc_component *component, dev_err(dev, "failed to request dma channel:%s\n", dma_params->chan_name[i]); sprd_pcm_release_dma_channel(substream); - return PTR_ERR(chan); + return -ENODEV; } data->chan = chan; } From 9814077275eca36ebf8d510d2f076d235ff9f51a Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 22 Sep 2026 20:13:48 -0700 Subject: [PATCH 1293/1417] ipe: fix use-after-free when auditing a newly loaded policy new_policy() audits the policy after ipe_new_policyfs_node() publishes it and drops the new directory's inode lock. A concurrent delete can free the policy while ipe_audit_policy_load() is still using it. Audit the successful load under that lock. Fixes: f44554b5067b ("audit,ipe: add IPE auditing support") Cc: stable@vger.kernel.org Assisted-by: LLM [FW: remove model name according to latest guideline] Signed-off-by: Fan Wu --- security/ipe/fs.c | 8 +++----- security/ipe/policy_fs.c | 3 +++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/security/ipe/fs.c b/security/ipe/fs.c index 076c111c85c869..847a76afb93d7d 100644 --- a/security/ipe/fs.c +++ b/security/ipe/fs.c @@ -159,18 +159,16 @@ static ssize_t new_policy(struct file *f, const char __user *data, } rc = ipe_new_policyfs_node(p); - if (rc) - goto out; out: kfree(copy); if (rc < 0) { ipe_free_policy(p); ipe_audit_policy_load(ERR_PTR(rc)); - } else { - ipe_audit_policy_load(p); + return rc; } - return (rc < 0) ? rc : len; + + return len; } static const struct file_operations np_fops = { diff --git a/security/ipe/policy_fs.c b/security/ipe/policy_fs.c index 9d92d8a14b13dd..a7aeb57483c6a7 100644 --- a/security/ipe/policy_fs.c +++ b/security/ipe/policy_fs.c @@ -481,6 +481,9 @@ int ipe_new_policyfs_node(struct ipe_policy *p) inode_lock(root); p->policyfs = policyfs; root->i_private = p; + /* Only audit signed policies from userspace */ + if (p->pkcs7) + ipe_audit_policy_load(p); inode_unlock(root); return 0; From 2776e9c28513a1c855a94792b292cbcc533418c8 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 22 Sep 2026 20:13:49 -0700 Subject: [PATCH 1294/1417] ipe: protect the dm-verity root hash with RCU ipe_bdev_setintegrity() frees the old root hash when dm-verity publishes a new one on ->preresume, while policy evaluation can still be dereferencing it. Protect the root hash with RCU. The evaluation path already runs under rcu_read_lock(). Fixes: e155858dd995 ("ipe: add support for dm-verity as a trust provider") Cc: stable@vger.kernel.org Assisted-by: LLM [FW: remove model name according to latest guideline] Signed-off-by: Fan Wu --- security/ipe/eval.c | 12 ++++++++---- security/ipe/eval.h | 2 +- security/ipe/hooks.c | 22 +++++++++++++++++----- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/security/ipe/eval.c b/security/ipe/eval.c index 21439c5be33648..4a7be96c84f030 100644 --- a/security/ipe/eval.c +++ b/security/ipe/eval.c @@ -134,10 +134,14 @@ static bool evaluate_boot_verified(const struct ipe_eval_ctx *const ctx) static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx, struct ipe_prop *p) { - return !!ctx->ipe_bdev && - !!ctx->ipe_bdev->root_hash && - ipe_digest_eval(p->value, - ctx->ipe_bdev->root_hash); + const struct digest_info *root_hash; + + if (!ctx->ipe_bdev) + return false; + + root_hash = rcu_dereference(ctx->ipe_bdev->root_hash); + + return root_hash && ipe_digest_eval(p->value, root_hash); } #else static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx, diff --git a/security/ipe/eval.h b/security/ipe/eval.h index fef65a36468cb8..b26d1147d0708b 100644 --- a/security/ipe/eval.h +++ b/security/ipe/eval.h @@ -27,7 +27,7 @@ struct ipe_bdev { #ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE bool dm_verity_signed; #endif /* CONFIG_IPE_PROP_DM_VERITY_SIGNATURE */ - struct digest_info *root_hash; + struct digest_info __rcu *root_hash; }; #endif /* CONFIG_IPE_PROP_DM_VERITY */ diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c index 0ae54a880405a9..d878b52ceec847 100644 --- a/security/ipe/hooks.c +++ b/security/ipe/hooks.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "ipe.h" #include "hooks.h" @@ -232,7 +233,20 @@ void ipe_bdev_free_security(struct block_device *bdev) { struct ipe_bdev *blob = ipe_bdev(bdev); - ipe_digest_free(blob->root_hash); + ipe_digest_free(rcu_access_pointer(blob->root_hash)); +} + +static void ipe_set_dmverity_roothash(struct ipe_bdev *blob, + struct digest_info *info) +{ + struct digest_info *old; + + /* Protected by device-mapper's md->suspend_lock */ + old = rcu_replace_pointer(blob->root_hash, info, true); + if (old) { + synchronize_rcu(); + ipe_digest_free(old); + } } #ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE @@ -280,8 +294,7 @@ int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type typ return -EINVAL; if (!value) { - ipe_digest_free(blob->root_hash); - blob->root_hash = NULL; + ipe_set_dmverity_roothash(blob, NULL); return 0; } @@ -301,8 +314,7 @@ int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type typ info->digest_len = digest->digest_len; - ipe_digest_free(blob->root_hash); - blob->root_hash = info; + ipe_set_dmverity_roothash(blob, info); return 0; err: From 12c1f6e03f944e399bd2c88441dca5dc702b95a5 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Wed, 23 Sep 2026 09:37:20 -0700 Subject: [PATCH 1295/1417] KVM: SEV: Free have_run_cpus during VM destruction even if VM is no longer SEV Unconditionally free SEV's "have run CPUs" cpumask in the VM destroy path, i.e. even for what appear to be non-SEV VMs, as an SEV VM becomes a non-SEV VM if its state is intra-host migrated. Alternatively, the mask could be freed in sev_migrate_from() when "converting" the source VM, but that gets annoying because ideally KVM would nullify the mask to guard against UAF, and nullifying the mask would need be conditioned on CPUMASK_OFFSTACK=y. Freeing the mask during sev_migrate_from() is also not robust against other KVM bugs, though that's kind of a moot point since any such bugs would show up even if sev->active is never set. I.e. KVM must get that side of things correct. But, that's not a great reason to add more code just to make things marginally less robust. Fixes: 6f38f8c57464 ("KVM: SVM: Flush cache only on CPUs running SEV guest") Cc: stable@vger.kernel.org Reported-by: Stefan Teodorescu Signed-off-by: Sean Christopherson Message-ID: <20260923163721.1584779-2-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/sev.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 5705723f1f4121..cdc1c04f60da77 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -2980,13 +2980,17 @@ void sev_vm_destroy(struct kvm *kvm) struct list_head *head = &sev->regions_list; struct list_head *pos, *q; + /* + * Free the mask even if the VM is not *currently* an SEV VM, as it may + * have been an SEV VM prior to intra-host migration. + */ + free_cpumask_var(sev->have_run_cpus); + if (!sev_guest(kvm)) return; WARN_ON(!list_empty(&sev->mirror_vms)); - free_cpumask_var(sev->have_run_cpus); - /* * If this is a mirror VM, remove it from the owner's list of a mirrors * and skip ASID cleanup (the ASID is tied to the lifetime of the owner). From 93de2a6a4b91b72607136dd656edf03fb399d27f Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Wed, 23 Sep 2026 09:37:21 -0700 Subject: [PATCH 1296/1417] KVM: SEV: Do cache maintenance on the source VM during intra-host migration Manually perform cache maintenance on the source VM during intra-host migration to ensure no stale data is left in CPU caches after the VM is destroyed. Because the source VM is "converted" to a non-SEV VM, KVM's memory reclaim flows won't trigger cache maintenance, e.g. when all guest memory is reclaimed in response to detaching from the mmu_notifier. Note, relying on the destination VM to do cache maintenance isn't an option as KVM doesn't require identical guest memory configurations, i.e. the source VM may have access to memory that the destination VM does not. Enforcing equivalent memory configurations is infeasible, as it would require a *deep* comparison of memslots, e.g. to verify that not only are the memslot identical, but what the memslots point at is also identical. Fixes: b56639318bb2 ("KVM: SEV: Add support for SEV intra host migration") Cc: stable@vger.kernel.org Reported-by: Stefan Teodorescu Signed-off-by: Sean Christopherson Message-ID: <20260923163721.1584779-3-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/sev.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index cdc1c04f60da77..63eb2155a7749b 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -2048,6 +2048,12 @@ static void sev_migrate_from(struct kvm *dst_kvm, struct kvm *src_kvm) src->pages_locked = 0; src->es_active = false; + /* + * Do cache maintenance on the source VM as it is no longer an SEV VM, + * i.e. memory reclaim flows won't trigger cache maintenance on the VM. + */ + sev_writeback_caches(src_kvm); + list_cut_before(&dst->regions_list, &src->regions_list, &src->regions_list); mutex_lock(&sev_mirror_lock); @@ -2187,6 +2193,10 @@ int sev_vm_move_enc_context_from(struct kvm *kvm, unsigned int source_fd) * the set of CPUs from the source. If a CPU was used to run a vCPU in * the source VM but is never used for the destination VM, then the CPU * can only have cached memory that was accessible to the source VM. + * Furthermore, KVM *must* perform cache maintenance on the source VM, + * as the source VM may have access to memory that the destination VM + * does not, i.e. KVM could skip flushes if memory is reclaimed from + * the old VM but not the new VM. */ if (!zalloc_cpumask_var(&dst_sev->have_run_cpus, GFP_KERNEL_ACCOUNT)) { ret = -ENOMEM; From 20b2f7f7562307abc70f932fe97147d175005935 Mon Sep 17 00:00:00 2001 From: Zain Aboobacker Date: Sat, 26 Sep 2026 12:25:33 -0400 Subject: [PATCH 1297/1417] ASoC: codecs: nau8825: fix typos in comments Fix various spelling mistakes in comments found by codespell. Assisted-by: Claude:claude-opus-5-5 codespell Signed-off-by: Zain Aboobacker Link: https://patch.msgid.link/20260926162533.25395-1-zainaboobacker33@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8825.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index 2eb0efc983a03f..63d927f3140902 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -348,7 +348,7 @@ static u32 nau8825_intlog10_dec3(u32 value) * @sig_org: original signal level * @sig_cros: cross talk signal level * - * The original and cross talk signal vlues need to be characterized. + * The original and cross talk signal values need to be characterized. * Once these values have been characterized, this sidetone value * can be converted to decibel with the equation below. * sidetone = 20 * log (original signal level / crosstalk signal level) @@ -725,7 +725,7 @@ static void nau8825_xtalk_measure(struct nau8825 *nau8825) break; case NAU8825_XTALK_IMM: /* In impedance measure state, the original and cross talk - * signal level vlues are ready. The side tone gain is deter- + * signal level values are ready. The side tone gain is deter- * mined with these signal level. After all, restore codec * configuration. */ @@ -771,7 +771,7 @@ static void nau8825_xtalk_work(struct work_struct *work) static void nau8825_xtalk_cancel(struct nau8825 *nau8825) { - /* If the crosstalk is eanbled and the process is on going, + /* If the crosstalk is enabled and the process is on going, * the driver forces to cancel the crosstalk task and * restores the configuration to original status. */ @@ -1303,7 +1303,7 @@ static int nau8825_hw_params(struct snd_pcm_substream *substream, NAU8825_CLK_ADC_SRC_MASK, osr->clk_src << NAU8825_CLK_ADC_SRC_SFT); - /* make BCLK and LRC divde configuration if the codec as master. */ + /* make BCLK and LRC divide configuration if the codec as master. */ regmap_read(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, &ctrl_val); if (ctrl_val & NAU8825_I2S_MS_MASTER) { /* get the bclk and fs ratio */ @@ -2897,7 +2897,7 @@ static int nau8825_i2c_probe(struct i2c_client *i2c) nau8825->dev = dev; nau8825->irq = i2c->irq; /* Initiate parameters, semaphore and work queue which are needed in - * cross talk suppression measurment function. + * cross talk suppression measurement function. */ nau8825->xtalk_state = NAU8825_XTALK_DONE; nau8825->xtalk_protect = false; From 31c88350b7dd1522792f726f79607f31bb55c50f Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Thu, 24 Sep 2026 04:27:27 +0000 Subject: [PATCH 1298/1417] cgroup/cpuset: Return PERR_NOCPUS in remote_partition_enable() on subpartitions_cpus conflict When a remote partition is created underneath an existing local partition via a non-partition (PRS_MEMBER) intermediate cgroup, update_prstate() sees parent->partition_root_state == PRS_MEMBER and calls remote_partition_enable(). Commit 86888c7bd117 ("cgroup/cpuset: Add warnings to catch inconsistency in exclusive CPUs") replaced the cpumask_intersects(tmp->new_cpus, subpartitions_cpus) error check in remote_partition_enable() with WARN_ON_ONCE(). As a result, remote_partition_enable() emits a warning and proceeds to enable the remote partition on CPUs that are already owned by the ancestor local partition in subpartitions_cpus. This can be reproduced on Linux 7.3.0-rc3 with: mkdir -p /tmp/cg1 mount -t cgroup2 none /tmp/cg1 echo "+cpuset" > /tmp/cg1/cgroup.subtree_control mkdir /tmp/cg1/A echo 1 > /tmp/cg1/A/cpuset.cpus echo 1 > /tmp/cg1/A/cpuset.cpus.exclusive echo root > /tmp/cg1/A/cpuset.cpus.partition echo "+cpuset" > /tmp/cg1/A/cgroup.subtree_control mkdir /tmp/cg1/A/B echo 1 > /tmp/cg1/A/B/cpuset.cpus echo 1 > /tmp/cg1/A/B/cpuset.cpus.exclusive echo "+cpuset" > /tmp/cg1/A/B/cgroup.subtree_control mkdir /tmp/cg1/A/B/D echo 1 > /tmp/cg1/A/B/D/cpuset.cpus echo 1 > /tmp/cg1/A/B/D/cpuset.cpus.exclusive echo root > /tmp/cg1/A/B/D/cpuset.cpus.partition which triggers: WARNING: kernel/cgroup/cpuset.c:1594 at remote_partition_enable+0x1c1/0x300 and leaves both /tmp/cg1/A and /tmp/cg1/A/B/D as active root partitions claiming exclusive CPU 1. Fix this by returning PERR_NOCPUS when tmp->new_cpus intersects subpartitions_cpus in remote_partition_enable(), matching the error code used by remote_cpus_update() for the same subpartitions_cpus conflict, and add a regression test case to tools/testing/selftests/cgroup/test_cpuset_prs.sh. Tested in QEMU on Linux 7.3.0-rc3 using the reproducer above and tools/testing/selftests/cgroup/test_cpuset_prs.sh. Fixes: 86888c7bd117 ("cgroup/cpuset: Add warnings to catch inconsistency in exclusive CPUs") Suggested-by: Guopeng Zhang Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng Reviewed-by: Waiman Long Signed-off-by: Tejun Heo --- kernel/cgroup/cpuset.c | 3 ++- tools/testing/selftests/cgroup/test_cpuset_prs.sh | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 8f24171b6055dc..1fcec89a28b9ad 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -1591,10 +1591,11 @@ static int remote_partition_enable(struct cpuset *cs, int new_prs, * above it or remote partition root underneath it is not allowed. */ compute_excpus(cs, tmp->new_cpus); - WARN_ON_ONCE(cpumask_intersects(tmp->new_cpus, subpartitions_cpus)); if (!cpumask_intersects(tmp->new_cpus, cpu_active_mask) || cpumask_subset(top_cpuset.effective_cpus, tmp->new_cpus)) return PERR_INVCPUS; + if (cpumask_intersects(tmp->new_cpus, subpartitions_cpus)) + return PERR_NOCPUS; if (((new_prs == PRS_ISOLATED) && !isolated_cpus_can_update(tmp->new_cpus, NULL)) || prstate_housekeeping_conflict(new_prs, tmp->new_cpus)) diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh index 131d8b4551ef37..7efd5e645767c6 100755 --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh @@ -298,6 +298,8 @@ TEST_MATRIX=( " C0-4:X2-4 C1-4:X2-4:P2 C2-4:X4:P1 \ . . . X1 . 0 A1:0-1|A2:2-4|A3:2-4 \ A1:P0|A2:P2|A3:P-1 2-4" + " CX1-3:P1 CX1-3 CX1-3 . . . P1 . 0 A1:1-3|A2:1-3|A3:1-3 \ + A1:P1|A2:P0|A3:P-1" # Remote partition offline tests " C0-3 C1-3 C2-3 . X2-3 X2-3 X2-3:P2:O2=0 . 0 A1:0-1|A2:1|A3:3 A1:P0|A3:P2 2-3" From 94480606a677deb68d5622cf0ded88626514b3f2 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 26 Sep 2026 09:23:02 -1000 Subject: [PATCH 1299/1417] sched_ext: Add a size argument to scx_bpf_cid_topo() so struct scx_cid_topo can grow scx_bpf_cid_topo() copies struct scx_cid_topo into a buffer the BPF program sized from its own vmlinux.h while the verifier sizes the write from the running kernel's BTF. The struct may grow and each growth then breaks every scheduler built against the older layout, rejected at load or written past its buffer. This is the usual hole for a struct handed to BPF, closed elsewhere with a size argument, and it was missed here. Take the buffer size, copy the smaller of it and the kernel's struct and set the rest to -1. Accesses to the copy are CO-RE relocated, so the struct can grow by appending fields, which its comment now states. The kfunc changes in place: the cid interface is still being finalized and no released scheduler uses the current form. Fixes: e9b55af47edf ("sched_ext: Add topological CPU IDs (cids)") Cc: stable@vger.kernel.org # v7.2+ Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/cid.c | 26 +++++++++++++++--------- kernel/sched/ext/types.h | 4 ++++ tools/sched_ext/include/scx/common.bpf.h | 2 +- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/kernel/sched/ext/cid.c b/kernel/sched/ext/cid.c index 39f88deb94bc4d..d4d5f537832a10 100644 --- a/kernel/sched/ext/cid.c +++ b/kernel/sched/ext/cid.c @@ -912,30 +912,36 @@ bool scx_cmask_empty(const struct scx_cmask *m) /** * scx_bpf_cid_topo - Copy out per-cid topology info * @cid: cid to look up - * @out__uninit: where to copy the topology info; fully written by this call + * @out: where to copy the topology info + * @out__sz: size of @out, the program's sizeof(struct scx_cid_topo) * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * - * Fill @out__uninit with the topology info for @cid. Trigger scx_error() if - * @cid is out of range. If @cid is valid but in the no-topo section, all fields - * are set to -1. All fields are also set to -1 when no cid tables have been - * published yet, which a program may observe while racing the root enable. + * Fill @out with the topology info for @cid. Trigger scx_error() if @cid is out + * of range. If @cid is valid but in the no-topo section, all fields are set to + * -1. All fields are also set to -1 when no cid tables have been published yet, + * which a program may observe while racing the root enable. + * + * The program's struct may be older or newer than the kernel's. The smaller of + * @out__sz and the kernel's size is copied and the rest of @out is set to -1. */ -__bpf_kfunc void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out__uninit, +__bpf_kfunc void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out, size_t out__sz, const struct bpf_prog_aux *aux) { + size_t len = min(out__sz, sizeof(*out)); struct scx_cid_topo *topo; struct scx_sched *sch; + /* the error cases and fields the kernel lacks read as -1 */ + memset(out, 0xff, out__sz); + guard(rcu)(); sch = scx_prog_sched(aux); topo = rcu_dereference(scx_cid_topo); - if (unlikely(!sch) || !cid_valid(sch, cid) || unlikely(!topo)) { - *out__uninit = SCX_CID_TOPO_NEG; + if (unlikely(!sch) || !cid_valid(sch, cid) || unlikely(!topo)) return; - } - *out__uninit = topo[cid]; + memcpy(out, &topo[cid], len); } __bpf_kfunc_end_defs(); diff --git a/kernel/sched/ext/types.h b/kernel/sched/ext/types.h index 943d8d429a2c9a..139176cf9fc6ee 100644 --- a/kernel/sched/ext/types.h +++ b/kernel/sched/ext/types.h @@ -70,6 +70,10 @@ enum scx_consts { * smaller shards if the LLC exceeds the target size. No-topo cids are packed * into their own max-sized shards. * + * New fields are appended, never inserted: scx_bpf_cid_topo() copies this + * struct out sized by the program's own layout, and an older program's copy + * must stay a prefix of the kernel's. + * * @core_cid: first cid of this cid's core (smt-sibling group) * @core_idx: global index of that core, in [0, nr_cores_at_init) * @llc_cid: first cid of this cid's LLC diff --git a/tools/sched_ext/include/scx/common.bpf.h b/tools/sched_ext/include/scx/common.bpf.h index 2ddb01a059fda1..22f24ebef8a9ae 100644 --- a/tools/sched_ext/include/scx/common.bpf.h +++ b/tools/sched_ext/include/scx/common.bpf.h @@ -106,7 +106,7 @@ u64 scx_bpf_now(void) __ksym __weak; void scx_bpf_events(struct scx_event_stats *events, size_t events__sz) __ksym __weak; s32 scx_bpf_cpu_to_cid(s32 cpu) __ksym __weak; s32 scx_bpf_cid_to_cpu(s32 cid) __ksym __weak; -void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out) __ksym __weak; +void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out, size_t out__sz) __ksym __weak; void scx_bpf_kick_cid(s32 cid, u64 flags) __ksym __weak; s32 scx_bpf_task_cid(const struct task_struct *p) __ksym __weak; s32 scx_bpf_this_cid(void) __ksym __weak; From db6365ced4d5855e321f772b240c0e473bcfcdd5 Mon Sep 17 00:00:00 2001 From: Pavankumar Kondeti Date: Fri, 25 Sep 2026 15:25:12 +0530 Subject: [PATCH 1300/1417] workqueue: Fix NULL current_pwq deref in flush dependency check check_flush_dependency() uses current_wq_worker() to determine whether the caller is a workqueue worker and then dereferences worker->current_pwq to test whether the current workqueue is WQ_MEM_RECLAIM. current_wq_worker() only means that %current has PF_WQ_WORKER set. A kworker can reach check_flush_dependency() while it is not executing a work item. One such path is worker_thread() acting as the pool manager, where create_worker() does GFP_KERNEL allocation and the allocation path invokes the OOM notifier. In that state worker->current_pwq is NULL because current_pwq is set only by process_one_work() and cleared again after the work function returns. [ 416.760634][ T375] Call trace: [ 416.760638][ T375] check_flush_dependency+0x80/0x120 (P) [ 416.760648][ T375] __flush_work+0x98/0x224 [ 416.760657][ T375] flush_work+0x30/0x44 [ 416.760665][ T375] ... [ 416.760710][ T375] blocking_notifier_call_chain+0x58/0xa0 [ 416.760719][ T375] out_of_memory+0xb4/0x458 [ 416.760730][ T375] __alloc_pages_may_oom+0x11c/0x1a8 [ 416.760739][ T375] __alloc_pages_slowpath+0x314/0x46c [ 416.760746][ T375] __alloc_frozen_pages_noprof+0x110/0x1a4 [ 416.760753][ T375] new_slab+0x12c/0x484 [ 416.760759][ T375] ___slab_alloc+0x7a8/0xc7c [ 416.760765][ T375] __slab_alloc+0x74/0xd8 [ 416.760772][ T375] __kmalloc_cache_node_noprof+0x2ac/0x304 [ 416.760779][ T375] alloc_worker+0x28/0x60 [ 416.760785][ T375] create_worker+0x4c/0x20c [ 416.760790][ T375] worker_thread+0xe8/0x2b8 [ 416.760796][ T375] kthread+0x1a8/0x200 [ 416.760805][ T375] ret_from_fork+0x10/0x20 Guard the WQ_MEM_RECLAIM-worker warning with worker->current_pwq. If the kworker is not currently executing a work item, there is no current workqueue to diagnose with that warning. The PF_MEMALLOC warning is left unchanged so explicit reclaim context flushing a !WQ_MEM_RECLAIM target is still reported. Fixes: fca839c00a12 ("workqueue: warn if memory reclaim tries to flush !WQ_MEM_RECLAIM workqueue") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Pavankumar Kondeti Signed-off-by: Tejun Heo --- kernel/workqueue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index b8bec1689b7a9f..4a73d305d88adf 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -3918,7 +3918,7 @@ static void check_flush_dependency(struct workqueue_struct *target_wq, WARN_ONCE(current->flags & PF_MEMALLOC, "workqueue: PF_MEMALLOC task %d(%s) is flushing !WQ_MEM_RECLAIM %s:%ps", current->pid, current->comm, target_wq->name, target_func); - WARN_ONCE(worker && ((worker->current_pwq->wq->flags & + WARN_ONCE(worker && worker->current_pwq && ((worker->current_pwq->wq->flags & (WQ_MEM_RECLAIM | __WQ_LEGACY)) == WQ_MEM_RECLAIM), "workqueue: WQ_MEM_RECLAIM %s:%ps is flushing !WQ_MEM_RECLAIM %s:%ps", worker->current_pwq->wq->name, worker->current_func, From 72d3fcf802c45d00b300f25b848a93c3a2bd7c7e Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 27 Sep 2026 13:55:01 -0700 Subject: [PATCH 1301/1417] Linux 7.3-rc5 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 751a08643bf85c..6b8b812f23b928 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc4 +EXTRAVERSION = -rc5 NAME = Baby Opossum Posse # *DOCUMENTATION* From a5af82f2077dd1af7cfebd718eb5c77902f032c4 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 12 Sep 2026 14:49:45 +0200 Subject: [PATCH 1302/1417] ALSA: seq: Mark OSS sequencer emulation as deprecated OSS sequencer API emulation has been implemented as a client of ALSA sequencer, decades ago, at the time where OSS was still use. But the API itself became very niche, and almost noone using it for a real purpose any longer -- only exceptions are AI and fuzzers, who have been causing significant maintenance burden for issues they hit & found. Meanwhile, the most features of OSS sequencer API emulation can be achieved in user-space, either by ALSA-OSS layer (aoss) or oss-seq-emu program: https://github.com/alsa-project/alsa-oss https://github.com/tiwai/oss-seq-emu where the former is LD_PRELOAD wrapper and the latter is a CUSE-based daemon. So we can drop the stuff from the kernel sooner or later. Now, let's mark the OSS sequencer emulation as deprecated and give a warning when loaded, so that we can drop it later actually. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260912124951.416761-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/Kconfig | 7 +++++-- sound/core/seq/oss/seq_oss.c | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig index e4f58cb985d47c..0d606159103e71 100644 --- a/sound/core/seq/Kconfig +++ b/sound/core/seq/Kconfig @@ -27,14 +27,17 @@ config SND_SEQ_DUMMY will be called snd-seq-dummy. config SND_SEQUENCER_OSS - tristate "OSS Sequencer API" + tristate "OSS Sequencer API (deprecated)" depends on SND_OSSEMUL select SND_SEQ_MIDI_EVENT help Say Y here to enable OSS sequencer emulation (both /dev/sequencer and /dev/music interfaces). - Many programs still use the OSS API, so say Y. + The OSS sequencer API is almost nowhere used, hence this option will + be dropped soon. + Use user-space wrapper (alsa-oss) or CUSE daemon (oss-seq-emu) + instead of the kernel-side emulation. To compile this driver as a module, choose M here: the module will be called snd-seq-oss. diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c index 2835576040ed4f..64a7e62029ccb8 100644 --- a/sound/core/seq/oss/seq_oss.c +++ b/sound/core/seq/oss/seq_oss.c @@ -67,6 +67,9 @@ static int __init alsa_seq_oss_init(void) { int rc; + pr_warn("seq-oss: kernel OSS sequencer emulation is deprecated\n"); + pr_warn("seq-oss: Use user-space solution (alsa-oss or oss-seq-emu) instead.\n"); + rc = register_device(); if (rc < 0) goto error; From e0cb0570d0ebce6f452ab492e8743f6f7eb2558e Mon Sep 17 00:00:00 2001 From: Jianyu Zhou <997536907@qq.com> Date: Thu, 17 Sep 2026 14:58:29 +0800 Subject: [PATCH 1303/1417] ALSA: hda/realtek: Add quirk for IPASON SmartBook S1 From 2adfedabfdd9f5ec800462b1f1c0859a198e63aa Mon Sep 17 00:00:00 2001 From: Jianyu Zhou <997536907@qq.com> Date: Thu, 17 Sep 2026 14:34:46 +0800 Subject: [PATCH v2] ALSA: hda/realtek: Add quirk for IPASON SmartBook S1 The internal speaker of the IPASON SmartBook S1 (an Emdoor ODM laptop) stays silent under Linux, while the Bluetooth audio and the headset output work fine. The same machine plays sound under Windows with the vendor driver, so the hardware and the amplifier are known good. The BIOS reports the pin default of the actual internal speaker pin (NID 0x1b) as 0x411111f0, i.e. the default association 0xf, which means "unconnected". The pin parser therefore drops that node already at the pin-configuration time, hence no mixer control is ever created for it and the pin is never driven, no matter how the output pin 0x14 is set up. Enabling 0x1b makes the speaker work immediately. The existing ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO quirk makes the speaker work, too, but it additionally rewrites the pins 0x14, 0x19 and 0x1a with Honor-specific values, which creates a bogus "Dock Mic" device that does not exist on this hardware. Add a minimal fixup that only enables 0x1b as the internal speaker. Touching 0x14 is not needed: with the pin configuration overridden at runtime, both keeping its BIOS value (0x90170110) and disabling it (0x411111f0) leave the speaker working, so 0x1b alone decides the output. Codec: Realtek ALC256 (0x10ec0256), SSID 0x2782:0x0206 BIOS pincfg: 0x14 0x90170110 0x18 0x411111f0 0x19 0x02a19040 0x1a 0x411111f0 0x1b 0x411111f0 0x1d 0x4067b945 0x1e 0x411111f0 0x21 0x02214020 Tested on 7.2.0-1-MANJARO and 6.18.45-1-MANJARO, with both the SOF and the legacy HDA driver paths. Signed-off-by: Jianyu Zhou <997536907@qq.com> Link: https://patch.msgid.link/tencent_3AB9214FB134949B31E9C471BC3669117F05@qq.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 5127a111c59cd7..fdca7e5db3a99d 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4384,6 +4384,7 @@ enum { ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, ALC285_LENOVO_DAC_RENAME, ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, + ALC256_FIXUP_IPASON_SMARTBOOK_S1, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -7136,6 +7137,13 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_TXNW2781_I2C, }, + [ALC256_FIXUP_IPASON_SMARTBOOK_S1] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x90170110 }, /* the real internal speaker */ + { } + }, + }, }; static const struct hda_quirk alc269_fixup_tbl[] = { @@ -8354,6 +8362,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), SND_PCI_QUIRK(0x2145, 0x0001, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x2782, 0x0206, "IPASON SmartBook S1", ALC256_FIXUP_IPASON_SMARTBOOK_S1), SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), From ae0f12706433995336784a0966d42d2f7f7b5dac Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Fri, 18 Sep 2026 19:03:01 +0800 Subject: [PATCH 1304/1417] ALSA: hda/realtek: Add quirk for Acer Nitro ANV16-41 The Acer Nitro ANV16-41 requires the ALC2XX_FIXUP_HEADSET_MIC quirk to make the headset microphone work properly. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221953 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260918110301.664172-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index fdca7e5db3a99d..30972cbe5f03cf 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7204,6 +7204,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x172c, "Acer Nitro ANV16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1731, "Acer Predator PHN16-72", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), From 3109ede2c311a822277c8a7bf9c253f7c65fcbea Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:16:10 +0200 Subject: [PATCH 1305/1417] ALSA: hda: Fix struct hdac_bus documentation Spelling fix so the description makes sense. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260918111610.3589324-1-cezary.rojewski@intel.com Signed-off-by: Takashi Iwai --- sound/hda/core/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/core/bus.c b/sound/hda/core/bus.c index 81498f1e413e2a..20fe1c4a2977e1 100644 --- a/sound/hda/core/bus.c +++ b/sound/hda/core/bus.c @@ -21,7 +21,7 @@ static const struct hdac_bus_ops default_ops = { }; /** - * snd_hdac_bus_init - initialize a HD-audio bas bus + * snd_hdac_bus_init - initialize a HD-audio bus device * @bus: the pointer to bus object * @dev: device pointer * @ops: bus verb operators From fc6b1d74fff198568251e48f77a0db6b888f2695 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 18 Sep 2026 22:15:20 +0800 Subject: [PATCH 1306/1417] ALSA/ASoC: use assign_bit() where applicable Convert open-coded if/else with set_bit/clear_bit and their non-atomic __set_bit/__clear_bit variants to the assign_bit/__assign_bit API. Done with Coccinelle semantic patch: // set_bit -> clear_bit => assign_bit @@ expression cond, bit, addr; @@ -if (cond) - set_bit(bit, addr); -else - clear_bit(bit, addr); +assign_bit(bit, addr, cond); // clear_bit -> set_bit => assign_bit @@ expression cond, bit, addr; @@ -if (cond) - clear_bit(bit, addr); -else - set_bit(bit, addr); +assign_bit(bit, addr, !cond); // __set_bit -> __clear_bit => __assign_bit @@ expression cond, bit, addr; @@ -if (cond) - __set_bit(bit, addr); -else - __clear_bit(bit, addr); +__assign_bit(bit, addr, cond); // __clear_bit -> __set_bit => __assign_bit @@ expression cond, bit, addr; @@ -if (cond) - __clear_bit(bit, addr); -else - __set_bit(bit, addr); +__assign_bit(bit, addr, !cond); Signed-off-by: Peng Fan Acked-by: Mark Brown Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260918141532.3022761-1-peng.fan@oss.nxp.com --- sound/drivers/serial-generic.c | 10 ++-------- sound/hda/core/component.c | 5 +---- sound/hda/core/controller.c | 5 +---- sound/soc/codecs/tas675x.c | 10 ++-------- sound/usb/midi.c | 5 +---- 5 files changed, 7 insertions(+), 28 deletions(-) diff --git a/sound/drivers/serial-generic.c b/sound/drivers/serial-generic.c index 766206c6ca75a8..8ba5e35af4a154 100644 --- a/sound/drivers/serial-generic.c +++ b/sound/drivers/serial-generic.c @@ -183,10 +183,7 @@ static void snd_serial_generic_input_trigger(struct snd_rawmidi_substream *subst { struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; - if (up) - set_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode); - else - clear_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode); + assign_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode, up); } static int snd_serial_generic_output_open(struct snd_rawmidi_substream *substream) @@ -230,10 +227,7 @@ static void snd_serial_generic_output_trigger(struct snd_rawmidi_substream *subs { struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; - if (up) - set_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode); - else - clear_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode); + assign_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode, up); if (up) snd_serial_generic_tx_wakeup(drvdata); diff --git a/sound/hda/core/component.c b/sound/hda/core/component.c index 04755903880e6b..83b31186b02765 100644 --- a/sound/hda/core/component.c +++ b/sound/hda/core/component.c @@ -70,10 +70,7 @@ void snd_hdac_display_power(struct hdac_bus *bus, unsigned int idx, bool enable) dev_dbg(bus->dev, "display power %s\n", str_enable_disable(enable)); guard(mutex)(&bus->lock); - if (enable) - set_bit(idx, &bus->display_power_status); - else - clear_bit(idx, &bus->display_power_status); + assign_bit(idx, &bus->display_power_status, enable); if (!acomp || !acomp->ops) return; diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c index 78855ac357c607..e8d40c24ed00ba 100644 --- a/sound/hda/core/controller.c +++ b/sound/hda/core/controller.c @@ -781,9 +781,6 @@ EXPORT_SYMBOL_GPL(snd_hdac_bus_free_stream_pages); */ void snd_hdac_bus_link_power(struct hdac_device *codec, bool enable) { - if (enable) - set_bit(codec->addr, &codec->bus->codec_powered); - else - clear_bit(codec->addr, &codec->bus->codec_powered); + assign_bit(codec->addr, &codec->bus->codec_powered, enable); } EXPORT_SYMBOL_GPL(snd_hdac_bus_link_power); diff --git a/sound/soc/codecs/tas675x.c b/sound/soc/codecs/tas675x.c index 404706b6215675..82eeaceb1dacf0 100644 --- a/sound/soc/codecs/tas675x.c +++ b/sound/soc/codecs/tas675x.c @@ -1379,10 +1379,7 @@ static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction) int ret; if (direction == SNDRV_PCM_STREAM_CAPTURE) { - if (mute) - clear_bit(dai->id, &tas->active_capture_dais); - else - set_bit(dai->id, &tas->active_capture_dais); + assign_bit(dai->id, &tas->active_capture_dais, !mute); return 0; } @@ -1391,10 +1388,7 @@ static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction) * The TAS675x has two playback DAIs (main audio and LLP). * Only transition to SLEEP when ALL are muted. */ - if (mute) - clear_bit(dai->id, &tas->active_playback_dais); - else - set_bit(dai->id, &tas->active_playback_dais); + assign_bit(dai->id, &tas->active_playback_dais, !mute); /* Last playback stream */ if (mute && !READ_ONCE(tas->active_playback_dais)) { diff --git a/sound/usb/midi.c b/sound/usb/midi.c index be2b178d13d619..be29379cfcb918 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -1280,10 +1280,7 @@ static void snd_usbmidi_input_trigger(struct snd_rawmidi_substream *substream, { struct snd_usb_midi *umidi = substream->rmidi->private_data; - if (up) - set_bit(substream->number, &umidi->input_triggered); - else - clear_bit(substream->number, &umidi->input_triggered); + assign_bit(substream->number, &umidi->input_triggered, up); } static const struct snd_rawmidi_ops snd_usbmidi_output_ops = { From 86f86e6fdedc33c5b8d38b9208b350f8accf57ee Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Fri, 18 Sep 2026 15:57:53 -0700 Subject: [PATCH 1307/1417] ALSA: ua101: reject mismatched capture/playback packet sizes detect_usb_format() cross-checks bSubframeSize, bBitResolution and tSamFreq between the capture and playback interfaces, but never relates the two endpoints' wMaxPacketSize and bNrChannels. Each playback URB gets a buffer of ua->playback.max_packet_bytes, while the number of bytes written into it is derived from the capture stream: capture_urb_complete() computes frames from the received capture packet and capture.frame_bytes, and start_usb_playback() and playback_work() multiply that by playback.frame_bytes. A device declaring a large capture wMaxPacketSize with few capture channels and a small playback wMaxPacketSize with many playback channels therefore memset()s and memcpy()s past the end of the playback buffer, in open() of the PCM node the driver registers during probe. usb_submit_urb() rejects the over-long iso_frame_desc[0].length with -EMSGSIZE, but only after the write. Reject such descriptors at probe time. Genuine UA-101/UA-1000 hardware declares proportional packet sizes and is unaffected. BUG: KASAN: slab-out-of-bounds in start_usb_playback (sound/usb/misc/ua101.c:586) Write of size 2048 at addr ffff8881098f3c00 by task exploit/5021 Call Trace: __asan_memset (mm/kasan/shadow.c:84) start_usb_playback (sound/usb/misc/ua101.c:586) playback_pcm_open (sound/usb/misc/ua101.c:679) snd_pcm_open_substream (sound/core/pcm_native.c:2829) snd_pcm_open (sound/core/pcm_native.c:2865 sound/core/pcm_native.c:2932) snd_pcm_playback_open (sound/core/pcm_native.c:2891) snd_open (sound/core/sound.c:166) chrdev_open (fs/char_dev.c:411) do_dentry_open (fs/open.c:996) vfs_open (fs/open.c:1101) path_openat (fs/namei.c:4837 fs/namei.c:5000) do_file_open (fs/namei.c:5029) do_sys_openat2 (fs/open.c:1417) __x64_sys_openat (fs/open.c:1423 fs/open.c:1439 fs/open.c:1434) do_syscall_64 (arch/x86/entry/syscall_64.c:61 arch/x86/entry/syscall_64.c:84) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) The buggy address belongs to the object at ffff8881098f3c00 which belongs to the cache kmalloc-192 of size 192 The buggy address is located 0 bytes inside of allocated 168-byte region [ffff8881098f3c00, ffff8881098f3ca8) Cc: stable@vger.kernel.org Fixes: 63978ab3e3e9 ("sound: add Edirol UA-101 support") Reported-by: Assisted-by: LLM Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260918225753.1278505-1-xmei5@asu.edu Signed-off-by: Takashi Iwai --- sound/usb/misc/ua101.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c index 860a62a3d74ba3..58d8a3cdc8b986 100644 --- a/sound/usb/misc/ua101.c +++ b/sound/usb/misc/ua101.c @@ -1007,6 +1007,15 @@ static int detect_usb_format(struct ua101 *ua) } ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd)); ua->playback.max_packet_bytes = usb_endpoint_maxp(epd); + + if (ua->capture.max_packet_bytes / ua->capture.frame_bytes * + ua->playback.frame_bytes > ua->playback.max_packet_bytes) { + dev_err(&ua->dev->dev, + "playback packet size %u too small for %u capture frames\n", + ua->playback.max_packet_bytes, + ua->capture.max_packet_bytes / ua->capture.frame_bytes); + return -ENXIO; + } return 0; } From 61b0a5a114fa4552892da5c0ff2f42e63bf08c6b Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Fri, 18 Sep 2026 16:40:14 -0700 Subject: [PATCH 1308/1417] ALSA: line6: reject oversized playback packets Each playback URB is handed a slice of out.buffer that is exactly LINE6_ISO_PACKETS * max_packet_size_out bytes, sized from the OUT endpoint, but the number of bytes written into that slice is never compared against it. submit_audio_out_urb() takes the frame count from prev_fsize, which audio_in_callback() derived from the *IN* endpoint's received packet length, and rescales it with the playback frame size; when prev_fsize is still zero it synthesizes a length from the sample rate instead. On a device declaring a large IN wMaxPacketSize and a small OUT wMaxPacketSize, the resulting memcpy(), or the memset() when the playback stream is idle, runs past its slice and, as the reproducer below shows, beyond the allocation. usb_submit_urb() is not a backstop. max_packet_size_out comes from usb_maxpacket(), which returns only the low 11 bits of wMaxPacketSize, while USB core validates a high-speed isochronous length against that base scaled by usb_endpoint_maxp_mult(). A length of up to three times the allocated slice is therefore accepted, and even a rejected URB is only rejected after the write. Reject a packet that does not fit the slice the driver allocated for it. Clamping it instead would silently shorten the capture-derived rate feedback and desynchronize the two streams. BUG: KASAN: slab-out-of-bounds in submit_audio_out_urb (sound/usb/line6/playback.c:229) Write of size 1024 at addr ffff888100bbd400 by task kworker/1:2/5002 Workqueue: events line6_startup_work Call Trace: __asan_memcpy (mm/kasan/shadow.c:106) submit_audio_out_urb (sound/usb/line6/playback.c:229) line6_submit_audio_out_all_urbs (sound/usb/line6/playback.c:291) line6_stream_start (sound/usb/line6/pcm.c:194) line6_pcm_acquire (sound/usb/line6/pcm.c:337) line6_startup_work (sound/usb/line6/driver.c:728) process_one_work (kernel/workqueue.c:3396) worker_thread (kernel/workqueue.c:3479 kernel/workqueue.c:3560) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) The buggy address belongs to the object at ffff888100bbd400 which belongs to the cache kmalloc-256 of size 256 The buggy address is located 0 bytes inside of allocated 256-byte region [ffff888100bbd400, ffff888100bbd500) Cc: stable@vger.kernel.org Fixes: 7a0f55aeeb8f ("ALSA: line6: Support assymetrical in/out configurations") Reported-by: Assisted-by: LLM Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260918234014.1318325-1-xmei5@asu.edu Signed-off-by: Takashi Iwai --- sound/usb/line6/playback.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index 7ebaf125f96920..aa6ddf8746a0ab 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -182,6 +182,13 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) fsize *= bytes_per_frame; + if (fsize > line6pcm->max_packet_size_out) { + dev_err(line6pcm->line6->ifcdev, + "playback packet too large: %d > %d\n", + fsize, line6pcm->max_packet_size_out); + return -EMSGSIZE; + } + fout->offset = urb_size; fout->length = fsize; urb_size += fsize; From f4b506f735e3c24c4fd4d3a60239e0b1b241600e Mon Sep 17 00:00:00 2001 From: Matt Barr Date: Mon, 21 Sep 2026 17:48:12 -0400 Subject: [PATCH 1309/1417] ALSA: hda/realtek: Drop Yoga Pro 9 16IAH10 PCI SSID quirk The PCI SSID quirk added for the Lenovo Yoga Pro 9 16IAH10 (PCI SSID 17aa:3846, codec SSID 17aa:3920) also matches every other machine that shares PCI SSID 17aa:3846. snd_hda_pick_fixup() tries all PCI SSID entries before it falls back to the codec SSID, so those machines now get ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1 and never reach their own codec-SSID quirk. The Lenovo Legion Pro 7 16IRX8H (product 82WQ) is one such machine: PCI SSID 17aa:3846, codec SSID 17aa:3884. Before this quirk it picked ALC287_FIXUP_TAS2781_I2C via the 17aa:3884 codec SSID fallback and bound its TAS2781 amplifier. With it, the codec never binds the amplifier and the internal speakers are silent: snd_hda_codec_alc269 hdaudioC0D0: ALC287: picked fixup for PCI SSID 17aa:3846 versus, on a kernel without the quirk: snd_hda_codec_alc269 hdaudioC0D0: ALC287: picked fixup for codec SSID 17aa:3884 snd_hda_codec_alc269 hdaudioC0D0: bound i2c-TIAS2781:00 (ops tas2781_hda_comp_ops ...) Drop the PCI SSID entry. The Yoga Pro 9 16IAH10 has the same amplifier as the Yoga S990-16 (codec SSID 17aa:3920), so it still gets a fixup through the codec SSID fallback, using the existing 17aa:3920 entry. Switch that entry from ALC287_FIXUP_TXNW2781_I2C to ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, which applies the DAC routing fix and then chains to ALC287_FIXUP_TXNW2781_I2C, so the amplifier setup is unchanged. Fixes: 41d60cbfde10 ("ALSA: hda/realtek: Fix bass speaker DAC routing for Lenovo Yoga Pro 9 16IAH10") Link: https://bugzilla.kernel.org/show_bug.cgi?id=220540 Suggested-by: Zhang Heng Signed-off-by: Matt Barr Link: https://patch.msgid.link/20260921214812.22139-1-matthewjaybarr@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 30972cbe5f03cf..b4164db7ecbca6 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8173,7 +8173,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), - SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; * use codec SSID to distinguish them */ @@ -8267,7 +8266,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), SND_PCI_QUIRK(0x17aa, 0x391f, "Yoga S990-16 pro Quad YC Quad", ALC287_FIXUP_TXNW2781_I2C), - SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), From d24b03248ac6f02fb87feff6beec0b5b1da5dafd Mon Sep 17 00:00:00 2001 From: Achilles Zhang Date: Mon, 21 Sep 2026 10:43:43 +0800 Subject: [PATCH 1310/1417] ALSA: usb-audio: Add native DSD quirk for HiBy FC4 The HiBy FC4 with USB ID 32bb:0004 advertises its native DSD stream as UAC2 RAW_DATA with a 4-byte, 32-bit subslot. Without a quirk snd-usb-audio leaves this alternate setting as SNDRV_PCM_FORMAT_SPECIAL, so userspace cannot select a native DSD format. Mark the device with QUIRK_FLAG_DSD_RAW so the existing generic raw DSD handling exposes the stream as DSD_U32_BE. Tested on an FC4 at DSD256. With the quirk the playback alternate setting changes from SPECIAL to DSD_U32_BE and reports DOP=0, bitrev=0; native DSD256 playback works correctly. Signed-off-by: Achilles Zhang Link: https://patch.msgid.link/20260921024344.391912-1-bnqzzdf@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index c5870901a8d4a1..924d930a9f0ef6 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2542,6 +2542,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x3255, 0x0000, /* Luxman D-10X */ QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x32bb, 0x0004, /* HiBy FC4 */ + QUIRK_FLAG_DSD_RAW), DEVICE_FLG(0x3302, 0x17c2, /* TTGK Technology USB-C Audio */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x339b, 0x3a07, /* Synaptics HONOR USB-C HEADSET */ From 786fb6b3f513937f712db5a9ba6a12cbc6982ba9 Mon Sep 17 00:00:00 2001 From: Hernan Ganzo Date: Mon, 21 Sep 2026 21:55:38 +0100 Subject: [PATCH 1311/1417] ALSA: hda/realtek: Limit internal mic boost on HP Pavilion 15 (103c:2164) The HP Pavilion 15 with the ALC3227 codec (SSID 103c:2164) exposes the full four-step internal mic boost on pin 0x12: nsteps=3 with stepsize=0x2f, that is 0, 12, 24 and 36 dB. Combined with the ADC capture gain of +30 dB the analogue cascade reaches 66 dB, and the internal microphone clips on the ambient noise of a quiet room; the upper two steps are unusable at any useful input level. This is the defect class ALC269_FIXUP_LIMIT_INT_MIC_BOOST exists for. The machine matches no SSID quirk and falls through to the pin table, which selects ALC269_FIXUP_HP_MUTE_LED_MIC1 through a pin signature shared with several other HP models. Add an SSID entry instead, using ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED: the existing fixup that applies alc269_fixup_limit_int_mic_boost() and chains to the same mute LED fixup, as already done for 103c:218b. The boost becomes 0 or 12 dB and the LED handling is unchanged. Tested on the affected machine by selecting the same fixup through the model string (snd-hda-intel.model), which bypasses the SSID and pin tables, with the driver confirming the selection: snd_hda_codec_alc269 hdaudioC1D0: ALC3227: picked fixup limit-mic-boost (model specified) With that, Internal Mic Boost reports Limits 0 - 1 (0 or 12 dB) instead of 0 - 3 (0 to 36 dB), so alc269_fixup_limit_int_mic_boost() clamps this codec's internal mic pin as intended. Note that /proc/asound/card1/codec#0 keeps printing the unclamped hardware caps (nsteps=0x03): that file reads codec parameters uncached by design, while the fixup overrides the driver's cached parameter that the control is built from. This does not by itself make the default capture volume sane -- the remaining 12 dB on top of the +30 dB ADC gain is still hotter than this machine's usable operating point. It removes the two unusable steps from the hardware range, which is what a quirk of this class can do. Signed-off-by: Hernan Ganzo Link: https://patch.msgid.link/20260921205538.41192-1-gp@sneemgp.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b4164db7ecbca6..b94b0e29bda571 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7312,6 +7312,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2), SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x2164, "HP Pavilion 15 Notebook PC", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x21f9, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), From 775aa96c310d59f3d15a6e5a9bd1796fd1ba95ec Mon Sep 17 00:00:00 2001 From: Luis Banha Date: Tue, 22 Sep 2026 00:01:05 +0100 Subject: [PATCH 1312/1417] ALSA: hda/realtek: Add mute LED quirk for HP Pavilion 15-ec1xxx The mute LED on the HP Pavilion Gaming Laptop 15-ec1xxx (103c:87b2) does not work automatically. Add the ALC285_FIXUP_HP_MUTE_LED quirk to fix this behavior. Signed-off-by: Luis Banha Link: https://patch.msgid.link/20260921230105.16844-1-luisbanha26@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b94b0e29bda571..8c29688d8e4125 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7433,6 +7433,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8786, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8787, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8788, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x87b2, "HP Pavilion Gaming Laptop 15-ec1xxx", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x87b7, "HP Laptop 14-fq0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x87c8, "HP", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x87cb, "HP Pavilion 15-eg0xxx", ALC287_FIXUP_HP_GPIO_LED), From 3867e38b2d4b844d89076f988c7b9741c87a9858 Mon Sep 17 00:00:00 2001 From: KIM MinWoo Date: Wed, 23 Sep 2026 10:37:56 +0900 Subject: [PATCH 1313/1417] ALSA: hda/realtek: Add quirk for Lenovo ThinkBook 16p Gen 3 ARH (21EK) The ThinkBook 16p Gen 3 ARH (SSID 17aa:3871) has a TI TAS2563 amplifier device on I2C, exposed by ACPI as INT8866, the same arrangement as the Yoga 7 14ARB7 (SSID 17aa:3870). Without a quirk, the codec falls back to the vendor-wide Lenovo quirk (ALC269_FIXUP_LENOVO_XPAD_ACPI), the amplifier is never bound, and the internal speakers stay silent while the headphone output works. Reuse the Yoga 7 14ARB7 fixup, which binds the INT8866 device as a side codec. ALC287_FIXUP_TAS2781_I2C does not work on this machine because it looks for a TIAS2781 device. Assisted-by: LLM Signed-off-by: KIM MinWoo Link: https://patch.msgid.link/20260923013756.106033-1-phrimm136@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 8c29688d8e4125..de478d01430e7a 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8199,6 +8199,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3871, "Lenovo ThinkBook 16p Gen 3 ARH", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3877, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3878, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), From d49f8d9680ee3f7f4e0339467cf0a244158c1adb Mon Sep 17 00:00:00 2001 From: Roman Bolotov Date: Wed, 23 Sep 2026 09:45:27 +0300 Subject: [PATCH 1314/1417] ALSA: usb-audio: Add quirk flag for ASUS SupremeFX Hi-Fi The ASUS SupremeFX Hi-Fi (0b05:1826, 0b05:1827) does not survive USB autosuspend. After a suspend/resume cycle the firmware degrades: HID probes begin to fail and the descriptors it returns become corrupted, until the device stops responding entirely and needs a physical power cycle to recover. Add QUIRK_FLAG_DISABLE_AUTOSUSPEND for both product IDs. Verified on 7.2.6 through the quirk_flags module parameter, with no code change, and with the local udev rules that had been forcing power/control commented out, so the parameter was the only mechanism in play. Both IDs were passed as 0b05:1827:disable_autosuspend;0b05:1826:disable_autosuspend The device then stayed awake across 2.5 hours (runtime_suspended_time remained 0), survived a system suspend/resume cycle, and continued to work afterwards with no failed probes and no power cycle needed. Signed-off-by: Roman Bolotov Link: https://patch.msgid.link/20260923064527.434441-1-hadros@ikeepitoblique.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 924d930a9f0ef6..294c7026b93c1e 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2366,6 +2366,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x0a73, 0x003a, /* Mackie DLZ Creator XS */ QUIRK_FLAG_ALWAYS_SET_RATE), + DEVICE_FLG(0x0b05, 0x1826, /* ASUS SupremeFX Hi-Fi */ + QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x0b05, 0x1827, /* ASUS SupremeFX Hi-Fi */ + QUIRK_FLAG_DISABLE_AUTOSUSPEND), DEVICE_FLG(0x0b05, 0x18a6, /* ASUSTek Computer, Inc. */ QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), DEVICE_FLG(0x0b0e, 0x0349, /* Jabra 550a */ From a01a223bd685be42e908d970de32eeb3646097f3 Mon Sep 17 00:00:00 2001 From: Tarun Noonemunthala Date: Wed, 23 Sep 2026 15:10:14 +0530 Subject: [PATCH 1315/1417] ALSA: hda/realtek: Add quirk for HP ENVY 13-aq1xxx The HP ENVY Laptop 13-aq1xxx (PCI SSID 103c:86ad) uses an ALC285 codec with two speaker pairs: front (pin 0x14) and bottom (pin 0x17). The BIOS leaves pin 0x14 as [N/A] ("not connected"), so the generic parser never enables the front pair and only the bottom speakers are audible. The vendor driver (RTKVHD64.sys) programs that pin with the value 0x90170150 and runs the HP amplifier initialization, which is already implemented in the driver as ALC285_FIXUP_HP_GPIO_AMP_INIT. Add a board fixup that applies the pin configuration for 0x14 and chains to the existing amplifier init fixup. The pin value was recovered from the vendor driver's own configuration data. The patch and this changelog were prepared with an AI coding assistant (see the Assisted-by tag below); the change was reviewed and tested by the submitter on the affected machine. Tested on an HP ENVY 13-aq1xxx by rebuilding snd-hda-codec-alc269 from the Fedora 7.1.10 source and loading it: the codec now reports "line_outs=2 (0x14/0x17)", pin 0x14 comes up as an output (Pin-ctls 0x40) with its amplifier unmuted, and both speaker pairs play. Assisted-by: OpenCode:deepseek-v4.1-flash checkpatch Signed-off-by: Tarun Noonemunthala Link: https://patch.msgid.link/20260923094014.58867-1-ntarun2000@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index de478d01430e7a..004f6a3eadfe56 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4124,6 +4124,7 @@ enum { ALC285_FIXUP_HP_SPECTRE_X360_EB1, ALC285_FIXUP_HP_SPECTRE_X360_DF1, ALC285_FIXUP_HP_ENVY_X360, + ALC285_FIXUP_HP_ENVY_13AQ, ALC288_FIXUP_DELL_HEADSET_MODE, ALC288_FIXUP_DELL1_MIC_NO_PRESENCE, ALC288_FIXUP_DELL_XPS_13, @@ -6401,6 +6402,15 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_HP_GPIO_AMP_INIT, }, + [ALC285_FIXUP_HP_ENVY_13AQ] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x90170150 }, /* front speakers */ + { } + }, + .chained = true, + .chain_id = ALC285_FIXUP_HP_GPIO_AMP_INIT, + }, [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP] = { .type = HDA_FIXUP_FUNC, .v.func = alc285_fixup_ideapad_s740_coef, @@ -7405,6 +7415,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x864f, "HP Laptop 15-dy0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x86ad, "HP ENVY 13-aq1xxx", ALC285_FIXUP_HP_ENVY_13AQ), SND_PCI_QUIRK(0x103c, 0x86c1, "HP Laptop 15-da3001TU", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), SND_PCI_QUIRK(0x103c, 0x86c8, "HP Laptop 14s-dr1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), From cfc0a117d773eb585ca7e87d20c50d1c415058e5 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Wed, 23 Sep 2026 20:16:40 +0800 Subject: [PATCH 1316/1417] ALSA: hda/realtek: Fix square wave output on ASUS Strix G615LR ASUS ROG Strix G16 G615LR (PCI SSID 1043:3f20) outputs a loud full-scale square wave from internal speakers since the TAS2781 amplifier driver started loading. The quirk was using ALC287_FIXUP_TXNW2781_I2C which chains to the ThinkPad headset jack fixup, but this is an ASUS device that should use ALC287_FIXUP_TXNW2781_I2C_ASUS which chains to ALC294_FIXUP_ASUS_SPK for proper EAPD and pin configuration. Fixes: f7cede182c96 ("ALSA: hda/realtek: Add Asus quirk for TAS amplifiers") Cc: stable@vger.kernel.org Cc: Baojun Xu Cc: Antheas Kapenekakis Reported-by: Aleksei Arsenev Link: https://bugzilla.kernel.org/show_bug.cgi?id=222045 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260923121640.272509-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 004f6a3eadfe56..b5cb9fa6e6d4db 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7938,7 +7938,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x3ef0, "ASUS Strix G635LR_LW_LX", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x1043, 0x3f00, "ASUS Strix G815LH_LM_LP", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x1043, 0x3f10, "ASUS Strix G835LR_LW_LX", ALC287_FIXUP_TXNW2781_I2C), - SND_PCI_QUIRK(0x1043, 0x3f20, "ASUS Strix G615LR_LW", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3f20, "ASUS Strix G615LR_LW", ALC287_FIXUP_TXNW2781_I2C_ASUS), SND_PCI_QUIRK(0x1043, 0x3f30, "ASUS Strix G815LR_LW", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x1043, 0x3fd0, "ASUS B3605CVA", ALC245_FIXUP_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x3ff0, "ASUS B5405CVA", ALC245_FIXUP_CS35L41_SPI_2), From 2a8ee6897302ca12ff39e8eb180503956797c60c Mon Sep 17 00:00:00 2001 From: Yu-Hsiang Tseng Date: Wed, 23 Sep 2026 22:13:44 +0800 Subject: [PATCH 1317/1417] ALSA: hda/realtek: Fix silent speaker on Higole F9B The Higole F9B is an Alder Lake-N mini PC with a 7" touchscreen and an ALC269VC codec (subsystem ID 0x10ec111e). Its DMI strings are all "Default string", so the SSID is the only way to identify it. The internal speaker is silent out of the box. The generic parser assigns DAC 0x03 to the speaker pin 0x14 and DAC 0x02 to the headphone pin 0x15, but the speaker only reproduces what DAC 0x02 plays, whatever its connection selection says. Checked one path at a time, only the gain of DAC 0x02 and the mute of pin 0x14 change what comes out of the speaker. User space only knows the path it was told the speaker uses, so selecting the speaker turns the path that actually reaches it all the way down. Route both 0x14 and 0x15 through mixer 0x0c (DAC 0x02), which is what ALC290_FIXUP_MONO_SPEAKERS already does. Tested on the device: the speaker plays. Cc: Assisted-by: LLM Signed-off-by: Yu-Hsiang Tseng Link: https://patch.msgid.link/20260923141344.1543158-1-asas1asas200@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b5cb9fa6e6d4db..502844dc4e2627 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7961,6 +7961,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x10cf, 0x1757, "Lifebook E752", ALC269_FIXUP_LIFEBOOK_HP_PIN), SND_PCI_QUIRK(0x10cf, 0x1845, "Lifebook U904", ALC269_FIXUP_LIFEBOOK_EXTMIC), SND_PCI_QUIRK(0x10ec, 0x10f2, "Intel Reference board", ALC700_FIXUP_INTEL_REFERENCE), + SND_PCI_QUIRK(0x10ec, 0x111e, "Higole F9B", ALC290_FIXUP_MONO_SPEAKERS), SND_PCI_QUIRK(0x10ec, 0x118c, "Medion EE4254 MD62100", ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE), SND_PCI_QUIRK(0x10ec, 0x119e, "Positivo SU C1400", ALC269_FIXUP_ASPIRE_HEADSET_MIC), SND_PCI_QUIRK(0x10ec, 0x11bc, "VAIO VJFE-IL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), From 7b457ae8cf711831f19d20963f5ed2fdf4842d3a Mon Sep 17 00:00:00 2001 From: Tomasz Bojanowski Date: Wed, 23 Sep 2026 20:49:57 +0200 Subject: [PATCH 1318/1417] ALSA: usb-audio: Add mixer mapping for MSI MAG B850M MORTAR WIFI The USB audio device 0db0:cc78 (Realtek ALC4080) on the MSI MAG B850M MORTAR WIFI motherboard exposes all playback controls as "PCM", which makes it hard to tell the outputs apart. Its mixer layout matches the one of the MSI MPG X570S Carbon Max Wifi (units 29, 30 and 32), so reuse msi_mpg_x570s_carbon_max_wifi_alc4080_map for this device too. With the mapping applied, the controls show up as "Speaker", "Front Headphone" and "IEC958". Tested with a patched snd-usb-audio module on a 7.2.7 kernel. Signed-off-by: Tomasz Bojanowski Link: https://patch.msgid.link/20260923184957.6687-1-tomasz@bojanowski.me Signed-off-by: Takashi Iwai --- sound/usb/mixer_maps.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 41454cb403d04a..f8e1f78039e11c 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -749,6 +749,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x0db0, 0xa073), .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, }, + { /* MSI MAG B850M MORTAR WIFI */ + .id = USB_ID(0x0db0, 0xcc78), + .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, + }, { /* MSI TRX40 */ .id = USB_ID(0x0db0, 0x543d), .map = trx40_mobo_map, From 0b7bd20f6379bc5e431fd74d866390f5b5b63f3c Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Sat, 26 Sep 2026 00:07:17 +0700 Subject: [PATCH 1319/1417] ALSA: caiaq: unregister the input device when probe fails setup_card() registers the input device, whose name and phys point into struct snd_usb_caiaqdev, and then goes on to snd_card_register() and snd_usb_caiaq_control_init(). If either fails, snd_probe() calls snd_card_free(), which frees the device state but leaves the input device registered: card_free() only clears the pointer, and the input device is unregistered from snd_disconnect() alone. Reading its "uevent" attribute afterwards dereferences freed memory: BUG: KASAN: slab-use-after-free in string+0x4a9/0x4f0 Read of size 1 at addr ffff8880208f5043 by task caiaq/4967 add_uevent_var+0x183/0x3a0 input_dev_uevent+0x162/0x900 dev_uevent+0x2f1/0x870 uevent_show+0x1ca/0x3a0 ... Unregister the input device on the probe error path, as snd_disconnect() does. snd_usb_caiaq_input_disconnect() is a no-op when no input device was registered. Reproduced with a raw-gadget Audio Kontrol 1 and a temporary hack that makes snd_usb_caiaq_control_init() fail. Fixes: 28abd224db4a ("ALSA: caiaq: Handle probe errors properly") Cc: stable@vger.kernel.org Reported-by: syzbot+2a123f6269da57ffefaa@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=2a123f6269da57ffefaa Signed-off-by: Nguyen Ngoc Thang Link: https://patch.msgid.link/20260925170717.22462-1-ngocthang2710.1999@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/caiaq/device.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index 3d821fde458206..f604846ec6e6f5 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -562,6 +562,9 @@ static int snd_probe(struct usb_interface *intf, ret = init_card(caiaqdev(card)); if (ret < 0) { dev_err(&usb_dev->dev, "unable to init card! (ret=%d)\n", ret); +#ifdef CONFIG_SND_USB_CAIAQ_INPUT + snd_usb_caiaq_input_disconnect(caiaqdev(card)); +#endif snd_card_free(card); return ret; } From ca51771c8efde5df05476ac98dd1b2af4e2b149f Mon Sep 17 00:00:00 2001 From: Zimeng Li Date: Sun, 27 Sep 2026 09:09:11 +0800 Subject: [PATCH 1320/1417] ASoC: qcom: lpass-cpu: use DAI table index for playback constraints The DAI ID is a hardware port ID and is not necessarily the index of the corresponding entry in variant->dai_driver. When configuring a DAI with LPAIF_I2SCTL_MODE_QUAD01, the probe code currently uses dai_id to index variant->dai_driver. This is incorrect for platforms where DAI IDs are sparse. For example, the IPQ806x MI2S DAI has ID 4 while it is the only entry in the DAI driver table. If that DAI is configured for QUAD01 and probe reaches this branch, the code writes beyond that single entry via dai_driver[4] instead of updating the DAI being processed. Use the loop index i when updating the current DAI's playback channel constraints, while retaining dai_id for indexing the hardware-port specific playback SD-line mode array. This fixes the incorrect DAI table access for platforms where the DAI ID does not match its position in the driver table. Fixes: c223f41c1a52 ("ASoC: qcom: Add four speaker support on MI2S secondary") Cc: stable@vger.kernel.org Signed-off-by: Zimeng Li Assisted-by: LLM-assisted source analysis Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260927010911.51980-1-me@lizi.moe Signed-off-by: Mark Brown --- sound/soc/qcom/lpass-cpu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c index 242bc16da36daf..079177a6d81e79 100644 --- a/sound/soc/qcom/lpass-cpu.c +++ b/sound/soc/qcom/lpass-cpu.c @@ -1234,8 +1234,8 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) } if (drvdata->mi2s_playback_sd_mode[dai_id] == LPAIF_I2SCTL_MODE_QUAD01) { - variant->dai_driver[dai_id].playback.channels_min = 4; - variant->dai_driver[dai_id].playback.channels_max = 4; + variant->dai_driver[i].playback.channels_min = 4; + variant->dai_driver[i].playback.channels_max = 4; } } From 89365897f4210979966aa808d7b013edf4080558 Mon Sep 17 00:00:00 2001 From: Aaron Kling Date: Sat, 26 Sep 2026 02:24:19 -0500 Subject: [PATCH 1321/1417] ALSA: hda/tegra: Re-enable SDO workaround for Tegra194 This workaround was originally added for Tegra194, but no longer gets applied for it. Commit 615d43540043 ("ALSA: hda/tegra: fix tegra-hda on tegra30 soc") changed the guard to tegra30-hda, which worked because all affected archs had the fallback compatible. However, commit 7f0ea5acfc19 ("arm64: tegra: Use correct compatible string for Tegra194 HDA") removed the fallback compatible for Tegra194, causing this to break. Fixes: 7f0ea5acfc19 ("arm64: tegra: Use correct compatible string for Tegra194 HDA") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Aaron Kling Link: https://patch.msgid.link/20260926-tegra194-hda-sdo-v1-1-f7a636a07a58@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/controllers/tegra.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/hda/controllers/tegra.c b/sound/hda/controllers/tegra.c index 31c14c4bbe683b..674cc0f79c3c88 100644 --- a/sound/hda/controllers/tegra.c +++ b/sound/hda/controllers/tegra.c @@ -374,7 +374,8 @@ static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev) * in powers of 2, next available ratio is 16 which can be * used as a limiting factor here. */ - if (of_device_is_compatible(np, "nvidia,tegra30-hda")) + if (of_device_is_compatible(np, "nvidia,tegra30-hda") || + of_device_is_compatible(np, "nvidia,tegra194-hda")) chip->bus.core.sdo_limit = 16; /* codec detection */ From 3e71ea6dca1d585692b645aaf3b8cc58f6e5eb35 Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Mon, 28 Sep 2026 04:45:13 +0000 Subject: [PATCH 1322/1417] firmware: cs_dsp: Annotate struct cs_dsp_coeff_ctl with __counted_by_ptr Annotate the 'cache' pointer member of 'struct cs_dsp_coeff_ctl' with the '__counted_by_ptr' attribute. This allows the compiler and KASAN to perform run-time bounds checking on accesses to the 'cache' buffer, preventing potential out-of-bounds reads or writes. The 'cache' pointer points to a buffer of size 'len' bytes, allocated to hold the cached value of a DSP coefficient control. The 'cache' and 'len' are initialized in 'cs_dsp_create_control()'. Every subsequent access to 'ctl->cache' is strictly validated to ensure that it lies within the bounds of 'ctl->len'. Cc: codemender-patching+linux@google.com Assisted-by: LLM Signed-off-by: Bill Wendling Reviewed-by: Richard Fitzgerald Link: https://patch.msgid.link/20260928044513.1330392-1-morbo@google.com Signed-off-by: Mark Brown --- include/linux/firmware/cirrus/cs_dsp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/firmware/cirrus/cs_dsp.h b/include/linux/firmware/cirrus/cs_dsp.h index 4e3baa5570687e..6aa1e1b2b4a57f 100644 --- a/include/linux/firmware/cirrus/cs_dsp.h +++ b/include/linux/firmware/cirrus/cs_dsp.h @@ -96,7 +96,7 @@ struct cs_dsp_alg_region { struct cs_dsp_coeff_ctl { struct list_head list; struct cs_dsp *dsp; - void *cache; + void *cache __counted_by_ptr(len); const char *fw_name; /* Subname is needed to match with firmware */ const char *subname; From c257e3bcb36f4597d6dbb6fbc546e466ef53e385 Mon Sep 17 00:00:00 2001 From: Yibo Tan Date: Wed, 23 Sep 2026 02:50:56 +0800 Subject: [PATCH 1323/1417] ASoC: amd: acp-es8336: Use an owned codec device reference acpi_get_first_physical_node() returns a borrowed device pointer. If the pa-enable GPIO lookup fails, st_es8336_late_probe() puts that pointer despite not owning a reference. A later physical-node teardown can then release the device while device_del() is still using it. This was reproduced on current mainline with the real static late-probe callback and normal platform-device unregister. The GPIO lookup returned -EPROBE_DEFER and KASAN reported a slab-use-after-free in device_del(), with the object freed by acpi_unbind_one(). Use acpi_bus_get_primary_device(), which obtains a stable device reference under the physical-node lock, and release it at callback exit with scoped cleanup. Keep the reference callback-local because late probe can be retried; registering one devres action per attempt would accumulate references. The same KASAN guest with this change reached the same -EPROBE_DEFER and unregister path without KASAN, WARNING, Oops or panic. The test directly invoked the production callback and did not emulate a complete ASoC card or physical AMD hardware. Fixes: 02527c3f2300 ("ASoC: amd: add Machine driver for Jadeite platform") Signed-off-by: Yibo Tan Link: https://patch.msgid.link/20260922185057.954150-2-lhfff@tju.edu.cn Signed-off-by: Mark Brown --- sound/soc/amd/acp-es8336.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sound/soc/amd/acp-es8336.c b/sound/soc/amd/acp-es8336.c index 9f3f11256788e2..0cb0ee76191d03 100644 --- a/sound/soc/amd/acp-es8336.c +++ b/sound/soc/amd/acp-es8336.c @@ -30,7 +30,6 @@ static unsigned long acp2x_machine_id; static struct snd_soc_jack st_jack; -static struct device *codec_dev; static struct gpio_desc *gpio_pa; static int sof_es8316_speaker_power_event(struct snd_soc_dapm_widget *w, @@ -191,6 +190,7 @@ static const struct acpi_gpio_mapping acpi_es8336_gpios[] = { static int st_es8336_late_probe(struct snd_soc_card *card) { + struct device *codec_dev __free(put_device) = NULL; struct acpi_device *adev; int ret; @@ -198,7 +198,7 @@ static int st_es8336_late_probe(struct snd_soc_card *card) if (!adev) return -ENODEV; - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) { dev_err(card->dev, "can not find codec dev\n"); @@ -213,7 +213,6 @@ static int st_es8336_late_probe(struct snd_soc_card *card) if (IS_ERR(gpio_pa)) { ret = dev_err_probe(card->dev, PTR_ERR(gpio_pa), "could not get pa-enable GPIO\n"); - put_device(codec_dev); return ret; } return 0; From 095025078dc3a1b4f0fcdc8b8d9dcb4d192a2a14 Mon Sep 17 00:00:00 2001 From: Yibo Tan Date: Wed, 23 Sep 2026 02:50:57 +0800 Subject: [PATCH 1324/1417] ASoC: amd: acp3x-es83xx: Keep an owned codec device reference acpi_get_first_physical_node() returns a borrowed device pointer. The private data allocation failure path in acp3x_es83xx_probe() puts that pointer despite not owning a reference. The successful path also saves the borrowed pointer for later card operations. This was reproduced on current mainline with failslab restricted to the real static callback. The rejected devm_kzalloc() returned -ENOMEM, and normal codec platform-device unregister then produced a KASAN slab-use-after-free in device_del(), with allocation in acpi_create_platform_device() and release in acpi_unbind_one(). Use acpi_bus_get_primary_device() to acquire the reference while the physical-node lock is held. After allocating private data, register a devres put action before publishing the pointer. This balances action allocation and later probe failures, keeps the saved pointer alive during card use, and drops the reference after ASoC card unregister during successful teardown. The existing OOM put now correctly balances the owned lookup. The same filtered KASAN guest with this change reached the same -ENOMEM and unregister path without KASAN, WARNING, Oops or panic. The test directly invoked the production callback and did not emulate a complete ACP/ASoC card or physical Huawei hardware. Fixes: 54fcd9dd44b2 ("ASoC: amd: acp: Add machine driver that enables sound for systems with a ES8336 codec") Signed-off-by: Yibo Tan Link: https://patch.msgid.link/20260922185057.954150-3-lhfff@tju.edu.cn Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c index 3a640e65231420..eb0e9a6af6ef10 100644 --- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,11 @@ struct acp3x_es83xx_private { struct snd_soc_dapm_route mic_map[2]; }; +static void acp3x_es83xx_put_codec_device(void *data) +{ + put_device(data); +} + static const unsigned int channels[] = { DUAL_CHANNEL, }; @@ -428,7 +434,7 @@ static int acp3x_es83xx_probe(struct snd_soc_card *card) return -ENXIO; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) { dev_warn(dev, "Error cannot find codec device, will defer probe\n"); @@ -441,6 +447,12 @@ static int acp3x_es83xx_probe(struct snd_soc_card *card) return -ENOMEM; } + ret = devm_add_action_or_reset(dev, + acp3x_es83xx_put_codec_device, + codec_dev); + if (ret) + return ret; + priv->codec_dev = codec_dev; priv->quirk = (unsigned long)dmi_id->driver_data; acp_drvdata->mach_priv = priv; From 3e9c6464dadd52334abc22bc089cc5307d46314a Mon Sep 17 00:00:00 2001 From: Alireza Feyzabadi Farahani Date: Sun, 27 Sep 2026 06:44:30 +0000 Subject: [PATCH 1325/1417] selftests: ALSA: Skip timer tests when device is unavailable When CONFIG_SND is disabled, opening /dev/snd/timer fails and the userspace timer tests report failures even though the test environment does not support ALSA timers. Skip the affected tests when opening the timer device fails because the device is unavailable. Continue to report unexpected open failures as test failures. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=218479 Signed-off-by: Alireza Feyzabadi Farahani Reviewed-by: Mark Brown Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260927064430.2112688-1-arfa79lg@gmail.com --- tools/testing/selftests/alsa/utimer-test.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/testing/selftests/alsa/utimer-test.c b/tools/testing/selftests/alsa/utimer-test.c index 1a9ff010cb11f4..07aeaa777649a7 100644 --- a/tools/testing/selftests/alsa/utimer-test.c +++ b/tools/testing/selftests/alsa/utimer-test.c @@ -51,6 +51,9 @@ FIXTURE_SETUP(timer_f) { self->utimer_info->resolution = (NANO / FRAME_RATE * PERIOD_SIZE); timer_dev_fd = open("/dev/snd/timer", O_RDONLY); + if (timer_dev_fd < 0 && + (errno == ENOENT || errno == ENODEV || errno == ENXIO)) + SKIP(return, "ALSA timer device unavailable"); ASSERT_GE(timer_dev_fd, 0); if (ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info) < 0) { @@ -156,6 +159,9 @@ TEST(wrong_timers_test) { }; timer_dev_fd = open("/dev/snd/timer", O_RDONLY); + if (timer_dev_fd < 0 && + (errno == ENOENT || errno == ENODEV || errno == ENXIO)) + SKIP(return, "ALSA timer device unavailable"); ASSERT_GE(timer_dev_fd, 0); utimer_fd = ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, &wrong_timer); From 9332af5220d42341bb1446a62c545c0518e990ef Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Sun, 27 Sep 2026 17:38:09 +0800 Subject: [PATCH 1326/1417] ALSA: mpu401: shut down timer before freeing MPU When an MPU401 interface has no usable IRQ, the driver polls raw-MIDI input and output with a timer. During raw-MIDI teardown, the timer callback can already be running when snd_mpu401_uart_remove_timer() clears the last mode bit and calls timer_delete(). That API neither waits for a running callback nor prevents it from rearming the timer, and free_irq() does not synchronize timer callbacks. snd_mpu401_uart_free() can therefore release mpu while the timer callback still uses it. Initialize the timer once when the MPU object is created and only rearm it while a timer mode is active. Shut it down synchronously after freeing the IRQ so an in-flight callback completes before mpu and its resources are released. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260927093809.681726-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/drivers/mpu401/mpu401_uart.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/drivers/mpu401/mpu401_uart.c b/sound/drivers/mpu401/mpu401_uart.c index 6de03d72a90874..fcf2b686f9f5e2 100644 --- a/sound/drivers/mpu401/mpu401_uart.c +++ b/sound/drivers/mpu401/mpu401_uart.c @@ -155,20 +155,20 @@ static void snd_mpu401_uart_timer(struct timer_list *t) scoped_guard(spinlock_irqsave, &mpu->timer_lock) { /*mpu->mode |= MPU401_MODE_TIMER;*/ - mod_timer(&mpu->timer, 1 + jiffies); + if (mpu->timer_invoked) + mod_timer(&mpu->timer, 1 + jiffies); } if (mpu->rmidi) _snd_mpu401_uart_interrupt(mpu); } /* - * initialize the timer callback if not programmed yet + * arm the timer if it is not already active */ static void snd_mpu401_uart_add_timer (struct snd_mpu401 *mpu, int input) { guard(spinlock_irqsave)(&mpu->timer_lock); if (mpu->timer_invoked == 0) { - timer_setup(&mpu->timer, snd_mpu401_uart_timer, 0); mod_timer(&mpu->timer, 1 + jiffies); } mpu->timer_invoked |= input ? MPU401_MODE_INPUT_TIMER : @@ -474,6 +474,7 @@ static void snd_mpu401_uart_free(struct snd_rawmidi *rmidi) struct snd_mpu401 *mpu = rmidi->private_data; if (mpu->irq >= 0) free_irq(mpu->irq, (void *) mpu); + timer_shutdown_sync(&mpu->timer); release_and_free_resource(mpu->res); kfree(mpu); } @@ -528,6 +529,7 @@ int snd_mpu401_uart_new(struct snd_card *card, int device, spin_lock_init(&mpu->input_lock); spin_lock_init(&mpu->output_lock); spin_lock_init(&mpu->timer_lock); + timer_setup(&mpu->timer, snd_mpu401_uart_timer, 0); mpu->hardware = hardware; mpu->irq = -1; mpu->rmidi = rmidi; From 5bdc232c3094304a7e17df5ddb66dcdacbead6bf Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Sun, 27 Sep 2026 20:17:09 +0800 Subject: [PATCH 1327/1417] ALSA: oxygen: use helper to activate the SPDIF PCM control oxygen_open() and oxygen_close() modify the SPDIF PCM control access flags while holding chip->mutex. Direct access changes bypass the control core locking and notification path. Move the access transition outside the driver mutex and use snd_ctl_activate_id(), which serializes the control update with the control core. Fixes: d0ce9946c52e ("[ALSA] add CMI8788 driver") Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260927121709.742840-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/pci/oxygen/oxygen_pcm.c | 37 +++++++++++++++-------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/sound/pci/oxygen/oxygen_pcm.c b/sound/pci/oxygen/oxygen_pcm.c index b716356010b898..75efbf59a67524 100644 --- a/sound/pci/oxygen/oxygen_pcm.c +++ b/sound/pci/oxygen/oxygen_pcm.c @@ -171,16 +171,14 @@ static int oxygen_open(struct snd_pcm_substream *substream, snd_pcm_set_sync(substream); chip->streams[channel] = substream; - guard(mutex)(&chip->mutex); - chip->pcm_active |= 1 << channel; - if (channel == PCM_SPDIF) { - chip->spdif_pcm_bits = chip->spdif_bits; - chip->controls[CONTROL_SPDIF_PCM]->vd[0].access &= - ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | - SNDRV_CTL_EVENT_MASK_INFO, - &chip->controls[CONTROL_SPDIF_PCM]->id); + scoped_guard(mutex, &chip->mutex) { + chip->pcm_active |= 1 << channel; + if (channel == PCM_SPDIF) + chip->spdif_pcm_bits = chip->spdif_bits; } + if (channel == PCM_SPDIF) + snd_ctl_activate_id(chip->card, + &chip->controls[CONTROL_SPDIF_PCM]->id, 1); return 0; } @@ -220,19 +218,16 @@ static int oxygen_close(struct snd_pcm_substream *substream) struct oxygen *chip = snd_pcm_substream_chip(substream); unsigned int channel = oxygen_substream_channel(substream); - guard(mutex)(&chip->mutex); - chip->pcm_active &= ~(1 << channel); - if (channel == PCM_SPDIF) { - chip->controls[CONTROL_SPDIF_PCM]->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | - SNDRV_CTL_EVENT_MASK_INFO, - &chip->controls[CONTROL_SPDIF_PCM]->id); - } - if (channel == PCM_SPDIF || channel == PCM_MULTICH) - oxygen_update_spdif_source(chip); + scoped_guard(mutex, &chip->mutex) { + chip->pcm_active &= ~(1 << channel); + if (channel == PCM_SPDIF || channel == PCM_MULTICH) + oxygen_update_spdif_source(chip); - chip->streams[channel] = NULL; + chip->streams[channel] = NULL; + } + if (channel == PCM_SPDIF) + snd_ctl_activate_id(chip->card, + &chip->controls[CONTROL_SPDIF_PCM]->id, 0); return 0; } From aee7c43126e3184d7eefde6545b3b88628e5591a Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Sun, 27 Sep 2026 22:14:47 +0800 Subject: [PATCH 1328/1417] ALSA: via82xx: use helper to activate DXS controls The VIA DXS playback callbacks modify control access flags directly when a stream opens or closes. This bypasses the control core locking and notification path. Use snd_ctl_activate_id() for both transitions so access changes are serialized by the control core and reported with the expected info event. Fixes: 3d00941371a7 ("sound: via82xx: deactivate DXS controls of inactive streams") Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260927141447.788664-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/pci/via82xx.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index 24ee1302f27d33..58816dea49963e 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -1242,11 +1242,7 @@ static int snd_via8233_playback_open(struct snd_pcm_substream *substream) VIA_DXS_MAX_VOLUME - (dxs_init_volume & 31); chip->playback_volume[stream][1] = VIA_DXS_MAX_VOLUME - (dxs_init_volume & 31); - chip->dxs_controls[stream]->vd[0].access &= - ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | - SNDRV_CTL_EVENT_MASK_INFO, - &chip->dxs_controls[stream]->id); + snd_ctl_activate_id(chip->card, &chip->dxs_controls[stream]->id, 1); } return 0; } @@ -1333,10 +1329,7 @@ static int snd_via8233_playback_close(struct snd_pcm_substream *substream) stream = viadev->reg_offset / 0x10; if (chip->dxs_controls[stream]) { - chip->dxs_controls[stream]->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, - &chip->dxs_controls[stream]->id); + snd_ctl_activate_id(chip->card, &chip->dxs_controls[stream]->id, 0); } return snd_via82xx_pcm_close(substream); } From 33be70ba410598f71996bdd061194ae039a28eca Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 28 Sep 2026 09:18:23 +0700 Subject: [PATCH 1329/1417] ALSA: dummy: Move pcm_substreams check out of the loop The pcm_substreams[dev] range check does not depend on the loop index. Do it once before the loop instead of on every iteration. No functional change. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260928021823.6517-1-phucduc.bui@gmail.com Signed-off-by: Takashi Iwai --- sound/drivers/dummy.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index b908d2564aee9c..4df69b9712c5ed 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -1063,11 +1063,13 @@ static int snd_dummy_probe(struct platform_device *devptr) break; } } + + if (pcm_substreams[dev] < 1) + pcm_substreams[dev] = 1; + if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS) + pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; + for (idx = 0; idx < MAX_PCM_DEVICES && idx < pcm_devs[dev]; idx++) { - if (pcm_substreams[dev] < 1) - pcm_substreams[dev] = 1; - if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS) - pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; err = snd_card_dummy_pcm(dummy, idx, pcm_substreams[dev]); if (err < 0) return err; From 37e117c23563d6428a97395c23aa9af196cdbda4 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:41:15 +0000 Subject: [PATCH 1330/1417] ASoC: soc-dai: use snd_soc_dai_stream_active() We already have snd_soc_dai_stream_active(). Let's use it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87o6dvjpz9.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-dai.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 83d030e14bb0be..0cf70f0903dc8d 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -688,7 +688,7 @@ int snd_soc_dai_active(const struct snd_soc_dai *dai) active = 0; for_each_pcm_streams(stream) - active += dai->stream[stream].active; + active += snd_soc_dai_stream_active(dai, stream); return active; } From d4c557f621dbfffa121dedfa421d79667ff396ad Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:41:53 +0000 Subject: [PATCH 1331/1417] ASoC: soc-dai: add snd_soc_dai_id() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. Add snd_soc_dai_id() to get ID from DAI. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87mrtfjpy6.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 1 + sound/soc/soc-dai.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 7e64e049d71d56..02b52fed97c9e8 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -283,6 +283,7 @@ int snd_soc_dai_matches_args(const struct snd_soc_dai *dai, int snd_soc_dai_matches_dlc(struct snd_soc_dai *dai, const struct snd_soc_dai_link_component *dlc); const char *snd_soc_dai_name(const struct snd_soc_dai *dai); +int snd_soc_dai_id(const struct snd_soc_dai *dai); struct snd_soc_dai_ops { /* DAI driver callbacks */ diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 0cf70f0903dc8d..71de29bfbbd0c4 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1073,6 +1073,12 @@ const char *snd_soc_dai_name(const struct snd_soc_dai *dai) } EXPORT_SYMBOL_GPL(snd_soc_dai_name); +int snd_soc_dai_id(const struct snd_soc_dai *dai) +{ + return dai->id; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_id); + const struct snd_soc_pcm_stream * snd_soc_dai_pcm_stream_get_i(const struct snd_soc_dai *dai, int stream) { From 724da1c8b6302ff27b260d9d8b85ba63289f697e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:43:10 +0000 Subject: [PATCH 1332/1417] ASoC: soc-dai: add snd_soc_dai_to_component() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. Add snd_soc_dai_to_component() to get component from DAI. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87ld8zjpw1.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 1 + sound/soc/soc-dai.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 02b52fed97c9e8..7f8a64db0dc7ff 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -212,6 +212,7 @@ int snd_soc_dai_get_channel_map(const struct snd_soc_dai *dai, unsigned int *tx_num, unsigned int *tx_slot, unsigned int *rx_num, unsigned int *rx_slot); +struct snd_soc_component *snd_soc_dai_to_component(const struct snd_soc_dai *dai); int snd_soc_dai_is_dummy(const struct snd_soc_dai *dai); int snd_soc_dai_add_controls(struct snd_soc_dai *dai, const struct snd_kcontrol_new *controls, int num_controls); diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 71de29bfbbd0c4..a859acc4b28f39 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -20,6 +20,12 @@ static inline int _soc_dai_ret(const struct snd_soc_dai *dai, "at %s() on %s\n", func, dai->name); } +struct snd_soc_component *snd_soc_dai_to_component(const struct snd_soc_dai *dai) +{ + return dai->component; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_to_component); + /* * We might want to check substream by using list. * In such case, we can update these macros. From e90c0220073862ccf66ea187d305971ba796e088 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:43:16 +0000 Subject: [PATCH 1333/1417] ASoC: soc-dai: add snd_soc_dai_to_driver() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. Add snd_soc_dai_to_driver() to get driver from DAI. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87jyojjpvv.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 1 + sound/soc/soc-dai.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 7f8a64db0dc7ff..375a8674d1452a 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -213,6 +213,7 @@ int snd_soc_dai_get_channel_map(const struct snd_soc_dai *dai, unsigned int *rx_num, unsigned int *rx_slot); struct snd_soc_component *snd_soc_dai_to_component(const struct snd_soc_dai *dai); +struct snd_soc_dai_driver *snd_soc_dai_to_driver(const struct snd_soc_dai *dai); int snd_soc_dai_is_dummy(const struct snd_soc_dai *dai); int snd_soc_dai_add_controls(struct snd_soc_dai *dai, const struct snd_kcontrol_new *controls, int num_controls); diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index a859acc4b28f39..3e553e8c0535e3 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -26,6 +26,12 @@ struct snd_soc_component *snd_soc_dai_to_component(const struct snd_soc_dai *dai } EXPORT_SYMBOL_GPL(snd_soc_dai_to_component); +struct snd_soc_dai_driver *snd_soc_dai_to_driver(const struct snd_soc_dai *dai) +{ + return dai->driver; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_to_driver); + /* * We might want to check substream by using list. * In such case, we can update these macros. From f54b3dd4aa3519ef930faa3a6ba6d1c5d719ccf6 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:43:27 +0000 Subject: [PATCH 1334/1417] ASoC: soc-dai: add snd_soc_dai_{to/from}_list() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. Add snd_soc_dai_{to/from}_list() to use for_each macro. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87ik43jpvk.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 14 ++++++++++---- include/sound/soc-dai.h | 2 ++ sound/soc/soc-dai.c | 12 ++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index c49b59630101fa..6c1acc984ecb06 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -258,10 +258,16 @@ struct snd_soc_component { void *priv; }; -#define for_each_component_dais(component, dai)\ - list_for_each_entry(dai, &(component)->dai_list, list) -#define for_each_component_dais_safe(component, dai, _dai)\ - list_for_each_entry_safe(dai, _dai, &(component)->dai_list, list) +#define for_each_component_dais(component, dai) \ + for (dai = snd_soc_dai_from_list((component)->dai_list.next); \ + snd_soc_dai_to_list(dai) != &(component)->dai_list; \ + dai = snd_soc_dai_from_list(snd_soc_dai_to_list(dai)->next)) + +#define for_each_component_dais_safe(component, dai, _dai) \ + for (dai = snd_soc_dai_from_list((component)->dai_list.next), \ + _dai = snd_soc_dai_from_list(snd_soc_dai_to_list(dai)->next); \ + snd_soc_dai_to_list(dai) != &(component)->dai_list; \ + dai = _dai, _dai = snd_soc_dai_from_list(snd_soc_dai_to_list(_dai)->next)) /** * snd_soc_component_to_dapm() - Returns the DAPM context associated with a diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 375a8674d1452a..bd714c3bbf3dfe 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -561,6 +561,8 @@ struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, bool legacy_dai_naming); void snd_soc_dai_unregister(struct snd_soc_dai *dai); +struct snd_soc_dai *snd_soc_dai_from_list(struct list_head *list); +struct list_head *snd_soc_dai_to_list(struct snd_soc_dai *dai); /* REMOVE ME */ #define snd_soc_dai_get_pcm_stream snd_soc_dai_pcm_stream_get diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 3e553e8c0535e3..69a3df3c56a80c 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -32,6 +32,18 @@ struct snd_soc_dai_driver *snd_soc_dai_to_driver(const struct snd_soc_dai *dai) } EXPORT_SYMBOL_GPL(snd_soc_dai_to_driver); +struct snd_soc_dai *snd_soc_dai_from_list(struct list_head *list) +{ + return list_entry(list, struct snd_soc_dai, list); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_from_list); + +struct list_head *snd_soc_dai_to_list(struct snd_soc_dai *dai) +{ + return &dai->list; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_to_list); + /* * We might want to check substream by using list. * In such case, we can update these macros. From ee4f730ac8f76d89ca529f04794921872f0beb4f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:44:07 +0000 Subject: [PATCH 1335/1417] ASoC: soc-dai: add snd_soc_dai_get_symmetric_xxx() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. Add snd_soc_dai_get_symmetric_xxx() to get symmetric param from DAI. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87h5jnjpug.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 3 +++ sound/soc/soc-dai.c | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index bd714c3bbf3dfe..fc23221766b63c 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -563,6 +563,9 @@ struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, void snd_soc_dai_unregister(struct snd_soc_dai *dai); struct snd_soc_dai *snd_soc_dai_from_list(struct list_head *list); struct list_head *snd_soc_dai_to_list(struct snd_soc_dai *dai); +unsigned int snd_soc_dai_get_symmetric_rate(struct snd_soc_dai *dai); +unsigned int snd_soc_dai_get_symmetric_channels(struct snd_soc_dai *dai); +unsigned int snd_soc_dai_get_symmetric_sample_bits(struct snd_soc_dai *dai); /* REMOVE ME */ #define snd_soc_dai_get_pcm_stream snd_soc_dai_pcm_stream_get diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 69a3df3c56a80c..9787d7913c3087 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1263,6 +1263,24 @@ struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, } EXPORT_SYMBOL_GPL(snd_soc_dai_register); +unsigned int snd_soc_dai_get_symmetric_rate(struct snd_soc_dai *dai) +{ + return dai->symmetric_rate; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_symmetric_rate); + +unsigned int snd_soc_dai_get_symmetric_channels(struct snd_soc_dai *dai) +{ + return dai->symmetric_channels; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_symmetric_channels); + +unsigned int snd_soc_dai_get_symmetric_sample_bits(struct snd_soc_dai *dai) +{ + return dai->symmetric_sample_bits; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_symmetric_sample_bits); + void snd_soc_dai_symmetric_set_params(struct snd_soc_dai *dai, struct snd_pcm_hw_params *params) { From 9ec8ccc97dd9bd089078574e0eab859c9ce95456 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:45:07 +0000 Subject: [PATCH 1336/1417] ASoC: soc-dai: add snd_soc_dai_{set/to}_priv() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. DAI priv has been mainly referred from SoundWire driver. Let's add snd_soc_dai_{set/to}_priv() for it. Signed-off-by: Kuninori Morimoto Reviewed-by: Charles Keepax Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87fqz7jpst.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 2 ++ sound/soc/sdca/sdca_asoc.c | 4 ++-- sound/soc/soc-dai.c | 12 ++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index fc23221766b63c..be4781b6198917 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -566,6 +566,8 @@ struct list_head *snd_soc_dai_to_list(struct snd_soc_dai *dai); unsigned int snd_soc_dai_get_symmetric_rate(struct snd_soc_dai *dai); unsigned int snd_soc_dai_get_symmetric_channels(struct snd_soc_dai *dai); unsigned int snd_soc_dai_get_symmetric_sample_bits(struct snd_soc_dai *dai); +void snd_soc_dai_set_priv(struct snd_soc_dai *dai, void *priv); +void *snd_soc_dai_to_priv(struct snd_soc_dai *dai); /* REMOVE ME */ #define snd_soc_dai_get_pcm_stream snd_soc_dai_pcm_stream_get diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index ce2e7c27076576..ccc579f878ff1e 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -1507,7 +1507,7 @@ int sdca_asoc_set_constraints(struct device *dev, struct regmap *regmap, return ret; } - dai->priv = constraint; + snd_soc_dai_set_priv(dai, constraint); return 0; } @@ -1523,7 +1523,7 @@ EXPORT_SYMBOL_NS(sdca_asoc_set_constraints, "SND_SOC_SDCA"); void sdca_asoc_free_constraints(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_pcm_hw_constraint_list *constraint = dai->priv; + struct snd_pcm_hw_constraint_list *constraint = snd_soc_dai_to_priv(dai); kfree(constraint); } diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 9787d7913c3087..7412294eaea462 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -32,6 +32,18 @@ struct snd_soc_dai_driver *snd_soc_dai_to_driver(const struct snd_soc_dai *dai) } EXPORT_SYMBOL_GPL(snd_soc_dai_to_driver); +void snd_soc_dai_set_priv(struct snd_soc_dai *dai, void *priv) +{ + dai->priv = priv; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_priv); + +void *snd_soc_dai_to_priv(struct snd_soc_dai *dai) +{ + return dai->priv; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_to_priv); + struct snd_soc_dai *snd_soc_dai_from_list(struct list_head *list) { return list_entry(list, struct snd_soc_dai, list); From d47f591c9a8d39b79cbce24055f78dfa8153bf11 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:45:51 +0000 Subject: [PATCH 1337/1417] ASoC: soc-dai: add snd_soc_dai_get_bclk[_ratio]() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DAI parameter will be capsuled soon, will be not enable to access from each drivers. Add snd_soc_dai_get_bclk[_ratio]() for internal use. Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87ecerjprk.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-dai.c | 10 ++++++++++ sound/soc/soc-internal.h | 2 ++ sound/soc/soc-pcm.c | 19 +++++++++++++------ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 7412294eaea462..edf7d808e46ea5 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -1391,3 +1391,13 @@ void snd_soc_dai_symmetric_update(struct snd_pcm_substream *substream) if (symmetry) substream->runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; } + +struct clk *snd_soc_dai_get_bclk(struct snd_soc_dai *dai) +{ + return dai->bclk; +} + +unsigned int snd_soc_dai_get_bclk_ratio(struct snd_soc_dai *dai) +{ + return dai->bclk_ratio; +} diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 3afc50d5760eb4..f350fe5280e384 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -32,5 +32,7 @@ void snd_soc_dai_symmetric_set_params(struct snd_soc_dai *dai, int snd_soc_dai_symmetric_apply(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); int snd_soc_dai_symmetric_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); void snd_soc_dai_symmetric_update(struct snd_pcm_substream *substream); +struct clk *snd_soc_dai_get_bclk(struct snd_soc_dai *dai); +unsigned int snd_soc_dai_get_bclk_ratio(struct snd_soc_dai *dai); #endif /* __SOC_INTERNAL_H */ diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index a446e9c1c98871..e63b6317f21d25 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -445,17 +445,24 @@ static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); struct snd_interval constraint = { .empty = 1 }; unsigned int target_rate; + struct clk *dai_bclk; + unsigned int dai_bclk_ratio; int i; /* Protect the rtd list traversal with the ASoC card mutex helper. */ guard(snd_soc_card_mutex)(card); + dai_bclk = snd_soc_dai_get_bclk(dai); + dai_bclk_ratio = snd_soc_dai_get_bclk_ratio(dai); + /* Scan all DAIs on the card for an active peer sharing the same BCLK */ for_each_card_rtds(card, rtd) { for_each_rtd_cpu_dais(rtd, i, other_dai) { + struct clk *other_dai_bclk = snd_soc_dai_get_bclk(other_dai); + if (other_dai == dai) continue; - if (!other_dai->bclk) + if (!other_dai_bclk) continue; if (!snd_soc_dai_active(other_dai)) continue; @@ -467,10 +474,10 @@ static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, */ if (!other_dai->symmetric_rate) continue; - if (!clk_is_match(dai->bclk, other_dai->bclk)) + if (!clk_is_match(dai_bclk, other_dai_bclk)) continue; - active_bclk_rate = clk_get_rate(other_dai->bclk); + active_bclk_rate = clk_get_rate(other_dai_bclk); if (active_bclk_rate) goto found; } @@ -479,13 +486,13 @@ static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, return 0; found: - if (dai->bclk_ratio) { + if (dai_bclk_ratio) { /* * Driver has set an explicit BCLK ratio (e.g. for TDM where * BCLK = rate * slots * slot_width). The only valid rate is * active_bclk_rate / bclk_ratio. */ - target_rate = active_bclk_rate / dai->bclk_ratio; + target_rate = active_bclk_rate / dai_bclk_ratio; constraint.min = target_rate; constraint.max = target_rate; @@ -522,7 +529,7 @@ static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, static int soc_pcm_apply_shared_bclk(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - if (!dai->bclk) + if (!snd_soc_dai_get_bclk(dai)) return 0; dev_dbg(dai->dev, From 885ce49a700c91eb6e173755fdf5dbcd88befa33 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 18 Sep 2026 01:46:17 +0000 Subject: [PATCH 1338/1417] ASoC: soc-dai: rename snd_soc_dai_action() to snd_soc_dai_active_update() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have snd_soc_dai_action() to update .active. and in the same time, we have snd_soc_dai_active() to get .active. They are confusing name. Let's rename _action() to _active_update(). And in the same time, we have snd_soc_dai_action() and has its wrapper macro _activate/deactivate(). But callers are using both original/wrapper. Let's remove wrapper to remove confusion. It is used only from ASoC framework, let's move it to soc-internal.h Signed-off-by: Kuninori Morimoto Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/87cxubjpqu.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dai.h | 13 +------------ sound/soc/soc-dai.c | 6 ++---- sound/soc/soc-dapm.c | 9 +++++---- sound/soc/soc-internal.h | 1 + sound/soc/soc-pcm.c | 2 +- 5 files changed, 10 insertions(+), 21 deletions(-) diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index be4781b6198917..c044909ef10d81 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -231,18 +231,7 @@ void snd_soc_dai_suspend(struct snd_soc_dai *dai); void snd_soc_dai_resume(struct snd_soc_dai *dai); int snd_soc_dai_compress_new(struct snd_soc_dai *dai, struct snd_soc_pcm_runtime *rtd); bool snd_soc_dai_stream_valid(const struct snd_soc_dai *dai, int stream); -void snd_soc_dai_action(struct snd_soc_dai *dai, - int stream, int action); -static inline void snd_soc_dai_activate(struct snd_soc_dai *dai, - int stream) -{ - snd_soc_dai_action(dai, stream, 1); -} -static inline void snd_soc_dai_deactivate(struct snd_soc_dai *dai, - int stream) -{ - snd_soc_dai_action(dai, stream, -1); -} + int snd_soc_dai_active(const struct snd_soc_dai *dai); int snd_soc_pcm_dai_probe(struct snd_soc_pcm_runtime *rtd, int order); diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index edf7d808e46ea5..d124b6afb1b8d6 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -707,8 +707,7 @@ bool snd_soc_dai_stream_valid(const struct snd_soc_dai *dai, int dir) return stream->channels_min; } -void snd_soc_dai_action(struct snd_soc_dai *dai, - int stream, int action) +void snd_soc_dai_active_update(struct snd_soc_dai *dai, int stream, int action) { /* see snd_soc_dai_stream_active() */ dai->stream[stream].active += action; @@ -716,7 +715,6 @@ void snd_soc_dai_action(struct snd_soc_dai *dai, /* see snd_soc_component_active() */ dai->component->active += action; } -EXPORT_SYMBOL_GPL(snd_soc_dai_action); int snd_soc_dai_active(const struct snd_soc_dai *dai) { @@ -1161,7 +1159,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_stream_tdm_mask_set); unsigned int snd_soc_dai_stream_active(const struct snd_soc_dai *dai, int stream) { - /* see snd_soc_dai_action() for setup */ + /* see snd_soc_dai_active_update() for setup */ return dai->stream[stream].active; } EXPORT_SYMBOL_GPL(snd_soc_dai_stream_active); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 06b85313af1260..74be5e20949337 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -37,6 +37,7 @@ #include #include #include +#include "soc-internal.h" #include @@ -4010,7 +4011,7 @@ static int dapm_dai_link_event_pre_pmu(struct snd_soc_dapm_widget *w, if (ret < 0) return ret; - snd_soc_dai_activate(source, substream->stream); + snd_soc_dai_active_update(source, substream->stream, 1); } substream->stream = SNDRV_PCM_STREAM_PLAYBACK; @@ -4021,7 +4022,7 @@ static int dapm_dai_link_event_pre_pmu(struct snd_soc_dapm_widget *w, if (ret < 0) return ret; - snd_soc_dai_activate(sink, substream->stream); + snd_soc_dai_active_update(sink, substream->stream, 1); } substream->hw_opened = 1; @@ -4150,14 +4151,14 @@ static int dapm_dai_link_event(struct snd_soc_dapm_widget *w, substream->stream = SNDRV_PCM_STREAM_CAPTURE; snd_soc_dapm_widget_for_each_source_path(w, path) { source = path->source->priv; - snd_soc_dai_deactivate(source, substream->stream); + snd_soc_dai_active_update(source, substream->stream, -1); snd_soc_dai_shutdown(source, substream, 0); } substream->stream = SNDRV_PCM_STREAM_PLAYBACK; snd_soc_dapm_widget_for_each_sink_path(w, path) { sink = path->sink->priv; - snd_soc_dai_deactivate(sink, substream->stream); + snd_soc_dai_active_update(sink, substream->stream, -1); snd_soc_dai_shutdown(sink, substream, 0); } break; diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index f350fe5280e384..31559918355f87 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -34,5 +34,6 @@ int snd_soc_dai_symmetric_params(struct snd_pcm_substream *substream, struct snd void snd_soc_dai_symmetric_update(struct snd_pcm_substream *substream); struct clk *snd_soc_dai_get_bclk(struct snd_soc_dai *dai); unsigned int snd_soc_dai_get_bclk_ratio(struct snd_soc_dai *dai); +void snd_soc_dai_active_update(struct snd_soc_dai *dai, int stream, int action); #endif /* __SOC_INTERNAL_H */ diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index e63b6317f21d25..1ddf199edc93c8 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -368,7 +368,7 @@ void snd_soc_runtime_action(struct snd_soc_pcm_runtime *rtd, snd_soc_dpcm_mutex_assert_held(rtd); for_each_rtd_dais(rtd, i, dai) - snd_soc_dai_action(dai, stream, action); + snd_soc_dai_active_update(dai, stream, action); /* Increments/Decrements the active count for components without DAIs */ for_each_rtd_components(rtd, i, component) { From eb8a4dd45471b19db4862af91455a187ebd8c426 Mon Sep 17 00:00:00 2001 From: Akhil Arul Date: Sun, 20 Sep 2026 18:36:20 +0530 Subject: [PATCH 1339/1417] ALSA: rawmidi: give up draining output when the device stops draining snd_rawmidi_drain_output() waits up to 10 seconds for the output buffer to empty. The wait is unconditional, so a substream that has stopped being consumed costs the full timeout on every call. The sequencer OSS emulation makes that expensive. midisynth_unuse() runs as the port unuse callback with grp->list_mutex held for write, and calls snd_rawmidi_drain_output(). A single teardown closes every OSS midi port of the device, so an unresponsive device exposing many ports holds the rwsem for minutes. snd_seq_port_connect() needs the same rwsem, and in the OSS path it runs under register_mutex, so every other odev_open() queues up behind it until the hung task detector fires: INFO: task syz.4.21:6176 blocked for more than 143 seconds. __mutex_lock odev_open chrdev_open vfs_open path_openat Detect a stalled drain by sampling the free space instead of always sleeping for the whole timeout, and give up once it has not increased for a second. A substream that is still making progress is given as long as it needs, within the same overall 10 second limit as before, and each wait is clipped to the remaining time so that limit is not overshot. The warning is rate limited because a stalled drain is now detected much more often than once per 10 seconds. Closing one such device in qemu, with the syzkaller reproducer supplying the gadget, took 72-103 seconds before and 9-11 seconds after. Reported-by: syzbot+825b7e3a03dd072c187f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=825b7e3a03dd072c187f Signed-off-by: Akhil Arul Link: https://patch.msgid.link/20260920130620.61431-1-akhilarul324@gmail.com Signed-off-by: Takashi Iwai --- sound/core/rawmidi.c | 67 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 2e582688923d93..3399e9aee04d48 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -36,6 +36,13 @@ module_param_array(amidi_map, int, NULL, 0444); MODULE_PARM_DESC(amidi_map, "Raw MIDI device number assigned to 2nd OSS device."); #endif /* CONFIG_SND_OSSEMUL */ +/* upper bound for draining the output buffer */ +#define SNDRV_RAWMIDI_DRAIN_TIMEOUT (10 * HZ) +/* interval at which drain progress is re-checked */ +#define SNDRV_RAWMIDI_DRAIN_POLL (HZ / 5) +/* polls without progress before the drain is considered stalled */ +#define SNDRV_RAWMIDI_DRAIN_STALLS 5 + static int snd_rawmidi_dev_free(struct snd_device *device); static int snd_rawmidi_dev_register(struct snd_device *device); static int snd_rawmidi_dev_disconnect(struct snd_device *device); @@ -246,11 +253,20 @@ int snd_rawmidi_drop_output(struct snd_rawmidi_substream *substream) } EXPORT_SYMBOL(snd_rawmidi_drop_output); +static bool output_drained(struct snd_rawmidi_runtime *runtime) +{ + return runtime->avail >= runtime->buffer_size; +} + int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream) { - int err = 0; - long timeout; struct snd_rawmidi_runtime *runtime; + size_t avail, prev_avail; + unsigned int stalls = 0; + unsigned long deadline; + long timeout, wait; + bool done; + int err = 0; scoped_guard(spinlock_irq, &substream->lock) { runtime = substream->runtime; @@ -259,19 +275,52 @@ int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream) return -EINVAL; snd_rawmidi_buffer_ref(runtime); runtime->drain = 1; + prev_avail = runtime->avail; + } + + /* + * Wait for the device to consume the buffer. Rather than always + * sleeping for the whole timeout, sample the free space and stop + * early once it has not increased for a second. A substream that + * is still making progress is given as long as it needs, within the + * same overall limit as before. + */ + deadline = jiffies + SNDRV_RAWMIDI_DRAIN_TIMEOUT; + for (;;) { + /* signed difference, so this is safe across a jiffies wrap */ + wait = (long)(deadline - jiffies); + if (wait <= 0) { + timeout = 0; + break; + } + wait = min_t(long, SNDRV_RAWMIDI_DRAIN_POLL, wait); + timeout = wait_event_interruptible_timeout(runtime->sleep, + output_drained(runtime), wait); + scoped_guard(spinlock_irq, &substream->lock) { + avail = runtime->avail; + done = output_drained(runtime); + } + if (done || signal_pending(current)) + break; + if (avail == prev_avail) { + if (++stalls >= SNDRV_RAWMIDI_DRAIN_STALLS) { + timeout = 0; + break; + } + } else { + stalls = 0; + prev_avail = avail; + } } - timeout = wait_event_interruptible_timeout(runtime->sleep, - (runtime->avail >= runtime->buffer_size), - 10*HZ); - scoped_guard(spinlock_irq, &substream->lock) { if (signal_pending(current)) err = -ERESTARTSYS; if (runtime->avail < runtime->buffer_size && !timeout) { - rmidi_warn(substream->rmidi, - "rawmidi drain error (avail = %li, buffer_size = %li)\n", - (long)runtime->avail, (long)runtime->buffer_size); + dev_warn_ratelimited(substream->rmidi->dev, + "rawmidi drain error (avail = %li, buffer_size = %li)\n", + (long)runtime->avail, + (long)runtime->buffer_size); err = -EIO; } runtime->drain = 0; From 608c0c8947f03069b651ace54538a115f5b23123 Mon Sep 17 00:00:00 2001 From: Kailang Yang Date: Thu, 17 Sep 2026 14:53:26 +0800 Subject: [PATCH 1340/1417] ALSA: hda/realtek - Add headset mode for Dell Pro QC1255 It lost its headset microphone functionality, and this patch will restore it. Fixes: 97272a5704bf ("ALSA: hda/realtek - Fixed Headphone noise issue for Dell QCM1255") Signed-off-by: Kailang Yang Link: https://lore.kernel.org/3c251e40a2944ae4ae479e4c2e4e63a4@realtek.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 502844dc4e2627..fb3a223bf9fa7c 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7098,6 +7098,8 @@ static const struct hda_fixup alc269_fixups[] = { [ALC236_FIXUP_DELL_HP_POP_NOISE] = { .type = HDA_FIXUP_FUNC, .v.func = alc285_fixup_invalidate_dacs, + .chained = true, + .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE }, [ALC274_FIXUP_HP_89E9_GPIO] = { .type = HDA_FIXUP_FUNC, From 556cca74b6ec6f0bc6a3dc27473487de3cc8c8fe Mon Sep 17 00:00:00 2001 From: Saim Masood Date: Sat, 19 Sep 2026 05:07:09 +0500 Subject: [PATCH 1341/1417] ALSA: hda/realtek: Fix speakers on ASUS ExpertBook PM3606CHA The internal speakers of the ASUS ExpertBook PM3606CHA (Realtek ALC256, PCI SSID 1043:3501) stay silent with the default codec setup, and the headphone jack is not detected either. The codec needs COEF values that differ from its power-on defaults: COEF 0x10, 0x16, 0x35, 0x37 and 0x57 on NID 0x20 have to be set to the values used by the Windows driver. Toggling the codec GPIOs, and the existing ALC256 fixups alone, make no difference. Apply the COEFs from a new fixup at the INIT action, so that they are restored after resume as well. Chain to ALC256_FIXUP_ASUS_MIC_NO_PRESENCE to keep the headset mic and the jack detection working, which the fallback pin quirk provided before for this machine. The COEF values were found by the reporter of the Ubuntu bug below, who compared the codec state against the one under the Windows driver. Tested on an ASUS ExpertBook PM3606CHA (BIOS PM3606CHA.304) with the change applied to v7.2.5: with this fixup alone, the speakers, the headphones with jack detection, and the headset mic all work, also after suspend and resume. Link: https://bugs.launchpad.net/bugs/2167288 Signed-off-by: Saim Masood Link: https://patch.msgid.link/065927dd-cabf-449a-992d-64887eedda01@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 27d9a438ae7910..f0212559643dba 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -3579,6 +3579,27 @@ static void alc233_fixup_no_audio_jack(struct hda_codec *codec, alc_process_coef_fw(codec, alc233_fixup_no_audio_jack_coefs); } +static const struct coef_fw alc256_asus_speaker_coefs[] = { + WRITE_COEF(0x10, 0x7f20), WRITE_COEF(0x16, 0x0c50), WRITE_COEF(0x35, 0x8d6a), + WRITE_COEF(0x37, 0xfe06), WRITE_COEF(0x57, 0x7f7f), + {} +}; + +static void alc256_fixup_asus_speaker_coefs(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + /* + * The internal speakers of the ASUS ExpertBook PM3606CHA stay silent + * with the default COEF values. These are the values set by the + * Windows driver. They are applied on every init so that they are + * restored after resume, too. + */ + if (action != HDA_FIXUP_ACT_INIT) + return; + alc_process_coef_fw(codec, alc256_asus_speaker_coefs); +} + static void alc256_fixup_mic_no_presence_and_resume(struct hda_codec *codec, const struct hda_fixup *fix, int action) @@ -4429,6 +4450,7 @@ enum { ALC285_LENOVO_DAC_RENAME, ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, ALC256_FIXUP_IPASON_SMARTBOOK_S1, + ALC256_FIXUP_ASUS_SPEAKER_COEFS, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -7203,6 +7225,12 @@ static const struct hda_fixup alc269_fixups[] = { { } }, }, + [ALC256_FIXUP_ASUS_SPEAKER_COEFS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_asus_speaker_coefs, + .chained = true, + .chain_id = ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, + }, }; static const struct hda_quirk alc269_fixup_tbl[] = { @@ -7969,6 +7997,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x31e1, "ASUS B5605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x31f1, "ASUS B3605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x3391, "ASUS PM3606CKA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x3501, "ASUS PM3606CHA", ALC256_FIXUP_ASUS_SPEAKER_COEFS), SND_PCI_QUIRK(0x1043, 0x3601, "ASUS PM5406CGA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x3611, "ASUS PM5606CGA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x3701, "ASUS P5406CCA", ALC245_FIXUP_CS35L41_SPI_2), From 4322ac247f4ce7932edbd46aa37f3dae62cde424 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 21 Sep 2026 20:44:49 +0800 Subject: [PATCH 1342/1417] ALSA: hda/realtek: Add quirk for Lenovo ThinkBook G8+ Fix Bass Speaker volume control on Lenovo ThinkBook G8+ by adding quirk 17aa:38dc with ALC285_FIXUP_SPEAKER2_TO_DAC1. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221799 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260921124450.639601-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index fb3a223bf9fa7c..2b1fa3d4585b7b 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8264,6 +8264,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x38d5, "Yoga S990-16 Pro IMH YC Quad", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38d6, "Yoga S990-16 Pro IMH VECO Quad", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38d7, "Lenovo Yoga 9 14IMH9", ALC287_FIXUP_YOGA9_14IMH9_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x38dc, "Lenovo ThinkBook G8", ALC285_FIXUP_SPEAKER2_TO_DAC1), SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), From 0d90a929836344f1d3b39f22f08ccecc62378a59 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 21 Sep 2026 20:44:50 +0800 Subject: [PATCH 1343/1417] ALSA: hda/realtek: Add quirk for Acer Nitro AN515-45 The Acer Nitro AN515-45 requires ALC2XX_FIXUP_HEADSET_MIC to make the headset microphone work properly. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221925 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260921124450.639601-2-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 2b1fa3d4585b7b..4bd37a15206e1d 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7203,6 +7203,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x142b, "Acer Swift SF314-42", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1430, "Acer TravelMate B311R-31", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1466, "Acer Aspire A515-56", ALC255_FIXUP_ACER_HEADPHONE_AND_MIC), + SND_PCI_QUIRK(0x1025, 0x151e, "Acer Nitro AN515-45", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1534, "Acer Predator PH315-54", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1539, "Acer Nitro 5 AN515-57", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x159c, "Acer Nitro 5 AN515-58", ALC287_FIXUP_ACER_MICMUTE_LED), From c748c42abb9b549fc79c41ea55e1f220eadb60ec Mon Sep 17 00:00:00 2001 From: Krish Gulati Date: Mon, 21 Sep 2026 18:47:03 +0530 Subject: [PATCH 1344/1417] ALSA: hda/realtek: Add mute LED quirk for HP Victus 15-fb2xxx The HP Victus 15-fb2xxx (PCI SSID 103c:8c2f) with the ALC245 codec does not turn on the mute LED when audio is muted. Add a quirk entry to enable it. Reported-by: erph.briones27@gmail.com Link: https://bugzilla.kernel.org/show_bug.cgi?id=221946 Signed-off-by: Krish Gulati Tested-by: Raphael Briones Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260921131728.8999-1-krishgulati7@gmail.com --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 4bd37a15206e1d..1db335b18c4e76 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7628,6 +7628,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), SND_PCI_QUIRK(0x103c, 0x8c21, "HP Pavilion Plus Laptop 14-ey0XXX", ALC245_FIXUP_HP_X360_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c2f, "HP Victus 15-fb2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), From 1e3e378d63be4e5887f5753c00562dcbcebf00aa Mon Sep 17 00:00:00 2001 From: Denis Yakovlev Date: Mon, 28 Sep 2026 00:04:55 +0300 Subject: [PATCH 1345/1417] ALSA: hda/realtek: Enable bass speakers on HONOR MagicBook Pro 16 2024 The HONOR MagicBook Pro 16 2024 (DRA-XX, board M1020, ALC256, SSID 1ee7:204e) has two tweeters and four woofers. The BIOS only configures the tweeter pin 0x1b; the woofer pin 0x14 is left at 0x411111f0, so the woofers stay silent. Pin 0x14 can only be connected to DAC 0x02, while the parser puts the tweeters on DAC 0x03, which turns the second speaker pair into channels 3/4 that a stereo stream never reaches (and SOF on Meteor Lake only exposes a 2-channel HDA analog PCM). Configure 0x14 as an internal speaker and restrict 0x1b to DAC 0x02, so both speaker pairs play the stereo stream with a shared volume control, as on Windows. Tested on DRA-XX with SOF (sof-hda-generic-2ch.tplg): woofers and tweeters play together, headphone auto-mute and the DMICs keep working. Assisted-by: LLM Signed-off-by: Denis Yakovlev Link: https://patch.msgid.link/20260927210455.13938-1-mfthesun@vivaldi.net Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index f0212559643dba..075dc1aa326bb2 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -2763,6 +2763,17 @@ static void alc294_fixup_bass_speaker_15(struct hda_codec *codec, } } +/* route Speaker (0x1b) to DAC 0x02, the only DAC reachable from Bass Speaker (0x14) */ +static void alc256_fixup_honor_dra_xx_share_dac(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + static const hda_nid_t conn[] = { 0x02 }; + + snd_hda_override_conn_list(codec, 0x1b, ARRAY_SIZE(conn), conn); + } +} + /* Hook to update amp GPIO4 for automute */ static void alc280_hp_gpio4_automute_hook(struct hda_codec *codec, struct hda_jack_callback *jack) @@ -4439,6 +4450,8 @@ enum { ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED, ALC236_FIXUP_HP_DMIC, ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, + ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS, + ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC, ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX, ALC287_FIXUP_ACER_MICMUTE_LED, ALC236_FIXUP_DELL_HP_POP_NOISE, @@ -7152,6 +7165,19 @@ static const struct hda_fixup alc269_fixups[] = { { } } }, + [ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x90170111 }, /* bass speakers */ + { } + }, + .chained = true, + .chain_id = ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC + }, + [ALC256_FIXUP_HONOR_DRA_XX_SHARE_DAC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_honor_dra_xx_share_dac, + }, [ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX] = { .type = HDA_FIXUP_FUNC, .v.func = cs35l41_fixup_i2c_two, @@ -8448,6 +8474,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1e50, 0x7007, "Positivo DN50E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x1e50, 0x7036, "Acer Gadget E10 ETBook", ALC233_FIXUP_WUJIE_SPEAKERS), SND_PCI_QUIRK(0x1e50, 0x7038, "Positivo DN140", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1ee7, 0x204e, "HONOR DRA-XX M1020", ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS), SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro", From 082723fee626532f176727b6438b666a96d5246c Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 16:48:53 +0000 Subject: [PATCH 1346/1417] ASoC: codecs: wcd9335: Fix SLIM interface device leak in wcd9335_slim_status() of_slim_get_device() returns a device with an elevated reference count, and wcd->slim_ifc_dev is intentionally kept for the codec lifetime on success. However, all the failure paths of wcd9335_slim_status() return without dropping that reference, leaking the SLIM interface device whenever the register maps cannot be created or the codec fails to come up. Put the device on each of those error paths. Fixes: 20aedafdf492 ("ASoC: wcd9335: add support to wcd9335 codec") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260917164853.2163184-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/wcd9335.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index 9332082092c791..a98d1fd789251a 100644 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -5135,25 +5135,32 @@ static int wcd9335_slim_status(struct slim_device *sdev, slim_get_logical_addr(wcd->slim_ifc_dev); wcd->regmap = regmap_init_slimbus(sdev, &wcd9335_regmap_config); - if (IS_ERR(wcd->regmap)) + if (IS_ERR(wcd->regmap)) { + put_device(&wcd->slim_ifc_dev->dev); return dev_err_probe(dev, PTR_ERR(wcd->regmap), "Failed to allocate slim register map\n"); + } wcd->if_regmap = regmap_init_slimbus(wcd->slim_ifc_dev, &wcd9335_ifc_regmap_config); - if (IS_ERR(wcd->if_regmap)) + if (IS_ERR(wcd->if_regmap)) { + put_device(&wcd->slim_ifc_dev->dev); return dev_err_probe(dev, PTR_ERR(wcd->if_regmap), "Failed to allocate ifc register map\n"); + } ret = wcd9335_bring_up(wcd); if (ret) { dev_err(dev, "Failed to bringup WCD9335\n"); + put_device(&wcd->slim_ifc_dev->dev); return ret; } ret = wcd9335_irq_init(wcd); - if (ret) + if (ret) { + put_device(&wcd->slim_ifc_dev->dev); return ret; + } wcd9335_probe(wcd); From c26d18964a9352e83cb5fdd7b327d9a2566abddf Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 28 Sep 2026 11:56:10 +0100 Subject: [PATCH 1347/1417] ASoC: Intel: Add quirk to block match table for Lenovo Yoga Slim 7 A variant of the Lenovo Yoga Slim 7 exists on Lunar Lake that doesn't have a physical jack socket. Cirrus codecs still use match tables on LNL but the match table makes no distinction between a system with or without a jack. This leads to the system failing probe as the jack in the topology file is not matched by a DAI link as the machine driver is constructed. Add a new quirk to block using match tables for specific devices, and rely on function topologies which can handle this difference seamlessly. This is slightly preferred over moving everything to function topologies due to the lower chance of causing regressions on other systems. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260928105610.242687-1-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- .../soc/intel/common/soc-acpi-intel-lnl-match.c | 1 + .../intel/common/soc-acpi-intel-sdca-quirks.c | 16 ++++++++++++++++ .../intel/common/soc-acpi-intel-sdca-quirks.h | 1 + 3 files changed, 18 insertions(+) diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c index 3b9758f73238bb..62b8811395cb43 100644 --- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c @@ -745,6 +745,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { .links = lnl_cs42l43_l0, .drv_name = "sof_sdw", .sof_tplg_filename = "sof-lnl-cs42l43-l0.tplg", + .machine_check = snd_soc_acpi_intel_force_function_topology, .get_function_tplg_files = sof_sdw_get_tplg_files, }, { diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c index 9fdeb6ad96ac25..e62ef81214ffe5 100644 --- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c @@ -53,6 +53,22 @@ bool snd_soc_acpi_intel_no_function_topology(void *arg) } EXPORT_SYMBOL_NS(snd_soc_acpi_intel_no_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); +static const struct dmi_system_id force_function_topology_quirk_table[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83R0"), + }, + }, + {} +}; + +bool snd_soc_acpi_intel_force_function_topology(void *arg) +{ + return !dmi_check_system(force_function_topology_quirk_table); +} +EXPORT_SYMBOL_NS(snd_soc_acpi_intel_force_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); + bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg) { return snd_soc_acpi_intel_sdca_is_device_rt712_vb(arg) && diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h index 60b665ab5924a4..10179004c86a3b 100644 --- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h @@ -11,6 +11,7 @@ bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg); bool snd_soc_acpi_intel_no_function_topology(void *arg); +bool snd_soc_acpi_intel_force_function_topology(void *arg); bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg); #endif From f090e7ff26fe08be4cd57e0950872ede4af5351a Mon Sep 17 00:00:00 2001 From: Bartosz Brom Date: Mon, 28 Sep 2026 15:43:35 +0200 Subject: [PATCH 1348/1417] ALSA: hda/realtek: Add mute LED quirks for HP Pavilion 15-eh3174nw and HP Laptop 15-dw1xxx The HP Pavilion 15-eh3174nw with an ALC287 codec requires the ALC287_FIXUP_HP_GPIO_LED fixup for its mute LED to work correctly. Add subsystem ID 0x103c:0x8bc7 to the quirk table to apply the existing fixup. Tested on an HP Pavilion 15-eh3174nw; the mute LED now follows the speaker mute state. The HP Laptop 15-dw1xxx with an ALC236 codec requires the ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO fixup for its mute LED to work correctly. Add subsystem ID 0x103c:0x85f1 to the quirk table to apply the existing fixup. Tested on an HP Laptop 15-dw1xxx; the mute LED now follows the speaker mute state. Signed-off-by: Bartosz Brom Link: https://patch.msgid.link/20260928134335.1875-1-bartosz.brom06@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 1db335b18c4e76..193e0fa3606dd5 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7411,6 +7411,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x85f1, "HP Laptop 15-dw1xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x85f3, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), @@ -7606,6 +7607,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bc7, "HP Pavilion 15-eh3174nw", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bd4, "HP Victus 16-s0xxx (MB 8BD4)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), From b57f5571a3e88febd38d60f9a9cefd8937a383ba Mon Sep 17 00:00:00 2001 From: Liu Eason Date: Sat, 19 Sep 2026 21:08:13 +0800 Subject: [PATCH 1349/1417] ALSA: hda/conexant: Fix silent speaker on Huawei MateBook 14 (SN6140) On the Huawei MateBook 14 (PCI SSID 19e5:329e), the SN6140 codec silently routes the speaker output through the headphone pin's connection select: whatever DAC the headphone pin 0x16 is connected to, the speaker pin 0x17 follows. The generic parser attaches the playback stream to DAC 0x10 (shared by the headphone path) and mutes that DAC when no headphone is plugged, silencing the internal speaker while the headphone jack keeps working. Add a quirk for this machine that suppresses the auto-mute, binds the speaker pin to the shared DAC 0x10, removes the mute switch on that DAC so userspace cannot mute it, and re-applies the routing after every output update; at init it routes both pins to DAC 0x10, enables the speaker EAPD (which the driver otherwise leaves off) and unmutes the DAC. Note that this is a different problem than the existing Huawei Matebook quirk for SSID 19e5:3289 (commit d152afd1cd7a), which fixes a missing headphone jack; that machine has working speakers. Tested on Ubuntu 26.04 (kernels 7.0.0-30 and 7.0.0-31): the speaker plays at boot without any userspace helper, and keeps working across suspend/resume. Signed-off-by: Liu Eason Link: https://lore.kernel.org/ME3PR01MB75042406F30EE48AB6DA3595BD862@ME3PR01MB7504.ausprd01.prod.outlook.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/conexant.c | 67 +++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index 9bfdbf5c903259..91cbd728372f1c 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -312,6 +312,7 @@ enum { CXT_FIXUP_HP_A_U, CXT_FIXUP_ACER_SWIFT_HP, CXT_FIXUP_HUAWEI_MATEBOOK_HP, + CXT_FIXUP_MATEBOOK14_SN6140, }; /* for hda_fixup_thinkpad_acpi() */ @@ -817,6 +818,67 @@ static void cxt_fixup_hp_a_u(struct hda_codec *codec, cxt_setup_gpio_unmute(codec, 0x2); } +/* Huawei MateBook 14 (SN6140): the speaker output follows the headphone + * pin's connection select in hardware. Both pins must be routed to the + * shared DAC 0x10, which carries the playback stream; the generic + * parser mutes that DAC when no headphone is plugged, silencing the + * speaker. Suppress the auto-mute, bind the speaker pin to the shared + * DAC, remove its mute switch and keep it unmuted; the auto-mute hook + * re-applies the routing after every output update. + */ +static void cxt_mb14_route_unmute(struct hda_codec *codec) +{ + /* route both pins to the shared DAC 0x10, enable the speaker + * EAPD and unmute the DAC at a moderate gain; the vmaster + * volume takes over once userspace starts + */ + snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_CONNECT_SEL, 0); + snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_CONNECT_SEL, 0); + snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_EAPD_BTLENABLE, 0x02); + snd_hda_codec_amp_stereo(codec, 0x10, HDA_OUTPUT, 0, + HDA_AMP_VOLMASK | HDA_AMP_MUTE, 0x40); +} + +static void cxt_mb14_automute(struct hda_codec *codec) +{ + snd_hda_gen_update_outputs(codec); + cxt_mb14_route_unmute(codec); +} + +static void cxt_fixup_matebook14_sn6140(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + static const hda_nid_t dac_0x10[] = { 0x10 }; + + spec->gen.suppress_auto_mute = 1; + spec->gen.automute_hook = cxt_mb14_automute; + /* Bind the speaker pin to the shared DAC 0x10. + */ + snd_hda_override_conn_list(codec, 0x17, 1, dac_0x10); + return; + } + if (action == HDA_FIXUP_ACT_PROBE) { + unsigned int caps; + + /* Remove the mute switch on the shared DAC so that + * userspace (WirePlumber) cannot mute it when the + * headphone route becomes unavailable (e.g. after + * resume). Must be done here: the codec regmap is not + * available at pre-probe time. + */ + caps = query_amp_caps(codec, 0x10, HDA_OUTPUT); + snd_hda_override_amp_caps(codec, 0x10, HDA_OUTPUT, + caps & ~AC_AMPCAP_MUTE); + return; + } + if (action != HDA_FIXUP_ACT_INIT) + return; + cxt_mb14_route_unmute(codec); +} + /* ThinkPad X200 & co with cxt5051 */ static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = { { 0x16, 0x042140ff }, /* HP (seq# overridden) */ @@ -1062,6 +1124,10 @@ static const struct hda_fixup cxt_fixups[] = { { } }, }, + [CXT_FIXUP_MATEBOOK14_SN6140] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_matebook14_sn6140, + }, }; static const struct hda_quirk cxt5045_fixups[] = { @@ -1164,6 +1230,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI), SND_PCI_QUIRK(0x19e5, 0x3289, "Huawei Matebook", CXT_FIXUP_HUAWEI_MATEBOOK_HP), + SND_PCI_QUIRK(0x19e5, 0x329e, "Huawei MateBook 14", CXT_FIXUP_MATEBOOK14_SN6140), SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004), SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205), SND_PCI_QUIRK(0x1d05, 0x3012, "MECHREVO Wujie 15X Pro", CXT_FIXUP_HEADSET_MIC), From 7103a8c7b2ce196aded3bdfd8c4cf5afef4a90ff Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Sun, 27 Sep 2026 09:36:23 +0800 Subject: [PATCH 1350/1417] ASoC: soc-generic-dmaengine-pcm: use dmaengine_get_dma_device() for DMA device Replace chan->device->dev with dmaengine_get_dma_device(chan) in dmaengine_dma_dev() so that DMA buffer allocation and mapping go through the correct device when the DMA controller has per-channel IOMMU domains (chan_dma_dev=true). Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260927-sound-dma-v1-1-8b06f971ac0e@nxp.com Signed-off-by: Mark Brown --- sound/soc/soc-generic-dmaengine-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index 6d1dbee8f6c14f..f6206ecaf26fec 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -32,7 +32,7 @@ static struct device *dmaengine_dma_dev(struct dmaengine_pcm *pcm, if (!pcm->chan[substream->stream]) return NULL; - return pcm->chan[substream->stream]->device->dev; + return dmaengine_get_dma_device(pcm->chan[substream->stream]); } /** From de0be324fcb97a49caad131c23ad33265dd4b0b8 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 28 Sep 2026 16:39:25 +0800 Subject: [PATCH 1351/1417] ASoC: amd: acp-config: Add ACP70 DMI quirk for ASUS UM5406GA Add ASUS Zenbook S14 UM5406GA (Strix Point, ACP 7.0) to acp70_acpi_flag_override_table so that the broken BIOS acp-audio-config-flag is ignored and the default SoundWire enumeration path is used. Without this, no ASoC machine driver matches and internal speakers/mic fail to probe. Reported-by: Jannick Tobler Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222009 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260928083925.266471-1-zhangheng@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index 4d8f86aa3d1635..b3b73096ac74a6 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -70,6 +70,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook 18 M1807GA"), }, }, + { + /* ASUS Zenbook S14 UM5406GA (Strix Point, ACP 7.0) */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "Zenbook S14 UM5406GA"), + }, + }, { /* HP OmniBook X Flip 14-kc0xxx (Strix Point, ACP 7.2) */ .matches = { From 72107104d4f522405c9dfdfbaeb230e371c295ee Mon Sep 17 00:00:00 2001 From: Jared Wuerzburger Date: Mon, 28 Sep 2026 10:29:25 -0500 Subject: [PATCH 1352/1417] ALSA: hda/realtek: Fix internal mic on Minisforum V3 and V3 SE On the Minisforum V3 and V3 SE (ALC245, PCI SSID 1f4c:e001) the internal digital microphone array never produces any audio. The DMIC pin 0x12 is parsed correctly ("Internal Mic=0x12"), but the headset mic jack pin 0x19 (BIOS pin config 0x04a19050, jack detect enabled) has no presence detect wired on these boards: reading GET_PIN_SENSE on 0x19 always returns "present" (as it does on the unused, unconnected pin 0x18), while the headphone jack 0x21 reports correctly. The generic parser therefore sets up auto-mic switching and, believing an external mic is always plugged in, permanently routes the ADC to the empty jack (mixer 0x23 input 0x19 unmuted, input 0x12 muted). The "Mic Jack" kcontrol reports "on" with nothing plugged in. Mark pin 0x19 as having no presence detect. The parser then stops auto-switching and exposes a normal "Capture Source" (Internal Mic / Mic) control, which desktop audio stacks drive from their Internal Microphone / Microphone ports. The existing bass speaker DAC routing is kept by chaining to ALC245_FIXUP_BASS_HP_DAC. Tested on both models with the same pin config applied through a snd-hda-intel patch firmware ([pincfg] 0x19 0x04a19150): - V3 SE (Ryzen 7 7735U, BIOS 1.03), kernel 7.2.6 - V3 (Ryzen 7 8840U, BIOS 1.06), kernel 7.2.7 Both boards report the same codec SSID and the same 0x19 default. On both, the internal mic records at a normal level, headphone jack detection on 0x21 still switches output, and the bass routing is retained (DAC1/DAC2 Playback Volume controls present). A headset mic on the combo jack was not tested (none available); with this change it has to be selected manually. V3 users already report that the jack mic gets no input [1], which is the other symptom of the same broken presence detect. The patched alc269.c was build-tested with W=1 on tiwai/sound.git for-linus (7.3-rc3) without new warnings. The diagnosis, the fixup and this changelog were written with the help of an AI coding assistant; I reviewed them and tested the change on the hardware listed above. Fixes: e34743018249 ("ALSA: hda/realtek: Add quirk for Minisforum V3 SE") Link: https://github.com/mudkipme/awesome-minisforum-v3/issues/14 [1] Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Jared Wuerzburger Link: https://patch.msgid.link/20260928152925.69032-1-jwuerz@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 075dc1aa326bb2..13bc741907522b 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4464,6 +4464,7 @@ enum { ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, ALC256_FIXUP_IPASON_SMARTBOOK_S1, ALC256_FIXUP_ASUS_SPEAKER_COEFS, + ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -7257,6 +7258,15 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, }, + [ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x04a19150 }, /* headset mic jack, no presence detect */ + { } + }, + .chained = true, + .chain_id = ALC245_FIXUP_BASS_HP_DAC + }, }; static const struct hda_quirk alc269_fixup_tbl[] = { @@ -8479,7 +8489,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro", ALC245_FIXUP_MINISFORUM_JACK_DETECT), - SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", ALC245_FIXUP_BASS_HP_DAC), + SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", + ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), From a718fd03e1e00211cf4662f5ab82d33f4c18e2a7 Mon Sep 17 00:00:00 2001 From: Niko Huuskonen Date: Sun, 27 Sep 2026 03:35:29 +0300 Subject: [PATCH 1353/1417] ALSA: caiaq: Serialize access to the EP1 command buffer snd_usb_caiaq_send_command() and snd_usb_caiaq_send_command_bank() copy the command into cdev->ep1_out_buf and send it with a synchronous bulk transfer. Nothing serializes their callers. An ALSA control write, which sets the LEDs on the Kore controllers and several other devices, can run at the same time as a PCM prepare, which sends the audio parameters through the same buffer. One caller can then overwrite the buffer while the transfer of the other is still in flight, and the device receives a mix of both commands. Protect the buffer with a mutex. All callers run in process context and already sleep in usb_bulk_msg(). The problem was found by code review while adding another user of the buffer, the Kore LCD support later in this series. It has not been observed or reproduced. Fixes: 8e3cd08ed8e5 ("[ALSA] caiaq - add control API and more input features") Assisted-by: LLM Signed-off-by: Niko Huuskonen Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260927003532.289468-2-niko.huuskonen.00@gmail.com --- sound/usb/caiaq/device.c | 5 +++++ sound/usb/caiaq/device.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index f604846ec6e6f5..e8619bb7435812 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -212,6 +212,8 @@ int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev, if (len > EP1_BUFSIZE - 1) len = EP1_BUFSIZE - 1; + guard(mutex)(&cdev->ep1_out_mutex); + if (buffer && len > 0) memcpy(cdev->ep1_out_buf+1, buffer, len); @@ -235,6 +237,8 @@ int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev, if (len > EP1_BUFSIZE - 2) len = EP1_BUFSIZE - 2; + guard(mutex)(&cdev->ep1_out_mutex); + if (buffer && len > 0) memcpy(cdev->ep1_out_buf+2, buffer, len); @@ -443,6 +447,7 @@ static int create_card(struct usb_device *usb_dev, cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor), le16_to_cpu(usb_dev->descriptor.idProduct)); spin_lock_init(&cdev->spinlock); + mutex_init(&cdev->ep1_out_mutex); *cardp = card; return 0; diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index 1c6f34693fa819..822d872c8ad1a1 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -2,6 +2,8 @@ #ifndef CAIAQ_DEVICE_H #define CAIAQ_DEVICE_H +#include + #include "../usbaudio.h" #define USB_VID_NATIVEINSTRUMENTS 0x17cc @@ -68,6 +70,7 @@ struct snd_usb_caiaqdev { unsigned char ep1_in_buf[EP1_BUFSIZE]; unsigned char ep1_out_buf[EP1_BUFSIZE]; + struct mutex ep1_out_mutex; /* protects ep1_out_buf */ unsigned char midi_out_buf[EP1_BUFSIZE]; struct caiaq_device_spec spec; From c0508fc6895137f9e2dd43c1a09cc834254cc149 Mon Sep 17 00:00:00 2001 From: Niko Huuskonen Date: Sun, 27 Sep 2026 03:35:30 +0300 Subject: [PATCH 1354/1417] ALSA: caiaq: Fix the Kore controller key map keycode_kore does not match the hardware in two places: - Softkeys 5 to 8 are listed in reverse order, so pressing the fifth softkey reports BTN_8, the sixth BTN_7 and so on. Softkeys 1 to 4 are correct. The OpenKoreBridge project, which drives a Kore 2 through this driver, works around the same reversal in userspace. - On the first Kore controller the touch sensors of the eight knobs are scrambled: touching knob 1 reports KEY_BRL_DOT6, knob 2 KEY_BRL_DOT8, knob 3 KEY_BRL_DOT2, knob 6 KEY_BRL_DOT7, knob 7 KEY_BRL_DOT1 and knob 8 KEY_BRL_DOT3. Only knobs 4 and 5 are right. Put the softkeys in order for both controllers, and give the first Kore controller its own touch sensor order, so that BTN_n and KEY_BRL_DOTn belong to the n-th knob. The touch sensor order of the Kore 2 is left alone, as it could not be checked. Userspace that compensates for the old order needs to follow. It can read the key map with EVIOCGKEYCODE, which also makes it possible to support kernels with and without this change. Tested on a Kore controller (USB ID 17cc:4711). Link: https://github.com/OpenKoreBridge/OpenKoreBridge Fixes: 8e3cd08ed8e5 ("[ALSA] caiaq - add control API and more input features") Assisted-by: LLM Signed-off-by: Niko Huuskonen Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260927003532.289468-3-niko.huuskonen.00@gmail.com --- sound/usb/caiaq/input.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index 8d924330c54c88..856235c3398ef0 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -41,11 +41,11 @@ static const unsigned short keycode_kore[] = { BTN_3, BTN_2, BTN_1, - BTN_8, - BTN_7, - BTN_6, BTN_5, - KEY_BRL_DOT4, /* touch sensitive knobs */ + BTN_6, + BTN_7, + BTN_8, + KEY_BRL_DOT4, /* touch sensitive knobs (Kore 2 order) */ KEY_BRL_DOT3, KEY_BRL_DOT2, KEY_BRL_DOT1, @@ -55,6 +55,21 @@ static const unsigned short keycode_kore[] = { KEY_BRL_DOT5 }; +/* index of the first touch sensor in keycode_kore */ +#define KORE_TOUCH_KEYS 24 + +/* the first Kore controller reports its touch sensors in this order */ +static const unsigned short keycode_kore1_touch[] = { + KEY_BRL_DOT4, + KEY_BRL_DOT8, + KEY_BRL_DOT3, + KEY_BRL_DOT7, + KEY_BRL_DOT2, + KEY_BRL_DOT6, + KEY_BRL_DOT1, + KEY_BRL_DOT5 +}; + #define MASCHINE_BUTTONS (42) #define MASCHINE_BUTTON(X) ((X) + BTN_MISC) #define MASCHINE_PADS (16) @@ -666,6 +681,12 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev) input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC); BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_kore)); memcpy(cdev->keycode, keycode_kore, sizeof(keycode_kore)); + BUILD_BUG_ON(KORE_TOUCH_KEYS + ARRAY_SIZE(keycode_kore1_touch) > + ARRAY_SIZE(keycode_kore)); + if (cdev->chip.usb_id == + USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER)) + memcpy(cdev->keycode + KORE_TOUCH_KEYS, keycode_kore1_touch, + sizeof(keycode_kore1_touch)); input->keycodemax = ARRAY_SIZE(keycode_kore); input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10); input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10); From 7a0b09d72d3710a6a5534c30505de657bec8a41d Mon Sep 17 00:00:00 2001 From: Niko Huuskonen Date: Sun, 27 Sep 2026 03:35:31 +0300 Subject: [PATCH 1355/1417] ALSA: uapi: Add hwdep interface ID for caiaq devices Add SNDRV_HWDEP_IFACE_CAIAQ for the hwdep device that snd-usb-caiaq creates for the LCD of the Native Instruments Kore controllers, added by the next patch. Assisted-by: LLM Signed-off-by: Niko Huuskonen Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260927003532.289468-4-niko.huuskonen.00@gmail.com --- include/uapi/sound/asound.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index c11da9656e3811..767c9627324592 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -99,9 +99,10 @@ enum { SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */ SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */ SNDRV_HWDEP_IFACE_FW_FIREFACE, /* RME Fireface series */ + SNDRV_HWDEP_IFACE_CAIAQ, /* Native Instruments caiaq USB devices */ /* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_CAIAQ }; struct snd_hwdep_info { From d243bb01c86cf7e6aaec53bf0eb8bac8c92ecffa Mon Sep 17 00:00:00 2001 From: Niko Huuskonen Date: Sun, 27 Sep 2026 03:35:32 +0300 Subject: [PATCH 1356/1417] ALSA: caiaq: Add LCD support for the Kore controllers Both Kore controllers have a 128x64 pixel monochrome LCD. The driver does not support it, and userspace cannot reach it while the driver is bound, so the display stays blank on Linux. The firmware passes EP1 packets with the command byte 0x08 on to an ST7565-style display controller: "08 00 " carries controller commands, "08 01 " display RAM data. The vendor software sets the controller up, then writes each 128 byte page in blocks of 32 bytes, each preceded by page and column address commands. The OpenKoreBridge project documented this from USB captures of the vendor software with a Kore 2. Add a hwdep device, "Kore LCD", for both controllers. A write carries one frame of 1024 bytes: 8 pages of 128 columns, with bit 0 as the top pixel of each page. The driver sets the controller up on the first write and afterwards only sends the pages that changed. The device is exclusive, so frames from different writers cannot interleave. Add an "LCD Contrast" control (0-63); the backlight is already the "LED lcd" control. Tested on a Kore controller (USB ID 17cc:4711): full frames, frames that change single pages and contrast changes show up as expected, while audio, MIDI, input and the LEDs keep working. A Kore 2 was not available for testing; it gets the protocol that OpenKoreBridge uses with it. Link: https://github.com/OpenKoreBridge/OpenKoreBridge Assisted-by: LLM Signed-off-by: Niko Huuskonen Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260927003532.289468-5-niko.huuskonen.00@gmail.com --- sound/usb/caiaq/Makefile | 2 +- sound/usb/caiaq/device.c | 7 ++ sound/usb/caiaq/device.h | 10 ++ sound/usb/caiaq/lcd.c | 258 +++++++++++++++++++++++++++++++++++++++ sound/usb/caiaq/lcd.h | 7 ++ 5 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 sound/usb/caiaq/lcd.c create mode 100644 sound/usb/caiaq/lcd.h diff --git a/sound/usb/caiaq/Makefile b/sound/usb/caiaq/Makefile index 9a99c17a2c1b17..ffa5a7edf95c0a 100644 --- a/sound/usb/caiaq/Makefile +++ b/sound/usb/caiaq/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -snd-usb-caiaq-y := device.o audio.o midi.o control.o +snd-usb-caiaq-y := device.o audio.o midi.o control.o lcd.o snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += input.o obj-$(CONFIG_SND_USB_CAIAQ) += snd-usb-caiaq.o diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index e8619bb7435812..dea3a68ca896ee 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -22,6 +22,7 @@ #include "midi.h" #include "control.h" #include "input.h" +#include "lcd.h" MODULE_AUTHOR("Daniel Mack "); MODULE_DESCRIPTION("caiaq USB audio"); @@ -387,6 +388,12 @@ static int setup_card(struct snd_usb_caiaqdev *cdev) } #endif + ret = snd_usb_caiaq_lcd_init(cdev); + if (ret < 0) { + dev_err(dev, "Unable to set up LCD (ret=%d)\n", ret); + return ret; + } + /* finally, register the card and all its sub-instances */ ret = snd_card_register(cdev->chip.card); if (ret < 0) { diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index 822d872c8ad1a1..dd726ca68e804b 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -30,6 +30,8 @@ #define MODNAME "snd-usb-caiaq" +#define CAIAQ_LCD_FRAME_SIZE 1024 + #define EP1_CMD_GET_DEVICE_INFO 0x1 #define EP1_CMD_READ_ERP 0x2 #define EP1_CMD_READ_ANALOG 0x3 @@ -37,6 +39,7 @@ #define EP1_CMD_WRITE_IO 0x5 #define EP1_CMD_MIDI_READ 0x6 #define EP1_CMD_MIDI_WRITE 0x7 +#define EP1_CMD_LCD 0x8 #define EP1_CMD_AUDIO_PARAMS 0x9 #define EP1_CMD_AUTO_MSG 0xb #define EP1_CMD_DIMM_LEDS 0xc @@ -101,6 +104,13 @@ struct snd_usb_caiaqdev { unsigned char control_state[256]; unsigned char ep8_out_buf[2]; + /* Kore LCD */ + struct mutex lcd_mutex; /* protects the lcd_* fields */ + unsigned char lcd_frame[CAIAQ_LCD_FRAME_SIZE]; /* shown frame */ + unsigned char lcd_contrast; + bool lcd_ready; /* controller set up */ + bool lcd_frame_valid; /* lcd_frame matches the display */ + /* Linux input */ #ifdef CONFIG_SND_USB_CAIAQ_INPUT struct input_dev *input_dev; diff --git a/sound/usb/caiaq/lcd.c b/sound/usb/caiaq/lcd.c new file mode 100644 index 00000000000000..dd2a3b949a43e6 --- /dev/null +++ b/sound/usb/caiaq/lcd.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LCD support for the Native Instruments Kore controllers + * + * Copyright (c) 2026 Niko Huuskonen + * + * Both Kore controllers have a 128x64 pixel monochrome LCD behind an + * ST7565-style controller. The firmware passes EP1 packets with the + * command byte EP1_CMD_LCD on to that controller: + * + * EP1_CMD_LCD 0x00 + * EP1_CMD_LCD 0x01 + * + * The display is exposed as an exclusive hwdep device. Each write carries + * a whole frame of CAIAQ_LCD_FRAME_SIZE bytes: 8 pages of 128 columns, + * one byte per column and page, with bit 0 as the top pixel of the page. + * Only the pages that changed since the previous frame are sent to the + * device. The contrast is an ALSA control; the backlight is the existing + * "LED lcd" control. + * + * The controller setup and the packet layout follow the USB traffic of + * the vendor software, as documented by the OpenKoreBridge project. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "lcd.h" + +#define LCD_WIDTH 128 +#define LCD_PAGES (CAIAQ_LCD_FRAME_SIZE / LCD_WIDTH) +#define LCD_COLUMN_OFFSET 4 /* first visible controller column */ +#define LCD_CHUNK 32 /* display RAM bytes per packet */ +#define LCD_CONTRAST_MAX 0x3f +#define LCD_CONTRAST_DEFAULT 0x1c + +#define LCD_KIND_COMMAND 0x00 +#define LCD_KIND_DATA 0x01 + +static int lcd_send(struct snd_usb_caiaqdev *cdev, u8 kind, + const u8 *bytes, unsigned int len) +{ + u8 buf[2 + LCD_CHUNK]; + + if (WARN_ON(len > LCD_CHUNK)) + return -EINVAL; + + buf[0] = kind; + buf[1] = len; + memcpy(buf + 2, bytes, len); + return snd_usb_caiaq_send_command(cdev, EP1_CMD_LCD, buf, len + 2); +} + +static int lcd_command(struct snd_usb_caiaqdev *cdev, u8 cmd) +{ + return lcd_send(cdev, LCD_KIND_COMMAND, &cmd, 1); +} + +static int lcd_set_contrast(struct snd_usb_caiaqdev *cdev) +{ + const u8 cmd[] = { 0x81, cdev->lcd_contrast }; + + return lcd_send(cdev, LCD_KIND_COMMAND, cmd, sizeof(cmd)); +} + +/* controller setup, in the order used by the vendor software */ +static int lcd_setup(struct snd_usb_caiaqdev *cdev) +{ + static const u8 head[] = { + 0xe2, /* reset */ + 0xa1, /* reverse column direction */ + 0xc8, /* reverse row direction */ + 0xa2, /* 1/9 bias */ + 0x2c, 0x2e, 0x2f, /* power up in three steps */ + 0x27, /* regulator resistor ratio */ + }; + static const u8 tail[] = { + 0xa6, /* normal, non-inverted display */ + 0x88, 0xef, /* sent by the vendor software */ + 0xaf, /* display on */ + }; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(head); i++) { + ret = lcd_command(cdev, head[i]); + if (ret) + return ret; + } + + ret = lcd_set_contrast(cdev); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(tail); i++) { + ret = lcd_command(cdev, tail[i]); + if (ret) + return ret; + } + + return 0; +} + +static int lcd_write_page(struct snd_usb_caiaqdev *cdev, unsigned int page, + const u8 *data) +{ + unsigned int col; + int ret; + + for (col = 0; col < LCD_WIDTH; col += LCD_CHUNK) { + unsigned int addr = LCD_COLUMN_OFFSET + col; + const u8 column[] = { 0x10 | (addr >> 4), addr & 0x0f }; + + ret = lcd_command(cdev, 0xb0 | page); + if (!ret) + ret = lcd_send(cdev, LCD_KIND_COMMAND, + column, sizeof(column)); + if (!ret) + ret = lcd_send(cdev, LCD_KIND_DATA, + data + col, LCD_CHUNK); + if (ret) + return ret; + } + + return 0; +} + +static long lcd_hwdep_write(struct snd_hwdep *hw, const char __user *buf, + long count, loff_t *offset) +{ + struct snd_usb_caiaqdev *cdev = hw->private_data; + unsigned int page; + int ret; + + if (count != CAIAQ_LCD_FRAME_SIZE) + return -EINVAL; + + u8 *frame __free(kfree) = memdup_user(buf, count); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + guard(mutex)(&cdev->lcd_mutex); + + if (!cdev->lcd_ready) { + ret = lcd_setup(cdev); + if (ret) + return ret; + cdev->lcd_ready = true; + cdev->lcd_frame_valid = false; + } + + for (page = 0; page < LCD_PAGES; page++) { + u8 *shown = cdev->lcd_frame + page * LCD_WIDTH; + const u8 *next = frame + page * LCD_WIDTH; + + if (cdev->lcd_frame_valid && !memcmp(shown, next, LCD_WIDTH)) + continue; + + ret = lcd_write_page(cdev, page, next); + if (ret) { + cdev->lcd_frame_valid = false; + return ret; + } + memcpy(shown, next, LCD_WIDTH); + } + cdev->lcd_frame_valid = true; + + return count; +} + +static int lcd_contrast_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = LCD_CONTRAST_MAX; + return 0; +} + +static int lcd_contrast_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_caiaqdev *cdev = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&cdev->lcd_mutex); + ucontrol->value.integer.value[0] = cdev->lcd_contrast; + return 0; +} + +static int lcd_contrast_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_caiaqdev *cdev = snd_kcontrol_chip(kcontrol); + long val = ucontrol->value.integer.value[0]; + int ret; + + if (val < 0 || val > LCD_CONTRAST_MAX) + return -EINVAL; + + guard(mutex)(&cdev->lcd_mutex); + if (val == cdev->lcd_contrast) + return 0; + + cdev->lcd_contrast = val; + if (cdev->lcd_ready) { + ret = lcd_set_contrast(cdev); + if (ret) + return ret; + } + + return 1; +} + +static const struct snd_kcontrol_new lcd_contrast_control = { + .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, + .name = "LCD Contrast", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = lcd_contrast_info, + .get = lcd_contrast_get, + .put = lcd_contrast_put, +}; + +int snd_usb_caiaq_lcd_init(struct snd_usb_caiaqdev *cdev) +{ + struct snd_card *card = cdev->chip.card; + struct snd_hwdep *hw; + int ret; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + break; + default: + return 0; + } + + mutex_init(&cdev->lcd_mutex); + cdev->lcd_contrast = LCD_CONTRAST_DEFAULT; + + ret = snd_hwdep_new(card, "Kore LCD", 0, &hw); + if (ret < 0) + return ret; + + strscpy(hw->name, "Kore LCD", sizeof(hw->name)); + hw->iface = SNDRV_HWDEP_IFACE_CAIAQ; + hw->private_data = cdev; + hw->exclusive = 1; + hw->ops.write = lcd_hwdep_write; + + return snd_ctl_add(card, snd_ctl_new1(&lcd_contrast_control, cdev)); +} diff --git a/sound/usb/caiaq/lcd.h b/sound/usb/caiaq/lcd.h new file mode 100644 index 00000000000000..4234647502780b --- /dev/null +++ b/sound/usb/caiaq/lcd.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_LCD_H +#define CAIAQ_LCD_H + +int snd_usb_caiaq_lcd_init(struct snd_usb_caiaqdev *cdev); + +#endif /* CAIAQ_LCD_H */ From e8c263e0ef957fd8286e299b2be9266b902cec16 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:38:57 +0700 Subject: [PATCH 1357/1417] ASoC: mediatek: mt8192: fix APLL mux error handling Propagate errors from clock operations in the enable path and clean up previously enabled clocks when a later operation fails. For the disable path, continue disabling all clocks even if a clock operation fails. Log the error without returning it, so the disable path can perform as much cleanup as possible. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-clk.c | 54 +++++++++++++--------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index 416aff72625399..bd778e5663b562 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -87,7 +87,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], ret); - goto EXIT; + return ret; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], afe_priv->clk[CLK_TOP_APLL1_CK]); @@ -95,7 +95,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_TOP_APLL1_CK], ret); - goto EXIT; + goto err_disable_mux_aud_1; } /* 180.6336 / 4 = 45.1584MHz */ @@ -103,7 +103,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], ret); - goto EXIT; + goto err_set_parent_mux_aud_1; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1], afe_priv->clk[CLK_TOP_APLL1_D4]); @@ -111,31 +111,36 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_TOP_APLL1_D4], ret); - goto EXIT; + goto err_disable_mux_aud_eng1; } } else { ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1], afe_priv->clk[CLK_CLK26M]); - if (ret) { + if (ret) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_CLK26M], ret); - goto EXIT; - } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], afe_priv->clk[CLK_CLK26M]); - if (ret) { + if (ret) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_CLK26M], ret); - goto EXIT; - } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); } -EXIT: + return 0; + +err_disable_mux_aud_eng1: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); +err_set_parent_mux_aud_1: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], + afe_priv->clk[CLK_CLK26M]); +err_disable_mux_aud_1: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); + return ret; } @@ -149,7 +154,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], ret); - goto EXIT; + return ret; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], afe_priv->clk[CLK_TOP_APLL2_CK]); @@ -157,7 +162,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_TOP_APLL2_CK], ret); - goto EXIT; + goto err_disable_mux_aud_2; } /* 196.608 / 4 = 49.152MHz */ @@ -165,7 +170,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], ret); - goto EXIT; + goto err_set_parent_mux_aud_2; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2], afe_priv->clk[CLK_TOP_APLL2_D4]); @@ -173,31 +178,36 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_TOP_APLL2_D4], ret); - goto EXIT; + goto err_disable_mux_aud_eng2; } } else { ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2], afe_priv->clk[CLK_CLK26M]); - if (ret) { + if (ret) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_CLK26M], ret); - goto EXIT; - } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], afe_priv->clk[CLK_CLK26M]); - if (ret) { + if (ret) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_CLK26M], ret); - goto EXIT; - } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); } -EXIT: + return 0; + +err_disable_mux_aud_eng2: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); +err_set_parent_mux_aud_2: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], + afe_priv->clk[CLK_CLK26M]); +err_disable_mux_aud_2: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); + return ret; } From 7c54781e6e60681af8d0858148d613becb582328 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:38:58 +0700 Subject: [PATCH 1358/1417] ASoC: mediatek: mt8192: fix AFE clock error handling If a clock operation fails in mt8192_afe_enable_clock(), the function returns immediately via the EXIT label without disabling the clocks that were successfully enabled earlier in the sequence. This leaves unneeded clocks running and causes a resource leak. Properly unwind clocks enabled before an error by jumping to appropriate cleanup labels on failure. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-clk.c | 28 +++++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index bd778e5663b562..7647bdd463d9cd 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -220,21 +220,21 @@ int mt8192_afe_enable_clock(struct mtk_base_afe *afe) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_INFRA_SYS_AUDIO], ret); - goto EXIT; + return ret; } ret = clk_prepare_enable(afe_priv->clk[CLK_INFRA_AUDIO_26M]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_INFRA_AUDIO_26M], ret); - goto EXIT; + goto err_disable_infra_sys_audio; } ret = clk_prepare_enable(afe_priv->clk[CLK_MUX_AUDIO]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIO], ret); - goto EXIT; + goto err_disable_infra_audio_26m; } ret = clk_set_parent(afe_priv->clk[CLK_MUX_AUDIO], afe_priv->clk[CLK_CLK26M]); @@ -242,14 +242,14 @@ int mt8192_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIO], aud_clks[CLK_CLK26M], ret); - goto EXIT; + goto err_disable_mux_audio; } ret = clk_prepare_enable(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIOINTBUS], ret); - goto EXIT; + goto err_disable_mux_audio; } ret = mt8192_set_audio_int_bus_parent(afe, CLK_CLK26M); @@ -257,7 +257,7 @@ int mt8192_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIOINTBUS], aud_clks[CLK_CLK26M], ret); - goto EXIT; + goto err_disable_mux_audiointbus; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUDIO_H], @@ -266,17 +266,27 @@ int mt8192_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUDIO_H], aud_clks[CLK_TOP_APLL2_CK], ret); - goto EXIT; + goto err_disable_mux_audiointbus; } ret = clk_prepare_enable(afe_priv->clk[CLK_AFE]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_AFE], ret); - goto EXIT; + goto err_disable_mux_audiointbus; } -EXIT: + return 0; + +err_disable_mux_audiointbus: + clk_disable_unprepare(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); +err_disable_mux_audio: + clk_disable_unprepare(afe_priv->clk[CLK_MUX_AUDIO]); +err_disable_infra_audio_26m: + clk_disable_unprepare(afe_priv->clk[CLK_INFRA_AUDIO_26M]); +err_disable_infra_sys_audio: + clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUDIO]); + return ret; } From 88760524ca995acd229c30ec69ced7d883a90a72 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:38:59 +0700 Subject: [PATCH 1359/1417] ASoC: mediatek: mt8192: fix error handling in APLL enable functions If a clock operation fails in mt8192_apll1_enable() or apll2_enable(), the functions return without checking the return value of mux settings or unwinding previously enabled clocks and MUX configurations. Fix this by checking the return value of apll1_mux_setting() and apll2_mux_setting(), and adding proper unwind handling on failure. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-clk.c | 32 ++++++++++++++++------ 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index 7647bdd463d9cd..118dd481968280 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -308,20 +308,22 @@ int mt8192_apll1_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll1_mux_setting(afe, true); + ret = apll1_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL22M]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_APLL22M], ret); - goto EXIT; + goto err_disable_mux_setting; } ret = clk_prepare_enable(afe_priv->clk[CLK_APLL1_TUNER]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_APLL1_TUNER], ret); - goto EXIT; + goto err_disable_apll22m; } regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, @@ -332,7 +334,13 @@ int mt8192_apll1_enable(struct mtk_base_afe *afe) AFE_22M_ON_MASK_SFT, 0x1 << AFE_22M_ON_SFT); -EXIT: + return 0; + +err_disable_apll22m: + clk_disable_unprepare(afe_priv->clk[CLK_APLL22M]); +err_disable_mux_setting: + apll1_mux_setting(afe, false); + return ret; } @@ -358,20 +366,22 @@ int mt8192_apll2_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll2_mux_setting(afe, true); + ret = apll2_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL24M]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_APLL24M], ret); - goto EXIT; + goto err_disable_mux_setting; } ret = clk_prepare_enable(afe_priv->clk[CLK_APLL2_TUNER]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_APLL2_TUNER], ret); - goto EXIT; + goto err_disable_apll24m; } regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, @@ -382,7 +392,13 @@ int mt8192_apll2_enable(struct mtk_base_afe *afe) AFE_24M_ON_MASK_SFT, 0x1 << AFE_24M_ON_SFT); -EXIT: + return 0; + +err_disable_apll24m: + clk_disable_unprepare(afe_priv->clk[CLK_APLL24M]); +err_disable_mux_setting: + apll2_mux_setting(afe, false); + return ret; } From 4a0ab266cb1594f8c6aef3c79726476ad99618c5 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:00 +0700 Subject: [PATCH 1360/1417] ASoC: mediatek: mt8192: fix MCK clock error handling If an error occurs after enabling 'm_sel_id' or 'div_clk_id' in mt8192_mck_enable(), the function returns directly without disabling the previously enabled clocks, leading to a resource leak. Fix this by jumping to proper cleanup labels to unwind enabled clocks on failure paths. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-clk.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index 118dd481968280..77f596cab92dad 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -619,7 +619,7 @@ int mt8192_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) dev_err(afe->dev, "%s(), clk_set_parent %s-%s fail %d\n", __func__, aud_clks[m_sel_id], aud_clks[apll_clk_id], ret); - return ret; + goto err_disable_m_sel; } } @@ -628,17 +628,25 @@ int mt8192_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[div_clk_id], ret); - return ret; + goto err_disable_m_sel; } ret = clk_set_rate(afe_priv->clk[div_clk_id], rate); if (ret) { dev_err(afe->dev, "%s(), clk_set_rate %s, rate %d, fail %d\n", __func__, aud_clks[div_clk_id], rate, ret); - return ret; + goto err_disable_div_clk; } return 0; + +err_disable_div_clk: + clk_disable_unprepare(afe_priv->clk[div_clk_id]); +err_disable_m_sel: + if (m_sel_id >= 0) + clk_disable_unprepare(afe_priv->clk[m_sel_id]); + + return ret; } void mt8192_mck_disable(struct mtk_base_afe *afe, int mck_id) From 6f4edfdce0099dc0c346c78c85f58a23d4ee646e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:01 +0700 Subject: [PATCH 1361/1417] ASoC: mediatek: mt8192: switch to devm_clk_get_optional() Switch from devm_clk_get() to devm_clk_get_optional() when requesting clocks, as optional clocks automatically return NULL when not present instead of requiring manual check and NULL assignment. Additionally, handle clock errors properly using dev_err_probe() to propagate error codes (such as -EPROBE_DEFER) on failure instead of just printing a warning and continuing with NULL pointers. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-clk.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index 77f596cab92dad..9b9a3465ddb522 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -672,13 +672,10 @@ int mt8192_init_clock(struct mtk_base_afe *afe) return -ENOMEM; for (i = 0; i < CLK_NUM; i++) { - afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_warn(afe->dev, "%s devm_clk_get %s fail, ret %ld\n", - __func__, - aud_clks[i], PTR_ERR(afe_priv->clk[i])); - afe_priv->clk[i] = NULL; - } + afe_priv->clk[i] = devm_clk_get_optional(afe->dev, aud_clks[i]); + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } afe_priv->apmixedsys = syscon_regmap_lookup_by_phandle(of_node, From bed395cb305b16016dd39dc1fdcd0e47674d3132 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:02 +0700 Subject: [PATCH 1362/1417] ASoC: mediatek: mt8192: Use dev_err_probe() in mt8192_init_clock() Use dev_err_probe() when obtaining clocks to avoid redundant error messages, particularly for probe deferral. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-clk.c | 24 ++++++++-------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index 9b9a3465ddb522..c6ca4fe7fb1939 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -680,27 +680,21 @@ int mt8192_init_clock(struct mtk_base_afe *afe) afe_priv->apmixedsys = syscon_regmap_lookup_by_phandle(of_node, "mediatek,apmixedsys"); - if (IS_ERR(afe_priv->apmixedsys)) { - dev_err(afe->dev, "%s() Cannot find apmixedsys controller: %ld\n", - __func__, PTR_ERR(afe_priv->apmixedsys)); - return PTR_ERR(afe_priv->apmixedsys); - } + if (IS_ERR(afe_priv->apmixedsys)) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->apmixedsys), + "Cannot find apmixedsys controller\n"); afe_priv->topckgen = syscon_regmap_lookup_by_phandle(of_node, "mediatek,topckgen"); - if (IS_ERR(afe_priv->topckgen)) { - dev_err(afe->dev, "%s() Cannot find topckgen controller: %ld\n", - __func__, PTR_ERR(afe_priv->topckgen)); - return PTR_ERR(afe_priv->topckgen); - } + if (IS_ERR(afe_priv->topckgen)) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->topckgen), + "Cannot find topckgen controller\n"); afe_priv->infracfg = syscon_regmap_lookup_by_phandle(of_node, "mediatek,infracfg"); - if (IS_ERR(afe_priv->infracfg)) { - dev_err(afe->dev, "%s() Cannot find infracfg: %ld\n", - __func__, PTR_ERR(afe_priv->infracfg)); - return PTR_ERR(afe_priv->infracfg); - } + if (IS_ERR(afe_priv->infracfg)) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->infracfg), + "Cannot find infracfg\n"); return 0; } From 411be3902fa8f879db6f2f55d0c15507efe09c92 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:03 +0700 Subject: [PATCH 1363/1417] ASoC: mediatek: mt8192: Propagate errors in mt8192_afe_gpio_request() Currently, mt8192_afe_gpio_request() ignores the return values from mt8192_afe_gpio_select() and other helper functions. If configuring a GPIO state fails, the function still returns 0, hiding the failure from callers. Fix this by capturing and returning the error codes properly. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-gpio.c | 74 +++++++++++---------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c index b993ca2dbd7c83..ed6dfdf2589bbc 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c @@ -208,98 +208,104 @@ static int mt8192_afe_gpio_adda_ch34_ul(struct device *dev, bool enable) int mt8192_afe_gpio_request(struct device *dev, bool enable, int dai, int uplink) { + int ret; + guard(mutex)(&gpio_request_mutex); switch (dai) { case MT8192_DAI_ADDA: if (uplink) - mt8192_afe_gpio_adda_ul(dev, enable); + ret = mt8192_afe_gpio_adda_ul(dev, enable); else - mt8192_afe_gpio_adda_dl(dev, enable); + ret = mt8192_afe_gpio_adda_dl(dev, enable); break; case MT8192_DAI_ADDA_CH34: if (uplink) - mt8192_afe_gpio_adda_ch34_ul(dev, enable); + ret = mt8192_afe_gpio_adda_ch34_ul(dev, enable); else - mt8192_afe_gpio_adda_ch34_dl(dev, enable); + ret = mt8192_afe_gpio_adda_ch34_dl(dev, enable); break; case MT8192_DAI_I2S_0: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S0_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S0_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S0_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S0_OFF); break; case MT8192_DAI_I2S_1: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S1_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S1_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S1_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S1_OFF); break; case MT8192_DAI_I2S_2: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S2_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S2_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S2_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S2_OFF); break; case MT8192_DAI_I2S_3: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S3_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S3_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S3_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S3_OFF); break; case MT8192_DAI_I2S_5: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S5_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S5_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S5_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S5_OFF); break; case MT8192_DAI_I2S_6: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S6_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S6_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S6_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S6_OFF); break; case MT8192_DAI_I2S_7: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S7_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S7_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S7_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S7_OFF); break; case MT8192_DAI_I2S_8: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S8_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S8_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S8_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S8_OFF); break; case MT8192_DAI_I2S_9: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S9_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S9_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S9_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S9_OFF); break; case MT8192_DAI_TDM: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_TDM_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_TDM_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_TDM_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_TDM_OFF); break; case MT8192_DAI_VOW: if (enable) { - mt8192_afe_gpio_select(dev, - MT8192_AFE_GPIO_VOW_CLK_ON); - mt8192_afe_gpio_select(dev, - MT8192_AFE_GPIO_VOW_DAT_ON); + ret = mt8192_afe_gpio_select(dev, + MT8192_AFE_GPIO_VOW_CLK_ON); + if (ret) + break; + ret = mt8192_afe_gpio_select(dev, + MT8192_AFE_GPIO_VOW_DAT_ON); } else { - mt8192_afe_gpio_select(dev, - MT8192_AFE_GPIO_VOW_CLK_OFF); - mt8192_afe_gpio_select(dev, - MT8192_AFE_GPIO_VOW_DAT_OFF); + ret = mt8192_afe_gpio_select(dev, + MT8192_AFE_GPIO_VOW_CLK_OFF); + if (ret) + break; + ret = mt8192_afe_gpio_select(dev, + MT8192_AFE_GPIO_VOW_DAT_OFF); } break; default: dev_warn(dev, "%s(), invalid dai %d\n", __func__, dai); - return -EINVAL; + ret = -EINVAL; } - return 0; + return ret; } EXPORT_SYMBOL(mt8192_afe_gpio_request); From 59608878c59373c5f866000fff81be8eaea8a7ef Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:04 +0700 Subject: [PATCH 1364/1417] ASoC: mediatek: mt8192: Use dev_err_probe() for devm_pinctrl_get() Use dev_err_probe() when obtaining clocks to avoid redundant error messages, particularly for probe deferral. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-9-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-gpio.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c index ed6dfdf2589bbc..d4218f9708f7fd 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c @@ -132,12 +132,8 @@ int mt8192_afe_gpio_init(struct device *dev) int i, ret; aud_pinctrl = devm_pinctrl_get(dev); - if (IS_ERR(aud_pinctrl)) { - ret = PTR_ERR(aud_pinctrl); - dev_err(dev, "%s(), ret %d, cannot get aud_pinctrl!\n", - __func__, ret); - return ret; - } + if (IS_ERR(aud_pinctrl)) + return dev_err_probe(dev, PTR_ERR(aud_pinctrl), "cannot get aud_pinctrl!\n"); for (i = 0; i < ARRAY_SIZE(aud_gpios); i++) { aud_gpios[i].gpioctrl = pinctrl_lookup_state(aud_pinctrl, From 7cca184ad434436e8f4648f71756bab0c3593ed9 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:05 +0700 Subject: [PATCH 1365/1417] ASoC: mediatek: mt8192: check return values in mt8192_afe_gpio_init() Currently, mt8192_afe_gpio_init() ignores the return values of mt8192_afe_gpio_select() and mt8192_afe_gpio_request(). If configuring GPIO states or requesting GPIOs fails during initialization, the function still proceeds and returns 0, masking the initialization failure. Fix this by checking and propagating the return values properly. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-10-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-gpio.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c index d4218f9708f7fd..d2e99c55b8c61d 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c @@ -147,13 +147,17 @@ int mt8192_afe_gpio_init(struct device *dev) } } - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_CLK_MOSI_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_CLK_MOSI_ON); + if (ret) + return ret; /* gpio status init */ - mt8192_afe_gpio_request(dev, false, MT8192_DAI_ADDA, 0); - mt8192_afe_gpio_request(dev, false, MT8192_DAI_ADDA, 1); + ret = mt8192_afe_gpio_request(dev, false, MT8192_DAI_ADDA, 0); + if (ret) + return ret; + ret = mt8192_afe_gpio_request(dev, false, MT8192_DAI_ADDA, 1); - return 0; + return ret; } EXPORT_SYMBOL(mt8192_afe_gpio_init); From d9e264741b041fc9dffc555bc1ab68708658cefd Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:06 +0700 Subject: [PATCH 1366/1417] ASoC: mediatek: mt8192: Handle regcache_sync() failure in runtime resume Currently, the return value of regcache_sync() in mt8192_afe_runtime_resume() is ignored. If regcache_sync() fails, the function continues execution with clocks enabled and regcache out of sync. Fix this by checking the return value of regcache_sync(). On failure, restore regcache to cache-only mode and disable the clocks enabled earlier in the function. Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-11-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-pcm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c index db0ae44a86afea..5e633e9573a31f 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c @@ -2104,7 +2104,11 @@ static int mt8192_afe_runtime_resume(struct device *dev) goto skip_regmap; regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) { + regcache_cache_only(afe->regmap, true); + mt8192_afe_disable_clock(afe); + } /* enable audio sys DCM for power saving */ regmap_update_bits(afe_priv->infracfg, From d31c0d3eb4142347cb35486e8fc1fe68a4555987 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:07 +0700 Subject: [PATCH 1367/1417] ASoC: mediatek: mt8192: Remove redundant error message The errors handled here are already reported by the called functions, either directly or deeper in the call chain. Therefore, the additional dev_err() and dev_err_probe() call is redundant and can be removed. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-12-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-afe-pcm.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c index 5e633e9573a31f..a9b203af9039d5 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c @@ -2201,10 +2201,8 @@ static int mt8192_afe_pcm_dev_probe(struct platform_device *pdev) /* init audio related clock */ ret = mt8192_init_clock(afe); - if (ret) { - dev_err(dev, "init clock error\n"); + if (ret) return ret; - } /* reset controller to reset audio regs before regmap cache */ rstc = devm_reset_control_get_exclusive(dev, "audiosys"); @@ -2280,7 +2278,7 @@ static int mt8192_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt8192_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); From eed9414a1b075e2df308e7222e3786981bd42b32 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:08 +0700 Subject: [PATCH 1368/1417] ASoC: mediatek: mt8192: Propagate mt8192_afe_gpio_request() errors in ADDA DAI Currently, DAPM event handlers in mt8192-dai-adda.c ignore the return value of mt8192_afe_gpio_request(). If GPIO configuration fails during PRE_PMU or POST_PMD events, the error is silently ignored. Fix this by checking and propagating error codes from mt8192_afe_gpio_request() in all ADDA DAPM event handlers. Fixes: 607ac4859564 ("ASoC: mediatek: mt8192: support adda in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-13-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-dai-adda.c | 44 +++++++++++++++------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-dai-adda.c b/sound/soc/mediatek/mt8192/mt8192-dai-adda.c index f8cb84621d386f..c5c726a6e4230d 100644 --- a/sound/soc/mediatek/mt8192/mt8192-dai-adda.c +++ b/sound/soc/mediatek/mt8192/mt8192-dai-adda.c @@ -218,10 +218,13 @@ static int mtk_adda_ul_event(struct snd_soc_dapm_widget *w, struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mt8192_afe_private *afe_priv = afe->platform_priv; int mtkaif_dmic = afe_priv->mtkaif_dmic; + int ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1); + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1); + if (ret) + return ret; /* update setting to dmic */ if (mtkaif_dmic) { @@ -239,7 +242,9 @@ static int mtk_adda_ul_event(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_POST_PMD: /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ usleep_range(125, 135); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 1); + ret = mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 1); + if (ret) + return ret; break; default: break; @@ -257,11 +262,14 @@ static int mtk_adda_ch34_ul_event(struct snd_soc_dapm_widget *w, struct mt8192_afe_private *afe_priv = afe->platform_priv; int mtkaif_dmic = afe_priv->mtkaif_dmic_ch34; int mtkaif_adda6_only = afe_priv->mtkaif_adda6_only; + int ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, - 1); + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, + 1); + if (ret) + return ret; /* update setting to dmic */ if (mtkaif_dmic) { @@ -291,8 +299,10 @@ static int mtk_adda_ch34_ul_event(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_POST_PMD: /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ usleep_range(125, 135); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, - 1); + ret = mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, + 1); + if (ret) + return ret; /* reset dmic */ afe_priv->mtkaif_dmic_ch34 = 0; @@ -446,15 +456,20 @@ static int mtk_adda_dl_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0); + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ usleep_range(125, 135); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 0); + ret = mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 0); + if (ret) + return ret; break; default: break; @@ -469,17 +484,22 @@ static int mtk_adda_ch34_dl_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, - 0); + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, + 0); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ usleep_range(125, 135); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, - 0); + ret = mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, + 0); + if (ret) + return ret; break; default: break; From d0bc9613bc2d9e20a7e4f3e447cd9427c95d194a Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:09 +0700 Subject: [PATCH 1369/1417] ASoC: mediatek: mt8192: Propagate errors in TDM DAI DAPM event handlers Currently, DAPM event handlers in mt8192-dai-tdm.c ignore return values from mt8192_afe_gpio_request() and mt8192_mck_enable(). If GPIO configuration or master clock setup fails during DAPM events, the error is silently ignored. Fix this by checking and propagating error codes properly in all TDM DAI DAPM event handlers. Fixes: 52fcd65414ab ("ASoC: mediatek: mt8192: support tdm in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-14-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-dai-tdm.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-dai-tdm.c b/sound/soc/mediatek/mt8192/mt8192-dai-tdm.c index 49440db370af07..36fa0f32ed1cb4 100644 --- a/sound/soc/mediatek/mt8192/mt8192-dai-tdm.c +++ b/sound/soc/mediatek/mt8192/mt8192-dai-tdm.c @@ -250,6 +250,7 @@ static int mtk_tdm_en_event(struct snd_soc_dapm_widget *w, struct mt8192_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; if (!tdm_priv) { dev_warn(afe->dev, "%s(), tdm_priv == NULL", __func__); @@ -261,16 +262,17 @@ static int mtk_tdm_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, tdm_priv->id, 0); + ret = mt8192_afe_gpio_request(afe->dev, true, tdm_priv->id, 0); break; case SND_SOC_DAPM_POST_PMD: - mt8192_afe_gpio_request(afe->dev, false, tdm_priv->id, 0); + ret = mt8192_afe_gpio_request(afe->dev, false, tdm_priv->id, 0); break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, @@ -282,6 +284,7 @@ static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, struct mt8192_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; if (!tdm_priv) { dev_warn(afe->dev, "%s(), tdm_priv == NULL", __func__); @@ -293,7 +296,9 @@ static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); + ret = mt8192_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: mt8192_mck_disable(afe, tdm_priv->bck_id); @@ -314,6 +319,7 @@ static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, struct mt8192_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; if (!tdm_priv) { dev_warn(afe->dev, "%s(), tdm_priv == NULL", __func__); @@ -325,7 +331,9 @@ static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); + ret = mt8192_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: tdm_priv->mclk_rate = 0; From 49aeb628d10ef3d5694c39b7a3427d34bde06bb2 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:10 +0700 Subject: [PATCH 1370/1417] ASoC: mediatek: mt8192: Propagate errors in I2S DAI DAPM event handlers Currently, DAPM event handlers in mt8192-dai-i2s.c ignore return values from mt8192_afe_gpio_request(), mt8192_apll1_enable(), mt8192_apll2_enable(), and mt8192_mck_enable(). If GPIO configuration or clock setup fails during DAPM events, the error is silently ignored. Fix this by checking and propagating error codes properly in all I2S DAI DAPM event handlers. Fixes: 2c37b4ed730b ("ASoC: mediatek: mt8192: support i2s in platform driver") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-15-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-dai-i2s.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-dai-i2s.c b/sound/soc/mediatek/mt8192/mt8192-dai-i2s.c index 1632fc94776dab..5608b534af0d96 100644 --- a/sound/soc/mediatek/mt8192/mt8192-dai-i2s.c +++ b/sound/soc/mediatek/mt8192/mt8192-dai-i2s.c @@ -586,6 +586,7 @@ static int mtk_i2s_en_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mtk_afe_i2s_priv *i2s_priv; + int ret; i2s_priv = get_i2s_priv_by_name(afe, w->name); @@ -599,16 +600,17 @@ static int mtk_i2s_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, i2s_priv->id, 0); + ret = mt8192_afe_gpio_request(afe->dev, true, i2s_priv->id, 0); break; case SND_SOC_DAPM_POST_PMD: - mt8192_afe_gpio_request(afe->dev, false, i2s_priv->id, 0); + ret = mt8192_afe_gpio_request(afe->dev, false, i2s_priv->id, 0); break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_apll_event(struct snd_soc_dapm_widget *w, @@ -617,6 +619,7 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", __func__, w->name, event); @@ -624,9 +627,11 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: if (snd_soc_dapm_widget_name_cmp(w, APLL1_W_NAME) == 0) - mt8192_apll1_enable(afe); + ret = mt8192_apll1_enable(afe); else - mt8192_apll2_enable(afe); + ret = mt8192_apll2_enable(afe); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: if (snd_soc_dapm_widget_name_cmp(w, APLL1_W_NAME) == 0) @@ -704,6 +709,7 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mtk_afe_i2s_priv *i2s_priv; + int ret; dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", __func__, w->name, event); @@ -716,7 +722,9 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); + ret = mt8192_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: i2s_priv->mclk_rate = 0; From d6c8a0b5bf696d89350e43bf554362de8929f801 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 20:39:11 +0700 Subject: [PATCH 1371/1417] ASoC: mediatek: mt8192-mt6359: Fix error handling in MTKAIF calibration mt8192_mt6359_mtkaif_calibration() ignored the return values of pm_runtime_get_sync() and mt8192_afe_gpio_request(), which could leave PM runtime references or GPIO state unbalanced on failure. Switch to pm_runtime_resume_and_get(), check each GPIO request, and unwind on error. Fixes: 18b13ff23fab ("ASoC: mediatek: mt8192: add machine driver with mt6359, rt1015 and rt5682") Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260918133912.133799-16-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- .../mt8192/mt8192-mt6359-rt1015-rt5682.c | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c index c8634869164145..b010726e9b5732 100644 --- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c @@ -163,12 +163,23 @@ static int mt8192_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) int chosen_phase_1, chosen_phase_2, chosen_phase_3; int counter; int mtkaif_calib_ok; + int ret = 0; - pm_runtime_get_sync(afe->dev); - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1); - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0); - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, 1); - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, 0); + ret = pm_runtime_resume_and_get(afe->dev); + if (ret < 0) + return ret; + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1); + if (ret) + goto err_pm_put; + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0); + if (ret) + goto err_disable_adda_1; + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, 1); + if (ret) + goto err_disable_adda_0; + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, 0); + if (ret) + goto err_disable_adda_ch34_1; mt6359_mtkaif_calibration_enable(cmpnt_codec); @@ -289,10 +300,14 @@ static int mt8192_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) mt6359_mtkaif_calibration_disable(cmpnt_codec); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 1); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 0); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, 1); mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, 0); +err_disable_adda_ch34_1: + mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, 1); +err_disable_adda_0: + mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 0); +err_disable_adda_1: + mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 1); +err_pm_put: pm_runtime_put(afe->dev); dev_dbg(afe->dev, "%s(), mtkaif_chosen_phase[0/1/2]:%d/%d/%d\n", @@ -301,7 +316,7 @@ static int mt8192_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) afe_priv->mtkaif_chosen_phase[1], afe_priv->mtkaif_chosen_phase[2]); - return 0; + return ret; } static int mt8192_mt6359_init(struct snd_soc_pcm_runtime *rtd) @@ -319,9 +334,7 @@ static int mt8192_mt6359_init(struct snd_soc_pcm_runtime *rtd) afe_priv->mtkaif_protocol = MTKAIF_PROTOCOL_2_CLK_P2; /* mtkaif calibration */ - mt8192_mt6359_mtkaif_calibration(rtd); - - return 0; + return mt8192_mt6359_mtkaif_calibration(rtd); } static int mt8192_rt5682_init(struct snd_soc_pcm_runtime *rtd) From 7845324094fcf88060170b44661519e57b179b61 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Mon, 28 Sep 2026 14:11:45 +0800 Subject: [PATCH 1372/1417] ASoC: dt-bindings: fsl,imx-asrc: update port binding to support multiple paths All i.MX ASRC variants (imx35 through imx952) support up to three conversion pairs (A, B, C). The existing binding exposed only a single generic audio-graph port, which cannot represent independent conversion paths when multiple pairs are in use simultaneously. Add a ports container that enumerates up to three sub-ports, each representing an independent conversion path: port@0 -- conversion path 0 port@1 -- conversion path 1 port@2 -- conversion path 2 Each sub-port references audio-graph-port.yaml as before. Hardware pairs are allocated dynamically at stream open time; each active stream direction (playback or capture) on a port consumes one hardware pair. For backwards compatibility, keep the old single port property but mark it deprecated. A schema constraint (not: required: [port, ports]) ensures that both forms cannot be used at the same time. The binding example is updated to show the preferred ports form. Signed-off-by: Shengjiu Wang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260928061149.1844121-2-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- .../bindings/sound/fsl,imx-asrc.yaml | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml b/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml index cd69bad107c793..67d2826eef3fb5 100644 --- a/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml @@ -78,9 +78,19 @@ properties: power-domains: maxItems: 1 + ports: + $ref: /schemas/graph.yaml#/properties/ports + patternProperties: + "^port@[0-2]$": + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: port for an independent conversion path + port: $ref: audio-graph-port.yaml# unevaluatedProperties: false + deprecated: true + description: deprecated, use ports instead fsl,asrc-rate: $ref: /schemas/types.yaml#/definitions/uint32 @@ -125,6 +135,10 @@ required: - fsl,asrc-width allOf: + - not: + required: + - port + - ports - $ref: dai-common.yaml# - if: properties: @@ -180,11 +194,32 @@ examples: fsl,asrc-rate = <48000>; fsl,asrc-width = <16>; - port { - playback-only; + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + playback-only; + endpoint { + remote-endpoint = <&fe00_ep>; + }; + }; + + port@1 { + reg = <1>; + capture-only; + endpoint { + remote-endpoint = <&fe01_ep>; + }; + }; - asrc_endpoint: endpoint { - remote-endpoint = <&fe00_ep>; + port@2 { + reg = <2>; + capture-only; + endpoint { + remote-endpoint = <&fe02_ep>; + }; }; }; }; From 33598785b31c1a550e193b7c7dc5402fa5d04a13 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Mon, 28 Sep 2026 14:11:46 +0800 Subject: [PATCH 1373/1417] ASoC: dt-bindings: fsl,easrc: add ports binding for multiple conversion paths The i.MX EASRC hardware supports up to four conversion contexts (A, B, C, D). Add a ports container property to the binding to allow each independent conversion path to be represented as an individual audio-graph port: port@0 -- conversion path 0 port@1 -- conversion path 1 port@2 -- conversion path 2 port@3 -- conversion path 3 Each sub-port references audio-graph-port.yaml and follows the standard audio-graph binding conventions. Contexts are allocated dynamically at stream open time; each active stream direction (playback or capture) on a port consumes one hardware context. Signed-off-by: Shengjiu Wang Reviewed-by: Frank Li Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260928061149.1844121-3-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/fsl,easrc.yaml | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/fsl,easrc.yaml b/Documentation/devicetree/bindings/sound/fsl,easrc.yaml index d5727f8bfb0b56..a53ec4f6f1c08e 100644 --- a/Documentation/devicetree/bindings/sound/fsl,easrc.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,easrc.yaml @@ -55,6 +55,14 @@ properties: - const: imx/easrc/easrc-imx8mn.bin description: The coefficient table for the filters + ports: + $ref: /schemas/graph.yaml#/properties/ports + patternProperties: + "^port@[0-3]$": + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: port for an independent conversion path + fsl,asrc-rate: $ref: /schemas/types.yaml#/definitions/uint32 minimum: 8000 @@ -106,4 +114,41 @@ examples: firmware-name = "imx/easrc/easrc-imx8mn.bin"; fsl,asrc-rate = <8000>; fsl,asrc-format = <2>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + playback-only; + endpoint { + remote-endpoint = <&fe00_ep>; + }; + }; + + port@1 { + reg = <1>; + playback-only; + endpoint { + remote-endpoint = <&fe01_ep>; + }; + }; + + port@2 { + reg = <2>; + capture-only; + endpoint { + remote-endpoint = <&fe02_ep>; + }; + }; + + port@3 { + reg = <3>; + capture-only; + endpoint { + remote-endpoint = <&fe03_ep>; + }; + }; + }; }; From 76915128bde26af7e357dfd4501f5e329d1364be Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Mon, 28 Sep 2026 14:11:47 +0800 Subject: [PATCH 1374/1417] ASoC: fsl_asrc/fsl_easrc: move DMA params into pair/context struct The shared dma_params_tx/rx fields in struct fsl_asrc were a single instance written by every concurrent fsl_asrc_dma_hw_params() call. With multiple DAIs registered (one per pair/context), parallel stream open+hw_params races would corrupt addr and maxburst for all active streams. Fix by moving dma_params into struct fsl_asrc_pair so each pair owns its own copy. Initialise them in fsl_asrc_dma_startup() where the pair is allocated, which makes the per-DAI probe callbacks in fsl_asrc.c and fsl_easrc.c redundant; remove those as well. Signed-off-by: Shengjiu Wang Reviewed-by: Frank Li Reviewed-by: Daniel Baluta Link: https://patch.msgid.link/20260928061149.1844121-4-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc.c | 11 ----------- sound/soc/fsl/fsl_asrc_common.h | 7 +++---- sound/soc/fsl/fsl_asrc_dma.c | 14 ++++++++++++++ sound/soc/fsl/fsl_easrc.c | 11 ----------- 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index cc304abbc8bd95..9be903dc75e40d 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -781,18 +781,7 @@ static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd, return 0; } -static int fsl_asrc_dai_probe(struct snd_soc_dai *dai) -{ - struct fsl_asrc *asrc = snd_soc_dai_get_drvdata(dai); - - snd_soc_dai_init_dma_data(dai, &asrc->dma_params_tx, - &asrc->dma_params_rx); - - return 0; -} - static const struct snd_soc_dai_ops fsl_asrc_dai_ops = { - .probe = fsl_asrc_dai_probe, .startup = fsl_asrc_dai_startup, .hw_params = fsl_asrc_dai_hw_params, .hw_free = fsl_asrc_dai_hw_free, diff --git a/sound/soc/fsl/fsl_asrc_common.h b/sound/soc/fsl/fsl_asrc_common.h index c8a1a2b5915d37..4e6b00cb5d6343 100644 --- a/sound/soc/fsl/fsl_asrc_common.h +++ b/sound/soc/fsl/fsl_asrc_common.h @@ -53,6 +53,7 @@ struct fsl_asrc_m2m_cap { * @dma_data: private dma data * @pos: hardware pointer position * @req_dma_chan: flag to release dev_to_dev chan + * @dma_params: DMA parameters for transmit/receive channel * @private: pair private area * @complete: dma task complete * @sample_format: format of m2m @@ -76,6 +77,8 @@ struct fsl_asrc_pair { unsigned int pos; bool req_dma_chan; + struct snd_dmaengine_dai_dma_data dma_params; + void *private; /* used for m2m */ @@ -92,8 +95,6 @@ struct fsl_asrc_pair { /** * fsl_asrc: ASRC common data * - * @dma_params_rx: DMA parameters for receive channel - * @dma_params_tx: DMA parameters for transmit channel * @pdev: platform device pointer * @regmap: regmap handler * @paddr: physical address to the base address of registers @@ -128,8 +129,6 @@ struct fsl_asrc_pair { * @private: private data structure */ struct fsl_asrc { - struct snd_dmaengine_dai_dma_data dma_params_rx; - struct snd_dmaengine_dai_dma_data dma_params_tx; struct platform_device *pdev; struct regmap *regmap; unsigned long paddr; diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index 0aa5db8973d6f0..b417de6abebe4a 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -400,6 +400,20 @@ static int fsl_asrc_dma_startup(struct snd_soc_component *component, runtime->private_data = pair; + /* + * Point the cpu DAI dma_data at the per-pair params so that + * concurrent hw_params calls on different pairs each write to + * their own struct and do not race on addr/maxburst. Use the + * per-direction setters so that a concurrent open of the other + * direction on the same DAI does not NULL out its pointer. + */ + if (tx) + snd_soc_dai_dma_data_set_playback(snd_soc_rtd_to_cpu(rtd, 0), + &pair->dma_params); + else + snd_soc_dai_dma_data_set_capture(snd_soc_rtd_to_cpu(rtd, 0), + &pair->dma_params); + /* Request a dummy pair, which will be released later. * Request pair function needs channel num as input, for this * dummy pair, we just request "1" channel temporarily. diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index d30cc3e9021563..4401c4e100b31d 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -1582,18 +1582,7 @@ static int fsl_easrc_hw_free(struct snd_pcm_substream *substream, return 0; } -static int fsl_easrc_dai_probe(struct snd_soc_dai *cpu_dai) -{ - struct fsl_asrc *easrc = dev_get_drvdata(cpu_dai->dev); - - snd_soc_dai_init_dma_data(cpu_dai, - &easrc->dma_params_tx, - &easrc->dma_params_rx); - return 0; -} - static const struct snd_soc_dai_ops fsl_easrc_dai_ops = { - .probe = fsl_easrc_dai_probe, .startup = fsl_easrc_startup, .trigger = fsl_easrc_trigger, .hw_params = fsl_easrc_hw_params, From 6aef9cb5c7977dae92a90e6e732f14e3784660cf Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Mon, 28 Sep 2026 14:17:27 +0800 Subject: [PATCH 1375/1417] ASoC: fsl_asrc: expose individual DAIs per conversion path The i.MX ASRC hardware supports three independent conversion pairs (A, B, C). The driver previously registered a single DAI with generic stream names "ASRC-Playback" and "ASRC-Capture", which prevents multiple independent conversion paths from being used simultaneously. Replace the single fsl_asrc_dai instance with an array of three DAI drivers to allow up to three independent conversion paths to be active at the same time: asrc-0 -- ASRC-Playback / ASRC-Capture (backward compatible) asrc-1 -- ASRC1-Playback / ASRC1-Capture asrc-2 -- ASRC2-Playback / ASRC2-Capture asrc-0 retains the original generic stream names to preserve backward compatibility with existing machine drivers and board configurations. Each DAI retains the same channel, rate and format capabilities as the original. Update the devm_snd_soc_register_component() call to register all three DAIs. Signed-off-by: Shengjiu Wang Reviewed-by: Chancel Liu Reviewed-by: Frank Li Link: https://patch.msgid.link/20260928061727.1844167-1-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc.c | 89 +++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 20 deletions(-) diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index 9be903dc75e40d..faf348dd582d39 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -792,27 +792,76 @@ static const struct snd_soc_dai_ops fsl_asrc_dai_ops = { SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_3LE) -static struct snd_soc_dai_driver fsl_asrc_dai = { - .playback = { - .stream_name = "ASRC-Playback", - .channels_min = 1, - .channels_max = 10, - .rate_min = 5512, - .rate_max = 192000, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_ASRC_FORMATS | - SNDRV_PCM_FMTBIT_S8, +static struct snd_soc_dai_driver fsl_asrc_dai[] = { + { + .name = "asrc-0", + .playback = { + .stream_name = "ASRC-Playback", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS | + SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "ASRC-Capture", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS, + }, + .ops = &fsl_asrc_dai_ops, + }, + { + .name = "asrc-1", + .playback = { + .stream_name = "ASRC1-Playback", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS | + SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "ASRC1-Capture", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS, + }, + .ops = &fsl_asrc_dai_ops, }, - .capture = { - .stream_name = "ASRC-Capture", - .channels_min = 1, - .channels_max = 10, - .rate_min = 5512, - .rate_max = 192000, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_ASRC_FORMATS, + { + .name = "asrc-2", + .playback = { + .stream_name = "ASRC2-Playback", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS | + SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "ASRC2-Capture", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS, + }, + .ops = &fsl_asrc_dai_ops, }, - .ops = &fsl_asrc_dai_ops, }; static bool fsl_asrc_readable_reg(struct device *dev, unsigned int reg) @@ -1393,7 +1442,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) goto err_pm_get_sync; ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component, - &fsl_asrc_dai, 1); + fsl_asrc_dai, ARRAY_SIZE(fsl_asrc_dai)); if (ret) { dev_err(&pdev->dev, "failed to register ASoC DAI\n"); goto err_pm_get_sync; From 788e893edb6a18a042afed661d3fabc2c0c101c9 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Mon, 28 Sep 2026 14:17:56 +0800 Subject: [PATCH 1376/1417] ASoC: fsl_easrc: expose individual DAIs per conversion path The i.MX EASRC hardware supports four independent conversion contexts (A, B, C, D). The driver previously registered a single DAI with generic stream names "ASRC-Playback" and "ASRC-Capture", which prevents multiple independent conversion paths from being used simultaneously. Replace the single fsl_easrc_dai instance with an array of four DAI drivers to allow up to four independent conversion paths to be active at the same time: easrc-0 -- ASRC-Playback / ASRC-Capture (backward compatible) easrc-1 -- ASRC1-Playback / ASRC1-Capture easrc-2 -- ASRC2-Playback / ASRC2-Capture easrc-3 -- ASRC3-Playback / ASRC3-Capture easrc-0 retains the original generic stream names to preserve backward compatibility with existing machine drivers and board configurations. Each DAI retains the same channel, rate and format capabilities as the original, including IEC958 subframe support on the capture side. Update the devm_snd_soc_register_component() call to register all four DAIs. Signed-off-by: Shengjiu Wang Reviewed-by: Chancel Liu Reviewed-by: Frank Li Reviewed-by: Daniel Baluta Link: https://patch.msgid.link/20260928061756.1844185-1-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_easrc.c | 112 +++++++++++++++++++++++++++++++------- 1 file changed, 92 insertions(+), 20 deletions(-) diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index 4401c4e100b31d..5d715bc0735729 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -1589,27 +1589,99 @@ static const struct snd_soc_dai_ops fsl_easrc_dai_ops = { .hw_free = fsl_easrc_hw_free, }; -static struct snd_soc_dai_driver fsl_easrc_dai = { - .playback = { - .stream_name = "ASRC-Playback", - .channels_min = 1, - .channels_max = 32, - .rate_min = 8000, - .rate_max = 768000, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_EASRC_FORMATS, +static struct snd_soc_dai_driver fsl_easrc_dai[] = { + { + .name = "easrc-0", + .playback = { + .stream_name = "ASRC-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, }, - .capture = { - .stream_name = "ASRC-Capture", - .channels_min = 1, - .channels_max = 32, - .rate_min = 8000, - .rate_max = 768000, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_EASRC_FORMATS | - SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + { + .name = "easrc-1", + .playback = { + .stream_name = "ASRC1-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC1-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, + }, + { + .name = "easrc-2", + .playback = { + .stream_name = "ASRC2-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC2-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, + }, + { + .name = "easrc-3", + .playback = { + .stream_name = "ASRC3-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC3-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, }, - .ops = &fsl_easrc_dai_ops, }; static const struct snd_soc_component_driver fsl_easrc_component = { @@ -2234,7 +2306,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) regcache_cache_only(easrc->regmap, true); ret = devm_snd_soc_register_component(dev, &fsl_easrc_component, - &fsl_easrc_dai, 1); + fsl_easrc_dai, ARRAY_SIZE(fsl_easrc_dai)); if (ret) { dev_err(dev, "failed to register ASoC DAI\n"); goto err_pm_disable; From 80328b41ea2887229fbdc1bd03357ae0a9fa43f2 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 14:30:52 +0800 Subject: [PATCH 1377/1417] regmap: convert lock/unlock to a scoped guard The regmap lock and unlock callbacks are invoked directly as map->lock(map->lock_arg) / map->unlock(map->lock_arg) at every call site. This open-coded pattern requires manual unlocking on every return path, which spreads goto out_unlock chains and duplicated unlock statements throughout the code and is an easy place to leak the lock on an error path. Define a scoped guard for the regmap lock in internal.h. The lock and unlock callbacks are chosen at init time (mutex, spinlock, raw spinlock, hwspinlock or none) and return void, so an unconditional DEFINE_GUARD() is sufficient. Convert all lock/unlock users in regmap.c to guard(regmap)() for function-scope critical sections and scoped_guard(regmap, ...) where work must run outside the lock (e.g. regmap_register_patch() calling regmap_async_complete()). This drops every manual unlock and the associated goto out_unlock labels with no functional change. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260924-regmap-lock-guard-v3-1-8a6127223c16@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 11 ++ drivers/base/regmap/regmap.c | 182 +++++++++++---------------------- 2 files changed, 70 insertions(+), 123 deletions(-) diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index a6e4689000af80..0a17fe3e8cf461 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -10,6 +10,7 @@ #ifndef _REGMAP_INTERNAL_H #define _REGMAP_INTERNAL_H +#include #include #include #include @@ -185,6 +186,16 @@ struct regmap { struct hwspinlock *hwlock; }; +/* + * Scoped guard for the regmap lock. The lock/unlock callbacks are selected + * at init time (mutex, spinlock, raw spinlock, hwspinlock or none) and never + * fail, so an unconditional guard is sufficient. Use with guard(regmap)(map) + * or scoped_guard(regmap, map) { ... }. + */ +DEFINE_GUARD(regmap, struct regmap *, + _T->lock(_T->lock_arg), + _T->unlock(_T->lock_arg)) + struct regcache_ops { const char *name; enum regcache_type type; diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index e6e022b0263753..f3a269f3229737 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -115,9 +115,8 @@ bool regmap_cached(struct regmap *map, unsigned int reg) if (map->max_register_is_set && reg > map->max_register) return false; - map->lock(map->lock_arg); - ret = regcache_read(map, reg, &val); - map->unlock(map->lock_arg); + scoped_guard(regmap, map) + ret = regcache_read(map, reg, &val); if (ret) return false; @@ -1977,18 +1976,12 @@ int _regmap_write(struct regmap *map, unsigned int reg, */ int regmap_write(struct regmap *map, unsigned int reg, unsigned int val) { - int ret; - if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; - map->lock(map->lock_arg); - - ret = _regmap_write(map, reg, val); - - map->unlock(map->lock_arg); + guard(regmap)(map); - return ret; + return _regmap_write(map, reg, val); } EXPORT_SYMBOL_GPL(regmap_write); @@ -2009,7 +2002,7 @@ int regmap_write_async(struct regmap *map, unsigned int reg, unsigned int val) if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); map->async = true; @@ -2017,8 +2010,6 @@ int regmap_write_async(struct regmap *map, unsigned int reg, unsigned int val) map->async = false; - map->unlock(map->lock_arg); - return ret; } EXPORT_SYMBOL_GPL(regmap_write_async); @@ -2080,20 +2071,14 @@ int _regmap_raw_write(struct regmap *map, unsigned int reg, int regmap_raw_write(struct regmap *map, unsigned int reg, const void *val, size_t val_len) { - int ret; - if (!regmap_can_raw_write(map)) return -EINVAL; if (val_len % map->format.val_bytes) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); - ret = _regmap_raw_write(map, reg, val, val_len, false); - - map->unlock(map->lock_arg); - - return ret; + return _regmap_raw_write(map, reg, val, val_len, false); } EXPORT_SYMBOL_GPL(regmap_raw_write); @@ -2211,21 +2196,17 @@ int regmap_noinc_write(struct regmap *map, unsigned int reg, if (val_len == 0) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); - if (!regmap_volatile(map, reg) || !regmap_writeable_noinc(map, reg)) { - ret = -EINVAL; - goto out_unlock; - } + if (!regmap_volatile(map, reg) || !regmap_writeable_noinc(map, reg)) + return -EINVAL; /* * Use the accelerated operation if we can. The val drops the const * typing in order to facilitate code reuse in regmap_noinc_readwrite(). */ - if (map->bus->reg_noinc_write) { - ret = regmap_noinc_readwrite(map, reg, (void *)val, val_len, true); - goto out_unlock; - } + if (map->bus->reg_noinc_write) + return regmap_noinc_readwrite(map, reg, (void *)val, val_len, true); while (val_len) { if (map->max_raw_write && map->max_raw_write < val_len) @@ -2234,14 +2215,12 @@ int regmap_noinc_write(struct regmap *map, unsigned int reg, write_len = val_len; ret = _regmap_raw_write(map, reg, val, write_len, true); if (ret) - goto out_unlock; + return ret; val = ((u8 *)val) + write_len; val_len -= write_len; } -out_unlock: - map->unlock(map->lock_arg); - return ret; + return 0; } EXPORT_SYMBOL_GPL(regmap_noinc_write); @@ -2357,7 +2336,8 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, * single write operations. */ if (!map->write || !map->format.parse_inplace) { - map->lock(map->lock_arg); + guard(regmap)(map); + for (i = 0; i < val_count; i++) { unsigned int ival; @@ -2372,18 +2352,15 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, ival = *(u32 *)(val + (i * val_bytes)); break; default: - ret = -EINVAL; - goto out; + return -EINVAL; } ret = _regmap_write(map, reg + regmap_get_offset(map, i), ival); if (ret != 0) - goto out; + return ret; } -out: - map->unlock(map->lock_arg); } else { void *wval; @@ -2653,15 +2630,9 @@ static int _regmap_multi_reg_write(struct regmap *map, int regmap_multi_reg_write(struct regmap *map, const struct reg_sequence *regs, int num_regs) { - int ret; - - map->lock(map->lock_arg); - - ret = _regmap_multi_reg_write(map, regs, num_regs); - - map->unlock(map->lock_arg); + guard(regmap)(map); - return ret; + return _regmap_multi_reg_write(map, regs, num_regs); } EXPORT_SYMBOL_GPL(regmap_multi_reg_write); @@ -2690,7 +2661,7 @@ int regmap_multi_reg_write_bypassed(struct regmap *map, int ret; bool bypass; - map->lock(map->lock_arg); + guard(regmap)(map); bypass = map->cache_bypass; map->cache_bypass = true; @@ -2699,8 +2670,6 @@ int regmap_multi_reg_write_bypassed(struct regmap *map, map->cache_bypass = bypass; - map->unlock(map->lock_arg); - return ret; } EXPORT_SYMBOL_GPL(regmap_multi_reg_write_bypassed); @@ -2737,7 +2706,7 @@ int regmap_raw_write_async(struct regmap *map, unsigned int reg, if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); map->async = true; @@ -2745,8 +2714,6 @@ int regmap_raw_write_async(struct regmap *map, unsigned int reg, map->async = false; - map->unlock(map->lock_arg); - return ret; } EXPORT_SYMBOL_GPL(regmap_raw_write_async); @@ -2863,18 +2830,12 @@ static int _regmap_read(struct regmap *map, unsigned int reg, */ int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val) { - int ret; - if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); - ret = _regmap_read(map, reg, val); - - map->unlock(map->lock_arg); - - return ret; + return _regmap_read(map, reg, val); } EXPORT_SYMBOL_GPL(regmap_read); @@ -2897,7 +2858,7 @@ int regmap_read_bypassed(struct regmap *map, unsigned int reg, unsigned int *val if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); bypass = map->cache_bypass; cache_only = map->cache_only; @@ -2909,8 +2870,6 @@ int regmap_read_bypassed(struct regmap *map, unsigned int reg, unsigned int *val map->cache_bypass = bypass; map->cache_only = cache_only; - map->unlock(map->lock_arg); - return ret; } EXPORT_SYMBOL_GPL(regmap_read_bypassed); @@ -2941,22 +2900,18 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, if (val_count == 0) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); if (regmap_volatile_range(map, reg, val_count) || map->cache_bypass || map->cache_type == REGCACHE_NONE) { size_t chunk_count, chunk_bytes; size_t chunk_regs = val_count; - if (!map->cache_bypass && map->cache_only) { - ret = -EBUSY; - goto out; - } + if (!map->cache_bypass && map->cache_only) + return -EBUSY; - if (!map->read) { - ret = -ENOTSUPP; - goto out; - } + if (!map->read) + return -ENOTSUPP; if (map->use_single_read) chunk_regs = 1; @@ -2970,7 +2925,7 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, for (i = 0; i < chunk_count; i++) { ret = _regmap_raw_read(map, reg, val, chunk_bytes, false); if (ret != 0) - goto out; + return ret; reg += regmap_get_offset(map, chunk_regs); val += chunk_bytes; @@ -2981,7 +2936,7 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, if (val_len) { ret = _regmap_raw_read(map, reg, val, val_len, false); if (ret != 0) - goto out; + return ret; } } else { /* Otherwise go word by word for the cache; should be low @@ -2991,16 +2946,13 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, ret = _regmap_read(map, reg + regmap_get_offset(map, i), &v); if (ret != 0) - goto out; + return ret; map->format.format_val(val + (i * val_bytes), v, 0); } } - out: - map->unlock(map->lock_arg); - - return ret; + return 0; } EXPORT_SYMBOL_GPL(regmap_raw_read); @@ -3041,12 +2993,10 @@ int regmap_noinc_read(struct regmap *map, unsigned int reg, if (val_len == 0) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); - if (!regmap_volatile(map, reg) || !regmap_readable_noinc(map, reg)) { - ret = -EINVAL; - goto out_unlock; - } + if (!regmap_volatile(map, reg) || !regmap_readable_noinc(map, reg)) + return -EINVAL; /* * We have not defined the FIFO semantics for cache, as the @@ -3054,16 +3004,12 @@ int regmap_noinc_read(struct regmap *map, unsigned int reg, * written value? Just avoid this by always reading the FIFO * even when using cache. Cache only will not work. */ - if (!map->cache_bypass && map->cache_only) { - ret = -EBUSY; - goto out_unlock; - } + if (!map->cache_bypass && map->cache_only) + return -EBUSY; /* Use the accelerated operation if we can */ - if (map->bus->reg_noinc_read) { - ret = regmap_noinc_readwrite(map, reg, val, val_len, false); - goto out_unlock; - } + if (map->bus->reg_noinc_read) + return regmap_noinc_readwrite(map, reg, val, val_len, false); while (val_len) { if (map->max_raw_read && map->max_raw_read < val_len) @@ -3072,14 +3018,12 @@ int regmap_noinc_read(struct regmap *map, unsigned int reg, read_len = val_len; ret = _regmap_raw_read(map, reg, val, read_len, true); if (ret) - goto out_unlock; + return ret; val = ((u8 *)val) + read_len; val_len -= read_len; } -out_unlock: - map->unlock(map->lock_arg); - return ret; + return 0; } EXPORT_SYMBOL_GPL(regmap_noinc_read); @@ -3149,22 +3093,20 @@ static int _regmap_bulk_read(struct regmap *map, unsigned int reg, u8 *u8 = val; int ret, i; - map->lock(map->lock_arg); + guard(regmap)(map); for (i = 0; i < val_count; i++) { unsigned int ival; if (regs) { - if (!IS_ALIGNED(regs[i], map->reg_stride)) { - ret = -EINVAL; - goto out; - } + if (!IS_ALIGNED(regs[i], map->reg_stride)) + return -EINVAL; ret = _regmap_read(map, regs[i], &ival); } else { ret = _regmap_read(map, reg + regmap_get_offset(map, i), &ival); } if (ret != 0) - goto out; + return ret; switch (map->format.val_bytes) { case 4: @@ -3177,13 +3119,11 @@ static int _regmap_bulk_read(struct regmap *map, unsigned int reg, u8[i] = ival; break; default: - ret = -EINVAL; - goto out; + return -EINVAL; } } -out: - map->unlock(map->lock_arg); - return ret; + + return 0; } /** @@ -3310,7 +3250,7 @@ int regmap_update_bits_base(struct regmap *map, unsigned int reg, { int ret; - map->lock(map->lock_arg); + guard(regmap)(map); map->async = async; @@ -3318,8 +3258,6 @@ int regmap_update_bits_base(struct regmap *map, unsigned int reg, map->async = false; - map->unlock(map->lock_arg); - return ret; } EXPORT_SYMBOL_GPL(regmap_update_bits_base); @@ -3452,19 +3390,17 @@ int regmap_register_patch(struct regmap *map, const struct reg_sequence *regs, return -ENOMEM; } - map->lock(map->lock_arg); + scoped_guard(regmap, map) { + bypass = map->cache_bypass; - bypass = map->cache_bypass; + map->cache_bypass = true; + map->async = true; - map->cache_bypass = true; - map->async = true; - - ret = _regmap_multi_reg_write(map, regs, num_regs); + ret = _regmap_multi_reg_write(map, regs, num_regs); - map->async = false; - map->cache_bypass = bypass; - - map->unlock(map->lock_arg); + map->async = false; + map->cache_bypass = bypass; + } regmap_async_complete(map); From 23f1120dcbea0089b63f2e4afd671b4b012864cb Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 14:30:53 +0800 Subject: [PATCH 1378/1417] regcache: use the regmap scoped lock guard Convert the open-coded map->lock()/map->unlock() users in regcache.c to the regmap scoped guard introduced for regmap.c. Use scoped_guard(regmap, ...) in regcache_exit(), where the locked region is a subsection of the function, and guard(regmap)() for the function-scope critical sections. regcache_init() keeps explicit map->lock()/map->unlock() calls: it has a goto err_* cleanup ladder and mixing goto with cleanup helpers in the same function is not allowed by cleanup.h. regcache_sync() and regcache_sync_region() are left as-is: they already use a single goto out unlock path, so converting them would require either mixing a goto with a scoped_guard scope or restructuring their control flow, neither of which is an improvement. No functional change. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260924-regmap-lock-guard-v3-2-8a6127223c16@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 0d58d900a2ca8e..4c80db5d6f6f32 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -281,9 +281,8 @@ void regcache_exit(struct regmap *map) if (map->cache_ops->exit) { dev_dbg(map->dev, "Destroying %s cache\n", map->cache_ops->name); - map->lock(map->lock_arg); - map->cache_ops->exit(map); - map->unlock(map->lock_arg); + scoped_guard(regmap, map) + map->cache_ops->exit(map); } kfree(map->reg_defaults); @@ -584,20 +583,14 @@ EXPORT_SYMBOL_GPL(regcache_sync_region); int regcache_drop_region(struct regmap *map, unsigned int min, unsigned int max) { - int ret = 0; - if (!map->cache_ops || !map->cache_ops->drop) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); trace_regcache_drop_region(map, min, max); - ret = map->cache_ops->drop(map, min, max); - - map->unlock(map->lock_arg); - - return ret; + return map->cache_ops->drop(map, min, max); } EXPORT_SYMBOL_GPL(regcache_drop_region); @@ -615,12 +608,11 @@ EXPORT_SYMBOL_GPL(regcache_drop_region); */ void regcache_cache_only(struct regmap *map, bool enable) { - map->lock(map->lock_arg); + guard(regmap)(map); WARN_ON(map->cache_type != REGCACHE_NONE && map->cache_bypass && enable); map->cache_only = enable; trace_regmap_cache_only(map, enable); - map->unlock(map->lock_arg); } EXPORT_SYMBOL_GPL(regcache_cache_only); @@ -639,10 +631,9 @@ EXPORT_SYMBOL_GPL(regcache_cache_only); */ void regcache_mark_dirty(struct regmap *map) { - map->lock(map->lock_arg); + guard(regmap)(map); map->cache_dirty = true; map->no_sync_defaults = true; - map->unlock(map->lock_arg); } EXPORT_SYMBOL_GPL(regcache_mark_dirty); @@ -659,11 +650,10 @@ EXPORT_SYMBOL_GPL(regcache_mark_dirty); */ void regcache_cache_bypass(struct regmap *map, bool enable) { - map->lock(map->lock_arg); + guard(regmap)(map); WARN_ON(map->cache_only && enable); map->cache_bypass = enable; trace_regmap_cache_bypass(map, enable); - map->unlock(map->lock_arg); } EXPORT_SYMBOL_GPL(regcache_cache_bypass); @@ -680,12 +670,10 @@ bool regcache_reg_cached(struct regmap *map, unsigned int reg) unsigned int val; int ret; - map->lock(map->lock_arg); + guard(regmap)(map); ret = regcache_read(map, reg, &val); - map->unlock(map->lock_arg); - return ret == 0; } EXPORT_SYMBOL_GPL(regcache_reg_cached); From 555a1bd78bbc234342a8f3324a2487a56a118749 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 14:30:54 +0800 Subject: [PATCH 1379/1417] regcache: rbtree: use the regmap scoped lock guard Convert the open-coded map->lock()/map->unlock() pair in rbtree_show() to guard(regmap)(). The locked region spans the whole function body up to the single return, so a function-scope guard drops the manual unlock with no functional change. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260924-regmap-lock-guard-v3-3-8a6127223c16@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache-rbtree.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index 520d5f8ba3cd99..e2feed6dbecffd 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -141,7 +141,7 @@ static int rbtree_show(struct seq_file *s, void *ignored) int registers = 0; int this_registers, average; - map->lock(map->lock_arg); + guard(regmap)(map); mem_size = sizeof(*rbtree_ctx); @@ -168,8 +168,6 @@ static int rbtree_show(struct seq_file *s, void *ignored) seq_printf(s, "%d nodes, %d registers, average %d registers, used %zu bytes\n", nodes, registers, average, mem_size); - map->unlock(map->lock_arg); - return 0; } From 9999d5a07ee037d4653c7231884b0a1e45be801c Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 14:30:55 +0800 Subject: [PATCH 1380/1417] regmap: debugfs: use the regmap scoped lock guard Convert the open-coded map->lock()/map->unlock() users in the debugfs cache_only and cache_bypass write handlers to the regmap scoped guard. regmap_cache_bypass_write_file() uses guard(regmap)() since the locked region runs to the return. regmap_cache_only_write_file() uses scoped_guard(regmap, ...) because the subsequent regcache_sync() must run with the lock released - it takes the regmap lock itself - so it stays outside the guarded scope exactly as before. No functional change. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260924-regmap-lock-guard-v3-4-8a6127223c16@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-debugfs.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 18f1c60749fed5..ec207548a6bbe7 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -471,18 +471,16 @@ static ssize_t regmap_cache_only_write_file(struct file *file, if (err) return count; - map->lock(map->lock_arg); - - if (new_val && !map->cache_only) { - dev_warn(map->dev, "debugfs cache_only=Y forced\n"); - add_taint(TAINT_USER, LOCKDEP_STILL_OK); - } else if (!new_val && map->cache_only) { - dev_warn(map->dev, "debugfs cache_only=N forced: syncing cache\n"); - require_sync = true; + scoped_guard(regmap, map) { + if (new_val && !map->cache_only) { + dev_warn(map->dev, "debugfs cache_only=Y forced\n"); + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + } else if (!new_val && map->cache_only) { + dev_warn(map->dev, "debugfs cache_only=N forced: syncing cache\n"); + require_sync = true; + } + map->cache_only = new_val; } - map->cache_only = new_val; - - map->unlock(map->lock_arg); if (require_sync) { err = regcache_sync(map); @@ -513,7 +511,7 @@ static ssize_t regmap_cache_bypass_write_file(struct file *file, if (err) return count; - map->lock(map->lock_arg); + guard(regmap)(map); if (new_val && !map->cache_bypass) { dev_warn(map->dev, "debugfs cache_bypass=Y forced\n"); @@ -523,8 +521,6 @@ static ssize_t regmap_cache_bypass_write_file(struct file *file, } map->cache_bypass = new_val; - map->unlock(map->lock_arg); - return count; } From 27a868b40e7d18e4be57d2bbbeac7581fd1219db Mon Sep 17 00:00:00 2001 From: Preston Lam Date: Mon, 28 Sep 2026 19:16:21 -0400 Subject: [PATCH 1381/1417] ALSA: hda/realtek: Add mute LED quirk for HP Laptop 15-fd2xxx The HP Laptop 15-fd2xxx (PCI SSID 103c:8e43, Realtek ALC236) has a speaker mute LED on the F5 key and a microphone mute LED on the F8 key. Neither lights up, because the model has no quirk entry and only the generic HP vendor fallback (ALC269_FIXUP_HP_MUTE_LED) is applied. Probing the codec with hda-verb showed how the LEDs are wired: - mute LED: COEF index 0x07, bit 0, on NID 0x20 (set = LED on) - mic mute LED: codec GPIO0, active low (driven low = LED on) Driving GPIO1 and GPIO2 did not light the mute LED. This wiring is exactly what the existing ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO fixup implements (COEF 0x07 bit 0 for the mute LED, GPIO0 with inverted polarity for the micmute LED), so use it for this model. The sibling HP Laptop 15-fd0xxx (103c:8dd7) already uses the same fixup. Tested on this machine running Ubuntu 7.0.0-34-generic (SOF driver, the quirk built into snd-hda-codec-alc269 from Ubuntu's 7.0.0-34.34 source tree): the hda::mute and hda::micmute LEDs are created, follow the Master mute state and the Dmic0 capture switch respectively, and the F5 and F8 keys toggle them correctly. Muting the microphone also silences the capture stream. The LEDs also kept the correct state across an s2idle suspend/resume cycle (speaker muted in one run, microphone in another) and still responded to mute changes afterwards; long suspends were not tested. The desktop session was SwayFX (wlroots) with PipeWire, which only changes the mute state through the normal mixer controls; no other desktop was tried. Not tested on a mainline kernel tree directly; the change is only the quirk table entry. AI assistance: this patch, the hardware analysis behind it (probing the codec with hda-verb and choosing the existing fixup) and this commit message were written by Claude Code (Anthropic, model claude-sonnet-5-5). The submitter ran the privileged commands and reboots and reported the LED behavior; Claude Code ran the tests and read the kernel logs. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Preston Lam Link: https://patch.msgid.link/20260928231621.1672704-1-plamlam99@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 193e0fa3606dd5..dfb5b5ab899d1b 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7740,6 +7740,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8e37, "HP 16 Piston OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e3a, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e3b, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e43, "HP Laptop 15-fd2xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8e60, "HP OmniBook 7 Laptop 16-bh0xxx", ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8e61, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e62, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), From 1bbc033983b76f41d51d4a48838bf94c5e6b6f50 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Mon, 28 Sep 2026 13:23:52 -0500 Subject: [PATCH 1382/1417] ALSA: timer: Handle userspace-driven timer ioctls from 32-bit tasks 32-bit tasks on a 64-bit kernel can't use userspace-driven timers. The compat handler for /dev/snd/timer doesn't list SNDRV_TIMER_IOCTL_CREATE, so creating a timer fails with -ENOTTY. The file returned for a timer has no .compat_ioctl either, so SNDRV_TIMER_IOCTL_TRIGGER fails with -ENOTTY too. struct snd_timer_uinfo has the same layout for 32-bit and 64-bit tasks, and SNDRV_TIMER_IOCTL_TRIGGER takes no argument, so both commands can be passed through unchanged. The utimer selftest doesn't catch this when built with -m32: it takes -ENOTTY from SNDRV_TIMER_IOCTL_CREATE to mean CONFIG_SND_UTIMER=n and skips. Fixes: 37745918e0e7 ("ALSA: timer: Introduce virtual userspace-driven timers") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260928182352.309457-1-danishkhateeb03@gmail.com Signed-off-by: Takashi Iwai --- sound/core/timer.c | 1 + sound/core/timer_compat.c | 1 + 2 files changed, 2 insertions(+) diff --git a/sound/core/timer.c b/sound/core/timer.c index 679b2643567090..69a59b25fe0a80 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -2176,6 +2176,7 @@ static const struct file_operations snd_utimer_fops = { .llseek = noop_llseek, .release = snd_utimer_release, .unlocked_ioctl = snd_utimer_ioctl, + .compat_ioctl = snd_utimer_ioctl, }; static int snd_utimer_start(struct snd_timer *t) diff --git a/sound/core/timer_compat.c b/sound/core/timer_compat.c index 25ee81c1668b75..5bab00466de7f9 100644 --- a/sound/core/timer_compat.c +++ b/sound/core/timer_compat.c @@ -99,6 +99,7 @@ static long __snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, case SNDRV_TIMER_IOCTL_PAUSE: case SNDRV_TIMER_IOCTL_PAUSE_OLD: case SNDRV_TIMER_IOCTL_NEXT_DEVICE: + case SNDRV_TIMER_IOCTL_CREATE: return __snd_timer_user_ioctl(file, cmd, (unsigned long)argp, true); case SNDRV_TIMER_IOCTL_GPARAMS32: return snd_timer_user_gparams_compat(file, argp); From 763183f76d6bc093fa603377862d6739cb7ccad6 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Tue, 29 Sep 2026 17:11:09 +0800 Subject: [PATCH 1383/1417] ALSA: hda/realtek: Add quirk for HP Pavilion AiO 24 speaker Add ALC225_FIXUP_HP_AIO_SPK to enable the internal speaker on HP Pavilion AiO 24 (103c:85ba). Link: https://bugzilla.kernel.org/show_bug.cgi?id=221691 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260929091109.400313-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index dfb5b5ab899d1b..d13cd276ec6150 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4200,6 +4200,7 @@ enum { ALC225_FIXUP_DELL_WYSE_AIO_MIC_NO_PRESENCE, ALC225_FIXUP_WYSE_AUTO_MUTE, ALC225_FIXUP_WYSE_DISABLE_MIC_VREF, + ALC225_FIXUP_HP_AIO_SPK, ALC286_FIXUP_ACER_AIO_HEADSET_MIC, ALC256_FIXUP_ASUS_HEADSET_MIC, ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, @@ -5721,6 +5722,14 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC }, + [ALC225_FIXUP_HP_AIO_SPK] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0220 }, + { } + }, + }, [ALC286_FIXUP_ACER_AIO_HEADSET_MIC] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { @@ -7408,6 +7417,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x854a, "HP EliteBook 830 G6", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x854d, "HP EliteBook 840 G6", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x856a, "HP Pavilion 15-cs1xxx", ALC295_FIXUP_HP_PAVILION_MUTE_LED_1B), + HDA_CODEC_QUIRK(0x103c, 0x85ba, "HP Pavilion AiO 24", ALC225_FIXUP_HP_AIO_SPK), SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), From 0eee030c2c09c7a44be778de5f451d9e7a2f09c1 Mon Sep 17 00:00:00 2001 From: Muhtasham Nawr al-Mahmud Date: Thu, 24 Sep 2026 20:45:12 +0600 Subject: [PATCH 1384/1417] ALSA: hda/realtek: Fix speakers on ASUS PM3406CHA The ASUS ExpertBook PM3406CHA uses an ALC256 codec with subsystem ID 1043:1664. On Linux boot, the internal speakers are silent even though playback is routed to the codec and the speaker pin is configured for output. The codec starts with coefficient 0x10 set to 0x1d20. Writing 0x7f20 to coefficient 0x10 immediately restores internal speaker output. This was reproduced on the affected hardware from a fresh boot using hda-verb. Reducing a larger initialization sequence showed that this coefficient write alone is sufficient; no GPIO, EAPD, pin-control, or other coefficient changes are required. After applying the coefficient in userspace, speaker output also remains functional across suspend and resume. Add a machine-specific HDA fixup that writes the required coefficient. The fixup has been compile-tested, but could not be boot-tested because the affected hardware is no longer available. Closes: https://bugs.launchpad.net/ubuntu/+source/alsa-driver/+bug/2162718 Signed-off-by: Muhtasham Nawr al-Mahmud Link: https://patch.msgid.link/20260924144512.3989-1-muhtaseem2005@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index d13cd276ec6150..333689f3177551 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4025,6 +4025,7 @@ enum { ALC269_FIXUP_SONY_VAIO, ALC275_FIXUP_SONY_VAIO_GPIO2, ALC269_FIXUP_DELL_M101Z, + ALC256_FIXUP_ASUS_PM3406CHA, ALC269_FIXUP_SKU_IGNORE, ALC269_FIXUP_ASUS_G73JW, ALC269_FIXUP_ASUS_N7601ZM_PINS, @@ -4484,6 +4485,14 @@ static const struct hda_fixup alc269_fixups[] = { {} } }, + [ALC256_FIXUP_ASUS_PM3406CHA] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x7f20 }, + {} + } + }, [ALC269_FIXUP_SKU_IGNORE] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_sku_ignore, @@ -7853,6 +7862,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1652, "ASUS ROG Zephyrus Do 15 SE", ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK), SND_PCI_QUIRK(0x1043, 0x1662, "ASUS GV301QH", ALC294_FIXUP_ASUS_DUAL_SPK), SND_PCI_QUIRK(0x1043, 0x1663, "ASUS GU603ZI/ZJ/ZQ/ZU/ZV", ALC285_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1664, "ASUS ExpertBook PM3406CHA", ALC256_FIXUP_ASUS_PM3406CHA), SND_PCI_QUIRK(0x1043, 0x1683, "ASUS UM3402YAR", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC245_FIXUP_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x16b2, "ASUS GU603", ALC289_FIXUP_ASUS_GA401), From 2d1d019ddd292ac4e570f044e936acf974a0f866 Mon Sep 17 00:00:00 2001 From: Xucheng Pan Date: Wed, 23 Sep 2026 23:25:52 +0800 Subject: [PATCH 1385/1417] ALSA: usb-audio: Re-read descriptors for Xiaomi 2717:d005 The Xiaomi 2717:d005 USB audio transmitter supports Sound 2 Pro and Sound 2 Max speakers. This issue was reproduced with two Sound 2 Pro speakers paired in stereo. After a USB replug, Linux detects the transmitter and exposes a hardware volume control, but moving the volume slider changes neither the audible level nor the speakers' volume LEDs. Windows controls the same speakers with its in-box USB Audio driver. USB captures show standard UAC2 volume SET_CUR requests on both systems. Re-reading the device descriptor (18 bytes), configuration header (9 bytes), and full configuration descriptor (184 bytes on the tested unit) after 2717:d005 is configured restores hardware volume control. The USBFS sequence recovered the device after multiple replug tests. Reading only the device descriptor did not restore volume control. Perform these reads when snd-usb-audio first probes 2717:d005. Use the configuration header's wTotalLength for the final read. If a read fails, warn and continue probing so that playback remains available. Tested on 7.2.6-zen2-1-zen with the existing userspace workaround disabled. After a physical USB replug, the quirk ran when 2717:d005 appeared, and both audible volume and the speakers' LEDs followed the KDE volume slider. Signed-off-by: Xucheng Pan Link: https://patch.msgid.link/20260923152552.943151-1-panxucc@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 2b169aabf6f93f..20140cf7f4aa42 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -871,6 +871,49 @@ static int snd_usb_cm1a_boot_quirk(struct usb_device *dev) return 0; } +/* + * The Xiaomi audio connector ignores UAC2 volume changes after reconnecting + * until the device and configuration descriptors have been read again. + */ +#define XIAOMI_AUDIO_MAX_CONFIG_SIZE 1024 + +static void snd_usb_xiaomi_boot_quirk(struct usb_device *dev) +{ + u8 *buf __free(kfree) = kmalloc(XIAOMI_AUDIO_MAX_CONFIG_SIZE, GFP_KERNEL); + struct usb_config_descriptor *config; + unsigned int length; + int ret; + + if (!buf) + return; + + ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, buf, + USB_DT_DEVICE_SIZE); + if (ret != USB_DT_DEVICE_SIZE) { + dev_warn(&dev->dev, "device descriptor re-read failed: %d\n", ret); + return; + } + + ret = usb_get_descriptor(dev, USB_DT_CONFIG, 0, buf, + USB_DT_CONFIG_SIZE); + if (ret != USB_DT_CONFIG_SIZE) { + dev_warn(&dev->dev, "configuration header re-read failed: %d\n", ret); + return; + } + + config = (struct usb_config_descriptor *)buf; + length = le16_to_cpu(config->wTotalLength); + if (length < USB_DT_CONFIG_SIZE || + length > XIAOMI_AUDIO_MAX_CONFIG_SIZE) { + dev_warn(&dev->dev, "unexpected configuration length: %u\n", length); + return; + } + + ret = usb_get_descriptor(dev, USB_DT_CONFIG, 0, buf, length); + if (ret != (int)length) + dev_warn(&dev->dev, "configuration descriptor re-read failed: %d\n", ret); +} + /* * Some sound cards from Native Instruments are in fact compliant to the USB * audio standard of version 2 and other approved USB standards, even though @@ -1743,6 +1786,9 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, return snd_usb_motu_m_series_boot_quirk(dev); case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ return snd_usb_cm1a_boot_quirk(dev); + case USB_ID(0x2717, 0xd005): /* Xiaomi audio connector */ + snd_usb_xiaomi_boot_quirk(dev); + return 0; } return 0; From d842c03f89aef141874149f8dc8a28c104455f32 Mon Sep 17 00:00:00 2001 From: Darren Chang Date: Sun, 20 Sep 2026 20:26:53 +0800 Subject: [PATCH 1386/1417] ALSA: usb-audio: Add quirk for inverted sample rates on NUX NAI-24 The NUX NAI-24 (USB 3703:2000) has a UAC2 clock source that reports bmAttributes = 0x01 (internal fixed clock) and bmControls = 0x07, so the driver treats its sample rate as programmable. When the driver sends SET_CUR(SAMPLING_FREQ_CONTROL), the firmware acknowledges the request and then runs the clock on the opposite base-rate family: asking for 44100 Hz makes the device run at 48000 Hz, and asking for 48000 Hz makes it run at 44100 Hz. The result is playback that is about 8.8% fast, or 8.4% slow with heavy static on the 48000 Hz family. macOS and Windows ignore bmControls, treat the clock as fixed and resample, so they are unaffected. Work around it by sending the partner rate in SET_CUR for this device, so the device runs at the requested rate. A new QUIRK_FLAG_SWAP_RATES flag controls this, applied through the quirk flags table. Only the 44.1/48 kHz pair has been verified on hardware; the 88.2/96 kHz and 176.4/192 kHz pairs are untested but follow the same pattern. Testing: the equivalent change ran on the device as a locally built module (44100 Hz PCM, device clock 44100 Hz, no xruns, correct tempo, no static). This upstream form compiles for sound/usb without warnings and passes checkpatch, but has not been built in a full kernel tree or load-tested. Assisted-by: LLM Signed-off-by: Darren Chang Link: https://patch.msgid.link/20260920122653.41993-1-darrenchangjr01@gmail.com Signed-off-by: Takashi Iwai --- Documentation/sound/alsa-configuration.rst | 6 +++++ sound/usb/clock.c | 27 ++++++++++++++++++++++ sound/usb/quirks.c | 3 +++ sound/usb/usbaudio.h | 6 +++++ 4 files changed, 42 insertions(+) diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index e500ec84e3ef72..689eccf6a5921a 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2407,6 +2407,12 @@ quirk_flags the requested rate. A device advertising a single rate is otherwise never sent the request at all, and some require it before streaming will start. + * bit 33: ``swap_rates`` + The clock source applies a requested rate to the other base-rate + family, i.e. asking for 44100 makes the device run at 48000 and vice + versa (same for 88200 <-> 96000 and 176400 <-> 192000). The driver + sends the partner rate in SET_CUR so the device ends up running at the + requested one. This module supports multiple devices, autoprobe and hotplugging. diff --git a/sound/usb/clock.c b/sound/usb/clock.c index 34832183a1f0b9..cc77eace7b592d 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -549,6 +549,30 @@ static int get_sample_rate_v2v3(struct snd_usb_audio *chip, int iface, return le32_to_cpu(data); } +/* + * Return the partner rate in the other base-rate family. + * See QUIRK_FLAG_SWAP_RATES. + */ +static unsigned int swap_base_rate(unsigned int rate) +{ + switch (rate) { + case 44100: + return 48000; + case 48000: + return 44100; + case 88200: + return 96000; + case 96000: + return 88200; + case 176400: + return 192000; + case 192000: + return 176400; + default: + return rate; + } +} + /* * Try to set the given sample rate: * @@ -585,6 +609,9 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, if (!writeable) return 0; + if (chip->quirk_flags & QUIRK_FLAG_SWAP_RATES) + rate = swap_base_rate(rate); + data = cpu_to_le32(rate); err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 20140cf7f4aa42..1c248075c48ece 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2776,6 +2776,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), DEVICE_FLG(0x36f9, 0xc009, /* XIBERIA K03S */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x3703, 0x2000, /* NUX NAI-24 */ + QUIRK_FLAG_SWAP_RATES), DEVICE_FLG(0x3c20, 0x3d21, /* AB13X USB Audio */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x413c, 0xa506, /* Dell AE515 sound bar */ @@ -2901,6 +2903,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + QUIRK_STRING_ENTRY(SWAP_RATES), NULL }; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 24436473931da1..c6d59218d35f7a 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -258,6 +258,10 @@ extern bool snd_usb_skip_validation; * Issue SET_CUR for the sample rate even when the clock already reports the * requested rate. A device advertising a single rate is otherwise never sent * the request at all, and some require it before streaming will start. + * QUIRK_FLAG_SWAP_RATES + * The device applies a requested sample rate to the other base-rate family + * (44100 <-> 48000, 88200 <-> 96000, 176400 <-> 192000). Send the partner + * rate in SET_CUR so that the device runs at the requested rate. */ enum { @@ -294,6 +298,7 @@ enum { QUIRK_TYPE_MIXER_GET_CUR_OK = 30, QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, QUIRK_TYPE_ALWAYS_SET_RATE = 32, + QUIRK_TYPE_SWAP_RATES = 33, /* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ }; @@ -332,5 +337,6 @@ enum { #define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK) #define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) #define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) +#define QUIRK_FLAG_SWAP_RATES QUIRK_FLAG(SWAP_RATES) #endif /* __USBAUDIO_H */ From f38638f5181ab0545ca36f2ac3b0d2cf3404b1b7 Mon Sep 17 00:00:00 2001 From: Alexander Koch Date: Tue, 29 Sep 2026 11:08:20 +0200 Subject: [PATCH 1387/1417] ASoC: codecs: ak4619: Add optional powerdown GPIO Add optional device tree binding that enables operation of the 'PDN' pin during initialization: codec@10 { compatible = "asahi-kasei,ak4619"; reg = <0x10>; powerdown-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>; // <-- #sound-dai-cells = <0>; }; If defined, PDN is set high during driver probe, followed by a 10 ms delay as required before first register access [1]. If no 'powerdown-gpios' is defined, the behaviour is unchanged. [1] AK4619 datasheet, sec. 2 'Power-up/down Sequence', page 39 Signed-off-by: Alexander Koch Link: https://patch.msgid.link/20260929090821.1476257-2-mail@alexanderkoch.net Signed-off-by: Mark Brown --- sound/soc/codecs/ak4619.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/soc/codecs/ak4619.c b/sound/soc/codecs/ak4619.c index d9c9f6b200284a..c4f0db1e51c8d5 100644 --- a/sound/soc/codecs/ak4619.c +++ b/sound/soc/codecs/ak4619.c @@ -152,6 +152,7 @@ struct ak4619_priv { struct regmap *regmap; struct snd_pcm_hw_constraint_list constraint; + struct gpio_desc *pdn_gpio; int deemph_en; unsigned int playback_rate; unsigned int sysclk; @@ -875,10 +876,20 @@ static int ak4619_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, ak4619); + ak4619->pdn_gpio = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_LOW); + if (IS_ERR(ak4619->pdn_gpio)) + return dev_err_probe(dev, PTR_ERR(ak4619->pdn_gpio), + "powerdown GPIO request failed\n"); + if (ak4619->pdn_gpio) + msleep(10); + ak4619->regmap = devm_regmap_init_i2c(i2c, &ak4619_regmap_cfg); if (IS_ERR(ak4619->regmap)) { ret = PTR_ERR(ak4619->regmap); dev_err(dev, "regmap_init() failed: %d\n", ret); + if (ak4619->pdn_gpio) + gpiod_set_value_cansleep(ak4619->pdn_gpio, 1); return ret; } @@ -887,6 +898,8 @@ static int ak4619_i2c_probe(struct i2c_client *i2c) if (ret < 0) { dev_err(dev, "Failed to register ak4619 component: %d\n", ret); + if (ak4619->pdn_gpio) + gpiod_set_value_cansleep(ak4619->pdn_gpio, 1); return ret; } From b224c8932773c63f4905d217544ab67bfef1c64e Mon Sep 17 00:00:00 2001 From: Alexander Koch Date: Tue, 29 Sep 2026 11:08:21 +0200 Subject: [PATCH 1388/1417] ASoC: dt-bindings: asahi-kasei,ak4619: Add powerdown GPIO Add documentation for an optional property that enables operation of the 'PDN' (powerdown) pin during initialization: codec@10 { compatible = "asahi-kasei,ak4619"; reg = <0x10>; powerdown-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>; // <-- #sound-dai-cells = <0>; }; Signed-off-by: Alexander Koch Link: https://patch.msgid.link/20260929090821.1476257-3-mail@alexanderkoch.net Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/asahi-kasei,ak4619.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml index d412531ef9a2ba..86e0b8cbfc29bd 100644 --- a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml +++ b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml @@ -34,6 +34,12 @@ properties: $ref: audio-graph-port.yaml# unevaluatedProperties: false + powerdown-gpios: + maxItems: 1 + description: + GPIO connected to the 'PDN' pin, if any. Needs to be set low to + power down the codec, hence the GPIO must be GPIO_ACTIVE_LOW. + required: - compatible - reg @@ -42,6 +48,8 @@ unevaluatedProperties: false examples: - | + #include + i2c { #address-cells = <1>; #size-cells = <0>; @@ -49,6 +57,8 @@ examples: compatible = "asahi-kasei,ak4619"; reg = <0x10>; + powerdown-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>; + clocks = <&rcar_sound>; clock-names = "mclk"; From 677507a5303a6716d9e0bbf3a8ba8c0f27391ab2 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:42 +0100 Subject: [PATCH 1389/1417] ASoC: SDCA: allow building without ACPI The SDCA class driver is useful on non-ACPI platforms where the topology is supplied statically via sdca_class_ops.populate_function. Drop 'depends on ACPI' from SND_SOC_SDCA and guard the ACPI-specific enumeration helpers (sdca_lookup_functions, sdca_lookup_swft, sdca_lookup_interface_revision) in sdca_device.c and sdca_functions.c with IS_ENABLED(CONFIG_ACPI), providing empty stubs when ACPI is off. The fwnode-based DisCo parser (sdca_parse_function() and its helpers in sdca_functions.c) stays compiled unconditionally: it uses only fwnode_* APIs and can in principle be reached from any firmware backend that supplies a matching device tree. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260925161049.758913-2-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- include/sound/sdca.h | 23 ++++++++++++++++++----- sound/soc/sdca/Kconfig | 6 ++++-- sound/soc/sdca/sdca_device.c | 4 ++++ sound/soc/sdca/sdca_functions.c | 2 ++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/include/sound/sdca.h b/include/sound/sdca.h index 2bdf4e333e0449..db2672b3aae27a 100644 --- a/include/sound/sdca.h +++ b/include/sound/sdca.h @@ -60,20 +60,16 @@ enum sdca_quirk { SDCA_QUIRKS_SKIP_FUNC_TYPE_PATCHING, }; -#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SDCA) +#if IS_ENABLED(CONFIG_SND_SOC_SDCA) -void sdca_lookup_functions(struct sdw_slave *slave); void sdca_lookup_swft(struct sdw_slave *slave); -void sdca_lookup_interface_revision(struct sdw_slave *slave); bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk); int sdca_dev_register_functions(struct sdw_slave *slave); void sdca_dev_unregister_functions(struct sdw_slave *slave); #else -static inline void sdca_lookup_functions(struct sdw_slave *slave) {} static inline void sdca_lookup_swft(struct sdw_slave *slave) {} -static inline void sdca_lookup_interface_revision(struct sdw_slave *slave) {} static inline bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk) { return false; @@ -88,4 +84,21 @@ static inline void sdca_dev_unregister_functions(struct sdw_slave *slave) {} #endif +/* + * Called from the SoundWire bus during peripheral enumeration; gated on + * ACPI to avoid a soundwire_bus <-> snd_soc_sdca module cycle on DT builds + * (where the bodies are stubs anyway). + */ +#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SDCA) + +void sdca_lookup_functions(struct sdw_slave *slave); +void sdca_lookup_interface_revision(struct sdw_slave *slave); + +#else + +static inline void sdca_lookup_functions(struct sdw_slave *slave) {} +static inline void sdca_lookup_interface_revision(struct sdw_slave *slave) {} + +#endif + #endif diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig index 4c0dcb9ff3b944..c371323cc36e2b 100644 --- a/sound/soc/sdca/Kconfig +++ b/sound/soc/sdca/Kconfig @@ -3,11 +3,13 @@ menu "SoundWire (SDCA)" config SND_SOC_SDCA tristate "SDCA core support" - depends on ACPI select AUXILIARY_BUS help This option enables support for the MIPI SoundWire Device - Class for Audio (SDCA). + Class for Audio (SDCA). The ACPI-specific enumeration + helpers are only built when CONFIG_ACPI is enabled; on + non-ACPI platforms the SDCA function data is supplied + statically by the codec driver. config SND_SOC_SDCA_HID bool "SDCA HID support" diff --git a/sound/soc/sdca/sdca_device.c b/sound/soc/sdca/sdca_device.c index 4bcd8d1fdff821..3f302bfa545779 100644 --- a/sound/soc/sdca/sdca_device.c +++ b/sound/soc/sdca/sdca_device.c @@ -15,6 +15,7 @@ #include #include +#if IS_ENABLED(CONFIG_ACPI) void sdca_lookup_interface_revision(struct sdw_slave *slave) { struct fwnode_handle *fwnode = slave->dev.fwnode; @@ -45,6 +46,9 @@ void sdca_lookup_swft(struct sdw_slave *slave) devm_add_action_or_reset(&slave->dev, devm_acpi_table_put, slave->sdca_data.swft); } +#else +void sdca_lookup_swft(struct sdw_slave *slave) { } +#endif EXPORT_SYMBOL_NS(sdca_lookup_swft, "SND_SOC_SDCA"); static bool sdca_device_quirk_rt712_vb(struct sdw_slave *slave) diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index f39008ac0e7d30..1eb6418e132dcb 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -25,6 +25,7 @@ */ #define SDCA_PROPERTY_LENGTH 64 +#if IS_ENABLED(CONFIG_ACPI) static int patch_sdca_function_type(u32 interface_revision, u32 *function_type) { /* @@ -201,6 +202,7 @@ void sdca_lookup_functions(struct sdw_slave *slave) acpi_dev_for_each_child(adev, find_sdca_function, &slave->sdca_data); } EXPORT_SYMBOL_NS(sdca_lookup_functions, "SND_SOC_SDCA"); +#endif struct raw_init_write { __le32 addr; From 0985313a2a9d2e84d6a6b1041c88b5c57ee46dfa Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:43 +0100 Subject: [PATCH 1390/1417] ASoC: SDCA: export PM helpers keyed on sdca_class_drv The class PM callbacks pull sdca_class_drv out of drvdata, so the built-in class_sdw_driver owns the drvdata slot. That works for the generic case but blocks codec drivers that want to embed sdca_class_drv in their own private struct -- they need drvdata for their codec priv. Split the four callbacks into exported helpers that take a struct sdca_class_drv * directly: sdca_class_system_suspend() sdca_class_system_resume() sdca_class_runtime_suspend() sdca_class_runtime_resume() Codec drivers can now compose these into their own dev_pm_ops without going through drvdata. For the built-in class_sdw_driver, add small dev_pm_ops wrappers that fetch drv from drvdata and wire them into sdca_class_pm_ops. The ops struct itself stays private to this file; codec drivers build their own dev_pm_ops from the four exported helpers above. No functional change: the built-in class_sdw_driver keeps the same PM semantics; only the internal plumbing shifts to operate on sdca_class_drv instead of struct device *dev. Signed-off-by: Srinivas Kandagatla Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260925161049.758913-3-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- .../soc/sdca => include/sound}/sdca_class.h | 14 ++++ sound/soc/sdca/sdca_class.c | 81 ++++++++++++++----- sound/soc/sdca/sdca_class_function.c | 2 +- 3 files changed, 78 insertions(+), 19 deletions(-) rename {sound/soc/sdca => include/sound}/sdca_class.h (58%) diff --git a/sound/soc/sdca/sdca_class.h b/include/sound/sdca_class.h similarity index 58% rename from sound/soc/sdca/sdca_class.h rename to include/sound/sdca_class.h index 57f7f8d08f4970..8a6a34a8dd4c3a 100644 --- a/sound/soc/sdca/sdca_class.h +++ b/include/sound/sdca_class.h @@ -32,4 +32,18 @@ struct sdca_class_drv { struct work_struct boot_work; }; +/* + * PM helpers. Codec drivers embed sdca_class_drv in their own priv, + * own dev_set_drvdata(), and compose these into their own dev_pm_ops: + * + * static int wcd_runtime_suspend(struct device *dev) { + * struct wcd_priv *priv = dev_get_drvdata(dev); + * return sdca_class_runtime_suspend(&priv->class); + * } + */ +int sdca_class_runtime_suspend(struct sdca_class_drv *drv); +int sdca_class_runtime_resume(struct sdca_class_drv *drv); +int sdca_class_system_suspend(struct sdca_class_drv *drv); +int sdca_class_system_resume(struct sdca_class_drv *drv); + #endif /* __SDCA_CLASS_H__ */ diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index d7444f442c71c6..a092ebaa620aa3 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -20,7 +20,7 @@ #include #include #include -#include "sdca_class.h" +#include #define CLASS_SDW_ATTACH_TIMEOUT_MS 5000 @@ -194,30 +194,41 @@ static void class_sdw_remove(struct sdw_slave *sdw) cancel_work_sync(&drv->boot_work); } -static int class_suspend(struct device *dev) +/** + * sdca_class_system_suspend - SDCA class system suspend helper + * @drv: caller-owned sdca_class_drv. + * + * Codec drivers compose this into their own dev_pm_ops. Disables the + * SoundWire interrupt and forces runtime suspend of the underlying + * class regmap. + */ +int sdca_class_system_suspend(struct sdca_class_drv *drv) { - struct sdca_class_drv *drv = dev_get_drvdata(dev); int ret; disable_irq(drv->sdw->irq); - ret = pm_runtime_force_suspend(dev); + ret = pm_runtime_force_suspend(drv->dev); if (ret) { - dev_err(dev, "failed to force suspend: %d\n", ret); + dev_err(drv->dev, "failed to force suspend: %d\n", ret); return ret; } return 0; } +EXPORT_SYMBOL_NS_GPL(sdca_class_system_suspend, "SND_SOC_SDCA_CLASS"); -static int class_resume(struct device *dev) +/** + * sdca_class_system_resume - SDCA class system resume helper + * @drv: caller-owned sdca_class_drv. + */ +int sdca_class_system_resume(struct sdca_class_drv *drv) { - struct sdca_class_drv *drv = dev_get_drvdata(dev); int ret; - ret = pm_runtime_force_resume(dev); + ret = pm_runtime_force_resume(drv->dev); if (ret) { - dev_err(dev, "failed to force resume: %d\n", ret); + dev_err(drv->dev, "failed to force resume: %d\n", ret); return ret; } @@ -225,11 +236,14 @@ static int class_resume(struct device *dev) return 0; } +EXPORT_SYMBOL_NS_GPL(sdca_class_system_resume, "SND_SOC_SDCA_CLASS"); -static int class_runtime_suspend(struct device *dev) +/** + * sdca_class_runtime_suspend - SDCA class runtime suspend helper + * @drv: caller-owned sdca_class_drv. + */ +int sdca_class_runtime_suspend(struct sdca_class_drv *drv) { - struct sdca_class_drv *drv = dev_get_drvdata(dev); - /* * Whilst the driver doesn't power the chip down here, going into runtime * suspend lets the SoundWire bus power down, which means the driver @@ -239,10 +253,14 @@ static int class_runtime_suspend(struct device *dev) return 0; } +EXPORT_SYMBOL_NS_GPL(sdca_class_runtime_suspend, "SND_SOC_SDCA_CLASS"); -static int class_runtime_resume(struct device *dev) +/** + * sdca_class_runtime_resume - SDCA class runtime resume helper + * @drv: caller-owned sdca_class_drv. + */ +int sdca_class_runtime_resume(struct sdca_class_drv *drv) { - struct sdca_class_drv *drv = dev_get_drvdata(dev); int ret; ret = sdw_slave_wait_for_init(drv->sdw, CLASS_SDW_ATTACH_TIMEOUT_MS); @@ -265,10 +283,37 @@ static int class_runtime_resume(struct device *dev) return ret; } +EXPORT_SYMBOL_NS_GPL(sdca_class_runtime_resume, "SND_SOC_SDCA_CLASS"); + +/* + * Convenience dev_pm_ops used by the built-in class_sdw_driver, which + * stashes its sdca_class_drv in drvdata directly. Codec drivers that + * embed sdca_class_drv in their own priv compose their own dev_pm_ops + * using the sdca_class_*_suspend/resume helpers above. + */ +static int class_pm_system_suspend(struct device *dev) +{ + return sdca_class_system_suspend(dev_get_drvdata(dev)); +} + +static int class_pm_system_resume(struct device *dev) +{ + return sdca_class_system_resume(dev_get_drvdata(dev)); +} + +static int class_pm_runtime_suspend(struct device *dev) +{ + return sdca_class_runtime_suspend(dev_get_drvdata(dev)); +} + +static int class_pm_runtime_resume(struct device *dev) +{ + return sdca_class_runtime_resume(dev_get_drvdata(dev)); +} -static const struct dev_pm_ops class_pm_ops = { - SYSTEM_SLEEP_PM_OPS(class_suspend, class_resume) - RUNTIME_PM_OPS(class_runtime_suspend, class_runtime_resume, NULL) +static const struct dev_pm_ops sdca_class_pm_ops = { + SYSTEM_SLEEP_PM_OPS(class_pm_system_suspend, class_pm_system_resume) + RUNTIME_PM_OPS(class_pm_runtime_suspend, class_pm_runtime_resume, NULL) }; static const struct sdw_device_id class_sdw_id[] = { @@ -282,7 +327,7 @@ MODULE_DEVICE_TABLE(sdw, class_sdw_id); static struct sdw_driver class_sdw_driver = { .driver = { .name = "sdca_class", - .pm = pm_ptr(&class_pm_ops), + .pm = pm_ptr(&sdca_class_pm_ops), }, .probe = class_sdw_probe, diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index 1d7fd660388290..411e3d717bb6b6 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -26,7 +26,7 @@ #include #include #include -#include "sdca_class.h" +#include #include "sdca_function_device.h" struct class_function_drv { From a60f1ac41fe0a2ea83f57ce252caf6f08ae48d62 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:44 +0100 Subject: [PATCH 1391/1417] ASoC: SDCA: expose class SoundWire probe/remove as library Split the internal class_sdw_probe/class_sdw_remove functions into caller-friendly library helpers: sdca_class_probe(sdw, drv) sdca_class_remove(drv) The class_sdw_probe/class_sdw_remove callbacks of the built-in class_sdw_driver are now thin wrappers that allocate a bare sdca_class_drv, stash it in drvdata, and defer to the exported helpers. The exported sdca_class_probe() takes a caller-owned struct sdca_class_drv * so codec-specific SoundWire drivers can embed the class state in their own priv struct, own dev_set_drvdata() themselves, and avoid a second allocation. No functional change for the built-in driver. This lays the groundwork for codec-specific SDCA SoundWire drivers that want to compose the class-side probe with their own quirks; the next patches add the class_ops mechanism on top. Signed-off-by: Srinivas Kandagatla Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260925161049.758913-4-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- include/sound/sdca_class.h | 4 +++ sound/soc/sdca/sdca_class.c | 62 +++++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/include/sound/sdca_class.h b/include/sound/sdca_class.h index 8a6a34a8dd4c3a..890c2dbd88a234 100644 --- a/include/sound/sdca_class.h +++ b/include/sound/sdca_class.h @@ -32,6 +32,10 @@ struct sdca_class_drv { struct work_struct boot_work; }; +/* Library helpers used by codec-specific SDCA SoundWire drivers. */ +int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv); +void sdca_class_remove(struct sdca_class_drv *drv); + /* * PM helpers. Codec drivers embed sdca_class_drv in their own priv, * own dev_set_drvdata(), and compose these into their own dev_pm_ops: diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index a092ebaa620aa3..0e6dead414959a 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -136,18 +136,31 @@ static void class_boot_work(struct work_struct *work) pm_runtime_put_sync(drv->dev); } -static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id) +/** + * sdca_class_probe - SDCA class SoundWire slave probe helper + * @sdw: SoundWire slave + * @drv: caller-allocated sdca_class_drv storage. The caller (a codec + * driver, or the built-in class_sdw_driver in this file) owns the + * allocation and sets its own dev_set_drvdata() -- the framework + * does not touch drvdata. Typically embedded in the codec's own + * priv struct so codec drivers can keep per-slave state. + * + * Codec-specific SoundWire drivers call this from their .probe after + * allocating a struct sdca_class_drv (usually embedded in their own + * priv) and setting drvdata to their priv. The framework fills in the + * sdca_class_drv fields, sets up the class regmap, and queues the + * deferred boot work. + */ +int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv) { struct device *dev = &sdw->dev; struct regmap_config *dev_config; - struct sdca_class_drv *drv; int ret; sdca_lookup_swft(sdw); - drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); if (!drv) - return -ENOMEM; + return -EINVAL; dev_config = devm_kmemdup(dev, &class_dev_regmap_config, sizeof(*dev_config), GFP_KERNEL); @@ -159,8 +172,6 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id mutex_init(&drv->regmap_lock); mutex_init(&drv->init_lock); - dev_set_drvdata(drv->dev, drv); - INIT_WORK(&drv->boot_work, class_boot_work); dev_config->lock_arg = &drv->regmap_lock; @@ -185,14 +196,47 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id return 0; } +EXPORT_SYMBOL_NS_GPL(sdca_class_probe, "SND_SOC_SDCA_CLASS"); -static void class_sdw_remove(struct sdw_slave *sdw) +static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id) { - struct device *dev = &sdw->dev; - struct sdca_class_drv *drv = dev_get_drvdata(dev); + struct sdca_class_drv *drv; + + /* + * Pure-generic SDCA parts: no codec priv to embed, so allocate a + * bare sdca_class_drv here and stash it in drvdata for the + * built-in PM ops to fetch. + */ + drv = devm_kzalloc(&sdw->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + dev_set_drvdata(&sdw->dev, drv); + + return sdca_class_probe(sdw, drv); +} + +/** + * sdca_class_remove - SDCA class SoundWire slave remove helper + * @drv: caller-owned sdca_class_drv (the one handed to sdca_class_probe()). + * + * Cancels the deferred boot work so devres can safely free @drv and the + * embedding codec priv without racing class_boot_work. Codec-specific + * SoundWire drivers that call sdca_class_probe() must call this from + * their .remove with the same drv pointer they passed to probe. + */ +void sdca_class_remove(struct sdca_class_drv *drv) +{ cancel_work_sync(&drv->boot_work); } +EXPORT_SYMBOL_NS_GPL(sdca_class_remove, "SND_SOC_SDCA_CLASS"); + +static void class_sdw_remove(struct sdw_slave *sdw) +{ + struct sdca_class_drv *drv = dev_get_drvdata(&sdw->dev); + + sdca_class_remove(drv); +} /** * sdca_class_system_suspend - SDCA class system suspend helper From 3273ec3e149ade420500014d442e4159bcc0d264 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:45 +0100 Subject: [PATCH 1392/1417] ASoC: SDCA: add class_ops with populate_function Add struct sdca_class_ops with a populate_function callback that lets codec drivers supply the SDCA topology (entities, clusters, init_table, ...) from static tables in place of sdca_parse_function() on DT/non-DisCo platforms. The callback is a pure data source and performs no bus I/O. Codec drivers embed sdca_class_drv in their own priv and register their populate_function through class_ops passed to sdca_class_probe(). Signed-off-by: Srinivas Kandagatla Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260925161049.758913-5-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- include/sound/sdca_class.h | 19 ++++++++++++++++++- sound/soc/sdca/sdca_class.c | 9 +++++++-- sound/soc/sdca/sdca_class_function.c | 9 ++++++++- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/include/sound/sdca_class.h b/include/sound/sdca_class.h index 890c2dbd88a234..794a756472cd62 100644 --- a/include/sound/sdca_class.h +++ b/include/sound/sdca_class.h @@ -19,6 +19,19 @@ struct regmap; struct sdw_slave; struct sdca_function_data; +/** + * struct sdca_class_ops - optional codec-provided class callbacks + * @populate_function: fill @function (entities, clusters, init_table, ...) + * from static tables in place of sdca_parse_function() on + * DT/non-DisCo platforms. Called once per SDCA function with + * @dev pointing at that function's device. Pure data source; + * performs no bus I/O. Return 0 on success or a negative errno. + */ +struct sdca_class_ops { + int (*populate_function)(struct device *dev, + struct sdca_function_data *function); +}; + struct sdca_class_drv { struct device *dev; struct regmap *dev_regmap; @@ -26,6 +39,8 @@ struct sdca_class_drv { struct sdca_interrupt_info *irq_info; + const struct sdca_class_ops *ops; + struct mutex regmap_lock; /* Serialise function initialisations */ struct mutex init_lock; @@ -33,7 +48,9 @@ struct sdca_class_drv { }; /* Library helpers used by codec-specific SDCA SoundWire drivers. */ -int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv); +int sdca_class_probe(struct sdw_slave *sdw, + struct sdca_class_drv *drv, + const struct sdca_class_ops *ops); void sdca_class_remove(struct sdca_class_drv *drv); /* diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index 0e6dead414959a..79fdb672be1d7e 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -144,6 +144,8 @@ static void class_boot_work(struct work_struct *work) * allocation and sets its own dev_set_drvdata() -- the framework * does not touch drvdata. Typically embedded in the codec's own * priv struct so codec drivers can keep per-slave state. + * @ops: optional codec-provided class callbacks (may be NULL for + * pure-generic SDCA parts that need no quirks) * * Codec-specific SoundWire drivers call this from their .probe after * allocating a struct sdca_class_drv (usually embedded in their own @@ -151,7 +153,9 @@ static void class_boot_work(struct work_struct *work) * sdca_class_drv fields, sets up the class regmap, and queues the * deferred boot work. */ -int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv) +int sdca_class_probe(struct sdw_slave *sdw, + struct sdca_class_drv *drv, + const struct sdca_class_ops *ops) { struct device *dev = &sdw->dev; struct regmap_config *dev_config; @@ -169,6 +173,7 @@ int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv) drv->dev = dev; drv->sdw = sdw; + drv->ops = ops; mutex_init(&drv->regmap_lock); mutex_init(&drv->init_lock); @@ -213,7 +218,7 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id dev_set_drvdata(&sdw->dev, drv); - return sdca_class_probe(sdw, drv); + return sdca_class_probe(sdw, drv, NULL); } /** diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index 411e3d717bb6b6..c49afa2e626d57 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -329,7 +329,14 @@ static int class_function_probe(struct auxiliary_device *auxdev, drv->core = core; drv->function = &sdev->function; - ret = sdca_parse_function(dev, drv->function); + if (core->ops && core->ops->populate_function) { + ret = core->ops->populate_function(dev, drv->function); + } else if (drv->function->desc->node) { + ret = sdca_parse_function(dev, drv->function); + } else { + dev_err(dev, "no firmware node and no populate_function hook\n"); + return -ENOENT; + } if (ret) return ret; From e0e876c99f280fd00156a6a11eace1f91ea883f5 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:46 +0100 Subject: [PATCH 1393/1417] ASoC: SDCA: class_function: xlate sound-dai cell by entity index sdca_asoc_populate_dais() sets each DAI id from the SDCA Function's entity array index. ASoC's default of_xlate_dai_name treats the phandle cell as a positional index into the DAI list, so on a Function with non-DAI entries between dataport entities a sound-dai phandle resolves to the wrong DAI. Register a custom of_xlate_dai_name that walks the entity array and returns the dataport entity whose array index matches the cell value. Signed-off-by: Srinivas Kandagatla Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260925161049.758913-6-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class_function.c | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index c49afa2e626d57..c716e28e5ddc2d 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -216,9 +216,43 @@ static int class_function_set_jack(struct snd_soc_component *component, return sdca_jack_set_jack(core->irq_info, jack); } +/* + * DT phandle cell is the SDCA entity index (matches dais[].id), not the + * positional DAI index the default xlate assumes. + */ +static int class_function_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name) +{ + struct class_function_drv *drv = snd_soc_component_get_drvdata(component); + struct sdca_function_data *function = drv->function; + struct sdca_entity *entity; + u32 target; + + if (args->args_count != 1) + return -EINVAL; + + target = args->args[0]; + if (target >= function->num_entities) + goto err; + + entity = &function->entities[target]; + if ((entity->type != SDCA_ENTITY_TYPE_IT && + entity->type != SDCA_ENTITY_TYPE_OT) || !entity->iot.is_dataport) + goto err; + + *dai_name = entity->label; + return 0; +err: + dev_err(component->dev, "xlate: no dataport entity at index %u (num_entities=%d)\n", + target, function->num_entities); + return -EINVAL; +} + static const struct snd_soc_component_driver class_function_component_drv = { .fixup_controls = class_function_component_fixup_controls, .remove = class_function_component_remove, + .of_xlate_dai_name = class_function_of_xlate_dai_name, .endianness = 1, }; From a36d24da5cf96d8129948fece042c331ab769d3e Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:47 +0100 Subject: [PATCH 1394/1417] ASoC: dt-bindings: qcom: add Tambora WCD9378 SDCA codec Describe the WCD9378 SoundWire peripheral which is SDCA compatible soundwire device. It provides SDCA SimpleJack for headphone playback headset mic capture and MBHC jack detection. The same silicon is integrated in two ways and enumerates with the same SoundWire class ID: mobile mode: two slaves (tx, rx) on separate SoundWire masters. SDCA / compute mode: one aggregated slave on a multi-lane master. Note: both of them have different bus toplogies 1 device vs 2 devices. Currently only compute mode is supported by this bindings, mobile mode will extend this binding as required. Signed-off-by: Srinivas Kandagatla Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260925161049.758913-7-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- .../bindings/sound/qcom,wcd9378-sdw.yaml | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml diff --git a/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml b/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml new file mode 100644 index 00000000000000..62d85b1dca7f2e --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/qcom,wcd9378-sdw.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm WCD9378 (Tambora) SoundWire codec + +maintainers: + - Srinivas Kandagatla + +description: + Qualcomm WCD9378 (Tambora) SoundWire codec. The same silicon can be + fused for a mobile (non-SDCA) mode or an SDCA-compliant mode; the + qcom,sdca-compliant property selects the latter and is required by + this driver. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: sdw20217011000 + + reg: + maxItems: 1 + + qcom,sdca-compliant: + description: + Present when the codec is fused as SDCA-compliant. The mobile + (non-SDCA) variant carries the same class-ID compatible and is + handled by a different driver. + type: boolean + + qcom,port-mapping: + description: + Each entry maps a slave data port to a master data port. + Entries are in order starting from slave port 1; slave port 0 + is reserved and not represented. SDCA-compliant mode uses 8 + entries covering DP1..DP8. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 8 + maxItems: 8 + + reset-gpios: + description: GPIO used to release the codec from reset. + maxItems: 1 + + vdd-buck-supply: + description: 1.8 V analog buck supply. + + vdd-rxtx-supply: + description: 1.8 V RX/TX supply. + + vdd-io-supply: + description: Digital I/O supply. + + vdd-mic-bias-supply: + description: Mic bias supply. + + '#sound-dai-cells': + const: 1 + +required: + - compatible + - reg + - qcom,sdca-compliant + - qcom,port-mapping + - reset-gpios + - vdd-buck-supply + - vdd-rxtx-supply + - vdd-io-supply + - vdd-mic-bias-supply + - '#sound-dai-cells' + +unevaluatedProperties: false + +examples: + - | + #include + + soundwire@7630000 { + reg = <0x07630000 0x10000>; + #address-cells = <2>; + #size-cells = <0>; + + wcd9378c_sdw: audio-codec@0,3 { + compatible = "sdw20217011000"; + reg = <0 3>; + + qcom,sdca-compliant; + + qcom,port-mapping = <2 2 3 4 5 6 7 8>; + + reset-gpios = <&tlmm 191 GPIO_ACTIVE_LOW>; + + vdd-buck-supply = <&vreg_l15b_1p8>; + vdd-rxtx-supply = <&vreg_l15b_1p8>; + vdd-io-supply = <&vreg_l18b_1p2>; + vdd-mic-bias-supply = <&vreg_bob1>; + + #sound-dai-cells = <1>; + }; + }; +... From 0c30b8ca84aeea378d23874e7b14108021288b18 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 25 Sep 2026 17:10:48 +0100 Subject: [PATCH 1395/1417] ASoC: codecs: add Qualcomm Tambora (WCD9378) SDCA codec Add support for the Qualcomm Tambora (WCD9378) headset codec in SDCA mode over SoundWire. On ARM/DT platforms without ACPI/DisCo firmware the SDCA topology and SoundWire port properties are supplied as static data through the codec driver. The codec exposes a single SimpleJack SDCA Function providing: - Headphone playback via FU 6 (mute + Q7.8 volume) and OT 43/45. - Headset mic capture via IT 33 with MICB2 bias at 2.75V. - MBHC-based headset jack detection. The driver enables the codec supplies, releases the reset GPIO, and uses: - sdw_slave_ops.read_prop to publish SoundWire slave properties and dpn caps for the compute-mode dataports. - sdca_class_ops.populate_function to hand the SDCA Function data (entities, clusters, init_table) to the class driver from static tables. Binds SoundWire slave id 0x0217:0x0110 when qcom,sdca-compliant is set on the DT node. Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260925161049.758913-8-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 11 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wcd9378-sdca.c | 1080 +++++++++++++++++++++++++++++++ sound/soc/codecs/wcd9378-sdca.h | 20 + sound/soc/codecs/wcd9378-sdw.c | 51 ++ 5 files changed, 1164 insertions(+) create mode 100644 sound/soc/codecs/wcd9378-sdca.c create mode 100644 sound/soc/codecs/wcd9378-sdca.h create mode 100644 sound/soc/codecs/wcd9378-sdw.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 3cce95528dbff5..ba59dabeeccde5 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -2552,6 +2552,17 @@ config SND_SOC_WCD939X_SDW The WCD9390/9395 is a audio codec IC Integrated in Qualcomm SoCs like SM8650. +config SND_SOC_WCD9378_SDCA + tristate "Qualcomm Tambora (WCD9378) SDCA codec" + depends on SOUNDWIRE + depends on SND_SOC_SDCA_CLASS + help + This enables support for the Qualcomm Tambora (WCD9378) headset + codec when driven via the SDCA class driver on ARM platforms + without ACPI/DisCo tables. It provides the static SDCA topology + and SoundWire data port properties transcribed from the factory + ACPI tables. + config SND_SOC_WM0010 tristate depends on SPI_MASTER diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 1679c3ffc63434..3826f9e4fce8a2 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -369,6 +369,7 @@ snd-soc-wcd938x-y := wcd938x.o snd-soc-wcd938x-sdw-y := wcd938x-sdw.o snd-soc-wcd939x-y := wcd939x.o snd-soc-wcd939x-sdw-y := wcd939x-sdw.o +snd-soc-wcd9378-y := wcd9378-sdw.o wcd9378-sdca.o snd-soc-wm-adsp-y := wm_adsp.o snd-soc-wm-adsp-test-y := wm_adsp_fw_find_test.o snd-soc-wm0010-y := wm0010.o @@ -825,6 +826,7 @@ ifdef CONFIG_SND_SOC_WCD939X_SDW # avoid link failure by forcing sdw code built-in when needed obj-$(CONFIG_SND_SOC_WCD939X) += snd-soc-wcd939x-sdw.o endif +obj-$(CONFIG_SND_SOC_WCD9378_SDCA) += snd-soc-wcd9378.o obj-$(CONFIG_SND_SOC_WM0010) += snd-soc-wm0010.o obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o diff --git a/sound/soc/codecs/wcd9378-sdca.c b/sound/soc/codecs/wcd9378-sdca.c new file mode 100644 index 00000000000000..7c15f885390ce8 --- /dev/null +++ b/sound/soc/codecs/wcd9378-sdca.c @@ -0,0 +1,1080 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright (c) 2025 Qualcomm Technologies, Inc. All rights reserved. + +/* + * WCD9378 (Tambora) SDCA SimpleJack codec. Supplies the static SDCA + * topology on DT platforms where no ACPI/DisCo enumeration exists. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wcd9378-sdca.h" + +struct wcd9378_priv { + struct sdca_class_drv class; +}; + +static struct sdca_entity wcd9378_sdca_entities[]; +/* + * Entity array index map. Order matches the ASL entity-id-list; + * Function (Entity 0) is last. + * + * [0] E001 IT 41 (0x1) [12] E00F IT 33 (0xf) + * [1] E002 CS 41 (0x2) [13] E010 PDE 34 (0x10) + * [2] E003 MFPU 21 (0x3) [14] E011 FU 33 (0x11) + * [3] E004 XU 42 (0x4) [15] E012 SU 35 (0x12) + * [4] E007 SU 43 (0x7) [16] E013 XU 36 (0x13) + * [5] E008 SU 45 (0x8) [17] E015 CS 36 (0x15) + * [6] E009 PDE 47 (0x9) [18] E016 OT 36 (0x16) + * [7] E00A OT 43 (0xa) [19] E017 MFPU 236 (0x17) + * [8] E00B OT 45 (0xb) [20] E018 CS 236 (0x18) + * [9] E00C GE 35 (0xc) [21] E019 OT 236 (0x19) + * [10] E00D IT 131 (0xd) [22] E006 FU 6 (0x6) + * [11] E00E CS 131 (0xe) [23] E000 Function (0x0) + */ +#define QSJ_IT41 0 +#define QSJ_CS41 1 +#define QSJ_MFPU21 2 +#define QSJ_XU42 3 +#define QSJ_SU43 4 +#define QSJ_SU45 5 +#define QSJ_PDE47 6 +#define QSJ_OT43 7 +#define QSJ_OT45 8 +#define QSJ_GE35 9 +#define QSJ_IT131 10 +#define QSJ_CS131 11 +#define QSJ_IT33 12 +#define QSJ_PDE34 13 +#define QSJ_FU33 14 +#define QSJ_SU35 15 +#define QSJ_XU36 16 +#define QSJ_CS36 17 +#define QSJ_OT36 18 +#define QSJ_MFPU236 19 +#define QSJ_CS236 20 +#define QSJ_OT236 21 +#define QSJ_FU6 22 + +/* Range data: {cols, rows, data[cols*rows]}, transcribed from ASL. */ + +/* IT 41 Usage: HIFI (0x2). ULP (0x3) does not route to the HPH analog driver. */ +static u32 range_it41_usage_data[] = { + /* usage, CBN, sample_rate, sample_width, full_scale, noise_floor, tag */ + 0x2, 0x2d0, 0xbb80, 0x10, 0x0, 0x0, 0x0, +}; + +static u32 range_it41_cluster_data[] = { 0x1, 0x1 }; + +/* IT 41 DataPort selector: DP6 (HPH render). */ +static u32 range_it41_dp_data[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* CS 41 SampleRateIndex: 1 -> 48kHz PCM. */ +static u32 range_cs41_sr_data[] = { 0x1, 0xbb80 }; + +/* SU selector: disconnected / connected. */ +static u32 range_su_sel_data[] = { 0x0, 0x1 }; + +/* PDE Requested_PS: PS0 / PS3. */ +static u32 range_pde_req_ps_data[] = { 0x0, 0x3 }; + +/* + * GE 35 SelectedMode -> terminal type. + * + * Tambora MBHC only handles mechanical detection; ADC HP/HS + * discrimination is not implemented, so modes 0 (Unplugged) and 1 + * (Unknown) are aliased to Headphone (mode 4) to keep the DAPM path + * alive. Mode 2 (Line-out) is not fitted on this board. + */ +static u32 range_ge35_mode_data[] = { + 0x0, 0x6c0, /* Unplugged -> HPH (alias) */ + 0x1, 0x6c0, /* Unknown -> HPH (alias) */ + 0x3, 0x6d0, /* Headset */ + 0x4, 0x6c0, /* Headphone */ +}; + +/* IT 131 Usage: optimization render stream at 192kHz. */ +static u32 range_it131_usage_data[] = { + 0x3, 0x334, 0x2ee00, 0x8, 0x0, 0x0, 0x0, +}; + +static u32 range_it131_cluster_data[] = { 0x1, 0x3 }; + +/* IT 131 DataPort selector: DP7. */ +static u32 range_it131_dp_data[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* CS 131 SampleRateIndex: 1 -> 192kHz. */ +static u32 range_cs131_sr_data[] = { 0x1, 0x2ee00 }; + +/* IT 33 MIC_BIAS: SDCA MIC_BIAS index 0x5 (2.75V). */ +static u32 range_it33_micbias_data[] = { 0x5 }; + +static u32 range_it33_usage_data[] = { + 0x1, 0x2c6, 0x0, 0x0, 0x0, 0x0, 0x0, +}; + +static u32 range_it33_cluster_data[] = { 0x1, 0x2 }; + +static u32 range_cs36_sr_data[] = { 0x1, 0xbb80 }; + +/* OT 36 Usage: PDM capture, host-visible 48kHz/16-bit PCM. */ +static u32 range_ot36_usage_data[] = { + 0x1, 0x2c6, 0xbb80, 0x10, 0x0, 0x0, 0x0, +}; + +/* OT 36 DataPort selector: DP2. */ +static u32 range_ot36_dp_data[] = { + 0xff, 0xff, 0x2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +static u32 range_cs236_sr_data[] = { 0x1, 0xbb80 }; + +/* OT 236 Usage: optimization capture at 192kHz (clocked by CS 131). */ +static u32 range_ot236_usage_data[] = { + 0x1, 0x334, 0x2ee00, 0x8, 0x0, 0x0, 0x0, +}; + +/* OT 236 DataPort selector: DP5. */ +static u32 range_ot236_dp_data[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0x5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* Entity 0 (Function) Control Values */ +static int ctrl_fun_sdca_ver_vals[] = { 0x11 }; +static int ctrl_fun_type_vals[] = { 0x08 }; /* SimpleJack */ +static int ctrl_fun_man_id_vals[] = { 0x0217 }; +static int ctrl_fun_id_vals[] = { 0x3 }; +static int ctrl_fun_ver_vals[] = { 0x0 }; +static int ctrl_dev_sdca_ver_vals[] = { 0x11 }; + +/* Entity 1 (IT 41) Control Values */ +static int ctrl_it41_latency_vals[] = { 0x0 }; +static int ctrl_it41_cluster_vals[] = { 0x1 }; +static int ctrl_it41_dp_vals[] = { 0x6 }; + +/* Entity 2 (CS 41) Control Values */ +static int ctrl_cs41_sr_vals[] = { 0x1 }; + +/* Entity 3 (MFPU 21) Control Values */ +static int ctrl_mfpu21_bypass_vals[] = { 0x1 }; + +/* Entity 4 (XU 42) Control Values */ +static int ctrl_xu42_id_vals[] = { 0x2131 }; +static int ctrl_xu42_ver_vals[] = { 0x1 }; + +/* Entity 0xd (IT 131) Control Values */ +static int ctrl_it131_latency_vals[] = { 0x0 }; +static int ctrl_it131_cluster_vals[] = { 0x1 }; +static int ctrl_it131_dp_vals[] = { 0x7 }; + +/* Entity 0xe (CS 131) Control Values */ +static int ctrl_cs131_sr_vals[] = { 0x1 }; + +/* IT 33 MIC_BIAS: fixed 2.75V (SDCA MIC_BIAS index 0x5), reapplied by PDE34. */ +static int ctrl_it33_micbias_vals[] = { 0x5 }; +static int ctrl_it33_latency_vals[] = { 0x0 }; +static int ctrl_it33_cluster_vals[] = { 0x1 }; + +/* Entity 0x13 (XU 36) Control Values */ +static int ctrl_xu36_bypass_vals[] = { 0x1 }; +static int ctrl_xu36_id_vals[] = { 0x2131 }; +static int ctrl_xu36_ver_vals[] = { 0x1 }; + +/* Entity 0x15 (CS 36) Control Values */ +static int ctrl_cs36_sr_vals[] = { 0x1 }; + +/* Entity 0x16 (OT 36) Control Values */ +static int ctrl_ot36_latency_vals[] = { 0x0 }; +static int ctrl_ot36_dp_vals[] = { 0x2 }; + +/* Entity 0x17 (MFPU 236) Control Values */ +static int ctrl_mfpu236_bypass_vals[] = { 0x1 }; + +/* Entity 0x18 (CS 236) Control Values */ +static int ctrl_cs236_sr_vals[] = { 0x1 }; + +/* Entity 0x19 (OT 236) Control Values */ +static int ctrl_ot236_latency_vals[] = { 0x0 }; +static int ctrl_ot236_dp_vals[] = { 0x5 }; + +/* Entity 0 (Function) Controls */ +static struct sdca_control entity0_controls[] = { + { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_COMMIT_GROUP_MASK_NAME }, + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_sdca_ver_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_SDCA_VERSION_NAME }, + { .sel = 0x5, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_type_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_TYPE_NAME }, + { .sel = 0x6, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_man_id_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_MANUFACTURER_ID_NAME }, + { .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_id_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_ID_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_ver_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_VERSION_NAME }, + { .sel = 0x9, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_FUNCTION_EXTENSION_ID_NAME }, + { .sel = 0xa, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_FUNCTION_EXTENSION_VERSION_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_RW1C, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_FUNCTION_STATUS_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_RW1S, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_FUNCTION_ACTION_NAME }, + { .sel = 0x2c, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_DEVICE_MANUFACTURER_ID_NAME }, + { .sel = 0x2d, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_DEVICE_PART_ID_NAME }, + { .sel = 0x2e, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_DEVICE_VERSION_NAME }, + { .sel = 0x2f, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_dev_sdca_ver_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DEVICE_SDCA_VERSION_NAME }, +}; + +/* Entity 1 (IT 41) Controls */ +static struct sdca_control entity_it41_controls[] = { + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_it41_usage_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_it41_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it41_cluster_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_it41_cluster_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_CLUSTERINDEX_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it41_dp_vals, .has_fixed = true, + .range = { .cols = 0x10, .rows = 0x4, .data = range_it41_dp_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DATAPORT_SELECTOR_NAME }, +}; + +/* Entity 2 (CS 41) Controls */ +static struct sdca_control entity_cs41_controls[] = { + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_cs41_sr_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_cs41_sr_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SAMPLERATEINDEX_NAME }, +}; + +/* Entity 3 (MFPU 21) Controls */ +static struct sdca_entity *entity_mfpu21_sources[] = { + &wcd9378_sdca_entities[QSJ_IT41], + &wcd9378_sdca_entities[QSJ_IT131], +}; + +static struct sdca_control entity_mfpu21_controls[] = { + { .sel = SDCA_CTL_MFPU_BYPASS, .mode = SDCA_ACCESS_MODE_DC, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .values = ctrl_mfpu21_bypass_vals, .has_fixed = true, .label = SDCA_CTL_BYPASS_NAME }, +}; + +/* Entity 4 (XU 42) Controls */ +static struct sdca_entity *entity_xu42_sources[] = { &wcd9378_sdca_entities[QSJ_MFPU21] }; + +static struct sdca_control entity_xu42_controls[] = { + { .sel = SDCA_CTL_XU_BYPASS, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .label = SDCA_CTL_BYPASS_NAME }, + { .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_xu42_id_vals, .has_fixed = true, .label = SDCA_CTL_XU_ID_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_xu42_ver_vals, .has_fixed = true, .label = SDCA_CTL_XU_VERSION_NAME }, +}; + +/* + * SU Selector is RO but its value is deterministic from the current + * GE mode which the class framework drives; leave it non-volatile so + * regmap caches it and DAPM reads succeed when the codec is + * runtime-suspended. + */ + +/* Entity 7 (SU 43) Controls */ +static struct sdca_entity *entity_su43_sources[] = { &wcd9378_sdca_entities[QSJ_XU42] }; + +static struct sdca_control entity_su43_controls[] = { + { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_DEVICE, + .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SELECTOR_NAME }, +}; + +/* Entity 8 (SU 45) Controls */ +static struct sdca_entity *entity_su45_sources[] = { &wcd9378_sdca_entities[QSJ_XU42] }; + +static struct sdca_control entity_su45_controls[] = { + { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_DEVICE, + .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SELECTOR_NAME }, +}; + +/* + * PDE 47 manages the HPH render path (OT 43, OT 45). FU 6 is listed + * so its cached mute/volume are reasserted on PS0 entry. + */ +static struct sdca_entity *entity_pde47_managed[] = { + &wcd9378_sdca_entities[QSJ_FU6], + &wcd9378_sdca_entities[QSJ_OT43], + &wcd9378_sdca_entities[QSJ_OT45], +}; + +static struct sdca_pde_delay pde47_delays[] = { + { .from_ps = 3, .to_ps = 0, .us = 30000 }, + { .from_ps = 0, .to_ps = 3, .us = 30000 }, +}; + +static struct sdca_control entity_pde47_controls[] = { + { .sel = SDCA_CTL_PDE_REQUESTED_PS, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_pde_req_ps_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 3, + .label = SDCA_CTL_REQUESTED_PS_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_ACTUAL_PS_NAME }, + /* + * HPH protection IRQs (OCP/CNP/SURGE) fire in codec HW but are + * not exposed to Linux; needs an SDCA framework interface for + * standalone status-bit IRQs. Wire up in a follow-up. + */ +}; + +/* OT 43 (Headphone), OT 45 (Headset): no controls. */ +static struct sdca_entity *entity_ot43_sources[] = { &wcd9378_sdca_entities[QSJ_IT41] }; +static struct sdca_entity *entity_ot45_sources[] = { &wcd9378_sdca_entities[QSJ_IT41] }; + +/* + * GE 35 mode -> SU selector. Modes 0/1 alias to Headphone (see + * range_ge35_mode_data). SU 45 = source 1 (XU 42) for HPH paths, + * SU 43 = source 1 (XU 42) for Headset. + */ +static struct sdca_ge_control ge35_mode0_controls[] = { + { .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 }, +}; + +static struct sdca_ge_control ge35_mode1_controls[] = { + { .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 }, +}; + +static struct sdca_ge_control ge35_mode3_controls[] = { + { .id = 0x7, .sel = 0x1, .cn = 0x0, .val = 0x1 }, +}; + +static struct sdca_ge_control ge35_mode4_controls[] = { + { .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 }, +}; + +static struct sdca_ge_mode ge35_modes[] = { + { .val = 0x0, .num_controls = ARRAY_SIZE(ge35_mode0_controls), .controls = ge35_mode0_controls }, + { .val = 0x1, .num_controls = ARRAY_SIZE(ge35_mode1_controls), .controls = ge35_mode1_controls }, + { .val = 0x3, .num_controls = ARRAY_SIZE(ge35_mode3_controls), .controls = ge35_mode3_controls }, + { .val = 0x4, .num_controls = ARRAY_SIZE(ge35_mode4_controls), .controls = ge35_mode4_controls }, +}; + +static struct sdca_control entity_ge35_controls[] = { + { .sel = SDCA_CTL_GE_SELECTED_MODE, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .range = { .cols = 0x2, .rows = 0x4, .data = range_ge35_mode_data }, + .has_reset = true, .reset = 0, + .label = SDCA_CTL_SELECTED_MODE_NAME }, + { .sel = 0x2, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = 4, /* SDCA_4 = GE_DETECTED_MODE, see init_table INTMASK_1 */ + .is_volatile = true, .label = SDCA_CTL_DETECTED_MODE_NAME }, +}; + +static struct sdca_control entity_it131_controls[] = { + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_it131_usage_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_it131_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it131_cluster_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_it131_cluster_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_CLUSTERINDEX_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it131_dp_vals, .has_fixed = true, + .range = { .cols = 0x10, .rows = 0x4, .data = range_it131_dp_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DATAPORT_SELECTOR_NAME }, +}; + +/* Entity 0xe (CS 131) Controls */ +static struct sdca_control entity_cs131_controls[] = { + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_cs131_sr_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_cs131_sr_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SAMPLERATEINDEX_NAME }, +}; + +/* Entity 0xf (IT 33) - headset mic input */ +static struct sdca_control entity_it33_controls[] = { + { .sel = 0x3, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it33_micbias_vals, .has_default = true, + .range = { .cols = 0x1, .rows = 0x1, .data = range_it33_micbias_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_MIC_BIAS_NAME }, + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_it33_usage_data }, + .has_reset = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_it33_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it33_cluster_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_it33_cluster_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_CLUSTERINDEX_NAME }, +}; + +/* Entity 0x10 (PDE 34) - manages IT 33 */ +static struct sdca_entity *entity_pde34_managed[] = { &wcd9378_sdca_entities[QSJ_IT33] }; + +static struct sdca_pde_delay pde34_delays[] = { + { .from_ps = 3, .to_ps = 0, .us = 30000 }, + { .from_ps = 0, .to_ps = 3, .us = 30000 }, +}; + +static struct sdca_control entity_pde34_controls[] = { + { .sel = SDCA_CTL_PDE_REQUESTED_PS, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_pde_req_ps_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 3, + .label = SDCA_CTL_REQUESTED_PS_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_ACTUAL_PS_NAME }, +}; + +/* + * FU 6 (vendor FU42): HPH mute + Q7.8 volume. DUAL-mode CVR-alias + * writes take effect without an SCP_COMMIT. + */ +/* + * FU 6 volume range: MIN, MAX, STEP in Q7.8 (LSB = 1/256 dB). + * 0x8000 = -128 dB, 0x7fff = +127.996 dB, STEP = 1. Framework + * sign-extends and converts to 0.01 dB TLV via (val * 100) >> 8. + */ +static u32 range_fu6_vol_data[] = { + 0x00008000, 0x00007fff, 0x00000001, +}; + +static struct sdca_control entity_fu6_controls[] = { + { .sel = SDCA_CTL_FU_MUTE, .mode = SDCA_ACCESS_MODE_DUAL, + .layers = SDCA_ACCESS_LAYER_USER, .cn_list = 0x6, .nbits = 1, + .type = SDCA_CTL_DATATYPE_ONEBIT, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .label = SDCA_CTL_MUTE_NAME }, + { .sel = SDCA_CTL_FU_CHANNEL_VOLUME, .mode = SDCA_ACCESS_MODE_DUAL, + .layers = SDCA_ACCESS_LAYER_USER, .cn_list = 0x6, .nbits = 16, + .type = SDCA_CTL_DATATYPE_Q7P8DB, + .range = { .cols = SDCA_VOLUME_LINEAR_NCOLS, .rows = 1, + .data = range_fu6_vol_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_CHANNEL_VOLUME_NAME }, +}; + +/* Entity 0x11 (FU 33) - no controls */ +static struct sdca_entity *entity_fu33_sources[] = { &wcd9378_sdca_entities[QSJ_IT33] }; + +/* Entity 0x12 (SU 35) Controls */ +static struct sdca_entity *entity_su35_sources[] = { &wcd9378_sdca_entities[QSJ_FU33] }; + +/* + * SU 35 is class-layer: the SDCA class framework drives the Selector + * directly via a DAPM mux widget, so the Control must be writeable. + */ +static struct sdca_control entity_su35_controls[] = { + { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SELECTOR_NAME }, +}; + +/* Entity 0x13 (XU 36) Controls */ +static struct sdca_entity *entity_xu36_sources[] = { &wcd9378_sdca_entities[QSJ_SU35] }; + +static struct sdca_control entity_xu36_controls[] = { + /* bypass=1: pass mic signal through XU36 without proprietary processing */ + { .sel = SDCA_CTL_XU_BYPASS, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .values = ctrl_xu36_bypass_vals, .has_default = true, .label = SDCA_CTL_BYPASS_NAME }, + { .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_xu36_id_vals, .has_fixed = true, .label = SDCA_CTL_XU_ID_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_xu36_ver_vals, .has_fixed = true, .label = SDCA_CTL_XU_VERSION_NAME }, +}; + +/* Entity 0x15 (CS 36) Controls */ +static struct sdca_control entity_cs36_controls[] = { + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_cs36_sr_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_cs36_sr_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SAMPLERATEINDEX_NAME }, +}; + +/* OT 36 mic capture: IT33 -> FU33 -> SU35 -> XU36 -> OT36. */ +static struct sdca_entity *entity_ot36_sources[] = { &wcd9378_sdca_entities[QSJ_XU36] }; + +static struct sdca_control entity_ot36_controls[] = { + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_ot36_usage_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_ot36_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_ot36_dp_vals, .has_fixed = true, + .range = { .cols = 0x10, .rows = 0x4, .data = range_ot36_dp_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DATAPORT_SELECTOR_NAME }, +}; + +/* Entity 0x17 (MFPU 236) Controls */ +static struct sdca_control entity_mfpu236_controls[] = { + { .sel = SDCA_CTL_MFPU_BYPASS, .mode = SDCA_ACCESS_MODE_DC, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .values = ctrl_mfpu236_bypass_vals, .has_fixed = true, .label = SDCA_CTL_BYPASS_NAME }, +}; + +/* Entity 0x18 (CS 236) Controls */ +static struct sdca_control entity_cs236_controls[] = { + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_cs236_sr_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_cs236_sr_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SAMPLERATEINDEX_NAME }, +}; + +/* Entity 0x19 (OT 236) - optimization stream capture output */ +static struct sdca_entity *entity_ot236_sources[] = { &wcd9378_sdca_entities[QSJ_IT33] }; + +static struct sdca_control entity_ot236_controls[] = { + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_ot236_usage_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_ot236_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_ot236_dp_vals, .has_fixed = true, + .range = { .cols = 0x10, .rows = 0x4, .data = range_ot236_dp_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DATAPORT_SELECTOR_NAME }, +}; + +static struct sdca_entity wcd9378_sdca_entities[] = { + /* [0] E001: IT 41 - PDM render stream input */ + { .id = 0x1, .label = "IT 41", .type = SDCA_ENTITY_TYPE_IT, + .iot = { .type = 0x0191, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS41] }, + .num_controls = ARRAY_SIZE(entity_it41_controls), .controls = entity_it41_controls }, + /* [1] E002: CS 41 */ + { .id = 0x2, .label = "CS 41", .type = SDCA_ENTITY_TYPE_CS, + .cs = { .type = 0x0 }, + .num_controls = ARRAY_SIZE(entity_cs41_controls), .controls = entity_cs41_controls }, + /* [2] E003: MFPU 21 */ + { .id = 0x3, .label = "MFPU 21", .type = SDCA_ENTITY_TYPE_MFPU, + .num_controls = ARRAY_SIZE(entity_mfpu21_controls), .controls = entity_mfpu21_controls, + .num_sources = ARRAY_SIZE(entity_mfpu21_sources), .sources = entity_mfpu21_sources }, + /* [3] E004: XU 42 */ + { .id = 0x4, .label = "XU 42", .type = SDCA_ENTITY_TYPE_XU, + .num_controls = ARRAY_SIZE(entity_xu42_controls), .controls = entity_xu42_controls, + .num_sources = ARRAY_SIZE(entity_xu42_sources), .sources = entity_xu42_sources }, + /* [4] E007: SU 43 - render selector (headset path), driven by GE 35 jack detection */ + { .id = 0x7, .label = "SU 43", .type = SDCA_ENTITY_TYPE_SU, + .group = &wcd9378_sdca_entities[QSJ_GE35], + .num_controls = ARRAY_SIZE(entity_su43_controls), .controls = entity_su43_controls, + .num_sources = ARRAY_SIZE(entity_su43_sources), .sources = entity_su43_sources }, + /* [5] E008: SU 45 - render selector (headphone path), driven by GE 35 jack detection */ + { .id = 0x8, .label = "SU 45", .type = SDCA_ENTITY_TYPE_SU, + .group = &wcd9378_sdca_entities[QSJ_GE35], + .num_controls = ARRAY_SIZE(entity_su45_controls), .controls = entity_su45_controls, + .num_sources = ARRAY_SIZE(entity_su45_sources), .sources = entity_su45_sources }, + /* [6] E009: PDE 47 - render power domain */ + { .id = 0x9, .label = "PDE 47", .type = SDCA_ENTITY_TYPE_PDE, + .pde = { .num_managed = ARRAY_SIZE(entity_pde47_managed), .managed = entity_pde47_managed, + .num_max_delay = ARRAY_SIZE(pde47_delays), .max_delay = pde47_delays }, + .num_controls = ARRAY_SIZE(entity_pde47_controls), .controls = entity_pde47_controls }, + /* [7] E00A: OT 43 - Headphone on jack */ + { .id = 0xa, .label = "OT 43", .type = SDCA_ENTITY_TYPE_OT, + .iot = { .type = 0x06c0 }, + .num_sources = ARRAY_SIZE(entity_ot43_sources), .sources = entity_ot43_sources }, + /* [8] E00B: OT 45 - Headset output on jack */ + { .id = 0xb, .label = "OT 45", .type = SDCA_ENTITY_TYPE_OT, + .iot = { .type = 0x06d0 }, + .num_sources = ARRAY_SIZE(entity_ot45_sources), .sources = entity_ot45_sources }, + /* [9] E00C: GE 35 - jack detection group entity */ + { .id = 0xc, .label = "GE 35", .type = SDCA_ENTITY_TYPE_GE, + .ge = { .num_modes = ARRAY_SIZE(ge35_modes), .modes = ge35_modes }, + .num_controls = ARRAY_SIZE(entity_ge35_controls), .controls = entity_ge35_controls }, + /* [10] E00D: IT 131 - optimization stream input */ + { .id = 0xd, .label = "IT 131", .type = SDCA_ENTITY_TYPE_IT, + .iot = { .type = 0x0190, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS131] }, + .num_controls = ARRAY_SIZE(entity_it131_controls), .controls = entity_it131_controls }, + /* [11] E00E: CS 131 */ + { .id = 0xe, .label = "CS 131", .type = SDCA_ENTITY_TYPE_CS, + .cs = { .type = 0x0 }, + .num_controls = ARRAY_SIZE(entity_cs131_controls), .controls = entity_cs131_controls }, + /* [12] E00F: IT 33 - headset mic input */ + { .id = 0xf, .label = "IT 33", .type = SDCA_ENTITY_TYPE_IT, + .iot = { .type = 0x06d0 }, + .num_controls = ARRAY_SIZE(entity_it33_controls), .controls = entity_it33_controls }, + /* [13] E010: PDE 34 - mic power domain */ + { .id = 0x10, .label = "PDE 34", .type = SDCA_ENTITY_TYPE_PDE, + .pde = { .num_managed = ARRAY_SIZE(entity_pde34_managed), .managed = entity_pde34_managed, + .num_max_delay = ARRAY_SIZE(pde34_delays), .max_delay = pde34_delays }, + .num_controls = ARRAY_SIZE(entity_pde34_controls), .controls = entity_pde34_controls }, + /* [14] E011: FU 33 - mic feature unit */ + { .id = 0x11, .label = "FU 33", .type = SDCA_ENTITY_TYPE_FU, + .num_sources = ARRAY_SIZE(entity_fu33_sources), .sources = entity_fu33_sources }, + /* [15] E012: SU 35 - mic selector */ + { .id = 0x12, .label = "SU 35", .type = SDCA_ENTITY_TYPE_SU, + .num_controls = ARRAY_SIZE(entity_su35_controls), .controls = entity_su35_controls, + .num_sources = ARRAY_SIZE(entity_su35_sources), .sources = entity_su35_sources }, + /* [16] E013: XU 36 - mic extension unit */ + { .id = 0x13, .label = "XU 36", .type = SDCA_ENTITY_TYPE_XU, + .num_controls = ARRAY_SIZE(entity_xu36_controls), .controls = entity_xu36_controls, + .num_sources = ARRAY_SIZE(entity_xu36_sources), .sources = entity_xu36_sources }, + /* [17] E015: CS 36 */ + { .id = 0x15, .label = "CS 36", .type = SDCA_ENTITY_TYPE_CS, + .cs = { .type = 0x0 }, + .num_controls = ARRAY_SIZE(entity_cs36_controls), .controls = entity_cs36_controls }, + /* [18] E016: OT 36 - PDM mic capture output */ + { .id = 0x16, .label = "OT 36", .type = SDCA_ENTITY_TYPE_OT, + .iot = { .type = 0x0191, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS36] }, + .num_controls = ARRAY_SIZE(entity_ot36_controls), .controls = entity_ot36_controls, + .num_sources = ARRAY_SIZE(entity_ot36_sources), .sources = entity_ot36_sources }, + /* [19] E017: MFPU 236 - optimization TX processing */ + { .id = 0x17, .label = "MFPU 236", .type = SDCA_ENTITY_TYPE_MFPU, + .num_controls = ARRAY_SIZE(entity_mfpu236_controls), .controls = entity_mfpu236_controls }, + /* [20] E018: CS 236 */ + { .id = 0x18, .label = "CS 236", .type = SDCA_ENTITY_TYPE_CS, + .cs = { .type = 0x0 }, + .num_controls = ARRAY_SIZE(entity_cs236_controls), .controls = entity_cs236_controls }, + /* [21] E019: OT 236 - optimization stream capture output */ + { .id = 0x19, .label = "OT 236", .type = SDCA_ENTITY_TYPE_OT, + .iot = { .type = 0x0190, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS131] }, + .num_controls = ARRAY_SIZE(entity_ot236_controls), .controls = entity_ot236_controls, + .num_sources = ARRAY_SIZE(entity_ot236_sources), .sources = entity_ot236_sources }, + /* E006: FU 6 (FU42) - vendor Feature Unit; HPH mute + Q7.8 volume. */ + { .id = 0x6, .label = "FU 6", .type = SDCA_ENTITY_TYPE_FU, + .num_controls = ARRAY_SIZE(entity_fu6_controls), .controls = entity_fu6_controls }, + /* Entity 0 (Function) */ + { .id = 0x0, .label = "entity0", + .num_controls = ARRAY_SIZE(entity0_controls), .controls = entity0_controls }, +}; + +/* Clusters */ +static struct sdca_channel cl1_channels[] = { /* CL01 - render (HPH) stereo */ + { .id = 0x1, .purpose = 0x1, .relationship = 0x2 }, /* Left */ + { .id = 0x2, .purpose = 0x1, .relationship = 0x3 }, /* Right */ +}; + +static struct sdca_channel cl2_channels[] = { /* CL02 - mic capture mono */ + { .id = 0xff, .purpose = 0x1, .relationship = 0x1 }, +}; + +static struct sdca_channel cl3_channels[] = { /* CL03 - optimization RX */ + { .id = 0xff, .purpose = 0x1, .relationship = 0x1 }, /* Mono */ + { .id = 0x1, .purpose = 0x1, .relationship = 0x2 }, /* Left */ + { .id = 0x2, .purpose = 0x1, .relationship = 0x3 }, /* Right */ +}; + +static struct sdca_channel cl5_channels[] = { /* CL05 - optimization TX mono */ + { .id = 0xff, .purpose = 0x1, .relationship = 0x1 }, +}; + +static struct sdca_cluster wcd9378_sdca_clusters[] = { + { .id = 0x1, .num_channels = ARRAY_SIZE(cl1_channels), .channels = cl1_channels }, + { .id = 0x2, .num_channels = ARRAY_SIZE(cl2_channels), .channels = cl2_channels }, + { .id = 0x3, .num_channels = ARRAY_SIZE(cl3_channels), .channels = cl3_channels }, + { .id = 0x5, .num_channels = ARRAY_SIZE(cl5_channels), .channels = cl5_channels }, +}; + +/* Init table transcribed from ASL. */ +static struct sdca_init_write wcd9378_sdca_init_table[] = { + { .addr = 0x401804f0, .val = 0x00 }, /* DIGITAL_PLATFORM_CTL */ + { .addr = 0x4018046e, .val = 0x10 }, /* DIGITAL_INTR_MODE */ + { .addr = 0x0000004d, .val = 0x01 }, /* SWRS_SCP_BUSCLOCK_BASE */ + { .addr = 0x00000062, .val = 0x02 }, /* SWRS_SCP_BUSCLOCK_SCALE_BANK */ + { .addr = 0x4018016a, .val = 0x80 }, /* CP_DTOP_CTRL_14 */ + { .addr = 0x40180165, .val = 0x6b }, /* CP_DTOP_CTRL_9 */ + { .addr = 0x40180103, .val = 0x1e }, /* SLEEP_CTL BG_CTL (0.9V) */ + { .addr = 0x40180103, .val = 0x9e }, /* SLEEP_CTL BG_EN */ + { .addr = 0x40180103, .val = 0xde }, /* SLEEP_CTL LDOL_BG_SEL */ + { .addr = 0x40180029, .val = 0xb5 }, /* BIAS_VBG_FINE_ADJ */ + { .addr = 0x40180001, .val = 0x80 }, /* ANA_BIAS ANALOG_BIAS_EN */ + { .addr = 0x40180001, .val = 0xc0 }, /* ANA_BIAS PRECHRG_EN(1) */ + { .addr = 0x40180001, .val = 0x80 }, /* ANA_BIAS PRECHRG_EN(0) */ + { .addr = 0x4018007b, .val = 0xa2 }, /* TX_COM_TXFE_DIV_CTL SEQ_BYPASS */ + { .addr = 0x40180465, .val = 0x17 }, /* PDM_WD_CTL0 TIME_OUT_SEL_PCM */ + { .addr = 0x40180466, .val = 0x17 }, /* PDM_WD_CTL1 TIME_OUT_SEL_PCM */ + { .addr = 0x4018006c, .val = 0x01 }, /* MICB1_TEST_CTL_2 IBIAS_LDO_DRIVER */ + { .addr = 0x40180072, .val = 0x81 }, /* MICB3_TEST_CTL_2 IBIAS_LDO_DRIVER */ + { .addr = 0x401800ce, .val = 0x38 }, /* HPH_OCP_CTL OCP_FSM_EN */ + { .addr = 0x401800ce, .val = 0x3a }, /* HPH_OCP_CTL SCD_OP_EN */ + { .addr = 0x401800d4, .val = 0xe1 }, /* HPH_L_TEST OCP_DET_EN */ + { .addr = 0x401800d7, .val = 0xe1 }, /* HPH_R_TEST OCP_DET_EN */ + { .addr = 0x4018044e, .val = 0x04 }, /* CDC_HPH_GAIN_CTL HPHL_RX_EN */ + { .addr = 0x4018044e, .val = 0x0c }, /* CDC_HPH_GAIN_CTL HPHR_RX_EN */ + { .addr = 0x4018000f, .val = 0x0c }, /* ANA_TX_CH2 GAIN (18.0dB) */ + { .addr = 0x40180133, .val = 0x84 }, /* HPH_NEW_INT_RDAC_HD2_CTL_L */ + { .addr = 0x40180136, .val = 0x84 }, /* HPH_NEW_INT_RDAC_HD2_CTL_R */ + { .addr = 0x401800d9, .val = 0x19 }, /* HPH_RDAC_CLK_CTL1 OPAMP_CHOP_CLK_EN */ + { .addr = 0x40180132, .val = 0x50 }, /* HPH_NEW_INT_RDAC_GAIN_CTL RDAC_GAINCTL(0.55) */ + { .addr = 0x40180510, .val = 0x05 }, /* SEQR_CTRL HPH_UP_T0 */ + { .addr = 0x40180519, .val = 0x05 }, /* SEQR_CTRL HPH_UP_T9 */ + { .addr = 0x4018051b, .val = 0x06 }, /* SEQR_CTRL HPH_DN_T0 */ + { .addr = 0x40180414, .val = 0x02 }, /* CDC_COMP_CTL_0 HPHL_COMP_EN */ + { .addr = 0x40180414, .val = 0x03 }, /* CDC_COMP_CTL_0 HPHR_COMP_EN */ + { .addr = 0x401804f2, .val = 0x80 }, /* DRE_DLY_VAL SWR_HPHL(0) */ + { .addr = 0x401804f2, .val = 0x00 }, /* DRE_DLY_VAL SWR_HPHR(0) */ + { .addr = 0x40180501, .val = 0x01 }, /* SEQR_CTRL SYS_USAGE_CTRL */ + /* Arms MBHC: jack insertion asserts SDCA_4 (GE_DETECTED_MODE). */ + { .addr = 0x40180601, .val = 0x01 }, /* MBHC_CTRL DEVICE_DET */ + { .addr = 0x40180414, .val = 0x00 }, /* CDC_COMP_CTL_0 */ + { .addr = 0x401804f2, .val = 0x88 }, /* DRE_DLY_VAL */ + { .addr = 0x40180517, .val = 0x07 }, /* SEQR_CTRL HPH_UP_T7 */ + { .addr = 0x4018051c, .val = 0x07 }, /* SEQR_CTRL HPH_DN_T1 */ + { .addr = 0x401800ce, .val = 0x28 }, /* HPH_OCP_CTL */ + { .addr = 0x401800d4, .val = 0xe0 }, /* HPH_L_TEST */ + { .addr = 0x401800d7, .val = 0xe0 }, /* HPH_R_TEST */ + { .addr = 0x40180510, .val = 0x07 }, /* SEQR_CTRL HPH_UP_T0 */ + { .addr = 0x40c80008, .val = 0x01 }, /* SMP_JACK_CTRL FUNC_ACT (RESET_FUNCTION_NOW) */ + { .addr = 0x40c00008, .val = 0x02 }, /* SMP_JACK_CTRL CMT_GRP_MASK */ + { .addr = 0x40c80000, .val = 0xff }, /* SMP_JACK_CTRL FUNC_STAT */ + { .addr = 0x00000000, .val = 0x08 }, /* clear DP0 INT status SDCA_CASCADE */ + { .addr = 0x00000041, .val = 0x08 }, /* SCP_INT_STATUS_MASK_1 PORT_0_CASCADE_3 */ + /* Only SDCA_4 unmasked; protection IRQs stay masked. */ + { .addr = 0x0000005c, .val = 0x10 }, /* INTMASK_1 SDCA_4 (GE_DETECTED_MODE) */ + { .addr = 0x4018016a, .val = 0x00 }, /* CP_DTOP_CTRL_14 */ + { .addr = 0x40180165, .val = 0x6b }, /* CP_DTOP_CTRL_9 */ + /* + * L_DET_EN is left disabled: asserting SDCA_4 before the machine + * card registers the jack blocks the deferred card probe. + */ +}; + +/* Function Descriptor */ +static struct sdca_function_desc wcd9378_sdca_desc = { + .adr = 0x3, + .type = SDCA_FUNCTION_TYPE_SIMPLE_JACK, + .name = SDCA_FUNCTION_TYPE_SIMPLE_NAME, +}; + +/* Main Function Data */ +static struct sdca_function_data wcd9378_sdca_data = { + .desc = &wcd9378_sdca_desc, + .num_entities = ARRAY_SIZE(wcd9378_sdca_entities), + .entities = wcd9378_sdca_entities, + .num_clusters = ARRAY_SIZE(wcd9378_sdca_clusters), + .clusters = wcd9378_sdca_clusters, + .num_init_table = ARRAY_SIZE(wcd9378_sdca_init_table), + .init_table = wcd9378_sdca_init_table, + .reset_max_delay = 25000, /* 25ms — WCD9378 power-on reset completes well within this */ +}; + +/* Vendor SCP register: host clock divide-by-2 (bank 1 shadow). */ +#define WCD9378_SCP_HOST_CLK_DIV2_CTL_B1 0xf0 + +/* Slave ports 1..8, mapped to master ports via qcom,port-mapping. */ +#define WCD9378_SDCA_MAX_PORTS 8 + +static const char * const wcd9378_sdca_supplies[] = { + "vdd-buck", "vdd-rxtx", "vdd-io", "vdd-mic-bias", +}; + +int wcd9378_sdca_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + struct device *dev = &slave->dev; + struct sdw_dpn_prop *sink, *src; + int ret; + + /* + * All SoundWire slave properties for compute-mode WCD9378 are + * fixed by the silicon and are not described in DT, so we do + * not call sdw_slave_read_prop() here. + */ + prop->use_domain_irq = true; + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY | + SDW_SCP_INT1_IMPL_DEF; + prop->simple_clk_stop_capable = true; + prop->paging_support = true; + prop->clock_reg_supported = true; + prop->lane_control_support = true; + + /* Source ports: DP2 (headset mic), DP5 (optimisation TX). */ + prop->source_ports = BIT(2) | BIT(5); + /* Sink ports: DP6 (HPH audio), DP7 (HPH envelope), DP8 (optimisation RX). */ + prop->sink_ports = BIT(6) | BIT(7) | BIT(8); + + src = devm_kcalloc(dev, 2, sizeof(*src), GFP_KERNEL); + if (!src) + return -ENOMEM; + + src[0].num = 2; + src[0].type = SDW_DPN_SIMPLE; + src[0].simple_ch_prep_sm = true; + src[0].ch_prep_timeout = 10; + src[0].max_ch = 1; + src[0].min_ch = 1; + + src[1].num = 5; + src[1].type = SDW_DPN_SIMPLE; + src[1].simple_ch_prep_sm = true; + src[1].ch_prep_timeout = 10; + src[1].max_ch = 1; + src[1].min_ch = 1; + + prop->src_dpn_prop = src; + + sink = devm_kcalloc(dev, 3, sizeof(*sink), GFP_KERNEL); + if (!sink) + return -ENOMEM; + + sink[0].num = 6; + sink[0].type = SDW_DPN_SIMPLE; + sink[0].simple_ch_prep_sm = true; + sink[0].ch_prep_timeout = 10; + sink[0].max_ch = 2; + sink[0].min_ch = 1; + + sink[1].num = 7; + sink[1].type = SDW_DPN_FULL; + sink[1].simple_ch_prep_sm = true; + sink[1].ch_prep_timeout = 10; + sink[1].max_ch = 1; + sink[1].min_ch = 1; + + sink[2].num = 8; + sink[2].type = SDW_DPN_REDUCED; + sink[2].simple_ch_prep_sm = true; + sink[2].ch_prep_timeout = 10; + sink[2].max_ch = 2; + sink[2].min_ch = 1; + + prop->sink_dpn_prop = sink; + + ret = device_property_read_u32_array(dev, "qcom,port-mapping", + &slave->m_port_map[1], + WCD9378_SDCA_MAX_PORTS); + if (ret) + return dev_err_probe(dev, ret, "qcom,port-mapping missing\n"); + + return 0; +} + +static int wcd9378_sdca_populate_function(struct device *dev, + struct sdca_function_data *function) +{ + /* @function->desc is already set by the framework; fill payload only. */ + if (function->desc->type != wcd9378_sdca_desc.type) + return -EINVAL; + + function->num_entities = wcd9378_sdca_data.num_entities; + function->entities = wcd9378_sdca_data.entities; + function->num_clusters = wcd9378_sdca_data.num_clusters; + function->clusters = wcd9378_sdca_data.clusters; + function->num_init_table = wcd9378_sdca_data.num_init_table; + function->init_table = wcd9378_sdca_data.init_table; + function->reset_max_delay = wcd9378_sdca_data.reset_max_delay; + + return 0; +} + +static const struct sdca_class_ops wcd9378_sdca_class_ops = { + .populate_function = wcd9378_sdca_populate_function, +}; + +int wcd9378_sdca_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct device *dev = &slave->dev; + struct sdca_device_data *data = &slave->sdca_data; + struct wcd9378_priv *priv; + struct gpio_desc *reset; + int ret; + + /* + * 0x0217:0x0110 covers both mobile (non-SDCA) and SDCA-compliant + * fuse variants. Only SDCA-compliant nodes bind to this driver; + * mobile-mode nodes are handled by a different driver. + */ + if (!device_property_read_bool(dev, "qcom,sdca-compliant")) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + + /* No SPMI parent: supplies and reset live on the SoundWire DT node. */ + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(wcd9378_sdca_supplies), + wcd9378_sdca_supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to enable supplies\n"); + + reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), + "failed to get reset GPIO\n"); + + if (reset) { + gpiod_set_value(reset, 1); + usleep_range(20, 30); + gpiod_set_value(reset, 0); + usleep_range(20, 30); + } + + /* SCP writes below need the slave attached. */ + ret = sdw_slave_wait_for_init(slave, 5000); + if (ret) + return dev_err_probe(dev, ret, + "slave attach timeout: %d\n", ret); + + /* + * TX PDM clock: bank-1 shadow + SCP_COMMIT. SCP survives PDE + * cycles; one-shot before any port is enabled. + */ + ret = sdw_write_no_pm(slave, WCD9378_SCP_HOST_CLK_DIV2_CTL_B1, 0x01); + if (ret) + return dev_err_probe(dev, ret, + "HOST_CLK_DIV2_CTL_B1: %d\n", ret); + + ret = sdw_write_no_pm(slave, SDW_SCP_COMMIT, 0x02); + if (ret) + return dev_err_probe(dev, ret, "SCP_COMMIT: %d\n", ret); + + /* DT has no DisCo enumeration; seed the descriptor here. */ + if (!data->num_functions) { + data->function[0].type = wcd9378_sdca_desc.type; + data->function[0].adr = wcd9378_sdca_desc.adr; + data->function[0].name = wcd9378_sdca_desc.name; + data->num_functions = 1; + } + + return sdca_class_probe(slave, &priv->class, &wcd9378_sdca_class_ops); +} + +void wcd9378_sdca_remove(struct sdw_slave *slave) +{ + struct wcd9378_priv *priv = dev_get_drvdata(&slave->dev); + + sdca_class_remove(&priv->class); +} + +int wcd9378_sdca_runtime_suspend(struct device *dev) +{ + struct wcd9378_priv *priv = dev_get_drvdata(dev); + + return sdca_class_runtime_suspend(&priv->class); +} + +int wcd9378_sdca_runtime_resume(struct device *dev) +{ + struct wcd9378_priv *priv = dev_get_drvdata(dev); + + return sdca_class_runtime_resume(&priv->class); +} + +int wcd9378_sdca_system_suspend(struct device *dev) +{ + struct wcd9378_priv *priv = dev_get_drvdata(dev); + + return sdca_class_system_suspend(&priv->class); +} + +int wcd9378_sdca_system_resume(struct device *dev) +{ + struct wcd9378_priv *priv = dev_get_drvdata(dev); + + return sdca_class_system_resume(&priv->class); +} + +/* SoundWire slave-driver plumbing lives in wcd9378-sdw.c. */ diff --git a/sound/soc/codecs/wcd9378-sdca.h b/sound/soc/codecs/wcd9378-sdca.h new file mode 100644 index 00000000000000..6ddae9da395b55 --- /dev/null +++ b/sound/soc/codecs/wcd9378-sdca.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* Copyright (c) 2025 Qualcomm Technologies, Inc. */ + +#ifndef _WCD9378_SDCA_H +#define _WCD9378_SDCA_H + +#include +#include + +int wcd9378_sdca_probe(struct sdw_slave *slave, + const struct sdw_device_id *id); +void wcd9378_sdca_remove(struct sdw_slave *slave); +int wcd9378_sdca_read_prop(struct sdw_slave *slave); + +int wcd9378_sdca_runtime_suspend(struct device *dev); +int wcd9378_sdca_runtime_resume(struct device *dev); +int wcd9378_sdca_system_suspend(struct device *dev); +int wcd9378_sdca_system_resume(struct device *dev); + +#endif /* _WCD9378_SDCA_H */ diff --git a/sound/soc/codecs/wcd9378-sdw.c b/sound/soc/codecs/wcd9378-sdw.c new file mode 100644 index 00000000000000..52295e7646cf06 --- /dev/null +++ b/sound/soc/codecs/wcd9378-sdw.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright (c) 2025 Qualcomm Technologies, Inc. + +/* + * WCD9378 (Tambora) SoundWire driver glue for the SDCA compute-mode + * codec. The SDCA topology and hardware-specific probe logic live in + * wcd9378-sdca.c; this file carries the SoundWire slave driver plumbing + * and the module boilerplate. + */ + +#include +#include +#include +#include +#include +#include "wcd9378-sdca.h" + +static const struct dev_pm_ops wcd9378_sdw_pm_ops = { + SYSTEM_SLEEP_PM_OPS(wcd9378_sdca_system_suspend, + wcd9378_sdca_system_resume) + RUNTIME_PM_OPS(wcd9378_sdca_runtime_suspend, + wcd9378_sdca_runtime_resume, NULL) +}; + +static const struct sdw_slave_ops wcd9378_sdw_ops = { + .read_prop = wcd9378_sdca_read_prop, +}; + +static const struct sdw_device_id wcd9378_sdw_id[] = { + SDW_SLAVE_ENTRY(0x0217, 0x0110, 0), + { } +}; +MODULE_DEVICE_TABLE(sdw, wcd9378_sdw_id); + +static struct sdw_driver wcd9378_sdw_driver = { + .driver = { + .name = "wcd9378", + .pm = pm_ptr(&wcd9378_sdw_pm_ops), + }, + .probe = wcd9378_sdca_probe, + .remove = wcd9378_sdca_remove, + .id_table = wcd9378_sdw_id, + .ops = &wcd9378_sdw_ops, +}; +module_sdw_driver(wcd9378_sdw_driver); + +MODULE_DESCRIPTION("Qualcomm WCD9378 (Tambora) SoundWire codec"); +MODULE_AUTHOR("Qualcomm Technologies, Inc."); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS("SND_SOC_SDCA"); +MODULE_IMPORT_NS("SND_SOC_SDCA_CLASS"); From b2047b8cadadccb1c9269ce756c399cd9aef2c82 Mon Sep 17 00:00:00 2001 From: Liviu Nicoara Date: Tue, 29 Sep 2026 07:57:55 -0400 Subject: [PATCH 1396/1417] ASoC: codecs: lpass-wsa-macro: rewrite the interpolator volume after enabling clocks Commit 902f497a1ff5 ("ASoC: codecs: lpass-wsa-macro: remove useless gain read/write sequence") removed the read and write of the digital volume register in wsa_macro_enable_interpolator(), on the grounds that writing back the value just read does nothing. The comment above it, "apply gain after int clk is enabled", was left in place. On the Dell XPS 13 9345 (X1E80100, four WSA8845 amplifiers on two WSA macros) the write does something: a volume change made while the path is idle does not take effect when playback starts. Lowering the digital volume from 81 to 63 with nothing playing, then playing a test tone, gave about the same level as before the change. With the rewrite restored, lowering it from 81 to 69 while idle played audibly quieter, and restoring 81 while idle brought the level back. Changes made during playback take effect with or without the rewrite. The register already holds the new value when this happens. Without the rewrite, after an idle change from 69 to 81, playback stayed at the old level while the register, read from the hardware through /dev/mem, held the new one (0xfd). Writing that same value back through /dev/mem brought the level up at once. This is the behaviour described in commit 46188db080bd ("ASoC: codecs: lpass-wsa-macro: fix compander volume hack"): "the volume registers still need to be written after enabling clocks in order for any prior updates to take effect." The value read comes from the register cache, so the write pushes the last requested volume to the hardware once its clock runs. Restore the rewrite in the interpolator's POST_PMU event only. The mix path event removed later in the same series is not brought back. Fixes: 902f497a1ff5 ("ASoC: codecs: lpass-wsa-macro: remove useless gain read/write sequence") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Liviu Nicoara Reviewed-by: Johan Hovold Link: https://patch.msgid.link/20260929115755.6096-1-lnicoara@thinkoid.org Signed-off-by: Mark Brown --- sound/soc/codecs/lpass-wsa-macro.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c index cfd2ac0a6cdac4..38e37908baf91a 100644 --- a/sound/soc/codecs/lpass-wsa-macro.c +++ b/sound/soc/codecs/lpass-wsa-macro.c @@ -1841,6 +1841,7 @@ static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int gain; u16 gain_reg; u16 reg; struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); @@ -1882,6 +1883,8 @@ static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, CDC_WSA_RX_PGA_HALF_DB_MASK, CDC_WSA_RX_PGA_HALF_DB_ENABLE); } + gain = snd_soc_component_read(component, gain_reg); + snd_soc_component_write(component, gain_reg, gain); wsa_macro_config_ear_spkr_gain(component, wsa, event, gain_reg); break; From 08e4f9133c68413acbe365a8d02fa15fb24cb27b Mon Sep 17 00:00:00 2001 From: Christian Ruppert Date: Sat, 19 Sep 2026 17:18:38 +0200 Subject: [PATCH 1397/1417] Fix Audient EVO4 master playback control name The Audient EVO4 master volume mixer channel enumerates as 'EVO4 '. Rename it to 'Master' according to control-names.rst. Signed-off-by: Christian Ruppert Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260919151840.24371-2-arc@gmx.li --- sound/usb/mixer_maps.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index f8e1f78039e11c..98b4146216ae7c 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -505,6 +505,14 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { {} }; +/* Audient EVO4: Rename FU 10 from 'EVO4 ' to 'Master' according to the + * ALSA naming convention + */ +static const struct usbmix_name_map audient_evo4_map[] = { + { 10, "Master Playback" }, + {} +}; + /* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is * traced through to the Speaker output terminal, so it is named "Speaker * Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI @@ -632,6 +640,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x2708, 0x0002), .map = audient_id14_map, }, + { + /* Audient EVO4 MkI */ + .id = USB_ID(0x2708, 0x0006), + .map = audient_evo4_map, + }, { /* Audient iD14 MkII */ .id = USB_ID(0x2708, 0x0008), From 025877a3833d12aff023ce9e89d75f4d6fd61ddb Mon Sep 17 00:00:00 2001 From: Christian Ruppert Date: Sat, 19 Sep 2026 17:18:39 +0200 Subject: [PATCH 1398/1417] Add Audient EVO4 mixer quirks The controls enumerated by the Audient EVO4 are incomplete. Add missing controls for * channel muting * the cross-fader * the recording mixer * phantom power The driver is structured so that it should be trivial to extend it to the EVO8 (and potentially other Audient devices) but we need a kind soul who has access to that hardware to correctly map the control names and test everything. Signed-off-by: Christian Ruppert Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260919151840.24371-3-arc@gmx.li --- MAINTAINERS | 7 + sound/usb/Makefile | 1 + sound/usb/mixer_evo.c | 505 +++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_evo.h | 12 + sound/usb/mixer_quirks.c | 5 + 5 files changed, 530 insertions(+) create mode 100644 sound/usb/mixer_evo.c create mode 100644 sound/usb/mixer_evo.h diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c24..656721c0471331 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4346,6 +4346,13 @@ F: drivers/net/ieee802154/at86rf230.h F: drivers/net/ieee802154/atusb.c F: drivers/net/ieee802154/atusb.h +AUDIENT EVO MIXER DRIVER +M: Christian Ruppert +L: linux-sound@vger.kernel.org +S: Maintained +F: sound/usb/mixer_evo.c +F: sound/usb/mixer_evo.h + AUDIT SUBSYSTEM M: Paul Moore M: Eric Paris diff --git a/sound/usb/Makefile b/sound/usb/Makefile index e62794a87e73aa..2e842dcc73de1a 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -11,6 +11,7 @@ snd-usb-audio-y := card.o \ helper.o \ implicit.o \ mixer.o \ + mixer_evo.o \ mixer_quirks.o \ mixer_scarlett.o \ mixer_scarlett2.o \ diff --git a/sound/usb/mixer_evo.c b/sound/usb/mixer_evo.c new file mode 100644 index 00000000000000..c327b82c8d2d60 --- /dev/null +++ b/sound/usb/mixer_evo.c @@ -0,0 +1,505 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Audient EVO driver for ALSA + * Copyright (C) 2026 Christian Ruppert + * + * Based on the work of Ivan Hazucha for audient-evo-py. + * https://github.com/vanzaho/audient-evo-py/ + */ + + +#include +#include +#include +#include +#include +#include + +#include "usbaudio.h" +#include "mixer.h" +#include "mixer_quirks.h" +#include "helper.h" +#include "mixer_evo.h" + + +enum snd_evo_type { + SND_EVO_TYPE_MONITOR = 0, + SND_EVO_TYPE_PHANTOM, + SND_EVO_TYPE_MUTE, + SND_EVO_TYPE_MUTE_OUT, + SND_EVO_TYPE_MIXER, + SND_EVO_TYPE_NCTYPES +}; + +static const struct snd_evo_ctrl_type { + snd_ctl_elem_type_t type; + bool invert; + union { + int integer; + } min; + union { + int integer; + } max; + u16 wValue; + u16 wIndex; +} snd_evo_ctypes[SND_EVO_TYPE_NCTYPES] = { + [SND_EVO_TYPE_MONITOR] = { + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .min.integer = 0, + .max.integer = 127, + .wValue = 0x0000, + .wIndex = 0x3800, + }, + [SND_EVO_TYPE_PHANTOM] = { + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .invert = false, + .min.integer = 0, + .max.integer = 1, + .wValue = 0x0000, + .wIndex = 0x3A00, + }, + [SND_EVO_TYPE_MUTE] = { + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .invert = true, + .min.integer = 0, + .max.integer = 1, + .wValue = 0x0200, + .wIndex = 0x3A00, + }, + [SND_EVO_TYPE_MUTE_OUT] = { + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .invert = true, + .min.integer = 0, + .max.integer = 1, + .wValue = 0x0100, + .wIndex = 0x3B00, + }, + [SND_EVO_TYPE_MIXER] = { + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .min.integer = -128 * 256, + .max.integer = 6 * 256, + .wValue = 0x0100, + .wIndex = 0x3C00, + }, +}; + +#define SND_EVO_FLAGS_DIRECTION (1L<<0) +#define SND_EVO_FLAGS_CAPTURE (1L<<0) +#define SND_EVO_FLAGS_PLAYBACK (0L<<0) + +struct snd_evo_ctrl { + char *basename; + enum snd_evo_type type; + unsigned int channels; + unsigned int hwchannels; + unsigned int val_offs; + unsigned long flags; +}; + +static const struct snd_evo_ctrl evo4_controls[] = { + { + .basename = "Master", + .type = SND_EVO_TYPE_MUTE_OUT, + .channels = 1, + .hwchannels = 1, + .val_offs = 0, + .flags = SND_EVO_FLAGS_PLAYBACK, + }, + { + .basename = "Mic", + .type = SND_EVO_TYPE_MUTE, + .channels = 4, + .hwchannels = 2, + .val_offs = 0, + .flags = SND_EVO_FLAGS_PLAYBACK, + }, + { + .basename = "Monitor", + .type = SND_EVO_TYPE_MONITOR, + .channels = 1, + .hwchannels = 1, + .val_offs = 0, + .flags = SND_EVO_FLAGS_PLAYBACK, + }, + { + .basename = "Mic 1", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 0, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Mic 2", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 2, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Master Left", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 4, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Master Right", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 6, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Loopback Left", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 8, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Loopback Right", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 10, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Mic 1 Phantom", + .type = SND_EVO_TYPE_PHANTOM, + .channels = 1, + .hwchannels = 1, + .val_offs = 0, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Mic 2 Phantom", + .type = SND_EVO_TYPE_PHANTOM, + .channels = 1, + .hwchannels = 1, + .val_offs = 1, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { 0 }, /* sentinel */ +}; + +static const struct evo_devinfo { + u32 usb_id; + const struct snd_evo_ctrl *controls; +} evo_devinfo[] = { + { + .usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID), + .controls = evo4_controls, + }, + { + .usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID), + .controls = NULL, + }, + { 0 } /* sentinel */ +}; + + +static inline unsigned long snd_evo_kctl_priv(enum snd_evo_type type, + u8 valoffs, u8 hwchannels) +{ + unsigned long type_l = type & 0xFF; + unsigned long valoffs_l = valoffs & 0xFF; + unsigned long hwchannels_l = hwchannels & 0xFF; + + return (hwchannels_l << 16) | (valoffs_l << 8) | type_l; +} + +static __always_inline +const struct snd_evo_ctrl_type *snd_evo_kctl_type(struct snd_kcontrol *kctl) +{ + return &snd_evo_ctypes[kctl->private_value & 0xFF]; +} + +static __always_inline u16 snd_evo_kctl_valoffs(struct snd_kcontrol *kctl) +{ + return (kctl->private_value >> 8) & 0xFF; +} + +static __always_inline u16 snd_evo_kctl_hwchannels(struct snd_kcontrol *kctl) +{ + return (kctl->private_value >> 16) & 0xFF; +} + +static inline u16 snd_evo_kctl_channels(struct snd_kcontrol *kctl) +{ + struct usb_mixer_elem_info *info = kctl->private_data; + + return info->channels; +} + + +#define EVO_REQ_CUR (0x01) +#define EVO_REQTYPE_SET (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) +#define EVO_REQTYPE_GET (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +static inline int snd_evo_get_cur(struct usb_mixer_interface *mixer, + u16 value, u16 index, + void *buf, size_t len) +{ + struct usb_device *const dev = mixer->chip->dev; + + return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + EVO_REQ_CUR, EVO_REQTYPE_GET, + value, index, buf, len); +} + +static inline int snd_evo_set_cur(struct usb_mixer_interface *mixer, + u16 value, u16 index, + void *buf, size_t len) +{ + struct usb_device *const dev = mixer->chip->dev; + + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + EVO_REQ_CUR, EVO_REQTYPE_SET, + value, index, buf, len); +} + + +static int snd_ctl_evo_boolean_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *const mixer = list->mixer; + + const unsigned int nchan = snd_evo_kctl_channels(kctl); + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int i; + + for (i = 0; i < hwchan; i++) { + u32 buf; + int ret; + + ret = snd_evo_get_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + + ctl_val->value.integer.value[i] = le32_to_cpu(buf) ? 1 : 0; + + if (ctype->invert) + ctl_val->value.integer.value[i] ^= 0x1; + } + + for (; i < nchan; i++) + ctl_val->value.integer.value[i] = ctype->invert + ? ctype->max.integer + : ctype->min.integer; + + return 0; +} + +static int snd_ctl_evo_boolean_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int changed = 0; + int i; + + for (i = 0; i < hwchan; i++) { + u32 rbuf; + u32 buf = cpu_to_le32(ctl_val->value.integer.value[i] ? 1 : 0); + int ret; + + if (ctype->invert) + buf ^= 0x1; + + ret = snd_evo_get_cur(mixer, value + i, index, + &rbuf, sizeof(rbuf)); + if (ret < 0) + return ret; + + if (rbuf != buf) { + changed = 1; + + ret = snd_evo_set_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + } + } + + return changed; +} + +static int snd_ctl_evo_integer_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *const mixer = list->mixer; + + const unsigned int nchan = snd_evo_kctl_channels(kctl); + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int i; + + for (i = 0; i < hwchan; i++) { + u16 buf; + s16 val; + int ret; + + ret = snd_evo_get_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + + val = le16_to_cpu(buf); + ctl_val->value.integer.value[i] = val; + } + + for (; i < nchan; i++) + ctl_val->value.integer.value[i] = ctype->min.integer; + + return 0; +} + +static int snd_ctl_evo_integer_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int changed = 0; + int i; + + for (i = 0; i < hwchan; i++) { + s16 rbuf; + s16 buf = cpu_to_le16(ctl_val->value.integer.value[i]); + int ret; + + ret = snd_evo_get_cur(mixer, value + i, index, + &rbuf, sizeof(rbuf)); + if (ret < 0) + return ret; + + if (rbuf != buf) { + changed = 1; + + ret = snd_evo_set_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + } + } + + return changed; +} + +static int snd_ctl_evo_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + + uinfo->type = ctype->type; + uinfo->access = SNDRV_CTL_ELEM_ACCESS_READWRITE + | SNDRV_CTL_ELEM_ACCESS_VOLATILE; + uinfo->count = snd_evo_kctl_channels(kctl); + uinfo->value.integer.min = ctype->min.integer; + uinfo->value.integer.max = ctype->max.integer; + return 0; +} + + +static int snd_evo_add_ctrl(struct usb_mixer_interface *mixer, + const struct snd_evo_ctrl *ctrl) +{ + const struct snd_evo_ctrl_type *ctype = &snd_evo_ctypes[ctrl->type]; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct snd_kcontrol_new knew = { 0 }; + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.name = name; + knew.info = snd_ctl_evo_info; + knew.private_value = snd_evo_kctl_priv(ctrl->type, ctrl->val_offs, + ctrl->hwchannels); + + switch (ctype->type) { + case SNDRV_CTL_ELEM_TYPE_INTEGER: + knew.get = snd_ctl_evo_integer_get; + knew.put = snd_ctl_evo_integer_set; + break; + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + knew.get = snd_ctl_evo_boolean_get; + knew.put = snd_ctl_evo_boolean_set; + break; + default: + return -EINVAL; + } + + snprintf(name, sizeof(name), "%s %s %s", ctrl->basename, + ((ctrl->flags & SND_EVO_FLAGS_DIRECTION) + == SND_EVO_FLAGS_CAPTURE) ? "Capture" : "Playback", + (ctype->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN) + ? "Switch" : "Volume"); + + elem = kzalloc_obj(*elem); + if (!elem) + return -ENOMEM; + + elem->head.mixer = mixer; + elem->channels = ctrl->channels; + + kctl = snd_ctl_new1(&knew, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + + kctl->private_free = snd_usb_mixer_elem_free; + + return snd_usb_mixer_add_control(&elem->head, kctl); +} + + +/* called from mixer_quirks.c */ +int snd_evo_controls_create(struct usb_mixer_interface *mixer) +{ + const struct evo_devinfo *info; + const struct snd_evo_ctrl *ctrl; + + for (info = evo_devinfo; info->controls; info++) + if (info->usb_id == mixer->chip->usb_id) + break; + + if (!info->controls) + return -ENODEV; + + for (ctrl = info->controls; ctrl->basename; ctrl++) { + const int ret = snd_evo_add_ctrl(mixer, ctrl); + + if (ret < 0) + return ret; + } + + return 0; +} diff --git a/sound/usb/mixer_evo.h b/sound/usb/mixer_evo.h new file mode 100644 index 00000000000000..7fe7e197185cf3 --- /dev/null +++ b/sound/usb/mixer_evo.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_MIXER_EVO_H +#define __USB_MIXER_EVO_H + +#include "mixer.h" + +#define USB_AUDIENT_VID (0x2708) +#define USB_EVO4_PID (0x0006) + +int snd_evo_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_EVO_H */ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index fc622eb95dc584..1e266fdbbb8e77 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -38,6 +38,7 @@ #include "mixer_scarlett2.h" #include "mixer_us16x08.h" #include "mixer_s1810c.h" +#include "mixer_evo.h" #include "helper.h" #include "fcp.h" @@ -4539,6 +4540,10 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) err = snd_fcp_init(mixer); break; + case USB_ID(USB_AUDIENT_VID, USB_EVO4_PID): /* Audient EVO 4 */ + err = snd_evo_controls_create(mixer); + break; + case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */ err = snd_soundblaster_e1_switch_create(mixer); break; From 19b42c1fff7f220a6346c614d79ce1bda6d95b40 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 29 Sep 2026 22:17:26 +0800 Subject: [PATCH 1399/1417] ALSA: usb-audio: Protect Roland control activation The USB MIDI driver changes the Roland MIDI Input Mode control's access flags directly from the rawmidi open and close paths. These changes are not protected by the ALSA control core and the corresponding notifications can race with control access. Use snd_ctl_activate_id() so that the control core updates the access flags and sends the notification under its lock. Release the USB MIDI mutex before calling it because the control write path holds controls_rwsem while roland_load_put() takes the USB MIDI mutex. Keep the state transition and alternate-setting change under the USB MIDI mutex; rawmidi's open mutex serializes the enclosing open and close paths. Fixes: 96f61d9ade82 ("sound: usb-audio: allow switching altsetting on Roland USB MIDI devices") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260929141726.2166899-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/usb/midi.c | 53 ++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/sound/usb/midi.c b/sound/usb/midi.c index 0480a9b89e8f3b..e3d15755406138 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -1150,41 +1150,42 @@ static int substream_open(struct snd_rawmidi_substream *substream, int dir, int open) { struct snd_usb_midi *umidi = substream->rmidi->private_data; - struct snd_kcontrol *ctl; + struct snd_ctl_elem_id ctl_id; + bool activate_ctl = false; + bool active; guard(rwsem_read)(&umidi->disc_rwsem); if (umidi->disconnected) return open ? -ENODEV : 0; - guard(mutex)(&umidi->mutex); - if (open) { - if (!umidi->opened[0] && !umidi->opened[1]) { - if (umidi->roland_load_ctl) { - ctl = umidi->roland_load_ctl; - ctl->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(umidi->card, - SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); - update_roland_altsetting(umidi); + scoped_guard(mutex, &umidi->mutex) { + if (open) { + if (!umidi->opened[0] && !umidi->opened[1]) { + if (umidi->roland_load_ctl) { + ctl_id = umidi->roland_load_ctl->id; + activate_ctl = true; + active = false; + update_roland_altsetting(umidi); + } } - } - umidi->opened[dir]++; - if (umidi->opened[1]) - snd_usbmidi_input_start(&umidi->list); - } else { - umidi->opened[dir]--; - if (!umidi->opened[1] && !umidi->keep_input_running) - snd_usbmidi_input_stop(&umidi->list); - if (!umidi->opened[0] && !umidi->opened[1]) { - if (umidi->roland_load_ctl) { - ctl = umidi->roland_load_ctl; - ctl->vd[0].access &= - ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(umidi->card, - SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); + umidi->opened[dir]++; + if (umidi->opened[1]) + snd_usbmidi_input_start(&umidi->list); + } else { + umidi->opened[dir]--; + if (!umidi->opened[1] && !umidi->keep_input_running) + snd_usbmidi_input_stop(&umidi->list); + if (!umidi->opened[0] && !umidi->opened[1]) { + if (umidi->roland_load_ctl) { + ctl_id = umidi->roland_load_ctl->id; + activate_ctl = true; + active = true; + } } } } + if (activate_ctl) + snd_ctl_activate_id(umidi->card, &ctl_id, active); return 0; } From e4f960371ffdc387b0b559143f09357af342dcdc Mon Sep 17 00:00:00 2001 From: Ilya Pavlukhin Date: Sun, 20 Sep 2026 12:28:05 +0300 Subject: [PATCH 1400/1417] ACPI: scan: Add CLSA0102 to the serial bus ignore list The Lenovo Yoga Slim 7 Carbon 14ACN6 (82L0) describes the two CS35L41 amplifiers of its bass speakers as a single ACPI node, HID CLSA0102, with two I2cSerialBusV2 resources (0x40 and 0x41), like CLSA0100 and CLSA0101 on the Legion 7. Skip the default I2C enumeration so that serial-multi-instantiate can create a client for each amplifier. Link: https://bugzilla.kernel.org/show_bug.cgi?id=215632 Assisted-by: Claude Opus 5 Signed-off-by: Ilya Pavlukhin Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260920092808.17234-2-i.pavluhin@ya.ru --- drivers/acpi/scan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 163a3cccf197b0..41a4756f0ea90f 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1764,6 +1764,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) /* Non-conforming _HID for Cirrus Logic already released */ {"CLSA0100", }, {"CLSA0101", }, + {"CLSA0102", }, /* * Some ACPI devs contain SerialBus resources even though they are not * attached to a serial bus at all. From 5a0f971766c276f4efbafbc5d6d9b2f60181d08e Mon Sep 17 00:00:00 2001 From: Ilya Pavlukhin Date: Sun, 20 Sep 2026 12:28:06 +0300 Subject: [PATCH 1401/1417] platform/x86: serial-multi-instantiate: Add CLSA0102 Instantiate both CS35L41 amplifiers of the CLSA0102 node found on the Lenovo Yoga Slim 7 Carbon 14ACN6 (82L0), the same way as for CLSA0100 and CLSA0101. Link: https://bugzilla.kernel.org/show_bug.cgi?id=215632 Assisted-by: Claude Opus 5 Signed-off-by: Ilya Pavlukhin Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260920092808.17234-3-i.pavluhin@ya.ru --- drivers/platform/x86/serial-multi-instantiate.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c index 3e89fcdd45f785..d535025a3f733f 100644 --- a/drivers/platform/x86/serial-multi-instantiate.c +++ b/drivers/platform/x86/serial-multi-instantiate.c @@ -426,6 +426,7 @@ static const struct acpi_device_id smi_acpi_ids[] = { /* Non-conforming _HID for Cirrus Logic already released */ { "CLSA0100", (unsigned long)&cs35l41_hda }, { "CLSA0101", (unsigned long)&cs35l41_hda }, + { "CLSA0102", (unsigned long)&cs35l41_hda }, { } }; MODULE_DEVICE_TABLE(acpi, smi_acpi_ids); From 7cd9f9f4e1f5fdf22720e1dd13050c6de67cde9e Mon Sep 17 00:00:00 2001 From: Ilya Pavlukhin Date: Sun, 20 Sep 2026 12:28:07 +0300 Subject: [PATCH 1402/1417] ALSA: hda: cs35l41: Add support for CLSA0102 The Lenovo Yoga Slim 7 Carbon 14ACN6 (82L0) drives its bass speakers with two CS35L41 amplifiers, described by ACPI HID CLSA0102 without a _DSD. The node has the same layout as CLSA0100/CLSA0101: both amps in one node, reset on GPIO index 0 and the interrupt on a GpioInt. Unlike the Legion 7 there is no speaker ID GPIO, index 2 is the interrupt line. The Windows driver runs the amps from an external VSPK supply (ExternalVspkControl, boost converter disabled), so use CS35L41_EXT_BOOST with the VSPK switch on GPIO1. With the internal boost converter enabled the amps report "Amp short error" during playback. There is no _SUB in the ACPI node, so the firmware files are looked up by the codec SSID, 17aa3856. Link: https://bugzilla.kernel.org/show_bug.cgi?id=215632 Assisted-by: Claude Opus 5 Signed-off-by: Ilya Pavlukhin Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260920092808.17234-4-i.pavluhin@ya.ru --- .../hda/codecs/side-codecs/cs35l41_hda_i2c.c | 3 ++ .../codecs/side-codecs/cs35l41_hda_property.c | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c b/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c index fdf406e92fca4d..59b82238f2b8b5 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c @@ -23,6 +23,8 @@ static int cs35l41_hda_i2c_probe(struct i2c_client *clt) device_name = "CLSA0100"; else if (strstr(dev_name(&clt->dev), "CLSA0101")) device_name = "CLSA0101"; + else if (strstr(dev_name(&clt->dev), "CLSA0102")) + device_name = "CLSA0102"; else if (strstr(dev_name(&clt->dev), "CSC3551")) device_name = "CSC3551"; else @@ -45,6 +47,7 @@ static const struct i2c_device_id cs35l41_hda_i2c_id[] = { static const struct acpi_device_id cs35l41_acpi_hda_match[] = { {"CLSA0100", 0 }, {"CLSA0101", 0 }, + {"CLSA0102", 0 }, {"CSC3551", 0 }, {} }; diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index 416d7bf3e289c0..c4f2bbed07480a 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -439,6 +439,33 @@ static int lenovo_legion_no_acpi(struct cs35l41_hda *cs35l41, struct device *phy return 0; } +/* + * Device CLSA0102 (Lenovo Yoga Slim 7 Carbon 14ACN6) has the same ACPI layout as CLSA010(0/1): + * no _DSD, both amps in one node, reset on GPIO index 0. It has no speaker ID GPIO, the + * interrupt GPIO sits at index 2. The speakers use an external VSPK supply switched by GPIO1 + * (the Windows driver sets ExternalVspkControl and keeps the boost converter off). + */ +static int lenovo_yoga_slim7_carbon_no_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, + int id, const char *hid) +{ + struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; + + /* check I2C address to assign the index */ + cs35l41->index = id == 0x40 ? 0 : 1; + cs35l41->channel_index = 0; + cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH); + cs35l41->speaker_id = -ENOENT; + hw_cfg->spk_pos = cs35l41->index; + hw_cfg->bst_type = CS35L41_EXT_BOOST; + hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; + hw_cfg->gpio1.valid = true; + hw_cfg->gpio2.func = CS35L41_INTERRUPT; + hw_cfg->gpio2.valid = true; + hw_cfg->valid = true; + + return 0; +} + static int missing_speaker_id_gpio2(struct cs35l41_hda *cs35l41, struct device *physdev, int id, const char *hid) { @@ -463,6 +490,7 @@ struct cs35l41_prop_model { static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CLSA0100", NULL, lenovo_legion_no_acpi }, { "CLSA0101", NULL, lenovo_legion_no_acpi }, + { "CLSA0102", NULL, lenovo_yoga_slim7_carbon_no_acpi }, { "CSC3551", "10251826", generic_dsd_config }, { "CSC3551", "1025182C", generic_dsd_config }, { "CSC3551", "10251844", generic_dsd_config }, From 2afb053cf1e92213344134289088bd4cdb366a43 Mon Sep 17 00:00:00 2001 From: Ilya Pavlukhin Date: Sun, 20 Sep 2026 12:28:08 +0300 Subject: [PATCH 1403/1417] ALSA: hda/realtek: Add quirk for Lenovo Yoga Slim 7 Carbon 14ACN6 The bass speakers of the Lenovo Yoga Slim 7 Carbon 14ACN6 (82L0) are driven by two CS35L41 amplifiers, fed over I2S by the ALC287. The codec only clocks the I2S bus while pin 0x17 is enabled, but the BIOS reports the pin as unconnected (0x411111f0). The amplifiers then never see a clock and fail to power up: cs35l41-hda i2c-CLSA0102:00-cs35l41-hda.0: Enable(1) failed: -110 Configure pin 0x17 as a speaker and keep it on DAC 0x02 together with pin 0x14: with DAC 0x06 the path is powered down during stereo playback, which stops the clock again. Then bind the two CS35L41 amplifiers. The PCI SSID of this machine is 17aa:0000, so match on the codec SSID. Link: https://bugzilla.kernel.org/show_bug.cgi?id=215632 Assisted-by: Claude Opus 5 Signed-off-by: Ilya Pavlukhin Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260920092808.17234-5-i.pavluhin@ya.ru --- sound/hda/codecs/realtek/alc269.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 13bc741907522b..ebef36c8e9ed23 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -3486,6 +3486,28 @@ static void alc287_fixup_legion_16ithg6_speakers(struct hda_codec *cdc, const st comp_generic_fixup(cdc, action, "i2c", "CLSA0101", "-%s:00-cs35l41-hda.%d", 2); } +static void alc287_fixup_yoga_slim7_carbon_speakers(struct hda_codec *cdc, + const struct hda_fixup *fix, int action) +{ + /* + * The bass speakers are driven by two CS35L41 amps fed over I2S. The codec only + * clocks the I2S bus while pin 0x17 is enabled, but the BIOS marks it unconnected. + * Keep the pin on DAC 0x02 with the other speakers: DAC 0x06 gets powered down + * with stereo streams, which stops the clock and the amps fail to power up. + */ + static const struct hda_pintbl pincfgs[] = { + { 0x17, 0x90170121 }, + { } + }; + static const hda_nid_t conn[] = { 0x02 }; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + snd_hda_apply_pincfgs(cdc, pincfgs); + snd_hda_override_conn_list(cdc, 0x17, ARRAY_SIZE(conn), conn); + } + comp_generic_fixup(cdc, action, "i2c", "CLSA0102", "-%s:00-cs35l41-hda.%d", 2); +} + static void alc285_fixup_asus_ga403u(struct hda_codec *cdc, const struct hda_fixup *fix, int action) { /* @@ -4390,6 +4412,7 @@ enum { ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE, ALC295_FIXUP_FRAMEWORK_LAPTOP_LIMIT_INT_MIC_BOOST, ALC287_FIXUP_LEGION_16ITHG6, + ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS, ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK, ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, ALC287_FIXUP_YOGA9_14IMH9_BASS_SPK_PIN, @@ -6778,6 +6801,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc287_fixup_legion_16ithg6_speakers, }, + [ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_yoga_slim7_carbon_speakers, + }, [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { @@ -8316,6 +8343,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), + HDA_CODEC_QUIRK(0x17aa, 0x3856, "Lenovo Yoga Slim 7 Carbon 14ACN6", ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), From b5c4823cf3e49b3419a3bc8c6715101e1ac6cc59 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 29 Sep 2026 14:29:25 +0200 Subject: [PATCH 1404/1417] ALSA: usb-audio: Apply boot quirk for Behringer models generically Jan reported that a few Behringer devices became broken since the recent optimization to avoid usb_string() call at probe time at the commit b364a0d23cae ("ALSA: usb-audio: Use strings in struct usb_dev for manufacturer & co"). Interestingly, the devices seem requiring the explicit descriptor read at probing time, and the optimization above dropped it. There is already a boot quirk for another model, Behringer CM1A (1397:1234), that adds a device descriptor read, and this seems working for them, too. As the quirk is safe and cheap, just apply the same boot quirk to all Behringer devices for avoiding the pitfall again. Fixes: b364a0d23cae ("ALSA: usb-audio: Use strings in struct usb_dev for manufacturer & co") Reported-by: Jan Lentfer Closes: https://lore.kernel.org/e7087d42-5e74-4d85-b1c5-b11eff235d41@web.de Link: https://patch.msgid.link/20260929122938.1471867-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 294c7026b93c1e..f55b02d6dd39f7 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -1702,10 +1702,12 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, switch (id) { case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ return snd_usb_motu_m_series_boot_quirk(dev); - case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ - return snd_usb_cm1a_boot_quirk(dev); } + /* Behringer devices may need explicit device descriptor read at boot */ + if (USB_ID_VENDOR(id) == 0x1397) + return snd_usb_cm1a_boot_quirk(dev); + return 0; } From a154f7b9f197b3d5e41fa3ad62083f5aa93b1fe8 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 29 Sep 2026 16:41:26 +0200 Subject: [PATCH 1405/1417] ALSA: usb-audio: Apply IGNORE_CTL_ERROR quirk to all Audient devices Faaris reported a problem with Audient EVO4 device and it turned out to be a regression by the recent fix commit 87a6f2fa6e6c ("ALSA: usb-audio: Propagate write errors in generic mixer put callbacks"). It exhibited that the hardware gives errors at accessing the mixer unit 10 on certain channels, and the change above made it a fatal error. We had already a quirk for Audient iD14 to work around such errors from the mixer controls, and we can simply apply the same for EVO4. OTOH, it's highly possible that other Audient devices suffer from the same issue; they must be using similar firmware, after all. So, in this patch, we apply the quirk generically to all devices with the vendor ID Audient (2708), instead. Ignoring the control error isn't usually less critical than overreaction to the firmware misbehavior. Fixes: 87a6f2fa6e6c ("ALSA: usb-audio: Propagate write errors in generic mixer put callbacks") Reported-and-tested-by: Faaris Ansari Closes: https://lore.kernel.org/CANBVYRCL=8QdLxGg4S6qrahrFtwJxhv-aSGpW7-1S=+iOe4ZGA@mail.gmail.com Cc: Link: https://patch.msgid.link/20260929144132.1521617-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index f55b02d6dd39f7..3afc4956596381 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2516,8 +2516,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x262a, 0x9302, /* ddHiFi TC44C */ QUIRK_FLAG_DSD_RAW), - DEVICE_FLG(0x2708, 0x0002, /* Audient iD14 */ - QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x2772, 0x0502, /* Musical Fidelity M6s DAC */ 0), /* for avoiding QUIRK_FLAG_DSD_RAW with vendor match */ DEVICE_FLG(0x2912, 0x30c8, /* Audioengine D1 */ @@ -2616,6 +2614,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_DSD_RAW), VENDOR_FLG(0x2622, /* IAG Limited devices */ QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2708, /* Audient devices */ + QUIRK_FLAG_IGNORE_CTL_ERROR), VENDOR_FLG(0x2772, /* Musical Fidelity devices */ QUIRK_FLAG_DSD_RAW), VENDOR_FLG(0x278b, /* Rotel? */ From af3d225b639c6f9fb8fd5e1e27f11672d56724b6 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 29 Sep 2026 16:45:40 +0800 Subject: [PATCH 1406/1417] ALSA: sgio2audio: Only free successfully requested IRQs snd_sgio2audio_create() calls snd_sgio2audio_free() when an IRQ request fails. The cleanup helper currently walks the complete IRQ table, including entries whose request_irq() calls have not been attempted yet. free_irq() then receives an IRQ and dev_id pair that was never registered. Pass the number of successfully requested IRQs to the cleanup helper on the request failure path, while retaining the full-table cleanup for teardown after all requests have succeeded. Fixes: 862c2c0a61c5 ("ALSA: ALSA driver for SGI O2 audio board") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260929084540.2065213-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/mips/sgio2audio.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sound/mips/sgio2audio.c b/sound/mips/sgio2audio.c index 497d84cff12f30..35648b0b97b908 100644 --- a/sound/mips/sgio2audio.c +++ b/sound/mips/sgio2audio.c @@ -742,7 +742,8 @@ static struct { /* ALSA driver */ -static int snd_sgio2audio_free(struct snd_sgio2audio *chip) +static int snd_sgio2audio_free(struct snd_sgio2audio *chip, + unsigned int irq_count) { int i; @@ -752,7 +753,7 @@ static int snd_sgio2audio_free(struct snd_sgio2audio *chip) writeq(0, &mace->perif.audio.control); /* release IRQ's */ - for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) + for (i = 0; i < irq_count; i++) free_irq(snd_sgio2_isr_table[i].irq, &chip->channel[snd_sgio2_isr_table[i].idx]); @@ -768,7 +769,7 @@ static int snd_sgio2audio_dev_free(struct snd_device *device) { struct snd_sgio2audio *chip = device->device_data; - return snd_sgio2audio_free(chip); + return snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table)); } static const struct snd_device_ops ops = { @@ -819,7 +820,7 @@ static int snd_sgio2audio_create(struct snd_card *card, 0, snd_sgio2_isr_table[i].desc, &chip->channel[snd_sgio2_isr_table[i].idx])) { - snd_sgio2audio_free(chip); + snd_sgio2audio_free(chip, i); printk(KERN_ERR "sgio2audio: cannot allocate irq %d\n", snd_sgio2_isr_table[i].irq); return -EBUSY; @@ -843,13 +844,13 @@ static int snd_sgio2audio_create(struct snd_card *card, /* initialize the AD1843 codec */ err = ad1843_init(&chip->ad1843); if (err < 0) { - snd_sgio2audio_free(chip); + snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table)); return err; } err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); if (err < 0) { - snd_sgio2audio_free(chip); + snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table)); return err; } *rchip = chip; From fa2e77ac5bf81a60864e71fc72196227692d7162 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 29 Sep 2026 16:54:00 +0800 Subject: [PATCH 1407/1417] m68k: dmasound: Keep the Q40 IRQ handler registered Q40PlayNextFrame() re-registers the sample IRQ after stopping it. This function can be called with dmasound.lock held and from the sample IRQ handler, so free_irq() and request_irq() can sleep or deadlock. A failed re-registration can also leave playback without an IRQ. Register one handler for the lifetime of the driver and select mono or stereo sample handling inside that handler. Call the frame completion path after releasing dmasound.lock so it can refill the queue safely. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260929085400.2068977-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/oss/dmasound/dmasound_q40.c | 65 ++++++++++++++----------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/sound/oss/dmasound/dmasound_q40.c b/sound/oss/dmasound/dmasound_q40.c index e25a78dd1bf2af..339311e5d80702 100644 --- a/sound/oss/dmasound/dmasound_q40.c +++ b/sound/oss/dmasound/dmasound_q40.c @@ -49,9 +49,8 @@ static int Q40SetFormat(int format); static int Q40SetVolume(int volume); static void Q40PlayNextFrame(int index); static void Q40Play(void); -static irqreturn_t Q40StereoInterrupt(int irq, void *dummy); -static irqreturn_t Q40MonoInterrupt(int irq, void *dummy); -static void Q40Interrupt(void); +static irqreturn_t q40_audio_interrupt(int irq, void *dummy); +static void q40_frame_done(void); /*** Mid level stuff *********************************************************/ @@ -372,8 +371,8 @@ static void Q40Free(void *ptr, unsigned int size) static int __init Q40IrqInit(void) { /* Register interrupt handler. */ - if (request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, - "DMA sound", Q40Interrupt)) + if (request_irq(Q40_IRQ_SAMPLE, q40_audio_interrupt, 0, + "DMA sound", q40_frame_done)) return 0; return(1); @@ -384,7 +383,7 @@ static int __init Q40IrqInit(void) static void Q40IrqCleanUp(void) { master_outb(0,SAMPLE_ENABLE_REG); - free_irq(Q40_IRQ_SAMPLE, Q40Interrupt); + free_irq(Q40_IRQ_SAMPLE, q40_frame_done); } #endif /* MODULE */ @@ -403,7 +402,6 @@ static void Q40PlayNextFrame(int index) u_char *start; u_long size; u_char speed; - int error; /* used by Q40Play() if all doubts whether there really is something * to be played are already wiped out. @@ -420,15 +418,6 @@ static void Q40PlayNextFrame(int index) speed=(dmasound.hard.speed==10000 ? 0 : 1); master_outb( 0,SAMPLE_ENABLE_REG); - free_irq(Q40_IRQ_SAMPLE, Q40Interrupt); - if (dmasound.soft.stereo) - error = request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, - "Q40 sound", Q40Interrupt); - else - error = request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0, - "Q40 sound", Q40Interrupt); - if (error && printk_ratelimit()) - pr_err("Couldn't register sound interrupt\n"); master_outb( speed, SAMPLE_RATE_REG); master_outb( 1,SAMPLE_CLEAR_REG); @@ -456,31 +445,35 @@ static void Q40Play(void) spin_unlock_irqrestore(&dmasound.lock, flags); } -static irqreturn_t Q40StereoInterrupt(int irq, void *dummy) -{ - spin_lock(&dmasound.lock); - if (q40_sc>1){ - *DAC_LEFT=*q40_pp++; - *DAC_RIGHT=*q40_pp++; - q40_sc -=2; - master_outb(1,SAMPLE_CLEAR_REG); - }else Q40Interrupt(); - spin_unlock(&dmasound.lock); - return IRQ_HANDLED; -} -static irqreturn_t Q40MonoInterrupt(int irq, void *dummy) +static irqreturn_t q40_audio_interrupt(int irq, void *dummy) { + bool frame_done = false; + spin_lock(&dmasound.lock); - if (q40_sc>0){ - *DAC_LEFT=*q40_pp; - *DAC_RIGHT=*q40_pp++; - q40_sc --; - master_outb(1,SAMPLE_CLEAR_REG); - }else Q40Interrupt(); + if (dmasound.hard.stereo) { + if (q40_sc > 1) { + *DAC_LEFT = *q40_pp++; + *DAC_RIGHT = *q40_pp++; + q40_sc -= 2; + master_outb(1, SAMPLE_CLEAR_REG); + } else { + frame_done = true; + } + } else if (q40_sc > 0) { + *DAC_LEFT = *q40_pp; + *DAC_RIGHT = *q40_pp++; + q40_sc--; + master_outb(1, SAMPLE_CLEAR_REG); + } else { + frame_done = true; + } spin_unlock(&dmasound.lock); + if (frame_done) + q40_frame_done(); return IRQ_HANDLED; } -static void Q40Interrupt(void) + +static void q40_frame_done(void) { if (!write_sq.active) { /* playing was interrupted and sq_reset() has already cleared From ad63c5b62843895e8c3cd7291ed07cc659df0355 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 29 Sep 2026 14:53:41 +0200 Subject: [PATCH 1408/1417] ALSA: core: Check shutdown flag at D0 power-state, too The snd_power_ref_and_wait() skips the card->shutdown check when the card is already in D0 state and immediately returns as successful, by assuming the all-green in D0. This assumption makes the code after this sync point behaving as if all power is up and ready, even though actually it might have been already at the disconnected state. Add the missing check of shutdown flag there for the more consistent behavior. Cc: stable@vger.kernel.org Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260929125346.1478631-2-tiwai@suse.de --- sound/core/init.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/core/init.c b/sound/core/init.c index 1bcb6a2e75507a..a51b71812fd9b3 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -1174,12 +1174,12 @@ EXPORT_SYMBOL(snd_card_file_remove); int snd_power_ref_and_wait(struct snd_card *card) { snd_power_ref(card); - if (snd_power_get_state(card) == SNDRV_CTL_POWER_D0) - return 0; - wait_event_cmd(card->power_sleep, - card->shutdown || - snd_power_get_state(card) == SNDRV_CTL_POWER_D0, - snd_power_unref(card), snd_power_ref(card)); + if (snd_power_get_state(card) != SNDRV_CTL_POWER_D0) { + wait_event_cmd(card->power_sleep, + card->shutdown || + snd_power_get_state(card) == SNDRV_CTL_POWER_D0, + snd_power_unref(card), snd_power_ref(card)); + } if (card->shutdown) { snd_power_unref(card); return -ENODEV; From d4febca239be3c91498762dd0535a87d4af1eabc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 29 Sep 2026 14:53:42 +0200 Subject: [PATCH 1409/1417] ALSA: control: Fix UAF in snd_ctl_elem_add() on card disconnect A use-after-free can be triggered via SNDRV_CTL_IOCTL_ELEM_ADD when a USB audio card is disconnected while an ELEM_ADD ioctl is in flight. The reproducer parks the ioctl thread inside copy_from_user() using userfaultfd, then tears down the USB device. Unlike every other ALSA control _user handler (ELEM_INFO, ELEM_READ, ELEM_WRITE, TLV_*), snd_ctl_elem_add_user() never calls snd_power_ref_and_wait(), so the parked thread holds no power reference. snd_card_disconnect() therefore cannot observe it via snd_power_sync_ref() and proceeds unimpeded: 1. card->shutdown is set to 1 2. device_del(&card->card_dev) drops the kobject reference on the parent USB interface device (card->dev = &intf->dev) 3. The USB core drops its own reference and calls device_release(), freeing the struct usb_interface, including the embedded struct device that card->dev points to When the userfaultfd is resolved and the thread resumes, snd_ctl_elem_add() acquires controls_rwsem without checking card->shutdown and calls __snd_ctl_add_replace(). Because the reproducer pre-registered the same control, the CTL_ADD_EXCLUSIVE path calls dev_err(card->dev, ...) on the freed USB interface: KASAN: slab-use-after-free Read in __dev_printk Add a card->shutdown guard immediately after acquiring controls_rwsem in snd_ctl_elem_add(). At that point card->shutdown is guaranteed to be stable: snd_card_disconnect() sets it before freeing the parent device, and it never transitions back to 0. A thread that acquired the lock before disconnect sees shutdown=0 and holds the write lock through the rest of the operation, preventing concurrent disconnect from proceeding past its own controls_rwsem-less shutdown=1 store (which happened earlier, outside the lock) from racing with dev_err(). Reported-by: Farhad Alemi Cc: stable@vger.kernel.org Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260929125346.1478631-3-tiwai@suse.de --- sound/core/control.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/core/control.c b/sound/core/control.c index 4199342d4ffe09..535ceba294acf8 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -1802,6 +1802,8 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, alloc_size = compute_user_elem_size(private_size, count); guard(rwsem_write)(&card->controls_rwsem); + if (card->shutdown) + return -ENODEV; if (check_user_elem_overflow(card, alloc_size)) return -ENOMEM; From 0bebef817d4816a95a5d13a00210908f96596552 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Tue, 15 Sep 2026 12:47:35 +0800 Subject: [PATCH 1410/1417] ASoC: tas2781: Add TAS2573 calibration support This patch adds complete calibration workflow support for TAS2573. Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20260915044735.1305-1-shenghao-ding@ti.com Signed-off-by: Mark Brown --- include/sound/tas2781-dsp.h | 9 + include/sound/tas2781.h | 24 +++ sound/soc/codecs/tas2781-fmwlib.c | 36 +++- sound/soc/codecs/tas2781-i2c.c | 320 +++++++++++++++++++++++++++--- 4 files changed, 361 insertions(+), 28 deletions(-) diff --git a/include/sound/tas2781-dsp.h b/include/sound/tas2781-dsp.h index de087f4ca13f7c..3380dfd526de7c 100644 --- a/include/sound/tas2781-dsp.h +++ b/include/sound/tas2781-dsp.h @@ -146,6 +146,7 @@ struct tasdevice_fw { struct tasdevice_calibration *calibrations; struct fct_param_address fct_par_addr; struct device *dev; + int calibration_config_id; }; enum tasdevice_fw_state { @@ -210,6 +211,14 @@ struct tasdevice_rca { * capture. */ int capture_profile_id; + /* + * Primarily designed for speaker calibration scenarios with special + * requirements. For regular use cases, when the default value -1 is + * set, calibration will directly reuse the current playback + * configuration; non-negative values can be customized for special + * calibration demands. + */ + int calibration_profile_id; /* * Since version 0x105, the keyword 'init' was introduced into the * profile, which is used for chip initialization, particularly to diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index b763da6137699d..7367c76959aa1a 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -91,6 +91,30 @@ /* prm_TE_1_Beta1 */ #define TAS2563_TE_DT_REG TASDEVICE_REG(0x00, 0x0f, 0x70) +#define TAS2573_FCT_INT_LATCH TASDEVICE_REG(0x00, 0x00, 0x67) +/* YM57 */ +#define TAS2573_FCT_OUTPUT_R0 TASDEVICE_REG(0x00, 0x65, 0x74) +/* YM58 */ +#define TAS2573_FCT_OUTPUT_R0_LOW TASDEVICE_REG(0x00, 0x65, 0x78) +/* YM59 */ +#define TAS2573_FCT_OUTPUT_INV_R0 TASDEVICE_REG(0x00, 0x65, 0x7C) +/* YM61 */ +#define TAS2573_FCT_OUTPUT_POWERTOT TASDEVICE_REG(0x00, 0x66, 0x0C) +/* YM64 */ +#define TAS2573_FCT_OUTPUT_F0 TASDEVICE_REG(0x00, 0x65, 0x18) +/* YM147 */ +#define TAS2573_FCT_STATUS_CTRL TASDEVICE_REG(0x00, 0x68, 0x74) +/* YM148 */ +#define TAS2573_FCT_STATUS_BINNING TASDEVICE_REG(0x00, 0x68, 0x78) +/* YM664 */ +#define TAS2573_RE_OUT TASDEVICE_REG(0x00, 0x7A, 0x18) +/* YM952 */ +#define TAS2573_SILENCE_DETECTED TASDEVICE_REG(0x00, 0x83, 0x60) +/* YM954 */ +#define TAS2573_OPEN_CIRCUIT TASDEVICE_REG(0x00, 0x83, 0x68) +/* YM955 */ +#define TAS2573_SHORTCKT TASDEVICE_REG(0x00, 0x83, 0x6C) + #define TAS2781_PRM_INT_MASK_REG TASDEVICE_REG(0x00, 0x00, 0x3b) #define TAS2781_PRM_CLK_CFG_REG TASDEVICE_REG(0x00, 0x00, 0x5c) #define TAS2781_PRM_RSVD_REG TASDEVICE_REG(0x00, 0x01, 0x19) diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c index dc1c552206f8f9..e8ceeb027e1baf 100644 --- a/sound/soc/codecs/tas2781-fmwlib.c +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -199,15 +199,22 @@ static struct tasdevice_config_info *tasdevice_add_config( dev_err(tas_priv->dev, "add conf: Out of boundary\n"); goto out; } - /* If in the RCA bin file are several profiles with the - * keyword "init", init_profile_id only store the last - * init profile id. + /* + * If in the RCA bin file are several profiles with the + * keyword "init"/"calib", init_profile_id only store the last + * init profile id, and same as calibration_profile_id. */ if (strnstr(&config_data[config_offset], "init", 64)) { tas_priv->rcabin.init_profile_id = tas_priv->rcabin.ncfgs - 1; dev_dbg(tas_priv->dev, "%s: init profile id = %d\n", __func__, tas_priv->rcabin.init_profile_id); + } else if (strnstr(&config_data[config_offset], "calib", 64)) { + tas_priv->rcabin.calibration_profile_id = + tas_priv->rcabin.ncfgs - 1; + dev_dbg(tas_priv->dev, "%s: calib profile id = %d\n", + __func__, + tas_priv->rcabin.calibration_profile_id); } config_offset += 64; } @@ -314,6 +321,7 @@ int tasdevice_rca_parser(void *context, const struct firmware *fmw) rca = &(tas_priv->rcabin); /* Initialize to none */ rca->init_profile_id = -1; + rca->calibration_profile_id = -1; fw_hdr = &(rca->fw_hdr); if (!fmw || !fmw->data) { dev_err(tas_priv->dev, "Failed to read %s\n", @@ -632,6 +640,17 @@ static int fw_parse_configuration_data_kernel( goto out; } memcpy(config->name, &data[offset], 64); + + /* + * If in the coef bin file are several configs with the + * keyword "calib", calibration_config_id only store the last + * calibration profile id. + */ + if (strnstr(config->name, "calib", 64)) { + tas_fmw->calibration_config_id = i; + dev_dbg(tas_priv->dev, "%s: calib cofig = %d\n", + __func__, tas_fmw->calibration_config_id); + } /*skip extra 16 bytes*/ offset += 80; @@ -1412,6 +1431,17 @@ static int fw_parse_configuration_data( memcpy(config->name, &data[offset], 64); offset += 64; + /* + * If in the coef bin file are several configs with the + * keyword "calib", calibration_config_id only store the last + * calibration profile id. + */ + if (strnstr(config->name, "calib", 64)) { + tas_fmw->calibration_config_id = i; + dev_dbg(tas_priv->dev, "%s: calib cofig id = %d\n", + __func__, tas_fmw->calibration_config_id); + } + n = tasdevice_fw_strnlen(fmw, offset); if (n < 0) { dev_err(tas_priv->dev, "Description err\n"); diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c index 6f349f88f194f7..995bfd52f5fcff 100644 --- a/sound/soc/codecs/tas2781-i2c.c +++ b/sound/soc/codecs/tas2781-i2c.c @@ -613,6 +613,39 @@ static void tas2563_calib_stop_put(struct tasdevice_priv *tas_priv) } } +static void tas2573_calib_stop_put(struct tasdevice_priv *tas_priv) +{ + int cal_prof_id = tas_priv->rcabin.calibration_profile_id; + + if (cal_prof_id >= 0) + tasdevice_select_cfg_blk(tas_priv, cal_prof_id, + TASDEVICE_BIN_BLK_PRE_SHUTDOWN); +} + +static int tas2573_calib_start_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(comp); + int cal_prof_id = tas_priv->rcabin.calibration_profile_id; + int cal_conf_id = tas_priv->fmw->calibration_config_id; + + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->chip_id != TAS2573) { + WARN_ON_ONCE(1); + return -ENODEV; + } + + if (cal_prof_id >= 0) + tasdevice_select_cfg_blk(tas_priv, cal_prof_id, + TASDEVICE_BIN_BLK_PRE_POWER_UP); + + tasdevice_select_tuningprm_cfg(tas_priv, tas_priv->cur_prog, + cal_conf_id, cal_prof_id); + + return 1; +} + static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -621,10 +654,22 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, int i; guard(mutex)(&priv->codec_lock); - if (priv->chip_id == TAS2563) + + switch (priv->chip_id) { + case TAS2563: tas2563_calib_stop_put(priv); - else + break; + case TAS2573: + tas2573_calib_stop_put(priv); + break; + case TAS2781: tas2781_calib_stop_put(priv); + break; + default: + dev_err(priv->dev, "%s: Chip(%d) unsupports calibration\n", + __func__, priv->chip_id); + return -1; + } /* * Set reloading-firmware flag after calibration, the flag will work @@ -983,6 +1028,13 @@ static const struct snd_kcontrol_new tas2563_cali_controls[] = { tasdev_nop_get, tas2563_calib_start_put), }; +static const struct snd_kcontrol_new tas2573_cali_controls[] = { + SOC_SINGLE_EXT("Calibration Start", SND_SOC_NOPM, 0, 1, 0, + tasdev_nop_get, tas2573_calib_start_put), + SOC_SINGLE_EXT("Calibration Stop", SND_SOC_NOPM, 0, 1, 0, + tasdev_nop_get, tasdev_calib_stop_put), +}; + static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -1493,6 +1545,182 @@ static int create_tas2781_cali_start_ktrl(struct tasdevice_priv return 0; } +static int tas2573_re_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *priv = snd_soc_component_get_drvdata(comp); + unsigned char *dst = ucontrol->value.bytes.data; + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *) kcontrol->private_value; + unsigned int i = 0; + unsigned int j; + int rc; + + guard(mutex)(&priv->codec_lock); + dst[i++] = bytes_ext->max; + dst[i++] = 'r'; + + dst[i++] = TASDEVICE_BOOK_ID(TAS2573_RE_OUT); + dst[i++] = TASDEVICE_PAGE_ID(TAS2573_RE_OUT); + dst[i++] = TASDEVICE_PAGE_REG(TAS2573_RE_OUT); + + dst[i++] = 'D'; + + for (j = 0; j < priv->ndev; j++) { + dst[i++] = j; + + rc = tasdevice_dev_bulk_read(priv, j, TAS2573_RE_OUT, + &dst[i], 4); + if (rc < 0) + dev_err(priv->dev, "chn %d bulk_rd err = %d\n", j, rc); + + i += 4; + } + + return 0; +} + +static int tas2573_calib_status_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *priv = snd_soc_component_get_drvdata(comp); + unsigned char *dst = ucontrol->value.bytes.data; + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *) kcontrol->private_value; + unsigned int fct_status_regs[] = { + TAS2573_FCT_INT_LATCH, + TAS2573_FCT_STATUS_CTRL, + TAS2573_FCT_STATUS_BINNING, + TAS2573_SILENCE_DETECTED, + TAS2573_OPEN_CIRCUIT, + TAS2573_SHORTCKT, + TAS2573_FCT_OUTPUT_R0, + TAS2573_FCT_OUTPUT_R0_LOW, + TAS2573_FCT_OUTPUT_INV_R0, + TAS2573_FCT_OUTPUT_POWERTOT, + TAS2573_FCT_OUTPUT_F0, + TAS2573_RE_OUT, + }; + unsigned int j, k, val; + unsigned int i = 0; + int rc; + + guard(mutex)(&priv->codec_lock); + dst[i++] = bytes_ext->max; + dst[i++] = 'r'; + + for (j = 0; j < ARRAY_SIZE(fct_status_regs); j++) { + dst[i++] = TASDEVICE_BOOK_ID(fct_status_regs[j]); + dst[i++] = TASDEVICE_PAGE_ID(fct_status_regs[j]); + dst[i++] = TASDEVICE_PAGE_REG(fct_status_regs[j]); + } + dst[i++] = 'D'; + + for (j = 0; j < priv->ndev; j++) { + dst[i++] = j; + dst[i++] = 0; + dst[i++] = 0; + dst[i++] = 0; + + rc = tasdevice_dev_read(priv, j, fct_status_regs[0], &val); + if (rc < 0) + dev_err(priv->dev, + "chn %d fct_status_regs[0] rd err = %d\n", + j, rc); + else + dst[i++] = val; + + for (k = 1; k < ARRAY_SIZE(fct_status_regs); k++, i += 4) { + rc = tasdevice_dev_bulk_read(priv, j, + fct_status_regs[k], &dst[i], 4); + if (rc < 0) { + dev_err(priv->dev, + "chn %d regs[%u] bulk_rd err = %d\n", + j, k, rc); + } + } + + } + + return 0; +} + +static int create_tas2573_cali_status_ktrl(struct tasdevice_priv + *priv, struct snd_kcontrol_new *cali_ctrl) +{ + struct soc_bytes_ext *ext_cali_start; + char *cali_start_name; + + ext_cali_start = devm_kzalloc(priv->dev, + sizeof(*ext_cali_start), GFP_KERNEL); + if (!ext_cali_start) + return -ENOMEM; + + cali_start_name = devm_kstrdup(priv->dev, + "Calibration Status", GFP_KERNEL); + if (!cali_start_name) + return -ENOMEM; + /* + * package structure for tas2573 fct status: + * Pkg len (1 byte) + * Reg id (1 byte, constant 'r') + * book, page, register for fct status (total 36 bytes) + * Data Start Flag (1 byte, constant 'D') + * for (i = 0; i < Device-Sum; i++) { + * Device #i index_info (1 byte) + * Sine gain for Device #i (48 bytes) + * } + */ + ext_cali_start->max = 39 + priv->ndev * 49; + cali_ctrl->name = cali_start_name; + cali_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + cali_ctrl->info = snd_soc_bytes_info_ext; + cali_ctrl->put = NULL; + cali_ctrl->get = tas2573_calib_status_get; + cali_ctrl->private_value = (unsigned long)ext_cali_start; + + return 0; +} + +static int create_tas2573_cali_re_ktrl(struct tasdevice_priv + *priv, struct snd_kcontrol_new *cali_ctrl) +{ + struct soc_bytes_ext *ext_cali_start; + char *cali_start_name; + + ext_cali_start = devm_kzalloc(priv->dev, + sizeof(*ext_cali_start), GFP_KERNEL); + if (!ext_cali_start) + return -ENOMEM; + + cali_start_name = devm_kstrdup(priv->dev, + "Real-time Speaker Impedance", GFP_KERNEL); + if (!cali_start_name) + return -ENOMEM; + /* + * package structure for tas2573 real-time spk impedance: + * Pkg len (1 byte) + * Reg id (1 byte, constant 'r') + * book, page, register for fct status (total 3 bytes) + * Data Start Flag (1 byte, constant 'D') + * for (i = 0; i < Device-Sum; i++) { + * Device #i index_info (1 byte) + * Sine gain for Device #i (4 bytes) + * } + */ + ext_cali_start->max = 6 + priv->ndev * 5; + cali_ctrl->name = cali_start_name; + cali_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + cali_ctrl->info = snd_soc_bytes_info_ext; + cali_ctrl->put = NULL; + cali_ctrl->get = tas2573_re_get; + cali_ctrl->private_value = (unsigned long)ext_cali_start; + + return 0; +} + static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) { struct calidata *cali_data = &priv->cali_data; @@ -1504,17 +1732,43 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) char *cali_name; int rc, i; - rc = snd_soc_add_component_controls(priv->codec, - tasdevice_cali_controls, ARRAY_SIZE(tasdevice_cali_controls)); - if (rc < 0) { - dev_err(priv->dev, "%s: Add cali controls err rc = %d", - __func__, rc); - return rc; + switch (priv->chip_id) { + case TAS2563: { + rc = snd_soc_add_component_controls(priv->codec, + tasdevice_cali_controls, + ARRAY_SIZE(tasdevice_cali_controls)); + if (rc < 0) { + dev_err(priv->dev, "%s: Add cali controls err rc = %d", + __func__, rc); + return rc; + } + cali_ctrls = (struct snd_kcontrol_new *)tas2563_cali_controls; + nctrls = ARRAY_SIZE(tas2563_cali_controls); + for (i = 0; i < priv->ndev; i++) { + tasdev[i].cali_data_backup = + kmemdup(tas2563_cali_start_reg, + sizeof(tas2563_cali_start_reg), GFP_KERNEL); + if (!tasdev[i].cali_data_backup) + return -ENOMEM; + } } - - if (priv->chip_id == TAS2781) { + break; + case TAS2573: + cali_ctrls = (struct snd_kcontrol_new *)tas2573_cali_controls; + nctrls = ARRAY_SIZE(tas2573_cali_controls); + break; + case TAS2781: { struct fct_param_address *t = &(fmw->fct_par_addr); + rc = snd_soc_add_component_controls(priv->codec, + tasdevice_cali_controls, + ARRAY_SIZE(tasdevice_cali_controls)); + if (rc < 0) { + dev_err(priv->dev, "%s: Add cali controls err rc = %d", + __func__, rc); + return rc; + } + cali_ctrls = (struct snd_kcontrol_new *)tas2781_cali_controls; nctrls = ARRAY_SIZE(tas2781_cali_controls); for (i = 0; i < priv->ndev; i++) { @@ -1533,16 +1787,12 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) } } } - } else { - cali_ctrls = (struct snd_kcontrol_new *)tas2563_cali_controls; - nctrls = ARRAY_SIZE(tas2563_cali_controls); - for (i = 0; i < priv->ndev; i++) { - tasdev[i].cali_data_backup = - kmemdup(tas2563_cali_start_reg, - sizeof(tas2563_cali_start_reg), GFP_KERNEL); - if (!tasdev[i].cali_data_backup) - return -ENOMEM; - } + } + break; + default: + dev_err(priv->dev, "%s: Wrong chip id = %d", __func__, + priv->chip_id); + return -EINVAL; } rc = snd_soc_add_component_controls(priv->codec, cali_ctrls, nctrls); @@ -1554,10 +1804,17 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) /* index for cali_ctrls */ i = 0; - if (priv->chip_id == TAS2781) + switch (priv->chip_id) { + case TAS2573: + nctrls = 3; + break; + case TAS2781: nctrls = 2; - else + break; + default: nctrls = 1; + break; + } /* * Alloc kcontrol via devm_kzalloc(), which don't manually @@ -1610,12 +1867,24 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) * it, for the default value is 0, which means the first device. */ cali_data->data[0] = 0xff; - if (priv->chip_id == TAS2781) { + + switch (priv->chip_id) { + case TAS2573: + rc = create_tas2573_cali_status_ktrl(priv, &cali_ctrls[i]); + if (rc != 0) + return rc; + i++; + rc = create_tas2573_cali_re_ktrl(priv, &cali_ctrls[i]); + if (rc != 0) + return rc; + i++; + break; + case TAS2781: rc = create_tas2781_cali_start_ktrl(priv, &cali_ctrls[i]); if (rc != 0) return rc; i++; - + break; } return snd_soc_add_component_controls(priv->codec, cali_ctrls, @@ -1822,7 +2091,8 @@ static void tasdevice_fw_ready(const struct firmware *fmw, tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK; /* There is no calibration required for TAS58XX. */ - if (tas_priv->chip_id == TAS2563 || tas_priv->chip_id == TAS2781) { + if (tas_priv->chip_id == TAS2563 || tas_priv->chip_id == TAS2573 || + tas_priv->chip_id == TAS2781) { ret = tasdevice_create_cali_ctrls(tas_priv); if (ret) { dev_err(tas_priv->dev, "cali controls error\n"); From 995b96380ba5d98131acc162608da3538e1395d8 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 29 Sep 2026 17:59:20 +0800 Subject: [PATCH 1411/1417] ASoC: pcm6240: do not free an unrequested IRQ The driver obtains an IRQ number from device tree but never requests an IRQ handler for the PCM6240 device. Removing the device must not pass that number to free_irq(). Leave IRQ ownership to a matching request path if one is added later. Fixes: 1324eafd37aa ("ASoc: PCM6240: Create PCM6240 Family driver code") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260929095920.2092147-1-runyu.xiao@seu.edu.cn Signed-off-by: Mark Brown --- sound/soc/codecs/pcm6240.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c index 4b5569a5b0622a..db70a33d14b8dd 100644 --- a/sound/soc/codecs/pcm6240.c +++ b/sound/soc/codecs/pcm6240.c @@ -2019,8 +2019,6 @@ static const struct regmap_config pcmdevice_i2c_regmap = { static void pcmdevice_remove(struct pcmdevice_priv *pcm_dev) { - if (pcm_dev->irq) - free_irq(pcm_dev->irq, pcm_dev); mutex_destroy(&pcm_dev->codec_lock); } From 22185ffd7b31885d03fdeb20055a8707779a70f9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 30 Sep 2026 17:53:50 +0200 Subject: [PATCH 1412/1417] ALSA: usb-audio: Check mixer matrix size for UAC2/3 at parsing The matrix of input/output channels specified in a UAC2/3 mixer unit must fit to the upper limit 256. Add a sanity check and returns an error if an invalid size is detected. Link: https://lore.kernel.org/d46fcac6-bd7e-4fc4-95e1-4e8d39f92ad3@zipdox.net Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260930155356.348608-2-tiwai@suse.de --- sound/usb/mixer.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 33a6a12814106d..ddfd7e01a3efa6 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -2436,6 +2436,16 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, num_outs = err; input_pins = desc->bNrInPins; + if (state->mixer->protocol == UAC_VERSION_2 || + state->mixer->protocol == UAC_VERSION_3) { + if (input_pins * num_outs > 256) { + usb_audio_err(state->chip, + "invalid channels for MIXER UNIT %d: input=%d, output=%d\n", + unitid, input_pins, num_outs); + return -EINVAL; + } + } + num_ins = 0; ich = 0; for (pin = 0; pin < input_pins; pin++) { From 0458d97859840506c3688a963dcd4f72b4a23083 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 30 Sep 2026 17:53:51 +0200 Subject: [PATCH 1413/1417] ALSA: usb-audio: Fix UAC2 mixer unit request handling For a request for a Mixer Unit on UAC2 (also UAC3), the wValue is different from UAC1 and an incompatible value must be passed. Namely, UAC1 takes a word consisting of 1-based input channel in the high byte and 1-based output channel in the low byte. Meanwhile, UAC2/3 takes UAC2_MU_MIXER in the high byte and a MCN (0-based bit position of input/output channels) in the low byte. The current driver implementation blindly assumes the UAC1 way, hence it would cause a firmware error. This patch attempts to implement the conversion to UAC2 MCN at get_ctl_value_v2() and snd_usb_mixer_set_ctl_value() for mixer units. At the points above, the old wValue containing ICN and OCN is converted to the corresponding MCN, and it's used as the proper wValue. Reported-by: Zipdox Closes: https://lore.kernel.org/d46fcac6-bd7e-4fc4-95e1-4e8d39f92ad3@zipdox.net Fixes: 23caaf19b11e ("ALSA: usb-mixer: Add support for Audio Class v2.0") Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260930155356.348608-3-tiwai@suse.de --- sound/usb/mixer.c | 23 +++++++++++++++++++++++ sound/usb/mixer.h | 2 ++ 2 files changed, 25 insertions(+) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index ddfd7e01a3efa6..a8bdd1696a20b7 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -335,6 +335,17 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, return -EINVAL; } +/* convert the given UAC1 wValue (ICN|OCN) to UAC2 MCN */ +static unsigned char to_mcn(const struct usb_mixer_elem_info *cval, + unsigned int validx) +{ + unsigned char m = (validx >> 8) & 0xff; /* 1-based input channel */ + unsigned char v = validx & 0xff; /* 1-based output channel */ + + /* num_inputs * num_outputs is guaranteed to be < 256 */ + return (m - 1) * cval->num_outputs + (v - 1); +} + static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) { @@ -347,6 +358,10 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, val_size = uac2_ctl_value_size(cval->val_type); + /* correct wValue for UAC2 mixer control with MCN */ + if (cval->v2_mixer) + validx = (UAC2_MU_MIXER << 8) | to_mcn(cval, validx); + if (request == UAC_GET_CUR) { bRequest = UAC2_CS_CUR; size = val_size; @@ -478,6 +493,10 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, } request = UAC2_CS_CUR; + + /* correct wValue for UAC2 mixer control with MCN */ + if (cval->v2_mixer) + validx = (UAC2_MU_MIXER << 8) | to_mcn(cval, validx); } value_set = convert_bytes_value(cval, value_set); @@ -2345,6 +2364,10 @@ static void build_mixer_unit_ctl(struct mixer_build *state, snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid); cval->control = in_ch + 1; /* based on 1 */ + if (state->mixer->protocol == UAC_VERSION_2 || + state->mixer->protocol == UAC_VERSION_3) + cval->v2_mixer = true; + cval->num_outputs = num_outs; cval->val_type = USB_MIXER_S16; for (i = 0; i < num_outs; i++) { __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol); diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 037b446d8b6f5b..cf45c39cbccc59 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -97,6 +97,8 @@ struct usb_mixer_elem_info { u8 initialized; u8 min_mute; u8 get_cur_broken; + u8 num_outputs; + bool v2_mixer; void *private_data; }; From 8b4d98aad61c3a9a5e01e0840470f9d7f32bc339 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 30 Sep 2026 17:53:52 +0200 Subject: [PATCH 1414/1417] ALSA: usb-audio: Optimize min/max/res parse for UAC2 UAC2 feature and mixer units provide the mixer information about minimum and max channels as well as the resolution in a single UAC2_CS_RANGE request, but the current code tries to extract each of them in an old way of UAC1. This patch refactors the code to optimize the range info extraction for UAC2. Now the code for obtaining min/max/res info is done in get_ctl_range() function. For UAC1, this will call UAC_GET_MIN, UAC_GET_MAX and UAC_GET_RES requests, while it calls a single UAC2_CS_RANGE for UAC2/3. Link: https://lore.kernel.org/d46fcac6-bd7e-4fc4-95e1-4e8d39f92ad3@zipdox.net Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260930155356.348608-4-tiwai@suse.de --- sound/usb/mixer.c | 218 ++++++++++++++++++++++++++-------------------- 1 file changed, 125 insertions(+), 93 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index a8bdd1696a20b7..b6e22244e03a66 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -304,8 +304,9 @@ static inline int mixer_ctrl_intf(struct usb_mixer_interface *mixer) return get_iface_desc(mixer->hostif)->bInterfaceNumber; } -static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, - int validx, int *value_ret) +/* send a request for UAC1 feature & mixer unit */ +static int request_get_ctl_v1(struct usb_mixer_elem_info *cval, + u8 request, int validx, int *value_ret) { struct snd_usb_audio *chip = cval->head.mixer->chip; unsigned char buf[2]; @@ -317,6 +318,8 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, if (pm.err < 0) return -EIO; + validx += cval->idx_off; + while (timeout-- > 0) { idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request, @@ -346,98 +349,73 @@ static unsigned char to_mcn(const struct usb_mixer_elem_info *cval, return (m - 1) * cval->num_outputs + (v - 1); } -static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, - int validx, int *value_ret) +/* send a request for UAC2 feature & mixer unit */ +static int request_get_ctl_v2(struct usb_mixer_elem_info *cval, + u8 request, int validx, unsigned char *buf, + int size) { struct snd_usb_audio *chip = cval->head.mixer->chip; - /* enough space for one range */ - unsigned char buf[sizeof(__u16) + 3 * sizeof(__u32)]; - unsigned char *val; - int idx = 0, ret, val_size, size; - __u8 bRequest; + int idx, ret; - val_size = uac2_ctl_value_size(cval->val_type); + CLASS(snd_usb_lock, pm)(chip); + if (pm.err) + return -EIO; + + validx += cval->idx_off; /* correct wValue for UAC2 mixer control with MCN */ if (cval->v2_mixer) validx = (UAC2_MU_MIXER << 8) | to_mcn(cval, validx); - if (request == UAC_GET_CUR) { - bRequest = UAC2_CS_CUR; - size = val_size; - } else { - bRequest = UAC2_CS_RANGE; - size = sizeof(__u16) + 3 * val_size; - } - - memset(buf, 0, sizeof(buf)); - - { - CLASS(snd_usb_lock, pm)(chip); - if (pm.err) - return -EIO; - - idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); - ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, - validx, idx, buf, size); - } - - if (ret < 0) { + memset(buf, 0, size); + idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + request, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, idx, buf, size); + if (ret < 0) usb_audio_dbg(chip, "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", request, validx, idx, cval->val_type); - return ret; - } - /* FIXME: how should we handle multiple triplets here? */ - - switch (request) { - case UAC_GET_CUR: - val = buf; - break; - case UAC_GET_MIN: - val = buf + sizeof(__u16); - break; - case UAC_GET_MAX: - val = buf + sizeof(__u16) + val_size; - break; - case UAC_GET_RES: - val = buf + sizeof(__u16) + val_size * 2; - break; - default: - return -EINVAL; - } - - *value_ret = convert_signed_value(cval, - snd_usb_combine_bytes(val, val_size)); - - return 0; + return ret; } -static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, - int validx, int *value_ret) +/* read the current value for UAC2 */ +static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, + int validx, int *value_ret) { - validx += cval->idx_off; + /* enough space for one value */ + unsigned char buf[sizeof(__u32)]; + int ret, val_size; - return (cval->head.mixer->protocol == UAC_VERSION_1) ? - get_ctl_value_v1(cval, request, validx, value_ret) : - get_ctl_value_v2(cval, request, validx, value_ret); + val_size = uac2_ctl_value_size(cval->val_type); + + ret = request_get_ctl_v2(cval, UAC2_CS_CUR, validx, buf, val_size); + if (ret < 0) + return ret; + + *value_ret = convert_signed_value(cval, + snd_usb_combine_bytes(buf, val_size)); + return 0; } +/* read the current value */ static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, - int validx, int *value) + int validx, int *value_ret) { - return get_ctl_value(cval, UAC_GET_CUR, validx, value); + return (cval->head.mixer->protocol == UAC_VERSION_1) ? + request_get_ctl_v1(cval, UAC_GET_CUR, validx, value_ret) : + get_ctl_value_v2(cval, validx, value_ret); } /* channel = 0: master, 1 = first channel */ static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, int channel, int *value) { - return get_ctl_value(cval, UAC_GET_CUR, - (cval->control << 8) | channel, - value); + return get_cur_ctl_value(cval, + (cval->control << 8) | channel, + value); } int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, @@ -467,6 +445,81 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, return 0; } +/* extract the mixer min/max/res info from UAC1 feature / mixer unit */ +static int get_ctl_range_v1(struct usb_mixer_elem_info *cval, int validx) +{ + int last_valid_res; + + if (request_get_ctl_v1(cval, UAC_GET_MAX, validx, &cval->max) < 0 || + request_get_ctl_v1(cval, UAC_GET_MIN, validx, &cval->min) < 0) { + usb_audio_err(cval->head.mixer->chip, + "%d:%d: cannot get min/max values for control %d (id %d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->control, cval->head.id); + return -EAGAIN; /* handled by the caller later again */ + } + + if (request_get_ctl_v1(cval, UAC_GET_RES, validx, &cval->res) < 0) { + cval->res = 1; + return 0; + } + + last_valid_res = cval->res; + while (cval->res > 1) { + if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, + validx, cval->res / 2) < 0) + break; + cval->res /= 2; + } + if (request_get_ctl_v1(cval, UAC_GET_RES, validx, &cval->res) < 0) + cval->res = last_valid_res; + + return 0; +} + +/* extract the mixer min/max/res info from UAC2 feature / mixer unit */ +static int get_ctl_range_v2(struct usb_mixer_elem_info *cval, int validx) +{ + /* enough space for one range */ + unsigned char buf[sizeof(__u16) + 3 * sizeof(__u32)]; + unsigned char *val; + int val_size, size; + + val_size = uac2_ctl_value_size(cval->val_type); + size = sizeof(__u16) + 3 * val_size; + + if (request_get_ctl_v2(cval, UAC2_CS_RANGE, validx, buf, size) < 0) { + usb_audio_err(cval->head.mixer->chip, + "%d:%d: cannot get RANGE values for control %d (id %d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->control, cval->head.id); + return -EAGAIN; /* handled by the caller later again */ + } + + /* FIXME: how should we handle multiple triplets here? */ + val = buf + 2; + cval->min = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size)); + val += val_size; + cval->max = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size)); + val += val_size; + cval->res = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size)); + return 0; +} + +/* extract the mixer min/max/res info */ +static int get_ctl_range(struct usb_mixer_elem_info *cval, int validx) +{ + switch (cval->head.mixer->protocol) { + case UAC_VERSION_1: + return get_ctl_range_v1(cval, validx); + case UAC_VERSION_2: + case UAC_VERSION_3: + return get_ctl_range_v2(cval, validx); + default: + return -EINVAL; + } +} + /* * set a mixer value */ @@ -1363,32 +1416,11 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, break; } } - if (get_ctl_value(cval, UAC_GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 || - get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) { - usb_audio_err(cval->head.mixer->chip, - "%d:%d: cannot get min/max values for control %d (id %d)\n", - cval->head.id, mixer_ctrl_intf(cval->head.mixer), - cval->control, cval->head.id); - return -EAGAIN; - } - if (get_ctl_value(cval, UAC_GET_RES, - (cval->control << 8) | minchn, - &cval->res) < 0) { - cval->res = 1; - } else if (cval->head.mixer->protocol == UAC_VERSION_1) { - int last_valid_res = cval->res; - while (cval->res > 1) { - if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, - (cval->control << 8) | minchn, - cval->res / 2) < 0) - break; - cval->res /= 2; - } - if (get_ctl_value(cval, UAC_GET_RES, - (cval->control << 8) | minchn, &cval->res) < 0) - cval->res = last_valid_res; - } + ret = get_ctl_range(cval, (cval->control << 8) | minchn); + if (ret < 0) + return ret; + if (cval->res == 0) cval->res = 1; From 5367243a37f7f74d5f595ceac780508138f1e3b0 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Thu, 1 Oct 2026 10:40:49 +0800 Subject: [PATCH 1415/1417] ASoC: tas2781: Refactor register error log with unified macro Add reusable TAS_REG_ERR_LOG macro in tas2781 header, replace all duplicated error logging code in register read/write/bulk ops and update_bits functions. Add channel ID to error log for better debuggability. Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20261001024049.93-2-shenghao-ding@ti.com Signed-off-by: Mark Brown --- include/sound/tas2781.h | 7 +++++++ sound/soc/codecs/tas2781-comlib-i2c.c | 5 +---- sound/soc/codecs/tas2781-comlib.c | 20 ++++---------------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index 7367c76959aa1a..f4275226366de7 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -49,6 +49,13 @@ #define TASDEVICE_REG(book, page, reg) (((book * 256 * 128) + \ (page * 128)) + reg) +#define TAS_REG_ERR_LOG(dev, chn, reg, ret) \ + dev_err((dev), "%s, C%uBx%02xPx%02xRx%02x E=%d\n", \ + __func__, chn, TASDEVICE_BOOK_ID(reg), \ + TASDEVICE_PAGE_ID(reg), \ + TASDEVICE_PAGE_REG(reg), \ + (ret)) + /* Software Reset, compatble with new device (TAS5825). */ #define TASDEVICE_REG_SWRESET TASDEVICE_REG(0x0, 0x0, 0x01) #define TASDEVICE_REG_SWRESET_RESET BIT(0) diff --git a/sound/soc/codecs/tas2781-comlib-i2c.c b/sound/soc/codecs/tas2781-comlib-i2c.c index dd86fc90a7d017..46777be765efb7 100644 --- a/sound/soc/codecs/tas2781-comlib-i2c.c +++ b/sound/soc/codecs/tas2781-comlib-i2c.c @@ -131,10 +131,7 @@ int tasdevice_dev_update_bits( ret = regmap_update_bits(map, TASDEVICE_PGRG(reg), mask, value); if (ret < 0) - dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", - __func__, TASDEVICE_BOOK_ID(reg), - TASDEVICE_PAGE_ID(reg), - TASDEVICE_PAGE_REG(reg), ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else { dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, chn); diff --git a/sound/soc/codecs/tas2781-comlib.c b/sound/soc/codecs/tas2781-comlib.c index e4c8a04a9187bf..58e19d6d1f752b 100644 --- a/sound/soc/codecs/tas2781-comlib.c +++ b/sound/soc/codecs/tas2781-comlib.c @@ -34,10 +34,7 @@ int tasdevice_dev_read(struct tasdevice_priv *tas_priv, ret = regmap_read(map, TASDEVICE_PGRG(reg), val); if (ret < 0) - dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", - __func__, TASDEVICE_BOOK_ID(reg), - TASDEVICE_PAGE_ID(reg), - TASDEVICE_PAGE_REG(reg), ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, @@ -65,10 +62,7 @@ int tasdevice_dev_bulk_read(struct tasdevice_priv *tas_priv, ret = regmap_bulk_read(map, TASDEVICE_PGRG(reg), data, len); if (ret < 0) - dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", - __func__, TASDEVICE_BOOK_ID(reg), - TASDEVICE_PAGE_ID(reg), - TASDEVICE_PAGE_REG(reg), ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, chn); @@ -94,10 +88,7 @@ int tasdevice_dev_write(struct tasdevice_priv *tas_priv, ret = regmap_write(map, TASDEVICE_PGRG(reg), value); if (ret < 0) - dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", - __func__, TASDEVICE_BOOK_ID(reg), - TASDEVICE_PAGE_ID(reg), - TASDEVICE_PAGE_REG(reg), ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, @@ -127,10 +118,7 @@ int tasdevice_dev_bulk_write( ret = regmap_bulk_write(map, TASDEVICE_PGRG(reg), data, len); if (ret < 0) - dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", - __func__, TASDEVICE_BOOK_ID(reg), - TASDEVICE_PAGE_ID(reg), - TASDEVICE_PAGE_REG(reg), ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, From 923ff8155ef6e83b73b852300d50f2053b969552 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Thu, 1 Oct 2026 17:02:19 +0800 Subject: [PATCH 1416/1417] ASoC: rt712: avoid duplicate reset in io_init The codec should be reset only once during io_init, regardless of whether the system performs a warm or cold reboot. Fixes: 4c9854ad3cdc ("ASoC: rt712-sdca: reset codec at io_init to fix silent headphone") Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20261001090219.797880-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c index a98537e23c219b..20961e5760c652 100644 --- a/sound/soc/codecs/rt712-sdca.c +++ b/sound/soc/codecs/rt712-sdca.c @@ -2088,7 +2088,8 @@ int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave) pm_runtime_get_noresume(&slave->dev); - rt712_sdca_reset(rt712); + if (!rt712->first_hw_init) + rt712_sdca_reset(rt712); rt712_sdca_index_read(rt712, RT712_VENDOR_REG, RT712_JD_PRODUCT_NUM, &val); rt712->hw_id = (val & 0xf000) >> 12; @@ -2117,7 +2118,7 @@ int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave) * if set_jack callback occurred early than io_init, * we set up the jack detection function now */ - if (rt712->hs_jack) + if (rt712->hs_jack && (!rt712->first_hw_init)) rt712_sdca_jack_init(rt712); rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_SW_CONFIG1, 0x0001); From cad16d850e3759dd82cd869f739b56e9844a1fee Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 1 Oct 2026 09:58:30 +0100 Subject: [PATCH 1417/1417] spi: cs42l43: Workaround for wrong speaker ID on Dell XPS 13 DX13260 On Dell XPS 13 DX13260 create an acpi_gpio_mapping with exactly two GPIO entries to point at the two pins in the GpioIo(). Use this to read the speaker ID GPIOs. This fixes problems on Dell XPS 13 DX13260: - No speaker audio - The wrong firmware was loaded so the speaker protection did not match the speaker characteristics. The Dell XPS 13 DX13260 has two speaker ID GPIOs, to form a 2-bit ID. The ACPI GpioIo() has both pins but the Linux-specific spk-id-gpios property only has a mapping to the first pin. This meant that the speaker ID was wrong in most cases, and that would lead to the codec driver loading the wrong amp firmware, or not finding a firmware (as 0 is not a valid ID on this laptop). Assisted-by: Codex:gpt-6-sol Reported-by: Wiza Jalakasi Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221956 Tested-by: Wiza Jalakasi Fixes: f3c605147741e ("spi: cs42l43: Add GPIO speaker id support to the bridge configuration") Signed-off-by: Richard Fitzgerald Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20261001085830.4014291-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- drivers/spi/spi-cs42l43.c | 97 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/drivers/spi/spi-cs42l43.c b/drivers/spi/spi-cs42l43.c index 7106a8a4f2805b..6772abb483dd3a 100644 --- a/drivers/spi/spi-cs42l43.c +++ b/drivers/spi/spi-cs42l43.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -215,12 +216,108 @@ static size_t cs42l43_spi_max_length(struct spi_device *spi) return CS42L43_SPI_MAX_LENGTH; } +/* + * Workaround needed for two speaker ID pins in one ACPI GpioIo() but + * the Linux-specific _DSD property only contains one pin. + * Create a temporary acpi_gpio_mapping pointing at both pins. + */ +static const struct acpi_gpio_params cs42l43_2bit_speaker_id_from_one_gpioio_params[] = { + [0] = { + .crs_entry_index = 0, + .line_index = 0, + }, + [1] = { + .crs_entry_index = 0, + .line_index = 1, + }, +}; + +static const struct acpi_gpio_mapping cs42l43_2bit_speaker_id_from_one_gpioio_mapping[] = { + { + .name = "spk-id-quirk-gpios", + .data = cs42l43_2bit_speaker_id_from_one_gpioio_params, + .size = ARRAY_SIZE(cs42l43_2bit_speaker_id_from_one_gpioio_params), + }, + { } +}; + +static int cs42l43_get_2bit_speaker_id_from_one_gpioio(struct cs42l43_spi *priv, int *result) +{ + struct fwnode_reference_args args; + struct acpi_device *adev; + struct gpio_desc *desc; + u32 spkid = 0; + int i, ret; + + /* Use the _DSD property to get the node containing the GpioIo() */ + ret = fwnode_property_get_reference_args(dev_fwnode(priv->dev), "spk-id-gpios", + NULL, 3, 0, &args); + if (ret) + return ret; + + struct fwnode_handle *fwnode __free(fwnode_handle) = args.fwnode; + + /* An acpi_gpio_mapping must be added to the node that contains the GpioIo() */ + adev = to_acpi_device_node(fwnode); + if (!adev) + return -EINVAL; + + ret = acpi_dev_add_driver_gpios(adev, cs42l43_2bit_speaker_id_from_one_gpioio_mapping); + if (ret) + return ret; + + /* gpiod_get_array() can't read from a mapping in a child node */ + for (i = 0; i < ARRAY_SIZE(cs42l43_2bit_speaker_id_from_one_gpioio_params); i++) { + desc = fwnode_gpiod_get_index(fwnode, "spk-id-quirk", i, GPIOD_IN, + dev_name(priv->dev)); + if (IS_ERR(desc)) { + ret = PTR_ERR(desc); + goto out; + } + + ret = gpiod_get_value_cansleep(desc); + gpiod_put(desc); + if (ret < 0) + goto out; + + spkid |= (u32)ret << i; + } + + dev_dbg(priv->dev, "spk-id-gpios = %u\n", spkid); + *result = spkid; + ret = 0; +out: + acpi_dev_remove_driver_gpios(adev); + + return ret; +} + +static const struct dmi_system_id cs42l43_spk_id_quirks[] = { + { + .ident = "Dell XPS 13 DX13260", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0E53"), + }, + .driver_data = cs42l43_get_2bit_speaker_id_from_one_gpioio, + }, + { } +}; + static int cs42l43_get_speaker_id_gpios(struct cs42l43_spi *priv, int *result) { + const struct dmi_system_id *dmi_id; struct gpio_descs *descs; u32 spkid; int i, ret; + dmi_id = dmi_first_match(cs42l43_spk_id_quirks); + if (dmi_id) { + int (*get_speaker_id)(struct cs42l43_spi *priv, int *result) = dmi_id->driver_data; + + return get_speaker_id(priv, result); + } + descs = gpiod_get_array_optional(priv->dev, "spk-id", GPIOD_IN); if (!descs) return 0;