--- /dev/null
+From stable+bounces-280758-greg=kroah.com@vger.kernel.org Tue Jul 21 23:11:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 14:01:49 -0400
+Subject: audit: fix recursive locking deadlock in audit_dupe_exe()
+To: stable@vger.kernel.org
+Cc: Ricardo Robaina <rrobaina@redhat.com>, stable@kernel.org, Waiman Long <longman@redhat.com>, Richard Guy Briggs <rgb@redhat.com>, Nathan Chancellor <nathan@kernel.org>, Paul Moore <paul@paul-moore.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721180149.286443-2-sashal@kernel.org>
+
+From: Ricardo Robaina <rrobaina@redhat.com>
+
+[ Upstream commit 81905b5acbe77284734438df3fbec1158e6429a3 ]
+
+A deadlock occurs in the audit subsystem when duplicating
+executable-related rules.
+
+When a file is moved (e.g., via do_renameat2()), the VFS layer locks
+the parent directory (I_MUTEX_PARENT), which synchronously triggers an
+fsnotify_move event. If an existing executable audit rule matches the
+file being moved, the audit subsystem catches this event and calls
+audit_dupe_exe() to duplicate the watch and update the rule. Then,
+audit_alloc_mark() would call kern_path_parent() to resolve the path,
+leading to a blind attempt to acquire the exact same I_MUTEX_PARENT lock
+already held by the task, resulting in the following recursive locking
+deadlock:
+
+ ============================================
+ WARNING: possible recursive locking detected
+ 6.12.0-55.27.1.el10_0.x86_64+debug #1 Not tainted
+ --------------------------------------------
+ mv/5099 is trying to acquire lock:
+ ffff888132845358 (&inode->i_sb->s_type->i_mutex_dir_key/1){+.+.}-{3:3},
+ at: __kern_path_locked+0x10a/0x2f0
+
+ but task is already holding lock:
+ ffff888132846b58 (&inode->i_sb->s_type->i_mutex_dir_key/1){+.+.}-{3:3},
+ at: lock_two_directories+0x13f/0x2b0
+
+ other info that might help us debug this:
+ Possible unsafe locking scenario:
+
+ CPU0
+ ----
+ lock(&inode->i_sb->s_type->i_mutex_dir_key/1);
+ lock(&inode->i_sb->s_type->i_mutex_dir_key/1);
+
+ *** DEADLOCK ***
+
+ May be due to missing lock nesting notation
+
+ 6 locks held by mv/5099:
+ #0: ffff888112a9c440 (sb_writers#13)
+ at: do_renameat2+0x34c/0xbc0
+ #1: ffff888112a9c790 (&type->s_vfs_rename_key#3)
+ at: do_renameat2+0x415/0xbc0
+ #2: ffff888132846b58 (&inode->i_sb->s_type->i_mutex_dir_key/1)
+ at: lock_two_directories+0x13f/0x2b0
+ #3: ffff888132845358 (&inode->i_sb->s_type->i_mutex_dir_key/5)
+ at: lock_two_directories+0x175/0x2b0
+ #4: ffffffffb3a1fb10 (&fsnotify_mark_srcu)
+ at: fsnotify+0x454/0x28a0
+ #5: ffffffffaf886230 (audit_filter_mutex)
+ at: audit_update_watch+0x36/0x11e0
+
+ stack backtrace:
+ Call Trace:
+ <TASK>
+ dump_stack_lvl+0x6f/0xb0
+ print_deadlock_bug.cold+0xbd/0xca
+ validate_chain+0x83a/0xf00
+ __lock_acquire+0xcac/0x1d20
+ lock_acquire.part.0+0x11b/0x360
+ down_write_nested+0x9f/0x230
+ __kern_path_locked+0x10a/0x2f0
+ kern_path_locked+0x26/0x40
+ audit_alloc_mark+0xfb/0x4f0
+ audit_dupe_exe+0x6c/0xe0
+ audit_dupe_rule+0x6c2/0xc00
+ audit_update_watch+0x4cc/0x11e0
+ audit_watch_handle_event+0x12c/0x1b0
+ send_to_group+0x5d0/0x8b0
+ fsnotify+0x615/0x28a0
+ fsnotify_move+0x1d8/0x630
+ vfs_rename+0xdcd/0x1df0
+ do_renameat2+0x9d4/0xbc0
+ __x64_sys_renameat+0x192/0x260
+ do_syscall_64+0x92/0x180
+ entry_SYSCALL_64_after_hwframe+0x76/0x7e
+ RIP: 0033:0x7f0491fe8c4e
+ Code: 0f 1f 40 00 48 8b 15 c1 e1 16 00 f7 d8 64 89 02 b8 ff ff ff ff
+ c3 66 0f 1f 44 00 00 f3 0f 1e fa 49 89 ca b8 08 01 00 00 0f 05 <48>
+ 3d 00 f0 ff ff 77 0a c3 66 0f 1f 84 00 00 00 00 00 48 8b 15 89
+ RSP: 002b:00007ffc7210bf38 EFLAGS: 00000246 ORIG_RAX: 0000000000000108
+ RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f0491fe8c4e
+ RDX: 0000000000000003 RSI: 00007ffc7210e6c8 RDI: 00000000ffffff9c
+ RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000001
+ R10: 00005575eb2dae2a R11: 0000000000000246 R12: 00005575eb2dae2a
+ R13: 00007ffc7210e6c8 R14: 0000000000000003 R15: 00000000ffffff9c
+ </TASK>
+
+The aforementioned deadlock can be consistently reproduced by running
+the script below:
+
+ audit-dupe-exe-deadlock.sh
+ --------------------------
+ #!/bin/bash
+ auditctl -D
+ mkdir -p /tmp/foo
+ touch /tmp/file
+ auditctl -a always,exit -F exe=/tmp/file -F path=/tmp/file -S all -k dr
+ mv /tmp/file /tmp/foo/file
+ rm -Rf /tmp/foo
+
+This patch fixes the issue by introducing struct audit_watch_ctx to pass
+the fsnotify event context down to audit_alloc_mark(). By utilizing the
+already-resolved directory inode provided by the event, we bypass the
+kern_path_parent() path resolution entirely, safely avoiding the
+recursive lock. Furthermore, it explicitly allows duplicate fsnotify
+marks (allow_dups = 1) during the rename update, allowing the new rule's
+mark to safely coexist with the old rule's mark until the old rule is
+freed.
+
+P.S.: This issue was identified and reproduced during a comprehensive
+code coverage analysis of the audit subsystem. The full report is
+available at the link below:
+
+https://people.redhat.com/rrobaina/audit-code-coverage-analysis.pdf
+
+P.P.S: With the permission of both Ricardo and Nathan, I've squashed a
+fixup patch from Nathan that addresses a compile time error when
+CONFIG_AUDITSYSCALL=n.
+
+Cc: stable@kernel.org
+Fixes: 34d99af52ad4 ("audit: implement audit by executable")
+Acked-by: Waiman Long <longman@redhat.com>
+Acked-by: Richard Guy Briggs <rgb@redhat.com>
+Signed-off-by: Nathan Chancellor <nathan@kernel.org>
+Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
+[PM: move link metadata into the msg, apply fix from NC]
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/audit.h | 17 ++++++++++++-----
+ kernel/audit_fsnotify.c | 32 +++++++++++++++++++++++---------
+ kernel/audit_watch.c | 25 +++++++++++++++++--------
+ kernel/auditfilter.c | 9 +++++----
+ 4 files changed, 57 insertions(+), 26 deletions(-)
+
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -256,8 +256,13 @@ extern int audit_del_rule(struct audit_e
+ extern void audit_free_rule_rcu(struct rcu_head *head);
+ extern struct list_head audit_filter_list[];
+
+-extern struct audit_entry *audit_dupe_rule(struct audit_krule *old);
++struct audit_watch_ctx {
++ struct inode *dir;
++ struct inode *child;
++};
+
++extern struct audit_entry *audit_dupe_rule(struct audit_krule *old,
++ struct audit_watch_ctx *ctx);
+ extern void audit_log_d_path_exe(struct audit_buffer *ab,
+ struct mm_struct *mm);
+
+@@ -280,13 +285,15 @@ extern char *audit_watch_path(struct aud
+ extern int audit_watch_compare(struct audit_watch *watch, u64 ino, dev_t dev);
+
+ extern struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule,
+- char *pathname, int len);
++ char *pathname, int len,
++ struct audit_watch_ctx *ctx);
+ extern char *audit_mark_path(struct audit_fsnotify_mark *mark);
+ extern void audit_remove_mark(struct audit_fsnotify_mark *audit_mark);
+ extern void audit_remove_mark_rule(struct audit_krule *krule);
+ extern int audit_mark_compare(struct audit_fsnotify_mark *mark, u64 ino,
+ dev_t dev);
+-extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old);
++extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old,
++ struct audit_watch_ctx *ctx);
+ extern int audit_exe_compare(struct task_struct *tsk,
+ struct audit_fsnotify_mark *mark);
+
+@@ -317,13 +324,13 @@ extern struct list_head *audit_killed_tr
+ #define audit_watch_path(w) ""
+ #define audit_watch_compare(w, i, d) 0
+
+-#define audit_alloc_mark(k, p, l) (ERR_PTR(-EINVAL))
++#define audit_alloc_mark(k, p, l, c) (ERR_PTR(-EINVAL))
+ #define audit_mark_path(m) ""
+ #define audit_remove_mark(m) do { } while (0)
+ #define audit_remove_mark_rule(k) do { } while (0)
+ #define audit_mark_compare(m, i, d) 0
+ #define audit_exe_compare(t, m) (-EINVAL)
+-#define audit_dupe_exe(n, o) (-EINVAL)
++#define audit_dupe_exe(n, o, c) (-EINVAL)
+
+ #define audit_remove_tree_rule(rule) BUG()
+ #define audit_add_tree_rule(rule) -EINVAL
+--- a/kernel/audit_fsnotify.c
++++ b/kernel/audit_fsnotify.c
+@@ -71,19 +71,30 @@ static void audit_update_mark(struct aud
+ audit_mark->ino = inode ? inode->i_ino : AUDIT_INO_UNSET;
+ }
+
+-struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule, char *pathname, int len)
++struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule, char *pathname,
++ int len, struct audit_watch_ctx *ctx)
+ {
+ struct audit_fsnotify_mark *audit_mark;
+ struct path path;
+ struct dentry *dentry;
+- int ret;
++ struct inode *dir, *child;
++ int ret, allow_dups;
+
+ if (pathname[0] != '/' || pathname[len-1] == '/')
+ return ERR_PTR(-EINVAL);
+
+- dentry = kern_path_parent(pathname, &path);
+- if (IS_ERR(dentry))
+- return ERR_CAST(dentry); /* returning an error */
++ if (!ctx) {
++ dentry = kern_path_parent(pathname, &path);
++ if (IS_ERR(dentry))
++ return ERR_CAST(dentry); /* returning an error */
++ dir = d_inode(path.dentry);
++ child = d_inode(dentry);
++ allow_dups = 0;
++ } else {
++ dir = ctx->dir;
++ child = ctx->child;
++ allow_dups = 1;
++ }
+
+ audit_mark = kzalloc_obj(*audit_mark);
+ if (unlikely(!audit_mark)) {
+@@ -94,18 +105,21 @@ struct audit_fsnotify_mark *audit_alloc_
+ fsnotify_init_mark(&audit_mark->mark, audit_fsnotify_group);
+ audit_mark->mark.mask = AUDIT_FS_EVENTS;
+ audit_mark->path = pathname;
+- audit_update_mark(audit_mark, dentry->d_inode);
+ audit_mark->rule = krule;
+
+- ret = fsnotify_add_inode_mark(&audit_mark->mark, path.dentry->d_inode, 0);
++ audit_update_mark(audit_mark, child);
++ ret = fsnotify_add_inode_mark(&audit_mark->mark, dir, allow_dups);
++
+ if (ret < 0) {
+ audit_mark->path = NULL;
+ fsnotify_put_mark(&audit_mark->mark);
+ audit_mark = ERR_PTR(ret);
+ }
+ out:
+- dput(dentry);
+- path_put(&path);
++ if (!ctx) {
++ dput(dentry);
++ path_put(&path);
++ }
+ return audit_mark;
+ }
+
+--- a/kernel/audit_watch.c
++++ b/kernel/audit_watch.c
+@@ -244,7 +244,8 @@ static void audit_watch_log_rule_change(
+ /* Update inode info in audit rules based on filesystem event. */
+ static void audit_update_watch(struct audit_parent *parent,
+ const struct qstr *dname, dev_t dev,
+- u64 ino, unsigned int invalidating)
++ u64 ino, unsigned int invalidating,
++ struct audit_watch_ctx *ctx)
+ {
+ struct audit_watch *owatch, *nwatch, *nextw;
+ struct audit_krule *r, *nextr;
+@@ -280,7 +281,7 @@ static void audit_update_watch(struct au
+ list_del(&oentry->rule.rlist);
+ list_del_rcu(&oentry->list);
+
+- nentry = audit_dupe_rule(&oentry->rule);
++ nentry = audit_dupe_rule(&oentry->rule, ctx);
+ if (IS_ERR(nentry)) {
+ list_del(&oentry->rule.list);
+ audit_panic("error updating watch, removing");
+@@ -479,10 +480,17 @@ static int audit_watch_handle_event(stru
+ if (WARN_ON_ONCE(inode_mark->group != audit_watch_group))
+ return 0;
+
+- if (mask & (FS_CREATE|FS_MOVED_TO) && inode)
+- audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0);
+- else if (mask & (FS_DELETE|FS_MOVED_FROM))
+- audit_update_watch(parent, dname, AUDIT_DEV_UNSET, AUDIT_INO_UNSET, 1);
++ if (mask & (FS_CREATE|FS_MOVED_TO) && inode) {
++ struct audit_watch_ctx ctx = { .dir = dir, .child = inode };
++
++ audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0,
++ &ctx);
++ } else if (mask & (FS_DELETE|FS_MOVED_FROM)) {
++ struct audit_watch_ctx ctx = { .dir = dir, .child = NULL };
++
++ audit_update_watch(parent, dname, AUDIT_DEV_UNSET, AUDIT_INO_UNSET, 1,
++ &ctx);
++ }
+ else if (mask & (FS_DELETE_SELF|FS_UNMOUNT|FS_MOVE_SELF))
+ audit_remove_parent_watches(parent);
+
+@@ -505,7 +513,8 @@ static int __init audit_watch_init(void)
+ }
+ device_initcall(audit_watch_init);
+
+-int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old)
++int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old,
++ struct audit_watch_ctx *ctx)
+ {
+ struct audit_fsnotify_mark *audit_mark;
+ char *pathname;
+@@ -514,7 +523,7 @@ int audit_dupe_exe(struct audit_krule *n
+ if (!pathname)
+ return -ENOMEM;
+
+- audit_mark = audit_alloc_mark(new, pathname, strlen(pathname));
++ audit_mark = audit_alloc_mark(new, pathname, strlen(pathname), ctx);
+ if (IS_ERR(audit_mark)) {
+ kfree(pathname);
+ return PTR_ERR(audit_mark);
+--- a/kernel/auditfilter.c
++++ b/kernel/auditfilter.c
+@@ -589,7 +589,7 @@ static struct audit_entry *audit_data_to
+ err = PTR_ERR(str);
+ goto exit_free;
+ }
+- audit_mark = audit_alloc_mark(&entry->rule, str, f_val);
++ audit_mark = audit_alloc_mark(&entry->rule, str, f_val, NULL);
+ if (IS_ERR(audit_mark)) {
+ kfree(str);
+ err = PTR_ERR(audit_mark);
+@@ -816,7 +816,8 @@ static inline int audit_dupe_lsm_field(s
+ * rule with the new rule in the filterlist, then free the old rule.
+ * The rlist element is undefined; list manipulations are handled apart from
+ * the initial copy. */
+-struct audit_entry *audit_dupe_rule(struct audit_krule *old)
++struct audit_entry *audit_dupe_rule(struct audit_krule *old,
++ struct audit_watch_ctx *ctx)
+ {
+ u32 fcount = old->field_count;
+ struct audit_entry *entry;
+@@ -875,7 +876,7 @@ struct audit_entry *audit_dupe_rule(stru
+ new->filterkey = fk;
+ break;
+ case AUDIT_EXE:
+- err = audit_dupe_exe(new, old);
++ err = audit_dupe_exe(new, old, ctx);
+ break;
+ }
+ if (err) {
+@@ -1414,7 +1415,7 @@ static int update_lsm_rule(struct audit_
+ if (!security_audit_rule_known(r))
+ return 0;
+
+- nentry = audit_dupe_rule(r);
++ nentry = audit_dupe_rule(r, NULL);
+ if (entry->rule.exe)
+ audit_remove_mark(entry->rule.exe);
+ if (IS_ERR(nentry)) {
--- /dev/null
+From stable+bounces-280757-greg=kroah.com@vger.kernel.org Tue Jul 21 23:11:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 14:01:48 -0400
+Subject: audit: use 'unsigned int' instead of 'unsigned'
+To: stable@vger.kernel.org
+Cc: Ricardo Robaina <rrobaina@redhat.com>, Paul Moore <paul@paul-moore.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721180149.286443-1-sashal@kernel.org>
+
+From: Ricardo Robaina <rrobaina@redhat.com>
+
+[ Upstream commit 8b226771014beab1292081151a99530886ce54b4 ]
+
+Address checkpatch.pl warning below, across the audit subsystem:
+
+ WARNING: Prefer 'unsigned int' to bare use of 'unsigned'
+
+Minor cleanup, no functional changes.
+
+Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/audit.h | 4 ++--
+ include/linux/audit_arch.h | 12 ++++++------
+ kernel/audit.c | 2 +-
+ kernel/audit.h | 2 +-
+ kernel/audit_tree.c | 2 +-
+ kernel/audit_watch.c | 2 +-
+ kernel/auditfilter.c | 8 ++++----
+ kernel/auditsc.c | 2 +-
+ lib/compat_audit.c | 12 ++++++------
+ 9 files changed, 23 insertions(+), 23 deletions(-)
+
+--- a/include/linux/audit.h
++++ b/include/linux/audit.h
+@@ -133,8 +133,8 @@ enum audit_nfcfgop {
+ AUDIT_NFT_OP_INVALID,
+ };
+
+-extern int __init audit_register_class(int class, unsigned *list);
+-extern int audit_classify_syscall(int abi, unsigned syscall);
++extern int __init audit_register_class(int class, unsigned int *list);
++extern int audit_classify_syscall(int abi, unsigned int syscall);
+ extern int audit_classify_arch(int arch);
+
+ /* audit_names->type values */
+--- a/include/linux/audit_arch.h
++++ b/include/linux/audit_arch.h
+@@ -21,13 +21,13 @@ enum auditsc_class_t {
+ AUDITSC_NVALS /* count */
+ };
+
+-extern int audit_classify_compat_syscall(int abi, unsigned syscall);
++extern int audit_classify_compat_syscall(int abi, unsigned int syscall);
+
+ /* only for compat system calls */
+-extern unsigned compat_write_class[];
+-extern unsigned compat_read_class[];
+-extern unsigned compat_dir_class[];
+-extern unsigned compat_chattr_class[];
+-extern unsigned compat_signal_class[];
++extern unsigned int compat_write_class[];
++extern unsigned int compat_read_class[];
++extern unsigned int compat_dir_class[];
++extern unsigned int compat_chattr_class[];
++extern unsigned int compat_signal_class[];
+
+ #endif
+--- a/kernel/audit.c
++++ b/kernel/audit.c
+@@ -2035,7 +2035,7 @@ void audit_log_vformat(struct audit_buff
+ * here and AUDIT_BUFSIZ is at least 1024, then we can
+ * log everything that printk could have logged. */
+ avail = audit_expand(ab,
+- max_t(unsigned, AUDIT_BUFSIZ, 1+len-avail));
++ max_t(unsigned int, AUDIT_BUFSIZ, 1+len-avail));
+ if (!avail)
+ goto out_va_end;
+ len = vsnprintf(skb_tail_pointer(skb), avail, fmt, args2);
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -233,7 +233,7 @@ static inline int audit_hash_ino(u64 ino
+ /* Indicates that audit should log the full pathname. */
+ #define AUDIT_NAME_FULL -1
+
+-extern int audit_match_class(int class, unsigned syscall);
++extern int audit_match_class(int class, unsigned int syscall);
+ extern int audit_comparator(const u32 left, const u32 op, const u32 right);
+ extern int audit_uid_comparator(kuid_t left, u32 op, kuid_t right);
+ extern int audit_gid_comparator(kgid_t left, u32 op, kgid_t right);
+--- a/kernel/audit_tree.c
++++ b/kernel/audit_tree.c
+@@ -33,7 +33,7 @@ struct audit_chunk {
+ struct audit_node {
+ struct list_head list;
+ struct audit_tree *owner;
+- unsigned index; /* index; upper bit indicates 'will prune' */
++ unsigned int index; /* index; upper bit indicates 'will prune' */
+ } owners[] __counted_by(count);
+ };
+
+--- a/kernel/audit_watch.c
++++ b/kernel/audit_watch.c
+@@ -244,7 +244,7 @@ static void audit_watch_log_rule_change(
+ /* Update inode info in audit rules based on filesystem event. */
+ static void audit_update_watch(struct audit_parent *parent,
+ const struct qstr *dname, dev_t dev,
+- u64 ino, unsigned invalidating)
++ u64 ino, unsigned int invalidating)
+ {
+ struct audit_watch *owatch, *nwatch, *nextw;
+ struct audit_krule *r, *nextr;
+--- a/kernel/auditfilter.c
++++ b/kernel/auditfilter.c
+@@ -165,13 +165,13 @@ static inline int audit_to_inode(struct
+
+ static __u32 *classes[AUDIT_SYSCALL_CLASSES];
+
+-int __init audit_register_class(int class, unsigned *list)
++int __init audit_register_class(int class, unsigned int *list)
+ {
+ __u32 *p = kcalloc(AUDIT_BITMASK_SIZE, sizeof(__u32), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ while (*list != ~0U) {
+- unsigned n = *list++;
++ unsigned int n = *list++;
+ if (n >= AUDIT_BITMASK_SIZE * 32 - AUDIT_SYSCALL_CLASSES) {
+ kfree(p);
+ return -EINVAL;
+@@ -186,7 +186,7 @@ int __init audit_register_class(int clas
+ return 0;
+ }
+
+-int audit_match_class(int class, unsigned syscall)
++int audit_match_class(int class, unsigned int syscall)
+ {
+ if (unlikely(syscall >= AUDIT_BITMASK_SIZE * 32))
+ return 0;
+@@ -237,7 +237,7 @@ static int audit_match_signal(struct aud
+ /* Common user-space to kernel rule translation. */
+ static inline struct audit_entry *audit_to_entry_common(struct audit_rule_data *rule)
+ {
+- unsigned listnr;
++ unsigned int listnr;
+ struct audit_entry *entry;
+ int i, err;
+
+--- a/kernel/auditsc.c
++++ b/kernel/auditsc.c
+@@ -150,7 +150,7 @@ static const struct audit_nfcfgop_tab au
+
+ static int audit_match_perm(struct audit_context *ctx, int mask)
+ {
+- unsigned n;
++ unsigned int n;
+
+ if (unlikely(!ctx))
+ return 0;
+--- a/lib/compat_audit.c
++++ b/lib/compat_audit.c
+@@ -4,32 +4,32 @@
+ #include <linux/audit_arch.h>
+ #include <asm/unistd32.h>
+
+-unsigned compat_dir_class[] = {
++unsigned int compat_dir_class[] = {
+ #include <asm-generic/audit_dir_write.h>
+ ~0U
+ };
+
+-unsigned compat_read_class[] = {
++unsigned int compat_read_class[] = {
+ #include <asm-generic/audit_read.h>
+ ~0U
+ };
+
+-unsigned compat_write_class[] = {
++unsigned int compat_write_class[] = {
+ #include <asm-generic/audit_write.h>
+ ~0U
+ };
+
+-unsigned compat_chattr_class[] = {
++unsigned int compat_chattr_class[] = {
+ #include <asm-generic/audit_change_attr.h>
+ ~0U
+ };
+
+-unsigned compat_signal_class[] = {
++unsigned int compat_signal_class[] = {
+ #include <asm-generic/audit_signal.h>
+ ~0U
+ };
+
+-int audit_classify_compat_syscall(int abi, unsigned syscall)
++int audit_classify_compat_syscall(int abi, unsigned int syscall)
+ {
+ switch (syscall) {
+ #ifdef __NR_open
--- /dev/null
+From stable+bounces-290075-greg=kroah.com@vger.kernel.org Tue Jul 28 19:10:17 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 12:10:43 -0400
+Subject: bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()
+To: stable@vger.kernel.org
+Cc: Breno Leitao <leitao@debian.org>, "Masami Hiramatsu (Google)" <mhiramat@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728161043.36478-2-sashal@kernel.org>
+
+From: Breno Leitao <leitao@debian.org>
+
+[ Upstream commit dec4d8118c179b3d12bca7e609054c6011c4f2ce ]
+
+xbc_snprint_cmdline() is meant to be called twice: first with
+buf=NULL, size=0 to probe the rendered length, then with a real
+buffer to fill it (the standard snprintf() two-pass pattern). The
+probe call makes the function compute "buf + size" (NULL + 0) and,
+on every iteration, advance "buf += ret" from that NULL base and
+pass the result back into snprintf().
+
+Pointer arithmetic on a NULL pointer is undefined behavior. It is
+harmless in the in-kernel callers today, but the follow-up patches
+run this same code in the userspace tools/bootconfig parser at kernel
+build time, where host UBSan / FORTIFY_SOURCE abort the build.
+
+Track a running written length (size_t) instead of mutating @buf, and
+only form "buf + len" when @buf is non-NULL. snprintf(NULL, 0, ...)
+is itself well defined and returns the would-be length, so the
+two-pass "probe then fill" usage returns identical byte counts.
+
+Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-1-24ab72139c29@debian.org/
+
+Fixes: 51887d03aca1 ("bootconfig: init: Allow admin to use bootconfig for kernel command line")
+Cc: stable@vger.kernel.org
+Signed-off-by: Breno Leitao <leitao@debian.org>
+Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ lib/bootconfig.c | 23 ++++++++++++++++-------
+ 1 file changed, 16 insertions(+), 7 deletions(-)
+
+--- a/lib/bootconfig.c
++++ b/lib/bootconfig.c
+@@ -427,10 +427,18 @@ static char xbc_namebuf[XBC_KEYLEN_MAX]
+ int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root)
+ {
+ struct xbc_node *knode, *vnode;
+- char *end = buf + size;
+ const char *val, *q;
++ size_t len = 0;
+ int ret;
+
++ /*
++ * Track the running written length rather than advancing @buf, so we
++ * never form "buf + size" or "buf += ret" while @buf is NULL (the
++ * size-probe call passes buf=NULL, size=0). NULL pointer arithmetic
++ * is undefined behavior and trips host UBSan / FORTIFY_SOURCE when
++ * this renderer runs at kernel build time. snprintf(NULL, 0, ...)
++ * itself is well defined and returns the would-be length.
++ */
+ xbc_node_for_each_key_value(root, knode, val) {
+ ret = xbc_node_compose_key_after(root, knode,
+ xbc_namebuf, XBC_KEYLEN_MAX);
+@@ -439,10 +447,11 @@ int __init xbc_snprint_cmdline(char *buf
+
+ vnode = xbc_node_get_child(knode);
+ if (!vnode) {
+- ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf);
++ ret = snprintf(buf ? buf + len : NULL, rest(len, size),
++ "%s ", xbc_namebuf);
+ if (ret < 0)
+ return ret;
+- buf += ret;
++ len += ret;
+ continue;
+ }
+ xbc_array_for_each_value(vnode, val) {
+@@ -452,15 +461,15 @@ int __init xbc_snprint_cmdline(char *buf
+ * whitespace.
+ */
+ q = strpbrk(val, " \t\r\n") ? "\"" : "";
+- ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ",
+- xbc_namebuf, q, val, q);
++ ret = snprintf(buf ? buf + len : NULL, rest(len, size),
++ "%s=%s%s%s ", xbc_namebuf, q, val, q);
+ if (ret < 0)
+ return ret;
+- buf += ret;
++ len += ret;
+ }
+ }
+
+- return buf - (end - size);
++ return len;
+ }
+ #undef rest
+
--- /dev/null
+From stable+bounces-290074-greg=kroah.com@vger.kernel.org Tue Jul 28 18:58:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 12:10:42 -0400
+Subject: bootconfig: move xbc_snprint_cmdline() to lib/bootconfig.c
+To: stable@vger.kernel.org
+Cc: Breno Leitao <leitao@debian.org>, "Masami Hiramatsu (Google)" <mhiramat@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728161043.36478-1-sashal@kernel.org>
+
+From: Breno Leitao <leitao@debian.org>
+
+[ Upstream commit 5a643e4623238e14b03d75ca0d4eda0645720cee ]
+
+Move xbc_snprint_cmdline() from init/main.c to lib/bootconfig.c so the
+function (and its xbc_namebuf scratch buffer) becomes part of the shared
+parser library. tools/bootconfig already compiles lib/bootconfig.c
+directly, which lets a follow-up patch reuse the same renderer in the
+userspace tool to convert a bootconfig file into a flat cmdline string
+at build time.
+
+No functional change.
+
+Link: https://lore.kernel.org/all/20260508-bootconfig_using_tools-v1-1-1132219aa773@debian.org/
+
+Signed-off-by: Breno Leitao <leitao@debian.org>
+Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Stable-dep-of: dec4d8118c17 ("bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/bootconfig.h | 3 ++
+ init/main.c | 45 ------------------------------------
+ lib/bootconfig.c | 56 +++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 59 insertions(+), 45 deletions(-)
+
+--- a/include/linux/bootconfig.h
++++ b/include/linux/bootconfig.h
+@@ -265,6 +265,9 @@ static inline struct xbc_node * __init x
+ int __init xbc_node_compose_key_after(struct xbc_node *root,
+ struct xbc_node *node, char *buf, size_t size);
+
++/* Render key/value pairs under @root as a flat cmdline string */
++int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root);
++
+ /**
+ * xbc_node_compose_key() - Compose full key string of the XBC node
+ * @node: An XBC node.
+--- a/init/main.c
++++ b/init/main.c
+@@ -324,51 +324,6 @@ static void * __init get_boot_config_fro
+
+ #ifdef CONFIG_BOOT_CONFIG
+
+-static char xbc_namebuf[XBC_KEYLEN_MAX] __initdata;
+-
+-#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0)
+-
+-static int __init xbc_snprint_cmdline(char *buf, size_t size,
+- struct xbc_node *root)
+-{
+- struct xbc_node *knode, *vnode;
+- char *end = buf + size;
+- const char *val, *q;
+- int ret;
+-
+- xbc_node_for_each_key_value(root, knode, val) {
+- ret = xbc_node_compose_key_after(root, knode,
+- xbc_namebuf, XBC_KEYLEN_MAX);
+- if (ret < 0)
+- return ret;
+-
+- vnode = xbc_node_get_child(knode);
+- if (!vnode) {
+- ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf);
+- if (ret < 0)
+- return ret;
+- buf += ret;
+- continue;
+- }
+- xbc_array_for_each_value(vnode, val) {
+- /*
+- * For prettier and more readable /proc/cmdline, only
+- * quote the value when necessary, i.e. when it contains
+- * whitespace.
+- */
+- q = strpbrk(val, " \t\r\n") ? "\"" : "";
+- ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ",
+- xbc_namebuf, q, val, q);
+- if (ret < 0)
+- return ret;
+- buf += ret;
+- }
+- }
+-
+- return buf - (end - size);
+-}
+-#undef rest
+-
+ /* Make an extra command line under given key word */
+ static char * __init xbc_make_cmdline(const char *key)
+ {
+--- a/lib/bootconfig.c
++++ b/lib/bootconfig.c
+@@ -408,6 +408,62 @@ const char * __init xbc_node_find_next_k
+ return ""; /* No value key */
+ }
+
++static char xbc_namebuf[XBC_KEYLEN_MAX] __initdata;
++
++#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0)
++
++/**
++ * xbc_snprint_cmdline() - Render bootconfig keys under @root as a cmdline string
++ * @buf: Destination buffer (may be NULL when @size is 0 to query the length)
++ * @size: Size of @buf in bytes
++ * @root: Subtree root whose key=value pairs should be rendered
++ *
++ * Walk all key/value pairs under @root and emit them as a space-separated
++ * cmdline string into @buf. Values containing whitespace are quoted with
++ * double quotes. Returns the number of bytes that would be written if @buf
++ * were large enough (matching snprintf semantics), or a negative errno on
++ * failure.
++ */
++int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root)
++{
++ struct xbc_node *knode, *vnode;
++ char *end = buf + size;
++ const char *val, *q;
++ int ret;
++
++ xbc_node_for_each_key_value(root, knode, val) {
++ ret = xbc_node_compose_key_after(root, knode,
++ xbc_namebuf, XBC_KEYLEN_MAX);
++ if (ret < 0)
++ return ret;
++
++ vnode = xbc_node_get_child(knode);
++ if (!vnode) {
++ ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf);
++ if (ret < 0)
++ return ret;
++ buf += ret;
++ continue;
++ }
++ xbc_array_for_each_value(vnode, val) {
++ /*
++ * For prettier and more readable /proc/cmdline, only
++ * quote the value when necessary, i.e. when it contains
++ * whitespace.
++ */
++ q = strpbrk(val, " \t\r\n") ? "\"" : "";
++ ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ",
++ xbc_namebuf, q, val, q);
++ if (ret < 0)
++ return ret;
++ buf += ret;
++ }
++ }
++
++ return buf - (end - size);
++}
++#undef rest
++
+ /* XBC parse and tree build */
+
+ static int __init xbc_init_node(struct xbc_node *node, char *data, uint16_t flag)
--- /dev/null
+From stable+bounces-287812-greg=kroah.com@vger.kernel.org Wed Jul 22 13:34:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 07:24:31 -0400
+Subject: fuse-uring: fix race between registration and connection abortion
+To: stable@vger.kernel.org
+Cc: Joanne Koong <joannelkoong@gmail.com>, Bernd Schubert <bernd@bsbernd.com>, Miklos Szeredi <mszeredi@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722112431.1115514-1-sashal@kernel.org>
+
+From: Joanne Koong <joannelkoong@gmail.com>
+
+[ Upstream commit 952b5d36f6a298f57c52a59e72076c69386a8aaf ]
+
+This fixes this race:
+- thread a: io_uring_enter -> register sqe ->
+ fuse_uring_create_ring_ent -> allocate ent but doesn't grab queue_ref
+ yet
+- thread b: fuse_conn_destroy() -> fuse_chan_abort() ->
+ fuse_uring_abort() is a no-op due to queue ref being 0
+- thread a: grabs the queue_ref, queue_ref is now 1, rest of
+ fuse_uring_do_register() logic executes
+- thread b: fuse_chan_abort() returns, fuse_chan_wait_aborted() now runs
+ and calls
+ "wait_event(ring->stop_waitq, atomic_read(&ring->queue_refs) == 0);"
+The abort/unmount thread will hang indefinitely in unkillable state as
+nothing will decrement queue_refs or wake stop_waitq, and the ring,
+queue, and ent are leaked.
+
+Fix this by checking fch->connected under fch->lock after the created
+ent has grabbed a ref count on the queue. This ensures that in the
+scenario above, it is guaranteed that we either release the queue ref
+and wake up stop_waitq (in case fuse_chan_wait_aborted() is already
+waiting) in fuse_uring_do_register() when we detect !fch->connected, or
+if the connection is aborted after the check, it is guaranteed that the
+async teardown worker will be running in the background cleaning up ents
+and decrementing the ent's ref on the queue, which will unblock the
+eventual queue and ring teardown.
+
+Fixes: 24fe962c86f5 ("fuse: {io-uring} Handle SQEs - register commands")
+Cc: stable@vger.kernel.org
+Reviewed-by: Bernd Schubert <bernd@bsbernd.com>
+Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+[ changed fch->lock/fch->connected references to fc->lock/fc->connected since struct fuse_chan does not exist in this tree ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev_uring.c | 22 ++++++++++++++++------
+ 1 file changed, 16 insertions(+), 6 deletions(-)
+
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -995,15 +995,26 @@ static bool is_ring_ready(struct fuse_ri
+ /*
+ * fuse_uring_req_fetch command handling
+ */
+-static void fuse_uring_do_register(struct fuse_ring_ent *ent,
+- struct io_uring_cmd *cmd,
+- unsigned int issue_flags)
++static int fuse_uring_do_register(struct fuse_ring_ent *ent,
++ struct io_uring_cmd *cmd,
++ unsigned int issue_flags)
+ {
+ struct fuse_ring_queue *queue = ent->queue;
+ struct fuse_ring *ring = queue->ring;
+ struct fuse_conn *fc = ring->fc;
+ struct fuse_iqueue *fiq = &fc->iq;
+
++ spin_lock(&fc->lock);
++ /* abort teardown path is running or has run */
++ if (!fc->connected) {
++ spin_unlock(&fc->lock);
++ if (atomic_dec_and_test(&ring->queue_refs))
++ wake_up_all(&ring->stop_waitq);
++ kfree(ent);
++ return -ECONNABORTED;
++ }
++ spin_unlock(&fc->lock);
++
+ fuse_uring_prepare_cancel(cmd, issue_flags, ent);
+
+ spin_lock(&queue->lock);
+@@ -1020,6 +1031,7 @@ static void fuse_uring_do_register(struc
+ wake_up_all(&fc->blocked_waitq);
+ }
+ }
++ return 0;
+ }
+
+ /*
+@@ -1136,9 +1148,7 @@ static int fuse_uring_register(struct io
+ if (IS_ERR(ent))
+ return PTR_ERR(ent);
+
+- fuse_uring_do_register(ent, cmd, issue_flags);
+-
+- return 0;
++ return fuse_uring_do_register(ent, cmd, issue_flags);
+ }
+
+ /*
--- /dev/null
+From stable+bounces-288837-greg=kroah.com@vger.kernel.org Fri Jul 24 13:00:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 06:49:06 -0400
+Subject: mm/sparse-vmemmap: fix DAX vmemmap accounting with optimization
+To: stable@vger.kernel.org
+Cc: Muchun Song <songmuchun@bytedance.com>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Oscar Salvador <osalvador@suse.de>, "David Hildenbrand (Arm)" <david@kernel.org>, "Liam R. Howlett" <liam@infradead.org>, "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>, Joao Martins <joao.m.martins@oracle.com>, Lorenzo Stoakes <ljs@kernel.org>, Madhavan Srinivasan <maddy@linux.ibm.com>, Michael Ellerman <mpe@ellerman.id.au>, Michal Hocko <mhocko@suse.com>, Nicholas Piggin <npiggin@gmail.com>, Suren Baghdasaryan <surenb@google.com>, Vlastimil Babka <vbabka@kernel.org>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260724104906.3974254-4-sashal@kernel.org>
+
+From: Muchun Song <songmuchun@bytedance.com>
+
+[ Upstream commit 721a73e30c9e3e8fcffe1725bcede1bbd20b4918 ]
+
+When vmemmap optimization is enabled for DAX, the nr_memmap_pages counter
+in /proc/vmstat is incorrect. The current code always accounts for the
+full, non-optimized vmemmap size, but vmemmap optimization reduces the
+actual number of vmemmap pages by reusing tail pages. This causes the
+system to overcount vmemmap usage, leading to inaccurate page statistics
+in /proc/vmstat.
+
+Fix this by introducing section_nr_vmemmap_pages(), which returns the
+exact vmemmap page count for a given pfn range based on whether
+optimization is in effect.
+
+Link: https://lore.kernel.org/20260428081855.1249045-5-songmuchun@bytedance.com
+Fixes: 15995a352474 ("mm: report per-page metadata information")
+Signed-off-by: Muchun Song <songmuchun@bytedance.com>
+Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Acked-by: Oscar Salvador <osalvador@suse.de>
+Acked-by: David Hildenbrand (Arm) <david@kernel.org>
+Acked-by: Liam R. Howlett <liam@infradead.org>
+Cc: "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>
+Cc: Joao Martins <joao.m.martins@oracle.com>
+Cc: Lorenzo Stoakes <ljs@kernel.org>
+Cc: Madhavan Srinivasan <maddy@linux.ibm.com>
+Cc: Michael Ellerman <mpe@ellerman.id.au>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Nicholas Piggin <npiggin@gmail.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: Vlastimil Babka <vbabka@kernel.org>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ mm/sparse-vmemmap.c | 34 ++++++++++++++++++++++++++++++----
+ 1 file changed, 30 insertions(+), 4 deletions(-)
+
+--- a/mm/sparse-vmemmap.c
++++ b/mm/sparse-vmemmap.c
+@@ -652,6 +652,31 @@ void offline_mem_sections(unsigned long
+ }
+ }
+
++static int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
++ struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
++{
++ const unsigned int order = pgmap ? pgmap->vmemmap_shift : 0;
++ const unsigned long pages_per_compound = 1UL << order;
++
++ VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SUBSECTION));
++ VM_WARN_ON_ONCE(nr_pages > PAGES_PER_SECTION);
++
++ if (!vmemmap_can_optimize(altmap, pgmap))
++ return DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE);
++
++ if (order < PFN_SECTION_SHIFT) {
++ VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, pages_per_compound));
++ return VMEMMAP_RESERVE_NR * nr_pages / pages_per_compound;
++ }
++
++ VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION));
++
++ if (IS_ALIGNED(pfn, pages_per_compound))
++ return VMEMMAP_RESERVE_NR;
++
++ return 0;
++}
++
+ static struct page * __meminit populate_section_memmap(unsigned long pfn,
+ unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
+ struct dev_pagemap *pgmap)
+@@ -659,7 +684,7 @@ static struct page * __meminit populate_
+ struct page *page = __populate_section_memmap(pfn, nr_pages, nid, altmap,
+ pgmap);
+
+- memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE));
++ memmap_pages_add(section_nr_vmemmap_pages(pfn, nr_pages, altmap, pgmap));
+
+ return page;
+ }
+@@ -670,7 +695,7 @@ static void depopulate_section_memmap(un
+ unsigned long start = (unsigned long) pfn_to_page(pfn);
+ unsigned long end = start + nr_pages * sizeof(struct page);
+
+- memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
++ memmap_pages_add(-section_nr_vmemmap_pages(pfn, nr_pages, altmap, pgmap));
+ vmemmap_free(start, end, altmap);
+ }
+
+@@ -678,9 +703,10 @@ static void free_map_bootmem(struct page
+ {
+ unsigned long start = (unsigned long)memmap;
+ unsigned long end = (unsigned long)(memmap + PAGES_PER_SECTION);
++ unsigned long pfn = page_to_pfn(memmap);
+
+- memmap_boot_pages_add(-1L * (DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page),
+- PAGE_SIZE)));
++ memmap_boot_pages_add(-section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION,
++ NULL, NULL));
+ vmemmap_free(start, end, NULL);
+ }
+
--- /dev/null
+From stable+bounces-288835-greg=kroah.com@vger.kernel.org Fri Jul 24 12:58:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 06:49:05 -0400
+Subject: mm/sparse-vmemmap: pass @pgmap argument to memory deactivation paths
+To: stable@vger.kernel.org
+Cc: Muchun Song <songmuchun@bytedance.com>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Oscar Salvador <osalvador@suse.de>, "David Hildenbrand (Arm)" <david@kernel.org>, "Liam R. Howlett" <liam@infradead.org>, "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>, Joao Martins <joao.m.martins@oracle.com>, Lorenzo Stoakes <ljs@kernel.org>, Madhavan Srinivasan <maddy@linux.ibm.com>, Michael Ellerman <mpe@ellerman.id.au>, Michal Hocko <mhocko@suse.com>, Nicholas Piggin <npiggin@gmail.com>, Suren Baghdasaryan <surenb@google.com>, Vlastimil Babka <vbabka@kernel.org>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260724104906.3974254-3-sashal@kernel.org>
+
+From: Muchun Song <songmuchun@bytedance.com>
+
+[ Upstream commit 3bbc54dd1b62f1a4b218c70aafbeceeba7c90c5d ]
+
+Currently, the memory hot-remove call chain -- arch_remove_memory(),
+__remove_pages(), sparse_remove_section() and section_deactivate() -- does
+not carry the struct dev_pagemap pointer. This prevents the lower levels
+from knowing whether the section was originally populated with vmemmap
+optimizations (e.g., DAX with vmemmap optimization enabled).
+
+Without this information, we cannot call vmemmap_can_optimize() to
+determine if the vmemmap pages were optimized. As a result, the vmemmap
+page accounting during teardown will mistakenly assume a non-optimized
+allocation, leading to incorrect memmap statistics.
+
+To lay the groundwork for fixing the vmemmap page accounting, we need to
+pass the @pgmap pointer down to the deactivation location. Plumb the
+@pgmap argument through the APIs of arch_remove_memory(), __remove_pages()
+and sparse_remove_section(), mirroring the corresponding *_activate()
+paths.
+
+Link: https://lore.kernel.org/20260428081855.1249045-4-songmuchun@bytedance.com
+Signed-off-by: Muchun Song <songmuchun@bytedance.com>
+Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Reviewed-by: Oscar Salvador <osalvador@suse.de>
+Acked-by: David Hildenbrand (Arm) <david@kernel.org>
+Acked-by: Liam R. Howlett <liam@infradead.org>
+Cc: "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>
+Cc: Joao Martins <joao.m.martins@oracle.com>
+Cc: Lorenzo Stoakes <ljs@kernel.org>
+Cc: Madhavan Srinivasan <maddy@linux.ibm.com>
+Cc: Michael Ellerman <mpe@ellerman.id.au>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Nicholas Piggin <npiggin@gmail.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: Vlastimil Babka <vbabka@kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Stable-dep-of: 721a73e30c9e ("mm/sparse-vmemmap: fix DAX vmemmap accounting with optimization")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/arm64/mm/mmu.c | 5 +++--
+ arch/loongarch/mm/init.c | 5 +++--
+ arch/powerpc/mm/mem.c | 5 +++--
+ arch/riscv/mm/init.c | 5 +++--
+ arch/s390/mm/init.c | 5 +++--
+ arch/x86/mm/init_64.c | 5 +++--
+ include/linux/memory_hotplug.h | 8 +++++---
+ mm/memory_hotplug.c | 13 +++++++------
+ mm/memremap.c | 4 ++--
+ mm/sparse-vmemmap.c | 12 ++++++------
+ 10 files changed, 38 insertions(+), 29 deletions(-)
+
+--- a/arch/arm64/mm/mmu.c
++++ b/arch/arm64/mm/mmu.c
+@@ -2032,12 +2032,13 @@ err:
+ return ret;
+ }
+
+-void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++ struct dev_pagemap *pgmap)
+ {
+ unsigned long start_pfn = start >> PAGE_SHIFT;
+ unsigned long nr_pages = size >> PAGE_SHIFT;
+
+- __remove_pages(start_pfn, nr_pages, altmap);
++ __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+ __remove_pgd_mapping(swapper_pg_dir, __phys_to_virt(start), size);
+ }
+
+--- a/arch/loongarch/mm/init.c
++++ b/arch/loongarch/mm/init.c
+@@ -119,12 +119,13 @@ int arch_add_memory(int nid, u64 start,
+ return ret;
+ }
+
+-void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++ struct dev_pagemap *pgmap)
+ {
+ unsigned long start_pfn = start >> PAGE_SHIFT;
+ unsigned long nr_pages = size >> PAGE_SHIFT;
+
+- __remove_pages(start_pfn, nr_pages, altmap);
++ __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+ }
+ #endif
+
+--- a/arch/powerpc/mm/mem.c
++++ b/arch/powerpc/mm/mem.c
+@@ -158,12 +158,13 @@ int __ref arch_add_memory(int nid, u64 s
+ return rc;
+ }
+
+-void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++ struct dev_pagemap *pgmap)
+ {
+ unsigned long start_pfn = start >> PAGE_SHIFT;
+ unsigned long nr_pages = size >> PAGE_SHIFT;
+
+- __remove_pages(start_pfn, nr_pages, altmap);
++ __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+ arch_remove_linear_mapping(start, size);
+ }
+ #endif
+--- a/arch/riscv/mm/init.c
++++ b/arch/riscv/mm/init.c
+@@ -1742,9 +1742,10 @@ int __ref arch_add_memory(int nid, u64 s
+ return ret;
+ }
+
+-void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++ struct dev_pagemap *pgmap)
+ {
+- __remove_pages(start >> PAGE_SHIFT, size >> PAGE_SHIFT, altmap);
++ __remove_pages(start >> PAGE_SHIFT, size >> PAGE_SHIFT, altmap, pgmap);
+ remove_linear_mapping(start, size);
+ flush_tlb_all();
+ }
+--- a/arch/s390/mm/init.c
++++ b/arch/s390/mm/init.c
+@@ -276,12 +276,13 @@ int arch_add_memory(int nid, u64 start,
+ return rc;
+ }
+
+-void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++ struct dev_pagemap *pgmap)
+ {
+ unsigned long start_pfn = start >> PAGE_SHIFT;
+ unsigned long nr_pages = size >> PAGE_SHIFT;
+
+- __remove_pages(start_pfn, nr_pages, altmap);
++ __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+ vmem_remove_mapping(start, size);
+ }
+ #endif /* CONFIG_MEMORY_HOTPLUG */
+--- a/arch/x86/mm/init_64.c
++++ b/arch/x86/mm/init_64.c
+@@ -1300,12 +1300,13 @@ kernel_physical_mapping_remove(unsigned
+ remove_pagetable(start, end, true, NULL);
+ }
+
+-void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++ struct dev_pagemap *pgmap)
+ {
+ unsigned long start_pfn = start >> PAGE_SHIFT;
+ unsigned long nr_pages = size >> PAGE_SHIFT;
+
+- __remove_pages(start_pfn, nr_pages, altmap);
++ __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+ kernel_physical_mapping_remove(start, start + size);
+ }
+ #endif /* CONFIG_MEMORY_HOTPLUG */
+--- a/include/linux/memory_hotplug.h
++++ b/include/linux/memory_hotplug.h
+@@ -135,9 +135,10 @@ static inline bool movable_node_is_enabl
+ return movable_node_enabled;
+ }
+
+-extern void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap);
++extern void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++ struct dev_pagemap *pgmap);
+ extern void __remove_pages(unsigned long start_pfn, unsigned long nr_pages,
+- struct vmem_altmap *altmap);
++ struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
+
+ /* reasonably generic interface to expand the physical pages */
+ extern int __add_pages(int nid, unsigned long start_pfn, unsigned long nr_pages,
+@@ -307,7 +308,8 @@ extern int sparse_add_section(int nid, u
+ unsigned long nr_pages, struct vmem_altmap *altmap,
+ struct dev_pagemap *pgmap);
+ extern void sparse_remove_section(unsigned long pfn, unsigned long nr_pages,
+- struct vmem_altmap *altmap);
++ struct vmem_altmap *altmap,
++ struct dev_pagemap *pgmap);
+ extern struct zone *zone_for_pfn_range(enum mmop online_type,
+ int nid, struct memory_group *group, unsigned long start_pfn,
+ unsigned long nr_pages);
+--- a/mm/memory_hotplug.c
++++ b/mm/memory_hotplug.c
+@@ -576,6 +576,7 @@ void remove_pfn_range_from_zone(struct z
+ * @pfn: starting pageframe (must be aligned to start of a section)
+ * @nr_pages: number of pages to remove (must be multiple of section size)
+ * @altmap: alternative device page map or %NULL if default memmap is used
++ * @pgmap: device page map or %NULL if not ZONE_DEVICE
+ *
+ * Generic helper function to remove section mappings and sysfs entries
+ * for the section of the memory we are removing. Caller needs to make
+@@ -583,7 +584,7 @@ void remove_pfn_range_from_zone(struct z
+ * calling offline_pages().
+ */
+ void __remove_pages(unsigned long pfn, unsigned long nr_pages,
+- struct vmem_altmap *altmap)
++ struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+ {
+ const unsigned long end_pfn = pfn + nr_pages;
+ unsigned long cur_nr_pages;
+@@ -598,7 +599,7 @@ void __remove_pages(unsigned long pfn, u
+ /* Select all remaining pages up to the next section boundary */
+ cur_nr_pages = min(end_pfn - pfn,
+ SECTION_ALIGN_UP(pfn + 1) - pfn);
+- sparse_remove_section(pfn, cur_nr_pages, altmap);
++ sparse_remove_section(pfn, cur_nr_pages, altmap, pgmap);
+ }
+ }
+
+@@ -1427,7 +1428,7 @@ static void remove_memory_blocks_and_alt
+
+ remove_memory_block_devices(cur_start, memblock_size);
+
+- arch_remove_memory(cur_start, memblock_size, altmap);
++ arch_remove_memory(cur_start, memblock_size, altmap, NULL);
+
+ /* Verify that all vmemmap pages have actually been freed. */
+ WARN(altmap->alloc, "Altmap not fully unmapped");
+@@ -1470,7 +1471,7 @@ static int create_altmaps_and_memory_blo
+ ret = create_memory_block_devices(cur_start, memblock_size, nid,
+ params.altmap, group);
+ if (ret) {
+- arch_remove_memory(cur_start, memblock_size, params.altmap);
++ arch_remove_memory(cur_start, memblock_size, params.altmap, NULL);
+ kfree(params.altmap);
+ goto out;
+ }
+@@ -1556,7 +1557,7 @@ int add_memory_resource(int nid, struct
+ /* create memory block devices after memory was added */
+ ret = create_memory_block_devices(start, size, nid, NULL, group);
+ if (ret) {
+- arch_remove_memory(start, size, params.altmap);
++ arch_remove_memory(start, size, params.altmap, NULL);
+ goto error;
+ }
+ }
+@@ -2268,7 +2269,7 @@ static int try_remove_memory(u64 start,
+ * No altmaps present, do the removal directly
+ */
+ remove_memory_block_devices(start, size);
+- arch_remove_memory(start, size, NULL);
++ arch_remove_memory(start, size, NULL, NULL);
+ } else {
+ /* all memblocks in the range have altmaps */
+ remove_memory_blocks_and_altmaps(start, size);
+--- a/mm/memremap.c
++++ b/mm/memremap.c
+@@ -97,10 +97,10 @@ static void pageunmap_range(struct dev_p
+ PHYS_PFN(range_len(range)));
+ if (pgmap->type == MEMORY_DEVICE_PRIVATE) {
+ __remove_pages(PHYS_PFN(range->start),
+- PHYS_PFN(range_len(range)), NULL);
++ PHYS_PFN(range_len(range)), NULL, pgmap);
+ } else {
+ arch_remove_memory(range->start, range_len(range),
+- pgmap_altmap(pgmap));
++ pgmap_altmap(pgmap), pgmap);
+ kasan_remove_zero_shadow(__va(range->start), range_len(range));
+ }
+ mem_hotplug_done();
+--- a/mm/sparse-vmemmap.c
++++ b/mm/sparse-vmemmap.c
+@@ -665,7 +665,7 @@ static struct page * __meminit populate_
+ }
+
+ static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
+- struct vmem_altmap *altmap)
++ struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+ {
+ unsigned long start = (unsigned long) pfn_to_page(pfn);
+ unsigned long end = start + nr_pages * sizeof(struct page);
+@@ -746,7 +746,7 @@ static int fill_subsection_map(unsigned
+ * usage map, but still need to free the vmemmap range.
+ */
+ static void section_deactivate(unsigned long pfn, unsigned long nr_pages,
+- struct vmem_altmap *altmap)
++ struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+ {
+ struct mem_section *ms = __pfn_to_section(pfn);
+ bool section_is_early = early_section(ms);
+@@ -784,7 +784,7 @@ static void section_deactivate(unsigned
+ * section_activate() and pfn_valid() .
+ */
+ if (!section_is_early)
+- depopulate_section_memmap(pfn, nr_pages, altmap);
++ depopulate_section_memmap(pfn, nr_pages, altmap, pgmap);
+ else if (memmap)
+ free_map_bootmem(memmap);
+
+@@ -828,7 +828,7 @@ static struct page * __meminit section_a
+
+ memmap = populate_section_memmap(pfn, nr_pages, nid, altmap, pgmap);
+ if (!memmap) {
+- section_deactivate(pfn, nr_pages, altmap);
++ section_deactivate(pfn, nr_pages, altmap, pgmap);
+ return ERR_PTR(-ENOMEM);
+ }
+
+@@ -889,13 +889,13 @@ int __meminit sparse_add_section(int nid
+ }
+
+ void sparse_remove_section(unsigned long pfn, unsigned long nr_pages,
+- struct vmem_altmap *altmap)
++ struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+ {
+ struct mem_section *ms = __pfn_to_section(pfn);
+
+ if (WARN_ON_ONCE(!valid_section(ms)))
+ return;
+
+- section_deactivate(pfn, nr_pages, altmap);
++ section_deactivate(pfn, nr_pages, altmap, pgmap);
+ }
+ #endif /* CONFIG_MEMORY_HOTPLUG */
--- /dev/null
+From stable+bounces-289855-greg=kroah.com@vger.kernel.org Tue Jul 28 05:51:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 23:44:39 -0400
+Subject: net: mana: Optimize irq affinity for low vcpu configs
+To: stable@vger.kernel.org
+Cc: Shradha Gupta <shradhagupta@linux.microsoft.com>, Erni Sri Satya Vennela <ernis@linux.microsoft.com>, Haiyang Zhang <haiyangz@microsoft.com>, Simon Horman <horms@kernel.org>, Yury Norov <ynorov@nvidia.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728034439.3162982-1-sashal@kernel.org>
+
+From: Shradha Gupta <shradhagupta@linux.microsoft.com>
+
+[ Upstream commit 5316394b1752f6cf3f9901e7fefdec1cd1d97fd3 ]
+
+Before the commit 755391121038 ("net: mana: Allocate MSI-X vectors
+dynamically"), all the MANA IRQs were assigned statically and together
+during early driver load.
+
+After this commit, the IRQ allocation for MANA was done in two phases.
+HWC IRQ allocated earlier and then, queue IRQs dynamically added at a
+later point. By this time, the IRQ weights on vCPUs can become imbalanced
+and if IRQ count is greater than the vCPU count the topology aware IRQ
+distribution logic in MANA can cause multiple MANA IRQs to land on the
+same vCPUs, while other sibling vCPUs have none (case 1).
+
+On SMP enabled, low-vCPU systems, this becomes a bigger problem as the
+softIRQ handling overhead of two IRQs on the same vCPUs becomes much more
+than their overheads if they were spread across sibling vCPUs.
+
+In such cases when many parallel TCP connections are tested, the
+throughput drops significantly.
+
+Fix the affinity assignment logic, in cases where the IRQ count is greater
+than the vCPU count and when IRQs are added dynamically, by utilizing all
+the vCPUs irrespective of their NUMA/core bindings (case 2).
+
+The results of setting the affinity and hint to NULL were also studied,
+and we observed that, with this logic if there are pre-existing IRQs
+allocated on the VM (apart from MANA), during MANA IRQs allocation, it
+leads to clustering of the MANA queue IRQs again (case 3).
+
+=======================================================
+Case 1: without this patch
+=======================================================
+4 vcpu(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
+
+ TYPE effective vCPU aff
+=======================================================
+IRQ0: HWC 0
+IRQ1: mana_q1 0
+IRQ2: mana_q2 2
+IRQ3: mana_q3 0
+IRQ4: mana_q4 3
+
+%soft on each vCPU(mpstat -P ALL 1) on receiver
+vCPU 0 1 2 3
+=======================================================
+pass 1: 38.85 0.03 24.89 24.65
+pass 2: 39.15 0.03 24.57 25.28
+pass 3: 40.36 0.03 23.20 23.17
+
+=======================================================
+Case 2: with this patch
+=======================================================
+4 vcpu(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
+
+ TYPE effective vCPU aff
+=======================================================
+IRQ0: HWC 0
+IRQ1: mana_q1 0
+IRQ2: mana_q2 1
+IRQ3: mana_q3 2
+IRQ4: mana_q4 3
+
+%soft on each vCPU(mpstat -P ALL 1) on receiver
+vCPU 0 1 2 3
+=======================================================
+pass 1: 15.42 15.85 14.99 14.51
+pass 2: 15.53 15.94 15.81 15.93
+pass 3: 16.41 16.35 16.40 16.36
+
+=======================================================
+Case 3: with affinity set to NULL
+=======================================================
+4 vCPU(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
+
+ TYPE effective vCPU aff
+=======================================================
+IRQ0: HWC 0
+IRQ1: mana_q1 2
+IRQ2: mana_q2 3
+IRQ3: mana_q3 2
+IRQ4: mana_q4 3
+
+=======================================================
+Throughput Impact(in Gbps, same env)
+=======================================================
+TCP conn with patch w/o patch aff NULL
+20480 15.65 7.73 5.25
+10240 15.63 8.93 5.77
+8192 15.64 9.69 7.16
+6144 15.64 13.16 9.33
+4096 15.69 15.75 13.50
+2048 15.69 15.83 13.61
+1024 15.71 15.28 13.60
+
+Fixes: 755391121038 ("net: mana: Allocate MSI-X vectors dynamically")
+Cc: stable@vger.kernel.org
+Co-developed-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
+Signed-off-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
+Signed-off-by: Shradha Gupta <shradhagupta@linux.microsoft.com>
+Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Reviewed-by: Yury Norov <ynorov@nvidia.com>
+Link: https://patch.msgid.link/20260624072138.1632849-1-shradhagupta@linux.microsoft.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+[ Kept 7.1's `int *irqs, irq, err, i;` declaration (minus the deleted `skip_first_cpu`) instead of upstream's `msi` variant, as this tree lacks the `mana_gd_get_gic()` refactor. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/microsoft/mana/gdma_main.c | 78 +++++++++++++++++++-----
+ 1 file changed, 64 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
+@@ -197,6 +197,8 @@ static int mana_gd_query_max_resources(s
+ } else {
+ /* If dynamic allocation is enabled we have already allocated
+ * hwc msi
++ * Also, we make sure in this case the following is always true
++ * (num_msix_usable - 1 HWC) <= num_online_cpus()
+ */
+ gc->num_msix_usable = min(resp.max_msix, num_online_cpus() + 1);
+ }
+@@ -1667,8 +1669,8 @@ void mana_gd_free_res_map(struct gdma_re
+ * do the same thing.
+ */
+
+-static int irq_setup(unsigned int *irqs, unsigned int len, int node,
+- bool skip_first_cpu)
++static int mana_irq_setup_numa_aware(unsigned int *irqs, unsigned int len,
++ int node, bool skip_first_cpu)
+ {
+ const struct cpumask *next, *prev = cpu_none_mask;
+ cpumask_var_t cpus __free(free_cpumask_var);
+@@ -1704,11 +1706,24 @@ done:
+ return 0;
+ }
+
++/* must be called with cpus_read_lock() held */
++static void mana_irq_setup_linear(unsigned int *irqs, unsigned int len)
++{
++ int cpu;
++
++ for_each_online_cpu(cpu) {
++ if (len == 0)
++ break;
++
++ irq_set_affinity_and_hint(*irqs++, cpumask_of(cpu));
++ len--;
++ }
++}
++
+ static int mana_gd_setup_dyn_irqs(struct pci_dev *pdev, int nvec)
+ {
+ struct gdma_context *gc = pci_get_drvdata(pdev);
+ struct gdma_irq_context *gic;
+- bool skip_first_cpu = false;
+ int *irqs, irq, err, i;
+
+ irqs = kmalloc_objs(int, nvec);
+@@ -1716,10 +1731,12 @@ static int mana_gd_setup_dyn_irqs(struct
+ return -ENOMEM;
+
+ /*
++ * In this function, num_msix_usable = HWC IRQ + Queue IRQ.
++ * nvec is only Queue IRQ (HWC already setup).
+ * While processing the next pci irq vector, we start with index 1,
+ * as IRQ vector at index 0 is already processed for HWC.
+ * However, the population of irqs array starts with index 0, to be
+- * further used in irq_setup()
++ * further used in mana_irq_setup_numa_aware()
+ */
+ for (i = 1; i <= nvec; i++) {
+ gic = kzalloc_obj(*gic);
+@@ -1749,18 +1766,51 @@ static int mana_gd_setup_dyn_irqs(struct
+ }
+
+ /*
+- * When calling irq_setup() for dynamically added IRQs, if number of
+- * CPUs is more than or equal to allocated MSI-X, we need to skip the
+- * first CPU sibling group since they are already affinitized to HWC IRQ
++ * When calling mana_irq_setup_numa_aware() for dynamically added IRQs,
++ * if number of CPUs is more than or equal to allocated MSI-X, we need to
++ * skip the first CPU sibling group since they are already affinitized to
++ * HWC IRQ
+ */
+ cpus_read_lock();
+- if (gc->num_msix_usable <= num_online_cpus())
+- skip_first_cpu = true;
++ if (gc->num_msix_usable <= num_online_cpus()) {
++ err = mana_irq_setup_numa_aware(irqs, nvec, gc->numa_node,
++ true);
++ if (err) {
++ cpus_read_unlock();
++ goto free_irq;
++ }
++ } else {
++ /*
++ * When num_msix_usable are more than num_online_cpus, our
++ * queue IRQs should be equal to num of online vCPUs.
++ * We try to make sure queue IRQs spread across all vCPUs.
++ * In such a case NUMA or CPU core affinity does not matter.
++ * Note: in this case the total mana IRQ should always be
++ * num_online_cpus + 1. The first HWC IRQ is already handled
++ * in HWC setup calls
++ * However, if CPUs went offline since num_msix_usable was
++ * computed, queue IRQs will be more than num_online_cpus().
++ * In such cases remaining extra IRQs will retain their default
++ * affinity.
++ */
++ int first_unassigned = num_online_cpus();
+
+- err = irq_setup(irqs, nvec, gc->numa_node, skip_first_cpu);
+- if (err) {
+- cpus_read_unlock();
+- goto free_irq;
++ if (nvec > first_unassigned) {
++ char buf[32];
++
++ if (first_unassigned == nvec - 1)
++ snprintf(buf, sizeof(buf), "%d",
++ first_unassigned);
++ else
++ snprintf(buf, sizeof(buf), "%d-%d",
++ first_unassigned, nvec - 1);
++
++ dev_dbg(&pdev->dev,
++ "MANA IRQ indices #%s will retain the default CPU affinity\n",
++ buf);
++ }
++
++ mana_irq_setup_linear(irqs, nvec);
+ }
+
+ cpus_read_unlock();
+@@ -1846,7 +1896,7 @@ static int mana_gd_setup_irqs(struct pci
+ nvec -= 1;
+ }
+
+- err = irq_setup(irqs, nvec, gc->numa_node, false);
++ err = mana_irq_setup_numa_aware(irqs, nvec, gc->numa_node, false);
+ if (err) {
+ cpus_read_unlock();
+ goto free_irq;
--- /dev/null
+From stable+bounces-290832-greg=kroah.com@vger.kernel.org Wed Jul 29 18:35:11 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 12:24:32 -0400
+Subject: sched_ext: Move shared helpers from ext.c into internal.h and cid.h
+To: stable@vger.kernel.org
+Cc: Tejun Heo <tj@kernel.org>, Peter Zijlstra <peterz@infradead.org>, Andrea Righi <arighi@nvidia.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729162433.3523095-1-sashal@kernel.org>
+
+From: Tejun Heo <tj@kernel.org>
+
+[ Upstream commit 4437ad129cf5b37c00a5bc9fa5989d1da4d64d07 ]
+
+idle.c and cid.c are included into build_policy.c together with ext.c and
+use helpers that ext.c defines. Because the helpers live in ext.c, the two
+files can not parse as standalone units and clangd reports errors in them.
+
+Move the helpers to the headers they belong to. The op-dispatch macros and
+helpers plus scx_parent() to internal.h, and scx_cpu_arg()/scx_cpu_ret() to
+cid.h. No functional change. idle.c and cid.c now parse clean standalone.
+
+Suggested-by: Peter Zijlstra <peterz@infradead.org>
+Signed-off-by: Tejun Heo <tj@kernel.org>
+Reviewed-by: Andrea Righi <arighi@nvidia.com>
+Stable-dep-of: 18d62044cda7 ("sched_ext: Preserve rq tracking across local DSQ dispatch")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/sched/ext.c | 29 -----------------------------
+ kernel/sched/ext_internal.h | 30 ++++++++++++++++++++++++++++++
+ 2 files changed, 30 insertions(+), 29 deletions(-)
+
+--- a/kernel/sched/ext.c
++++ b/kernel/sched/ext.c
+@@ -218,8 +218,6 @@ static __printf(4, 5) bool scx_exit(stru
+ #define scx_error(sch, fmt, args...) scx_exit((sch), SCX_EXIT_ERROR, 0, fmt, ##args)
+ #define scx_verror(sch, fmt, args) scx_vexit((sch), SCX_EXIT_ERROR, 0, fmt, args)
+
+-#define SCX_HAS_OP(sch, op) test_bit(SCX_OP_IDX(op), (sch)->has_op)
+-
+ static long jiffies_delta_msecs(unsigned long at, unsigned long now)
+ {
+ if (time_after(at, now))
+@@ -235,20 +233,6 @@ static bool u32_before(u32 a, u32 b)
+
+ #ifdef CONFIG_EXT_SUB_SCHED
+ /**
+- * scx_parent - Find the parent sched
+- * @sch: sched to find the parent of
+- *
+- * Returns the parent scheduler or %NULL if @sch is root.
+- */
+-static struct scx_sched *scx_parent(struct scx_sched *sch)
+-{
+- if (sch->level)
+- return sch->ancestors[sch->level - 1];
+- else
+- return NULL;
+-}
+-
+-/**
+ * scx_next_descendant_pre - find the next descendant for pre-order walk
+ * @pos: the current position (%NULL to initiate traversal)
+ * @root: sched whose descendants to walk
+@@ -295,7 +279,6 @@ static void scx_set_task_sched(struct ta
+ rcu_assign_pointer(p->scx.sched, sch);
+ }
+ #else /* CONFIG_EXT_SUB_SCHED */
+-static struct scx_sched *scx_parent(struct scx_sched *sch) { return NULL; }
+ static struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root) { return pos ? NULL : root; }
+ static void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch) {}
+ #endif /* CONFIG_EXT_SUB_SCHED */
+@@ -455,18 +438,6 @@ static bool rq_is_open(struct rq *rq, u6
+ */
+ DEFINE_PER_CPU(struct rq *, scx_locked_rq_state);
+
+-static inline void update_locked_rq(struct rq *rq)
+-{
+- /*
+- * Check whether @rq is actually locked. This can help expose bugs
+- * or incorrect assumptions about the context in which a kfunc or
+- * callback is executed.
+- */
+- if (rq)
+- lockdep_assert_rq_held(rq);
+- __this_cpu_write(scx_locked_rq_state, rq);
+-}
+-
+ /*
+ * SCX ops can recurse via scx_bpf_sub_dispatch() - the inner call must not
+ * clobber the outer's scx_locked_rq_state. Save it on entry, restore on exit.
+--- a/kernel/sched/ext_internal.h
++++ b/kernel/sched/ext_internal.h
+@@ -1377,6 +1377,20 @@ static inline struct rq *scx_locked_rq(v
+ return __this_cpu_read(scx_locked_rq_state);
+ }
+
++static inline void update_locked_rq(struct rq *rq)
++{
++ /*
++ * Check whether @rq is actually locked. This can help expose bugs
++ * or incorrect assumptions about the context in which a kfunc or
++ * callback is executed.
++ */
++ if (rq)
++ lockdep_assert_rq_held(rq);
++ __this_cpu_write(scx_locked_rq_state, rq);
++}
++
++#define SCX_HAS_OP(sch, op) test_bit(SCX_OP_IDX(op), (sch)->has_op)
++
+ static inline bool scx_bypassing(struct scx_sched *sch, s32 cpu)
+ {
+ return unlikely(per_cpu_ptr(sch->pcpu, cpu)->flags &
+@@ -1457,6 +1471,20 @@ static inline struct scx_sched *scx_prog
+
+ return NULL;
+ }
++
++/**
++ * scx_parent - Find the parent sched
++ * @sch: sched to find the parent of
++ *
++ * Returns the parent scheduler or %NULL if @sch is root.
++ */
++static inline struct scx_sched *scx_parent(struct scx_sched *sch)
++{
++ if (sch->level)
++ return sch->ancestors[sch->level - 1];
++ else
++ return NULL;
++}
+ #else /* CONFIG_EXT_SUB_SCHED */
+ static inline struct scx_sched *scx_task_sched(const struct task_struct *p)
+ {
+@@ -1480,4 +1508,6 @@ static struct scx_sched *scx_prog_sched(
+ {
+ return rcu_dereference_all(scx_root);
+ }
++
++static inline struct scx_sched *scx_parent(struct scx_sched *sch) { return NULL; }
+ #endif /* CONFIG_EXT_SUB_SCHED */
--- /dev/null
+From stable+bounces-290831-greg=kroah.com@vger.kernel.org Wed Jul 29 18:35:11 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 12:24:33 -0400
+Subject: sched_ext: Preserve rq tracking across local DSQ dispatch
+To: stable@vger.kernel.org
+Cc: Andrea Righi <arighi@nvidia.com>, Tejun Heo <tj@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729162433.3523095-2-sashal@kernel.org>
+
+From: Andrea Righi <arighi@nvidia.com>
+
+[ Upstream commit 18d62044cda7a2b40f59d910659c0b0d6accad37 ]
+
+dispatch_to_local_dsq() can run from scx_bpf_dsq_move_to_local() while
+ops.dispatch() has recorded the current rq. Moving a task to a local DSQ
+may switch to the source or destination rq before synchronously invoking
+ops.dequeue() through the following path:
+
+ SCX_CALL_OP(dispatch, rq)
+ ops.dispatch()
+ scx_bpf_dsq_move_to_local()
+ scx_flush_dispatch_buf()
+ finish_dispatch()
+ dispatch_to_local_dsq()
+ scx_dispatch_enqueue()
+ local_dsq_post_enq()
+ call_task_dequeue()
+ SCX_CALL_OP_TASK(dequeue, locked_rq, ...)
+
+The nested callback saves the recorded rq and restores it on return. If
+the rq tracking does not follow the lock switch, update_locked_rq() can
+trigger the following lockdep assertion while restoring an rq which is
+no longer held:
+
+ WARNING: kernel/sched/sched.h:1641 at call_task_dequeue+0x160/0x170
+ Call Trace:
+ scx_dispatch_enqueue+0x2b0/0x460
+ dispatch_to_local_dsq+0x138/0x230
+ scx_flush_dispatch_buf+0x1af/0x220
+ scx_bpf_dsq_move_to_local___v2+0xe2/0x1c0
+ bpf__sched_ext_ops_dispatch+0x4b/0xa7
+ do_pick_task_scx+0x3b6/0x910
+ __pick_next_task+0x105/0x1f0
+ __schedule+0x3e7/0x1980
+
+Introduce switch_rq_lock() to update the tracking state together with
+each rq lock handoff. Use it in dispatch_to_local_dsq(),
+move_remote_task_to_local_dsq() and the in-balance paths of
+scx_dsq_move(), ensuring that scx_locked_rq() consistently refers to the
+rq whose lock is actually held throughout the lock dance.
+
+Fixes: 7fb39e4eb4c3 ("sched_ext: Save and restore scx_locked_rq across SCX_CALL_OP")
+Cc: stable@vger.kernel.org # 7.1+
+Signed-off-by: Andrea Righi <arighi@nvidia.com>
+Signed-off-by: Tejun Heo <tj@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/sched/ext.c | 36 ++++++++++++++++++++----------------
+ 1 file changed, 20 insertions(+), 16 deletions(-)
+
+--- a/kernel/sched/ext.c
++++ b/kernel/sched/ext.c
+@@ -438,6 +438,18 @@ static bool rq_is_open(struct rq *rq, u6
+ */
+ DEFINE_PER_CPU(struct rq *, scx_locked_rq_state);
+
++static void switch_rq_lock(struct rq *from, struct rq *to)
++{
++ bool tracked = scx_locked_rq() == from;
++
++ if (tracked)
++ update_locked_rq(NULL);
++ raw_spin_rq_unlock(from);
++ raw_spin_rq_lock(to);
++ if (tracked)
++ update_locked_rq(to);
++}
++
+ /*
+ * SCX ops can recurse via scx_bpf_sub_dispatch() - the inner call must not
+ * clobber the outer's scx_locked_rq_state. Save it on entry, restore on exit.
+@@ -2262,8 +2274,7 @@ static void move_remote_task_to_local_ds
+ deactivate_task(src_rq, p, 0);
+ set_task_cpu(p, cpu_of(dst_rq));
+
+- raw_spin_rq_unlock(src_rq);
+- raw_spin_rq_lock(dst_rq);
++ switch_rq_lock(src_rq, dst_rq);
+
+ /*
+ * We want to pass scx-specific enq_flags but activate_task() will
+@@ -2594,9 +2605,8 @@ static void dispatch_to_local_dsq(struct
+
+ /* switch to @src_rq lock */
+ if (locked_rq != src_rq) {
+- raw_spin_rq_unlock(locked_rq);
++ switch_rq_lock(locked_rq, src_rq);
+ locked_rq = src_rq;
+- raw_spin_rq_lock(src_rq);
+ }
+
+ /* task_rq couldn't have changed if we're still the holding cpu */
+@@ -2624,10 +2634,8 @@ static void dispatch_to_local_dsq(struct
+ }
+
+ /* switch back to @rq lock */
+- if (locked_rq != rq) {
+- raw_spin_rq_unlock(locked_rq);
+- raw_spin_rq_lock(rq);
+- }
++ if (locked_rq != rq)
++ switch_rq_lock(locked_rq, rq);
+ }
+
+ /**
+@@ -8405,10 +8413,8 @@ static bool scx_dsq_move(struct bpf_iter
+ in_balance = this_rq->scx.flags & SCX_RQ_IN_BALANCE;
+
+ if (in_balance) {
+- if (this_rq != src_rq) {
+- raw_spin_rq_unlock(this_rq);
+- raw_spin_rq_lock(src_rq);
+- }
++ if (this_rq != src_rq)
++ switch_rq_lock(this_rq, src_rq);
+ } else {
+ raw_spin_rq_lock(src_rq);
+ }
+@@ -8440,10 +8446,8 @@ static bool scx_dsq_move(struct bpf_iter
+ dispatched = true;
+ out:
+ if (in_balance) {
+- if (this_rq != locked_rq) {
+- raw_spin_rq_unlock(locked_rq);
+- raw_spin_rq_lock(this_rq);
+- }
++ if (this_rq != locked_rq)
++ switch_rq_lock(locked_rq, this_rq);
+ } else {
+ raw_spin_rq_unlock_irqrestore(locked_rq, flags);
+ }
ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch
ksmbd-validate-ace-size-against-sid-sub-authorities.patch
drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch
+audit-use-unsigned-int-instead-of-unsigned.patch
+audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch
+fuse-uring-fix-race-between-registration-and-connection-abortion.patch
+xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch
+sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch
+sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch
+mm-sparse-vmemmap-pass-pgmap-argument-to-memory-deactivation-paths.patch
+mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch
+thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch
+thunderbolt-remove-service-debugfs-entries-during-unregister.patch
+thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch
+thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch
+net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch
+bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch
+bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch
+sched_ext-move-shared-helpers-from-ext.c-into-internal.h-and-cid.h.patch
+sched_ext-preserve-rq-tracking-across-local-dsq-dispatch.patch
--- /dev/null
+From stable+bounces-288461-greg=kroah.com@vger.kernel.org Thu Jul 23 19:47:54 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 13:35:36 -0400
+Subject: SUNRPC: Add helpers to convert xdr_buf byte ranges to scatterlists
+To: stable@vger.kernel.org
+Cc: Chuck Lever <chuck.lever@oracle.com>, Jeff Layton <jlayton@kernel.org>, Anna Schumaker <anna.schumaker@hammerspace.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723173538.3416792-1-sashal@kernel.org>
+
+From: Chuck Lever <chuck.lever@oracle.com>
+
+[ Upstream commit e9be933959b581effd426f93b86654f5fbf0c574 ]
+
+The crypto/krb5 library accepts data in scatterlist form, but
+the GSS-API layer presents RPC payloads as struct xdr_buf.
+Bridge that gap with a pair of helper functions:
+
+ xdr_buf_to_sg() - populate a caller-supplied scatterlist
+ array from a byte range
+ xdr_buf_to_sg_alloc() - populate a caller-supplied inline
+ scatterlist, chaining to a heap-
+ allocated overflow for large payloads
+
+The inline array (typically stack-allocated at eight entries)
+covers the common case of small RPCs with no heap allocation
+on the encrypt/decrypt path. Only buffers spanning many pages
+incur a kmalloc for the chained extension.
+
+The segment-walking logic follows the same head, page array,
+tail traversal as xdr_process_buf(), but populates a
+scatterlist directly rather than invoking a per-segment
+callback. sg_next() traversal makes the walker safe for
+chained scatterlists. Once subsequent patches reroute all
+per-message crypto operations through crypto/krb5,
+xdr_process_buf() loses its last callers and is removed.
+
+Assisted-by: Claude:claude-opus-4-6
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/sunrpc/xdr.h | 15 +++
+ net/sunrpc/xdr.c | 199 +++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 214 insertions(+)
+
+--- a/include/linux/sunrpc/xdr.h
++++ b/include/linux/sunrpc/xdr.h
+@@ -140,6 +140,21 @@ int xdr_alloc_bvec(struct xdr_buf *buf,
+ void xdr_free_bvec(struct xdr_buf *buf);
+ unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
+ const struct xdr_buf *xdr);
++int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
++ unsigned int len, struct scatterlist *sg, unsigned int nsg);
++int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
++ unsigned int len, struct scatterlist *sg_head,
++ unsigned int sg_head_nents,
++ struct scatterlist **sg_overflow, gfp_t gfp);
++
++/*
++ * Inline scatterlist entries for xdr_buf_to_sg_alloc(). Sized to cover the
++ * head kvec, tail kvec, and a few page fragments without any heap allocation.
++ */
++enum {
++ XDR_BUF_TO_SG_NENTS = 8,
++};
++
+
+ static inline __be32 *xdr_encode_array(__be32 *p, const void *s, unsigned int len)
+ {
+--- a/net/sunrpc/xdr.c
++++ b/net/sunrpc/xdr.c
+@@ -192,6 +192,205 @@ bvec_overflow:
+ EXPORT_SYMBOL_GPL(xdr_buf_to_bvec);
+
+ /**
++ * xdr_buf_to_sg - Populate a scatterlist from an xdr_buf range
++ * @buf: xdr_buf to map
++ * @offset: starting byte offset within @buf
++ * @len: number of bytes to cover
++ * @sg: scatterlist array initialized with sg_init_table()
++ * @nsg: number of entries available in @sg
++ *
++ * @sg is traversed with sg_next(), so callers may pass a list
++ * assembled with sg_chain().
++ *
++ * Return: on success, the number of scatterlist entries used; the
++ * last used entry is marked with sg_mark_end(). On failure, a
++ * negative errno.
++ */
++int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
++ unsigned int len, struct scatterlist *sg, unsigned int nsg)
++{
++ unsigned int page_len, thislen, page_offset;
++ struct scatterlist *cur = sg, *prev = NULL;
++ int nents = 0;
++ int i;
++
++ if (len == 0)
++ return 0;
++
++ if (offset >= buf->head[0].iov_len) {
++ offset -= buf->head[0].iov_len;
++ } else {
++ thislen = min_t(unsigned int,
++ buf->head[0].iov_len - offset, len);
++ if (nents >= nsg)
++ return -ENOSPC;
++ sg_set_buf(cur, buf->head[0].iov_base + offset,
++ thislen);
++ prev = cur;
++ cur = sg_next(cur);
++ nents++;
++ len -= thislen;
++ offset = 0;
++ }
++ if (len == 0)
++ goto done;
++
++ if (offset >= buf->page_len) {
++ offset -= buf->page_len;
++ } else {
++ page_len = min(buf->page_len - offset, len);
++ len -= page_len;
++ page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1);
++ i = (offset + buf->page_base) >> PAGE_SHIFT;
++ thislen = PAGE_SIZE - page_offset;
++ do {
++ if (thislen > page_len)
++ thislen = page_len;
++ if (nents >= nsg)
++ return -ENOSPC;
++ sg_set_page(cur, buf->pages[i],
++ thislen, page_offset);
++ prev = cur;
++ cur = sg_next(cur);
++ nents++;
++ page_len -= thislen;
++ i++;
++ page_offset = 0;
++ thislen = PAGE_SIZE;
++ } while (page_len != 0);
++ offset = 0;
++ }
++ if (len == 0)
++ goto done;
++
++ if (offset < buf->tail[0].iov_len) {
++ thislen = min_t(unsigned int,
++ buf->tail[0].iov_len - offset, len);
++ if (nents >= nsg)
++ return -ENOSPC;
++ sg_set_buf(cur, buf->tail[0].iov_base + offset,
++ thislen);
++ prev = cur;
++ nents++;
++ len -= thislen;
++ }
++ if (len != 0)
++ return -EINVAL;
++
++done:
++ if (prev)
++ sg_mark_end(prev);
++ return nents;
++}
++EXPORT_SYMBOL_GPL(xdr_buf_to_sg);
++
++/*
++ * Count the scatterlist entries needed to cover [offset, offset + len)
++ * within @buf. Mirrors the walk in xdr_buf_to_sg() so the caller can
++ * size an allocation that matches the requested sub-range rather than
++ * the full xdr_buf.
++ */
++static unsigned int xdr_buf_sg_nents(const struct xdr_buf *buf,
++ unsigned int offset, unsigned int len)
++{
++ unsigned int nsg = 0, thislen, page_offset;
++
++ if (len == 0)
++ return 0;
++
++ if (offset < buf->head[0].iov_len) {
++ thislen = min_t(unsigned int,
++ buf->head[0].iov_len - offset, len);
++ nsg++;
++ len -= thislen;
++ offset = 0;
++ } else {
++ offset -= buf->head[0].iov_len;
++ }
++ if (len == 0)
++ return nsg;
++
++ if (offset < buf->page_len) {
++ thislen = min(buf->page_len - offset, len);
++ page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1);
++ nsg += DIV_ROUND_UP(page_offset + thislen, PAGE_SIZE);
++ len -= thislen;
++ offset = 0;
++ } else {
++ offset -= buf->page_len;
++ }
++ if (len == 0)
++ return nsg;
++
++ if (offset < buf->tail[0].iov_len)
++ nsg++;
++ return nsg;
++}
++
++/**
++ * xdr_buf_to_sg_alloc - Populate a scatterlist for an xdr_buf range
++ * @buf: xdr_buf to map
++ * @offset: starting byte offset within @buf
++ * @len: number of bytes to cover
++ * @sg_head: caller-provided scatterlist array (typically stack-allocated)
++ * @sg_head_nents: number of entries in @sg_head
++ * @sg_overflow: OUT: chained extension, or NULL when @sg_head sufficed
++ * @gfp: memory allocation flags for overflow
++ *
++ * Populates @sg_head directly when the xdr_buf fits. When more
++ * entries are needed, an overflow scatterlist is allocated and
++ * chained from @sg_head so that the result is traversable with
++ * sg_next().
++ *
++ * Return: on success, the number of populated scatterlist entries
++ * (counting only data entries, not chain entries). @sg_head is
++ * the head of the resulting list. Caller must kfree @sg_overflow
++ * when done. On failure, a negative errno.
++ */
++int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
++ unsigned int len, struct scatterlist *sg_head,
++ unsigned int sg_head_nents,
++ struct scatterlist **sg_overflow, gfp_t gfp)
++{
++ unsigned int nsg;
++ int ret;
++
++ *sg_overflow = NULL;
++ if (len == 0)
++ return 0;
++
++ nsg = xdr_buf_sg_nents(buf, offset, len);
++ if (nsg == 0)
++ return -EINVAL;
++
++ if (nsg <= sg_head_nents) {
++ sg_init_table(sg_head, nsg);
++ } else {
++ /* +1 replaces the slot sg_chain() consumes as the link. */
++ unsigned int overflow_nents = nsg - sg_head_nents + 1;
++ struct scatterlist *overflow;
++
++ overflow = kmalloc_array(overflow_nents, sizeof(*overflow),
++ gfp);
++ if (!overflow)
++ return -ENOMEM;
++
++ sg_init_table(sg_head, sg_head_nents);
++ sg_init_table(overflow, overflow_nents);
++ sg_chain(sg_head, sg_head_nents, overflow);
++ *sg_overflow = overflow;
++ }
++
++ ret = xdr_buf_to_sg(buf, offset, len, sg_head, nsg);
++ if (ret < 0) {
++ kfree(*sg_overflow);
++ *sg_overflow = NULL;
++ }
++ return ret;
++}
++EXPORT_SYMBOL_GPL(xdr_buf_to_sg_alloc);
++
++/**
+ * xdr_inline_pages - Prepare receive buffer for a large reply
+ * @xdr: xdr_buf into which reply will be placed
+ * @offset: expected offset where data payload will start, in bytes
--- /dev/null
+From stable+bounces-288463-greg=kroah.com@vger.kernel.org Thu Jul 23 19:37:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 13:35:38 -0400
+Subject: SUNRPC: Return an error from xdr_buf_to_bvec() on overflow
+To: stable@vger.kernel.org
+Cc: Chuck Lever <chuck.lever@oracle.com>, Chris Mason <clm@meta.com>, Jeff Layton <jlayton@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723173538.3416792-3-sashal@kernel.org>
+
+From: Chuck Lever <chuck.lever@oracle.com>
+
+[ Upstream commit 18c1cc69886192e33536498289d26dba6894e3d5 ]
+
+xdr_buf_to_bvec() returns a slot count even when the caller's bvec
+budget is exhausted partway through the xdr_buf. Callers feed that
+count into iov_iter_bvec() and continue as if the conversion had
+succeeded, silently sending or writing fewer bytes than the data
+length declares. For an NFS WRITE the server reports the truncated
+transfer to the client as full success.
+
+The overflow represents an internal invariant violation: a higher
+layer reserved a bvec budget too small for the xdr_buf it then
+asked the encoder to convert. That is a server-side fault, not a
+media I/O failure and not a malformed client argument.
+
+Change xdr_buf_to_bvec() to return a signed int and have the
+overflow label return -ESERVERFAULT. Update the three callers to
+detect the negative return and fail the request: nfsd_vfs_write()
+folds the error into host_err, which nfserrno() translates to
+nfserr_serverfault for the WRITE reply; svc_udp_sendto() and
+svc_tcp_sendmsg() propagate the error out of the send path.
+
+Reported-by: Chris Mason <clm@meta.com>
+Fixes: 2eb2b9358181 ("SUNRPC: Convert svc_tcp_sendmsg to use bio_vecs directly")
+Cc: stable@vger.kernel.org
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/nfsd/vfs.c | 6 +++++-
+ include/linux/sunrpc/xdr.h | 4 ++--
+ net/sunrpc/svcsock.c | 14 ++++++++++++--
+ net/sunrpc/xdr.c | 11 ++++++-----
+ 4 files changed, 25 insertions(+), 10 deletions(-)
+
+--- a/fs/nfsd/vfs.c
++++ b/fs/nfsd/vfs.c
+@@ -1439,7 +1439,7 @@ nfsd_vfs_write(struct svc_rqst *rqstp, s
+ unsigned long exp_op_flags = 0;
+ unsigned int pflags = current->flags;
+ bool restore_flags = false;
+- unsigned int nvecs;
++ int nvecs;
+
+ trace_nfsd_write_opened(rqstp, fhp, offset, *cnt);
+
+@@ -1479,6 +1479,10 @@ nfsd_vfs_write(struct svc_rqst *rqstp, s
+ }
+
+ nvecs = xdr_buf_to_bvec(rqstp->rq_bvec, rqstp->rq_maxpages, payload);
++ if (nvecs < 0) {
++ host_err = nvecs;
++ goto out_nfserr;
++ }
+
+ since = READ_ONCE(file->f_wb_err);
+ if (verf)
+--- a/include/linux/sunrpc/xdr.h
++++ b/include/linux/sunrpc/xdr.h
+@@ -138,8 +138,8 @@ void xdr_terminate_string(const struct x
+ size_t xdr_buf_pagecount(const struct xdr_buf *buf);
+ int xdr_alloc_bvec(struct xdr_buf *buf, gfp_t gfp);
+ void xdr_free_bvec(struct xdr_buf *buf);
+-unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
+- const struct xdr_buf *xdr);
++int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
++ const struct xdr_buf *xdr);
+ int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
+ unsigned int len, struct scatterlist *sg, unsigned int nsg);
+ int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
+--- a/net/sunrpc/svcsock.c
++++ b/net/sunrpc/svcsock.c
+@@ -742,7 +742,7 @@ static int svc_udp_sendto(struct svc_rqs
+ .msg_flags = MSG_SPLICE_PAGES,
+ .msg_controllen = sizeof(buffer),
+ };
+- unsigned int count;
++ int count;
+ int err;
+
+ svc_udp_release_ctxt(xprt, rqstp->rq_xprt_ctxt);
+@@ -756,6 +756,10 @@ static int svc_udp_sendto(struct svc_rqs
+ goto out_notconn;
+
+ count = xdr_buf_to_bvec(svsk->sk_bvec, SUNRPC_MAX_UDP_SENDPAGES, xdr);
++ if (count < 0) {
++ err = count;
++ goto out_trace;
++ }
+
+ iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+ count, rqstp->rq_res.len);
+@@ -767,6 +771,7 @@ static int svc_udp_sendto(struct svc_rqs
+ err = sock_sendmsg(svsk->sk_sock, &msg);
+ }
+
++out_trace:
+ trace_svcsock_udp_send(xprt, err);
+
+ mutex_unlock(&xprt->xpt_mutex);
+@@ -1247,7 +1252,7 @@ static int svc_tcp_sendmsg(struct svc_so
+ struct msghdr msg = {
+ .msg_flags = MSG_SPLICE_PAGES,
+ };
+- unsigned int count;
++ int count;
+ void *buf;
+ int ret;
+
+@@ -1263,10 +1268,15 @@ static int svc_tcp_sendmsg(struct svc_so
+
+ count = xdr_buf_to_bvec(svsk->sk_bvec + 1, rqstp->rq_maxpages,
+ &rqstp->rq_res);
++ if (count < 0) {
++ ret = count;
++ goto out;
++ }
+
+ iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+ 1 + count, sizeof(marker) + rqstp->rq_res.len);
+ ret = sock_sendmsg(svsk->sk_sock, &msg);
++out:
+ page_frag_free(buf);
+ return ret;
+ }
+--- a/net/sunrpc/xdr.c
++++ b/net/sunrpc/xdr.c
+@@ -139,13 +139,14 @@ xdr_free_bvec(struct xdr_buf *buf)
+ /**
+ * xdr_buf_to_bvec - Copy components of an xdr_buf into a bio_vec array
+ * @bvec: bio_vec array to populate
+- * @bvec_size: element count of @bio_vec
++ * @bvec_size: element count of @bvec
+ * @xdr: xdr_buf to be copied
+ *
+- * Returns the number of entries consumed in @bvec.
++ * Returns the number of entries consumed in @bvec on success, or
++ * -ESERVERFAULT when @xdr does not fit within @bvec_size entries.
+ */
+-unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
+- const struct xdr_buf *xdr)
++int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
++ const struct xdr_buf *xdr)
+ {
+ const struct kvec *head = xdr->head;
+ const struct kvec *tail = xdr->tail;
+@@ -187,7 +188,7 @@ unsigned int xdr_buf_to_bvec(struct bio_
+
+ bvec_overflow:
+ pr_warn_once("%s: bio_vec array overflow\n", __func__);
+- return count;
++ return -ESERVERFAULT;
+ }
+ EXPORT_SYMBOL_GPL(xdr_buf_to_bvec);
+
--- /dev/null
+From stable+bounces-289101-greg=kroah.com@vger.kernel.org Sat Jul 25 16:12:32 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 10:12:19 -0400
+Subject: thunderbolt: Keep XDomain reference during the lifetime of a service
+To: stable@vger.kernel.org
+Cc: Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725141222.3814514-1-sashal@kernel.org>
+
+From: Mika Westerberg <mika.westerberg@linux.intel.com>
+
+[ Upstream commit 8b4060998637f06975fceee9b73845d8672d411e ]
+
+This is needed because we release the service ID in tb_service_release()
+and the ID array is owned by the parent XDomain.
+
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/xdomain.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -1012,6 +1012,7 @@ static void tb_service_release(struct de
+ ida_free(&xd->service_ids, svc->id);
+ kfree(svc->key);
+ kfree(svc);
++ tb_xdomain_put(xd);
+ }
+
+ const struct device_type tb_service_type = {
+@@ -1120,7 +1121,7 @@ static void enumerate_services(struct tb
+ svc->id = id;
+ svc->dev.bus = &tb_bus_type;
+ svc->dev.type = &tb_service_type;
+- svc->dev.parent = &xd->dev;
++ svc->dev.parent = get_device(&xd->dev);
+ dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), svc->id);
+
+ tb_service_debugfs_init(svc);
--- /dev/null
+From stable+bounces-289104-greg=kroah.com@vger.kernel.org Sat Jul 25 16:13:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 10:12:22 -0400
+Subject: thunderbolt: Prevent XDomain delayed work use-after-free on disconnect
+To: stable@vger.kernel.org
+Cc: Michael Bommarito <michael.bommarito@gmail.com>, Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725141222.3814514-4-sashal@kernel.org>
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit 2c5d2d3c3f70cde2565d7b279b544893a2035842 ]
+
+tb_xdp_handle_request() runs on system_wq and queues
+xd->state_work via queue_delayed_work() in three request handlers:
+PROPERTIES_CHANGED_REQUEST, UUID_REQUEST (via start_handshake),
+and LINK_STATE_CHANGE_REQUEST. Similarly, update_xdomain() queues
+xd->properties_changed_work when local properties change.
+
+Concurrently, tb_xdomain_remove() calls stop_handshake() which does
+cancel_delayed_work_sync() on both delayed works. Later,
+tb_xdomain_unregister() calls device_unregister() which eventually
+frees the xdomain. Since commit 559c1e1e0134 ("thunderbolt: Run
+tb_xdp_handle_request() in system workqueue") moved the request
+handler off tb->wq, the handler and the remove path are no longer
+serialized. If queue_delayed_work() executes after
+cancel_delayed_work_sync() but before the xdomain is freed, the
+delayed work fires on a freed object.
+
+Add xd->removing that tb_xdomain_remove() sets under xd->lock
+before calling stop_handshake(). Each external queue site holds
+the same lock and checks removing before calling
+queue_delayed_work(). This provides the mutual exclusion needed:
+either the queue site acquires the lock first and queues work that
+the subsequent cancel will see, or the remove path acquires the
+lock first and the queue site observes removing == true and skips
+the queue.
+
+Fixes: 559c1e1e0134 ("thunderbolt: Run tb_xdp_handle_request() in system workqueue")
+Cc: stable@vger.kernel.org
+Assisted-by: Claude:claude-opus-4-7
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/xdomain.c | 26 +++++++++++++-------------
+ 1 file changed, 13 insertions(+), 13 deletions(-)
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -909,6 +909,19 @@ void tb_unregister_service_driver(struct
+ }
+ EXPORT_SYMBOL_GPL(tb_unregister_service_driver);
+
++static int update_xdomain(struct device *dev, void *data)
++{
++ struct tb_xdomain *xd;
++
++ xd = tb_to_xdomain(dev);
++ if (xd) {
++ queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
++ msecs_to_jiffies(50));
++ }
++
++ return 0;
++}
++
+ static ssize_t key_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+ {
+@@ -2500,19 +2513,6 @@ bool tb_xdomain_handle_request(struct tb
+ return ret > 0;
+ }
+
+-static int update_xdomain(struct device *dev, void *data)
+-{
+- struct tb_xdomain *xd;
+-
+- xd = tb_to_xdomain(dev);
+- if (xd) {
+- queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
+- msecs_to_jiffies(50));
+- }
+-
+- return 0;
+-}
+-
+ static void update_all_xdomains(void)
+ {
+ bus_for_each_dev(&tb_bus_type, NULL, NULL, update_xdomain);
--- /dev/null
+From stable+bounces-289102-greg=kroah.com@vger.kernel.org Sat Jul 25 16:12:32 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 10:12:20 -0400
+Subject: thunderbolt: Remove service debugfs entries during unregister
+To: stable@vger.kernel.org
+Cc: Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725141222.3814514-2-sashal@kernel.org>
+
+From: Mika Westerberg <mika.westerberg@linux.intel.com>
+
+[ Upstream commit 4d5fc3f4068568dfcb8cbe2852b4adc56394aa26 ]
+
+We add them as part of the register path so to keep it symmetric remove
+them as part of the unregister path. This also removes them even if the
+service itself is not yet released (but is unregistered), thus allowing
+new register with the same service name to happen.
+
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/xdomain.c | 14 +++++++++++---
+ 1 file changed, 11 insertions(+), 3 deletions(-)
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -1008,7 +1008,6 @@ static void tb_service_release(struct de
+ struct tb_service *svc = container_of(dev, struct tb_service, dev);
+ struct tb_xdomain *xd = tb_service_parent(svc);
+
+- tb_service_debugfs_remove(svc);
+ ida_free(&xd->service_ids, svc->id);
+ kfree(svc->key);
+ kfree(svc);
+@@ -1023,6 +1022,14 @@ const struct device_type tb_service_type
+ };
+ EXPORT_SYMBOL_GPL(tb_service_type);
+
++static void __unregister_service(struct device *dev)
++{
++ struct tb_service *svc = tb_to_service(dev);
++
++ tb_service_debugfs_remove(svc);
++ device_unregister(&svc->dev);
++}
++
+ static int remove_missing_service(struct device *dev, void *data)
+ {
+ struct tb_xdomain *xd = data;
+@@ -1034,7 +1041,7 @@ static int remove_missing_service(struct
+
+ if (!tb_property_find(xd->remote_properties, svc->key,
+ TB_PROPERTY_TYPE_DIRECTORY))
+- device_unregister(dev);
++ __unregister_service(dev);
+
+ return 0;
+ }
+@@ -1127,6 +1134,7 @@ static void enumerate_services(struct tb
+ tb_service_debugfs_init(svc);
+
+ if (device_register(&svc->dev)) {
++ tb_service_debugfs_remove(svc);
+ put_device(&svc->dev);
+ break;
+ }
+@@ -2059,7 +2067,7 @@ void tb_xdomain_add(struct tb_xdomain *x
+
+ static int unregister_service(struct device *dev, void *data)
+ {
+- device_unregister(dev);
++ __unregister_service(dev);
+ return 0;
+ }
+
--- /dev/null
+From stable+bounces-289103-greg=kroah.com@vger.kernel.org Sat Jul 25 16:12:40 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 10:12:21 -0400
+Subject: thunderbolt: Remove XDomain from the bus without holding tb->lock
+To: stable@vger.kernel.org
+Cc: Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725141222.3814514-3-sashal@kernel.org>
+
+From: Mika Westerberg <mika.westerberg@linux.intel.com>
+
+[ Upstream commit a8937f35cf39c39c64325aa84d0463d866850857 ]
+
+Currently we call device_unregister() for services and the XDomain
+itself with tb->lock held. This prevents the service drivers from
+calling any functions that may take it. For this reason separate
+removing the XDomain from the topology data structures (where we need
+the lock) from unregistering the device from the bus (where remove
+callbacks of the drivers are being called).
+
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/debugfs.c | 2 +
+ drivers/thunderbolt/domain.c | 30 +++++++++++++++++++++
+ drivers/thunderbolt/icm.c | 5 +++
+ drivers/thunderbolt/switch.c | 14 +++++++++
+ drivers/thunderbolt/tb.c | 59 ++++++++++++++++++++----------------------
+ drivers/thunderbolt/tb.h | 2 +
+ drivers/thunderbolt/xdomain.c | 53 +++++++++++++++++++++++--------------
+ 7 files changed, 115 insertions(+), 50 deletions(-)
+
+--- a/drivers/thunderbolt/debugfs.c
++++ b/drivers/thunderbolt/debugfs.c
+@@ -1786,6 +1786,8 @@ static void margining_port_remove(struct
+
+ if (!port->usb4)
+ return;
++ if (!port->usb4->margining)
++ return;
+
+ snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
+ parent = debugfs_lookup(dir_name, port->sw->debugfs_dir);
+--- a/drivers/thunderbolt/domain.c
++++ b/drivers/thunderbolt/domain.c
+@@ -850,6 +850,36 @@ int tb_domain_disconnect_all_paths(struc
+ return bus_for_each_dev(&tb_bus_type, NULL, tb, disconnect_xdomain);
+ }
+
++struct unregister_context {
++ const struct tb *tb;
++ int n;
++};
++
++static int unregister_unplugged_xdomain(struct device *dev, void *data)
++{
++ struct unregister_context *ctx = data;
++ struct tb_xdomain *xd;
++
++ xd = tb_to_xdomain(dev);
++ if (xd && xd->tb == ctx->tb && xd->is_unplugged) {
++ tb_xdomain_unregister(xd);
++ ctx->n++;
++ }
++ return 0;
++}
++
++int tb_domain_unregister_unplugged_xdomains(struct tb *tb)
++{
++ struct unregister_context ctx;
++
++ ctx.tb = tb_domain_get(tb);
++ ctx.n = 0;
++ bus_for_each_dev(&tb_bus_type, NULL, &ctx, unregister_unplugged_xdomain);
++ tb_domain_put(tb);
++
++ return ctx.n;
++}
++
+ int tb_domain_init(void)
+ {
+ int ret;
+--- a/drivers/thunderbolt/icm.c
++++ b/drivers/thunderbolt/icm.c
+@@ -738,6 +738,7 @@ static void remove_xdomain(struct tb_xdo
+
+ sw = tb_to_switch(xd->dev.parent);
+ tb_port_at(xd->route, sw)->xdomain = NULL;
++ xd->is_unplugged = true;
+ tb_xdomain_remove(xd);
+ }
+
+@@ -1762,6 +1763,8 @@ static void icm_handle_notification(stru
+
+ kfree(n->pkg);
+ kfree(n);
++
++ tb_domain_unregister_unplugged_xdomains(tb);
+ }
+
+ static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
+@@ -2112,6 +2115,8 @@ static void icm_rescan_work(struct work_
+ if (tb->root_switch)
+ icm_free_unplugged_children(tb->root_switch);
+ mutex_unlock(&tb->lock);
++
++ tb_domain_unregister_unplugged_xdomains(tb);
+ }
+
+ static void icm_complete(struct tb *tb)
+--- a/drivers/thunderbolt/switch.c
++++ b/drivers/thunderbolt/switch.c
+@@ -3625,6 +3625,20 @@ int tb_switch_resume(struct tb_switch *s
+ tb_port_warn(port,
+ "lost during suspend, disconnecting\n");
+ tb_sw_set_unplugged(port->remote->sw);
++ } else if (port->xdomain) {
++ /*
++ * If the user replaced the XDomain with
++ * another router, this will succeed in
++ * which case we must remove the XDomain
++ * before adding the new router.
++ */
++ err = tb_cfg_get_upstream_port(sw->tb->ctl,
++ port->xdomain->route);
++ if (err > 0) {
++ tb_port_warn(port,
++ "XDomain was disconnected\n");
++ port->xdomain->is_unplugged = true;
++ }
+ }
+ }
+ }
+--- a/drivers/thunderbolt/tb.c
++++ b/drivers/thunderbolt/tb.c
+@@ -2524,6 +2524,8 @@ put_sw:
+ out:
+ mutex_unlock(&tb->lock);
+
++ tb_domain_unregister_unplugged_xdomains(tb);
++
+ pm_runtime_mark_last_busy(&tb->dev);
+ pm_runtime_put_autosuspend(&tb->dev);
+
+@@ -3110,6 +3112,24 @@ static void tb_restore_children(struct t
+ }
+ }
+
++static void tb_free_unplugged_xdomains(struct tb_switch *sw)
++{
++ struct tb_port *port;
++
++ tb_switch_for_each_port(sw, port) {
++ if (tb_is_upstream_port(port))
++ continue;
++ if (port->xdomain && port->xdomain->is_unplugged) {
++ tb_retimer_remove_all(port);
++ tb_xdomain_remove(port->xdomain);
++ tb_port_unconfigure_xdomain(port);
++ port->xdomain = NULL;
++ } else if (port->remote) {
++ tb_free_unplugged_xdomains(port->remote->sw);
++ }
++ }
++}
++
+ static int tb_resume_noirq(struct tb *tb)
+ {
+ struct tb_cm *tcm = tb_priv(tb);
+@@ -3129,6 +3149,7 @@ static int tb_resume_noirq(struct tb *tb
+ tb_switch_resume(tb->root_switch, false);
+ tb_free_invalid_tunnels(tb);
+ tb_free_unplugged_children(tb->root_switch);
++ tb_free_unplugged_xdomains(tb->root_switch);
+ tb_restore_children(tb->root_switch);
+
+ /*
+@@ -3171,28 +3192,6 @@ static int tb_resume_noirq(struct tb *tb
+ return 0;
+ }
+
+-static int tb_free_unplugged_xdomains(struct tb_switch *sw)
+-{
+- struct tb_port *port;
+- int ret = 0;
+-
+- tb_switch_for_each_port(sw, port) {
+- if (tb_is_upstream_port(port))
+- continue;
+- if (port->xdomain && port->xdomain->is_unplugged) {
+- tb_retimer_remove_all(port);
+- tb_xdomain_remove(port->xdomain);
+- tb_port_unconfigure_xdomain(port);
+- port->xdomain = NULL;
+- ret++;
+- } else if (port->remote) {
+- ret += tb_free_unplugged_xdomains(port->remote->sw);
+- }
+- }
+-
+- return ret;
+-}
+-
+ static int tb_freeze_noirq(struct tb *tb)
+ {
+ struct tb_cm *tcm = tb_priv(tb);
+@@ -3212,14 +3211,14 @@ static int tb_thaw_noirq(struct tb *tb)
+ static void tb_complete(struct tb *tb)
+ {
+ /*
+- * Release any unplugged XDomains and if there is a case where
++ * Unregister unplugged XDomains and if there is a case where
+ * another domain is swapped in place of unplugged XDomain we
+ * need to run another rescan.
+ */
+- mutex_lock(&tb->lock);
+- if (tb_free_unplugged_xdomains(tb->root_switch))
+- tb_scan_switch(tb->root_switch);
+- mutex_unlock(&tb->lock);
++ if (tb_domain_unregister_unplugged_xdomains(tb)) {
++ scoped_guard(mutex, &tb->lock)
++ tb_scan_switch(tb->root_switch);
++ }
+ }
+
+ static int tb_runtime_suspend(struct tb *tb)
+@@ -3246,11 +3245,11 @@ static void tb_remove_work(struct work_s
+ struct tb *tb = tcm_to_tb(tcm);
+
+ mutex_lock(&tb->lock);
+- if (tb->root_switch) {
++ if (tb->root_switch)
+ tb_free_unplugged_children(tb->root_switch);
+- tb_free_unplugged_xdomains(tb->root_switch);
+- }
+ mutex_unlock(&tb->lock);
++
++ tb_free_unplugged_xdomains(tb->root_switch);
+ }
+
+ static int tb_runtime_resume(struct tb *tb)
+--- a/drivers/thunderbolt/tb.h
++++ b/drivers/thunderbolt/tb.h
+@@ -793,6 +793,7 @@ int tb_domain_disconnect_xdomain_paths(s
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring);
+ int tb_domain_disconnect_all_paths(struct tb *tb);
++int tb_domain_unregister_unplugged_xdomains(struct tb *tb);
+
+ static inline struct tb *tb_domain_get(struct tb *tb)
+ {
+@@ -1263,6 +1264,7 @@ struct tb_xdomain *tb_xdomain_alloc(stru
+ const uuid_t *remote_uuid);
+ void tb_xdomain_add(struct tb_xdomain *xd);
+ void tb_xdomain_remove(struct tb_xdomain *xd);
++void tb_xdomain_unregister(struct tb_xdomain *xd);
+ struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
+ u8 depth);
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -2072,41 +2072,54 @@ static int unregister_service(struct dev
+ }
+
+ /**
+- * tb_xdomain_remove() - Remove XDomain from the bus
++ * tb_xdomain_remove() - Remove XDomain
+ * @xd: XDomain to remove
+ *
+- * This will stop all ongoing configuration work and remove the XDomain
+- * along with any services from the bus. When the last reference to @xd
+- * is released the object will be released as well.
++ * This will stop all ongoing configuration work. XDomain is not removed
++ * from the bus if it was added. That needs to be done separately by
++ * calling tb_xdomain_unregister().
++ *
++ * Called with @tb->lock held.
+ */
+ void tb_xdomain_remove(struct tb_xdomain *xd)
+ {
+ tb_xdomain_debugfs_remove(xd);
+-
+ stop_handshake(xd);
+-
+- device_for_each_child_reverse(&xd->dev, xd, unregister_service);
+-
+ tb_xdomain_link_exit(xd);
+
+- /*
+- * Undo runtime PM here explicitly because it is possible that
+- * the XDomain was never added to the bus and thus device_del()
+- * is not called for it (device_del() would handle this otherwise).
+- */
+- pm_runtime_disable(&xd->dev);
+- pm_runtime_put_noidle(&xd->dev);
+- pm_runtime_set_suspended(&xd->dev);
+-
+ if (!device_is_registered(&xd->dev)) {
++ /*
++ * Undo runtime PM here explicitly because it is
++ * possible that the XDomain was never added to the bus
++ * and thus device_del() is not called for it
++ * (device_del() would handle this otherwise).
++ */
++ pm_runtime_disable(&xd->dev);
++ pm_runtime_put_noidle(&xd->dev);
++ pm_runtime_set_suspended(&xd->dev);
+ put_device(&xd->dev);
+- } else {
+- dev_info(&xd->dev, "host disconnected\n");
+- device_unregister(&xd->dev);
+ }
+ }
+
+ /**
++ * tb_xdomain_unregister() - Unregister XDomain
++ * @xd: XDomain to unregister
++ *
++ * This will unregister the XDomain along with any services from the
++ * bus. When the last reference to @xd is released the object will be
++ * released as well.
++ */
++void tb_xdomain_unregister(struct tb_xdomain *xd)
++{
++ lockdep_assert_not_held(&xd->tb->lock);
++
++ device_for_each_child_reverse(&xd->dev, xd, unregister_service);
++
++ dev_info(&xd->dev, "host disconnected\n");
++ device_unregister(&xd->dev);
++}
++
++/**
+ * tb_xdomain_lane_bonding_enable() - Enable lane bonding on XDomain
+ * @xd: XDomain connection
+ *
--- /dev/null
+From stable+bounces-287813-greg=kroah.com@vger.kernel.org Wed Jul 22 13:25:36 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 07:24:48 -0400
+Subject: xfs: don't replace the wrong part of the cow fork
+To: stable@vger.kernel.org
+Cc: "Darrick J. Wong" <djwong@kernel.org>, Christoph Hellwig <hch@lst.de>, Carlos Maiolino <cem@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722112448.1117201-1-sashal@kernel.org>
+
+From: "Darrick J. Wong" <djwong@kernel.org>
+
+[ Upstream commit a1caeeadbf57ff86dfc3454398c46de86056a74e ]
+
+LOLLM points out that xfs_iext_lookup_extent can return a @got where
+got->br_startoff < startoff. In this case, xrep_cow_replace_range
+replaces the entire mapping instead of just the part that had been
+marked bad in the bitmap, but advances the bitmap cursor in
+xrep_cow_replace by the amount replaced. As a result, we fail to
+replace the end of the bad range, and replace part of the good range.
+
+Fix this by rewriting the replace method to handle replacing the middle
+of a cow fork mapping. This we do by returning both the current mapping
+as @got, and the subset of the mapping that we want to replace as @rep,
+using @rep to store the results of the new allocation, and comparing
+@rep to @got to figure out the exact transformations needed.
+
+Cc: stable@vger.kernel.org # v6.8
+Fixes: dbbdbd0086320a ("xfs: repair problems in CoW forks")
+Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
+Assisted-by: LOLLM # finding obvious bugs
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Signed-off-by: Carlos Maiolino <cem@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/xfs/scrub/cow_repair.c | 203 +++++++++++++++++++++++++++++-----------------
+ fs/xfs/scrub/trace.h | 28 +++---
+ 2 files changed, 148 insertions(+), 83 deletions(-)
+
+--- a/fs/xfs/scrub/cow_repair.c
++++ b/fs/xfs/scrub/cow_repair.c
+@@ -80,12 +80,6 @@ struct xrep_cow {
+ unsigned int next_bno;
+ };
+
+-/* CoW staging extent. */
+-struct xrep_cow_extent {
+- xfs_fsblock_t fsbno;
+- xfs_extlen_t len;
+-};
+-
+ /*
+ * Mark the part of the file range that corresponds to the given physical
+ * space. Caller must ensure that the physical range is within xc->irec.
+@@ -401,22 +395,21 @@ out_rtg:
+ STATIC int
+ xrep_cow_alloc(
+ struct xfs_scrub *sc,
+- xfs_extlen_t maxlen,
+- struct xrep_cow_extent *repl)
++ struct xfs_bmbt_irec *del)
+ {
+ struct xfs_alloc_arg args = {
+ .tp = sc->tp,
+ .mp = sc->mp,
+ .oinfo = XFS_RMAP_OINFO_SKIP_UPDATE,
+ .minlen = 1,
+- .maxlen = maxlen,
++ .maxlen = del->br_blockcount,
+ .prod = 1,
+ .resv = XFS_AG_RESV_NONE,
+ .datatype = XFS_ALLOC_USERDATA,
+ };
+ int error;
+
+- error = xfs_trans_reserve_more(sc->tp, maxlen, 0);
++ error = xfs_trans_reserve_more(sc->tp, del->br_blockcount, 0);
+ if (error)
+ return error;
+
+@@ -429,8 +422,8 @@ xrep_cow_alloc(
+
+ xfs_refcount_alloc_cow_extent(sc->tp, false, args.fsbno, args.len);
+
+- repl->fsbno = args.fsbno;
+- repl->len = args.len;
++ del->br_startblock = args.fsbno;
++ del->br_blockcount = args.len;
+ return 0;
+ }
+
+@@ -441,10 +434,12 @@ xrep_cow_alloc(
+ STATIC int
+ xrep_cow_alloc_rt(
+ struct xfs_scrub *sc,
+- xfs_extlen_t maxlen,
+- struct xrep_cow_extent *repl)
++ struct xfs_bmbt_irec *del)
+ {
+- xfs_rtxlen_t maxrtx = xfs_rtb_to_rtx(sc->mp, maxlen);
++ xfs_fsblock_t fsbno;
++ xfs_rtxlen_t maxrtx =
++ min(U32_MAX, xfs_blen_to_rtbxlen(sc->mp, del->br_blockcount));
++ xfs_extlen_t len;
+ int error;
+
+ error = xfs_trans_reserve_more(sc->tp, 0, maxrtx);
+@@ -452,11 +447,14 @@ xrep_cow_alloc_rt(
+ return error;
+
+ error = xfs_rtallocate_rtgs(sc->tp, NULLRTBLOCK, 1, maxrtx, 1, false,
+- false, &repl->fsbno, &repl->len);
++ false, &fsbno, &len);
+ if (error)
+ return error;
+
+- xfs_refcount_alloc_cow_extent(sc->tp, true, repl->fsbno, repl->len);
++ xfs_refcount_alloc_cow_extent(sc->tp, true, fsbno, len);
++
++ del->br_startblock = fsbno;
++ del->br_blockcount = len;
+ return 0;
+ }
+
+@@ -470,19 +468,19 @@ static inline int
+ xrep_cow_find_mapping(
+ struct xrep_cow *xc,
+ struct xfs_iext_cursor *icur,
+- xfs_fileoff_t startoff,
+- struct xfs_bmbt_irec *got)
++ xfs_fileoff_t badoff,
++ xfs_extlen_t badlen,
++ struct xfs_bmbt_irec *got,
++ struct xfs_bmbt_irec *rep)
+ {
+ struct xfs_inode *ip = xc->sc->ip;
+ struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
+
+- if (!xfs_iext_lookup_extent(ip, ifp, startoff, icur, got))
++ if (!xfs_iext_lookup_extent(ip, ifp, badoff, icur, got))
+ goto bad;
++ memcpy(rep, got, sizeof(*rep));
+
+- if (got->br_startoff > startoff)
+- goto bad;
+-
+- if (got->br_blockcount == 0)
++ if (got->br_startoff > badoff)
+ goto bad;
+
+ if (isnullstartblock(got->br_startblock))
+@@ -491,6 +489,24 @@ xrep_cow_find_mapping(
+ if (xfs_bmap_is_written_extent(got))
+ goto bad;
+
++ if (got->br_startoff < badoff) {
++ const int64_t delta = badoff - got->br_startoff;
++
++ rep->br_blockcount -= delta;
++ rep->br_startoff += delta;
++ rep->br_startblock += delta;
++ }
++
++ if (got->br_startoff + got->br_blockcount > badoff + badlen) {
++ const int64_t delta = (got->br_startoff + got->br_blockcount) -
++ (badoff + badlen);
++
++ rep->br_blockcount -= delta;
++ }
++
++ if (got->br_blockcount == 0)
++ goto bad;
++
+ return 0;
+ bad:
+ ASSERT(0);
+@@ -501,46 +517,92 @@ bad:
+ #define REPLACE_RIGHT_SIDE (1U << 1)
+
+ /*
+- * Given a CoW fork mapping @got and a replacement mapping @repl, remap the
+- * beginning of @got with the space described by @rep.
++ * Given a CoW fork mapping @got and a replacement mapping @rep, map the space
++ * described by @rep into the cow fork, pushing aside @got as necessary. @icur
++ * must point to iext tree leaf containing @got.
+ */
+ static inline void
+ xrep_cow_replace_mapping(
+- struct xfs_inode *ip,
+- struct xfs_iext_cursor *icur,
+- const struct xfs_bmbt_irec *got,
+- const struct xrep_cow_extent *repl)
++ struct xfs_inode *ip,
++ struct xfs_iext_cursor *icur,
++ struct xfs_bmbt_irec *got,
++ struct xfs_bmbt_irec *rep)
+ {
+- struct xfs_bmbt_irec new = *got; /* struct copy */
++ struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
++ xfs_fileoff_t rep_endoff =
++ rep->br_startoff + rep->br_blockcount;
++ xfs_fileoff_t got_endoff =
++ got->br_startoff + got->br_blockcount;
++ uint32_t state = BMAP_COWFORK;
+
+- ASSERT(repl->len > 0);
++ ASSERT(rep->br_blockcount > 0);
+ ASSERT(!isnullstartblock(got->br_startblock));
++ ASSERT(got->br_startoff <= rep->br_startoff);
++ ASSERT(got_endoff >= rep_endoff);
+
+- trace_xrep_cow_replace_mapping(ip, got, repl->fsbno, repl->len);
++ trace_xrep_cow_replace_mapping(ip, got, rep);
+
+- if (got->br_blockcount == repl->len) {
++ if (got->br_startoff == rep->br_startoff)
++ state |= BMAP_LEFT_FILLING;
++ if (got_endoff == rep_endoff)
++ state |= BMAP_RIGHT_FILLING;
++
++ switch (state & (BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING)) {
++ case BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING:
+ /*
+- * The new extent is a complete replacement for the existing
+- * extent. Update the COW fork record.
++ * Replacement matches the whole mapping, update the record.
+ */
+- new.br_startblock = repl->fsbno;
+- xfs_iext_update_extent(ip, BMAP_COWFORK, icur, &new);
+- return;
+- }
++ xfs_iext_update_extent(ip, state, icur, rep);
++ break;
++ case BMAP_LEFT_FILLING:
++ /*
++ * Replace the first part of the mapping: Update the cursor
++ * position with the new mapping, then add a record with the
++ * tail of the old mapping.
++ */
++ got->br_startoff = rep_endoff;
++ got->br_blockcount -= rep->br_blockcount;
++ got->br_startblock += rep->br_blockcount;
++
++ xfs_iext_update_extent(ip, state, icur, rep);
++ xfs_iext_next(ifp, icur);
++ xfs_iext_insert(ip, icur, got, state);
++ break;
++ case BMAP_RIGHT_FILLING:
++ /*
++ * Replacing the last part of the mapping. Shorten the current
++ * mapping then add a record with the new mapping.
++ */
++ got->br_blockcount -= rep->br_blockcount;
+
+- /*
+- * The new extent can replace the beginning of the COW fork record.
+- * Move the left side of @got upwards, then insert the new record.
+- */
+- new.br_startoff += repl->len;
+- new.br_startblock += repl->len;
+- new.br_blockcount -= repl->len;
+- xfs_iext_update_extent(ip, BMAP_COWFORK, icur, &new);
+-
+- new.br_startoff = got->br_startoff;
+- new.br_startblock = repl->fsbno;
+- new.br_blockcount = repl->len;
+- xfs_iext_insert(ip, icur, &new, BMAP_COWFORK);
++ xfs_iext_update_extent(ip, state, icur, got);
++ xfs_iext_next(ifp, icur);
++ xfs_iext_insert(ip, icur, rep, state);
++ break;
++ case 0:
++ /*
++ * Replacing the middle of the extent. Shorten the current
++ * mapping, add a new record with the new mapping, and add a
++ * second new record with the tail of the old mapping.
++ */
++ got->br_blockcount = rep->br_startoff - got->br_startoff;
++
++ struct xfs_bmbt_irec new = {
++ .br_startoff = rep_endoff,
++ .br_blockcount = got_endoff - rep_endoff,
++ .br_state = got->br_state,
++ .br_startblock = got->br_startblock +
++ rep->br_blockcount +
++ got->br_blockcount,
++ };
++
++ xfs_iext_update_extent(ip, state, icur, got);
++ xfs_iext_next(ifp, icur);
++ xfs_iext_insert(ip, icur, rep, state);
++ xfs_iext_next(ifp, icur);
++ xfs_iext_insert(ip, icur, &new, state);
++ break;
++ }
+ }
+
+ /*
+@@ -554,33 +616,30 @@ xrep_cow_replace_range(
+ xfs_extlen_t *blockcount)
+ {
+ struct xfs_iext_cursor icur;
+- struct xrep_cow_extent repl;
+- struct xfs_bmbt_irec got;
++ struct xfs_bmbt_irec got, rep;
+ struct xfs_scrub *sc = xc->sc;
+- xfs_fileoff_t nextoff;
+- xfs_extlen_t alloc_len;
++ xfs_fsblock_t old_fsbno;
+ int error;
+
+ /*
+- * Put the existing CoW fork mapping in @got. If @got ends before
+- * @rep, truncate @rep so we only replace one extent mapping at a time.
++ * Put the existing CoW fork mapping in @got, and put in @rep the
++ * contents of @got trimmed to @startoff/@blockcount. We only want
++ * to replace the bad region, and only one mapping at a time.
+ */
+- error = xrep_cow_find_mapping(xc, &icur, startoff, &got);
++ error = xrep_cow_find_mapping(xc, &icur, startoff, *blockcount, &got,
++ &rep);
+ if (error)
+ return error;
+- nextoff = min(startoff + *blockcount,
+- got.br_startoff + got.br_blockcount);
++ old_fsbno = rep.br_startblock;
+
+ /*
+ * Allocate a replacement extent. If we don't fill all the blocks,
+ * shorten the quantity that will be deleted in this step.
+ */
+- alloc_len = min_t(xfs_fileoff_t, XFS_MAX_BMBT_EXTLEN,
+- nextoff - startoff);
+ if (XFS_IS_REALTIME_INODE(sc->ip))
+- error = xrep_cow_alloc_rt(sc, alloc_len, &repl);
++ error = xrep_cow_alloc_rt(sc, &rep);
+ else
+- error = xrep_cow_alloc(sc, alloc_len, &repl);
++ error = xrep_cow_alloc(sc, &rep);
+ if (error)
+ return error;
+
+@@ -588,7 +647,7 @@ xrep_cow_replace_range(
+ * Replace the old mapping with the new one, and commit the metadata
+ * changes made so far.
+ */
+- xrep_cow_replace_mapping(sc->ip, &icur, &got, &repl);
++ xrep_cow_replace_mapping(sc->ip, &icur, &got, &rep);
+
+ xfs_inode_set_cowblocks_tag(sc->ip);
+ error = xfs_defer_finish(&sc->tp);
+@@ -597,15 +656,15 @@ xrep_cow_replace_range(
+
+ /* Note the old CoW staging extents; we'll reap them all later. */
+ if (XFS_IS_REALTIME_INODE(sc->ip))
+- error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks,
+- got.br_startblock, repl.len);
++ error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks, old_fsbno,
++ rep.br_blockcount);
+ else
+- error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks,
+- got.br_startblock, repl.len);
++ error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks, old_fsbno,
++ rep.br_blockcount);
+ if (error)
+ return error;
+
+- *blockcount = repl.len;
++ *blockcount = rep.br_blockcount;
+ return 0;
+ }
+
+--- a/fs/xfs/scrub/trace.h
++++ b/fs/xfs/scrub/trace.h
+@@ -2672,9 +2672,9 @@ TRACE_EVENT(xrep_cow_mark_file_range,
+ );
+
+ TRACE_EVENT(xrep_cow_replace_mapping,
+- TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *irec,
+- xfs_fsblock_t new_startblock, xfs_extlen_t new_blockcount),
+- TP_ARGS(ip, irec, new_startblock, new_blockcount),
++ TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *got,
++ const struct xfs_bmbt_irec *rep),
++ TP_ARGS(ip, got, rep),
+ TP_STRUCT__entry(
+ __field(dev_t, dev)
+ __field(xfs_ino_t, ino)
+@@ -2682,28 +2682,34 @@ TRACE_EVENT(xrep_cow_replace_mapping,
+ __field(xfs_fileoff_t, startoff)
+ __field(xfs_filblks_t, blockcount)
+ __field(xfs_exntst_t, state)
++ __field(xfs_fileoff_t, new_startoff)
+ __field(xfs_fsblock_t, new_startblock)
+ __field(xfs_extlen_t, new_blockcount)
++ __field(xfs_exntst_t, new_state)
+ ),
+ TP_fast_assign(
+ __entry->dev = ip->i_mount->m_super->s_dev;
+ __entry->ino = ip->i_ino;
+- __entry->startoff = irec->br_startoff;
+- __entry->startblock = irec->br_startblock;
+- __entry->blockcount = irec->br_blockcount;
+- __entry->state = irec->br_state;
+- __entry->new_startblock = new_startblock;
+- __entry->new_blockcount = new_blockcount;
++ __entry->startoff = got->br_startoff;
++ __entry->startblock = got->br_startblock;
++ __entry->blockcount = got->br_blockcount;
++ __entry->state = got->br_state;
++ __entry->new_startoff = rep->br_startoff;
++ __entry->new_startblock = rep->br_startblock;
++ __entry->new_blockcount = rep->br_blockcount;
++ __entry->new_state = rep->br_state;
+ ),
+- TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startblock 0x%llx new_fsbcount 0x%x",
++ TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startoff 0x%llx new_startblock 0x%llx new_fsbcount 0x%x new_state 0x%x",
+ MAJOR(__entry->dev), MINOR(__entry->dev),
+ __entry->ino,
+ __entry->startoff,
+ __entry->startblock,
+ __entry->blockcount,
+ __entry->state,
++ __entry->new_startoff,
+ __entry->new_startblock,
+- __entry->new_blockcount)
++ __entry->new_blockcount,
++ __entry->new_state)
+ );
+
+ TRACE_EVENT(xrep_cow_free_staging,