--- /dev/null
+From stable+bounces-290946-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:52 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:32 -0400
+Subject: afs: Drop the net parameter from afs_unuse_cell()
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-3-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 469c82b558628482002e4819d35b7670f0a989fd ]
+
+Remove the redundant net parameter to afs_unuse_cell() as cell->net can be
+used instead.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-12-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-8-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/cell.c | 12 ++++++------
+ fs/afs/dynroot.c | 4 ++--
+ fs/afs/internal.h | 2 +-
+ fs/afs/mntpt.c | 2 +-
+ fs/afs/proc.c | 2 +-
+ fs/afs/super.c | 9 ++++-----
+ fs/afs/vl_alias.c | 4 ++--
+ include/trace/events/afs.h | 1 +
+ 8 files changed, 18 insertions(+), 18 deletions(-)
+
+--- a/fs/afs/cell.c
++++ b/fs/afs/cell.c
+@@ -339,7 +339,7 @@ cell_already_exists:
+ goto wait_for_cell;
+ goto error_noput;
+ error:
+- afs_unuse_cell(net, cell, afs_cell_trace_unuse_lookup);
++ afs_unuse_cell(cell, afs_cell_trace_unuse_lookup_error);
+ error_noput:
+ _leave(" = %d [error]", ret);
+ return ERR_PTR(ret);
+@@ -402,7 +402,7 @@ int afs_cell_init(struct afs_net *net, c
+ lockdep_is_held(&net->cells_lock));
+ up_write(&net->cells_lock);
+
+- afs_unuse_cell(net, old_root, afs_cell_trace_unuse_ws);
++ afs_unuse_cell(old_root, afs_cell_trace_unuse_ws);
+ _leave(" = 0");
+ return 0;
+ }
+@@ -522,7 +522,7 @@ static void afs_cell_destroy(struct rcu_
+ trace_afs_cell(cell->debug_id, r, atomic_read(&cell->active), afs_cell_trace_free);
+
+ afs_put_vlserverlist(net, rcu_access_pointer(cell->vl_servers));
+- afs_unuse_cell(net, cell->alias_of, afs_cell_trace_unuse_alias);
++ afs_unuse_cell(cell->alias_of, afs_cell_trace_unuse_alias);
+ key_put(cell->anonymous_key);
+ idr_remove(&net->cells_dyn_ino, cell->dynroot_ino);
+ kfree(cell->name - 1);
+@@ -610,7 +610,7 @@ struct afs_cell *afs_use_cell(struct afs
+ * Record a cell becoming less active. When the active counter reaches 1, it
+ * is scheduled for destruction, but may get reactivated.
+ */
+-void afs_unuse_cell(struct afs_net *net, struct afs_cell *cell, enum afs_cell_trace reason)
++void afs_unuse_cell(struct afs_cell *cell, enum afs_cell_trace reason)
+ {
+ unsigned int debug_id;
+ time64_t now, expire_delay;
+@@ -634,7 +634,7 @@ void afs_unuse_cell(struct afs_net *net,
+ WARN_ON(a == 0);
+ if (a == 1)
+ /* 'cell' may now be garbage collected. */
+- afs_set_cell_timer(net, expire_delay);
++ afs_set_cell_timer(cell->net, expire_delay);
+ }
+
+ /*
+@@ -959,7 +959,7 @@ void afs_cell_purge(struct afs_net *net)
+ ws = rcu_replace_pointer(net->ws_cell, NULL,
+ lockdep_is_held(&net->cells_lock));
+ up_write(&net->cells_lock);
+- afs_unuse_cell(net, ws, afs_cell_trace_unuse_ws);
++ afs_unuse_cell(ws, afs_cell_trace_unuse_ws);
+
+ _debug("del timer");
+ if (del_timer_sync(&net->cells_timer))
+--- a/fs/afs/dynroot.c
++++ b/fs/afs/dynroot.c
+@@ -125,7 +125,7 @@ static struct dentry *afs_dynroot_lookup
+ return d_splice_alias(inode, dentry);
+
+ out:
+- afs_unuse_cell(cell->net, cell, afs_cell_trace_unuse_lookup_dynroot);
++ afs_unuse_cell(cell, afs_cell_trace_unuse_lookup_dynroot);
+ out_no_cell:
+ if (!inode)
+ return d_splice_alias(inode, dentry);
+@@ -167,7 +167,7 @@ static void afs_dynroot_d_release(struct
+ {
+ struct afs_cell *cell = dentry->d_fsdata;
+
+- afs_unuse_cell(cell->net, cell, afs_cell_trace_unuse_dynroot_mntpt);
++ afs_unuse_cell(cell, afs_cell_trace_unuse_dynroot_mntpt);
+ }
+
+ /*
+--- a/fs/afs/internal.h
++++ b/fs/afs/internal.h
+@@ -1045,7 +1045,7 @@ struct afs_cell *afs_lookup_cell(struct
+ const char *vllist, bool excl,
+ enum afs_cell_trace trace);
+ extern struct afs_cell *afs_use_cell(struct afs_cell *, enum afs_cell_trace);
+-extern void afs_unuse_cell(struct afs_net *, struct afs_cell *, enum afs_cell_trace);
++void afs_unuse_cell(struct afs_cell *cell, enum afs_cell_trace reason);
+ extern struct afs_cell *afs_get_cell(struct afs_cell *, enum afs_cell_trace);
+ extern void afs_see_cell(struct afs_cell *, enum afs_cell_trace);
+ extern void afs_put_cell(struct afs_cell *, enum afs_cell_trace);
+--- a/fs/afs/mntpt.c
++++ b/fs/afs/mntpt.c
+@@ -87,7 +87,7 @@ static int afs_mntpt_set_params(struct f
+ ctx->force = true;
+ }
+ if (ctx->cell) {
+- afs_unuse_cell(ctx->net, ctx->cell, afs_cell_trace_unuse_mntpt);
++ afs_unuse_cell(ctx->cell, afs_cell_trace_unuse_mntpt);
+ ctx->cell = NULL;
+ }
+ if (test_bit(AFS_VNODE_PSEUDODIR, &vnode->flags)) {
+--- a/fs/afs/proc.c
++++ b/fs/afs/proc.c
+@@ -130,7 +130,7 @@ static int afs_proc_cells_write(struct f
+ }
+
+ if (test_and_set_bit(AFS_CELL_FL_NO_GC, &cell->flags))
+- afs_unuse_cell(net, cell, afs_cell_trace_unuse_no_pin);
++ afs_unuse_cell(cell, afs_cell_trace_unuse_no_pin);
+ } else {
+ goto inval;
+ }
+--- a/fs/afs/super.c
++++ b/fs/afs/super.c
+@@ -297,7 +297,7 @@ static int afs_parse_source(struct fs_co
+ cellnamesz, cellnamesz, cellname ?: "");
+ return PTR_ERR(cell);
+ }
+- afs_unuse_cell(ctx->net, ctx->cell, afs_cell_trace_unuse_parse);
++ afs_unuse_cell(ctx->cell, afs_cell_trace_unuse_parse);
+ afs_see_cell(cell, afs_cell_trace_see_source);
+ ctx->cell = cell;
+ }
+@@ -394,7 +394,7 @@ static int afs_validate_fc(struct fs_con
+ ctx->key = NULL;
+ cell = afs_use_cell(ctx->cell->alias_of,
+ afs_cell_trace_use_fc_alias);
+- afs_unuse_cell(ctx->net, ctx->cell, afs_cell_trace_unuse_fc);
++ afs_unuse_cell(ctx->cell, afs_cell_trace_unuse_fc);
+ ctx->cell = cell;
+ goto reget_key;
+ }
+@@ -520,9 +520,8 @@ static struct afs_super_info *afs_alloc_
+ static void afs_destroy_sbi(struct afs_super_info *as)
+ {
+ if (as) {
+- struct afs_net *net = afs_net(as->net_ns);
+ afs_put_volume(as->volume, afs_volume_trace_put_destroy_sbi);
+- afs_unuse_cell(net, as->cell, afs_cell_trace_unuse_sbi);
++ afs_unuse_cell(as->cell, afs_cell_trace_unuse_sbi);
+ put_net(as->net_ns);
+ kfree(as);
+ }
+@@ -606,7 +605,7 @@ static void afs_free_fc(struct fs_contex
+
+ afs_destroy_sbi(fc->s_fs_info);
+ afs_put_volume(ctx->volume, afs_volume_trace_put_free_fc);
+- afs_unuse_cell(ctx->net, ctx->cell, afs_cell_trace_unuse_fc);
++ afs_unuse_cell(ctx->cell, afs_cell_trace_unuse_fc);
+ key_put(ctx->key);
+ kfree(ctx);
+ }
+--- a/fs/afs/vl_alias.c
++++ b/fs/afs/vl_alias.c
+@@ -205,11 +205,11 @@ static int afs_query_for_alias(struct af
+ goto is_alias;
+
+ if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0) {
+- afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
++ afs_unuse_cell(p, afs_cell_trace_unuse_check_alias);
+ return -ERESTARTSYS;
+ }
+
+- afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
++ afs_unuse_cell(p, afs_cell_trace_unuse_check_alias);
+ }
+
+ mutex_unlock(&cell->net->proc_cells_lock);
+--- a/include/trace/events/afs.h
++++ b/include/trace/events/afs.h
+@@ -197,6 +197,7 @@ enum yfs_cm_operation {
+ EM(afs_cell_trace_unuse_fc, "UNU fc ") \
+ EM(afs_cell_trace_unuse_lookup, "UNU lookup") \
+ EM(afs_cell_trace_unuse_lookup_dynroot, "UNU lu-dyn") \
++ EM(afs_cell_trace_unuse_lookup_error, "UNU lu-err") \
+ EM(afs_cell_trace_unuse_mntpt, "UNU mntpt ") \
+ EM(afs_cell_trace_unuse_no_pin, "UNU no-pin") \
+ EM(afs_cell_trace_unuse_parse, "UNU parse ") \
--- /dev/null
+From stable+bounces-290952-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:35 -0400
+Subject: afs: Fix afs_server ref accounting
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-6-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 4882ba78574e2d8c579658f65f6784b0d139d173 ]
+
+The current way that afs_server refs are accounted and cleaned up sometimes
+cause rmmod to hang when it is waiting for cell records to be removed. The
+problem is that the cell cleanup might occasionally happen before the
+server cleanup and then there's nothing that causes the cell to
+garbage-collect the remaining servers as they become inactive.
+
+Partially fix this by:
+
+ (1) Give each afs_server record its own management timer that rather than
+ relying on the cell manager's central timer to drive each individual
+ cell's maintenance work item to garbage collect servers.
+
+ This timer is set when afs_unuse_server() reduces a server's activity
+ count to zero and will schedule the server's destroyer work item upon
+ firing.
+
+ (2) Give each afs_server record its own destroyer work item that removes
+ the record from the cell's database, shuts down the timer, cancels any
+ pending work for itself, sends an RPC to the server to cancel
+ outstanding callbacks.
+
+ This change, in combination with the timer, obviates the need to try
+ and coordinate so closely between the cell record and a bunch of other
+ server records to try and tear everything down in a coordinated
+ fashion. With this, the cell record is pinned until the server RCU is
+ complete and namespace/module removal will wait until all the cell
+ records are removed.
+
+ (3) Now that incoming calls are mapped to servers (and thus cells) using
+ data attached to an rxrpc_peer, the UUID-to-server mapping tree is
+ moved from the namespace to the cell (cell->fs_servers). This means
+ there can no longer be duplicates therein - and that allows the
+ mapping tree to be simpler as there doesn't need to be a chain of
+ same-UUID servers that are in different cells.
+
+ (4) The lock protecting the UUID mapping tree is switched to an
+ rw_semaphore on the cell rather than a seqlock on the namespace as
+ it's now only used during mounting in contexts in which we're allowed
+ to sleep.
+
+ (5) When it comes time for a cell that is being removed to purge its set
+ of servers, it just needs to iterate over them and wake them up. Once
+ a server becomes inactive, its destroyer work item will observe the
+ state of the cell and immediately remove that record.
+
+ (6) When a server record is removed, it is marked AFS_SERVER_FL_EXPIRED to
+ prevent reattempts at removal. The record will be dispatched to RCU
+ for destruction once its refcount reaches 0.
+
+ (7) The AFS_SERVER_FL_UNCREATED/CREATING flags are used to synchronise
+ simultaneous creation attempts. If one attempt fails, it will abandon
+ the attempt and allow another to try again.
+
+ Note that the record can't just be abandoned when dead as it's bound
+ into a server list attached to a volume and only subject to
+ replacement if the server list obtained for the volume from the VLDB
+ changes.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-15-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-11-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/cell.c | 3
+ fs/afs/fsclient.c | 4
+ fs/afs/internal.h | 54 ++--
+ fs/afs/main.c | 10
+ fs/afs/server.c | 560 +++++++++++++++++++--------------------------
+ fs/afs/server_list.c | 4
+ include/trace/events/afs.h | 7
+ 7 files changed, 287 insertions(+), 355 deletions(-)
+
+--- a/fs/afs/cell.c
++++ b/fs/afs/cell.c
+@@ -169,7 +169,7 @@ static struct afs_cell *afs_alloc_cell(s
+ INIT_HLIST_HEAD(&cell->proc_volumes);
+ seqlock_init(&cell->volume_lock);
+ cell->fs_servers = RB_ROOT;
+- seqlock_init(&cell->fs_lock);
++ init_rwsem(&cell->fs_lock);
+ rwlock_init(&cell->vl_servers_lock);
+ cell->flags = (1 << AFS_CELL_FL_CHECK_ALIAS);
+
+@@ -840,6 +840,7 @@ final_destruction:
+ /* The root volume is pinning the cell */
+ afs_put_volume(cell->root_volume, afs_volume_trace_put_cell_root);
+ cell->root_volume = NULL;
++ afs_purge_servers(cell);
+ afs_put_cell(cell, afs_cell_trace_put_destroy);
+ }
+
+--- a/fs/afs/fsclient.c
++++ b/fs/afs/fsclient.c
+@@ -1650,7 +1650,7 @@ int afs_fs_give_up_all_callbacks(struct
+ bp = call->request;
+ *bp++ = htonl(FSGIVEUPALLCALLBACKS);
+
+- call->server = afs_use_server(server, afs_server_trace_use_give_up_cb);
++ call->server = afs_use_server(server, false, afs_server_trace_use_give_up_cb);
+ afs_make_call(call, GFP_NOFS);
+ afs_wait_for_call_to_complete(call);
+ ret = call->error;
+@@ -1756,7 +1756,7 @@ bool afs_fs_get_capabilities(struct afs_
+ return false;
+
+ call->key = key;
+- call->server = afs_use_server(server, afs_server_trace_use_get_caps);
++ call->server = afs_use_server(server, false, afs_server_trace_use_get_caps);
+ call->peer = rxrpc_kernel_get_peer(estate->addresses->addrs[addr_index].peer);
+ call->probe = afs_get_endpoint_state(estate, afs_estate_trace_get_getcaps);
+ call->probe_index = addr_index;
+--- a/fs/afs/internal.h
++++ b/fs/afs/internal.h
+@@ -317,18 +317,11 @@ struct afs_net {
+ * cell, but in practice, people create aliases and subsets and there's
+ * no easy way to distinguish them.
+ */
+- seqlock_t fs_lock; /* For fs_servers, fs_probe_*, fs_proc */
+- struct rb_root fs_servers; /* afs_server (by server UUID or address) */
++ seqlock_t fs_lock; /* For fs_probe_*, fs_proc */
+ struct list_head fs_probe_fast; /* List of afs_server to probe at 30s intervals */
+ struct list_head fs_probe_slow; /* List of afs_server to probe at 5m intervals */
+ struct hlist_head fs_proc; /* procfs servers list */
+
+- struct hlist_head fs_addresses; /* afs_server (by lowest IPv6 addr) */
+- seqlock_t fs_addr_lock; /* For fs_addresses[46] */
+-
+- struct work_struct fs_manager;
+- struct timer_list fs_timer;
+-
+ struct work_struct fs_prober;
+ struct timer_list fs_probe_timer;
+ atomic_t servers_outstanding;
+@@ -424,7 +417,7 @@ struct afs_cell {
+
+ /* Active fileserver interaction state. */
+ struct rb_root fs_servers; /* afs_server (by server UUID) */
+- seqlock_t fs_lock; /* For fs_servers */
++ struct rw_semaphore fs_lock; /* For fs_servers */
+
+ /* VL server list. */
+ rwlock_t vl_servers_lock; /* Lock on vl_servers */
+@@ -559,22 +552,22 @@ struct afs_server {
+ };
+
+ struct afs_cell *cell; /* Cell to which belongs (pins ref) */
+- struct rb_node uuid_rb; /* Link in net->fs_servers */
+- struct afs_server __rcu *uuid_next; /* Next server with same UUID */
+- struct afs_server *uuid_prev; /* Previous server with same UUID */
+- struct list_head probe_link; /* Link in net->fs_probe_list */
+- struct hlist_node addr_link; /* Link in net->fs_addresses6 */
++ struct rb_node uuid_rb; /* Link in cell->fs_servers */
++ struct list_head probe_link; /* Link in net->fs_probe_* */
+ struct hlist_node proc_link; /* Link in net->fs_proc */
+ struct list_head volumes; /* RCU list of afs_server_entry objects */
+- struct afs_server *gc_next; /* Next server in manager's list */
++ struct work_struct destroyer; /* Work item to try and destroy a server */
++ struct timer_list timer; /* Management timer */
+ time64_t unuse_time; /* Time at which last unused */
+ unsigned long flags;
+ #define AFS_SERVER_FL_RESPONDING 0 /* The server is responding */
+ #define AFS_SERVER_FL_UPDATING 1
+ #define AFS_SERVER_FL_NEEDS_UPDATE 2 /* Fileserver address list is out of date */
+-#define AFS_SERVER_FL_NOT_READY 4 /* The record is not ready for use */
+-#define AFS_SERVER_FL_NOT_FOUND 5 /* VL server says no such server */
+-#define AFS_SERVER_FL_VL_FAIL 6 /* Failed to access VL server */
++#define AFS_SERVER_FL_UNCREATED 3 /* The record needs creating */
++#define AFS_SERVER_FL_CREATING 4 /* The record is being created */
++#define AFS_SERVER_FL_EXPIRED 5 /* The record has expired */
++#define AFS_SERVER_FL_NOT_FOUND 6 /* VL server says no such server */
++#define AFS_SERVER_FL_VL_FAIL 7 /* Failed to access VL server */
+ #define AFS_SERVER_FL_MAY_HAVE_CB 8 /* May have callbacks on this fileserver */
+ #define AFS_SERVER_FL_IS_YFS 16 /* Server is YFS not AFS */
+ #define AFS_SERVER_FL_NO_IBULK 17 /* Fileserver doesn't support FS.InlineBulkStatus */
+@@ -584,6 +577,7 @@ struct afs_server {
+ atomic_t active; /* Active user count */
+ u32 addr_version; /* Address list version */
+ u16 service_id; /* Service ID we're using. */
++ short create_error; /* Creation error */
+ unsigned int rtt; /* Server's current RTT in uS */
+ unsigned int debug_id; /* Debugging ID for traces */
+
+@@ -1478,19 +1472,29 @@ extern void __exit afs_clean_up_permit_c
+ extern spinlock_t afs_server_peer_lock;
+
+ struct afs_server *afs_find_server(const struct rxrpc_peer *peer);
+-extern struct afs_server *afs_find_server_by_uuid(struct afs_net *, const uuid_t *);
+ extern struct afs_server *afs_lookup_server(struct afs_cell *, struct key *, const uuid_t *, u32);
+ extern struct afs_server *afs_get_server(struct afs_server *, enum afs_server_trace);
+-extern struct afs_server *afs_use_server(struct afs_server *, enum afs_server_trace);
+-extern void afs_unuse_server(struct afs_net *, struct afs_server *, enum afs_server_trace);
+-extern void afs_unuse_server_notime(struct afs_net *, struct afs_server *, enum afs_server_trace);
++struct afs_server *afs_use_server(struct afs_server *server, bool activate,
++ enum afs_server_trace reason);
++void afs_unuse_server(struct afs_net *net, struct afs_server *server,
++ enum afs_server_trace reason);
++void afs_unuse_server_notime(struct afs_net *net, struct afs_server *server,
++ enum afs_server_trace reason);
+ extern void afs_put_server(struct afs_net *, struct afs_server *, enum afs_server_trace);
+-extern void afs_manage_servers(struct work_struct *);
+-extern void afs_servers_timer(struct timer_list *);
++void afs_purge_servers(struct afs_cell *cell);
+ extern void afs_fs_probe_timer(struct timer_list *);
+-extern void __net_exit afs_purge_servers(struct afs_net *);
++void __net_exit afs_wait_for_servers(struct afs_net *net);
+ bool afs_check_server_record(struct afs_operation *op, struct afs_server *server, struct key *key);
+
++static inline void afs_see_server(struct afs_server *server, enum afs_server_trace trace)
++{
++ int r = refcount_read(&server->ref);
++ int a = atomic_read(&server->active);
++
++ trace_afs_server(server->debug_id, r, a, trace);
++
++}
++
+ static inline void afs_inc_servers_outstanding(struct afs_net *net)
+ {
+ atomic_inc(&net->servers_outstanding);
+--- a/fs/afs/main.c
++++ b/fs/afs/main.c
+@@ -86,16 +86,10 @@ static int __net_init afs_net_init(struc
+ INIT_HLIST_HEAD(&net->proc_cells);
+
+ seqlock_init(&net->fs_lock);
+- net->fs_servers = RB_ROOT;
+ INIT_LIST_HEAD(&net->fs_probe_fast);
+ INIT_LIST_HEAD(&net->fs_probe_slow);
+ INIT_HLIST_HEAD(&net->fs_proc);
+
+- INIT_HLIST_HEAD(&net->fs_addresses);
+- seqlock_init(&net->fs_addr_lock);
+-
+- INIT_WORK(&net->fs_manager, afs_manage_servers);
+- timer_setup(&net->fs_timer, afs_servers_timer, 0);
+ INIT_WORK(&net->fs_prober, afs_fs_probe_dispatcher);
+ timer_setup(&net->fs_probe_timer, afs_fs_probe_timer, 0);
+ atomic_set(&net->servers_outstanding, 1);
+@@ -131,7 +125,7 @@ error_open_socket:
+ net->live = false;
+ afs_fs_probe_cleanup(net);
+ afs_cell_purge(net);
+- afs_purge_servers(net);
++ afs_wait_for_servers(net);
+ error_cell_init:
+ net->live = false;
+ afs_proc_cleanup(net);
+@@ -153,7 +147,7 @@ static void __net_exit afs_net_exit(stru
+ net->live = false;
+ afs_fs_probe_cleanup(net);
+ afs_cell_purge(net);
+- afs_purge_servers(net);
++ afs_wait_for_servers(net);
+ afs_close_socket(net);
+ afs_proc_cleanup(net);
+ afs_put_sysnames(net->sysnames);
+--- a/fs/afs/server.c
++++ b/fs/afs/server.c
+@@ -14,9 +14,9 @@
+ static unsigned afs_server_gc_delay = 10; /* Server record timeout in seconds */
+ static atomic_t afs_server_debug_id;
+
+-static struct afs_server *afs_maybe_use_server(struct afs_server *,
+- enum afs_server_trace);
+ static void __afs_put_server(struct afs_net *, struct afs_server *);
++static void afs_server_timer(struct timer_list *timer);
++static void afs_server_destroyer(struct work_struct *work);
+
+ /*
+ * Find a server by one of its addresses.
+@@ -27,148 +27,91 @@ struct afs_server *afs_find_server(const
+
+ if (!server)
+ return NULL;
+- return afs_maybe_use_server(server, afs_server_trace_use_cm_call);
++ return afs_use_server(server, false, afs_server_trace_use_cm_call);
+ }
+
+ /*
+- * Look up a server by its UUID and mark it active.
++ * Look up a server by its UUID and mark it active. The caller must hold
++ * cell->fs_lock.
+ */
+-struct afs_server *afs_find_server_by_uuid(struct afs_net *net, const uuid_t *uuid)
++static struct afs_server *afs_find_server_by_uuid(struct afs_cell *cell, const uuid_t *uuid)
+ {
+- struct afs_server *server = NULL;
++ struct afs_server *server;
+ struct rb_node *p;
+- int diff, seq = 1;
++ int diff;
+
+ _enter("%pU", uuid);
+
+- do {
+- /* Unfortunately, rbtree walking doesn't give reliable results
+- * under just the RCU read lock, so we have to check for
+- * changes.
+- */
+- if (server)
+- afs_unuse_server(net, server, afs_server_trace_unuse_uuid_rsq);
+- server = NULL;
+- seq++; /* 2 on the 1st/lockless path, otherwise odd */
+- read_seqbegin_or_lock(&net->fs_lock, &seq);
+-
+- p = net->fs_servers.rb_node;
+- while (p) {
+- server = rb_entry(p, struct afs_server, uuid_rb);
+-
+- diff = memcmp(uuid, &server->uuid, sizeof(*uuid));
+- if (diff < 0) {
+- p = p->rb_left;
+- } else if (diff > 0) {
+- p = p->rb_right;
+- } else {
+- afs_use_server(server, afs_server_trace_use_by_uuid);
+- break;
+- }
++ p = cell->fs_servers.rb_node;
++ while (p) {
++ server = rb_entry(p, struct afs_server, uuid_rb);
+
+- server = NULL;
++ diff = memcmp(uuid, &server->uuid, sizeof(*uuid));
++ if (diff < 0) {
++ p = p->rb_left;
++ } else if (diff > 0) {
++ p = p->rb_right;
++ } else {
++ if (test_bit(AFS_SERVER_FL_UNCREATED, &server->flags))
++ return NULL; /* Need a write lock */
++ afs_use_server(server, true, afs_server_trace_use_by_uuid);
++ return server;
+ }
+- } while (need_seqretry(&net->fs_lock, seq));
+-
+- done_seqretry(&net->fs_lock, seq);
++ }
+
+- _leave(" = %p", server);
+- return server;
++ return NULL;
+ }
+
+ /*
+- * Install a server record in the namespace tree. If there's a clash, we stick
+- * it into a list anchored on whichever afs_server struct is actually in the
+- * tree.
++ * Install a server record in the cell tree. The caller must hold an exclusive
++ * lock on cell->fs_lock.
+ */
+ static struct afs_server *afs_install_server(struct afs_cell *cell,
+- struct afs_server *candidate)
++ struct afs_server **candidate)
+ {
+- const struct afs_endpoint_state *estate;
+- const struct afs_addr_list *alist;
+- struct afs_server *server, *next;
++ struct afs_server *server;
+ struct afs_net *net = cell->net;
+ struct rb_node **pp, *p;
+ int diff;
+
+ _enter("%p", candidate);
+
+- write_seqlock(&net->fs_lock);
+-
+ /* Firstly install the server in the UUID lookup tree */
+- pp = &net->fs_servers.rb_node;
++ pp = &cell->fs_servers.rb_node;
+ p = NULL;
+ while (*pp) {
+ p = *pp;
+ _debug("- consider %p", p);
+ server = rb_entry(p, struct afs_server, uuid_rb);
+- diff = memcmp(&candidate->uuid, &server->uuid, sizeof(uuid_t));
+- if (diff < 0) {
++ diff = memcmp(&(*candidate)->uuid, &server->uuid, sizeof(uuid_t));
++ if (diff < 0)
+ pp = &(*pp)->rb_left;
+- } else if (diff > 0) {
++ else if (diff > 0)
+ pp = &(*pp)->rb_right;
+- } else {
+- if (server->cell == cell)
+- goto exists;
+-
+- /* We have the same UUID representing servers in
+- * different cells. Append the new server to the list.
+- */
+- for (;;) {
+- next = rcu_dereference_protected(
+- server->uuid_next,
+- lockdep_is_held(&net->fs_lock.lock));
+- if (!next)
+- break;
+- server = next;
+- }
+- rcu_assign_pointer(server->uuid_next, candidate);
+- candidate->uuid_prev = server;
+- server = candidate;
+- goto added_dup;
+- }
++ else
++ goto exists;
+ }
+
+- server = candidate;
++ server = *candidate;
++ *candidate = NULL;
+ rb_link_node(&server->uuid_rb, p, pp);
+- rb_insert_color(&server->uuid_rb, &net->fs_servers);
++ rb_insert_color(&server->uuid_rb, &cell->fs_servers);
++ write_seqlock(&net->fs_lock);
+ hlist_add_head_rcu(&server->proc_link, &net->fs_proc);
++ write_sequnlock(&net->fs_lock);
+
+ afs_get_cell(cell, afs_cell_trace_get_server);
+
+-added_dup:
+- write_seqlock(&net->fs_addr_lock);
+- estate = rcu_dereference_protected(server->endpoint_state,
+- lockdep_is_held(&net->fs_addr_lock.lock));
+- alist = estate->addresses;
+-
+- /* Secondly, if the server has any IPv4 and/or IPv6 addresses, install
+- * it in the IPv4 and/or IPv6 reverse-map lists.
+- *
+- * TODO: For speed we want to use something other than a flat list
+- * here; even sorting the list in terms of lowest address would help a
+- * bit, but anything we might want to do gets messy and memory
+- * intensive.
+- */
+- if (alist->nr_addrs > 0)
+- hlist_add_head_rcu(&server->addr_link, &net->fs_addresses);
+-
+- write_sequnlock(&net->fs_addr_lock);
+-
+ exists:
+- afs_get_server(server, afs_server_trace_get_install);
+- write_sequnlock(&net->fs_lock);
++ afs_use_server(server, true, afs_server_trace_get_install);
+ return server;
+ }
+
+ /*
+- * Allocate a new server record and mark it active.
++ * Allocate a new server record and mark it as active but uncreated.
+ */
+-static struct afs_server *afs_alloc_server(struct afs_cell *cell,
+- const uuid_t *uuid,
+- struct afs_addr_list *alist)
++static struct afs_server *afs_alloc_server(struct afs_cell *cell, const uuid_t *uuid)
+ {
+- struct afs_endpoint_state *estate;
+ struct afs_server *server;
+ struct afs_net *net = cell->net;
+
+@@ -176,65 +119,49 @@ static struct afs_server *afs_alloc_serv
+
+ server = kzalloc(sizeof(struct afs_server), GFP_KERNEL);
+ if (!server)
+- goto enomem;
+-
+- estate = kzalloc(sizeof(struct afs_endpoint_state), GFP_KERNEL);
+- if (!estate)
+- goto enomem_server;
++ return NULL;
+
+ refcount_set(&server->ref, 1);
+- atomic_set(&server->active, 1);
++ atomic_set(&server->active, 0);
++ __set_bit(AFS_SERVER_FL_UNCREATED, &server->flags);
+ server->debug_id = atomic_inc_return(&afs_server_debug_id);
+- server->addr_version = alist->version;
+ server->uuid = *uuid;
+ rwlock_init(&server->fs_lock);
++ INIT_WORK(&server->destroyer, &afs_server_destroyer);
++ timer_setup(&server->timer, afs_server_timer, 0);
+ INIT_LIST_HEAD(&server->volumes);
+ init_waitqueue_head(&server->probe_wq);
+ INIT_LIST_HEAD(&server->probe_link);
++ INIT_HLIST_NODE(&server->proc_link);
+ spin_lock_init(&server->probe_lock);
+ server->cell = cell;
+ server->rtt = UINT_MAX;
+ server->service_id = FS_SERVICE;
+-
+ server->probe_counter = 1;
+ server->probed_at = jiffies - LONG_MAX / 2;
+- refcount_set(&estate->ref, 1);
+- estate->addresses = alist;
+- estate->server_id = server->debug_id;
+- estate->probe_seq = 1;
+- rcu_assign_pointer(server->endpoint_state, estate);
+
+ afs_inc_servers_outstanding(net);
+- trace_afs_server(server->debug_id, 1, 1, afs_server_trace_alloc);
+- trace_afs_estate(estate->server_id, estate->probe_seq, refcount_read(&estate->ref),
+- afs_estate_trace_alloc_server);
+ _leave(" = %p", server);
+ return server;
+-
+-enomem_server:
+- kfree(server);
+-enomem:
+- _leave(" = NULL [nomem]");
+- return NULL;
+ }
+
+ /*
+ * Look up an address record for a server
+ */
+-static struct afs_addr_list *afs_vl_lookup_addrs(struct afs_cell *cell,
+- struct key *key, const uuid_t *uuid)
++static struct afs_addr_list *afs_vl_lookup_addrs(struct afs_server *server,
++ struct key *key)
+ {
+ struct afs_vl_cursor vc;
+ struct afs_addr_list *alist = NULL;
+ int ret;
+
+ ret = -ERESTARTSYS;
+- if (afs_begin_vlserver_operation(&vc, cell, key)) {
++ if (afs_begin_vlserver_operation(&vc, server->cell, key)) {
+ while (afs_select_vlserver(&vc)) {
+ if (test_bit(AFS_VLSERVER_FL_IS_YFS, &vc.server->flags))
+- alist = afs_yfsvl_get_endpoints(&vc, uuid);
++ alist = afs_yfsvl_get_endpoints(&vc, &server->uuid);
+ else
+- alist = afs_vl_get_addrs_u(&vc, uuid);
++ alist = afs_vl_get_addrs_u(&vc, &server->uuid);
+ }
+
+ ret = afs_end_vlserver_operation(&vc);
+@@ -250,67 +177,116 @@ static struct afs_addr_list *afs_vl_look
+ struct afs_server *afs_lookup_server(struct afs_cell *cell, struct key *key,
+ const uuid_t *uuid, u32 addr_version)
+ {
+- struct afs_addr_list *alist;
+- struct afs_server *server, *candidate;
++ struct afs_addr_list *alist = NULL;
++ struct afs_server *server, *candidate = NULL;
++ bool creating = false;
++ int ret;
+
+ _enter("%p,%pU", cell->net, uuid);
+
+- server = afs_find_server_by_uuid(cell->net, uuid);
++ down_read(&cell->fs_lock);
++ server = afs_find_server_by_uuid(cell, uuid);
++ /* Won't see servers marked uncreated. */
++ up_read(&cell->fs_lock);
++
+ if (server) {
++ timer_delete_sync(&server->timer);
++ if (test_bit(AFS_SERVER_FL_CREATING, &server->flags))
++ goto wait_for_creation;
+ if (server->addr_version != addr_version)
+ set_bit(AFS_SERVER_FL_NEEDS_UPDATE, &server->flags);
+ return server;
+ }
+
+- alist = afs_vl_lookup_addrs(cell, key, uuid);
+- if (IS_ERR(alist))
+- return ERR_CAST(alist);
+-
+- candidate = afs_alloc_server(cell, uuid, alist);
++ candidate = afs_alloc_server(cell, uuid);
+ if (!candidate) {
+ afs_put_addrlist(alist, afs_alist_trace_put_server_oom);
+ return ERR_PTR(-ENOMEM);
+ }
+
+- server = afs_install_server(cell, candidate);
+- if (server != candidate) {
+- afs_put_addrlist(alist, afs_alist_trace_put_server_dup);
++ down_write(&cell->fs_lock);
++ server = afs_install_server(cell, &candidate);
++ if (test_bit(AFS_SERVER_FL_CREATING, &server->flags)) {
++ /* We need to wait for creation to complete. */
++ up_write(&cell->fs_lock);
++ goto wait_for_creation;
++ }
++ if (test_bit(AFS_SERVER_FL_UNCREATED, &server->flags)) {
++ set_bit(AFS_SERVER_FL_CREATING, &server->flags);
++ clear_bit(AFS_SERVER_FL_UNCREATED, &server->flags);
++ creating = true;
++ }
++ up_write(&cell->fs_lock);
++ timer_delete_sync(&server->timer);
++
++ /* If we get to create the server, we look up the addresses and then
++ * immediately dispatch an asynchronous probe to each interface on the
++ * fileserver. This will make sure the repeat-probing service is
++ * started.
++ */
++ if (creating) {
++ alist = afs_vl_lookup_addrs(server, key);
++ if (IS_ERR(alist)) {
++ ret = PTR_ERR(alist);
++ goto create_failed;
++ }
++
++ ret = afs_fs_probe_fileserver(cell->net, server, alist, key);
++ if (ret)
++ goto create_failed;
++
++ clear_and_wake_up_bit(AFS_SERVER_FL_CREATING, &server->flags);
++ }
++
++out:
++ afs_put_addrlist(alist, afs_alist_trace_put_server_create);
++ if (candidate) {
++ kfree(rcu_access_pointer(server->endpoint_state));
+ kfree(candidate);
+- } else {
+- /* Immediately dispatch an asynchronous probe to each interface
+- * on the fileserver. This will make sure the repeat-probing
+- * service is started.
+- */
+- afs_fs_probe_fileserver(cell->net, server, alist, key);
++ afs_dec_servers_outstanding(cell->net);
+ }
++ return server ?: ERR_PTR(ret);
+
+- return server;
+-}
++wait_for_creation:
++ afs_see_server(server, afs_server_trace_wait_create);
++ wait_on_bit(&server->flags, AFS_SERVER_FL_CREATING, TASK_UNINTERRUPTIBLE);
++ if (test_bit_acquire(AFS_SERVER_FL_UNCREATED, &server->flags)) {
++ /* Barrier: read flag before error */
++ ret = READ_ONCE(server->create_error);
++ afs_put_server(cell->net, server, afs_server_trace_unuse_create_fail);
++ server = NULL;
++ goto out;
++ }
+
+-/*
+- * Set the server timer to fire after a given delay, assuming it's not already
+- * set for an earlier time.
+- */
+-static void afs_set_server_timer(struct afs_net *net, time64_t delay)
+-{
+- if (net->live) {
+- afs_inc_servers_outstanding(net);
+- if (timer_reduce(&net->fs_timer, jiffies + delay * HZ))
+- afs_dec_servers_outstanding(net);
++ ret = 0;
++ goto out;
++
++create_failed:
++ down_write(&cell->fs_lock);
++
++ WRITE_ONCE(server->create_error, ret);
++ smp_wmb(); /* Barrier: set error before flag. */
++ set_bit(AFS_SERVER_FL_UNCREATED, &server->flags);
++
++ clear_and_wake_up_bit(AFS_SERVER_FL_CREATING, &server->flags);
++
++ if (test_bit(AFS_SERVER_FL_UNCREATED, &server->flags)) {
++ clear_bit(AFS_SERVER_FL_UNCREATED, &server->flags);
++ creating = true;
+ }
++ afs_unuse_server(cell->net, server, afs_server_trace_unuse_create_fail);
++ server = NULL;
++
++ up_write(&cell->fs_lock);
++ goto out;
+ }
+
+ /*
+- * Server management timer. We have an increment on fs_outstanding that we
+- * need to pass along to the work item.
++ * Set/reduce a server's timer.
+ */
+-void afs_servers_timer(struct timer_list *timer)
++static void afs_set_server_timer(struct afs_server *server, unsigned int delay_secs)
+ {
+- struct afs_net *net = container_of(timer, struct afs_net, fs_timer);
+-
+- _enter("");
+- if (!queue_work(afs_wq, &net->fs_manager))
+- afs_dec_servers_outstanding(net);
++ mod_timer(&server->timer, jiffies + delay_secs * HZ);
+ }
+
+ /*
+@@ -329,32 +305,20 @@ struct afs_server *afs_get_server(struct
+ }
+
+ /*
+- * Try to get a reference on a server object.
++ * Get an active count on a server object and maybe remove from the inactive
++ * list.
+ */
+-static struct afs_server *afs_maybe_use_server(struct afs_server *server,
+- enum afs_server_trace reason)
+-{
+- unsigned int a;
+- int r;
+-
+- if (!__refcount_inc_not_zero(&server->ref, &r))
+- return NULL;
+-
+- a = atomic_inc_return(&server->active);
+- trace_afs_server(server->debug_id, r + 1, a, reason);
+- return server;
+-}
+-
+-/*
+- * Get an active count on a server object.
+- */
+-struct afs_server *afs_use_server(struct afs_server *server, enum afs_server_trace reason)
++struct afs_server *afs_use_server(struct afs_server *server, bool activate,
++ enum afs_server_trace reason)
+ {
+ unsigned int a;
+ int r;
+
+ __refcount_inc(&server->ref, &r);
+ a = atomic_inc_return(&server->active);
++ if (a == 1 && activate &&
++ !test_bit(AFS_SERVER_FL_EXPIRED, &server->flags))
++ del_timer(&server->timer);
+
+ trace_afs_server(server->debug_id, r + 1, a, reason);
+ return server;
+@@ -388,13 +352,16 @@ void afs_put_server(struct afs_net *net,
+ void afs_unuse_server_notime(struct afs_net *net, struct afs_server *server,
+ enum afs_server_trace reason)
+ {
+- if (server) {
+- unsigned int active = atomic_dec_return(&server->active);
++ if (!server)
++ return;
+
+- if (active == 0)
+- afs_set_server_timer(net, afs_server_gc_delay);
+- afs_put_server(net, server, reason);
++ if (atomic_dec_and_test(&server->active)) {
++ if (test_bit(AFS_SERVER_FL_EXPIRED, &server->flags) ||
++ READ_ONCE(server->cell->state) >= AFS_CELL_FAILED)
++ schedule_work(&server->destroyer);
+ }
++
++ afs_put_server(net, server, reason);
+ }
+
+ /*
+@@ -403,10 +370,22 @@ void afs_unuse_server_notime(struct afs_
+ void afs_unuse_server(struct afs_net *net, struct afs_server *server,
+ enum afs_server_trace reason)
+ {
+- if (server) {
+- server->unuse_time = ktime_get_real_seconds();
+- afs_unuse_server_notime(net, server, reason);
++ if (!server)
++ return;
++
++ if (atomic_dec_and_test(&server->active)) {
++ if (!test_bit(AFS_SERVER_FL_EXPIRED, &server->flags) &&
++ READ_ONCE(server->cell->state) < AFS_CELL_FAILED) {
++ time64_t unuse_time = ktime_get_real_seconds();
++
++ server->unuse_time = unuse_time;
++ afs_set_server_timer(server, afs_server_gc_delay);
++ } else {
++ schedule_work(&server->destroyer);
++ }
+ }
++
++ afs_put_server(net, server, reason);
+ }
+
+ static void afs_server_rcu(struct rcu_head *rcu)
+@@ -436,166 +415,119 @@ static void afs_give_up_callbacks(struct
+ }
+
+ /*
+- * destroy a dead server
++ * Check to see if the server record has expired.
+ */
+-static void afs_destroy_server(struct afs_net *net, struct afs_server *server)
++static bool afs_has_server_expired(const struct afs_server *server)
+ {
+- struct afs_endpoint_state *estate;
++ time64_t expires_at;
+
+- if (test_bit(AFS_SERVER_FL_MAY_HAVE_CB, &server->flags))
+- afs_give_up_callbacks(net, server);
++ if (atomic_read(&server->active))
++ return false;
+
+- /* Unbind the rxrpc_peer records from the server. */
+- estate = rcu_access_pointer(server->endpoint_state);
+- if (estate)
+- afs_set_peer_appdata(server, estate->addresses, NULL);
++ if (server->cell->net->live ||
++ server->cell->state >= AFS_CELL_FAILED) {
++ trace_afs_server(server->debug_id, refcount_read(&server->ref),
++ 0, afs_server_trace_purging);
++ return true;
++ }
+
+- afs_put_server(net, server, afs_server_trace_destroy);
++ expires_at = server->unuse_time;
++ if (!test_bit(AFS_SERVER_FL_VL_FAIL, &server->flags) &&
++ !test_bit(AFS_SERVER_FL_NOT_FOUND, &server->flags))
++ expires_at += afs_server_gc_delay;
++
++ return ktime_get_real_seconds() > expires_at;
+ }
+
+ /*
+- * Garbage collect any expired servers.
++ * Remove a server record from it's parent cell's database.
+ */
+-static void afs_gc_servers(struct afs_net *net, struct afs_server *gc_list)
++static bool afs_remove_server_from_cell(struct afs_server *server)
+ {
+- struct afs_server *server, *next, *prev;
+- int active;
++ struct afs_cell *cell = server->cell;
+
+- while ((server = gc_list)) {
+- gc_list = server->gc_next;
+-
+- write_seqlock(&net->fs_lock);
+-
+- active = atomic_read(&server->active);
+- if (active == 0) {
+- trace_afs_server(server->debug_id, refcount_read(&server->ref),
+- active, afs_server_trace_gc);
+- next = rcu_dereference_protected(
+- server->uuid_next, lockdep_is_held(&net->fs_lock.lock));
+- prev = server->uuid_prev;
+- if (!prev) {
+- /* The one at the front is in the tree */
+- if (!next) {
+- rb_erase(&server->uuid_rb, &net->fs_servers);
+- } else {
+- rb_replace_node_rcu(&server->uuid_rb,
+- &next->uuid_rb,
+- &net->fs_servers);
+- next->uuid_prev = NULL;
+- }
+- } else {
+- /* This server is not at the front */
+- rcu_assign_pointer(prev->uuid_next, next);
+- if (next)
+- next->uuid_prev = prev;
+- }
+-
+- list_del(&server->probe_link);
+- hlist_del_rcu(&server->proc_link);
+- if (!hlist_unhashed(&server->addr_link))
+- hlist_del_rcu(&server->addr_link);
+- }
+- write_sequnlock(&net->fs_lock);
++ down_write(&cell->fs_lock);
+
+- if (active == 0)
+- afs_destroy_server(net, server);
++ if (!afs_has_server_expired(server)) {
++ up_write(&cell->fs_lock);
++ return false;
+ }
++
++ set_bit(AFS_SERVER_FL_EXPIRED, &server->flags);
++ _debug("expire %pU %u", &server->uuid, atomic_read(&server->active));
++ afs_see_server(server, afs_server_trace_see_expired);
++ rb_erase(&server->uuid_rb, &cell->fs_servers);
++ up_write(&cell->fs_lock);
++ return true;
+ }
+
+-/*
+- * Manage the records of servers known to be within a network namespace. This
+- * includes garbage collecting unused servers.
+- *
+- * Note also that we were given an increment on net->servers_outstanding by
+- * whoever queued us that we need to deal with before returning.
+- */
+-void afs_manage_servers(struct work_struct *work)
++static void afs_server_destroyer(struct work_struct *work)
+ {
+- struct afs_net *net = container_of(work, struct afs_net, fs_manager);
+- struct afs_server *gc_list = NULL;
+- struct rb_node *cursor;
+- time64_t now = ktime_get_real_seconds(), next_manage = TIME64_MAX;
+- bool purging = !net->live;
++ struct afs_endpoint_state *estate;
++ struct afs_server *server = container_of(work, struct afs_server, destroyer);
++ struct afs_net *net = server->cell->net;
+
+- _enter("");
++ afs_see_server(server, afs_server_trace_see_destroyer);
+
+- /* Trawl the server list looking for servers that have expired from
+- * lack of use.
+- */
+- read_seqlock_excl(&net->fs_lock);
++ if (test_bit(AFS_SERVER_FL_EXPIRED, &server->flags))
++ return;
+
+- for (cursor = rb_first(&net->fs_servers); cursor; cursor = rb_next(cursor)) {
+- struct afs_server *server =
+- rb_entry(cursor, struct afs_server, uuid_rb);
+- int active = atomic_read(&server->active);
+-
+- _debug("manage %pU %u", &server->uuid, active);
+-
+- if (purging) {
+- trace_afs_server(server->debug_id, refcount_read(&server->ref),
+- active, afs_server_trace_purging);
+- if (active != 0)
+- pr_notice("Can't purge s=%08x\n", server->debug_id);
+- }
++ if (!afs_remove_server_from_cell(server))
++ return;
+
+- if (active == 0) {
+- time64_t expire_at = server->unuse_time;
++ timer_shutdown_sync(&server->timer);
++ cancel_work(&server->destroyer);
+
+- if (!test_bit(AFS_SERVER_FL_VL_FAIL, &server->flags) &&
+- !test_bit(AFS_SERVER_FL_NOT_FOUND, &server->flags))
+- expire_at += afs_server_gc_delay;
+- if (purging || expire_at <= now) {
+- server->gc_next = gc_list;
+- gc_list = server;
+- } else if (expire_at < next_manage) {
+- next_manage = expire_at;
+- }
+- }
+- }
++ if (test_bit(AFS_SERVER_FL_MAY_HAVE_CB, &server->flags))
++ afs_give_up_callbacks(net, server);
+
+- read_sequnlock_excl(&net->fs_lock);
++ /* Unbind the rxrpc_peer records from the server. */
++ estate = rcu_access_pointer(server->endpoint_state);
++ if (estate)
++ afs_set_peer_appdata(server, estate->addresses, NULL);
+
+- /* Update the timer on the way out. We have to pass an increment on
+- * servers_outstanding in the namespace that we are in to the timer or
+- * the work scheduler.
+- */
+- if (!purging && next_manage < TIME64_MAX) {
+- now = ktime_get_real_seconds();
++ write_seqlock(&net->fs_lock);
++ list_del_init(&server->probe_link);
++ if (!hlist_unhashed(&server->proc_link))
++ hlist_del_rcu(&server->proc_link);
++ write_sequnlock(&net->fs_lock);
+
+- if (next_manage - now <= 0) {
+- if (queue_work(afs_wq, &net->fs_manager))
+- afs_inc_servers_outstanding(net);
+- } else {
+- afs_set_server_timer(net, next_manage - now);
+- }
+- }
++ afs_put_server(net, server, afs_server_trace_destroy);
++}
+
+- afs_gc_servers(net, gc_list);
++static void afs_server_timer(struct timer_list *timer)
++{
++ struct afs_server *server = container_of(timer, struct afs_server, timer);
+
+- afs_dec_servers_outstanding(net);
+- _leave(" [%d]", atomic_read(&net->servers_outstanding));
++ afs_see_server(server, afs_server_trace_see_timer);
++ if (!test_bit(AFS_SERVER_FL_EXPIRED, &server->flags))
++ schedule_work(&server->destroyer);
+ }
+
+-static void afs_queue_server_manager(struct afs_net *net)
++/*
++ * Wake up all the servers in a cell so that they can purge themselves.
++ */
++void afs_purge_servers(struct afs_cell *cell)
+ {
+- afs_inc_servers_outstanding(net);
+- if (!queue_work(afs_wq, &net->fs_manager))
+- afs_dec_servers_outstanding(net);
++ struct afs_server *server;
++ struct rb_node *rb;
++
++ down_read(&cell->fs_lock);
++ for (rb = rb_first(&cell->fs_servers); rb; rb = rb_next(rb)) {
++ server = rb_entry(rb, struct afs_server, uuid_rb);
++ afs_see_server(server, afs_server_trace_see_purge);
++ schedule_work(&server->destroyer);
++ }
++ up_read(&cell->fs_lock);
+ }
+
+ /*
+- * Purge list of servers.
++ * Wait for outstanding servers.
+ */
+-void afs_purge_servers(struct afs_net *net)
++void afs_wait_for_servers(struct afs_net *net)
+ {
+ _enter("");
+
+- if (del_timer_sync(&net->fs_timer))
+- afs_dec_servers_outstanding(net);
+-
+- afs_queue_server_manager(net);
+-
+- _debug("wait");
+ atomic_dec(&net->servers_outstanding);
+ wait_var_event(&net->servers_outstanding,
+ !atomic_read(&net->servers_outstanding));
+@@ -619,7 +551,7 @@ static noinline bool afs_update_server_r
+ atomic_read(&server->active),
+ afs_server_trace_update);
+
+- alist = afs_vl_lookup_addrs(op->volume->cell, op->key, &server->uuid);
++ alist = afs_vl_lookup_addrs(server, op->key);
+ if (IS_ERR(alist)) {
+ rcu_read_lock();
+ estate = rcu_dereference(server->endpoint_state);
+--- a/fs/afs/server_list.c
++++ b/fs/afs/server_list.c
+@@ -97,8 +97,8 @@ struct afs_server_list *afs_alloc_server
+ break;
+ if (j < slist->nr_servers) {
+ if (slist->servers[j].server == server) {
+- afs_unuse_server(volume->cell->net, server,
+- afs_server_trace_unuse_slist_isort);
++ afs_unuse_server_notime(volume->cell->net, server,
++ afs_server_trace_unuse_slist_isort);
+ continue;
+ }
+
+--- a/include/trace/events/afs.h
++++ b/include/trace/events/afs.h
+@@ -127,7 +127,6 @@ enum yfs_cm_operation {
+ E_(afs_call_trace_work, "QUEUE")
+
+ #define afs_server_traces \
+- EM(afs_server_trace_alloc, "ALLOC ") \
+ EM(afs_server_trace_callback, "CALLBACK ") \
+ EM(afs_server_trace_destroy, "DESTROY ") \
+ EM(afs_server_trace_free, "FREE ") \
+@@ -137,12 +136,14 @@ enum yfs_cm_operation {
+ EM(afs_server_trace_purging, "PURGE ") \
+ EM(afs_server_trace_put_cbi, "PUT cbi ") \
+ EM(afs_server_trace_put_probe, "PUT probe") \
++ EM(afs_server_trace_see_destroyer, "SEE destr") \
+ EM(afs_server_trace_see_expired, "SEE expd ") \
++ EM(afs_server_trace_see_purge, "SEE purge") \
++ EM(afs_server_trace_see_timer, "SEE timer") \
+ EM(afs_server_trace_unuse_call, "UNU call ") \
+ EM(afs_server_trace_unuse_create_fail, "UNU cfail") \
+ EM(afs_server_trace_unuse_slist, "UNU slist") \
+ EM(afs_server_trace_unuse_slist_isort, "UNU isort") \
+- EM(afs_server_trace_unuse_uuid_rsq, "PUT u-req") \
+ EM(afs_server_trace_update, "UPDATE ") \
+ EM(afs_server_trace_use_by_uuid, "USE uuid ") \
+ EM(afs_server_trace_use_cm_call, "USE cm-cl") \
+@@ -229,7 +230,7 @@ enum yfs_cm_operation {
+ EM(afs_alist_trace_put_getaddru, "PUT GtAdrU") \
+ EM(afs_alist_trace_put_parse_empty, "PUT p-empt") \
+ EM(afs_alist_trace_put_parse_error, "PUT p-err ") \
+- EM(afs_alist_trace_put_server_dup, "PUT sv-dup") \
++ EM(afs_alist_trace_put_server_create, "PUT sv-crt") \
+ EM(afs_alist_trace_put_server_oom, "PUT sv-oom") \
+ EM(afs_alist_trace_put_server_update, "PUT sv-upd") \
+ EM(afs_alist_trace_put_vlgetcaps, "PUT vgtcap") \
--- /dev/null
+From stable+bounces-290954-greg=kroah.com@vger.kernel.org Thu Jul 30 03:05:15 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:37 -0400
+Subject: afs: Fix dynamic lookup to fail on cell lookup failure
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Markus Suvanto <markus.suvanto@gmail.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, Christian Brauner <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-8-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 330e2c514823008b22e6afd2055715bc46dd8d55 ]
+
+When a process tries to access an entry in /afs, normally what happens is
+that an automount dentry is created by ->lookup() and then triggered, which
+jumps through the ->d_automount() op. Currently, afs_dynroot_lookup() does
+not do cell DNS lookup, leaving that to afs_d_automount() to perform -
+however, it is possible to use access() or stat() on the automount point,
+which will always return successfully, have briefly created an afs_cell
+record if one did not already exist.
+
+This means that something like:
+
+ test -d "/afs/.west" && echo Directory exists
+
+will print "Directory exists" even though no such cell is configured. This
+breaks the "west" python module available on PIP as it expects this access
+to fail.
+
+Now, it could be possible to make afs_dynroot_lookup() perform the DNS[*]
+lookup, but that would make "ls --color /afs" do this for each cell in /afs
+that is listed but not yet probed. kafs-client, probably wrongly, preloads
+the entire cell database and all the known cells are then listed in /afs -
+and doing ls /afs would be very, very slow, especially if any cell supplied
+addresses but was wholly inaccessible.
+
+ [*] When I say "DNS", actually read getaddrinfo(), which could use any one
+ of a host of mechanisms. Could also use static configuration.
+
+To fix this, make the following changes:
+
+ (1) Create an enum to specify the origination point of a call to
+ afs_lookup_cell() and pass this value into that function in place of
+ the "excl" parameter (which can be derived from it). There are six
+ points of origination:
+
+ - Cell preload through /proc/net/afs/cells
+ - Root cell config through /proc/net/afs/rootcell
+ - Lookup in dynamic root
+ - Automount trigger
+ - Direct mount with mount() syscall
+ - Alias check where YFS tells us the cell name is different
+
+ (2) Add an extra state into the afs_cell state machine to indicate a cell
+ that's been initialised, but not yet looked up. This is separate from
+ one that can be considered active and has been looked up at least
+ once.
+
+ (3) Make afs_lookup_cell() vary its behaviour more, depending on where it
+ was called from:
+
+ If called from preload or root cell config, DNS lookup will not happen
+ until we definitely want to use the cell (dynroot mount, automount,
+ direct mount or alias check). The cell will appear in /afs but stat()
+ won't trigger DNS lookup.
+
+ If the cell already exists, dynroot will not wait for the DNS lookup
+ to complete. If the cell did not already exist, dynroot will wait.
+
+ If called from automount, direct mount or alias check, it will wait
+ for the DNS lookup to complete.
+
+ (4) Make afs_lookup_cell() return an error if lookup failed in one way or
+ another. We try to return -ENOENT if the DNS says the cell does not
+ exist and -EDESTADDRREQ if we couldn't access the DNS.
+
+Reported-by: Markus Suvanto <markus.suvanto@gmail.com>
+Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220685
+Signed-off-by: David Howells <dhowells@redhat.com>
+Link: https://patch.msgid.link/1784747.1761158912@warthog.procyon.org.uk
+Fixes: 1d0b929fc070 ("afs: Change dynroot to create contents on demand")
+Tested-by: Markus Suvanto <markus.suvanto@gmail.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/cell.c | 78 +++++++++++++++++++++++++++++++++++++++++++++---------
+ fs/afs/dynroot.c | 3 +-
+ fs/afs/internal.h | 12 +++++++-
+ fs/afs/mntpt.c | 3 +-
+ fs/afs/proc.c | 3 +-
+ fs/afs/super.c | 2 -
+ fs/afs/vl_alias.c | 3 +-
+ 7 files changed, 86 insertions(+), 18 deletions(-)
+
+--- a/fs/afs/cell.c
++++ b/fs/afs/cell.c
+@@ -228,7 +228,7 @@ error:
+ * @name: The name of the cell.
+ * @namesz: The strlen of the cell name.
+ * @vllist: A colon/comma separated list of numeric IP addresses or NULL.
+- * @excl: T if an error should be given if the cell name already exists.
++ * @reason: The reason we're doing the lookup
+ * @trace: The reason to be logged if the lookup is successful.
+ *
+ * Look up a cell record by name and query the DNS for VL server addresses if
+@@ -238,7 +238,8 @@ error:
+ */
+ struct afs_cell *afs_lookup_cell(struct afs_net *net,
+ const char *name, unsigned int namesz,
+- const char *vllist, bool excl,
++ const char *vllist,
++ enum afs_lookup_cell_for reason,
+ enum afs_cell_trace trace)
+ {
+ struct afs_cell *cell, *candidate, *cursor;
+@@ -246,12 +247,18 @@ struct afs_cell *afs_lookup_cell(struct
+ enum afs_cell_state state;
+ int ret, n;
+
+- _enter("%s,%s", name, vllist);
++ _enter("%s,%s,%u", name, vllist, reason);
+
+- if (!excl) {
++ if (reason != AFS_LOOKUP_CELL_PRELOAD) {
+ cell = afs_find_cell(net, name, namesz, trace);
+- if (!IS_ERR(cell))
++ if (!IS_ERR(cell)) {
++ if (reason == AFS_LOOKUP_CELL_DYNROOT)
++ goto no_wait;
++ if (cell->state == AFS_CELL_SETTING_UP ||
++ cell->state == AFS_CELL_UNLOOKED)
++ goto lookup_cell;
+ goto wait_for_cell;
++ }
+ }
+
+ /* Assume we're probably going to create a cell and preallocate and
+@@ -297,26 +304,69 @@ struct afs_cell *afs_lookup_cell(struct
+ rb_insert_color(&cell->net_node, &net->cells);
+ up_write(&net->cells_lock);
+
+- afs_queue_cell(cell, afs_cell_trace_queue_new);
++lookup_cell:
++ if (reason != AFS_LOOKUP_CELL_PRELOAD &&
++ reason != AFS_LOOKUP_CELL_ROOTCELL) {
++ set_bit(AFS_CELL_FL_DO_LOOKUP, &cell->flags);
++ afs_queue_cell(cell, afs_cell_trace_queue_new);
++ }
+
+ wait_for_cell:
+- _debug("wait_for_cell");
+ state = smp_load_acquire(&cell->state); /* vs error */
+- if (state != AFS_CELL_ACTIVE &&
+- state != AFS_CELL_DEAD) {
++ switch (state) {
++ case AFS_CELL_ACTIVE:
++ case AFS_CELL_DEAD:
++ break;
++ case AFS_CELL_UNLOOKED:
++ default:
++ if (reason == AFS_LOOKUP_CELL_PRELOAD ||
++ reason == AFS_LOOKUP_CELL_ROOTCELL)
++ break;
++ _debug("wait_for_cell");
+ afs_see_cell(cell, afs_cell_trace_wait);
+ wait_var_event(&cell->state,
+ ({
+ state = smp_load_acquire(&cell->state); /* vs error */
+ state == AFS_CELL_ACTIVE || state == AFS_CELL_DEAD;
+ }));
++ _debug("waited_for_cell %d %d", cell->state, cell->error);
+ }
+
++no_wait:
+ /* Check the state obtained from the wait check. */
++ state = smp_load_acquire(&cell->state); /* vs error */
+ if (state == AFS_CELL_DEAD) {
+ ret = cell->error;
+ goto error;
+ }
++ if (state == AFS_CELL_ACTIVE) {
++ switch (cell->dns_status) {
++ case DNS_LOOKUP_NOT_DONE:
++ if (cell->dns_source == DNS_RECORD_FROM_CONFIG) {
++ ret = 0;
++ break;
++ }
++ fallthrough;
++ default:
++ ret = -EIO;
++ goto error;
++ case DNS_LOOKUP_GOOD:
++ case DNS_LOOKUP_GOOD_WITH_BAD:
++ ret = 0;
++ break;
++ case DNS_LOOKUP_GOT_NOT_FOUND:
++ ret = -ENOENT;
++ goto error;
++ case DNS_LOOKUP_BAD:
++ ret = -EREMOTEIO;
++ goto error;
++ case DNS_LOOKUP_GOT_LOCAL_FAILURE:
++ case DNS_LOOKUP_GOT_TEMP_FAILURE:
++ case DNS_LOOKUP_GOT_NS_FAILURE:
++ ret = -EDESTADDRREQ;
++ goto error;
++ }
++ }
+
+ _leave(" = %p [cell]", cell);
+ return cell;
+@@ -324,7 +374,7 @@ wait_for_cell:
+ cell_already_exists:
+ _debug("cell exists");
+ cell = cursor;
+- if (excl) {
++ if (reason == AFS_LOOKUP_CELL_PRELOAD) {
+ ret = -EEXIST;
+ } else {
+ afs_use_cell(cursor, trace);
+@@ -383,7 +433,8 @@ int afs_cell_init(struct afs_net *net, c
+ return -EINVAL;
+
+ /* allocate a cell record for the root/workstation cell */
+- new_root = afs_lookup_cell(net, rootcell, len, vllist, false,
++ new_root = afs_lookup_cell(net, rootcell, len, vllist,
++ AFS_LOOKUP_CELL_ROOTCELL,
+ afs_cell_trace_use_lookup_ws);
+ if (IS_ERR(new_root)) {
+ _leave(" = %ld", PTR_ERR(new_root));
+@@ -778,6 +829,7 @@ static bool afs_manage_cell(struct afs_c
+ switch (cell->state) {
+ case AFS_CELL_SETTING_UP:
+ goto set_up_cell;
++ case AFS_CELL_UNLOOKED:
+ case AFS_CELL_ACTIVE:
+ goto cell_is_active;
+ case AFS_CELL_REMOVING:
+@@ -798,7 +850,7 @@ set_up_cell:
+ goto remove_cell;
+ }
+
+- afs_set_cell_state(cell, AFS_CELL_ACTIVE);
++ afs_set_cell_state(cell, AFS_CELL_UNLOOKED);
+
+ cell_is_active:
+ if (afs_has_cell_expired(cell, &next_manage))
+@@ -808,6 +860,8 @@ cell_is_active:
+ ret = afs_update_cell(cell);
+ if (ret < 0)
+ cell->error = ret;
++ if (cell->state == AFS_CELL_UNLOOKED)
++ afs_set_cell_state(cell, AFS_CELL_ACTIVE);
+ }
+
+ if (next_manage < TIME64_MAX && cell->net->live) {
+--- a/fs/afs/dynroot.c
++++ b/fs/afs/dynroot.c
+@@ -108,7 +108,8 @@ static struct dentry *afs_dynroot_lookup
+ dotted = true;
+ }
+
+- cell = afs_lookup_cell(net, name, len, NULL, false,
++ cell = afs_lookup_cell(net, name, len, NULL,
++ AFS_LOOKUP_CELL_DYNROOT,
+ afs_cell_trace_use_lookup_dynroot);
+ if (IS_ERR(cell)) {
+ ret = PTR_ERR(cell);
+--- a/fs/afs/internal.h
++++ b/fs/afs/internal.h
+@@ -353,6 +353,7 @@ extern const char afs_init_sysname[];
+
+ enum afs_cell_state {
+ AFS_CELL_SETTING_UP,
++ AFS_CELL_UNLOOKED,
+ AFS_CELL_ACTIVE,
+ AFS_CELL_REMOVING,
+ AFS_CELL_DEAD,
+@@ -1034,9 +1035,18 @@ static inline bool afs_cb_is_broken(unsi
+ extern int afs_cell_init(struct afs_net *, const char *);
+ extern struct afs_cell *afs_find_cell(struct afs_net *, const char *, unsigned,
+ enum afs_cell_trace);
++enum afs_lookup_cell_for {
++ AFS_LOOKUP_CELL_DYNROOT,
++ AFS_LOOKUP_CELL_MOUNTPOINT,
++ AFS_LOOKUP_CELL_DIRECT_MOUNT,
++ AFS_LOOKUP_CELL_PRELOAD,
++ AFS_LOOKUP_CELL_ROOTCELL,
++ AFS_LOOKUP_CELL_ALIAS_CHECK,
++};
+ struct afs_cell *afs_lookup_cell(struct afs_net *net,
+ const char *name, unsigned int namesz,
+- const char *vllist, bool excl,
++ const char *vllist,
++ enum afs_lookup_cell_for reason,
+ enum afs_cell_trace trace);
+ extern struct afs_cell *afs_use_cell(struct afs_cell *, enum afs_cell_trace);
+ void afs_unuse_cell(struct afs_cell *cell, enum afs_cell_trace reason);
+--- a/fs/afs/mntpt.c
++++ b/fs/afs/mntpt.c
+@@ -107,7 +107,8 @@ static int afs_mntpt_set_params(struct f
+ if (size > AFS_MAXCELLNAME)
+ return -ENAMETOOLONG;
+
+- cell = afs_lookup_cell(ctx->net, p, size, NULL, false,
++ cell = afs_lookup_cell(ctx->net, p, size, NULL,
++ AFS_LOOKUP_CELL_MOUNTPOINT,
+ afs_cell_trace_use_lookup_mntpt);
+ if (IS_ERR(cell)) {
+ pr_err("kAFS: unable to lookup cell '%pd'\n", mntpt);
+--- a/fs/afs/proc.c
++++ b/fs/afs/proc.c
+@@ -122,7 +122,8 @@ static int afs_proc_cells_write(struct f
+ if (strcmp(buf, "add") == 0) {
+ struct afs_cell *cell;
+
+- cell = afs_lookup_cell(net, name, strlen(name), args, true,
++ cell = afs_lookup_cell(net, name, strlen(name), args,
++ AFS_LOOKUP_CELL_PRELOAD,
+ afs_cell_trace_use_lookup_add);
+ if (IS_ERR(cell)) {
+ ret = PTR_ERR(cell);
+--- a/fs/afs/super.c
++++ b/fs/afs/super.c
+@@ -290,7 +290,7 @@ static int afs_parse_source(struct fs_co
+ /* lookup the cell record */
+ if (cellname) {
+ cell = afs_lookup_cell(ctx->net, cellname, cellnamesz,
+- NULL, false,
++ NULL, AFS_LOOKUP_CELL_DIRECT_MOUNT,
+ afs_cell_trace_use_lookup_mount);
+ if (IS_ERR(cell)) {
+ pr_err("kAFS: unable to lookup cell '%*.*s'\n",
+--- a/fs/afs/vl_alias.c
++++ b/fs/afs/vl_alias.c
+@@ -269,7 +269,8 @@ static int yfs_check_canonical_cell_name
+ if (!name_len || name_len > AFS_MAXCELLNAME)
+ master = ERR_PTR(-EOPNOTSUPP);
+ else
+- master = afs_lookup_cell(cell->net, cell_name, name_len, NULL, false,
++ master = afs_lookup_cell(cell->net, cell_name, name_len, NULL,
++ AFS_LOOKUP_CELL_ALIAS_CHECK,
+ afs_cell_trace_use_lookup_canonical);
+ kfree(cell_name);
+ if (IS_ERR(master))
--- /dev/null
+From stable+bounces-290959-greg=kroah.com@vger.kernel.org Thu Jul 30 03:06:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:39 -0400
+Subject: afs: Fix lack of locking around modifications of net->cells_dyn_ino
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, "Christian Brauner (Amutable)" <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010140.1700208-8-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 55e841836c6f4646490f7b0347192b7a92d431ba ]
+
+Fix the lack of locking around modifications of net->cells_dyn_ino by
+taking net->cells_lock exclusively. This also requires to cell to be
+removed from net->cells_dyn_ino in afs_destroy_cell_work() rather than in
+afs_cell_destroy() as the latter runs in RCU cleanup context and sleeping
+locks cannot be taken there.
+
+Fixes: 1d0b929fc070 ("afs: Change dynroot to create contents on demand")
+Closes: https://sashiko.dev/#/patchset/20260618074903.2374756-1-dhowells%40redhat.com
+Signed-off-by: David Howells <dhowells@redhat.com>
+Link: https://patch.msgid.link/20260622090856.2746629-19-dhowells@redhat.com
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Stable-dep-of: 26f17ce6fa3f ("afs: Fix premature cell exposure through /afs")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/cell.c | 8 +++++++-
+ fs/afs/dynroot.c | 2 +-
+ 2 files changed, 8 insertions(+), 2 deletions(-)
+
+--- a/fs/afs/cell.c
++++ b/fs/afs/cell.c
+@@ -198,8 +198,10 @@ static struct afs_cell *afs_alloc_cell(s
+ cell->dns_source = vllist->source;
+ cell->dns_status = vllist->status;
+ smp_store_release(&cell->dns_lookup_count, 1); /* vs source/status */
++ down_write(&net->cells_lock);
+ ret = idr_alloc_cyclic(&net->cells_dyn_ino, cell,
+ 2, INT_MAX / 2, GFP_KERNEL);
++ up_write(&net->cells_lock);
+ if (ret < 0)
+ goto error;
+ atomic_inc(&net->cells_outstanding);
+@@ -572,7 +574,6 @@ static void afs_cell_destroy(struct rcu_
+ afs_put_vlserverlist(net, rcu_access_pointer(cell->vl_servers));
+ afs_unuse_cell(cell->alias_of, afs_cell_trace_unuse_alias);
+ key_put(cell->anonymous_key);
+- idr_remove(&net->cells_dyn_ino, cell->dynroot_ino);
+ kfree(cell->name - 1);
+ kfree(cell);
+
+@@ -587,6 +588,11 @@ static void afs_destroy_cell_work(struct
+ afs_see_cell(cell, afs_cell_trace_destroy);
+ timer_delete_sync(&cell->management_timer);
+ cancel_work_sync(&cell->manager);
++
++ down_write(&cell->net->cells_lock);
++ idr_remove(&cell->net->cells_dyn_ino, cell->dynroot_ino);
++ up_write(&cell->net->cells_lock);
++
+ call_rcu(&cell->rcu, afs_cell_destroy);
+ }
+
+--- a/fs/afs/dynroot.c
++++ b/fs/afs/dynroot.c
+@@ -278,7 +278,7 @@ static struct dentry *afs_lookup_atcell(
+ }
+
+ /*
+- * Transcribe the cell database into readdir content under the RCU read lock.
++ * Transcribe the cell database into readdir content under net->cells_lock.
+ * Each cell produces two entries, one prefixed with a dot and one not.
+ */
+ static int afs_dynroot_readdir_cells(struct afs_net *net, struct dir_context *ctx)
--- /dev/null
+From stable+bounces-290944-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:30 -0400
+Subject: afs: Improve server refcount/active count tracing
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-1-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 76daa300d41acc1180f8a46eead36905054beafb ]
+
+Improve server refcount/active count tracing to distinguish between simply
+getting/putting a ref and using/unusing the server record (which changes
+the activity count as well as the refcount). This makes it a bit easier to
+work out what's going on.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-10-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-6-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/fsclient.c | 4 ++--
+ fs/afs/rxrpc.c | 2 +-
+ fs/afs/server.c | 11 ++++++-----
+ fs/afs/server_list.c | 4 ++--
+ include/trace/events/afs.h | 27 +++++++++++++++------------
+ 5 files changed, 26 insertions(+), 22 deletions(-)
+
+--- a/fs/afs/fsclient.c
++++ b/fs/afs/fsclient.c
+@@ -1650,7 +1650,7 @@ int afs_fs_give_up_all_callbacks(struct
+ bp = call->request;
+ *bp++ = htonl(FSGIVEUPALLCALLBACKS);
+
+- call->server = afs_use_server(server, afs_server_trace_give_up_cb);
++ call->server = afs_use_server(server, afs_server_trace_use_give_up_cb);
+ afs_make_call(call, GFP_NOFS);
+ afs_wait_for_call_to_complete(call);
+ ret = call->error;
+@@ -1756,7 +1756,7 @@ bool afs_fs_get_capabilities(struct afs_
+ return false;
+
+ call->key = key;
+- call->server = afs_use_server(server, afs_server_trace_get_caps);
++ call->server = afs_use_server(server, afs_server_trace_use_get_caps);
+ call->peer = rxrpc_kernel_get_peer(estate->addresses->addrs[addr_index].peer);
+ call->probe = afs_get_endpoint_state(estate, afs_estate_trace_get_getcaps);
+ call->probe_index = addr_index;
+--- a/fs/afs/rxrpc.c
++++ b/fs/afs/rxrpc.c
+@@ -190,7 +190,7 @@ static void afs_free_call(struct afs_cal
+ if (call->type->destructor)
+ call->type->destructor(call);
+
+- afs_unuse_server_notime(call->net, call->server, afs_server_trace_put_call);
++ afs_unuse_server_notime(call->net, call->server, afs_server_trace_unuse_call);
+ kfree(call->request);
+
+ o = atomic_read(&net->nr_outstanding_calls);
+--- a/fs/afs/server.c
++++ b/fs/afs/server.c
+@@ -33,7 +33,7 @@ struct afs_server *afs_find_server(struc
+
+ do {
+ if (server)
+- afs_unuse_server_notime(net, server, afs_server_trace_put_find_rsq);
++ afs_unuse_server_notime(net, server, afs_server_trace_unuse_find_rsq);
+ server = NULL;
+ seq++; /* 2 on the 1st/lockless path, otherwise odd */
+ read_seqbegin_or_lock(&net->fs_addr_lock, &seq);
+@@ -49,7 +49,7 @@ struct afs_server *afs_find_server(struc
+ server = NULL;
+ continue;
+ found:
+- server = afs_maybe_use_server(server, afs_server_trace_get_by_addr);
++ server = afs_maybe_use_server(server, afs_server_trace_use_by_addr);
+
+ } while (need_seqretry(&net->fs_addr_lock, seq));
+
+@@ -76,7 +76,7 @@ struct afs_server *afs_find_server_by_uu
+ * changes.
+ */
+ if (server)
+- afs_unuse_server(net, server, afs_server_trace_put_uuid_rsq);
++ afs_unuse_server(net, server, afs_server_trace_unuse_uuid_rsq);
+ server = NULL;
+ seq++; /* 2 on the 1st/lockless path, otherwise odd */
+ read_seqbegin_or_lock(&net->fs_lock, &seq);
+@@ -91,7 +91,7 @@ struct afs_server *afs_find_server_by_uu
+ } else if (diff > 0) {
+ p = p->rb_right;
+ } else {
+- afs_use_server(server, afs_server_trace_get_by_uuid);
++ afs_use_server(server, afs_server_trace_use_by_uuid);
+ break;
+ }
+
+@@ -273,7 +273,8 @@ static struct afs_addr_list *afs_vl_look
+ }
+
+ /*
+- * Get or create a fileserver record.
++ * Get or create a fileserver record and return it with an active-use count on
++ * it.
+ */
+ struct afs_server *afs_lookup_server(struct afs_cell *cell, struct key *key,
+ const uuid_t *uuid, u32 addr_version)
+--- a/fs/afs/server_list.c
++++ b/fs/afs/server_list.c
+@@ -16,7 +16,7 @@ void afs_put_serverlist(struct afs_net *
+ if (slist && refcount_dec_and_test(&slist->usage)) {
+ for (i = 0; i < slist->nr_servers; i++)
+ afs_unuse_server(net, slist->servers[i].server,
+- afs_server_trace_put_slist);
++ afs_server_trace_unuse_slist);
+ kfree_rcu(slist, rcu);
+ }
+ }
+@@ -98,7 +98,7 @@ struct afs_server_list *afs_alloc_server
+ if (j < slist->nr_servers) {
+ if (slist->servers[j].server == server) {
+ afs_unuse_server(volume->cell->net, server,
+- afs_server_trace_put_slist_isort);
++ afs_server_trace_unuse_slist_isort);
+ continue;
+ }
+
+--- a/include/trace/events/afs.h
++++ b/include/trace/events/afs.h
+@@ -132,22 +132,25 @@ enum yfs_cm_operation {
+ EM(afs_server_trace_destroy, "DESTROY ") \
+ EM(afs_server_trace_free, "FREE ") \
+ EM(afs_server_trace_gc, "GC ") \
+- EM(afs_server_trace_get_by_addr, "GET addr ") \
+- EM(afs_server_trace_get_by_uuid, "GET uuid ") \
+- EM(afs_server_trace_get_caps, "GET caps ") \
+ EM(afs_server_trace_get_install, "GET inst ") \
+- EM(afs_server_trace_get_new_cbi, "GET cbi ") \
+ EM(afs_server_trace_get_probe, "GET probe") \
+- EM(afs_server_trace_give_up_cb, "giveup-cb") \
+ EM(afs_server_trace_purging, "PURGE ") \
+- EM(afs_server_trace_put_call, "PUT call ") \
+ EM(afs_server_trace_put_cbi, "PUT cbi ") \
+- EM(afs_server_trace_put_find_rsq, "PUT f-rsq") \
+ EM(afs_server_trace_put_probe, "PUT probe") \
+- EM(afs_server_trace_put_slist, "PUT slist") \
+- EM(afs_server_trace_put_slist_isort, "PUT isort") \
+- EM(afs_server_trace_put_uuid_rsq, "PUT u-req") \
+- E_(afs_server_trace_update, "UPDATE")
++ EM(afs_server_trace_see_expired, "SEE expd ") \
++ EM(afs_server_trace_unuse_call, "UNU call ") \
++ EM(afs_server_trace_unuse_create_fail, "UNU cfail") \
++ EM(afs_server_trace_unuse_find_rsq, "UNU f-rsq") \
++ EM(afs_server_trace_unuse_slist, "UNU slist") \
++ EM(afs_server_trace_unuse_slist_isort, "UNU isort") \
++ EM(afs_server_trace_unuse_uuid_rsq, "PUT u-req") \
++ EM(afs_server_trace_update, "UPDATE ") \
++ EM(afs_server_trace_use_by_addr, "USE addr ") \
++ EM(afs_server_trace_use_by_uuid, "USE uuid ") \
++ EM(afs_server_trace_use_cm_call, "USE cm-cl") \
++ EM(afs_server_trace_use_get_caps, "USE gcaps") \
++ EM(afs_server_trace_use_give_up_cb, "USE gvupc") \
++ E_(afs_server_trace_wait_create, "WAIT crt ")
+
+ #define afs_volume_traces \
+ EM(afs_volume_trace_alloc, "ALLOC ") \
+@@ -1369,7 +1372,7 @@ TRACE_EVENT(afs_server,
+ __entry->reason = reason;
+ ),
+
+- TP_printk("s=%08x %s u=%d a=%d",
++ TP_printk("s=%08x %s r=%d a=%d",
+ __entry->server,
+ __print_symbolic(__entry->reason, afs_server_traces),
+ __entry->ref,
--- /dev/null
+From stable+bounces-290945-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:31 -0400
+Subject: afs: Make afs_lookup_cell() take a trace note
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-2-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 92c48157ade88e7a543a64af4a806613fbde2ef3 ]
+
+Pass a note to be added to the afs_cell tracepoint to afs_lookup_cell() so
+that different callers can be distinguished.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-11-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-7-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/cell.c | 13 ++++++++-----
+ fs/afs/dynroot.c | 3 ++-
+ fs/afs/internal.h | 6 ++++--
+ fs/afs/mntpt.c | 3 ++-
+ fs/afs/proc.c | 3 ++-
+ fs/afs/super.c | 3 ++-
+ fs/afs/vl_alias.c | 3 ++-
+ include/trace/events/afs.h | 7 ++++++-
+ 8 files changed, 28 insertions(+), 13 deletions(-)
+
+--- a/fs/afs/cell.c
++++ b/fs/afs/cell.c
+@@ -233,6 +233,7 @@ error:
+ * @namesz: The strlen of the cell name.
+ * @vllist: A colon/comma separated list of numeric IP addresses or NULL.
+ * @excl: T if an error should be given if the cell name already exists.
++ * @trace: The reason to be logged if the lookup is successful.
+ *
+ * Look up a cell record by name and query the DNS for VL server addresses if
+ * needed. Note that that actual DNS query is punted off to the manager thread
+@@ -241,7 +242,8 @@ error:
+ */
+ struct afs_cell *afs_lookup_cell(struct afs_net *net,
+ const char *name, unsigned int namesz,
+- const char *vllist, bool excl)
++ const char *vllist, bool excl,
++ enum afs_cell_trace trace)
+ {
+ struct afs_cell *cell, *candidate, *cursor;
+ struct rb_node *parent, **pp;
+@@ -251,7 +253,7 @@ struct afs_cell *afs_lookup_cell(struct
+ _enter("%s,%s", name, vllist);
+
+ if (!excl) {
+- cell = afs_find_cell(net, name, namesz, afs_cell_trace_use_lookup);
++ cell = afs_find_cell(net, name, namesz, trace);
+ if (!IS_ERR(cell))
+ goto wait_for_cell;
+ }
+@@ -327,7 +329,7 @@ cell_already_exists:
+ if (excl) {
+ ret = -EEXIST;
+ } else {
+- afs_use_cell(cursor, afs_cell_trace_use_lookup);
++ afs_use_cell(cursor, trace);
+ ret = 0;
+ }
+ up_write(&net->cells_lock);
+@@ -382,8 +384,9 @@ int afs_cell_init(struct afs_net *net, c
+ if (cp && cp < rootcell + len)
+ return -EINVAL;
+
+- /* allocate a cell record for the root cell */
+- new_root = afs_lookup_cell(net, rootcell, len, vllist, false);
++ /* allocate a cell record for the root/workstation cell */
++ new_root = afs_lookup_cell(net, rootcell, len, vllist, false,
++ afs_cell_trace_use_lookup_ws);
+ if (IS_ERR(new_root)) {
+ _leave(" = %ld", PTR_ERR(new_root));
+ return PTR_ERR(new_root);
+--- a/fs/afs/dynroot.c
++++ b/fs/afs/dynroot.c
+@@ -108,7 +108,8 @@ static struct dentry *afs_dynroot_lookup
+ dotted = true;
+ }
+
+- cell = afs_lookup_cell(net, name, len, NULL, false);
++ cell = afs_lookup_cell(net, name, len, NULL, false,
++ afs_cell_trace_use_lookup_dynroot);
+ if (IS_ERR(cell)) {
+ ret = PTR_ERR(cell);
+ goto out_no_cell;
+--- a/fs/afs/internal.h
++++ b/fs/afs/internal.h
+@@ -1040,8 +1040,10 @@ static inline bool afs_cb_is_broken(unsi
+ extern int afs_cell_init(struct afs_net *, const char *);
+ extern struct afs_cell *afs_find_cell(struct afs_net *, const char *, unsigned,
+ enum afs_cell_trace);
+-extern struct afs_cell *afs_lookup_cell(struct afs_net *, const char *, unsigned,
+- const char *, bool);
++struct afs_cell *afs_lookup_cell(struct afs_net *net,
++ const char *name, unsigned int namesz,
++ const char *vllist, bool excl,
++ enum afs_cell_trace trace);
+ extern struct afs_cell *afs_use_cell(struct afs_cell *, enum afs_cell_trace);
+ extern void afs_unuse_cell(struct afs_net *, struct afs_cell *, enum afs_cell_trace);
+ extern struct afs_cell *afs_get_cell(struct afs_cell *, enum afs_cell_trace);
+--- a/fs/afs/mntpt.c
++++ b/fs/afs/mntpt.c
+@@ -107,7 +107,8 @@ static int afs_mntpt_set_params(struct f
+ if (size > AFS_MAXCELLNAME)
+ return -ENAMETOOLONG;
+
+- cell = afs_lookup_cell(ctx->net, p, size, NULL, false);
++ cell = afs_lookup_cell(ctx->net, p, size, NULL, false,
++ afs_cell_trace_use_lookup_mntpt);
+ if (IS_ERR(cell)) {
+ pr_err("kAFS: unable to lookup cell '%pd'\n", mntpt);
+ return PTR_ERR(cell);
+--- a/fs/afs/proc.c
++++ b/fs/afs/proc.c
+@@ -122,7 +122,8 @@ static int afs_proc_cells_write(struct f
+ if (strcmp(buf, "add") == 0) {
+ struct afs_cell *cell;
+
+- cell = afs_lookup_cell(net, name, strlen(name), args, true);
++ cell = afs_lookup_cell(net, name, strlen(name), args, true,
++ afs_cell_trace_use_lookup_add);
+ if (IS_ERR(cell)) {
+ ret = PTR_ERR(cell);
+ goto done;
+--- a/fs/afs/super.c
++++ b/fs/afs/super.c
+@@ -290,7 +290,8 @@ static int afs_parse_source(struct fs_co
+ /* lookup the cell record */
+ if (cellname) {
+ cell = afs_lookup_cell(ctx->net, cellname, cellnamesz,
+- NULL, false);
++ NULL, false,
++ afs_cell_trace_use_lookup_mount);
+ if (IS_ERR(cell)) {
+ pr_err("kAFS: unable to lookup cell '%*.*s'\n",
+ cellnamesz, cellnamesz, cellname ?: "");
+--- a/fs/afs/vl_alias.c
++++ b/fs/afs/vl_alias.c
+@@ -269,7 +269,8 @@ static int yfs_check_canonical_cell_name
+ if (!name_len || name_len > AFS_MAXCELLNAME)
+ master = ERR_PTR(-EOPNOTSUPP);
+ else
+- master = afs_lookup_cell(cell->net, cell_name, name_len, NULL, false);
++ master = afs_lookup_cell(cell->net, cell_name, name_len, NULL, false,
++ afs_cell_trace_use_lookup_canonical);
+ kfree(cell_name);
+ if (IS_ERR(master))
+ return PTR_ERR(master);
+--- a/include/trace/events/afs.h
++++ b/include/trace/events/afs.h
+@@ -208,7 +208,12 @@ enum yfs_cm_operation {
+ EM(afs_cell_trace_use_check_alias, "USE chk-al") \
+ EM(afs_cell_trace_use_fc, "USE fc ") \
+ EM(afs_cell_trace_use_fc_alias, "USE fc-al ") \
+- EM(afs_cell_trace_use_lookup, "USE lookup") \
++ EM(afs_cell_trace_use_lookup_add, "USE lu-add") \
++ EM(afs_cell_trace_use_lookup_canonical, "USE lu-can") \
++ EM(afs_cell_trace_use_lookup_dynroot, "USE lu-dyn") \
++ EM(afs_cell_trace_use_lookup_mntpt, "USE lu-mpt") \
++ EM(afs_cell_trace_use_lookup_mount, "USE lu-mnt") \
++ EM(afs_cell_trace_use_lookup_ws, "USE lu-ws ") \
+ EM(afs_cell_trace_use_mntpt, "USE mntpt ") \
+ EM(afs_cell_trace_use_pin, "USE pin ") \
+ EM(afs_cell_trace_use_probe, "USE probe ") \
--- /dev/null
+From stable+bounces-290953-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:36 -0400
+Subject: afs: Simplify cell record handling
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-7-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit e2c2cb8ef07affd9f69497ea128fa801240fdf32 ]
+
+Simplify afs_cell record handling to avoid very occasional races that cause
+module removal to hang (it waits for all cell records to be removed).
+
+There are two things that particularly contribute to the difficulty:
+firstly, the code tries to pass a ref on the cell to the cell's maintenance
+work item (which gets awkward if the work item is already queued); and,
+secondly, there's an overall cell manager that tries to use just one timer
+for the entire cell collection (to avoid having loads of timers). However,
+both of these are probably unnecessarily restrictive.
+
+To simplify this, the following changes are made:
+
+ (1) The cell record collection manager is removed. Each cell record
+ manages itself individually.
+
+ (2) Each afs_cell is given a second work item (cell->destroyer) that is
+ queued when its refcount reaches zero. This is not done in the
+ context of the putting thread as it might be in an inconvenient place
+ to sleep.
+
+ (3) Each afs_cell is given its own timer. The timer is used to expire the
+ cell record after a period of unuse if not otherwise pinned and can
+ also be used for other maintenance tasks if necessary (of which there
+ are currently none as DNS refresh is triggered by filesystem
+ operations).
+
+ (4) The afs_cell manager work item (cell->manager) is no longer given a
+ ref on the cell when queued; rather, the manager must be deleted.
+ This does away with the need to deal with the consequences of losing a
+ race to queue cell->manager. Clean up of extra queuing is deferred to
+ the destroyer.
+
+ (5) The cell destroyer work item makes sure the cell timer is removed and
+ that the normal cell work is cancelled before farming the actual
+ destruction off to RCU.
+
+ (6) When a network namespace is destroyed or the kafs module is unloaded,
+ it's now a simple matter of marking the namespace as dead then just
+ waking up all the cell work items. They will then remove and destroy
+ themselves once all remaining activity counts and/or a ref counts are
+ dropped. This makes sure that all server records are dropped first.
+
+ (7) The cell record state set is reduced to just four states: SETTING_UP,
+ ACTIVE, REMOVING and DEAD. The record persists in the active state
+ even when it's not being used until the time comes to remove it rather
+ than downgrading it to an inactive state from whence it can be
+ restored.
+
+ This means that the cell still appears in /proc and /afs when not in
+ use until it switches to the REMOVING state - at which point it is
+ removed.
+
+ Note that the REMOVING state is included so that someone wanting to
+ resurrect the cell record is forced to wait whilst the cell is torn
+ down in that state. Once it's in the DEAD state, it has been removed
+ from net->cells tree and is no longer findable and can be replaced.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-16-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-12-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/cell.c | 404 ++++++++++++++++++---------------------------
+ fs/afs/dynroot.c | 4
+ fs/afs/internal.h | 16 -
+ fs/afs/main.c | 3
+ fs/afs/server.c | 8
+ fs/afs/vl_rotate.c | 2
+ include/trace/events/afs.h | 23 +-
+ 7 files changed, 187 insertions(+), 273 deletions(-)
+
+--- a/fs/afs/cell.c
++++ b/fs/afs/cell.c
+@@ -20,8 +20,9 @@ static unsigned __read_mostly afs_cell_m
+ static unsigned __read_mostly afs_cell_max_ttl = 24 * 60 * 60;
+ static atomic_t cell_debug_id;
+
+-static void afs_queue_cell_manager(struct afs_net *);
+-static void afs_manage_cell_work(struct work_struct *);
++static void afs_cell_timer(struct timer_list *timer);
++static void afs_destroy_cell_work(struct work_struct *work);
++static void afs_manage_cell_work(struct work_struct *work);
+
+ static void afs_dec_cells_outstanding(struct afs_net *net)
+ {
+@@ -29,19 +30,11 @@ static void afs_dec_cells_outstanding(st
+ wake_up_var(&net->cells_outstanding);
+ }
+
+-/*
+- * Set the cell timer to fire after a given delay, assuming it's not already
+- * set for an earlier time.
+- */
+-static void afs_set_cell_timer(struct afs_net *net, time64_t delay)
++static void afs_set_cell_state(struct afs_cell *cell, enum afs_cell_state state)
+ {
+- if (net->live) {
+- atomic_inc(&net->cells_outstanding);
+- if (timer_reduce(&net->cells_timer, jiffies + delay * HZ))
+- afs_dec_cells_outstanding(net);
+- } else {
+- afs_queue_cell_manager(net);
+- }
++ smp_store_release(&cell->state, state); /* Commit cell changes before state */
++ smp_wmb(); /* Set cell state before task state */
++ wake_up_var(&cell->state);
+ }
+
+ /*
+@@ -116,7 +109,7 @@ static struct afs_cell *afs_alloc_cell(s
+ const char *name, unsigned int namelen,
+ const char *addresses)
+ {
+- struct afs_vlserver_list *vllist;
++ struct afs_vlserver_list *vllist = NULL;
+ struct afs_cell *cell;
+ int i, ret;
+
+@@ -163,7 +156,9 @@ static struct afs_cell *afs_alloc_cell(s
+ cell->net = net;
+ refcount_set(&cell->ref, 1);
+ atomic_set(&cell->active, 0);
++ INIT_WORK(&cell->destroyer, afs_destroy_cell_work);
+ INIT_WORK(&cell->manager, afs_manage_cell_work);
++ timer_setup(&cell->management_timer, afs_cell_timer, 0);
+ init_rwsem(&cell->vs_lock);
+ cell->volumes = RB_ROOT;
+ INIT_HLIST_HEAD(&cell->proc_volumes);
+@@ -220,6 +215,7 @@ parse_failed:
+ if (ret == -EINVAL)
+ printk(KERN_ERR "kAFS: bad VL server IP address\n");
+ error:
++ afs_put_vlserverlist(cell->net, vllist);
+ kfree(cell->name - 1);
+ kfree(cell);
+ _leave(" = %d", ret);
+@@ -296,26 +292,28 @@ struct afs_cell *afs_lookup_cell(struct
+
+ cell = candidate;
+ candidate = NULL;
+- atomic_set(&cell->active, 2);
+- trace_afs_cell(cell->debug_id, refcount_read(&cell->ref), 2, afs_cell_trace_insert);
++ afs_use_cell(cell, trace);
+ rb_link_node_rcu(&cell->net_node, parent, pp);
+ rb_insert_color(&cell->net_node, &net->cells);
+ up_write(&net->cells_lock);
+
+- afs_queue_cell(cell, afs_cell_trace_get_queue_new);
++ afs_queue_cell(cell, afs_cell_trace_queue_new);
+
+ wait_for_cell:
+- trace_afs_cell(cell->debug_id, refcount_read(&cell->ref), atomic_read(&cell->active),
+- afs_cell_trace_wait);
+ _debug("wait_for_cell");
+- wait_var_event(&cell->state,
+- ({
+- state = smp_load_acquire(&cell->state); /* vs error */
+- state == AFS_CELL_ACTIVE || state == AFS_CELL_REMOVED;
+- }));
++ state = smp_load_acquire(&cell->state); /* vs error */
++ if (state != AFS_CELL_ACTIVE &&
++ state != AFS_CELL_DEAD) {
++ afs_see_cell(cell, afs_cell_trace_wait);
++ wait_var_event(&cell->state,
++ ({
++ state = smp_load_acquire(&cell->state); /* vs error */
++ state == AFS_CELL_ACTIVE || state == AFS_CELL_DEAD;
++ }));
++ }
+
+ /* Check the state obtained from the wait check. */
+- if (state == AFS_CELL_REMOVED) {
++ if (state == AFS_CELL_DEAD) {
+ ret = cell->error;
+ goto error;
+ }
+@@ -397,7 +395,6 @@ int afs_cell_init(struct afs_net *net, c
+
+ /* install the new cell */
+ down_write(&net->cells_lock);
+- afs_see_cell(new_root, afs_cell_trace_see_ws);
+ old_root = rcu_replace_pointer(net->ws_cell, new_root,
+ lockdep_is_held(&net->cells_lock));
+ up_write(&net->cells_lock);
+@@ -532,30 +529,14 @@ static void afs_cell_destroy(struct rcu_
+ _leave(" [destroyed]");
+ }
+
+-/*
+- * Queue the cell manager.
+- */
+-static void afs_queue_cell_manager(struct afs_net *net)
+-{
+- int outstanding = atomic_inc_return(&net->cells_outstanding);
+-
+- _enter("%d", outstanding);
+-
+- if (!queue_work(afs_wq, &net->cells_manager))
+- afs_dec_cells_outstanding(net);
+-}
+-
+-/*
+- * Cell management timer. We have an increment on cells_outstanding that we
+- * need to pass along to the work item.
+- */
+-void afs_cells_timer(struct timer_list *timer)
++static void afs_destroy_cell_work(struct work_struct *work)
+ {
+- struct afs_net *net = container_of(timer, struct afs_net, cells_timer);
++ struct afs_cell *cell = container_of(work, struct afs_cell, destroyer);
+
+- _enter("");
+- if (!queue_work(afs_wq, &net->cells_manager))
+- afs_dec_cells_outstanding(net);
++ afs_see_cell(cell, afs_cell_trace_destroy);
++ timer_delete_sync(&cell->management_timer);
++ cancel_work_sync(&cell->manager);
++ call_rcu(&cell->rcu, afs_cell_destroy);
+ }
+
+ /*
+@@ -587,7 +568,7 @@ void afs_put_cell(struct afs_cell *cell,
+ if (zero) {
+ a = atomic_read(&cell->active);
+ WARN(a != 0, "Cell active count %u > 0\n", a);
+- call_rcu(&cell->rcu, afs_cell_destroy);
++ WARN_ON(!queue_work(afs_wq, &cell->destroyer));
+ }
+ }
+ }
+@@ -599,10 +580,9 @@ struct afs_cell *afs_use_cell(struct afs
+ {
+ int r, a;
+
+- r = refcount_read(&cell->ref);
+- WARN_ON(r == 0);
++ __refcount_inc(&cell->ref, &r);
+ a = atomic_inc_return(&cell->active);
+- trace_afs_cell(cell->debug_id, r, a, reason);
++ trace_afs_cell(cell->debug_id, r + 1, a, reason);
+ return cell;
+ }
+
+@@ -614,6 +594,7 @@ void afs_unuse_cell(struct afs_cell *cel
+ {
+ unsigned int debug_id;
+ time64_t now, expire_delay;
++ bool zero;
+ int r, a;
+
+ if (!cell)
+@@ -628,13 +609,15 @@ void afs_unuse_cell(struct afs_cell *cel
+ expire_delay = afs_cell_gc_delay;
+
+ debug_id = cell->debug_id;
+- r = refcount_read(&cell->ref);
+ a = atomic_dec_return(&cell->active);
+- trace_afs_cell(debug_id, r, a, reason);
+- WARN_ON(a == 0);
+- if (a == 1)
++ if (!a)
+ /* 'cell' may now be garbage collected. */
+- afs_set_cell_timer(cell->net, expire_delay);
++ afs_set_cell_timer(cell, expire_delay);
++
++ zero = __refcount_dec_and_test(&cell->ref, &r);
++ trace_afs_cell(debug_id, r - 1, a, reason);
++ if (zero)
++ WARN_ON(!queue_work(afs_wq, &cell->destroyer));
+ }
+
+ /*
+@@ -654,9 +637,27 @@ void afs_see_cell(struct afs_cell *cell,
+ */
+ void afs_queue_cell(struct afs_cell *cell, enum afs_cell_trace reason)
+ {
+- afs_get_cell(cell, reason);
+- if (!queue_work(afs_wq, &cell->manager))
+- afs_put_cell(cell, afs_cell_trace_put_queue_fail);
++ queue_work(afs_wq, &cell->manager);
++}
++
++/*
++ * Cell-specific management timer.
++ */
++static void afs_cell_timer(struct timer_list *timer)
++{
++ struct afs_cell *cell = container_of(timer, struct afs_cell, management_timer);
++
++ afs_see_cell(cell, afs_cell_trace_see_mgmt_timer);
++ if (refcount_read(&cell->ref) > 0 && cell->net->live)
++ queue_work(afs_wq, &cell->manager);
++}
++
++/*
++ * Set/reduce the cell timer.
++ */
++void afs_set_cell_timer(struct afs_cell *cell, unsigned int delay_secs)
++{
++ timer_reduce(&cell->management_timer, jiffies + delay_secs * HZ);
+ }
+
+ /*
+@@ -739,212 +740,125 @@ static void afs_deactivate_cell(struct a
+ _leave("");
+ }
+
++static bool afs_has_cell_expired(struct afs_cell *cell, time64_t *_next_manage)
++{
++ const struct afs_vlserver_list *vllist;
++ time64_t expire_at = cell->last_inactive;
++ time64_t now = ktime_get_real_seconds();
++
++ if (atomic_read(&cell->active))
++ return false;
++ if (!cell->net->live)
++ return true;
++
++ vllist = rcu_dereference_protected(cell->vl_servers, true);
++ if (vllist && vllist->nr_servers > 0)
++ expire_at += afs_cell_gc_delay;
++
++ if (expire_at <= now)
++ return true;
++ if (expire_at < *_next_manage)
++ *_next_manage = expire_at;
++ return false;
++}
++
+ /*
+ * Manage a cell record, initialising and destroying it, maintaining its DNS
+ * records.
+ */
+-static void afs_manage_cell(struct afs_cell *cell)
++static bool afs_manage_cell(struct afs_cell *cell)
+ {
+ struct afs_net *net = cell->net;
+- int ret, active;
++ time64_t next_manage = TIME64_MAX;
++ int ret;
+
+ _enter("%s", cell->name);
+
+-again:
+ _debug("state %u", cell->state);
+ switch (cell->state) {
+- case AFS_CELL_INACTIVE:
+- case AFS_CELL_FAILED:
+- down_write(&net->cells_lock);
+- active = 1;
+- if (atomic_try_cmpxchg_relaxed(&cell->active, &active, 0)) {
+- rb_erase(&cell->net_node, &net->cells);
+- trace_afs_cell(cell->debug_id, refcount_read(&cell->ref), 0,
+- afs_cell_trace_unuse_delete);
+- smp_store_release(&cell->state, AFS_CELL_REMOVED);
+- }
+- up_write(&net->cells_lock);
+- if (cell->state == AFS_CELL_REMOVED) {
+- wake_up_var(&cell->state);
+- goto final_destruction;
+- }
+- if (cell->state == AFS_CELL_FAILED)
+- goto done;
+- smp_store_release(&cell->state, AFS_CELL_UNSET);
+- wake_up_var(&cell->state);
+- goto again;
+-
+- case AFS_CELL_UNSET:
+- smp_store_release(&cell->state, AFS_CELL_ACTIVATING);
+- wake_up_var(&cell->state);
+- goto again;
++ case AFS_CELL_SETTING_UP:
++ goto set_up_cell;
++ case AFS_CELL_ACTIVE:
++ goto cell_is_active;
++ case AFS_CELL_REMOVING:
++ WARN_ON_ONCE(1);
++ return false;
++ case AFS_CELL_DEAD:
++ return false;
++ default:
++ _debug("bad state %u", cell->state);
++ WARN_ON_ONCE(1); /* Unhandled state */
++ return false;
++ }
+
+- case AFS_CELL_ACTIVATING:
+- ret = afs_activate_cell(net, cell);
+- if (ret < 0)
+- goto activation_failed;
++set_up_cell:
++ ret = afs_activate_cell(net, cell);
++ if (ret < 0) {
++ cell->error = ret;
++ goto remove_cell;
++ }
+
+- smp_store_release(&cell->state, AFS_CELL_ACTIVE);
+- wake_up_var(&cell->state);
+- goto again;
++ afs_set_cell_state(cell, AFS_CELL_ACTIVE);
+
+- case AFS_CELL_ACTIVE:
+- if (atomic_read(&cell->active) > 1) {
+- if (test_and_clear_bit(AFS_CELL_FL_DO_LOOKUP, &cell->flags)) {
+- ret = afs_update_cell(cell);
+- if (ret < 0)
+- cell->error = ret;
+- }
+- goto done;
+- }
+- smp_store_release(&cell->state, AFS_CELL_DEACTIVATING);
+- wake_up_var(&cell->state);
+- goto again;
+-
+- case AFS_CELL_DEACTIVATING:
+- if (atomic_read(&cell->active) > 1)
+- goto reverse_deactivation;
+- afs_deactivate_cell(net, cell);
+- smp_store_release(&cell->state, AFS_CELL_INACTIVE);
+- wake_up_var(&cell->state);
+- goto again;
++cell_is_active:
++ if (afs_has_cell_expired(cell, &next_manage))
++ goto remove_cell;
+
+- case AFS_CELL_REMOVED:
+- goto done;
++ if (test_and_clear_bit(AFS_CELL_FL_DO_LOOKUP, &cell->flags)) {
++ ret = afs_update_cell(cell);
++ if (ret < 0)
++ cell->error = ret;
++ }
+
+- default:
+- break;
++ if (next_manage < TIME64_MAX && cell->net->live) {
++ time64_t now = ktime_get_real_seconds();
++
++ if (next_manage - now <= 0)
++ afs_queue_cell(cell, afs_cell_trace_queue_again);
++ else
++ afs_set_cell_timer(cell, next_manage - now);
+ }
+- _debug("bad state %u", cell->state);
+- BUG(); /* Unhandled state */
++ _leave(" [done %u]", cell->state);
++ return false;
+
+-activation_failed:
+- cell->error = ret;
+- afs_deactivate_cell(net, cell);
++remove_cell:
++ down_write(&net->cells_lock);
+
+- smp_store_release(&cell->state, AFS_CELL_FAILED); /* vs error */
+- wake_up_var(&cell->state);
+- goto again;
++ if (atomic_read(&cell->active)) {
++ up_write(&net->cells_lock);
++ goto cell_is_active;
++ }
+
+-reverse_deactivation:
+- smp_store_release(&cell->state, AFS_CELL_ACTIVE);
+- wake_up_var(&cell->state);
+- _leave(" [deact->act]");
+- return;
++ /* Make sure that the expiring server records are going to see the fact
++ * that the cell is caput.
++ */
++ afs_set_cell_state(cell, AFS_CELL_REMOVING);
+
+-done:
+- _leave(" [done %u]", cell->state);
+- return;
++ afs_deactivate_cell(net, cell);
++ afs_purge_servers(cell);
++
++ rb_erase(&cell->net_node, &net->cells);
++ afs_see_cell(cell, afs_cell_trace_unuse_delete);
++ up_write(&net->cells_lock);
+
+-final_destruction:
+ /* The root volume is pinning the cell */
+ afs_put_volume(cell->root_volume, afs_volume_trace_put_cell_root);
+ cell->root_volume = NULL;
+- afs_purge_servers(cell);
+- afs_put_cell(cell, afs_cell_trace_put_destroy);
++
++ afs_set_cell_state(cell, AFS_CELL_DEAD);
++ return true;
+ }
+
+ static void afs_manage_cell_work(struct work_struct *work)
+ {
+ struct afs_cell *cell = container_of(work, struct afs_cell, manager);
++ bool final_put;
+
+- afs_manage_cell(cell);
+- afs_put_cell(cell, afs_cell_trace_put_queue_work);
+-}
+-
+-/*
+- * Manage the records of cells known to a network namespace. This includes
+- * updating the DNS records and garbage collecting unused cells that were
+- * automatically added.
+- *
+- * Note that constructed cell records may only be removed from net->cells by
+- * this work item, so it is safe for this work item to stash a cursor pointing
+- * into the tree and then return to caller (provided it skips cells that are
+- * still under construction).
+- *
+- * Note also that we were given an increment on net->cells_outstanding by
+- * whoever queued us that we need to deal with before returning.
+- */
+-void afs_manage_cells(struct work_struct *work)
+-{
+- struct afs_net *net = container_of(work, struct afs_net, cells_manager);
+- struct rb_node *cursor;
+- time64_t now = ktime_get_real_seconds(), next_manage = TIME64_MAX;
+- bool purging = !net->live;
+-
+- _enter("");
+-
+- /* Trawl the cell database looking for cells that have expired from
+- * lack of use and cells whose DNS results have expired and dispatch
+- * their managers.
+- */
+- down_read(&net->cells_lock);
+-
+- for (cursor = rb_first(&net->cells); cursor; cursor = rb_next(cursor)) {
+- struct afs_cell *cell =
+- rb_entry(cursor, struct afs_cell, net_node);
+- unsigned active;
+- bool sched_cell = false;
+-
+- active = atomic_read(&cell->active);
+- trace_afs_cell(cell->debug_id, refcount_read(&cell->ref),
+- active, afs_cell_trace_manage);
+-
+- ASSERTCMP(active, >=, 1);
+-
+- if (purging) {
+- if (test_and_clear_bit(AFS_CELL_FL_NO_GC, &cell->flags)) {
+- active = atomic_dec_return(&cell->active);
+- trace_afs_cell(cell->debug_id, refcount_read(&cell->ref),
+- active, afs_cell_trace_unuse_pin);
+- }
+- }
+-
+- if (active == 1) {
+- struct afs_vlserver_list *vllist;
+- time64_t expire_at = cell->last_inactive;
+-
+- read_lock(&cell->vl_servers_lock);
+- vllist = rcu_dereference_protected(
+- cell->vl_servers,
+- lockdep_is_held(&cell->vl_servers_lock));
+- if (vllist->nr_servers > 0)
+- expire_at += afs_cell_gc_delay;
+- read_unlock(&cell->vl_servers_lock);
+- if (purging || expire_at <= now)
+- sched_cell = true;
+- else if (expire_at < next_manage)
+- next_manage = expire_at;
+- }
+-
+- if (!purging) {
+- if (test_bit(AFS_CELL_FL_DO_LOOKUP, &cell->flags))
+- sched_cell = true;
+- }
+-
+- if (sched_cell)
+- afs_queue_cell(cell, afs_cell_trace_get_queue_manage);
+- }
+-
+- up_read(&net->cells_lock);
+-
+- /* Update the timer on the way out. We have to pass an increment on
+- * cells_outstanding in the namespace that we are in to the timer or
+- * the work scheduler.
+- */
+- if (!purging && next_manage < TIME64_MAX) {
+- now = ktime_get_real_seconds();
+-
+- if (next_manage - now <= 0) {
+- if (queue_work(afs_wq, &net->cells_manager))
+- atomic_inc(&net->cells_outstanding);
+- } else {
+- afs_set_cell_timer(net, next_manage - now);
+- }
+- }
+-
+- afs_dec_cells_outstanding(net);
+- _leave(" [%d]", atomic_read(&net->cells_outstanding));
++ afs_see_cell(cell, afs_cell_trace_manage);
++ final_put = afs_manage_cell(cell);
++ afs_see_cell(cell, afs_cell_trace_managed);
++ if (final_put)
++ afs_put_cell(cell, afs_cell_trace_put_final);
+ }
+
+ /*
+@@ -953,6 +867,7 @@ void afs_manage_cells(struct work_struct
+ void afs_cell_purge(struct afs_net *net)
+ {
+ struct afs_cell *ws;
++ struct rb_node *cursor;
+
+ _enter("");
+
+@@ -962,12 +877,19 @@ void afs_cell_purge(struct afs_net *net)
+ up_write(&net->cells_lock);
+ afs_unuse_cell(ws, afs_cell_trace_unuse_ws);
+
+- _debug("del timer");
+- if (del_timer_sync(&net->cells_timer))
+- atomic_dec(&net->cells_outstanding);
++ _debug("kick cells");
++ down_read(&net->cells_lock);
++ for (cursor = rb_first(&net->cells); cursor; cursor = rb_next(cursor)) {
++ struct afs_cell *cell = rb_entry(cursor, struct afs_cell, net_node);
++
++ afs_see_cell(cell, afs_cell_trace_purge);
+
+- _debug("kick mgr");
+- afs_queue_cell_manager(net);
++ if (test_and_clear_bit(AFS_CELL_FL_NO_GC, &cell->flags))
++ afs_unuse_cell(cell, afs_cell_trace_unuse_pin);
++
++ afs_queue_cell(cell, afs_cell_trace_queue_purge);
++ }
++ up_read(&net->cells_lock);
+
+ _debug("wait");
+ wait_var_event(&net->cells_outstanding,
+--- a/fs/afs/dynroot.c
++++ b/fs/afs/dynroot.c
+@@ -293,8 +293,8 @@ static int afs_dynroot_readdir_cells(str
+ cell = idr_get_next(&net->cells_dyn_ino, &ix);
+ if (!cell)
+ return 0;
+- if (READ_ONCE(cell->state) == AFS_CELL_FAILED ||
+- READ_ONCE(cell->state) == AFS_CELL_REMOVED) {
++ if (READ_ONCE(cell->state) == AFS_CELL_REMOVING ||
++ READ_ONCE(cell->state) == AFS_CELL_DEAD) {
+ ctx->pos += 2;
+ ctx->pos &= ~1;
+ continue;
+--- a/fs/afs/internal.h
++++ b/fs/afs/internal.h
+@@ -304,8 +304,6 @@ struct afs_net {
+ struct rb_root cells;
+ struct idr cells_dyn_ino; /* cell->dynroot_ino mapping */
+ struct afs_cell __rcu *ws_cell;
+- struct work_struct cells_manager;
+- struct timer_list cells_timer;
+ atomic_t cells_outstanding;
+ struct rw_semaphore cells_lock;
+ struct mutex cells_alias_lock;
+@@ -354,13 +352,10 @@ struct afs_net {
+ extern const char afs_init_sysname[];
+
+ enum afs_cell_state {
+- AFS_CELL_UNSET,
+- AFS_CELL_ACTIVATING,
++ AFS_CELL_SETTING_UP,
+ AFS_CELL_ACTIVE,
+- AFS_CELL_DEACTIVATING,
+- AFS_CELL_INACTIVE,
+- AFS_CELL_FAILED,
+- AFS_CELL_REMOVED,
++ AFS_CELL_REMOVING,
++ AFS_CELL_DEAD,
+ };
+
+ /*
+@@ -391,7 +386,9 @@ struct afs_cell {
+ struct afs_cell *alias_of; /* The cell this is an alias of */
+ struct afs_volume *root_volume; /* The root.cell volume if there is one */
+ struct key *anonymous_key; /* anonymous user key for this cell */
++ struct work_struct destroyer; /* Destroyer for cell */
+ struct work_struct manager; /* Manager for init/deinit/dns */
++ struct timer_list management_timer; /* General management timer */
+ struct hlist_node proc_link; /* /proc cell list link */
+ time64_t dns_expiry; /* Time AFSDB/SRV record expires */
+ time64_t last_inactive; /* Time of last drop of usage count */
+@@ -1047,8 +1044,7 @@ extern struct afs_cell *afs_get_cell(str
+ extern void afs_see_cell(struct afs_cell *, enum afs_cell_trace);
+ extern void afs_put_cell(struct afs_cell *, enum afs_cell_trace);
+ extern void afs_queue_cell(struct afs_cell *, enum afs_cell_trace);
+-extern void afs_manage_cells(struct work_struct *);
+-extern void afs_cells_timer(struct timer_list *);
++void afs_set_cell_timer(struct afs_cell *cell, unsigned int delay_secs);
+ extern void __net_exit afs_cell_purge(struct afs_net *);
+
+ /*
+--- a/fs/afs/main.c
++++ b/fs/afs/main.c
+@@ -78,9 +78,6 @@ static int __net_init afs_net_init(struc
+ net->cells = RB_ROOT;
+ idr_init(&net->cells_dyn_ino);
+ init_rwsem(&net->cells_lock);
+- INIT_WORK(&net->cells_manager, afs_manage_cells);
+- timer_setup(&net->cells_timer, afs_cells_timer, 0);
+-
+ mutex_init(&net->cells_alias_lock);
+ mutex_init(&net->proc_cells_lock);
+ INIT_HLIST_HEAD(&net->proc_cells);
+--- a/fs/afs/server.c
++++ b/fs/afs/server.c
+@@ -103,7 +103,7 @@ static struct afs_server *afs_install_se
+ afs_get_cell(cell, afs_cell_trace_get_server);
+
+ exists:
+- afs_use_server(server, true, afs_server_trace_get_install);
++ afs_use_server(server, true, afs_server_trace_use_install);
+ return server;
+ }
+
+@@ -357,7 +357,7 @@ void afs_unuse_server_notime(struct afs_
+
+ if (atomic_dec_and_test(&server->active)) {
+ if (test_bit(AFS_SERVER_FL_EXPIRED, &server->flags) ||
+- READ_ONCE(server->cell->state) >= AFS_CELL_FAILED)
++ READ_ONCE(server->cell->state) >= AFS_CELL_REMOVING)
+ schedule_work(&server->destroyer);
+ }
+
+@@ -375,7 +375,7 @@ void afs_unuse_server(struct afs_net *ne
+
+ if (atomic_dec_and_test(&server->active)) {
+ if (!test_bit(AFS_SERVER_FL_EXPIRED, &server->flags) &&
+- READ_ONCE(server->cell->state) < AFS_CELL_FAILED) {
++ READ_ONCE(server->cell->state) < AFS_CELL_REMOVING) {
+ time64_t unuse_time = ktime_get_real_seconds();
+
+ server->unuse_time = unuse_time;
+@@ -425,7 +425,7 @@ static bool afs_has_server_expired(const
+ return false;
+
+ if (server->cell->net->live ||
+- server->cell->state >= AFS_CELL_FAILED) {
++ server->cell->state >= AFS_CELL_REMOVING) {
+ trace_afs_server(server->debug_id, refcount_read(&server->ref),
+ 0, afs_server_trace_purging);
+ return true;
+--- a/fs/afs/vl_rotate.c
++++ b/fs/afs/vl_rotate.c
+@@ -48,7 +48,7 @@ static bool afs_start_vl_iteration(struc
+ cell->dns_expiry <= ktime_get_real_seconds()) {
+ dns_lookup_count = smp_load_acquire(&cell->dns_lookup_count);
+ set_bit(AFS_CELL_FL_DO_LOOKUP, &cell->flags);
+- afs_queue_cell(cell, afs_cell_trace_get_queue_dns);
++ afs_queue_cell(cell, afs_cell_trace_queue_dns);
+
+ if (cell->dns_source == DNS_RECORD_UNAVAILABLE) {
+ if (wait_var_event_interruptible(
+--- a/include/trace/events/afs.h
++++ b/include/trace/events/afs.h
+@@ -131,7 +131,6 @@ enum yfs_cm_operation {
+ EM(afs_server_trace_destroy, "DESTROY ") \
+ EM(afs_server_trace_free, "FREE ") \
+ EM(afs_server_trace_gc, "GC ") \
+- EM(afs_server_trace_get_install, "GET inst ") \
+ EM(afs_server_trace_get_probe, "GET probe") \
+ EM(afs_server_trace_purging, "PURGE ") \
+ EM(afs_server_trace_put_cbi, "PUT cbi ") \
+@@ -149,6 +148,7 @@ enum yfs_cm_operation {
+ EM(afs_server_trace_use_cm_call, "USE cm-cl") \
+ EM(afs_server_trace_use_get_caps, "USE gcaps") \
+ EM(afs_server_trace_use_give_up_cb, "USE gvupc") \
++ EM(afs_server_trace_use_install, "USE inst ") \
+ E_(afs_server_trace_wait_create, "WAIT crt ")
+
+ #define afs_volume_traces \
+@@ -171,37 +171,36 @@ enum yfs_cm_operation {
+
+ #define afs_cell_traces \
+ EM(afs_cell_trace_alloc, "ALLOC ") \
++ EM(afs_cell_trace_destroy, "DESTROY ") \
+ EM(afs_cell_trace_free, "FREE ") \
+ EM(afs_cell_trace_get_atcell, "GET atcell") \
+- EM(afs_cell_trace_get_queue_dns, "GET q-dns ") \
+- EM(afs_cell_trace_get_queue_manage, "GET q-mng ") \
+- EM(afs_cell_trace_get_queue_new, "GET q-new ") \
+ EM(afs_cell_trace_get_server, "GET server") \
+ EM(afs_cell_trace_get_vol, "GET vol ") \
+- EM(afs_cell_trace_insert, "INSERT ") \
+- EM(afs_cell_trace_manage, "MANAGE ") \
++ EM(afs_cell_trace_purge, "PURGE ") \
+ EM(afs_cell_trace_put_atcell, "PUT atcell") \
+ EM(afs_cell_trace_put_candidate, "PUT candid") \
+- EM(afs_cell_trace_put_destroy, "PUT destry") \
+- EM(afs_cell_trace_put_queue_work, "PUT q-work") \
+- EM(afs_cell_trace_put_queue_fail, "PUT q-fail") \
++ EM(afs_cell_trace_put_final, "PUT final ") \
+ EM(afs_cell_trace_put_server, "PUT server") \
+ EM(afs_cell_trace_put_vol, "PUT vol ") \
++ EM(afs_cell_trace_queue_again, "QUE again ") \
++ EM(afs_cell_trace_queue_dns, "QUE dns ") \
++ EM(afs_cell_trace_queue_new, "QUE new ") \
++ EM(afs_cell_trace_queue_purge, "QUE purge ") \
++ EM(afs_cell_trace_manage, "MANAGE ") \
++ EM(afs_cell_trace_managed, "MANAGED ") \
+ EM(afs_cell_trace_see_source, "SEE source") \
+- EM(afs_cell_trace_see_ws, "SEE ws ") \
++ EM(afs_cell_trace_see_mgmt_timer, "SEE mtimer") \
+ EM(afs_cell_trace_unuse_alias, "UNU alias ") \
+ EM(afs_cell_trace_unuse_check_alias, "UNU chk-al") \
+ EM(afs_cell_trace_unuse_delete, "UNU delete") \
+ EM(afs_cell_trace_unuse_dynroot_mntpt, "UNU dyn-mp") \
+ EM(afs_cell_trace_unuse_fc, "UNU fc ") \
+- EM(afs_cell_trace_unuse_lookup, "UNU lookup") \
+ EM(afs_cell_trace_unuse_lookup_dynroot, "UNU lu-dyn") \
+ EM(afs_cell_trace_unuse_lookup_error, "UNU lu-err") \
+ EM(afs_cell_trace_unuse_mntpt, "UNU mntpt ") \
+ EM(afs_cell_trace_unuse_no_pin, "UNU no-pin") \
+ EM(afs_cell_trace_unuse_parse, "UNU parse ") \
+ EM(afs_cell_trace_unuse_pin, "UNU pin ") \
+- EM(afs_cell_trace_unuse_probe, "UNU probe ") \
+ EM(afs_cell_trace_unuse_sbi, "UNU sbi ") \
+ EM(afs_cell_trace_unuse_ws, "UNU ws ") \
+ EM(afs_cell_trace_use_alias, "USE alias ") \
--- /dev/null
+From stable+bounces-290950-greg=kroah.com@vger.kernel.org Thu Jul 30 03:04:15 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:34 -0400
+Subject: afs: Use the per-peer app data provided by rxrpc
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-5-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 40e8b52fe8c8ab6920ea5f59c5469b6918cce624 ]
+
+Make use of the per-peer application data that rxrpc now allows the
+application to store on the rxrpc_peer struct to hold a back pointer to the
+afs_server record that peer represents an endpoint for.
+
+Then, when a call comes in to the AFS cache manager, this can be used to
+map it to the correct server record rather than having to use a
+UUID-to-server mapping table and having to do an additional lookup.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-14-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-10-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/addr_list.c | 50 +++++++++++++++++++++++++
+ fs/afs/cmservice.c | 87 +++++++++------------------------------------
+ fs/afs/fs_probe.c | 32 +++++++++++-----
+ fs/afs/internal.h | 9 +++-
+ fs/afs/proc.c | 10 ++++-
+ fs/afs/rxrpc.c | 6 +++
+ fs/afs/server.c | 46 ++++++-----------------
+ include/trace/events/afs.h | 4 --
+ net/rxrpc/peer_object.c | 4 +-
+ 9 files changed, 125 insertions(+), 123 deletions(-)
+
+--- a/fs/afs/addr_list.c
++++ b/fs/afs/addr_list.c
+@@ -362,3 +362,53 @@ int afs_merge_fs_addr6(struct afs_net *n
+ alist->nr_addrs++;
+ return 0;
+ }
++
++/*
++ * Set the app data on the rxrpc peers an address list points to
++ */
++void afs_set_peer_appdata(struct afs_server *server,
++ struct afs_addr_list *old_alist,
++ struct afs_addr_list *new_alist)
++{
++ unsigned long data = (unsigned long)server;
++ int n = 0, o = 0;
++
++ if (!old_alist) {
++ /* New server. Just set all. */
++ for (; n < new_alist->nr_addrs; n++)
++ rxrpc_kernel_set_peer_data(new_alist->addrs[n].peer, data);
++ return;
++ }
++ if (!new_alist) {
++ /* Dead server. Just remove all. */
++ for (; o < old_alist->nr_addrs; o++)
++ rxrpc_kernel_set_peer_data(old_alist->addrs[o].peer, 0);
++ return;
++ }
++
++ /* Walk through the two lists simultaneously, setting new peers and
++ * clearing old ones. The two lists are ordered by pointer to peer
++ * record.
++ */
++ while (n < new_alist->nr_addrs && o < old_alist->nr_addrs) {
++ struct rxrpc_peer *pn = new_alist->addrs[n].peer;
++ struct rxrpc_peer *po = old_alist->addrs[o].peer;
++
++ if (pn == po)
++ continue;
++ if (pn < po) {
++ rxrpc_kernel_set_peer_data(pn, data);
++ n++;
++ } else {
++ rxrpc_kernel_set_peer_data(po, 0);
++ o++;
++ }
++ }
++
++ if (n < new_alist->nr_addrs)
++ for (; n < new_alist->nr_addrs; n++)
++ rxrpc_kernel_set_peer_data(new_alist->addrs[n].peer, data);
++ if (o < old_alist->nr_addrs)
++ for (; o < old_alist->nr_addrs; o++)
++ rxrpc_kernel_set_peer_data(old_alist->addrs[o].peer, 0);
++}
+--- a/fs/afs/cmservice.c
++++ b/fs/afs/cmservice.c
+@@ -139,49 +139,6 @@ bool afs_cm_incoming_call(struct afs_cal
+ }
+
+ /*
+- * Find the server record by peer address and record a probe to the cache
+- * manager from a server.
+- */
+-static int afs_find_cm_server_by_peer(struct afs_call *call)
+-{
+- struct sockaddr_rxrpc srx;
+- struct afs_server *server;
+- struct rxrpc_peer *peer;
+-
+- peer = rxrpc_kernel_get_call_peer(call->net->socket, call->rxcall);
+-
+- server = afs_find_server(call->net, peer);
+- if (!server) {
+- trace_afs_cm_no_server(call, &srx);
+- return 0;
+- }
+-
+- call->server = server;
+- return 0;
+-}
+-
+-/*
+- * Find the server record by server UUID and record a probe to the cache
+- * manager from a server.
+- */
+-static int afs_find_cm_server_by_uuid(struct afs_call *call,
+- struct afs_uuid *uuid)
+-{
+- struct afs_server *server;
+-
+- rcu_read_lock();
+- server = afs_find_server_by_uuid(call->net, call->request);
+- rcu_read_unlock();
+- if (!server) {
+- trace_afs_cm_no_server_u(call, call->request);
+- return 0;
+- }
+-
+- call->server = server;
+- return 0;
+-}
+-
+-/*
+ * Clean up a cache manager call.
+ */
+ static void afs_cm_destructor(struct afs_call *call)
+@@ -322,10 +279,7 @@ static int afs_deliver_cb_callback(struc
+
+ if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+ return afs_io_error(call, afs_io_error_cm_reply);
+-
+- /* we'll need the file server record as that tells us which set of
+- * vnodes to operate upon */
+- return afs_find_cm_server_by_peer(call);
++ return 0;
+ }
+
+ /*
+@@ -349,18 +303,10 @@ static void SRXAFSCB_InitCallBackState(s
+ */
+ static int afs_deliver_cb_init_call_back_state(struct afs_call *call)
+ {
+- int ret;
+-
+ _enter("");
+
+ afs_extract_discard(call, 0);
+- ret = afs_extract_data(call, false);
+- if (ret < 0)
+- return ret;
+-
+- /* we'll need the file server record as that tells us which set of
+- * vnodes to operate upon */
+- return afs_find_cm_server_by_peer(call);
++ return afs_extract_data(call, false);
+ }
+
+ /*
+@@ -373,8 +319,6 @@ static int afs_deliver_cb_init_call_back
+ __be32 *b;
+ int ret;
+
+- _enter("");
+-
+ _enter("{%u}", call->unmarshall);
+
+ switch (call->unmarshall) {
+@@ -420,9 +364,18 @@ static int afs_deliver_cb_init_call_back
+ if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+ return afs_io_error(call, afs_io_error_cm_reply);
+
+- /* we'll need the file server record as that tells us which set of
+- * vnodes to operate upon */
+- return afs_find_cm_server_by_uuid(call, call->request);
++ if (!call->server) {
++ trace_afs_cm_no_server_u(call, call->request);
++ return 0;
++ }
++
++ if (memcmp(call->request, &call->server->_uuid, sizeof(call->server->_uuid)) != 0) {
++ pr_notice("Callback UUID does not match fileserver UUID\n");
++ trace_afs_cm_no_server_u(call, call->request);
++ return 0;
++ }
++
++ return 0;
+ }
+
+ /*
+@@ -454,7 +407,7 @@ static int afs_deliver_cb_probe(struct a
+
+ if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+ return afs_io_error(call, afs_io_error_cm_reply);
+- return afs_find_cm_server_by_peer(call);
++ return 0;
+ }
+
+ /*
+@@ -531,7 +484,7 @@ static int afs_deliver_cb_probe_uuid(str
+
+ if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+ return afs_io_error(call, afs_io_error_cm_reply);
+- return afs_find_cm_server_by_peer(call);
++ return 0;
+ }
+
+ /*
+@@ -591,7 +544,7 @@ static int afs_deliver_cb_tell_me_about_
+
+ if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+ return afs_io_error(call, afs_io_error_cm_reply);
+- return afs_find_cm_server_by_peer(call);
++ return 0;
+ }
+
+ /*
+@@ -665,9 +618,5 @@ static int afs_deliver_yfs_cb_callback(s
+
+ if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
+ return afs_io_error(call, afs_io_error_cm_reply);
+-
+- /* We'll need the file server record as that tells us which set of
+- * vnodes to operate upon.
+- */
+- return afs_find_cm_server_by_peer(call);
++ return 0;
+ }
+--- a/fs/afs/fs_probe.c
++++ b/fs/afs/fs_probe.c
+@@ -235,20 +235,20 @@ out:
+ * Probe all of a fileserver's addresses to find out the best route and to
+ * query its capabilities.
+ */
+-void afs_fs_probe_fileserver(struct afs_net *net, struct afs_server *server,
+- struct afs_addr_list *new_alist, struct key *key)
++int afs_fs_probe_fileserver(struct afs_net *net, struct afs_server *server,
++ struct afs_addr_list *new_alist, struct key *key)
+ {
+ struct afs_endpoint_state *estate, *old;
+- struct afs_addr_list *alist;
++ struct afs_addr_list *old_alist = NULL, *alist;
+ unsigned long unprobed;
+
+ _enter("%pU", &server->uuid);
+
+ estate = kzalloc(sizeof(*estate), GFP_KERNEL);
+ if (!estate)
+- return;
++ return -ENOMEM;
+
+- refcount_set(&estate->ref, 1);
++ refcount_set(&estate->ref, 2);
+ estate->server_id = server->debug_id;
+ estate->rtt = UINT_MAX;
+
+@@ -256,21 +256,31 @@ void afs_fs_probe_fileserver(struct afs_
+
+ old = rcu_dereference_protected(server->endpoint_state,
+ lockdep_is_held(&server->fs_lock));
+- estate->responsive_set = old->responsive_set;
+- estate->addresses = afs_get_addrlist(new_alist ?: old->addresses,
+- afs_alist_trace_get_estate);
++ if (old) {
++ estate->responsive_set = old->responsive_set;
++ if (!new_alist)
++ new_alist = old->addresses;
++ }
++
++ if (old_alist != new_alist)
++ afs_set_peer_appdata(server, old_alist, new_alist);
++
++ estate->addresses = afs_get_addrlist(new_alist, afs_alist_trace_get_estate);
+ alist = estate->addresses;
+ estate->probe_seq = ++server->probe_counter;
+ atomic_set(&estate->nr_probing, alist->nr_addrs);
+
++ if (new_alist)
++ server->addr_version = new_alist->version;
+ rcu_assign_pointer(server->endpoint_state, estate);
+- set_bit(AFS_ESTATE_SUPERSEDED, &old->flags);
+ write_unlock(&server->fs_lock);
++ if (old)
++ set_bit(AFS_ESTATE_SUPERSEDED, &old->flags);
+
+ trace_afs_estate(estate->server_id, estate->probe_seq, refcount_read(&estate->ref),
+ afs_estate_trace_alloc_probe);
+
+- afs_get_address_preferences(net, alist);
++ afs_get_address_preferences(net, new_alist);
+
+ server->probed_at = jiffies;
+ unprobed = (1UL << alist->nr_addrs) - 1;
+@@ -293,6 +303,8 @@ void afs_fs_probe_fileserver(struct afs_
+ }
+
+ afs_put_endpoint_state(old, afs_estate_trace_put_probe);
++ afs_put_endpoint_state(estate, afs_estate_trace_put_probe);
++ return 0;
+ }
+
+ /*
+--- a/fs/afs/internal.h
++++ b/fs/afs/internal.h
+@@ -1004,6 +1004,9 @@ extern int afs_merge_fs_addr4(struct afs
+ __be32 xdr, u16 port);
+ extern int afs_merge_fs_addr6(struct afs_net *net, struct afs_addr_list *addr,
+ __be32 *xdr, u16 port);
++void afs_set_peer_appdata(struct afs_server *server,
++ struct afs_addr_list *old_alist,
++ struct afs_addr_list *new_alist);
+
+ /*
+ * addr_prefs.c
+@@ -1190,8 +1193,8 @@ struct afs_endpoint_state *afs_get_endpo
+ enum afs_estate_trace where);
+ void afs_put_endpoint_state(struct afs_endpoint_state *estate, enum afs_estate_trace where);
+ extern void afs_fileserver_probe_result(struct afs_call *);
+-void afs_fs_probe_fileserver(struct afs_net *net, struct afs_server *server,
+- struct afs_addr_list *new_addrs, struct key *key);
++int afs_fs_probe_fileserver(struct afs_net *net, struct afs_server *server,
++ struct afs_addr_list *new_alist, struct key *key);
+ int afs_wait_for_fs_probes(struct afs_operation *op, struct afs_server_state *states, bool intr);
+ extern void afs_probe_fileserver(struct afs_net *, struct afs_server *);
+ extern void afs_fs_probe_dispatcher(struct work_struct *);
+@@ -1474,7 +1477,7 @@ extern void __exit afs_clean_up_permit_c
+ */
+ extern spinlock_t afs_server_peer_lock;
+
+-extern struct afs_server *afs_find_server(struct afs_net *, const struct rxrpc_peer *);
++struct afs_server *afs_find_server(const struct rxrpc_peer *peer);
+ extern struct afs_server *afs_find_server_by_uuid(struct afs_net *, const uuid_t *);
+ extern struct afs_server *afs_lookup_server(struct afs_cell *, struct key *, const uuid_t *, u32);
+ extern struct afs_server *afs_get_server(struct afs_server *, enum afs_server_trace);
+--- a/fs/afs/proc.c
++++ b/fs/afs/proc.c
+@@ -444,8 +444,6 @@ static int afs_proc_servers_show(struct
+ }
+
+ server = list_entry(v, struct afs_server, proc_link);
+- estate = rcu_dereference(server->endpoint_state);
+- alist = estate->addresses;
+ seq_printf(m, "%pU %3d %3d %s\n",
+ &server->uuid,
+ refcount_read(&server->ref),
+@@ -455,10 +453,16 @@ static int afs_proc_servers_show(struct
+ server->flags, server->rtt);
+ seq_printf(m, " - probe: last=%d\n",
+ (int)(jiffies - server->probed_at) / HZ);
++
++ estate = rcu_dereference(server->endpoint_state);
++ if (!estate)
++ goto out;
+ failed = estate->failed_set;
+ seq_printf(m, " - ESTATE pq=%x np=%u rsp=%lx f=%lx\n",
+ estate->probe_seq, atomic_read(&estate->nr_probing),
+ estate->responsive_set, estate->failed_set);
++
++ alist = estate->addresses;
+ seq_printf(m, " - ALIST v=%u ap=%u\n",
+ alist->version, alist->addr_pref_version);
+ for (i = 0; i < alist->nr_addrs; i++) {
+@@ -471,6 +475,8 @@ static int afs_proc_servers_show(struct
+ rxrpc_kernel_get_srtt(addr->peer),
+ addr->last_error, addr->prio);
+ }
++
++out:
+ return 0;
+ }
+
+--- a/fs/afs/rxrpc.c
++++ b/fs/afs/rxrpc.c
+@@ -791,8 +791,14 @@ static void afs_rx_discard_new_call(stru
+ static void afs_rx_new_call(struct sock *sk, struct rxrpc_call *rxcall,
+ unsigned long user_call_ID)
+ {
++ struct afs_call *call = (struct afs_call *)user_call_ID;
+ struct afs_net *net = afs_sock2net(sk);
+
++ call->peer = rxrpc_kernel_get_call_peer(sk->sk_socket, call->rxcall);
++ call->server = afs_find_server(call->peer);
++ if (!call->server)
++ trace_afs_cm_no_server(call, rxrpc_kernel_remote_srx(call->peer));
++
+ if (net->live)
+ queue_work(afs_wq, &net->charge_preallocation_work);
+ }
+--- a/fs/afs/server.c
++++ b/fs/afs/server.c
+@@ -21,42 +21,13 @@ static void __afs_put_server(struct afs_
+ /*
+ * Find a server by one of its addresses.
+ */
+-struct afs_server *afs_find_server(struct afs_net *net, const struct rxrpc_peer *peer)
++struct afs_server *afs_find_server(const struct rxrpc_peer *peer)
+ {
+- const struct afs_endpoint_state *estate;
+- const struct afs_addr_list *alist;
+- struct afs_server *server = NULL;
+- unsigned int i;
+- int seq = 1;
+-
+- rcu_read_lock();
+-
+- do {
+- if (server)
+- afs_unuse_server_notime(net, server, afs_server_trace_unuse_find_rsq);
+- server = NULL;
+- seq++; /* 2 on the 1st/lockless path, otherwise odd */
+- read_seqbegin_or_lock(&net->fs_addr_lock, &seq);
+-
+- hlist_for_each_entry_rcu(server, &net->fs_addresses, addr_link) {
+- estate = rcu_dereference(server->endpoint_state);
+- alist = estate->addresses;
+- for (i = 0; i < alist->nr_addrs; i++)
+- if (alist->addrs[i].peer == peer)
+- goto found;
+- }
++ struct afs_server *server = (struct afs_server *)rxrpc_kernel_get_peer_data(peer);
+
+- server = NULL;
+- continue;
+- found:
+- server = afs_maybe_use_server(server, afs_server_trace_use_by_addr);
+-
+- } while (need_seqretry(&net->fs_addr_lock, seq));
+-
+- done_seqretry(&net->fs_addr_lock, seq);
+-
+- rcu_read_unlock();
+- return server;
++ if (!server)
++ return NULL;
++ return afs_maybe_use_server(server, afs_server_trace_use_cm_call);
+ }
+
+ /*
+@@ -469,9 +440,16 @@ static void afs_give_up_callbacks(struct
+ */
+ static void afs_destroy_server(struct afs_net *net, struct afs_server *server)
+ {
++ struct afs_endpoint_state *estate;
++
+ if (test_bit(AFS_SERVER_FL_MAY_HAVE_CB, &server->flags))
+ afs_give_up_callbacks(net, server);
+
++ /* Unbind the rxrpc_peer records from the server. */
++ estate = rcu_access_pointer(server->endpoint_state);
++ if (estate)
++ afs_set_peer_appdata(server, estate->addresses, NULL);
++
+ afs_put_server(net, server, afs_server_trace_destroy);
+ }
+
+--- a/include/trace/events/afs.h
++++ b/include/trace/events/afs.h
+@@ -140,12 +140,10 @@ enum yfs_cm_operation {
+ EM(afs_server_trace_see_expired, "SEE expd ") \
+ EM(afs_server_trace_unuse_call, "UNU call ") \
+ EM(afs_server_trace_unuse_create_fail, "UNU cfail") \
+- EM(afs_server_trace_unuse_find_rsq, "UNU f-rsq") \
+ EM(afs_server_trace_unuse_slist, "UNU slist") \
+ EM(afs_server_trace_unuse_slist_isort, "UNU isort") \
+ EM(afs_server_trace_unuse_uuid_rsq, "PUT u-req") \
+ EM(afs_server_trace_update, "UPDATE ") \
+- EM(afs_server_trace_use_by_addr, "USE addr ") \
+ EM(afs_server_trace_use_by_uuid, "USE uuid ") \
+ EM(afs_server_trace_use_cm_call, "USE cm-cl") \
+ EM(afs_server_trace_use_get_caps, "USE gcaps") \
+@@ -1119,7 +1117,7 @@ TRACE_EVENT(afs_bulkstat_error,
+ );
+
+ TRACE_EVENT(afs_cm_no_server,
+- TP_PROTO(struct afs_call *call, struct sockaddr_rxrpc *srx),
++ TP_PROTO(struct afs_call *call, const struct sockaddr_rxrpc *srx),
+
+ TP_ARGS(call, srx),
+
+--- a/net/rxrpc/peer_object.c
++++ b/net/rxrpc/peer_object.c
+@@ -451,7 +451,7 @@ void rxrpc_destroy_all_peers(struct rxrp
+ continue;
+
+ hlist_for_each_entry(peer, &rxnet->peer_hash[i], hash_link) {
+- pr_err("Leaked peer %u {%u} %pISp\n",
++ pr_err("Leaked peer %x {%u} %pISp\n",
+ peer->debug_id,
+ refcount_read(&peer->ref),
+ &peer->srx.transport);
+@@ -468,7 +468,7 @@ void rxrpc_destroy_all_peers(struct rxrp
+ */
+ struct rxrpc_peer *rxrpc_kernel_get_call_peer(struct socket *sock, struct rxrpc_call *call)
+ {
+- return call->peer;
++ return rxrpc_get_peer(call->peer, rxrpc_peer_get_application);
+ }
+ EXPORT_SYMBOL(rxrpc_kernel_get_call_peer);
+
--- /dev/null
+From stable+bounces-287797-greg=kroah.com@vger.kernel.org Wed Jul 22 12:57:35 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 06:51:04 -0400
+Subject: ALSA: hda: conexant: Remove mic bias threshold override
+To: stable@vger.kernel.org
+Cc: Zhang Heng <zhangheng@kylinos.cn>, Takashi Iwai <tiwai@suse.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722105104.989130-1-sashal@kernel.org>
+
+From: Zhang Heng <zhangheng@kylinos.cn>
+
+[ Upstream commit f52524da7084c1a54683ae9fbc73e93fff19dd64 ]
+
+Remove the mic bias current comparator threshold override (NID 0x1c,
+verb 0x320, value 0x010) from Conexant codec driver.
+
+This override was originally intended to support volume up/down controls on
+headsets with inline remote controls, but it causes microphone detection
+failures on some headsets with impedance less than 1k ohm.
+
+After consulting with the vendor's engineers, it was confirmed that this
+setting is board-specific and should be handled by BIOS/firmware rather
+than the generic codec driver, especially since inline remote support
+is not currently implemented.
+
+Fixes: 7aeb25908648 ("ALSA: hda/conexant: Fix headset auto detect fail in cx8070 and SN6140")
+Cc: stable@vger.kernel.org
+Signed-off-by: Zhang Heng <zhangheng@kylinos.cn>
+Link: https://patch.msgid.link/20260713100329.306892-1-zhangheng@kylinos.cn
+Signed-off-by: Takashi Iwai <tiwai@suse.de>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/pci/hda/patch_conexant.c | 3 ---
+ 1 file changed, 3 deletions(-)
+
+--- a/sound/pci/hda/patch_conexant.c
++++ b/sound/pci/hda/patch_conexant.c
+@@ -168,9 +168,6 @@ static void cx_fixup_headset_recog(struc
+ {
+ unsigned int mic_present;
+
+- /* fix some headset type recognize fail issue, such as EDIFIER headset */
+- /* set micbias output current comparator threshold from 66% to 55%. */
+- snd_hda_codec_write(codec, 0x1c, 0, 0x320, 0x010);
+ /* set OFF voltage for DFET from -1.2V to -0.8V, set headset micbias register
+ * value adjustment trim from 2.2K ohms to 2.0K ohms.
+ */
--- /dev/null
+From stable+bounces-287802-greg=kroah.com@vger.kernel.org Wed Jul 22 13:03:12 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 07:01:22 -0400
+Subject: ALSA: hda: Fix cached processing coefficient verbs
+To: stable@vger.kernel.org
+Cc: Xu Rao <raoxu@uniontech.com>, Takashi Iwai <tiwai@suse.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722110122.1006393-1-sashal@kernel.org>
+
+From: Xu Rao <raoxu@uniontech.com>
+
+[ Upstream commit f67be28fdf8b5d31ac1cc1152bb17250f9f8f513 ]
+
+Intel HD Audio defines Coefficient Index and Processing Coefficient as
+separate audio widget controls in the Audio Widget Verb Definitions:
+Coefficient Index selects the coefficient slot, while Processing
+Coefficient accesses the value at the selected slot.
+
+hda_reg_read_coef() selects the slot with AC_VERB_SET_COEF_INDEX, but
+then uses AC_VERB_GET_COEF_INDEX for the value read. That reads back the
+selected index instead of the coefficient value. hda_reg_write_coef()
+has the same issue and builds the value write from AC_VERB_GET_COEF_INDEX
+instead of AC_VERB_SET_PROC_COEF.
+
+This only affects the regmap coefficient cache path used by codecs that
+set codec->cache_coef. Direct coefficient helpers already use the normal
+SET_COEF_INDEX followed by GET_PROC_COEF or SET_PROC_COEF sequence, which
+is likely why this has not been noticed widely.
+
+Use AC_VERB_GET_PROC_COEF for cached coefficient reads and
+AC_VERB_SET_PROC_COEF for cached coefficient writes.
+
+Fixes: 40ba66a702b8 ("ALSA: hda - Add cache support for COEF read/write")
+Cc: stable@vger.kernel.org
+Signed-off-by: Xu Rao <raoxu@uniontech.com>
+Link: https://patch.msgid.link/DB9023BF2920BA99+20260707132419.1731342-1-raoxu@uniontech.com
+Signed-off-by: Takashi Iwai <tiwai@suse.de>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/hda/hdac_regmap.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/sound/hda/hdac_regmap.c
++++ b/sound/hda/hdac_regmap.c
+@@ -214,7 +214,7 @@ static int hda_reg_read_coef(struct hdac
+ err = snd_hdac_exec_verb(codec, verb, 0, NULL);
+ if (err < 0)
+ return err;
+- verb = (reg & ~0xfffff) | (AC_VERB_GET_COEF_INDEX << 8);
++ verb = (reg & ~0xfffff) | (AC_VERB_GET_PROC_COEF << 8);
+ return snd_hdac_exec_verb(codec, verb, 0, val);
+ }
+
+@@ -232,7 +232,7 @@ static int hda_reg_write_coef(struct hda
+ err = snd_hdac_exec_verb(codec, verb, 0, NULL);
+ if (err < 0)
+ return err;
+- verb = (reg & ~0xfffff) | (AC_VERB_GET_COEF_INDEX << 8) |
++ verb = (reg & ~0xfffff) | (AC_VERB_SET_PROC_COEF << 8) |
+ (val & 0xffff);
+ return snd_hdac_exec_verb(codec, verb, 0, NULL);
+ }
--- /dev/null
+From stable+bounces-288120-greg=kroah.com@vger.kernel.org Thu Jul 23 01:53:22 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 19:46:59 -0400
+Subject: arm64: dts: qcom: correct RBR opp entry
+To: stable@vger.kernel.org
+Cc: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>, Bjorn Andersson <andersson@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722234700.2612312-1-sashal@kernel.org>
+
+From: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+
+[ Upstream commit a5c21b9bd5f531e50141b0484faabb707b92f1e2 ]
+
+DisplayPort Reduced Bit Rate uses link rate of 1.62 Gbps, the main link
+clock should be 162 MHz. Having the incorrect frequency (160 MHz) in the
+OPP table will result in selecting wrong link frequency. Correct the
+entry in the OPP table.
+
+Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Link: https://lore.kernel.org/r/20260304-msm-fix-rbr-v1-1-b9eba986eaef@oss.qualcomm.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Stable-dep-of: c17e22094667 ("arm64: dts: qcom: hamoa: Fix OPP tables for all DisplayPort controllers")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/arm64/boot/dts/qcom/sc7180.dtsi | 4 ++--
+ arch/arm64/boot/dts/qcom/sc7280.dtsi | 8 ++++----
+ arch/arm64/boot/dts/qcom/sc8180x.dtsi | 12 ++++++------
+ arch/arm64/boot/dts/qcom/sc8280xp.dtsi | 32 ++++++++++++++++----------------
+ arch/arm64/boot/dts/qcom/sm6350.dtsi | 4 ++--
+ arch/arm64/boot/dts/qcom/sm8150.dtsi | 4 ++--
+ arch/arm64/boot/dts/qcom/sm8250.dtsi | 4 ++--
+ arch/arm64/boot/dts/qcom/sm8350.dtsi | 4 ++--
+ arch/arm64/boot/dts/qcom/sm8450.dtsi | 4 ++--
+ arch/arm64/boot/dts/qcom/x1e80100.dtsi | 16 ++++++++--------
+ 10 files changed, 46 insertions(+), 46 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/sc7180.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc7180.dtsi
+@@ -3410,8 +3410,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sc7280.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc7280.dtsi
+@@ -4723,8 +4723,8 @@
+ edp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -4820,8 +4820,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sc8180x.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc8180x.dtsi
+@@ -3269,8 +3269,8 @@
+ dp0_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -3347,8 +3347,8 @@
+ dp1_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -3415,8 +3415,8 @@
+ edp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sc8280xp.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc8280xp.dtsi
+@@ -4259,8 +4259,8 @@
+ mdss0_dp0_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -4337,8 +4337,8 @@
+ mdss0_dp1_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -4409,8 +4409,8 @@
+ mdss0_dp2_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -4481,8 +4481,8 @@
+ mdss0_dp3_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -5547,8 +5547,8 @@
+ mdss1_dp0_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -5619,8 +5619,8 @@
+ mdss1_dp1_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -5691,8 +5691,8 @@
+ mdss1_dp2_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -5763,8 +5763,8 @@
+ mdss1_dp3_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sm6350.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm6350.dtsi
+@@ -2222,8 +2222,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sm8150.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm8150.dtsi
+@@ -3932,8 +3932,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sm8250.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm8250.dtsi
+@@ -4818,8 +4818,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sm8350.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm8350.dtsi
+@@ -2909,8 +2909,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sm8450.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm8450.dtsi
+@@ -3223,8 +3223,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/x1e80100.dtsi
++++ b/arch/arm64/boot/dts/qcom/x1e80100.dtsi
+@@ -4758,8 +4758,8 @@
+ mdss_dp0_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -4841,8 +4841,8 @@
+ mdss_dp1_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -4923,8 +4923,8 @@
+ mdss_dp2_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -5002,8 +5002,8 @@
+ mdss_dp3_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
--- /dev/null
+From stable+bounces-288119-greg=kroah.com@vger.kernel.org Thu Jul 23 01:53:30 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 19:47:00 -0400
+Subject: arm64: dts: qcom: hamoa: Fix OPP tables for all DisplayPort controllers
+To: stable@vger.kernel.org
+Cc: Abel Vesa <abel.vesa@oss.qualcomm.com>, Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>, Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>, Bjorn Andersson <andersson@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722234700.2612312-2-sashal@kernel.org>
+
+From: Abel Vesa <abel.vesa@oss.qualcomm.com>
+
+[ Upstream commit c17e220946675232d383620ed9cff6685735ec48 ]
+
+According to internal documentation, the corners specific for each rate
+from the DP link clock are:
+ - LOWSVS_D1 -> 19.2 MHz
+ - LOWSVS -> 270 MHz
+ - SVS -> 540 MHz (594 MHz in case of DP3)
+ - SVS_L1 -> 594 MHz
+ - NOM -> 810 MHz
+ - NOM_L1 -> 810 MHz
+ - TURBO -> 810 MHz
+
+So fix all tables for each of the four controllers according to the
+documentation, but since DP0 through DP2 have the same entries in their
+tables, lets drop the DP1 and DP2 and have all of them share the DP0
+table instead. However keep a separate table for the DP3 as it is
+different for the SVS, compared to the rest of the controllers.
+
+The 19.2 MHz @ LOWSVS_D1 isn't needed as it's not an actual working
+frequency and the controller will never select it. So remove it.
+
+Cc: stable@vger.kernel.org # v6.9+
+Fixes: 1940c25eaa63 ("arm64: dts: qcom: x1e80100: Add display nodes")
+Suggested-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
+Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
+Link: https://lore.kernel.org/r/20260323-hamoa-fix-dp3-opp-table-v3-1-a823776bd1b0@oss.qualcomm.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/arm64/boot/dts/qcom/x1e80100.dtsi | 77 +++++----------------------------
+ 1 file changed, 12 insertions(+), 65 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/x1e80100.dtsi
++++ b/arch/arm64/boot/dts/qcom/x1e80100.dtsi
+@@ -4758,18 +4758,18 @@
+ mdss_dp0_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-162000000 {
+- opp-hz = /bits/ 64 <162000000>;
+- required-opps = <&rpmhpd_opp_low_svs>;
+- };
+-
+ opp-270000000 {
+ opp-hz = /bits/ 64 <270000000>;
+- required-opps = <&rpmhpd_opp_svs>;
++ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+ opp-540000000 {
+ opp-hz = /bits/ 64 <540000000>;
++ required-opps = <&rpmhpd_opp_svs>;
++ };
++
++ opp-594000000 {
++ opp-hz = /bits/ 64 <594000000>;
+ required-opps = <&rpmhpd_opp_svs_l1>;
+ };
+
+@@ -4806,7 +4806,7 @@
+ assigned-clock-parents = <&usb_1_ss1_qmpphy QMP_USB43DP_DP_LINK_CLK>,
+ <&usb_1_ss1_qmpphy QMP_USB43DP_DP_VCO_DIV_CLK>;
+
+- operating-points-v2 = <&mdss_dp1_opp_table>;
++ operating-points-v2 = <&mdss_dp0_opp_table>;
+
+ power-domains = <&rpmhpd RPMHPD_MMCX>;
+
+@@ -4837,30 +4837,6 @@
+ };
+ };
+ };
+-
+- mdss_dp1_opp_table: opp-table {
+- compatible = "operating-points-v2";
+-
+- opp-162000000 {
+- opp-hz = /bits/ 64 <162000000>;
+- required-opps = <&rpmhpd_opp_low_svs>;
+- };
+-
+- opp-270000000 {
+- opp-hz = /bits/ 64 <270000000>;
+- required-opps = <&rpmhpd_opp_svs>;
+- };
+-
+- opp-540000000 {
+- opp-hz = /bits/ 64 <540000000>;
+- required-opps = <&rpmhpd_opp_svs_l1>;
+- };
+-
+- opp-810000000 {
+- opp-hz = /bits/ 64 <810000000>;
+- required-opps = <&rpmhpd_opp_nom>;
+- };
+- };
+ };
+
+ mdss_dp2: displayport-controller@ae9a000 {
+@@ -4889,7 +4865,7 @@
+ assigned-clock-parents = <&usb_1_ss2_qmpphy QMP_USB43DP_DP_LINK_CLK>,
+ <&usb_1_ss2_qmpphy QMP_USB43DP_DP_VCO_DIV_CLK>;
+
+- operating-points-v2 = <&mdss_dp2_opp_table>;
++ operating-points-v2 = <&mdss_dp0_opp_table>;
+
+ power-domains = <&rpmhpd RPMHPD_MMCX>;
+
+@@ -4919,30 +4895,6 @@
+ };
+ };
+ };
+-
+- mdss_dp2_opp_table: opp-table {
+- compatible = "operating-points-v2";
+-
+- opp-162000000 {
+- opp-hz = /bits/ 64 <162000000>;
+- required-opps = <&rpmhpd_opp_low_svs>;
+- };
+-
+- opp-270000000 {
+- opp-hz = /bits/ 64 <270000000>;
+- required-opps = <&rpmhpd_opp_svs>;
+- };
+-
+- opp-540000000 {
+- opp-hz = /bits/ 64 <540000000>;
+- required-opps = <&rpmhpd_opp_svs_l1>;
+- };
+-
+- opp-810000000 {
+- opp-hz = /bits/ 64 <810000000>;
+- required-opps = <&rpmhpd_opp_nom>;
+- };
+- };
+ };
+
+ mdss_dp3: displayport-controller@aea0000 {
+@@ -5002,19 +4954,14 @@
+ mdss_dp3_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-162000000 {
+- opp-hz = /bits/ 64 <162000000>;
+- required-opps = <&rpmhpd_opp_low_svs>;
+- };
+-
+ opp-270000000 {
+ opp-hz = /bits/ 64 <270000000>;
+- required-opps = <&rpmhpd_opp_svs>;
++ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+- opp-540000000 {
+- opp-hz = /bits/ 64 <540000000>;
+- required-opps = <&rpmhpd_opp_svs_l1>;
++ opp-594000000 {
++ opp-hz = /bits/ 64 <594000000>;
++ required-opps = <&rpmhpd_opp_svs>;
+ };
+
+ opp-810000000 {
--- /dev/null
+From stable+bounces-288164-greg=kroah.com@vger.kernel.org Thu Jul 23 04:29:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 22:29:06 -0400
+Subject: ASoC: mediatek: mt8183-afe-pcm: Shorten memif_data table using macros
+To: stable@vger.kernel.org
+Cc: Chen-Yu Tsai <wenst@chromium.org>, AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>, Mark Brown <broonie@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723022909.2845199-1-sashal@kernel.org>
+
+From: Chen-Yu Tsai <wenst@chromium.org>
+
+[ Upstream commit 91c2685430f217ae8f2866e4372948eaf123b5c4 ]
+
+The memif_data table describes all the supported PCM channels for the
+audio frontend. Most of the fields are either the same or can be derived
+from the interface's name. This results in a very long table (in source
+code) that can be shortened with macros.
+
+Do just that. Some "convenience" macros were added to cover non-existent
+register fields that would otherwise require multiple layers of macros
+to handle.
+
+Signed-off-by: Chen-Yu Tsai <wenst@chromium.org>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Link: https://patch.msgid.link/20250515073825.4155297-2-wenst@chromium.org
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Stable-dep-of: f0334fbfd107 ("ASoC: mediatek: mt8183: Check runtime resume during probe")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/soc/mediatek/mt8183/mt8183-afe-pcm.c | 243 ++++++-----------------------
+ 1 file changed, 54 insertions(+), 189 deletions(-)
+
+--- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
++++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
+@@ -425,196 +425,61 @@ static const struct snd_soc_component_dr
+ .name = "mt8183-afe-pcm-dai",
+ };
+
++#define MT8183_MEMIF_BASE(_id, _en_reg, _fs_reg, _mono_reg) \
++ [MT8183_MEMIF_##_id] = { \
++ .name = #_id, \
++ .id = MT8183_MEMIF_##_id, \
++ .reg_ofs_base = AFE_##_id##_BASE, \
++ .reg_ofs_cur = AFE_##_id##_CUR, \
++ .reg_ofs_end = AFE_##_id##_END, \
++ .fs_reg = (_fs_reg), \
++ .fs_shift = _id##_MODE_SFT, \
++ .fs_maskbit = _id##_MODE_MASK, \
++ .mono_reg = (_mono_reg), \
++ .mono_shift = _id##_DATA_SFT, \
++ .enable_reg = (_en_reg), \
++ .enable_shift = _id##_ON_SFT, \
++ .hd_reg = AFE_MEMIF_HD_MODE, \
++ .hd_align_reg = AFE_MEMIF_HDALIGN, \
++ .hd_shift = _id##_HD_SFT, \
++ .hd_align_mshift = _id##_HD_ALIGN_SFT, \
++ .agent_disable_reg = -1, \
++ .agent_disable_shift = -1, \
++ .msb_reg = -1, \
++ .msb_shift = -1, \
++ }
++
++#define MT8183_MEMIF(_id, _fs_reg, _mono_reg) \
++ MT8183_MEMIF_BASE(_id, AFE_DAC_CON0, _fs_reg, _mono_reg)
++
++/* For convenience with macros: missing register fields */
++#define MOD_DAI_DATA_SFT -1
++#define HDMI_MODE_SFT -1
++#define HDMI_MODE_MASK -1
++#define HDMI_DATA_SFT -1
++#define HDMI_ON_SFT -1
++
++/* For convenience with macros: register name differences */
++#define AFE_VUL12_BASE AFE_VUL_D2_BASE
++#define AFE_VUL12_CUR AFE_VUL_D2_CUR
++#define AFE_VUL12_END AFE_VUL_D2_END
++#define AWB2_HD_ALIGN_SFT AWB2_ALIGN_SFT
++#define VUL12_DATA_SFT VUL12_MONO_SFT
++#define AFE_HDMI_BASE AFE_HDMI_OUT_BASE
++#define AFE_HDMI_CUR AFE_HDMI_OUT_CUR
++#define AFE_HDMI_END AFE_HDMI_OUT_END
++
+ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = {
+- [MT8183_MEMIF_DL1] = {
+- .name = "DL1",
+- .id = MT8183_MEMIF_DL1,
+- .reg_ofs_base = AFE_DL1_BASE,
+- .reg_ofs_cur = AFE_DL1_CUR,
+- .fs_reg = AFE_DAC_CON1,
+- .fs_shift = DL1_MODE_SFT,
+- .fs_maskbit = DL1_MODE_MASK,
+- .mono_reg = AFE_DAC_CON1,
+- .mono_shift = DL1_DATA_SFT,
+- .enable_reg = AFE_DAC_CON0,
+- .enable_shift = DL1_ON_SFT,
+- .hd_reg = AFE_MEMIF_HD_MODE,
+- .hd_align_reg = AFE_MEMIF_HDALIGN,
+- .hd_shift = DL1_HD_SFT,
+- .hd_align_mshift = DL1_HD_ALIGN_SFT,
+- .agent_disable_reg = -1,
+- .agent_disable_shift = -1,
+- .msb_reg = -1,
+- .msb_shift = -1,
+- },
+- [MT8183_MEMIF_DL2] = {
+- .name = "DL2",
+- .id = MT8183_MEMIF_DL2,
+- .reg_ofs_base = AFE_DL2_BASE,
+- .reg_ofs_cur = AFE_DL2_CUR,
+- .fs_reg = AFE_DAC_CON1,
+- .fs_shift = DL2_MODE_SFT,
+- .fs_maskbit = DL2_MODE_MASK,
+- .mono_reg = AFE_DAC_CON1,
+- .mono_shift = DL2_DATA_SFT,
+- .enable_reg = AFE_DAC_CON0,
+- .enable_shift = DL2_ON_SFT,
+- .hd_reg = AFE_MEMIF_HD_MODE,
+- .hd_align_reg = AFE_MEMIF_HDALIGN,
+- .hd_shift = DL2_HD_SFT,
+- .hd_align_mshift = DL2_HD_ALIGN_SFT,
+- .agent_disable_reg = -1,
+- .agent_disable_shift = -1,
+- .msb_reg = -1,
+- .msb_shift = -1,
+- },
+- [MT8183_MEMIF_DL3] = {
+- .name = "DL3",
+- .id = MT8183_MEMIF_DL3,
+- .reg_ofs_base = AFE_DL3_BASE,
+- .reg_ofs_cur = AFE_DL3_CUR,
+- .fs_reg = AFE_DAC_CON2,
+- .fs_shift = DL3_MODE_SFT,
+- .fs_maskbit = DL3_MODE_MASK,
+- .mono_reg = AFE_DAC_CON1,
+- .mono_shift = DL3_DATA_SFT,
+- .enable_reg = AFE_DAC_CON0,
+- .enable_shift = DL3_ON_SFT,
+- .hd_reg = AFE_MEMIF_HD_MODE,
+- .hd_align_reg = AFE_MEMIF_HDALIGN,
+- .hd_shift = DL3_HD_SFT,
+- .hd_align_mshift = DL3_HD_ALIGN_SFT,
+- .agent_disable_reg = -1,
+- .agent_disable_shift = -1,
+- .msb_reg = -1,
+- .msb_shift = -1,
+- },
+- [MT8183_MEMIF_VUL2] = {
+- .name = "VUL2",
+- .id = MT8183_MEMIF_VUL2,
+- .reg_ofs_base = AFE_VUL2_BASE,
+- .reg_ofs_cur = AFE_VUL2_CUR,
+- .fs_reg = AFE_DAC_CON2,
+- .fs_shift = VUL2_MODE_SFT,
+- .fs_maskbit = VUL2_MODE_MASK,
+- .mono_reg = AFE_DAC_CON2,
+- .mono_shift = VUL2_DATA_SFT,
+- .enable_reg = AFE_DAC_CON0,
+- .enable_shift = VUL2_ON_SFT,
+- .hd_reg = AFE_MEMIF_HD_MODE,
+- .hd_align_reg = AFE_MEMIF_HDALIGN,
+- .hd_shift = VUL2_HD_SFT,
+- .hd_align_mshift = VUL2_HD_ALIGN_SFT,
+- .agent_disable_reg = -1,
+- .agent_disable_shift = -1,
+- .msb_reg = -1,
+- .msb_shift = -1,
+- },
+- [MT8183_MEMIF_AWB] = {
+- .name = "AWB",
+- .id = MT8183_MEMIF_AWB,
+- .reg_ofs_base = AFE_AWB_BASE,
+- .reg_ofs_cur = AFE_AWB_CUR,
+- .fs_reg = AFE_DAC_CON1,
+- .fs_shift = AWB_MODE_SFT,
+- .fs_maskbit = AWB_MODE_MASK,
+- .mono_reg = AFE_DAC_CON1,
+- .mono_shift = AWB_DATA_SFT,
+- .enable_reg = AFE_DAC_CON0,
+- .enable_shift = AWB_ON_SFT,
+- .hd_reg = AFE_MEMIF_HD_MODE,
+- .hd_align_reg = AFE_MEMIF_HDALIGN,
+- .hd_shift = AWB_HD_SFT,
+- .hd_align_mshift = AWB_HD_ALIGN_SFT,
+- .agent_disable_reg = -1,
+- .agent_disable_shift = -1,
+- .msb_reg = -1,
+- .msb_shift = -1,
+- },
+- [MT8183_MEMIF_AWB2] = {
+- .name = "AWB2",
+- .id = MT8183_MEMIF_AWB2,
+- .reg_ofs_base = AFE_AWB2_BASE,
+- .reg_ofs_cur = AFE_AWB2_CUR,
+- .fs_reg = AFE_DAC_CON2,
+- .fs_shift = AWB2_MODE_SFT,
+- .fs_maskbit = AWB2_MODE_MASK,
+- .mono_reg = AFE_DAC_CON2,
+- .mono_shift = AWB2_DATA_SFT,
+- .enable_reg = AFE_DAC_CON0,
+- .enable_shift = AWB2_ON_SFT,
+- .hd_reg = AFE_MEMIF_HD_MODE,
+- .hd_align_reg = AFE_MEMIF_HDALIGN,
+- .hd_shift = AWB2_HD_SFT,
+- .hd_align_mshift = AWB2_ALIGN_SFT,
+- .agent_disable_reg = -1,
+- .agent_disable_shift = -1,
+- .msb_reg = -1,
+- .msb_shift = -1,
+- },
+- [MT8183_MEMIF_VUL12] = {
+- .name = "VUL12",
+- .id = MT8183_MEMIF_VUL12,
+- .reg_ofs_base = AFE_VUL_D2_BASE,
+- .reg_ofs_cur = AFE_VUL_D2_CUR,
+- .fs_reg = AFE_DAC_CON0,
+- .fs_shift = VUL12_MODE_SFT,
+- .fs_maskbit = VUL12_MODE_MASK,
+- .mono_reg = AFE_DAC_CON0,
+- .mono_shift = VUL12_MONO_SFT,
+- .enable_reg = AFE_DAC_CON0,
+- .enable_shift = VUL12_ON_SFT,
+- .hd_reg = AFE_MEMIF_HD_MODE,
+- .hd_align_reg = AFE_MEMIF_HDALIGN,
+- .hd_shift = VUL12_HD_SFT,
+- .hd_align_mshift = VUL12_HD_ALIGN_SFT,
+- .agent_disable_reg = -1,
+- .agent_disable_shift = -1,
+- .msb_reg = -1,
+- .msb_shift = -1,
+- },
+- [MT8183_MEMIF_MOD_DAI] = {
+- .name = "MOD_DAI",
+- .id = MT8183_MEMIF_MOD_DAI,
+- .reg_ofs_base = AFE_MOD_DAI_BASE,
+- .reg_ofs_cur = AFE_MOD_DAI_CUR,
+- .fs_reg = AFE_DAC_CON1,
+- .fs_shift = MOD_DAI_MODE_SFT,
+- .fs_maskbit = MOD_DAI_MODE_MASK,
+- .mono_reg = -1,
+- .mono_shift = 0,
+- .enable_reg = AFE_DAC_CON0,
+- .enable_shift = MOD_DAI_ON_SFT,
+- .hd_reg = AFE_MEMIF_HD_MODE,
+- .hd_align_reg = AFE_MEMIF_HDALIGN,
+- .hd_shift = MOD_DAI_HD_SFT,
+- .hd_align_mshift = MOD_DAI_HD_ALIGN_SFT,
+- .agent_disable_reg = -1,
+- .agent_disable_shift = -1,
+- .msb_reg = -1,
+- .msb_shift = -1,
+- },
+- [MT8183_MEMIF_HDMI] = {
+- .name = "HDMI",
+- .id = MT8183_MEMIF_HDMI,
+- .reg_ofs_base = AFE_HDMI_OUT_BASE,
+- .reg_ofs_cur = AFE_HDMI_OUT_CUR,
+- .fs_reg = -1,
+- .fs_shift = -1,
+- .fs_maskbit = -1,
+- .mono_reg = -1,
+- .mono_shift = -1,
+- .enable_reg = -1, /* control in tdm for sync start */
+- .enable_shift = -1,
+- .hd_reg = AFE_MEMIF_HD_MODE,
+- .hd_align_reg = AFE_MEMIF_HDALIGN,
+- .hd_shift = HDMI_HD_SFT,
+- .hd_align_mshift = HDMI_HD_ALIGN_SFT,
+- .agent_disable_reg = -1,
+- .agent_disable_shift = -1,
+- .msb_reg = -1,
+- .msb_shift = -1,
+- },
++ MT8183_MEMIF(DL1, AFE_DAC_CON1, AFE_DAC_CON1),
++ MT8183_MEMIF(DL2, AFE_DAC_CON1, AFE_DAC_CON1),
++ MT8183_MEMIF(DL3, AFE_DAC_CON2, AFE_DAC_CON1),
++ MT8183_MEMIF(VUL2, AFE_DAC_CON2, AFE_DAC_CON2),
++ MT8183_MEMIF(AWB, AFE_DAC_CON1, AFE_DAC_CON1),
++ MT8183_MEMIF(AWB2, AFE_DAC_CON2, AFE_DAC_CON2),
++ MT8183_MEMIF(VUL12, AFE_DAC_CON0, AFE_DAC_CON0),
++ MT8183_MEMIF(MOD_DAI, AFE_DAC_CON1, -1),
++ /* enable control in tdm for sync start */
++ MT8183_MEMIF_BASE(HDMI, -1, -1, -1),
+ };
+
+ static const struct mtk_base_irq_data irq_data[MT8183_IRQ_NUM] = {
--- /dev/null
+From stable+bounces-288165-greg=kroah.com@vger.kernel.org Thu Jul 23 04:29:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 22:29:07 -0400
+Subject: ASoC: mediatek: mt8183-afe-pcm: Support >32 bit DMA addresses
+To: stable@vger.kernel.org
+Cc: Chen-Yu Tsai <wenst@chromium.org>, AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>, Mark Brown <broonie@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723022909.2845199-2-sashal@kernel.org>
+
+From: Chen-Yu Tsai <wenst@chromium.org>
+
+[ Upstream commit 9e7bc5cb8d089d9799e17a9ac99c5da9b13b02e3 ]
+
+The AFE DMA hardware supports up to 34 bits for DMA addresses. This is
+missing from the driver and prevents reserved memory regions from
+working properly when the allocated region is above the 4GB line.
+
+Fill in the related register offsets for each DAI, and also set the
+DMA mask. Also fill in the LSB end register offsets for completeness.
+
+Fixes: a94aec035a12 ("ASoC: mediatek: mt8183: add platform driver")
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Signed-off-by: Chen-Yu Tsai <wenst@chromium.org>
+Link: https://patch.msgid.link/20250612074901.4023253-8-wenst@chromium.org
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Stable-dep-of: f0334fbfd107 ("ASoC: mediatek: mt8183: Check runtime resume during probe")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/soc/mediatek/mt8183/mt8183-afe-pcm.c | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
++++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
+@@ -6,6 +6,7 @@
+ // Author: KaiChieh Chuang <kaichieh.chuang@mediatek.com>
+
+ #include <linux/delay.h>
++#include <linux/dma-mapping.h>
+ #include <linux/module.h>
+ #include <linux/mfd/syscon.h>
+ #include <linux/of.h>
+@@ -432,6 +433,9 @@ static const struct snd_soc_component_dr
+ .reg_ofs_base = AFE_##_id##_BASE, \
+ .reg_ofs_cur = AFE_##_id##_CUR, \
+ .reg_ofs_end = AFE_##_id##_END, \
++ .reg_ofs_base_msb = AFE_##_id##_BASE_MSB, \
++ .reg_ofs_cur_msb = AFE_##_id##_CUR_MSB, \
++ .reg_ofs_end_msb = AFE_##_id##_END_MSB, \
+ .fs_reg = (_fs_reg), \
+ .fs_shift = _id##_MODE_SFT, \
+ .fs_maskbit = _id##_MODE_MASK, \
+@@ -463,11 +467,17 @@ static const struct snd_soc_component_dr
+ #define AFE_VUL12_BASE AFE_VUL_D2_BASE
+ #define AFE_VUL12_CUR AFE_VUL_D2_CUR
+ #define AFE_VUL12_END AFE_VUL_D2_END
++#define AFE_VUL12_BASE_MSB AFE_VUL_D2_BASE_MSB
++#define AFE_VUL12_CUR_MSB AFE_VUL_D2_CUR_MSB
++#define AFE_VUL12_END_MSB AFE_VUL_D2_END_MSB
+ #define AWB2_HD_ALIGN_SFT AWB2_ALIGN_SFT
+ #define VUL12_DATA_SFT VUL12_MONO_SFT
+ #define AFE_HDMI_BASE AFE_HDMI_OUT_BASE
+ #define AFE_HDMI_CUR AFE_HDMI_OUT_CUR
+ #define AFE_HDMI_END AFE_HDMI_OUT_END
++#define AFE_HDMI_BASE_MSB AFE_HDMI_OUT_BASE_MSB
++#define AFE_HDMI_CUR_MSB AFE_HDMI_OUT_CUR_MSB
++#define AFE_HDMI_END_MSB AFE_HDMI_OUT_END_MSB
+
+ static const struct mtk_base_memif_data memif_data[MT8183_MEMIF_NUM] = {
+ MT8183_MEMIF(DL1, AFE_DAC_CON1, AFE_DAC_CON1),
+@@ -951,6 +961,10 @@ static int mt8183_afe_pcm_dev_probe(stru
+ struct reset_control *rstc;
+ int i, irq_id, ret;
+
++ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(34));
++ if (ret)
++ return ret;
++
+ afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL);
+ if (!afe)
+ return -ENOMEM;
--- /dev/null
+From stable+bounces-288166-greg=kroah.com@vger.kernel.org Thu Jul 23 04:30:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 22:29:08 -0400
+Subject: ASoC: mediatek: mt8183-afe-pcm: use local `dev` pointer in driver callbacks
+To: stable@vger.kernel.org
+Cc: Chen-Yu Tsai <wenst@chromium.org>, AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>, Mark Brown <broonie@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723022909.2845199-3-sashal@kernel.org>
+
+From: Chen-Yu Tsai <wenst@chromium.org>
+
+[ Upstream commit bb8d8ba4715cb8f997d63d90ba935f6073595df5 ]
+
+The probe and remove functions in the mt8183-afe-pcm driver repeatedly uses
+`&pdev->dev` for |struct device *|, but then assigns this value to
+`afe->dev` and uses that in other places in the same function.
+
+Store `&pdev->dev` in a local pointer and use that exclusively to avoid
+the numerous dereferences and to make the code more consistent. Lines
+are reflowed where it makes sense.
+
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Signed-off-by: Chen-Yu Tsai <wenst@chromium.org>
+Link: https://patch.msgid.link/20250612074901.4023253-10-wenst@chromium.org
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Stable-dep-of: f0334fbfd107 ("ASoC: mediatek: mt8183: Check runtime resume during probe")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/soc/mediatek/mt8183/mt8183-afe-pcm.c | 37 +++++++++++++----------------
+ 1 file changed, 17 insertions(+), 20 deletions(-)
+
+--- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
++++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
+@@ -957,27 +957,25 @@ static int mt8183_afe_pcm_dev_probe(stru
+ {
+ struct mtk_base_afe *afe;
+ struct mt8183_afe_private *afe_priv;
+- struct device *dev;
++ struct device *dev = &pdev->dev;
+ struct reset_control *rstc;
+ int i, irq_id, ret;
+
+- ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(34));
++ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(34));
+ if (ret)
+ return ret;
+
+- afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL);
++ afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL);
+ if (!afe)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, afe);
+
+- afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv),
+- GFP_KERNEL);
++ afe->platform_priv = devm_kzalloc(dev, sizeof(*afe_priv), GFP_KERNEL);
+ if (!afe->platform_priv)
+ return -ENOMEM;
+
+ afe_priv = afe->platform_priv;
+- afe->dev = &pdev->dev;
+- dev = afe->dev;
++ afe->dev = dev;
+
+ ret = of_reserved_mem_device_init(dev);
+ if (ret) {
+@@ -1028,7 +1026,7 @@ static int mt8183_afe_pcm_dev_probe(stru
+
+ /* enable clock for regcache get default value from hw */
+ afe_priv->pm_runtime_bypass_reg_ctl = true;
+- pm_runtime_get_sync(&pdev->dev);
++ pm_runtime_get_sync(dev);
+
+ ret = regmap_reinit_cache(afe->regmap, &mt8183_afe_regmap_config);
+ if (ret) {
+@@ -1036,7 +1034,7 @@ static int mt8183_afe_pcm_dev_probe(stru
+ goto err_pm_disable;
+ }
+
+- pm_runtime_put_sync(&pdev->dev);
++ pm_runtime_put_sync(dev);
+ afe_priv->pm_runtime_bypass_reg_ctl = false;
+
+ regcache_cache_only(afe->regmap, true);
+@@ -1094,7 +1092,7 @@ static int mt8183_afe_pcm_dev_probe(stru
+ for (i = 0; i < ARRAY_SIZE(dai_register_cbs); i++) {
+ ret = dai_register_cbs[i](afe);
+ if (ret) {
+- dev_warn(afe->dev, "dai register i %d fail, ret %d\n",
++ dev_warn(dev, "dai register i %d fail, ret %d\n",
+ i, ret);
+ goto err_pm_disable;
+ }
+@@ -1103,8 +1101,7 @@ static int mt8183_afe_pcm_dev_probe(stru
+ /* init dai_driver and component_driver */
+ ret = mtk_afe_combine_sub_dai(afe);
+ if (ret) {
+- dev_warn(afe->dev, "mtk_afe_combine_sub_dai fail, ret %d\n",
+- ret);
++ dev_warn(dev, "mtk_afe_combine_sub_dai fail, ret %d\n", ret);
+ goto err_pm_disable;
+ }
+
+@@ -1116,16 +1113,14 @@ static int mt8183_afe_pcm_dev_probe(stru
+ afe->runtime_suspend = mt8183_afe_runtime_suspend;
+
+ /* register component */
+- ret = devm_snd_soc_register_component(&pdev->dev,
+- &mtk_afe_pcm_platform,
++ ret = devm_snd_soc_register_component(dev, &mtk_afe_pcm_platform,
+ NULL, 0);
+ if (ret) {
+ dev_warn(dev, "err_platform\n");
+ goto err_pm_disable;
+ }
+
+- ret = devm_snd_soc_register_component(afe->dev,
+- &mt8183_afe_pcm_dai_component,
++ ret = devm_snd_soc_register_component(dev, &mt8183_afe_pcm_dai_component,
+ afe->dai_drivers,
+ afe->num_dai_drivers);
+ if (ret) {
+@@ -1136,15 +1131,17 @@ static int mt8183_afe_pcm_dev_probe(stru
+ return ret;
+
+ err_pm_disable:
+- pm_runtime_disable(&pdev->dev);
++ pm_runtime_disable(dev);
+ return ret;
+ }
+
+ static void mt8183_afe_pcm_dev_remove(struct platform_device *pdev)
+ {
+- pm_runtime_disable(&pdev->dev);
+- if (!pm_runtime_status_suspended(&pdev->dev))
+- mt8183_afe_runtime_suspend(&pdev->dev);
++ struct device *dev = &pdev->dev;
++
++ pm_runtime_disable(dev);
++ if (!pm_runtime_status_suspended(dev))
++ mt8183_afe_runtime_suspend(dev);
+ }
+
+ static const struct of_device_id mt8183_afe_pcm_dt_match[] = {
--- /dev/null
+From stable+bounces-288167-greg=kroah.com@vger.kernel.org Thu Jul 23 04:30:48 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 22:29:09 -0400
+Subject: ASoC: mediatek: mt8183: Check runtime resume during probe
+To: stable@vger.kernel.org
+Cc: "Cássio Gabriel" <cassiogabrielcontato@gmail.com>, "Mark Brown" <broonie@kernel.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260723022909.2845199-4-sashal@kernel.org>
+
+From: Cássio Gabriel <cassiogabrielcontato@gmail.com>
+
+[ Upstream commit f0334fbfd107682d0c95f3f71e25f6127038e2b9 ]
+
+The MT8183 AFE probe uses pm_runtime_get_sync() before reading hardware
+defaults into the regmap cache, but does not check whether runtime resume
+failed. If regmap_reinit_cache() then fails, the temporary runtime PM
+usage count is also not released.
+
+Use pm_runtime_resume_and_get() so resume failures abort probe without
+leaking a usage count, and release the temporary reference before
+handling the regmap cache result.
+
+Fixes: a94aec035a12 ("ASoC: mediatek: mt8183: add platform driver")
+Cc: stable@vger.kernel.org
+Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
+Link: https://patch.msgid.link/20260527-asoc-mt8183-probe-cleanup-v1-2-4f4f5593c8d1@gmail.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/soc/mediatek/mt8183/mt8183-afe-pcm.c | 12 ++++++++----
+ 1 file changed, 8 insertions(+), 4 deletions(-)
+
+--- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
++++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c
+@@ -1026,17 +1026,21 @@ static int mt8183_afe_pcm_dev_probe(stru
+
+ /* enable clock for regcache get default value from hw */
+ afe_priv->pm_runtime_bypass_reg_ctl = true;
+- pm_runtime_get_sync(dev);
+-
+- ret = regmap_reinit_cache(afe->regmap, &mt8183_afe_regmap_config);
++ ret = pm_runtime_resume_and_get(dev);
+ if (ret) {
+- dev_err(dev, "regmap_reinit_cache fail, ret %d\n", ret);
++ afe_priv->pm_runtime_bypass_reg_ctl = false;
+ goto err_pm_disable;
+ }
+
++ ret = regmap_reinit_cache(afe->regmap, &mt8183_afe_regmap_config);
+ pm_runtime_put_sync(dev);
+ afe_priv->pm_runtime_bypass_reg_ctl = false;
+
++ if (ret) {
++ dev_err(dev, "regmap_reinit_cache fail, ret %d\n", ret);
++ goto err_pm_disable;
++ }
++
+ regcache_cache_only(afe->regmap, true);
+ regcache_mark_dirty(afe->regmap);
+
--- /dev/null
+From stable+bounces-288152-greg=kroah.com@vger.kernel.org Thu Jul 23 03:18:39 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 21:17:14 -0400
+Subject: ASoC: mediatek: mt8192-afe-pcm: Simplify probe() with local dev variable
+To: stable@vger.kernel.org
+Cc: Tang Bin <tangbin@cmss.chinamobile.com>, Mark Brown <broonie@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723011715.2780615-1-sashal@kernel.org>
+
+From: Tang Bin <tangbin@cmss.chinamobile.com>
+
+[ Upstream commit 01981565c764c554cc96e2d30a71c42975171416 ]
+
+Simplify the function mt8192_afe_pcm_dev_probe() by
+using local 'dev' instead of '&pdev->dev'.
+
+Signed-off-by: Tang Bin <tangbin@cmss.chinamobile.com>
+Link: https://patch.msgid.link/20241025080026.2393-1-tangbin@cmss.chinamobile.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Stable-dep-of: e24d5dde56a5 ("ASoC: mediatek: mt8192: Check runtime resume during probe")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/soc/mediatek/mt8192/mt8192-afe-pcm.c | 19 +++++++++----------
+ 1 file changed, 9 insertions(+), 10 deletions(-)
+
+--- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c
++++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c
+@@ -2164,27 +2164,26 @@ static int mt8192_afe_pcm_dev_probe(stru
+ {
+ struct mtk_base_afe *afe;
+ struct mt8192_afe_private *afe_priv;
+- struct device *dev;
++ struct device *dev = &pdev->dev;
+ struct reset_control *rstc;
+ int i, ret, irq_id;
+
+- ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(34));
++ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(34));
+ if (ret)
+ return ret;
+
+- afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL);
++ afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL);
+ if (!afe)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, afe);
+
+- afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv),
++ afe->platform_priv = devm_kzalloc(dev, sizeof(*afe_priv),
+ GFP_KERNEL);
+ if (!afe->platform_priv)
+ return -ENOMEM;
+ afe_priv = afe->platform_priv;
+
+- afe->dev = &pdev->dev;
+- dev = afe->dev;
++ afe->dev = dev;
+
+ ret = of_reserved_mem_device_init(dev);
+ if (ret) {
+@@ -2212,7 +2211,7 @@ static int mt8192_afe_pcm_dev_probe(stru
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to trigger audio reset\n");
+
+- ret = devm_pm_runtime_enable(&pdev->dev);
++ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
+
+@@ -2228,13 +2227,13 @@ static int mt8192_afe_pcm_dev_probe(stru
+
+ /* enable clock for regcache get default value from hw */
+ afe_priv->pm_runtime_bypass_reg_ctl = true;
+- pm_runtime_get_sync(&pdev->dev);
++ pm_runtime_get_sync(dev);
+
+ ret = regmap_reinit_cache(afe->regmap, &mt8192_afe_regmap_config);
+ if (ret)
+ return dev_err_probe(dev, ret, "regmap_reinit_cache fail\n");
+
+- pm_runtime_put_sync(&pdev->dev);
++ pm_runtime_put_sync(dev);
+ afe_priv->pm_runtime_bypass_reg_ctl = false;
+
+ regcache_cache_only(afe->regmap, true);
+@@ -2301,7 +2300,7 @@ static int mt8192_afe_pcm_dev_probe(stru
+ afe->runtime_suspend = mt8192_afe_runtime_suspend;
+
+ /* register platform */
+- ret = devm_snd_soc_register_component(&pdev->dev,
++ ret = devm_snd_soc_register_component(dev,
+ &mtk_afe_pcm_platform,
+ afe->dai_drivers,
+ afe->num_dai_drivers);
--- /dev/null
+From stable+bounces-288151-greg=kroah.com@vger.kernel.org Thu Jul 23 03:18:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 21:17:15 -0400
+Subject: ASoC: mediatek: mt8192: Check runtime resume during probe
+To: stable@vger.kernel.org
+Cc: "Cássio Gabriel" <cassiogabrielcontato@gmail.com>, "Mark Brown" <broonie@kernel.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260723011715.2780615-2-sashal@kernel.org>
+
+From: Cássio Gabriel <cassiogabrielcontato@gmail.com>
+
+[ Upstream commit e24d5dde56a50946020b134fa8448869093db76a ]
+
+The MT8192 AFE probe enables runtime PM temporarily while reinitializing
+the regmap cache from hardware, but it uses pm_runtime_get_sync()
+without checking the return value. If runtime resume fails, probe keeps
+going without the device necessarily being accessible, and
+pm_runtime_get_sync() may leave the PM usage count incremented.
+
+The regmap_reinit_cache() failure path also returns before dropping the
+temporary PM reference and before clearing pm_runtime_bypass_reg_ctl.
+
+Use pm_runtime_resume_and_get() so resume failures do not leak a usage
+count, and clear the temporary bypass flag after dropping the probe PM
+reference on all regmap_reinit_cache() outcomes.
+
+Fixes: 125ab5d588b0 ("ASoC: mediatek: mt8192: add platform driver")
+Cc: stable@vger.kernel.org
+Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
+Link: https://patch.msgid.link/20260527-asoc-mt8192-probe-cleanup-v1-2-1bb834d05b72@gmail.com
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/soc/mediatek/mt8192/mt8192-afe-pcm.c | 12 ++++++++----
+ 1 file changed, 8 insertions(+), 4 deletions(-)
+
+--- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c
++++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c
+@@ -2227,15 +2227,19 @@ static int mt8192_afe_pcm_dev_probe(stru
+
+ /* enable clock for regcache get default value from hw */
+ afe_priv->pm_runtime_bypass_reg_ctl = true;
+- pm_runtime_get_sync(dev);
++ ret = pm_runtime_resume_and_get(dev);
++ if (ret) {
++ afe_priv->pm_runtime_bypass_reg_ctl = false;
++ return dev_err_probe(dev, ret, "failed to resume device\n");
++ }
+
+ ret = regmap_reinit_cache(afe->regmap, &mt8192_afe_regmap_config);
+- if (ret)
+- return dev_err_probe(dev, ret, "regmap_reinit_cache fail\n");
+-
+ pm_runtime_put_sync(dev);
+ afe_priv->pm_runtime_bypass_reg_ctl = false;
+
++ if (ret)
++ return dev_err_probe(dev, ret, "regmap_reinit_cache fail\n");
++
+ regcache_cache_only(afe->regmap, true);
+ regcache_mark_dirty(afe->regmap);
+
--- /dev/null
+From stable+bounces-290226-greg=kroah.com@vger.kernel.org Wed Jul 29 04:31:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 22:29:48 -0400
+Subject: ata: libata-core: Reject an invalid concurrent positioning ranges count
+To: stable@vger.kernel.org
+Cc: Bryam Vargas <hexlabsecurity@proton.me>, Damien Le Moal <dlemoal@kernel.org>, Niklas Cassel <cassel@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729022948.1112469-1-sashal@kernel.org>
+
+From: Bryam Vargas <hexlabsecurity@proton.me>
+
+[ Upstream commit 533a0b940f901c15e5cbbd4b5d66e871c209e8ce ]
+
+ata_dev_config_cpr() takes the number of range descriptors from buf[0]
+of the concurrent positioning ranges log (up to 255), which the device
+reports independently of the log size in the GPL directory. The count is
+then walked at a fixed 32-byte stride in two places with no bound: the
+log read here, and the INQUIRY VPD page B9h emitter, which writes one
+descriptor per range into the fixed 2048-byte ata_scsi_rbuf. A device
+reporting a count larger than its own log overflows the read buffer (up
+to 7704 bytes past a 512-byte slab), and a count above 62 overflows the
+response buffer on the emit side.
+
+Bound the count once, on probe, against both the log the device returned
+and the number of descriptors the VPD B9h response buffer can hold
+(ATA_DEV_MAX_CPR, derived from the rbuf size). Reject an out-of-range
+count with a warning; this keeps the emitter in bounds with no separate
+change there.
+
+Suggested-by: Damien Le Moal <dlemoal@kernel.org>
+Fixes: fe22e1c2f705 ("libata: support concurrent positioning ranges log")
+Fixes: c745dfc541e7 ("libata: fix reading concurrent positioning ranges log")
+Cc: stable@vger.kernel.org
+Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
+Reviewed-by: Niklas Cassel <cassel@kernel.org>
+Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
+[ adapted `kzalloc_flex()` allocation to `kzalloc(struct_size(...), GFP_KERNEL)` and adjusted context offsets. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/ata/libata-core.c | 18 ++++++++++++++++++
+ drivers/ata/libata-scsi.c | 2 --
+ drivers/ata/libata.h | 9 +++++++++
+ 3 files changed, 27 insertions(+), 2 deletions(-)
+
+--- a/drivers/ata/libata-core.c
++++ b/drivers/ata/libata-core.c
+@@ -2804,6 +2804,24 @@ static void ata_dev_config_cpr(struct at
+ if (!nr_cpr)
+ goto out;
+
++ /*
++ * The device reports the number of CPR descriptors independently of the
++ * log size, and that count is also used to emit VPD page B9h into the
++ * fixed-size rbuf. Reject a count larger than what that buffer can hold
++ * (ATA_DEV_MAX_CPR) or larger than the log the device actually returned.
++ */
++ if (nr_cpr > ATA_DEV_MAX_CPR) {
++ ata_dev_warn(dev,
++ "Too many concurrent positioning ranges\n");
++ goto out;
++ }
++
++ if (buf_len < 64 + (size_t)nr_cpr * 32) {
++ ata_dev_warn(dev,
++ "Invalid number of concurrent positioning ranges\n");
++ goto out;
++ }
++
+ cpr_log = kzalloc(struct_size(cpr_log, cpr, nr_cpr), GFP_KERNEL);
+ if (!cpr_log)
+ goto out;
+--- a/drivers/ata/libata-scsi.c
++++ b/drivers/ata/libata-scsi.c
+@@ -37,8 +37,6 @@
+ #include "libata.h"
+ #include "libata-transport.h"
+
+-#define ATA_SCSI_RBUF_SIZE 2048
+-
+ static DEFINE_SPINLOCK(ata_scsi_rbuf_lock);
+ static u8 ata_scsi_rbuf[ATA_SCSI_RBUF_SIZE];
+
+--- a/drivers/ata/libata.h
++++ b/drivers/ata/libata.h
+@@ -145,6 +145,15 @@ static inline void ata_acpi_bind_dev(str
+ #endif
+
+ /* libata-scsi.c */
++#define ATA_SCSI_RBUF_SIZE 2048
++
++/*
++ * Maximum number of concurrent positioning ranges (CPR) supported. The ACS
++ * specifications allow up to 255, but we limit this to the number of CPR
++ * descriptors that fit in the rbuf buffer used to emit VPD page B9h.
++ */
++#define ATA_DEV_MAX_CPR min(255, ((ATA_SCSI_RBUF_SIZE - 64) / 32))
++
+ extern struct ata_device *ata_scsi_find_dev(struct ata_port *ap,
+ const struct scsi_device *scsidev);
+ extern int ata_scsi_add_hosts(struct ata_host *host,
--- /dev/null
+From stable+bounces-287660-greg=kroah.com@vger.kernel.org Wed Jul 22 02:00:45 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 19:59:04 -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: <20260721235904.525962-4-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
+@@ -250,8 +250,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);
+
+@@ -274,13 +279,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);
+
+@@ -311,13 +318,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(sizeof(*audit_mark), GFP_KERNEL);
+ 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
+@@ -590,7 +590,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);
+@@ -818,7 +818,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;
+@@ -877,7 +878,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) {
+@@ -1408,7 +1409,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-287659-greg=kroah.com@vger.kernel.org Wed Jul 22 02:19:46 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 19:59:03 -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: <20260721235904.525962-3-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/auditfilter.c | 8 ++++----
+ kernel/auditsc.c | 2 +-
+ lib/compat_audit.c | 12 ++++++------
+ 8 files changed, 22 insertions(+), 22 deletions(-)
+
+--- a/include/linux/audit.h
++++ b/include/linux/audit.h
+@@ -123,8 +123,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
+@@ -1985,7 +1985,7 @@ static void audit_log_vformat(struct aud
+ * 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
+@@ -227,7 +227,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/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-287658-greg=kroah.com@vger.kernel.org Wed Jul 22 02:00:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 19:59:02 -0400
+Subject: audit: widen ino fields to u64
+To: stable@vger.kernel.org
+Cc: Jeff Layton <jlayton@kernel.org>, Paul Moore <paul@paul-moore.com>, Christian Brauner <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721235904.525962-2-sashal@kernel.org>
+
+From: Jeff Layton <jlayton@kernel.org>
+
+[ Upstream commit 125dfa218134df7cc112667e92984de9d8cd0bf6 ]
+
+inode->i_ino is being widened from unsigned long to u64. The audit
+subsystem uses unsigned long ino in struct fields, function parameters,
+and local variables that store inode numbers from arbitrary filesystems.
+On 32-bit platforms this truncates inode numbers that exceed 32 bits,
+which will cause incorrect audit log entries and broken watch/mark
+comparisons.
+
+Widen all audit ino fields, parameters, and locals to u64, and update
+the inode format string from %lu to %llu to match.
+
+Signed-off-by: Jeff Layton <jlayton@kernel.org>
+Link: https://patch.msgid.link/20260304-iino-u64-v3-2-2257ad83d372@kernel.org
+Acked-by: Paul Moore <paul@paul-moore.com>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+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 | 2 +-
+ kernel/audit.h | 9 ++++-----
+ kernel/audit_fsnotify.c | 2 +-
+ kernel/audit_watch.c | 10 +++++-----
+ kernel/auditsc.c | 4 ++--
+ 5 files changed, 13 insertions(+), 14 deletions(-)
+
+--- a/include/linux/audit.h
++++ b/include/linux/audit.h
+@@ -16,7 +16,7 @@
+ #include <uapi/linux/netfilter/nf_tables.h>
+ #include <uapi/linux/fanotify.h>
+
+-#define AUDIT_INO_UNSET ((unsigned long)-1)
++#define AUDIT_INO_UNSET ((u64)-1)
+ #define AUDIT_DEV_UNSET ((dev_t)-1)
+
+ struct audit_sig_info {
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -75,7 +75,7 @@ struct audit_names {
+ int name_len; /* number of chars to log */
+ bool hidden; /* don't log this record */
+
+- unsigned long ino;
++ u64 ino;
+ dev_t dev;
+ umode_t mode;
+ kuid_t uid;
+@@ -219,9 +219,9 @@ extern int auditd_test_task(struct task_
+ #define AUDIT_INODE_BUCKETS 32
+ extern struct list_head audit_inode_hash[AUDIT_INODE_BUCKETS];
+
+-static inline int audit_hash_ino(u32 ino)
++static inline int audit_hash_ino(u64 ino)
+ {
+- return (ino & (AUDIT_INODE_BUCKETS-1));
++ return ((u32)ino & (AUDIT_INODE_BUCKETS-1));
+ }
+
+ /* Indicates that audit should log the full pathname. */
+@@ -271,8 +271,7 @@ extern int audit_to_watch(struct audit_k
+ extern int audit_add_watch(struct audit_krule *krule, struct list_head **list);
+ extern void audit_remove_watch_rule(struct audit_krule *krule);
+ extern char *audit_watch_path(struct audit_watch *watch);
+-extern int audit_watch_compare(struct audit_watch *watch, unsigned long ino,
+- dev_t dev);
++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);
+--- a/kernel/audit_fsnotify.c
++++ b/kernel/audit_fsnotify.c
+@@ -25,7 +25,7 @@
+ */
+ struct audit_fsnotify_mark {
+ dev_t dev; /* associated superblock device */
+- unsigned long ino; /* associated inode number */
++ u64 ino; /* associated inode number */
+ char *path; /* insertion path */
+ struct fsnotify_mark mark; /* fsnotify mark on the inode */
+ struct audit_krule *rule;
+--- a/kernel/audit_watch.c
++++ b/kernel/audit_watch.c
+@@ -37,7 +37,7 @@ struct audit_watch {
+ refcount_t count; /* reference count */
+ dev_t dev; /* associated superblock device */
+ char *path; /* insertion path */
+- unsigned long ino; /* associated inode number */
++ u64 ino; /* associated inode number */
+ struct audit_parent *parent; /* associated parent */
+ struct list_head wlist; /* entry in parent->watches list */
+ struct list_head rules; /* anchor for krule->rlist */
+@@ -125,7 +125,7 @@ char *audit_watch_path(struct audit_watc
+ return watch->path;
+ }
+
+-int audit_watch_compare(struct audit_watch *watch, unsigned long ino, dev_t dev)
++int audit_watch_compare(struct audit_watch *watch, u64 ino, dev_t dev)
+ {
+ return (watch->ino != AUDIT_INO_UNSET) &&
+ (watch->ino == ino) &&
+@@ -285,7 +285,7 @@ static void audit_update_watch(struct au
+ list_del(&oentry->rule.list);
+ audit_panic("error updating watch, removing");
+ } else {
+- int h = audit_hash_ino((u32)ino);
++ int h = audit_hash_ino(ino);
+
+ /*
+ * nentry->rule.watch == oentry->rule.watch so
+@@ -439,7 +439,7 @@ int audit_add_watch(struct audit_krule *
+
+ audit_add_to_parent(krule, parent);
+
+- h = audit_hash_ino((u32)watch->ino);
++ h = audit_hash_ino(watch->ino);
+ *list = &audit_inode_hash[h];
+ error:
+ path_put(&parent_path);
+@@ -527,7 +527,7 @@ int audit_dupe_exe(struct audit_krule *n
+ int audit_exe_compare(struct task_struct *tsk, struct audit_fsnotify_mark *mark)
+ {
+ struct file *exe_file;
+- unsigned long ino;
++ u64 ino;
+ dev_t dev;
+
+ /* only do exe filtering if we are recording @current events/records */
+--- a/kernel/auditsc.c
++++ b/kernel/auditsc.c
+@@ -884,7 +884,7 @@ static int audit_filter_inode_name(struc
+ struct audit_names *n,
+ struct audit_context *ctx)
+ {
+- int h = audit_hash_ino((u32)n->ino);
++ int h = audit_hash_ino(n->ino);
+ struct list_head *list = &audit_inode_hash[h];
+
+ return __audit_filter_op(tsk, ctx, list, n, ctx->major);
+@@ -1549,7 +1549,7 @@ static void audit_log_name(struct audit_
+ audit_log_format(ab, " name=(null)");
+
+ if (n->ino != AUDIT_INO_UNSET)
+- audit_log_format(ab, " inode=%lu dev=%02x:%02x mode=%#ho ouid=%u ogid=%u rdev=%02x:%02x",
++ audit_log_format(ab, " inode=%llu dev=%02x:%02x mode=%#ho ouid=%u ogid=%u rdev=%02x:%02x",
+ n->ino,
+ MAJOR(n->dev),
+ MINOR(n->dev),
--- /dev/null
+From stable+bounces-289610-greg=kroah.com@vger.kernel.org Mon Jul 27 18:06:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:59:27 -0400
+Subject: block: add helper add_disk_final()
+To: stable@vger.kernel.org
+Cc: Ming Lei <ming.lei@redhat.com>, Hannes Reinecke <hare@suse.de>, Nilay Shroff <nilay@linux.ibm.com>, Christoph Hellwig <hch@lst.de>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727155928.1732187-1-sashal@kernel.org>
+
+From: Ming Lei <ming.lei@redhat.com>
+
+[ Upstream commit 5fad1490ef510e3b70ad8b0a5a1e28a26638a95f ]
+
+Add helper add_disk_final() for scanning partitions, announcing disk and
+handling the last thing for adding disk.
+
+No functional change, and prepare for prevent adding disk from happening
+when updating nr_hw_queues.
+
+Signed-off-by: Ming Lei <ming.lei@redhat.com>
+Reviewed-by: Hannes Reinecke <hare@suse.de>
+Reviewed-by: Nilay Shroff <nilay@linux.ibm.com>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Link: https://lore.kernel.org/r/20250505141805.2751237-8-ming.lei@redhat.com
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+Stable-dep-of: 181bb9c9eae4 ("block: remove redundant GD_NEED_PART_SCAN in add_disk_final()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ block/genhd.c | 46 +++++++++++++++++++++++++++-------------------
+ 1 file changed, 27 insertions(+), 19 deletions(-)
+
+--- a/block/genhd.c
++++ b/block/genhd.c
+@@ -382,6 +382,32 @@ int disk_scan_partitions(struct gendisk
+ return ret;
+ }
+
++static void add_disk_final(struct gendisk *disk)
++{
++ struct device *ddev = disk_to_dev(disk);
++
++ if (!(disk->flags & GENHD_FL_HIDDEN)) {
++ /* Make sure the first partition scan will be proceed */
++ if (get_capacity(disk) && disk_has_partscan(disk))
++ set_bit(GD_NEED_PART_SCAN, &disk->state);
++
++ bdev_add(disk->part0, ddev->devt);
++ if (get_capacity(disk))
++ disk_scan_partitions(disk, BLK_OPEN_READ);
++
++ /*
++ * Announce the disk and partitions after all partitions are
++ * created. (for hidden disks uevents remain suppressed forever)
++ */
++ dev_set_uevent_suppress(ddev, 0);
++ disk_uevent(disk, KOBJ_ADD);
++ }
++
++ blk_apply_bdi_limits(disk->bdi, &disk->queue->limits);
++ disk_add_events(disk);
++ set_bit(GD_ADDED, &disk->state);
++}
++
+ /**
+ * device_add_disk - add disk information to kernel list
+ * @parent: parent device for the disk
+@@ -500,21 +526,6 @@ int __must_check device_add_disk(struct
+ &disk->bdi->dev->kobj, "bdi");
+ if (ret)
+ goto out_unregister_bdi;
+-
+- /* Make sure the first partition scan will be proceed */
+- if (get_capacity(disk) && disk_has_partscan(disk))
+- set_bit(GD_NEED_PART_SCAN, &disk->state);
+-
+- bdev_add(disk->part0, ddev->devt);
+- if (get_capacity(disk))
+- disk_scan_partitions(disk, BLK_OPEN_READ);
+-
+- /*
+- * Announce the disk and partitions after all partitions are
+- * created. (for hidden disks uevents remain suppressed forever)
+- */
+- dev_set_uevent_suppress(ddev, 0);
+- disk_uevent(disk, KOBJ_ADD);
+ } else {
+ /*
+ * Even if the block_device for a hidden gendisk is not
+@@ -523,10 +534,7 @@ int __must_check device_add_disk(struct
+ */
+ disk->part0->bd_dev = MKDEV(disk->major, disk->first_minor);
+ }
+-
+- blk_apply_bdi_limits(disk->bdi, &disk->queue->limits);
+- disk_add_events(disk);
+- set_bit(GD_ADDED, &disk->state);
++ add_disk_final(disk);
+ return 0;
+
+ out_unregister_bdi:
--- /dev/null
+From stable+bounces-289611-greg=kroah.com@vger.kernel.org Mon Jul 27 18:07:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:59:28 -0400
+Subject: block: remove redundant GD_NEED_PART_SCAN in add_disk_final()
+To: stable@vger.kernel.org
+Cc: Connor Williamson <connordw@amazon.com>, Christoph Hellwig <hch@lst.de>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727155928.1732187-2-sashal@kernel.org>
+
+From: Connor Williamson <connordw@amazon.com>
+
+[ Upstream commit 181bb9c9eae4f69fe510a62a42c2932d0314a800 ]
+
+add_disk_final() sets GD_NEED_PART_SCAN before calling bdev_add(),
+then calls disk_scan_partitions() which sets the flag itself. The
+early set is redundant and introduces a race.
+
+Between bdev_add() and disk_scan_partitions(), concurrent openers
+(multipathd, blkid, LVM) see the flag in blkdev_get_whole() and
+trigger bdev_disk_changed(). When disk_scan_partitions() then runs,
+it calls bdev_disk_changed() again, dropping the partitions the
+concurrent opener already created before re-adding them, which can
+result in transient partition disappearances.
+
+The race is observable by inserting an msleep() between bdev_add()
+and disk_scan_partitions() while running concurrent open() calls
+during device bind. Without artificial delay, it manifests under
+scheduling pressure during boot on systems with aggressive device
+scanners (multipathd, systemd-udevd).
+
+Therefore, do not set GD_NEED_PART_SCAN in add_disk_final(). Other
+GD_NEED_PART_SCAN consumers (blkdev_get_whole(),
+sd_need_revalidate()) should not be affected as the flag
+is set internally by disk_scan_partitions().
+
+The retry-on-next-open intention from commit e5cfefa97bcc
+("block: fix scan partition for exclusively open device again")
+should also not be affected as the early return paths in
+disk_scan_partitions() should be unreachable at device registration
+time (bd_holder is NULL and open_partitions is zero).
+
+Fixes: e5cfefa97bcc ("block: fix scan partition for exclusively open device again")
+Cc: stable@vger.kernel.org
+Signed-off-by: Connor Williamson <connordw@amazon.com>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Link: https://patch.msgid.link/20260615130715.53693-1-connordw@amazon.com
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ block/genhd.c | 4 ----
+ 1 file changed, 4 deletions(-)
+
+--- a/block/genhd.c
++++ b/block/genhd.c
+@@ -387,10 +387,6 @@ static void add_disk_final(struct gendis
+ struct device *ddev = disk_to_dev(disk);
+
+ if (!(disk->flags & GENHD_FL_HIDDEN)) {
+- /* Make sure the first partition scan will be proceed */
+- if (get_capacity(disk) && disk_has_partscan(disk))
+- set_bit(GD_NEED_PART_SCAN, &disk->state);
+-
+ bdev_add(disk->part0, ddev->devt);
+ if (get_capacity(disk))
+ disk_scan_partitions(disk, BLK_OPEN_READ);
--- /dev/null
+From stable+bounces-290941-greg=kroah.com@vger.kernel.org Thu Jul 30 03:00:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 20:58:16 -0400
+Subject: Bluetooth: Add PA_LINK to distinguish BIG sync and PA sync connections
+To: stable@vger.kernel.org
+Cc: Yang Li <yang.li@amlogic.com>, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730005817.1696365-1-sashal@kernel.org>
+
+From: Yang Li <yang.li@amlogic.com>
+
+[ Upstream commit a7bcffc673de219af2698fbb90627016233de67b ]
+
+Currently, BIS_LINK is used for both BIG sync and PA sync connections,
+which makes it impossible to distinguish them when searching for a PA
+sync connection.
+
+Adding PA_LINK will make the distinction clearer and simplify future
+extensions for PA-related features.
+
+Signed-off-by: Yang Li <yang.li@amlogic.com>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Stable-dep-of: 9d4b01a0bf8d ("Bluetooth: hci_core: Fix not accounting for BIS/CIS/PA links separately")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/bluetooth/hci.h | 1 +
+ include/net/bluetooth/hci_core.h | 10 +++++++---
+ net/bluetooth/hci_conn.c | 13 ++++++++-----
+ net/bluetooth/hci_core.c | 27 +++++++++++++++------------
+ net/bluetooth/hci_event.c | 7 ++++---
+ net/bluetooth/hci_sync.c | 10 +++++-----
+ net/bluetooth/iso.c | 6 ++++--
+ net/bluetooth/mgmt.c | 1 +
+ 8 files changed, 45 insertions(+), 30 deletions(-)
+
+--- a/include/net/bluetooth/hci.h
++++ b/include/net/bluetooth/hci.h
+@@ -560,6 +560,7 @@ enum {
+ #define LE_LINK 0x80
+ #define CIS_LINK 0x82
+ #define BIS_LINK 0x83
++#define PA_LINK 0x84
+ #define INVALID_LINK 0xff
+
+ /* LMP features */
+--- a/include/net/bluetooth/hci_core.h
++++ b/include/net/bluetooth/hci_core.h
+@@ -1001,6 +1001,7 @@ static inline void hci_conn_hash_add(str
+ break;
+ case CIS_LINK:
+ case BIS_LINK:
++ case PA_LINK:
+ h->iso_num++;
+ break;
+ }
+@@ -1028,6 +1029,7 @@ static inline void hci_conn_hash_del(str
+ break;
+ case CIS_LINK:
+ case BIS_LINK:
++ case PA_LINK:
+ h->iso_num--;
+ break;
+ }
+@@ -1046,6 +1048,7 @@ static inline unsigned int hci_conn_num(
+ return h->sco_num;
+ case CIS_LINK:
+ case BIS_LINK:
++ case PA_LINK:
+ return h->iso_num;
+ default:
+ return 0;
+@@ -1128,7 +1131,7 @@ hci_conn_hash_lookup_create_pa_sync(stru
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (c->type != BIS_LINK)
++ if (c->type != PA_LINK)
+ continue;
+
+ if (!test_bit(HCI_CONN_CREATE_PA_SYNC, &c->flags))
+@@ -1344,7 +1347,7 @@ hci_conn_hash_lookup_big_sync_pend(struc
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (c->type != BIS_LINK)
++ if (c->type != PA_LINK)
+ continue;
+
+ if (handle == c->iso_qos.bcast.big && num_bis == c->num_bis) {
+@@ -1413,7 +1416,7 @@ hci_conn_hash_lookup_pa_sync_handle(stru
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (c->type != BIS_LINK)
++ if (c->type != PA_LINK)
+ continue;
+
+ /* Ignore the listen hcon, we are looking
+@@ -2025,6 +2028,7 @@ static inline int hci_proto_connect_ind(
+
+ case CIS_LINK:
+ case BIS_LINK:
++ case PA_LINK:
+ return iso_connect_ind(hdev, bdaddr, flags);
+
+ default:
+--- a/net/bluetooth/hci_conn.c
++++ b/net/bluetooth/hci_conn.c
+@@ -785,7 +785,7 @@ static int hci_le_big_terminate(struct h
+ d->sync_handle = conn->sync_handle;
+
+ if (test_and_clear_bit(HCI_CONN_PA_SYNC, &conn->flags)) {
+- hci_conn_hash_list_flag(hdev, find_bis, BIS_LINK,
++ hci_conn_hash_list_flag(hdev, find_bis, PA_LINK,
+ HCI_CONN_PA_SYNC, d);
+
+ if (!d->count)
+@@ -914,6 +914,7 @@ static struct hci_conn *__hci_conn_add(s
+ break;
+ case CIS_LINK:
+ case BIS_LINK:
++ case PA_LINK:
+ if (hdev->iso_mtu)
+ /* Dedicated ISO Buffer exists */
+ break;
+@@ -980,6 +981,7 @@ static struct hci_conn *__hci_conn_add(s
+ break;
+ case CIS_LINK:
+ case BIS_LINK:
++ case PA_LINK:
+ /* conn->src should reflect the local identity address */
+ hci_copy_identity_address(hdev, &conn->src, &conn->src_type);
+
+@@ -1033,7 +1035,6 @@ static struct hci_conn *__hci_conn_add(s
+ }
+
+ hci_conn_init_sysfs(conn);
+-
+ return conn;
+ }
+
+@@ -1077,6 +1078,7 @@ static void hci_conn_cleanup_child(struc
+ break;
+ case CIS_LINK:
+ case BIS_LINK:
++ case PA_LINK:
+ if ((conn->state != BT_CONNECTED &&
+ !test_bit(HCI_CONN_CREATE_CIS, &conn->flags)) ||
+ test_bit(HCI_CONN_BIG_CREATED, &conn->flags))
+@@ -1152,7 +1154,8 @@ void hci_conn_del(struct hci_conn *conn)
+ } else {
+ /* Unacked ISO frames */
+ if (conn->type == CIS_LINK ||
+- conn->type == BIS_LINK) {
++ conn->type == BIS_LINK ||
++ conn->type == PA_LINK) {
+ if (hdev->iso_pkts)
+ hdev->iso_cnt += conn->sent;
+ else if (hdev->le_pkts)
+@@ -2089,7 +2092,7 @@ struct hci_conn *hci_pa_create_sync(stru
+
+ bt_dev_dbg(hdev, "dst %pMR type %d sid %d", dst, dst_type, sid);
+
+- conn = hci_conn_add_unset(hdev, BIS_LINK, dst, HCI_ROLE_SLAVE);
++ conn = hci_conn_add_unset(hdev, PA_LINK, dst, HCI_ROLE_SLAVE);
+ if (IS_ERR(conn))
+ return conn;
+
+@@ -2266,7 +2269,7 @@ struct hci_conn *hci_connect_bis(struct
+ * the start periodic advertising and create BIG commands have
+ * been queued
+ */
+- hci_conn_hash_list_state(hdev, bis_mark_per_adv, BIS_LINK,
++ hci_conn_hash_list_state(hdev, bis_mark_per_adv, PA_LINK,
+ BT_BOUND, &data);
+
+ /* Queue start periodic advertising and create BIG */
+--- a/net/bluetooth/hci_core.c
++++ b/net/bluetooth/hci_core.c
+@@ -2960,12 +2960,14 @@ int hci_recv_frame(struct hci_dev *hdev,
+ case HCI_ACLDATA_PKT:
+ /* Detect if ISO packet has been sent as ACL */
+ if (hci_conn_num(hdev, CIS_LINK) ||
+- hci_conn_num(hdev, BIS_LINK)) {
++ hci_conn_num(hdev, BIS_LINK) ||
++ hci_conn_num(hdev, PA_LINK)) {
+ __u16 handle = __le16_to_cpu(hci_acl_hdr(skb)->handle);
+ __u8 type;
+
+ type = hci_conn_lookup_type(hdev, hci_handle(handle));
+- if (type == CIS_LINK || type == BIS_LINK)
++ if (type == CIS_LINK || type == BIS_LINK ||
++ type == PA_LINK)
+ hci_skb_pkt_type(skb) = HCI_ISODATA_PKT;
+ }
+ break;
+@@ -3402,6 +3404,7 @@ static inline void hci_quote_sent(struct
+ break;
+ case CIS_LINK:
+ case BIS_LINK:
++ case PA_LINK:
+ cnt = hdev->iso_mtu ? hdev->iso_cnt :
+ hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt;
+ break;
+@@ -3415,7 +3418,7 @@ static inline void hci_quote_sent(struct
+ }
+
+ static struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type,
+- __u8 type2, int *quote)
++ int *quote)
+ {
+ struct hci_conn_hash *h = &hdev->conn_hash;
+ struct hci_conn *conn = NULL, *c;
+@@ -3427,7 +3430,7 @@ static struct hci_conn *hci_low_sent(str
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if ((c->type != type && c->type != type2) ||
++ if (c->type != type ||
+ skb_queue_empty(&c->data_q))
+ continue;
+
+@@ -3631,7 +3634,7 @@ static void hci_sched_sco(struct hci_dev
+ else
+ cnt = &hdev->sco_cnt;
+
+- while (*cnt && (conn = hci_low_sent(hdev, type, type, "e))) {
++ while (*cnt && (conn = hci_low_sent(hdev, type, "e))) {
+ while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
+ BT_DBG("skb %p len %d", skb, skb->len);
+ hci_send_frame(hdev, skb);
+@@ -3750,8 +3753,8 @@ static void hci_sched_le(struct hci_dev
+ hci_prio_recalculate(hdev, LE_LINK);
+ }
+
+-/* Schedule CIS */
+-static void hci_sched_iso(struct hci_dev *hdev)
++/* Schedule iso */
++static void hci_sched_iso(struct hci_dev *hdev, __u8 type)
+ {
+ struct hci_conn *conn;
+ struct sk_buff *skb;
+@@ -3759,14 +3762,12 @@ static void hci_sched_iso(struct hci_dev
+
+ BT_DBG("%s", hdev->name);
+
+- if (!hci_conn_num(hdev, CIS_LINK) &&
+- !hci_conn_num(hdev, BIS_LINK))
++ if (!hci_conn_num(hdev, type))
+ return;
+
+ cnt = hdev->iso_pkts ? &hdev->iso_cnt :
+ hdev->le_pkts ? &hdev->le_cnt : &hdev->acl_cnt;
+- while (*cnt && (conn = hci_low_sent(hdev, CIS_LINK, BIS_LINK,
+- "e))) {
++ while (*cnt && (conn = hci_low_sent(hdev, type, "e))) {
+ while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
+ BT_DBG("skb %p len %d", skb, skb->len);
+ hci_send_frame(hdev, skb);
+@@ -3791,7 +3792,9 @@ static void hci_tx_work(struct work_stru
+ /* Schedule queues and send stuff to HCI driver */
+ hci_sched_sco(hdev, SCO_LINK);
+ hci_sched_sco(hdev, ESCO_LINK);
+- hci_sched_iso(hdev);
++ hci_sched_iso(hdev, CIS_LINK);
++ hci_sched_iso(hdev, BIS_LINK);
++ hci_sched_iso(hdev, PA_LINK);
+ hci_sched_acl(hdev);
+ hci_sched_le(hdev);
+ }
+--- a/net/bluetooth/hci_event.c
++++ b/net/bluetooth/hci_event.c
+@@ -4455,6 +4455,7 @@ static void hci_num_comp_pkts_evt(struct
+
+ case CIS_LINK:
+ case BIS_LINK:
++ case PA_LINK:
+ if (hdev->iso_pkts) {
+ hdev->iso_cnt += count;
+ if (hdev->iso_cnt > hdev->iso_pkts)
+@@ -6421,7 +6422,7 @@ static void hci_le_pa_sync_estabilished_
+ conn->sync_handle = le16_to_cpu(ev->handle);
+ conn->sid = HCI_SID_INVALID;
+
+- mask |= hci_proto_connect_ind(hdev, &ev->bdaddr, BIS_LINK,
++ mask |= hci_proto_connect_ind(hdev, &ev->bdaddr, PA_LINK,
+ &flags);
+ if (!(mask & HCI_LM_ACCEPT)) {
+ hci_le_pa_term_sync(hdev, ev->handle);
+@@ -6432,7 +6433,7 @@ static void hci_le_pa_sync_estabilished_
+ goto unlock;
+
+ /* Add connection to indicate PA sync event */
+- pa_sync = hci_conn_add_unset(hdev, BIS_LINK, BDADDR_ANY,
++ pa_sync = hci_conn_add_unset(hdev, PA_LINK, BDADDR_ANY,
+ HCI_ROLE_SLAVE);
+
+ if (IS_ERR(pa_sync))
+@@ -6463,7 +6464,7 @@ static void hci_le_per_adv_report_evt(st
+
+ hci_dev_lock(hdev);
+
+- mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, BIS_LINK, &flags);
++ mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, PA_LINK, &flags);
+ if (!(mask & HCI_LM_ACCEPT))
+ goto unlock;
+
+--- a/net/bluetooth/hci_sync.c
++++ b/net/bluetooth/hci_sync.c
+@@ -2988,7 +2988,7 @@ static int hci_le_set_ext_scan_param_syn
+ if (sent) {
+ struct hci_conn *conn;
+
+- conn = hci_conn_hash_lookup_ba(hdev, BIS_LINK,
++ conn = hci_conn_hash_lookup_ba(hdev, PA_LINK,
+ &sent->bdaddr);
+ if (conn) {
+ struct bt_iso_qos *qos = &conn->iso_qos;
+@@ -5552,7 +5552,7 @@ static int hci_disconnect_sync(struct hc
+ {
+ struct hci_cp_disconnect cp;
+
+- if (conn->type == BIS_LINK) {
++ if (conn->type == BIS_LINK || conn->type == PA_LINK) {
+ /* This is a BIS connection, hci_conn_del will
+ * do the necessary cleanup.
+ */
+@@ -5621,7 +5621,7 @@ static int hci_connect_cancel_sync(struc
+ return HCI_ERROR_LOCAL_HOST_TERM;
+ }
+
+- if (conn->type == BIS_LINK) {
++ if (conn->type == BIS_LINK || conn->type == PA_LINK) {
+ /* There is no way to cancel a BIS without terminating the BIG
+ * which is done later on connection cleanup.
+ */
+@@ -5686,7 +5686,7 @@ static int hci_reject_conn_sync(struct h
+ if (conn->type == CIS_LINK)
+ return hci_le_reject_cis_sync(hdev, conn, reason);
+
+- if (conn->type == BIS_LINK)
++ if (conn->type == BIS_LINK || conn->type == PA_LINK)
+ return -EINVAL;
+
+ if (conn->type == SCO_LINK || conn->type == ESCO_LINK)
+@@ -7157,7 +7157,7 @@ static void create_pa_complete(struct hc
+ goto unlock;
+
+ /* Add connection to indicate PA sync error */
+- pa_sync = hci_conn_add_unset(hdev, BIS_LINK, BDADDR_ANY,
++ pa_sync = hci_conn_add_unset(hdev, PA_LINK, BDADDR_ANY,
+ HCI_ROLE_SLAVE);
+
+ if (IS_ERR(pa_sync))
+--- a/net/bluetooth/iso.c
++++ b/net/bluetooth/iso.c
+@@ -2211,7 +2211,8 @@ done:
+
+ static void iso_connect_cfm(struct hci_conn *hcon, __u8 status)
+ {
+- if (hcon->type != CIS_LINK && hcon->type != BIS_LINK) {
++ if (hcon->type != CIS_LINK && hcon->type != BIS_LINK &&
++ hcon->type != PA_LINK) {
+ if (hcon->type != LE_LINK)
+ return;
+
+@@ -2252,7 +2253,8 @@ static void iso_connect_cfm(struct hci_c
+
+ static void iso_disconn_cfm(struct hci_conn *hcon, __u8 reason)
+ {
+- if (hcon->type != CIS_LINK && hcon->type != BIS_LINK)
++ if (hcon->type != CIS_LINK && hcon->type != BIS_LINK &&
++ hcon->type != PA_LINK)
+ return;
+
+ BT_DBG("hcon %p reason %d", hcon, reason);
+--- a/net/bluetooth/mgmt.c
++++ b/net/bluetooth/mgmt.c
+@@ -3258,6 +3258,7 @@ static u8 link_to_bdaddr(u8 link_type, u
+ switch (link_type) {
+ case CIS_LINK:
+ case BIS_LINK:
++ case PA_LINK:
+ case LE_LINK:
+ switch (addr_type) {
+ case ADDR_LE_DEV_PUBLIC:
--- /dev/null
+From stable+bounces-290942-greg=kroah.com@vger.kernel.org Thu Jul 30 03:00:41 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 20:58:17 -0400
+Subject: Bluetooth: hci_core: Fix not accounting for BIS/CIS/PA links separately
+To: stable@vger.kernel.org
+Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730005817.1696365-2-sashal@kernel.org>
+
+From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+
+[ Upstream commit 9d4b01a0bf8d2163ae129c9c537cb0753ad5a2aa ]
+
+This fixes the likes of hci_conn_num(CIS_LINK) returning the total of
+ISO connection which includes BIS_LINK as well, so this splits the
+iso_num into each link type and introduces hci_iso_num that can be used
+in places where the total number of ISO connection still needs to be
+used.
+
+Fixes: 23205562ffc8 ("Bluetooth: separate CIS_LINK and BIS_LINK link types")
+Fixes: a7bcffc673de ("Bluetooth: Add PA_LINK to distinguish BIG sync and PA sync connections")
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/bluetooth/hci_core.h | 30 +++++++++++++++++++++++++-----
+ 1 file changed, 25 insertions(+), 5 deletions(-)
+
+--- a/include/net/bluetooth/hci_core.h
++++ b/include/net/bluetooth/hci_core.h
+@@ -128,7 +128,9 @@ struct hci_conn_hash {
+ struct list_head list;
+ unsigned int acl_num;
+ unsigned int sco_num;
+- unsigned int iso_num;
++ unsigned int cis_num;
++ unsigned int bis_num;
++ unsigned int pa_num;
+ unsigned int le_num;
+ unsigned int le_num_peripheral;
+ };
+@@ -1000,9 +1002,13 @@ static inline void hci_conn_hash_add(str
+ h->sco_num++;
+ break;
+ case CIS_LINK:
++ h->cis_num++;
++ break;
+ case BIS_LINK:
++ h->bis_num++;
++ break;
+ case PA_LINK:
+- h->iso_num++;
++ h->pa_num++;
+ break;
+ }
+ }
+@@ -1028,9 +1034,13 @@ static inline void hci_conn_hash_del(str
+ h->sco_num--;
+ break;
+ case CIS_LINK:
++ h->cis_num--;
++ break;
+ case BIS_LINK:
++ h->bis_num--;
++ break;
+ case PA_LINK:
+- h->iso_num--;
++ h->pa_num--;
+ break;
+ }
+ }
+@@ -1047,9 +1057,11 @@ static inline unsigned int hci_conn_num(
+ case ESCO_LINK:
+ return h->sco_num;
+ case CIS_LINK:
++ return h->cis_num;
+ case BIS_LINK:
++ return h->bis_num;
+ case PA_LINK:
+- return h->iso_num;
++ return h->pa_num;
+ default:
+ return 0;
+ }
+@@ -1059,7 +1071,15 @@ static inline unsigned int hci_conn_coun
+ {
+ struct hci_conn_hash *c = &hdev->conn_hash;
+
+- return c->acl_num + c->sco_num + c->le_num + c->iso_num;
++ return c->acl_num + c->sco_num + c->le_num + c->cis_num + c->bis_num +
++ c->pa_num;
++}
++
++static inline unsigned int hci_iso_count(struct hci_dev *hdev)
++{
++ struct hci_conn_hash *c = &hdev->conn_hash;
++
++ return c->cis_num + c->bis_num;
+ }
+
+ static inline bool hci_conn_valid(struct hci_dev *hdev, struct hci_conn *conn)
--- /dev/null
+From stable+bounces-290094-greg=kroah.com@vger.kernel.org Tue Jul 28 19:53:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 13:07:08 -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: <20260728170708.183603-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
+@@ -424,10 +424,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);
+@@ -436,10 +444,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) {
+@@ -449,15 +458,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-290093-greg=kroah.com@vger.kernel.org Tue Jul 28 19:53:27 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 13:07:07 -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: <20260728170708.183603-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
+@@ -318,51 +318,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
+@@ -405,6 +405,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, uint32_t flag)
--- /dev/null
+From stable+bounces-289595-greg=kroah.com@vger.kernel.org Mon Jul 27 17:52:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:41:01 -0400
+Subject: bpf: Refactor {acquire,release}_reference_state
+To: stable@vger.kernel.org
+Cc: Kumar Kartikeya Dwivedi <memxor@gmail.com>, Eduard Zingerman <eddyz87@gmail.com>, Alexei Starovoitov <ast@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727154103.1515347-1-sashal@kernel.org>
+
+From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
+
+[ Upstream commit 769b0f1c821455ab29baf42491e1ea1d726451fa ]
+
+In preparation for introducing support for more reference types which
+have to add and remove reference state, refactor the
+acquire_reference_state and release_reference_state functions to share
+common logic.
+
+The acquire_reference_state function simply handles growing the acquired
+refs and returning the pointer to the new uninitialized element, which
+can be filled in by the caller.
+
+The release_reference_state function simply erases a reference state
+entry in the acquired_refs array and shrinks it. The callers are
+responsible for finding the suitable element by matching on various
+fields of the reference state and requesting deletion through this
+function. It is not supposed to be called directly.
+
+Existing callers of release_reference_state were using it to find and
+remove state for a given ref_obj_id without scrubbing the associated
+registers in the verifier state. Introduce release_reference_nomark to
+provide this functionality and convert callers. We now use this new
+release_reference_nomark function within release_reference as well.
+It needs to operate on a verifier state instead of taking verifier env
+as mark_ptr_or_null_regs requires operating on verifier state of the
+two branches of a NULL condition check, therefore env->cur_state cannot
+be used directly.
+
+Acked-by: Eduard Zingerman <eddyz87@gmail.com>
+Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
+Link: https://lore.kernel.org/r/20241204030400.208005-3-memxor@gmail.com
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Stable-dep-of: 5e0b273e0a62 ("bpf: Reset register bounds before narrowing retval range in check_mem_access()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/bpf/verifier.c | 28 +++++++++++++++-------------
+ 1 file changed, 15 insertions(+), 13 deletions(-)
+
+--- a/kernel/bpf/verifier.c
++++ b/kernel/bpf/verifier.c
+@@ -7207,11 +7207,12 @@ static int check_mem_access(struct bpf_v
+ if (!err && value_regno >= 0 && (t == BPF_READ || rdonly_mem))
+ mark_reg_unknown(env, regs, value_regno);
+ } else if (reg->type == PTR_TO_CTX) {
+- bool is_retval = false;
++ struct bpf_insn_access_aux info = {
++ .reg_type = SCALAR_VALUE,
++ .is_ldsx = is_ldsx,
++ .log = &env->log,
++ };
+ struct bpf_retval_range range;
+- enum bpf_reg_type reg_type = SCALAR_VALUE;
+- struct btf *btf = NULL;
+- u32 btf_id = 0;
+
+ if (t == BPF_WRITE && value_regno >= 0 &&
+ is_pointer_value(env, value_regno)) {
+@@ -7223,8 +7224,9 @@ static int check_mem_access(struct bpf_v
+ if (err < 0)
+ return err;
+
+- err = check_ctx_access(env, insn_idx, off, size, t, ®_type, &btf,
+- &btf_id, &is_retval, is_ldsx);
++ err = check_ctx_access(env, insn_idx, off, size, t, &info.reg_type,
++ &info.btf, &info.btf_id, &info.is_retval,
++ info.is_ldsx);
+ if (err)
+ verbose_linfo(env, insn_idx, "; ");
+ if (!err && t == BPF_READ && value_regno >= 0) {
+@@ -7232,8 +7234,8 @@ static int check_mem_access(struct bpf_v
+ * PTR_TO_PACKET[_META,_END]. In the latter
+ * case, we know the offset is zero.
+ */
+- if (reg_type == SCALAR_VALUE) {
+- if (is_retval && get_func_retval_range(env->prog, &range)) {
++ if (info.reg_type == SCALAR_VALUE) {
++ if (info.is_retval && get_func_retval_range(env->prog, &range)) {
+ err = __mark_reg_s32_range(env, regs, value_regno,
+ range.minval, range.maxval);
+ if (err)
+@@ -7244,7 +7246,7 @@ static int check_mem_access(struct bpf_v
+ } else {
+ mark_reg_known_zero(env, regs,
+ value_regno);
+- if (type_may_be_null(reg_type))
++ if (type_may_be_null(info.reg_type))
+ regs[value_regno].id = ++env->id_gen;
+ /* A load of ctx field could have different
+ * actual load size with the one encoded in the
+@@ -7252,12 +7254,12 @@ static int check_mem_access(struct bpf_v
+ * a sub-register.
+ */
+ regs[value_regno].subreg_def = DEF_NOT_SUBREG;
+- if (base_type(reg_type) == PTR_TO_BTF_ID) {
+- regs[value_regno].btf = btf;
+- regs[value_regno].btf_id = btf_id;
++ if (base_type(info.reg_type) == PTR_TO_BTF_ID) {
++ regs[value_regno].btf = info.btf;
++ regs[value_regno].btf_id = info.btf_id;
+ }
+ }
+- regs[value_regno].type = reg_type;
++ regs[value_regno].type = info.reg_type;
+ }
+
+ } else if (reg->type == PTR_TO_STACK) {
--- /dev/null
+From stable+bounces-289596-greg=kroah.com@vger.kernel.org Mon Jul 27 17:49:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:41:02 -0400
+Subject: bpf: Refactor check_ctx_access()
+To: stable@vger.kernel.org
+Cc: Amery Hung <ameryhung@gmail.com>, Alexei Starovoitov <ast@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727154103.1515347-2-sashal@kernel.org>
+
+From: Amery Hung <ameryhung@gmail.com>
+
+[ Upstream commit 201b62ccc83153d2925d310a2afe762905e0c455 ]
+
+Reduce the variable passing madness surrounding check_ctx_access().
+Currently, check_mem_access() passes many pointers to local variables to
+check_ctx_access(). They are used to initialize "struct
+bpf_insn_access_aux info" in check_ctx_access() and then passed to
+is_valid_access(). Then, check_ctx_access() takes the data our from
+info and write them back the pointers to pass them back. This can be
+simpilified by moving info up to check_mem_access().
+
+No functional change.
+
+Signed-off-by: Amery Hung <ameryhung@gmail.com>
+Link: https://lore.kernel.org/r/20250221175644.1822383-1-ameryhung@gmail.com
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Stable-dep-of: 5e0b273e0a62 ("bpf: Reset register bounds before narrowing retval range in check_mem_access()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/bpf/verifier.c | 27 +++++----------------------
+ 1 file changed, 5 insertions(+), 22 deletions(-)
+
+--- a/kernel/bpf/verifier.c
++++ b/kernel/bpf/verifier.c
+@@ -5829,18 +5829,10 @@ static int check_packet_access(struct bp
+
+ /* check access to 'struct bpf_context' fields. Supports fixed offsets only */
+ static int check_ctx_access(struct bpf_verifier_env *env, int insn_idx, int off, int size,
+- enum bpf_access_type t, enum bpf_reg_type *reg_type,
+- struct btf **btf, u32 *btf_id, bool *is_retval, bool is_ldsx)
++ enum bpf_access_type t, struct bpf_insn_access_aux *info)
+ {
+- struct bpf_insn_access_aux info = {
+- .reg_type = *reg_type,
+- .log = &env->log,
+- .is_retval = false,
+- .is_ldsx = is_ldsx,
+- };
+-
+ if (env->ops->is_valid_access &&
+- env->ops->is_valid_access(off, size, t, env->prog, &info)) {
++ env->ops->is_valid_access(off, size, t, env->prog, info)) {
+ /* A non zero info.ctx_field_size indicates that this field is a
+ * candidate for later verifier transformation to load the whole
+ * field and then apply a mask when accessed with a narrower
+@@ -5848,15 +5840,8 @@ static int check_ctx_access(struct bpf_v
+ * will only allow for whole field access and rejects any other
+ * type of narrower access.
+ */
+- *reg_type = info.reg_type;
+- *is_retval = info.is_retval;
+-
+- if (base_type(*reg_type) == PTR_TO_BTF_ID) {
+- *btf = info.btf;
+- *btf_id = info.btf_id;
+- } else {
+- env->insn_aux_data[insn_idx].ctx_field_size = info.ctx_field_size;
+- }
++ if (base_type(info->reg_type) != PTR_TO_BTF_ID)
++ env->insn_aux_data[insn_idx].ctx_field_size = info->ctx_field_size;
+ /* remember the offset of last byte accessed in ctx */
+ if (env->prog->aux->max_ctx_offset < off + size)
+ env->prog->aux->max_ctx_offset = off + size;
+@@ -7224,9 +7209,7 @@ static int check_mem_access(struct bpf_v
+ if (err < 0)
+ return err;
+
+- err = check_ctx_access(env, insn_idx, off, size, t, &info.reg_type,
+- &info.btf, &info.btf_id, &info.is_retval,
+- info.is_ldsx);
++ err = check_ctx_access(env, insn_idx, off, size, t, &info);
+ if (err)
+ verbose_linfo(env, insn_idx, "; ");
+ if (!err && t == BPF_READ && value_regno >= 0) {
--- /dev/null
+From stable+bounces-289597-greg=kroah.com@vger.kernel.org Mon Jul 27 17:52:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:41:03 -0400
+Subject: bpf: Reset register bounds before narrowing retval range in check_mem_access()
+To: stable@vger.kernel.org
+Cc: Tristan Madani <tristan@talencesecurity.com>, Eduard Zingerman <eddyz87@gmail.com>, Alexei Starovoitov <ast@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727154103.1515347-3-sashal@kernel.org>
+
+From: Tristan Madani <tristan@talencesecurity.com>
+
+[ Upstream commit 5e0b273e0a62cc04ec338c7b502797c66c2ed42a ]
+
+When the BPF verifier processes a context load of an LSM hook return
+value, it calls __mark_reg_s32_range() to narrow the register to the
+hook's valid range. However, __mark_reg_s32_range() intersects the new
+range with the register's existing bounds using max_t()/min_t() rather
+than replacing them.
+
+If the destination register carries stale bounds from a prior instruction
+(e.g. BPF_MOV64_IMM), the intersection can produce a range narrower than
+reality. The verifier then believes it knows the register's exact value,
+while at runtime the actual hook return value is loaded, creating a
+verifier/runtime mismatch that can be used to bypass BPF memory safety
+checks.
+
+The else branch already calls mark_reg_unknown() to reset register state
+before any narrowing. Apply the same reset in the is_retval path so
+stale bounds are cleared before __mark_reg_s32_range() intersects.
+
+Fixes: 5d99e198be27 ("bpf, lsm: Add check for BPF LSM return value")
+Cc: stable@vger.kernel.org
+Signed-off-by: Tristan Madani <tristan@talencesecurity.com>
+Acked-by: Eduard Zingerman <eddyz87@gmail.com>
+Link: https://lore.kernel.org/r/20260622230123.3695446-2-tristmd@gmail.com
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/bpf/verifier.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/kernel/bpf/verifier.c
++++ b/kernel/bpf/verifier.c
+@@ -7219,6 +7219,7 @@ static int check_mem_access(struct bpf_v
+ */
+ if (info.reg_type == SCALAR_VALUE) {
+ if (info.is_retval && get_func_retval_range(env->prog, &range)) {
++ mark_reg_unknown(env, regs, value_regno);
+ err = __mark_reg_s32_range(env, regs, value_regno,
+ range.minval, range.maxval);
+ if (err)
--- /dev/null
+From stable+bounces-289715-greg=kroah.com@vger.kernel.org Tue Jul 28 00:25:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:21:05 -0400
+Subject: cleanup: add a scoped version of CLASS()
+To: stable@vger.kernel.org
+Cc: Christian Brauner <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222109.2119761-1-sashal@kernel.org>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 5c21c5f22d0701ac6c1cafc0e8de4bf42e5c53e5 ]
+
+This will make it possible to use:
+
+scoped_class() {
+}
+
+constructs to limit variables to certain scopes and still perform
+auto-cleanup.
+
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/cleanup.h | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/include/linux/cleanup.h
++++ b/include/linux/cleanup.h
+@@ -277,6 +277,14 @@ static inline class_##_name##_t class_##
+ class_##_name##_t var __cleanup(class_##_name##_destructor) = \
+ class_##_name##_constructor
+
++#define scoped_class(_name, var, args) \
++ for (CLASS(_name, var)(args); \
++ __guard_ptr(_name)(&var) || !__is_cond_ptr(_name); \
++ ({ goto _label; })) \
++ if (0) { \
++_label: \
++ break; \
++ } else
+
+ /*
+ * DEFINE_GUARD(name, type, lock, unlock):
--- /dev/null
+From stable+bounces-289716-greg=kroah.com@vger.kernel.org Tue Jul 28 00:21:17 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:21:06 -0400
+Subject: cleanup: fix scoped_class()
+To: stable@vger.kernel.org
+Cc: Christian Brauner <brauner@kernel.org>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222109.2119761-2-sashal@kernel.org>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 4e97bae1b412cd6ed8053b3d8a242122952985cc ]
+
+This is a class, not a guard so why on earth is it checking for guard
+pointers or conditional lock acquisition? None of it makes any sense at
+all.
+
+I'm not sure what happened back then. Maybe I had a brief psychedelic
+period that I completely forgot about and spaced out into a zone where
+that initial macro implementation made any sense at all.
+
+Link: https://patch.msgid.link/20251103-work-creds-init_cred-v1-1-cb3ec8711a6a@kernel.org
+Fixes: 5c21c5f22d07 ("cleanup: add a scoped version of CLASS()")
+Reviewed-by: Jens Axboe <axboe@kernel.dk>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/cleanup.h | 15 ++++++++-------
+ 1 file changed, 8 insertions(+), 7 deletions(-)
+
+--- a/include/linux/cleanup.h
++++ b/include/linux/cleanup.h
+@@ -277,15 +277,16 @@ static inline class_##_name##_t class_##
+ class_##_name##_t var __cleanup(class_##_name##_destructor) = \
+ class_##_name##_constructor
+
+-#define scoped_class(_name, var, args) \
+- for (CLASS(_name, var)(args); \
+- __guard_ptr(_name)(&var) || !__is_cond_ptr(_name); \
+- ({ goto _label; })) \
+- if (0) { \
+-_label: \
+- break; \
++#define __scoped_class(_name, var, _label, args...) \
++ for (CLASS(_name, var)(args); ; ({ goto _label; })) \
++ if (0) { \
++_label: \
++ break; \
+ } else
+
++#define scoped_class(_name, var, args...) \
++ __scoped_class(_name, var, __UNIQUE_ID(label), args)
++
+ /*
+ * DEFINE_GUARD(name, type, lock, unlock):
+ * trivial wrapper around DEFINE_CLASS() above specifically
--- /dev/null
+From stable+bounces-289717-greg=kroah.com@vger.kernel.org Tue Jul 28 00:25:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:21:07 -0400
+Subject: cred: add kernel_cred() helper
+To: stable@vger.kernel.org
+Cc: Christian Brauner <brauner@kernel.org>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222109.2119761-3-sashal@kernel.org>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 4c7ceeb62d3330b6fb2b549ae833a92c0f481f3e ]
+
+Access kernel creds based off of init_task. This will let us avoid any
+direct access to init_cred.
+
+Link: https://patch.msgid.link/20251103-work-creds-init_cred-v1-2-cb3ec8711a6a@kernel.org
+Reviewed-by: Jens Axboe <axboe@kernel.dk>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/cred.h | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+--- a/include/linux/cred.h
++++ b/include/linux/cred.h
+@@ -20,6 +20,8 @@
+ struct cred;
+ struct inode;
+
++extern struct task_struct init_task;
++
+ /*
+ * COW Supplementary groups list
+ */
+@@ -158,6 +160,11 @@ extern void abort_creds(struct cred *);
+ extern const struct cred *override_creds(const struct cred *);
+ extern void revert_creds(const struct cred *);
+ extern struct cred *prepare_kernel_cred(struct task_struct *);
++static inline const struct cred *kernel_cred(void)
++{
++ /* shut up sparse */
++ return rcu_dereference_raw(init_task.cred);
++}
+ extern int set_security_override(struct cred *, u32);
+ extern int set_security_override_from_ctx(struct cred *, const char *);
+ extern int set_create_files_as(struct cred *, struct inode *);
--- /dev/null
+From stable+bounces-289718-greg=kroah.com@vger.kernel.org Tue Jul 28 00:21:17 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:21:08 -0400
+Subject: cred: add scoped_with_kernel_creds()
+To: stable@vger.kernel.org
+Cc: Christian Brauner <brauner@kernel.org>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222109.2119761-4-sashal@kernel.org>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit ae40e6c65791f47c76cc14d0cce2707fe6053f72 ]
+
+Add a new cleanup class for override creds. We can make use of this in a
+bunch of places going forward.
+
+Based on this scoped_with_kernel_creds() that can be used to temporarily
+assume kernel credentials for specific tasks such as firmware loading,
+or coredump socket connections. At no point will the caller interact
+with the kernel credentials directly.
+
+Link: https://patch.msgid.link/20251103-work-creds-init_cred-v1-4-cb3ec8711a6a@kernel.org
+Reviewed-by: Jens Axboe <axboe@kernel.dk>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/cred.h | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/include/linux/cred.h
++++ b/include/linux/cred.h
+@@ -205,6 +205,14 @@ static inline struct cred *get_new_cred(
+ return get_new_cred_many(cred, 1);
+ }
+
++DEFINE_CLASS(override_creds,
++ const struct cred *,
++ revert_creds(_T),
++ override_creds(override_cred), const struct cred *override_cred)
++
++#define scoped_with_kernel_creds() \
++ scoped_class(override_creds, __UNIQUE_ID(cred), kernel_cred())
++
+ /**
+ * get_cred_many - Get references on a set of credentials
+ * @cred: The credentials to reference
--- /dev/null
+From stable+bounces-289719-greg=kroah.com@vger.kernel.org Tue Jul 28 00:21:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:21:09 -0400
+Subject: dm: avoid leaking the caller's thread keyring via the table device file
+To: stable@vger.kernel.org
+Cc: Ingo Blechschmidt <iblech@speicherleck.de>, Mikulas Patocka <mpatocka@redhat.com>, Ondrej Kozina <okozina@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222109.2119761-5-sashal@kernel.org>
+
+From: Ingo Blechschmidt <iblech@speicherleck.de>
+
+[ Upstream commit 981ccd97f7153d310dfa92a534525bbaf46752c2 ]
+
+The refactoring in commit a28d893eb327 ("md: port block device access to file")
+accidentally causes the caller's thread keyring to be kept alive long
+beyond the caller's lifetime.
+
+As a result, "cryptsetup luksSuspend" silently fails to wipe the
+LUKS volume key from memory.
+
+In detail: "cryptsetup luksOpen" uses its supposedly ephemeral thread
+keyring to pass the volume key to the kernel. dm-crypt's
+crypt_set_keyring_key() copies the key material into its own
+crypt_config structure and then drops its own reference to the key in
+the keyring with key_put().
+
+With this fix, restoring pre-v6.9 behavior, the copy in the thread
+keyring is then promptly garbage collected, such that exactly one copy
+of the volume key remains. This single copy is correctly wiped from
+memory on "cryptsetup luksSuspend".
+
+Without this fix, the thread keyring and the volume key in it remains.
+This second copy is only freed on "luksClose". "luksSuspend" neither
+knows about this copy nor has any way to remove it, so the key remains
+recoverable from RAM after a suspend that is documented to have wiped it.
+
+This fix should not introduce new security problems, as the code is
+anyway gated by CAP_SYS_ADMIN. The device-mapper core, not the calling
+task, is the legitimate owner of this long-lived file.
+
+Fixes: a28d893eb327 ("md: port block device access to file")
+Closes: https://gitlab.com/cryptsetup/cryptsetup/-/work_items/993
+Link: https://www.speicherleck.de/iblech/cryptsetup-luksSuspend-issue-reproduction/
+Signed-off-by: Ingo Blechschmidt <iblech@speicherleck.de>
+Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
+Cc: stable@vger.kernel.org
+Tested-by: Ondrej Kozina <okozina@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/md/dm.c | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+--- a/drivers/md/dm.c
++++ b/drivers/md/dm.c
+@@ -737,7 +737,16 @@ static struct table_device *open_table_d
+ return ERR_PTR(-ENOMEM);
+ refcount_set(&td->count, 1);
+
+- bdev_file = bdev_file_open_by_dev(dev, mode, _dm_claim_ptr, NULL);
++ /*
++ * Open the backing device with kernel rather than caller
++ * credentials. Otherwise the caller's credentials would be
++ * pinned in bdev_file->f_cred until the table device is closed.
++ * That would keep the caller's thread keyring alive long beyond the
++ * lifetime of the caller, breaking userspace expectation (e.g.
++ * cryptsetup(8) leaking the LUKS volume key).
++ */
++ scoped_with_kernel_creds()
++ bdev_file = bdev_file_open_by_dev(dev, mode, _dm_claim_ptr, NULL);
+ if (IS_ERR(bdev_file)) {
+ r = PTR_ERR(bdev_file);
+ goto out_free_td;
--- /dev/null
+From stable+bounces-289643-greg=kroah.com@vger.kernel.org Mon Jul 27 20:43:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 14:39:57 -0400
+Subject: dm-integrity: fix leaking uninitialized kernel memory
+To: stable@vger.kernel.org
+Cc: Mikulas Patocka <mpatocka@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727183957.1545701-1-sashal@kernel.org>
+
+From: Mikulas Patocka <mpatocka@redhat.com>
+
+[ Upstream commit 7bb03b2b01b814a9fc14afbfc2cbb2cca5b34750 ]
+
+If hash size is less than device's tuple size, dm-integrity is supposed
+to zero the remaining space. There was a bug in the code that zeroing
+didn't work. This commit fixes it.
+
+Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
+Assisted-by: Claude:claude-opus-4.6
+Fixes: fb0987682c62 ("dm-integrity: introduce the Inline mode")
+Cc: stable@vger.kernel.org
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/md/dm-integrity.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/md/dm-integrity.c
++++ b/drivers/md/dm-integrity.c
+@@ -2502,7 +2502,7 @@ skip_spinlock:
+ struct bio_vec bv = bio_iter_iovec(bio, dio->bio_details.bi_iter);
+ const char *mem = bvec_kmap_local(&bv);
+ if (ic->tag_size < ic->tuple_size)
+- memset(dio->integrity_payload + pos + ic->tag_size, 0, ic->tuple_size - ic->tuple_size);
++ memset(dio->integrity_payload + pos + ic->tag_size, 0, ic->tuple_size - ic->tag_size);
+ integrity_sector_checksum(ic, dio->bio_details.bi_iter.bi_sector, mem, dio->integrity_payload + pos);
+ kunmap_local(mem);
+ pos += ic->tuple_size;
--- /dev/null
+From stable+bounces-289277-greg=kroah.com@vger.kernel.org Sun Jul 26 15:22:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 09:21:52 -0400
+Subject: dma: dw-edma: Fix build warning in dw_edma_pcie_probe()
+To: stable@vger.kernel.org
+Cc: Abinash Singh <abinashlalotra@gmail.com>, Abinash Singh <abinashsinghlalotra@gmail.com>, Manivannan Sadhasivam <mani@kernel.org>, Vinod Koul <vkoul@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726132154.327006-1-sashal@kernel.org>
+
+From: Abinash Singh <abinashlalotra@gmail.com>
+
+[ Upstream commit 3df63fa8f2afd051848e37ef1b8299dee28d4f87 ]
+
+The function dw_edma_pcie_probe() in dw-edma-pcie.c triggered a
+frame size warning:
+ld.lld:warning:
+ drivers/dma/dw-edma/dw-edma-pcie.c:162:0: stack frame size (1040) exceeds limit (1024) in function 'dw_edma_pcie_probe'
+
+This patch reduces the stack usage by dynamically allocating the
+`vsec_data` structure using kmalloc(), rather than placing it on
+the stack. This eliminates the overflow warning and improves kernel
+robustness.
+
+Signed-off-by: Abinash Singh <abinashsinghlalotra@gmail.com>
+Acked-by: Manivannan Sadhasivam <mani@kernel.org>
+Link: https://lore.kernel.org/r/20250705160055.808165-1-abinashsinghlalotra@gmail.com
+Signed-off-by: Vinod Koul <vkoul@kernel.org>
+Stable-dep-of: 11d7cfe0c119 ("dmaengine: dw-edma-pcie: Reject devices without driver data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/dma/dw-edma/dw-edma-pcie.c | 62 +++++++++++++++++++------------------
+ 1 file changed, 33 insertions(+), 29 deletions(-)
+
+--- a/drivers/dma/dw-edma/dw-edma-pcie.c
++++ b/drivers/dma/dw-edma/dw-edma-pcie.c
+@@ -160,12 +160,16 @@ static int dw_edma_pcie_probe(struct pci
+ const struct pci_device_id *pid)
+ {
+ struct dw_edma_pcie_data *pdata = (void *)pid->driver_data;
+- struct dw_edma_pcie_data vsec_data;
++ struct dw_edma_pcie_data *vsec_data __free(kfree) = NULL;
+ struct device *dev = &pdev->dev;
+ struct dw_edma_chip *chip;
+ int err, nr_irqs;
+ int i, mask;
+
++ vsec_data = kmalloc(sizeof(*vsec_data), GFP_KERNEL);
++ if (!vsec_data)
++ return -ENOMEM;
++
+ /* Enable PCI device */
+ err = pcim_enable_device(pdev);
+ if (err) {
+@@ -173,23 +177,23 @@ static int dw_edma_pcie_probe(struct pci
+ return err;
+ }
+
+- memcpy(&vsec_data, pdata, sizeof(struct dw_edma_pcie_data));
++ memcpy(vsec_data, pdata, sizeof(struct dw_edma_pcie_data));
+
+ /*
+ * Tries to find if exists a PCIe Vendor-Specific Extended Capability
+ * for the DMA, if one exists, then reconfigures it.
+ */
+- dw_edma_pcie_get_vsec_dma_data(pdev, &vsec_data);
++ dw_edma_pcie_get_vsec_dma_data(pdev, vsec_data);
+
+ /* Mapping PCI BAR regions */
+- mask = BIT(vsec_data.rg.bar);
+- for (i = 0; i < vsec_data.wr_ch_cnt; i++) {
+- mask |= BIT(vsec_data.ll_wr[i].bar);
+- mask |= BIT(vsec_data.dt_wr[i].bar);
+- }
+- for (i = 0; i < vsec_data.rd_ch_cnt; i++) {
+- mask |= BIT(vsec_data.ll_rd[i].bar);
+- mask |= BIT(vsec_data.dt_rd[i].bar);
++ mask = BIT(vsec_data->rg.bar);
++ for (i = 0; i < vsec_data->wr_ch_cnt; i++) {
++ mask |= BIT(vsec_data->ll_wr[i].bar);
++ mask |= BIT(vsec_data->dt_wr[i].bar);
++ }
++ for (i = 0; i < vsec_data->rd_ch_cnt; i++) {
++ mask |= BIT(vsec_data->ll_rd[i].bar);
++ mask |= BIT(vsec_data->dt_rd[i].bar);
+ }
+ err = pcim_iomap_regions(pdev, mask, pci_name(pdev));
+ if (err) {
+@@ -212,7 +216,7 @@ static int dw_edma_pcie_probe(struct pci
+ return -ENOMEM;
+
+ /* IRQs allocation */
+- nr_irqs = pci_alloc_irq_vectors(pdev, 1, vsec_data.irqs,
++ nr_irqs = pci_alloc_irq_vectors(pdev, 1, vsec_data->irqs,
+ PCI_IRQ_MSI | PCI_IRQ_MSIX);
+ if (nr_irqs < 1) {
+ pci_err(pdev, "fail to alloc IRQ vector (number of IRQs=%u)\n",
+@@ -223,22 +227,22 @@ static int dw_edma_pcie_probe(struct pci
+ /* Data structure initialization */
+ chip->dev = dev;
+
+- chip->mf = vsec_data.mf;
++ chip->mf = vsec_data->mf;
+ chip->nr_irqs = nr_irqs;
+ chip->ops = &dw_edma_pcie_plat_ops;
+
+- chip->ll_wr_cnt = vsec_data.wr_ch_cnt;
+- chip->ll_rd_cnt = vsec_data.rd_ch_cnt;
++ chip->ll_wr_cnt = vsec_data->wr_ch_cnt;
++ chip->ll_rd_cnt = vsec_data->rd_ch_cnt;
+
+- chip->reg_base = pcim_iomap_table(pdev)[vsec_data.rg.bar];
++ chip->reg_base = pcim_iomap_table(pdev)[vsec_data->rg.bar];
+ if (!chip->reg_base)
+ return -ENOMEM;
+
+ for (i = 0; i < chip->ll_wr_cnt; i++) {
+ struct dw_edma_region *ll_region = &chip->ll_region_wr[i];
+ struct dw_edma_region *dt_region = &chip->dt_region_wr[i];
+- struct dw_edma_block *ll_block = &vsec_data.ll_wr[i];
+- struct dw_edma_block *dt_block = &vsec_data.dt_wr[i];
++ struct dw_edma_block *ll_block = &vsec_data->ll_wr[i];
++ struct dw_edma_block *dt_block = &vsec_data->dt_wr[i];
+
+ ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar];
+ if (!ll_region->vaddr.io)
+@@ -262,8 +266,8 @@ static int dw_edma_pcie_probe(struct pci
+ for (i = 0; i < chip->ll_rd_cnt; i++) {
+ struct dw_edma_region *ll_region = &chip->ll_region_rd[i];
+ struct dw_edma_region *dt_region = &chip->dt_region_rd[i];
+- struct dw_edma_block *ll_block = &vsec_data.ll_rd[i];
+- struct dw_edma_block *dt_block = &vsec_data.dt_rd[i];
++ struct dw_edma_block *ll_block = &vsec_data->ll_rd[i];
++ struct dw_edma_block *dt_block = &vsec_data->dt_rd[i];
+
+ ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar];
+ if (!ll_region->vaddr.io)
+@@ -295,31 +299,31 @@ static int dw_edma_pcie_probe(struct pci
+ pci_dbg(pdev, "Version:\tUnknown (0x%x)\n", chip->mf);
+
+ pci_dbg(pdev, "Registers:\tBAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p)\n",
+- vsec_data.rg.bar, vsec_data.rg.off, vsec_data.rg.sz,
++ vsec_data->rg.bar, vsec_data->rg.off, vsec_data->rg.sz,
+ chip->reg_base);
+
+
+ for (i = 0; i < chip->ll_wr_cnt; i++) {
+ pci_dbg(pdev, "L. List:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
+- i, vsec_data.ll_wr[i].bar,
+- vsec_data.ll_wr[i].off, chip->ll_region_wr[i].sz,
++ i, vsec_data->ll_wr[i].bar,
++ vsec_data->ll_wr[i].off, chip->ll_region_wr[i].sz,
+ chip->ll_region_wr[i].vaddr.io, &chip->ll_region_wr[i].paddr);
+
+ pci_dbg(pdev, "Data:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
+- i, vsec_data.dt_wr[i].bar,
+- vsec_data.dt_wr[i].off, chip->dt_region_wr[i].sz,
++ i, vsec_data->dt_wr[i].bar,
++ vsec_data->dt_wr[i].off, chip->dt_region_wr[i].sz,
+ chip->dt_region_wr[i].vaddr.io, &chip->dt_region_wr[i].paddr);
+ }
+
+ for (i = 0; i < chip->ll_rd_cnt; i++) {
+ pci_dbg(pdev, "L. List:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
+- i, vsec_data.ll_rd[i].bar,
+- vsec_data.ll_rd[i].off, chip->ll_region_rd[i].sz,
++ i, vsec_data->ll_rd[i].bar,
++ vsec_data->ll_rd[i].off, chip->ll_region_rd[i].sz,
+ chip->ll_region_rd[i].vaddr.io, &chip->ll_region_rd[i].paddr);
+
+ pci_dbg(pdev, "Data:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
+- i, vsec_data.dt_rd[i].bar,
+- vsec_data.dt_rd[i].off, chip->dt_region_rd[i].sz,
++ i, vsec_data->dt_rd[i].bar,
++ vsec_data->dt_rd[i].off, chip->dt_region_rd[i].sz,
+ chip->dt_region_rd[i].vaddr.io, &chip->dt_region_rd[i].paddr);
+ }
+
--- /dev/null
+From stable+bounces-289280-greg=kroah.com@vger.kernel.org Sun Jul 26 15:22:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 09:21:53 -0400
+Subject: dmaengine: dw-edma: Fix confusing cleanup.h syntax
+To: stable@vger.kernel.org
+Cc: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>, Manivannan Sadhasivam <mani@kernel.org>, Vinod Koul <vkoul@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726132154.327006-2-sashal@kernel.org>
+
+From: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
+
+[ Upstream commit f9ef8dedee34e2d7828d5a6a0643cd969aaa8437 ]
+
+Initializing automatic __free variables to NULL without need (e.g.
+branches with different allocations), followed by actual allocation is
+in contrary to explicit coding rules guiding cleanup.h:
+
+"Given that the "__free(...) = NULL" pattern for variables defined at
+the top of the function poses this potential interdependency problem the
+recommendation is to always define and assign variables in one statement
+and not group variable definitions at the top of the function when
+__free() is used."
+
+Code does not have a bug, but is less readable and uses discouraged
+coding practice, so fix that by moving declaration to the place of
+assignment.
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
+Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
+Link: https://patch.msgid.link/20251208020729.4654-2-krzysztof.kozlowski@oss.qualcomm.com
+Signed-off-by: Vinod Koul <vkoul@kernel.org>
+Stable-dep-of: 11d7cfe0c119 ("dmaengine: dw-edma-pcie: Reject devices without driver data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/dma/dw-edma/dw-edma-pcie.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/dma/dw-edma/dw-edma-pcie.c
++++ b/drivers/dma/dw-edma/dw-edma-pcie.c
+@@ -160,13 +160,13 @@ static int dw_edma_pcie_probe(struct pci
+ const struct pci_device_id *pid)
+ {
+ struct dw_edma_pcie_data *pdata = (void *)pid->driver_data;
+- struct dw_edma_pcie_data *vsec_data __free(kfree) = NULL;
+ struct device *dev = &pdev->dev;
+ struct dw_edma_chip *chip;
+ int err, nr_irqs;
+ int i, mask;
+
+- vsec_data = kmalloc(sizeof(*vsec_data), GFP_KERNEL);
++ struct dw_edma_pcie_data *vsec_data __free(kfree) =
++ kmalloc(sizeof(*vsec_data), GFP_KERNEL);
+ if (!vsec_data)
+ return -ENOMEM;
+
--- /dev/null
+From stable+bounces-289279-greg=kroah.com@vger.kernel.org Sun Jul 26 15:22:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 09:21:54 -0400
+Subject: dmaengine: dw-edma-pcie: Reject devices without driver data
+To: stable@vger.kernel.org
+Cc: Koichiro Den <den@valinux.co.jp>, Frank Li <Frank.Li@nxp.com>, Vinod Koul <vkoul@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726132154.327006-3-sashal@kernel.org>
+
+From: Koichiro Den <den@valinux.co.jp>
+
+[ Upstream commit 11d7cfe0c119691b2dafbb699bbca90258c678aa ]
+
+dw_edma_pcie_probe() treats the PCI device ID driver_data as the
+template for the controller layout and copies it unconditionally. A
+device bound dynamically via sysfs can match the driver without that
+data, which leads to a NULL pointer dereference.
+
+Reject such matches before enabling the device.
+
+Fixes: 41aaff2a2ac0 ("dmaengine: Add Synopsys eDMA IP PCIe glue-logic")
+Cc: stable@vger.kernel.org
+Signed-off-by: Koichiro Den <den@valinux.co.jp>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Link: https://patch.msgid.link/20260521142153.2957432-3-den@valinux.co.jp
+Signed-off-by: Vinod Koul <vkoul@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/dma/dw-edma/dw-edma-pcie.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/dma/dw-edma/dw-edma-pcie.c
++++ b/drivers/dma/dw-edma/dw-edma-pcie.c
+@@ -165,6 +165,9 @@ static int dw_edma_pcie_probe(struct pci
+ int err, nr_irqs;
+ int i, mask;
+
++ if (!pdata)
++ return -ENODEV;
++
+ struct dw_edma_pcie_data *vsec_data __free(kfree) =
+ kmalloc(sizeof(*vsec_data), GFP_KERNEL);
+ if (!vsec_data)
--- /dev/null
+From stable+bounces-287902-greg=kroah.com@vger.kernel.org Wed Jul 22 16:52:24 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 10:48:41 -0400
+Subject: fbcon: Rename struct fbcon_ops to struct fbcon_par
+To: stable@vger.kernel.org
+Cc: Thomas Zimmermann <tzimmermann@suse.de>, Sam Ravnborg <sam@ravnborg.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722144842.1601797-1-sashal@kernel.org>
+
+From: Thomas Zimmermann <tzimmermann@suse.de>
+
+[ Upstream commit a6adbbc4c32a016146e117b1e9e5242724a75e10 ]
+
+The type struct fbcon_ops contains fbcon state and callbacks. As the
+callbacks will be removed from struct fbcon_ops, rename the data type
+to struct fbcon_par. Also rename the variables from ops to par.
+
+The _par postfix ("private access registers") is used throughout the
+fbdev subsystem for per-driver state. The fbcon pointer within struct
+fb_info is also named fbcon_par. Hence, the new naming fits existing
+practice.
+
+v2:
+- rename struct fbcon_ops to struct fbcon_par
+- fix build for CONFIG_FB_TILEBITTING=n (kernel test robot)
+- fix indention
+
+Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
+Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
+Link: https://lore.kernel.org/r/20250909124616.143365-3-tzimmermann@suse.de
+Stable-dep-of: 84202754fb17 ("fbcon: Use correct type for vc_resize() return value")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/video/fbdev/core/bitblit.c | 120 ++++-----
+ drivers/video/fbdev/core/fbcon.c | 417 +++++++++++++++-----------------
+ drivers/video/fbdev/core/fbcon.h | 6
+ drivers/video/fbdev/core/fbcon_ccw.c | 146 +++++------
+ drivers/video/fbdev/core/fbcon_cw.c | 146 +++++------
+ drivers/video/fbdev/core/fbcon_rotate.c | 44 +--
+ drivers/video/fbdev/core/fbcon_rotate.h | 6
+ drivers/video/fbdev/core/fbcon_ud.c | 162 ++++++------
+ drivers/video/fbdev/core/softcursor.c | 20 -
+ drivers/video/fbdev/core/tileblit.c | 28 +-
+ 10 files changed, 543 insertions(+), 552 deletions(-)
+
+--- a/drivers/video/fbdev/core/bitblit.c
++++ b/drivers/video/fbdev/core/bitblit.c
+@@ -261,10 +261,10 @@ static void bit_cursor(struct vc_data *v
+ int fg, int bg)
+ {
+ struct fb_cursor cursor;
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+ int w = DIV_ROUND_UP(vc->vc_font.width, 8), c;
+- int y = real_y(ops->p, vc->state.y);
++ int y = real_y(par->p, vc->state.y);
+ int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
+ int err = 1;
+ char *src;
+@@ -278,10 +278,10 @@ static void bit_cursor(struct vc_data *v
+ attribute = get_attribute(info, c);
+ src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height));
+
+- if (ops->cursor_state.image.data != src ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.data = src;
+- cursor.set |= FB_CUR_SETIMAGE;
++ if (par->cursor_state.image.data != src ||
++ par->cursor_reset) {
++ par->cursor_state.image.data = src;
++ cursor.set |= FB_CUR_SETIMAGE;
+ }
+
+ if (attribute) {
+@@ -290,46 +290,46 @@ static void bit_cursor(struct vc_data *v
+ dst = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC);
+ if (!dst)
+ return;
+- kfree(ops->cursor_data);
+- ops->cursor_data = dst;
++ kfree(par->cursor_data);
++ par->cursor_data = dst;
+ update_attr(dst, src, attribute, vc);
+ src = dst;
+ }
+
+- if (ops->cursor_state.image.fg_color != fg ||
+- ops->cursor_state.image.bg_color != bg ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.fg_color = fg;
+- ops->cursor_state.image.bg_color = bg;
++ if (par->cursor_state.image.fg_color != fg ||
++ par->cursor_state.image.bg_color != bg ||
++ par->cursor_reset) {
++ par->cursor_state.image.fg_color = fg;
++ par->cursor_state.image.bg_color = bg;
+ cursor.set |= FB_CUR_SETCMAP;
+ }
+
+- if ((ops->cursor_state.image.dx != (vc->vc_font.width * vc->state.x)) ||
+- (ops->cursor_state.image.dy != (vc->vc_font.height * y)) ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.dx = vc->vc_font.width * vc->state.x;
+- ops->cursor_state.image.dy = vc->vc_font.height * y;
++ if ((par->cursor_state.image.dx != (vc->vc_font.width * vc->state.x)) ||
++ (par->cursor_state.image.dy != (vc->vc_font.height * y)) ||
++ par->cursor_reset) {
++ par->cursor_state.image.dx = vc->vc_font.width * vc->state.x;
++ par->cursor_state.image.dy = vc->vc_font.height * y;
+ cursor.set |= FB_CUR_SETPOS;
+ }
+
+- if (ops->cursor_state.image.height != vc->vc_font.height ||
+- ops->cursor_state.image.width != vc->vc_font.width ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.height = vc->vc_font.height;
+- ops->cursor_state.image.width = vc->vc_font.width;
++ if (par->cursor_state.image.height != vc->vc_font.height ||
++ par->cursor_state.image.width != vc->vc_font.width ||
++ par->cursor_reset) {
++ par->cursor_state.image.height = vc->vc_font.height;
++ par->cursor_state.image.width = vc->vc_font.width;
+ cursor.set |= FB_CUR_SETSIZE;
+ }
+
+- if (ops->cursor_state.hot.x || ops->cursor_state.hot.y ||
+- ops->cursor_reset) {
+- ops->cursor_state.hot.x = cursor.hot.y = 0;
++ if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
++ par->cursor_reset) {
++ par->cursor_state.hot.x = cursor.hot.y = 0;
+ cursor.set |= FB_CUR_SETHOT;
+ }
+
+ if (cursor.set & FB_CUR_SETSIZE ||
+- vc->vc_cursor_type != ops->p->cursor_shape ||
+- ops->cursor_state.mask == NULL ||
+- ops->cursor_reset) {
++ vc->vc_cursor_type != par->p->cursor_shape ||
++ par->cursor_state.mask == NULL ||
++ par->cursor_reset) {
+ char *mask = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC);
+ int cur_height, size, i = 0;
+ u8 msk = 0xff;
+@@ -337,13 +337,13 @@ static void bit_cursor(struct vc_data *v
+ if (!mask)
+ return;
+
+- kfree(ops->cursor_state.mask);
+- ops->cursor_state.mask = mask;
++ kfree(par->cursor_state.mask);
++ par->cursor_state.mask = mask;
+
+- ops->p->cursor_shape = vc->vc_cursor_type;
++ par->p->cursor_shape = vc->vc_cursor_type;
+ cursor.set |= FB_CUR_SETSHAPE;
+
+- switch (CUR_SIZE(ops->p->cursor_shape)) {
++ switch (CUR_SIZE(par->p->cursor_shape)) {
+ case CUR_NONE:
+ cur_height = 0;
+ break;
+@@ -372,19 +372,19 @@ static void bit_cursor(struct vc_data *v
+ mask[i++] = msk;
+ }
+
+- ops->cursor_state.enable = enable && !use_sw;
++ par->cursor_state.enable = enable && !use_sw;
+
+ cursor.image.data = src;
+- cursor.image.fg_color = ops->cursor_state.image.fg_color;
+- cursor.image.bg_color = ops->cursor_state.image.bg_color;
+- cursor.image.dx = ops->cursor_state.image.dx;
+- cursor.image.dy = ops->cursor_state.image.dy;
+- cursor.image.height = ops->cursor_state.image.height;
+- cursor.image.width = ops->cursor_state.image.width;
+- cursor.hot.x = ops->cursor_state.hot.x;
+- cursor.hot.y = ops->cursor_state.hot.y;
+- cursor.mask = ops->cursor_state.mask;
+- cursor.enable = ops->cursor_state.enable;
++ cursor.image.fg_color = par->cursor_state.image.fg_color;
++ cursor.image.bg_color = par->cursor_state.image.bg_color;
++ cursor.image.dx = par->cursor_state.image.dx;
++ cursor.image.dy = par->cursor_state.image.dy;
++ cursor.image.height = par->cursor_state.image.height;
++ cursor.image.width = par->cursor_state.image.width;
++ cursor.hot.x = par->cursor_state.hot.x;
++ cursor.hot.y = par->cursor_state.hot.y;
++ cursor.mask = par->cursor_state.mask;
++ cursor.enable = par->cursor_state.enable;
+ cursor.image.depth = 1;
+ cursor.rop = ROP_XOR;
+
+@@ -394,31 +394,31 @@ static void bit_cursor(struct vc_data *v
+ if (err)
+ soft_cursor(info, &cursor);
+
+- ops->cursor_reset = 0;
++ par->cursor_reset = 0;
+ }
+
+ static int bit_update_start(struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ int err;
+
+- err = fb_pan_display(info, &ops->var);
+- ops->var.xoffset = info->var.xoffset;
+- ops->var.yoffset = info->var.yoffset;
+- ops->var.vmode = info->var.vmode;
++ err = fb_pan_display(info, &par->var);
++ par->var.xoffset = info->var.xoffset;
++ par->var.yoffset = info->var.yoffset;
++ par->var.vmode = info->var.vmode;
+ return err;
+ }
+
+-void fbcon_set_bitops(struct fbcon_ops *ops)
++void fbcon_set_bitops(struct fbcon_par *par)
+ {
+- ops->bmove = bit_bmove;
+- ops->clear = bit_clear;
+- ops->putcs = bit_putcs;
+- ops->clear_margins = bit_clear_margins;
+- ops->cursor = bit_cursor;
+- ops->update_start = bit_update_start;
+- ops->rotate_font = NULL;
++ par->bmove = bit_bmove;
++ par->clear = bit_clear;
++ par->putcs = bit_putcs;
++ par->clear_margins = bit_clear_margins;
++ par->cursor = bit_cursor;
++ par->update_start = bit_update_start;
++ par->rotate_font = NULL;
+
+- if (ops->rotate)
+- fbcon_set_rotate(ops);
++ if (par->rotate)
++ fbcon_set_rotate(par);
+ }
+--- a/drivers/video/fbdev/core/fbcon.c
++++ b/drivers/video/fbdev/core/fbcon.c
+@@ -200,27 +200,27 @@ static struct device *fbcon_device;
+ #ifdef CONFIG_FRAMEBUFFER_CONSOLE_ROTATION
+ static inline void fbcon_set_rotation(struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+ if (!(info->flags & FBINFO_MISC_TILEBLITTING) &&
+- ops->p->con_rotate < 4)
+- ops->rotate = ops->p->con_rotate;
++ par->p->con_rotate < 4)
++ par->rotate = par->p->con_rotate;
+ else
+- ops->rotate = 0;
++ par->rotate = 0;
+ }
+
+ static void fbcon_rotate(struct fb_info *info, u32 rotate)
+ {
+- struct fbcon_ops *ops= info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fb_info *fb_info;
+
+- if (!ops || ops->currcon == -1)
++ if (!par || par->currcon == -1)
+ return;
+
+- fb_info = fbcon_info_from_console(ops->currcon);
++ fb_info = fbcon_info_from_console(par->currcon);
+
+ if (info == fb_info) {
+- struct fbcon_display *p = &fb_display[ops->currcon];
++ struct fbcon_display *p = &fb_display[par->currcon];
+
+ if (rotate < 4)
+ p->con_rotate = rotate;
+@@ -233,12 +233,12 @@ static void fbcon_rotate(struct fb_info
+
+ static void fbcon_rotate_all(struct fb_info *info, u32 rotate)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct vc_data *vc;
+ struct fbcon_display *p;
+ int i;
+
+- if (!ops || ops->currcon < 0 || rotate > 3)
++ if (!par || par->currcon < 0 || rotate > 3)
+ return;
+
+ for (i = first_fb_vc; i <= last_fb_vc; i++) {
+@@ -256,9 +256,9 @@ static void fbcon_rotate_all(struct fb_i
+ #else
+ static inline void fbcon_set_rotation(struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+- ops->rotate = FB_ROTATE_UR;
++ par->rotate = FB_ROTATE_UR;
+ }
+
+ static void fbcon_rotate(struct fb_info *info, u32 rotate)
+@@ -274,9 +274,9 @@ static void fbcon_rotate_all(struct fb_i
+
+ static int fbcon_get_rotate(struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+- return (ops) ? ops->rotate : 0;
++ return (par) ? par->rotate : 0;
+ }
+
+ static bool fbcon_skip_panic(struct fb_info *info)
+@@ -293,10 +293,10 @@ static bool fbcon_skip_panic(struct fb_i
+
+ static inline int fbcon_is_inactive(struct vc_data *vc, struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+ return (info->state != FBINFO_STATE_RUNNING ||
+- vc->vc_mode != KD_TEXT || ops->graphics || fbcon_skip_panic(info));
++ vc->vc_mode != KD_TEXT || par->graphics || fbcon_skip_panic(info));
+ }
+
+ static int get_color(struct vc_data *vc, struct fb_info *info,
+@@ -368,7 +368,7 @@ static int get_color(struct vc_data *vc,
+
+ static void fb_flashcursor(struct work_struct *work)
+ {
+- struct fbcon_ops *ops = container_of(work, struct fbcon_ops, cursor_work.work);
++ struct fbcon_par *par = container_of(work, struct fbcon_par, cursor_work.work);
+ struct fb_info *info;
+ struct vc_data *vc = NULL;
+ int c;
+@@ -383,10 +383,10 @@ static void fb_flashcursor(struct work_s
+ return;
+
+ /* protected by console_lock */
+- info = ops->info;
++ info = par->info;
+
+- if (ops->currcon != -1)
+- vc = vc_cons[ops->currcon].d;
++ if (par->currcon != -1)
++ vc = vc_cons[par->currcon].d;
+
+ if (!vc || !con_is_visible(vc) ||
+ fbcon_info_from_console(vc->vc_num) != info ||
+@@ -396,29 +396,29 @@ static void fb_flashcursor(struct work_s
+ }
+
+ c = scr_readw((u16 *) vc->vc_pos);
+- enable = ops->cursor_flash && !ops->cursor_state.enable;
+- ops->cursor(vc, info, enable, get_color(vc, info, c, 1),
++ enable = par->cursor_flash && !par->cursor_state.enable;
++ par->cursor(vc, info, enable, get_color(vc, info, c, 1),
+ get_color(vc, info, c, 0));
+ console_unlock();
+
+- queue_delayed_work(system_power_efficient_wq, &ops->cursor_work,
+- ops->cur_blink_jiffies);
++ queue_delayed_work(system_power_efficient_wq, &par->cursor_work,
++ par->cur_blink_jiffies);
+ }
+
+ static void fbcon_add_cursor_work(struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+ if (!fbcon_cursor_noblink)
+- queue_delayed_work(system_power_efficient_wq, &ops->cursor_work,
+- ops->cur_blink_jiffies);
++ queue_delayed_work(system_power_efficient_wq, &par->cursor_work,
++ par->cur_blink_jiffies);
+ }
+
+ static void fbcon_del_cursor_work(struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+- cancel_delayed_work_sync(&ops->cursor_work);
++ cancel_delayed_work_sync(&par->cursor_work);
+ }
+
+ #ifndef MODULE
+@@ -578,7 +578,7 @@ static void fbcon_prepare_logo(struct vc
+ int cols, int rows, int new_cols, int new_rows)
+ {
+ /* Need to make room for the logo */
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ int cnt, erase = vc->vc_video_erase_char, step;
+ unsigned short *save = NULL, *r, *q;
+ int logo_height;
+@@ -594,7 +594,7 @@ static void fbcon_prepare_logo(struct vc
+ */
+ if (fb_get_color_depth(&info->var, &info->fix) == 1)
+ erase &= ~0x400;
+- logo_height = fb_prepare_logo(info, ops->rotate);
++ logo_height = fb_prepare_logo(info, par->rotate);
+ logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height);
+ q = (unsigned short *) (vc->vc_origin +
+ vc->vc_size_row * rows);
+@@ -666,15 +666,15 @@ static void fbcon_prepare_logo(struct vc
+ #ifdef CONFIG_FB_TILEBLITTING
+ static void set_blitting_type(struct vc_data *vc, struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+- ops->p = &fb_display[vc->vc_num];
++ par->p = &fb_display[vc->vc_num];
+
+ if ((info->flags & FBINFO_MISC_TILEBLITTING))
+ fbcon_set_tileops(vc, info);
+ else {
+ fbcon_set_rotation(info);
+- fbcon_set_bitops(ops);
++ fbcon_set_bitops(par);
+ }
+ }
+
+@@ -691,12 +691,12 @@ static int fbcon_invalid_charcount(struc
+ #else
+ static void set_blitting_type(struct vc_data *vc, struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+ info->flags &= ~FBINFO_MISC_TILEBLITTING;
+- ops->p = &fb_display[vc->vc_num];
++ par->p = &fb_display[vc->vc_num];
+ fbcon_set_rotation(info);
+- fbcon_set_bitops(ops);
++ fbcon_set_bitops(par);
+ }
+
+ static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount)
+@@ -716,13 +716,13 @@ static void fbcon_release(struct fb_info
+ module_put(info->fbops->owner);
+
+ if (info->fbcon_par) {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+ fbcon_del_cursor_work(info);
+- kfree(ops->cursor_state.mask);
+- kfree(ops->cursor_data);
+- kfree(ops->cursor_src);
+- kfree(ops->fontbuffer);
++ kfree(par->cursor_state.mask);
++ kfree(par->cursor_data);
++ kfree(par->cursor_src);
++ kfree(par->fontbuffer);
+ kfree(info->fbcon_par);
+ info->fbcon_par = NULL;
+ }
+@@ -730,7 +730,7 @@ static void fbcon_release(struct fb_info
+
+ static int fbcon_open(struct fb_info *info)
+ {
+- struct fbcon_ops *ops;
++ struct fbcon_par *par;
+
+ if (!try_module_get(info->fbops->owner))
+ return -ENODEV;
+@@ -744,16 +744,16 @@ static int fbcon_open(struct fb_info *in
+ }
+ unlock_fb_info(info);
+
+- ops = kzalloc(sizeof(struct fbcon_ops), GFP_KERNEL);
+- if (!ops) {
++ par = kzalloc(sizeof(*par), GFP_KERNEL);
++ if (!par) {
+ fbcon_release(info);
+ return -ENOMEM;
+ }
+
+- INIT_DELAYED_WORK(&ops->cursor_work, fb_flashcursor);
+- ops->info = info;
+- info->fbcon_par = ops;
+- ops->cur_blink_jiffies = HZ / 5;
++ INIT_DELAYED_WORK(&par->cursor_work, fb_flashcursor);
++ par->info = info;
++ info->fbcon_par = par;
++ par->cur_blink_jiffies = HZ / 5;
+
+ return 0;
+ }
+@@ -800,12 +800,12 @@ static void con2fb_release_oldinfo(struc
+ static void con2fb_init_display(struct vc_data *vc, struct fb_info *info,
+ int unit, int show_logo)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ int ret;
+
+- ops->currcon = fg_console;
++ par->currcon = fg_console;
+
+- if (info->fbops->fb_set_par && !ops->initialized) {
++ if (info->fbops->fb_set_par && !par->initialized) {
+ ret = info->fbops->fb_set_par(info);
+
+ if (ret)
+@@ -814,8 +814,8 @@ static void con2fb_init_display(struct v
+ "error code %d\n", ret);
+ }
+
+- ops->initialized = true;
+- ops->graphics = 0;
++ par->initialized = true;
++ par->graphics = 0;
+ fbcon_set_disp(info, &info->var, unit);
+
+ if (show_logo) {
+@@ -952,7 +952,7 @@ static const char *fbcon_startup(void)
+ struct vc_data *vc = vc_cons[fg_console].d;
+ const struct font_desc *font = NULL;
+ struct fb_info *info = NULL;
+- struct fbcon_ops *ops;
++ struct fbcon_par *par;
+ int rows, cols;
+
+ /*
+@@ -972,10 +972,10 @@ static const char *fbcon_startup(void)
+ if (fbcon_open(info))
+ return NULL;
+
+- ops = info->fbcon_par;
+- ops->currcon = -1;
+- ops->graphics = 1;
+- ops->cur_rotate = -1;
++ par = info->fbcon_par;
++ par->currcon = -1;
++ par->graphics = 1;
++ par->cur_rotate = -1;
+
+ p->con_rotate = initial_rotation;
+ if (p->con_rotate == -1)
+@@ -998,8 +998,8 @@ static const char *fbcon_startup(void)
+ vc->vc_font.charcount = font->charcount;
+ }
+
+- cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
+- rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
++ cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
++ rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
+ cols /= vc->vc_font.width;
+ rows /= vc->vc_font.height;
+ vc_resize(vc, cols, rows);
+@@ -1017,7 +1017,7 @@ static const char *fbcon_startup(void)
+ static void fbcon_init(struct vc_data *vc, bool init)
+ {
+ struct fb_info *info;
+- struct fbcon_ops *ops;
++ struct fbcon_par *par;
+ struct vc_data **default_mode = vc->vc_display_fg;
+ struct vc_data *svc = *default_mode;
+ struct fbcon_display *t, *p = &fb_display[vc->vc_num];
+@@ -1092,8 +1092,8 @@ static void fbcon_init(struct vc_data *v
+ if (!*vc->uni_pagedict_loc)
+ con_copy_unimap(vc, svc);
+
+- ops = info->fbcon_par;
+- ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
++ par = info->fbcon_par;
++ par->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
+
+ p->con_rotate = initial_rotation;
+ if (p->con_rotate == -1)
+@@ -1105,8 +1105,8 @@ static void fbcon_init(struct vc_data *v
+
+ cols = vc->vc_cols;
+ rows = vc->vc_rows;
+- new_cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
+- new_rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
++ new_cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
++ new_rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
+ new_cols /= vc->vc_font.width;
+ new_rows /= vc->vc_font.height;
+
+@@ -1118,7 +1118,7 @@ static void fbcon_init(struct vc_data *v
+ * We need to do it in fbcon_init() to prevent screen corruption.
+ */
+ if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) {
+- if (info->fbops->fb_set_par && !ops->initialized) {
++ if (info->fbops->fb_set_par && !par->initialized) {
+ ret = info->fbops->fb_set_par(info);
+
+ if (ret)
+@@ -1127,10 +1127,10 @@ static void fbcon_init(struct vc_data *v
+ "error code %d\n", ret);
+ }
+
+- ops->initialized = true;
++ par->initialized = true;
+ }
+
+- ops->graphics = 0;
++ par->graphics = 0;
+
+ #ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
+ if ((info->flags & FBINFO_HWACCEL_COPYAREA) &&
+@@ -1154,12 +1154,12 @@ static void fbcon_init(struct vc_data *v
+ if (logo)
+ fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows);
+
+- if (ops->rotate_font && ops->rotate_font(info, vc)) {
+- ops->rotate = FB_ROTATE_UR;
++ if (par->rotate_font && par->rotate_font(info, vc)) {
++ par->rotate = FB_ROTATE_UR;
+ set_blitting_type(vc, info);
+ }
+
+- ops->p = &fb_display[fg_console];
++ par->p = &fb_display[fg_console];
+ }
+
+ static void fbcon_free_font(struct fbcon_display *p)
+@@ -1197,7 +1197,7 @@ static void fbcon_deinit(struct vc_data
+ {
+ struct fbcon_display *p = &fb_display[vc->vc_num];
+ struct fb_info *info;
+- struct fbcon_ops *ops;
++ struct fbcon_par *par;
+ int idx;
+
+ fbcon_free_font(p);
+@@ -1212,15 +1212,15 @@ static void fbcon_deinit(struct vc_data
+ if (!info)
+ goto finished;
+
+- ops = info->fbcon_par;
++ par = info->fbcon_par;
+
+- if (!ops)
++ if (!par)
+ goto finished;
+
+ if (con_is_visible(vc))
+ fbcon_del_cursor_work(info);
+
+- ops->initialized = false;
++ par->initialized = false;
+ finished:
+
+ fbcon_free_font(p);
+@@ -1267,7 +1267,7 @@ static void __fbcon_clear(struct vc_data
+ unsigned int height, unsigned int width)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ int fg, bg;
+ struct fbcon_display *p = &fb_display[vc->vc_num];
+ u_int y_break;
+@@ -1282,7 +1282,7 @@ static void __fbcon_clear(struct vc_data
+ vc->vc_top = 0;
+ /*
+ * If the font dimensions are not an integral of the display
+- * dimensions then the ops->clear below won't end up clearing
++ * dimensions then the par->clear below won't end up clearing
+ * the margins. Call clear_margins here in case the logo
+ * bitmap stretched into the margin area.
+ */
+@@ -1296,11 +1296,10 @@ static void __fbcon_clear(struct vc_data
+ y_break = p->vrows - p->yscroll;
+ if (sy < y_break && sy + height - 1 >= y_break) {
+ u_int b = y_break - sy;
+- ops->clear(vc, info, real_y(p, sy), sx, b, width, fg, bg);
+- ops->clear(vc, info, real_y(p, sy + b), sx, height - b,
+- width, fg, bg);
++ par->clear(vc, info, real_y(p, sy), sx, b, width, fg, bg);
++ par->clear(vc, info, real_y(p, sy + b), sx, height - b, width, fg, bg);
+ } else
+- ops->clear(vc, info, real_y(p, sy), sx, height, width, fg, bg);
++ par->clear(vc, info, real_y(p, sy), sx, height, width, fg, bg);
+ }
+
+ static void fbcon_clear(struct vc_data *vc, unsigned int sy, unsigned int sx,
+@@ -1314,10 +1313,10 @@ static void fbcon_putcs(struct vc_data *
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+ struct fbcon_display *p = &fb_display[vc->vc_num];
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+ if (!fbcon_is_inactive(vc, info))
+- ops->putcs(vc, info, s, count, real_y(p, ypos), xpos,
++ par->putcs(vc, info, s, count, real_y(p, ypos), xpos,
+ get_color(vc, info, scr_readw(s), 1),
+ get_color(vc, info, scr_readw(s), 0));
+ }
+@@ -1325,19 +1324,19 @@ static void fbcon_putcs(struct vc_data *
+ static void fbcon_clear_margins(struct vc_data *vc, int bottom_only)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+ if (!fbcon_is_inactive(vc, info))
+- ops->clear_margins(vc, info, margin_color, bottom_only);
++ par->clear_margins(vc, info, margin_color, bottom_only);
+ }
+
+ static void fbcon_cursor(struct vc_data *vc, bool enable)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ int c = scr_readw((u16 *) vc->vc_pos);
+
+- ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
++ par->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
+
+ if (fbcon_is_inactive(vc, info) || vc->vc_deccm != 1)
+ return;
+@@ -1347,12 +1346,12 @@ static void fbcon_cursor(struct vc_data
+ else
+ fbcon_add_cursor_work(info);
+
+- ops->cursor_flash = enable;
++ par->cursor_flash = enable;
+
+- if (!ops->cursor)
++ if (!par->cursor)
+ return;
+
+- ops->cursor(vc, info, enable, get_color(vc, info, c, 1),
++ par->cursor(vc, info, enable, get_color(vc, info, c, 1),
+ get_color(vc, info, c, 0));
+ }
+
+@@ -1366,7 +1365,7 @@ static void fbcon_set_disp(struct fb_inf
+ struct fbcon_display *p, *t;
+ struct vc_data **default_mode, *vc;
+ struct vc_data *svc;
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ int rows, cols;
+ unsigned long ret = 0;
+
+@@ -1399,7 +1398,7 @@ static void fbcon_set_disp(struct fb_inf
+ var->yoffset = info->var.yoffset;
+ var->xoffset = info->var.xoffset;
+ fb_set_var(info, var);
+- ops->var = info->var;
++ par->var = info->var;
+ vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1);
+ vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
+ if (vc->vc_font.charcount == 256) {
+@@ -1415,8 +1414,8 @@ static void fbcon_set_disp(struct fb_inf
+ if (!*vc->uni_pagedict_loc)
+ con_copy_unimap(vc, svc);
+
+- cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
+- rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
++ cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
++ rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
+ cols /= vc->vc_font.width;
+ rows /= vc->vc_font.height;
+ ret = vc_resize(vc, cols, rows);
+@@ -1428,16 +1427,16 @@ static void fbcon_set_disp(struct fb_inf
+ static __inline__ void ywrap_up(struct vc_data *vc, int count)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fbcon_display *p = &fb_display[vc->vc_num];
+
+ p->yscroll += count;
+ if (p->yscroll >= p->vrows) /* Deal with wrap */
+ p->yscroll -= p->vrows;
+- ops->var.xoffset = 0;
+- ops->var.yoffset = p->yscroll * vc->vc_font.height;
+- ops->var.vmode |= FB_VMODE_YWRAP;
+- ops->update_start(info);
++ par->var.xoffset = 0;
++ par->var.yoffset = p->yscroll * vc->vc_font.height;
++ par->var.vmode |= FB_VMODE_YWRAP;
++ par->update_start(info);
+ scrollback_max += count;
+ if (scrollback_max > scrollback_phys_max)
+ scrollback_max = scrollback_phys_max;
+@@ -1447,16 +1446,16 @@ static __inline__ void ywrap_up(struct v
+ static __inline__ void ywrap_down(struct vc_data *vc, int count)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fbcon_display *p = &fb_display[vc->vc_num];
+
+ p->yscroll -= count;
+ if (p->yscroll < 0) /* Deal with wrap */
+ p->yscroll += p->vrows;
+- ops->var.xoffset = 0;
+- ops->var.yoffset = p->yscroll * vc->vc_font.height;
+- ops->var.vmode |= FB_VMODE_YWRAP;
+- ops->update_start(info);
++ par->var.xoffset = 0;
++ par->var.yoffset = p->yscroll * vc->vc_font.height;
++ par->var.vmode |= FB_VMODE_YWRAP;
++ par->update_start(info);
+ scrollback_max -= count;
+ if (scrollback_max < 0)
+ scrollback_max = 0;
+@@ -1467,19 +1466,19 @@ static __inline__ void ypan_up(struct vc
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+ struct fbcon_display *p = &fb_display[vc->vc_num];
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+ p->yscroll += count;
+ if (p->yscroll > p->vrows - vc->vc_rows) {
+- ops->bmove(vc, info, p->vrows - vc->vc_rows,
++ par->bmove(vc, info, p->vrows - vc->vc_rows,
+ 0, 0, 0, vc->vc_rows, vc->vc_cols);
+ p->yscroll -= p->vrows - vc->vc_rows;
+ }
+
+- ops->var.xoffset = 0;
+- ops->var.yoffset = p->yscroll * vc->vc_font.height;
+- ops->var.vmode &= ~FB_VMODE_YWRAP;
+- ops->update_start(info);
++ par->var.xoffset = 0;
++ par->var.yoffset = p->yscroll * vc->vc_font.height;
++ par->var.vmode &= ~FB_VMODE_YWRAP;
++ par->update_start(info);
+ fbcon_clear_margins(vc, 1);
+ scrollback_max += count;
+ if (scrollback_max > scrollback_phys_max)
+@@ -1490,7 +1489,7 @@ static __inline__ void ypan_up(struct vc
+ static __inline__ void ypan_up_redraw(struct vc_data *vc, int t, int count)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fbcon_display *p = &fb_display[vc->vc_num];
+
+ p->yscroll += count;
+@@ -1500,10 +1499,10 @@ static __inline__ void ypan_up_redraw(st
+ fbcon_redraw_move(vc, p, t + count, vc->vc_rows - count, t);
+ }
+
+- ops->var.xoffset = 0;
+- ops->var.yoffset = p->yscroll * vc->vc_font.height;
+- ops->var.vmode &= ~FB_VMODE_YWRAP;
+- ops->update_start(info);
++ par->var.xoffset = 0;
++ par->var.yoffset = p->yscroll * vc->vc_font.height;
++ par->var.vmode &= ~FB_VMODE_YWRAP;
++ par->update_start(info);
+ fbcon_clear_margins(vc, 1);
+ scrollback_max += count;
+ if (scrollback_max > scrollback_phys_max)
+@@ -1515,19 +1514,19 @@ static __inline__ void ypan_down(struct
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+ struct fbcon_display *p = &fb_display[vc->vc_num];
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+ p->yscroll -= count;
+ if (p->yscroll < 0) {
+- ops->bmove(vc, info, 0, 0, p->vrows - vc->vc_rows,
++ par->bmove(vc, info, 0, 0, p->vrows - vc->vc_rows,
+ 0, vc->vc_rows, vc->vc_cols);
+ p->yscroll += p->vrows - vc->vc_rows;
+ }
+
+- ops->var.xoffset = 0;
+- ops->var.yoffset = p->yscroll * vc->vc_font.height;
+- ops->var.vmode &= ~FB_VMODE_YWRAP;
+- ops->update_start(info);
++ par->var.xoffset = 0;
++ par->var.yoffset = p->yscroll * vc->vc_font.height;
++ par->var.vmode &= ~FB_VMODE_YWRAP;
++ par->update_start(info);
+ fbcon_clear_margins(vc, 1);
+ scrollback_max -= count;
+ if (scrollback_max < 0)
+@@ -1538,7 +1537,7 @@ static __inline__ void ypan_down(struct
+ static __inline__ void ypan_down_redraw(struct vc_data *vc, int t, int count)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fbcon_display *p = &fb_display[vc->vc_num];
+
+ p->yscroll -= count;
+@@ -1548,10 +1547,10 @@ static __inline__ void ypan_down_redraw(
+ fbcon_redraw_move(vc, p, t, vc->vc_rows - count, t + count);
+ }
+
+- ops->var.xoffset = 0;
+- ops->var.yoffset = p->yscroll * vc->vc_font.height;
+- ops->var.vmode &= ~FB_VMODE_YWRAP;
+- ops->update_start(info);
++ par->var.xoffset = 0;
++ par->var.yoffset = p->yscroll * vc->vc_font.height;
++ par->var.vmode &= ~FB_VMODE_YWRAP;
++ par->update_start(info);
+ fbcon_clear_margins(vc, 1);
+ scrollback_max -= count;
+ if (scrollback_max < 0)
+@@ -1600,7 +1599,7 @@ static void fbcon_redraw_blit(struct vc_
+ unsigned short *d = (unsigned short *)
+ (vc->vc_origin + vc->vc_size_row * line);
+ unsigned short *s = d + offset;
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+ while (count--) {
+ unsigned short *start = s;
+@@ -1613,8 +1612,8 @@ static void fbcon_redraw_blit(struct vc_
+
+ if (c == scr_readw(d)) {
+ if (s > start) {
+- ops->bmove(vc, info, line + ycount, x,
+- line, x, 1, s-start);
++ par->bmove(vc, info, line + ycount, x,
++ line, x, 1, s - start);
+ x += s - start + 1;
+ start = s + 1;
+ } else {
+@@ -1629,8 +1628,7 @@ static void fbcon_redraw_blit(struct vc_
+ d++;
+ } while (s < le);
+ if (s > start)
+- ops->bmove(vc, info, line + ycount, x, line, x, 1,
+- s-start);
++ par->bmove(vc, info, line + ycount, x, line, x, 1, s - start);
+ console_conditional_schedule();
+ if (ycount > 0)
+ line++;
+@@ -1701,7 +1699,7 @@ static void fbcon_bmove_rec(struct vc_da
+ int dy, int dx, int height, int width, u_int y_break)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ u_int b;
+
+ if (sy < y_break && sy + height > y_break) {
+@@ -1735,8 +1733,7 @@ static void fbcon_bmove_rec(struct vc_da
+ }
+ return;
+ }
+- ops->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx,
+- height, width);
++ par->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx, height, width);
+ }
+
+ static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx,
+@@ -1963,15 +1960,13 @@ static void updatescrollmode_accel(struc
+ struct vc_data *vc)
+ {
+ #ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ int cap = info->flags;
+ u16 t = 0;
+- int ypan = FBCON_SWAP(ops->rotate, info->fix.ypanstep,
+- info->fix.xpanstep);
+- int ywrap = FBCON_SWAP(ops->rotate, info->fix.ywrapstep, t);
+- int yres = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
+- int vyres = FBCON_SWAP(ops->rotate, info->var.yres_virtual,
+- info->var.xres_virtual);
++ int ypan = FBCON_SWAP(par->rotate, info->fix.ypanstep, info->fix.xpanstep);
++ int ywrap = FBCON_SWAP(par->rotate, info->fix.ywrapstep, t);
++ int yres = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
++ int vyres = FBCON_SWAP(par->rotate, info->var.yres_virtual, info->var.xres_virtual);
+ int good_pan = (cap & FBINFO_HWACCEL_YPAN) &&
+ divides(ypan, vc->vc_font.height) && vyres > yres;
+ int good_wrap = (cap & FBINFO_HWACCEL_YWRAP) &&
+@@ -2004,11 +1999,10 @@ static void updatescrollmode(struct fbco
+ struct fb_info *info,
+ struct vc_data *vc)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ int fh = vc->vc_font.height;
+- int yres = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
+- int vyres = FBCON_SWAP(ops->rotate, info->var.yres_virtual,
+- info->var.xres_virtual);
++ int yres = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
++ int vyres = FBCON_SWAP(par->rotate, info->var.yres_virtual, info->var.xres_virtual);
+
+ p->vrows = vyres/fh;
+ if (yres > (fh * (vc->vc_rows + 1)))
+@@ -2027,7 +2021,7 @@ static int fbcon_resize(struct vc_data *
+ unsigned int height, bool from_user)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fbcon_display *p = &fb_display[vc->vc_num];
+ struct fb_var_screeninfo var = info->var;
+ int x_diff, y_diff, virt_w, virt_h, virt_fw, virt_fh;
+@@ -2050,12 +2044,10 @@ static int fbcon_resize(struct vc_data *
+ return -EINVAL;
+ }
+
+- virt_w = FBCON_SWAP(ops->rotate, width, height);
+- virt_h = FBCON_SWAP(ops->rotate, height, width);
+- virt_fw = FBCON_SWAP(ops->rotate, vc->vc_font.width,
+- vc->vc_font.height);
+- virt_fh = FBCON_SWAP(ops->rotate, vc->vc_font.height,
+- vc->vc_font.width);
++ virt_w = FBCON_SWAP(par->rotate, width, height);
++ virt_h = FBCON_SWAP(par->rotate, height, width);
++ virt_fw = FBCON_SWAP(par->rotate, vc->vc_font.width, vc->vc_font.height);
++ virt_fh = FBCON_SWAP(par->rotate, vc->vc_font.height, vc->vc_font.width);
+ var.xres = virt_w * virt_fw;
+ var.yres = virt_h * virt_fh;
+ x_diff = info->var.xres - var.xres;
+@@ -2081,7 +2073,7 @@ static int fbcon_resize(struct vc_data *
+ fb_set_var(info, &var);
+ }
+ var_to_display(p, &info->var, info);
+- ops->var = info->var;
++ par->var = info->var;
+ }
+ updatescrollmode(p, info, vc);
+ return 0;
+@@ -2090,13 +2082,13 @@ static int fbcon_resize(struct vc_data *
+ static bool fbcon_switch(struct vc_data *vc)
+ {
+ struct fb_info *info, *old_info = NULL;
+- struct fbcon_ops *ops;
++ struct fbcon_par *par;
+ struct fbcon_display *p = &fb_display[vc->vc_num];
+ struct fb_var_screeninfo var;
+ int i, ret, prev_console;
+
+ info = fbcon_info_from_console(vc->vc_num);
+- ops = info->fbcon_par;
++ par = info->fbcon_par;
+
+ if (logo_shown >= 0) {
+ struct vc_data *conp2 = vc_cons[logo_shown].d;
+@@ -2107,7 +2099,7 @@ static bool fbcon_switch(struct vc_data
+ logo_shown = FBCON_LOGO_CANSHOW;
+ }
+
+- prev_console = ops->currcon;
++ prev_console = par->currcon;
+ if (prev_console != -1)
+ old_info = fbcon_info_from_console(prev_console);
+ /*
+@@ -2120,9 +2112,9 @@ static bool fbcon_switch(struct vc_data
+ */
+ fbcon_for_each_registered_fb(i) {
+ if (fbcon_registered_fb[i]->fbcon_par) {
+- struct fbcon_ops *o = fbcon_registered_fb[i]->fbcon_par;
++ struct fbcon_par *par = fbcon_registered_fb[i]->fbcon_par;
+
+- o->currcon = vc->vc_num;
++ par->currcon = vc->vc_num;
+ }
+ }
+ memset(&var, 0, sizeof(struct fb_var_screeninfo));
+@@ -2136,7 +2128,7 @@ static bool fbcon_switch(struct vc_data
+ info->var.activate = var.activate;
+ var.vmode |= info->var.vmode & ~FB_VMODE_MASK;
+ fb_set_var(info, &var);
+- ops->var = info->var;
++ par->var = info->var;
+
+ if (old_info != NULL && (old_info != info ||
+ info->flags & FBINFO_MISC_ALWAYS_SETPAR)) {
+@@ -2154,16 +2146,16 @@ static bool fbcon_switch(struct vc_data
+ }
+
+ if (fbcon_is_inactive(vc, info) ||
+- ops->blank_state != FB_BLANK_UNBLANK)
++ par->blank_state != FB_BLANK_UNBLANK)
+ fbcon_del_cursor_work(info);
+ else
+ fbcon_add_cursor_work(info);
+
+ set_blitting_type(vc, info);
+- ops->cursor_reset = 1;
++ par->cursor_reset = 1;
+
+- if (ops->rotate_font && ops->rotate_font(info, vc)) {
+- ops->rotate = FB_ROTATE_UR;
++ if (par->rotate_font && par->rotate_font(info, vc)) {
++ par->rotate = FB_ROTATE_UR;
+ set_blitting_type(vc, info);
+ }
+
+@@ -2194,8 +2186,8 @@ static bool fbcon_switch(struct vc_data
+ scrollback_current = 0;
+
+ if (!fbcon_is_inactive(vc, info)) {
+- ops->var.xoffset = ops->var.yoffset = p->yscroll = 0;
+- ops->update_start(info);
++ par->var.xoffset = par->var.yoffset = p->yscroll = 0;
++ par->update_start(info);
+ }
+
+ fbcon_set_palette(vc, color_table);
+@@ -2204,7 +2196,7 @@ static bool fbcon_switch(struct vc_data
+ if (logo_shown == FBCON_LOGO_DRAW) {
+
+ logo_shown = fg_console;
+- fb_show_logo(info, ops->rotate);
++ fb_show_logo(info, par->rotate);
+ update_region(vc,
+ vc->vc_origin + vc->vc_size_row * vc->vc_top,
+ vc->vc_size_row * (vc->vc_bottom -
+@@ -2233,27 +2225,27 @@ static bool fbcon_blank(struct vc_data *
+ bool mode_switch)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+ if (mode_switch) {
+ struct fb_var_screeninfo var = info->var;
+
+- ops->graphics = 1;
++ par->graphics = 1;
+
+ if (!blank) {
+ var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE |
+ FB_ACTIVATE_KD_TEXT;
+ fb_set_var(info, &var);
+- ops->graphics = 0;
+- ops->var = info->var;
++ par->graphics = 0;
++ par->var = info->var;
+ }
+ }
+
+ if (!fbcon_is_inactive(vc, info)) {
+- if (ops->blank_state != blank) {
+- ops->blank_state = blank;
++ if (par->blank_state != blank) {
++ par->blank_state = blank;
+ fbcon_cursor(vc, !blank);
+- ops->cursor_flash = (!blank);
++ par->cursor_flash = (!blank);
+
+ if (fb_blank(info, blank))
+ fbcon_generic_blank(vc, info, blank);
+@@ -2264,7 +2256,7 @@ static bool fbcon_blank(struct vc_data *
+ }
+
+ if (mode_switch || fbcon_is_inactive(vc, info) ||
+- ops->blank_state != FB_BLANK_UNBLANK)
++ par->blank_state != FB_BLANK_UNBLANK)
+ fbcon_del_cursor_work(info);
+ else
+ fbcon_add_cursor_work(info);
+@@ -2275,10 +2267,10 @@ static bool fbcon_blank(struct vc_data *
+ static void fbcon_debug_enter(struct vc_data *vc)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+- ops->save_graphics = ops->graphics;
+- ops->graphics = 0;
++ par->save_graphics = par->graphics;
++ par->graphics = 0;
+ if (info->fbops->fb_debug_enter)
+ info->fbops->fb_debug_enter(info);
+ fbcon_set_palette(vc, color_table);
+@@ -2287,9 +2279,9 @@ static void fbcon_debug_enter(struct vc_
+ static void fbcon_debug_leave(struct vc_data *vc)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+- ops->graphics = ops->save_graphics;
++ par->graphics = par->save_graphics;
+ if (info->fbops->fb_debug_leave)
+ info->fbops->fb_debug_leave(info);
+ }
+@@ -2424,7 +2416,7 @@ static int fbcon_do_set_font(struct vc_d
+ const u8 * data, int userfont)
+ {
+ struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fbcon_display *p = &fb_display[vc->vc_num];
+ int resize, ret, old_userfont, old_width, old_height, old_charcount;
+ u8 *old_data = vc->vc_font.data;
+@@ -2451,8 +2443,8 @@ static int fbcon_do_set_font(struct vc_d
+ if (resize) {
+ int cols, rows;
+
+- cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
+- rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
++ cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
++ rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
+ cols /= w;
+ rows /= h;
+ ret = vc_resize(vc, cols, rows);
+@@ -2657,11 +2649,11 @@ static void fbcon_invert_region(struct v
+ void fbcon_suspended(struct fb_info *info)
+ {
+ struct vc_data *vc = NULL;
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+- if (!ops || ops->currcon < 0)
++ if (!par || par->currcon < 0)
+ return;
+- vc = vc_cons[ops->currcon].d;
++ vc = vc_cons[par->currcon].d;
+
+ /* Clear cursor, restore saved data */
+ fbcon_cursor(vc, false);
+@@ -2670,27 +2662,27 @@ void fbcon_suspended(struct fb_info *inf
+ void fbcon_resumed(struct fb_info *info)
+ {
+ struct vc_data *vc;
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+- if (!ops || ops->currcon < 0)
++ if (!par || par->currcon < 0)
+ return;
+- vc = vc_cons[ops->currcon].d;
++ vc = vc_cons[par->currcon].d;
+
+ update_screen(vc);
+ }
+
+ static void fbcon_modechanged(struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct vc_data *vc;
+ struct fbcon_display *p;
+ int rows, cols;
+
+- if (!ops || ops->currcon < 0)
++ if (!par || par->currcon < 0)
+ return;
+- vc = vc_cons[ops->currcon].d;
++ vc = vc_cons[par->currcon].d;
+ if (vc->vc_mode != KD_TEXT ||
+- fbcon_info_from_console(ops->currcon) != info)
++ fbcon_info_from_console(par->currcon) != info)
+ return;
+
+ p = &fb_display[vc->vc_num];
+@@ -2698,8 +2690,8 @@ static void fbcon_modechanged(struct fb_
+
+ if (con_is_visible(vc)) {
+ var_to_display(p, &info->var, info);
+- cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
+- rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
++ cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
++ rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
+ cols /= vc->vc_font.width;
+ rows /= vc->vc_font.height;
+ vc_resize(vc, cols, rows);
+@@ -2708,8 +2700,8 @@ static void fbcon_modechanged(struct fb_
+ scrollback_current = 0;
+
+ if (!fbcon_is_inactive(vc, info)) {
+- ops->var.xoffset = ops->var.yoffset = p->yscroll = 0;
+- ops->update_start(info);
++ par->var.xoffset = par->var.yoffset = p->yscroll = 0;
++ par->update_start(info);
+ }
+
+ fbcon_set_palette(vc, color_table);
+@@ -2719,12 +2711,12 @@ static void fbcon_modechanged(struct fb_
+
+ static void fbcon_set_all_vcs(struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct vc_data *vc;
+ struct fbcon_display *p;
+ int i, rows, cols, fg = -1;
+
+- if (!ops || ops->currcon < 0)
++ if (!par || par->currcon < 0)
+ return;
+
+ for (i = first_fb_vc; i <= last_fb_vc; i++) {
+@@ -2741,8 +2733,8 @@ static void fbcon_set_all_vcs(struct fb_
+ p = &fb_display[vc->vc_num];
+ set_blitting_type(vc, info);
+ var_to_display(p, &info->var, info);
+- cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres);
+- rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres);
++ cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
++ rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
+ cols /= vc->vc_font.width;
+ rows /= vc->vc_font.height;
+ vc_resize(vc, cols, rows);
+@@ -2765,13 +2757,13 @@ EXPORT_SYMBOL(fbcon_update_vcs);
+ /* let fbcon check if it supports a new screen resolution */
+ int fbcon_modechange_possible(struct fb_info *info, struct fb_var_screeninfo *var)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct vc_data *vc;
+ unsigned int i;
+
+ WARN_CONSOLE_UNLOCKED();
+
+- if (!ops)
++ if (!par)
+ return 0;
+
+ /* prevent setting a screen size which is smaller than font size */
+@@ -3069,15 +3061,14 @@ int fbcon_fb_registered(struct fb_info *
+
+ void fbcon_fb_blanked(struct fb_info *info, int blank)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct vc_data *vc;
+
+- if (!ops || ops->currcon < 0)
++ if (!par || par->currcon < 0)
+ return;
+
+- vc = vc_cons[ops->currcon].d;
+- if (vc->vc_mode != KD_TEXT ||
+- fbcon_info_from_console(ops->currcon) != info)
++ vc = vc_cons[par->currcon].d;
++ if (vc->vc_mode != KD_TEXT || fbcon_info_from_console(par->currcon) != info)
+ return;
+
+ if (con_is_visible(vc)) {
+@@ -3086,7 +3077,7 @@ void fbcon_fb_blanked(struct fb_info *in
+ else
+ do_unblank_screen(0);
+ }
+- ops->blank_state = blank;
++ par->blank_state = blank;
+ }
+
+ void fbcon_new_modelist(struct fb_info *info)
+@@ -3276,7 +3267,7 @@ static ssize_t show_cursor_blink(struct
+ struct device_attribute *attr, char *buf)
+ {
+ struct fb_info *info;
+- struct fbcon_ops *ops;
++ struct fbcon_par *par;
+ int idx, blink = -1;
+
+ console_lock();
+@@ -3286,12 +3277,12 @@ static ssize_t show_cursor_blink(struct
+ goto err;
+
+ info = fbcon_registered_fb[idx];
+- ops = info->fbcon_par;
++ par = info->fbcon_par;
+
+- if (!ops)
++ if (!par)
+ goto err;
+
+- blink = delayed_work_pending(&ops->cursor_work);
++ blink = delayed_work_pending(&par->cursor_work);
+ err:
+ console_unlock();
+ return sysfs_emit(buf, "%d\n", blink);
+--- a/drivers/video/fbdev/core/fbcon.h
++++ b/drivers/video/fbdev/core/fbcon.h
+@@ -50,7 +50,7 @@ struct fbcon_display {
+ const struct fb_videomode *mode;
+ };
+
+-struct fbcon_ops {
++struct fbcon_par {
+ void (*bmove)(struct vc_data *vc, struct fb_info *info, int sy,
+ int sx, int dy, int dx, int height, int width);
+ void (*clear)(struct vc_data *vc, struct fb_info *info, int sy,
+@@ -185,7 +185,7 @@ static inline u_short fb_scrollmode(stru
+ #ifdef CONFIG_FB_TILEBLITTING
+ extern void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info);
+ #endif
+-extern void fbcon_set_bitops(struct fbcon_ops *ops);
++extern void fbcon_set_bitops(struct fbcon_par *par);
+ extern int soft_cursor(struct fb_info *info, struct fb_cursor *cursor);
+
+ #define FBCON_ATTRIBUTE_UNDERLINE 1
+@@ -224,7 +224,7 @@ static inline int get_attribute(struct f
+ (i == FB_ROTATE_UR || i == FB_ROTATE_UD) ? _r : _v; })
+
+ #ifdef CONFIG_FRAMEBUFFER_CONSOLE_ROTATION
+-extern void fbcon_set_rotate(struct fbcon_ops *ops);
++extern void fbcon_set_rotate(struct fbcon_par *par);
+ #else
+ #define fbcon_set_rotate(x) do {} while(0)
+ #endif /* CONFIG_FRAMEBUFFER_CONSOLE_ROTATION */
+--- a/drivers/video/fbdev/core/fbcon_ccw.c
++++ b/drivers/video/fbdev/core/fbcon_ccw.c
+@@ -63,9 +63,9 @@ static void ccw_update_attr(u8 *dst, u8
+ static void ccw_bmove(struct vc_data *vc, struct fb_info *info, int sy,
+ int sx, int dy, int dx, int height, int width)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fb_copyarea area;
+- u32 vyres = GETVYRES(ops->p, info);
++ u32 vyres = GETVYRES(par->p, info);
+
+ area.sx = sy * vc->vc_font.height;
+ area.sy = vyres - ((sx + width) * vc->vc_font.width);
+@@ -80,9 +80,9 @@ static void ccw_bmove(struct vc_data *vc
+ static void ccw_clear(struct vc_data *vc, struct fb_info *info, int sy,
+ int sx, int height, int width, int fg, int bg)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fb_fillrect region;
+- u32 vyres = GETVYRES(ops->p, info);
++ u32 vyres = GETVYRES(par->p, info);
+
+ region.color = bg;
+ region.dx = sy * vc->vc_font.height;
+@@ -99,13 +99,13 @@ static inline void ccw_putcs_aligned(str
+ u32 d_pitch, u32 s_pitch, u32 cellsize,
+ struct fb_image *image, u8 *buf, u8 *dst)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+ u32 idx = (vc->vc_font.height + 7) >> 3;
+ u8 *src;
+
+ while (cnt--) {
+- src = ops->fontbuffer + (scr_readw(s--) & charmask)*cellsize;
++ src = par->fontbuffer + (scr_readw(s--) & charmask) * cellsize;
+
+ if (attr) {
+ ccw_update_attr(buf, src, attr, vc);
+@@ -130,7 +130,7 @@ static void ccw_putcs(struct vc_data *vc
+ int fg, int bg)
+ {
+ struct fb_image image;
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ u32 width = (vc->vc_font.height + 7)/8;
+ u32 cellsize = width * vc->vc_font.width;
+ u32 maxcnt = info->pixmap.size/cellsize;
+@@ -139,9 +139,9 @@ static void ccw_putcs(struct vc_data *vc
+ u32 cnt, pitch, size;
+ u32 attribute = get_attribute(info, scr_readw(s));
+ u8 *dst, *buf = NULL;
+- u32 vyres = GETVYRES(ops->p, info);
++ u32 vyres = GETVYRES(par->p, info);
+
+- if (!ops->fontbuffer)
++ if (!par->fontbuffer)
+ return;
+
+ image.fg_color = fg;
+@@ -221,28 +221,28 @@ static void ccw_cursor(struct vc_data *v
+ int fg, int bg)
+ {
+ struct fb_cursor cursor;
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+ int w = (vc->vc_font.height + 7) >> 3, c;
+- int y = real_y(ops->p, vc->state.y);
++ int y = real_y(par->p, vc->state.y);
+ int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
+ int err = 1, dx, dy;
+ char *src;
+- u32 vyres = GETVYRES(ops->p, info);
++ u32 vyres = GETVYRES(par->p, info);
+
+- if (!ops->fontbuffer)
++ if (!par->fontbuffer)
+ return;
+
+ cursor.set = 0;
+
+ c = scr_readw((u16 *) vc->vc_pos);
+ attribute = get_attribute(info, c);
+- src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width));
++ src = par->fontbuffer + ((c & charmask) * (w * vc->vc_font.width));
+
+- if (ops->cursor_state.image.data != src ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.data = src;
+- cursor.set |= FB_CUR_SETIMAGE;
++ if (par->cursor_state.image.data != src ||
++ par->cursor_reset) {
++ par->cursor_state.image.data = src;
++ cursor.set |= FB_CUR_SETIMAGE;
+ }
+
+ if (attribute) {
+@@ -251,49 +251,49 @@ static void ccw_cursor(struct vc_data *v
+ dst = kmalloc_array(w, vc->vc_font.width, GFP_ATOMIC);
+ if (!dst)
+ return;
+- kfree(ops->cursor_data);
+- ops->cursor_data = dst;
++ kfree(par->cursor_data);
++ par->cursor_data = dst;
+ ccw_update_attr(dst, src, attribute, vc);
+ src = dst;
+ }
+
+- if (ops->cursor_state.image.fg_color != fg ||
+- ops->cursor_state.image.bg_color != bg ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.fg_color = fg;
+- ops->cursor_state.image.bg_color = bg;
++ if (par->cursor_state.image.fg_color != fg ||
++ par->cursor_state.image.bg_color != bg ||
++ par->cursor_reset) {
++ par->cursor_state.image.fg_color = fg;
++ par->cursor_state.image.bg_color = bg;
+ cursor.set |= FB_CUR_SETCMAP;
+ }
+
+- if (ops->cursor_state.image.height != vc->vc_font.width ||
+- ops->cursor_state.image.width != vc->vc_font.height ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.height = vc->vc_font.width;
+- ops->cursor_state.image.width = vc->vc_font.height;
++ if (par->cursor_state.image.height != vc->vc_font.width ||
++ par->cursor_state.image.width != vc->vc_font.height ||
++ par->cursor_reset) {
++ par->cursor_state.image.height = vc->vc_font.width;
++ par->cursor_state.image.width = vc->vc_font.height;
+ cursor.set |= FB_CUR_SETSIZE;
+ }
+
+ dx = y * vc->vc_font.height;
+ dy = vyres - ((vc->state.x + 1) * vc->vc_font.width);
+
+- if (ops->cursor_state.image.dx != dx ||
+- ops->cursor_state.image.dy != dy ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.dx = dx;
+- ops->cursor_state.image.dy = dy;
++ if (par->cursor_state.image.dx != dx ||
++ par->cursor_state.image.dy != dy ||
++ par->cursor_reset) {
++ par->cursor_state.image.dx = dx;
++ par->cursor_state.image.dy = dy;
+ cursor.set |= FB_CUR_SETPOS;
+ }
+
+- if (ops->cursor_state.hot.x || ops->cursor_state.hot.y ||
+- ops->cursor_reset) {
+- ops->cursor_state.hot.x = cursor.hot.y = 0;
++ if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
++ par->cursor_reset) {
++ par->cursor_state.hot.x = cursor.hot.y = 0;
+ cursor.set |= FB_CUR_SETHOT;
+ }
+
+ if (cursor.set & FB_CUR_SETSIZE ||
+- vc->vc_cursor_type != ops->p->cursor_shape ||
+- ops->cursor_state.mask == NULL ||
+- ops->cursor_reset) {
++ vc->vc_cursor_type != par->p->cursor_shape ||
++ par->cursor_state.mask == NULL ||
++ par->cursor_reset) {
+ char *tmp, *mask = kmalloc_array(w, vc->vc_font.width,
+ GFP_ATOMIC);
+ int cur_height, size, i = 0;
+@@ -309,13 +309,13 @@ static void ccw_cursor(struct vc_data *v
+ return;
+ }
+
+- kfree(ops->cursor_state.mask);
+- ops->cursor_state.mask = mask;
++ kfree(par->cursor_state.mask);
++ par->cursor_state.mask = mask;
+
+- ops->p->cursor_shape = vc->vc_cursor_type;
++ par->p->cursor_shape = vc->vc_cursor_type;
+ cursor.set |= FB_CUR_SETSHAPE;
+
+- switch (CUR_SIZE(ops->p->cursor_shape)) {
++ switch (CUR_SIZE(par->p->cursor_shape)) {
+ case CUR_NONE:
+ cur_height = 0;
+ break;
+@@ -348,19 +348,19 @@ static void ccw_cursor(struct vc_data *v
+ kfree(tmp);
+ }
+
+- ops->cursor_state.enable = enable && !use_sw;
++ par->cursor_state.enable = enable && !use_sw;
+
+ cursor.image.data = src;
+- cursor.image.fg_color = ops->cursor_state.image.fg_color;
+- cursor.image.bg_color = ops->cursor_state.image.bg_color;
+- cursor.image.dx = ops->cursor_state.image.dx;
+- cursor.image.dy = ops->cursor_state.image.dy;
+- cursor.image.height = ops->cursor_state.image.height;
+- cursor.image.width = ops->cursor_state.image.width;
+- cursor.hot.x = ops->cursor_state.hot.x;
+- cursor.hot.y = ops->cursor_state.hot.y;
+- cursor.mask = ops->cursor_state.mask;
+- cursor.enable = ops->cursor_state.enable;
++ cursor.image.fg_color = par->cursor_state.image.fg_color;
++ cursor.image.bg_color = par->cursor_state.image.bg_color;
++ cursor.image.dx = par->cursor_state.image.dx;
++ cursor.image.dy = par->cursor_state.image.dy;
++ cursor.image.height = par->cursor_state.image.height;
++ cursor.image.width = par->cursor_state.image.width;
++ cursor.hot.x = par->cursor_state.hot.x;
++ cursor.hot.y = par->cursor_state.hot.y;
++ cursor.mask = par->cursor_state.mask;
++ cursor.enable = par->cursor_state.enable;
+ cursor.image.depth = 1;
+ cursor.rop = ROP_XOR;
+
+@@ -370,32 +370,32 @@ static void ccw_cursor(struct vc_data *v
+ if (err)
+ soft_cursor(info, &cursor);
+
+- ops->cursor_reset = 0;
++ par->cursor_reset = 0;
+ }
+
+ static int ccw_update_start(struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ u32 yoffset;
+- u32 vyres = GETVYRES(ops->p, info);
++ u32 vyres = GETVYRES(par->p, info);
+ int err;
+
+- yoffset = (vyres - info->var.yres) - ops->var.xoffset;
+- ops->var.xoffset = ops->var.yoffset;
+- ops->var.yoffset = yoffset;
+- err = fb_pan_display(info, &ops->var);
+- ops->var.xoffset = info->var.xoffset;
+- ops->var.yoffset = info->var.yoffset;
+- ops->var.vmode = info->var.vmode;
++ yoffset = (vyres - info->var.yres) - par->var.xoffset;
++ par->var.xoffset = par->var.yoffset;
++ par->var.yoffset = yoffset;
++ err = fb_pan_display(info, &par->var);
++ par->var.xoffset = info->var.xoffset;
++ par->var.yoffset = info->var.yoffset;
++ par->var.vmode = info->var.vmode;
+ return err;
+ }
+
+-void fbcon_rotate_ccw(struct fbcon_ops *ops)
++void fbcon_rotate_ccw(struct fbcon_par *par)
+ {
+- ops->bmove = ccw_bmove;
+- ops->clear = ccw_clear;
+- ops->putcs = ccw_putcs;
+- ops->clear_margins = ccw_clear_margins;
+- ops->cursor = ccw_cursor;
+- ops->update_start = ccw_update_start;
++ par->bmove = ccw_bmove;
++ par->clear = ccw_clear;
++ par->putcs = ccw_putcs;
++ par->clear_margins = ccw_clear_margins;
++ par->cursor = ccw_cursor;
++ par->update_start = ccw_update_start;
+ }
+--- a/drivers/video/fbdev/core/fbcon_cw.c
++++ b/drivers/video/fbdev/core/fbcon_cw.c
+@@ -48,9 +48,9 @@ static void cw_update_attr(u8 *dst, u8 *
+ static void cw_bmove(struct vc_data *vc, struct fb_info *info, int sy,
+ int sx, int dy, int dx, int height, int width)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fb_copyarea area;
+- u32 vxres = GETVXRES(ops->p, info);
++ u32 vxres = GETVXRES(par->p, info);
+
+ area.sx = vxres - ((sy + height) * vc->vc_font.height);
+ area.sy = sx * vc->vc_font.width;
+@@ -65,9 +65,9 @@ static void cw_bmove(struct vc_data *vc,
+ static void cw_clear(struct vc_data *vc, struct fb_info *info, int sy,
+ int sx, int height, int width, int fg, int bg)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fb_fillrect region;
+- u32 vxres = GETVXRES(ops->p, info);
++ u32 vxres = GETVXRES(par->p, info);
+
+ region.color = bg;
+ region.dx = vxres - ((sy + height) * vc->vc_font.height);
+@@ -84,13 +84,13 @@ static inline void cw_putcs_aligned(stru
+ u32 d_pitch, u32 s_pitch, u32 cellsize,
+ struct fb_image *image, u8 *buf, u8 *dst)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+ u32 idx = (vc->vc_font.height + 7) >> 3;
+ u8 *src;
+
+ while (cnt--) {
+- src = ops->fontbuffer + (scr_readw(s++) & charmask)*cellsize;
++ src = par->fontbuffer + (scr_readw(s++) & charmask) * cellsize;
+
+ if (attr) {
+ cw_update_attr(buf, src, attr, vc);
+@@ -115,7 +115,7 @@ static void cw_putcs(struct vc_data *vc,
+ int fg, int bg)
+ {
+ struct fb_image image;
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ u32 width = (vc->vc_font.height + 7)/8;
+ u32 cellsize = width * vc->vc_font.width;
+ u32 maxcnt = info->pixmap.size/cellsize;
+@@ -124,9 +124,9 @@ static void cw_putcs(struct vc_data *vc,
+ u32 cnt, pitch, size;
+ u32 attribute = get_attribute(info, scr_readw(s));
+ u8 *dst, *buf = NULL;
+- u32 vxres = GETVXRES(ops->p, info);
++ u32 vxres = GETVXRES(par->p, info);
+
+- if (!ops->fontbuffer)
++ if (!par->fontbuffer)
+ return;
+
+ image.fg_color = fg;
+@@ -204,28 +204,28 @@ static void cw_cursor(struct vc_data *vc
+ int fg, int bg)
+ {
+ struct fb_cursor cursor;
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+ int w = (vc->vc_font.height + 7) >> 3, c;
+- int y = real_y(ops->p, vc->state.y);
++ int y = real_y(par->p, vc->state.y);
+ int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
+ int err = 1, dx, dy;
+ char *src;
+- u32 vxres = GETVXRES(ops->p, info);
++ u32 vxres = GETVXRES(par->p, info);
+
+- if (!ops->fontbuffer)
++ if (!par->fontbuffer)
+ return;
+
+ cursor.set = 0;
+
+ c = scr_readw((u16 *) vc->vc_pos);
+ attribute = get_attribute(info, c);
+- src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width));
++ src = par->fontbuffer + ((c & charmask) * (w * vc->vc_font.width));
+
+- if (ops->cursor_state.image.data != src ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.data = src;
+- cursor.set |= FB_CUR_SETIMAGE;
++ if (par->cursor_state.image.data != src ||
++ par->cursor_reset) {
++ par->cursor_state.image.data = src;
++ cursor.set |= FB_CUR_SETIMAGE;
+ }
+
+ if (attribute) {
+@@ -234,49 +234,49 @@ static void cw_cursor(struct vc_data *vc
+ dst = kmalloc_array(w, vc->vc_font.width, GFP_ATOMIC);
+ if (!dst)
+ return;
+- kfree(ops->cursor_data);
+- ops->cursor_data = dst;
++ kfree(par->cursor_data);
++ par->cursor_data = dst;
+ cw_update_attr(dst, src, attribute, vc);
+ src = dst;
+ }
+
+- if (ops->cursor_state.image.fg_color != fg ||
+- ops->cursor_state.image.bg_color != bg ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.fg_color = fg;
+- ops->cursor_state.image.bg_color = bg;
++ if (par->cursor_state.image.fg_color != fg ||
++ par->cursor_state.image.bg_color != bg ||
++ par->cursor_reset) {
++ par->cursor_state.image.fg_color = fg;
++ par->cursor_state.image.bg_color = bg;
+ cursor.set |= FB_CUR_SETCMAP;
+ }
+
+- if (ops->cursor_state.image.height != vc->vc_font.width ||
+- ops->cursor_state.image.width != vc->vc_font.height ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.height = vc->vc_font.width;
+- ops->cursor_state.image.width = vc->vc_font.height;
++ if (par->cursor_state.image.height != vc->vc_font.width ||
++ par->cursor_state.image.width != vc->vc_font.height ||
++ par->cursor_reset) {
++ par->cursor_state.image.height = vc->vc_font.width;
++ par->cursor_state.image.width = vc->vc_font.height;
+ cursor.set |= FB_CUR_SETSIZE;
+ }
+
+ dx = vxres - ((y * vc->vc_font.height) + vc->vc_font.height);
+ dy = vc->state.x * vc->vc_font.width;
+
+- if (ops->cursor_state.image.dx != dx ||
+- ops->cursor_state.image.dy != dy ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.dx = dx;
+- ops->cursor_state.image.dy = dy;
++ if (par->cursor_state.image.dx != dx ||
++ par->cursor_state.image.dy != dy ||
++ par->cursor_reset) {
++ par->cursor_state.image.dx = dx;
++ par->cursor_state.image.dy = dy;
+ cursor.set |= FB_CUR_SETPOS;
+ }
+
+- if (ops->cursor_state.hot.x || ops->cursor_state.hot.y ||
+- ops->cursor_reset) {
+- ops->cursor_state.hot.x = cursor.hot.y = 0;
++ if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
++ par->cursor_reset) {
++ par->cursor_state.hot.x = cursor.hot.y = 0;
+ cursor.set |= FB_CUR_SETHOT;
+ }
+
+ if (cursor.set & FB_CUR_SETSIZE ||
+- vc->vc_cursor_type != ops->p->cursor_shape ||
+- ops->cursor_state.mask == NULL ||
+- ops->cursor_reset) {
++ vc->vc_cursor_type != par->p->cursor_shape ||
++ par->cursor_state.mask == NULL ||
++ par->cursor_reset) {
+ char *tmp, *mask = kmalloc_array(w, vc->vc_font.width,
+ GFP_ATOMIC);
+ int cur_height, size, i = 0;
+@@ -292,13 +292,13 @@ static void cw_cursor(struct vc_data *vc
+ return;
+ }
+
+- kfree(ops->cursor_state.mask);
+- ops->cursor_state.mask = mask;
++ kfree(par->cursor_state.mask);
++ par->cursor_state.mask = mask;
+
+- ops->p->cursor_shape = vc->vc_cursor_type;
++ par->p->cursor_shape = vc->vc_cursor_type;
+ cursor.set |= FB_CUR_SETSHAPE;
+
+- switch (CUR_SIZE(ops->p->cursor_shape)) {
++ switch (CUR_SIZE(par->p->cursor_shape)) {
+ case CUR_NONE:
+ cur_height = 0;
+ break;
+@@ -331,19 +331,19 @@ static void cw_cursor(struct vc_data *vc
+ kfree(tmp);
+ }
+
+- ops->cursor_state.enable = enable && !use_sw;
++ par->cursor_state.enable = enable && !use_sw;
+
+ cursor.image.data = src;
+- cursor.image.fg_color = ops->cursor_state.image.fg_color;
+- cursor.image.bg_color = ops->cursor_state.image.bg_color;
+- cursor.image.dx = ops->cursor_state.image.dx;
+- cursor.image.dy = ops->cursor_state.image.dy;
+- cursor.image.height = ops->cursor_state.image.height;
+- cursor.image.width = ops->cursor_state.image.width;
+- cursor.hot.x = ops->cursor_state.hot.x;
+- cursor.hot.y = ops->cursor_state.hot.y;
+- cursor.mask = ops->cursor_state.mask;
+- cursor.enable = ops->cursor_state.enable;
++ cursor.image.fg_color = par->cursor_state.image.fg_color;
++ cursor.image.bg_color = par->cursor_state.image.bg_color;
++ cursor.image.dx = par->cursor_state.image.dx;
++ cursor.image.dy = par->cursor_state.image.dy;
++ cursor.image.height = par->cursor_state.image.height;
++ cursor.image.width = par->cursor_state.image.width;
++ cursor.hot.x = par->cursor_state.hot.x;
++ cursor.hot.y = par->cursor_state.hot.y;
++ cursor.mask = par->cursor_state.mask;
++ cursor.enable = par->cursor_state.enable;
+ cursor.image.depth = 1;
+ cursor.rop = ROP_XOR;
+
+@@ -353,32 +353,32 @@ static void cw_cursor(struct vc_data *vc
+ if (err)
+ soft_cursor(info, &cursor);
+
+- ops->cursor_reset = 0;
++ par->cursor_reset = 0;
+ }
+
+ static int cw_update_start(struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
+- u32 vxres = GETVXRES(ops->p, info);
++ struct fbcon_par *par = info->fbcon_par;
++ u32 vxres = GETVXRES(par->p, info);
+ u32 xoffset;
+ int err;
+
+- xoffset = vxres - (info->var.xres + ops->var.yoffset);
+- ops->var.yoffset = ops->var.xoffset;
+- ops->var.xoffset = xoffset;
+- err = fb_pan_display(info, &ops->var);
+- ops->var.xoffset = info->var.xoffset;
+- ops->var.yoffset = info->var.yoffset;
+- ops->var.vmode = info->var.vmode;
++ xoffset = vxres - (info->var.xres + par->var.yoffset);
++ par->var.yoffset = par->var.xoffset;
++ par->var.xoffset = xoffset;
++ err = fb_pan_display(info, &par->var);
++ par->var.xoffset = info->var.xoffset;
++ par->var.yoffset = info->var.yoffset;
++ par->var.vmode = info->var.vmode;
+ return err;
+ }
+
+-void fbcon_rotate_cw(struct fbcon_ops *ops)
++void fbcon_rotate_cw(struct fbcon_par *par)
+ {
+- ops->bmove = cw_bmove;
+- ops->clear = cw_clear;
+- ops->putcs = cw_putcs;
+- ops->clear_margins = cw_clear_margins;
+- ops->cursor = cw_cursor;
+- ops->update_start = cw_update_start;
++ par->bmove = cw_bmove;
++ par->clear = cw_clear;
++ par->putcs = cw_putcs;
++ par->clear_margins = cw_clear_margins;
++ par->cursor = cw_cursor;
++ par->update_start = cw_update_start;
+ }
+--- a/drivers/video/fbdev/core/fbcon_rotate.c
++++ b/drivers/video/fbdev/core/fbcon_rotate.c
+@@ -20,35 +20,35 @@
+
+ static int fbcon_rotate_font(struct fb_info *info, struct vc_data *vc)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ int len, err = 0;
+ int s_cellsize, d_cellsize, i;
+ const u8 *src;
+ u8 *dst;
+
+- if (vc->vc_font.data == ops->fontdata &&
+- ops->p->con_rotate == ops->cur_rotate)
++ if (vc->vc_font.data == par->fontdata &&
++ par->p->con_rotate == par->cur_rotate)
+ goto finished;
+
+- src = ops->fontdata = vc->vc_font.data;
+- ops->cur_rotate = ops->p->con_rotate;
++ src = par->fontdata = vc->vc_font.data;
++ par->cur_rotate = par->p->con_rotate;
+ len = vc->vc_font.charcount;
+ s_cellsize = ((vc->vc_font.width + 7)/8) *
+ vc->vc_font.height;
+ d_cellsize = s_cellsize;
+
+- if (ops->rotate == FB_ROTATE_CW ||
+- ops->rotate == FB_ROTATE_CCW)
++ if (par->rotate == FB_ROTATE_CW ||
++ par->rotate == FB_ROTATE_CCW)
+ d_cellsize = ((vc->vc_font.height + 7)/8) *
+ vc->vc_font.width;
+
+ if (info->fbops->fb_sync)
+ info->fbops->fb_sync(info);
+
+- if (ops->fd_size < d_cellsize * len) {
+- kfree(ops->fontbuffer);
+- ops->fontbuffer = NULL;
+- ops->fd_size = 0;
++ if (par->fd_size < d_cellsize * len) {
++ kfree(par->fontbuffer);
++ par->fontbuffer = NULL;
++ par->fd_size = 0;
+
+ dst = kmalloc_array(len, d_cellsize, GFP_KERNEL);
+
+@@ -57,14 +57,14 @@ static int fbcon_rotate_font(struct fb_i
+ goto finished;
+ }
+
+- ops->fd_size = d_cellsize * len;
+- ops->fontbuffer = dst;
++ par->fd_size = d_cellsize * len;
++ par->fontbuffer = dst;
+ }
+
+- dst = ops->fontbuffer;
+- memset(dst, 0, ops->fd_size);
++ dst = par->fontbuffer;
++ memset(dst, 0, par->fd_size);
+
+- switch (ops->rotate) {
++ switch (par->rotate) {
+ case FB_ROTATE_UD:
+ for (i = len; i--; ) {
+ rotate_ud(src, dst, vc->vc_font.width,
+@@ -96,19 +96,19 @@ finished:
+ return err;
+ }
+
+-void fbcon_set_rotate(struct fbcon_ops *ops)
++void fbcon_set_rotate(struct fbcon_par *par)
+ {
+- ops->rotate_font = fbcon_rotate_font;
++ par->rotate_font = fbcon_rotate_font;
+
+- switch(ops->rotate) {
++ switch (par->rotate) {
+ case FB_ROTATE_CW:
+- fbcon_rotate_cw(ops);
++ fbcon_rotate_cw(par);
+ break;
+ case FB_ROTATE_UD:
+- fbcon_rotate_ud(ops);
++ fbcon_rotate_ud(par);
+ break;
+ case FB_ROTATE_CCW:
+- fbcon_rotate_ccw(ops);
++ fbcon_rotate_ccw(par);
+ break;
+ }
+ }
+--- a/drivers/video/fbdev/core/fbcon_rotate.h
++++ b/drivers/video/fbdev/core/fbcon_rotate.h
+@@ -90,7 +90,7 @@ static inline void rotate_ccw(const char
+ }
+ }
+
+-extern void fbcon_rotate_cw(struct fbcon_ops *ops);
+-extern void fbcon_rotate_ud(struct fbcon_ops *ops);
+-extern void fbcon_rotate_ccw(struct fbcon_ops *ops);
++extern void fbcon_rotate_cw(struct fbcon_par *par);
++extern void fbcon_rotate_ud(struct fbcon_par *par);
++extern void fbcon_rotate_ccw(struct fbcon_par *par);
+ #endif
+--- a/drivers/video/fbdev/core/fbcon_ud.c
++++ b/drivers/video/fbdev/core/fbcon_ud.c
+@@ -48,10 +48,10 @@ static void ud_update_attr(u8 *dst, u8 *
+ static void ud_bmove(struct vc_data *vc, struct fb_info *info, int sy,
+ int sx, int dy, int dx, int height, int width)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fb_copyarea area;
+- u32 vyres = GETVYRES(ops->p, info);
+- u32 vxres = GETVXRES(ops->p, info);
++ u32 vyres = GETVYRES(par->p, info);
++ u32 vxres = GETVXRES(par->p, info);
+
+ area.sy = vyres - ((sy + height) * vc->vc_font.height);
+ area.sx = vxres - ((sx + width) * vc->vc_font.width);
+@@ -66,10 +66,10 @@ static void ud_bmove(struct vc_data *vc,
+ static void ud_clear(struct vc_data *vc, struct fb_info *info, int sy,
+ int sx, int height, int width, int fg, int bg)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ struct fb_fillrect region;
+- u32 vyres = GETVYRES(ops->p, info);
+- u32 vxres = GETVXRES(ops->p, info);
++ u32 vyres = GETVYRES(par->p, info);
++ u32 vxres = GETVXRES(par->p, info);
+
+ region.color = bg;
+ region.dy = vyres - ((sy + height) * vc->vc_font.height);
+@@ -86,13 +86,13 @@ static inline void ud_putcs_aligned(stru
+ u32 d_pitch, u32 s_pitch, u32 cellsize,
+ struct fb_image *image, u8 *buf, u8 *dst)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+ u32 idx = vc->vc_font.width >> 3;
+ u8 *src;
+
+ while (cnt--) {
+- src = ops->fontbuffer + (scr_readw(s--) & charmask)*cellsize;
++ src = par->fontbuffer + (scr_readw(s--) & charmask) * cellsize;
+
+ if (attr) {
+ ud_update_attr(buf, src, attr, vc);
+@@ -119,7 +119,7 @@ static inline void ud_putcs_unaligned(st
+ struct fb_image *image, u8 *buf,
+ u8 *dst)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+ u32 shift_low = 0, mod = vc->vc_font.width % 8;
+ u32 shift_high = 8;
+@@ -127,7 +127,7 @@ static inline void ud_putcs_unaligned(st
+ u8 *src;
+
+ while (cnt--) {
+- src = ops->fontbuffer + (scr_readw(s--) & charmask)*cellsize;
++ src = par->fontbuffer + (scr_readw(s--) & charmask) * cellsize;
+
+ if (attr) {
+ ud_update_attr(buf, src, attr, vc);
+@@ -152,7 +152,7 @@ static void ud_putcs(struct vc_data *vc,
+ int fg, int bg)
+ {
+ struct fb_image image;
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ u32 width = (vc->vc_font.width + 7)/8;
+ u32 cellsize = width * vc->vc_font.height;
+ u32 maxcnt = info->pixmap.size/cellsize;
+@@ -161,10 +161,10 @@ static void ud_putcs(struct vc_data *vc,
+ u32 mod = vc->vc_font.width % 8, cnt, pitch, size;
+ u32 attribute = get_attribute(info, scr_readw(s));
+ u8 *dst, *buf = NULL;
+- u32 vyres = GETVYRES(ops->p, info);
+- u32 vxres = GETVXRES(ops->p, info);
++ u32 vyres = GETVYRES(par->p, info);
++ u32 vxres = GETVXRES(par->p, info);
+
+- if (!ops->fontbuffer)
++ if (!par->fontbuffer)
+ return;
+
+ image.fg_color = fg;
+@@ -251,29 +251,29 @@ static void ud_cursor(struct vc_data *vc
+ int fg, int bg)
+ {
+ struct fb_cursor cursor;
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
+ int w = (vc->vc_font.width + 7) >> 3, c;
+- int y = real_y(ops->p, vc->state.y);
++ int y = real_y(par->p, vc->state.y);
+ int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
+ int err = 1, dx, dy;
+ char *src;
+- u32 vyres = GETVYRES(ops->p, info);
+- u32 vxres = GETVXRES(ops->p, info);
++ u32 vyres = GETVYRES(par->p, info);
++ u32 vxres = GETVXRES(par->p, info);
+
+- if (!ops->fontbuffer)
++ if (!par->fontbuffer)
+ return;
+
+ cursor.set = 0;
+
+ c = scr_readw((u16 *) vc->vc_pos);
+ attribute = get_attribute(info, c);
+- src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.height));
++ src = par->fontbuffer + ((c & charmask) * (w * vc->vc_font.height));
+
+- if (ops->cursor_state.image.data != src ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.data = src;
+- cursor.set |= FB_CUR_SETIMAGE;
++ if (par->cursor_state.image.data != src ||
++ par->cursor_reset) {
++ par->cursor_state.image.data = src;
++ cursor.set |= FB_CUR_SETIMAGE;
+ }
+
+ if (attribute) {
+@@ -282,49 +282,49 @@ static void ud_cursor(struct vc_data *vc
+ dst = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC);
+ if (!dst)
+ return;
+- kfree(ops->cursor_data);
+- ops->cursor_data = dst;
++ kfree(par->cursor_data);
++ par->cursor_data = dst;
+ ud_update_attr(dst, src, attribute, vc);
+ src = dst;
+ }
+
+- if (ops->cursor_state.image.fg_color != fg ||
+- ops->cursor_state.image.bg_color != bg ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.fg_color = fg;
+- ops->cursor_state.image.bg_color = bg;
++ if (par->cursor_state.image.fg_color != fg ||
++ par->cursor_state.image.bg_color != bg ||
++ par->cursor_reset) {
++ par->cursor_state.image.fg_color = fg;
++ par->cursor_state.image.bg_color = bg;
+ cursor.set |= FB_CUR_SETCMAP;
+ }
+
+- if (ops->cursor_state.image.height != vc->vc_font.height ||
+- ops->cursor_state.image.width != vc->vc_font.width ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.height = vc->vc_font.height;
+- ops->cursor_state.image.width = vc->vc_font.width;
++ if (par->cursor_state.image.height != vc->vc_font.height ||
++ par->cursor_state.image.width != vc->vc_font.width ||
++ par->cursor_reset) {
++ par->cursor_state.image.height = vc->vc_font.height;
++ par->cursor_state.image.width = vc->vc_font.width;
+ cursor.set |= FB_CUR_SETSIZE;
+ }
+
+ dy = vyres - ((y * vc->vc_font.height) + vc->vc_font.height);
+ dx = vxres - ((vc->state.x * vc->vc_font.width) + vc->vc_font.width);
+
+- if (ops->cursor_state.image.dx != dx ||
+- ops->cursor_state.image.dy != dy ||
+- ops->cursor_reset) {
+- ops->cursor_state.image.dx = dx;
+- ops->cursor_state.image.dy = dy;
++ if (par->cursor_state.image.dx != dx ||
++ par->cursor_state.image.dy != dy ||
++ par->cursor_reset) {
++ par->cursor_state.image.dx = dx;
++ par->cursor_state.image.dy = dy;
+ cursor.set |= FB_CUR_SETPOS;
+ }
+
+- if (ops->cursor_state.hot.x || ops->cursor_state.hot.y ||
+- ops->cursor_reset) {
+- ops->cursor_state.hot.x = cursor.hot.y = 0;
++ if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
++ par->cursor_reset) {
++ par->cursor_state.hot.x = cursor.hot.y = 0;
+ cursor.set |= FB_CUR_SETHOT;
+ }
+
+ if (cursor.set & FB_CUR_SETSIZE ||
+- vc->vc_cursor_type != ops->p->cursor_shape ||
+- ops->cursor_state.mask == NULL ||
+- ops->cursor_reset) {
++ vc->vc_cursor_type != par->p->cursor_shape ||
++ par->cursor_state.mask == NULL ||
++ par->cursor_reset) {
+ char *mask = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC);
+ int cur_height, size, i = 0;
+ u8 msk = 0xff;
+@@ -332,13 +332,13 @@ static void ud_cursor(struct vc_data *vc
+ if (!mask)
+ return;
+
+- kfree(ops->cursor_state.mask);
+- ops->cursor_state.mask = mask;
++ kfree(par->cursor_state.mask);
++ par->cursor_state.mask = mask;
+
+- ops->p->cursor_shape = vc->vc_cursor_type;
++ par->p->cursor_shape = vc->vc_cursor_type;
+ cursor.set |= FB_CUR_SETSHAPE;
+
+- switch (CUR_SIZE(ops->p->cursor_shape)) {
++ switch (CUR_SIZE(par->p->cursor_shape)) {
+ case CUR_NONE:
+ cur_height = 0;
+ break;
+@@ -371,19 +371,19 @@ static void ud_cursor(struct vc_data *vc
+ mask[i++] = ~msk;
+ }
+
+- ops->cursor_state.enable = enable && !use_sw;
++ par->cursor_state.enable = enable && !use_sw;
+
+ cursor.image.data = src;
+- cursor.image.fg_color = ops->cursor_state.image.fg_color;
+- cursor.image.bg_color = ops->cursor_state.image.bg_color;
+- cursor.image.dx = ops->cursor_state.image.dx;
+- cursor.image.dy = ops->cursor_state.image.dy;
+- cursor.image.height = ops->cursor_state.image.height;
+- cursor.image.width = ops->cursor_state.image.width;
+- cursor.hot.x = ops->cursor_state.hot.x;
+- cursor.hot.y = ops->cursor_state.hot.y;
+- cursor.mask = ops->cursor_state.mask;
+- cursor.enable = ops->cursor_state.enable;
++ cursor.image.fg_color = par->cursor_state.image.fg_color;
++ cursor.image.bg_color = par->cursor_state.image.bg_color;
++ cursor.image.dx = par->cursor_state.image.dx;
++ cursor.image.dy = par->cursor_state.image.dy;
++ cursor.image.height = par->cursor_state.image.height;
++ cursor.image.width = par->cursor_state.image.width;
++ cursor.hot.x = par->cursor_state.hot.x;
++ cursor.hot.y = par->cursor_state.hot.y;
++ cursor.mask = par->cursor_state.mask;
++ cursor.enable = par->cursor_state.enable;
+ cursor.image.depth = 1;
+ cursor.rop = ROP_XOR;
+
+@@ -393,36 +393,36 @@ static void ud_cursor(struct vc_data *vc
+ if (err)
+ soft_cursor(info, &cursor);
+
+- ops->cursor_reset = 0;
++ par->cursor_reset = 0;
+ }
+
+ static int ud_update_start(struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ int xoffset, yoffset;
+- u32 vyres = GETVYRES(ops->p, info);
+- u32 vxres = GETVXRES(ops->p, info);
++ u32 vyres = GETVYRES(par->p, info);
++ u32 vxres = GETVXRES(par->p, info);
+ int err;
+
+- xoffset = vxres - info->var.xres - ops->var.xoffset;
+- yoffset = vyres - info->var.yres - ops->var.yoffset;
++ xoffset = vxres - info->var.xres - par->var.xoffset;
++ yoffset = vyres - info->var.yres - par->var.yoffset;
+ if (yoffset < 0)
+ yoffset += vyres;
+- ops->var.xoffset = xoffset;
+- ops->var.yoffset = yoffset;
+- err = fb_pan_display(info, &ops->var);
+- ops->var.xoffset = info->var.xoffset;
+- ops->var.yoffset = info->var.yoffset;
+- ops->var.vmode = info->var.vmode;
++ par->var.xoffset = xoffset;
++ par->var.yoffset = yoffset;
++ err = fb_pan_display(info, &par->var);
++ par->var.xoffset = info->var.xoffset;
++ par->var.yoffset = info->var.yoffset;
++ par->var.vmode = info->var.vmode;
+ return err;
+ }
+
+-void fbcon_rotate_ud(struct fbcon_ops *ops)
++void fbcon_rotate_ud(struct fbcon_par *par)
+ {
+- ops->bmove = ud_bmove;
+- ops->clear = ud_clear;
+- ops->putcs = ud_putcs;
+- ops->clear_margins = ud_clear_margins;
+- ops->cursor = ud_cursor;
+- ops->update_start = ud_update_start;
++ par->bmove = ud_bmove;
++ par->clear = ud_clear;
++ par->putcs = ud_putcs;
++ par->clear_margins = ud_clear_margins;
++ par->cursor = ud_cursor;
++ par->update_start = ud_update_start;
+ }
+--- a/drivers/video/fbdev/core/softcursor.c
++++ b/drivers/video/fbdev/core/softcursor.c
+@@ -21,7 +21,7 @@
+
+ int soft_cursor(struct fb_info *info, struct fb_cursor *cursor)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ unsigned int scan_align = info->pixmap.scan_align - 1;
+ unsigned int buf_align = info->pixmap.buf_align - 1;
+ unsigned int i, size, dsize, s_pitch, d_pitch;
+@@ -34,19 +34,19 @@ int soft_cursor(struct fb_info *info, st
+ s_pitch = (cursor->image.width + 7) >> 3;
+ dsize = s_pitch * cursor->image.height;
+
+- if (dsize + sizeof(struct fb_image) != ops->cursor_size) {
+- kfree(ops->cursor_src);
+- ops->cursor_size = dsize + sizeof(struct fb_image);
+-
+- ops->cursor_src = kmalloc(ops->cursor_size, GFP_ATOMIC);
+- if (!ops->cursor_src) {
+- ops->cursor_size = 0;
++ if (dsize + sizeof(struct fb_image) != par->cursor_size) {
++ kfree(par->cursor_src);
++ par->cursor_size = dsize + sizeof(struct fb_image);
++
++ par->cursor_src = kmalloc(par->cursor_size, GFP_ATOMIC);
++ if (!par->cursor_src) {
++ par->cursor_size = 0;
+ return -ENOMEM;
+ }
+ }
+
+- src = ops->cursor_src + sizeof(struct fb_image);
+- image = (struct fb_image *)ops->cursor_src;
++ src = par->cursor_src + sizeof(struct fb_image);
++ image = (struct fb_image *)par->cursor_src;
+ *image = cursor->image;
+ d_pitch = (s_pitch + scan_align) & ~scan_align;
+
+--- a/drivers/video/fbdev/core/tileblit.c
++++ b/drivers/video/fbdev/core/tileblit.c
+@@ -151,34 +151,34 @@ static void tile_cursor(struct vc_data *
+
+ static int tile_update_start(struct fb_info *info)
+ {
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+ int err;
+
+- err = fb_pan_display(info, &ops->var);
+- ops->var.xoffset = info->var.xoffset;
+- ops->var.yoffset = info->var.yoffset;
+- ops->var.vmode = info->var.vmode;
++ err = fb_pan_display(info, &par->var);
++ par->var.xoffset = info->var.xoffset;
++ par->var.yoffset = info->var.yoffset;
++ par->var.vmode = info->var.vmode;
+ return err;
+ }
+
+ void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info)
+ {
+ struct fb_tilemap map;
+- struct fbcon_ops *ops = info->fbcon_par;
++ struct fbcon_par *par = info->fbcon_par;
+
+- ops->bmove = tile_bmove;
+- ops->clear = tile_clear;
+- ops->putcs = tile_putcs;
+- ops->clear_margins = tile_clear_margins;
+- ops->cursor = tile_cursor;
+- ops->update_start = tile_update_start;
++ par->bmove = tile_bmove;
++ par->clear = tile_clear;
++ par->putcs = tile_putcs;
++ par->clear_margins = tile_clear_margins;
++ par->cursor = tile_cursor;
++ par->update_start = tile_update_start;
+
+- if (ops->p) {
++ if (par->p) {
+ map.width = vc->vc_font.width;
+ map.height = vc->vc_font.height;
+ map.depth = 1;
+ map.length = vc->vc_font.charcount;
+- map.data = ops->p->fontdata;
++ map.data = par->p->fontdata;
+ info->tileops->fb_settile(info, &map);
+ }
+ }
--- /dev/null
+From stable+bounces-287900-greg=kroah.com@vger.kernel.org Wed Jul 22 17:03:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 10:48:42 -0400
+Subject: fbcon: Use correct type for vc_resize() return value
+To: stable@vger.kernel.org
+Cc: Jiacheng Yu <yujiacheng3@huawei.com>, Thomas Zimmermann <tzimmermann@suse.de>, Helge Deller <deller@gmx.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722144842.1601797-2-sashal@kernel.org>
+
+From: Jiacheng Yu <yujiacheng3@huawei.com>
+
+[ Upstream commit 84202754fb1727dc3ee87f47104e4162ecc8ba3a ]
+
+The return value of vc_resize() is int, but fbcon_set_disp() stores it
+in an unsigned long variable. While the !ret check happens to work
+correctly by coincidence (negative values become large positive values),
+the types should match. Use int instead.
+
+Eliminates the following W=3 warning:
+
+ drivers/video/fbdev/core/fbcon.c: In function 'fbcon_set_disp':
+ drivers/video/fbdev/core/fbcon.c:1494:14: warning: implicit conversion from 'int' to 'unsigned long' [-Wconversion]
+
+Fixes: af0db3c1f898 ("fbdev: Fix vmalloc out-of-bounds write in fast_imageblit")
+Cc: stable@vger.kernel.org # v6.17+
+Signed-off-by: Jiacheng Yu <yujiacheng3@huawei.com>
+Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
+Signed-off-by: Helge Deller <deller@gmx.de>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/video/fbdev/core/fbcon.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/drivers/video/fbdev/core/fbcon.c
++++ b/drivers/video/fbdev/core/fbcon.c
+@@ -1366,8 +1366,7 @@ static void fbcon_set_disp(struct fb_inf
+ struct vc_data **default_mode, *vc;
+ struct vc_data *svc;
+ struct fbcon_par *par = info->fbcon_par;
+- int rows, cols;
+- unsigned long ret = 0;
++ int rows, cols, ret;
+
+ p = &fb_display[unit];
+
--- /dev/null
+From stable+bounces-281853-greg=kroah.com@vger.kernel.org Tue Jul 21 21:04:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 14:49:06 -0400
+Subject: fpga: dfl-afu: validate DMA mapping length in afu_dma_map_region()
+To: stable@vger.kernel.org
+Cc: Sebastian Alba Vives <sebasjosue84@gmail.com>, Xu Yilun <yilun.xu@intel.com>, Xu Yilun <yilun.xu@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721184906.383124-1-sashal@kernel.org>
+
+From: Sebastian Alba Vives <sebasjosue84@gmail.com>
+
+[ Upstream commit fc3b071a7c8dc0f5d56defddf6e6fd5aaa3e1e27 ]
+
+afu_ioctl_dma_map() accepts a 64-bit length from userspace via
+DFL_FPGA_PORT_DMA_MAP ioctl without an upper bound check. The value
+is passed to afu_dma_pin_pages() where npages is derived as
+length >> PAGE_SHIFT and passed to pin_user_pages_fast() which takes
+int nr_pages, causing implicit truncation if length is very large.
+
+Validate map.length at the ioctl entry point before calling
+afu_dma_map_region(), rejecting values whose page count exceeds
+INT_MAX.
+
+Fixes: fa8dda1edef9 ("fpga: dfl: afu: add DFL_FPGA_PORT_DMA_MAP/UNMAP ioctls support")
+Cc: stable@vger.kernel.org
+Signed-off-by: Sebastian Alba Vives <sebasjosue84@gmail.com>
+Reviewed-by: Xu Yilun <yilun.xu@intel.com>
+Link: https://lore.kernel.org/r/20260518190742.61426-3-sebasjosue84@gmail.com
+Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/fpga/dfl-afu-main.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/fpga/dfl-afu-main.c
++++ b/drivers/fpga/dfl-afu-main.c
+@@ -720,6 +720,9 @@ afu_ioctl_dma_map(struct dfl_feature_pla
+ if (map.argsz < minsz || map.flags)
+ return -EINVAL;
+
++ if (map.length >> PAGE_SHIFT > (u64)INT_MAX)
++ return -EINVAL;
++
+ ret = afu_dma_map_region(pdata, map.user_addr, map.length, &map.iova);
+ if (ret)
+ return ret;
--- /dev/null
+From stable+bounces-290227-greg=kroah.com@vger.kernel.org Wed Jul 29 04:30:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 22:29:52 -0400
+Subject: fs/resctrl: Fix double-add of pseudo-locked region's RMID to free list
+To: stable@vger.kernel.org
+Cc: Reinette Chatre <reinette.chatre@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, stable@kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729022952.1112657-1-sashal@kernel.org>
+
+From: Reinette Chatre <reinette.chatre@intel.com>
+
+[ Upstream commit b9f089723aee892efc77c349ae47a6b452b293c4 ]
+
+A pseudo-locked group's RMID is freed when it is created. On unmount
+rmdir_all_sub() unconditionally frees all RMID of all groups, resulting
+in a double-free of the pseudo-locked group's RMID. The consequence of this
+is that the original free results in the pseudo-locked group's RMID being
+added to the rmid_free_lru linked list and the second free then attempts
+to add the same RMID entry to the rmid_free_lru again.
+
+Do not double-free a pseudo-locked group's RMID.
+
+Fixes: e0bdfe8e36f3 ("x86/intel_rdt: Support creation/removal of pseudo-locked region")
+Signed-off-by: Reinette Chatre <reinette.chatre@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Cc: <stable@kernel.org>
+Link: https://patch.msgid.link/551432dd7e624a862b8e58314c38aaba0afff3e9.1783377598.git.reinette.chatre@intel.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/x86/kernel/cpu/resctrl/rdtgroup.c | 12 +++++++-----
+ 1 file changed, 7 insertions(+), 5 deletions(-)
+
+--- a/arch/x86/kernel/cpu/resctrl/rdtgroup.c
++++ b/arch/x86/kernel/cpu/resctrl/rdtgroup.c
+@@ -2922,10 +2922,6 @@ static void rmdir_all_sub(void)
+ if (rdtgrp == &rdtgroup_default)
+ continue;
+
+- if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP ||
+- rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED)
+- rdtgroup_pseudo_lock_remove(rdtgrp);
+-
+ /*
+ * Give any CPUs back to the default group. We cannot copy
+ * cpu_online_mask because a CPU might have executed the
+@@ -2934,7 +2930,13 @@ static void rmdir_all_sub(void)
+ cpumask_or(&rdtgroup_default.cpu_mask,
+ &rdtgroup_default.cpu_mask, &rdtgrp->cpu_mask);
+
+- free_rmid(rdtgrp->closid, rdtgrp->mon.rmid);
++ if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP ||
++ rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED) {
++ rdtgroup_pseudo_lock_remove(rdtgrp);
++ } else {
++ /* Pseudo-locked group's RMID is freed during setup. */
++ free_rmid(rdtgrp->closid, rdtgrp->mon.rmid);
++ }
+
+ kernfs_remove(rdtgrp->kn);
+ list_del(&rdtgrp->rdtgroup_list);
--- /dev/null
+From stable+bounces-290047-greg=kroah.com@vger.kernel.org Tue Jul 28 18:13:03 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 11:43:27 -0400
+Subject: gpio: mt7621: avoid corruption of shared interrupt trigger state
+To: stable@vger.kernel.org
+Cc: Sergio Paracuellos <sergio.paracuellos@gmail.com>, Sashiko <sashiko-bot@kernel.org>, Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728154327.4192271-1-sashal@kernel.org>
+
+From: Sergio Paracuellos <sergio.paracuellos@gmail.com>
+
+[ Upstream commit 1781172526d1092323af443fa03f00e6de560401 ]
+
+The bank-shared fields like 'rising' and 'falling' are modified using
+non-atomic read-modify-write operations. Since every gpio chip instance
+represents an entire bank of 32 pins, if 'mediatek_gpio_irq_type()' is
+called concurrently for different IRQs on the same bank a possible overwrite
+of each other's configuration is possible. Thus, protect this state with
+'gpio_generic_lock_irqsave' lock in the same way it is handled in irp_chip
+'mediatek_gpio_irq_mask()' and 'mediatek_gpio_irq_unmask()' callbacks.
+
+Cc: stable@vger.kernel.org
+Reported-by: Sashiko <sashiko-bot@kernel.org>
+Fixes: 4ba9c3afda41 ("gpio: mt7621: Add a driver for MT7621")
+Signed-off-by: Sergio Paracuellos <sergio.paracuellos@gmail.com>
+Link: https://patch.msgid.link/20260626060112.2498324-2-sergio.paracuellos@gmail.com
+Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
+[ Changed `guard(gpio_generic_lock_irqsave)(&rg->chip)` to `guard(spinlock_irqsave)(&rg->lock)` as the generic GPIO chip lock does not exist in this tree. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpio/gpio-mt7621.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/gpio/gpio-mt7621.c
++++ b/drivers/gpio/gpio-mt7621.c
+@@ -156,6 +156,8 @@ mediatek_gpio_irq_type(struct irq_data *
+ int pin = d->hwirq;
+ u32 mask = BIT(pin);
+
++ guard(spinlock_irqsave)(&rg->lock);
++
+ if (type == IRQ_TYPE_PROBE) {
+ if ((rg->rising | rg->falling |
+ rg->hlevel | rg->llevel) & mask)
--- /dev/null
+From stable+bounces-288440-greg=kroah.com@vger.kernel.org Thu Jul 23 19:08:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 12:54:43 -0400
+Subject: gpu/buddy: bail out of try_harder when alignment cannot be honoured
+To: stable@vger.kernel.org
+Cc: "Arunpravin Paneer Selvam" <Arunpravin.PaneerSelvam@amd.com>, "Christian König" <christian.koenig@amd.com>, "Matthew Auld" <matthew.auld@intel.com>, "Timur Kristóf" <timur.kristof@gmail.com>, "John Olender" <john.olender@gmail.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260723165444.3392251-2-sashal@kernel.org>
+
+From: Arunpravin Paneer Selvam <Arunpravin.PaneerSelvam@amd.com>
+
+[ Upstream commit 56bc6384314fb9ae98975fb2af8b143097ede3dc ]
+
+The try_harder contiguous fallback could return a range whose start
+offset did not match the caller's min_block_size. When a candidate's
+start is misaligned, realign it: free the misaligned run and reallocate
+exactly @size at the next lower min_block_size boundary. This keeps the
+returned size unchanged with no surplus to trim, and rejects the request
+only when no aligned candidate fits.
+
+v2: align misaligned candidates down to min_block_size instead of
+ bailing out, for both the RHS and LHS paths (Matthew).
+
+Fixes: 0a1844bf0b53 ("drm/buddy: Improve contiguous memory allocation")
+Suggested-by: Christian König <christian.koenig@amd.com>
+Cc: Matthew Auld <matthew.auld@intel.com>
+Cc: Christian König <christian.koenig@amd.com>
+Cc: Timur Kristóf <timur.kristof@gmail.com>
+Cc: stable@vger.kernel.org
+Reviewed-by: Matthew Auld <matthew.auld@intel.com>
+Tested-by: John Olender <john.olender@gmail.com>
+Signed-off-by: Arunpravin Paneer Selvam <Arunpravin.PaneerSelvam@amd.com>
+Link: https://patch.msgid.link/20260709131050.1022759-1-Arunpravin.PaneerSelvam@amd.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/buddy.c | 65 ++++++++++++++++++++++++++++++++++++----------------
+ 1 file changed, 45 insertions(+), 20 deletions(-)
+
+--- a/drivers/gpu/buddy.c
++++ b/drivers/gpu/buddy.c
+@@ -901,22 +901,30 @@ static int __gpu_buddy_alloc_range(struc
+ blocks, total_allocated_on_err);
+ }
+
++static int __alloc_contig_aligned_retry(struct gpu_buddy *mm,
++ u64 unaligned_offset,
++ u64 size,
++ u64 min_block_size,
++ struct list_head *blocks)
++{
++ u64 aligned_offset = round_down(unaligned_offset, min_block_size);
++
++ return __gpu_buddy_alloc_range(mm, aligned_offset, size, NULL, blocks);
++}
++
+ static int __alloc_contig_try_harder(struct gpu_buddy *mm,
+ u64 size,
+ u64 min_block_size,
+ struct list_head *blocks)
+ {
+- u64 rhs_offset, lhs_offset, lhs_size, filled;
++ u64 rhs_offset, lhs_offset, filled;
+ struct gpu_buddy_block *block;
+ unsigned int tree, order;
+- LIST_HEAD(blocks_lhs);
+- unsigned long pages;
+ u64 modify_size;
+ int err;
+
+ modify_size = rounddown_pow_of_two(size);
+- pages = modify_size >> ilog2(mm->chunk_size);
+- order = fls(pages) - 1;
++ order = ilog2(modify_size) - ilog2(mm->chunk_size);
+ if (order == 0)
+ return -ENOSPC;
+
+@@ -932,31 +940,48 @@ static int __alloc_contig_try_harder(str
+ while (iter) {
+ block = rbtree_get_free_block(iter);
+
+- /* Allocate blocks traversing RHS */
+ rhs_offset = gpu_buddy_block_offset(block);
++
++ /* Allocate blocks traversing RHS */
+ err = __gpu_buddy_alloc_range(mm, rhs_offset, size,
+ &filled, blocks);
+- if (!err || err != -ENOSPC)
++ if (err && err != -ENOSPC)
+ return err;
+-
+- lhs_size = max((size - filled), min_block_size);
+- if (!IS_ALIGNED(lhs_size, min_block_size))
+- lhs_size = round_up(lhs_size, min_block_size);
+-
+- /* Allocate blocks traversing LHS */
+- lhs_offset = gpu_buddy_block_offset(block) - lhs_size;
+- err = __gpu_buddy_alloc_range(mm, lhs_offset, lhs_size,
+- NULL, &blocks_lhs);
++ if (!err && IS_ALIGNED(rhs_offset, min_block_size))
++ return 0;
+ if (!err) {
+- list_splice(&blocks_lhs, blocks);
++ /* Allocate the unaligned RHS offset using round_down */
++ gpu_buddy_free_list_internal(mm, blocks);
++ err = __alloc_contig_aligned_retry(mm, rhs_offset,
++ size,
++ min_block_size,
++ blocks);
++ if (!err)
++ return 0;
++ if (err != -ENOSPC) {
++ gpu_buddy_free_list_internal(mm, blocks);
++ return err;
++ }
++ goto next;
++ }
++
++ if (size - filled > rhs_offset)
++ goto next;
++
++ lhs_offset = rhs_offset - (size - filled);
++
++ /* Allocate the unaligned LHS offset using round_down */
++ gpu_buddy_free_list_internal(mm, blocks);
++ err = __alloc_contig_aligned_retry(mm, lhs_offset, size,
++ min_block_size, blocks);
++ if (!err)
+ return 0;
+- } else if (err != -ENOSPC) {
++ if (err != -ENOSPC) {
+ gpu_buddy_free_list_internal(mm, blocks);
+ return err;
+ }
+- /* Free blocks for the next iteration */
++next:
+ gpu_buddy_free_list_internal(mm, blocks);
+-
+ iter = rb_prev(iter);
+ }
+ }
--- /dev/null
+From stable+bounces-288441-greg=kroah.com@vger.kernel.org Thu Jul 23 18:58:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 12:54:42 -0400
+Subject: gpu: Move DRM buddy allocator one level up (part two)
+To: stable@vger.kernel.org
+Cc: Joel Fernandes <joelagnelf@nvidia.com>, Dave Airlie <airlied@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723165444.3392251-1-sashal@kernel.org>
+
+From: Joel Fernandes <joelagnelf@nvidia.com>
+
+[ Upstream commit ba110db8e1bc206c13fd7d985e79b033f53bfdea ]
+
+Move the DRM buddy allocator one level up so that it can be used by GPU
+drivers (example, nova-core) that have usecases other than DRM (such as
+VFIO vGPU support). Modify the API, structures and Kconfigs to use
+"gpu_buddy" terminology. Adapt the drivers and tests to use the new API.
+
+The commit cannot be split due to bisectability, however no functional
+change is intended. Verified by running K-UNIT tests and build tested
+various configurations.
+
+Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
+Reviewed-by: Dave Airlie <airlied@redhat.com>
+[airlied: I've split this into two so git can find copies easier.
+I've also just nuked drm_random library, that stuff needs to be done
+elsewhere and only the buddy tests seem to be using it].
+Signed-off-by: Dave Airlie <airlied@redhat.com>
+Stable-dep-of: 56bc6384314f ("gpu/buddy: bail out of try_harder when alignment cannot be honoured")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ Documentation/gpu/drm-mm.rst | 10
+ drivers/gpu/Kconfig | 13
+ drivers/gpu/Makefile | 3
+ drivers/gpu/buddy.c | 1322 +++++++++++++++++++
+ drivers/gpu/drm/Kconfig | 6
+ drivers/gpu/drm/Makefile | 1
+ drivers/gpu/drm/amd/amdgpu/amdgpu_res_cursor.h | 12
+ drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c | 77 -
+ drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h | 18
+ drivers/gpu/drm/drm_buddy.c | 1279 ------------------
+ drivers/gpu/drm/i915/gem/i915_gem_ttm.c | 2
+ drivers/gpu/drm/i915/i915_scatterlist.c | 10
+ drivers/gpu/drm/i915/i915_ttm_buddy_manager.c | 55
+ drivers/gpu/drm/i915/i915_ttm_buddy_manager.h | 4
+ drivers/gpu/drm/i915/selftests/intel_memory_region.c | 20
+ drivers/gpu/drm/lib/drm_random.c | 43
+ drivers/gpu/drm/lib/drm_random.h | 28
+ drivers/gpu/drm/tests/Makefile | 1
+ drivers/gpu/drm/tests/drm_buddy_test.c | 778 -----------
+ drivers/gpu/drm/tests/drm_exec_test.c | 2
+ drivers/gpu/drm/tests/drm_mm_test.c | 2
+ drivers/gpu/drm/ttm/tests/ttm_bo_validate_test.c | 4
+ drivers/gpu/drm/ttm/tests/ttm_mock_manager.c | 18
+ drivers/gpu/drm/ttm/tests/ttm_mock_manager.h | 4
+ drivers/gpu/drm/xe/xe_res_cursor.h | 34
+ drivers/gpu/drm/xe/xe_ttm_vram_mgr.c | 73 -
+ drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h | 4
+ drivers/gpu/tests/Makefile | 4
+ drivers/gpu/tests/gpu_buddy_test.c | 778 +++++++++++
+ drivers/gpu/tests/gpu_random.c | 43
+ drivers/gpu/tests/gpu_random.h | 28
+ drivers/video/Kconfig | 1
+ include/drm/drm_buddy.h | 174 --
+ include/linux/gpu_buddy.h | 188 ++
+ 34 files changed, 2576 insertions(+), 2463 deletions(-)
+ create mode 100644 drivers/gpu/Kconfig
+ create mode 100644 drivers/gpu/buddy.c
+ create mode 100644 drivers/gpu/tests/Makefile
+ rename drivers/gpu/{drm/tests/drm_buddy_test.c => tests/gpu_buddy_test.c} (70%)
+ rename drivers/gpu/{drm/lib/drm_random.c => tests/gpu_random.c} (58%)
+ rename drivers/gpu/{drm/lib/drm_random.h => tests/gpu_random.h} (53%)
+ create mode 100644 include/linux/gpu_buddy.h
+
+--- a/Documentation/gpu/drm-mm.rst
++++ b/Documentation/gpu/drm-mm.rst
+@@ -509,8 +509,14 @@ DRM GPUVM Function References
+ DRM Buddy Allocator
+ ===================
+
+-DRM Buddy Function References
+------------------------------
++Buddy Allocator Function References (GPU buddy)
++-----------------------------------------------
++
++.. kernel-doc:: drivers/gpu/buddy.c
++ :export:
++
++DRM Buddy Specific Logging Function References
++----------------------------------------------
+
+ .. kernel-doc:: drivers/gpu/drm/drm_buddy.c
+ :export:
+--- /dev/null
++++ b/drivers/gpu/Kconfig
+@@ -0,0 +1,13 @@
++# SPDX-License-Identifier: GPL-2.0
++
++config GPU_BUDDY
++ bool
++ help
++ A page based buddy allocator for GPU memory.
++
++config GPU_BUDDY_KUNIT_TEST
++ tristate "KUnit tests for GPU buddy allocator" if !KUNIT_ALL_TESTS
++ depends on GPU_BUDDY && KUNIT
++ default KUNIT_ALL_TESTS
++ help
++ KUnit tests for the GPU buddy allocator.
+--- a/drivers/gpu/Makefile
++++ b/drivers/gpu/Makefile
+@@ -2,6 +2,7 @@
+ # drm/tegra depends on host1x, so if both drivers are built-in care must be
+ # taken to initialize them in the correct order. Link order is the only way
+ # to ensure this currently.
+-obj-y += host1x/ drm/ vga/
++obj-y += host1x/ drm/ vga/ tests/
+ obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/
+ obj-$(CONFIG_TRACE_GPU_MEM) += trace/
++obj-$(CONFIG_GPU_BUDDY) += buddy.o
+--- /dev/null
++++ b/drivers/gpu/buddy.c
+@@ -0,0 +1,1322 @@
++// SPDX-License-Identifier: MIT
++/*
++ * Copyright © 2021 Intel Corporation
++ */
++
++#include <kunit/test-bug.h>
++
++#include <linux/export.h>
++#include <linux/kmemleak.h>
++#include <linux/module.h>
++#include <linux/sizes.h>
++
++#include <linux/gpu_buddy.h>
++
++static struct kmem_cache *slab_blocks;
++
++static struct gpu_buddy_block *gpu_block_alloc(struct gpu_buddy *mm,
++ struct gpu_buddy_block *parent,
++ unsigned int order,
++ u64 offset)
++{
++ struct gpu_buddy_block *block;
++
++ BUG_ON(order > GPU_BUDDY_MAX_ORDER);
++
++ block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
++ if (!block)
++ return NULL;
++
++ block->header = offset;
++ block->header |= order;
++ block->parent = parent;
++
++ RB_CLEAR_NODE(&block->rb);
++
++ BUG_ON(block->header & GPU_BUDDY_HEADER_UNUSED);
++ return block;
++}
++
++static void gpu_block_free(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ kmem_cache_free(slab_blocks, block);
++}
++
++static enum gpu_buddy_free_tree
++get_block_tree(struct gpu_buddy_block *block)
++{
++ return gpu_buddy_block_is_clear(block) ?
++ GPU_BUDDY_CLEAR_TREE : GPU_BUDDY_DIRTY_TREE;
++}
++
++static struct gpu_buddy_block *
++rbtree_get_free_block(const struct rb_node *node)
++{
++ return node ? rb_entry(node, struct gpu_buddy_block, rb) : NULL;
++}
++
++static struct gpu_buddy_block *
++rbtree_last_free_block(struct rb_root *root)
++{
++ return rbtree_get_free_block(rb_last(root));
++}
++
++static bool rbtree_is_empty(struct rb_root *root)
++{
++ return RB_EMPTY_ROOT(root);
++}
++
++static bool gpu_buddy_block_offset_less(const struct gpu_buddy_block *block,
++ const struct gpu_buddy_block *node)
++{
++ return gpu_buddy_block_offset(block) < gpu_buddy_block_offset(node);
++}
++
++static bool rbtree_block_offset_less(struct rb_node *block,
++ const struct rb_node *node)
++{
++ return gpu_buddy_block_offset_less(rbtree_get_free_block(block),
++ rbtree_get_free_block(node));
++}
++
++static void rbtree_insert(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block,
++ enum gpu_buddy_free_tree tree)
++{
++ rb_add(&block->rb,
++ &mm->free_trees[tree][gpu_buddy_block_order(block)],
++ rbtree_block_offset_less);
++}
++
++static void rbtree_remove(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ unsigned int order = gpu_buddy_block_order(block);
++ enum gpu_buddy_free_tree tree;
++ struct rb_root *root;
++
++ tree = get_block_tree(block);
++ root = &mm->free_trees[tree][order];
++
++ rb_erase(&block->rb, root);
++ RB_CLEAR_NODE(&block->rb);
++}
++
++static void clear_reset(struct gpu_buddy_block *block)
++{
++ block->header &= ~GPU_BUDDY_HEADER_CLEAR;
++}
++
++static void mark_cleared(struct gpu_buddy_block *block)
++{
++ block->header |= GPU_BUDDY_HEADER_CLEAR;
++}
++
++static void mark_allocated(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ block->header &= ~GPU_BUDDY_HEADER_STATE;
++ block->header |= GPU_BUDDY_ALLOCATED;
++
++ rbtree_remove(mm, block);
++}
++
++static void mark_free(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ enum gpu_buddy_free_tree tree;
++
++ block->header &= ~GPU_BUDDY_HEADER_STATE;
++ block->header |= GPU_BUDDY_FREE;
++
++ tree = get_block_tree(block);
++ rbtree_insert(mm, block, tree);
++}
++
++static void mark_split(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ block->header &= ~GPU_BUDDY_HEADER_STATE;
++ block->header |= GPU_BUDDY_SPLIT;
++
++ rbtree_remove(mm, block);
++}
++
++static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
++{
++ return s1 <= e2 && e1 >= s2;
++}
++
++static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
++{
++ return s1 <= s2 && e1 >= e2;
++}
++
++static struct gpu_buddy_block *
++__get_buddy(struct gpu_buddy_block *block)
++{
++ struct gpu_buddy_block *parent;
++
++ parent = block->parent;
++ if (!parent)
++ return NULL;
++
++ if (parent->left == block)
++ return parent->right;
++
++ return parent->left;
++}
++
++static unsigned int __gpu_buddy_free(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block,
++ bool force_merge)
++{
++ struct gpu_buddy_block *parent;
++ unsigned int order;
++
++ while ((parent = block->parent)) {
++ struct gpu_buddy_block *buddy;
++
++ buddy = __get_buddy(block);
++
++ if (!gpu_buddy_block_is_free(buddy))
++ break;
++
++ if (!force_merge) {
++ /*
++ * Check the block and its buddy clear state and exit
++ * the loop if they both have the dissimilar state.
++ */
++ if (gpu_buddy_block_is_clear(block) !=
++ gpu_buddy_block_is_clear(buddy))
++ break;
++
++ if (gpu_buddy_block_is_clear(block))
++ mark_cleared(parent);
++ }
++
++ rbtree_remove(mm, buddy);
++ if (force_merge && gpu_buddy_block_is_clear(buddy))
++ mm->clear_avail -= gpu_buddy_block_size(mm, buddy);
++
++ gpu_block_free(mm, block);
++ gpu_block_free(mm, buddy);
++
++ block = parent;
++ }
++
++ order = gpu_buddy_block_order(block);
++ mark_free(mm, block);
++
++ return order;
++}
++
++static int __force_merge(struct gpu_buddy *mm,
++ u64 start,
++ u64 end,
++ unsigned int min_order)
++{
++ unsigned int tree, order;
++ int i;
++
++ if (!min_order)
++ return -ENOMEM;
++
++ if (min_order > mm->max_order)
++ return -EINVAL;
++
++ for_each_free_tree(tree) {
++ for (i = min_order - 1; i >= 0; i--) {
++ struct rb_node *iter = rb_last(&mm->free_trees[tree][i]);
++
++ while (iter) {
++ struct gpu_buddy_block *block, *buddy;
++ u64 block_start, block_end;
++
++ block = rbtree_get_free_block(iter);
++ iter = rb_prev(iter);
++
++ if (!block || !block->parent)
++ continue;
++
++ block_start = gpu_buddy_block_offset(block);
++ block_end = block_start + gpu_buddy_block_size(mm, block) - 1;
++
++ if (!contains(start, end, block_start, block_end))
++ continue;
++
++ buddy = __get_buddy(block);
++ if (!gpu_buddy_block_is_free(buddy))
++ continue;
++
++ WARN_ON(gpu_buddy_block_is_clear(block) ==
++ gpu_buddy_block_is_clear(buddy));
++
++ /*
++ * Advance to the next node when the current node is the buddy,
++ * as freeing the block will also remove its buddy from the tree.
++ */
++ if (iter == &buddy->rb)
++ iter = rb_prev(iter);
++
++ rbtree_remove(mm, block);
++ if (gpu_buddy_block_is_clear(block))
++ mm->clear_avail -= gpu_buddy_block_size(mm, block);
++
++ order = __gpu_buddy_free(mm, block, true);
++ if (order >= min_order)
++ return 0;
++ }
++ }
++ }
++
++ return -ENOMEM;
++}
++
++/**
++ * gpu_buddy_init - init memory manager
++ *
++ * @mm: GPU buddy manager to initialize
++ * @size: size in bytes to manage
++ * @chunk_size: minimum page size in bytes for our allocations
++ *
++ * Initializes the memory manager and its resources.
++ *
++ * Returns:
++ * 0 on success, error code on failure.
++ */
++int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size)
++{
++ unsigned int i, j, root_count = 0;
++ u64 offset = 0;
++
++ if (size < chunk_size)
++ return -EINVAL;
++
++ if (chunk_size < SZ_4K)
++ return -EINVAL;
++
++ if (!is_power_of_2(chunk_size))
++ return -EINVAL;
++
++ size = round_down(size, chunk_size);
++
++ mm->size = size;
++ mm->avail = size;
++ mm->clear_avail = 0;
++ mm->chunk_size = chunk_size;
++ mm->max_order = ilog2(size) - ilog2(chunk_size);
++
++ BUG_ON(mm->max_order > GPU_BUDDY_MAX_ORDER);
++
++ mm->free_trees = kmalloc_array(GPU_BUDDY_MAX_FREE_TREES,
++ sizeof(*mm->free_trees),
++ GFP_KERNEL);
++ if (!mm->free_trees)
++ return -ENOMEM;
++
++ for_each_free_tree(i) {
++ mm->free_trees[i] = kmalloc_array(mm->max_order + 1,
++ sizeof(struct rb_root),
++ GFP_KERNEL);
++ if (!mm->free_trees[i])
++ goto out_free_tree;
++
++ for (j = 0; j <= mm->max_order; ++j)
++ mm->free_trees[i][j] = RB_ROOT;
++ }
++
++ mm->n_roots = hweight64(size);
++
++ mm->roots = kmalloc_array(mm->n_roots,
++ sizeof(struct gpu_buddy_block *),
++ GFP_KERNEL);
++ if (!mm->roots)
++ goto out_free_tree;
++
++ /*
++ * Split into power-of-two blocks, in case we are given a size that is
++ * not itself a power-of-two.
++ */
++ do {
++ struct gpu_buddy_block *root;
++ unsigned int order;
++ u64 root_size;
++
++ order = ilog2(size) - ilog2(chunk_size);
++ root_size = chunk_size << order;
++
++ root = gpu_block_alloc(mm, NULL, order, offset);
++ if (!root)
++ goto out_free_roots;
++
++ mark_free(mm, root);
++
++ BUG_ON(root_count > mm->max_order);
++ BUG_ON(gpu_buddy_block_size(mm, root) < chunk_size);
++
++ mm->roots[root_count] = root;
++
++ offset += root_size;
++ size -= root_size;
++ root_count++;
++ } while (size);
++
++ return 0;
++
++out_free_roots:
++ while (root_count--)
++ gpu_block_free(mm, mm->roots[root_count]);
++ kfree(mm->roots);
++out_free_tree:
++ while (i--)
++ kfree(mm->free_trees[i]);
++ kfree(mm->free_trees);
++ return -ENOMEM;
++}
++EXPORT_SYMBOL(gpu_buddy_init);
++
++/**
++ * gpu_buddy_fini - tear down the memory manager
++ *
++ * @mm: GPU buddy manager to free
++ *
++ * Cleanup memory manager resources and the freetree
++ */
++void gpu_buddy_fini(struct gpu_buddy *mm)
++{
++ u64 root_size, size, start;
++ unsigned int order;
++ int i;
++
++ size = mm->size;
++
++ for (i = 0; i < mm->n_roots; ++i) {
++ order = ilog2(size) - ilog2(mm->chunk_size);
++ start = gpu_buddy_block_offset(mm->roots[i]);
++ __force_merge(mm, start, start + size, order);
++
++ if (WARN_ON(!gpu_buddy_block_is_free(mm->roots[i])))
++ kunit_fail_current_test("buddy_fini() root");
++
++ gpu_block_free(mm, mm->roots[i]);
++
++ root_size = mm->chunk_size << order;
++ size -= root_size;
++ }
++
++ WARN_ON(mm->avail != mm->size);
++
++ for_each_free_tree(i)
++ kfree(mm->free_trees[i]);
++ kfree(mm->free_trees);
++ kfree(mm->roots);
++}
++EXPORT_SYMBOL(gpu_buddy_fini);
++
++static int split_block(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ unsigned int block_order = gpu_buddy_block_order(block) - 1;
++ u64 offset = gpu_buddy_block_offset(block);
++
++ BUG_ON(!gpu_buddy_block_is_free(block));
++ BUG_ON(!gpu_buddy_block_order(block));
++
++ block->left = gpu_block_alloc(mm, block, block_order, offset);
++ if (!block->left)
++ return -ENOMEM;
++
++ block->right = gpu_block_alloc(mm, block, block_order,
++ offset + (mm->chunk_size << block_order));
++ if (!block->right) {
++ gpu_block_free(mm, block->left);
++ return -ENOMEM;
++ }
++
++ mark_split(mm, block);
++
++ if (gpu_buddy_block_is_clear(block)) {
++ mark_cleared(block->left);
++ mark_cleared(block->right);
++ clear_reset(block);
++ }
++
++ mark_free(mm, block->left);
++ mark_free(mm, block->right);
++
++ return 0;
++}
++
++/**
++ * gpu_get_buddy - get buddy address
++ *
++ * @block: GPU buddy block
++ *
++ * Returns the corresponding buddy block for @block, or NULL
++ * if this is a root block and can't be merged further.
++ * Requires some kind of locking to protect against
++ * any concurrent allocate and free operations.
++ */
++struct gpu_buddy_block *
++gpu_get_buddy(struct gpu_buddy_block *block)
++{
++ return __get_buddy(block);
++}
++EXPORT_SYMBOL(gpu_get_buddy);
++
++/**
++ * gpu_buddy_reset_clear - reset blocks clear state
++ *
++ * @mm: GPU buddy manager
++ * @is_clear: blocks clear state
++ *
++ * Reset the clear state based on @is_clear value for each block
++ * in the freetree.
++ */
++void gpu_buddy_reset_clear(struct gpu_buddy *mm, bool is_clear)
++{
++ enum gpu_buddy_free_tree src_tree, dst_tree;
++ u64 root_size, size, start;
++ unsigned int order;
++ int i;
++
++ size = mm->size;
++ for (i = 0; i < mm->n_roots; ++i) {
++ order = ilog2(size) - ilog2(mm->chunk_size);
++ start = gpu_buddy_block_offset(mm->roots[i]);
++ __force_merge(mm, start, start + size, order);
++
++ root_size = mm->chunk_size << order;
++ size -= root_size;
++ }
++
++ src_tree = is_clear ? GPU_BUDDY_DIRTY_TREE : GPU_BUDDY_CLEAR_TREE;
++ dst_tree = is_clear ? GPU_BUDDY_CLEAR_TREE : GPU_BUDDY_DIRTY_TREE;
++
++ for (i = 0; i <= mm->max_order; ++i) {
++ struct rb_root *root = &mm->free_trees[src_tree][i];
++ struct gpu_buddy_block *block, *tmp;
++
++ rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
++ rbtree_remove(mm, block);
++ if (is_clear) {
++ mark_cleared(block);
++ mm->clear_avail += gpu_buddy_block_size(mm, block);
++ } else {
++ clear_reset(block);
++ mm->clear_avail -= gpu_buddy_block_size(mm, block);
++ }
++
++ rbtree_insert(mm, block, dst_tree);
++ }
++ }
++}
++EXPORT_SYMBOL(gpu_buddy_reset_clear);
++
++/**
++ * gpu_buddy_free_block - free a block
++ *
++ * @mm: GPU buddy manager
++ * @block: block to be freed
++ */
++void gpu_buddy_free_block(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ BUG_ON(!gpu_buddy_block_is_allocated(block));
++ mm->avail += gpu_buddy_block_size(mm, block);
++ if (gpu_buddy_block_is_clear(block))
++ mm->clear_avail += gpu_buddy_block_size(mm, block);
++
++ __gpu_buddy_free(mm, block, false);
++}
++EXPORT_SYMBOL(gpu_buddy_free_block);
++
++static void __gpu_buddy_free_list(struct gpu_buddy *mm,
++ struct list_head *objects,
++ bool mark_clear,
++ bool mark_dirty)
++{
++ struct gpu_buddy_block *block, *on;
++
++ WARN_ON(mark_dirty && mark_clear);
++
++ list_for_each_entry_safe(block, on, objects, link) {
++ if (mark_clear)
++ mark_cleared(block);
++ else if (mark_dirty)
++ clear_reset(block);
++ gpu_buddy_free_block(mm, block);
++ cond_resched();
++ }
++ INIT_LIST_HEAD(objects);
++}
++
++static void gpu_buddy_free_list_internal(struct gpu_buddy *mm,
++ struct list_head *objects)
++{
++ /*
++ * Don't touch the clear/dirty bit, since allocation is still internal
++ * at this point. For example we might have just failed part of the
++ * allocation.
++ */
++ __gpu_buddy_free_list(mm, objects, false, false);
++}
++
++/**
++ * gpu_buddy_free_list - free blocks
++ *
++ * @mm: GPU buddy manager
++ * @objects: input list head to free blocks
++ * @flags: optional flags like GPU_BUDDY_CLEARED
++ */
++void gpu_buddy_free_list(struct gpu_buddy *mm,
++ struct list_head *objects,
++ unsigned int flags)
++{
++ bool mark_clear = flags & GPU_BUDDY_CLEARED;
++
++ __gpu_buddy_free_list(mm, objects, mark_clear, !mark_clear);
++}
++EXPORT_SYMBOL(gpu_buddy_free_list);
++
++static bool block_incompatible(struct gpu_buddy_block *block, unsigned int flags)
++{
++ bool needs_clear = flags & GPU_BUDDY_CLEAR_ALLOCATION;
++
++ return needs_clear != gpu_buddy_block_is_clear(block);
++}
++
++static struct gpu_buddy_block *
++__alloc_range_bias(struct gpu_buddy *mm,
++ u64 start, u64 end,
++ unsigned int order,
++ unsigned long flags,
++ bool fallback)
++{
++ u64 req_size = mm->chunk_size << order;
++ struct gpu_buddy_block *block;
++ struct gpu_buddy_block *buddy;
++ LIST_HEAD(dfs);
++ int err;
++ int i;
++
++ end = end - 1;
++
++ for (i = 0; i < mm->n_roots; ++i)
++ list_add_tail(&mm->roots[i]->tmp_link, &dfs);
++
++ do {
++ u64 block_start;
++ u64 block_end;
++
++ block = list_first_entry_or_null(&dfs,
++ struct gpu_buddy_block,
++ tmp_link);
++ if (!block)
++ break;
++
++ list_del(&block->tmp_link);
++
++ if (gpu_buddy_block_order(block) < order)
++ continue;
++
++ block_start = gpu_buddy_block_offset(block);
++ block_end = block_start + gpu_buddy_block_size(mm, block) - 1;
++
++ if (!overlaps(start, end, block_start, block_end))
++ continue;
++
++ if (gpu_buddy_block_is_allocated(block))
++ continue;
++
++ if (block_start < start || block_end > end) {
++ u64 adjusted_start = max(block_start, start);
++ u64 adjusted_end = min(block_end, end);
++
++ if (round_down(adjusted_end + 1, req_size) <=
++ round_up(adjusted_start, req_size))
++ continue;
++ }
++
++ if (!fallback && block_incompatible(block, flags))
++ continue;
++
++ if (contains(start, end, block_start, block_end) &&
++ order == gpu_buddy_block_order(block)) {
++ /*
++ * Find the free block within the range.
++ */
++ if (gpu_buddy_block_is_free(block))
++ return block;
++
++ continue;
++ }
++
++ if (!gpu_buddy_block_is_split(block)) {
++ err = split_block(mm, block);
++ if (unlikely(err))
++ goto err_undo;
++ }
++
++ list_add(&block->right->tmp_link, &dfs);
++ list_add(&block->left->tmp_link, &dfs);
++ } while (1);
++
++ return ERR_PTR(-ENOSPC);
++
++err_undo:
++ /*
++ * We really don't want to leave around a bunch of split blocks, since
++ * bigger is better, so make sure we merge everything back before we
++ * free the allocated blocks.
++ */
++ buddy = __get_buddy(block);
++ if (buddy &&
++ (gpu_buddy_block_is_free(block) &&
++ gpu_buddy_block_is_free(buddy)))
++ __gpu_buddy_free(mm, block, false);
++ return ERR_PTR(err);
++}
++
++static struct gpu_buddy_block *
++__gpu_buddy_alloc_range_bias(struct gpu_buddy *mm,
++ u64 start, u64 end,
++ unsigned int order,
++ unsigned long flags)
++{
++ struct gpu_buddy_block *block;
++ bool fallback = false;
++
++ block = __alloc_range_bias(mm, start, end, order,
++ flags, fallback);
++ if (IS_ERR(block))
++ return __alloc_range_bias(mm, start, end, order,
++ flags, !fallback);
++
++ return block;
++}
++
++static struct gpu_buddy_block *
++get_maxblock(struct gpu_buddy *mm,
++ unsigned int order,
++ enum gpu_buddy_free_tree tree)
++{
++ struct gpu_buddy_block *max_block = NULL, *block = NULL;
++ struct rb_root *root;
++ unsigned int i;
++
++ for (i = order; i <= mm->max_order; ++i) {
++ root = &mm->free_trees[tree][i];
++ block = rbtree_last_free_block(root);
++ if (!block)
++ continue;
++
++ if (!max_block) {
++ max_block = block;
++ continue;
++ }
++
++ if (gpu_buddy_block_offset(block) >
++ gpu_buddy_block_offset(max_block)) {
++ max_block = block;
++ }
++ }
++
++ return max_block;
++}
++
++static struct gpu_buddy_block *
++alloc_from_freetree(struct gpu_buddy *mm,
++ unsigned int order,
++ unsigned long flags)
++{
++ struct gpu_buddy_block *block = NULL;
++ struct rb_root *root;
++ enum gpu_buddy_free_tree tree;
++ unsigned int tmp;
++ int err;
++
++ tree = (flags & GPU_BUDDY_CLEAR_ALLOCATION) ?
++ GPU_BUDDY_CLEAR_TREE : GPU_BUDDY_DIRTY_TREE;
++
++ if (flags & GPU_BUDDY_TOPDOWN_ALLOCATION) {
++ block = get_maxblock(mm, order, tree);
++ if (block)
++ /* Store the obtained block order */
++ tmp = gpu_buddy_block_order(block);
++ } else {
++ for (tmp = order; tmp <= mm->max_order; ++tmp) {
++ /* Get RB tree root for this order and tree */
++ root = &mm->free_trees[tree][tmp];
++ block = rbtree_last_free_block(root);
++ if (block)
++ break;
++ }
++ }
++
++ if (!block) {
++ /* Try allocating from the other tree */
++ tree = (tree == GPU_BUDDY_CLEAR_TREE) ?
++ GPU_BUDDY_DIRTY_TREE : GPU_BUDDY_CLEAR_TREE;
++
++ for (tmp = order; tmp <= mm->max_order; ++tmp) {
++ root = &mm->free_trees[tree][tmp];
++ block = rbtree_last_free_block(root);
++ if (block)
++ break;
++ }
++
++ if (!block)
++ return ERR_PTR(-ENOSPC);
++ }
++
++ BUG_ON(!gpu_buddy_block_is_free(block));
++
++ while (tmp != order) {
++ err = split_block(mm, block);
++ if (unlikely(err))
++ goto err_undo;
++
++ block = block->right;
++ tmp--;
++ }
++ return block;
++
++err_undo:
++ if (tmp != order)
++ __gpu_buddy_free(mm, block, false);
++ return ERR_PTR(err);
++}
++
++static int __alloc_range(struct gpu_buddy *mm,
++ struct list_head *dfs,
++ u64 start, u64 size,
++ struct list_head *blocks,
++ u64 *total_allocated_on_err)
++{
++ struct gpu_buddy_block *block;
++ struct gpu_buddy_block *buddy;
++ u64 total_allocated = 0;
++ LIST_HEAD(allocated);
++ u64 end;
++ int err;
++
++ end = start + size - 1;
++
++ do {
++ u64 block_start;
++ u64 block_end;
++
++ block = list_first_entry_or_null(dfs,
++ struct gpu_buddy_block,
++ tmp_link);
++ if (!block)
++ break;
++
++ list_del(&block->tmp_link);
++
++ block_start = gpu_buddy_block_offset(block);
++ block_end = block_start + gpu_buddy_block_size(mm, block) - 1;
++
++ if (!overlaps(start, end, block_start, block_end))
++ continue;
++
++ if (gpu_buddy_block_is_allocated(block)) {
++ err = -ENOSPC;
++ goto err_free;
++ }
++
++ if (contains(start, end, block_start, block_end)) {
++ if (gpu_buddy_block_is_free(block)) {
++ mark_allocated(mm, block);
++ total_allocated += gpu_buddy_block_size(mm, block);
++ mm->avail -= gpu_buddy_block_size(mm, block);
++ if (gpu_buddy_block_is_clear(block))
++ mm->clear_avail -= gpu_buddy_block_size(mm, block);
++ list_add_tail(&block->link, &allocated);
++ continue;
++ } else if (!mm->clear_avail) {
++ err = -ENOSPC;
++ goto err_free;
++ }
++ }
++
++ if (!gpu_buddy_block_is_split(block)) {
++ err = split_block(mm, block);
++ if (unlikely(err))
++ goto err_undo;
++ }
++
++ list_add(&block->right->tmp_link, dfs);
++ list_add(&block->left->tmp_link, dfs);
++ } while (1);
++
++ if (total_allocated < size) {
++ err = -ENOSPC;
++ goto err_free;
++ }
++
++ list_splice_tail(&allocated, blocks);
++
++ return 0;
++
++err_undo:
++ /*
++ * We really don't want to leave around a bunch of split blocks, since
++ * bigger is better, so make sure we merge everything back before we
++ * free the allocated blocks.
++ */
++ buddy = __get_buddy(block);
++ if (buddy &&
++ (gpu_buddy_block_is_free(block) &&
++ gpu_buddy_block_is_free(buddy)))
++ __gpu_buddy_free(mm, block, false);
++
++err_free:
++ if (err == -ENOSPC && total_allocated_on_err) {
++ list_splice_tail(&allocated, blocks);
++ *total_allocated_on_err = total_allocated;
++ } else {
++ gpu_buddy_free_list_internal(mm, &allocated);
++ }
++
++ return err;
++}
++
++static int __gpu_buddy_alloc_range(struct gpu_buddy *mm,
++ u64 start,
++ u64 size,
++ u64 *total_allocated_on_err,
++ struct list_head *blocks)
++{
++ LIST_HEAD(dfs);
++ int i;
++
++ for (i = 0; i < mm->n_roots; ++i)
++ list_add_tail(&mm->roots[i]->tmp_link, &dfs);
++
++ return __alloc_range(mm, &dfs, start, size,
++ blocks, total_allocated_on_err);
++}
++
++static int __alloc_contig_try_harder(struct gpu_buddy *mm,
++ u64 size,
++ u64 min_block_size,
++ struct list_head *blocks)
++{
++ u64 rhs_offset, lhs_offset, lhs_size, filled;
++ struct gpu_buddy_block *block;
++ unsigned int tree, order;
++ LIST_HEAD(blocks_lhs);
++ unsigned long pages;
++ u64 modify_size;
++ int err;
++
++ modify_size = rounddown_pow_of_two(size);
++ pages = modify_size >> ilog2(mm->chunk_size);
++ order = fls(pages) - 1;
++ if (order == 0)
++ return -ENOSPC;
++
++ for_each_free_tree(tree) {
++ struct rb_root *root;
++ struct rb_node *iter;
++
++ root = &mm->free_trees[tree][order];
++ if (rbtree_is_empty(root))
++ continue;
++
++ iter = rb_last(root);
++ while (iter) {
++ block = rbtree_get_free_block(iter);
++
++ /* Allocate blocks traversing RHS */
++ rhs_offset = gpu_buddy_block_offset(block);
++ err = __gpu_buddy_alloc_range(mm, rhs_offset, size,
++ &filled, blocks);
++ if (!err || err != -ENOSPC)
++ return err;
++
++ lhs_size = max((size - filled), min_block_size);
++ if (!IS_ALIGNED(lhs_size, min_block_size))
++ lhs_size = round_up(lhs_size, min_block_size);
++
++ /* Allocate blocks traversing LHS */
++ lhs_offset = gpu_buddy_block_offset(block) - lhs_size;
++ err = __gpu_buddy_alloc_range(mm, lhs_offset, lhs_size,
++ NULL, &blocks_lhs);
++ if (!err) {
++ list_splice(&blocks_lhs, blocks);
++ return 0;
++ } else if (err != -ENOSPC) {
++ gpu_buddy_free_list_internal(mm, blocks);
++ return err;
++ }
++ /* Free blocks for the next iteration */
++ gpu_buddy_free_list_internal(mm, blocks);
++
++ iter = rb_prev(iter);
++ }
++ }
++
++ return -ENOSPC;
++}
++
++/**
++ * gpu_buddy_block_trim - free unused pages
++ *
++ * @mm: GPU buddy manager
++ * @start: start address to begin the trimming.
++ * @new_size: original size requested
++ * @blocks: Input and output list of allocated blocks.
++ * MUST contain single block as input to be trimmed.
++ * On success will contain the newly allocated blocks
++ * making up the @new_size. Blocks always appear in
++ * ascending order
++ *
++ * For contiguous allocation, we round up the size to the nearest
++ * power of two value, drivers consume *actual* size, so remaining
++ * portions are unused and can be optionally freed with this function
++ *
++ * Returns:
++ * 0 on success, error code on failure.
++ */
++int gpu_buddy_block_trim(struct gpu_buddy *mm,
++ u64 *start,
++ u64 new_size,
++ struct list_head *blocks)
++{
++ struct gpu_buddy_block *parent;
++ struct gpu_buddy_block *block;
++ u64 block_start, block_end;
++ LIST_HEAD(dfs);
++ u64 new_start;
++ int err;
++
++ if (!list_is_singular(blocks))
++ return -EINVAL;
++
++ block = list_first_entry(blocks,
++ struct gpu_buddy_block,
++ link);
++
++ block_start = gpu_buddy_block_offset(block);
++ block_end = block_start + gpu_buddy_block_size(mm, block);
++
++ if (WARN_ON(!gpu_buddy_block_is_allocated(block)))
++ return -EINVAL;
++
++ if (new_size > gpu_buddy_block_size(mm, block))
++ return -EINVAL;
++
++ if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
++ return -EINVAL;
++
++ if (new_size == gpu_buddy_block_size(mm, block))
++ return 0;
++
++ new_start = block_start;
++ if (start) {
++ new_start = *start;
++
++ if (new_start < block_start)
++ return -EINVAL;
++
++ if (!IS_ALIGNED(new_start, mm->chunk_size))
++ return -EINVAL;
++
++ if (range_overflows(new_start, new_size, block_end))
++ return -EINVAL;
++ }
++
++ list_del(&block->link);
++ mark_free(mm, block);
++ mm->avail += gpu_buddy_block_size(mm, block);
++ if (gpu_buddy_block_is_clear(block))
++ mm->clear_avail += gpu_buddy_block_size(mm, block);
++
++ /* Prevent recursively freeing this node */
++ parent = block->parent;
++ block->parent = NULL;
++
++ list_add(&block->tmp_link, &dfs);
++ err = __alloc_range(mm, &dfs, new_start, new_size, blocks, NULL);
++ if (err) {
++ mark_allocated(mm, block);
++ mm->avail -= gpu_buddy_block_size(mm, block);
++ if (gpu_buddy_block_is_clear(block))
++ mm->clear_avail -= gpu_buddy_block_size(mm, block);
++ list_add(&block->link, blocks);
++ }
++
++ block->parent = parent;
++ return err;
++}
++EXPORT_SYMBOL(gpu_buddy_block_trim);
++
++static struct gpu_buddy_block *
++__gpu_buddy_alloc_blocks(struct gpu_buddy *mm,
++ u64 start, u64 end,
++ unsigned int order,
++ unsigned long flags)
++{
++ if (flags & GPU_BUDDY_RANGE_ALLOCATION)
++ /* Allocate traversing within the range */
++ return __gpu_buddy_alloc_range_bias(mm, start, end,
++ order, flags);
++ else
++ /* Allocate from freetree */
++ return alloc_from_freetree(mm, order, flags);
++}
++
++/**
++ * gpu_buddy_alloc_blocks - allocate power-of-two blocks
++ *
++ * @mm: GPU buddy manager to allocate from
++ * @start: start of the allowed range for this block
++ * @end: end of the allowed range for this block
++ * @size: size of the allocation in bytes
++ * @min_block_size: alignment of the allocation
++ * @blocks: output list head to add allocated blocks
++ * @flags: GPU_BUDDY_*_ALLOCATION flags
++ *
++ * alloc_range_bias() called on range limitations, which traverses
++ * the tree and returns the desired block.
++ *
++ * alloc_from_freetree() called when *no* range restrictions
++ * are enforced, which picks the block from the freetree.
++ *
++ * Returns:
++ * 0 on success, error code on failure.
++ */
++int gpu_buddy_alloc_blocks(struct gpu_buddy *mm,
++ u64 start, u64 end, u64 size,
++ u64 min_block_size,
++ struct list_head *blocks,
++ unsigned long flags)
++{
++ struct gpu_buddy_block *block = NULL;
++ u64 original_size, original_min_size;
++ unsigned int min_order, order;
++ LIST_HEAD(allocated);
++ unsigned long pages;
++ int err;
++
++ if (size < mm->chunk_size)
++ return -EINVAL;
++
++ if (min_block_size < mm->chunk_size)
++ return -EINVAL;
++
++ if (!is_power_of_2(min_block_size))
++ return -EINVAL;
++
++ if (!IS_ALIGNED(start | end | size, mm->chunk_size))
++ return -EINVAL;
++
++ if (end > mm->size)
++ return -EINVAL;
++
++ if (range_overflows(start, size, mm->size))
++ return -EINVAL;
++
++ /* Actual range allocation */
++ if (start + size == end) {
++ if (!IS_ALIGNED(start | end, min_block_size))
++ return -EINVAL;
++
++ return __gpu_buddy_alloc_range(mm, start, size, NULL, blocks);
++ }
++
++ original_size = size;
++ original_min_size = min_block_size;
++
++ /* Roundup the size to power of 2 */
++ if (flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION) {
++ size = roundup_pow_of_two(size);
++ min_block_size = size;
++ /* Align size value to min_block_size */
++ } else if (!IS_ALIGNED(size, min_block_size)) {
++ size = round_up(size, min_block_size);
++ }
++
++ pages = size >> ilog2(mm->chunk_size);
++ order = fls(pages) - 1;
++ min_order = ilog2(min_block_size) - ilog2(mm->chunk_size);
++
++ if (order > mm->max_order || size > mm->size) {
++ if ((flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION) &&
++ !(flags & GPU_BUDDY_RANGE_ALLOCATION))
++ return __alloc_contig_try_harder(mm, original_size,
++ original_min_size, blocks);
++
++ return -EINVAL;
++ }
++
++ do {
++ order = min(order, (unsigned int)fls(pages) - 1);
++ BUG_ON(order > mm->max_order);
++ BUG_ON(order < min_order);
++
++ do {
++ block = __gpu_buddy_alloc_blocks(mm, start,
++ end,
++ order,
++ flags);
++ if (!IS_ERR(block))
++ break;
++
++ if (order-- == min_order) {
++ /* Try allocation through force merge method */
++ if (mm->clear_avail &&
++ !__force_merge(mm, start, end, min_order)) {
++ block = __gpu_buddy_alloc_blocks(mm, start,
++ end,
++ min_order,
++ flags);
++ if (!IS_ERR(block)) {
++ order = min_order;
++ break;
++ }
++ }
++
++ /*
++ * Try contiguous block allocation through
++ * try harder method.
++ */
++ if (flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION &&
++ !(flags & GPU_BUDDY_RANGE_ALLOCATION))
++ return __alloc_contig_try_harder(mm,
++ original_size,
++ original_min_size,
++ blocks);
++ err = -ENOSPC;
++ goto err_free;
++ }
++ } while (1);
++
++ mark_allocated(mm, block);
++ mm->avail -= gpu_buddy_block_size(mm, block);
++ if (gpu_buddy_block_is_clear(block))
++ mm->clear_avail -= gpu_buddy_block_size(mm, block);
++ kmemleak_update_trace(block);
++ list_add_tail(&block->link, &allocated);
++
++ pages -= BIT(order);
++
++ if (!pages)
++ break;
++ } while (1);
++
++ /* Trim the allocated block to the required size */
++ if (!(flags & GPU_BUDDY_TRIM_DISABLE) &&
++ original_size != size) {
++ struct list_head *trim_list;
++ LIST_HEAD(temp);
++ u64 trim_size;
++
++ trim_list = &allocated;
++ trim_size = original_size;
++
++ if (!list_is_singular(&allocated)) {
++ block = list_last_entry(&allocated, typeof(*block), link);
++ list_move(&block->link, &temp);
++ trim_list = &temp;
++ trim_size = gpu_buddy_block_size(mm, block) -
++ (size - original_size);
++ }
++
++ gpu_buddy_block_trim(mm,
++ NULL,
++ trim_size,
++ trim_list);
++
++ if (!list_empty(&temp))
++ list_splice_tail(trim_list, &allocated);
++ }
++
++ list_splice_tail(&allocated, blocks);
++ return 0;
++
++err_free:
++ gpu_buddy_free_list_internal(mm, &allocated);
++ return err;
++}
++EXPORT_SYMBOL(gpu_buddy_alloc_blocks);
++
++/**
++ * gpu_buddy_block_print - print block information
++ *
++ * @mm: GPU buddy manager
++ * @block: GPU buddy block
++ */
++void gpu_buddy_block_print(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ u64 start = gpu_buddy_block_offset(block);
++ u64 size = gpu_buddy_block_size(mm, block);
++
++ pr_info("%#018llx-%#018llx: %llu\n", start, start + size, size);
++}
++EXPORT_SYMBOL(gpu_buddy_block_print);
++
++/**
++ * gpu_buddy_print - print allocator state
++ *
++ * @mm: GPU buddy manager
++ * @p: GPU printer to use
++ */
++void gpu_buddy_print(struct gpu_buddy *mm)
++{
++ int order;
++
++ pr_info("chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB, clear_free: %lluMiB\n",
++ mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20);
++
++ for (order = mm->max_order; order >= 0; order--) {
++ struct gpu_buddy_block *block, *tmp;
++ struct rb_root *root;
++ u64 count = 0, free;
++ unsigned int tree;
++
++ for_each_free_tree(tree) {
++ root = &mm->free_trees[tree][order];
++
++ rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
++ BUG_ON(!gpu_buddy_block_is_free(block));
++ count++;
++ }
++ }
++
++ free = count * (mm->chunk_size << order);
++ if (free < SZ_1M)
++ pr_info("order-%2d free: %8llu KiB, blocks: %llu\n",
++ order, free >> 10, count);
++ else
++ pr_info("order-%2d free: %8llu MiB, blocks: %llu\n",
++ order, free >> 20, count);
++ }
++}
++EXPORT_SYMBOL(gpu_buddy_print);
++
++static void gpu_buddy_module_exit(void)
++{
++ kmem_cache_destroy(slab_blocks);
++}
++
++static int __init gpu_buddy_module_init(void)
++{
++ slab_blocks = KMEM_CACHE(gpu_buddy_block, 0);
++ if (!slab_blocks)
++ return -ENOMEM;
++
++ return 0;
++}
++
++module_init(gpu_buddy_module_init);
++module_exit(gpu_buddy_module_exit);
++
++MODULE_DESCRIPTION("GPU Buddy Allocator");
++MODULE_LICENSE("Dual MIT/GPL");
+--- a/drivers/gpu/drm/Kconfig
++++ b/drivers/gpu/drm/Kconfig
+@@ -85,7 +85,6 @@ config DRM_KUNIT_TEST
+ select DRM_EXPORT_FOR_TESTS if m
+ select DRM_GEM_SHMEM_HELPER
+ select DRM_KUNIT_TEST_HELPERS
+- select DRM_LIB_RANDOM
+ select PRIME_NUMBERS
+ default KUNIT_ALL_TESTS
+ help
+@@ -325,6 +324,7 @@ config DRM_GPUVM
+ config DRM_BUDDY
+ tristate
+ depends on DRM
++ select GPU_BUDDY
+ help
+ A page based buddy allocator
+
+@@ -507,10 +507,6 @@ config DRM_EXPORT_FOR_TESTS
+ config DRM_PANEL_BACKLIGHT_QUIRKS
+ tristate
+
+-config DRM_LIB_RANDOM
+- bool
+- default n
+-
+ config DRM_PRIVACY_SCREEN
+ bool
+ default n
+--- a/drivers/gpu/drm/Makefile
++++ b/drivers/gpu/drm/Makefile
+@@ -75,7 +75,6 @@ drm-y := \
+ drm_vblank_work.o \
+ drm_vma_manager.o \
+ drm_writeback.o
+-drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
+ drm-$(CONFIG_COMPAT) += drm_ioc32.o
+ drm-$(CONFIG_DRM_PANEL) += drm_panel.o
+ drm-$(CONFIG_OF) += drm_of.o
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_res_cursor.h
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_res_cursor.h
+@@ -55,7 +55,7 @@ static inline void amdgpu_res_first(stru
+ uint64_t start, uint64_t size,
+ struct amdgpu_res_cursor *cur)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ struct list_head *head, *next;
+ struct drm_mm_node *node;
+
+@@ -71,7 +71,7 @@ static inline void amdgpu_res_first(stru
+ head = &to_amdgpu_vram_mgr_resource(res)->blocks;
+
+ block = list_first_entry_or_null(head,
+- struct drm_buddy_block,
++ struct gpu_buddy_block,
+ link);
+ if (!block)
+ goto fallback;
+@@ -81,7 +81,7 @@ static inline void amdgpu_res_first(stru
+
+ next = block->link.next;
+ if (next != head)
+- block = list_entry(next, struct drm_buddy_block, link);
++ block = list_entry(next, struct gpu_buddy_block, link);
+ }
+
+ cur->start = amdgpu_vram_mgr_block_start(block) + start;
+@@ -124,7 +124,7 @@ fallback:
+ */
+ static inline void amdgpu_res_next(struct amdgpu_res_cursor *cur, uint64_t size)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ struct drm_mm_node *node;
+ struct list_head *next;
+
+@@ -145,7 +145,7 @@ static inline void amdgpu_res_next(struc
+ block = cur->node;
+
+ next = block->link.next;
+- block = list_entry(next, struct drm_buddy_block, link);
++ block = list_entry(next, struct gpu_buddy_block, link);
+
+ cur->node = block;
+ cur->start = amdgpu_vram_mgr_block_start(block);
+@@ -173,7 +173,7 @@ static inline void amdgpu_res_next(struc
+ */
+ static inline bool amdgpu_res_cleared(struct amdgpu_res_cursor *cur)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+
+ switch (cur->mem_type) {
+ case TTM_PL_VRAM:
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c
+@@ -24,6 +24,7 @@
+
+ #include <linux/dma-mapping.h>
+ #include <drm/ttm/ttm_range_manager.h>
++#include <drm/drm_buddy.h>
+
+ #include "amdgpu.h"
+ #include "amdgpu_vm.h"
+@@ -52,15 +53,15 @@ to_amdgpu_device(struct amdgpu_vram_mgr
+ return container_of(mgr, struct amdgpu_device, mman.vram_mgr);
+ }
+
+-static inline struct drm_buddy_block *
++static inline struct gpu_buddy_block *
+ amdgpu_vram_mgr_first_block(struct list_head *list)
+ {
+- return list_first_entry_or_null(list, struct drm_buddy_block, link);
++ return list_first_entry_or_null(list, struct gpu_buddy_block, link);
+ }
+
+ static inline bool amdgpu_is_vram_mgr_blocks_contiguous(struct list_head *head)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ u64 start, size;
+
+ block = amdgpu_vram_mgr_first_block(head);
+@@ -71,7 +72,7 @@ static inline bool amdgpu_is_vram_mgr_bl
+ start = amdgpu_vram_mgr_block_start(block);
+ size = amdgpu_vram_mgr_block_size(block);
+
+- block = list_entry(block->link.next, struct drm_buddy_block, link);
++ block = list_entry(block->link.next, struct gpu_buddy_block, link);
+ if (start + size != amdgpu_vram_mgr_block_start(block))
+ return false;
+ }
+@@ -81,7 +82,7 @@ static inline bool amdgpu_is_vram_mgr_bl
+
+ static inline u64 amdgpu_vram_mgr_blocks_size(struct list_head *head)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ u64 size = 0;
+
+ list_for_each_entry(block, head, link)
+@@ -254,7 +255,7 @@ const struct attribute_group amdgpu_vram
+ * Calculate how many bytes of the DRM BUDDY block are inside visible VRAM
+ */
+ static u64 amdgpu_vram_mgr_vis_size(struct amdgpu_device *adev,
+- struct drm_buddy_block *block)
++ struct gpu_buddy_block *block)
+ {
+ u64 start = amdgpu_vram_mgr_block_start(block);
+ u64 end = start + amdgpu_vram_mgr_block_size(block);
+@@ -279,7 +280,7 @@ u64 amdgpu_vram_mgr_bo_visible_size(stru
+ struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev);
+ struct ttm_resource *res = bo->tbo.resource;
+ struct amdgpu_vram_mgr_resource *vres = to_amdgpu_vram_mgr_resource(res);
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ u64 usage = 0;
+
+ if (amdgpu_gmc_vram_full_visible(&adev->gmc))
+@@ -299,15 +300,15 @@ static void amdgpu_vram_mgr_do_reserve(s
+ {
+ struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
+ struct amdgpu_device *adev = to_amdgpu_device(mgr);
+- struct drm_buddy *mm = &mgr->mm;
++ struct gpu_buddy *mm = &mgr->mm;
+ struct amdgpu_vram_reservation *rsv, *temp;
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ uint64_t vis_usage;
+
+ list_for_each_entry_safe(rsv, temp, &mgr->reservations_pending, blocks) {
+- if (drm_buddy_alloc_blocks(mm, rsv->start, rsv->start + rsv->size,
++ if (gpu_buddy_alloc_blocks(mm, rsv->start, rsv->start + rsv->size,
+ rsv->size, mm->chunk_size, &rsv->allocated,
+- DRM_BUDDY_RANGE_ALLOCATION))
++ GPU_BUDDY_RANGE_ALLOCATION))
+ continue;
+
+ block = amdgpu_vram_mgr_first_block(&rsv->allocated);
+@@ -460,8 +461,8 @@ static int amdgpu_vram_mgr_new(struct tt
+ struct amdgpu_vram_mgr_resource *vres;
+ u64 size, remaining_size, lpfn, fpfn;
+ unsigned int adjust_dcc_size = 0;
+- struct drm_buddy *mm = &mgr->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = &mgr->mm;
++ struct gpu_buddy_block *block;
+ unsigned long pages_per_block;
+ int r;
+
+@@ -503,17 +504,17 @@ static int amdgpu_vram_mgr_new(struct tt
+ INIT_LIST_HEAD(&vres->blocks);
+
+ if (place->flags & TTM_PL_FLAG_TOPDOWN)
+- vres->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
++ vres->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
+
+ if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS)
+- vres->flags |= DRM_BUDDY_CONTIGUOUS_ALLOCATION;
++ vres->flags |= GPU_BUDDY_CONTIGUOUS_ALLOCATION;
+
+ if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CLEARED)
+- vres->flags |= DRM_BUDDY_CLEAR_ALLOCATION;
++ vres->flags |= GPU_BUDDY_CLEAR_ALLOCATION;
+
+ if (fpfn || lpfn != mgr->mm.size)
+ /* Allocate blocks in desired range */
+- vres->flags |= DRM_BUDDY_RANGE_ALLOCATION;
++ vres->flags |= GPU_BUDDY_RANGE_ALLOCATION;
+
+ if (bo->flags & AMDGPU_GEM_CREATE_GFX12_DCC &&
+ adev->gmc.gmc_funcs->get_dcc_alignment)
+@@ -526,7 +527,7 @@ static int amdgpu_vram_mgr_new(struct tt
+ dcc_size = roundup_pow_of_two(vres->base.size + adjust_dcc_size);
+ remaining_size = (u64)dcc_size;
+
+- vres->flags |= DRM_BUDDY_TRIM_DISABLE;
++ vres->flags |= GPU_BUDDY_TRIM_DISABLE;
+ }
+
+ mutex_lock(&mgr->lock);
+@@ -546,7 +547,7 @@ static int amdgpu_vram_mgr_new(struct tt
+
+ BUG_ON(min_block_size < mm->chunk_size);
+
+- r = drm_buddy_alloc_blocks(mm, fpfn,
++ r = gpu_buddy_alloc_blocks(mm, fpfn,
+ lpfn,
+ size,
+ min_block_size,
+@@ -555,7 +556,7 @@ static int amdgpu_vram_mgr_new(struct tt
+
+ if (unlikely(r == -ENOSPC) && pages_per_block == ~0ul &&
+ !(place->flags & TTM_PL_FLAG_CONTIGUOUS)) {
+- vres->flags &= ~DRM_BUDDY_CONTIGUOUS_ALLOCATION;
++ vres->flags &= ~GPU_BUDDY_CONTIGUOUS_ALLOCATION;
+ pages_per_block = max_t(u32, 2UL << (20UL - PAGE_SHIFT),
+ tbo->page_alignment);
+
+@@ -572,7 +573,7 @@ static int amdgpu_vram_mgr_new(struct tt
+ }
+
+ if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS && adjust_dcc_size) {
+- struct drm_buddy_block *dcc_block;
++ struct gpu_buddy_block *dcc_block;
+ unsigned long dcc_start;
+ u64 trim_start;
+
+@@ -582,7 +583,7 @@ static int amdgpu_vram_mgr_new(struct tt
+ roundup((unsigned long)amdgpu_vram_mgr_block_start(dcc_block),
+ adjust_dcc_size);
+ trim_start = (u64)dcc_start;
+- drm_buddy_block_trim(mm, &trim_start,
++ gpu_buddy_block_trim(mm, &trim_start,
+ (u64)vres->base.size,
+ &vres->blocks);
+ }
+@@ -620,7 +621,7 @@ static int amdgpu_vram_mgr_new(struct tt
+ return 0;
+
+ error_free_blocks:
+- drm_buddy_free_list(mm, &vres->blocks, 0);
++ gpu_buddy_free_list(mm, &vres->blocks, 0);
+ mutex_unlock(&mgr->lock);
+ error_fini:
+ ttm_resource_fini(man, &vres->base);
+@@ -643,15 +644,15 @@ static void amdgpu_vram_mgr_del(struct t
+ struct amdgpu_vram_mgr_resource *vres = to_amdgpu_vram_mgr_resource(res);
+ struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
+ struct amdgpu_device *adev = to_amdgpu_device(mgr);
+- struct drm_buddy *mm = &mgr->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = &mgr->mm;
++ struct gpu_buddy_block *block;
+ uint64_t vis_usage = 0;
+
+ mutex_lock(&mgr->lock);
+ list_for_each_entry(block, &vres->blocks, link)
+ vis_usage += amdgpu_vram_mgr_vis_size(adev, block);
+
+- drm_buddy_free_list(mm, &vres->blocks, vres->flags);
++ gpu_buddy_free_list(mm, &vres->blocks, vres->flags);
+ amdgpu_vram_mgr_do_reserve(man);
+ mutex_unlock(&mgr->lock);
+
+@@ -690,7 +691,7 @@ int amdgpu_vram_mgr_alloc_sgt(struct amd
+ if (!*sgt)
+ return -ENOMEM;
+
+- /* Determine the number of DRM_BUDDY blocks to export */
++ /* Determine the number of GPU_BUDDY blocks to export */
+ amdgpu_res_first(res, offset, length, &cursor);
+ while (cursor.remaining) {
+ num_entries++;
+@@ -706,10 +707,10 @@ int amdgpu_vram_mgr_alloc_sgt(struct amd
+ sg->length = 0;
+
+ /*
+- * Walk down DRM_BUDDY blocks to populate scatterlist nodes
+- * @note: Use iterator api to get first the DRM_BUDDY block
++ * Walk down GPU_BUDDY blocks to populate scatterlist nodes
++ * @note: Use iterator api to get first the GPU_BUDDY block
+ * and the number of bytes from it. Access the following
+- * DRM_BUDDY block(s) if more buffer needs to exported
++ * GPU_BUDDY block(s) if more buffer needs to exported
+ */
+ amdgpu_res_first(res, offset, length, &cursor);
+ for_each_sgtable_sg((*sgt), sg, i) {
+@@ -794,10 +795,10 @@ uint64_t amdgpu_vram_mgr_vis_usage(struc
+ void amdgpu_vram_mgr_clear_reset_blocks(struct amdgpu_device *adev)
+ {
+ struct amdgpu_vram_mgr *mgr = &adev->mman.vram_mgr;
+- struct drm_buddy *mm = &mgr->mm;
++ struct gpu_buddy *mm = &mgr->mm;
+
+ mutex_lock(&mgr->lock);
+- drm_buddy_reset_clear(mm, false);
++ gpu_buddy_reset_clear(mm, false);
+ mutex_unlock(&mgr->lock);
+ }
+
+@@ -817,7 +818,7 @@ static bool amdgpu_vram_mgr_intersects(s
+ size_t size)
+ {
+ struct amdgpu_vram_mgr_resource *mgr = to_amdgpu_vram_mgr_resource(res);
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+
+ /* Check each drm buddy block individually */
+ list_for_each_entry(block, &mgr->blocks, link) {
+@@ -850,7 +851,7 @@ static bool amdgpu_vram_mgr_compatible(s
+ size_t size)
+ {
+ struct amdgpu_vram_mgr_resource *mgr = to_amdgpu_vram_mgr_resource(res);
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+
+ /* Check each drm buddy block individually */
+ list_for_each_entry(block, &mgr->blocks, link) {
+@@ -879,7 +880,7 @@ static void amdgpu_vram_mgr_debug(struct
+ struct drm_printer *printer)
+ {
+ struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
+- struct drm_buddy *mm = &mgr->mm;
++ struct gpu_buddy *mm = &mgr->mm;
+ struct amdgpu_vram_reservation *rsv;
+
+ drm_printf(printer, " vis usage:%llu\n",
+@@ -938,7 +939,7 @@ int amdgpu_vram_mgr_init(struct amdgpu_d
+ if (!adev->gmc.is_app_apu) {
+ man->func = &amdgpu_vram_mgr_func;
+
+- err = drm_buddy_init(&mgr->mm, man->size, PAGE_SIZE);
++ err = gpu_buddy_init(&mgr->mm, man->size, PAGE_SIZE);
+ if (err)
+ return err;
+ } else {
+@@ -977,11 +978,11 @@ void amdgpu_vram_mgr_fini(struct amdgpu_
+ kfree(rsv);
+
+ list_for_each_entry_safe(rsv, temp, &mgr->reserved_pages, blocks) {
+- drm_buddy_free_list(&mgr->mm, &rsv->allocated, 0);
++ gpu_buddy_free_list(&mgr->mm, &rsv->allocated, 0);
+ kfree(rsv);
+ }
+ if (!adev->gmc.is_app_apu)
+- drm_buddy_fini(&mgr->mm);
++ gpu_buddy_fini(&mgr->mm);
+ mutex_unlock(&mgr->lock);
+
+ ttm_resource_manager_cleanup(man);
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h
+@@ -24,11 +24,11 @@
+ #ifndef __AMDGPU_VRAM_MGR_H__
+ #define __AMDGPU_VRAM_MGR_H__
+
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+
+ struct amdgpu_vram_mgr {
+ struct ttm_resource_manager manager;
+- struct drm_buddy mm;
++ struct gpu_buddy mm;
+ /* protects access to buffer objects */
+ struct mutex lock;
+ struct list_head reservations_pending;
+@@ -43,19 +43,19 @@ struct amdgpu_vram_mgr_resource {
+ unsigned long flags;
+ };
+
+-static inline u64 amdgpu_vram_mgr_block_start(struct drm_buddy_block *block)
++static inline u64 amdgpu_vram_mgr_block_start(struct gpu_buddy_block *block)
+ {
+- return drm_buddy_block_offset(block);
++ return gpu_buddy_block_offset(block);
+ }
+
+-static inline u64 amdgpu_vram_mgr_block_size(struct drm_buddy_block *block)
++static inline u64 amdgpu_vram_mgr_block_size(struct gpu_buddy_block *block)
+ {
+- return (u64)PAGE_SIZE << drm_buddy_block_order(block);
++ return (u64)PAGE_SIZE << gpu_buddy_block_order(block);
+ }
+
+-static inline bool amdgpu_vram_mgr_is_cleared(struct drm_buddy_block *block)
++static inline bool amdgpu_vram_mgr_is_cleared(struct gpu_buddy_block *block)
+ {
+- return drm_buddy_block_is_clear(block);
++ return gpu_buddy_block_is_clear(block);
+ }
+
+ static inline struct amdgpu_vram_mgr_resource *
+@@ -66,7 +66,7 @@ to_amdgpu_vram_mgr_resource(struct ttm_r
+
+ static inline void amdgpu_vram_mgr_set_cleared(struct ttm_resource *res)
+ {
+- to_amdgpu_vram_mgr_resource(res)->flags |= DRM_BUDDY_CLEARED;
++ to_amdgpu_vram_mgr_resource(res)->flags |= GPU_BUDDY_CLEARED;
+ }
+
+ #endif
+--- a/drivers/gpu/drm/drm_buddy.c
++++ b/drivers/gpu/drm/drm_buddy.c
+@@ -3,1252 +3,16 @@
+ * Copyright © 2021 Intel Corporation
+ */
+
++#include <kunit/test-bug.h>
++
++#include <linux/export.h>
+ #include <linux/kmemleak.h>
+ #include <linux/module.h>
+ #include <linux/sizes.h>
+
++#include <linux/gpu_buddy.h>
+ #include <drm/drm_buddy.h>
+-
+-enum drm_buddy_free_tree {
+- DRM_BUDDY_CLEAR_TREE = 0,
+- DRM_BUDDY_DIRTY_TREE,
+- DRM_BUDDY_MAX_FREE_TREES,
+-};
+-
+-static struct kmem_cache *slab_blocks;
+-
+-#define for_each_free_tree(tree) \
+- for ((tree) = 0; (tree) < DRM_BUDDY_MAX_FREE_TREES; (tree)++)
+-
+-static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm,
+- struct drm_buddy_block *parent,
+- unsigned int order,
+- u64 offset)
+-{
+- struct drm_buddy_block *block;
+-
+- BUG_ON(order > DRM_BUDDY_MAX_ORDER);
+-
+- block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
+- if (!block)
+- return NULL;
+-
+- block->header = offset;
+- block->header |= order;
+- block->parent = parent;
+-
+- RB_CLEAR_NODE(&block->rb);
+-
+- BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED);
+- return block;
+-}
+-
+-static void drm_block_free(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- kmem_cache_free(slab_blocks, block);
+-}
+-
+-static enum drm_buddy_free_tree
+-get_block_tree(struct drm_buddy_block *block)
+-{
+- return drm_buddy_block_is_clear(block) ?
+- DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
+-}
+-
+-static struct drm_buddy_block *
+-rbtree_get_free_block(const struct rb_node *node)
+-{
+- return node ? rb_entry(node, struct drm_buddy_block, rb) : NULL;
+-}
+-
+-static struct drm_buddy_block *
+-rbtree_last_free_block(struct rb_root *root)
+-{
+- return rbtree_get_free_block(rb_last(root));
+-}
+-
+-static bool rbtree_is_empty(struct rb_root *root)
+-{
+- return RB_EMPTY_ROOT(root);
+-}
+-
+-static bool drm_buddy_block_offset_less(const struct drm_buddy_block *block,
+- const struct drm_buddy_block *node)
+-{
+- return drm_buddy_block_offset(block) < drm_buddy_block_offset(node);
+-}
+-
+-static bool rbtree_block_offset_less(struct rb_node *block,
+- const struct rb_node *node)
+-{
+- return drm_buddy_block_offset_less(rbtree_get_free_block(block),
+- rbtree_get_free_block(node));
+-}
+-
+-static void rbtree_insert(struct drm_buddy *mm,
+- struct drm_buddy_block *block,
+- enum drm_buddy_free_tree tree)
+-{
+- rb_add(&block->rb,
+- &mm->free_trees[tree][drm_buddy_block_order(block)],
+- rbtree_block_offset_less);
+-}
+-
+-static void rbtree_remove(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- unsigned int order = drm_buddy_block_order(block);
+- enum drm_buddy_free_tree tree;
+- struct rb_root *root;
+-
+- tree = get_block_tree(block);
+- root = &mm->free_trees[tree][order];
+-
+- rb_erase(&block->rb, root);
+- RB_CLEAR_NODE(&block->rb);
+-}
+-
+-static void clear_reset(struct drm_buddy_block *block)
+-{
+- block->header &= ~DRM_BUDDY_HEADER_CLEAR;
+-}
+-
+-static void mark_cleared(struct drm_buddy_block *block)
+-{
+- block->header |= DRM_BUDDY_HEADER_CLEAR;
+-}
+-
+-static void mark_allocated(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- block->header &= ~DRM_BUDDY_HEADER_STATE;
+- block->header |= DRM_BUDDY_ALLOCATED;
+-
+- rbtree_remove(mm, block);
+-}
+-
+-static void mark_free(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- enum drm_buddy_free_tree tree;
+-
+- block->header &= ~DRM_BUDDY_HEADER_STATE;
+- block->header |= DRM_BUDDY_FREE;
+-
+- tree = get_block_tree(block);
+- rbtree_insert(mm, block, tree);
+-}
+-
+-static void mark_split(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- block->header &= ~DRM_BUDDY_HEADER_STATE;
+- block->header |= DRM_BUDDY_SPLIT;
+-
+- rbtree_remove(mm, block);
+-}
+-
+-static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
+-{
+- return s1 <= e2 && e1 >= s2;
+-}
+-
+-static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
+-{
+- return s1 <= s2 && e1 >= e2;
+-}
+-
+-static struct drm_buddy_block *
+-__get_buddy(struct drm_buddy_block *block)
+-{
+- struct drm_buddy_block *parent;
+-
+- parent = block->parent;
+- if (!parent)
+- return NULL;
+-
+- if (parent->left == block)
+- return parent->right;
+-
+- return parent->left;
+-}
+-
+-static unsigned int __drm_buddy_free(struct drm_buddy *mm,
+- struct drm_buddy_block *block,
+- bool force_merge)
+-{
+- struct drm_buddy_block *parent;
+- unsigned int order;
+-
+- while ((parent = block->parent)) {
+- struct drm_buddy_block *buddy;
+-
+- buddy = __get_buddy(block);
+-
+- if (!drm_buddy_block_is_free(buddy))
+- break;
+-
+- if (!force_merge) {
+- /*
+- * Check the block and its buddy clear state and exit
+- * the loop if they both have the dissimilar state.
+- */
+- if (drm_buddy_block_is_clear(block) !=
+- drm_buddy_block_is_clear(buddy))
+- break;
+-
+- if (drm_buddy_block_is_clear(block))
+- mark_cleared(parent);
+- }
+-
+- rbtree_remove(mm, buddy);
+- if (force_merge && drm_buddy_block_is_clear(buddy))
+- mm->clear_avail -= drm_buddy_block_size(mm, buddy);
+-
+- drm_block_free(mm, block);
+- drm_block_free(mm, buddy);
+-
+- block = parent;
+- }
+-
+- order = drm_buddy_block_order(block);
+- mark_free(mm, block);
+-
+- return order;
+-}
+-
+-static int __force_merge(struct drm_buddy *mm,
+- u64 start,
+- u64 end,
+- unsigned int min_order)
+-{
+- unsigned int tree, order;
+- int i;
+-
+- if (!min_order)
+- return -ENOMEM;
+-
+- if (min_order > mm->max_order)
+- return -EINVAL;
+-
+- for_each_free_tree(tree) {
+- for (i = min_order - 1; i >= 0; i--) {
+- struct rb_node *iter = rb_last(&mm->free_trees[tree][i]);
+-
+- while (iter) {
+- struct drm_buddy_block *block, *buddy;
+- u64 block_start, block_end;
+-
+- block = rbtree_get_free_block(iter);
+- iter = rb_prev(iter);
+-
+- if (!block || !block->parent)
+- continue;
+-
+- block_start = drm_buddy_block_offset(block);
+- block_end = block_start + drm_buddy_block_size(mm, block) - 1;
+-
+- if (!contains(start, end, block_start, block_end))
+- continue;
+-
+- buddy = __get_buddy(block);
+- if (!drm_buddy_block_is_free(buddy))
+- continue;
+-
+- WARN_ON(drm_buddy_block_is_clear(block) ==
+- drm_buddy_block_is_clear(buddy));
+-
+- /*
+- * Advance to the next node when the current node is the buddy,
+- * as freeing the block will also remove its buddy from the tree.
+- */
+- if (iter == &buddy->rb)
+- iter = rb_prev(iter);
+-
+- rbtree_remove(mm, block);
+- if (drm_buddy_block_is_clear(block))
+- mm->clear_avail -= drm_buddy_block_size(mm, block);
+-
+- order = __drm_buddy_free(mm, block, true);
+- if (order >= min_order)
+- return 0;
+- }
+- }
+- }
+-
+- return -ENOMEM;
+-}
+-
+-/**
+- * drm_buddy_init - init memory manager
+- *
+- * @mm: DRM buddy manager to initialize
+- * @size: size in bytes to manage
+- * @chunk_size: minimum page size in bytes for our allocations
+- *
+- * Initializes the memory manager and its resources.
+- *
+- * Returns:
+- * 0 on success, error code on failure.
+- */
+-int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size)
+-{
+- unsigned int i, j, root_count = 0;
+- u64 offset = 0;
+-
+- if (size < chunk_size)
+- return -EINVAL;
+-
+- if (chunk_size < SZ_4K)
+- return -EINVAL;
+-
+- if (!is_power_of_2(chunk_size))
+- return -EINVAL;
+-
+- size = round_down(size, chunk_size);
+-
+- mm->size = size;
+- mm->avail = size;
+- mm->clear_avail = 0;
+- mm->chunk_size = chunk_size;
+- mm->max_order = ilog2(size) - ilog2(chunk_size);
+-
+- BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER);
+-
+- mm->free_trees = kmalloc_array(DRM_BUDDY_MAX_FREE_TREES,
+- sizeof(*mm->free_trees),
+- GFP_KERNEL);
+- if (!mm->free_trees)
+- return -ENOMEM;
+-
+- for_each_free_tree(i) {
+- mm->free_trees[i] = kmalloc_array(mm->max_order + 1,
+- sizeof(struct rb_root),
+- GFP_KERNEL);
+- if (!mm->free_trees[i])
+- goto out_free_tree;
+-
+- for (j = 0; j <= mm->max_order; ++j)
+- mm->free_trees[i][j] = RB_ROOT;
+- }
+-
+- mm->n_roots = hweight64(size);
+-
+- mm->roots = kmalloc_array(mm->n_roots,
+- sizeof(struct drm_buddy_block *),
+- GFP_KERNEL);
+- if (!mm->roots)
+- goto out_free_tree;
+-
+- /*
+- * Split into power-of-two blocks, in case we are given a size that is
+- * not itself a power-of-two.
+- */
+- do {
+- struct drm_buddy_block *root;
+- unsigned int order;
+- u64 root_size;
+-
+- order = ilog2(size) - ilog2(chunk_size);
+- root_size = chunk_size << order;
+-
+- root = drm_block_alloc(mm, NULL, order, offset);
+- if (!root)
+- goto out_free_roots;
+-
+- mark_free(mm, root);
+-
+- BUG_ON(root_count > mm->max_order);
+- BUG_ON(drm_buddy_block_size(mm, root) < chunk_size);
+-
+- mm->roots[root_count] = root;
+-
+- offset += root_size;
+- size -= root_size;
+- root_count++;
+- } while (size);
+-
+- return 0;
+-
+-out_free_roots:
+- while (root_count--)
+- drm_block_free(mm, mm->roots[root_count]);
+- kfree(mm->roots);
+-out_free_tree:
+- while (i--)
+- kfree(mm->free_trees[i]);
+- kfree(mm->free_trees);
+- return -ENOMEM;
+-}
+-EXPORT_SYMBOL(drm_buddy_init);
+-
+-/**
+- * drm_buddy_fini - tear down the memory manager
+- *
+- * @mm: DRM buddy manager to free
+- *
+- * Cleanup memory manager resources and the freetree
+- */
+-void drm_buddy_fini(struct drm_buddy *mm)
+-{
+- u64 root_size, size, start;
+- unsigned int order;
+- int i;
+-
+- size = mm->size;
+-
+- for (i = 0; i < mm->n_roots; ++i) {
+- order = ilog2(size) - ilog2(mm->chunk_size);
+- start = drm_buddy_block_offset(mm->roots[i]);
+- __force_merge(mm, start, start + size, order);
+-
+- WARN_ON(!drm_buddy_block_is_free(mm->roots[i]));
+- drm_block_free(mm, mm->roots[i]);
+-
+- root_size = mm->chunk_size << order;
+- size -= root_size;
+- }
+-
+- WARN_ON(mm->avail != mm->size);
+-
+- for_each_free_tree(i)
+- kfree(mm->free_trees[i]);
+- kfree(mm->free_trees);
+- kfree(mm->roots);
+-}
+-EXPORT_SYMBOL(drm_buddy_fini);
+-
+-static int split_block(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- unsigned int block_order = drm_buddy_block_order(block) - 1;
+- u64 offset = drm_buddy_block_offset(block);
+-
+- BUG_ON(!drm_buddy_block_is_free(block));
+- BUG_ON(!drm_buddy_block_order(block));
+-
+- block->left = drm_block_alloc(mm, block, block_order, offset);
+- if (!block->left)
+- return -ENOMEM;
+-
+- block->right = drm_block_alloc(mm, block, block_order,
+- offset + (mm->chunk_size << block_order));
+- if (!block->right) {
+- drm_block_free(mm, block->left);
+- return -ENOMEM;
+- }
+-
+- mark_split(mm, block);
+-
+- if (drm_buddy_block_is_clear(block)) {
+- mark_cleared(block->left);
+- mark_cleared(block->right);
+- clear_reset(block);
+- }
+-
+- mark_free(mm, block->left);
+- mark_free(mm, block->right);
+-
+- return 0;
+-}
+-
+-/**
+- * drm_get_buddy - get buddy address
+- *
+- * @block: DRM buddy block
+- *
+- * Returns the corresponding buddy block for @block, or NULL
+- * if this is a root block and can't be merged further.
+- * Requires some kind of locking to protect against
+- * any concurrent allocate and free operations.
+- */
+-struct drm_buddy_block *
+-drm_get_buddy(struct drm_buddy_block *block)
+-{
+- return __get_buddy(block);
+-}
+-EXPORT_SYMBOL(drm_get_buddy);
+-
+-/**
+- * drm_buddy_reset_clear - reset blocks clear state
+- *
+- * @mm: DRM buddy manager
+- * @is_clear: blocks clear state
+- *
+- * Reset the clear state based on @is_clear value for each block
+- * in the freetree.
+- */
+-void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear)
+-{
+- enum drm_buddy_free_tree src_tree, dst_tree;
+- u64 root_size, size, start;
+- unsigned int order;
+- int i;
+-
+- size = mm->size;
+- for (i = 0; i < mm->n_roots; ++i) {
+- order = ilog2(size) - ilog2(mm->chunk_size);
+- start = drm_buddy_block_offset(mm->roots[i]);
+- __force_merge(mm, start, start + size, order);
+-
+- root_size = mm->chunk_size << order;
+- size -= root_size;
+- }
+-
+- src_tree = is_clear ? DRM_BUDDY_DIRTY_TREE : DRM_BUDDY_CLEAR_TREE;
+- dst_tree = is_clear ? DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
+-
+- for (i = 0; i <= mm->max_order; ++i) {
+- struct rb_root *root = &mm->free_trees[src_tree][i];
+- struct drm_buddy_block *block, *tmp;
+-
+- rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
+- rbtree_remove(mm, block);
+- if (is_clear) {
+- mark_cleared(block);
+- mm->clear_avail += drm_buddy_block_size(mm, block);
+- } else {
+- clear_reset(block);
+- mm->clear_avail -= drm_buddy_block_size(mm, block);
+- }
+-
+- rbtree_insert(mm, block, dst_tree);
+- }
+- }
+-}
+-EXPORT_SYMBOL(drm_buddy_reset_clear);
+-
+-/**
+- * drm_buddy_free_block - free a block
+- *
+- * @mm: DRM buddy manager
+- * @block: block to be freed
+- */
+-void drm_buddy_free_block(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- BUG_ON(!drm_buddy_block_is_allocated(block));
+- mm->avail += drm_buddy_block_size(mm, block);
+- if (drm_buddy_block_is_clear(block))
+- mm->clear_avail += drm_buddy_block_size(mm, block);
+-
+- __drm_buddy_free(mm, block, false);
+-}
+-EXPORT_SYMBOL(drm_buddy_free_block);
+-
+-static void __drm_buddy_free_list(struct drm_buddy *mm,
+- struct list_head *objects,
+- bool mark_clear,
+- bool mark_dirty)
+-{
+- struct drm_buddy_block *block, *on;
+-
+- WARN_ON(mark_dirty && mark_clear);
+-
+- list_for_each_entry_safe(block, on, objects, link) {
+- if (mark_clear)
+- mark_cleared(block);
+- else if (mark_dirty)
+- clear_reset(block);
+- drm_buddy_free_block(mm, block);
+- cond_resched();
+- }
+- INIT_LIST_HEAD(objects);
+-}
+-
+-static void drm_buddy_free_list_internal(struct drm_buddy *mm,
+- struct list_head *objects)
+-{
+- /*
+- * Don't touch the clear/dirty bit, since allocation is still internal
+- * at this point. For example we might have just failed part of the
+- * allocation.
+- */
+- __drm_buddy_free_list(mm, objects, false, false);
+-}
+-
+-/**
+- * drm_buddy_free_list - free blocks
+- *
+- * @mm: DRM buddy manager
+- * @objects: input list head to free blocks
+- * @flags: optional flags like DRM_BUDDY_CLEARED
+- */
+-void drm_buddy_free_list(struct drm_buddy *mm,
+- struct list_head *objects,
+- unsigned int flags)
+-{
+- bool mark_clear = flags & DRM_BUDDY_CLEARED;
+-
+- __drm_buddy_free_list(mm, objects, mark_clear, !mark_clear);
+-}
+-EXPORT_SYMBOL(drm_buddy_free_list);
+-
+-static bool block_incompatible(struct drm_buddy_block *block, unsigned int flags)
+-{
+- bool needs_clear = flags & DRM_BUDDY_CLEAR_ALLOCATION;
+-
+- return needs_clear != drm_buddy_block_is_clear(block);
+-}
+-
+-static struct drm_buddy_block *
+-__alloc_range_bias(struct drm_buddy *mm,
+- u64 start, u64 end,
+- unsigned int order,
+- unsigned long flags,
+- bool fallback)
+-{
+- u64 req_size = mm->chunk_size << order;
+- struct drm_buddy_block *block;
+- struct drm_buddy_block *buddy;
+- LIST_HEAD(dfs);
+- int err;
+- int i;
+-
+- end = end - 1;
+-
+- for (i = 0; i < mm->n_roots; ++i)
+- list_add_tail(&mm->roots[i]->tmp_link, &dfs);
+-
+- do {
+- u64 block_start;
+- u64 block_end;
+-
+- block = list_first_entry_or_null(&dfs,
+- struct drm_buddy_block,
+- tmp_link);
+- if (!block)
+- break;
+-
+- list_del(&block->tmp_link);
+-
+- if (drm_buddy_block_order(block) < order)
+- continue;
+-
+- block_start = drm_buddy_block_offset(block);
+- block_end = block_start + drm_buddy_block_size(mm, block) - 1;
+-
+- if (!overlaps(start, end, block_start, block_end))
+- continue;
+-
+- if (drm_buddy_block_is_allocated(block))
+- continue;
+-
+- if (block_start < start || block_end > end) {
+- u64 adjusted_start = max(block_start, start);
+- u64 adjusted_end = min(block_end, end);
+-
+- if (round_down(adjusted_end + 1, req_size) <=
+- round_up(adjusted_start, req_size))
+- continue;
+- }
+-
+- if (!fallback && block_incompatible(block, flags))
+- continue;
+-
+- if (contains(start, end, block_start, block_end) &&
+- order == drm_buddy_block_order(block)) {
+- /*
+- * Find the free block within the range.
+- */
+- if (drm_buddy_block_is_free(block))
+- return block;
+-
+- continue;
+- }
+-
+- if (!drm_buddy_block_is_split(block)) {
+- err = split_block(mm, block);
+- if (unlikely(err))
+- goto err_undo;
+- }
+-
+- list_add(&block->right->tmp_link, &dfs);
+- list_add(&block->left->tmp_link, &dfs);
+- } while (1);
+-
+- return ERR_PTR(-ENOSPC);
+-
+-err_undo:
+- /*
+- * We really don't want to leave around a bunch of split blocks, since
+- * bigger is better, so make sure we merge everything back before we
+- * free the allocated blocks.
+- */
+- buddy = __get_buddy(block);
+- if (buddy &&
+- (drm_buddy_block_is_free(block) &&
+- drm_buddy_block_is_free(buddy)))
+- __drm_buddy_free(mm, block, false);
+- return ERR_PTR(err);
+-}
+-
+-static struct drm_buddy_block *
+-__drm_buddy_alloc_range_bias(struct drm_buddy *mm,
+- u64 start, u64 end,
+- unsigned int order,
+- unsigned long flags)
+-{
+- struct drm_buddy_block *block;
+- bool fallback = false;
+-
+- block = __alloc_range_bias(mm, start, end, order,
+- flags, fallback);
+- if (IS_ERR(block))
+- return __alloc_range_bias(mm, start, end, order,
+- flags, !fallback);
+-
+- return block;
+-}
+-
+-static struct drm_buddy_block *
+-get_maxblock(struct drm_buddy *mm,
+- unsigned int order,
+- enum drm_buddy_free_tree tree)
+-{
+- struct drm_buddy_block *max_block = NULL, *block = NULL;
+- struct rb_root *root;
+- unsigned int i;
+-
+- for (i = order; i <= mm->max_order; ++i) {
+- root = &mm->free_trees[tree][i];
+- block = rbtree_last_free_block(root);
+- if (!block)
+- continue;
+-
+- if (!max_block) {
+- max_block = block;
+- continue;
+- }
+-
+- if (drm_buddy_block_offset(block) >
+- drm_buddy_block_offset(max_block)) {
+- max_block = block;
+- }
+- }
+-
+- return max_block;
+-}
+-
+-static struct drm_buddy_block *
+-alloc_from_freetree(struct drm_buddy *mm,
+- unsigned int order,
+- unsigned long flags)
+-{
+- struct drm_buddy_block *block = NULL;
+- struct rb_root *root;
+- enum drm_buddy_free_tree tree;
+- unsigned int tmp;
+- int err;
+-
+- tree = (flags & DRM_BUDDY_CLEAR_ALLOCATION) ?
+- DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
+-
+- if (flags & DRM_BUDDY_TOPDOWN_ALLOCATION) {
+- block = get_maxblock(mm, order, tree);
+- if (block)
+- /* Store the obtained block order */
+- tmp = drm_buddy_block_order(block);
+- } else {
+- for (tmp = order; tmp <= mm->max_order; ++tmp) {
+- /* Get RB tree root for this order and tree */
+- root = &mm->free_trees[tree][tmp];
+- block = rbtree_last_free_block(root);
+- if (block)
+- break;
+- }
+- }
+-
+- if (!block) {
+- /* Try allocating from the other tree */
+- tree = (tree == DRM_BUDDY_CLEAR_TREE) ?
+- DRM_BUDDY_DIRTY_TREE : DRM_BUDDY_CLEAR_TREE;
+-
+- for (tmp = order; tmp <= mm->max_order; ++tmp) {
+- root = &mm->free_trees[tree][tmp];
+- block = rbtree_last_free_block(root);
+- if (block)
+- break;
+- }
+-
+- if (!block)
+- return ERR_PTR(-ENOSPC);
+- }
+-
+- BUG_ON(!drm_buddy_block_is_free(block));
+-
+- while (tmp != order) {
+- err = split_block(mm, block);
+- if (unlikely(err))
+- goto err_undo;
+-
+- block = block->right;
+- tmp--;
+- }
+- return block;
+-
+-err_undo:
+- if (tmp != order)
+- __drm_buddy_free(mm, block, false);
+- return ERR_PTR(err);
+-}
+-
+-static int __alloc_range(struct drm_buddy *mm,
+- struct list_head *dfs,
+- u64 start, u64 size,
+- struct list_head *blocks,
+- u64 *total_allocated_on_err)
+-{
+- struct drm_buddy_block *block;
+- struct drm_buddy_block *buddy;
+- u64 total_allocated = 0;
+- LIST_HEAD(allocated);
+- u64 end;
+- int err;
+-
+- end = start + size - 1;
+-
+- do {
+- u64 block_start;
+- u64 block_end;
+-
+- block = list_first_entry_or_null(dfs,
+- struct drm_buddy_block,
+- tmp_link);
+- if (!block)
+- break;
+-
+- list_del(&block->tmp_link);
+-
+- block_start = drm_buddy_block_offset(block);
+- block_end = block_start + drm_buddy_block_size(mm, block) - 1;
+-
+- if (!overlaps(start, end, block_start, block_end))
+- continue;
+-
+- if (drm_buddy_block_is_allocated(block)) {
+- err = -ENOSPC;
+- goto err_free;
+- }
+-
+- if (contains(start, end, block_start, block_end)) {
+- if (drm_buddy_block_is_free(block)) {
+- mark_allocated(mm, block);
+- total_allocated += drm_buddy_block_size(mm, block);
+- mm->avail -= drm_buddy_block_size(mm, block);
+- if (drm_buddy_block_is_clear(block))
+- mm->clear_avail -= drm_buddy_block_size(mm, block);
+- list_add_tail(&block->link, &allocated);
+- continue;
+- } else if (!mm->clear_avail) {
+- err = -ENOSPC;
+- goto err_free;
+- }
+- }
+-
+- if (!drm_buddy_block_is_split(block)) {
+- err = split_block(mm, block);
+- if (unlikely(err))
+- goto err_undo;
+- }
+-
+- list_add(&block->right->tmp_link, dfs);
+- list_add(&block->left->tmp_link, dfs);
+- } while (1);
+-
+- if (total_allocated < size) {
+- err = -ENOSPC;
+- goto err_free;
+- }
+-
+- list_splice_tail(&allocated, blocks);
+-
+- return 0;
+-
+-err_undo:
+- /*
+- * We really don't want to leave around a bunch of split blocks, since
+- * bigger is better, so make sure we merge everything back before we
+- * free the allocated blocks.
+- */
+- buddy = __get_buddy(block);
+- if (buddy &&
+- (drm_buddy_block_is_free(block) &&
+- drm_buddy_block_is_free(buddy)))
+- __drm_buddy_free(mm, block, false);
+-
+-err_free:
+- if (err == -ENOSPC && total_allocated_on_err) {
+- list_splice_tail(&allocated, blocks);
+- *total_allocated_on_err = total_allocated;
+- } else {
+- drm_buddy_free_list_internal(mm, &allocated);
+- }
+-
+- return err;
+-}
+-
+-static int __drm_buddy_alloc_range(struct drm_buddy *mm,
+- u64 start,
+- u64 size,
+- u64 *total_allocated_on_err,
+- struct list_head *blocks)
+-{
+- LIST_HEAD(dfs);
+- int i;
+-
+- for (i = 0; i < mm->n_roots; ++i)
+- list_add_tail(&mm->roots[i]->tmp_link, &dfs);
+-
+- return __alloc_range(mm, &dfs, start, size,
+- blocks, total_allocated_on_err);
+-}
+-
+-static int __alloc_contig_try_harder(struct drm_buddy *mm,
+- u64 size,
+- u64 min_block_size,
+- struct list_head *blocks)
+-{
+- u64 rhs_offset, lhs_offset, lhs_size, filled;
+- struct drm_buddy_block *block;
+- unsigned int tree, order;
+- LIST_HEAD(blocks_lhs);
+- unsigned long pages;
+- u64 modify_size;
+- int err;
+-
+- modify_size = rounddown_pow_of_two(size);
+- pages = modify_size >> ilog2(mm->chunk_size);
+- order = fls(pages) - 1;
+- if (order == 0)
+- return -ENOSPC;
+-
+- for_each_free_tree(tree) {
+- struct rb_root *root;
+- struct rb_node *iter;
+-
+- root = &mm->free_trees[tree][order];
+- if (rbtree_is_empty(root))
+- continue;
+-
+- iter = rb_last(root);
+- while (iter) {
+- block = rbtree_get_free_block(iter);
+-
+- /* Allocate blocks traversing RHS */
+- rhs_offset = drm_buddy_block_offset(block);
+- err = __drm_buddy_alloc_range(mm, rhs_offset, size,
+- &filled, blocks);
+- if (!err || err != -ENOSPC)
+- return err;
+-
+- lhs_size = max((size - filled), min_block_size);
+- if (!IS_ALIGNED(lhs_size, min_block_size))
+- lhs_size = round_up(lhs_size, min_block_size);
+-
+- /* Allocate blocks traversing LHS */
+- lhs_offset = drm_buddy_block_offset(block) - lhs_size;
+- err = __drm_buddy_alloc_range(mm, lhs_offset, lhs_size,
+- NULL, &blocks_lhs);
+- if (!err) {
+- list_splice(&blocks_lhs, blocks);
+- return 0;
+- } else if (err != -ENOSPC) {
+- drm_buddy_free_list_internal(mm, blocks);
+- return err;
+- }
+- /* Free blocks for the next iteration */
+- drm_buddy_free_list_internal(mm, blocks);
+-
+- iter = rb_prev(iter);
+- }
+- }
+-
+- return -ENOSPC;
+-}
+-
+-/**
+- * drm_buddy_block_trim - free unused pages
+- *
+- * @mm: DRM buddy manager
+- * @start: start address to begin the trimming.
+- * @new_size: original size requested
+- * @blocks: Input and output list of allocated blocks.
+- * MUST contain single block as input to be trimmed.
+- * On success will contain the newly allocated blocks
+- * making up the @new_size. Blocks always appear in
+- * ascending order
+- *
+- * For contiguous allocation, we round up the size to the nearest
+- * power of two value, drivers consume *actual* size, so remaining
+- * portions are unused and can be optionally freed with this function
+- *
+- * Returns:
+- * 0 on success, error code on failure.
+- */
+-int drm_buddy_block_trim(struct drm_buddy *mm,
+- u64 *start,
+- u64 new_size,
+- struct list_head *blocks)
+-{
+- struct drm_buddy_block *parent;
+- struct drm_buddy_block *block;
+- u64 block_start, block_end;
+- LIST_HEAD(dfs);
+- u64 new_start;
+- int err;
+-
+- if (!list_is_singular(blocks))
+- return -EINVAL;
+-
+- block = list_first_entry(blocks,
+- struct drm_buddy_block,
+- link);
+-
+- block_start = drm_buddy_block_offset(block);
+- block_end = block_start + drm_buddy_block_size(mm, block);
+-
+- if (WARN_ON(!drm_buddy_block_is_allocated(block)))
+- return -EINVAL;
+-
+- if (new_size > drm_buddy_block_size(mm, block))
+- return -EINVAL;
+-
+- if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
+- return -EINVAL;
+-
+- if (new_size == drm_buddy_block_size(mm, block))
+- return 0;
+-
+- new_start = block_start;
+- if (start) {
+- new_start = *start;
+-
+- if (new_start < block_start)
+- return -EINVAL;
+-
+- if (!IS_ALIGNED(new_start, mm->chunk_size))
+- return -EINVAL;
+-
+- if (range_overflows(new_start, new_size, block_end))
+- return -EINVAL;
+- }
+-
+- list_del(&block->link);
+- mark_free(mm, block);
+- mm->avail += drm_buddy_block_size(mm, block);
+- if (drm_buddy_block_is_clear(block))
+- mm->clear_avail += drm_buddy_block_size(mm, block);
+-
+- /* Prevent recursively freeing this node */
+- parent = block->parent;
+- block->parent = NULL;
+-
+- list_add(&block->tmp_link, &dfs);
+- err = __alloc_range(mm, &dfs, new_start, new_size, blocks, NULL);
+- if (err) {
+- mark_allocated(mm, block);
+- mm->avail -= drm_buddy_block_size(mm, block);
+- if (drm_buddy_block_is_clear(block))
+- mm->clear_avail -= drm_buddy_block_size(mm, block);
+- list_add(&block->link, blocks);
+- }
+-
+- block->parent = parent;
+- return err;
+-}
+-EXPORT_SYMBOL(drm_buddy_block_trim);
+-
+-static struct drm_buddy_block *
+-__drm_buddy_alloc_blocks(struct drm_buddy *mm,
+- u64 start, u64 end,
+- unsigned int order,
+- unsigned long flags)
+-{
+- if (flags & DRM_BUDDY_RANGE_ALLOCATION)
+- /* Allocate traversing within the range */
+- return __drm_buddy_alloc_range_bias(mm, start, end,
+- order, flags);
+- else
+- /* Allocate from freetree */
+- return alloc_from_freetree(mm, order, flags);
+-}
+-
+-/**
+- * drm_buddy_alloc_blocks - allocate power-of-two blocks
+- *
+- * @mm: DRM buddy manager to allocate from
+- * @start: start of the allowed range for this block
+- * @end: end of the allowed range for this block
+- * @size: size of the allocation in bytes
+- * @min_block_size: alignment of the allocation
+- * @blocks: output list head to add allocated blocks
+- * @flags: DRM_BUDDY_*_ALLOCATION flags
+- *
+- * alloc_range_bias() called on range limitations, which traverses
+- * the tree and returns the desired block.
+- *
+- * alloc_from_freetree() called when *no* range restrictions
+- * are enforced, which picks the block from the freetree.
+- *
+- * Returns:
+- * 0 on success, error code on failure.
+- */
+-int drm_buddy_alloc_blocks(struct drm_buddy *mm,
+- u64 start, u64 end, u64 size,
+- u64 min_block_size,
+- struct list_head *blocks,
+- unsigned long flags)
+-{
+- struct drm_buddy_block *block = NULL;
+- u64 original_size, original_min_size;
+- unsigned int min_order, order;
+- LIST_HEAD(allocated);
+- unsigned long pages;
+- int err;
+-
+- if (size < mm->chunk_size)
+- return -EINVAL;
+-
+- if (min_block_size < mm->chunk_size)
+- return -EINVAL;
+-
+- if (!is_power_of_2(min_block_size))
+- return -EINVAL;
+-
+- if (!IS_ALIGNED(start | end | size, mm->chunk_size))
+- return -EINVAL;
+-
+- if (end > mm->size)
+- return -EINVAL;
+-
+- if (range_overflows(start, size, mm->size))
+- return -EINVAL;
+-
+- /* Actual range allocation */
+- if (start + size == end) {
+- if (!IS_ALIGNED(start | end, min_block_size))
+- return -EINVAL;
+-
+- return __drm_buddy_alloc_range(mm, start, size, NULL, blocks);
+- }
+-
+- original_size = size;
+- original_min_size = min_block_size;
+-
+- /* Roundup the size to power of 2 */
+- if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION) {
+- size = roundup_pow_of_two(size);
+- min_block_size = size;
+- /* Align size value to min_block_size */
+- } else if (!IS_ALIGNED(size, min_block_size)) {
+- size = round_up(size, min_block_size);
+- }
+-
+- pages = size >> ilog2(mm->chunk_size);
+- order = fls(pages) - 1;
+- min_order = ilog2(min_block_size) - ilog2(mm->chunk_size);
+-
+- if (order > mm->max_order || size > mm->size) {
+- if ((flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION) &&
+- !(flags & DRM_BUDDY_RANGE_ALLOCATION))
+- return __alloc_contig_try_harder(mm, original_size,
+- original_min_size, blocks);
+-
+- return -EINVAL;
+- }
+-
+- do {
+- order = min(order, (unsigned int)fls(pages) - 1);
+- BUG_ON(order > mm->max_order);
+- BUG_ON(order < min_order);
+-
+- do {
+- block = __drm_buddy_alloc_blocks(mm, start,
+- end,
+- order,
+- flags);
+- if (!IS_ERR(block))
+- break;
+-
+- if (order-- == min_order) {
+- /* Try allocation through force merge method */
+- if (mm->clear_avail &&
+- !__force_merge(mm, start, end, min_order)) {
+- block = __drm_buddy_alloc_blocks(mm, start,
+- end,
+- min_order,
+- flags);
+- if (!IS_ERR(block)) {
+- order = min_order;
+- break;
+- }
+- }
+-
+- /*
+- * Try contiguous block allocation through
+- * try harder method.
+- */
+- if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION &&
+- !(flags & DRM_BUDDY_RANGE_ALLOCATION))
+- return __alloc_contig_try_harder(mm,
+- original_size,
+- original_min_size,
+- blocks);
+- err = -ENOSPC;
+- goto err_free;
+- }
+- } while (1);
+-
+- mark_allocated(mm, block);
+- mm->avail -= drm_buddy_block_size(mm, block);
+- if (drm_buddy_block_is_clear(block))
+- mm->clear_avail -= drm_buddy_block_size(mm, block);
+- kmemleak_update_trace(block);
+- list_add_tail(&block->link, &allocated);
+-
+- pages -= BIT(order);
+-
+- if (!pages)
+- break;
+- } while (1);
+-
+- /* Trim the allocated block to the required size */
+- if (!(flags & DRM_BUDDY_TRIM_DISABLE) &&
+- original_size != size) {
+- struct list_head *trim_list;
+- LIST_HEAD(temp);
+- u64 trim_size;
+-
+- trim_list = &allocated;
+- trim_size = original_size;
+-
+- if (!list_is_singular(&allocated)) {
+- block = list_last_entry(&allocated, typeof(*block), link);
+- list_move(&block->link, &temp);
+- trim_list = &temp;
+- trim_size = drm_buddy_block_size(mm, block) -
+- (size - original_size);
+- }
+-
+- drm_buddy_block_trim(mm,
+- NULL,
+- trim_size,
+- trim_list);
+-
+- if (!list_empty(&temp))
+- list_splice_tail(trim_list, &allocated);
+- }
+-
+- list_splice_tail(&allocated, blocks);
+- return 0;
+-
+-err_free:
+- drm_buddy_free_list_internal(mm, &allocated);
+- return err;
+-}
+-EXPORT_SYMBOL(drm_buddy_alloc_blocks);
++#include <drm/drm_print.h>
+
+ /**
+ * drm_buddy_block_print - print block information
+@@ -1257,12 +21,12 @@ EXPORT_SYMBOL(drm_buddy_alloc_blocks);
+ * @block: DRM buddy block
+ * @p: DRM printer to use
+ */
+-void drm_buddy_block_print(struct drm_buddy *mm,
+- struct drm_buddy_block *block,
++void drm_buddy_block_print(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block,
+ struct drm_printer *p)
+ {
+- u64 start = drm_buddy_block_offset(block);
+- u64 size = drm_buddy_block_size(mm, block);
++ u64 start = gpu_buddy_block_offset(block);
++ u64 size = gpu_buddy_block_size(mm, block);
+
+ drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size);
+ }
+@@ -1274,7 +38,7 @@ EXPORT_SYMBOL(drm_buddy_block_print);
+ * @mm: DRM buddy manager
+ * @p: DRM printer to use
+ */
+-void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p)
++void drm_buddy_print(struct gpu_buddy *mm, struct drm_printer *p)
+ {
+ int order;
+
+@@ -1282,7 +46,7 @@ void drm_buddy_print(struct drm_buddy *m
+ mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20);
+
+ for (order = mm->max_order; order >= 0; order--) {
+- struct drm_buddy_block *block, *tmp;
++ struct gpu_buddy_block *block, *tmp;
+ struct rb_root *root;
+ u64 count = 0, free;
+ unsigned int tree;
+@@ -1291,7 +55,7 @@ void drm_buddy_print(struct drm_buddy *m
+ root = &mm->free_trees[tree][order];
+
+ rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
+- BUG_ON(!drm_buddy_block_is_free(block));
++ BUG_ON(!gpu_buddy_block_is_free(block));
+ count++;
+ }
+ }
+@@ -1309,22 +73,5 @@ void drm_buddy_print(struct drm_buddy *m
+ }
+ EXPORT_SYMBOL(drm_buddy_print);
+
+-static void drm_buddy_module_exit(void)
+-{
+- kmem_cache_destroy(slab_blocks);
+-}
+-
+-static int __init drm_buddy_module_init(void)
+-{
+- slab_blocks = KMEM_CACHE(drm_buddy_block, 0);
+- if (!slab_blocks)
+- return -ENOMEM;
+-
+- return 0;
+-}
+-
+-module_init(drm_buddy_module_init);
+-module_exit(drm_buddy_module_exit);
+-
+-MODULE_DESCRIPTION("DRM Buddy Allocator");
++MODULE_DESCRIPTION("DRM-specific GPU Buddy Allocator Print Helpers");
+ MODULE_LICENSE("Dual MIT/GPL");
+--- a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c
++++ b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c
+@@ -7,7 +7,7 @@
+
+ #include <drm/ttm/ttm_placement.h>
+ #include <drm/ttm/ttm_tt.h>
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+
+ #include "i915_drv.h"
+ #include "i915_ttm_buddy_manager.h"
+--- a/drivers/gpu/drm/i915/i915_scatterlist.c
++++ b/drivers/gpu/drm/i915/i915_scatterlist.c
+@@ -7,7 +7,7 @@
+ #include "i915_scatterlist.h"
+ #include "i915_ttm_buddy_manager.h"
+
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+ #include <drm/drm_mm.h>
+
+ #include <linux/slab.h>
+@@ -167,9 +167,9 @@ struct i915_refct_sgt *i915_rsgt_from_bu
+ struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res);
+ const u64 size = res->size;
+ const u32 max_segment = round_down(UINT_MAX, page_alignment);
+- struct drm_buddy *mm = bman_res->mm;
++ struct gpu_buddy *mm = bman_res->mm;
+ struct list_head *blocks = &bman_res->blocks;
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ struct i915_refct_sgt *rsgt;
+ struct scatterlist *sg;
+ struct sg_table *st;
+@@ -202,8 +202,8 @@ struct i915_refct_sgt *i915_rsgt_from_bu
+ list_for_each_entry(block, blocks, link) {
+ u64 block_size, offset;
+
+- block_size = min_t(u64, size, drm_buddy_block_size(mm, block));
+- offset = drm_buddy_block_offset(block);
++ block_size = min_t(u64, size, gpu_buddy_block_size(mm, block));
++ offset = gpu_buddy_block_offset(block);
+
+ while (block_size) {
+ u64 len;
+--- a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c
++++ b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c
+@@ -8,6 +8,7 @@
+ #include <drm/ttm/ttm_placement.h>
+ #include <drm/ttm/ttm_bo.h>
+
++#include <linux/gpu_buddy.h>
+ #include <drm/drm_buddy.h>
+
+ #include "i915_ttm_buddy_manager.h"
+@@ -16,7 +17,7 @@
+
+ struct i915_ttm_buddy_manager {
+ struct ttm_resource_manager manager;
+- struct drm_buddy mm;
++ struct gpu_buddy mm;
+ struct list_head reserved;
+ struct mutex lock;
+ unsigned long visible_size;
+@@ -38,7 +39,7 @@ static int i915_ttm_buddy_man_alloc(stru
+ {
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+ struct i915_ttm_buddy_resource *bman_res;
+- struct drm_buddy *mm = &bman->mm;
++ struct gpu_buddy *mm = &bman->mm;
+ unsigned long n_pages, lpfn;
+ u64 min_page_size;
+ u64 size;
+@@ -57,13 +58,13 @@ static int i915_ttm_buddy_man_alloc(stru
+ bman_res->mm = mm;
+
+ if (place->flags & TTM_PL_FLAG_TOPDOWN)
+- bman_res->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
++ bman_res->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
+
+ if (place->flags & TTM_PL_FLAG_CONTIGUOUS)
+- bman_res->flags |= DRM_BUDDY_CONTIGUOUS_ALLOCATION;
++ bman_res->flags |= GPU_BUDDY_CONTIGUOUS_ALLOCATION;
+
+ if (place->fpfn || lpfn != man->size)
+- bman_res->flags |= DRM_BUDDY_RANGE_ALLOCATION;
++ bman_res->flags |= GPU_BUDDY_RANGE_ALLOCATION;
+
+ GEM_BUG_ON(!bman_res->base.size);
+ size = bman_res->base.size;
+@@ -89,7 +90,7 @@ static int i915_ttm_buddy_man_alloc(stru
+ goto err_free_res;
+ }
+
+- err = drm_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
++ err = gpu_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
+ (u64)lpfn << PAGE_SHIFT,
+ (u64)n_pages << PAGE_SHIFT,
+ min_page_size,
+@@ -101,15 +102,15 @@ static int i915_ttm_buddy_man_alloc(stru
+ if (lpfn <= bman->visible_size) {
+ bman_res->used_visible_size = PFN_UP(bman_res->base.size);
+ } else {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+
+ list_for_each_entry(block, &bman_res->blocks, link) {
+ unsigned long start =
+- drm_buddy_block_offset(block) >> PAGE_SHIFT;
++ gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+
+ if (start < bman->visible_size) {
+ unsigned long end = start +
+- (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++ (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+
+ bman_res->used_visible_size +=
+ min(end, bman->visible_size) - start;
+@@ -126,7 +127,7 @@ static int i915_ttm_buddy_man_alloc(stru
+ return 0;
+
+ err_free_blocks:
+- drm_buddy_free_list(mm, &bman_res->blocks, 0);
++ gpu_buddy_free_list(mm, &bman_res->blocks, 0);
+ mutex_unlock(&bman->lock);
+ err_free_res:
+ ttm_resource_fini(man, &bman_res->base);
+@@ -141,7 +142,7 @@ static void i915_ttm_buddy_man_free(stru
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+
+ mutex_lock(&bman->lock);
+- drm_buddy_free_list(&bman->mm, &bman_res->blocks, 0);
++ gpu_buddy_free_list(&bman->mm, &bman_res->blocks, 0);
+ bman->visible_avail += bman_res->used_visible_size;
+ mutex_unlock(&bman->lock);
+
+@@ -156,8 +157,8 @@ static bool i915_ttm_buddy_man_intersect
+ {
+ struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res);
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+- struct drm_buddy *mm = &bman->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = &bman->mm;
++ struct gpu_buddy_block *block;
+
+ if (!place->fpfn && !place->lpfn)
+ return true;
+@@ -176,9 +177,9 @@ static bool i915_ttm_buddy_man_intersect
+ /* Check each drm buddy block individually */
+ list_for_each_entry(block, &bman_res->blocks, link) {
+ unsigned long fpfn =
+- drm_buddy_block_offset(block) >> PAGE_SHIFT;
++ gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+ unsigned long lpfn = fpfn +
+- (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++ (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+
+ if (place->fpfn < lpfn && place->lpfn > fpfn)
+ return true;
+@@ -194,8 +195,8 @@ static bool i915_ttm_buddy_man_compatibl
+ {
+ struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res);
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+- struct drm_buddy *mm = &bman->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = &bman->mm;
++ struct gpu_buddy_block *block;
+
+ if (!place->fpfn && !place->lpfn)
+ return true;
+@@ -209,9 +210,9 @@ static bool i915_ttm_buddy_man_compatibl
+ /* Check each drm buddy block individually */
+ list_for_each_entry(block, &bman_res->blocks, link) {
+ unsigned long fpfn =
+- drm_buddy_block_offset(block) >> PAGE_SHIFT;
++ gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+ unsigned long lpfn = fpfn +
+- (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++ (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+
+ if (fpfn < place->fpfn || lpfn > place->lpfn)
+ return false;
+@@ -224,7 +225,7 @@ static void i915_ttm_buddy_man_debug(str
+ struct drm_printer *printer)
+ {
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+
+ mutex_lock(&bman->lock);
+ drm_printf(printer, "default_page_size: %lluKiB\n",
+@@ -293,7 +294,7 @@ int i915_ttm_buddy_man_init(struct ttm_d
+ if (!bman)
+ return -ENOMEM;
+
+- err = drm_buddy_init(&bman->mm, size, chunk_size);
++ err = gpu_buddy_init(&bman->mm, size, chunk_size);
+ if (err)
+ goto err_free_bman;
+
+@@ -333,7 +334,7 @@ int i915_ttm_buddy_man_fini(struct ttm_d
+ {
+ struct ttm_resource_manager *man = ttm_manager_type(bdev, type);
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+- struct drm_buddy *mm = &bman->mm;
++ struct gpu_buddy *mm = &bman->mm;
+ int ret;
+
+ ttm_resource_manager_set_used(man, false);
+@@ -345,8 +346,8 @@ int i915_ttm_buddy_man_fini(struct ttm_d
+ ttm_set_driver_manager(bdev, type, NULL);
+
+ mutex_lock(&bman->lock);
+- drm_buddy_free_list(mm, &bman->reserved, 0);
+- drm_buddy_fini(mm);
++ gpu_buddy_free_list(mm, &bman->reserved, 0);
++ gpu_buddy_fini(mm);
+ bman->visible_avail += bman->visible_reserved;
+ WARN_ON_ONCE(bman->visible_avail != bman->visible_size);
+ mutex_unlock(&bman->lock);
+@@ -371,15 +372,15 @@ int i915_ttm_buddy_man_reserve(struct tt
+ u64 start, u64 size)
+ {
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+- struct drm_buddy *mm = &bman->mm;
++ struct gpu_buddy *mm = &bman->mm;
+ unsigned long fpfn = start >> PAGE_SHIFT;
+ unsigned long flags = 0;
+ int ret;
+
+- flags |= DRM_BUDDY_RANGE_ALLOCATION;
++ flags |= GPU_BUDDY_RANGE_ALLOCATION;
+
+ mutex_lock(&bman->lock);
+- ret = drm_buddy_alloc_blocks(mm, start,
++ ret = gpu_buddy_alloc_blocks(mm, start,
+ start + size,
+ size, mm->chunk_size,
+ &bman->reserved,
+--- a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.h
++++ b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.h
+@@ -13,7 +13,7 @@
+
+ struct ttm_device;
+ struct ttm_resource_manager;
+-struct drm_buddy;
++struct gpu_buddy;
+
+ /**
+ * struct i915_ttm_buddy_resource
+@@ -33,7 +33,7 @@ struct i915_ttm_buddy_resource {
+ struct list_head blocks;
+ unsigned long flags;
+ unsigned long used_visible_size;
+- struct drm_buddy *mm;
++ struct gpu_buddy *mm;
+ };
+
+ /**
+--- a/drivers/gpu/drm/i915/selftests/intel_memory_region.c
++++ b/drivers/gpu/drm/i915/selftests/intel_memory_region.c
+@@ -6,7 +6,7 @@
+ #include <linux/prime_numbers.h>
+ #include <linux/sort.h>
+
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+
+ #include "../i915_selftest.h"
+
+@@ -371,7 +371,7 @@ static int igt_mock_splintered_region(vo
+ struct drm_i915_private *i915 = mem->i915;
+ struct i915_ttm_buddy_resource *res;
+ struct drm_i915_gem_object *obj;
+- struct drm_buddy *mm;
++ struct gpu_buddy *mm;
+ unsigned int expected_order;
+ LIST_HEAD(objects);
+ u64 size;
+@@ -455,8 +455,8 @@ static int igt_mock_max_segment(void *ar
+ struct drm_i915_private *i915 = mem->i915;
+ struct i915_ttm_buddy_resource *res;
+ struct drm_i915_gem_object *obj;
+- struct drm_buddy_block *block;
+- struct drm_buddy *mm;
++ struct gpu_buddy_block *block;
++ struct gpu_buddy *mm;
+ struct list_head *blocks;
+ struct scatterlist *sg;
+ I915_RND_STATE(prng);
+@@ -495,8 +495,8 @@ static int igt_mock_max_segment(void *ar
+ mm = res->mm;
+ size = 0;
+ list_for_each_entry(block, blocks, link) {
+- if (drm_buddy_block_size(mm, block) > size)
+- size = drm_buddy_block_size(mm, block);
++ if (gpu_buddy_block_size(mm, block) > size)
++ size = gpu_buddy_block_size(mm, block);
+ }
+ if (size < max_segment) {
+ pr_err("%s: Failed to create a huge contiguous block [> %u], largest block %lld\n",
+@@ -535,14 +535,14 @@ static u64 igt_object_mappable_total(str
+ struct intel_memory_region *mr = obj->mm.region;
+ struct i915_ttm_buddy_resource *bman_res =
+ to_ttm_buddy_resource(obj->mm.res);
+- struct drm_buddy *mm = bman_res->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = bman_res->mm;
++ struct gpu_buddy_block *block;
+ u64 total;
+
+ total = 0;
+ list_for_each_entry(block, &bman_res->blocks, link) {
+- u64 start = drm_buddy_block_offset(block);
+- u64 end = start + drm_buddy_block_size(mm, block);
++ u64 start = gpu_buddy_block_offset(block);
++ u64 end = start + gpu_buddy_block_size(mm, block);
+
+ if (start < resource_size(&mr->io))
+ total += min_t(u64, end, resource_size(&mr->io)) - start;
+--- a/drivers/gpu/drm/lib/drm_random.c
++++ /dev/null
+@@ -1,43 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-#include <linux/bitops.h>
+-#include <linux/kernel.h>
+-#include <linux/random.h>
+-#include <linux/slab.h>
+-#include <linux/types.h>
+-
+-#include "drm_random.h"
+-
+-u32 drm_prandom_u32_max_state(u32 ep_ro, struct rnd_state *state)
+-{
+- return upper_32_bits((u64)prandom_u32_state(state) * ep_ro);
+-}
+-EXPORT_SYMBOL(drm_prandom_u32_max_state);
+-
+-void drm_random_reorder(unsigned int *order, unsigned int count,
+- struct rnd_state *state)
+-{
+- unsigned int i, j;
+-
+- for (i = 0; i < count; ++i) {
+- BUILD_BUG_ON(sizeof(unsigned int) > sizeof(u32));
+- j = drm_prandom_u32_max_state(count, state);
+- swap(order[i], order[j]);
+- }
+-}
+-EXPORT_SYMBOL(drm_random_reorder);
+-
+-unsigned int *drm_random_order(unsigned int count, struct rnd_state *state)
+-{
+- unsigned int *order, i;
+-
+- order = kmalloc_array(count, sizeof(*order), GFP_KERNEL);
+- if (!order)
+- return order;
+-
+- for (i = 0; i < count; i++)
+- order[i] = i;
+-
+- drm_random_reorder(order, count, state);
+- return order;
+-}
+-EXPORT_SYMBOL(drm_random_order);
+--- a/drivers/gpu/drm/lib/drm_random.h
++++ /dev/null
+@@ -1,28 +0,0 @@
+-/* SPDX-License-Identifier: GPL-2.0 */
+-#ifndef __DRM_RANDOM_H__
+-#define __DRM_RANDOM_H__
+-
+-/* This is a temporary home for a couple of utility functions that should
+- * be transposed to lib/ at the earliest convenience.
+- */
+-
+-#include <linux/random.h>
+-
+-#define DRM_RND_STATE_INITIALIZER(seed__) ({ \
+- struct rnd_state state__; \
+- prandom_seed_state(&state__, (seed__)); \
+- state__; \
+-})
+-
+-#define DRM_RND_STATE(name__, seed__) \
+- struct rnd_state name__ = DRM_RND_STATE_INITIALIZER(seed__)
+-
+-unsigned int *drm_random_order(unsigned int count,
+- struct rnd_state *state);
+-void drm_random_reorder(unsigned int *order,
+- unsigned int count,
+- struct rnd_state *state);
+-u32 drm_prandom_u32_max_state(u32 ep_ro,
+- struct rnd_state *state);
+-
+-#endif /* !__DRM_RANDOM_H__ */
+--- a/drivers/gpu/drm/tests/Makefile
++++ b/drivers/gpu/drm/tests/Makefile
+@@ -4,7 +4,6 @@ obj-$(CONFIG_DRM_KUNIT_TEST_HELPERS) +=
+ drm_kunit_helpers.o
+
+ obj-$(CONFIG_DRM_KUNIT_TEST) += \
+- drm_buddy_test.o \
+ drm_cmdline_parser_test.o \
+ drm_connector_test.o \
+ drm_damage_helper_test.o \
+--- a/drivers/gpu/drm/tests/drm_buddy_test.c
++++ /dev/null
+@@ -1,778 +0,0 @@
+-// SPDX-License-Identifier: MIT
+-/*
+- * Copyright © 2019 Intel Corporation
+- * Copyright © 2022 MaÃra Canal <mairacanal@riseup.net>
+- */
+-
+-#include <kunit/test.h>
+-
+-#include <linux/prime_numbers.h>
+-#include <linux/sched/signal.h>
+-#include <linux/sizes.h>
+-
+-#include <drm/drm_buddy.h>
+-
+-#include "../lib/drm_random.h"
+-
+-static unsigned int random_seed;
+-
+-static inline u64 get_size(int order, u64 chunk_size)
+-{
+- return (1 << order) * chunk_size;
+-}
+-
+-static void drm_test_buddy_alloc_range_bias(struct kunit *test)
+-{
+- u32 mm_size, size, ps, bias_size, bias_start, bias_end, bias_rem;
+- DRM_RND_STATE(prng, random_seed);
+- unsigned int i, count, *order;
+- struct drm_buddy_block *block;
+- unsigned long flags;
+- struct drm_buddy mm;
+- LIST_HEAD(allocated);
+-
+- bias_size = SZ_1M;
+- ps = roundup_pow_of_two(prandom_u32_state(&prng) % bias_size);
+- ps = max(SZ_4K, ps);
+- mm_size = (SZ_8M-1) & ~(ps-1); /* Multiple roots */
+-
+- kunit_info(test, "mm_size=%u, ps=%u\n", mm_size, ps);
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
+- "buddy_init failed\n");
+-
+- count = mm_size / bias_size;
+- order = drm_random_order(count, &prng);
+- KUNIT_EXPECT_TRUE(test, order);
+-
+- /*
+- * Idea is to split the address space into uniform bias ranges, and then
+- * in some random order allocate within each bias, using various
+- * patterns within. This should detect if allocations leak out from a
+- * given bias, for example.
+- */
+-
+- for (i = 0; i < count; i++) {
+- LIST_HEAD(tmp);
+- u32 size;
+-
+- bias_start = order[i] * bias_size;
+- bias_end = bias_start + bias_size;
+- bias_rem = bias_size;
+-
+- /* internal round_up too big */
+- KUNIT_ASSERT_TRUE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, bias_size + ps, bias_size,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, bias_size, bias_size);
+-
+- /* size too big */
+- KUNIT_ASSERT_TRUE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, bias_size + ps, ps,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, bias_size + ps, ps);
+-
+- /* bias range too small for size */
+- KUNIT_ASSERT_TRUE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start + ps,
+- bias_end, bias_size, ps,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start + ps, bias_end, bias_size, ps);
+-
+- /* bias misaligned */
+- KUNIT_ASSERT_TRUE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start + ps,
+- bias_end - ps,
+- bias_size >> 1, bias_size >> 1,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc h didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start + ps, bias_end - ps, bias_size >> 1, bias_size >> 1);
+-
+- /* single big page */
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, bias_size, bias_size,
+- &tmp,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc i failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, bias_size, bias_size);
+- drm_buddy_free_list(&mm, &tmp, 0);
+-
+- /* single page with internal round_up */
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, ps, bias_size,
+- &tmp,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, ps, bias_size);
+- drm_buddy_free_list(&mm, &tmp, 0);
+-
+- /* random size within */
+- size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+- if (size)
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, size, ps,
+- &tmp,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, size, ps);
+-
+- bias_rem -= size;
+- /* too big for current avail */
+- KUNIT_ASSERT_TRUE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, bias_rem + ps, ps,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, bias_rem + ps, ps);
+-
+- if (bias_rem) {
+- /* random fill of the remainder */
+- size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+- size = max(size, ps);
+-
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, size, ps,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, size, ps);
+- /*
+- * Intentionally allow some space to be left
+- * unallocated, and ideally not always on the bias
+- * boundaries.
+- */
+- drm_buddy_free_list(&mm, &tmp, 0);
+- } else {
+- list_splice_tail(&tmp, &allocated);
+- }
+- }
+-
+- kfree(order);
+- drm_buddy_free_list(&mm, &allocated, 0);
+- drm_buddy_fini(&mm);
+-
+- /*
+- * Something more free-form. Idea is to pick a random starting bias
+- * range within the address space and then start filling it up. Also
+- * randomly grow the bias range in both directions as we go along. This
+- * should give us bias start/end which is not always uniform like above,
+- * and in some cases will require the allocator to jump over already
+- * allocated nodes in the middle of the address space.
+- */
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
+- "buddy_init failed\n");
+-
+- bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
+- bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
+- bias_end = max(bias_end, bias_start + ps);
+- bias_rem = bias_end - bias_start;
+-
+- do {
+- u32 size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+-
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, size, ps,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, size, ps);
+- bias_rem -= size;
+-
+- /*
+- * Try to randomly grow the bias range in both directions, or
+- * only one, or perhaps don't grow at all.
+- */
+- do {
+- u32 old_bias_start = bias_start;
+- u32 old_bias_end = bias_end;
+-
+- if (bias_start)
+- bias_start -= round_up(prandom_u32_state(&prng) % bias_start, ps);
+- if (bias_end != mm_size)
+- bias_end += round_up(prandom_u32_state(&prng) % (mm_size - bias_end), ps);
+-
+- bias_rem += old_bias_start - bias_start;
+- bias_rem += bias_end - old_bias_end;
+- } while (!bias_rem && (bias_start || bias_end != mm_size));
+- } while (bias_rem);
+-
+- KUNIT_ASSERT_EQ(test, bias_start, 0);
+- KUNIT_ASSERT_EQ(test, bias_end, mm_size);
+- KUNIT_ASSERT_TRUE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start, bias_end,
+- ps, ps,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc passed with bias(%x-%x), size=%u\n",
+- bias_start, bias_end, ps);
+-
+- drm_buddy_free_list(&mm, &allocated, 0);
+- drm_buddy_fini(&mm);
+-
+- /*
+- * Allocate cleared blocks in the bias range when the DRM buddy's clear avail is
+- * zero. This will validate the bias range allocation in scenarios like system boot
+- * when no cleared blocks are available and exercise the fallback path too. The resulting
+- * blocks should always be dirty.
+- */
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
+- "buddy_init failed\n");
+-
+- bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
+- bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
+- bias_end = max(bias_end, bias_start + ps);
+- bias_rem = bias_end - bias_start;
+-
+- flags = DRM_BUDDY_CLEAR_ALLOCATION | DRM_BUDDY_RANGE_ALLOCATION;
+- size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+-
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, size, ps,
+- &allocated,
+- flags),
+- "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, size, ps);
+-
+- list_for_each_entry(block, &allocated, link)
+- KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
+-
+- drm_buddy_free_list(&mm, &allocated, 0);
+- drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_clear(struct kunit *test)
+-{
+- unsigned long n_pages, total, i = 0;
+- DRM_RND_STATE(prng, random_seed);
+- const unsigned long ps = SZ_4K;
+- struct drm_buddy_block *block;
+- const int max_order = 12;
+- LIST_HEAD(allocated);
+- struct drm_buddy mm;
+- unsigned int order;
+- u32 mm_size, size;
+- LIST_HEAD(dirty);
+- LIST_HEAD(clean);
+-
+- mm_size = SZ_4K << max_order;
+- KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+-
+- KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-
+- /*
+- * Idea is to allocate and free some random portion of the address space,
+- * returning those pages as non-dirty and randomly alternate between
+- * requesting dirty and non-dirty pages (not going over the limit
+- * we freed as non-dirty), putting that into two separate lists.
+- * Loop over both lists at the end checking that the dirty list
+- * is indeed all dirty pages and vice versa. Free it all again,
+- * keeping the dirty/clear status.
+- */
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 5 * ps, ps, &allocated,
+- DRM_BUDDY_TOPDOWN_ALLOCATION),
+- "buddy_alloc hit an error size=%lu\n", 5 * ps);
+- drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+-
+- n_pages = 10;
+- do {
+- unsigned long flags;
+- struct list_head *list;
+- int slot = i % 2;
+-
+- if (slot == 0) {
+- list = &dirty;
+- flags = 0;
+- } else {
+- list = &clean;
+- flags = DRM_BUDDY_CLEAR_ALLOCATION;
+- }
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- ps, ps, list,
+- flags),
+- "buddy_alloc hit an error size=%lu\n", ps);
+- } while (++i < n_pages);
+-
+- list_for_each_entry(block, &clean, link)
+- KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), true);
+-
+- list_for_each_entry(block, &dirty, link)
+- KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
+-
+- drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
+-
+- /*
+- * Trying to go over the clear limit for some allocation.
+- * The allocation should never fail with reasonable page-size.
+- */
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 10 * ps, ps, &clean,
+- DRM_BUDDY_CLEAR_ALLOCATION),
+- "buddy_alloc hit an error size=%lu\n", 10 * ps);
+-
+- drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
+- drm_buddy_free_list(&mm, &dirty, 0);
+- drm_buddy_fini(&mm);
+-
+- KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+-
+- /*
+- * Create a new mm. Intentionally fragment the address space by creating
+- * two alternating lists. Free both lists, one as dirty the other as clean.
+- * Try to allocate double the previous size with matching min_page_size. The
+- * allocation should never fail as it calls the force_merge. Also check that
+- * the page is always dirty after force_merge. Free the page as dirty, then
+- * repeat the whole thing, increment the order until we hit the max_order.
+- */
+-
+- i = 0;
+- n_pages = mm_size / ps;
+- do {
+- struct list_head *list;
+- int slot = i % 2;
+-
+- if (slot == 0)
+- list = &dirty;
+- else
+- list = &clean;
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- ps, ps, list, 0),
+- "buddy_alloc hit an error size=%lu\n", ps);
+- } while (++i < n_pages);
+-
+- drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
+- drm_buddy_free_list(&mm, &dirty, 0);
+-
+- order = 1;
+- do {
+- size = SZ_4K << order;
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- size, size, &allocated,
+- DRM_BUDDY_CLEAR_ALLOCATION),
+- "buddy_alloc hit an error size=%u\n", size);
+- total = 0;
+- list_for_each_entry(block, &allocated, link) {
+- if (size != mm_size)
+- KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
+- total += drm_buddy_block_size(&mm, block);
+- }
+- KUNIT_EXPECT_EQ(test, total, size);
+-
+- drm_buddy_free_list(&mm, &allocated, 0);
+- } while (++order <= max_order);
+-
+- drm_buddy_fini(&mm);
+-
+- /*
+- * Create a new mm with a non power-of-two size. Allocate a random size, free as
+- * cleared and then call fini. This will ensure the multi-root force merge during
+- * fini.
+- */
+- mm_size = 12 * SZ_4K;
+- size = max(round_up(prandom_u32_state(&prng) % mm_size, ps), ps);
+- KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- size, ps, &allocated,
+- DRM_BUDDY_TOPDOWN_ALLOCATION),
+- "buddy_alloc hit an error size=%u\n", size);
+- drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+- drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_contiguous(struct kunit *test)
+-{
+- const unsigned long ps = SZ_4K, mm_size = 16 * 3 * SZ_4K;
+- unsigned long i, n_pages, total;
+- struct drm_buddy_block *block;
+- struct drm_buddy mm;
+- LIST_HEAD(left);
+- LIST_HEAD(middle);
+- LIST_HEAD(right);
+- LIST_HEAD(allocated);
+-
+- KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+-
+- /*
+- * Idea is to fragment the address space by alternating block
+- * allocations between three different lists; one for left, middle and
+- * right. We can then free a list to simulate fragmentation. In
+- * particular we want to exercise the DRM_BUDDY_CONTIGUOUS_ALLOCATION,
+- * including the try_harder path.
+- */
+-
+- i = 0;
+- n_pages = mm_size / ps;
+- do {
+- struct list_head *list;
+- int slot = i % 3;
+-
+- if (slot == 0)
+- list = &left;
+- else if (slot == 1)
+- list = &middle;
+- else
+- list = &right;
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- ps, ps, list, 0),
+- "buddy_alloc hit an error size=%lu\n",
+- ps);
+- } while (++i < n_pages);
+-
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 3 * ps, ps, &allocated,
+- DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+- "buddy_alloc didn't error size=%lu\n", 3 * ps);
+-
+- drm_buddy_free_list(&mm, &middle, 0);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 3 * ps, ps, &allocated,
+- DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+- "buddy_alloc didn't error size=%lu\n", 3 * ps);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 2 * ps, ps, &allocated,
+- DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+- "buddy_alloc didn't error size=%lu\n", 2 * ps);
+-
+- drm_buddy_free_list(&mm, &right, 0);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 3 * ps, ps, &allocated,
+- DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+- "buddy_alloc didn't error size=%lu\n", 3 * ps);
+- /*
+- * At this point we should have enough contiguous space for 2 blocks,
+- * however they are never buddies (since we freed middle and right) so
+- * will require the try_harder logic to find them.
+- */
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 2 * ps, ps, &allocated,
+- DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+- "buddy_alloc hit an error size=%lu\n", 2 * ps);
+-
+- drm_buddy_free_list(&mm, &left, 0);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 3 * ps, ps, &allocated,
+- DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+- "buddy_alloc hit an error size=%lu\n", 3 * ps);
+-
+- total = 0;
+- list_for_each_entry(block, &allocated, link)
+- total += drm_buddy_block_size(&mm, block);
+-
+- KUNIT_ASSERT_EQ(test, total, ps * 2 + ps * 3);
+-
+- drm_buddy_free_list(&mm, &allocated, 0);
+- drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_pathological(struct kunit *test)
+-{
+- u64 mm_size, size, start = 0;
+- struct drm_buddy_block *block;
+- const int max_order = 3;
+- unsigned long flags = 0;
+- int order, top;
+- struct drm_buddy mm;
+- LIST_HEAD(blocks);
+- LIST_HEAD(holes);
+- LIST_HEAD(tmp);
+-
+- /*
+- * Create a pot-sized mm, then allocate one of each possible
+- * order within. This should leave the mm with exactly one
+- * page left. Free the largest block, then whittle down again.
+- * Eventually we will have a fully 50% fragmented mm.
+- */
+-
+- mm_size = SZ_4K << max_order;
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+- "buddy_init failed\n");
+-
+- KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-
+- for (top = max_order; top; top--) {
+- /* Make room by freeing the largest allocated block */
+- block = list_first_entry_or_null(&blocks, typeof(*block), link);
+- if (block) {
+- list_del(&block->link);
+- drm_buddy_free_block(&mm, block);
+- }
+-
+- for (order = top; order--;) {
+- size = get_size(order, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start,
+- mm_size, size, size,
+- &tmp, flags),
+- "buddy_alloc hit -ENOMEM with order=%d, top=%d\n",
+- order, top);
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_move_tail(&block->link, &blocks);
+- }
+-
+- /* There should be one final page for this sub-allocation */
+- size = get_size(0, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc hit -ENOMEM for hole\n");
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_move_tail(&block->link, &holes);
+-
+- size = get_size(top, mm.chunk_size);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc unexpectedly succeeded at top-order %d/%d, it should be full!",
+- top, max_order);
+- }
+-
+- drm_buddy_free_list(&mm, &holes, 0);
+-
+- /* Nothing larger than blocks of chunk_size now available */
+- for (order = 1; order <= max_order; order++) {
+- size = get_size(order, mm.chunk_size);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc unexpectedly succeeded at order %d, it should be full!",
+- order);
+- }
+-
+- list_splice_tail(&holes, &blocks);
+- drm_buddy_free_list(&mm, &blocks, 0);
+- drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_pessimistic(struct kunit *test)
+-{
+- u64 mm_size, size, start = 0;
+- struct drm_buddy_block *block, *bn;
+- const unsigned int max_order = 16;
+- unsigned long flags = 0;
+- struct drm_buddy mm;
+- unsigned int order;
+- LIST_HEAD(blocks);
+- LIST_HEAD(tmp);
+-
+- /*
+- * Create a pot-sized mm, then allocate one of each possible
+- * order within. This should leave the mm with exactly one
+- * page left.
+- */
+-
+- mm_size = SZ_4K << max_order;
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+- "buddy_init failed\n");
+-
+- KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-
+- for (order = 0; order < max_order; order++) {
+- size = get_size(order, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc hit -ENOMEM with order=%d\n",
+- order);
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_move_tail(&block->link, &blocks);
+- }
+-
+- /* And now the last remaining block available */
+- size = get_size(0, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc hit -ENOMEM on final alloc\n");
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_move_tail(&block->link, &blocks);
+-
+- /* Should be completely full! */
+- for (order = max_order; order--;) {
+- size = get_size(order, mm.chunk_size);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc unexpectedly succeeded, it should be full!");
+- }
+-
+- block = list_last_entry(&blocks, typeof(*block), link);
+- list_del(&block->link);
+- drm_buddy_free_block(&mm, block);
+-
+- /* As we free in increasing size, we make available larger blocks */
+- order = 1;
+- list_for_each_entry_safe(block, bn, &blocks, link) {
+- list_del(&block->link);
+- drm_buddy_free_block(&mm, block);
+-
+- size = get_size(order, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc hit -ENOMEM with order=%d\n",
+- order);
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_del(&block->link);
+- drm_buddy_free_block(&mm, block);
+- order++;
+- }
+-
+- /* To confirm, now the whole mm should be available */
+- size = get_size(max_order, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc (realloc) hit -ENOMEM with order=%d\n",
+- max_order);
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_del(&block->link);
+- drm_buddy_free_block(&mm, block);
+- drm_buddy_free_list(&mm, &blocks, 0);
+- drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_optimistic(struct kunit *test)
+-{
+- u64 mm_size, size, start = 0;
+- struct drm_buddy_block *block;
+- unsigned long flags = 0;
+- const int max_order = 16;
+- struct drm_buddy mm;
+- LIST_HEAD(blocks);
+- LIST_HEAD(tmp);
+- int order;
+-
+- /*
+- * Create a mm with one block of each order available, and
+- * try to allocate them all.
+- */
+-
+- mm_size = SZ_4K * ((1 << (max_order + 1)) - 1);
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+- "buddy_init failed\n");
+-
+- KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-
+- for (order = 0; order <= max_order; order++) {
+- size = get_size(order, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc hit -ENOMEM with order=%d\n",
+- order);
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_move_tail(&block->link, &blocks);
+- }
+-
+- /* Should be completely full! */
+- size = get_size(0, mm.chunk_size);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc unexpectedly succeeded, it should be full!");
+-
+- drm_buddy_free_list(&mm, &blocks, 0);
+- drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_limit(struct kunit *test)
+-{
+- u64 size = U64_MAX, start = 0;
+- struct drm_buddy_block *block;
+- unsigned long flags = 0;
+- LIST_HEAD(allocated);
+- struct drm_buddy mm;
+-
+- KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, size, SZ_4K));
+-
+- KUNIT_EXPECT_EQ_MSG(test, mm.max_order, DRM_BUDDY_MAX_ORDER,
+- "mm.max_order(%d) != %d\n", mm.max_order,
+- DRM_BUDDY_MAX_ORDER);
+-
+- size = mm.chunk_size << mm.max_order;
+- KUNIT_EXPECT_FALSE(test, drm_buddy_alloc_blocks(&mm, start, size, size,
+- mm.chunk_size, &allocated, flags));
+-
+- block = list_first_entry_or_null(&allocated, struct drm_buddy_block, link);
+- KUNIT_EXPECT_TRUE(test, block);
+-
+- KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_order(block), mm.max_order,
+- "block order(%d) != %d\n",
+- drm_buddy_block_order(block), mm.max_order);
+-
+- KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_size(&mm, block),
+- BIT_ULL(mm.max_order) * mm.chunk_size,
+- "block size(%llu) != %llu\n",
+- drm_buddy_block_size(&mm, block),
+- BIT_ULL(mm.max_order) * mm.chunk_size);
+-
+- drm_buddy_free_list(&mm, &allocated, 0);
+- drm_buddy_fini(&mm);
+-}
+-
+-static int drm_buddy_suite_init(struct kunit_suite *suite)
+-{
+- while (!random_seed)
+- random_seed = get_random_u32();
+-
+- kunit_info(suite, "Testing DRM buddy manager, with random_seed=0x%x\n",
+- random_seed);
+-
+- return 0;
+-}
+-
+-static struct kunit_case drm_buddy_tests[] = {
+- KUNIT_CASE(drm_test_buddy_alloc_limit),
+- KUNIT_CASE(drm_test_buddy_alloc_optimistic),
+- KUNIT_CASE(drm_test_buddy_alloc_pessimistic),
+- KUNIT_CASE(drm_test_buddy_alloc_pathological),
+- KUNIT_CASE(drm_test_buddy_alloc_contiguous),
+- KUNIT_CASE(drm_test_buddy_alloc_clear),
+- KUNIT_CASE(drm_test_buddy_alloc_range_bias),
+- {}
+-};
+-
+-static struct kunit_suite drm_buddy_test_suite = {
+- .name = "drm_buddy",
+- .suite_init = drm_buddy_suite_init,
+- .test_cases = drm_buddy_tests,
+-};
+-
+-kunit_test_suite(drm_buddy_test_suite);
+-
+-MODULE_AUTHOR("Intel Corporation");
+-MODULE_DESCRIPTION("Kunit test for drm_buddy functions");
+-MODULE_LICENSE("GPL");
+--- a/drivers/gpu/drm/tests/drm_exec_test.c
++++ b/drivers/gpu/drm/tests/drm_exec_test.c
+@@ -16,8 +16,6 @@
+ #include <drm/drm_gem.h>
+ #include <drm/drm_kunit_helpers.h>
+
+-#include "../lib/drm_random.h"
+-
+ struct drm_exec_priv {
+ struct device *dev;
+ struct drm_device *drm;
+--- a/drivers/gpu/drm/tests/drm_mm_test.c
++++ b/drivers/gpu/drm/tests/drm_mm_test.c
+@@ -15,8 +15,6 @@
+
+ #include <drm/drm_mm.h>
+
+-#include "../lib/drm_random.h"
+-
+ enum {
+ BEST,
+ BOTTOMUP,
+--- a/drivers/gpu/drm/ttm/tests/ttm_bo_validate_test.c
++++ b/drivers/gpu/drm/ttm/tests/ttm_bo_validate_test.c
+@@ -251,7 +251,7 @@ static void ttm_bo_validate_basic(struct
+ NULL, &dummy_ttm_bo_destroy);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+- snd_place = ttm_place_kunit_init(test, snd_mem, DRM_BUDDY_TOPDOWN_ALLOCATION);
++ snd_place = ttm_place_kunit_init(test, snd_mem, GPU_BUDDY_TOPDOWN_ALLOCATION);
+ snd_placement = ttm_placement_kunit_init(test, snd_place, 1);
+
+ err = ttm_bo_validate(bo, snd_placement, &ctx_val);
+@@ -263,7 +263,7 @@ static void ttm_bo_validate_basic(struct
+ KUNIT_EXPECT_TRUE(test, ttm_tt_is_populated(bo->ttm));
+ KUNIT_EXPECT_EQ(test, bo->resource->mem_type, snd_mem);
+ KUNIT_EXPECT_EQ(test, bo->resource->placement,
+- DRM_BUDDY_TOPDOWN_ALLOCATION);
++ GPU_BUDDY_TOPDOWN_ALLOCATION);
+
+ ttm_bo_put(bo);
+ ttm_mock_manager_fini(priv->ttm_dev, snd_mem);
+--- a/drivers/gpu/drm/ttm/tests/ttm_mock_manager.c
++++ b/drivers/gpu/drm/ttm/tests/ttm_mock_manager.c
+@@ -27,7 +27,7 @@ static int ttm_mock_manager_alloc(struct
+ {
+ struct ttm_mock_manager *manager = to_mock_mgr(man);
+ struct ttm_mock_resource *mock_res;
+- struct drm_buddy *mm = &manager->mm;
++ struct gpu_buddy *mm = &manager->mm;
+ u64 lpfn, fpfn, alloc_size;
+ int err;
+
+@@ -43,14 +43,14 @@ static int ttm_mock_manager_alloc(struct
+ INIT_LIST_HEAD(&mock_res->blocks);
+
+ if (place->flags & TTM_PL_FLAG_TOPDOWN)
+- mock_res->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
++ mock_res->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
+
+ if (place->flags & TTM_PL_FLAG_CONTIGUOUS)
+- mock_res->flags |= DRM_BUDDY_CONTIGUOUS_ALLOCATION;
++ mock_res->flags |= GPU_BUDDY_CONTIGUOUS_ALLOCATION;
+
+ alloc_size = (uint64_t)mock_res->base.size;
+ mutex_lock(&manager->lock);
+- err = drm_buddy_alloc_blocks(mm, fpfn, lpfn, alloc_size,
++ err = gpu_buddy_alloc_blocks(mm, fpfn, lpfn, alloc_size,
+ manager->default_page_size,
+ &mock_res->blocks,
+ mock_res->flags);
+@@ -63,7 +63,7 @@ static int ttm_mock_manager_alloc(struct
+ return 0;
+
+ error_free_blocks:
+- drm_buddy_free_list(mm, &mock_res->blocks, 0);
++ gpu_buddy_free_list(mm, &mock_res->blocks, 0);
+ ttm_resource_fini(man, &mock_res->base);
+ mutex_unlock(&manager->lock);
+
+@@ -75,10 +75,10 @@ static void ttm_mock_manager_free(struct
+ {
+ struct ttm_mock_manager *manager = to_mock_mgr(man);
+ struct ttm_mock_resource *mock_res = to_mock_mgr_resource(res);
+- struct drm_buddy *mm = &manager->mm;
++ struct gpu_buddy *mm = &manager->mm;
+
+ mutex_lock(&manager->lock);
+- drm_buddy_free_list(mm, &mock_res->blocks, 0);
++ gpu_buddy_free_list(mm, &mock_res->blocks, 0);
+ mutex_unlock(&manager->lock);
+
+ ttm_resource_fini(man, res);
+@@ -102,7 +102,7 @@ int ttm_mock_manager_init(struct ttm_dev
+
+ mutex_init(&manager->lock);
+
+- err = drm_buddy_init(&manager->mm, size, PAGE_SIZE);
++ err = gpu_buddy_init(&manager->mm, size, PAGE_SIZE);
+
+ if (err) {
+ kfree(manager);
+@@ -138,7 +138,7 @@ void ttm_mock_manager_fini(struct ttm_de
+ ttm_resource_manager_set_used(man, false);
+
+ mutex_lock(&mock_man->lock);
+- drm_buddy_fini(&mock_man->mm);
++ gpu_buddy_fini(&mock_man->mm);
+ mutex_unlock(&mock_man->lock);
+
+ ttm_set_driver_manager(bdev, mem_type, NULL);
+--- a/drivers/gpu/drm/ttm/tests/ttm_mock_manager.h
++++ b/drivers/gpu/drm/ttm/tests/ttm_mock_manager.h
+@@ -5,11 +5,11 @@
+ #ifndef TTM_MOCK_MANAGER_H
+ #define TTM_MOCK_MANAGER_H
+
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+
+ struct ttm_mock_manager {
+ struct ttm_resource_manager man;
+- struct drm_buddy mm;
++ struct gpu_buddy mm;
+ u64 default_page_size;
+ /* protects allocations of mock buffer objects */
+ struct mutex lock;
+--- a/drivers/gpu/drm/xe/xe_res_cursor.h
++++ b/drivers/gpu/drm/xe/xe_res_cursor.h
+@@ -44,10 +44,10 @@ struct xe_res_cursor {
+ void *node;
+ u32 mem_type;
+ struct scatterlist *sgl;
+- struct drm_buddy *mm;
++ struct gpu_buddy *mm;
+ };
+
+-static struct drm_buddy *xe_res_get_buddy(struct ttm_resource *res)
++static struct gpu_buddy *xe_res_get_buddy(struct ttm_resource *res)
+ {
+ struct ttm_resource_manager *mgr;
+
+@@ -81,30 +81,30 @@ static inline void xe_res_first(struct t
+ case XE_PL_STOLEN:
+ case XE_PL_VRAM0:
+ case XE_PL_VRAM1: {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ struct list_head *head, *next;
+- struct drm_buddy *mm = xe_res_get_buddy(res);
++ struct gpu_buddy *mm = xe_res_get_buddy(res);
+
+ head = &to_xe_ttm_vram_mgr_resource(res)->blocks;
+
+ block = list_first_entry_or_null(head,
+- struct drm_buddy_block,
++ struct gpu_buddy_block,
+ link);
+ if (!block)
+ goto fallback;
+
+- while (start >= drm_buddy_block_size(mm, block)) {
+- start -= drm_buddy_block_size(mm, block);
++ while (start >= gpu_buddy_block_size(mm, block)) {
++ start -= gpu_buddy_block_size(mm, block);
+
+ next = block->link.next;
+ if (next != head)
+- block = list_entry(next, struct drm_buddy_block,
++ block = list_entry(next, struct gpu_buddy_block,
+ link);
+ }
+
+ cur->mm = mm;
+- cur->start = drm_buddy_block_offset(block) + start;
+- cur->size = min(drm_buddy_block_size(mm, block) - start,
++ cur->start = gpu_buddy_block_offset(block) + start;
++ cur->size = min(gpu_buddy_block_size(mm, block) - start,
+ size);
+ cur->remaining = size;
+ cur->node = block;
+@@ -175,7 +175,7 @@ static inline void xe_res_first_sg(const
+ */
+ static inline void xe_res_next(struct xe_res_cursor *cur, u64 size)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ struct list_head *next;
+ u64 start;
+
+@@ -205,18 +205,18 @@ static inline void xe_res_next(struct xe
+ block = cur->node;
+
+ next = block->link.next;
+- block = list_entry(next, struct drm_buddy_block, link);
++ block = list_entry(next, struct gpu_buddy_block, link);
+
+
+- while (start >= drm_buddy_block_size(cur->mm, block)) {
+- start -= drm_buddy_block_size(cur->mm, block);
++ while (start >= gpu_buddy_block_size(cur->mm, block)) {
++ start -= gpu_buddy_block_size(cur->mm, block);
+
+ next = block->link.next;
+- block = list_entry(next, struct drm_buddy_block, link);
++ block = list_entry(next, struct gpu_buddy_block, link);
+ }
+
+- cur->start = drm_buddy_block_offset(block) + start;
+- cur->size = min(drm_buddy_block_size(cur->mm, block) - start,
++ cur->start = gpu_buddy_block_offset(block) + start;
++ cur->size = min(gpu_buddy_block_size(cur->mm, block) - start,
+ cur->remaining);
+ cur->node = block;
+ break;
+--- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
++++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
+@@ -5,6 +5,7 @@
+ */
+
+ #include <drm/drm_managed.h>
++#include <drm/drm_buddy.h>
+
+ #include <drm/ttm/ttm_placement.h>
+ #include <drm/ttm/ttm_range_manager.h>
+@@ -15,16 +16,16 @@
+ #include "xe_res_cursor.h"
+ #include "xe_ttm_vram_mgr.h"
+
+-static inline struct drm_buddy_block *
++static inline struct gpu_buddy_block *
+ xe_ttm_vram_mgr_first_block(struct list_head *list)
+ {
+- return list_first_entry_or_null(list, struct drm_buddy_block, link);
++ return list_first_entry_or_null(list, struct gpu_buddy_block, link);
+ }
+
+-static inline bool xe_is_vram_mgr_blocks_contiguous(struct drm_buddy *mm,
++static inline bool xe_is_vram_mgr_blocks_contiguous(struct gpu_buddy *mm,
+ struct list_head *head)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ u64 start, size;
+
+ block = xe_ttm_vram_mgr_first_block(head);
+@@ -32,12 +33,12 @@ static inline bool xe_is_vram_mgr_blocks
+ return false;
+
+ while (head != block->link.next) {
+- start = drm_buddy_block_offset(block);
+- size = drm_buddy_block_size(mm, block);
++ start = gpu_buddy_block_offset(block);
++ size = gpu_buddy_block_size(mm, block);
+
+- block = list_entry(block->link.next, struct drm_buddy_block,
++ block = list_entry(block->link.next, struct gpu_buddy_block,
+ link);
+- if (start + size != drm_buddy_block_offset(block))
++ if (start + size != gpu_buddy_block_offset(block))
+ return false;
+ }
+
+@@ -51,7 +52,7 @@ static int xe_ttm_vram_mgr_new(struct tt
+ {
+ struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+ struct xe_ttm_vram_mgr_resource *vres;
+- struct drm_buddy *mm = &mgr->mm;
++ struct gpu_buddy *mm = &mgr->mm;
+ u64 size, remaining_size, min_page_size;
+ unsigned long lpfn;
+ int err;
+@@ -78,10 +79,10 @@ static int xe_ttm_vram_mgr_new(struct tt
+ INIT_LIST_HEAD(&vres->blocks);
+
+ if (place->flags & TTM_PL_FLAG_TOPDOWN)
+- vres->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
++ vres->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
+
+ if (place->fpfn || lpfn != man->size >> PAGE_SHIFT)
+- vres->flags |= DRM_BUDDY_RANGE_ALLOCATION;
++ vres->flags |= GPU_BUDDY_RANGE_ALLOCATION;
+
+ if (WARN_ON(!vres->base.size)) {
+ err = -EINVAL;
+@@ -137,7 +138,7 @@ static int xe_ttm_vram_mgr_new(struct tt
+ */
+ u64 alloc_size = min_t(u64, remaining_size, SZ_2G);
+
+- err = drm_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
++ err = gpu_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
+ (u64)lpfn << PAGE_SHIFT,
+ alloc_size,
+ min_page_size,
+@@ -150,20 +151,20 @@ static int xe_ttm_vram_mgr_new(struct tt
+ } while (remaining_size);
+
+ if (place->flags & TTM_PL_FLAG_CONTIGUOUS) {
+- if (!drm_buddy_block_trim(mm, NULL, vres->base.size, &vres->blocks))
++ if (!gpu_buddy_block_trim(mm, NULL, vres->base.size, &vres->blocks))
+ size = vres->base.size;
+ }
+
+ if (lpfn <= mgr->visible_size >> PAGE_SHIFT) {
+ vres->used_visible_size = size;
+ } else {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+
+ list_for_each_entry(block, &vres->blocks, link) {
+- u64 start = drm_buddy_block_offset(block);
++ u64 start = gpu_buddy_block_offset(block);
+
+ if (start < mgr->visible_size) {
+- u64 end = start + drm_buddy_block_size(mm, block);
++ u64 end = start + gpu_buddy_block_size(mm, block);
+
+ vres->used_visible_size +=
+ min(end, mgr->visible_size) - start;
+@@ -183,11 +184,11 @@ static int xe_ttm_vram_mgr_new(struct tt
+ * the object.
+ */
+ if (vres->base.placement & TTM_PL_FLAG_CONTIGUOUS) {
+- struct drm_buddy_block *block = list_first_entry(&vres->blocks,
++ struct gpu_buddy_block *block = list_first_entry(&vres->blocks,
+ typeof(*block),
+ link);
+
+- vres->base.start = drm_buddy_block_offset(block) >> PAGE_SHIFT;
++ vres->base.start = gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+ } else {
+ vres->base.start = XE_BO_INVALID_OFFSET;
+ }
+@@ -196,7 +197,7 @@ static int xe_ttm_vram_mgr_new(struct tt
+ return 0;
+
+ error_free_blocks:
+- drm_buddy_free_list(mm, &vres->blocks, 0);
++ gpu_buddy_free_list(mm, &vres->blocks, 0);
+ mutex_unlock(&mgr->lock);
+ error_fini:
+ ttm_resource_fini(man, &vres->base);
+@@ -211,10 +212,10 @@ static void xe_ttm_vram_mgr_del(struct t
+ struct xe_ttm_vram_mgr_resource *vres =
+ to_xe_ttm_vram_mgr_resource(res);
+ struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+- struct drm_buddy *mm = &mgr->mm;
++ struct gpu_buddy *mm = &mgr->mm;
+
+ mutex_lock(&mgr->lock);
+- drm_buddy_free_list(mm, &vres->blocks, 0);
++ gpu_buddy_free_list(mm, &vres->blocks, 0);
+ mgr->visible_avail += vres->used_visible_size;
+ mutex_unlock(&mgr->lock);
+
+@@ -227,7 +228,7 @@ static void xe_ttm_vram_mgr_debug(struct
+ struct drm_printer *printer)
+ {
+ struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+- struct drm_buddy *mm = &mgr->mm;
++ struct gpu_buddy *mm = &mgr->mm;
+
+ mutex_lock(&mgr->lock);
+ drm_printf(printer, "default_page_size: %lluKiB\n",
+@@ -250,8 +251,8 @@ static bool xe_ttm_vram_mgr_intersects(s
+ struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+ struct xe_ttm_vram_mgr_resource *vres =
+ to_xe_ttm_vram_mgr_resource(res);
+- struct drm_buddy *mm = &mgr->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = &mgr->mm;
++ struct gpu_buddy_block *block;
+
+ if (!place->fpfn && !place->lpfn)
+ return true;
+@@ -261,9 +262,9 @@ static bool xe_ttm_vram_mgr_intersects(s
+
+ list_for_each_entry(block, &vres->blocks, link) {
+ unsigned long fpfn =
+- drm_buddy_block_offset(block) >> PAGE_SHIFT;
++ gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+ unsigned long lpfn = fpfn +
+- (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++ (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+
+ if (place->fpfn < lpfn && place->lpfn > fpfn)
+ return true;
+@@ -280,8 +281,8 @@ static bool xe_ttm_vram_mgr_compatible(s
+ struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+ struct xe_ttm_vram_mgr_resource *vres =
+ to_xe_ttm_vram_mgr_resource(res);
+- struct drm_buddy *mm = &mgr->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = &mgr->mm;
++ struct gpu_buddy_block *block;
+
+ if (!place->fpfn && !place->lpfn)
+ return true;
+@@ -291,9 +292,9 @@ static bool xe_ttm_vram_mgr_compatible(s
+
+ list_for_each_entry(block, &vres->blocks, link) {
+ unsigned long fpfn =
+- drm_buddy_block_offset(block) >> PAGE_SHIFT;
++ gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+ unsigned long lpfn = fpfn +
+- (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++ (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+
+ if (fpfn < place->fpfn || lpfn > place->lpfn)
+ return false;
+@@ -323,7 +324,7 @@ static void ttm_vram_mgr_fini(struct drm
+
+ WARN_ON_ONCE(mgr->visible_avail != mgr->visible_size);
+
+- drm_buddy_fini(&mgr->mm);
++ gpu_buddy_fini(&mgr->mm);
+
+ ttm_resource_manager_cleanup(&mgr->manager);
+
+@@ -347,7 +348,7 @@ int __xe_ttm_vram_mgr_init(struct xe_dev
+ mgr->visible_avail = io_size;
+
+ ttm_resource_manager_init(man, &xe->ttm, size);
+- err = drm_buddy_init(&mgr->mm, man->size, default_page_size);
++ err = gpu_buddy_init(&mgr->mm, man->size, default_page_size);
+ if (err)
+ return err;
+
+@@ -389,7 +390,7 @@ int xe_ttm_vram_mgr_alloc_sgt(struct xe_
+ if (!*sgt)
+ return -ENOMEM;
+
+- /* Determine the number of DRM_BUDDY blocks to export */
++ /* Determine the number of GPU_BUDDY blocks to export */
+ xe_res_first(res, offset, length, &cursor);
+ while (cursor.remaining) {
+ num_entries++;
+@@ -405,10 +406,10 @@ int xe_ttm_vram_mgr_alloc_sgt(struct xe_
+ sg->length = 0;
+
+ /*
+- * Walk down DRM_BUDDY blocks to populate scatterlist nodes
+- * @note: Use iterator api to get first the DRM_BUDDY block
++ * Walk down GPU_BUDDY blocks to populate scatterlist nodes
++ * @note: Use iterator api to get first the GPU_BUDDY block
+ * and the number of bytes from it. Access the following
+- * DRM_BUDDY block(s) if more buffer needs to exported
++ * GPU_BUDDY block(s) if more buffer needs to exported
+ */
+ xe_res_first(res, offset, length, &cursor);
+ for_each_sgtable_sg((*sgt), sg, i) {
+--- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h
++++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h
+@@ -6,7 +6,7 @@
+ #ifndef _XE_TTM_VRAM_MGR_TYPES_H_
+ #define _XE_TTM_VRAM_MGR_TYPES_H_
+
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+ #include <drm/ttm/ttm_device.h>
+
+ struct xe_mem_region;
+@@ -20,7 +20,7 @@ struct xe_ttm_vram_mgr {
+ /** @manager: Base TTM resource manager */
+ struct ttm_resource_manager manager;
+ /** @mm: DRM buddy allocator which manages the VRAM */
+- struct drm_buddy mm;
++ struct gpu_buddy mm;
+ /** @vram: ptr to details of associated VRAM region */
+ struct xe_mem_region *vram;
+ /** @visible_size: Proped size of the CPU visible portion */
+--- /dev/null
++++ b/drivers/gpu/tests/Makefile
+@@ -0,0 +1,4 @@
++# SPDX-License-Identifier: GPL-2.0
++
++gpu_buddy_tests-y = gpu_buddy_test.o gpu_random.o
++obj-$(CONFIG_GPU_BUDDY_KUNIT_TEST) += gpu_buddy_tests.o
+--- /dev/null
++++ b/drivers/gpu/tests/gpu_buddy_test.c
+@@ -0,0 +1,778 @@
++// SPDX-License-Identifier: MIT
++/*
++ * Copyright © 2019 Intel Corporation
++ * Copyright © 2022 MaÃra Canal <mairacanal@riseup.net>
++ */
++
++#include <kunit/test.h>
++
++#include <linux/prime_numbers.h>
++#include <linux/sched/signal.h>
++#include <linux/sizes.h>
++
++#include <linux/gpu_buddy.h>
++
++#include "gpu_random.h"
++
++static unsigned int random_seed;
++
++static inline u64 get_size(int order, u64 chunk_size)
++{
++ return (1 << order) * chunk_size;
++}
++
++static void drm_test_buddy_alloc_range_bias(struct kunit *test)
++{
++ u32 mm_size, size, ps, bias_size, bias_start, bias_end, bias_rem;
++ GPU_RND_STATE(prng, random_seed);
++ unsigned int i, count, *order;
++ struct gpu_buddy_block *block;
++ unsigned long flags;
++ struct gpu_buddy mm;
++ LIST_HEAD(allocated);
++
++ bias_size = SZ_1M;
++ ps = roundup_pow_of_two(prandom_u32_state(&prng) % bias_size);
++ ps = max(SZ_4K, ps);
++ mm_size = (SZ_8M-1) & ~(ps-1); /* Multiple roots */
++
++ kunit_info(test, "mm_size=%u, ps=%u\n", mm_size, ps);
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps),
++ "buddy_init failed\n");
++
++ count = mm_size / bias_size;
++ order = gpu_random_order(count, &prng);
++ KUNIT_EXPECT_TRUE(test, order);
++
++ /*
++ * Idea is to split the address space into uniform bias ranges, and then
++ * in some random order allocate within each bias, using various
++ * patterns within. This should detect if allocations leak out from a
++ * given bias, for example.
++ */
++
++ for (i = 0; i < count; i++) {
++ LIST_HEAD(tmp);
++ u32 size;
++
++ bias_start = order[i] * bias_size;
++ bias_end = bias_start + bias_size;
++ bias_rem = bias_size;
++
++ /* internal round_up too big */
++ KUNIT_ASSERT_TRUE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, bias_size + ps, bias_size,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, bias_size, bias_size);
++
++ /* size too big */
++ KUNIT_ASSERT_TRUE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, bias_size + ps, ps,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, bias_size + ps, ps);
++
++ /* bias range too small for size */
++ KUNIT_ASSERT_TRUE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start + ps,
++ bias_end, bias_size, ps,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start + ps, bias_end, bias_size, ps);
++
++ /* bias misaligned */
++ KUNIT_ASSERT_TRUE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start + ps,
++ bias_end - ps,
++ bias_size >> 1, bias_size >> 1,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc h didn't fail with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start + ps, bias_end - ps, bias_size >> 1, bias_size >> 1);
++
++ /* single big page */
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, bias_size, bias_size,
++ &tmp,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc i failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, bias_size, bias_size);
++ gpu_buddy_free_list(&mm, &tmp, 0);
++
++ /* single page with internal round_up */
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, ps, bias_size,
++ &tmp,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, ps, bias_size);
++ gpu_buddy_free_list(&mm, &tmp, 0);
++
++ /* random size within */
++ size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
++ if (size)
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, size, ps,
++ &tmp,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, size, ps);
++
++ bias_rem -= size;
++ /* too big for current avail */
++ KUNIT_ASSERT_TRUE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, bias_rem + ps, ps,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, bias_rem + ps, ps);
++
++ if (bias_rem) {
++ /* random fill of the remainder */
++ size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
++ size = max(size, ps);
++
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, size, ps,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, size, ps);
++ /*
++ * Intentionally allow some space to be left
++ * unallocated, and ideally not always on the bias
++ * boundaries.
++ */
++ gpu_buddy_free_list(&mm, &tmp, 0);
++ } else {
++ list_splice_tail(&tmp, &allocated);
++ }
++ }
++
++ kfree(order);
++ gpu_buddy_free_list(&mm, &allocated, 0);
++ gpu_buddy_fini(&mm);
++
++ /*
++ * Something more free-form. Idea is to pick a random starting bias
++ * range within the address space and then start filling it up. Also
++ * randomly grow the bias range in both directions as we go along. This
++ * should give us bias start/end which is not always uniform like above,
++ * and in some cases will require the allocator to jump over already
++ * allocated nodes in the middle of the address space.
++ */
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps),
++ "buddy_init failed\n");
++
++ bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
++ bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
++ bias_end = max(bias_end, bias_start + ps);
++ bias_rem = bias_end - bias_start;
++
++ do {
++ u32 size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
++
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, size, ps,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, size, ps);
++ bias_rem -= size;
++
++ /*
++ * Try to randomly grow the bias range in both directions, or
++ * only one, or perhaps don't grow at all.
++ */
++ do {
++ u32 old_bias_start = bias_start;
++ u32 old_bias_end = bias_end;
++
++ if (bias_start)
++ bias_start -= round_up(prandom_u32_state(&prng) % bias_start, ps);
++ if (bias_end != mm_size)
++ bias_end += round_up(prandom_u32_state(&prng) % (mm_size - bias_end), ps);
++
++ bias_rem += old_bias_start - bias_start;
++ bias_rem += bias_end - old_bias_end;
++ } while (!bias_rem && (bias_start || bias_end != mm_size));
++ } while (bias_rem);
++
++ KUNIT_ASSERT_EQ(test, bias_start, 0);
++ KUNIT_ASSERT_EQ(test, bias_end, mm_size);
++ KUNIT_ASSERT_TRUE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start, bias_end,
++ ps, ps,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc passed with bias(%x-%x), size=%u\n",
++ bias_start, bias_end, ps);
++
++ gpu_buddy_free_list(&mm, &allocated, 0);
++ gpu_buddy_fini(&mm);
++
++ /*
++ * Allocate cleared blocks in the bias range when the DRM buddy's clear avail is
++ * zero. This will validate the bias range allocation in scenarios like system boot
++ * when no cleared blocks are available and exercise the fallback path too. The resulting
++ * blocks should always be dirty.
++ */
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps),
++ "buddy_init failed\n");
++
++ bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
++ bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
++ bias_end = max(bias_end, bias_start + ps);
++ bias_rem = bias_end - bias_start;
++
++ flags = GPU_BUDDY_CLEAR_ALLOCATION | GPU_BUDDY_RANGE_ALLOCATION;
++ size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
++
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, size, ps,
++ &allocated,
++ flags),
++ "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, size, ps);
++
++ list_for_each_entry(block, &allocated, link)
++ KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), false);
++
++ gpu_buddy_free_list(&mm, &allocated, 0);
++ gpu_buddy_fini(&mm);
++}
++
++static void drm_test_buddy_alloc_clear(struct kunit *test)
++{
++ unsigned long n_pages, total, i = 0;
++ GPU_RND_STATE(prng, random_seed);
++ const unsigned long ps = SZ_4K;
++ struct gpu_buddy_block *block;
++ const int max_order = 12;
++ LIST_HEAD(allocated);
++ struct gpu_buddy mm;
++ unsigned int order;
++ u32 mm_size, size;
++ LIST_HEAD(dirty);
++ LIST_HEAD(clean);
++
++ mm_size = SZ_4K << max_order;
++ KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++
++ KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++
++ /*
++ * Idea is to allocate and free some random portion of the address space,
++ * returning those pages as non-dirty and randomly alternate between
++ * requesting dirty and non-dirty pages (not going over the limit
++ * we freed as non-dirty), putting that into two separate lists.
++ * Loop over both lists at the end checking that the dirty list
++ * is indeed all dirty pages and vice versa. Free it all again,
++ * keeping the dirty/clear status.
++ */
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 5 * ps, ps, &allocated,
++ GPU_BUDDY_TOPDOWN_ALLOCATION),
++ "buddy_alloc hit an error size=%lu\n", 5 * ps);
++ gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED);
++
++ n_pages = 10;
++ do {
++ unsigned long flags;
++ struct list_head *list;
++ int slot = i % 2;
++
++ if (slot == 0) {
++ list = &dirty;
++ flags = 0;
++ } else {
++ list = &clean;
++ flags = GPU_BUDDY_CLEAR_ALLOCATION;
++ }
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ ps, ps, list,
++ flags),
++ "buddy_alloc hit an error size=%lu\n", ps);
++ } while (++i < n_pages);
++
++ list_for_each_entry(block, &clean, link)
++ KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), true);
++
++ list_for_each_entry(block, &dirty, link)
++ KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), false);
++
++ gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED);
++
++ /*
++ * Trying to go over the clear limit for some allocation.
++ * The allocation should never fail with reasonable page-size.
++ */
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 10 * ps, ps, &clean,
++ GPU_BUDDY_CLEAR_ALLOCATION),
++ "buddy_alloc hit an error size=%lu\n", 10 * ps);
++
++ gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED);
++ gpu_buddy_free_list(&mm, &dirty, 0);
++ gpu_buddy_fini(&mm);
++
++ KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++
++ /*
++ * Create a new mm. Intentionally fragment the address space by creating
++ * two alternating lists. Free both lists, one as dirty the other as clean.
++ * Try to allocate double the previous size with matching min_page_size. The
++ * allocation should never fail as it calls the force_merge. Also check that
++ * the page is always dirty after force_merge. Free the page as dirty, then
++ * repeat the whole thing, increment the order until we hit the max_order.
++ */
++
++ i = 0;
++ n_pages = mm_size / ps;
++ do {
++ struct list_head *list;
++ int slot = i % 2;
++
++ if (slot == 0)
++ list = &dirty;
++ else
++ list = &clean;
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ ps, ps, list, 0),
++ "buddy_alloc hit an error size=%lu\n", ps);
++ } while (++i < n_pages);
++
++ gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED);
++ gpu_buddy_free_list(&mm, &dirty, 0);
++
++ order = 1;
++ do {
++ size = SZ_4K << order;
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ size, size, &allocated,
++ GPU_BUDDY_CLEAR_ALLOCATION),
++ "buddy_alloc hit an error size=%u\n", size);
++ total = 0;
++ list_for_each_entry(block, &allocated, link) {
++ if (size != mm_size)
++ KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), false);
++ total += gpu_buddy_block_size(&mm, block);
++ }
++ KUNIT_EXPECT_EQ(test, total, size);
++
++ gpu_buddy_free_list(&mm, &allocated, 0);
++ } while (++order <= max_order);
++
++ gpu_buddy_fini(&mm);
++
++ /*
++ * Create a new mm with a non power-of-two size. Allocate a random size, free as
++ * cleared and then call fini. This will ensure the multi-root force merge during
++ * fini.
++ */
++ mm_size = 12 * SZ_4K;
++ size = max(round_up(prandom_u32_state(&prng) % mm_size, ps), ps);
++ KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ size, ps, &allocated,
++ GPU_BUDDY_TOPDOWN_ALLOCATION),
++ "buddy_alloc hit an error size=%u\n", size);
++ gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED);
++ gpu_buddy_fini(&mm);
++}
++
++static void drm_test_buddy_alloc_contiguous(struct kunit *test)
++{
++ const unsigned long ps = SZ_4K, mm_size = 16 * 3 * SZ_4K;
++ unsigned long i, n_pages, total;
++ struct gpu_buddy_block *block;
++ struct gpu_buddy mm;
++ LIST_HEAD(left);
++ LIST_HEAD(middle);
++ LIST_HEAD(right);
++ LIST_HEAD(allocated);
++
++ KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++
++ /*
++ * Idea is to fragment the address space by alternating block
++ * allocations between three different lists; one for left, middle and
++ * right. We can then free a list to simulate fragmentation. In
++ * particular we want to exercise the GPU_BUDDY_CONTIGUOUS_ALLOCATION,
++ * including the try_harder path.
++ */
++
++ i = 0;
++ n_pages = mm_size / ps;
++ do {
++ struct list_head *list;
++ int slot = i % 3;
++
++ if (slot == 0)
++ list = &left;
++ else if (slot == 1)
++ list = &middle;
++ else
++ list = &right;
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ ps, ps, list, 0),
++ "buddy_alloc hit an error size=%lu\n",
++ ps);
++ } while (++i < n_pages);
++
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 3 * ps, ps, &allocated,
++ GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++ "buddy_alloc didn't error size=%lu\n", 3 * ps);
++
++ gpu_buddy_free_list(&mm, &middle, 0);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 3 * ps, ps, &allocated,
++ GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++ "buddy_alloc didn't error size=%lu\n", 3 * ps);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 2 * ps, ps, &allocated,
++ GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++ "buddy_alloc didn't error size=%lu\n", 2 * ps);
++
++ gpu_buddy_free_list(&mm, &right, 0);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 3 * ps, ps, &allocated,
++ GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++ "buddy_alloc didn't error size=%lu\n", 3 * ps);
++ /*
++ * At this point we should have enough contiguous space for 2 blocks,
++ * however they are never buddies (since we freed middle and right) so
++ * will require the try_harder logic to find them.
++ */
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 2 * ps, ps, &allocated,
++ GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++ "buddy_alloc hit an error size=%lu\n", 2 * ps);
++
++ gpu_buddy_free_list(&mm, &left, 0);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 3 * ps, ps, &allocated,
++ GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++ "buddy_alloc hit an error size=%lu\n", 3 * ps);
++
++ total = 0;
++ list_for_each_entry(block, &allocated, link)
++ total += gpu_buddy_block_size(&mm, block);
++
++ KUNIT_ASSERT_EQ(test, total, ps * 2 + ps * 3);
++
++ gpu_buddy_free_list(&mm, &allocated, 0);
++ gpu_buddy_fini(&mm);
++}
++
++static void drm_test_buddy_alloc_pathological(struct kunit *test)
++{
++ u64 mm_size, size, start = 0;
++ struct gpu_buddy_block *block;
++ const int max_order = 3;
++ unsigned long flags = 0;
++ int order, top;
++ struct gpu_buddy mm;
++ LIST_HEAD(blocks);
++ LIST_HEAD(holes);
++ LIST_HEAD(tmp);
++
++ /*
++ * Create a pot-sized mm, then allocate one of each possible
++ * order within. This should leave the mm with exactly one
++ * page left. Free the largest block, then whittle down again.
++ * Eventually we will have a fully 50% fragmented mm.
++ */
++
++ mm_size = SZ_4K << max_order;
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K),
++ "buddy_init failed\n");
++
++ KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++
++ for (top = max_order; top; top--) {
++ /* Make room by freeing the largest allocated block */
++ block = list_first_entry_or_null(&blocks, typeof(*block), link);
++ if (block) {
++ list_del(&block->link);
++ gpu_buddy_free_block(&mm, block);
++ }
++
++ for (order = top; order--;) {
++ size = get_size(order, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start,
++ mm_size, size, size,
++ &tmp, flags),
++ "buddy_alloc hit -ENOMEM with order=%d, top=%d\n",
++ order, top);
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_move_tail(&block->link, &blocks);
++ }
++
++ /* There should be one final page for this sub-allocation */
++ size = get_size(0, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc hit -ENOMEM for hole\n");
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_move_tail(&block->link, &holes);
++
++ size = get_size(top, mm.chunk_size);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc unexpectedly succeeded at top-order %d/%d, it should be full!",
++ top, max_order);
++ }
++
++ gpu_buddy_free_list(&mm, &holes, 0);
++
++ /* Nothing larger than blocks of chunk_size now available */
++ for (order = 1; order <= max_order; order++) {
++ size = get_size(order, mm.chunk_size);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc unexpectedly succeeded at order %d, it should be full!",
++ order);
++ }
++
++ list_splice_tail(&holes, &blocks);
++ gpu_buddy_free_list(&mm, &blocks, 0);
++ gpu_buddy_fini(&mm);
++}
++
++static void drm_test_buddy_alloc_pessimistic(struct kunit *test)
++{
++ u64 mm_size, size, start = 0;
++ struct gpu_buddy_block *block, *bn;
++ const unsigned int max_order = 16;
++ unsigned long flags = 0;
++ struct gpu_buddy mm;
++ unsigned int order;
++ LIST_HEAD(blocks);
++ LIST_HEAD(tmp);
++
++ /*
++ * Create a pot-sized mm, then allocate one of each possible
++ * order within. This should leave the mm with exactly one
++ * page left.
++ */
++
++ mm_size = SZ_4K << max_order;
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K),
++ "buddy_init failed\n");
++
++ KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++
++ for (order = 0; order < max_order; order++) {
++ size = get_size(order, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc hit -ENOMEM with order=%d\n",
++ order);
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_move_tail(&block->link, &blocks);
++ }
++
++ /* And now the last remaining block available */
++ size = get_size(0, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc hit -ENOMEM on final alloc\n");
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_move_tail(&block->link, &blocks);
++
++ /* Should be completely full! */
++ for (order = max_order; order--;) {
++ size = get_size(order, mm.chunk_size);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc unexpectedly succeeded, it should be full!");
++ }
++
++ block = list_last_entry(&blocks, typeof(*block), link);
++ list_del(&block->link);
++ gpu_buddy_free_block(&mm, block);
++
++ /* As we free in increasing size, we make available larger blocks */
++ order = 1;
++ list_for_each_entry_safe(block, bn, &blocks, link) {
++ list_del(&block->link);
++ gpu_buddy_free_block(&mm, block);
++
++ size = get_size(order, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc hit -ENOMEM with order=%d\n",
++ order);
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_del(&block->link);
++ gpu_buddy_free_block(&mm, block);
++ order++;
++ }
++
++ /* To confirm, now the whole mm should be available */
++ size = get_size(max_order, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc (realloc) hit -ENOMEM with order=%d\n",
++ max_order);
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_del(&block->link);
++ gpu_buddy_free_block(&mm, block);
++ gpu_buddy_free_list(&mm, &blocks, 0);
++ gpu_buddy_fini(&mm);
++}
++
++static void drm_test_buddy_alloc_optimistic(struct kunit *test)
++{
++ u64 mm_size, size, start = 0;
++ struct gpu_buddy_block *block;
++ unsigned long flags = 0;
++ const int max_order = 16;
++ struct gpu_buddy mm;
++ LIST_HEAD(blocks);
++ LIST_HEAD(tmp);
++ int order;
++
++ /*
++ * Create a mm with one block of each order available, and
++ * try to allocate them all.
++ */
++
++ mm_size = SZ_4K * ((1 << (max_order + 1)) - 1);
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K),
++ "buddy_init failed\n");
++
++ KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++
++ for (order = 0; order <= max_order; order++) {
++ size = get_size(order, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc hit -ENOMEM with order=%d\n",
++ order);
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_move_tail(&block->link, &blocks);
++ }
++
++ /* Should be completely full! */
++ size = get_size(0, mm.chunk_size);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc unexpectedly succeeded, it should be full!");
++
++ gpu_buddy_free_list(&mm, &blocks, 0);
++ gpu_buddy_fini(&mm);
++}
++
++static void drm_test_buddy_alloc_limit(struct kunit *test)
++{
++ u64 size = U64_MAX, start = 0;
++ struct gpu_buddy_block *block;
++ unsigned long flags = 0;
++ LIST_HEAD(allocated);
++ struct gpu_buddy mm;
++
++ KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, size, SZ_4K));
++
++ KUNIT_EXPECT_EQ_MSG(test, mm.max_order, GPU_BUDDY_MAX_ORDER,
++ "mm.max_order(%d) != %d\n", mm.max_order,
++ GPU_BUDDY_MAX_ORDER);
++
++ size = mm.chunk_size << mm.max_order;
++ KUNIT_EXPECT_FALSE(test, gpu_buddy_alloc_blocks(&mm, start, size, size,
++ mm.chunk_size, &allocated, flags));
++
++ block = list_first_entry_or_null(&allocated, struct gpu_buddy_block, link);
++ KUNIT_EXPECT_TRUE(test, block);
++
++ KUNIT_EXPECT_EQ_MSG(test, gpu_buddy_block_order(block), mm.max_order,
++ "block order(%d) != %d\n",
++ gpu_buddy_block_order(block), mm.max_order);
++
++ KUNIT_EXPECT_EQ_MSG(test, gpu_buddy_block_size(&mm, block),
++ BIT_ULL(mm.max_order) * mm.chunk_size,
++ "block size(%llu) != %llu\n",
++ gpu_buddy_block_size(&mm, block),
++ BIT_ULL(mm.max_order) * mm.chunk_size);
++
++ gpu_buddy_free_list(&mm, &allocated, 0);
++ gpu_buddy_fini(&mm);
++}
++
++static int gpu_buddy_suite_init(struct kunit_suite *suite)
++{
++ while (!random_seed)
++ random_seed = get_random_u32();
++
++ kunit_info(suite, "Testing DRM buddy manager, with random_seed=0x%x\n",
++ random_seed);
++
++ return 0;
++}
++
++static struct kunit_case gpu_buddy_tests[] = {
++ KUNIT_CASE(drm_test_buddy_alloc_limit),
++ KUNIT_CASE(drm_test_buddy_alloc_optimistic),
++ KUNIT_CASE(drm_test_buddy_alloc_pessimistic),
++ KUNIT_CASE(drm_test_buddy_alloc_pathological),
++ KUNIT_CASE(drm_test_buddy_alloc_contiguous),
++ KUNIT_CASE(drm_test_buddy_alloc_clear),
++ KUNIT_CASE(drm_test_buddy_alloc_range_bias),
++ {}
++};
++
++static struct kunit_suite gpu_buddy_test_suite = {
++ .name = "gpu_buddy",
++ .suite_init = gpu_buddy_suite_init,
++ .test_cases = gpu_buddy_tests,
++};
++
++kunit_test_suite(gpu_buddy_test_suite);
++
++MODULE_AUTHOR("Intel Corporation");
++MODULE_DESCRIPTION("Kunit test for gpu_buddy functions");
++MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/drivers/gpu/tests/gpu_random.c
+@@ -0,0 +1,43 @@
++// SPDX-License-Identifier: GPL-2.0
++#include <linux/bitops.h>
++#include <linux/kernel.h>
++#include <linux/random.h>
++#include <linux/slab.h>
++#include <linux/types.h>
++
++#include "gpu_random.h"
++
++u32 gpu_prandom_u32_max_state(u32 ep_ro, struct rnd_state *state)
++{
++ return upper_32_bits((u64)prandom_u32_state(state) * ep_ro);
++}
++EXPORT_SYMBOL(gpu_prandom_u32_max_state);
++
++void gpu_random_reorder(unsigned int *order, unsigned int count,
++ struct rnd_state *state)
++{
++ unsigned int i, j;
++
++ for (i = 0; i < count; ++i) {
++ BUILD_BUG_ON(sizeof(unsigned int) > sizeof(u32));
++ j = gpu_prandom_u32_max_state(count, state);
++ swap(order[i], order[j]);
++ }
++}
++EXPORT_SYMBOL(gpu_random_reorder);
++
++unsigned int *gpu_random_order(unsigned int count, struct rnd_state *state)
++{
++ unsigned int *order, i;
++
++ order = kmalloc_array(count, sizeof(*order), GFP_KERNEL);
++ if (!order)
++ return order;
++
++ for (i = 0; i < count; i++)
++ order[i] = i;
++
++ gpu_random_reorder(order, count, state);
++ return order;
++}
++EXPORT_SYMBOL(gpu_random_order);
+--- /dev/null
++++ b/drivers/gpu/tests/gpu_random.h
+@@ -0,0 +1,28 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++#ifndef __GPU_RANDOM_H__
++#define __GPU_RANDOM_H__
++
++/* This is a temporary home for a couple of utility functions that should
++ * be transposed to lib/ at the earliest convenience.
++ */
++
++#include <linux/random.h>
++
++#define GPU_RND_STATE_INITIALIZER(seed__) ({ \
++ struct rnd_state state__; \
++ prandom_seed_state(&state__, (seed__)); \
++ state__; \
++})
++
++#define GPU_RND_STATE(name__, seed__) \
++ struct rnd_state name__ = GPU_RND_STATE_INITIALIZER(seed__)
++
++unsigned int *gpu_random_order(unsigned int count,
++ struct rnd_state *state);
++void gpu_random_reorder(unsigned int *order,
++ unsigned int count,
++ struct rnd_state *state);
++u32 gpu_prandom_u32_max_state(u32 ep_ro,
++ struct rnd_state *state);
++
++#endif /* !__GPU_RANDOM_H__ */
+--- a/drivers/video/Kconfig
++++ b/drivers/video/Kconfig
+@@ -37,6 +37,7 @@ source "drivers/char/agp/Kconfig"
+
+ source "drivers/gpu/vga/Kconfig"
+
++source "drivers/gpu/Kconfig"
+ source "drivers/gpu/host1x/Kconfig"
+ source "drivers/gpu/ipu-v3/Kconfig"
+
+--- a/include/drm/drm_buddy.h
++++ b/include/drm/drm_buddy.h
+@@ -6,175 +6,13 @@
+ #ifndef __DRM_BUDDY_H__
+ #define __DRM_BUDDY_H__
+
+-#include <linux/bitops.h>
+-#include <linux/list.h>
+-#include <linux/slab.h>
+-#include <linux/sched.h>
+-#include <linux/rbtree.h>
++#include <linux/gpu_buddy.h>
+
+-#include <drm/drm_print.h>
++struct drm_printer;
+
+-#define range_overflows(start, size, max) ({ \
+- typeof(start) start__ = (start); \
+- typeof(size) size__ = (size); \
+- typeof(max) max__ = (max); \
+- (void)(&start__ == &size__); \
+- (void)(&start__ == &max__); \
+- start__ >= max__ || size__ > max__ - start__; \
+-})
+-
+-#define DRM_BUDDY_RANGE_ALLOCATION BIT(0)
+-#define DRM_BUDDY_TOPDOWN_ALLOCATION BIT(1)
+-#define DRM_BUDDY_CONTIGUOUS_ALLOCATION BIT(2)
+-#define DRM_BUDDY_CLEAR_ALLOCATION BIT(3)
+-#define DRM_BUDDY_CLEARED BIT(4)
+-#define DRM_BUDDY_TRIM_DISABLE BIT(5)
+-
+-struct drm_buddy_block {
+-#define DRM_BUDDY_HEADER_OFFSET GENMASK_ULL(63, 12)
+-#define DRM_BUDDY_HEADER_STATE GENMASK_ULL(11, 10)
+-#define DRM_BUDDY_ALLOCATED (1 << 10)
+-#define DRM_BUDDY_FREE (2 << 10)
+-#define DRM_BUDDY_SPLIT (3 << 10)
+-#define DRM_BUDDY_HEADER_CLEAR GENMASK_ULL(9, 9)
+-/* Free to be used, if needed in the future */
+-#define DRM_BUDDY_HEADER_UNUSED GENMASK_ULL(8, 6)
+-#define DRM_BUDDY_HEADER_ORDER GENMASK_ULL(5, 0)
+- u64 header;
+-
+- struct drm_buddy_block *left;
+- struct drm_buddy_block *right;
+- struct drm_buddy_block *parent;
+-
+- void *private; /* owned by creator */
+-
+- /*
+- * While the block is allocated by the user through drm_buddy_alloc*,
+- * the user has ownership of the link, for example to maintain within
+- * a list, if so desired. As soon as the block is freed with
+- * drm_buddy_free* ownership is given back to the mm.
+- */
+- union {
+- struct rb_node rb;
+- struct list_head link;
+- };
+-
+- struct list_head tmp_link;
+-};
+-
+-/* Order-zero must be at least SZ_4K */
+-#define DRM_BUDDY_MAX_ORDER (63 - 12)
+-
+-/*
+- * Binary Buddy System.
+- *
+- * Locking should be handled by the user, a simple mutex around
+- * drm_buddy_alloc* and drm_buddy_free* should suffice.
+- */
+-struct drm_buddy {
+- /* Maintain a free list for each order. */
+- struct rb_root **free_trees;
+-
+- /*
+- * Maintain explicit binary tree(s) to track the allocation of the
+- * address space. This gives us a simple way of finding a buddy block
+- * and performing the potentially recursive merge step when freeing a
+- * block. Nodes are either allocated or free, in which case they will
+- * also exist on the respective free list.
+- */
+- struct drm_buddy_block **roots;
+-
+- /*
+- * Anything from here is public, and remains static for the lifetime of
+- * the mm. Everything above is considered do-not-touch.
+- */
+- unsigned int n_roots;
+- unsigned int max_order;
+-
+- /* Must be at least SZ_4K */
+- u64 chunk_size;
+- u64 size;
+- u64 avail;
+- u64 clear_avail;
+-};
+-
+-static inline u64
+-drm_buddy_block_offset(const struct drm_buddy_block *block)
+-{
+- return block->header & DRM_BUDDY_HEADER_OFFSET;
+-}
+-
+-static inline unsigned int
+-drm_buddy_block_order(struct drm_buddy_block *block)
+-{
+- return block->header & DRM_BUDDY_HEADER_ORDER;
+-}
+-
+-static inline unsigned int
+-drm_buddy_block_state(struct drm_buddy_block *block)
+-{
+- return block->header & DRM_BUDDY_HEADER_STATE;
+-}
+-
+-static inline bool
+-drm_buddy_block_is_allocated(struct drm_buddy_block *block)
+-{
+- return drm_buddy_block_state(block) == DRM_BUDDY_ALLOCATED;
+-}
+-
+-static inline bool
+-drm_buddy_block_is_clear(struct drm_buddy_block *block)
+-{
+- return block->header & DRM_BUDDY_HEADER_CLEAR;
+-}
+-
+-static inline bool
+-drm_buddy_block_is_free(struct drm_buddy_block *block)
+-{
+- return drm_buddy_block_state(block) == DRM_BUDDY_FREE;
+-}
+-
+-static inline bool
+-drm_buddy_block_is_split(struct drm_buddy_block *block)
+-{
+- return drm_buddy_block_state(block) == DRM_BUDDY_SPLIT;
+-}
+-
+-static inline u64
+-drm_buddy_block_size(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- return mm->chunk_size << drm_buddy_block_order(block);
+-}
+-
+-int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size);
+-
+-void drm_buddy_fini(struct drm_buddy *mm);
+-
+-struct drm_buddy_block *
+-drm_get_buddy(struct drm_buddy_block *block);
+-
+-int drm_buddy_alloc_blocks(struct drm_buddy *mm,
+- u64 start, u64 end, u64 size,
+- u64 min_page_size,
+- struct list_head *blocks,
+- unsigned long flags);
+-
+-int drm_buddy_block_trim(struct drm_buddy *mm,
+- u64 *start,
+- u64 new_size,
+- struct list_head *blocks);
+-
+-void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear);
+-
+-void drm_buddy_free_block(struct drm_buddy *mm, struct drm_buddy_block *block);
+-
+-void drm_buddy_free_list(struct drm_buddy *mm,
+- struct list_head *objects,
+- unsigned int flags);
+-
+-void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p);
+-void drm_buddy_block_print(struct drm_buddy *mm,
+- struct drm_buddy_block *block,
++/* DRM-specific GPU Buddy Allocator print helpers */
++void drm_buddy_print(struct gpu_buddy *mm, struct drm_printer *p);
++void drm_buddy_block_print(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block,
+ struct drm_printer *p);
+ #endif
+--- /dev/null
++++ b/include/linux/gpu_buddy.h
+@@ -0,0 +1,188 @@
++/* SPDX-License-Identifier: MIT */
++/*
++ * Copyright © 2021 Intel Corporation
++ */
++
++#ifndef __GPU_BUDDY_H__
++#define __GPU_BUDDY_H__
++
++#include <linux/bitops.h>
++#include <linux/list.h>
++#include <linux/slab.h>
++#include <linux/sched.h>
++#include <linux/rbtree.h>
++
++#include <drm/drm_print.h>
++
++#define range_overflows(start, size, max) ({ \
++ typeof(start) start__ = (start); \
++ typeof(size) size__ = (size); \
++ typeof(max) max__ = (max); \
++ (void)(&start__ == &size__); \
++ (void)(&start__ == &max__); \
++ start__ >= max__ || size__ > max__ - start__; \
++})
++
++#define GPU_BUDDY_RANGE_ALLOCATION BIT(0)
++#define GPU_BUDDY_TOPDOWN_ALLOCATION BIT(1)
++#define GPU_BUDDY_CONTIGUOUS_ALLOCATION BIT(2)
++#define GPU_BUDDY_CLEAR_ALLOCATION BIT(3)
++#define GPU_BUDDY_CLEARED BIT(4)
++#define GPU_BUDDY_TRIM_DISABLE BIT(5)
++
++enum gpu_buddy_free_tree {
++ GPU_BUDDY_CLEAR_TREE = 0,
++ GPU_BUDDY_DIRTY_TREE,
++ GPU_BUDDY_MAX_FREE_TREES,
++};
++
++#define for_each_free_tree(tree) \
++ for ((tree) = 0; (tree) < GPU_BUDDY_MAX_FREE_TREES; (tree)++)
++
++struct gpu_buddy_block {
++#define GPU_BUDDY_HEADER_OFFSET GENMASK_ULL(63, 12)
++#define GPU_BUDDY_HEADER_STATE GENMASK_ULL(11, 10)
++#define GPU_BUDDY_ALLOCATED (1 << 10)
++#define GPU_BUDDY_FREE (2 << 10)
++#define GPU_BUDDY_SPLIT (3 << 10)
++#define GPU_BUDDY_HEADER_CLEAR GENMASK_ULL(9, 9)
++/* Free to be used, if needed in the future */
++#define GPU_BUDDY_HEADER_UNUSED GENMASK_ULL(8, 6)
++#define GPU_BUDDY_HEADER_ORDER GENMASK_ULL(5, 0)
++ u64 header;
++
++ struct gpu_buddy_block *left;
++ struct gpu_buddy_block *right;
++ struct gpu_buddy_block *parent;
++
++ void *private; /* owned by creator */
++
++ /*
++ * While the block is allocated by the user through gpu_buddy_alloc*,
++ * the user has ownership of the link, for example to maintain within
++ * a list, if so desired. As soon as the block is freed with
++ * gpu_buddy_free* ownership is given back to the mm.
++ */
++ union {
++ struct rb_node rb;
++ struct list_head link;
++ };
++
++ struct list_head tmp_link;
++};
++
++/* Order-zero must be at least SZ_4K */
++#define GPU_BUDDY_MAX_ORDER (63 - 12)
++
++/*
++ * Binary Buddy System.
++ *
++ * Locking should be handled by the user, a simple mutex around
++ * gpu_buddy_alloc* and gpu_buddy_free* should suffice.
++ */
++struct gpu_buddy {
++ /* Maintain a free list for each order. */
++ struct rb_root **free_trees;
++
++ /*
++ * Maintain explicit binary tree(s) to track the allocation of the
++ * address space. This gives us a simple way of finding a buddy block
++ * and performing the potentially recursive merge step when freeing a
++ * block. Nodes are either allocated or free, in which case they will
++ * also exist on the respective free list.
++ */
++ struct gpu_buddy_block **roots;
++
++ /*
++ * Anything from here is public, and remains static for the lifetime of
++ * the mm. Everything above is considered do-not-touch.
++ */
++ unsigned int n_roots;
++ unsigned int max_order;
++
++ /* Must be at least SZ_4K */
++ u64 chunk_size;
++ u64 size;
++ u64 avail;
++ u64 clear_avail;
++};
++
++static inline u64
++gpu_buddy_block_offset(const struct gpu_buddy_block *block)
++{
++ return block->header & GPU_BUDDY_HEADER_OFFSET;
++}
++
++static inline unsigned int
++gpu_buddy_block_order(struct gpu_buddy_block *block)
++{
++ return block->header & GPU_BUDDY_HEADER_ORDER;
++}
++
++static inline unsigned int
++gpu_buddy_block_state(struct gpu_buddy_block *block)
++{
++ return block->header & GPU_BUDDY_HEADER_STATE;
++}
++
++static inline bool
++gpu_buddy_block_is_allocated(struct gpu_buddy_block *block)
++{
++ return gpu_buddy_block_state(block) == GPU_BUDDY_ALLOCATED;
++}
++
++static inline bool
++gpu_buddy_block_is_clear(struct gpu_buddy_block *block)
++{
++ return block->header & GPU_BUDDY_HEADER_CLEAR;
++}
++
++static inline bool
++gpu_buddy_block_is_free(struct gpu_buddy_block *block)
++{
++ return gpu_buddy_block_state(block) == GPU_BUDDY_FREE;
++}
++
++static inline bool
++gpu_buddy_block_is_split(struct gpu_buddy_block *block)
++{
++ return gpu_buddy_block_state(block) == GPU_BUDDY_SPLIT;
++}
++
++static inline u64
++gpu_buddy_block_size(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ return mm->chunk_size << gpu_buddy_block_order(block);
++}
++
++int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size);
++
++void gpu_buddy_fini(struct gpu_buddy *mm);
++
++struct gpu_buddy_block *
++gpu_get_buddy(struct gpu_buddy_block *block);
++
++int gpu_buddy_alloc_blocks(struct gpu_buddy *mm,
++ u64 start, u64 end, u64 size,
++ u64 min_page_size,
++ struct list_head *blocks,
++ unsigned long flags);
++
++int gpu_buddy_block_trim(struct gpu_buddy *mm,
++ u64 *start,
++ u64 new_size,
++ struct list_head *blocks);
++
++void gpu_buddy_reset_clear(struct gpu_buddy *mm, bool is_clear);
++
++void gpu_buddy_free_block(struct gpu_buddy *mm, struct gpu_buddy_block *block);
++
++void gpu_buddy_free_list(struct gpu_buddy *mm,
++ struct list_head *objects,
++ unsigned int flags);
++
++void gpu_buddy_print(struct gpu_buddy *mm);
++void gpu_buddy_block_print(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block);
++#endif
--- /dev/null
+From stable+bounces-289989-greg=kroah.com@vger.kernel.org Tue Jul 28 15:21:31 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 09:20:04 -0400
+Subject: gve: fix header buffer corruption with header-split and HW-GRO
+To: stable@vger.kernel.org
+Cc: Ankit Garg <nktgrg@google.com>, Praveen Kaligineedi <pkaligineedi@google.com>, Jordan Rhee <jordanrhee@google.com>, Harshitha Ramamurthy <hramamurthy@google.com>, Joshua Washington <joshwash@google.com>, Eric Dumazet <edumazet@google.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728132004.3954154-2-sashal@kernel.org>
+
+From: Ankit Garg <nktgrg@google.com>
+
+[ Upstream commit d676c9a73bdcd8237425dbb826f2bd1a25c36e40 ]
+
+The DQO RX datapath programs a per-buffer-queue-descriptor
+header_buf_addr at post time and reads the split header back at
+completion time. Both the post and the read currently index the
+header buffer by queue position rather than by the buffer's identity:
+
+ - post (gve_rx_post_buffers_dqo): header_buf_addr is computed from
+ bufq->tail
+ - read (gve_rx_dqo): the header is read from desc_idx (the completion
+ queue head index)
+
+This relies on the buffer-queue index and the completion-queue index
+being equal for the start of every packet, i.e. on the device consuming
+posted buffers and returning completions in the exact same order. That
+assumption does not hold once HW-GRO is enabled with multiple
+flows: coalesced segments are accepted and completed in an order that
+may differ from the order buffers were posted, and segments from
+different flows may interleave.
+
+That results in two problems:
+
+1. Wrong header slot on read. Because the read offset is derived from
+ the completion index (desc_idx) while the device wrote the header to
+ the address programmed for the buffer's buf_id, the driver can copy
+ a header belonging to a different packet. This shows up as
+ throughput drop (about 30% drop and large numbers of TCP
+ retransmissions) with header-split and HW-GRO both enabled and many
+ streams.
+
+2. Header buffer reused while still owned by the device. The driver
+ advances bufq->head by one per completion and re-posts buffers based
+ on that. Arrival of N RX completions only guarantees that at least N
+ RX buffer descriptors have been read by the device. It does not
+ guarantee that the device has relinquished the ownership of all the
+ buffers corresponding to those N descriptors. With out-of-order
+ completions (e.g. the completion for a packet copied into buffer N
+ arrives before the completion for a packet copied into buffer N-1),
+ the driver can re-post and overwrite a header buffer that the device
+ is still going to write into, corrupting the header of a packet
+ whose completion has not yet been processed.
+
+Fix both issues by indexing the header buffer by buf_id on both the post
+and read paths. Reading from buf_id's slot is therefore always correct
+regardless of completion ordering (fixes problem 1).
+
+Indexing by buf_id also ties each header slot to the lifetime of its
+buffer state. A buffer state is only returned to the free/recycle lists
+when its own completion (buf_id) is processed, so its header slot can
+only be re-posted after the device is done with it. This makes header
+slot reuse safe under out-of-order completions (fixes problem 2).
+
+Allocate (gve_rx_alloc_hdr_bufs) and free (gve_rx_free_hdr_bufs) the
+header buffers based on num_buf_states to match the buf_id indexing.
+
+Cc: stable@vger.kernel.org
+Fixes: 5e37d8254e7f ("gve: Add header split data path")
+Signed-off-by: Ankit Garg <nktgrg@google.com>
+Reviewed-by: Praveen Kaligineedi <pkaligineedi@google.com>
+Reviewed-by: Jordan Rhee <jordanrhee@google.com>
+Reviewed-by: Harshitha Ramamurthy <hramamurthy@google.com>
+Signed-off-by: Joshua Washington <joshwash@google.com>
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+Link: https://patch.msgid.link/20260617013208.3781453-1-joshwash@google.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/google/gve/gve_rx_dqo.c | 28 +++++++++++++++++----------
+ 1 file changed, 18 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c
++++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c
+@@ -212,11 +212,13 @@ static void gve_rx_starvation_timer(stru
+ static void gve_rx_free_hdr_bufs(struct gve_priv *priv, struct gve_rx_ring *rx)
+ {
+ struct device *hdev = &priv->pdev->dev;
+- int buf_count = rx->dqo.bufq.mask + 1;
+
+ if (rx->dqo.hdr_bufs.data) {
+- dma_free_coherent(hdev, priv->header_buf_size * buf_count,
+- rx->dqo.hdr_bufs.data, rx->dqo.hdr_bufs.addr);
++ size_t size =
++ (size_t)priv->header_buf_size * rx->dqo.num_buf_states;
++
++ dma_free_coherent(hdev, size, rx->dqo.hdr_bufs.data,
++ rx->dqo.hdr_bufs.addr);
+ rx->dqo.hdr_bufs.data = NULL;
+ }
+ }
+@@ -421,7 +423,7 @@ int gve_rx_alloc_ring_dqo(struct gve_pri
+
+ /* Allocate header buffers for header-split */
+ if (cfg->enable_header_split)
+- if (gve_rx_alloc_hdr_bufs(priv, rx, buffer_queue_slots))
++ if (gve_rx_alloc_hdr_bufs(priv, rx, rx->dqo.num_buf_states))
+ goto err;
+
+ /* Allocate RX completion queue */
+@@ -557,10 +559,13 @@ void gve_rx_post_buffers_dqo(struct gve_
+ desc->buf_id = cpu_to_le16(buf_state - rx->dqo.buf_states);
+ desc->buf_addr = cpu_to_le64(buf_state->addr +
+ buf_state->page_info.page_offset);
+- if (rx->dqo.hdr_bufs.data)
++ if (rx->dqo.hdr_bufs.data) {
++ u16 buf_id = le16_to_cpu(desc->buf_id);
++
+ desc->header_buf_addr =
+ cpu_to_le64(rx->dqo.hdr_bufs.addr +
+- priv->header_buf_size * bufq->tail);
++ (size_t)priv->header_buf_size * buf_id);
++ }
+
+ bufq->tail = (bufq->tail + 1) & bufq->mask;
+ complq->num_free_slots--;
+@@ -827,10 +832,13 @@ static int gve_rx_dqo(struct napi_struct
+ int unsplit = 0;
+
+ if (hdr_len && !hbo) {
+- rx->ctx.skb_head = gve_rx_copy_data(priv->dev, napi,
+- rx->dqo.hdr_bufs.data +
+- desc_idx * priv->header_buf_size,
+- hdr_len);
++ size_t offset =
++ (size_t)buffer_id * priv->header_buf_size;
++
++ rx->ctx.skb_head =
++ gve_rx_copy_data(priv->dev, napi,
++ rx->dqo.hdr_bufs.data + offset,
++ hdr_len);
+ if (unlikely(!rx->ctx.skb_head))
+ goto error;
+ rx->ctx.skb_tail = rx->ctx.skb_head;
--- /dev/null
+From stable+bounces-282938-greg=kroah.com@vger.kernel.org Tue Jul 21 21:57:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 15:36:03 -0400
+Subject: i2c: davinci: Unregister cpufreq notifier on probe failure
+To: stable@vger.kernel.org
+Cc: Haoxiang Li <haoxiang_li2024@163.com>, Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>, Andi Shyti <andi.shyti@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721193603.410310-1-sashal@kernel.org>
+
+From: Haoxiang Li <haoxiang_li2024@163.com>
+
+[ Upstream commit e43f32816a1b1fe5a86279411626fe3a9be56d45 ]
+
+davinci_i2c_probe() registers a cpufreq transition notifier before adding
+the I2C adapter. If i2c_add_numbered_adapter() fails, the probe error path
+releases the device resources without unregistering the notifier.
+
+Add a dedicated error path to unregister the cpufreq notifier after
+i2c_add_numbered_adapter() fails.
+
+Fixes: 82c0de11b734 ("i2c: davinci: Add cpufreq support")
+Signed-off-by: Haoxiang Li <haoxiang_li2024@163.com>
+Cc: <stable@vger.kernel.org> # v2.6.36+
+Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
+Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
+Link: https://lore.kernel.org/r/20260610030513.2651018-1-haoxiang_li2024@163.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/i2c/busses/i2c-davinci.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/i2c/busses/i2c-davinci.c
++++ b/drivers/i2c/busses/i2c-davinci.c
+@@ -864,13 +864,15 @@ static int davinci_i2c_probe(struct plat
+ adap->nr = pdev->id;
+ r = i2c_add_numbered_adapter(adap);
+ if (r)
+- goto err_unuse_clocks;
++ goto err_cpufreq;
+
+ pm_runtime_mark_last_busy(dev->dev);
+ pm_runtime_put_autosuspend(dev->dev);
+
+ return 0;
+
++err_cpufreq:
++ i2c_davinci_cpufreq_deregister(dev);
+ err_unuse_clocks:
+ pm_runtime_dont_use_autosuspend(dev->dev);
+ pm_runtime_put_sync(dev->dev);
--- /dev/null
+From stable+bounces-287675-greg=kroah.com@vger.kernel.org Wed Jul 22 04:46:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 20:51:11 -0400
+Subject: i2c: i801: fix hardware state machine corruption in error path
+To: stable@vger.kernel.org
+Cc: Mingyu Wang <25181214217@stu.xidian.edu.cn>, Andi Shyti <andi.shyti@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722005111.605431-1-sashal@kernel.org>
+
+From: Mingyu Wang <25181214217@stu.xidian.edu.cn>
+
+[ Upstream commit 10dd1a736d557e310a77117832874729a0175d57 ]
+
+A severe livelock and subsequent Hung Task panic were observed in the
+i2c-i801 driver during concurrent Fuzzing. The crash is caused by an
+unconditional hardware register cleanup in the error handling path of
+i801_access().
+
+When i801_check_pre() fails (e.g., returning -EBUSY because the SMBus
+controller is actively used by BIOS/ACPI), the kernel does not actually
+acquire the hardware ownership. However, the code jumps to the 'out'
+label and executes:
+
+ iowrite8(SMBHSTSTS_INUSE_STS | STATUS_FLAGS, SMBHSTSTS(priv));
+
+This forcefully clears the INUSE_STS lock and resets the hardware status
+flags without owning the controller. Doing so interrupts ongoing BIOS/ACPI
+transactions and totally corrupts the SMBus hardware state machine.
+
+Consequently, all subsequent i801_access() calls fail at the pre-check
+stage, triggering an endless stream of "SMBus is busy, can't use it!"
+error logs. Over a slow serial console, this printk flood monopolizes
+the CPU (Console Livelock), starving other processes trying to acquire
+the mmap_lock down_read semaphore, ultimately triggering the hung task
+watchdog.
+
+Fix this by moving the 'out' label below the hardware register cleanup.
+If i801_check_pre() fails, we safely bypass the iowrite8() and only
+release the software locks (pm_runtime and mutex), strictly adhering to
+the rule of not releasing resources that were never acquired.
+
+Fixes: 1f760b87e54c ("i2c: i801: Call i801_check_pre() from i801_access()")
+Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
+Cc: <stable@vger.kernel.org> # v6.3+
+Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
+Link: https://lore.kernel.org/r/20260512093534.348655-1-w15303746062@163.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/i2c/busses/i2c-i801.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/i2c/busses/i2c-i801.c
++++ b/drivers/i2c/busses/i2c-i801.c
+@@ -930,13 +930,13 @@ static s32 i801_access(struct i2c_adapte
+ */
+ if (hwpec)
+ outb_p(inb_p(SMBAUXCTL(priv)) & ~SMBAUXCTL_CRC, SMBAUXCTL(priv));
+-out:
+ /*
+ * Unlock the SMBus device for use by BIOS/ACPI,
+ * and clear status flags if not done already.
+ */
+ outb_p(SMBHSTSTS_INUSE_STS | STATUS_FLAGS, SMBHSTSTS(priv));
+
++out:
+ pm_runtime_mark_last_busy(&priv->pci_dev->dev);
+ pm_runtime_put_autosuspend(&priv->pci_dev->dev);
+ mutex_unlock(&priv->acpi_lock);
--- /dev/null
+From stable+bounces-289477-greg=kroah.com@vger.kernel.org Mon Jul 27 14:19:47 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:19:38 -0400
+Subject: i2c: imx: fix locked bus on SMBus block-read of 0 (atomic)
+To: stable@vger.kernel.org
+Cc: Vincent Jardin <vjardin@free.fr>, Oleksij Rempel <o.rempel@pengutronix.de>, Carlos Song <carlos.song@nxp.com>, Stefan Eichenberger <eichest@gmail.com>, Andi Shyti <andi.shyti@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727121938.3777594-2-sashal@kernel.org>
+
+From: Vincent Jardin <vjardin@free.fr>
+
+[ Upstream commit cb2fc37857693b55909fb77dc2c87cfbc1cdc476 ]
+
+SMBus 3.1 6.5.7 allows a Block Read byte count of 0, but the atomic
+(polling) path rejects it as -EPROTO. Worse, it returns without a
+NACK+STOP: the next receive cycle has already started, so the target
+keeps holding SDA and the bus stays stuck until a power cycle for
+this i2c controller.
+
+Reading I2DR to obtain the count likewise arms the next byte on the
+count > I2C_SMBUS_BLOCK_MAX path, which also returned -EPROTO directly
+and left the bus held.
+
+Handle both: NACK the in-flight dummy byte (TXAK) and extend msgs->len so
+the existing last-byte handling emits STOP; the dummy byte is discarded.
+A count of 0 is a valid empty block read; a count above
+I2C_SMBUS_BLOCK_MAX is still reported as -EPROTO, but only after the bus
+has been released.
+
+The interrupt-driven path has the same flaw from a later commit and is
+fixed separately, as it carries a different Fixes: tag and stable range.
+
+Fixes: 8e8782c71595 ("i2c: imx: add SMBus block read support")
+Signed-off-by: Vincent Jardin <vjardin@free.fr>
+Cc: <stable@vger.kernel.org> # v3.16+
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Acked-by: Carlos Song <carlos.song@nxp.com>
+Reviewed-by: Stefan Eichenberger <eichest@gmail.com>
+Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
+Link: https://lore.kernel.org/r/20260713-for-upstream-i2c-lx2160-fix-v1-v3-1-073ac9e103a5@free.fr
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/i2c/busses/i2c-imx.c | 19 ++++++++++++++++---
+ 1 file changed, 16 insertions(+), 3 deletions(-)
+
+--- a/drivers/i2c/busses/i2c-imx.c
++++ b/drivers/i2c/busses/i2c-imx.c
+@@ -1180,6 +1180,7 @@ static int i2c_imx_read(struct imx_i2c_s
+ int i, result;
+ unsigned int temp;
+ int block_data = msgs->flags & I2C_M_RECV_LEN;
++ int block_err = 0;
+
+ result = i2c_imx_prepare_read(i2c_imx, msgs, atomic, false);
+ if (result)
+@@ -1201,8 +1202,20 @@ static int i2c_imx_read(struct imx_i2c_s
+ */
+ if ((!i) && block_data) {
+ len = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);
+- if ((len == 0) || (len > I2C_SMBUS_BLOCK_MAX))
+- return -EPROTO;
++ if ((len == 0) || (len > I2C_SMBUS_BLOCK_MAX)) {
++ /*
++ * SMBus 3.1 6.5.7: support count byte of 0.
++ * I2C_SMBUS_BLOCK_MAX case should not hold the SDA either.
++ */
++ if (len > I2C_SMBUS_BLOCK_MAX)
++ block_err = -EPROTO;
++ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
++ temp |= I2CR_TXAK;
++ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
++ msgs->buf[0] = 0;
++ msgs->len = 2;
++ continue;
++ }
+ dev_dbg(&i2c_imx->adapter.dev,
+ "<%s> read length: 0x%X\n",
+ __func__, len);
+@@ -1250,7 +1263,7 @@ static int i2c_imx_read(struct imx_i2c_s
+ "<%s> read byte: B%d=0x%X\n",
+ __func__, i, msgs->buf[i]);
+ }
+- return 0;
++ return block_err;
+ }
+
+ static int i2c_imx_atomic_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
--- /dev/null
+From stable+bounces-289476-greg=kroah.com@vger.kernel.org Mon Jul 27 14:26:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:19:37 -0400
+Subject: i2c: imx: separate atomic, dma and non-dma use case
+To: stable@vger.kernel.org
+Cc: Stefan Eichenberger <stefan.eichenberger@toradex.com>, Frank Li <Frank.Li@nxp.com>, Oleksij Rempel <o.rempel@pengutronix.de>, Andi Shyti <andi.shyti@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727121938.3777594-1-sashal@kernel.org>
+
+From: Stefan Eichenberger <stefan.eichenberger@toradex.com>
+
+[ Upstream commit b460b15b3cc23ef3639cc51043bf8b2a70ca1878 ]
+
+Separate the atomic, dma and non-dma use case as a preparation step for
+moving the non-dma use case to the isr to avoid rescheduling while a
+transfer is in progress.
+
+Signed-off-by: Stefan Eichenberger <stefan.eichenberger@toradex.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
+Stable-dep-of: cb2fc3785769 ("i2c: imx: fix locked bus on SMBus block-read of 0 (atomic)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/i2c/busses/i2c-imx.c | 107 ++++++++++++++++++++++++++++---------------
+ 1 file changed, 70 insertions(+), 37 deletions(-)
+
+--- a/drivers/i2c/busses/i2c-imx.c
++++ b/drivers/i2c/busses/i2c-imx.c
+@@ -1007,6 +1007,43 @@ static int i2c_imx_dma_write(struct imx_
+ return i2c_imx_acked(i2c_imx);
+ }
+
++static int i2c_imx_prepare_read(struct imx_i2c_struct *i2c_imx,
++ struct i2c_msg *msgs, bool atomic,
++ bool use_dma)
++{
++ int result;
++ unsigned int temp = 0;
++
++ /* write slave address */
++ imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR);
++ result = i2c_imx_trx_complete(i2c_imx, atomic);
++ if (result)
++ return result;
++ result = i2c_imx_acked(i2c_imx);
++ if (result)
++ return result;
++
++ dev_dbg(&i2c_imx->adapter.dev, "<%s> setup bus\n", __func__);
++
++ /* setup bus to read data */
++ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
++ temp &= ~I2CR_MTX;
++
++ /*
++ * Reset the I2CR_TXAK flag initially for SMBus block read since the
++ * length is unknown
++ */
++ if (msgs->len - 1)
++ temp &= ~I2CR_TXAK;
++ if (use_dma)
++ temp |= I2CR_DMAEN;
++
++ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
++ imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); /* dummy read */
++
++ return 0;
++}
++
+ static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx,
+ struct i2c_msg *msgs, bool is_lastmsg)
+ {
+@@ -1017,6 +1054,11 @@ static int i2c_imx_dma_read(struct imx_i
+ struct imx_i2c_dma *dma = i2c_imx->dma;
+ struct device *dev = &i2c_imx->adapter.dev;
+
++ result = i2c_imx_prepare_read(i2c_imx, msgs, false, true);
++ if (result)
++ return result;
++
++ dev_dbg(&i2c_imx->adapter.dev, "<%s> read data\n", __func__);
+
+ dma->chan_using = dma->chan_rx;
+ dma->dma_transfer_dir = DMA_DEV_TO_MEM;
+@@ -1127,50 +1169,24 @@ static int i2c_imx_write(struct imx_i2c_
+ return 0;
+ }
+
++static int i2c_imx_atomic_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs)
++{
++ return i2c_imx_write(i2c_imx, msgs, true);
++}
++
+ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
+ bool is_lastmsg, bool atomic)
+ {
+ int i, result;
+ unsigned int temp;
+ int block_data = msgs->flags & I2C_M_RECV_LEN;
+- int use_dma = i2c_imx->dma && msgs->flags & I2C_M_DMA_SAFE &&
+- msgs->len >= DMA_THRESHOLD && !block_data;
+-
+- dev_dbg(&i2c_imx->adapter.dev,
+- "<%s> write slave address: addr=0x%x\n",
+- __func__, i2c_8bit_addr_from_msg(msgs));
+
+- /* write slave address */
+- imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR);
+- result = i2c_imx_trx_complete(i2c_imx, atomic);
+- if (result)
+- return result;
+- result = i2c_imx_acked(i2c_imx);
++ result = i2c_imx_prepare_read(i2c_imx, msgs, atomic, false);
+ if (result)
+ return result;
+
+- dev_dbg(&i2c_imx->adapter.dev, "<%s> setup bus\n", __func__);
+-
+- /* setup bus to read data */
+- temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
+- temp &= ~I2CR_MTX;
+-
+- /*
+- * Reset the I2CR_TXAK flag initially for SMBus block read since the
+- * length is unknown
+- */
+- if ((msgs->len - 1) || block_data)
+- temp &= ~I2CR_TXAK;
+- if (use_dma)
+- temp |= I2CR_DMAEN;
+- imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
+- imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); /* dummy read */
+-
+ dev_dbg(&i2c_imx->adapter.dev, "<%s> read data\n", __func__);
+
+- if (use_dma)
+- return i2c_imx_dma_read(i2c_imx, msgs, is_lastmsg);
+-
+ /* read data */
+ for (i = 0; i < msgs->len; i++) {
+ u8 len = 0;
+@@ -1237,6 +1253,12 @@ static int i2c_imx_read(struct imx_i2c_s
+ return 0;
+ }
+
++static int i2c_imx_atomic_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
++ bool is_lastmsg)
++{
++ return i2c_imx_read(i2c_imx, msgs, is_lastmsg, true);
++}
++
+ static int i2c_imx_xfer_common(struct i2c_adapter *adapter,
+ struct i2c_msg *msgs, int num, bool atomic)
+ {
+@@ -1244,6 +1266,7 @@ static int i2c_imx_xfer_common(struct i2
+ int result;
+ bool is_lastmsg = false;
+ struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
++ int use_dma = 0;
+
+ /* Start I2C transfer */
+ result = i2c_imx_start(i2c_imx, atomic);
+@@ -1296,15 +1319,25 @@ static int i2c_imx_xfer_common(struct i2
+ (temp & I2SR_SRW ? 1 : 0), (temp & I2SR_IIF ? 1 : 0),
+ (temp & I2SR_RXAK ? 1 : 0));
+ #endif
++
++ use_dma = i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD &&
++ msgs[i].flags & I2C_M_DMA_SAFE;
+ if (msgs[i].flags & I2C_M_RD) {
+- result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg, atomic);
++ int block_data = msgs->flags & I2C_M_RECV_LEN;
++
++ if (atomic)
++ result = i2c_imx_atomic_read(i2c_imx, &msgs[i], is_lastmsg);
++ else if (use_dma && !block_data)
++ result = i2c_imx_dma_read(i2c_imx, &msgs[i], is_lastmsg);
++ else
++ result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg, false);
+ } else {
+- if (!atomic &&
+- i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD &&
+- msgs[i].flags & I2C_M_DMA_SAFE)
++ if (atomic)
++ result = i2c_imx_atomic_write(i2c_imx, &msgs[i]);
++ else if (use_dma)
+ result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
+ else
+- result = i2c_imx_write(i2c_imx, &msgs[i], atomic);
++ result = i2c_imx_write(i2c_imx, &msgs[i], false);
+ }
+ if (result)
+ goto fail0;
--- /dev/null
+From stable+bounces-279661-greg=kroah.com@vger.kernel.org Tue Jul 21 19:25:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 13:13:53 -0400
+Subject: iommufd: Avoid partial fault group delivery in iommufd_fault_fops_read()
+To: stable@vger.kernel.org
+Cc: Nicolin Chen <nicolinc@nvidia.com>, Pranjal Shrivastava <praan@google.com>, Kevin Tian <kevin.tian@intel.com>, Jason Gunthorpe <jgg@nvidia.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721171353.111431-3-sashal@kernel.org>
+
+From: Nicolin Chen <nicolinc@nvidia.com>
+
+[ Upstream commit 091ab6d70dc444f56ed14faedbcacfc979f4c613 ]
+
+The cookie returned by xa_alloc() in iommufd_fault_fops_read() is per fault
+group, but the inner copy_to_user() runs per fault inside the group. If a
+copy fails mid-group, xa_erase clears the cookie and the group is restored
+to the deliver list, yet done is not rolled back. The function returns the
+partial byte count, with the successfully copied faults sitting at offsets
+below done carrying the now-erased cookie. The next read() then re-fetches
+the group, allocates a fresh cookie, and re-delivers every fault including
+the ones already copied; userspace sees duplicates carrying the new cookie,
+and a stale cookie that can never be responded to.
+
+Use a local group_done variable that tracks the per-group progress inside
+the inner loop, and only commit done = group_done after the inner loop has
+finished successfully. On a copy_to_user failure the outer break skips the
+commit, so done remains at its prior start-of-group baseline; the partial
+bytes already written past done are undefined to userspace per the read(2)
+contract, and the next read re-delivers the whole group atomically.
+
+Fixes: 07838f7fd529 ("iommufd: Add iommufd fault object")
+Link: https://patch.msgid.link/r/360cab4d4aeccb0bae275a970e2b3c340a71e0e0.1780343944.git.nicolinc@nvidia.com
+Cc: stable@vger.kernel.org
+Assisted-by: Claude:claude-opus-4-7
+Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
+Reviewed-by: Pranjal Shrivastava <praan@google.com>
+Reviewed-by: Kevin Tian <kevin.tian@intel.com>
+Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iommu/iommufd/fault.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+--- a/drivers/iommu/iommufd/fault.c
++++ b/drivers/iommu/iommufd/fault.c
+@@ -270,6 +270,8 @@ static ssize_t iommufd_fault_fops_read(s
+
+ mutex_lock(&fault->mutex);
+ while ((group = iommufd_fault_deliver_fetch(fault))) {
++ size_t group_done = done;
++
+ if (done >= count ||
+ group->fault_count * fault_size > count - done) {
+ iommufd_fault_deliver_restore(fault, group);
+@@ -291,16 +293,17 @@ static ssize_t iommufd_fault_fops_read(s
+ iommufd_compose_fault_message(&iopf->fault,
+ &data, idev,
+ group->cookie);
+- if (copy_to_user(buf + done, &data, fault_size)) {
++ if (copy_to_user(buf + group_done, &data, fault_size)) {
+ xa_erase(&fault->response, group->cookie);
+ iommufd_fault_deliver_restore(fault, group);
+ rc = -EFAULT;
+ break;
+ }
+- done += fault_size;
++ group_done += fault_size;
+ }
+ if (rc)
+ break;
++ done = group_done;
+ }
+ mutex_unlock(&fault->mutex);
+
--- /dev/null
+From stable+bounces-279638-greg=kroah.com@vger.kernel.org Tue Jul 21 20:37:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 13:13:11 -0400
+Subject: iommufd: Break the loop on failure in iommufd_fault_fops_read()
+To: stable@vger.kernel.org
+Cc: Nicolin Chen <nicolinc@nvidia.com>, Pranjal Shrivastava <praan@google.com>, Kevin Tian <kevin.tian@intel.com>, Jason Gunthorpe <jgg@nvidia.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721171311.108330-1-sashal@kernel.org>
+
+From: Nicolin Chen <nicolinc@nvidia.com>
+
+[ Upstream commit 172fc8b19825a0f5884c38f2289188284e2d45ee ]
+
+On a copy_to_user() failure inside the inner list_for_each_entry, only the
+inner loop breaks; the outer while re-fetches the just-restored fault group
+and retries the failing copy_to_user() forever, spinning the reader at 100%
+CPU with fault->mutex held.
+
+Check rc after the inner loop and break the outer while as well.
+
+Fixes: 07838f7fd529 ("iommufd: Add iommufd fault object")
+Link: https://patch.msgid.link/r/336a9b6e44fe66a24199d3be777c405c85c98622.1780343944.git.nicolinc@nvidia.com
+Cc: stable@vger.kernel.org
+Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
+Reviewed-by: Pranjal Shrivastava <praan@google.com>
+Reviewed-by: Kevin Tian <kevin.tian@intel.com>
+Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iommu/iommufd/fault.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/iommu/iommufd/fault.c
++++ b/drivers/iommu/iommufd/fault.c
+@@ -299,6 +299,8 @@ static ssize_t iommufd_fault_fops_read(s
+ }
+ done += fault_size;
+ }
++ if (rc)
++ break;
+ }
+ mutex_unlock(&fault->mutex);
+
--- /dev/null
+From stable+bounces-279627-greg=kroah.com@vger.kernel.org Tue Jul 21 19:24:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 13:12:50 -0400
+Subject: iommufd: Reject invalid read count in iommufd_fault_fops_read()
+To: stable@vger.kernel.org
+Cc: Nicolin Chen <nicolinc@nvidia.com>, Pranjal Shrivastava <praan@google.com>, Kevin Tian <kevin.tian@intel.com>, Jason Gunthorpe <jgg@nvidia.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721171250.107567-1-sashal@kernel.org>
+
+From: Nicolin Chen <nicolinc@nvidia.com>
+
+[ Upstream commit 47916a54eeb2a9e654512ee609f71bd5b29db702 ]
+
+The read count must be large enough to hold one fault or a group's faults.
+
+iommufd_fault_fops_read() does not validate the count, but returns 0 as if
+the read had succeeded while leaving the pending fault in the queue.
+
+Return -EINVAL in the undersize cases.
+
+Fixes: 07838f7fd529 ("iommufd: Add iommufd fault object")
+Link: https://patch.msgid.link/r/85c118a606fbedc5c132a1f5ec223a5ba23b92d2.1780343944.git.nicolinc@nvidia.com
+Cc: stable@vger.kernel.org
+Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
+Reviewed-by: Pranjal Shrivastava <praan@google.com>
+Reviewed-by: Kevin Tian <kevin.tian@intel.com>
+Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iommu/iommufd/fault.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/iommu/iommufd/fault.c
++++ b/drivers/iommu/iommufd/fault.c
+@@ -273,6 +273,9 @@ static ssize_t iommufd_fault_fops_read(s
+ if (done >= count ||
+ group->fault_count * fault_size > count - done) {
+ iommufd_fault_deliver_restore(fault, group);
++ /* Read count doesn't fit the first fault group */
++ if (done == 0)
++ rc = -EINVAL;
+ break;
+ }
+
--- /dev/null
+From stable+bounces-290096-greg=kroah.com@vger.kernel.org Tue Jul 28 19:41:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 13:07:22 -0400
+Subject: ipmi: fix refcount leak in i_ipmi_request()
+To: stable@vger.kernel.org
+Cc: Wentao Liang <vulab@iscas.ac.cn>, Corey Minyard <corey@minyard.net>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728170722.184404-1-sashal@kernel.org>
+
+From: Wentao Liang <vulab@iscas.ac.cn>
+
+[ Upstream commit a3f3859cecacb64f18fd446271ece9a3b3f2d4de ]
+
+When a caller provides a `supplied_recv` message to i_ipmi_request(),
+the function increments the user's `nr_msgs` reference count. If an
+error occurs later, the out_err cleanup path only frees the recv_msg
+if the function allocated it itself (i.e., !supplied_recv). In the
+supplied_recv case the cleanup is skipped, leaving the reference count
+elevated. The caller ipmi_request_supply_msgs() does not release the
+supplied_recv on error, so the reference is permanently leaked.
+
+Fix this by explicitly reverting the reference count operations when a
+supplied recv_msg with a valid user pointer is present in the error
+path: decrement nr_msgs and drop the user's kref.
+
+Cc: stable@vger.kernel.org
+Fixes: b52da4054ee0 ("ipmi: Rework user message limit handling")
+Signed-off-by: Wentao Liang <vulab@iscas.ac.cn>
+Message-ID: <20260603120634.3758747-1-vulab@iscas.ac.cn>
+Signed-off-by: Corey Minyard <corey@minyard.net>
+[ changed `free_ipmi_user` to `free_user` in the two added `kref_put()` calls ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/char/ipmi/ipmi_msghandler.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/drivers/char/ipmi/ipmi_msghandler.c
++++ b/drivers/char/ipmi/ipmi_msghandler.c
+@@ -2331,6 +2331,10 @@ static int i_ipmi_request(struct ipmi_us
+ if (smi_msg == NULL) {
+ if (!supplied_recv)
+ ipmi_free_recv_msg(recv_msg);
++ else if (recv_msg->user) {
++ atomic_dec(&recv_msg->user->nr_msgs);
++ kref_put(&recv_msg->user->refcount, free_user);
++ }
+ return -ENOMEM;
+ }
+ }
+@@ -2373,6 +2377,10 @@ out_err:
+ ipmi_free_smi_msg(smi_msg);
+ if (!supplied_recv)
+ ipmi_free_recv_msg(recv_msg);
++ else if (recv_msg->user) {
++ atomic_dec(&recv_msg->user->nr_msgs);
++ kref_put(&recv_msg->user->refcount, free_user);
++ }
+ } else {
+ dev_dbg(intf->si_dev, "Send: %*ph\n",
+ smi_msg->data_size, smi_msg->data);
--- /dev/null
+From stable+bounces-288959-greg=kroah.com@vger.kernel.org Fri Jul 24 19:00:37 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:44:56 -0400
+Subject: landlock: Fix LANDLOCK_SCOPE_SIGNAL bypass on the SIGIO path
+To: stable@vger.kernel.org
+Cc: "Bryam Vargas" <hexlabsecurity@proton.me>, "Günther Noack" <gnoack3000@gmail.com>, "Mickaël Salaün" <mic@digikod.net>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260724164456.469658-2-sashal@kernel.org>
+
+From: Bryam Vargas <hexlabsecurity@proton.me>
+
+[ Upstream commit 4b80320ca7ed03d6e683f95b6066565dc97b9f92 ]
+
+LANDLOCK_SCOPE_SIGNAL must prevent a sandboxed process from signaling
+processes outside its Landlock domain. It can be bypassed through the
+asynchronous SIGIO delivery path.
+
+A sandboxed process that owns any file or socket can arm it with
+fcntl(fd, F_SETOWN, -pgid), fcntl(fd, F_SETSIG, SIGKILL) and O_ASYNC, so
+that an I/O event makes the kernel deliver the chosen signal to the
+whole process group. As the head of its process group's task list (the
+default position right after fork()) that group can also hold the
+non-sandboxed process that launched it, e.g. a supervisor or a security
+monitor. The sandbox can thus kill or signal the processes
+LANDLOCK_SCOPE_SIGNAL is meant to protect from it.
+
+The scope is enforced in hook_file_send_sigiotask() against the Landlock
+domain recorded at F_SETOWN time, not the live domain of the sender.
+control_current_fowner() decides whether to record that domain and skips
+recording it when the fowner target is in the caller's thread group,
+which is safe only for a single-task target (PIDTYPE_PID, PIDTYPE_TGID).
+For a process group (PIDTYPE_PGID) pid_task() returns only one member;
+recording is skipped whenever that member shares the caller's thread
+group, and hook_file_send_sigiotask() then lets the signal fan out to
+the whole group unchecked.
+
+Record the domain for every non single-process target so the scope is
+enforced against each group member at delivery time.
+
+That recording is necessary but not sufficient on its own: the kernel
+signals a process group through its members' thread-group leaders, and
+the leader of the registrant's own process can carry a different
+Landlock domain than the sibling thread that armed the owner.
+domain_is_scoped() would then deny that leader, even though commit
+18eb75f3af40 ("landlock: Always allow signals between threads of the
+same process") requires same-process delivery to be allowed.
+hook_task_kill() avoids this by evaluating same_thread_group() live, per
+recipient; the SIGIO path instead delegates the whole decision to a
+single registration-time check, which a process-group fan-out cannot
+honor.
+
+So also record the registrant's thread group next to its domain and
+exempt it at delivery: hook_file_send_sigiotask() allows the signal
+whenever the recipient belongs to the registrant's own process,
+restoring the same-process guarantee while keeping out-of-domain group
+members blocked. The direct kill() path (hook_task_kill) already
+evaluates the live domain and is unaffected.
+
+Fixes: 18eb75f3af40 ("landlock: Always allow signals between threads of the same process")
+Cc: stable@vger.kernel.org
+Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
+Reviewed-by: Günther Noack <gnoack3000@gmail.com>
+Link: https://patch.msgid.link/56bffc24f3d0d08b45a686a48e99766b0a0821fa.1780614610.git.hexlabsecurity@proton.me
+[mic: Check pid_type earlier and improve comment, fix commit message,
+fix comment formatting]
+Signed-off-by: Mickaël Salaün <mic@digikod.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ security/landlock/fs.c | 14 ++++++++++++++
+ security/landlock/fs.h | 10 ++++++++++
+ security/landlock/task.c | 11 +++++++++++
+ 3 files changed, 35 insertions(+)
+
+--- a/security/landlock/fs.c
++++ b/security/landlock/fs.c
+@@ -1666,6 +1666,14 @@ static bool control_current_fowner(struc
+ lockdep_assert_held(&fown->lock);
+
+ /*
++ * A process-group or session owner (PIDTYPE_PGID/PIDTYPE_SID) fans the
++ * signal out to every member at delivery time, so record the domain and
++ * let hook_file_send_sigiotask() check the live scope per recipient.
++ */
++ if (fown->pid_type != PIDTYPE_PID && fown->pid_type != PIDTYPE_TGID)
++ return true;
++
++ /*
+ * Some callers (e.g. fcntl_dirnotify) may not be in an RCU read-side
+ * critical section.
+ */
+@@ -1681,6 +1689,7 @@ static void hook_file_set_fowner(struct
+ {
+ struct landlock_ruleset *prev_dom;
+ struct landlock_cred_security fown_subject = {};
++ struct pid *prev_tg, *fown_tg = NULL;
+ size_t fown_layer = 0;
+
+ /*
+@@ -1706,21 +1715,26 @@ static void hook_file_set_fowner(struct
+ if (new_subject) {
+ landlock_get_ruleset(new_subject->domain);
+ fown_subject = *new_subject;
++ fown_tg = get_pid(task_tgid(current));
+ }
+ }
+
+ prev_dom = landlock_file(file)->fown_subject.domain;
++ prev_tg = landlock_file(file)->fown_tg;
+ landlock_file(file)->fown_subject = fown_subject;
++ landlock_file(file)->fown_tg = fown_tg;
+ #ifdef CONFIG_AUDIT
+ landlock_file(file)->fown_layer = fown_layer;
+ #endif /* CONFIG_AUDIT*/
+
+ /* May be called in an RCU read-side critical section. */
+ landlock_put_ruleset_deferred(prev_dom);
++ put_pid(prev_tg);
+ }
+
+ static void hook_file_free_security(struct file *file)
+ {
++ put_pid(landlock_file(file)->fown_tg);
+ landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain);
+ }
+
+--- a/security/landlock/fs.h
++++ b/security/landlock/fs.h
+@@ -70,6 +70,16 @@ struct landlock_file_security {
+ * euid.
+ */
+ struct landlock_cred_security fown_subject;
++ /**
++ * @fown_tg: Thread group of the task that set the file owner, pinned
++ * while @fown_subject holds a domain. It lets
++ * hook_file_send_sigiotask() always allow a SIGIO delivered to the
++ * owner's own process -- e.g. the thread-group leader reached through a
++ * process-group owner -- matching the same-process exemption of
++ * hook_task_kill(). NULL when no domain is recorded. Protected by
++ * file->f_owner->lock, like @fown_subject.
++ */
++ struct pid *fown_tg;
+ };
+
+ /**
+--- a/security/landlock/task.c
++++ b/security/landlock/task.c
+@@ -314,6 +314,17 @@ static int hook_file_send_sigiotask(stru
+ if (!subject->domain)
+ return 0;
+
++ /*
++ * Always allow delivery to the file owner's own process, including a
++ * thread-group leader reached through a process-group owner. This
++ * mirrors hook_task_kill()'s same-process exemption and preserves the
++ * guarantee of commit 18eb75f3af40 ("landlock: Always allow signals
++ * between threads of the same process"), which the registration-time
++ * check cannot honor for a process-group target.
++ */
++ if (task_tgid(tsk) == landlock_file(fown->file)->fown_tg)
++ return 0;
++
+ scoped_guard(rcu)
+ {
+ is_scoped = domain_is_scoped(subject->domain,
--- /dev/null
+From stable+bounces-288958-greg=kroah.com@vger.kernel.org Fri Jul 24 19:00:37 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 12:44:55 -0400
+Subject: landlock: Prepare to use credential instead of domain for fowner
+To: stable@vger.kernel.org
+Cc: "Mickaël Salaün" <mic@digikod.net>, "Günther Noack" <gnoack@google.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260724164456.469658-1-sashal@kernel.org>
+
+From: Mickaël Salaün <mic@digikod.net>
+
+[ Upstream commit 79625f1b3a3df63d3289a0781fdf121bc42966f7 ]
+
+This cosmetic change is needed for audit support, specifically to be
+able to filter according to cross-execution boundaries.
+
+struct landlock_file_security's size stay the same for now but it will
+increase with struct landlock_cred_security's size.
+
+Only save Landlock domain in hook_file_set_fowner() if the current
+domain has LANDLOCK_SCOPE_SIGNAL, which was previously done for each
+hook_file_send_sigiotask() calls. This should improve a bit
+performance.
+
+Replace hardcoded LANDLOCK_SCOPE_SIGNAL with the signal_scope.scope
+variable.
+
+Use scoped guards for RCU read-side critical sections.
+
+Cc: Günther Noack <gnoack@google.com>
+Link: https://lore.kernel.org/r/20250320190717.2287696-8-mic@digikod.net
+Signed-off-by: Mickaël Salaün <mic@digikod.net>
+Stable-dep-of: 4b80320ca7ed ("landlock: Fix LANDLOCK_SCOPE_SIGNAL bypass on the SIGIO path")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ security/landlock/fs.c | 37 +++++++++++++++++++++++++++++++------
+ security/landlock/fs.h | 23 +++++++++++++++++------
+ security/landlock/task.c | 25 ++++++++++++++++---------
+ 3 files changed, 64 insertions(+), 21 deletions(-)
+
+--- a/security/landlock/fs.c
++++ b/security/landlock/fs.c
+@@ -1680,15 +1680,40 @@ static bool control_current_fowner(struc
+ static void hook_file_set_fowner(struct file *file)
+ {
+ struct landlock_ruleset *prev_dom;
+- struct landlock_ruleset *new_dom = NULL;
++ struct landlock_cred_security fown_subject = {};
++ size_t fown_layer = 0;
++
++ /*
++ * Keep this local to match the layout expected by follow-up changes
++ * shared with audit-enabled kernels.
++ */
++ (void)fown_layer;
+
+ if (control_current_fowner(file_f_owner(file))) {
+- new_dom = landlock_get_current_domain();
+- landlock_get_ruleset(new_dom);
++ static const struct access_masks signal_scope = {
++ .scope = LANDLOCK_SCOPE_SIGNAL,
++ };
++ struct landlock_ruleset *const current_domain =
++ landlock_get_current_domain();
++ const struct landlock_cred_security applicable_subject = {
++ .domain = current_domain,
++ };
++ const struct landlock_cred_security *new_subject = NULL;
++
++ if (landlock_get_applicable_domain(current_domain, signal_scope))
++ new_subject = &applicable_subject;
++
++ if (new_subject) {
++ landlock_get_ruleset(new_subject->domain);
++ fown_subject = *new_subject;
++ }
+ }
+
+- prev_dom = landlock_file(file)->fown_domain;
+- landlock_file(file)->fown_domain = new_dom;
++ prev_dom = landlock_file(file)->fown_subject.domain;
++ landlock_file(file)->fown_subject = fown_subject;
++#ifdef CONFIG_AUDIT
++ landlock_file(file)->fown_layer = fown_layer;
++#endif /* CONFIG_AUDIT*/
+
+ /* May be called in an RCU read-side critical section. */
+ landlock_put_ruleset_deferred(prev_dom);
+@@ -1696,7 +1721,7 @@ static void hook_file_set_fowner(struct
+
+ static void hook_file_free_security(struct file *file)
+ {
+- landlock_put_ruleset_deferred(landlock_file(file)->fown_domain);
++ landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain);
+ }
+
+ static struct security_hook_list landlock_hooks[] __ro_after_init = {
+--- a/security/landlock/fs.h
++++ b/security/landlock/fs.h
+@@ -1,9 +1,10 @@
+ /* SPDX-License-Identifier: GPL-2.0-only */
+ /*
+- * Landlock LSM - Filesystem management and hooks
++ * Landlock - Filesystem management and hooks
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
++ * Copyright © 2024-2025 Microsoft Corporation
+ */
+
+ #ifndef _SECURITY_LANDLOCK_FS_H
+@@ -13,6 +14,7 @@
+ #include <linux/init.h>
+ #include <linux/rcupdate.h>
+
++#include "cred.h"
+ #include "ruleset.h"
+ #include "setup.h"
+
+@@ -52,13 +54,22 @@ struct landlock_file_security {
+ * needed to authorize later operations on the open file.
+ */
+ access_mask_t allowed_access;
++
++#ifdef CONFIG_AUDIT
++ /**
++ * @fown_layer: Compatibility storage for follow-up fowner changes.
++ */
++ u8 fown_layer;
++#endif /* CONFIG_AUDIT */
++
+ /**
+- * @fown_domain: Domain of the task that set the PID that may receive a
+- * signal e.g., SIGURG when writing MSG_OOB to the related socket.
+- * This pointer is protected by the related file->f_owner->lock, as for
+- * fown_struct's members: pid, uid, and euid.
++ * @fown_subject: Landlock credential of the task that set the PID that
++ * may receive a signal e.g., SIGURG when writing MSG_OOB to the
++ * related socket. This pointer is protected by the related
++ * file->f_owner->lock, as for fown_struct's members: pid, uid, and
++ * euid.
+ */
+- struct landlock_ruleset *fown_domain;
++ struct landlock_cred_security fown_subject;
+ };
+
+ /**
+--- a/security/landlock/task.c
++++ b/security/landlock/task.c
+@@ -297,22 +297,29 @@ static int hook_task_kill(struct task_st
+ static int hook_file_send_sigiotask(struct task_struct *tsk,
+ struct fown_struct *fown, int signum)
+ {
+- const struct landlock_ruleset *dom;
++ const struct landlock_cred_security *subject;
+ bool is_scoped = false;
+
+ /* Lock already held by send_sigio() and send_sigurg(). */
+ lockdep_assert_held(&fown->lock);
+- dom = landlock_get_applicable_domain(
+- landlock_file(fown->file)->fown_domain, signal_scope);
++ subject = &landlock_file(fown->file)->fown_subject;
+
+- /* Quick return for unowned socket. */
+- if (!dom)
++ /*
++ * Quick return for unowned socket.
++ *
++ * subject->domain has already been filtered when saved by
++ * hook_file_set_fowner(), so there is no need to call
++ * landlock_get_applicable_subject() here.
++ */
++ if (!subject->domain)
+ return 0;
+
+- rcu_read_lock();
+- is_scoped = domain_is_scoped(dom, landlock_get_task_domain(tsk),
+- LANDLOCK_SCOPE_SIGNAL);
+- rcu_read_unlock();
++ scoped_guard(rcu)
++ {
++ is_scoped = domain_is_scoped(subject->domain,
++ landlock_get_task_domain(tsk),
++ signal_scope.scope);
++ }
+ if (is_scoped)
+ return -EPERM;
+
--- /dev/null
+From stable+bounces-289807-greg=kroah.com@vger.kernel.org Tue Jul 28 04:46:24 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 22:46:07 -0400
+Subject: locking/rt: Fix the incorrect RCU protection in rt_spin_unlock()
+To: stable@vger.kernel.org
+Cc: Thomas Gleixner <tglx@kernel.org>, syzbot+000c800a02097aaa10ed@syzkaller.appspotmail.com, Jann Horn <jannh@google.com>, Sebastian Andrzej Siewior <bigeasy@linutronix.de>, Al Viro <viro@zeniv.linux.org.uk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728024607.3052461-1-sashal@kernel.org>
+
+From: Thomas Gleixner <tglx@kernel.org>
+
+[ Upstream commit 89038cc87d80c77e7aa6f42a64b2573b74af339f ]
+
+rt_spin_unlock() releases the RCU protection before unlocking the
+lock. That opens the door for the following UAF scenario:
+
+ T1 T2
+ spin_lock(&p->lock); rcu_read_lock();
+ invalidate(p); p = rcu_dereference(ptr);
+ rcu_assign_pointer(ptr, NULL); if (!p) return;
+ spin_unlock(&p->lock); spin_lock(&p->lock)
+ lock(&lock->lock);
+ rcu_read_lock();
+ kfree_rcu(p); rcu_read_unlock();
+ ....
+ spin_unlock(&p->lock)
+ rcu_read_unlock(); // Ends grace period
+ rcu_do_batch()
+ kfree(p);
+ UAF -> rt_mutex_cmpxchg_release(&lock->lock...)
+
+Regular spinlocks keep preemption disabled accross the unlock operation,
+which provides full RCU protection, but the RT substitution fails to
+resemble that. Same applies for the rwlock substitution.
+
+Move the rcu_read_unlock() invocation past the unlock operations to match
+the non-RT semantics. This makes it asymmetric vs. rt_xxx_lock(), but
+that's harmless as the caller needs to hold RCU read lock across the lock
+operation. The migrate_enable() call stays before the unlock operation
+because there is no per CPU operation in the unlock path which would
+require migration to be kept disabled.
+
+Fixes: 0f383b6dc96e ("locking/spinlock: Provide RT variant")
+Reported-by: syzbot+000c800a02097aaa10ed@syzkaller.appspotmail.com
+Decoded-by: Jann Horn <jannh@google.com>
+Signed-off-by: Thomas Gleixner <tglx@kernel.org>
+Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+Acked-by: Al Viro <viro@zeniv.linux.org.uk>
+Cc: stable@vger.kernel.org
+Link: https://patch.msgid.link/87jyrud75z.ffs@fw13
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/locking/spinlock_rt.c | 27 ++++++++++++++++++++++++---
+ 1 file changed, 24 insertions(+), 3 deletions(-)
+
+--- a/kernel/locking/spinlock_rt.c
++++ b/kernel/locking/spinlock_rt.c
+@@ -79,10 +79,27 @@ void __sched rt_spin_unlock(spinlock_t *
+ {
+ spin_release(&lock->dep_map, _RET_IP_);
+ migrate_enable();
+- rcu_read_unlock();
+
+ if (unlikely(!rt_mutex_cmpxchg_release(&lock->lock, current, NULL)))
+ rt_mutex_slowunlock(&lock->lock);
++
++ /*
++ * This must be last to prevent the following UAF:
++ *
++ * T1 T2
++ * spin_lock(&p->lock); rcu_read_lock();
++ * invalidate(p); p = rcu_dereference(ptr);
++ * rcu_assign_pointer(ptr, NULL); if (!p) return;
++ * spin_unlock(&p->lock); spin_lock(&p->lock);
++ * kfree_rcu(p); rcu_read_unlock();
++ * ....
++ * spin_unlock(&p->lock)
++ * rcu_read_unlock(); // Ends grace period
++ * rcu_do_batch()
++ * kfree(p);
++ * UAF -> rt_mutex_cmpxchg_release(&p->lock.lock...)
++ */
++ rcu_read_unlock();
+ }
+ EXPORT_SYMBOL(rt_spin_unlock);
+
+@@ -262,17 +279,21 @@ void __sched rt_read_unlock(rwlock_t *rw
+ {
+ rwlock_release(&rwlock->dep_map, _RET_IP_);
+ migrate_enable();
+- rcu_read_unlock();
+ rwbase_read_unlock(&rwlock->rwbase, TASK_RTLOCK_WAIT);
++
++ /* This must be last. See comment in rt_spin_unlock() */
++ rcu_read_unlock();
+ }
+ EXPORT_SYMBOL(rt_read_unlock);
+
+ void __sched rt_write_unlock(rwlock_t *rwlock)
+ {
+ rwlock_release(&rwlock->dep_map, _RET_IP_);
+- rcu_read_unlock();
+ migrate_enable();
+ rwbase_write_unlock(&rwlock->rwbase);
++
++ /* This must be last. See comment in rt_spin_unlock() */
++ rcu_read_unlock();
+ }
+ EXPORT_SYMBOL(rt_write_unlock);
+
--- /dev/null
+From stable+bounces-288841-greg=kroah.com@vger.kernel.org Fri Jul 24 13:22:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 07:00:52 -0400
+Subject: mm/hugetlb: fix hugetlb cgroup rsvd charge/uncharge mismatch
+To: stable@vger.kernel.org
+Cc: Deepanshu Kartikey <kartikey406@gmail.com>, syzbot+226c1f947186f8fef796@syzkaller.appspotmail.com, Muchun Song <muchun.song@linux.dev>, David Hildenbrand <david@kernel.org>, Oscar Salvador <osalvador@suse.de>, Mina Almasry <almasrymina@google.com>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260724110052.3984407-1-sashal@kernel.org>
+
+From: Deepanshu Kartikey <kartikey406@gmail.com>
+
+[ Upstream commit 15807d0ddde37407af72859426b654f3d1972b00 ]
+
+In alloc_hugetlb_folio(), a single h_cg pointer is used for both the rsvd
+and non-rsvd hugetlb cgroup charges. When map_chg is set,
+hugetlb_cgroup_charge_cgroup_rsvd() stores the charged cgroup in h_cg, but
+the immediately following hugetlb_cgroup_charge_cgroup() overwrites h_cg
+with the non-rsvd cgroup pointer.
+
+As a result, hugetlb_cgroup_commit_charge_rsvd() stores the wrong
+(non-rsvd) cgroup pointer into the folio's rsvd slot.
+
+When the folio is later freed, free_huge_folio() unconditionally calls
+both hugetlb_cgroup_uncharge_folio() and
+hugetlb_cgroup_uncharge_folio_rsvd(). The rsvd uncharge reads back the
+wrong cgroup from the folio and decrements a counter that was never
+charged for that cgroup, causing a page_counter underflow:
+
+ page_counter underflow: -512 nr_pages=512
+ WARNING: mm/page_counter.c:61 at page_counter_cancel
+
+Fix this by introducing a separate h_cg_rsvd pointer exclusively for the
+rsvd charge path, keeping the rsvd and non-rsvd charges fully independent
+through their charge, commit, and error uncharge paths.
+
+Link: https://lore.kernel.org/20260328065534.346053-1-kartikey406@gmail.com
+Fixes: 08cf9faf7558 ("hugetlb_cgroup: support noreserve mappings")
+Reported-by: syzbot+226c1f947186f8fef796@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=226c1f947186f8fef796
+Signed-off-by: Deepanshu Kartikey <kartikey406@gmail.com>
+Reviewed-by: Muchun Song <muchun.song@linux.dev>
+Cc: David Hildenbrand <david@kernel.org>
+Cc: Oscar Salvador <osalvador@suse.de>
+Cc: Mina Almasry <almasrymina@google.com>
+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/hugetlb.c | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+--- a/mm/hugetlb.c
++++ b/mm/hugetlb.c
+@@ -2976,6 +2976,7 @@ struct folio *alloc_hugetlb_folio(struct
+ long gbl_chg;
+ int memcg_charge_ret, ret, idx;
+ struct hugetlb_cgroup *h_cg = NULL;
++ struct hugetlb_cgroup *h_cg_rsvd = NULL;
+ struct mem_cgroup *memcg;
+ bool deferred_reserve;
+ gfp_t gfp = htlb_alloc_mask(h) | __GFP_RETRY_MAYFAIL;
+@@ -3019,7 +3020,7 @@ struct folio *alloc_hugetlb_folio(struct
+ deferred_reserve = map_chg || avoid_reserve;
+ if (deferred_reserve) {
+ ret = hugetlb_cgroup_charge_cgroup_rsvd(
+- idx, pages_per_huge_page(h), &h_cg);
++ idx, pages_per_huge_page(h), &h_cg_rsvd);
+ if (ret)
+ goto out_subpool_put;
+ }
+@@ -3056,7 +3057,7 @@ struct folio *alloc_hugetlb_folio(struct
+ */
+ if (deferred_reserve) {
+ hugetlb_cgroup_commit_charge_rsvd(idx, pages_per_huge_page(h),
+- h_cg, folio);
++ h_cg_rsvd, folio);
+ }
+
+ spin_unlock_irq(&hugetlb_lock);
+@@ -3097,7 +3098,7 @@ out_uncharge_cgroup:
+ out_uncharge_cgroup_reservation:
+ if (deferred_reserve)
+ hugetlb_cgroup_uncharge_cgroup_rsvd(idx, pages_per_huge_page(h),
+- h_cg);
++ h_cg_rsvd);
+ out_subpool_put:
+ if (map_chg || avoid_reserve)
+ hugepage_subpool_put_pages(spool, 1);
--- /dev/null
+From stable+bounces-289041-greg=kroah.com@vger.kernel.org Sat Jul 25 03:37:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 21:37:41 -0400
+Subject: mm/mm_init: fix pageblock migratetype for ZONE_DEVICE compound pages
+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: <20260725013741.1892750-1-sashal@kernel.org>
+
+From: Muchun Song <songmuchun@bytedance.com>
+
+[ Upstream commit 94405c6136839f7c462249c8b4b957bcb9527a9d ]
+
+The memmap_init_zone_device() function only initializes the migratetype of
+the first pageblock of a compound page. If the compound page size exceeds
+pageblock_nr_pages (e.g., 1GB hugepages with 2MB pageblocks), subsequent
+pageblocks in the compound page remain uninitialized.
+
+Move the migratetype initialization out of __init_zone_device_page() and
+into a separate pageblock_migratetype_init_range() function. This
+iterates over the entire PFN range of the memory, ensuring that all
+pageblocks are correctly initialized.
+
+Also remove the stale confusing comment about MEMINIT_HOTPLUG above the
+migratetype setting since it is an obsolete relic from commit 966cf44f637e
+("mm: defer ZONE_DEVICE page initialization to the point where we init
+pgmap") and no longer makes sense here.
+
+Link: https://lore.kernel.org/20260428081855.1249045-6-songmuchun@bytedance.com
+Fixes: c4386bd8ee3a ("mm/memremap: add ZONE_DEVICE support for compound pages")
+Signed-off-by: Muchun Song <songmuchun@bytedance.com>
+Reviewed-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>
+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/mm_init.c | 34 +++++++++++++++++++---------------
+ 1 file changed, 19 insertions(+), 15 deletions(-)
+
+--- a/mm/mm_init.c
++++ b/mm/mm_init.c
+@@ -650,6 +650,20 @@ static inline void fixup_hashdist(void)
+ static inline void fixup_hashdist(void) {}
+ #endif /* CONFIG_NUMA */
+
++#ifdef CONFIG_ZONE_DEVICE
++static __meminit void pageblock_migratetype_init_range(unsigned long pfn,
++ unsigned long nr_pages, int migratetype)
++{
++ const unsigned long end = pfn + nr_pages;
++
++ for (pfn = pageblock_align(pfn); pfn < end; pfn += pageblock_nr_pages) {
++ set_pageblock_migratetype(pfn_to_page(pfn), migratetype);
++ if (IS_ALIGNED(pfn, PAGES_PER_SECTION))
++ cond_resched();
++ }
++}
++#endif
++
+ #ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT
+ static inline void pgdat_set_deferred_range(pg_data_t *pgdat)
+ {
+@@ -1000,21 +1014,6 @@ static void __ref __init_zone_device_pag
+ page->zone_device_data = NULL;
+
+ /*
+- * Mark the block movable so that blocks are reserved for
+- * movable at startup. This will force kernel allocations
+- * to reserve their blocks rather than leaking throughout
+- * the address space during boot when many long-lived
+- * kernel allocations are made.
+- *
+- * Please note that MEMINIT_HOTPLUG path doesn't clear memmap
+- * because this is done early in section_activate()
+- */
+- if (pageblock_aligned(pfn)) {
+- set_pageblock_migratetype(page, MIGRATE_MOVABLE);
+- cond_resched();
+- }
+-
+- /*
+ * ZONE_DEVICE pages are released directly to the driver page allocator
+ * which will set the page count to 1 when allocating the page.
+ */
+@@ -1105,6 +1104,9 @@ void __ref memmap_init_zone_device(struc
+
+ __init_zone_device_page(page, pfn, zone_idx, nid, pgmap);
+
++ if (IS_ALIGNED(pfn, PAGES_PER_SECTION))
++ cond_resched();
++
+ if (pfns_per_compound == 1)
+ continue;
+
+@@ -1112,6 +1114,8 @@ void __ref memmap_init_zone_device(struc
+ compound_nr_pages(pfn, altmap, pgmap));
+ }
+
++ pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE);
++
+ pr_debug("%s initialised %lu pages in %ums\n", __func__,
+ nr_pages, jiffies_to_msecs(jiffies - start));
+ }
--- /dev/null
+From stable+bounces-289228-greg=kroah.com@vger.kernel.org Sun Jul 26 14:03:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:00:45 -0400
+Subject: mm: prepare to move subsection_map_init() to mm/sparse-vmemmap.c
+To: stable@vger.kernel.org
+Cc: "David Hildenbrand (Arm)" <david@kernel.org>, "Lorenzo Stoakes (Oracle)" <ljs@kernel.org>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Axel Rasmussen <axelrasmussen@google.com>, Liam Howlett <liam.howlett@oracle.com>, Michal Hocko <mhocko@suse.com>, Oscar Salvador <osalvador@suse.de>, Sidhartha Kumar <sidhartha.kumar@oracle.com>, Suren Baghdasaryan <surenb@google.com>, Vlastimil Babka <vbabka@kernel.org>, Wei Xu <weixugc@google.com>, Yuanchu Xie <yuanchu@google.com>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726120046.4138231-1-sashal@kernel.org>
+
+From: "David Hildenbrand (Arm)" <david@kernel.org>
+
+[ Upstream commit fead6dcff83b02f8d6dc3c1ebbe4e09c05c54ee5 ]
+
+We want to move subsection_map_init() to mm/sparse-vmemmap.c.
+
+To prepare for getting rid of subsection_map_init() in mm/sparse.c
+completely, use a static inline function for !CONFIG_SPARSEMEM_VMEMMAP.
+
+While at it, move the declaration to internal.h and rename it to
+"sparse_init_subsection_map()".
+
+Link: https://lkml.kernel.org/r/20260320-sparsemem_cleanups-v2-11-096addc8800d@kernel.org
+Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
+Reviewed-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
+Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Cc: Axel Rasmussen <axelrasmussen@google.com>
+Cc: Liam Howlett <liam.howlett@oracle.com>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Oscar Salvador <osalvador@suse.de>
+Cc: Sidhartha Kumar <sidhartha.kumar@oracle.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: Vlastimil Babka <vbabka@kernel.org>
+Cc: Wei Xu <weixugc@google.com>
+Cc: Yuanchu Xie <yuanchu@google.com>
+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 -
+ include/linux/mmzone.h | 7
+ mm/internal.h | 47 ++++++
+ mm/memory_hotplug.c | 13 -
+ mm/memremap.c | 4
+ mm/mm_init.c | 2
+ mm/sparse-vmemmap.c | 310 +++++++++++++++++++++++++++++++++++++++++
+ mm/sparse.c | 80 ----------
+ 14 files changed, 399 insertions(+), 102 deletions(-)
+
+--- a/arch/arm64/mm/mmu.c
++++ b/arch/arm64/mm/mmu.c
+@@ -1384,12 +1384,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);
+ __remove_pgd_mapping(swapper_pg_dir, __phys_to_virt(start), size);
+ }
+
+--- a/arch/loongarch/mm/init.c
++++ b/arch/loongarch/mm/init.c
+@@ -103,12 +103,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);
+ }
+
+ #ifdef CONFIG_NUMA
+--- a/arch/powerpc/mm/mem.c
++++ b/arch/powerpc/mm/mem.c
+@@ -157,12 +157,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
+@@ -1839,9 +1839,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
+@@ -290,12 +290,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
+@@ -1293,12 +1293,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
+@@ -153,9 +153,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,
+@@ -321,7 +322,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 page *sparse_decode_mem_map(unsigned long coded_mem_map,
+ unsigned long pnum);
+ extern struct zone *zone_for_pfn_range(int online_type, int nid,
+--- a/include/linux/mmzone.h
++++ b/include/linux/mmzone.h
+@@ -1815,15 +1815,13 @@ struct mem_section_usage {
+ unsigned long pageblock_flags[0];
+ };
+
+-void subsection_map_init(unsigned long pfn, unsigned long nr_pages);
+-
+ struct page;
+ struct page_ext;
+ struct mem_section {
+ /*
+ * This is, logically, a pointer to an array of struct
+ * pages. However, it is stored with some other magic.
+- * (see sparse.c::sparse_init_one_section())
++ * (see sparse_init_one_section())
+ *
+ * Additionally during early boot we encode node id of
+ * the location of the section here to guide allocation.
+@@ -2092,8 +2090,9 @@ void sparse_init(void);
+ #else
+ #define sparse_init() do {} while (0)
+ #define sparse_index_init(_sec, _nid) do {} while (0)
++#define sparse_vmemmap_init_nid_early(_nid) do {} while (0)
++#define sparse_vmemmap_init_nid_late(_nid) do {} while (0)
+ #define pfn_in_present_section pfn_valid
+-#define subsection_map_init(_pfn, _nr_pages) do {} while (0)
+ #endif /* CONFIG_SPARSEMEM */
+
+ #endif /* !__GENERATING_BOUNDS.H */
+--- a/mm/internal.h
++++ b/mm/internal.h
+@@ -749,6 +749,53 @@ extern void *memmap_alloc(phys_addr_t si
+ void memmap_init_range(unsigned long, int, unsigned long, unsigned long,
+ unsigned long, enum meminit_context, struct vmem_altmap *, int);
+
++#ifdef CONFIG_SPARSEMEM
++int sparse_index_init(unsigned long section_nr, int nid);
++
++static inline void sparse_init_one_section(struct mem_section *ms,
++ unsigned long pnum, struct page *mem_map,
++ struct mem_section_usage *usage, unsigned long flags)
++{
++ unsigned long coded_mem_map;
++
++ BUILD_BUG_ON(SECTION_MAP_LAST_BIT > PFN_SECTION_SHIFT);
++
++ /*
++ * We encode the start PFN of the section into the mem_map such that
++ * page_to_pfn() on !CONFIG_SPARSEMEM_VMEMMAP can simply subtract it
++ * from the page pointer to obtain the PFN.
++ */
++ coded_mem_map = (unsigned long)(mem_map - section_nr_to_pfn(pnum));
++ VM_WARN_ON_ONCE(coded_mem_map & ~SECTION_MAP_MASK);
++
++ ms->section_mem_map &= ~SECTION_MAP_MASK;
++ ms->section_mem_map |= coded_mem_map;
++ ms->section_mem_map |= flags | SECTION_HAS_MEM_MAP;
++ ms->usage = usage;
++}
++
++static inline void __section_mark_present(struct mem_section *ms,
++ unsigned long section_nr)
++{
++ if (section_nr > __highest_present_section_nr)
++ __highest_present_section_nr = section_nr;
++
++ ms->section_mem_map |= SECTION_MARKED_PRESENT;
++}
++#endif /* CONFIG_SPARSEMEM */
++
++/*
++ * mm/sparse-vmemmap.c
++ */
++#ifdef CONFIG_SPARSEMEM_VMEMMAP
++void sparse_init_subsection_map(unsigned long pfn, unsigned long nr_pages);
++#else
++static inline void sparse_init_subsection_map(unsigned long pfn,
++ unsigned long nr_pages)
++{
++}
++#endif /* CONFIG_SPARSEMEM_VMEMMAP */
++
+ #if defined CONFIG_COMPACTION || defined CONFIG_CMA
+
+ /*
+--- a/mm/memory_hotplug.c
++++ b/mm/memory_hotplug.c
+@@ -564,6 +564,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
+@@ -571,7 +572,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;
+@@ -586,7 +587,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);
+ }
+ }
+
+@@ -1411,7 +1412,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");
+@@ -1456,7 +1457,7 @@ static int create_altmaps_and_memory_blo
+ ret = create_memory_block_devices(cur_start, memblock_size,
+ 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;
+ }
+@@ -1534,7 +1535,7 @@ int add_memory_resource(int nid, struct
+ /* create memory block devices after memory was added */
+ ret = create_memory_block_devices(start, size, NULL, group);
+ if (ret) {
+- arch_remove_memory(start, size, params.altmap);
++ arch_remove_memory(start, size, params.altmap, NULL);
+ goto error;
+ }
+ }
+@@ -2273,7 +2274,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
+@@ -122,10 +122,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/mm_init.c
++++ b/mm/mm_init.c
+@@ -1835,7 +1835,7 @@ void __init free_area_init(unsigned long
+ pr_info(" node %3d: [mem %#018Lx-%#018Lx]\n", nid,
+ (u64)start_pfn << PAGE_SHIFT,
+ ((u64)end_pfn << PAGE_SHIFT) - 1);
+- subsection_map_init(start_pfn, end_pfn - start_pfn);
++ sparse_init_subsection_map(start_pfn, end_pfn - start_pfn);
+ }
+
+ /* Initialise every node */
+--- a/mm/sparse-vmemmap.c
++++ b/mm/sparse-vmemmap.c
+@@ -31,6 +31,8 @@
+
+ #include <asm/dma.h>
+
++#include "internal.h"
++
+ /*
+ * Allocate a block of memory to be used to back the virtual memory map
+ * or to back the page tables that are used to create the mapping.
+@@ -476,3 +478,311 @@ struct page * __meminit __populate_secti
+
+ return pfn_to_page(pfn);
+ }
++
++static void subsection_mask_set(unsigned long *map, unsigned long pfn,
++ unsigned long nr_pages)
++{
++ int idx = subsection_map_index(pfn);
++ int end = subsection_map_index(pfn + nr_pages - 1);
++
++ bitmap_set(map, idx, end - idx + 1);
++}
++
++void __init sparse_init_subsection_map(unsigned long pfn, unsigned long nr_pages)
++{
++ int end_sec_nr = pfn_to_section_nr(pfn + nr_pages - 1);
++ unsigned long nr, start_sec_nr = pfn_to_section_nr(pfn);
++
++ for (nr = start_sec_nr; nr <= end_sec_nr; nr++) {
++ struct mem_section *ms;
++ unsigned long pfns;
++
++ pfns = min(nr_pages, PAGES_PER_SECTION
++ - (pfn & ~PAGE_SECTION_MASK));
++ ms = __nr_to_section(nr);
++ subsection_mask_set(ms->usage->subsection_map, pfn, pfns);
++
++ pr_debug("%s: sec: %lu pfns: %lu set(%d, %d)\n", __func__, nr,
++ pfns, subsection_map_index(pfn),
++ subsection_map_index(pfn + pfns - 1));
++
++ pfn += pfns;
++ nr_pages -= pfns;
++ }
++}
++
++#ifdef CONFIG_MEMORY_HOTPLUG
++
++/* Mark all memory sections within the pfn range as online */
++void online_mem_sections(unsigned long start_pfn, unsigned long end_pfn)
++{
++ unsigned long pfn;
++
++ for (pfn = start_pfn; pfn < end_pfn; pfn += PAGES_PER_SECTION) {
++ unsigned long section_nr = pfn_to_section_nr(pfn);
++ struct mem_section *ms = __nr_to_section(section_nr);
++
++ ms->section_mem_map |= SECTION_IS_ONLINE;
++ }
++}
++
++/* Mark all memory sections within the pfn range as offline */
++void offline_mem_sections(unsigned long start_pfn, unsigned long end_pfn)
++{
++ unsigned long pfn;
++
++ for (pfn = start_pfn; pfn < end_pfn; pfn += PAGES_PER_SECTION) {
++ unsigned long section_nr = pfn_to_section_nr(pfn);
++ struct mem_section *ms = __nr_to_section(section_nr);
++
++ ms->section_mem_map &= ~SECTION_IS_ONLINE;
++ }
++}
++
++static struct page * __meminit populate_section_memmap(unsigned long pfn,
++ unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
++ struct dev_pagemap *pgmap)
++{
++ 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));
++
++ return page;
++}
++
++static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
++ 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);
++
++ memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
++ vmemmap_free(start, end, altmap);
++}
++
++static void free_map_bootmem(struct page *memmap)
++{
++ unsigned long start = (unsigned long)memmap;
++ unsigned long end = (unsigned long)(memmap + PAGES_PER_SECTION);
++
++ memmap_boot_pages_add(-1L * (DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page),
++ PAGE_SIZE)));
++ vmemmap_free(start, end, NULL);
++}
++
++static int clear_subsection_map(unsigned long pfn, unsigned long nr_pages)
++{
++ DECLARE_BITMAP(map, SUBSECTIONS_PER_SECTION) = { 0 };
++ DECLARE_BITMAP(tmp, SUBSECTIONS_PER_SECTION) = { 0 };
++ struct mem_section *ms = __pfn_to_section(pfn);
++ unsigned long *subsection_map = ms->usage
++ ? &ms->usage->subsection_map[0] : NULL;
++
++ subsection_mask_set(map, pfn, nr_pages);
++ if (subsection_map)
++ bitmap_and(tmp, map, subsection_map, SUBSECTIONS_PER_SECTION);
++
++ if (WARN(!subsection_map || !bitmap_equal(tmp, map, SUBSECTIONS_PER_SECTION),
++ "section already deactivated (%#lx + %ld)\n",
++ pfn, nr_pages))
++ return -EINVAL;
++
++ bitmap_xor(subsection_map, map, subsection_map, SUBSECTIONS_PER_SECTION);
++ return 0;
++}
++
++static bool is_subsection_map_empty(struct mem_section *ms)
++{
++ return bitmap_empty(&ms->usage->subsection_map[0],
++ SUBSECTIONS_PER_SECTION);
++}
++
++static int fill_subsection_map(unsigned long pfn, unsigned long nr_pages)
++{
++ struct mem_section *ms = __pfn_to_section(pfn);
++ DECLARE_BITMAP(map, SUBSECTIONS_PER_SECTION) = { 0 };
++ unsigned long *subsection_map;
++ int rc = 0;
++
++ subsection_mask_set(map, pfn, nr_pages);
++
++ subsection_map = &ms->usage->subsection_map[0];
++
++ if (bitmap_empty(map, SUBSECTIONS_PER_SECTION))
++ rc = -EINVAL;
++ else if (bitmap_intersects(map, subsection_map, SUBSECTIONS_PER_SECTION))
++ rc = -EEXIST;
++ else
++ bitmap_or(subsection_map, map, subsection_map,
++ SUBSECTIONS_PER_SECTION);
++
++ return rc;
++}
++
++/*
++ * To deactivate a memory region, there are 3 cases to handle:
++ *
++ * 1. deactivation of a partial hot-added section:
++ * a) section was present at memory init.
++ * b) section was hot-added post memory init.
++ * 2. deactivation of a complete hot-added section.
++ * 3. deactivation of a complete section from memory init.
++ *
++ * For 1, when subsection_map does not empty we will not be freeing the
++ * 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 dev_pagemap *pgmap)
++{
++ struct mem_section *ms = __pfn_to_section(pfn);
++ bool section_is_early = early_section(ms);
++ struct page *memmap = NULL;
++ bool empty;
++
++ if (clear_subsection_map(pfn, nr_pages))
++ return;
++
++ empty = is_subsection_map_empty(ms);
++ if (empty) {
++ /*
++ * Mark the section invalid so that valid_section()
++ * return false. This prevents code from dereferencing
++ * ms->usage array.
++ */
++ ms->section_mem_map &= ~SECTION_HAS_MEM_MAP;
++
++ /*
++ * When removing an early section, the usage map is kept (as the
++ * usage maps of other sections fall into the same page). It
++ * will be re-used when re-adding the section - which is then no
++ * longer an early section. If the usage map is PageReserved, it
++ * was allocated during boot.
++ */
++ if (!PageReserved(virt_to_page(ms->usage))) {
++ kfree_rcu(ms->usage, rcu);
++ WRITE_ONCE(ms->usage, NULL);
++ }
++ memmap = pfn_to_page(SECTION_ALIGN_DOWN(pfn));
++ }
++
++ /*
++ * The memmap of early sections is always fully populated. See
++ * section_activate() and pfn_valid() .
++ */
++ if (!section_is_early)
++ depopulate_section_memmap(pfn, nr_pages, altmap, pgmap);
++ else if (memmap)
++ free_map_bootmem(memmap);
++
++ if (empty)
++ ms->section_mem_map = (unsigned long)NULL;
++}
++
++static struct page * __meminit section_activate(int nid, unsigned long pfn,
++ unsigned long nr_pages, struct vmem_altmap *altmap,
++ struct dev_pagemap *pgmap)
++{
++ struct mem_section *ms = __pfn_to_section(pfn);
++ struct mem_section_usage *usage = NULL;
++ struct page *memmap;
++ int rc;
++
++ if (!ms->usage) {
++ usage = kzalloc(mem_section_usage_size(), GFP_KERNEL);
++ if (!usage)
++ return ERR_PTR(-ENOMEM);
++ ms->usage = usage;
++ }
++
++ rc = fill_subsection_map(pfn, nr_pages);
++ if (rc) {
++ if (usage)
++ ms->usage = NULL;
++ kfree(usage);
++ return ERR_PTR(rc);
++ }
++
++ /*
++ * The early init code does not consider partially populated
++ * initial sections, it simply assumes that memory will never be
++ * referenced. If we hot-add memory into such a section then we
++ * do not need to populate the memmap and can simply reuse what
++ * is already there.
++ */
++ if (nr_pages < PAGES_PER_SECTION && early_section(ms))
++ return pfn_to_page(pfn);
++
++ memmap = populate_section_memmap(pfn, nr_pages, nid, altmap, pgmap);
++ if (!memmap) {
++ section_deactivate(pfn, nr_pages, altmap, pgmap);
++ return ERR_PTR(-ENOMEM);
++ }
++
++ return memmap;
++}
++
++/**
++ * sparse_add_section - add a memory section, or populate an existing one
++ * @nid: The node to add section on
++ * @start_pfn: start pfn of the memory range
++ * @nr_pages: number of pfns to add in the section
++ * @altmap: alternate pfns to allocate the memmap backing store
++ * @pgmap: alternate compound page geometry for devmap mappings
++ *
++ * This is only intended for hotplug.
++ *
++ * Note that only VMEMMAP supports sub-section aligned hotplug,
++ * the proper alignment and size are gated by check_pfn_span().
++ *
++ *
++ * Return:
++ * * 0 - On success.
++ * * -EEXIST - Section has been present.
++ * * -ENOMEM - Out of memory.
++ */
++int __meminit sparse_add_section(int nid, unsigned long start_pfn,
++ unsigned long nr_pages, struct vmem_altmap *altmap,
++ struct dev_pagemap *pgmap)
++{
++ unsigned long section_nr = pfn_to_section_nr(start_pfn);
++ struct mem_section *ms;
++ struct page *memmap;
++ int ret;
++
++ ret = sparse_index_init(section_nr, nid);
++ if (ret < 0)
++ return ret;
++
++ memmap = section_activate(nid, start_pfn, nr_pages, altmap, pgmap);
++ if (IS_ERR(memmap))
++ return PTR_ERR(memmap);
++
++ /*
++ * Poison uninitialized struct pages in order to catch invalid flags
++ * combinations.
++ */
++ page_init_poison(memmap, sizeof(struct page) * nr_pages);
++
++ ms = __nr_to_section(section_nr);
++ __section_mark_present(ms, section_nr);
++
++ /* Align memmap to section boundary in the subsection case */
++ if (section_nr_to_pfn(section_nr) != start_pfn)
++ memmap = pfn_to_page(section_nr_to_pfn(section_nr));
++ sparse_init_one_section(ms, section_nr, memmap, ms->usage, 0);
++
++ return 0;
++}
++
++void sparse_remove_section(unsigned long pfn, unsigned long nr_pages,
++ 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, pgmap);
++}
++#endif /* CONFIG_MEMORY_HOTPLUG */
+--- a/mm/sparse.c
++++ b/mm/sparse.c
+@@ -79,7 +79,7 @@ static noinline struct mem_section __ref
+ return section;
+ }
+
+-static int __meminit sparse_index_init(unsigned long section_nr, int nid)
++int __meminit sparse_index_init(unsigned long section_nr, int nid)
+ {
+ unsigned long root = SECTION_NR_TO_ROOT(section_nr);
+ struct mem_section *section;
+@@ -103,7 +103,7 @@ static int __meminit sparse_index_init(u
+ return 0;
+ }
+ #else /* !SPARSEMEM_EXTREME */
+-static inline int sparse_index_init(unsigned long section_nr, int nid)
++int sparse_index_init(unsigned long section_nr, int nid)
+ {
+ return 0;
+ }
+@@ -161,14 +161,6 @@ static void __meminit mminit_validate_me
+ * those loops early.
+ */
+ unsigned long __highest_present_section_nr;
+-static void __section_mark_present(struct mem_section *ms,
+- unsigned long section_nr)
+-{
+- if (section_nr > __highest_present_section_nr)
+- __highest_present_section_nr = section_nr;
+-
+- ms->section_mem_map |= SECTION_MARKED_PRESENT;
+-}
+
+ #define for_each_present_section_nr(start, section_nr) \
+ for (section_nr = next_present_section_nr(start-1); \
+@@ -180,44 +172,6 @@ static inline unsigned long first_presen
+ return next_present_section_nr(-1);
+ }
+
+-#ifdef CONFIG_SPARSEMEM_VMEMMAP
+-static void subsection_mask_set(unsigned long *map, unsigned long pfn,
+- unsigned long nr_pages)
+-{
+- int idx = subsection_map_index(pfn);
+- int end = subsection_map_index(pfn + nr_pages - 1);
+-
+- bitmap_set(map, idx, end - idx + 1);
+-}
+-
+-void __init subsection_map_init(unsigned long pfn, unsigned long nr_pages)
+-{
+- int end_sec_nr = pfn_to_section_nr(pfn + nr_pages - 1);
+- unsigned long nr, start_sec_nr = pfn_to_section_nr(pfn);
+-
+- for (nr = start_sec_nr; nr <= end_sec_nr; nr++) {
+- struct mem_section *ms;
+- unsigned long pfns;
+-
+- pfns = min(nr_pages, PAGES_PER_SECTION
+- - (pfn & ~PAGE_SECTION_MASK));
+- ms = __nr_to_section(nr);
+- subsection_mask_set(ms->usage->subsection_map, pfn, pfns);
+-
+- pr_debug("%s: sec: %lu pfns: %lu set(%d, %d)\n", __func__, nr,
+- pfns, subsection_map_index(pfn),
+- subsection_map_index(pfn + pfns - 1));
+-
+- pfn += pfns;
+- nr_pages -= pfns;
+- }
+-}
+-#else
+-void __init subsection_map_init(unsigned long pfn, unsigned long nr_pages)
+-{
+-}
+-#endif
+-
+ /* Record a memory area against a node. */
+ static void __init memory_present(int nid, unsigned long start, unsigned long end)
+ {
+@@ -268,20 +222,6 @@ static void __init memblocks_present(voi
+ memory_present(nid, start, end);
+ }
+
+-/*
+- * Subtle, we encode the real pfn into the mem_map such that
+- * the identity pfn - section_mem_map will return the actual
+- * physical page frame number.
+- */
+-static unsigned long sparse_encode_mem_map(struct page *mem_map, unsigned long pnum)
+-{
+- unsigned long coded_mem_map =
+- (unsigned long)(mem_map - (section_nr_to_pfn(pnum)));
+- BUILD_BUG_ON(SECTION_MAP_LAST_BIT > PFN_SECTION_SHIFT);
+- BUG_ON(coded_mem_map & ~SECTION_MAP_MASK);
+- return coded_mem_map;
+-}
+-
+ #ifdef CONFIG_MEMORY_HOTPLUG
+ /*
+ * Decode mem_map from the coded memmap
+@@ -294,16 +234,6 @@ struct page *sparse_decode_mem_map(unsig
+ }
+ #endif /* CONFIG_MEMORY_HOTPLUG */
+
+-static void __meminit sparse_init_one_section(struct mem_section *ms,
+- unsigned long pnum, struct page *mem_map,
+- struct mem_section_usage *usage, unsigned long flags)
+-{
+- ms->section_mem_map &= ~SECTION_MAP_MASK;
+- ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum)
+- | SECTION_HAS_MEM_MAP | flags;
+- ms->usage = usage;
+-}
+-
+ static unsigned long usemap_size(void)
+ {
+ return BITS_TO_LONGS(SECTION_BLOCKFLAGS_BITS) * sizeof(unsigned long);
+@@ -587,6 +517,7 @@ void __init sparse_init(void)
+ vmemmap_populate_print_last();
+ }
+
++#ifndef CONFIG_SPARSEMEM_VMEMMAP
+ #ifdef CONFIG_MEMORY_HOTPLUG
+
+ /* Mark all memory sections within the pfn range as online */
+@@ -930,7 +861,6 @@ int __meminit sparse_add_section(int nid
+ page_init_poison(memmap, sizeof(struct page) * nr_pages);
+
+ ms = __nr_to_section(section_nr);
+- set_section_nid(section_nr, nid);
+ __section_mark_present(ms, section_nr);
+
+ /* Align memmap to section boundary in the subsection case */
+@@ -942,7 +872,8 @@ 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);
+
+@@ -952,3 +883,4 @@ void sparse_remove_section(unsigned long
+ section_deactivate(pfn, nr_pages, altmap);
+ }
+ #endif /* CONFIG_MEMORY_HOTPLUG */
++#endif /* !CONFIG_SPARSEMEM_VMEMMAP */
--- /dev/null
+From stable+bounces-289229-greg=kroah.com@vger.kernel.org Sun Jul 26 14:00:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:00:46 -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: <20260726120046.4138231-2-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
+@@ -539,6 +539,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)
+@@ -546,7 +571,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;
+ }
+@@ -557,7 +582,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);
+ }
+
+@@ -565,9 +590,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-288842-greg=kroah.com@vger.kernel.org Fri Jul 24 13:23:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 07:00:55 -0400
+Subject: mm/sparse-vmemmap: fix vmemmap accounting underflow
+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: <20260724110055.3984461-1-sashal@kernel.org>
+
+From: Muchun Song <songmuchun@bytedance.com>
+
+[ Upstream commit c373f7f98e6ad591c85d40548cf8b6443be69311 ]
+
+Patch series "mm: Fix vmemmap optimization accounting and initialization",
+v8.
+
+The series fixes several bugs in vmemmap optimization, mainly around
+incorrect page accounting and memmap initialization in DAX and memory
+hotplug paths. It also fixes pageblock migratetype initialization and
+struct page initialization for ZONE_DEVICE compound pages.
+
+Patches 1-4 fix vmemmap accounting issues. Patch 1 fixes an accounting
+underflow in the section activation failure path by moving vmemmap page
+accounting into the lower-level allocation and freeing helpers. Patch 2
+fixes incorrect altmap passing in the memory hotplug error path. Patch 3
+passes pgmap through memory deactivation paths so the teardown side can
+determine whether vmemmap optimization was in effect. Patch 4 uses that
+information to account the optimized DAX vmemmap size correctly.
+
+Patches 5-6 fix initialization issues in mm/mm_init. One makes sure all
+pageblocks in ZONE_DEVICE compound pages get their migratetype
+initialized. The other fixes a case where DAX memory hotplug reuses an
+unoptimized early-section memmap while compound_nr_pages() still assumes
+vmemmap optimization, leaving tail struct pages uninitialized.
+
+This patch (of 6):
+
+In section_activate(), if populate_section_memmap() fails, the error
+handling path calls section_deactivate() to roll back the state. This
+causes a vmemmap accounting imbalance.
+
+Since commit c3576889d87b ("mm: fix accounting of memmap pages"), memmap
+pages are accounted for only after populate_section_memmap() succeeds.
+However, the failure path unconditionally calls section_deactivate(),
+which decreases the vmemmap count. Consequently, a failure in
+populate_section_memmap() leads to an accounting underflow, incorrectly
+reducing the system's tracked vmemmap usage.
+
+Fix this more thoroughly by moving all accounting calls into the lower
+level functions that actually perform the vmemmap allocation and freeing:
+
+ - populate_section_memmap() accounts for newly allocated vmemmap pages -
+depopulate_section_memmap() unaccounts when vmemmap is freed
+
+This ensures proper accounting in all code paths, including error handling
+and early section cases.
+
+Link: https://lore.kernel.org/20260428081855.1249045-1-songmuchun@bytedance.com
+Link: https://lore.kernel.org/20260428081855.1249045-2-songmuchun@bytedance.com
+Fixes: c3576889d87b ("mm: fix accounting of memmap pages")
+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.c | 32 ++++++++++++++++++++++----------
+ 1 file changed, 22 insertions(+), 10 deletions(-)
+
+--- a/mm/sparse.c
++++ b/mm/sparse.c
+@@ -633,7 +633,12 @@ static struct page * __meminit populate_
+ unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
+ struct dev_pagemap *pgmap)
+ {
+- return __populate_section_memmap(pfn, nr_pages, nid, altmap, pgmap);
++ 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));
++
++ return page;
+ }
+
+ static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
+@@ -642,13 +647,17 @@ 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)));
+ vmemmap_free(start, end, altmap);
+ }
++
+ static void free_map_bootmem(struct page *memmap)
+ {
+ unsigned long start = (unsigned long)memmap;
+ unsigned long end = (unsigned long)(memmap + PAGES_PER_SECTION);
+
++ memmap_boot_pages_add(-1L * (DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page),
++ PAGE_SIZE)));
+ vmemmap_free(start, end, NULL);
+ }
+
+@@ -705,13 +714,18 @@ static struct page * __meminit populate_
+ unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
+ struct dev_pagemap *pgmap)
+ {
+- return kvmalloc_node(array_size(sizeof(struct page),
+- PAGES_PER_SECTION), GFP_KERNEL, nid);
++ struct page *page = kvmalloc_node(array_size(sizeof(struct page),
++ PAGES_PER_SECTION), GFP_KERNEL, nid);
++
++ memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE));
++
++ return page;
+ }
+
+ static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
+ struct vmem_altmap *altmap)
+ {
++ memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
+ kvfree(pfn_to_page(pfn));
+ }
+
+@@ -724,6 +738,9 @@ static void free_map_bootmem(struct page
+ nr_pages = PAGE_ALIGN(PAGES_PER_SECTION * sizeof(struct page))
+ >> PAGE_SHIFT;
+
++ memmap_boot_pages_add(-1L * (DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page),
++ PAGE_SIZE)));
++
+ for (i = 0; i < nr_pages; i++, page++) {
+ magic = page->index;
+
+@@ -817,14 +834,10 @@ static void section_deactivate(unsigned
+ * The memmap of early sections is always fully populated. See
+ * section_activate() and pfn_valid() .
+ */
+- if (!section_is_early) {
+- memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
++ if (!section_is_early)
+ depopulate_section_memmap(pfn, nr_pages, altmap);
+- } else if (memmap) {
+- memmap_boot_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page),
+- PAGE_SIZE)));
++ else if (memmap)
+ free_map_bootmem(memmap);
+- }
+
+ if (empty)
+ ms->section_mem_map = (unsigned long)NULL;
+@@ -869,7 +882,6 @@ static struct page * __meminit section_a
+ section_deactivate(pfn, nr_pages, altmap);
+ return ERR_PTR(-ENOMEM);
+ }
+- memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE));
+
+ return memmap;
+ }
--- /dev/null
+From stable+bounces-289742-greg=kroah.com@vger.kernel.org Tue Jul 28 01:37:22 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:42 -0400
+Subject: mmc: vub300: fix use-after-free on disconnect
+To: stable@vger.kernel.org
+Cc: Johan Hovold <johan@kernel.org>, Binbin Zhou <zhoubinbin@loongson.cn>, Ulf Hansson <ulf.hansson@linaro.org>
+Message-ID: <20260727233445.2426041-1-sashal@kernel.org>
+
+From: Johan Hovold <johan@kernel.org>
+
+The vub300 driver maintains an explicit reference count for the
+controller and its driver data and the last reference can in theory be
+dropped after the driver has been unbound.
+
+This specifically means that the controller allocation must not be
+device managed as that can lead to use-after-free.
+
+Note that the lifetime is currently also incorrectly tied the parent USB
+device rather than interface, which can lead to memory leaks if the
+driver is unbound without its device being physically disconnected (e.g.
+on probe deferral).
+
+Fix both issues by reverting to non-managed allocation of the controller.
+
+Fixes: dcfdd698dc52 ("mmc: vub300: Use devm_mmc_alloc_host() helper")
+Cc: stable@vger.kernel.org # 6.17+
+Cc: Binbin Zhou <zhoubinbin@loongson.cn>
+Signed-off-by: Johan Hovold <johan@kernel.org>
+Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
+(cherry picked from commit 8f4d20a710225ec7a565f6a0459862d3b1f32330)
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mmc/host/vub300.c | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+--- a/drivers/mmc/host/vub300.c
++++ b/drivers/mmc/host/vub300.c
+@@ -2279,7 +2279,7 @@ static int vub300_probe(struct usb_inter
+ dev_err(&vub300->udev->dev,
+ "Could not find two sets of bulk-in/out endpoint pairs\n");
+ retval = -EINVAL;
+- goto error5;
++ goto err_free_host;
+ }
+ retval =
+ usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+@@ -2288,14 +2288,14 @@ static int vub300_probe(struct usb_inter
+ 0x0000, 0x0000, &vub300->hc_info,
+ sizeof(vub300->hc_info), 1000);
+ if (retval < 0)
+- goto error5;
++ goto err_free_host;
+ retval =
+ usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0),
+ SET_ROM_WAIT_STATES,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ firmware_rom_wait_states, 0x0000, NULL, 0, 1000);
+ if (retval < 0)
+- goto error5;
++ goto err_free_host;
+ dev_info(&vub300->udev->dev,
+ "operating_mode = %s %s %d MHz %s %d byte USB packets\n",
+ (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+@@ -2310,7 +2310,7 @@ static int vub300_probe(struct usb_inter
+ 0x0000, 0x0000, &vub300->system_port_status,
+ sizeof(vub300->system_port_status), 1000);
+ if (retval < 0) {
+- goto error5;
++ goto err_free_host;
+ } else if (sizeof(vub300->system_port_status) == retval) {
+ vub300->card_present =
+ (0x0001 & vub300->system_port_status.port_flags) ? 1 : 0;
+@@ -2318,7 +2318,7 @@ static int vub300_probe(struct usb_inter
+ (0x0010 & vub300->system_port_status.port_flags) ? 1 : 0;
+ } else {
+ retval = -EINVAL;
+- goto error5;
++ goto err_free_host;
+ }
+ usb_set_intfdata(interface, vub300);
+ INIT_DELAYED_WORK(&vub300->pollwork, vub300_pollwork_thread);
+@@ -2348,7 +2348,7 @@ static int vub300_probe(struct usb_inter
+ return 0;
+ error6:
+ del_timer_sync(&vub300->inactivity_timer);
+-error5:
++err_free_host:
+ mmc_free_host(mmc);
+ /*
+ * and hence also frees vub300
--- /dev/null
+From stable+bounces-289744-greg=kroah.com@vger.kernel.org Tue Jul 28 01:38:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:44 -0400
+Subject: mmc: vub300: fix use-after-free on probe failure
+To: stable@vger.kernel.org
+Cc: Guangshuo Li <lgs201920130244@gmail.com>, Johan Hovold <johan@kernel.org>, Ulf Hansson <ulfh@kernel.org>
+Message-ID: <20260727233445.2426041-3-sashal@kernel.org>
+
+From: Guangshuo Li <lgs201920130244@gmail.com>
+
+The vub300 driver lifetime-manages its controller state using
+vub300->kref, with vub300_delete() freeing the mmc host when the last
+reference is dropped. The probe error path after the inactivity timer has
+been armed still bypasses that lifetime rule, however, and falls through
+to mmc_free_host() directly if mmc_add_host() fails.
+
+The race window is between arming the inactivity timer and reaching the
+probe error unwind after mmc_add_host() fails:
+
+ probe thread timer/workqueue
+ ------------ ---------------
+ kref_init(&vub300->kref) ref = 1
+ kref_get(&vub300->kref) ref = 2, timer ref
+ add_timer(inactivity_timer) fires after one second
+ |
+ | race window
+ |<---------------------------------------------------->
+ |
+ mmc_add_host(mmc)
+ inactivity timer fires
+ vub300_queue_dead_work()
+ kref_get() ref = 3
+ queue_work(deadwork)
+ mmc_add_host() fails
+ timer_delete_sync()
+ mmc_free_host(mmc)
+ frees vub300
+ deadwork runs
+ use-after-free
+
+The inactivity timeout is one second, so this would require
+mmc_add_host() to both fail and take more than one second to do so. This
+is unlikely to happen in practice, but the error path is still wrong.
+
+timer_delete_sync() only waits for the timer callback itself. It does
+not flush deadwork that the callback may already have queued. As a
+result, queued deadwork can still hold a kref while the probe error path
+directly frees the backing mmc host, including the vub300 storage.
+
+Fix this by using the same lifetime mechanism as disconnect. Clear
+vub300->interface so that the timer callback and any queued deadwork
+return early and drop their references, then drop the initial probe
+reference and return without falling through to err_free_host.
+
+Fixes: 0613ad2401f8 ("mmc: vub300: fix return value check of mmc_add_host()")
+Signed-off-by: Guangshuo Li <lgs201920130244@gmail.com>
+Reviewed-by: Johan Hovold <johan@kernel.org>
+Cc: stable@vger.kernel.org
+Signed-off-by: Ulf Hansson <ulfh@kernel.org>
+(cherry picked from commit a3b5f242997a3be7404112fd48784881560aea57)
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mmc/host/vub300.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+--- a/drivers/mmc/host/vub300.c
++++ b/drivers/mmc/host/vub300.c
+@@ -2343,12 +2343,16 @@ static int vub300_probe(struct usb_inter
+ interface_to_InterfaceNumber(interface));
+ retval = mmc_add_host(mmc);
+ if (retval)
+- goto err_delete_timer;
++ goto err_stop_io;
+
+ return 0;
+
+-err_delete_timer:
+- timer_delete_sync(&vub300->inactivity_timer);
++err_stop_io:
++ vub300->interface = NULL;
++ kref_put(&vub300->kref, vub300_delete);
++
++ return retval;
++
+ err_free_host:
+ mmc_free_host(mmc);
+ /*
--- /dev/null
+From stable+bounces-289743-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:53 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:43 -0400
+Subject: mmc: vub300: rename probe error labels
+To: stable@vger.kernel.org
+Cc: Johan Hovold <johan@kernel.org>, Ulf Hansson <ulf.hansson@linaro.org>
+Message-ID: <20260727233445.2426041-2-sashal@kernel.org>
+
+From: Johan Hovold <johan@kernel.org>
+
+Error labels should be named after what they do.
+
+Rename the probe error labels.
+
+Signed-off-by: Johan Hovold <johan@kernel.org>
+Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
+(cherry picked from commit 5b8b35d6f4fa758dd5e8ae18526ea1c73f6787e0)
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mmc/host/vub300.c | 20 +++++++++++---------
+ 1 file changed, 11 insertions(+), 9 deletions(-)
+
+--- a/drivers/mmc/host/vub300.c
++++ b/drivers/mmc/host/vub300.c
+@@ -2114,19 +2114,19 @@ static int vub300_probe(struct usb_inter
+ command_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!command_out_urb) {
+ retval = -ENOMEM;
+- goto error0;
++ goto err_put_udev;
+ }
+ command_res_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!command_res_urb) {
+ retval = -ENOMEM;
+- goto error1;
++ goto err_free_out_urb;
+ }
+ /* this also allocates memory for our VUB300 mmc host device */
+ mmc = mmc_alloc_host(sizeof(struct vub300_mmc_host), &udev->dev);
+ if (!mmc) {
+ retval = -ENOMEM;
+ dev_err(&udev->dev, "not enough memory for the mmc_host\n");
+- goto error4;
++ goto err_free_res_urb;
+ }
+ /* MMC core transfer sizes tunable parameters */
+ mmc->caps = 0;
+@@ -2343,23 +2343,25 @@ static int vub300_probe(struct usb_inter
+ interface_to_InterfaceNumber(interface));
+ retval = mmc_add_host(mmc);
+ if (retval)
+- goto error6;
++ goto err_delete_timer;
+
+ return 0;
+-error6:
+- del_timer_sync(&vub300->inactivity_timer);
++
++err_delete_timer:
++ timer_delete_sync(&vub300->inactivity_timer);
+ err_free_host:
+ mmc_free_host(mmc);
+ /*
+ * and hence also frees vub300
+ * which is contained at the end of struct mmc
+ */
+-error4:
++err_free_res_urb:
+ usb_free_urb(command_res_urb);
+-error1:
++err_free_out_urb:
+ usb_free_urb(command_out_urb);
+-error0:
++err_put_udev:
+ usb_put_dev(udev);
++
+ return retval;
+ }
+
--- /dev/null
+From stable+bounces-289091-greg=kroah.com@vger.kernel.org Sat Jul 25 13:02:56 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 07:02:46 -0400
+Subject: mtd: maps: vmu-flash: fix fault in unaligned fixup
+To: stable@vger.kernel.org
+Cc: Florian Fuchs <fuchsfl@gmail.com>, Miquel Raynal <miquel.raynal@bootlin.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725110246.1858961-1-sashal@kernel.org>
+
+From: Florian Fuchs <fuchsfl@gmail.com>
+
+[ Upstream commit 79d1661502c6e4b6f626185cef72cf2fa78116e1 ]
+
+Use kzalloc_obj() / kzalloc_objs() to allocate the memcard structs,
+instead of kmalloc_obj() / kmalloc_objs() to prevent access to
+uninitialized data.
+
+Fixes runtime error: Fault in unaligned fixup: 0000 [#1] at
+mtd_get_fact_prot_info.
+
+Fixes: 47a72688fae7 ("mtd: flash mapping support for Dreamcast VMU.")
+Cc: stable@vger.kernel.org
+Signed-off-by: Florian Fuchs <fuchsfl@gmail.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mtd/maps/vmu-flash.c | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/mtd/maps/vmu-flash.c
++++ b/drivers/mtd/maps/vmu-flash.c
+@@ -610,7 +610,7 @@ static int vmu_connect(struct maple_devi
+
+ basic_flash_data = be32_to_cpu(mdev->devinfo.function_data[c - 1]);
+
+- card = kmalloc(sizeof(struct memcard), GFP_KERNEL);
++ card = kzalloc_obj(struct memcard);
+ if (!card) {
+ error = -ENOMEM;
+ goto fail_nomem;
+@@ -628,15 +628,13 @@ static int vmu_connect(struct maple_devi
+ * Not sure there are actually any multi-partition devices in the
+ * real world, but the hardware supports them, so, so will we
+ */
+- card->parts = kmalloc_array(card->partitions, sizeof(struct vmupart),
+- GFP_KERNEL);
++ card->parts = kzalloc_objs(struct vmupart, card->partitions);
+ if (!card->parts) {
+ error = -ENOMEM;
+ goto fail_partitions;
+ }
+
+- card->mtd = kmalloc_array(card->partitions, sizeof(struct mtd_info),
+- GFP_KERNEL);
++ card->mtd = kzalloc_objs(struct mtd_info, card->partitions);
+ if (!card->mtd) {
+ error = -ENOMEM;
+ goto fail_mtd_info;
--- /dev/null
+From stable+bounces-289987-greg=kroah.com@vger.kernel.org Tue Jul 28 15:25:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 09:19:53 -0400
+Subject: net: ip6_tunnel: require CAP_NET_ADMIN in the device netns for changelink
+To: stable@vger.kernel.org
+Cc: Maoyi Xie <maoyixie.tju@gmail.com>, Xiao Liang <shaw.leon@gmail.com>, Kuniyuki Iwashima <kuniyu@google.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728131953.3953668-1-sashal@kernel.org>
+
+From: Maoyi Xie <maoyixie.tju@gmail.com>
+
+[ Upstream commit 2496fa0b7d180b3ad356b514e7ff93bb14e6140a ]
+
+ip6_tnl_changelink() operates on at most two netns, dev_net(dev) and the
+tunnel link netns t->net. They differ once the device is created in or
+moved to a netns other than the one the request runs in. The rtnl
+changelink path checks CAP_NET_ADMIN only against dev_net(dev), so a
+caller privileged there but not in t->net can rewrite a tunnel that
+lives in t->net.
+
+Gate ip6_tnl_changelink() on rtnl_dev_link_net_capable() at its top,
+before any attribute is parsed.
+
+Reported-by: Xiao Liang <shaw.leon@gmail.com>
+Closes: https://lore.kernel.org/netdev/CABAhCOSzP1vaThGV35_VnsRCb=87_CPjPVsTHbq905k8A+BuUg@mail.gmail.com/
+Fixes: 0bd8762824e7 ("ip6tnl: add x-netns support")
+Cc: stable@vger.kernel.org
+Signed-off-by: Maoyi Xie <maoyixie.tju@gmail.com>
+Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
+Link: https://patch.msgid.link/20260612085941.3158249-5-maoyixie.tju@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/ipv6/ip6_tunnel.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/net/ipv6/ip6_tunnel.c
++++ b/net/ipv6/ip6_tunnel.c
+@@ -2053,6 +2053,9 @@ static int ip6_tnl_changelink(struct net
+ struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id);
+ struct ip_tunnel_encap ipencap;
+
++ if (!rtnl_dev_link_net_capable(dev, net))
++ return -EPERM;
++
+ if (dev == ip6n->fb_tnl_dev)
+ return -EINVAL;
+
--- /dev/null
+From stable+bounces-290233-greg=kroah.com@vger.kernel.org Wed Jul 29 04:47:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 22:40:04 -0400
+Subject: net: ipa: fix SMEM state handle leaks in SMP2P init
+To: stable@vger.kernel.org
+Cc: Haoxiang Li <haoxiang_li2024@163.com>, Larysa Zaremba <larysa.zaremba@intel.com>, Alex Elder <elder@riscstar.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729024004.1127220-1-sashal@kernel.org>
+
+From: Haoxiang Li <haoxiang_li2024@163.com>
+
+[ Upstream commit 96ca1e658ae459276292bd6d971ab5d8c7e0379a ]
+
+ipa_smp2p_init() acquires two Qualcomm SMEM state handles with
+qcom_smem_state_get(). However, neither the init error paths
+nor ipa_smp2p_exit() release them.
+
+Release both handles with qcom_smem_state_put() in the init
+error paths and in ipa_smp2p_exit().
+
+Fixes: 530f9216a953 ("soc: qcom: ipa: AP/modem communications")
+Cc: stable@vger.kernel.org
+Signed-off-by: Haoxiang Li <haoxiang_li2024@163.com>
+Reviewed-by: Larysa Zaremba <larysa.zaremba@intel.com>
+Reviewed-by: Alex Elder <elder@riscstar.com>
+Link: https://patch.msgid.link/20260624065955.2822765-1-haoxiang_li2024@163.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+[ kzalloc_obj() context line kept as kzalloc(sizeof(*smp2p), GFP_KERNEL) since ipa_smp2p.c was not yet converted in this tree ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ipa/ipa_smp2p.c | 30 ++++++++++++++++++++++--------
+ 1 file changed, 22 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/ipa/ipa_smp2p.c
++++ b/drivers/net/ipa/ipa_smp2p.c
+@@ -234,19 +234,27 @@ ipa_smp2p_init(struct ipa *ipa, struct p
+ &valid_bit);
+ if (IS_ERR(valid_state))
+ return PTR_ERR(valid_state);
+- if (valid_bit >= 32) /* BITS_PER_U32 */
+- return -EINVAL;
++ if (valid_bit >= 32) { /* BITS_PER_U32 */
++ ret = -EINVAL;
++ goto err_valid_state_put;
++ }
+
+ enabled_state = qcom_smem_state_get(dev, "ipa-clock-enabled",
+ &enabled_bit);
+- if (IS_ERR(enabled_state))
+- return PTR_ERR(enabled_state);
+- if (enabled_bit >= 32) /* BITS_PER_U32 */
+- return -EINVAL;
++ if (IS_ERR(enabled_state)) {
++ ret = PTR_ERR(enabled_state);
++ goto err_valid_state_put;
++ }
++ if (enabled_bit >= 32) { /* BITS_PER_U32 */
++ ret = -EINVAL;
++ goto err_enabled_state_put;
++ }
+
+ smp2p = kzalloc(sizeof(*smp2p), GFP_KERNEL);
+- if (!smp2p)
+- return -ENOMEM;
++ if (!smp2p) {
++ ret = -ENOMEM;
++ goto err_enabled_state_put;
++ }
+
+ smp2p->ipa = ipa;
+
+@@ -291,6 +299,10 @@ err_null_smp2p:
+ ipa->smp2p = NULL;
+ mutex_destroy(&smp2p->mutex);
+ kfree(smp2p);
++err_enabled_state_put:
++ qcom_smem_state_put(enabled_state);
++err_valid_state_put:
++ qcom_smem_state_put(valid_state);
+
+ return ret;
+ }
+@@ -307,6 +319,8 @@ void ipa_smp2p_exit(struct ipa *ipa)
+ ipa_smp2p_power_release(ipa);
+ ipa->smp2p = NULL;
+ mutex_destroy(&smp2p->mutex);
++ qcom_smem_state_put(smp2p->enabled_state);
++ qcom_smem_state_put(smp2p->valid_state);
+ kfree(smp2p);
+ }
+
--- /dev/null
+From stable+bounces-290228-greg=kroah.com@vger.kernel.org Wed Jul 29 04:30:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 22:30:02 -0400
+Subject: net: macb: drop in-flight Tx SKBs on close
+To: stable@vger.kernel.org
+Cc: "Théo Lebrun" <theo.lebrun@bootlin.com>, "Nicolai Buchwitz" <nb@tipi-net.de>, "Paolo Abeni" <pabeni@redhat.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260729023002.1113287-1-sashal@kernel.org>
+
+From: Théo Lebrun <theo.lebrun@bootlin.com>
+
+[ Upstream commit 27f575836cfebbf872dec020428742b10650a955 ]
+
+The MACB driver has since forever leaked the outgoing SKBs that
+have not yet been marked as completed. They live in queue->tx_skb
+which gets freed without remorse nor checking.
+
+macb_free_consistent() gets called in a few codepaths, but only close will
+trigger the added expressions. In macb_open() and macb_alloc_consistent()
+failure cases, queues' tx_skb just got allocated and are empty.
+
+Fixes: 89e5785fc8a6 ("[PATCH] Atmel MACB ethernet driver")
+Cc: stable@vger.kernel.org
+Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
+Signed-off-by: Théo Lebrun <theo.lebrun@bootlin.com>
+Link: https://patch.msgid.link/20260702-macb-drop-tx-v4-1-1c833eebdbc8@bootlin.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+[ adjusted context to free each queue's Tx/Rx rings individually since 6.12 lacks the single-dma_alloc_coherent refactor ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/cadence/macb_main.c | 22 ++++++++++++++++++++--
+ 1 file changed, 20 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/cadence/macb_main.c
++++ b/drivers/net/ethernet/cadence/macb_main.c
+@@ -2582,8 +2582,26 @@ static void macb_free_consistent(struct
+ bp->macbgem_ops.mog_free_rx_buffers(bp);
+
+ for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+- kfree(queue->tx_skb);
+- queue->tx_skb = NULL;
++ if (queue->tx_skb) {
++ unsigned int dropped = 0, tail;
++
++ for (tail = queue->tx_tail; tail != queue->tx_head;
++ tail++) {
++ if (macb_tx_skb(queue, tail)->skb)
++ dropped++;
++ macb_tx_unmap(bp, macb_tx_skb(queue, tail), 0);
++ }
++
++ queue->stats.tx_dropped += dropped;
++ bp->dev->stats.tx_dropped += dropped;
++
++ kfree(queue->tx_skb);
++ queue->tx_skb = NULL;
++ }
++
++ queue->tx_head = 0;
++ queue->tx_tail = 0;
++
+ if (queue->tx_ring) {
+ size = TX_RING_BYTES(bp) + bp->tx_bd_rd_prefetch;
+ dma_free_coherent(&bp->pdev->dev, size,
--- /dev/null
+From stable+bounces-289819-greg=kroah.com@vger.kernel.org Tue Jul 28 05:02:11 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 23:02:04 -0400
+Subject: net: mana: Validate the packet length reported by the NIC
+To: stable@vger.kernel.org
+Cc: Dexuan Cui <decui@microsoft.com>, Haiyang Zhang <haiyangz@microsoft.com>, Paolo Abeni <pabeni@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728030204.3123559-1-sashal@kernel.org>
+
+From: Dexuan Cui <decui@microsoft.com>
+
+[ Upstream commit 2e2a83b4998af4384e677d3b2ac08565274279bf ]
+
+Validate the packet length reported in the RX CQE before passing it
+to skb processing. The CQE is supplied by the NIC device and should
+not be blindly trusted.
+
+Cc: stable@vger.kernel.org
+Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com>
+Signed-off-by: Dexuan Cui <decui@microsoft.com>
+Fixes: ca9c54d2d6a5 ("net: mana: Add a driver for Microsoft Azure Network Adapter (MANA)")
+Link: https://patch.msgid.link/20260702041237.617719-2-decui@microsoft.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/microsoft/mana/mana_en.c | 13 +++++++++++++
+ 1 file changed, 13 insertions(+)
+
+--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
++++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
+@@ -1724,6 +1724,19 @@ static void mana_process_rx_cqe(struct m
+ rxbuf_oob = &rxq->rx_oobs[curr];
+ WARN_ON_ONCE(rxbuf_oob->wqe_inf.wqe_size_in_bu != 1);
+
++ if (unlikely(pktlen > rxq->datasize)) {
++ /* Increase it even if mana_rx_skb() isn't called. */
++ rxq->rx_cq.work_done++;
++
++ ++ndev->stats.rx_dropped;
++ netdev_warn_once(ndev,
++ "Dropped oversized RX packet: len=%u, datasize=%u\n",
++ pktlen, rxq->datasize);
++
++ /* Reuse the RX buffer since rxbuf_oob is unchanged. */
++ goto drop;
++ }
++
+ mana_refill_rx_oob(dev, rxq, rxbuf_oob, &old_buf, &old_fp);
+
+ /* Unsuccessful refill will have old_buf == NULL.
--- /dev/null
+From stable+bounces-290182-greg=kroah.com@vger.kernel.org Wed Jul 29 00:18:36 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 18:17:55 -0400
+Subject: net/mlx5: HWS, fix matcher leak on resize target setup failure
+To: stable@vger.kernel.org
+Cc: Dawei Feng <dawei.feng@seu.edu.cn>, Yevgeny Kliteynik <kliteyn@nvidia.com>, Tariq Toukan <tariqt@nvidia.com>, Paolo Abeni <pabeni@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728221755.746200-2-sashal@kernel.org>
+
+From: Dawei Feng <dawei.feng@seu.edu.cn>
+
+[ Upstream commit bb09d0e64ecaa0aa0f7d1133a1696ed74dead295 ]
+
+hws_bwc_matcher_move() allocates a replacement matcher before setting it
+as the resize target. If mlx5hws_matcher_resize_set_target() fails, the
+replacement matcher is not attached anywhere and is leaked.
+
+Fix the leak by destroying the replacement matcher before returning from
+the resize-target failure path.
+
+The bug was first flagged by an experimental analysis tool we are
+developing for kernel memory-management bugs while analyzing
+v6.13-rc1. The tool is still under development and is not yet publicly
+available. Manual inspection confirms that the bug is still
+present in v7.1.1.
+
+An x86_64 allyesconfig build showed no new warnings. As we do not have a
+mlx5 HWS-capable device to test with, no runtime testing was able to be
+performed.
+
+Fixes: 2111bb970c78 ("net/mlx5: HWS, added backward-compatible API handling")
+Cc: stable@vger.kernel.org
+Signed-off-by: Dawei Feng <dawei.feng@seu.edu.cn>
+Reviewed-by: Yevgeny Kliteynik <kliteyn@nvidia.com>
+Acked-by: Tariq Toukan <tariqt@nvidia.com>
+Link: https://patch.msgid.link/20260629064049.3852759-1-dawei.feng@seu.edu.cn
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/mellanox/mlx5/core/steering/hws/mlx5hws_bwc.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/mlx5hws_bwc.c
++++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/mlx5hws_bwc.c
+@@ -225,6 +225,7 @@ static int hws_bwc_matcher_move(struct m
+ ret = mlx5hws_matcher_resize_set_target(old_matcher, new_matcher);
+ if (ret) {
+ mlx5hws_err(ctx, "Rehash error: failed setting resize target\n");
++ mlx5hws_matcher_destroy(new_matcher);
+ return ret;
+ }
+
--- /dev/null
+From stable+bounces-290181-greg=kroah.com@vger.kernel.org Wed Jul 29 00:23:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 18:17:54 -0400
+Subject: net/mlx5: HWS, Rearrange to prevent forward declaration
+To: stable@vger.kernel.org
+Cc: Yevgeny Kliteynik <kliteyn@nvidia.com>, Mark Bloch <mbloch@nvidia.com>, Simon Horman <horms@kernel.org>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728221755.746200-1-sashal@kernel.org>
+
+From: Yevgeny Kliteynik <kliteyn@nvidia.com>
+
+[ Upstream commit 29063103f864fb63f7f7c436e670c5804df1b55b ]
+
+As a preparation for the following patch that will add support
+for shrinking empty matchers, rearrange the code to prevent
+forward declaration of functions.
+
+Signed-off-by: Yevgeny Kliteynik <kliteyn@nvidia.com>
+Signed-off-by: Mark Bloch <mbloch@nvidia.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20250703185431.445571-9-mbloch@nvidia.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: bb09d0e64eca ("net/mlx5: HWS, fix matcher leak on resize target setup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/mellanox/mlx5/core/steering/hws/mlx5hws_bwc.c | 354 +++++-----
+ 1 file changed, 177 insertions(+), 177 deletions(-)
+
+--- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/mlx5hws_bwc.c
++++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/mlx5hws_bwc.c
+@@ -63,6 +63,183 @@ static void hws_bwc_matcher_init_attr(st
+ attr->max_num_of_at_attach = MLX5HWS_BWC_MATCHER_ATTACH_AT_NUM;
+ }
+
++static int hws_bwc_queue_poll(struct mlx5hws_context *ctx,
++ u16 queue_id,
++ u32 *pending_rules,
++ bool drain)
++{
++ struct mlx5hws_flow_op_result comp[MLX5HWS_BWC_MATCHER_REHASH_BURST_TH];
++ u16 burst_th = hws_bwc_get_burst_th(ctx, queue_id);
++ bool got_comp = *pending_rules >= burst_th;
++ bool queue_full;
++ int err = 0;
++ int ret;
++ int i;
++
++ /* Check if there are any completions at all */
++ if (!got_comp && !drain)
++ return 0;
++
++ queue_full = mlx5hws_send_engine_full(&ctx->send_queue[queue_id]);
++ while (queue_full || ((got_comp || drain) && *pending_rules)) {
++ ret = mlx5hws_send_queue_poll(ctx, queue_id, comp, burst_th);
++ if (unlikely(ret < 0)) {
++ mlx5hws_err(ctx, "BWC poll error: polling queue %d returned %d\n",
++ queue_id, ret);
++ return -EINVAL;
++ }
++
++ if (ret) {
++ (*pending_rules) -= ret;
++ for (i = 0; i < ret; i++) {
++ if (unlikely(comp[i].status != MLX5HWS_FLOW_OP_SUCCESS)) {
++ mlx5hws_err(ctx,
++ "BWC poll error: polling queue %d returned completion with error\n",
++ queue_id);
++ err = -EINVAL;
++ }
++ }
++ queue_full = false;
++ }
++
++ got_comp = !!ret;
++ }
++
++ return err;
++}
++
++static int hws_bwc_matcher_move_all_simple(struct mlx5hws_bwc_matcher *bwc_matcher)
++{
++ struct mlx5hws_context *ctx = bwc_matcher->matcher->tbl->ctx;
++ u16 bwc_queues = mlx5hws_bwc_queues(ctx);
++ struct mlx5hws_bwc_rule **bwc_rules;
++ struct mlx5hws_rule_attr rule_attr;
++ u32 *pending_rules;
++ int i, j, ret = 0;
++ bool all_done;
++ u16 burst_th;
++
++ mlx5hws_bwc_rule_fill_attr(bwc_matcher, 0, 0, &rule_attr);
++
++ pending_rules = kcalloc(bwc_queues, sizeof(*pending_rules), GFP_KERNEL);
++ if (!pending_rules)
++ return -ENOMEM;
++
++ bwc_rules = kcalloc(bwc_queues, sizeof(*bwc_rules), GFP_KERNEL);
++ if (!bwc_rules) {
++ ret = -ENOMEM;
++ goto free_pending_rules;
++ }
++
++ for (i = 0; i < bwc_queues; i++) {
++ if (list_empty(&bwc_matcher->rules[i]))
++ bwc_rules[i] = NULL;
++ else
++ bwc_rules[i] = list_first_entry(&bwc_matcher->rules[i],
++ struct mlx5hws_bwc_rule,
++ list_node);
++ }
++
++ do {
++ all_done = true;
++
++ for (i = 0; i < bwc_queues; i++) {
++ rule_attr.queue_id = mlx5hws_bwc_get_queue_id(ctx, i);
++ burst_th = hws_bwc_get_burst_th(ctx, rule_attr.queue_id);
++
++ for (j = 0; j < burst_th && bwc_rules[i]; j++) {
++ rule_attr.burst = !!((j + 1) % burst_th);
++ ret = mlx5hws_matcher_resize_rule_move(bwc_matcher->matcher,
++ bwc_rules[i]->rule,
++ &rule_attr);
++ if (unlikely(ret)) {
++ mlx5hws_err(ctx,
++ "Moving BWC rule failed during rehash (%d)\n",
++ ret);
++ goto free_bwc_rules;
++ }
++
++ all_done = false;
++ pending_rules[i]++;
++ bwc_rules[i] = list_is_last(&bwc_rules[i]->list_node,
++ &bwc_matcher->rules[i]) ?
++ NULL : list_next_entry(bwc_rules[i], list_node);
++
++ ret = hws_bwc_queue_poll(ctx, rule_attr.queue_id,
++ &pending_rules[i], false);
++ if (unlikely(ret))
++ goto free_bwc_rules;
++ }
++ }
++ } while (!all_done);
++
++ /* drain all the bwc queues */
++ for (i = 0; i < bwc_queues; i++) {
++ if (pending_rules[i]) {
++ u16 queue_id = mlx5hws_bwc_get_queue_id(ctx, i);
++
++ mlx5hws_send_engine_flush_queue(&ctx->send_queue[queue_id]);
++ ret = hws_bwc_queue_poll(ctx, queue_id,
++ &pending_rules[i], true);
++ if (unlikely(ret))
++ goto free_bwc_rules;
++ }
++ }
++
++free_bwc_rules:
++ kfree(bwc_rules);
++free_pending_rules:
++ kfree(pending_rules);
++
++ return ret;
++}
++
++static int hws_bwc_matcher_move_all(struct mlx5hws_bwc_matcher *bwc_matcher)
++{
++ return hws_bwc_matcher_move_all_simple(bwc_matcher);
++}
++
++static int hws_bwc_matcher_move(struct mlx5hws_bwc_matcher *bwc_matcher)
++{
++ struct mlx5hws_context *ctx = bwc_matcher->matcher->tbl->ctx;
++ struct mlx5hws_matcher_attr matcher_attr = {0};
++ struct mlx5hws_matcher *old_matcher;
++ struct mlx5hws_matcher *new_matcher;
++ int ret;
++
++ hws_bwc_matcher_init_attr(&matcher_attr,
++ bwc_matcher->priority,
++ bwc_matcher->size_log);
++
++ old_matcher = bwc_matcher->matcher;
++ new_matcher = mlx5hws_matcher_create(old_matcher->tbl,
++ &bwc_matcher->mt, 1,
++ bwc_matcher->at,
++ bwc_matcher->num_of_at,
++ &matcher_attr);
++ if (!new_matcher) {
++ mlx5hws_err(ctx, "Rehash error: matcher creation failed\n");
++ return -ENOMEM;
++ }
++
++ ret = mlx5hws_matcher_resize_set_target(old_matcher, new_matcher);
++ if (ret) {
++ mlx5hws_err(ctx, "Rehash error: failed setting resize target\n");
++ return ret;
++ }
++
++ ret = hws_bwc_matcher_move_all(bwc_matcher);
++ if (ret) {
++ mlx5hws_err(ctx, "Rehash error: moving rules failed\n");
++ return -ENOMEM;
++ }
++
++ bwc_matcher->matcher = new_matcher;
++ mlx5hws_matcher_destroy(old_matcher);
++
++ return 0;
++}
++
+ int mlx5hws_bwc_matcher_create_simple(struct mlx5hws_bwc_matcher *bwc_matcher,
+ struct mlx5hws_table *table,
+ u32 priority,
+@@ -210,51 +387,6 @@ int mlx5hws_bwc_matcher_destroy(struct m
+ return 0;
+ }
+
+-static int hws_bwc_queue_poll(struct mlx5hws_context *ctx,
+- u16 queue_id,
+- u32 *pending_rules,
+- bool drain)
+-{
+- struct mlx5hws_flow_op_result comp[MLX5HWS_BWC_MATCHER_REHASH_BURST_TH];
+- u16 burst_th = hws_bwc_get_burst_th(ctx, queue_id);
+- bool got_comp = *pending_rules >= burst_th;
+- bool queue_full;
+- int err = 0;
+- int ret;
+- int i;
+-
+- /* Check if there are any completions at all */
+- if (!got_comp && !drain)
+- return 0;
+-
+- queue_full = mlx5hws_send_engine_full(&ctx->send_queue[queue_id]);
+- while (queue_full || ((got_comp || drain) && *pending_rules)) {
+- ret = mlx5hws_send_queue_poll(ctx, queue_id, comp, burst_th);
+- if (unlikely(ret < 0)) {
+- mlx5hws_err(ctx, "BWC poll error: polling queue %d returned %d\n",
+- queue_id, ret);
+- return -EINVAL;
+- }
+-
+- if (ret) {
+- (*pending_rules) -= ret;
+- for (i = 0; i < ret; i++) {
+- if (unlikely(comp[i].status != MLX5HWS_FLOW_OP_SUCCESS)) {
+- mlx5hws_err(ctx,
+- "BWC poll error: polling queue %d returned completion with error\n",
+- queue_id);
+- err = -EINVAL;
+- }
+- }
+- queue_full = false;
+- }
+-
+- got_comp = !!ret;
+- }
+-
+- return err;
+-}
+-
+ void
+ mlx5hws_bwc_rule_fill_attr(struct mlx5hws_bwc_matcher *bwc_matcher,
+ u16 bwc_queue_idx,
+@@ -556,138 +688,6 @@ hws_bwc_matcher_find_at(struct mlx5hws_b
+ return -1;
+ }
+
+-static int hws_bwc_matcher_move_all_simple(struct mlx5hws_bwc_matcher *bwc_matcher)
+-{
+- struct mlx5hws_context *ctx = bwc_matcher->matcher->tbl->ctx;
+- u16 bwc_queues = mlx5hws_bwc_queues(ctx);
+- struct mlx5hws_bwc_rule **bwc_rules;
+- struct mlx5hws_rule_attr rule_attr;
+- u32 *pending_rules;
+- int i, j, ret = 0;
+- bool all_done;
+- u16 burst_th;
+-
+- mlx5hws_bwc_rule_fill_attr(bwc_matcher, 0, 0, &rule_attr);
+-
+- pending_rules = kcalloc(bwc_queues, sizeof(*pending_rules), GFP_KERNEL);
+- if (!pending_rules)
+- return -ENOMEM;
+-
+- bwc_rules = kcalloc(bwc_queues, sizeof(*bwc_rules), GFP_KERNEL);
+- if (!bwc_rules) {
+- ret = -ENOMEM;
+- goto free_pending_rules;
+- }
+-
+- for (i = 0; i < bwc_queues; i++) {
+- if (list_empty(&bwc_matcher->rules[i]))
+- bwc_rules[i] = NULL;
+- else
+- bwc_rules[i] = list_first_entry(&bwc_matcher->rules[i],
+- struct mlx5hws_bwc_rule,
+- list_node);
+- }
+-
+- do {
+- all_done = true;
+-
+- for (i = 0; i < bwc_queues; i++) {
+- rule_attr.queue_id = mlx5hws_bwc_get_queue_id(ctx, i);
+- burst_th = hws_bwc_get_burst_th(ctx, rule_attr.queue_id);
+-
+- for (j = 0; j < burst_th && bwc_rules[i]; j++) {
+- rule_attr.burst = !!((j + 1) % burst_th);
+- ret = mlx5hws_matcher_resize_rule_move(bwc_matcher->matcher,
+- bwc_rules[i]->rule,
+- &rule_attr);
+- if (unlikely(ret)) {
+- mlx5hws_err(ctx,
+- "Moving BWC rule failed during rehash (%d)\n",
+- ret);
+- goto free_bwc_rules;
+- }
+-
+- all_done = false;
+- pending_rules[i]++;
+- bwc_rules[i] = list_is_last(&bwc_rules[i]->list_node,
+- &bwc_matcher->rules[i]) ?
+- NULL : list_next_entry(bwc_rules[i], list_node);
+-
+- ret = hws_bwc_queue_poll(ctx, rule_attr.queue_id,
+- &pending_rules[i], false);
+- if (unlikely(ret))
+- goto free_bwc_rules;
+- }
+- }
+- } while (!all_done);
+-
+- /* drain all the bwc queues */
+- for (i = 0; i < bwc_queues; i++) {
+- if (pending_rules[i]) {
+- u16 queue_id = mlx5hws_bwc_get_queue_id(ctx, i);
+-
+- mlx5hws_send_engine_flush_queue(&ctx->send_queue[queue_id]);
+- ret = hws_bwc_queue_poll(ctx, queue_id,
+- &pending_rules[i], true);
+- if (unlikely(ret))
+- goto free_bwc_rules;
+- }
+- }
+-
+-free_bwc_rules:
+- kfree(bwc_rules);
+-free_pending_rules:
+- kfree(pending_rules);
+-
+- return ret;
+-}
+-
+-static int hws_bwc_matcher_move_all(struct mlx5hws_bwc_matcher *bwc_matcher)
+-{
+- return hws_bwc_matcher_move_all_simple(bwc_matcher);
+-}
+-
+-static int hws_bwc_matcher_move(struct mlx5hws_bwc_matcher *bwc_matcher)
+-{
+- struct mlx5hws_context *ctx = bwc_matcher->matcher->tbl->ctx;
+- struct mlx5hws_matcher_attr matcher_attr = {0};
+- struct mlx5hws_matcher *old_matcher;
+- struct mlx5hws_matcher *new_matcher;
+- int ret;
+-
+- hws_bwc_matcher_init_attr(&matcher_attr,
+- bwc_matcher->priority,
+- bwc_matcher->size_log);
+-
+- old_matcher = bwc_matcher->matcher;
+- new_matcher = mlx5hws_matcher_create(old_matcher->tbl,
+- &bwc_matcher->mt, 1,
+- bwc_matcher->at,
+- bwc_matcher->num_of_at,
+- &matcher_attr);
+- if (!new_matcher) {
+- mlx5hws_err(ctx, "Rehash error: matcher creation failed\n");
+- return -ENOMEM;
+- }
+-
+- ret = mlx5hws_matcher_resize_set_target(old_matcher, new_matcher);
+- if (ret) {
+- mlx5hws_err(ctx, "Rehash error: failed setting resize target\n");
+- return ret;
+- }
+-
+- ret = hws_bwc_matcher_move_all(bwc_matcher);
+- if (ret) {
+- mlx5hws_err(ctx, "Rehash error: moving rules failed\n");
+- return -ENOMEM;
+- }
+-
+- bwc_matcher->matcher = new_matcher;
+- mlx5hws_matcher_destroy(old_matcher);
+-
+- return 0;
+-}
+-
+ static int
+ hws_bwc_matcher_rehash_size(struct mlx5hws_bwc_matcher *bwc_matcher)
+ {
--- /dev/null
+From stable+bounces-288424-greg=kroah.com@vger.kernel.org Thu Jul 23 19:05:45 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 12:54:16 -0400
+Subject: netfilter: bitwise: rename some boolean operation functions
+To: stable@vger.kernel.org
+Cc: Jeremy Sowden <jeremy@azazel.net>, Pablo Neira Ayuso <pablo@netfilter.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723165419.3390514-1-sashal@kernel.org>
+
+From: Jeremy Sowden <jeremy@azazel.net>
+
+[ Upstream commit a12143e6084c502fc3cfaa8b717bffc8c14cf806 ]
+
+In the next patch we add support for doing AND, OR and XOR operations
+directly in the kernel, so rename some functions and an enum constant
+related to mask-and-xor boolean operations.
+
+Signed-off-by: Jeremy Sowden <jeremy@azazel.net>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/uapi/linux/netfilter/nf_tables.h | 10 ++++++---
+ net/netfilter/nft_bitwise.c | 34 +++++++++++++++----------------
+ 2 files changed, 24 insertions(+), 20 deletions(-)
+
+--- a/include/uapi/linux/netfilter/nf_tables.h
++++ b/include/uapi/linux/netfilter/nf_tables.h
+@@ -564,16 +564,20 @@ enum nft_immediate_attributes {
+ /**
+ * enum nft_bitwise_ops - nf_tables bitwise operations
+ *
+- * @NFT_BITWISE_BOOL: mask-and-xor operation used to implement NOT, AND, OR and
+- * XOR boolean operations
++ * @NFT_BITWISE_MASK_XOR: mask-and-xor operation used to implement NOT, AND, OR
++ * and XOR boolean operations
+ * @NFT_BITWISE_LSHIFT: left-shift operation
+ * @NFT_BITWISE_RSHIFT: right-shift operation
+ */
+ enum nft_bitwise_ops {
+- NFT_BITWISE_BOOL,
++ NFT_BITWISE_MASK_XOR,
+ NFT_BITWISE_LSHIFT,
+ NFT_BITWISE_RSHIFT,
+ };
++/*
++ * Old name for NFT_BITWISE_MASK_XOR. Retained for backwards-compatibility.
++ */
++#define NFT_BITWISE_BOOL NFT_BITWISE_MASK_XOR
+
+ /**
+ * enum nft_bitwise_attributes - nf_tables bitwise expression netlink attributes
+--- a/net/netfilter/nft_bitwise.c
++++ b/net/netfilter/nft_bitwise.c
+@@ -25,8 +25,8 @@ struct nft_bitwise {
+ struct nft_data data;
+ };
+
+-static void nft_bitwise_eval_bool(u32 *dst, const u32 *src,
+- const struct nft_bitwise *priv)
++static void nft_bitwise_eval_mask_xor(u32 *dst, const u32 *src,
++ const struct nft_bitwise *priv)
+ {
+ unsigned int i;
+
+@@ -68,8 +68,8 @@ void nft_bitwise_eval(const struct nft_e
+ u32 *dst = ®s->data[priv->dreg];
+
+ switch (priv->op) {
+- case NFT_BITWISE_BOOL:
+- nft_bitwise_eval_bool(dst, src, priv);
++ case NFT_BITWISE_MASK_XOR:
++ nft_bitwise_eval_mask_xor(dst, src, priv);
+ break;
+ case NFT_BITWISE_LSHIFT:
+ nft_bitwise_eval_lshift(dst, src, priv);
+@@ -90,8 +90,8 @@ static const struct nla_policy nft_bitwi
+ [NFTA_BITWISE_DATA] = { .type = NLA_NESTED },
+ };
+
+-static int nft_bitwise_init_bool(struct nft_bitwise *priv,
+- const struct nlattr *const tb[])
++static int nft_bitwise_init_mask_xor(struct nft_bitwise *priv,
++ const struct nlattr *const tb[])
+ {
+ struct nft_data_desc mask = {
+ .type = NFT_DATA_VALUE,
+@@ -186,7 +186,7 @@ static int nft_bitwise_init(const struct
+ if (tb[NFTA_BITWISE_OP]) {
+ priv->op = ntohl(nla_get_be32(tb[NFTA_BITWISE_OP]));
+ switch (priv->op) {
+- case NFT_BITWISE_BOOL:
++ case NFT_BITWISE_MASK_XOR:
+ case NFT_BITWISE_LSHIFT:
+ case NFT_BITWISE_RSHIFT:
+ break;
+@@ -194,12 +194,12 @@ static int nft_bitwise_init(const struct
+ return -EOPNOTSUPP;
+ }
+ } else {
+- priv->op = NFT_BITWISE_BOOL;
++ priv->op = NFT_BITWISE_MASK_XOR;
+ }
+
+ switch(priv->op) {
+- case NFT_BITWISE_BOOL:
+- err = nft_bitwise_init_bool(priv, tb);
++ case NFT_BITWISE_MASK_XOR:
++ err = nft_bitwise_init_mask_xor(priv, tb);
+ break;
+ case NFT_BITWISE_LSHIFT:
+ case NFT_BITWISE_RSHIFT:
+@@ -210,8 +210,8 @@ static int nft_bitwise_init(const struct
+ return err;
+ }
+
+-static int nft_bitwise_dump_bool(struct sk_buff *skb,
+- const struct nft_bitwise *priv)
++static int nft_bitwise_dump_mask_xor(struct sk_buff *skb,
++ const struct nft_bitwise *priv)
+ {
+ if (nft_data_dump(skb, NFTA_BITWISE_MASK, &priv->mask,
+ NFT_DATA_VALUE, priv->len) < 0)
+@@ -249,8 +249,8 @@ static int nft_bitwise_dump(struct sk_bu
+ return -1;
+
+ switch (priv->op) {
+- case NFT_BITWISE_BOOL:
+- err = nft_bitwise_dump_bool(skb, priv);
++ case NFT_BITWISE_MASK_XOR:
++ err = nft_bitwise_dump_mask_xor(skb, priv);
+ break;
+ case NFT_BITWISE_LSHIFT:
+ case NFT_BITWISE_RSHIFT:
+@@ -270,7 +270,7 @@ static int nft_bitwise_offload(struct nf
+ const struct nft_bitwise *priv = nft_expr_priv(expr);
+ struct nft_offload_reg *reg = &ctx->regs[priv->dreg];
+
+- if (priv->op != NFT_BITWISE_BOOL)
++ if (priv->op != NFT_BITWISE_MASK_XOR)
+ return -EOPNOTSUPP;
+
+ if (memcmp(&priv->xor, &zero, sizeof(priv->xor)) ||
+@@ -407,7 +407,7 @@ nft_bitwise_fast_dump(struct sk_buff *sk
+ return -1;
+ if (nla_put_be32(skb, NFTA_BITWISE_LEN, htonl(sizeof(u32))))
+ return -1;
+- if (nla_put_be32(skb, NFTA_BITWISE_OP, htonl(NFT_BITWISE_BOOL)))
++ if (nla_put_be32(skb, NFTA_BITWISE_OP, htonl(NFT_BITWISE_MASK_XOR)))
+ return -1;
+
+ data.data[0] = priv->mask;
+@@ -502,7 +502,7 @@ nft_bitwise_select_ops(const struct nft_
+ return &nft_bitwise_ops;
+
+ if (tb[NFTA_BITWISE_OP] &&
+- ntohl(nla_get_be32(tb[NFTA_BITWISE_OP])) != NFT_BITWISE_BOOL)
++ ntohl(nla_get_be32(tb[NFTA_BITWISE_OP])) != NFT_BITWISE_MASK_XOR)
+ return &nft_bitwise_ops;
+
+ return &nft_bitwise_fast_ops;
--- /dev/null
+From stable+bounces-288358-greg=kroah.com@vger.kernel.org Thu Jul 23 16:39:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:34:29 -0400
+Subject: netfilter: nf_conntrack_sip: remove net variable shadowing
+To: stable@vger.kernel.org
+Cc: Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723143430.3226511-1-sashal@kernel.org>
+
+From: Florian Westphal <fw@strlen.de>
+
+[ Upstream commit 7970d6aaf710db166de98c5356a260089896fae5 ]
+
+net is already set, derived from nf_conn.
+I don't see how the device could be living in a different netns
+than the conntrack entry.
+
+Remove the extra variable and re-use existing one.
+
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Stable-dep-of: e5e24a365a5e ("netfilter: nf_conntrack_sip: validate skb_dst() before accessing it")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/netfilter/nf_conntrack_sip.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/net/netfilter/nf_conntrack_sip.c
++++ b/net/netfilter/nf_conntrack_sip.c
+@@ -948,9 +948,8 @@ static int set_expected_rtp_rtcp(struct
+ saddr = &ct->tuplehash[!dir].tuple.src.u3;
+ } else if (sip_external_media) {
+ struct net_device *dev = skb_dst(skb)->dev;
+- struct net *net = dev_net(dev);
+- struct flowi fl;
+ struct dst_entry *dst = NULL;
++ struct flowi fl;
+
+ memset(&fl, 0, sizeof(fl));
+
--- /dev/null
+From stable+bounces-288359-greg=kroah.com@vger.kernel.org Thu Jul 23 16:39:45 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:34:30 -0400
+Subject: netfilter: nf_conntrack_sip: validate skb_dst() before accessing it
+To: stable@vger.kernel.org
+Cc: Pablo Neira Ayuso <pablo@netfilter.org>, Ren Wei <n05ec@lzu.edu.cn>, Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723143430.3226511-2-sashal@kernel.org>
+
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+
+[ Upstream commit e5e24a365a5e024efef63cc49abb345fbd4852c5 ]
+
+tc ingress and openvswitch do not guarantee routing information to be
+available. These subsystems use the conntrack helper infrastructure, and
+the SIP helper relies on the skb_dst() to be present if
+sip_external_media is set to 1 (which is disabled by default as a module
+parameter).
+
+This effectively disables the sip_external_media toggle for these
+subsystems without resulting in a crash.
+
+Fixes: cae3a2627520 ("openvswitch: Allow attaching helpers to ct action")
+Fixes: b57dc7c13ea9 ("net/sched: Introduce action ct")
+Cc: stable@vger.kernel.org
+Reported-by: Ren Wei <n05ec@lzu.edu.cn>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/netfilter/nf_conntrack_sip.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+--- a/net/netfilter/nf_conntrack_sip.c
++++ b/net/netfilter/nf_conntrack_sip.c
+@@ -947,7 +947,6 @@ static int set_expected_rtp_rtcp(struct
+ return NF_ACCEPT;
+ saddr = &ct->tuplehash[!dir].tuple.src.u3;
+ } else if (sip_external_media) {
+- struct net_device *dev = skb_dst(skb)->dev;
+ struct dst_entry *dst = NULL;
+ struct flowi fl;
+
+@@ -969,7 +968,11 @@ static int set_expected_rtp_rtcp(struct
+ * through the same interface as the signalling peer.
+ */
+ if (dst) {
+- bool external_media = (dst->dev == dev);
++ const struct dst_entry *this_dst = skb_dst(skb);
++ bool external_media = false;
++
++ if (this_dst && dst->dev == this_dst->dev)
++ external_media = true;
+
+ dst_release(dst);
+ if (external_media)
--- /dev/null
+From stable+bounces-288429-greg=kroah.com@vger.kernel.org Thu Jul 23 18:57:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 12:54:18 -0400
+Subject: netfilter: nf_tables: remove register tracking infrastructure
+To: stable@vger.kernel.org
+Cc: Florian Westphal <fw@strlen.de>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723165419.3390514-3-sashal@kernel.org>
+
+From: Florian Westphal <fw@strlen.de>
+
+[ Upstream commit 6b94d081f81dd524626f7aab2b98a9de335edb72 ]
+
+This facility was disabled in commit
+9e539c5b6d9c ("netfilter: nf_tables: disable expression reduction infra"),
+because not all nft_exprs guarantee they will update the destination
+register: some may set NFT_BREAK instead to cancel evaluation of the
+rule.
+
+This has been dead code ever since.
+There are no plans to salvage this at this time, so remove this.
+
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Link: https://patch.msgid.link/20260224205048.4718-10-fw@strlen.de
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/netfilter/nf_tables.h | 32 ---------
+ include/net/netfilter/nft_fib.h | 2
+ include/net/netfilter/nft_meta.h | 3
+ net/bridge/netfilter/nft_meta_bridge.c | 20 ------
+ net/bridge/netfilter/nft_reject_bridge.c | 1
+ net/ipv4/netfilter/nft_dup_ipv4.c | 1
+ net/ipv4/netfilter/nft_fib_ipv4.c | 2
+ net/ipv4/netfilter/nft_reject_ipv4.c | 1
+ net/ipv6/netfilter/nft_dup_ipv6.c | 1
+ net/ipv6/netfilter/nft_fib_ipv6.c | 2
+ net/ipv6/netfilter/nft_reject_ipv6.c | 1
+ net/netfilter/nf_tables_api.c | 67 --------------------
+ net/netfilter/nft_bitwise.c | 103 -------------------------------
+ net/netfilter/nft_byteorder.c | 11 ---
+ net/netfilter/nft_cmp.c | 3
+ net/netfilter/nft_compat.c | 10 ---
+ net/netfilter/nft_connlimit.c | 1
+ net/netfilter/nft_counter.c | 1
+ net/netfilter/nft_ct.c | 46 -------------
+ net/netfilter/nft_dup_netdev.c | 1
+ net/netfilter/nft_dynset.c | 1
+ net/netfilter/nft_exthdr.c | 34 ----------
+ net/netfilter/nft_fib.c | 42 ------------
+ net/netfilter/nft_fib_inet.c | 1
+ net/netfilter/nft_fib_netdev.c | 1
+ net/netfilter/nft_flow_offload.c | 1
+ net/netfilter/nft_fwd_netdev.c | 2
+ net/netfilter/nft_hash.c | 36 ----------
+ net/netfilter/nft_immediate.c | 12 ---
+ net/netfilter/nft_last.c | 1
+ net/netfilter/nft_limit.c | 2
+ net/netfilter/nft_log.c | 1
+ net/netfilter/nft_lookup.c | 12 ---
+ net/netfilter/nft_masq.c | 3
+ net/netfilter/nft_meta.c | 45 -------------
+ net/netfilter/nft_nat.c | 2
+ net/netfilter/nft_numgen.c | 22 ------
+ net/netfilter/nft_objref.c | 2
+ net/netfilter/nft_osf.c | 25 -------
+ net/netfilter/nft_payload.c | 47 --------------
+ net/netfilter/nft_queue.c | 2
+ net/netfilter/nft_quota.c | 1
+ net/netfilter/nft_range.c | 1
+ net/netfilter/nft_redir.c | 3
+ net/netfilter/nft_reject_inet.c | 1
+ net/netfilter/nft_reject_netdev.c | 1
+ net/netfilter/nft_rt.c | 1
+ net/netfilter/nft_socket.c | 26 -------
+ net/netfilter/nft_synproxy.c | 1
+ net/netfilter/nft_tproxy.c | 1
+ net/netfilter/nft_tunnel.c | 26 -------
+ net/netfilter/nft_xfrm.c | 27 --------
+ 52 files changed, 692 deletions(-)
+
+--- a/include/net/netfilter/nf_tables.h
++++ b/include/net/netfilter/nf_tables.h
+@@ -123,17 +123,6 @@ struct nft_regs {
+ };
+ };
+
+-struct nft_regs_track {
+- struct {
+- const struct nft_expr *selector;
+- const struct nft_expr *bitwise;
+- u8 num_reg;
+- } regs[NFT_REG32_NUM];
+-
+- const struct nft_expr *cur;
+- const struct nft_expr *last;
+-};
+-
+ /* Store/load an u8, u16 or u64 integer to/from the u32 data register.
+ *
+ * Note, when using concatenations, register allocation happens at 32-bit
+@@ -424,8 +413,6 @@ int nft_expr_clone(struct nft_expr *dst,
+ void nft_expr_destroy(const struct nft_ctx *ctx, struct nft_expr *expr);
+ int nft_expr_dump(struct sk_buff *skb, unsigned int attr,
+ const struct nft_expr *expr, bool reset);
+-bool nft_expr_reduce_bitwise(struct nft_regs_track *track,
+- const struct nft_expr *expr);
+
+ struct nft_set_ext;
+
+@@ -940,7 +927,6 @@ struct nft_offload_ctx;
+ * @destroy_clone: destruction clone function
+ * @dump: function to dump parameters
+ * @validate: validate expression, called during loop detection
+- * @reduce: reduce expression
+ * @gc: garbage collection expression
+ * @offload: hardware offload expression
+ * @offload_action: function to report true/false to allocate one slot or not in the flow
+@@ -974,8 +960,6 @@ struct nft_expr_ops {
+ bool reset);
+ int (*validate)(const struct nft_ctx *ctx,
+ const struct nft_expr *expr);
+- bool (*reduce)(struct nft_regs_track *track,
+- const struct nft_expr *expr);
+ bool (*gc)(struct net *net,
+ const struct nft_expr *expr);
+ int (*offload)(struct nft_offload_ctx *ctx,
+@@ -1951,20 +1935,4 @@ static inline u64 nft_net_tstamp(const s
+ return nft_pernet(net)->tstamp;
+ }
+
+-#define __NFT_REDUCE_READONLY 1UL
+-#define NFT_REDUCE_READONLY (void *)__NFT_REDUCE_READONLY
+-
+-void nft_reg_track_update(struct nft_regs_track *track,
+- const struct nft_expr *expr, u8 dreg, u8 len);
+-void nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg, u8 len);
+-void __nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg);
+-
+-static inline bool nft_reg_track_cmp(struct nft_regs_track *track,
+- const struct nft_expr *expr, u8 dreg)
+-{
+- return track->regs[dreg].selector &&
+- track->regs[dreg].selector->ops == expr->ops &&
+- track->regs[dreg].num_reg == 0;
+-}
+-
+ #endif /* _NET_NF_TABLES_H */
+--- a/include/net/netfilter/nft_fib.h
++++ b/include/net/netfilter/nft_fib.h
+@@ -66,6 +66,4 @@ void nft_fib6_eval(const struct nft_expr
+ void nft_fib_store_result(void *reg, const struct nft_fib *priv,
+ const struct net_device *dev);
+
+-bool nft_fib_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr);
+ #endif
+--- a/include/net/netfilter/nft_meta.h
++++ b/include/net/netfilter/nft_meta.h
+@@ -43,9 +43,6 @@ void nft_meta_set_destroy(const struct n
+ int nft_meta_set_validate(const struct nft_ctx *ctx,
+ const struct nft_expr *expr);
+
+-bool nft_meta_get_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr);
+-
+ struct nft_inner_tun_ctx;
+ void nft_meta_inner_eval(const struct nft_expr *expr,
+ struct nft_regs *regs, const struct nft_pktinfo *pkt,
+--- a/net/bridge/netfilter/nft_meta_bridge.c
++++ b/net/bridge/netfilter/nft_meta_bridge.c
+@@ -104,7 +104,6 @@ static const struct nft_expr_ops nft_met
+ .eval = nft_meta_bridge_get_eval,
+ .init = nft_meta_bridge_get_init,
+ .dump = nft_meta_get_dump,
+- .reduce = nft_meta_get_reduce,
+ };
+
+ static void nft_meta_bridge_set_eval(const struct nft_expr *expr,
+@@ -151,24 +150,6 @@ static int nft_meta_bridge_set_init(cons
+ return 0;
+ }
+
+-static bool nft_meta_bridge_set_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- int i;
+-
+- for (i = 0; i < NFT_REG32_NUM; i++) {
+- if (!track->regs[i].selector)
+- continue;
+-
+- if (track->regs[i].selector->ops != &nft_meta_bridge_get_ops)
+- continue;
+-
+- __nft_reg_track_cancel(track, i);
+- }
+-
+- return false;
+-}
+-
+ static int nft_meta_bridge_set_validate(const struct nft_ctx *ctx,
+ const struct nft_expr *expr)
+ {
+@@ -193,7 +174,6 @@ static const struct nft_expr_ops nft_met
+ .init = nft_meta_bridge_set_init,
+ .destroy = nft_meta_set_destroy,
+ .dump = nft_meta_set_dump,
+- .reduce = nft_meta_bridge_set_reduce,
+ .validate = nft_meta_bridge_set_validate,
+ };
+
+--- a/net/bridge/netfilter/nft_reject_bridge.c
++++ b/net/bridge/netfilter/nft_reject_bridge.c
+@@ -184,7 +184,6 @@ static const struct nft_expr_ops nft_rej
+ .init = nft_reject_init,
+ .dump = nft_reject_dump,
+ .validate = nft_reject_bridge_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_reject_bridge_type __read_mostly = {
+--- a/net/ipv4/netfilter/nft_dup_ipv4.c
++++ b/net/ipv4/netfilter/nft_dup_ipv4.c
+@@ -76,7 +76,6 @@ static const struct nft_expr_ops nft_dup
+ .eval = nft_dup_ipv4_eval,
+ .init = nft_dup_ipv4_init,
+ .dump = nft_dup_ipv4_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nla_policy nft_dup_ipv4_policy[NFTA_DUP_MAX + 1] = {
+--- a/net/ipv4/netfilter/nft_fib_ipv4.c
++++ b/net/ipv4/netfilter/nft_fib_ipv4.c
+@@ -162,7 +162,6 @@ static const struct nft_expr_ops nft_fib
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+ .validate = nft_fib_validate,
+- .reduce = nft_fib_reduce,
+ };
+
+ static const struct nft_expr_ops nft_fib4_ops = {
+@@ -172,7 +171,6 @@ static const struct nft_expr_ops nft_fib
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+ .validate = nft_fib_validate,
+- .reduce = nft_fib_reduce,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/ipv4/netfilter/nft_reject_ipv4.c
++++ b/net/ipv4/netfilter/nft_reject_ipv4.c
+@@ -45,7 +45,6 @@ static const struct nft_expr_ops nft_rej
+ .init = nft_reject_init,
+ .dump = nft_reject_dump,
+ .validate = nft_reject_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_reject_ipv4_type __read_mostly = {
+--- a/net/ipv6/netfilter/nft_dup_ipv6.c
++++ b/net/ipv6/netfilter/nft_dup_ipv6.c
+@@ -74,7 +74,6 @@ static const struct nft_expr_ops nft_dup
+ .eval = nft_dup_ipv6_eval,
+ .init = nft_dup_ipv6_init,
+ .dump = nft_dup_ipv6_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nla_policy nft_dup_ipv6_policy[NFTA_DUP_MAX + 1] = {
+--- a/net/ipv6/netfilter/nft_fib_ipv6.c
++++ b/net/ipv6/netfilter/nft_fib_ipv6.c
+@@ -225,7 +225,6 @@ static const struct nft_expr_ops nft_fib
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+ .validate = nft_fib_validate,
+- .reduce = nft_fib_reduce,
+ };
+
+ static const struct nft_expr_ops nft_fib6_ops = {
+@@ -235,7 +234,6 @@ static const struct nft_expr_ops nft_fib
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+ .validate = nft_fib_validate,
+- .reduce = nft_fib_reduce,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/ipv6/netfilter/nft_reject_ipv6.c
++++ b/net/ipv6/netfilter/nft_reject_ipv6.c
+@@ -46,7 +46,6 @@ static const struct nft_expr_ops nft_rej
+ .init = nft_reject_init,
+ .dump = nft_reject_dump,
+ .validate = nft_reject_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_reject_ipv6_type __read_mostly = {
+--- a/net/netfilter/nf_tables_api.c
++++ b/net/netfilter/nf_tables_api.c
+@@ -802,58 +802,6 @@ static int nft_delflowtable(struct nft_c
+ return 0;
+ }
+
+-static void __nft_reg_track_clobber(struct nft_regs_track *track, u8 dreg)
+-{
+- int i;
+-
+- for (i = track->regs[dreg].num_reg; i > 0; i--)
+- __nft_reg_track_cancel(track, dreg - i);
+-}
+-
+-static void __nft_reg_track_update(struct nft_regs_track *track,
+- const struct nft_expr *expr,
+- u8 dreg, u8 num_reg)
+-{
+- track->regs[dreg].selector = expr;
+- track->regs[dreg].bitwise = NULL;
+- track->regs[dreg].num_reg = num_reg;
+-}
+-
+-void nft_reg_track_update(struct nft_regs_track *track,
+- const struct nft_expr *expr, u8 dreg, u8 len)
+-{
+- unsigned int regcount;
+- int i;
+-
+- __nft_reg_track_clobber(track, dreg);
+-
+- regcount = DIV_ROUND_UP(len, NFT_REG32_SIZE);
+- for (i = 0; i < regcount; i++, dreg++)
+- __nft_reg_track_update(track, expr, dreg, i);
+-}
+-EXPORT_SYMBOL_GPL(nft_reg_track_update);
+-
+-void nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg, u8 len)
+-{
+- unsigned int regcount;
+- int i;
+-
+- __nft_reg_track_clobber(track, dreg);
+-
+- regcount = DIV_ROUND_UP(len, NFT_REG32_SIZE);
+- for (i = 0; i < regcount; i++, dreg++)
+- __nft_reg_track_cancel(track, dreg);
+-}
+-EXPORT_SYMBOL_GPL(nft_reg_track_cancel);
+-
+-void __nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg)
+-{
+- track->regs[dreg].selector = NULL;
+- track->regs[dreg].bitwise = NULL;
+- track->regs[dreg].num_reg = 0;
+-}
+-EXPORT_SYMBOL_GPL(__nft_reg_track_cancel);
+-
+ /*
+ * Tables
+ */
+@@ -9685,16 +9633,9 @@ void nf_tables_trans_destroy_flush_work(
+ }
+ EXPORT_SYMBOL_GPL(nf_tables_trans_destroy_flush_work);
+
+-static bool nft_expr_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- return false;
+-}
+-
+ static int nf_tables_commit_chain_prepare(struct net *net, struct nft_chain *chain)
+ {
+ const struct nft_expr *expr, *last;
+- struct nft_regs_track track = {};
+ unsigned int size, data_size;
+ void *data, *data_boundary;
+ struct nft_rule_dp *prule;
+@@ -9731,15 +9672,7 @@ static int nf_tables_commit_chain_prepar
+ return -ENOMEM;
+
+ size = 0;
+- track.last = nft_expr_last(rule);
+ nft_rule_for_each_expr(expr, last, rule) {
+- track.cur = expr;
+-
+- if (nft_expr_reduce(&track, expr)) {
+- expr = track.cur;
+- continue;
+- }
+-
+ if (WARN_ON_ONCE(data + size + expr->ops->size > data_boundary))
+ return -ENOMEM;
+
+--- a/net/netfilter/nft_bitwise.c
++++ b/net/netfilter/nft_bitwise.c
+@@ -282,60 +282,12 @@ static int nft_bitwise_offload(struct nf
+ return 0;
+ }
+
+-static bool nft_bitwise_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_bitwise *priv = nft_expr_priv(expr);
+- const struct nft_bitwise *bitwise;
+- unsigned int regcount;
+- u8 dreg;
+- int i;
+-
+- if (!track->regs[priv->sreg].selector)
+- return false;
+-
+- bitwise = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (track->regs[priv->sreg].selector == track->regs[priv->dreg].selector &&
+- track->regs[priv->sreg].num_reg == 0 &&
+- track->regs[priv->dreg].bitwise &&
+- track->regs[priv->dreg].bitwise->ops == expr->ops &&
+- priv->sreg == bitwise->sreg &&
+- priv->dreg == bitwise->dreg &&
+- priv->op == bitwise->op &&
+- priv->len == bitwise->len &&
+- !memcmp(&priv->mask, &bitwise->mask, sizeof(priv->mask)) &&
+- !memcmp(&priv->xor, &bitwise->xor, sizeof(priv->xor)) &&
+- !memcmp(&priv->data, &bitwise->data, sizeof(priv->data))) {
+- track->cur = expr;
+- return true;
+- }
+-
+- if (track->regs[priv->sreg].bitwise ||
+- track->regs[priv->sreg].num_reg != 0) {
+- nft_reg_track_cancel(track, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (priv->sreg != priv->dreg) {
+- nft_reg_track_update(track, track->regs[priv->sreg].selector,
+- priv->dreg, priv->len);
+- }
+-
+- dreg = priv->dreg;
+- regcount = DIV_ROUND_UP(priv->len, NFT_REG32_SIZE);
+- for (i = 0; i < regcount; i++, dreg++)
+- track->regs[dreg].bitwise = expr;
+-
+- return false;
+-}
+-
+ static const struct nft_expr_ops nft_bitwise_ops = {
+ .type = &nft_bitwise_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_bitwise)),
+ .eval = nft_bitwise_eval,
+ .init = nft_bitwise_init,
+ .dump = nft_bitwise_dump,
+- .reduce = nft_bitwise_reduce,
+ .offload = nft_bitwise_offload,
+ };
+
+@@ -437,48 +389,12 @@ static int nft_bitwise_fast_offload(stru
+ return 0;
+ }
+
+-static bool nft_bitwise_fast_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_bitwise_fast_expr *priv = nft_expr_priv(expr);
+- const struct nft_bitwise_fast_expr *bitwise;
+-
+- if (!track->regs[priv->sreg].selector)
+- return false;
+-
+- bitwise = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (track->regs[priv->sreg].selector == track->regs[priv->dreg].selector &&
+- track->regs[priv->dreg].bitwise &&
+- track->regs[priv->dreg].bitwise->ops == expr->ops &&
+- priv->sreg == bitwise->sreg &&
+- priv->dreg == bitwise->dreg &&
+- priv->mask == bitwise->mask &&
+- priv->xor == bitwise->xor) {
+- track->cur = expr;
+- return true;
+- }
+-
+- if (track->regs[priv->sreg].bitwise) {
+- nft_reg_track_cancel(track, priv->dreg, NFT_REG32_SIZE);
+- return false;
+- }
+-
+- if (priv->sreg != priv->dreg) {
+- track->regs[priv->dreg].selector =
+- track->regs[priv->sreg].selector;
+- }
+- track->regs[priv->dreg].bitwise = expr;
+-
+- return false;
+-}
+-
+ const struct nft_expr_ops nft_bitwise_fast_ops = {
+ .type = &nft_bitwise_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_bitwise_fast_expr)),
+ .eval = NULL, /* inlined */
+ .init = nft_bitwise_fast_init,
+ .dump = nft_bitwise_fast_dump,
+- .reduce = nft_bitwise_fast_reduce,
+ .offload = nft_bitwise_fast_offload,
+ };
+
+@@ -515,22 +431,3 @@ struct nft_expr_type nft_bitwise_type __
+ .maxattr = NFTA_BITWISE_MAX,
+ .owner = THIS_MODULE,
+ };
+-
+-bool nft_expr_reduce_bitwise(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_expr *last = track->last;
+- const struct nft_expr *next;
+-
+- if (expr == last)
+- return false;
+-
+- next = nft_expr_next(expr);
+- if (next->ops == &nft_bitwise_ops)
+- return nft_bitwise_reduce(track, next);
+- else if (next->ops == &nft_bitwise_fast_ops)
+- return nft_bitwise_fast_reduce(track, next);
+-
+- return false;
+-}
+-EXPORT_SYMBOL_GPL(nft_expr_reduce_bitwise);
+--- a/net/netfilter/nft_byteorder.c
++++ b/net/netfilter/nft_byteorder.c
+@@ -170,23 +170,12 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_byteorder_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- struct nft_byteorder *priv = nft_expr_priv(expr);
+-
+- nft_reg_track_cancel(track, priv->dreg, priv->len);
+-
+- return false;
+-}
+-
+ static const struct nft_expr_ops nft_byteorder_ops = {
+ .type = &nft_byteorder_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_byteorder)),
+ .eval = nft_byteorder_eval,
+ .init = nft_byteorder_init,
+ .dump = nft_byteorder_dump,
+- .reduce = nft_byteorder_reduce,
+ };
+
+ struct nft_expr_type nft_byteorder_type __read_mostly = {
+--- a/net/netfilter/nft_cmp.c
++++ b/net/netfilter/nft_cmp.c
+@@ -190,7 +190,6 @@ static const struct nft_expr_ops nft_cmp
+ .eval = nft_cmp_eval,
+ .init = nft_cmp_init,
+ .dump = nft_cmp_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ .offload = nft_cmp_offload,
+ };
+
+@@ -282,7 +281,6 @@ const struct nft_expr_ops nft_cmp_fast_o
+ .eval = NULL, /* inlined */
+ .init = nft_cmp_fast_init,
+ .dump = nft_cmp_fast_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ .offload = nft_cmp_fast_offload,
+ };
+
+@@ -376,7 +374,6 @@ const struct nft_expr_ops nft_cmp16_fast
+ .eval = NULL, /* inlined */
+ .init = nft_cmp16_fast_init,
+ .dump = nft_cmp16_fast_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ .offload = nft_cmp16_fast_offload,
+ };
+
+--- a/net/netfilter/nft_compat.c
++++ b/net/netfilter/nft_compat.c
+@@ -794,14 +794,6 @@ static const struct nfnetlink_subsystem
+
+ static struct nft_expr_type nft_match_type;
+
+-static bool nft_match_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct xt_match *match = expr->ops->data;
+-
+- return strcmp(match->name, "comment") == 0;
+-}
+-
+ static const struct nft_expr_ops *
+ nft_match_select_ops(const struct nft_ctx *ctx,
+ const struct nlattr * const tb[])
+@@ -844,7 +836,6 @@ nft_match_select_ops(const struct nft_ct
+ ops->dump = nft_match_dump;
+ ops->validate = nft_match_validate;
+ ops->data = match;
+- ops->reduce = nft_match_reduce;
+
+ matchsize = NFT_EXPR_SIZE(XT_ALIGN(match->matchsize));
+ if (matchsize > NFT_MATCH_LARGE_THRESH) {
+@@ -933,7 +924,6 @@ nft_target_select_ops(const struct nft_c
+ ops->destroy = nft_target_destroy;
+ ops->dump = nft_target_dump;
+ ops->data = target;
+- ops->reduce = NFT_REDUCE_READONLY;
+
+ if (family == NFPROTO_BRIDGE) {
+ ops->eval = nft_target_eval_bridge;
+--- a/net/netfilter/nft_connlimit.c
++++ b/net/netfilter/nft_connlimit.c
+@@ -247,7 +247,6 @@ static const struct nft_expr_ops nft_con
+ .destroy_clone = nft_connlimit_destroy_clone,
+ .dump = nft_connlimit_dump,
+ .gc = nft_connlimit_gc,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_connlimit_type __read_mostly = {
+--- a/net/netfilter/nft_counter.c
++++ b/net/netfilter/nft_counter.c
+@@ -313,7 +313,6 @@ static const struct nft_expr_ops nft_cou
+ .destroy_clone = nft_counter_destroy,
+ .dump = nft_counter_dump,
+ .clone = nft_counter_clone,
+- .reduce = NFT_REDUCE_READONLY,
+ .offload = nft_counter_offload,
+ .offload_stats = nft_counter_offload_stats,
+ };
+--- a/net/netfilter/nft_ct.c
++++ b/net/netfilter/nft_ct.c
+@@ -679,29 +679,6 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_ct_get_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_ct *priv = nft_expr_priv(expr);
+- const struct nft_ct *ct;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- ct = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->key != ct->key) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static int nft_ct_set_dump(struct sk_buff *skb,
+ const struct nft_expr *expr, bool reset)
+ {
+@@ -736,27 +713,8 @@ static const struct nft_expr_ops nft_ct_
+ .init = nft_ct_get_init,
+ .destroy = nft_ct_get_destroy,
+ .dump = nft_ct_get_dump,
+- .reduce = nft_ct_get_reduce,
+ };
+
+-static bool nft_ct_set_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- int i;
+-
+- for (i = 0; i < NFT_REG32_NUM; i++) {
+- if (!track->regs[i].selector)
+- continue;
+-
+- if (track->regs[i].selector->ops != &nft_ct_get_ops)
+- continue;
+-
+- __nft_reg_track_cancel(track, i);
+- }
+-
+- return false;
+-}
+-
+ #ifdef CONFIG_MITIGATION_RETPOLINE
+ static const struct nft_expr_ops nft_ct_get_fast_ops = {
+ .type = &nft_ct_type,
+@@ -765,7 +723,6 @@ static const struct nft_expr_ops nft_ct_
+ .init = nft_ct_get_init,
+ .destroy = nft_ct_get_destroy,
+ .dump = nft_ct_get_dump,
+- .reduce = nft_ct_set_reduce,
+ };
+ #endif
+
+@@ -776,7 +733,6 @@ static const struct nft_expr_ops nft_ct_
+ .init = nft_ct_set_init,
+ .destroy = nft_ct_set_destroy,
+ .dump = nft_ct_set_dump,
+- .reduce = nft_ct_set_reduce,
+ };
+
+ #ifdef CONFIG_NF_CONNTRACK_ZONES
+@@ -787,7 +743,6 @@ static const struct nft_expr_ops nft_ct_
+ .init = nft_ct_set_init,
+ .destroy = nft_ct_set_destroy,
+ .dump = nft_ct_set_dump,
+- .reduce = nft_ct_set_reduce,
+ };
+ #endif
+
+@@ -857,7 +812,6 @@ static const struct nft_expr_ops nft_not
+ .type = &nft_notrack_type,
+ .size = NFT_EXPR_SIZE(0),
+ .eval = nft_notrack_eval,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_notrack_type __read_mostly = {
+--- a/net/netfilter/nft_dup_netdev.c
++++ b/net/netfilter/nft_dup_netdev.c
+@@ -80,7 +80,6 @@ static const struct nft_expr_ops nft_dup
+ .eval = nft_dup_netdev_eval,
+ .init = nft_dup_netdev_init,
+ .dump = nft_dup_netdev_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ .offload = nft_dup_netdev_offload,
+ .offload_action = nft_dup_netdev_offload_action,
+ };
+--- a/net/netfilter/nft_dynset.c
++++ b/net/netfilter/nft_dynset.c
+@@ -430,7 +430,6 @@ static const struct nft_expr_ops nft_dyn
+ .activate = nft_dynset_activate,
+ .deactivate = nft_dynset_deactivate,
+ .dump = nft_dynset_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ struct nft_expr_type nft_dynset_type __read_mostly = {
+--- a/net/netfilter/nft_exthdr.c
++++ b/net/netfilter/nft_exthdr.c
+@@ -701,40 +701,12 @@ static int nft_exthdr_dump_strip(struct
+ return nft_exthdr_dump_common(skb, priv);
+ }
+
+-static bool nft_exthdr_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_exthdr *priv = nft_expr_priv(expr);
+- const struct nft_exthdr *exthdr;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- exthdr = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->type != exthdr->type ||
+- priv->op != exthdr->op ||
+- priv->flags != exthdr->flags ||
+- priv->offset != exthdr->offset ||
+- priv->len != exthdr->len) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static const struct nft_expr_ops nft_exthdr_ipv6_ops = {
+ .type = &nft_exthdr_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
+ .eval = nft_exthdr_ipv6_eval,
+ .init = nft_exthdr_init,
+ .dump = nft_exthdr_dump,
+- .reduce = nft_exthdr_reduce,
+ };
+
+ static const struct nft_expr_ops nft_exthdr_ipv4_ops = {
+@@ -743,7 +715,6 @@ static const struct nft_expr_ops nft_ext
+ .eval = nft_exthdr_ipv4_eval,
+ .init = nft_exthdr_ipv4_init,
+ .dump = nft_exthdr_dump,
+- .reduce = nft_exthdr_reduce,
+ };
+
+ static const struct nft_expr_ops nft_exthdr_tcp_ops = {
+@@ -752,7 +723,6 @@ static const struct nft_expr_ops nft_ext
+ .eval = nft_exthdr_tcp_eval,
+ .init = nft_exthdr_init,
+ .dump = nft_exthdr_dump,
+- .reduce = nft_exthdr_reduce,
+ };
+
+ static const struct nft_expr_ops nft_exthdr_tcp_set_ops = {
+@@ -761,7 +731,6 @@ static const struct nft_expr_ops nft_ext
+ .eval = nft_exthdr_tcp_set_eval,
+ .init = nft_exthdr_tcp_set_init,
+ .dump = nft_exthdr_dump_set,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops nft_exthdr_tcp_strip_ops = {
+@@ -770,7 +739,6 @@ static const struct nft_expr_ops nft_ext
+ .eval = nft_exthdr_tcp_strip_eval,
+ .init = nft_exthdr_tcp_strip_init,
+ .dump = nft_exthdr_dump_strip,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops nft_exthdr_sctp_ops = {
+@@ -779,7 +747,6 @@ static const struct nft_expr_ops nft_ext
+ .eval = nft_exthdr_sctp_eval,
+ .init = nft_exthdr_init,
+ .dump = nft_exthdr_dump,
+- .reduce = nft_exthdr_reduce,
+ };
+
+ static const struct nft_expr_ops nft_exthdr_dccp_ops = {
+@@ -788,7 +755,6 @@ static const struct nft_expr_ops nft_ext
+ .eval = nft_exthdr_dccp_eval,
+ .init = nft_exthdr_dccp_init,
+ .dump = nft_exthdr_dump,
+- .reduce = nft_exthdr_reduce,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_fib.c
++++ b/net/netfilter/nft_fib.c
+@@ -168,48 +168,6 @@ void nft_fib_store_result(void *reg, con
+ }
+ EXPORT_SYMBOL_GPL(nft_fib_store_result);
+
+-bool nft_fib_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_fib *priv = nft_expr_priv(expr);
+- unsigned int len = NFT_REG32_SIZE;
+- const struct nft_fib *fib;
+-
+- switch (priv->result) {
+- case NFT_FIB_RESULT_OIF:
+- break;
+- case NFT_FIB_RESULT_OIFNAME:
+- if (priv->flags & NFTA_FIB_F_PRESENT)
+- len = NFT_REG32_SIZE;
+- else
+- len = IFNAMSIZ;
+- break;
+- case NFT_FIB_RESULT_ADDRTYPE:
+- break;
+- default:
+- WARN_ON_ONCE(1);
+- break;
+- }
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, len);
+- return false;
+- }
+-
+- fib = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->result != fib->result ||
+- priv->flags != fib->flags) {
+- nft_reg_track_update(track, expr, priv->dreg, len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return false;
+-}
+-EXPORT_SYMBOL_GPL(nft_fib_reduce);
+-
+ MODULE_LICENSE("GPL");
+ MODULE_DESCRIPTION("Query routing table from nftables");
+ MODULE_AUTHOR("Florian Westphal <fw@strlen.de>");
+--- a/net/netfilter/nft_fib_inet.c
++++ b/net/netfilter/nft_fib_inet.c
+@@ -49,7 +49,6 @@ static const struct nft_expr_ops nft_fib
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+ .validate = nft_fib_validate,
+- .reduce = nft_fib_reduce,
+ };
+
+ static struct nft_expr_type nft_fib_inet_type __read_mostly = {
+--- a/net/netfilter/nft_fib_netdev.c
++++ b/net/netfilter/nft_fib_netdev.c
+@@ -58,7 +58,6 @@ static const struct nft_expr_ops nft_fib
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+ .validate = nft_fib_validate,
+- .reduce = nft_fib_reduce,
+ };
+
+ static struct nft_expr_type nft_fib_netdev_type __read_mostly = {
+--- a/net/netfilter/nft_flow_offload.c
++++ b/net/netfilter/nft_flow_offload.c
+@@ -483,7 +483,6 @@ static const struct nft_expr_ops nft_flo
+ .destroy = nft_flow_offload_destroy,
+ .validate = nft_flow_offload_validate,
+ .dump = nft_flow_offload_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_flow_offload_type __read_mostly = {
+--- a/net/netfilter/nft_fwd_netdev.c
++++ b/net/netfilter/nft_fwd_netdev.c
+@@ -228,7 +228,6 @@ static const struct nft_expr_ops nft_fwd
+ .init = nft_fwd_neigh_init,
+ .dump = nft_fwd_neigh_dump,
+ .validate = nft_fwd_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops nft_fwd_netdev_ops = {
+@@ -238,7 +237,6 @@ static const struct nft_expr_ops nft_fwd
+ .init = nft_fwd_netdev_init,
+ .dump = nft_fwd_netdev_dump,
+ .validate = nft_fwd_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ .offload = nft_fwd_netdev_offload,
+ .offload_action = nft_fwd_netdev_offload_action,
+ };
+--- a/net/netfilter/nft_hash.c
++++ b/net/netfilter/nft_hash.c
+@@ -166,16 +166,6 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_jhash_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_jhash *priv = nft_expr_priv(expr);
+-
+- nft_reg_track_cancel(track, priv->dreg, sizeof(u32));
+-
+- return false;
+-}
+-
+ static int nft_symhash_dump(struct sk_buff *skb,
+ const struct nft_expr *expr, bool reset)
+ {
+@@ -196,30 +186,6 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_symhash_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- struct nft_symhash *priv = nft_expr_priv(expr);
+- struct nft_symhash *symhash;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, sizeof(u32));
+- return false;
+- }
+-
+- symhash = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->offset != symhash->offset ||
+- priv->modulus != symhash->modulus) {
+- nft_reg_track_update(track, expr, priv->dreg, sizeof(u32));
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return false;
+-}
+-
+ static struct nft_expr_type nft_hash_type;
+ static const struct nft_expr_ops nft_jhash_ops = {
+ .type = &nft_hash_type,
+@@ -227,7 +193,6 @@ static const struct nft_expr_ops nft_jha
+ .eval = nft_jhash_eval,
+ .init = nft_jhash_init,
+ .dump = nft_jhash_dump,
+- .reduce = nft_jhash_reduce,
+ };
+
+ static const struct nft_expr_ops nft_symhash_ops = {
+@@ -236,7 +201,6 @@ static const struct nft_expr_ops nft_sym
+ .eval = nft_symhash_eval,
+ .init = nft_symhash_init,
+ .dump = nft_symhash_dump,
+- .reduce = nft_symhash_reduce,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_immediate.c
++++ b/net/netfilter/nft_immediate.c
+@@ -320,17 +320,6 @@ static bool nft_immediate_offload_action
+ return false;
+ }
+
+-static bool nft_immediate_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_immediate_expr *priv = nft_expr_priv(expr);
+-
+- if (priv->dreg != NFT_REG_VERDICT)
+- nft_reg_track_cancel(track, priv->dreg, priv->dlen);
+-
+- return false;
+-}
+-
+ static const struct nft_expr_ops nft_imm_ops = {
+ .type = &nft_imm_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_immediate_expr)),
+@@ -341,7 +330,6 @@ static const struct nft_expr_ops nft_imm
+ .destroy = nft_immediate_destroy,
+ .dump = nft_immediate_dump,
+ .validate = nft_immediate_validate,
+- .reduce = nft_immediate_reduce,
+ .offload = nft_immediate_offload,
+ .offload_action = nft_immediate_offload_action,
+ };
+--- a/net/netfilter/nft_last.c
++++ b/net/netfilter/nft_last.c
+@@ -125,7 +125,6 @@ static const struct nft_expr_ops nft_las
+ .destroy = nft_last_destroy,
+ .clone = nft_last_clone,
+ .dump = nft_last_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ struct nft_expr_type nft_last_type __read_mostly = {
+--- a/net/netfilter/nft_limit.c
++++ b/net/netfilter/nft_limit.c
+@@ -243,7 +243,6 @@ static const struct nft_expr_ops nft_lim
+ .destroy = nft_limit_pkts_destroy,
+ .clone = nft_limit_pkts_clone,
+ .dump = nft_limit_pkts_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static void nft_limit_bytes_eval(const struct nft_expr *expr,
+@@ -299,7 +298,6 @@ static const struct nft_expr_ops nft_lim
+ .dump = nft_limit_bytes_dump,
+ .clone = nft_limit_bytes_clone,
+ .destroy = nft_limit_bytes_destroy,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_log.c
++++ b/net/netfilter/nft_log.c
+@@ -291,7 +291,6 @@ static const struct nft_expr_ops nft_log
+ .init = nft_log_init,
+ .destroy = nft_log_destroy,
+ .dump = nft_log_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_log_type __read_mostly = {
+--- a/net/netfilter/nft_lookup.c
++++ b/net/netfilter/nft_lookup.c
+@@ -269,17 +269,6 @@ static int nft_lookup_validate(const str
+ return 0;
+ }
+
+-static bool nft_lookup_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_lookup *priv = nft_expr_priv(expr);
+-
+- if (priv->set->flags & NFT_SET_MAP)
+- nft_reg_track_cancel(track, priv->dreg, priv->set->dlen);
+-
+- return false;
+-}
+-
+ static const struct nft_expr_ops nft_lookup_ops = {
+ .type = &nft_lookup_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_lookup)),
+@@ -290,7 +279,6 @@ static const struct nft_expr_ops nft_loo
+ .destroy = nft_lookup_destroy,
+ .dump = nft_lookup_dump,
+ .validate = nft_lookup_validate,
+- .reduce = nft_lookup_reduce,
+ };
+
+ struct nft_expr_type nft_lookup_type __read_mostly = {
+--- a/net/netfilter/nft_masq.c
++++ b/net/netfilter/nft_masq.c
+@@ -143,7 +143,6 @@ static const struct nft_expr_ops nft_mas
+ .destroy = nft_masq_ipv4_destroy,
+ .dump = nft_masq_dump,
+ .validate = nft_masq_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_masq_ipv4_type __read_mostly = {
+@@ -171,7 +170,6 @@ static const struct nft_expr_ops nft_mas
+ .destroy = nft_masq_ipv6_destroy,
+ .dump = nft_masq_dump,
+ .validate = nft_masq_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_masq_ipv6_type __read_mostly = {
+@@ -213,7 +211,6 @@ static const struct nft_expr_ops nft_mas
+ .destroy = nft_masq_inet_destroy,
+ .dump = nft_masq_dump,
+ .validate = nft_masq_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_masq_inet_type __read_mostly = {
+--- a/net/netfilter/nft_meta.c
++++ b/net/netfilter/nft_meta.c
+@@ -742,60 +742,16 @@ static int nft_meta_get_offload(struct n
+ return 0;
+ }
+
+-bool nft_meta_get_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_meta *priv = nft_expr_priv(expr);
+- const struct nft_meta *meta;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- meta = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->key != meta->key ||
+- priv->dreg != meta->dreg) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return nft_expr_reduce_bitwise(track, expr);
+-}
+-EXPORT_SYMBOL_GPL(nft_meta_get_reduce);
+-
+ static const struct nft_expr_ops nft_meta_get_ops = {
+ .type = &nft_meta_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_meta)),
+ .eval = nft_meta_get_eval,
+ .init = nft_meta_get_init,
+ .dump = nft_meta_get_dump,
+- .reduce = nft_meta_get_reduce,
+ .validate = nft_meta_get_validate,
+ .offload = nft_meta_get_offload,
+ };
+
+-static bool nft_meta_set_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- int i;
+-
+- for (i = 0; i < NFT_REG32_NUM; i++) {
+- if (!track->regs[i].selector)
+- continue;
+-
+- if (track->regs[i].selector->ops != &nft_meta_get_ops)
+- continue;
+-
+- __nft_reg_track_cancel(track, i);
+- }
+-
+- return false;
+-}
+-
+ static const struct nft_expr_ops nft_meta_set_ops = {
+ .type = &nft_meta_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_meta)),
+@@ -803,7 +759,6 @@ static const struct nft_expr_ops nft_met
+ .init = nft_meta_set_init,
+ .destroy = nft_meta_set_destroy,
+ .dump = nft_meta_set_dump,
+- .reduce = nft_meta_set_reduce,
+ .validate = nft_meta_set_validate,
+ };
+
+--- a/net/netfilter/nft_nat.c
++++ b/net/netfilter/nft_nat.c
+@@ -320,7 +320,6 @@ static const struct nft_expr_ops nft_nat
+ .destroy = nft_nat_destroy,
+ .dump = nft_nat_dump,
+ .validate = nft_nat_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_nat_type __read_mostly = {
+@@ -351,7 +350,6 @@ static const struct nft_expr_ops nft_nat
+ .destroy = nft_nat_destroy,
+ .dump = nft_nat_dump,
+ .validate = nft_nat_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_inet_nat_type __read_mostly = {
+--- a/net/netfilter/nft_numgen.c
++++ b/net/netfilter/nft_numgen.c
+@@ -84,16 +84,6 @@ err:
+ return err;
+ }
+
+-static bool nft_ng_inc_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_ng_inc *priv = nft_expr_priv(expr);
+-
+- nft_reg_track_cancel(track, priv->dreg, NFT_REG32_SIZE);
+-
+- return false;
+-}
+-
+ static int nft_ng_dump(struct sk_buff *skb, enum nft_registers dreg,
+ u32 modulus, enum nft_ng_types type, u32 offset)
+ {
+@@ -178,16 +168,6 @@ static int nft_ng_random_dump(struct sk_
+ priv->offset);
+ }
+
+-static bool nft_ng_random_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_ng_random *priv = nft_expr_priv(expr);
+-
+- nft_reg_track_cancel(track, priv->dreg, NFT_REG32_SIZE);
+-
+- return false;
+-}
+-
+ static struct nft_expr_type nft_ng_type;
+ static const struct nft_expr_ops nft_ng_inc_ops = {
+ .type = &nft_ng_type,
+@@ -196,7 +176,6 @@ static const struct nft_expr_ops nft_ng_
+ .init = nft_ng_inc_init,
+ .destroy = nft_ng_inc_destroy,
+ .dump = nft_ng_inc_dump,
+- .reduce = nft_ng_inc_reduce,
+ };
+
+ static const struct nft_expr_ops nft_ng_random_ops = {
+@@ -205,7 +184,6 @@ static const struct nft_expr_ops nft_ng_
+ .eval = nft_ng_random_eval,
+ .init = nft_ng_random_init,
+ .dump = nft_ng_random_dump,
+- .reduce = nft_ng_random_reduce,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_objref.c
++++ b/net/netfilter/nft_objref.c
+@@ -123,7 +123,6 @@ static const struct nft_expr_ops nft_obj
+ .deactivate = nft_objref_deactivate,
+ .dump = nft_objref_dump,
+ .validate = nft_objref_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ struct nft_objref_map {
+@@ -245,7 +244,6 @@ static const struct nft_expr_ops nft_obj
+ .destroy = nft_objref_map_destroy,
+ .dump = nft_objref_map_dump,
+ .validate = nft_objref_map_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_osf.c
++++ b/net/netfilter/nft_osf.c
+@@ -131,30 +131,6 @@ static int nft_osf_validate(const struct
+ return nft_chain_validate_hooks(ctx->chain, hooks);
+ }
+
+-static bool nft_osf_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- struct nft_osf *priv = nft_expr_priv(expr);
+- struct nft_osf *osf;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, NFT_OSF_MAXGENRELEN);
+- return false;
+- }
+-
+- osf = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->flags != osf->flags ||
+- priv->ttl != osf->ttl) {
+- nft_reg_track_update(track, expr, priv->dreg, NFT_OSF_MAXGENRELEN);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return false;
+-}
+-
+ static struct nft_expr_type nft_osf_type;
+ static const struct nft_expr_ops nft_osf_op = {
+ .eval = nft_osf_eval,
+@@ -163,7 +139,6 @@ static const struct nft_expr_ops nft_osf
+ .dump = nft_osf_dump,
+ .type = &nft_osf_type,
+ .validate = nft_osf_validate,
+- .reduce = nft_osf_reduce,
+ };
+
+ static struct nft_expr_type nft_osf_type __read_mostly = {
+--- a/net/netfilter/nft_payload.c
++++ b/net/netfilter/nft_payload.c
+@@ -250,31 +250,6 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_payload_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_payload *priv = nft_expr_priv(expr);
+- const struct nft_payload *payload;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- payload = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->base != payload->base ||
+- priv->offset != payload->offset ||
+- priv->len != payload->len) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static bool nft_payload_offload_mask(struct nft_offload_reg *reg,
+ u32 priv_len, u32 field_len)
+ {
+@@ -578,7 +553,6 @@ static const struct nft_expr_ops nft_pay
+ .eval = nft_payload_eval,
+ .init = nft_payload_init,
+ .dump = nft_payload_dump,
+- .reduce = nft_payload_reduce,
+ .offload = nft_payload_offload,
+ };
+
+@@ -588,7 +562,6 @@ const struct nft_expr_ops nft_payload_fa
+ .eval = nft_payload_eval,
+ .init = nft_payload_init,
+ .dump = nft_payload_dump,
+- .reduce = nft_payload_reduce,
+ .offload = nft_payload_offload,
+ };
+
+@@ -1008,32 +981,12 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_payload_set_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- int i;
+-
+- for (i = 0; i < NFT_REG32_NUM; i++) {
+- if (!track->regs[i].selector)
+- continue;
+-
+- if (track->regs[i].selector->ops != &nft_payload_ops &&
+- track->regs[i].selector->ops != &nft_payload_fast_ops)
+- continue;
+-
+- __nft_reg_track_cancel(track, i);
+- }
+-
+- return false;
+-}
+-
+ static const struct nft_expr_ops nft_payload_set_ops = {
+ .type = &nft_payload_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_payload_set)),
+ .eval = nft_payload_set_eval,
+ .init = nft_payload_set_init,
+ .dump = nft_payload_set_dump,
+- .reduce = nft_payload_set_reduce,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_queue.c
++++ b/net/netfilter/nft_queue.c
+@@ -191,7 +191,6 @@ static const struct nft_expr_ops nft_que
+ .init = nft_queue_init,
+ .dump = nft_queue_dump,
+ .validate = nft_queue_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops nft_queue_sreg_ops = {
+@@ -201,7 +200,6 @@ static const struct nft_expr_ops nft_que
+ .init = nft_queue_sreg_init,
+ .dump = nft_queue_sreg_dump,
+ .validate = nft_queue_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_quota.c
++++ b/net/netfilter/nft_quota.c
+@@ -266,7 +266,6 @@ static const struct nft_expr_ops nft_quo
+ .destroy = nft_quota_destroy,
+ .clone = nft_quota_clone,
+ .dump = nft_quota_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_quota_type __read_mostly = {
+--- a/net/netfilter/nft_range.c
++++ b/net/netfilter/nft_range.c
+@@ -138,7 +138,6 @@ static const struct nft_expr_ops nft_ran
+ .eval = nft_range_eval,
+ .init = nft_range_init,
+ .dump = nft_range_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ struct nft_expr_type nft_range_type __read_mostly = {
+--- a/net/netfilter/nft_redir.c
++++ b/net/netfilter/nft_redir.c
+@@ -146,7 +146,6 @@ static const struct nft_expr_ops nft_red
+ .destroy = nft_redir_ipv4_destroy,
+ .dump = nft_redir_dump,
+ .validate = nft_redir_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_redir_ipv4_type __read_mostly = {
+@@ -174,7 +173,6 @@ static const struct nft_expr_ops nft_red
+ .destroy = nft_redir_ipv6_destroy,
+ .dump = nft_redir_dump,
+ .validate = nft_redir_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_redir_ipv6_type __read_mostly = {
+@@ -203,7 +201,6 @@ static const struct nft_expr_ops nft_red
+ .destroy = nft_redir_inet_destroy,
+ .dump = nft_redir_dump,
+ .validate = nft_redir_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_redir_inet_type __read_mostly = {
+--- a/net/netfilter/nft_reject_inet.c
++++ b/net/netfilter/nft_reject_inet.c
+@@ -79,7 +79,6 @@ static const struct nft_expr_ops nft_rej
+ .init = nft_reject_init,
+ .dump = nft_reject_dump,
+ .validate = nft_reject_inet_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_reject_inet_type __read_mostly = {
+--- a/net/netfilter/nft_reject_netdev.c
++++ b/net/netfilter/nft_reject_netdev.c
+@@ -158,7 +158,6 @@ static const struct nft_expr_ops nft_rej
+ .init = nft_reject_init,
+ .dump = nft_reject_dump,
+ .validate = nft_reject_netdev_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_reject_netdev_type __read_mostly = {
+--- a/net/netfilter/nft_rt.c
++++ b/net/netfilter/nft_rt.c
+@@ -195,7 +195,6 @@ static const struct nft_expr_ops nft_rt_
+ .init = nft_rt_get_init,
+ .dump = nft_rt_get_dump,
+ .validate = nft_rt_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ struct nft_expr_type nft_rt_type __read_mostly = {
+--- a/net/netfilter/nft_socket.c
++++ b/net/netfilter/nft_socket.c
+@@ -249,31 +249,6 @@ static int nft_socket_dump(struct sk_buf
+ return 0;
+ }
+
+-static bool nft_socket_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_socket *priv = nft_expr_priv(expr);
+- const struct nft_socket *socket;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- socket = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->key != socket->key ||
+- priv->dreg != socket->dreg ||
+- priv->level != socket->level) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static int nft_socket_validate(const struct nft_ctx *ctx,
+ const struct nft_expr *expr)
+ {
+@@ -296,7 +271,6 @@ static const struct nft_expr_ops nft_soc
+ .init = nft_socket_init,
+ .dump = nft_socket_dump,
+ .validate = nft_socket_validate,
+- .reduce = nft_socket_reduce,
+ };
+
+ static struct nft_expr_type nft_socket_type __read_mostly = {
+--- a/net/netfilter/nft_synproxy.c
++++ b/net/netfilter/nft_synproxy.c
+@@ -290,7 +290,6 @@ static const struct nft_expr_ops nft_syn
+ .dump = nft_synproxy_dump,
+ .type = &nft_synproxy_type,
+ .validate = nft_synproxy_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_synproxy_type __read_mostly = {
+--- a/net/netfilter/nft_tproxy.c
++++ b/net/netfilter/nft_tproxy.c
+@@ -331,7 +331,6 @@ static const struct nft_expr_ops nft_tpr
+ .init = nft_tproxy_init,
+ .destroy = nft_tproxy_destroy,
+ .dump = nft_tproxy_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ .validate = nft_tproxy_validate,
+ };
+
+--- a/net/netfilter/nft_tunnel.c
++++ b/net/netfilter/nft_tunnel.c
+@@ -124,31 +124,6 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_tunnel_get_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_tunnel *priv = nft_expr_priv(expr);
+- const struct nft_tunnel *tunnel;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- tunnel = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->key != tunnel->key ||
+- priv->dreg != tunnel->dreg ||
+- priv->mode != tunnel->mode) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return false;
+-}
+-
+ static struct nft_expr_type nft_tunnel_type;
+ static const struct nft_expr_ops nft_tunnel_get_ops = {
+ .type = &nft_tunnel_type,
+@@ -156,7 +131,6 @@ static const struct nft_expr_ops nft_tun
+ .eval = nft_tunnel_get_eval,
+ .init = nft_tunnel_get_init,
+ .dump = nft_tunnel_get_dump,
+- .reduce = nft_tunnel_get_reduce,
+ };
+
+ static struct nft_expr_type nft_tunnel_type __read_mostly = {
+--- a/net/netfilter/nft_xfrm.c
++++ b/net/netfilter/nft_xfrm.c
+@@ -258,32 +258,6 @@ static int nft_xfrm_validate(const struc
+ return nft_chain_validate_hooks(ctx->chain, hooks);
+ }
+
+-static bool nft_xfrm_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_xfrm *priv = nft_expr_priv(expr);
+- const struct nft_xfrm *xfrm;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- xfrm = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->key != xfrm->key ||
+- priv->dreg != xfrm->dreg ||
+- priv->dir != xfrm->dir ||
+- priv->spnum != xfrm->spnum) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static struct nft_expr_type nft_xfrm_type;
+ static const struct nft_expr_ops nft_xfrm_get_ops = {
+ .type = &nft_xfrm_type,
+@@ -292,7 +266,6 @@ static const struct nft_expr_ops nft_xfr
+ .init = nft_xfrm_get_init,
+ .dump = nft_xfrm_get_dump,
+ .validate = nft_xfrm_validate,
+- .reduce = nft_xfrm_reduce,
+ };
+
+ static struct nft_expr_type nft_xfrm_type __read_mostly = {
--- /dev/null
+From stable+bounces-288425-greg=kroah.com@vger.kernel.org Thu Jul 23 19:06:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 12:54:17 -0400
+Subject: netfilter: nf_tables: Remove unused nft_reduce_is_readonly()
+To: stable@vger.kernel.org
+Cc: Yue Haibing <yuehaibing@huawei.com>, Simon Horman <horms@kernel.org>, Pablo Neira Ayuso <pablo@netfilter.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723165419.3390514-2-sashal@kernel.org>
+
+From: Yue Haibing <yuehaibing@huawei.com>
+
+[ Upstream commit bf6788742b8d6c73de441e088a71de7154f0d4aa ]
+
+Since commit 9e539c5b6d9c ("netfilter: nf_tables: disable expression
+reduction infra") this is unused.
+
+Signed-off-by: Yue Haibing <yuehaibing@huawei.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/netfilter/nf_tables.h | 5 -----
+ 1 file changed, 5 deletions(-)
+
+--- a/include/net/netfilter/nf_tables.h
++++ b/include/net/netfilter/nf_tables.h
+@@ -1954,11 +1954,6 @@ static inline u64 nft_net_tstamp(const s
+ #define __NFT_REDUCE_READONLY 1UL
+ #define NFT_REDUCE_READONLY (void *)__NFT_REDUCE_READONLY
+
+-static inline bool nft_reduce_is_readonly(const struct nft_expr *expr)
+-{
+- return expr->ops->reduce == NFT_REDUCE_READONLY;
+-}
+-
+ void nft_reg_track_update(struct nft_regs_track *track,
+ const struct nft_expr *expr, u8 dreg, u8 len);
+ void nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg, u8 len);
--- /dev/null
+From stable+bounces-288427-greg=kroah.com@vger.kernel.org Thu Jul 23 19:06:09 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 12:54:19 -0400
+Subject: netfilter: nft_fib: reject fib expression on the netdev egress hook
+To: stable@vger.kernel.org
+Cc: Theodor Arsenij Larionov-Trichkine <theodorlarionov@gmail.com>, Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723165419.3390514-4-sashal@kernel.org>
+
+From: Theodor Arsenij Larionov-Trichkine <theodorlarionov@gmail.com>
+
+[ Upstream commit d07955dd34ecae17d35d8c7d0a273a3fba653a8c ]
+
+A fib expression in a netdev egress base chain dereferences nft_in(pkt),
+NULL on the transmit path, causing a NULL pointer dereference at eval.
+nft_fib_validate() masks the hook with NF_INET_* values, but netdev hook
+numbers are a separate enum that aliases them (NF_NETDEV_EGRESS ==
+NF_INET_LOCAL_IN), so an egress chain passes validation and then faults.
+
+Add nft_fib_netdev_validate() that limits each result/flag to the netdev
+hook where the device it reads exists: the input-device cases (OIF,
+OIFNAME, ADDRTYPE with F_IIF) to ingress, the output-device case (ADDRTYPE
+with F_OIF) to egress, ADDRTYPE with no device flag to both. Also restrict
+nft_fib_validate() to NFPROTO_IPV4/IPV6/INET so its NF_INET_* masks are
+not applied to another family's hooks.
+
+Fixes: 42df6e1d221d ("netfilter: Introduce egress hook")
+Cc: stable@vger.kernel.org
+Link: https://lore.kernel.org/netfilter-devel/ajxsjcDOnwllMfoR@strlen.de/
+Signed-off-by: Theodor Arsenij Larionov-Trichkine <theodorlarionov@gmail.com>
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/netfilter/nft_fib.c | 9 +++++++++
+ net/netfilter/nft_fib_netdev.c | 29 ++++++++++++++++++++++++++++-
+ 2 files changed, 37 insertions(+), 1 deletion(-)
+
+--- a/net/netfilter/nft_fib.c
++++ b/net/netfilter/nft_fib.c
+@@ -31,6 +31,15 @@ int nft_fib_validate(const struct nft_ct
+ const struct nft_fib *priv = nft_expr_priv(expr);
+ unsigned int hooks;
+
++ switch (ctx->family) {
++ case NFPROTO_IPV4:
++ case NFPROTO_IPV6:
++ case NFPROTO_INET:
++ break;
++ default:
++ return -EOPNOTSUPP;
++ }
++
+ switch (priv->result) {
+ case NFT_FIB_RESULT_OIF:
+ case NFT_FIB_RESULT_OIFNAME:
+--- a/net/netfilter/nft_fib_netdev.c
++++ b/net/netfilter/nft_fib_netdev.c
+@@ -50,6 +50,33 @@ static void nft_fib_netdev_eval(const st
+ regs->verdict.code = NFT_BREAK;
+ }
+
++static int nft_fib_netdev_validate(const struct nft_ctx *ctx,
++ const struct nft_expr *expr)
++{
++ const struct nft_fib *priv = nft_expr_priv(expr);
++ unsigned int hooks;
++
++ switch (priv->result) {
++ case NFT_FIB_RESULT_OIF:
++ case NFT_FIB_RESULT_OIFNAME:
++ hooks = (1 << NF_NETDEV_INGRESS);
++ break;
++ case NFT_FIB_RESULT_ADDRTYPE:
++ if (priv->flags & NFTA_FIB_F_IIF)
++ hooks = (1 << NF_NETDEV_INGRESS);
++ else if (priv->flags & NFTA_FIB_F_OIF)
++ hooks = (1 << NF_NETDEV_EGRESS);
++ else
++ hooks = (1 << NF_NETDEV_INGRESS) |
++ (1 << NF_NETDEV_EGRESS);
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return nft_chain_validate_hooks(ctx->chain, hooks);
++}
++
+ static struct nft_expr_type nft_fib_netdev_type;
+ static const struct nft_expr_ops nft_fib_netdev_ops = {
+ .type = &nft_fib_netdev_type,
+@@ -57,7 +84,7 @@ static const struct nft_expr_ops nft_fib
+ .eval = nft_fib_netdev_eval,
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+- .validate = nft_fib_validate,
++ .validate = nft_fib_netdev_validate,
+ };
+
+ static struct nft_expr_type nft_fib_netdev_type __read_mostly = {
--- /dev/null
+From stable+bounces-288530-greg=kroah.com@vger.kernel.org Thu Jul 23 22:01:08 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 15:58:59 -0400
+Subject: NFSD: pass nfsd_file to nfsd_iter_read()
+To: stable@vger.kernel.org
+Cc: Mike Snitzer <snitzer@kernel.org>, Jeff Layton <jlayton@kernel.org>, NeilBrown <neil@brown.name>, Christoph Hellwig <hch@lst.de>, Chuck Lever <chuck.lever@oracle.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723195904.3685200-1-sashal@kernel.org>
+
+From: Mike Snitzer <snitzer@kernel.org>
+
+[ Upstream commit 803bc849f0039291f546ba0e2237faebeb5c073e ]
+
+Prepare for nfsd_iter_read() to use the DIO alignment stored in
+nfsd_file by passing the nfsd_file to nfsd_iter_read() rather than
+just the file which is associaed with the nfsd_file.
+
+This means nfsd4_encode_readv() now also needs the nfsd_file rather
+than the file. Instead of changing the file arg to be the nfsd_file,
+we discard the file arg as the nfsd_file (and indeed the file) is
+already available via the "read" argument.
+
+Signed-off-by: Mike Snitzer <snitzer@kernel.org>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Reviewed-by: NeilBrown <neil@brown.name>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+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>
+---
+ fs/nfsd/nfs4xdr.c | 8 ++++----
+ fs/nfsd/vfs.c | 7 ++++---
+ fs/nfsd/vfs.h | 2 +-
+ 3 files changed, 9 insertions(+), 8 deletions(-)
+
+--- a/fs/nfsd/nfs4xdr.c
++++ b/fs/nfsd/nfs4xdr.c
+@@ -4384,7 +4384,7 @@ out_err:
+
+ static __be32 nfsd4_encode_readv(struct nfsd4_compoundres *resp,
+ struct nfsd4_read *read,
+- struct file *file, unsigned long maxcount)
++ unsigned long maxcount)
+ {
+ struct xdr_stream *xdr = resp->xdr;
+ unsigned int base = xdr->buf->page_len & ~PAGE_MASK;
+@@ -4395,7 +4395,7 @@ static __be32 nfsd4_encode_readv(struct
+ if (xdr_reserve_space_vec(xdr, maxcount) < 0)
+ return nfserr_resource;
+
+- nfserr = nfsd_iter_read(resp->rqstp, read->rd_fhp, file,
++ nfserr = nfsd_iter_read(resp->rqstp, read->rd_fhp, read->rd_nf,
+ read->rd_offset, &maxcount, base,
+ &read->rd_eof);
+ read->rd_length = maxcount;
+@@ -4444,7 +4444,7 @@ nfsd4_encode_read(struct nfsd4_compoundr
+ if (file->f_op->splice_read && splice_ok)
+ nfserr = nfsd4_encode_splice_read(resp, read, file, maxcount);
+ else
+- nfserr = nfsd4_encode_readv(resp, read, file, maxcount);
++ nfserr = nfsd4_encode_readv(resp, read, maxcount);
+ if (nfserr) {
+ xdr_truncate_encode(xdr, starting_len);
+ return nfserr;
+@@ -5311,7 +5311,7 @@ nfsd4_encode_read_plus_data(struct nfsd4
+ if (file->f_op->splice_read && splice_ok)
+ nfserr = nfsd4_encode_splice_read(resp, read, file, maxcount);
+ else
+- nfserr = nfsd4_encode_readv(resp, read, file, maxcount);
++ nfserr = nfsd4_encode_readv(resp, read, maxcount);
+ if (nfserr)
+ return nfserr;
+
+--- a/fs/nfsd/vfs.c
++++ b/fs/nfsd/vfs.c
+@@ -1067,7 +1067,7 @@ __be32 nfsd_splice_read(struct svc_rqst
+ * nfsd_iter_read - Perform a VFS read using an iterator
+ * @rqstp: RPC transaction context
+ * @fhp: file handle of file to be read
+- * @file: opened struct file of file to be read
++ * @nf: opened struct nfsd_file of file to be read
+ * @offset: starting byte offset
+ * @count: IN: requested number of bytes; OUT: number of bytes read
+ * @base: offset in first page of read buffer
+@@ -1080,9 +1080,10 @@ __be32 nfsd_splice_read(struct svc_rqst
+ * returned.
+ */
+ __be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
+- struct file *file, loff_t offset, unsigned long *count,
++ struct nfsd_file *nf, loff_t offset, unsigned long *count,
+ unsigned int base, u32 *eof)
+ {
++ struct file *file = nf->nf_file;
+ unsigned long v, total;
+ struct iov_iter iter;
+ loff_t ppos = offset;
+@@ -1283,7 +1284,7 @@ __be32 nfsd_read(struct svc_rqst *rqstp,
+ if (file->f_op->splice_read && nfsd_read_splice_ok(rqstp))
+ err = nfsd_splice_read(rqstp, fhp, file, offset, count, eof);
+ else
+- err = nfsd_iter_read(rqstp, fhp, file, offset, count, 0, eof);
++ err = nfsd_iter_read(rqstp, fhp, nf, offset, count, 0, eof);
+
+ nfsd_file_put(nf);
+ trace_nfsd_read_done(rqstp, fhp, offset, *count);
+--- a/fs/nfsd/vfs.h
++++ b/fs/nfsd/vfs.h
+@@ -122,7 +122,7 @@ __be32 nfsd_splice_read(struct svc_rqst
+ unsigned long *count,
+ u32 *eof);
+ __be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
+- struct file *file, loff_t offset,
++ struct nfsd_file *nf, loff_t offset,
+ unsigned long *count, unsigned int base,
+ u32 *eof);
+ bool nfsd_read_splice_ok(struct svc_rqst *rqstp);
--- /dev/null
+From stable+bounces-289491-greg=kroah.com@vger.kernel.org Mon Jul 27 14:49:33 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:43:59 -0400
+Subject: nvmet-auth: reject short AUTH_RECEIVE buffers
+To: stable@vger.kernel.org
+Cc: Michael Bommarito <michael.bommarito@gmail.com>, Hannes Reinecke <hare@kernel.org>, Keith Busch <kbusch@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727124359.172046-2-sashal@kernel.org>
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit 779575bc35c687697ba69e904f2cd22e60112534 ]
+
+nvmet_execute_auth_receive() trusts the AUTH_RECEIVE allocation length
+after checking only that it is nonzero and matches the transfer length.
+In the SUCCESS1 and FAILURE1/default states, that lets a remote NVMe-oF
+initiator reach the fixed-size DH-HMAC-CHAP response builders with a
+kmalloc() buffer shorter than the response, so nvmet_auth_success1() and
+nvmet_auth_failure1() write past the allocation; both only WARN_ON the
+short length and then format the message anyway.
+
+Impact: A remote NVMe-oF initiator with access to an auth-enabled target
+can trigger a 16-byte heap out-of-bounds write via a one-byte
+AUTH_RECEIVE allocation length.
+
+Compute the minimum response length for the current DH-HMAC-CHAP step in
+nvmet_auth_receive_data_len() and report a zero data length when the
+host-supplied allocation length is shorter, so the existing zero-length
+check in nvmet_execute_auth_receive() rejects the command before any
+builder runs. The SUCCESS1 minimum is sizeof(struct
+nvmf_auth_dhchap_success1_data) plus the HMAC hash length, because the
+response hash is written into the rval[] flexible-array tail, so the
+minimum is state dependent rather than a flat sizeof. CHALLENGE keeps its
+existing variable-length guard in nvmet_auth_challenge().
+
+This is reachable only when in-band DH-HMAC-CHAP authentication is
+configured on the target.
+
+Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication")
+Cc: stable@vger.kernel.org
+Assisted-by: Codex:gpt-5-5-xhigh
+Assisted-by: Claude:claude-opus-4-8
+Reviewed-by: Hannes Reinecke <hare@kernel.org>
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
+Signed-off-by: Keith Busch <kbusch@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvme/target/fabrics-cmd-auth.c | 26 +++++++++++++++++++++++++-
+ 1 file changed, 25 insertions(+), 1 deletion(-)
+
+--- a/drivers/nvme/target/fabrics-cmd-auth.c
++++ b/drivers/nvme/target/fabrics-cmd-auth.c
+@@ -445,7 +445,31 @@ static void nvmet_auth_failure1(struct n
+
+ u32 nvmet_auth_receive_data_len(struct nvmet_req *req)
+ {
+- return le32_to_cpu(req->cmd->auth_receive.al);
++ struct nvmet_ctrl *ctrl = req->sq->ctrl;
++ u32 al = le32_to_cpu(req->cmd->auth_receive.al);
++ u32 min_len;
++
++ /*
++ * Reject too-short al before kmalloc(al), since the SUCCESS1 and
++ * FAILURE1/default builders write fixed response headers into it.
++ */
++ switch (req->sq->dhchap_step) {
++ case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE:
++ return al;
++ case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1:
++ min_len = sizeof(struct nvmf_auth_dhchap_success1_data);
++ if (req->sq->dhchap_c2)
++ min_len += nvme_auth_hmac_hash_len(ctrl->shash_id);
++ break;
++ default:
++ min_len = sizeof(struct nvmf_auth_dhchap_failure_data);
++ break;
++ }
++
++ if (al < min_len)
++ return 0;
++
++ return al;
+ }
+
+ void nvmet_execute_auth_receive(struct nvmet_req *req)
--- /dev/null
+From stable+bounces-289490-greg=kroah.com@vger.kernel.org Mon Jul 27 14:49:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:43:58 -0400
+Subject: nvmet: Introduce nvmet_req_transfer_len()
+To: stable@vger.kernel.org
+Cc: Damien Le Moal <dlemoal@kernel.org>, Christoph Hellwig <hch@lst.de>, Rick Wertenbroek <rick.wertenbroek@gmail.com>, Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>, Keith Busch <kbusch@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727124359.172046-1-sashal@kernel.org>
+
+From: Damien Le Moal <dlemoal@kernel.org>
+
+[ Upstream commit 43043c9b97258a008b3402cfbbf1c5d82151c77f ]
+
+Add the new function nvmet_req_transfer_len() to parse a request command
+to extract the transfer length of the command. This function
+implementation relies on multiple helper functions for parsing I/O
+commands (nvmet_io_cmd_transfer_len()), admin commands
+(nvmet_admin_cmd_data_len()) and fabrics connect commands
+(nvmet_connect_cmd_data_len).
+
+Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Tested-by: Rick Wertenbroek <rick.wertenbroek@gmail.com>
+Tested-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+Signed-off-by: Keith Busch <kbusch@kernel.org>
+Stable-dep-of: 779575bc35c6 ("nvmet-auth: reject short AUTH_RECEIVE buffers")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvme/target/admin-cmd.c | 21 ++++++++++++++++
+ drivers/nvme/target/core.c | 37 ++++++++++++++++++++++++++++
+ drivers/nvme/target/discovery.c | 14 ++++++++++
+ drivers/nvme/target/fabrics-cmd-auth.c | 14 +++++++++-
+ drivers/nvme/target/fabrics-cmd.c | 43 +++++++++++++++++++++++++++++++++
+ drivers/nvme/target/nvmet.h | 8 ++++++
+ 6 files changed, 135 insertions(+), 2 deletions(-)
+
+--- a/drivers/nvme/target/admin-cmd.c
++++ b/drivers/nvme/target/admin-cmd.c
+@@ -1008,6 +1008,27 @@ out:
+ nvmet_req_complete(req, status);
+ }
+
++u32 nvmet_admin_cmd_data_len(struct nvmet_req *req)
++{
++ struct nvme_command *cmd = req->cmd;
++
++ if (nvme_is_fabrics(cmd))
++ return nvmet_fabrics_admin_cmd_data_len(req);
++ if (nvmet_is_disc_subsys(nvmet_req_subsys(req)))
++ return nvmet_discovery_cmd_data_len(req);
++
++ switch (cmd->common.opcode) {
++ case nvme_admin_get_log_page:
++ return nvmet_get_log_page_len(cmd);
++ case nvme_admin_identify:
++ return NVME_IDENTIFY_DATA_SIZE;
++ case nvme_admin_get_features:
++ return nvmet_feat_data_len(req, le32_to_cpu(cmd->common.cdw10));
++ default:
++ return 0;
++ }
++}
++
+ u16 nvmet_parse_admin_cmd(struct nvmet_req *req)
+ {
+ struct nvme_command *cmd = req->cmd;
+--- a/drivers/nvme/target/core.c
++++ b/drivers/nvme/target/core.c
+@@ -891,6 +891,33 @@ static inline u16 nvmet_io_cmd_check_acc
+ return 0;
+ }
+
++static u32 nvmet_io_cmd_transfer_len(struct nvmet_req *req)
++{
++ struct nvme_command *cmd = req->cmd;
++ u32 metadata_len = 0;
++
++ if (nvme_is_fabrics(cmd))
++ return nvmet_fabrics_io_cmd_data_len(req);
++
++ if (!req->ns)
++ return 0;
++
++ switch (req->cmd->common.opcode) {
++ case nvme_cmd_read:
++ case nvme_cmd_write:
++ case nvme_cmd_zone_append:
++ if (req->sq->ctrl->pi_support && nvmet_ns_has_pi(req->ns))
++ metadata_len = nvmet_rw_metadata_len(req);
++ return nvmet_rw_data_len(req) + metadata_len;
++ case nvme_cmd_dsm:
++ return nvmet_dsm_len(req);
++ case nvme_cmd_zone_mgmt_recv:
++ return (le32_to_cpu(req->cmd->zmr.numd) + 1) << 2;
++ default:
++ return 0;
++ }
++}
++
+ static u16 nvmet_parse_io_cmd(struct nvmet_req *req)
+ {
+ struct nvme_command *cmd = req->cmd;
+@@ -1015,6 +1042,16 @@ void nvmet_req_uninit(struct nvmet_req *
+ }
+ EXPORT_SYMBOL_GPL(nvmet_req_uninit);
+
++size_t nvmet_req_transfer_len(struct nvmet_req *req)
++{
++ if (likely(req->sq->qid != 0))
++ return nvmet_io_cmd_transfer_len(req);
++ if (unlikely(!req->sq->ctrl))
++ return nvmet_connect_cmd_data_len(req);
++ return nvmet_admin_cmd_data_len(req);
++}
++EXPORT_SYMBOL_GPL(nvmet_req_transfer_len);
++
+ bool nvmet_check_transfer_len(struct nvmet_req *req, size_t len)
+ {
+ if (unlikely(len != req->transfer_len)) {
+--- a/drivers/nvme/target/discovery.c
++++ b/drivers/nvme/target/discovery.c
+@@ -373,6 +373,20 @@ static void nvmet_execute_disc_get_featu
+ nvmet_req_complete(req, stat);
+ }
+
++u32 nvmet_discovery_cmd_data_len(struct nvmet_req *req)
++{
++ struct nvme_command *cmd = req->cmd;
++
++ switch (cmd->common.opcode) {
++ case nvme_admin_get_log_page:
++ return nvmet_get_log_page_len(req->cmd);
++ case nvme_admin_identify:
++ return NVME_IDENTIFY_DATA_SIZE;
++ default:
++ return 0;
++ }
++}
++
+ u16 nvmet_parse_discovery_cmd(struct nvmet_req *req)
+ {
+ struct nvme_command *cmd = req->cmd;
+--- a/drivers/nvme/target/fabrics-cmd-auth.c
++++ b/drivers/nvme/target/fabrics-cmd-auth.c
+@@ -188,6 +188,11 @@ static u8 nvmet_auth_failure2(void *d)
+ return data->rescode_exp;
+ }
+
++u32 nvmet_auth_send_data_len(struct nvmet_req *req)
++{
++ return le32_to_cpu(req->cmd->auth_send.tl);
++}
++
+ void nvmet_execute_auth_send(struct nvmet_req *req)
+ {
+ struct nvmet_ctrl *ctrl = req->sq->ctrl;
+@@ -215,7 +220,7 @@ void nvmet_execute_auth_send(struct nvme
+ offsetof(struct nvmf_auth_send_command, spsp1);
+ goto done;
+ }
+- tl = le32_to_cpu(req->cmd->auth_send.tl);
++ tl = nvmet_auth_send_data_len(req);
+ if (!tl) {
+ status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+ req->error_loc =
+@@ -438,6 +443,11 @@ static void nvmet_auth_failure1(struct n
+ data->rescode_exp = req->sq->dhchap_status;
+ }
+
++u32 nvmet_auth_receive_data_len(struct nvmet_req *req)
++{
++ return le32_to_cpu(req->cmd->auth_receive.al);
++}
++
+ void nvmet_execute_auth_receive(struct nvmet_req *req)
+ {
+ struct nvmet_ctrl *ctrl = req->sq->ctrl;
+@@ -463,7 +473,7 @@ void nvmet_execute_auth_receive(struct n
+ offsetof(struct nvmf_auth_receive_command, spsp1);
+ goto done;
+ }
+- al = le32_to_cpu(req->cmd->auth_receive.al);
++ al = nvmet_auth_receive_data_len(req);
+ if (!al) {
+ status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+ req->error_loc =
+--- a/drivers/nvme/target/fabrics-cmd.c
++++ b/drivers/nvme/target/fabrics-cmd.c
+@@ -82,6 +82,22 @@ static void nvmet_execute_prop_get(struc
+ nvmet_req_complete(req, status);
+ }
+
++u32 nvmet_fabrics_admin_cmd_data_len(struct nvmet_req *req)
++{
++ struct nvme_command *cmd = req->cmd;
++
++ switch (cmd->fabrics.fctype) {
++#ifdef CONFIG_NVME_TARGET_AUTH
++ case nvme_fabrics_type_auth_send:
++ return nvmet_auth_send_data_len(req);
++ case nvme_fabrics_type_auth_receive:
++ return nvmet_auth_receive_data_len(req);
++#endif
++ default:
++ return 0;
++ }
++}
++
+ u16 nvmet_parse_fabrics_admin_cmd(struct nvmet_req *req)
+ {
+ struct nvme_command *cmd = req->cmd;
+@@ -111,6 +127,22 @@ u16 nvmet_parse_fabrics_admin_cmd(struct
+ return 0;
+ }
+
++u32 nvmet_fabrics_io_cmd_data_len(struct nvmet_req *req)
++{
++ struct nvme_command *cmd = req->cmd;
++
++ switch (cmd->fabrics.fctype) {
++#ifdef CONFIG_NVME_TARGET_AUTH
++ case nvme_fabrics_type_auth_send:
++ return nvmet_auth_send_data_len(req);
++ case nvme_fabrics_type_auth_receive:
++ return nvmet_auth_receive_data_len(req);
++#endif
++ default:
++ return 0;
++ }
++}
++
+ u16 nvmet_parse_fabrics_io_cmd(struct nvmet_req *req)
+ {
+ struct nvme_command *cmd = req->cmd;
+@@ -342,6 +374,17 @@ out_ctrl_put:
+ goto out;
+ }
+
++u32 nvmet_connect_cmd_data_len(struct nvmet_req *req)
++{
++ struct nvme_command *cmd = req->cmd;
++
++ if (!nvme_is_fabrics(cmd) ||
++ cmd->fabrics.fctype != nvme_fabrics_type_connect)
++ return 0;
++
++ return sizeof(struct nvmf_connect_data);
++}
++
+ u16 nvmet_parse_connect_cmd(struct nvmet_req *req)
+ {
+ struct nvme_command *cmd = req->cmd;
+--- a/drivers/nvme/target/nvmet.h
++++ b/drivers/nvme/target/nvmet.h
+@@ -466,18 +466,24 @@ void nvmet_start_keep_alive_timer(struct
+ void nvmet_stop_keep_alive_timer(struct nvmet_ctrl *ctrl);
+
+ u16 nvmet_parse_connect_cmd(struct nvmet_req *req);
++u32 nvmet_connect_cmd_data_len(struct nvmet_req *req);
+ void nvmet_bdev_set_limits(struct block_device *bdev, struct nvme_id_ns *id);
+ u16 nvmet_bdev_parse_io_cmd(struct nvmet_req *req);
+ u16 nvmet_file_parse_io_cmd(struct nvmet_req *req);
+ u16 nvmet_bdev_zns_parse_io_cmd(struct nvmet_req *req);
++u32 nvmet_admin_cmd_data_len(struct nvmet_req *req);
+ u16 nvmet_parse_admin_cmd(struct nvmet_req *req);
++u32 nvmet_discovery_cmd_data_len(struct nvmet_req *req);
+ u16 nvmet_parse_discovery_cmd(struct nvmet_req *req);
+ u16 nvmet_parse_fabrics_admin_cmd(struct nvmet_req *req);
++u32 nvmet_fabrics_admin_cmd_data_len(struct nvmet_req *req);
+ u16 nvmet_parse_fabrics_io_cmd(struct nvmet_req *req);
++u32 nvmet_fabrics_io_cmd_data_len(struct nvmet_req *req);
+
+ bool nvmet_req_init(struct nvmet_req *req, struct nvmet_cq *cq,
+ struct nvmet_sq *sq, const struct nvmet_fabrics_ops *ops);
+ void nvmet_req_uninit(struct nvmet_req *req);
++size_t nvmet_req_transfer_len(struct nvmet_req *req);
+ bool nvmet_check_transfer_len(struct nvmet_req *req, size_t len);
+ bool nvmet_check_data_len_lte(struct nvmet_req *req, size_t data_len);
+ void nvmet_req_complete(struct nvmet_req *req, u16 status);
+@@ -720,7 +726,9 @@ static inline void nvmet_req_bio_put(str
+ }
+
+ #ifdef CONFIG_NVME_TARGET_AUTH
++u32 nvmet_auth_send_data_len(struct nvmet_req *req);
+ void nvmet_execute_auth_send(struct nvmet_req *req);
++u32 nvmet_auth_receive_data_len(struct nvmet_req *req);
+ void nvmet_execute_auth_receive(struct nvmet_req *req);
+ int nvmet_auth_set_key(struct nvmet_host *host, const char *secret,
+ bool set_ctrl);
--- /dev/null
+From stable+bounces-290049-greg=kroah.com@vger.kernel.org Tue Jul 28 19:03:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 11:43:39 -0400
+Subject: octeontx2-af: cn10k: restrict VF LMTLINE sharing to its own PF
+To: stable@vger.kernel.org
+Cc: Junrui Luo <moonafterrain@outlook.com>, Yuhao Jiang <danisjiang@gmail.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728154339.4192777-1-sashal@kernel.org>
+
+From: Junrui Luo <moonafterrain@outlook.com>
+
+[ Upstream commit 8cdcf3d2caacdee7ddd363705fb4d93b0c1a0915 ]
+
+rvu_mbox_handler_lmtst_tbl_setup() uses req->base_pcifunc as a direct
+index into the LMT map table to read another function's LMTLINE
+physical base address and copy it into the caller's own LMT map table
+entry. The mailbox dispatcher authenticates req->hdr.pcifunc from the
+IRQ source, but req->base_pcifunc is a separate payload field and is
+not sanitized.
+
+Reject the request with -EPERM when a VF caller's base_pcifunc is not a
+valid function under its own PF. is_pf_func_valid() bounds the FUNC field
+to the PF's configured VF count, keeping the computed index inside the
+caller's own slot block.
+
+Fixes: 893ae97214c3 ("octeontx2-af: cn10k: Support configurable LMTST regions")
+Reported-by: Yuhao Jiang <danisjiang@gmail.com>
+Cc: stable@vger.kernel.org
+Signed-off-by: Junrui Luo <moonafterrain@outlook.com>
+Link: https://patch.msgid.link/SYBPR01MB78811656934E713B77DA6CEDAFE62@SYBPR01MB7881.ausprd01.prod.outlook.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+[ Dropped `rvu->pdev` argument from `rvu_get_pf()` calls and folded in the `is_pf_func_valid()` de-static plus its `rvu.h` declaration from commit 2156a29aecff. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/af/rvu.c | 2 +-
+ drivers/net/ethernet/marvell/octeontx2/af/rvu.h | 1 +
+ drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c | 9 +++++++++
+ 3 files changed, 11 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
+@@ -440,7 +440,7 @@ struct rvu_pfvf *rvu_get_pfvf(struct rvu
+ return &rvu->pf[rvu_get_pf(pcifunc)];
+ }
+
+-static bool is_pf_func_valid(struct rvu *rvu, u16 pcifunc)
++bool is_pf_func_valid(struct rvu *rvu, u16 pcifunc)
+ {
+ int pf, vf, nvfs;
+ u64 cfg;
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
+@@ -825,6 +825,7 @@ int rvu_get_pf(u16 pcifunc);
+ struct rvu_pfvf *rvu_get_pfvf(struct rvu *rvu, int pcifunc);
+ void rvu_get_pf_numvfs(struct rvu *rvu, int pf, int *numvfs, int *hwvf);
+ bool is_block_implemented(struct rvu_hwinfo *hw, int blkaddr);
++bool is_pf_func_valid(struct rvu *rvu, u16 pcifunc);
+ bool is_pffunc_map_valid(struct rvu *rvu, u16 pcifunc, int blktype);
+ int rvu_get_lf(struct rvu *rvu, struct rvu_block *block, u16 pcifunc, u16 slot);
+ int rvu_lf_reset(struct rvu *rvu, struct rvu_block *block, int lf);
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c
+@@ -178,6 +178,15 @@ int rvu_mbox_handler_lmtst_tbl_setup(str
+ * pcifunc (will be the one who is calling this mailbox).
+ */
+ if (req->base_pcifunc) {
++ /* A VF is untrusted and must not redirect its LMTLINE to
++ * another PF's region, so confine VF callers to their own PF.
++ */
++ if (is_vf(req->hdr.pcifunc) &&
++ (!is_pf_func_valid(rvu, req->base_pcifunc) ||
++ rvu_get_pf(req->hdr.pcifunc) !=
++ rvu_get_pf(req->base_pcifunc)))
++ return -EPERM;
++
+ /* Calculating the LMT table index equivalent to primary
+ * pcifunc.
+ */
--- /dev/null
+From stable+bounces-288043-greg=kroah.com@vger.kernel.org Wed Jul 22 20:12:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 14:07:47 -0400
+Subject: octeontx2: Annotate mmio regions as __iomem
+To: stable@vger.kernel.org
+Cc: Subbaraya Sundeep <sbhatta@marvell.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722180748.2202109-1-sashal@kernel.org>
+
+From: Subbaraya Sundeep <sbhatta@marvell.com>
+
+[ Upstream commit d0976b43956ee8c8bd093223df9115bfcf63dfe5 ]
+
+This patch removes unnecessary typecasts by marking the
+mbox_regions array as __iomem since it is used to store
+pointers to memory-mapped I/O (MMIO) regions. Also simplified
+the call to readq() in PF driver by removing redundant type casts.
+
+Signed-off-by: Subbaraya Sundeep <sbhatta@marvell.com>
+Link: https://patch.msgid.link/1749484309-3434-1-git-send-email-sbhatta@marvell.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: 0b352f04b9be ("octeontx2-vf: clear stale mailbox IRQ state before request_irq()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/af/rvu.c | 12 ++++++------
+ drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c | 3 +--
+ 2 files changed, 7 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
+@@ -2401,7 +2401,7 @@ static inline void rvu_afvf_mbox_up_hand
+ __rvu_mbox_up_handler(mwork, TYPE_AFVF);
+ }
+
+-static int rvu_get_mbox_regions(struct rvu *rvu, void **mbox_addr,
++static int rvu_get_mbox_regions(struct rvu *rvu, void __iomem **mbox_addr,
+ int num, int type, unsigned long *pf_bmap)
+ {
+ struct rvu_hwinfo *hw = rvu->hw;
+@@ -2426,7 +2426,7 @@ static int rvu_get_mbox_regions(struct r
+ bar4 = rvupf_read64(rvu, RVU_PF_VF_BAR4_ADDR);
+ bar4 += region * MBOX_SIZE;
+ }
+- mbox_addr[region] = (void *)ioremap_wc(bar4, MBOX_SIZE);
++ mbox_addr[region] = ioremap_wc(bar4, MBOX_SIZE);
+ if (!mbox_addr[region])
+ goto error;
+ }
+@@ -2449,7 +2449,7 @@ static int rvu_get_mbox_regions(struct r
+ RVU_AF_PF_BAR4_ADDR);
+ bar4 += region * MBOX_SIZE;
+ }
+- mbox_addr[region] = (void *)ioremap_wc(bar4, MBOX_SIZE);
++ mbox_addr[region] = ioremap_wc(bar4, MBOX_SIZE);
+ if (!mbox_addr[region])
+ goto error;
+ }
+@@ -2457,7 +2457,7 @@ static int rvu_get_mbox_regions(struct r
+
+ error:
+ while (region--)
+- iounmap((void __iomem *)mbox_addr[region]);
++ iounmap(mbox_addr[region]);
+ return -ENOMEM;
+ }
+
+@@ -2467,10 +2467,10 @@ static int rvu_mbox_init(struct rvu *rvu
+ void (mbox_up_handler)(struct work_struct *))
+ {
+ int err = -EINVAL, i, dir, dir_up;
++ void __iomem **mbox_regions;
+ void __iomem *reg_base;
+ struct rvu_work *mwork;
+ unsigned long *pf_bmap;
+- void **mbox_regions;
+ const char *name;
+ u64 cfg;
+
+@@ -2493,7 +2493,7 @@ static int rvu_mbox_init(struct rvu *rvu
+
+ mutex_init(&rvu->mbox_lock);
+
+- mbox_regions = kcalloc(num, sizeof(void *), GFP_KERNEL);
++ mbox_regions = kcalloc(num, sizeof(void __iomem *), GFP_KERNEL);
+ if (!mbox_regions) {
+ err = -ENOMEM;
+ goto free_bitmap;
+--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
+@@ -593,8 +593,7 @@ static int otx2_pfvf_mbox_init(struct ot
+ base = pci_resource_start(pf->pdev, PCI_MBOX_BAR_NUM) +
+ MBOX_SIZE;
+ else
+- base = readq((void __iomem *)((u64)pf->reg_base +
+- RVU_PF_VF_BAR4_ADDR));
++ base = readq(pf->reg_base + RVU_PF_VF_BAR4_ADDR);
+
+ hwbase = ioremap_wc(base, MBOX_SIZE * pf->total_vfs);
+ if (!hwbase) {
--- /dev/null
+From stable+bounces-288045-greg=kroah.com@vger.kernel.org Wed Jul 22 20:13:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 14:07:57 -0400
+Subject: octeontx2-pf: clear stale mailbox IRQ state before request_irq()
+To: stable@vger.kernel.org
+Cc: Runyu Xiao <runyu.xiao@seu.edu.cn>, Simon Horman <horms@kernel.org>, Ratheesh Kannoth <rkannoth@marvell.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722180757.2202348-2-sashal@kernel.org>
+
+From: Runyu Xiao <runyu.xiao@seu.edu.cn>
+
+[ Upstream commit f918554fb7246e89b98ef90abe80801f038258b3 ]
+
+otx2_register_mbox_intr() currently installs the PF mailbox IRQ handler
+before clearing stale mailbox interrupt state. The function itself then
+comments that the local interrupt bits must be cleared first to avoid
+spurious interrupts, but that clear happens only after request_irq() has
+already exposed the handler to irq delivery.
+
+A running system can reach this during PF mailbox interrupt registration
+while stale or latched RVU_PF_INT state is still present. If delivery
+happens in the request_irq()-to-clear window,
+otx2_pfaf_mbox_intr_handler() can run before local quiesce and touch
+the same pf->mbox and pf->mbox_wq carrier that probe and teardown later
+reuse or destroy.
+
+Move the stale mailbox interrupt clear ahead of request_irq(), but keep
+interrupt enabling after the handler is installed. This closes the
+pre-clear early-IRQ window without creating a new enable-before-handler
+window.
+
+Fixes: 5a6d7c9daef3 ("octeontx2-pf: Mailbox communication with AF")
+Cc: stable@vger.kernel.org
+Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Reviewed-by: Ratheesh Kannoth <rkannoth@marvell.com>
+Link: https://patch.msgid.link/20260611160014.3202224-2-runyu.xiao@seu.edu.cn
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
+@@ -1023,6 +1023,9 @@ static int otx2_register_mbox_intr(struc
+ char *irq_name;
+ int err;
+
++ /* Clear stale mailbox interrupt state before installing the handler. */
++ otx2_write64(pf, RVU_PF_INT, BIT_ULL(0));
++
+ /* Register mailbox interrupt handler */
+ irq_name = &hw->irq_name[RVU_PF_INT_VEC_AFPF_MBOX * NAME_SIZE];
+ snprintf(irq_name, NAME_SIZE, "RVUPFAF Mbox");
+@@ -1034,10 +1037,7 @@ static int otx2_register_mbox_intr(struc
+ return err;
+ }
+
+- /* Enable mailbox interrupt for msgs coming from AF.
+- * First clear to avoid spurious interrupts, if any.
+- */
+- otx2_write64(pf, RVU_PF_INT, BIT_ULL(0));
++ /* Enable mailbox interrupt for msgs coming from AF. */
+ otx2_write64(pf, RVU_PF_INT_ENA_W1S, BIT_ULL(0));
+
+ if (!probe_af)
--- /dev/null
+From stable+bounces-290199-greg=kroah.com@vger.kernel.org Wed Jul 29 02:31:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 20:30:53 -0400
+Subject: octeontx2-pf: fix SQB pointer leak on init failure
+To: stable@vger.kernel.org
+Cc: Dawei Feng <dawei.feng@seu.edu.cn>, Ratheesh Kannoth <rkannoth@marvell.com>, Paolo Abeni <pabeni@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729003053.889460-1-sashal@kernel.org>
+
+From: Dawei Feng <dawei.feng@seu.edu.cn>
+
+[ Upstream commit 62e7df6d042aeebd5efb581074e28865c04477be ]
+
+otx2_init_hw_resources() initializes SQ aura and pool resources before
+several later setup steps. On failure, err_free_sq_ptrs only frees SQB
+pages, leaving the per-SQ sqb_ptrs arrays behind.
+
+Use otx2_free_sq_res() for the SQ unwind path and let it free sqb_ptrs
+even when sq->sqe has not been allocated yet.
+
+The bug was first flagged by an experimental analysis tool we are
+developing for kernel memory-management bugs while analyzing
+v6.13-rc1. The tool is still under development and is not yet publicly
+available. Manual inspection confirms that the bug is still
+present in v7.1.1.
+
+An x86_64 allyesconfig build showed no new warnings. As we do not have an
+OcteonTX2 PF device and the corresponding AF mailbox setup to test with,
+no runtime testing was able to be performed.
+
+Fixes: caa2da34fd25 ("octeontx2-pf: Initialize and config queues")
+Cc: stable@vger.kernel.org
+Reviewed-by: Ratheesh Kannoth <rkannoth@marvell.com>
+Signed-off-by: Dawei Feng <dawei.feng@seu.edu.cn>
+Link: https://patch.msgid.link/20260630071625.349996-1-dawei.feng@seu.edu.cn
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c | 17 ++++++++---------
+ 1 file changed, 8 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
+@@ -1438,13 +1438,13 @@ static void otx2_free_sq_res(struct otx2
+ otx2_sq_free_sqbs(pf);
+ for (qidx = 0; qidx < otx2_get_total_tx_queues(pf); qidx++) {
+ sq = &qset->sq[qidx];
+- /* Skip freeing Qos queues if they are not initialized */
+- if (!sq->sqe)
+- continue;
+- qmem_free(pf->dev, sq->sqe);
+- qmem_free(pf->dev, sq->tso_hdrs);
+- qmem_free(pf->dev, sq->timestamps);
+- kfree(sq->sg);
++ /* sq->sqe is not initialized for unused QoS queues */
++ if (sq->sqe) {
++ qmem_free(pf->dev, sq->sqe);
++ qmem_free(pf->dev, sq->tso_hdrs);
++ qmem_free(pf->dev, sq->timestamps);
++ kfree(sq->sg);
++ }
+ kfree(sq->sqb_ptrs);
+ }
+ }
+@@ -1570,13 +1570,12 @@ static int otx2_init_hw_resources(struct
+ return err;
+
+ err_free_nix_queues:
+- otx2_free_sq_res(pf);
+ otx2_free_cq_res(pf);
+ otx2_ctx_disable(mbox, NIX_AQ_CTYPE_RQ, false);
+ err_free_txsch:
+ otx2_txschq_stop(pf);
+ err_free_sq_ptrs:
+- otx2_sq_free_sqbs(pf);
++ otx2_free_sq_res(pf);
+ err_free_rq_ptrs:
+ otx2_free_aura_ptr(pf, AURA_NIX_RQ);
+ otx2_ctx_disable(mbox, NPA_AQ_CTYPE_POOL, true);
--- /dev/null
+From stable+bounces-288042-greg=kroah.com@vger.kernel.org Wed Jul 22 20:12:46 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 14:07:48 -0400
+Subject: octeontx2-vf: clear stale mailbox IRQ state before request_irq()
+To: stable@vger.kernel.org
+Cc: Runyu Xiao <runyu.xiao@seu.edu.cn>, Simon Horman <horms@kernel.org>, Ratheesh Kannoth <rkannoth@marvell.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722180748.2202109-2-sashal@kernel.org>
+
+From: Runyu Xiao <runyu.xiao@seu.edu.cn>
+
+[ Upstream commit 0b352f04b9be2c83c0240aa6dae7257fefa90464 ]
+
+otx2vf_register_mbox_intr() currently installs the VF mailbox IRQ
+handler before clearing stale mailbox interrupt state. The code then says
+that local interrupt bits should be cleared first to avoid spurious
+interrupts, but that clear still happens only after request_irq() has
+already made the handler reachable.
+
+A running system can reach this during VF mailbox interrupt registration
+while stale or latched RVU_VF_INT state is still present. If delivery
+happens in the request_irq()-to-clear window,
+otx2vf_vfaf_mbox_intr_handler() can run before local quiesce and touch
+the same vf->mbox and vf->mbox_wq carrier that probe and teardown later
+reuse or destroy.
+
+Move the stale mailbox interrupt clear ahead of request_irq(), but keep
+interrupt enabling after the handler is installed. This closes the
+pre-clear early-IRQ window without creating a new enable-before-handler
+window.
+
+Fixes: 3184fb5ba96e ("octeontx2-vf: Virtual function driver support")
+Cc: stable@vger.kernel.org
+Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Reviewed-by: Ratheesh Kannoth <rkannoth@marvell.com>
+Link: https://patch.msgid.link/20260611160014.3202224-3-runyu.xiao@seu.edu.cn
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c | 13 ++++++++-----
+ 1 file changed, 8 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
++++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
+@@ -245,9 +245,15 @@ static int otx2vf_register_mbox_intr(str
+ {
+ struct otx2_hw *hw = &vf->hw;
+ struct msg_req *req;
++ u64 mbox_int_mask;
+ char *irq_name;
+ int err;
+
++ mbox_int_mask = BIT_ULL(0);
++
++ /* Clear stale mailbox interrupt state before installing the handler. */
++ otx2_write64(vf, RVU_VF_INT, mbox_int_mask);
++
+ /* Register mailbox interrupt handler */
+ irq_name = &hw->irq_name[RVU_VF_INT_VEC_MBOX * NAME_SIZE];
+ snprintf(irq_name, NAME_SIZE, "RVUVFAF Mbox");
+@@ -259,11 +265,8 @@ static int otx2vf_register_mbox_intr(str
+ return err;
+ }
+
+- /* Enable mailbox interrupt for msgs coming from PF.
+- * First clear to avoid spurious interrupts, if any.
+- */
+- otx2_write64(vf, RVU_VF_INT, BIT_ULL(0));
+- otx2_write64(vf, RVU_VF_INT_ENA_W1S, BIT_ULL(0));
++ /* Enable mailbox interrupt for msgs coming from PF. */
++ otx2_write64(vf, RVU_VF_INT_ENA_W1S, mbox_int_mask);
+
+ if (!probe_pf)
+ return 0;
--- /dev/null
+From stable+bounces-289568-greg=kroah.com@vger.kernel.org Mon Jul 27 15:43:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 09:40:26 -0400
+Subject: ovl: use linked upper dentry in copy-up tmpfile
+To: stable@vger.kernel.org
+Cc: Souvik Banerjee <souvik@amlalabs.com>, Amir Goldstein <amir73il@gmail.com>, Miklos Szeredi <mszeredi@redhat.com>, "Christian Brauner (Amutable)" <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727134026.589031-1-sashal@kernel.org>
+
+From: Souvik Banerjee <souvik@amlalabs.com>
+
+[ Upstream commit e348eecd4d8fa8d18a5157ff59f7be1dc59c5928 ]
+
+ovl_copy_up_tmpfile() stores the disconnected O_TMPFILE dentry as the
+overlay's upper dentry reference via ovl_inode_update(). vfs_tmpfile()
+allocated this dentry via d_alloc(parentpath->dentry, &slash_name), so
+d_name is "/" and d_parent is c->workdir. Local upper filesystems
+(ext4, btrfs, xfs, ...) immediately rename it to "#<inum>" via
+d_mark_tmpfile() inside their ->tmpfile() op; FUSE and virtiofs do
+not, so both fields stay that way. Neither identifies the destination
+directory and filename where ovl_do_link() actually linked the file.
+
+When the upper filesystem implements ->d_revalidate() (e.g. FUSE or
+virtiofs), ovl_revalidate_real() calls it with the dentry's parent
+inode and a snapshot of d_name. The server tries to look up "/" inside
+c->workdir, fails, and overlayfs reports -ESTALE.
+
+This causes persistent ESTALE errors for any file that was copied up via
+the tmpfile path, breaking dpkg, apt, and other tools that do
+rename-over-existing on overlayfs with a FUSE/virtiofs upper.
+
+Before commit 6b52243f633e ("ovl: fold copy-up helpers into callers"),
+the tmpfile copy-up path used a dedicated helper ovl_link_tmpfile()
+that captured the linked destination dentry returned by ovl_do_link():
+
+ err = ovl_do_link(temp, udir, upper);
+ ...
+ if (!err)
+ *newdentry = dget(upper);
+
+and published it via ovl_inode_update(d_inode(c->dentry), newdentry).
+The fold inlined ovl_do_link() into ovl_copy_up_tmpfile() but dropped
+the dget(upper) capture, and rewrote the publish line as
+ovl_inode_update(d_inode(c->dentry), dget(temp)) — where temp is the
+disconnected O_TMPFILE dentry.
+
+Fix by keeping a reference to the linked destination dentry after
+ovl_do_link() succeeds, and publishing that dentry at the existing
+ovl_inode_update() call site. The non-tmpfile/workdir path continues to
+publish the renamed temporary dentry.
+
+Reproducer:
+ - Mount overlayfs with virtiofs (or a FUSE fs whose server advertises
+ FUSE_TMPFILE) as upper
+ - Run: dpkg -i <any .deb>
+ - Observe: "error installing new file '...': Stale file handle"
+
+Fixes: 6b52243f633e ("ovl: fold copy-up helpers into callers")
+Cc: stable@vger.kernel.org # v4.20+
+Signed-off-by: Souvik Banerjee <souvik@amlalabs.com>
+Link: https://patch.msgid.link/20260501232735.2610824-1-souvik@amlalabs.com
+Reviewed-by: Amir Goldstein <amir73il@gmail.com>
+Reviewed-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+[ adapted scoped credential and creation helpers to explicit credential, locking, lookup, and cleanup handling ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/overlayfs/copy_up.c | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+--- a/fs/overlayfs/copy_up.c
++++ b/fs/overlayfs/copy_up.c
+@@ -864,7 +864,7 @@ static int ovl_copy_up_tmpfile(struct ov
+ {
+ struct ovl_fs *ofs = OVL_FS(c->dentry->d_sb);
+ struct inode *udir = d_inode(c->destdir);
+- struct dentry *temp, *upper;
++ struct dentry *temp, *upper, *newdentry = NULL;
+ struct file *tmpfile;
+ struct ovl_cu_creds cc;
+ int err;
+@@ -901,6 +901,14 @@ static int ovl_copy_up_tmpfile(struct ov
+ err = PTR_ERR(upper);
+ if (!IS_ERR(upper)) {
+ err = ovl_do_link(ofs, temp, udir, upper);
++ if (!err) {
++ /*
++ * Record the linked dentry -- not the disconnected
++ * O_TMPFILE dentry -- so that ->d_revalidate() on
++ * the upper fs sees the real parent/name.
++ */
++ newdentry = dget(upper);
++ }
+ dput(upper);
+ }
+ inode_unlock(udir);
+@@ -916,7 +924,7 @@ static int ovl_copy_up_tmpfile(struct ov
+
+ if (!c->metacopy)
+ ovl_set_upperdata(d_inode(c->dentry));
+- ovl_inode_update(d_inode(c->dentry), dget(temp));
++ ovl_inode_update(d_inode(c->dentry), newdentry);
+
+ out:
+ ovl_end_write(c->dentry);
--- /dev/null
+From stable+bounces-288601-greg=kroah.com@vger.kernel.org Fri Jul 24 03:25:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 21:24:56 -0400
+Subject: remoteproc: xlnx: Check remote core state
+To: stable@vger.kernel.org
+Cc: Tanmay Shah <tanmay.shah@amd.com>, Beleswar Padhi <b-padhi@ti.com>, Michal Simek <michal.simek@amd.com>, Mathieu Poirier <mathieu.poirier@linaro.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260724012456.3832700-1-sashal@kernel.org>
+
+From: Tanmay Shah <tanmay.shah@amd.com>
+
+[ Upstream commit a48df51d23138388900995add2854cda4aa68e55 ]
+
+The remote state is set to RPROC_DETACHED if the resource table is found
+in the memory. However, this can be wrong if the remote is not started,
+but firmware is still loaded in the memory. Use PM_GET_NODE_STATUS call
+to the firmware to request the state of the RPU node. If the RPU is
+actually out of reset and running, only then move the remote state to
+RPROC_DETACHED, otherwise keep the remote state to RPROC_OFFLINE.
+
+Signed-off-by: Tanmay Shah <tanmay.shah@amd.com>
+Fixes: bca4b02ef92e ("remoteproc: xlnx: Add attach detach support")
+Reviewed-by: Beleswar Padhi <b-padhi@ti.com>
+Acked-by: Michal Simek <michal.simek@amd.com>
+Cc: stable@vger.kernel.org
+Link: https://lore.kernel.org/r/20260428221855.313752-1-tanmay.shah@amd.com
+Signed-off-by: Mathieu Poirier <mathieu.poirier@linaro.org>
+[ replaced the unavailable zynqmp_pm_get_node_status() helper with a direct zynqmp_pm_invoke_fn() call and exported it for modular builds. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/firmware/xilinx/zynqmp.c | 1
+ drivers/remoteproc/xlnx_r5_remoteproc.c | 50 +++++++++++++++++++++++++-------
+ include/linux/firmware/xlnx-zynqmp.h | 13 ++++++++
+ 3 files changed, 54 insertions(+), 10 deletions(-)
+
+--- a/drivers/firmware/xilinx/zynqmp.c
++++ b/drivers/firmware/xilinx/zynqmp.c
+@@ -386,6 +386,7 @@ int zynqmp_pm_invoke_fn(u32 pm_api_id, u
+ return do_fw_call(ret_payload, 8, smc_arg[0], smc_arg[1], smc_arg[2], smc_arg[3],
+ smc_arg[4], smc_arg[5], smc_arg[6], smc_arg[7]);
+ }
++EXPORT_SYMBOL_GPL(zynqmp_pm_invoke_fn);
+
+ static u32 pm_api_version;
+ static u32 pm_tz_version;
+--- a/drivers/remoteproc/xlnx_r5_remoteproc.c
++++ b/drivers/remoteproc/xlnx_r5_remoteproc.c
+@@ -927,16 +927,6 @@ static struct zynqmp_r5_core *zynqmp_r5_
+ goto free_rproc;
+ }
+
+- /*
+- * If firmware is already available in the memory then move rproc state
+- * to DETACHED. Firmware can be preloaded via debugger or by any other
+- * agent (processors) in the system.
+- * If firmware isn't available in the memory and resource table isn't
+- * found, then rproc state remains OFFLINE.
+- */
+- if (!zynqmp_r5_get_rsc_table_va(r5_core))
+- r5_rproc->state = RPROC_DETACHED;
+-
+ r5_core->rproc = r5_rproc;
+ return r5_core;
+
+@@ -1189,6 +1179,7 @@ static int zynqmp_r5_core_init(struct zy
+ {
+ struct device *dev = cluster->dev;
+ struct zynqmp_r5_core *r5_core;
++ u32 payload[PAYLOAD_ARG_CNT];
+ int ret = -EINVAL, i;
+
+ r5_core = cluster->r5_cores[0];
+@@ -1234,6 +1225,45 @@ static int zynqmp_r5_core_init(struct zy
+ ret = zynqmp_r5_get_sram_banks(r5_core);
+ if (ret)
+ return ret;
++
++ /*
++ * It is possible that firmware is loaded into the memory, but
++ * RPU (remote) is not running. In such case, RPU state will be
++ * moved to RPROC_DETACHED wrongfully. To avoid it first make
++ * sure RPU is power-on and out of reset before parsing for the
++ * resource table.
++ */
++ ret = zynqmp_pm_feature(PM_GET_NODE_STATUS);
++ if (ret < PM_API_VERSION_2)
++ ret = -EOPNOTSUPP;
++ else
++ ret = zynqmp_pm_invoke_fn(PM_GET_NODE_STATUS, payload, 1,
++ r5_core->pm_domain_id);
++ if (ret) {
++ dev_warn(r5_core->dev,
++ "failed to get rpu node status, err %d\n", ret);
++ continue;
++ }
++
++ /*
++ * If RPU state is power on and out of reset i.e. running, then
++ * assign RPROC_DETACHED state. If the RPU is not out of reset
++ * then do not attempt to attach to the remote processor.
++ */
++ if (payload[1] == PM_NODE_RUNNING) {
++ /*
++ * Not all the firmware that is running on the remote
++ * core is expected to have the resource table. The
++ * firmware might not use RPMsg at all, and in that case
++ * resource table becomes irrelevant. However, we still
++ * need to make sure that running core is not reported
++ * as offline. so do not decide remote core state based
++ * on the resource table availability
++ */
++ if (zynqmp_r5_get_rsc_table_va(r5_core))
++ dev_dbg(r5_core->dev, "rsc tbl not found\n");
++ r5_core->rproc->state = RPROC_DETACHED;
++ }
+ }
+
+ return 0;
+--- a/include/linux/firmware/xlnx-zynqmp.h
++++ b/include/linux/firmware/xlnx-zynqmp.h
+@@ -143,6 +143,7 @@ enum pm_api_cb_id {
+ enum pm_api_id {
+ PM_API_FEATURES = 0,
+ PM_GET_API_VERSION = 1,
++ PM_GET_NODE_STATUS = 3,
+ PM_REGISTER_NOTIFIER = 5,
+ PM_FORCE_POWERDOWN = 8,
+ PM_REQUEST_WAKEUP = 10,
+@@ -519,6 +520,18 @@ enum pm_gem_config_type {
+ };
+
+ /**
++ * enum pm_node_status - Device node status provided by xilpm fw
++ * @PM_NODE_UNUSED: Device is not used
++ * @PM_NODE_RUNNING: Device is power-on and out of reset
++ * @PM_NODE_HALT: Device is power-on but in the reset state
++ */
++enum pm_node_status {
++ PM_NODE_UNUSED = 0,
++ PM_NODE_RUNNING = 1,
++ PM_NODE_HALT = 12,
++};
++
++/**
+ * struct zynqmp_pm_query_data - PM query data
+ * @qid: query ID
+ * @arg1: Argument 1 of query data
--- /dev/null
+From stable+bounces-290948-greg=kroah.com@vger.kernel.org Thu Jul 30 03:03:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 21:01:33 -0400
+Subject: rxrpc: Allow the app to store private data on peer structs
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, Jakub Kicinski <kuba@kernel.org>, "David S. Miller" <davem@davemloft.net>, Eric Dumazet <edumazet@google.com>, Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>, linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, netdev@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730010137.1700032-4-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit f3a123b25429dc03508e3bf598373005220b7203 ]
+
+Provide a way for the application (e.g. the afs filesystem) to store
+private data on the rxrpc_peer structs for later retrieval via the call
+object.
+
+This will allow afs to store a pointer to the afs_server object on the
+rxrpc_peer struct, thereby obviating the need for afs to keep lookup tables
+by which it can associate an incoming call with server that transmitted it.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: Jakub Kicinski <kuba@kernel.org>
+cc: "David S. Miller" <davem@davemloft.net>
+cc: Eric Dumazet <edumazet@google.com>
+cc: Paolo Abeni <pabeni@redhat.com>
+cc: Simon Horman <horms@kernel.org>
+cc: linux-afs@lists.infradead.org
+cc: linux-fsdevel@vger.kernel.org
+cc: netdev@vger.kernel.org
+Link: https://lore.kernel.org/r/20250224234154.2014840-13-dhowells@redhat.com/ # v1
+Link: https://lore.kernel.org/r/20250310094206.801057-9-dhowells@redhat.com/ # v4
+Stable-dep-of: 330e2c514823 ("afs: Fix dynamic lookup to fail on cell lookup failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/af_rxrpc.h | 2 ++
+ net/rxrpc/ar-internal.h | 1 +
+ net/rxrpc/peer_object.c | 26 ++++++++++++++++++++++++++
+ 3 files changed, 29 insertions(+)
+
+--- a/include/net/af_rxrpc.h
++++ b/include/net/af_rxrpc.h
+@@ -74,6 +74,8 @@ struct rxrpc_peer *rxrpc_kernel_get_peer
+ struct rxrpc_peer *rxrpc_kernel_get_call_peer(struct socket *sock, struct rxrpc_call *call);
+ const struct sockaddr_rxrpc *rxrpc_kernel_remote_srx(const struct rxrpc_peer *peer);
+ const struct sockaddr *rxrpc_kernel_remote_addr(const struct rxrpc_peer *peer);
++unsigned long rxrpc_kernel_set_peer_data(struct rxrpc_peer *peer, unsigned long app_data);
++unsigned long rxrpc_kernel_get_peer_data(const struct rxrpc_peer *peer);
+ unsigned int rxrpc_kernel_get_srtt(const struct rxrpc_peer *);
+ int rxrpc_kernel_charge_accept(struct socket *sock, rxrpc_notify_rx_t notify_rx,
+ unsigned long user_call_ID, gfp_t gfp,
+--- a/net/rxrpc/ar-internal.h
++++ b/net/rxrpc/ar-internal.h
+@@ -333,6 +333,7 @@ struct rxrpc_peer {
+ struct hlist_head error_targets; /* targets for net error distribution */
+ struct rb_root service_conns; /* Service connections */
+ struct list_head keepalive_link; /* Link in net->peer_keepalive[] */
++ unsigned long app_data; /* Application data (e.g. afs_server) */
+ unsigned int last_tx_at; /* Last time packet sent here (time64_t LSW) */
+ seqlock_t service_conn_lock;
+ spinlock_t lock; /* access lock */
+--- a/net/rxrpc/peer_object.c
++++ b/net/rxrpc/peer_object.c
+@@ -510,3 +510,29 @@ const struct sockaddr *rxrpc_kernel_remo
+ (peer ? &peer->srx.transport : &rxrpc_null_addr.transport);
+ }
+ EXPORT_SYMBOL(rxrpc_kernel_remote_addr);
++
++/**
++ * rxrpc_kernel_set_peer_data - Set app-specific data on a peer.
++ * @peer: The peer to alter
++ * @app_data: The data to set
++ *
++ * Set the app-specific data on a peer. AF_RXRPC makes no effort to retain
++ * anything the data might refer to. The previous app_data is returned.
++ */
++unsigned long rxrpc_kernel_set_peer_data(struct rxrpc_peer *peer, unsigned long app_data)
++{
++ return xchg(&peer->app_data, app_data);
++}
++EXPORT_SYMBOL(rxrpc_kernel_set_peer_data);
++
++/**
++ * rxrpc_kernel_get_peer_data - Get app-specific data from a peer.
++ * @peer: The peer to query
++ *
++ * Retrieve the app-specific data from a peer.
++ */
++unsigned long rxrpc_kernel_get_peer_data(const struct rxrpc_peer *peer)
++{
++ return peer->app_data;
++}
++EXPORT_SYMBOL(rxrpc_kernel_get_peer_data);
--- /dev/null
+From stable+bounces-287940-greg=kroah.com@vger.kernel.org Wed Jul 22 18:32:15 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:08:02 -0400
+Subject: rxrpc: Don't need barrier for ->tx_bottom and ->acks_hard_ack
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722160805.1741176-2-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 6396b48ac0a77165f9c2c40ab03d6c8188c89739 ]
+
+We don't need a barrier for the ->tx_bottom value (which indicates the
+lowest sequence still in the transmission queue) and the ->acks_hard_ack
+value (which tracks the DATA packets hard-ack'd by the latest ACK packet
+received and thus indicates which DATA packets can now be discarded) as the
+app thread doesn't use either value as a reference to memory to access.
+Rather, the app thread merely uses these as a guide to how much space is
+available in the transmission queue
+
+Change the code to use READ/WRITE_ONCE() instead.
+
+Also, change rxrpc_check_tx_space() to use the same value for tx_bottom
+throughout.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+Link: https://patch.msgid.link/20241204074710.990092-18-dhowells@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/rxrpc/sendmsg.c | 8 +++++---
+ net/rxrpc/txbuf.c | 4 ++--
+ 2 files changed, 7 insertions(+), 5 deletions(-)
+
+--- a/net/rxrpc/sendmsg.c
++++ b/net/rxrpc/sendmsg.c
+@@ -93,9 +93,11 @@ no_wait:
+ */
+ static bool rxrpc_check_tx_space(struct rxrpc_call *call, rxrpc_seq_t *_tx_win)
+ {
++ rxrpc_seq_t tx_bottom = READ_ONCE(call->tx_bottom);
++
+ if (_tx_win)
+- *_tx_win = call->tx_bottom;
+- return call->tx_prepared - call->tx_bottom < 256;
++ *_tx_win = tx_bottom;
++ return call->tx_prepared - tx_bottom < 256;
+ }
+
+ /*
+@@ -137,7 +139,7 @@ static int rxrpc_wait_for_tx_window_wait
+ rtt = 2;
+
+ timeout = rtt;
+- tx_start = smp_load_acquire(&call->acks_hard_ack);
++ tx_start = READ_ONCE(call->acks_hard_ack);
+
+ for (;;) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+--- a/net/rxrpc/txbuf.c
++++ b/net/rxrpc/txbuf.c
+@@ -216,14 +216,14 @@ void rxrpc_shrink_call_tx_buffer(struct
+
+ while ((txb = list_first_entry_or_null(&call->tx_buffer,
+ struct rxrpc_txbuf, call_link))) {
+- hard_ack = smp_load_acquire(&call->acks_hard_ack);
++ hard_ack = call->acks_hard_ack;
+ if (before(hard_ack, txb->seq))
+ break;
+
+ if (txb->seq != call->tx_bottom + 1)
+ rxrpc_see_txbuf(txb, rxrpc_txbuf_see_out_of_step);
+ ASSERTCMP(txb->seq, ==, call->tx_bottom + 1);
+- smp_store_release(&call->tx_bottom, call->tx_bottom + 1);
++ WRITE_ONCE(call->tx_bottom, call->tx_bottom + 1);
+ list_del_rcu(&txb->call_link);
+
+ trace_rxrpc_txqueue(call, rxrpc_txqueue_dequeue);
--- /dev/null
+From stable+bounces-287939-greg=kroah.com@vger.kernel.org Wed Jul 22 18:32:12 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:08:01 -0400
+Subject: rxrpc: Fix CPU time starvation in I/O thread
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722160805.1741176-1-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 9e3cccd176b5ec6ff78693287fb03097e453e69c ]
+
+Starvation can happen in the rxrpc I/O thread because it goes back to the
+top of the I/O loop after it does any one thing without trying to give any
+other connection or call CPU time. Also, because it processes one call
+packet at a time, it tries to do the retransmission loop after each ACK
+without checking to see if there are other ACKs already in the queue that
+can update the SACK state.
+
+Fix this by:
+
+ (1) Add a received-packet queue on each call.
+
+ (2) Distribute packets from the master Rx queue to the individual call,
+ conn and error queues and 'poking' calls to add them to the attend
+ queue first thing in the I/O thread.
+
+ (3) Go through all the attention-seeking connections and calls before
+ going back to the top of the I/O thread. Each queue is extracted as a
+ whole and then gone through so that new additions to insert themselves
+ into the queue.
+
+ (4) Make the call event handler go through all the packets currently on
+ the call's rx_queue before transmitting and retransmitting DATA
+ packets.
+
+ (5) Drop the skb argument from the call event handler as this is now
+ replaced with the rx_queue. Instead, keep track of whether we
+ received a packet or an ACK for the tests that used to rely on that.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+Link: https://patch.msgid.link/20241204074710.990092-14-dhowells@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/trace/events/rxrpc.h | 2
+ net/rxrpc/ar-internal.h | 10 +++-
+ net/rxrpc/call_accept.c | 2
+ net/rxrpc/call_event.c | 31 +++++++-----
+ net/rxrpc/call_object.c | 2
+ net/rxrpc/conn_client.c | 12 ++--
+ net/rxrpc/input.c | 2
+ net/rxrpc/io_thread.c | 104 +++++++++++++++++++++----------------------
+ net/rxrpc/peer_event.c | 2
+ 9 files changed, 93 insertions(+), 74 deletions(-)
+
+--- a/include/trace/events/rxrpc.h
++++ b/include/trace/events/rxrpc.h
+@@ -121,6 +121,7 @@
+ EM(rxrpc_call_poke_conn_abort, "Conn-abort") \
+ EM(rxrpc_call_poke_error, "Error") \
+ EM(rxrpc_call_poke_idle, "Idle") \
++ EM(rxrpc_call_poke_rx_packet, "Rx-packet") \
+ EM(rxrpc_call_poke_set_timeout, "Set-timo") \
+ EM(rxrpc_call_poke_start, "Start") \
+ EM(rxrpc_call_poke_timer, "Timer") \
+@@ -139,6 +140,7 @@
+ EM(rxrpc_skb_new_error_report, "NEW error-rpt") \
+ EM(rxrpc_skb_new_jumbo_subpacket, "NEW jumbo-sub") \
+ EM(rxrpc_skb_new_unshared, "NEW unshared ") \
++ EM(rxrpc_skb_put_call_rx, "PUT call-rx ") \
+ EM(rxrpc_skb_put_conn_secured, "PUT conn-secd") \
+ EM(rxrpc_skb_put_conn_work, "PUT conn-work") \
+ EM(rxrpc_skb_put_error_report, "PUT error-rep") \
+--- a/net/rxrpc/ar-internal.h
++++ b/net/rxrpc/ar-internal.h
+@@ -683,6 +683,7 @@ struct rxrpc_call {
+
+ /* Received data tracking */
+ struct sk_buff_head recvmsg_queue; /* Queue of packets ready for recvmsg() */
++ struct sk_buff_head rx_queue; /* Queue of packets for this call to receive */
+ struct sk_buff_head rx_oos_queue; /* Queue of out of sequence packets */
+ void *rx_dec_buffer; /* Decryption buffer */
+ unsigned short rx_dec_bsize; /* rx_dec_buffer size */
+@@ -871,7 +872,7 @@ void rxrpc_propose_delay_ACK(struct rxrp
+ void rxrpc_shrink_call_tx_buffer(struct rxrpc_call *);
+ void rxrpc_resend(struct rxrpc_call *call, struct sk_buff *ack_skb);
+
+-bool rxrpc_input_call_event(struct rxrpc_call *call, struct sk_buff *skb);
++bool rxrpc_input_call_event(struct rxrpc_call *call);
+
+ /*
+ * call_object.c
+@@ -1320,6 +1321,13 @@ static inline bool after_eq(u32 seq1, u3
+ return (s32)(seq1 - seq2) >= 0;
+ }
+
++static inline void rxrpc_queue_rx_call_packet(struct rxrpc_call *call, struct sk_buff *skb)
++{
++ rxrpc_get_skb(skb, rxrpc_skb_get_call_rx);
++ __skb_queue_tail(&call->rx_queue, skb);
++ rxrpc_poke_call(call, rxrpc_call_poke_rx_packet);
++}
++
+ /*
+ * debug tracing
+ */
+--- a/net/rxrpc/call_accept.c
++++ b/net/rxrpc/call_accept.c
+@@ -415,7 +415,7 @@ bool rxrpc_new_incoming_call(struct rxrp
+ }
+
+ _leave(" = %p{%d}", call, call->debug_id);
+- rxrpc_input_call_event(call, skb);
++ rxrpc_queue_rx_call_packet(call, skb);
+ rxrpc_put_call(call, rxrpc_call_put_input);
+ return true;
+
+--- a/net/rxrpc/call_event.c
++++ b/net/rxrpc/call_event.c
+@@ -315,10 +315,11 @@ static void rxrpc_send_initial_ping(stru
+ /*
+ * Handle retransmission and deferred ACK/abort generation.
+ */
+-bool rxrpc_input_call_event(struct rxrpc_call *call, struct sk_buff *skb)
++bool rxrpc_input_call_event(struct rxrpc_call *call)
+ {
++ struct sk_buff *skb;
+ ktime_t now, t;
+- bool resend = false;
++ bool resend = false, saw_ack = false;
+ s32 abort_code;
+
+ rxrpc_see_call(call, rxrpc_call_see_input);
+@@ -328,9 +329,6 @@ bool rxrpc_input_call_event(struct rxrpc
+ call->debug_id, rxrpc_call_states[__rxrpc_call_state(call)],
+ call->events);
+
+- if (__rxrpc_call_is_complete(call))
+- goto out;
+-
+ /* Handle abort request locklessly, vs rxrpc_propose_abort(). */
+ abort_code = smp_load_acquire(&call->send_abort);
+ if (abort_code) {
+@@ -339,11 +337,20 @@ bool rxrpc_input_call_event(struct rxrpc
+ goto out;
+ }
+
+- if (skb && skb->mark == RXRPC_SKB_MARK_ERROR)
+- goto out;
++ while ((skb = __skb_dequeue(&call->rx_queue))) {
++ struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
++
++ if (__rxrpc_call_is_complete(call) ||
++ skb->mark == RXRPC_SKB_MARK_ERROR) {
++ rxrpc_free_skb(skb, rxrpc_skb_put_call_rx);
++ goto out;
++ }
++
++ saw_ack |= sp->hdr.type == RXRPC_PACKET_TYPE_ACK;
+
+- if (skb)
+ rxrpc_input_call_packet(call, skb);
++ rxrpc_free_skb(skb, rxrpc_skb_put_call_rx);
++ }
+
+ /* If we see our async-event poke, check for timeout trippage. */
+ now = ktime_get_real();
+@@ -409,12 +416,8 @@ bool rxrpc_input_call_event(struct rxrpc
+ rxrpc_propose_ack_ping_for_keepalive);
+ }
+
+- if (skb) {
+- struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
+-
+- if (sp->hdr.type == RXRPC_PACKET_TYPE_ACK)
+- rxrpc_congestion_degrade(call);
+- }
++ if (saw_ack)
++ rxrpc_congestion_degrade(call);
+
+ if (test_and_clear_bit(RXRPC_CALL_EV_INITIAL_PING, &call->events))
+ rxrpc_send_initial_ping(call);
+--- a/net/rxrpc/call_object.c
++++ b/net/rxrpc/call_object.c
+@@ -147,6 +147,7 @@ struct rxrpc_call *rxrpc_alloc_call(stru
+ INIT_LIST_HEAD(&call->attend_link);
+ INIT_LIST_HEAD(&call->tx_sendmsg);
+ INIT_LIST_HEAD(&call->tx_buffer);
++ skb_queue_head_init(&call->rx_queue);
+ skb_queue_head_init(&call->recvmsg_queue);
+ skb_queue_head_init(&call->rx_oos_queue);
+ init_waitqueue_head(&call->waitq);
+@@ -535,6 +536,7 @@ void rxrpc_get_call(struct rxrpc_call *c
+ static void rxrpc_cleanup_ring(struct rxrpc_call *call)
+ {
+ rxrpc_purge_queue(&call->recvmsg_queue);
++ rxrpc_purge_queue(&call->rx_queue);
+ rxrpc_purge_queue(&call->rx_oos_queue);
+ kfree(call->rx_dec_buffer);
+ }
+--- a/net/rxrpc/conn_client.c
++++ b/net/rxrpc/conn_client.c
+@@ -508,16 +508,18 @@ static void rxrpc_activate_channels(stru
+ void rxrpc_connect_client_calls(struct rxrpc_local *local)
+ {
+ struct rxrpc_call *call;
++ LIST_HEAD(new_client_calls);
+
+- while ((call = list_first_entry_or_null(&local->new_client_calls,
+- struct rxrpc_call, wait_link))
+- ) {
++ spin_lock(&local->client_call_lock);
++ list_splice_tail_init(&local->new_client_calls, &new_client_calls);
++ spin_unlock(&local->client_call_lock);
++
++ while ((call = list_first_entry_or_null(&new_client_calls,
++ struct rxrpc_call, wait_link))) {
+ struct rxrpc_bundle *bundle = call->bundle;
+
+- spin_lock(&local->client_call_lock);
+ list_move_tail(&call->wait_link, &bundle->waiting_calls);
+ rxrpc_see_call(call, rxrpc_call_see_waiting_call);
+- spin_unlock(&local->client_call_lock);
+
+ if (rxrpc_bundle_has_space(bundle))
+ rxrpc_activate_channels(bundle);
+--- a/net/rxrpc/input.c
++++ b/net/rxrpc/input.c
+@@ -1122,5 +1122,5 @@ void rxrpc_implicit_end_call(struct rxrp
+ break;
+ }
+
+- rxrpc_input_call_event(call, skb);
++ rxrpc_input_call_event(call);
+ }
+--- a/net/rxrpc/io_thread.c
++++ b/net/rxrpc/io_thread.c
+@@ -318,7 +318,6 @@ static int rxrpc_input_packet_on_conn(st
+ struct rxrpc_channel *chan;
+ struct rxrpc_call *call = NULL;
+ unsigned int channel;
+- bool ret;
+
+ if (sp->hdr.securityIndex != conn->security_ix)
+ return rxrpc_direct_abort(skb, rxrpc_eproto_wrong_security,
+@@ -400,9 +399,9 @@ static int rxrpc_input_packet_on_conn(st
+ peer_srx, skb);
+ }
+
+- ret = rxrpc_input_call_event(call, skb);
++ rxrpc_queue_rx_call_packet(call, skb);
+ rxrpc_put_call(call, rxrpc_call_put_input);
+- return ret;
++ return true;
+ }
+
+ /*
+@@ -419,6 +418,8 @@ int rxrpc_io_thread(void *data)
+ ktime_t now;
+ #endif
+ bool should_stop;
++ LIST_HEAD(conn_attend_q);
++ LIST_HEAD(call_attend_q);
+
+ complete(&local->io_thread_ready);
+
+@@ -429,43 +430,25 @@ int rxrpc_io_thread(void *data)
+ for (;;) {
+ rxrpc_inc_stat(local->rxnet, stat_io_loop);
+
+- /* Deal with connections that want immediate attention. */
+- conn = list_first_entry_or_null(&local->conn_attend_q,
+- struct rxrpc_connection,
+- attend_link);
+- if (conn) {
+- spin_lock_bh(&local->lock);
+- list_del_init(&conn->attend_link);
+- spin_unlock_bh(&local->lock);
+-
+- rxrpc_input_conn_event(conn, NULL);
+- rxrpc_put_connection(conn, rxrpc_conn_put_poke);
+- continue;
++ /* Inject a delay into packets if requested. */
++#ifdef CONFIG_AF_RXRPC_INJECT_RX_DELAY
++ now = ktime_get_real();
++ while ((skb = skb_peek(&local->rx_delay_queue))) {
++ if (ktime_before(now, skb->tstamp))
++ break;
++ skb = skb_dequeue(&local->rx_delay_queue);
++ skb_queue_tail(&local->rx_queue, skb);
+ }
++#endif
+
+- if (test_and_clear_bit(RXRPC_CLIENT_CONN_REAP_TIMER,
+- &local->client_conn_flags))
+- rxrpc_discard_expired_client_conns(local);
+-
+- /* Deal with calls that want immediate attention. */
+- if ((call = list_first_entry_or_null(&local->call_attend_q,
+- struct rxrpc_call,
+- attend_link))) {
+- spin_lock_bh(&local->lock);
+- list_del_init(&call->attend_link);
+- spin_unlock_bh(&local->lock);
+-
+- trace_rxrpc_call_poked(call);
+- rxrpc_input_call_event(call, NULL);
+- rxrpc_put_call(call, rxrpc_call_put_poke);
+- continue;
++ if (!skb_queue_empty(&local->rx_queue)) {
++ spin_lock_irq(&local->rx_queue.lock);
++ skb_queue_splice_tail_init(&local->rx_queue, &rx_queue);
++ spin_unlock_irq(&local->rx_queue.lock);
+ }
+
+- if (!list_empty(&local->new_client_calls))
+- rxrpc_connect_client_calls(local);
+-
+- /* Process received packets and errors. */
+- if ((skb = __skb_dequeue(&rx_queue))) {
++ /* Distribute packets and errors. */
++ while ((skb = __skb_dequeue(&rx_queue))) {
+ struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
+ switch (skb->mark) {
+ case RXRPC_SKB_MARK_PACKET:
+@@ -489,27 +472,46 @@ int rxrpc_io_thread(void *data)
+ rxrpc_free_skb(skb, rxrpc_skb_put_unknown);
+ break;
+ }
+- continue;
+ }
+
+- /* Inject a delay into packets if requested. */
+-#ifdef CONFIG_AF_RXRPC_INJECT_RX_DELAY
+- now = ktime_get_real();
+- while ((skb = skb_peek(&local->rx_delay_queue))) {
+- if (ktime_before(now, skb->tstamp))
+- break;
+- skb = skb_dequeue(&local->rx_delay_queue);
+- skb_queue_tail(&local->rx_queue, skb);
++ /* Deal with connections that want immediate attention. */
++ spin_lock_bh(&local->lock);
++ list_splice_tail_init(&local->conn_attend_q, &conn_attend_q);
++ spin_unlock_bh(&local->lock);
++
++ while ((conn = list_first_entry_or_null(&conn_attend_q,
++ struct rxrpc_connection,
++ attend_link))) {
++ spin_lock_bh(&local->lock);
++ list_del_init(&conn->attend_link);
++ spin_unlock_bh(&local->lock);
++ rxrpc_input_conn_event(conn, NULL);
++ rxrpc_put_connection(conn, rxrpc_conn_put_poke);
+ }
+-#endif
+
+- if (!skb_queue_empty(&local->rx_queue)) {
+- spin_lock_irq(&local->rx_queue.lock);
+- skb_queue_splice_tail_init(&local->rx_queue, &rx_queue);
+- spin_unlock_irq(&local->rx_queue.lock);
+- continue;
++ if (test_and_clear_bit(RXRPC_CLIENT_CONN_REAP_TIMER,
++ &local->client_conn_flags))
++ rxrpc_discard_expired_client_conns(local);
++
++ /* Deal with calls that want immediate attention. */
++ spin_lock_bh(&local->lock);
++ list_splice_tail_init(&local->call_attend_q, &call_attend_q);
++ spin_unlock_bh(&local->lock);
++
++ while ((call = list_first_entry_or_null(&call_attend_q,
++ struct rxrpc_call,
++ attend_link))) {
++ spin_lock_bh(&local->lock);
++ list_del_init(&call->attend_link);
++ spin_unlock_bh(&local->lock);
++ trace_rxrpc_call_poked(call);
++ rxrpc_input_call_event(call);
++ rxrpc_put_call(call, rxrpc_call_put_poke);
+ }
+
++ if (!list_empty(&local->new_client_calls))
++ rxrpc_connect_client_calls(local);
++
+ set_current_state(TASK_INTERRUPTIBLE);
+ should_stop = kthread_should_stop();
+ if (!skb_queue_empty(&local->rx_queue) ||
+--- a/net/rxrpc/peer_event.c
++++ b/net/rxrpc/peer_event.c
+@@ -216,7 +216,7 @@ static void rxrpc_distribute_error(struc
+
+ rxrpc_see_call(call, rxrpc_call_see_distribute_error);
+ rxrpc_set_call_completion(call, compl, 0, -err);
+- rxrpc_input_call_event(call, skb);
++ rxrpc_input_call_event(call);
+
+ spin_lock(&peer->lock);
+ }
--- /dev/null
+From stable+bounces-287942-greg=kroah.com@vger.kernel.org Wed Jul 22 18:32:08 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:08:04 -0400
+Subject: rxrpc: Fix notification vs call-release vs recvmsg
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Jeffrey Altman <jaltman@auristor.com>, Marc Dionne <marc.dionne@auristor.com>, "Junvyyang, Tencent Zhuque Lab" <zhuque@tencent.com>, LePremierHomme <kwqcheii@proton.me>, Simon Horman <horms@kernel.org>, linux-afs@lists.infradead.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722160805.1741176-4-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 2fd895842d49c23137ae48252dd211e5d6d8a3ed ]
+
+When a call is released, rxrpc takes the spinlock and removes it from
+->recvmsg_q in an effort to prevent racing recvmsg() invocations from
+seeing the same call. Now, rxrpc_recvmsg() only takes the spinlock when
+actually removing a call from the queue; it doesn't, however, take it in
+the lead up to that when it checks to see if the queue is empty. It *does*
+hold the socket lock, which prevents a recvmsg/recvmsg race - but this
+doesn't prevent sendmsg from ending the call because sendmsg() drops the
+socket lock and relies on the call->user_mutex.
+
+Fix this by firstly removing the bit in rxrpc_release_call() that dequeues
+the released call and, instead, rely on recvmsg() to simply discard
+released calls (done in a preceding fix).
+
+Secondly, rxrpc_notify_socket() is abandoned if the call is already marked
+as released rather than trying to be clever by setting both pointers in
+call->recvmsg_link to NULL to trick list_empty(). This isn't perfect and
+can still race, resulting in a released call on the queue, but recvmsg()
+will now clean that up.
+
+Fixes: 17926a79320a ("[AF_RXRPC]: Provide secure RxRPC sockets for use by userspace and kernel both")
+Signed-off-by: David Howells <dhowells@redhat.com>
+Reviewed-by: Jeffrey Altman <jaltman@auristor.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: Junvyyang, Tencent Zhuque Lab <zhuque@tencent.com>
+cc: LePremierHomme <kwqcheii@proton.me>
+cc: Simon Horman <horms@kernel.org>
+cc: linux-afs@lists.infradead.org
+Link: https://patch.msgid.link/20250717074350.3767366-4-dhowells@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/trace/events/rxrpc.h | 3 ++-
+ net/rxrpc/call_object.c | 28 ++++++++++++----------------
+ net/rxrpc/recvmsg.c | 4 ++++
+ 3 files changed, 18 insertions(+), 17 deletions(-)
+
+--- a/include/trace/events/rxrpc.h
++++ b/include/trace/events/rxrpc.h
+@@ -278,10 +278,10 @@
+ EM(rxrpc_call_put_poke, "PUT poke ") \
+ EM(rxrpc_call_put_recvmsg, "PUT recvmsg ") \
+ EM(rxrpc_call_put_recvmsg_peek_nowait, "PUT peek-nwt") \
++ EM(rxrpc_call_put_release_recvmsg_q, "PUT rls-rcmq") \
+ EM(rxrpc_call_put_release_sock, "PUT rls-sock") \
+ EM(rxrpc_call_put_release_sock_tba, "PUT rls-sk-a") \
+ EM(rxrpc_call_put_sendmsg, "PUT sendmsg ") \
+- EM(rxrpc_call_put_unnotify, "PUT unnotify") \
+ EM(rxrpc_call_put_userid_exists, "PUT u-exists") \
+ EM(rxrpc_call_put_userid, "PUT user-id ") \
+ EM(rxrpc_call_see_accept, "SEE accept ") \
+@@ -294,6 +294,7 @@
+ EM(rxrpc_call_see_disconnected, "SEE disconn ") \
+ EM(rxrpc_call_see_distribute_error, "SEE dist-err") \
+ EM(rxrpc_call_see_input, "SEE input ") \
++ EM(rxrpc_call_see_notify_released, "SEE nfy-rlsd") \
+ EM(rxrpc_call_see_recvmsg, "SEE recvmsg ") \
+ EM(rxrpc_call_see_recvmsg_requeue, "SEE recv-rqu") \
+ EM(rxrpc_call_see_recvmsg_requeue_first, "SEE recv-rqF") \
+--- a/net/rxrpc/call_object.c
++++ b/net/rxrpc/call_object.c
+@@ -547,7 +547,7 @@ static void rxrpc_cleanup_ring(struct rx
+ void rxrpc_release_call(struct rxrpc_sock *rx, struct rxrpc_call *call)
+ {
+ struct rxrpc_connection *conn = call->conn;
+- bool put = false, putu = false;
++ bool putu = false;
+
+ _enter("{%d,%d}", call->debug_id, refcount_read(&call->ref));
+
+@@ -559,23 +559,13 @@ void rxrpc_release_call(struct rxrpc_soc
+
+ rxrpc_put_call_slot(call);
+
+- /* Make sure we don't get any more notifications */
++ /* Note that at this point, the call may still be on or may have been
++ * added back on to the socket receive queue. recvmsg() must discard
++ * released calls. The CALL_RELEASED flag should prevent further
++ * notifications.
++ */
+ spin_lock_irq(&rx->recvmsg_lock);
+-
+- if (!list_empty(&call->recvmsg_link)) {
+- _debug("unlinking once-pending call %p { e=%lx f=%lx }",
+- call, call->events, call->flags);
+- list_del(&call->recvmsg_link);
+- put = true;
+- }
+-
+- /* list_empty() must return false in rxrpc_notify_socket() */
+- call->recvmsg_link.next = NULL;
+- call->recvmsg_link.prev = NULL;
+-
+ spin_unlock_irq(&rx->recvmsg_lock);
+- if (put)
+- rxrpc_put_call(call, rxrpc_call_put_unnotify);
+
+ write_lock(&rx->call_lock);
+
+@@ -624,6 +614,12 @@ void rxrpc_release_calls_on_socket(struc
+ rxrpc_put_call(call, rxrpc_call_put_release_sock);
+ }
+
++ while ((call = list_first_entry_or_null(&rx->recvmsg_q,
++ struct rxrpc_call, recvmsg_link))) {
++ list_del_init(&call->recvmsg_link);
++ rxrpc_put_call(call, rxrpc_call_put_release_recvmsg_q);
++ }
++
+ _leave("");
+ }
+
+--- a/net/rxrpc/recvmsg.c
++++ b/net/rxrpc/recvmsg.c
+@@ -29,6 +29,10 @@ void rxrpc_notify_socket(struct rxrpc_ca
+
+ if (!list_empty(&call->recvmsg_link))
+ return;
++ if (test_bit(RXRPC_CALL_RELEASED, &call->flags)) {
++ rxrpc_see_call(call, rxrpc_call_see_notify_released);
++ return;
++ }
+
+ rcu_read_lock();
+
--- /dev/null
+From stable+bounces-287943-greg=kroah.com@vger.kernel.org Wed Jul 22 18:10:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:08:05 -0400
+Subject: rxrpc: Fix socket notification race
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, Jeffrey Altman <jaltman@auristor.com>, Simon Horman <horms@kernel.org>, linux-afs@lists.infradead.org, stable@kernel.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722160805.1741176-5-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit e66f8f32f50116670dbbee5bc9e692cd2cd0c8f8 ]
+
+There's a race between rxrpc_recvmsg() and rxrpc_notify_socket(), whereby
+the latter's attempt to avoid disabling interrupts and taking the socket's
+recvmsg_lock if the call is already queued may happen simultaneously with
+the former's discarding of a call that has nothing queued.
+
+Fix this by removing the shortcut. Note that this only affects userspace's
+use of AF_RXRPC; the AFS filesystem driver doesn't use the socket queue.
+
+Fixes: 248f219cb8bc ("rxrpc: Rewrite the data and ack handling code")
+Link: https://sashiko.dev/#/patchset/20260616155749.2125907-1-dhowells%40redhat.com
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: Jeffrey Altman <jaltman@auristor.com>
+cc: Simon Horman <horms@kernel.org>
+cc: linux-afs@lists.infradead.org
+cc: stable@kernel.org
+Link: https://patch.msgid.link/20260624163819.3017002-10-dhowells@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/rxrpc/recvmsg.c | 2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/net/rxrpc/recvmsg.c
++++ b/net/rxrpc/recvmsg.c
+@@ -27,8 +27,6 @@ void rxrpc_notify_socket(struct rxrpc_ca
+
+ _enter("%d", call->debug_id);
+
+- if (!list_empty(&call->recvmsg_link))
+- return;
+ if (test_bit(RXRPC_CALL_RELEASED, &call->flags)) {
+ rxrpc_see_call(call, rxrpc_call_see_notify_released);
+ return;
--- /dev/null
+From stable+bounces-287863-greg=kroah.com@vger.kernel.org Wed Jul 22 16:03:05 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 09:54:49 -0400
+Subject: rxrpc: Pull out certain app callback funcs into an ops table
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, Simon Horman <horms@kernel.org>, linux-afs@lists.infradead.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722135450.1365902-1-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit 23738cc8048322cf324f330cd697380fb3455da5 ]
+
+A number of functions separately furnish an AF_RXRPC socket with callback
+function pointers into a kernel app (such as the AFS filesystem) that is
+using it. Replace most of these with an ops table for the entire socket.
+This makes it easier to add more callback functions.
+
+Note that the call incoming data processing callback is retaind as that
+gets set to different things, depending on the type of op.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: Simon Horman <horms@kernel.org>
+cc: linux-afs@lists.infradead.org
+Link: https://patch.msgid.link/20250411095303.2316168-3-dhowells@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: dc175389b18c ("rxrpc: serialize kernel accept preallocation with socket teardown")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/afs/rxrpc.c | 11 ++++++++---
+ include/net/af_rxrpc.h | 25 +++++++++++++++----------
+ net/rxrpc/af_rxrpc.c | 20 ++++++++------------
+ net/rxrpc/ar-internal.h | 3 +--
+ net/rxrpc/call_accept.c | 34 ++++++++++++++++------------------
+ net/rxrpc/rxperf.c | 10 +++++++---
+ 6 files changed, 55 insertions(+), 48 deletions(-)
+
+--- a/fs/afs/rxrpc.c
++++ b/fs/afs/rxrpc.c
+@@ -24,8 +24,15 @@ static void afs_wake_up_async_call(struc
+ static void afs_process_async_call(struct work_struct *);
+ static void afs_rx_new_call(struct sock *, struct rxrpc_call *, unsigned long);
+ static void afs_rx_discard_new_call(struct rxrpc_call *, unsigned long);
++static void afs_rx_attach(struct rxrpc_call *rxcall, unsigned long user_call_ID);
+ static int afs_deliver_cm_op_id(struct afs_call *);
+
++static const struct rxrpc_kernel_ops afs_rxrpc_callback_ops = {
++ .notify_new_call = afs_rx_new_call,
++ .discard_new_call = afs_rx_discard_new_call,
++ .user_attach_call = afs_rx_attach,
++};
++
+ /* asynchronous incoming call initial processing */
+ static const struct afs_call_type afs_RXCMxxxx = {
+ .name = "CB.xxxx",
+@@ -84,8 +91,7 @@ int afs_open_socket(struct afs_net *net)
+ * it sends back to us.
+ */
+
+- rxrpc_kernel_new_call_notification(socket, afs_rx_new_call,
+- afs_rx_discard_new_call);
++ rxrpc_kernel_set_notifications(socket, &afs_rxrpc_callback_ops);
+
+ ret = kernel_listen(socket, INT_MAX);
+ if (ret < 0)
+@@ -758,7 +764,6 @@ void afs_charge_preallocation(struct wor
+
+ if (rxrpc_kernel_charge_accept(net->socket,
+ afs_wake_up_async_call,
+- afs_rx_attach,
+ (unsigned long)call,
+ GFP_KERNEL,
+ call->debug_id) < 0)
+--- a/include/net/af_rxrpc.h
++++ b/include/net/af_rxrpc.h
+@@ -29,18 +29,23 @@ enum rxrpc_interruptibility {
+ */
+ extern atomic_t rxrpc_debug_id;
+
++/*
++ * Operations table for rxrpc to call out to a kernel application (e.g. kAFS).
++ */
++struct rxrpc_kernel_ops {
++ void (*notify_new_call)(struct sock *sk, struct rxrpc_call *call,
++ unsigned long user_call_ID);
++ void (*discard_new_call)(struct rxrpc_call *call, unsigned long user_call_ID);
++ void (*user_attach_call)(struct rxrpc_call *call, unsigned long user_call_ID);
++};
++
+ typedef void (*rxrpc_notify_rx_t)(struct sock *, struct rxrpc_call *,
+ unsigned long);
+ typedef void (*rxrpc_notify_end_tx_t)(struct sock *, struct rxrpc_call *,
+ unsigned long);
+-typedef void (*rxrpc_notify_new_call_t)(struct sock *, struct rxrpc_call *,
+- unsigned long);
+-typedef void (*rxrpc_discard_new_call_t)(struct rxrpc_call *, unsigned long);
+-typedef void (*rxrpc_user_attach_call_t)(struct rxrpc_call *, unsigned long);
+
+-void rxrpc_kernel_new_call_notification(struct socket *,
+- rxrpc_notify_new_call_t,
+- rxrpc_discard_new_call_t);
++void rxrpc_kernel_set_notifications(struct socket *sock,
++ const struct rxrpc_kernel_ops *app_ops);
+ struct rxrpc_call *rxrpc_kernel_begin_call(struct socket *sock,
+ struct rxrpc_peer *peer,
+ struct key *key,
+@@ -70,9 +75,9 @@ struct rxrpc_peer *rxrpc_kernel_get_call
+ const struct sockaddr_rxrpc *rxrpc_kernel_remote_srx(const struct rxrpc_peer *peer);
+ const struct sockaddr *rxrpc_kernel_remote_addr(const struct rxrpc_peer *peer);
+ unsigned int rxrpc_kernel_get_srtt(const struct rxrpc_peer *);
+-int rxrpc_kernel_charge_accept(struct socket *, rxrpc_notify_rx_t,
+- rxrpc_user_attach_call_t, unsigned long, gfp_t,
+- unsigned int);
++int rxrpc_kernel_charge_accept(struct socket *sock, rxrpc_notify_rx_t notify_rx,
++ unsigned long user_call_ID, gfp_t gfp,
++ unsigned int debug_id);
+ void rxrpc_kernel_set_tx_length(struct socket *, struct rxrpc_call *, s64);
+ bool rxrpc_kernel_check_life(const struct socket *, const struct rxrpc_call *);
+ u32 rxrpc_kernel_get_epoch(struct socket *, struct rxrpc_call *);
+--- a/net/rxrpc/af_rxrpc.c
++++ b/net/rxrpc/af_rxrpc.c
+@@ -464,24 +464,20 @@ u32 rxrpc_kernel_get_epoch(struct socket
+ EXPORT_SYMBOL(rxrpc_kernel_get_epoch);
+
+ /**
+- * rxrpc_kernel_new_call_notification - Get notifications of new calls
+- * @sock: The socket to intercept received messages on
+- * @notify_new_call: Function to be called when new calls appear
+- * @discard_new_call: Function to discard preallocated calls
++ * rxrpc_kernel_set_notifications - Set table of callback operations
++ * @sock: The socket to install table upon
++ * @app_ops: Callback operation table to set
+ *
+- * Allow a kernel service to be given notifications about new calls.
++ * Allow a kernel service to set a table of event notifications on a socket.
+ */
+-void rxrpc_kernel_new_call_notification(
+- struct socket *sock,
+- rxrpc_notify_new_call_t notify_new_call,
+- rxrpc_discard_new_call_t discard_new_call)
++void rxrpc_kernel_set_notifications(struct socket *sock,
++ const struct rxrpc_kernel_ops *app_ops)
+ {
+ struct rxrpc_sock *rx = rxrpc_sk(sock->sk);
+
+- rx->notify_new_call = notify_new_call;
+- rx->discard_new_call = discard_new_call;
++ rx->app_ops = app_ops;
+ }
+-EXPORT_SYMBOL(rxrpc_kernel_new_call_notification);
++EXPORT_SYMBOL(rxrpc_kernel_set_notifications);
+
+ /**
+ * rxrpc_kernel_set_max_life - Set maximum lifespan on a call
+--- a/net/rxrpc/ar-internal.h
++++ b/net/rxrpc/ar-internal.h
+@@ -142,8 +142,7 @@ struct rxrpc_backlog {
+ struct rxrpc_sock {
+ /* WARNING: sk has to be the first member */
+ struct sock sk;
+- rxrpc_notify_new_call_t notify_new_call; /* Func to notify of new call */
+- rxrpc_discard_new_call_t discard_new_call; /* Func to discard a new call */
++ const struct rxrpc_kernel_ops *app_ops; /* Table of kernel app notification funcs */
+ struct rxrpc_local *local; /* local endpoint */
+ struct rxrpc_backlog *backlog; /* Preallocation for services */
+ spinlock_t incoming_lock; /* Incoming call vs service shutdown lock */
+--- a/net/rxrpc/call_accept.c
++++ b/net/rxrpc/call_accept.c
+@@ -34,7 +34,6 @@ static void rxrpc_dummy_notify(struct so
+ static int rxrpc_service_prealloc_one(struct rxrpc_sock *rx,
+ struct rxrpc_backlog *b,
+ rxrpc_notify_rx_t notify_rx,
+- rxrpc_user_attach_call_t user_attach_call,
+ unsigned long user_call_ID, gfp_t gfp,
+ unsigned int debug_id)
+ {
+@@ -123,9 +122,10 @@ static int rxrpc_service_prealloc_one(st
+
+ call->user_call_ID = user_call_ID;
+ call->notify_rx = notify_rx;
+- if (user_attach_call) {
++ if (rx->app_ops &&
++ rx->app_ops->user_attach_call) {
+ rxrpc_get_call(call, rxrpc_call_get_kernel_service);
+- user_attach_call(call, user_call_ID);
++ rx->app_ops->user_attach_call(call, user_call_ID);
+ }
+
+ rxrpc_get_call(call, rxrpc_call_get_userid);
+@@ -221,9 +221,10 @@ void rxrpc_discard_prealloc(struct rxrpc
+ struct rxrpc_call *call = b->call_backlog[tail];
+ rxrpc_see_call(call, rxrpc_call_see_discard);
+ rcu_assign_pointer(call->socket, rx);
+- if (rx->discard_new_call) {
++ if (rx->app_ops &&
++ rx->app_ops->discard_new_call) {
+ _debug("discard %lx", call->user_call_ID);
+- rx->discard_new_call(call, call->user_call_ID);
++ rx->app_ops->discard_new_call(call, call->user_call_ID);
+ if (call->notify_rx)
+ call->notify_rx = rxrpc_dummy_notify;
+ rxrpc_put_call(call, rxrpc_call_put_kernel);
+@@ -392,8 +393,9 @@ bool rxrpc_new_incoming_call(struct rxrp
+ rxrpc_incoming_call(rx, call, skb);
+ conn = call->conn;
+
+- if (rx->notify_new_call)
+- rx->notify_new_call(&rx->sk, call, call->user_call_ID);
++ if (rx->app_ops &&
++ rx->app_ops->notify_new_call)
++ rx->app_ops->notify_new_call(&rx->sk, call, call->user_call_ID);
+
+ spin_lock(&conn->state_lock);
+ if (conn->state == RXRPC_CONN_SERVICE_UNSECURED) {
+@@ -445,8 +447,7 @@ int rxrpc_user_charge_accept(struct rxrp
+ if (rx->sk.sk_state == RXRPC_CLOSE)
+ return -ESHUTDOWN;
+
+- return rxrpc_service_prealloc_one(rx, b, NULL, NULL, user_call_ID,
+- GFP_KERNEL,
++ return rxrpc_service_prealloc_one(rx, b, NULL, user_call_ID, GFP_KERNEL,
+ atomic_inc_return(&rxrpc_debug_id));
+ }
+
+@@ -454,20 +455,18 @@ int rxrpc_user_charge_accept(struct rxrp
+ * rxrpc_kernel_charge_accept - Charge up socket with preallocated calls
+ * @sock: The socket on which to preallocate
+ * @notify_rx: Event notification function for the call
+- * @user_attach_call: Func to attach call to user_call_ID
+ * @user_call_ID: The tag to attach to the preallocated call
+ * @gfp: The allocation conditions.
+ * @debug_id: The tracing debug ID.
+ *
+- * Charge up the socket with preallocated calls, each with a user ID. A
+- * function should be provided to effect the attachment from the user's side.
+- * The user is given a ref to hold on the call.
++ * Charge up the socket with preallocated calls, each with a user ID. The
++ * ->user_attach_call() callback function should be provided to effect the
++ * attachment from the user's side. The user is given a ref to hold on the
++ * call.
+ *
+ * Note that the call may be come connected before this function returns.
+ */
+-int rxrpc_kernel_charge_accept(struct socket *sock,
+- rxrpc_notify_rx_t notify_rx,
+- rxrpc_user_attach_call_t user_attach_call,
++int rxrpc_kernel_charge_accept(struct socket *sock, rxrpc_notify_rx_t notify_rx,
+ unsigned long user_call_ID, gfp_t gfp,
+ unsigned int debug_id)
+ {
+@@ -477,8 +476,7 @@ int rxrpc_kernel_charge_accept(struct so
+ if (sock->sk->sk_state == RXRPC_CLOSE)
+ return -ESHUTDOWN;
+
+- return rxrpc_service_prealloc_one(rx, b, notify_rx,
+- user_attach_call, user_call_ID,
++ return rxrpc_service_prealloc_one(rx, b, notify_rx, user_call_ID,
+ gfp, debug_id);
+ }
+ EXPORT_SYMBOL(rxrpc_kernel_charge_accept);
+--- a/net/rxrpc/rxperf.c
++++ b/net/rxrpc/rxperf.c
+@@ -136,6 +136,12 @@ static void rxperf_notify_end_reply_tx(s
+ RXPERF_CALL_SV_AWAIT_ACK);
+ }
+
++static const struct rxrpc_kernel_ops rxperf_rxrpc_callback_ops = {
++ .notify_new_call = rxperf_rx_new_call,
++ .discard_new_call = rxperf_rx_discard_new_call,
++ .user_attach_call = rxperf_rx_attach,
++};
++
+ /*
+ * Charge the incoming call preallocation.
+ */
+@@ -161,7 +167,6 @@ static void rxperf_charge_preallocation(
+
+ if (rxrpc_kernel_charge_accept(rxperf_socket,
+ rxperf_notify_rx,
+- rxperf_rx_attach,
+ (unsigned long)call,
+ GFP_KERNEL,
+ call->debug_id) < 0)
+@@ -209,8 +214,7 @@ static int rxperf_open_socket(void)
+ if (ret < 0)
+ goto error_2;
+
+- rxrpc_kernel_new_call_notification(socket, rxperf_rx_new_call,
+- rxperf_rx_discard_new_call);
++ rxrpc_kernel_set_notifications(socket, &rxperf_rxrpc_callback_ops);
+
+ ret = kernel_listen(socket, INT_MAX);
+ if (ret < 0)
--- /dev/null
+From stable+bounces-287864-greg=kroah.com@vger.kernel.org Wed Jul 22 16:03:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 09:54:50 -0400
+Subject: rxrpc: serialize kernel accept preallocation with socket teardown
+To: stable@vger.kernel.org
+Cc: Li Daming <d4n.for.sec@gmail.com>, Yuan Tan <yuantan098@gmail.com>, Yifan Wu <yifanwucs@gmail.com>, Juefei Pu <tomapufckgml@gmail.com>, Xin Liu <bird@lzu.edu.cn>, Ren Wei <n05ec@lzu.edu.cn>, David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, Jeffrey Altman <jaltman@auristor.com>, Simon Horman <horms@kernel.org>, linux-afs@lists.infradead.org, stable@kernel.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722135450.1365902-2-sashal@kernel.org>
+
+From: Li Daming <d4n.for.sec@gmail.com>
+
+[ Upstream commit dc175389b18c29a5303ee83169ec653adfae3e17 ]
+
+rxrpc_kernel_charge_accept() reads rx->backlog without any
+socket/backlog synchronization and passes that raw pointer into
+rxrpc_service_prealloc_one(). A concurrent rxrpc_discard_prealloc()
+sets rx->backlog = NULL and frees the backlog rings, so a kernel
+preallocation worker can keep using a freed struct rxrpc_backlog
+while updating *_backlog_head/tail and array slots.
+
+Serialize the state check and backlog lookup with the socket lock,
+and reject kernel preallocation once teardown has disabled
+listening or discarded the service backlog.
+
+Fixes: 00e907127e6f ("rxrpc: Preallocate peers, conns and calls for incoming service requests")
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Yifan Wu <yifanwucs@gmail.com>
+Reported-by: Juefei Pu <tomapufckgml@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Signed-off-by: Li Daming <d4n.for.sec@gmail.com>
+Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: Jeffrey Altman <jaltman@auristor.com>
+cc: Simon Horman <horms@kernel.org>
+cc: linux-afs@lists.infradead.org
+cc: stable@kernel.org
+Link: https://patch.msgid.link/20260609140911.838677-6-dhowells@redhat.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/rxrpc/call_accept.c | 25 +++++++++++++++++++------
+ 1 file changed, 19 insertions(+), 6 deletions(-)
+
+--- a/net/rxrpc/call_accept.c
++++ b/net/rxrpc/call_accept.c
+@@ -470,13 +470,26 @@ int rxrpc_kernel_charge_accept(struct so
+ unsigned long user_call_ID, gfp_t gfp,
+ unsigned int debug_id)
+ {
+- struct rxrpc_sock *rx = rxrpc_sk(sock->sk);
+- struct rxrpc_backlog *b = rx->backlog;
++ struct rxrpc_backlog *b;
++ struct rxrpc_sock *rx;
++ struct sock *sk;
++ int ret;
+
+- if (sock->sk->sk_state == RXRPC_CLOSE)
+- return -ESHUTDOWN;
++ sk = sock->sk;
++ rx = rxrpc_sk(sk);
+
+- return rxrpc_service_prealloc_one(rx, b, notify_rx, user_call_ID,
+- gfp, debug_id);
++ lock_sock(sk);
++ if (sk->sk_state != RXRPC_SERVER_LISTENING || !rx->backlog) {
++ ret = -ESHUTDOWN;
++ goto out;
++ }
++
++ b = rx->backlog;
++ ret = rxrpc_service_prealloc_one(rx, b, notify_rx, user_call_ID,
++ gfp, debug_id);
++
++out:
++ release_sock(sk);
++ return ret;
+ }
+ EXPORT_SYMBOL(rxrpc_kernel_charge_accept);
--- /dev/null
+From stable+bounces-287941-greg=kroah.com@vger.kernel.org Wed Jul 22 18:33:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:08:03 -0400
+Subject: rxrpc: Use irq-disabling spinlocks between app and I/O thread
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, Marc Dionne <marc.dionne@auristor.com>, linux-afs@lists.infradead.org, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722160805.1741176-3-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit a2ea9a9072607c2fd6442bd1ffb4dbdbf882aed7 ]
+
+Where a spinlock is used by both the application thread and the I/O thread,
+use irq-disabling locking so that an interrupt taken on the app thread
+doesn't also slow down the I/O thread.
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+cc: Marc Dionne <marc.dionne@auristor.com>
+cc: linux-afs@lists.infradead.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/rxrpc/af_rxrpc.c | 4 ++--
+ net/rxrpc/call_accept.c | 20 ++++++++++----------
+ net/rxrpc/call_object.c | 14 +++++++-------
+ net/rxrpc/conn_client.c | 12 ++++++------
+ net/rxrpc/conn_event.c | 14 +++++++-------
+ net/rxrpc/conn_object.c | 8 ++++----
+ net/rxrpc/input.c | 5 +----
+ net/rxrpc/io_thread.c | 8 ++++----
+ net/rxrpc/peer_event.c | 8 ++++----
+ net/rxrpc/peer_object.c | 1 +
+ net/rxrpc/recvmsg.c | 22 +++++++++++-----------
+ net/rxrpc/security.c | 4 ++--
+ 12 files changed, 59 insertions(+), 61 deletions(-)
+
+--- a/net/rxrpc/af_rxrpc.c
++++ b/net/rxrpc/af_rxrpc.c
+@@ -408,9 +408,9 @@ void rxrpc_kernel_shutdown_call(struct s
+
+ /* Make sure we're not going to call back into a kernel service */
+ if (call->notify_rx) {
+- spin_lock(&call->notify_lock);
++ spin_lock_irq(&call->notify_lock);
+ call->notify_rx = rxrpc_dummy_notify_rx;
+- spin_unlock(&call->notify_lock);
++ spin_unlock_irq(&call->notify_lock);
+ }
+ }
+ mutex_unlock(&call->user_mutex);
+--- a/net/rxrpc/call_accept.c
++++ b/net/rxrpc/call_accept.c
+@@ -189,8 +189,8 @@ void rxrpc_discard_prealloc(struct rxrpc
+ /* Make sure that there aren't any incoming calls in progress before we
+ * clear the preallocation buffers.
+ */
+- spin_lock(&rx->incoming_lock);
+- spin_unlock(&rx->incoming_lock);
++ spin_lock_irq(&rx->incoming_lock);
++ spin_unlock_irq(&rx->incoming_lock);
+
+ head = b->peer_backlog_head;
+ tail = b->peer_backlog_tail;
+@@ -349,7 +349,7 @@ bool rxrpc_new_incoming_call(struct rxrp
+ if (sp->hdr.type != RXRPC_PACKET_TYPE_DATA)
+ return rxrpc_protocol_error(skb, rxrpc_eproto_no_service_call);
+
+- read_lock(&local->services_lock);
++ read_lock_irq(&local->services_lock);
+
+ /* Weed out packets to services we're not offering. Packets that would
+ * begin a call are explicitly rejected and the rest are just
+@@ -406,12 +406,12 @@ bool rxrpc_new_incoming_call(struct rxrp
+ spin_unlock(&conn->state_lock);
+
+ spin_unlock(&rx->incoming_lock);
+- read_unlock(&local->services_lock);
++ read_unlock_irq(&local->services_lock);
+
+ if (hlist_unhashed(&call->error_link)) {
+- spin_lock(&call->peer->lock);
++ spin_lock_irq(&call->peer->lock);
+ hlist_add_head(&call->error_link, &call->peer->error_targets);
+- spin_unlock(&call->peer->lock);
++ spin_unlock_irq(&call->peer->lock);
+ }
+
+ _leave(" = %p{%d}", call, call->debug_id);
+@@ -420,20 +420,20 @@ bool rxrpc_new_incoming_call(struct rxrp
+ return true;
+
+ unsupported_service:
+- read_unlock(&local->services_lock);
++ read_unlock_irq(&local->services_lock);
+ return rxrpc_direct_abort(skb, rxrpc_abort_service_not_offered,
+ RX_INVALID_OPERATION, -EOPNOTSUPP);
+ unsupported_security:
+- read_unlock(&local->services_lock);
++ read_unlock_irq(&local->services_lock);
+ return rxrpc_direct_abort(skb, rxrpc_abort_service_not_offered,
+ RX_INVALID_OPERATION, -EKEYREJECTED);
+ no_call:
+ spin_unlock(&rx->incoming_lock);
+- read_unlock(&local->services_lock);
++ read_unlock_irq(&local->services_lock);
+ _leave(" = f [%u]", skb->mark);
+ return false;
+ discard:
+- read_unlock(&local->services_lock);
++ read_unlock_irq(&local->services_lock);
+ return true;
+ }
+
+--- a/net/rxrpc/call_object.c
++++ b/net/rxrpc/call_object.c
+@@ -48,7 +48,7 @@ void rxrpc_poke_call(struct rxrpc_call *
+ bool busy;
+
+ if (!test_bit(RXRPC_CALL_DISCONNECTED, &call->flags)) {
+- spin_lock_bh(&local->lock);
++ spin_lock_irq(&local->lock);
+ busy = !list_empty(&call->attend_link);
+ trace_rxrpc_poke_call(call, busy, what);
+ if (!busy && !rxrpc_try_get_call(call, rxrpc_call_get_poke))
+@@ -56,7 +56,7 @@ void rxrpc_poke_call(struct rxrpc_call *
+ if (!busy) {
+ list_add_tail(&call->attend_link, &local->call_attend_q);
+ }
+- spin_unlock_bh(&local->lock);
++ spin_unlock_irq(&local->lock);
+ if (!busy)
+ rxrpc_wake_up_io_thread(local);
+ }
+@@ -303,9 +303,9 @@ static int rxrpc_connect_call(struct rxr
+
+ trace_rxrpc_client(NULL, -1, rxrpc_client_queue_new_call);
+ rxrpc_get_call(call, rxrpc_call_get_io_thread);
+- spin_lock(&local->client_call_lock);
++ spin_lock_irq(&local->client_call_lock);
+ list_add_tail(&call->wait_link, &local->new_client_calls);
+- spin_unlock(&local->client_call_lock);
++ spin_unlock_irq(&local->client_call_lock);
+ rxrpc_wake_up_io_thread(local);
+ return 0;
+
+@@ -435,7 +435,7 @@ error_attached_to_socket:
+
+ /*
+ * Set up an incoming call. call->conn points to the connection.
+- * This is called in BH context and isn't allowed to fail.
++ * This is called with interrupts disabled and isn't allowed to fail.
+ */
+ void rxrpc_incoming_call(struct rxrpc_sock *rx,
+ struct rxrpc_call *call,
+@@ -560,7 +560,7 @@ void rxrpc_release_call(struct rxrpc_soc
+ rxrpc_put_call_slot(call);
+
+ /* Make sure we don't get any more notifications */
+- spin_lock(&rx->recvmsg_lock);
++ spin_lock_irq(&rx->recvmsg_lock);
+
+ if (!list_empty(&call->recvmsg_link)) {
+ _debug("unlinking once-pending call %p { e=%lx f=%lx }",
+@@ -573,7 +573,7 @@ void rxrpc_release_call(struct rxrpc_soc
+ call->recvmsg_link.next = NULL;
+ call->recvmsg_link.prev = NULL;
+
+- spin_unlock(&rx->recvmsg_lock);
++ spin_unlock_irq(&rx->recvmsg_lock);
+ if (put)
+ rxrpc_put_call(call, rxrpc_call_put_unnotify);
+
+--- a/net/rxrpc/conn_client.c
++++ b/net/rxrpc/conn_client.c
+@@ -510,9 +510,9 @@ void rxrpc_connect_client_calls(struct r
+ struct rxrpc_call *call;
+ LIST_HEAD(new_client_calls);
+
+- spin_lock(&local->client_call_lock);
++ spin_lock_irq(&local->client_call_lock);
+ list_splice_tail_init(&local->new_client_calls, &new_client_calls);
+- spin_unlock(&local->client_call_lock);
++ spin_unlock_irq(&local->client_call_lock);
+
+ while ((call = list_first_entry_or_null(&new_client_calls,
+ struct rxrpc_call, wait_link))) {
+@@ -547,9 +547,9 @@ void rxrpc_expose_client_call(struct rxr
+ set_bit(RXRPC_CONN_DONT_REUSE, &conn->flags);
+ trace_rxrpc_client(conn, channel, rxrpc_client_exposed);
+
+- spin_lock(&call->peer->lock);
++ spin_lock_irq(&call->peer->lock);
+ hlist_add_head(&call->error_link, &call->peer->error_targets);
+- spin_unlock(&call->peer->lock);
++ spin_unlock_irq(&call->peer->lock);
+ }
+ }
+
+@@ -590,9 +590,9 @@ void rxrpc_disconnect_client_call(struct
+ ASSERTCMP(call->call_id, ==, 0);
+ ASSERT(!test_bit(RXRPC_CALL_EXPOSED, &call->flags));
+ /* May still be on ->new_client_calls. */
+- spin_lock(&local->client_call_lock);
++ spin_lock_irq(&local->client_call_lock);
+ list_del_init(&call->wait_link);
+- spin_unlock(&local->client_call_lock);
++ spin_unlock_irq(&local->client_call_lock);
+ return;
+ }
+
+--- a/net/rxrpc/conn_event.c
++++ b/net/rxrpc/conn_event.c
+@@ -26,7 +26,7 @@ static bool rxrpc_set_conn_aborted(struc
+ bool aborted = false;
+
+ if (conn->state != RXRPC_CONN_ABORTED) {
+- spin_lock(&conn->state_lock);
++ spin_lock_irq(&conn->state_lock);
+ if (conn->state != RXRPC_CONN_ABORTED) {
+ conn->abort_code = abort_code;
+ conn->error = err;
+@@ -37,7 +37,7 @@ static bool rxrpc_set_conn_aborted(struc
+ set_bit(RXRPC_CONN_EV_ABORT_CALLS, &conn->events);
+ aborted = true;
+ }
+- spin_unlock(&conn->state_lock);
++ spin_unlock_irq(&conn->state_lock);
+ }
+
+ return aborted;
+@@ -268,12 +268,12 @@ static int rxrpc_process_event(struct rx
+ return conn->security->respond_to_challenge(conn, skb);
+
+ case RXRPC_PACKET_TYPE_RESPONSE:
+- spin_lock(&conn->state_lock);
++ spin_lock_irq(&conn->state_lock);
+ if (conn->state != RXRPC_CONN_SERVICE_CHALLENGING) {
+- spin_unlock(&conn->state_lock);
++ spin_unlock_irq(&conn->state_lock);
+ return 0;
+ }
+- spin_unlock(&conn->state_lock);
++ spin_unlock_irq(&conn->state_lock);
+
+ ret = rxrpc_verify_response(conn, skb);
+ if (ret < 0)
+@@ -284,12 +284,12 @@ static int rxrpc_process_event(struct rx
+ if (ret < 0)
+ return ret;
+
+- spin_lock(&conn->state_lock);
++ spin_lock_irq(&conn->state_lock);
+ if (conn->state == RXRPC_CONN_SERVICE_CHALLENGING) {
+ conn->state = RXRPC_CONN_SERVICE;
+ secured = true;
+ }
+- spin_unlock(&conn->state_lock);
++ spin_unlock_irq(&conn->state_lock);
+
+ if (secured) {
+ /* Offload call state flipping to the I/O thread. As
+--- a/net/rxrpc/conn_object.c
++++ b/net/rxrpc/conn_object.c
+@@ -31,13 +31,13 @@ void rxrpc_poke_conn(struct rxrpc_connec
+ if (WARN_ON_ONCE(!local))
+ return;
+
+- spin_lock_bh(&local->lock);
++ spin_lock_irq(&local->lock);
+ busy = !list_empty(&conn->attend_link);
+ if (!busy) {
+ rxrpc_get_connection(conn, why);
+ list_add_tail(&conn->attend_link, &local->conn_attend_q);
+ }
+- spin_unlock_bh(&local->lock);
++ spin_unlock_irq(&local->lock);
+ rxrpc_wake_up_io_thread(local);
+ }
+
+@@ -197,9 +197,9 @@ void rxrpc_disconnect_call(struct rxrpc_
+ call->peer->cong_ssthresh = call->cong_ssthresh;
+
+ if (!hlist_unhashed(&call->error_link)) {
+- spin_lock(&call->peer->lock);
++ spin_lock_irq(&call->peer->lock);
+ hlist_del_init(&call->error_link);
+- spin_unlock(&call->peer->lock);
++ spin_unlock_irq(&call->peer->lock);
+ }
+
+ if (rxrpc_is_client_call(call)) {
+--- a/net/rxrpc/input.c
++++ b/net/rxrpc/input.c
+@@ -365,7 +365,7 @@ static void rxrpc_input_queue_data(struc
+ struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
+ bool last = sp->hdr.flags & RXRPC_LAST_PACKET;
+
+- __skb_queue_tail(&call->recvmsg_queue, skb);
++ skb_queue_tail(&call->recvmsg_queue, skb);
+ rxrpc_input_update_ack_window(call, window, wtop);
+ trace_rxrpc_receive(call, last ? why + 1 : why, sp->hdr.serial, sp->hdr.seq);
+ if (last)
+@@ -442,7 +442,6 @@ static void rxrpc_input_data_one(struct
+
+ rxrpc_get_skb(skb, rxrpc_skb_get_to_recvmsg);
+
+- spin_lock(&call->recvmsg_queue.lock);
+ rxrpc_input_queue_data(call, skb, window, wtop, rxrpc_receive_queue);
+ *_notify = true;
+
+@@ -464,8 +463,6 @@ static void rxrpc_input_data_one(struct
+ rxrpc_receive_queue_oos);
+ }
+
+- spin_unlock(&call->recvmsg_queue.lock);
+-
+ call->ackr_sack_base = sack;
+ } else {
+ unsigned int slot;
+--- a/net/rxrpc/io_thread.c
++++ b/net/rxrpc/io_thread.c
+@@ -475,9 +475,9 @@ int rxrpc_io_thread(void *data)
+ }
+
+ /* Deal with connections that want immediate attention. */
+- spin_lock_bh(&local->lock);
++ spin_lock_irq(&local->lock);
+ list_splice_tail_init(&local->conn_attend_q, &conn_attend_q);
+- spin_unlock_bh(&local->lock);
++ spin_unlock_irq(&local->lock);
+
+ while ((conn = list_first_entry_or_null(&conn_attend_q,
+ struct rxrpc_connection,
+@@ -494,9 +494,9 @@ int rxrpc_io_thread(void *data)
+ rxrpc_discard_expired_client_conns(local);
+
+ /* Deal with calls that want immediate attention. */
+- spin_lock_bh(&local->lock);
++ spin_lock_irq(&local->lock);
+ list_splice_tail_init(&local->call_attend_q, &call_attend_q);
+- spin_unlock_bh(&local->lock);
++ spin_unlock_irq(&local->lock);
+
+ while ((call = list_first_entry_or_null(&call_attend_q,
+ struct rxrpc_call,
+--- a/net/rxrpc/peer_event.c
++++ b/net/rxrpc/peer_event.c
+@@ -205,23 +205,23 @@ static void rxrpc_distribute_error(struc
+ struct rxrpc_call *call;
+ HLIST_HEAD(error_targets);
+
+- spin_lock(&peer->lock);
++ spin_lock_irq(&peer->lock);
+ hlist_move_list(&peer->error_targets, &error_targets);
+
+ while (!hlist_empty(&error_targets)) {
+ call = hlist_entry(error_targets.first,
+ struct rxrpc_call, error_link);
+ hlist_del_init(&call->error_link);
+- spin_unlock(&peer->lock);
++ spin_unlock_irq(&peer->lock);
+
+ rxrpc_see_call(call, rxrpc_call_see_distribute_error);
+ rxrpc_set_call_completion(call, compl, 0, -err);
+ rxrpc_input_call_event(call);
+
+- spin_lock(&peer->lock);
++ spin_lock_irq(&peer->lock);
+ }
+
+- spin_unlock(&peer->lock);
++ spin_unlock_irq(&peer->lock);
+ }
+
+ /*
+--- a/net/rxrpc/peer_object.c
++++ b/net/rxrpc/peer_object.c
+@@ -304,6 +304,7 @@ static void rxrpc_free_peer(struct rxrpc
+ * Set up a new incoming peer. There shouldn't be any other matching peers
+ * since we've already done a search in the list from the non-reentrant context
+ * (the data_ready handler) that is the only place we can add new peers.
++ * Called with interrupts disabled.
+ */
+ void rxrpc_new_incoming_peer(struct rxrpc_local *local, struct rxrpc_peer *peer)
+ {
+--- a/net/rxrpc/recvmsg.c
++++ b/net/rxrpc/recvmsg.c
+@@ -36,16 +36,16 @@ void rxrpc_notify_socket(struct rxrpc_ca
+ sk = &rx->sk;
+ if (rx && sk->sk_state < RXRPC_CLOSE) {
+ if (call->notify_rx) {
+- spin_lock(&call->notify_lock);
++ spin_lock_irq(&call->notify_lock);
+ call->notify_rx(sk, call, call->user_call_ID);
+- spin_unlock(&call->notify_lock);
++ spin_unlock_irq(&call->notify_lock);
+ } else {
+- spin_lock(&rx->recvmsg_lock);
++ spin_lock_irq(&rx->recvmsg_lock);
+ if (list_empty(&call->recvmsg_link)) {
+ rxrpc_get_call(call, rxrpc_call_get_notify_socket);
+ list_add_tail(&call->recvmsg_link, &rx->recvmsg_q);
+ }
+- spin_unlock(&rx->recvmsg_lock);
++ spin_unlock_irq(&rx->recvmsg_lock);
+
+ if (!sock_flag(sk, SOCK_DEAD)) {
+ _debug("call %ps", sk->sk_data_ready);
+@@ -379,14 +379,14 @@ try_again:
+ * We also want to weed out calls that got requeued whilst we were
+ * shovelling data out.
+ */
+- spin_lock(&rx->recvmsg_lock);
++ spin_lock_irq(&rx->recvmsg_lock);
+ l = rx->recvmsg_q.next;
+ call = list_entry(l, struct rxrpc_call, recvmsg_link);
+
+ if (!rxrpc_call_is_complete(call) &&
+ skb_queue_empty(&call->recvmsg_queue)) {
+ list_del_init(&call->recvmsg_link);
+- spin_unlock(&rx->recvmsg_lock);
++ spin_unlock_irq(&rx->recvmsg_lock);
+ release_sock(&rx->sk);
+ trace_rxrpc_recvmsg(call->debug_id, rxrpc_recvmsg_unqueue, 0);
+ rxrpc_put_call(call, rxrpc_call_put_recvmsg);
+@@ -407,7 +407,7 @@ try_again:
+ list_del_init(&call->recvmsg_link);
+ else
+ rxrpc_get_call(call, rxrpc_call_get_recvmsg);
+- spin_unlock(&rx->recvmsg_lock);
++ spin_unlock_irq(&rx->recvmsg_lock);
+
+ call_debug_id = call->debug_id;
+ trace_rxrpc_recvmsg(call_debug_id, rxrpc_recvmsg_dequeue, 0);
+@@ -502,17 +502,17 @@ error_unlock_call:
+
+ error_requeue_call:
+ if (!(flags & MSG_PEEK)) {
+- spin_lock(&rx->recvmsg_lock);
++ spin_lock_irq(&rx->recvmsg_lock);
+ if (list_empty(&call->recvmsg_link)) {
+ list_add(&call->recvmsg_link, &rx->recvmsg_q);
+ rxrpc_see_call(call, rxrpc_call_see_recvmsg_requeue);
+- spin_unlock(&rx->recvmsg_lock);
++ spin_unlock_irq(&rx->recvmsg_lock);
+ } else if (list_is_first(&call->recvmsg_link, &rx->recvmsg_q)) {
+- spin_unlock(&rx->recvmsg_lock);
++ spin_unlock_irq(&rx->recvmsg_lock);
+ rxrpc_put_call(call, rxrpc_call_see_recvmsg_requeue_first);
+ } else {
+ list_move(&call->recvmsg_link, &rx->recvmsg_q);
+- spin_unlock(&rx->recvmsg_lock);
++ spin_unlock_irq(&rx->recvmsg_lock);
+ rxrpc_put_call(call, rxrpc_call_see_recvmsg_requeue_move);
+ }
+ trace_rxrpc_recvmsg(call_debug_id, rxrpc_recvmsg_requeue, 0);
+--- a/net/rxrpc/security.c
++++ b/net/rxrpc/security.c
+@@ -114,10 +114,10 @@ found:
+ if (conn->state == RXRPC_CONN_CLIENT_UNSECURED) {
+ ret = conn->security->init_connection_security(conn, token);
+ if (ret == 0) {
+- spin_lock(&conn->state_lock);
++ spin_lock_irq(&conn->state_lock);
+ if (conn->state == RXRPC_CONN_CLIENT_UNSECURED)
+ conn->state = RXRPC_CONN_CLIENT;
+- spin_unlock(&conn->state_lock);
++ spin_unlock_irq(&conn->state_lock);
+ }
+ }
+ mutex_unlock(&conn->security_lock);
rust-allow-suspicious_runtime_symbol_definitions-lint-for-rust-1.98.patch
mm-damon-core-validate-ranges-in-damon_set_regions.patch
mm-damon-core-disallow-overlapping-input-ranges-for-damon_set_regions.patch
+iommufd-reject-invalid-read-count-in-iommufd_fault_fops_read.patch
+iommufd-break-the-loop-on-failure-in-iommufd_fault_fops_read.patch
+iommufd-avoid-partial-fault-group-delivery-in-iommufd_fault_fops_read.patch
+fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch
+i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch
+vfs-audit-introduce-kern_path_parent-for-audit.patch
+audit-widen-ino-fields-to-u64.patch
+audit-use-unsigned-int-instead-of-unsigned.patch
+audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch
+i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch
+alsa-hda-conexant-remove-mic-bias-threshold-override.patch
+alsa-hda-fix-cached-processing-coefficient-verbs.patch
+rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch
+rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch
+xfs-factor-out-xfs_attr3_leaf_init.patch
+xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch
+fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch
+fbcon-use-correct-type-for-vc_resize-return-value.patch
+rxrpc-fix-cpu-time-starvation-in-i-o-thread.patch
+rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch
+rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch
+rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch
+rxrpc-fix-socket-notification-race.patch
+tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch
+vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch
+vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch
+vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch
+vduse-avoid-leaking-information-to-userspace.patch
+octeontx2-annotate-mmio-regions-as-__iomem.patch
+octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch
+octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch
+arm64-dts-qcom-correct-rbr-opp-entry.patch
+arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch
+asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch
+asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch
+asoc-mediatek-mt8183-afe-pcm-shorten-memif_data-table-using-macros.patch
+asoc-mediatek-mt8183-afe-pcm-support-32-bit-dma-addresses.patch
+asoc-mediatek-mt8183-afe-pcm-use-local-dev-pointer-in-driver-callbacks.patch
+asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch
+netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch
+netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch
+netfilter-bitwise-rename-some-boolean-operation-functions.patch
+netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch
+netfilter-nf_tables-remove-register-tracking-infrastructure.patch
+netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch
+gpu-move-drm-buddy-allocator-one-level-up-part-two.patch
+gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch
+nfsd-pass-nfsd_file-to-nfsd_iter_read.patch
+sunrpc-allocate-a-separate-bvec-array-for-socket-sends.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
+remoteproc-xlnx-check-remote-core-state.patch
+mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch
+mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch
+landlock-prepare-to-use-credential-instead-of-domain-for-fowner.patch
+landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch
+mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch
+mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch
+mm-prepare-to-move-subsection_map_init-to-mm-sparse-vmemmap.c.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
+dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch
+dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch
+dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch
+i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch
+i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch
+xfrm-use-nested-bh-locking-for-nat_keepalive_sk_ipv.patch
+xfrm-nat_keepalive-avoid-double-free-on-send-error.patch
+tcp-defer-md5sig_info-kfree-past-rcu-grace-period-in-tcp_connect.patch
+tcp-decrement-tcp_md5_needed-static-branch.patch
+nvmet-introduce-nvmet_req_transfer_len.patch
+nvmet-auth-reject-short-auth_receive-buffers.patch
+ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch
+bpf-refactor-acquire-release-_reference_state.patch
+bpf-refactor-check_ctx_access.patch
+bpf-reset-register-bounds-before-narrowing-retval-range-in-check_mem_access.patch
+block-add-helper-add_disk_final.patch
+block-remove-redundant-gd_need_part_scan-in-add_disk_final.patch
+dm-integrity-fix-leaking-uninitialized-kernel-memory.patch
+cleanup-add-a-scoped-version-of-class.patch
+cleanup-fix-scoped_class.patch
+cred-add-kernel_cred-helper.patch
+cred-add-scoped_with_kernel_creds.patch
+dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch
+wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch
+mmc-vub300-fix-use-after-free-on-disconnect.patch
+mmc-vub300-rename-probe-error-labels.patch
+mmc-vub300-fix-use-after-free-on-probe-failure.patch
+locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch
+net-mana-validate-the-packet-length-reported-by-the-nic.patch
+net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch
+gve-fix-header-buffer-corruption-with-header-split-and-hw-gro.patch
+gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch
+octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch
+bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch
+bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch
+ipmi-fix-refcount-leak-in-i_ipmi_request.patch
+net-mlx5-hws-rearrange-to-prevent-forward-declaration.patch
+net-mlx5-hws-fix-matcher-leak-on-resize-target-setup-failure.patch
+octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch
+ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch
+fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch
+net-macb-drop-in-flight-tx-skbs-on-close.patch
+net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch
+bluetooth-add-pa_link-to-distinguish-big-sync-and-pa-sync-connections.patch
+bluetooth-hci_core-fix-not-accounting-for-bis-cis-pa-links-separately.patch
+afs-improve-server-refcount-active-count-tracing.patch
+afs-make-afs_lookup_cell-take-a-trace-note.patch
+afs-drop-the-net-parameter-from-afs_unuse_cell.patch
+rxrpc-allow-the-app-to-store-private-data-on-peer-structs.patch
+afs-use-the-per-peer-app-data-provided-by-rxrpc.patch
+afs-fix-afs_server-ref-accounting.patch
+afs-simplify-cell-record-handling.patch
+afs-fix-dynamic-lookup-to-fail-on-cell-lookup-failure.patch
+afs-fix-lack-of-locking-around-modifications-of-net-cells_dyn_ino.patch
+usb-gadget-use-str_enable_disable-like-helpers.patch
+usb-gadget-fsl-udc-fix-dev_printk-device.patch
--- /dev/null
+From stable+bounces-288527-greg=kroah.com@vger.kernel.org Thu Jul 23 22:00:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 15:59:01 -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: <20260723195904.3685200-3-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
+@@ -141,6 +141,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
+@@ -219,6 +219,205 @@ bvec_overflow:
+ }
+
+ /**
++ * 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-288528-greg=kroah.com@vger.kernel.org Thu Jul 23 22:01:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 15:59:00 -0400
+Subject: sunrpc: allocate a separate bvec array for socket sends
+To: stable@vger.kernel.org
+Cc: Jeff Layton <jlayton@kernel.org>, NeilBrown <neil@brown.name>, Chuck Lever <chuck.lever@oracle.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723195904.3685200-2-sashal@kernel.org>
+
+From: Jeff Layton <jlayton@kernel.org>
+
+[ Upstream commit 6b3b697d65d46a0f640216a3f6c72856c159c567 ]
+
+svc_tcp_sendmsg() calls xdr_buf_to_bvec() with the second slot of
+rq_bvec as the start, but doesn't reduce the array length by one, which
+could lead to an array overrun. Also, rq_bvec is always rq_maxpages in
+length, which can be too short in some cases, since the TCP record
+marker consumes a slot.
+
+Fix both problems by adding a separate bvec array to the svc_sock that
+is specifically for sending. For TCP, make this array one slot longer
+than rq_maxpages, to account for the record marker. For UDP, only
+allocate as large an array as we need since it's limited to 64k of
+payload.
+
+Signed-off-by: Jeff Layton <jlayton@kernel.org>
+Reviewed-by: NeilBrown <neil@brown.name>
+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/svcsock.h | 3 +++
+ net/sunrpc/svcsock.c | 37 ++++++++++++++++++++++++++++---------
+ 2 files changed, 31 insertions(+), 9 deletions(-)
+
+--- a/include/linux/sunrpc/svcsock.h
++++ b/include/linux/sunrpc/svcsock.h
+@@ -26,6 +26,9 @@ struct svc_sock {
+ void (*sk_odata)(struct sock *);
+ void (*sk_owspace)(struct sock *);
+
++ /* For sends (protected by xpt_mutex) */
++ struct bio_vec *sk_bvec;
++
+ /* private TCP part */
+ /* On-the-wire fragment header: */
+ __be32 sk_marker;
+--- a/net/sunrpc/svcsock.c
++++ b/net/sunrpc/svcsock.c
+@@ -68,6 +68,17 @@
+
+ #define RPCDBG_FACILITY RPCDBG_SVCXPRT
+
++/*
++ * For UDP:
++ * 1 for header page
++ * enough pages for RPCSVC_MAXPAYLOAD_UDP
++ * 1 in case payload is not aligned
++ * 1 for tail page
++ */
++enum {
++ SUNRPC_MAX_UDP_SENDPAGES = 1 + RPCSVC_MAXPAYLOAD_UDP / PAGE_SIZE + 1 + 1
++};
++
+ /* To-do: to avoid tying up an nfsd thread while waiting for a
+ * handshake request, the request could instead be deferred.
+ */
+@@ -750,15 +761,14 @@ static int svc_udp_sendto(struct svc_rqs
+ if (svc_xprt_is_dead(xprt))
+ goto out_notconn;
+
+- count = xdr_buf_to_bvec(rqstp->rq_bvec,
+- ARRAY_SIZE(rqstp->rq_bvec), xdr);
++ count = xdr_buf_to_bvec(svsk->sk_bvec, SUNRPC_MAX_UDP_SENDPAGES, xdr);
+
+- iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, rqstp->rq_bvec,
++ iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+ count, rqstp->rq_res.len);
+ err = sock_sendmsg(svsk->sk_sock, &msg);
+ if (err == -ECONNREFUSED) {
+ /* ICMP error on earlier request. */
+- iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, rqstp->rq_bvec,
++ iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+ count, rqstp->rq_res.len);
+ err = sock_sendmsg(svsk->sk_sock, &msg);
+ }
+@@ -1248,19 +1258,19 @@ static int svc_tcp_sendmsg(struct svc_so
+ *sentp = 0;
+
+ /* The stream record marker is copied into a temporary page
+- * fragment buffer so that it can be included in rq_bvec.
++ * fragment buffer so that it can be included in sk_bvec.
+ */
+ buf = page_frag_alloc(&svsk->sk_frag_cache, sizeof(marker),
+ GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ memcpy(buf, &marker, sizeof(marker));
+- bvec_set_virt(rqstp->rq_bvec, buf, sizeof(marker));
++ bvec_set_virt(svsk->sk_bvec, buf, sizeof(marker));
+
+- count = xdr_buf_to_bvec(rqstp->rq_bvec + 1,
+- ARRAY_SIZE(rqstp->rq_bvec) - 1, &rqstp->rq_res);
++ count = xdr_buf_to_bvec(svsk->sk_bvec + 1, RPCSVC_MAXPAGES,
++ &rqstp->rq_res);
+
+- iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, rqstp->rq_bvec,
++ 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);
+ page_frag_free(buf);
+@@ -1423,6 +1433,13 @@ static struct svc_sock *svc_setup_socket
+ if (!svsk)
+ return ERR_PTR(-ENOMEM);
+
++ svsk->sk_bvec = kcalloc(RPCSVC_MAXPAGES + 1, sizeof(*svsk->sk_bvec),
++ GFP_KERNEL);
++ if (!svsk->sk_bvec) {
++ kfree(svsk);
++ return ERR_PTR(-ENOMEM);
++ }
++
+ inet = sock->sk;
+
+ if (pmap_register) {
+@@ -1432,6 +1449,7 @@ static struct svc_sock *svc_setup_socket
+ inet->sk_protocol,
+ ntohs(inet_sk(inet)->inet_sport));
+ if (err < 0) {
++ kfree(svsk->sk_bvec);
+ kfree(svsk);
+ return ERR_PTR(err);
+ }
+@@ -1651,5 +1669,6 @@ static void svc_sock_free(struct svc_xpr
+ if (pfc->va)
+ __page_frag_cache_drain(virt_to_head_page(pfc->va),
+ pfc->pagecnt_bias);
++ kfree(svsk->sk_bvec);
+ kfree(svsk);
+ }
--- /dev/null
+From stable+bounces-288531-greg=kroah.com@vger.kernel.org Thu Jul 23 22:01:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 15:59:03 -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: <20260723195904.3685200-5-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>
+---
+ include/linux/sunrpc/xdr.h | 4 ++--
+ net/sunrpc/svcsock.c | 14 ++++++++++++--
+ net/sunrpc/xdr.c | 11 ++++++-----
+ 3 files changed, 20 insertions(+), 9 deletions(-)
+
+--- a/include/linux/sunrpc/xdr.h
++++ b/include/linux/sunrpc/xdr.h
+@@ -139,8 +139,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
+@@ -748,7 +748,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);
+@@ -762,6 +762,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);
+@@ -773,6 +777,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);
+@@ -1251,7 +1256,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;
+
+@@ -1269,10 +1274,15 @@ static int svc_tcp_sendmsg(struct svc_so
+
+ count = xdr_buf_to_bvec(svsk->sk_bvec + 1, RPCSVC_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);
+ if (ret < 0)
+ return ret;
+--- a/net/sunrpc/xdr.c
++++ b/net/sunrpc/xdr.c
+@@ -167,13 +167,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;
+@@ -215,7 +216,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;
+ }
+
+ /**
--- /dev/null
+From stable+bounces-289482-greg=kroah.com@vger.kernel.org Mon Jul 27 14:24:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:19:56 -0400
+Subject: tcp: Decrement tcp_md5_needed static branch
+To: stable@vger.kernel.org
+Cc: Dmitry Safonov <0x7f454c46@gmail.com>, Qihang <q.h.hack.winter@gmail.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727121956.3778569-2-sashal@kernel.org>
+
+From: Dmitry Safonov <0x7f454c46@gmail.com>
+
+[ Upstream commit 6f6e860e370c9e4e919b92118a25e9e1f82e9180 ]
+
+In case of early freeing an unwanted TCP-MD5 key on TCP-AO connect(),
+md5sig_info is freed right away (and set to NULL). Later, at
+the moment of socket destruction, the static branch counter
+is not getting decremented.
+
+Add a missing decrement for TCP-MD5 static branch.
+
+Reported-by: Qihang <q.h.hack.winter@gmail.com>
+Fixes: 0aadc73995d0 ("net/tcp: Prevent TCP-MD5 with TCP-AO being set")
+Cc: stable@vger.kernel.org
+Signed-off-by: Dmitry Safonov <0x7f454c46@gmail.com>
+Link: https://patch.msgid.link/20260625-tcp-md5-connect-v3-3-1fd313d6c1e0@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/ipv4/tcp_output.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/net/ipv4/tcp_output.c
++++ b/net/ipv4/tcp_output.c
+@@ -4124,8 +4124,8 @@ int tcp_connect(struct sock *sk)
+ tcp_clear_md5_list(sk);
+ md5sig = rcu_replace_pointer(tp->md5sig_info, NULL,
+ lockdep_sock_is_held(sk));
+- if (md5sig)
+- kfree_rcu(md5sig, rcu);
++ kfree_rcu(md5sig, rcu);
++ static_branch_slow_dec_deferred(&tcp_md5_needed);
+ }
+ }
+ #endif
--- /dev/null
+From stable+bounces-289481-greg=kroah.com@vger.kernel.org Mon Jul 27 14:24:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:19:55 -0400
+Subject: tcp: defer md5sig_info kfree past RCU grace period in tcp_connect
+To: stable@vger.kernel.org
+Cc: Michael Bommarito <michael.bommarito@gmail.com>, Eric Dumazet <edumazet@google.com>, Dmitry Safonov <dima@arista.com>, Qihang <q.h.hack.winter@gmail.com>, Dmitry Safonov <0x7f454c46@gmail.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727121956.3778569-1-sashal@kernel.org>
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit b74cd55038905d5e74c1de109ab78a30b2ea0e1f ]
+
+The md5+ao reconciliation in tcp_connect() (net/ipv4/tcp_output.c)
+has two symmetric branches:
+
+ if (needs_md5) {
+ tcp_ao_destroy_sock(sk, false);
+ } else if (needs_ao) {
+ tcp_clear_md5_list(sk);
+ kfree(rcu_replace_pointer(tp->md5sig_info, NULL, ...));
+ }
+
+Both branches free a per-socket auth-info object while the socket is
+in TCP_SYN_SENT and is already on the inet ehash (inserted by
+inet_hash_connect() in tcp_v4_connect()). Both branches are reachable
+by softirq RX-path readers that load the corresponding info pointer
+via implicit RCU before bh_lock_sock_nested() is taken.
+
+The needs_md5 branch is fixed in the prior patch by re-introducing
+the call_rcu() free in tcp_ao_destroy_sock(): the equivalent per-key
+loop runs inside tcp_ao_info_free_rcu(), the RCU callback, so by the
+time it frees each tcp_ao_key all softirq readers that captured the
+container have already completed rcu_read_unlock().
+
+The needs_ao branch is not symmetric in the same way. The container
+free can be deferred via kfree_rcu(md5sig, rcu) -- struct
+tcp_md5sig_info already has the required rcu member
+(include/net/tcp.h:1999-2002), and the rest of the tree already does
+this in the tcp_md5sig_info_add() rollback paths
+(net/ipv4/tcp_ipv4.c:1410, 1436). But the per-key teardown is done
+by tcp_clear_md5_list() in process context BEFORE the container's
+RCU grace period: it walks &md5sig->head and frees each
+tcp_md5sig_key with bare hlist_del + kfree. A concurrent softirq
+reader in __tcp_md5_do_lookup() / __tcp_md5_do_lookup_exact()
+(tcp_ipv4.c:1253, 1298) walks the same list via
+hlist_for_each_entry_rcu() and races with that bare kfree on the
+keys themselves -- a per-key slab use-after-free of the same class
+as the TCP-AO bug, on the same race window.
+
+Fix this in two halves:
+
+ 1. Convert the bare kfree() in tcp_connect() to kfree_rcu() so the
+ md5sig_info container joins the rest of the md5sig lifecycle.
+ The local-variable lift is mechanical and required because
+ kfree_rcu() is a macro that expects an lvalue.
+
+ 2. Make tcp_clear_md5_list() RCU-safe by replacing hlist_del +
+ kfree(key) with hlist_del_rcu + kfree_rcu(key, rcu). struct
+ tcp_md5sig_key already carries the rcu member
+ (include/net/tcp.h:1995) and tcp_md5_do_del()
+ (net/ipv4/tcp_ipv4.c:1456) already uses kfree_rcu, so this
+ restores the lifecycle invariant the rest of the file follows
+ rather than introducing a one-off.
+
+The other caller of tcp_clear_md5_list() is tcp_md5_destruct_sock()
+(net/ipv4/tcp.c:412), which runs from the sock destructor when the
+socket is already unhashed and unreachable; the extra grace period
+there is unnecessary but harmless. Making the helper unconditionally
+RCU-safe is the cleaner contract.
+
+The needs_ao branch is not reachable by the userns reproducer used
+to demonstrate the AO-side splat (the repro installs both keys but
+ends up in the needs_md5 branch because the connect peer matches
+the MD5 key, not the AO key); however the symmetric race exists
+and a maintainer touching this code should not have to think about
+which branch escapes RCU and which one does not.
+
+Fixes: 51e547e8c89c ("tcp: Free TCP-AO/TCP-MD5 info/keys without RCU")
+Cc: stable@vger.kernel.org # v6.18+
+Suggested-by: Eric Dumazet <edumazet@google.com>
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
+Reviewed-by: Dmitry Safonov <dima@arista.com>
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+[also credits to Qihang, who found that this races with tcp-diag]
+Reported-by: Qihang <q.h.hack.winter@gmail.com>
+Signed-off-by: Dmitry Safonov <0x7f454c46@gmail.com>
+Link: https://patch.msgid.link/20260625-tcp-md5-connect-v3-2-1fd313d6c1e0@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: 6f6e860e370c ("tcp: Decrement tcp_md5_needed static branch")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/ipv4/tcp_output.c | 8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+
+--- a/net/ipv4/tcp_output.c
++++ b/net/ipv4/tcp_output.c
+@@ -4119,9 +4119,13 @@ int tcp_connect(struct sock *sk)
+ if (needs_md5) {
+ tcp_ao_destroy_sock(sk, false);
+ } else if (needs_ao) {
++ struct tcp_md5sig_info *md5sig;
++
+ tcp_clear_md5_list(sk);
+- kfree(rcu_replace_pointer(tp->md5sig_info, NULL,
+- lockdep_sock_is_held(sk)));
++ md5sig = rcu_replace_pointer(tp->md5sig_info, NULL,
++ lockdep_sock_is_held(sk));
++ if (md5sig)
++ kfree_rcu(md5sig, rcu);
+ }
+ }
+ #endif
--- /dev/null
+From stable+bounces-289260-greg=kroah.com@vger.kernel.org Sun Jul 26 14:36:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:36:10 -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: <20260726123613.37376-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
+@@ -1008,6 +1008,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 = {
+@@ -1116,7 +1117,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-289263-greg=kroah.com@vger.kernel.org Sun Jul 26 14:36:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:36:13 -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: <20260726123613.37376-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
+@@ -905,6 +905,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)
+ {
+@@ -2480,19 +2493,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-289261-greg=kroah.com@vger.kernel.org Sun Jul 26 14:36:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:36:11 -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: <20260726123613.37376-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
+@@ -1004,7 +1004,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);
+@@ -1019,6 +1018,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;
+@@ -1030,7 +1037,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;
+ }
+@@ -1123,6 +1130,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;
+ }
+@@ -2053,7 +2061,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-289262-greg=kroah.com@vger.kernel.org Sun Jul 26 14:36:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:36:12 -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: <20260726123613.37376-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
+@@ -1508,6 +1508,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
+@@ -871,6 +871,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
+@@ -713,6 +713,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);
+ }
+
+@@ -1728,6 +1729,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,
+@@ -2078,6 +2081,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
+@@ -3554,6 +3554,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
+@@ -2450,6 +2450,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);
+
+@@ -3008,6 +3010,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);
+@@ -3027,6 +3047,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);
+
+ /*
+@@ -3069,28 +3090,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);
+@@ -3110,14 +3109,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)
+@@ -3144,11 +3143,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
+@@ -786,6 +786,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)
+ {
+@@ -1233,6 +1234,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
+@@ -2066,41 +2066,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-287970-greg=kroah.com@vger.kernel.org Wed Jul 22 18:56:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:54:29 -0400
+Subject: tipc: restrict socket queue dumps in enqueue tracepoints
+To: stable@vger.kernel.org
+Cc: Li Xiasong <lixiasong1@huawei.com>, Tung Nguyen <tung.quang.nguyen@est.tech>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722165429.1853397-1-sashal@kernel.org>
+
+From: Li Xiasong <lixiasong1@huawei.com>
+
+[ Upstream commit acd7df8d955480a6f6e5bb809da67b1500cc3cf4 ]
+
+tipc_sk_enqueue() runs with sk->sk_lock.slock held while the socket is
+owned by user context. The spinlock protects the backlog queue in this
+path, but it does not serialize against the socket owner consuming or
+purging sk_receive_queue.
+
+KASAN reported:
+
+ CPU: 14 UID: 0 PID: 1050 Comm: tipc3 Not tainted 7.1.0-rc6+ #126 PREEMPT(lazy)
+ Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
+ Call Trace:
+ <TASK>
+ dump_stack_lvl+0x76/0xa0 lib/dump_stack.c:123
+ print_report+0xce/0x5b0 mm/kasan/report.c:482
+ kasan_report+0xc6/0x100 mm/kasan/report.c:597
+ __asan_report_load4_noabort+0x14/0x30 mm/kasan/report_generic.c:380
+ tipc_skb_dump+0x1327/0x16f0 net/tipc/trace.c:73
+ tipc_list_dump+0x208/0x2e0 net/tipc/trace.c:187
+ tipc_sk_dump+0xaf6/0xd60 net/tipc/socket.c:3996
+ trace_event_raw_event_tipc_sk_class+0x312/0x5a0 net/tipc/trace.h:188
+ tipc_sk_rcv+0xb1d/0x1d50 net/tipc/socket.c:2497
+ tipc_node_xmit+0x1c3/0x1440 net/tipc/node.c:1689
+ __tipc_sendmsg+0x97a/0x1440 net/tipc/socket.c:1512
+ tipc_sendmsg+0x52/0x80 net/tipc/socket.c:1400
+ sock_sendmsg+0x2f6/0x3e0 net/socket.c:825
+ splice_to_socket+0x7f9/0x1010 fs/splice.c:884
+ do_splice+0xe21/0x2330 fs/splice.c:936
+ __do_splice+0x153/0x260 fs/splice.c:1431
+ __x64_sys_splice+0x150/0x230 fs/splice.c:1616
+ x64_sys_call+0xeb5/0x2790 arch/x86/entry/syscall_64.c:41
+ do_syscall_64+0xf3/0x620 arch/x86/entry/syscall_64.c:63
+ entry_SYSCALL_64_after_hwframe+0x76/0x7e arch/x86/entry/entry_64.S:130
+ RIP: 0033:0x71624e8aafe2
+ Code: 08 0f 85 71 3a ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 66 2e 0f 1f 84 00 00 00 00 00 66 2e 0f 1f 84 00 00 00 00 00 66
+ RSP: 002b:0000716157ffed68 EFLAGS: 00000246 ORIG_RAX: 0000000000000113
+ RAX: ffffffffffffffda RBX: 0000716157fff6c0 RCX: 000071624e8aafe2
+ RDX: 000000000000005f RSI: 0000000000000000 RDI: 0000000000000066
+ RBP: 0000716157ffed90 R08: 0000000000008000 R09: 0000000000000001
+ R10: 0000000000000000 R11: 0000000000000246 R12: ffffffffffffff00
+ R13: 0000000000000021 R14: 0000000000000000 R15: 00007fff89799c40
+ </TASK>
+
+The TIPC_DUMP_ALL tracepoints in tipc_sk_enqueue() also dump
+sk_receive_queue and can therefore dereference skbs that the socket
+owner has already dequeued or freed. Restrict these dumps to
+TIPC_DUMP_SK_BKLGQ, which matches the queue protected by the held
+spinlock.
+
+Keep the change limited to the enqueue path, where the unsafe queue dump
+is reachable while the socket is owned by user context.
+
+Fixes: 01e661ebfbad ("tipc: add trace_events for tipc socket")
+Cc: stable@vger.kernel.org
+Signed-off-by: Li Xiasong <lixiasong1@huawei.com>
+Reviewed-by: Tung Nguyen <tung.quang.nguyen@est.tech>
+Link: https://patch.msgid.link/20260611135647.3666727-1-lixiasong1@huawei.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/tipc/socket.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/net/tipc/socket.c
++++ b/net/tipc/socket.c
+@@ -2456,17 +2456,17 @@ static void tipc_sk_enqueue(struct sk_bu
+ atomic_set(dcnt, 0);
+ lim = rcvbuf_limit(sk, skb) + atomic_read(dcnt);
+ if (likely(!sk_add_backlog(sk, skb, lim))) {
+- trace_tipc_sk_overlimit1(sk, skb, TIPC_DUMP_ALL,
++ trace_tipc_sk_overlimit1(sk, skb, TIPC_DUMP_SK_BKLGQ,
+ "bklg & rcvq >90% allocated!");
+ continue;
+ }
+
+- trace_tipc_sk_dump(sk, skb, TIPC_DUMP_ALL, "err_overload!");
++ trace_tipc_sk_dump(sk, skb, TIPC_DUMP_SK_BKLGQ, "err_overload!");
+ /* Overload => reject message back to sender */
+ onode = tipc_own_addr(sock_net(sk));
+ atomic_inc(&sk->sk_drops);
+ if (tipc_msg_reverse(onode, &skb, TIPC_ERR_OVERLOAD)) {
+- trace_tipc_sk_rej_msg(sk, skb, TIPC_DUMP_ALL,
++ trace_tipc_sk_rej_msg(sk, skb, TIPC_DUMP_SK_BKLGQ,
+ "@sk_enqueue!");
+ __skb_queue_tail(xmitq, skb);
+ }
--- /dev/null
+From stable+bounces-290986-greg=kroah.com@vger.kernel.org Thu Jul 30 04:31:09 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 22:31:00 -0400
+Subject: USB: gadget: fsl-udc: fix dev_printk() device
+To: stable@vger.kernel.org
+Cc: "Johan Hovold" <johan@kernel.org>, stable <stable@kernel.org>, "Uwe Kleine-König" <u.kleine-koenig@baylibre.com>, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260730023101.1859045-2-sashal@kernel.org>
+
+From: Johan Hovold <johan@kernel.org>
+
+[ Upstream commit c4dd150fceab281496acb3a643ae712aacb74864 ]
+
+A change replacing custom printk() macros with dev_printk() incorrectly
+used the gadget struct device instead of the controller struct device
+(including for messages printed before the gadget device name has been
+initialised).
+
+Switch to using the controller platform device with dev_printk() so that
+the controller device and driver names are included in log messages as
+expected.
+
+Fixes: 6025f20f16c2 ("usb: gadget: fsl-udc: Replace custom log wrappers by dev_{err,warn,dbg,vdbg}")
+Cc: stable <stable@kernel.org>
+Cc: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
+Signed-off-by: Johan Hovold <johan@kernel.org>
+Acked-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
+Link: https://patch.msgid.link/20260702141536.90887-4-johan@kernel.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/udc/fsl_udc_core.c | 92 +++++++++++++++-------------------
+ drivers/usb/gadget/udc/fsl_usb2_udc.h | 1
+ 2 files changed, 43 insertions(+), 50 deletions(-)
+
+--- a/drivers/usb/gadget/udc/fsl_udc_core.c
++++ b/drivers/usb/gadget/udc/fsl_udc_core.c
+@@ -184,7 +184,7 @@ __acquires(ep->udc->lock)
+ usb_gadget_unmap_request(&ep->udc->gadget, &req->req, ep_is_in(ep));
+
+ if (status && (status != -ESHUTDOWN))
+- dev_vdbg(&udc->gadget.dev, "complete %s req %p stat %d len %u/%u\n",
++ dev_vdbg(udc->dev, "complete %s req %p stat %d len %u/%u\n",
+ ep->ep.name, &req->req, status,
+ req->req.actual, req->req.length);
+
+@@ -286,7 +286,7 @@ static int dr_controller_setup(struct fs
+ timeout = jiffies + FSL_UDC_RESET_TIMEOUT;
+ while (fsl_readl(&dr_regs->usbcmd) & USB_CMD_CTRL_RESET) {
+ if (time_after(jiffies, timeout)) {
+- dev_err(&udc->gadget.dev, "udc reset timeout!\n");
++ dev_err(udc->dev, "udc reset timeout!\n");
+ return -ETIMEDOUT;
+ }
+ cpu_relax();
+@@ -309,7 +309,7 @@ static int dr_controller_setup(struct fs
+ tmp &= USB_EP_LIST_ADDRESS_MASK;
+ fsl_writel(tmp, &dr_regs->endpointlistaddr);
+
+- dev_vdbg(&udc->gadget.dev,
++ dev_vdbg(udc->dev,
+ "vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x\n",
+ udc->ep_qh, (int)tmp,
+ fsl_readl(&dr_regs->endpointlistaddr));
+@@ -500,7 +500,7 @@ static void struct_ep_qh_setup(struct fs
+ tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS;
+ break;
+ default:
+- dev_vdbg(&udc->gadget.dev, "error ep type is %d\n", ep_type);
++ dev_vdbg(udc->dev, "error ep type is %d\n", ep_type);
+ return;
+ }
+ if (zlt)
+@@ -613,7 +613,7 @@ static int fsl_ep_enable(struct usb_ep *
+ spin_unlock_irqrestore(&udc->lock, flags);
+ retval = 0;
+
+- dev_vdbg(&udc->gadget.dev, "enabled %s (ep%d%s) maxpacket %d\n",
++ dev_vdbg(udc->dev, "enabled %s (ep%d%s) maxpacket %d\n",
+ ep->ep.name, ep->ep.desc->bEndpointAddress & 0x0f,
+ (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out",
+ max);
+@@ -634,13 +634,8 @@ static int fsl_ep_disable(struct usb_ep
+ int ep_num;
+
+ ep = container_of(_ep, struct fsl_ep, ep);
+- if (!_ep || !ep->ep.desc) {
+- /*
+- * dev_vdbg(&udc->gadget.dev, "%s not enabled\n",
+- * _ep ? ep->ep.name : NULL);
+- */
++ if (!_ep || !ep->ep.desc)
+ return -EINVAL;
+- }
+
+ /* disable ep on controller */
+ ep_num = ep_index(ep);
+@@ -664,7 +659,7 @@ static int fsl_ep_disable(struct usb_ep
+ ep->stopped = 1;
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+- dev_vdbg(&udc->gadget.dev, "disabled %s OK\n", _ep->name);
++ dev_vdbg(udc->dev, "disabled %s OK\n", _ep->name);
+ return 0;
+ }
+
+@@ -724,9 +719,6 @@ static void fsl_queue_td(struct fsl_ep *
+ {
+ u32 temp, bitmask, tmp_stat;
+
+- /* dev_vdbg(&udc->gadget.dev, "QH addr Register 0x%8x\n", dr_regs->endpointlistaddr);
+- dev_vdbg(&udc->gadget.dev, "ep_qh[%d] addr is 0x%8x\n", i, (u32)&(ep->udc->ep_qh[i])); */
+-
+ bitmask = ep_is_in(ep)
+ ? (1 << (ep_index(ep) + 16))
+ : (1 << (ep_index(ep)));
+@@ -813,7 +805,7 @@ static struct ep_td_struct *fsl_build_dt
+ *is_last = 0;
+
+ if ((*is_last) == 0)
+- dev_vdbg(&udc_controller->gadget.dev, "multi-dtd request!\n");
++ dev_vdbg(udc_controller->dev, "multi-dtd request!\n");
+ /* Fill in the transfer size; set active bit */
+ swap_temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE);
+
+@@ -825,7 +817,7 @@ static struct ep_td_struct *fsl_build_dt
+
+ mb();
+
+- dev_vdbg(&udc_controller->gadget.dev, "length = %d address= 0x%x\n", *length, (int)*dma);
++ dev_vdbg(udc_controller->dev, "length = %d address= 0x%x\n", *length, (int)*dma);
+
+ return dtd;
+ }
+@@ -876,11 +868,11 @@ fsl_ep_queue(struct usb_ep *_ep, struct
+ /* catch various bogus parameters */
+ if (!_req || !req->req.complete || !req->req.buf
+ || !list_empty(&req->queue)) {
+- dev_vdbg(&udc->gadget.dev, "%s, bad params\n", __func__);
++ dev_vdbg(udc->dev, "%s, bad params\n", __func__);
+ return -EINVAL;
+ }
+ if (unlikely(!ep->ep.desc)) {
+- dev_vdbg(&udc->gadget.dev, "%s, bad ep\n", __func__);
++ dev_vdbg(udc->dev, "%s, bad ep\n", __func__);
+ return -EINVAL;
+ }
+ if (usb_endpoint_xfer_isoc(ep->ep.desc)) {
+@@ -1040,7 +1032,7 @@ static int fsl_ep_set_halt(struct usb_ep
+ udc->ep0_dir = 0;
+ }
+ out:
+- dev_vdbg(&udc->gadget.dev, "%s %s halt stat %d\n", ep->ep.name,
++ dev_vdbg(udc->dev, "%s %s halt stat %d\n", ep->ep.name,
+ value ? "set" : "clear", status);
+
+ return status;
+@@ -1109,7 +1101,7 @@ static void fsl_ep_fifo_flush(struct usb
+ /* Wait until flush complete */
+ while (fsl_readl(&dr_regs->endptflush)) {
+ if (time_after(jiffies, timeout)) {
+- dev_err(&udc_controller->gadget.dev,
++ dev_err(udc_controller->dev,
+ "ep flush timeout\n");
+ return;
+ }
+@@ -1182,7 +1174,7 @@ static int fsl_vbus_session(struct usb_g
+
+ udc = container_of(gadget, struct fsl_udc, gadget);
+ spin_lock_irqsave(&udc->lock, flags);
+- dev_vdbg(&gadget->dev, "VBUS %s\n", str_on_off(is_active));
++ dev_vdbg(udc->dev, "VBUS %s\n", str_on_off(is_active));
+ udc->vbus_active = (is_active != 0);
+ if (can_pullup(udc))
+ fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP),
+@@ -1548,7 +1540,7 @@ static void ep0_req_complete(struct fsl_
+ udc->ep0_state = WAIT_FOR_SETUP;
+ break;
+ case WAIT_FOR_SETUP:
+- dev_err(&udc->gadget.dev, "Unexpected ep0 packets\n");
++ dev_err(udc->dev, "Unexpected ep0 packets\n");
+ break;
+ default:
+ ep0stall(udc);
+@@ -1617,7 +1609,7 @@ static int process_ep_req(struct fsl_udc
+ errors = hc32_to_cpu(curr_td->size_ioc_sts);
+ if (errors & DTD_ERROR_MASK) {
+ if (errors & DTD_STATUS_HALTED) {
+- dev_err(&udc->gadget.dev, "dTD error %08x QH=%d\n", errors, pipe);
++ dev_err(udc->dev, "dTD error %08x QH=%d\n", errors, pipe);
+ /* Clear the errors and Halt condition */
+ tmp = hc32_to_cpu(curr_qh->size_ioc_int_sts);
+ tmp &= ~errors;
+@@ -1628,26 +1620,26 @@ static int process_ep_req(struct fsl_udc
+ break;
+ }
+ if (errors & DTD_STATUS_DATA_BUFF_ERR) {
+- dev_vdbg(&udc->gadget.dev, "Transfer overflow\n");
++ dev_vdbg(udc->dev, "Transfer overflow\n");
+ status = -EPROTO;
+ break;
+ } else if (errors & DTD_STATUS_TRANSACTION_ERR) {
+- dev_vdbg(&udc->gadget.dev, "ISO error\n");
++ dev_vdbg(udc->dev, "ISO error\n");
+ status = -EILSEQ;
+ break;
+ } else
+- dev_err(&udc->gadget.dev,
++ dev_err(udc->dev,
+ "Unknown error has occurred (0x%x)!\n",
+ errors);
+
+ } else if (hc32_to_cpu(curr_td->size_ioc_sts)
+ & DTD_STATUS_ACTIVE) {
+- dev_vdbg(&udc->gadget.dev, "Request not complete\n");
++ dev_vdbg(udc->dev, "Request not complete\n");
+ status = REQ_UNCOMPLETE;
+ return status;
+ } else if (remaining_length) {
+ if (direction) {
+- dev_vdbg(&udc->gadget.dev,
++ dev_vdbg(udc->dev,
+ "Transmit dTD remaining length not zero\n");
+ status = -EPROTO;
+ break;
+@@ -1655,8 +1647,7 @@ static int process_ep_req(struct fsl_udc
+ break;
+ }
+ } else {
+- dev_vdbg(&udc->gadget.dev,
+- "dTD transmitted successful\n");
++ dev_vdbg(udc->dev, "dTD transmitted successful\n");
+ }
+
+ if (j != curr_req->dtd_count - 1)
+@@ -1699,7 +1690,7 @@ static void dtd_complete_irq(struct fsl_
+
+ /* If the ep is configured */
+ if (!curr_ep->ep.name) {
+- dev_warn(&udc->gadget.dev, "Invalid EP?\n");
++ dev_warn(udc->dev, "Invalid EP?\n");
+ continue;
+ }
+
+@@ -1708,7 +1699,7 @@ static void dtd_complete_irq(struct fsl_
+ queue) {
+ status = process_ep_req(udc, i, curr_req);
+
+- dev_vdbg(&udc->gadget.dev,
++ dev_vdbg(udc->dev,
+ "status of process_ep_req= %d, ep = %d\n",
+ status, ep_num);
+ if (status == REQ_UNCOMPLETE)
+@@ -1829,7 +1820,7 @@ static void reset_irq(struct fsl_udc *ud
+ while (fsl_readl(&dr_regs->endpointprime)) {
+ /* Wait until all endptprime bits cleared */
+ if (time_after(jiffies, timeout)) {
+- dev_err(&udc->gadget.dev, "Timeout for reset\n");
++ dev_err(udc->dev, "Timeout for reset\n");
+ break;
+ }
+ cpu_relax();
+@@ -1839,7 +1830,7 @@ static void reset_irq(struct fsl_udc *ud
+ fsl_writel(0xffffffff, &dr_regs->endptflush);
+
+ if (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) {
+- dev_vdbg(&udc->gadget.dev, "Bus reset\n");
++ dev_vdbg(udc->dev, "Bus reset\n");
+ /* Bus is reseting */
+ udc->bus_reset = 1;
+ /* Reset all the queues, include XD, dTD, EP queue
+@@ -1847,7 +1838,7 @@ static void reset_irq(struct fsl_udc *ud
+ reset_queues(udc, true);
+ udc->usb_state = USB_STATE_DEFAULT;
+ } else {
+- dev_vdbg(&udc->gadget.dev, "Controller reset\n");
++ dev_vdbg(udc->dev, "Controller reset\n");
+ /* initialize usb hw reg except for regs for EP, not
+ * touch usbintr reg */
+ dr_controller_setup(udc);
+@@ -1881,7 +1872,7 @@ static irqreturn_t fsl_udc_irq(int irq,
+ /* Clear notification bits */
+ fsl_writel(irq_src, &dr_regs->usbsts);
+
+- /* dev_vdbg(&udc->gadget.dev, "irq_src [0x%8x]", irq_src); */
++ /* dev_vdbg(udc->dev, "irq_src [0x%8x]", irq_src); */
+
+ /* Need to resume? */
+ if (udc->usb_state == USB_STATE_SUSPENDED)
+@@ -1890,7 +1881,7 @@ static irqreturn_t fsl_udc_irq(int irq,
+
+ /* USB Interrupt */
+ if (irq_src & USB_STS_INT) {
+- dev_vdbg(&udc->gadget.dev, "Packet int\n");
++ dev_vdbg(udc->dev, "Packet int\n");
+ /* Setup package, we only support ep0 as control ep */
+ if (fsl_readl(&dr_regs->endptsetupstat) & EP_SETUP_STATUS_EP0) {
+ tripwire_handler(udc, 0,
+@@ -1919,7 +1910,7 @@ static irqreturn_t fsl_udc_irq(int irq,
+
+ /* Reset Received */
+ if (irq_src & USB_STS_RESET) {
+- dev_vdbg(&udc->gadget.dev, "reset int\n");
++ dev_vdbg(udc->dev, "reset int\n");
+ reset_irq(udc);
+ status = IRQ_HANDLED;
+ }
+@@ -1931,7 +1922,7 @@ static irqreturn_t fsl_udc_irq(int irq,
+ }
+
+ if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) {
+- dev_vdbg(&udc->gadget.dev, "Error IRQ %x\n", irq_src);
++ dev_vdbg(udc->dev, "Error IRQ %x\n", irq_src);
+ }
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+@@ -1967,7 +1958,7 @@ static int fsl_udc_start(struct usb_gadg
+ udc_controller->transceiver->otg,
+ &udc_controller->gadget);
+ if (retval < 0) {
+- dev_err(&udc_controller->gadget.dev, "can't bind to transceiver\n");
++ dev_err(udc_controller->dev, "can't bind to transceiver\n");
+ udc_controller->driver = NULL;
+ return retval;
+ }
+@@ -2252,7 +2243,7 @@ static int struct_udc_setup(struct fsl_u
+
+ udc->eps = kcalloc(udc->max_ep, sizeof(struct fsl_ep), GFP_KERNEL);
+ if (!udc->eps) {
+- dev_err(&udc->gadget.dev, "kmalloc udc endpoint status failed\n");
++ dev_err(udc->dev, "kmalloc udc endpoint status failed\n");
+ goto eps_alloc_failed;
+ }
+
+@@ -2267,7 +2258,7 @@ static int struct_udc_setup(struct fsl_u
+ udc->ep_qh = dma_alloc_coherent(&pdev->dev, size,
+ &udc->ep_qh_dma, GFP_KERNEL);
+ if (!udc->ep_qh) {
+- dev_err(&udc->gadget.dev, "malloc QHs for udc failed\n");
++ dev_err(udc->dev, "malloc QHs for udc failed\n");
+ goto ep_queue_alloc_failed;
+ }
+
+@@ -2278,14 +2269,14 @@ static int struct_udc_setup(struct fsl_u
+ udc->status_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL),
+ struct fsl_req, req);
+ if (!udc->status_req) {
+- dev_err(&udc->gadget.dev, "kzalloc for udc status request failed\n");
++ dev_err(udc->dev, "kzalloc for udc status request failed\n");
+ goto udc_status_alloc_failed;
+ }
+
+ /* allocate a small amount of memory to get valid address */
+ udc->status_req->req.buf = kmalloc(8, GFP_KERNEL);
+ if (!udc->status_req->req.buf) {
+- dev_err(&udc->gadget.dev, "kzalloc for udc request buffer failed\n");
++ dev_err(udc->dev, "kzalloc for udc request buffer failed\n");
+ goto udc_req_buf_alloc_failed;
+ }
+
+@@ -2373,6 +2364,7 @@ static int fsl_udc_probe(struct platform
+ if (udc_controller == NULL)
+ return -ENOMEM;
+
++ udc_controller->dev = &pdev->dev;
+ pdata = dev_get_platdata(&pdev->dev);
+ udc_controller->pdata = pdata;
+ spin_lock_init(&udc_controller->lock);
+@@ -2382,7 +2374,7 @@ static int fsl_udc_probe(struct platform
+ if (pdata->operating_mode == FSL_USB2_DR_OTG) {
+ udc_controller->transceiver = usb_get_phy(USB_PHY_TYPE_USB2);
+ if (IS_ERR_OR_NULL(udc_controller->transceiver)) {
+- dev_err(&udc_controller->gadget.dev, "Can't find OTG driver!\n");
++ dev_err(&pdev->dev, "Can't find OTG driver!\n");
+ ret = -ENODEV;
+ goto err_kfree;
+ }
+@@ -2398,7 +2390,7 @@ static int fsl_udc_probe(struct platform
+ if (pdata->operating_mode == FSL_USB2_DR_DEVICE) {
+ if (!request_mem_region(res->start, resource_size(res),
+ driver_name)) {
+- dev_err(&udc_controller->gadget.dev, "request mem region for %s failed\n", pdev->name);
++ dev_err(&pdev->dev, "failed to request mem region\n");
+ ret = -EBUSY;
+ goto err_kfree;
+ }
+@@ -2429,7 +2421,7 @@ static int fsl_udc_probe(struct platform
+ /* Read Device Controller Capability Parameters register */
+ dccparams = fsl_readl(&dr_regs->dccparams);
+ if (!(dccparams & DCCPARAMS_DC)) {
+- dev_err(&udc_controller->gadget.dev, "This SOC doesn't support device role\n");
++ dev_err(&pdev->dev, "This SOC doesn't support device role\n");
+ ret = -ENODEV;
+ goto err_exit;
+ }
+@@ -2447,14 +2439,14 @@ static int fsl_udc_probe(struct platform
+ ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED,
+ driver_name, udc_controller);
+ if (ret != 0) {
+- dev_err(&udc_controller->gadget.dev, "cannot request irq %d err %d\n",
++ dev_err(&pdev->dev, "cannot request irq %d err %d\n",
+ udc_controller->irq, ret);
+ goto err_exit;
+ }
+
+ /* Initialize the udc structure including QH member and other member */
+ if (struct_udc_setup(udc_controller, pdev)) {
+- dev_err(&udc_controller->gadget.dev, "Can't initialize udc data structure\n");
++ dev_err(&pdev->dev, "Can't initialize udc data structure\n");
+ ret = -ENOMEM;
+ goto err_free_irq;
+ }
+--- a/drivers/usb/gadget/udc/fsl_usb2_udc.h
++++ b/drivers/usb/gadget/udc/fsl_usb2_udc.h
+@@ -470,6 +470,7 @@ struct fsl_ep {
+ #define EP_DIR_OUT 0
+
+ struct fsl_udc {
++ struct device *dev;
+ struct usb_gadget gadget;
+ struct usb_gadget_driver *driver;
+ struct fsl_usb2_platform_data *pdata;
--- /dev/null
+From sashal@kernel.org Thu Jul 30 04:31:04 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 22:30:59 -0400
+Subject: USB: gadget: Use str_enable_disable-like helpers
+To: stable@vger.kernel.org
+Cc: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260730023101.1859045-1-sashal@kernel.org>
+
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+
+[ Upstream commit 5b6dc50e9ed870fffbf2ae6de77b30fb0d15eab8 ]
+
+Replace ternary (condition ? "enable" : "disable") syntax with helpers
+from string_choices.h because:
+1. Simple function call with one argument is easier to read. Ternary
+ operator has three arguments and with wrapping might lead to quite
+ long code.
+2. Is slightly shorter thus also easier to read.
+3. It brings uniformity in the text - same string.
+4. Allows deduping by the linker, which results in a smaller binary
+ file.
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Link: https://lore.kernel.org/r/20250114-str-enable-disable-usb-v1-5-c8405df47c19@linaro.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Stable-dep-of: c4dd150fceab ("USB: gadget: fsl-udc: fix dev_printk() device")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_ecm.c | 4 ++--
+ drivers/usb/gadget/function/f_ncm.c | 3 ++-
+ drivers/usb/gadget/function/u_serial.c | 3 ++-
+ drivers/usb/gadget/legacy/inode.c | 3 ++-
+ drivers/usb/gadget/udc/aspeed-vhub/hub.c | 3 ++-
+ drivers/usb/gadget/udc/at91_udc.c | 3 ++-
+ drivers/usb/gadget/udc/cdns2/cdns2-gadget.c | 13 +++++++------
+ drivers/usb/gadget/udc/dummy_hcd.c | 3 ++-
+ drivers/usb/gadget/udc/fsl_udc_core.c | 3 ++-
+ drivers/usb/gadget/udc/omap_udc.c | 3 ++-
+ drivers/usb/gadget/udc/pxa27x_udc.c | 3 ++-
+ 11 files changed, 27 insertions(+), 17 deletions(-)
+
+--- a/drivers/usb/gadget/function/f_ecm.c
++++ b/drivers/usb/gadget/function/f_ecm.c
+@@ -14,6 +14,7 @@
+ #include <linux/module.h>
+ #include <linux/device.h>
+ #include <linux/etherdevice.h>
++#include <linux/string_choices.h>
+
+ #include <linux/usb/gadget.h>
+
+@@ -390,8 +391,7 @@ static void ecm_do_notify(struct f_ecm *
+ event->wLength = 0;
+ req->length = sizeof *event;
+
+- DBG(cdev, "notify connect %s\n",
+- ecm->is_open ? "true" : "false");
++ DBG(cdev, "notify connect %s\n", str_true_false(ecm->is_open));
+ ecm->notify_state = ECM_NOTIFY_SPEED;
+ break;
+
+--- a/drivers/usb/gadget/function/f_ncm.c
++++ b/drivers/usb/gadget/function/f_ncm.c
+@@ -18,6 +18,7 @@
+ #include <linux/device.h>
+ #include <linux/etherdevice.h>
+ #include <linux/crc32.h>
++#include <linux/string_choices.h>
+
+ #include <linux/usb/cdc.h>
+ #include <linux/usb/gadget.h>
+@@ -560,7 +561,7 @@ static void ncm_do_notify(struct f_ncm *
+ req->length = sizeof *event;
+
+ DBG(cdev, "notify connect %s\n",
+- ncm->is_open ? "true" : "false");
++ str_true_false(ncm->is_open));
+ ncm->notify_state = NCM_NOTIFY_NONE;
+ break;
+
+--- a/drivers/usb/gadget/function/u_serial.c
++++ b/drivers/usb/gadget/function/u_serial.c
+@@ -21,6 +21,7 @@
+ #include <linux/tty.h>
+ #include <linux/tty_flip.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/export.h>
+ #include <linux/module.h>
+ #include <linux/console.h>
+@@ -1541,7 +1542,7 @@ static int __init userial_init(void)
+
+ pr_debug("%s: registered %d ttyGS* device%s\n", __func__,
+ MAX_U_SERIAL_PORTS,
+- (MAX_U_SERIAL_PORTS == 1) ? "" : "s");
++ str_plural(MAX_U_SERIAL_PORTS));
+
+ return status;
+ fail:
+--- a/drivers/usb/gadget/legacy/inode.c
++++ b/drivers/usb/gadget/legacy/inode.c
+@@ -20,6 +20,7 @@
+ #include <linux/uaccess.h>
+ #include <linux/sched.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/poll.h>
+ #include <linux/kthread.h>
+ #include <linux/aio.h>
+@@ -1182,7 +1183,7 @@ ep0_fasync (int f, struct file *fd, int
+ {
+ struct dev_data *dev = fd->private_data;
+ // caller must F_SETOWN before signal delivery happens
+- VDEBUG (dev, "%s %s\n", __func__, on ? "on" : "off");
++ VDEBUG(dev, "%s %s\n", __func__, str_on_off(on));
+ return fasync_helper (f, fd, on, &dev->fasync);
+ }
+
+--- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c
++++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c
+@@ -22,6 +22,7 @@
+ #include <linux/usb/gadget.h>
+ #include <linux/of.h>
+ #include <linux/regmap.h>
++#include <linux/string_choices.h>
+ #include <linux/dma-mapping.h>
+ #include <linux/bcd.h>
+ #include <linux/version.h>
+@@ -219,7 +220,7 @@ static int ast_vhub_hub_dev_feature(stru
+ if (wValue == USB_DEVICE_REMOTE_WAKEUP) {
+ ep->vhub->wakeup_en = is_set;
+ EPDBG(ep, "Hub remote wakeup %s\n",
+- is_set ? "enabled" : "disabled");
++ str_enabled_disabled(is_set));
+ return std_req_complete;
+ }
+
+--- a/drivers/usb/gadget/udc/at91_udc.c
++++ b/drivers/usb/gadget/udc/at91_udc.c
+@@ -16,6 +16,7 @@
+ #include <linux/delay.h>
+ #include <linux/ioport.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/errno.h>
+ #include <linux/list.h>
+ #include <linux/interrupt.h>
+@@ -131,7 +132,7 @@ static void proc_ep_show(struct seq_file
+ seq_printf(s, "csr %08x rxbytes=%d %s %s %s" EIGHTBITS "\n",
+ csr,
+ (csr & 0x07ff0000) >> 16,
+- (csr & (1 << 15)) ? "enabled" : "disabled",
++ str_enabled_disabled(csr & (1 << 15)),
+ (csr & (1 << 11)) ? "DATA1" : "DATA0",
+ types[(csr & 0x700) >> 8],
+
+--- a/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c
++++ b/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c
+@@ -29,6 +29,7 @@
+ #include <linux/pm_runtime.h>
+ #include <linux/interrupt.h>
+ #include <linux/property.h>
++#include <linux/string_choices.h>
+ #include <linux/dmapool.h>
+ #include <linux/iopoll.h>
+
+@@ -2233,12 +2234,12 @@ static int cdns2_init_eps(struct cdns2_d
+ dev_dbg(pdev->dev, "Init %s, SupType: CTRL: %s, INT: %s, "
+ "BULK: %s, ISOC %s, SupDir IN: %s, OUT: %s\n",
+ pep->name,
+- (pep->endpoint.caps.type_control) ? "yes" : "no",
+- (pep->endpoint.caps.type_int) ? "yes" : "no",
+- (pep->endpoint.caps.type_bulk) ? "yes" : "no",
+- (pep->endpoint.caps.type_iso) ? "yes" : "no",
+- (pep->endpoint.caps.dir_in) ? "yes" : "no",
+- (pep->endpoint.caps.dir_out) ? "yes" : "no");
++ str_yes_no(pep->endpoint.caps.type_control),
++ str_yes_no(pep->endpoint.caps.type_int),
++ str_yes_no(pep->endpoint.caps.type_bulk),
++ str_yes_no(pep->endpoint.caps.type_iso),
++ str_yes_no(pep->endpoint.caps.dir_in),
++ str_yes_no(pep->endpoint.caps.dir_out));
+
+ INIT_LIST_HEAD(&pep->pending_list);
+ INIT_LIST_HEAD(&pep->deferred_list);
+--- a/drivers/usb/gadget/udc/dummy_hcd.c
++++ b/drivers/usb/gadget/udc/dummy_hcd.c
+@@ -28,6 +28,7 @@
+ #include <linux/delay.h>
+ #include <linux/ioport.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/errno.h>
+ #include <linux/init.h>
+ #include <linux/hrtimer.h>
+@@ -649,7 +650,7 @@ static int dummy_enable(struct usb_ep *_
+ desc->bEndpointAddress & 0x0f,
+ (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out",
+ usb_ep_type_string(usb_endpoint_type(desc)),
+- max, ep->stream_en ? "enabled" : "disabled");
++ max, str_enabled_disabled(ep->stream_en));
+
+ /* at this point real hardware should be NAKing transfers
+ * to that endpoint, until a buffer is queued to it.
+--- a/drivers/usb/gadget/udc/fsl_udc_core.c
++++ b/drivers/usb/gadget/udc/fsl_udc_core.c
+@@ -22,6 +22,7 @@
+ #include <linux/errno.h>
+ #include <linux/err.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/init.h>
+ #include <linux/list.h>
+ #include <linux/interrupt.h>
+@@ -1181,7 +1182,7 @@ static int fsl_vbus_session(struct usb_g
+
+ udc = container_of(gadget, struct fsl_udc, gadget);
+ spin_lock_irqsave(&udc->lock, flags);
+- dev_vdbg(&gadget->dev, "VBUS %s\n", is_active ? "on" : "off");
++ dev_vdbg(&gadget->dev, "VBUS %s\n", str_on_off(is_active));
+ udc->vbus_active = (is_active != 0);
+ if (can_pullup(udc))
+ fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP),
+--- a/drivers/usb/gadget/udc/omap_udc.c
++++ b/drivers/usb/gadget/udc/omap_udc.c
+@@ -18,6 +18,7 @@
+ #include <linux/errno.h>
+ #include <linux/delay.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/timer.h>
+ #include <linux/list.h>
+ #include <linux/interrupt.h>
+@@ -1248,7 +1249,7 @@ static int omap_vbus_session(struct usb_
+
+ udc = container_of(gadget, struct omap_udc, gadget);
+ spin_lock_irqsave(&udc->lock, flags);
+- VDBG("VBUS %s\n", is_active ? "on" : "off");
++ VDBG("VBUS %s\n", str_on_off(is_active));
+ udc->vbus_active = (is_active != 0);
+ if (cpu_is_omap15xx()) {
+ /* "software" detect, ignored if !VBUS_MODE_1510 */
+--- a/drivers/usb/gadget/udc/pxa27x_udc.c
++++ b/drivers/usb/gadget/udc/pxa27x_udc.c
+@@ -20,6 +20,7 @@
+ #include <linux/gpio.h>
+ #include <linux/gpio/consumer.h>
+ #include <linux/slab.h>
++#include <linux/string_choices.h>
+ #include <linux/prefetch.h>
+ #include <linux/byteorder/generic.h>
+ #include <linux/platform_data/pxa2xx_udc.h>
+@@ -1083,7 +1084,7 @@ static int pxa_ep_queue(struct usb_ep *_
+
+ is_first_req = list_empty(&ep->queue);
+ ep_dbg(ep, "queue req %p(first=%s), len %d buf %p\n",
+- _req, is_first_req ? "yes" : "no",
++ _req, str_yes_no(is_first_req),
+ _req->length, _req->buf);
+
+ if (!ep->enabled) {
--- /dev/null
+From stable+bounces-287974-greg=kroah.com@vger.kernel.org Wed Jul 22 19:15:33 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:54:33 -0400
+Subject: VDUSE: avoid leaking information to userspace
+To: stable@vger.kernel.org
+Cc: "Jason Wang" <jasowang@redhat.com>, "Xie Yongji" <xieyongji@bytedance.com>, "Eugenio Pérez" <eperezma@redhat.com>, "Michael S. Tsirkin" <mst@redhat.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260722165433.1853588-4-sashal@kernel.org>
+
+From: Jason Wang <jasowang@redhat.com>
+
+[ Upstream commit 9c1523803445ee0348f62b77793266dd981596e0 ]
+
+The bounceing is not necessarily page aligned, so current VDUSE can
+leak kernel information through mapping bounce pages to
+userspace. Allocate bounce pages with __GFP_ZERO to avoid leaking
+information to userspace.
+
+Fixes: 8c773d53fb7b ("vduse: Implement an MMU-based software IOTLB")
+Cc: stable@vger.kernel.org
+Signed-off-by: Jason Wang <jasowang@redhat.com>
+Reviewed-by: Xie Yongji <xieyongji@bytedance.com>
+Reviewed-by: Eugenio Pérez <eperezma@redhat.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Message-ID: <20260130050750.4050-1-jasowang@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vdpa/vdpa_user/iova_domain.c | 2 +-
+ drivers/vdpa/vdpa_user/vduse_dev.c | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -124,7 +124,7 @@ static int vduse_domain_map_bounce_page(
+ if (!map->bounce_page) {
+ head_map = &domain->bounce_maps[(iova & PAGE_MASK) >> BOUNCE_MAP_SHIFT];
+ if (!head_map->bounce_page) {
+- tmp_page = alloc_page(GFP_ATOMIC);
++ tmp_page = alloc_page(GFP_ATOMIC | __GFP_ZERO);
+ if (!tmp_page)
+ return -ENOMEM;
+ if (cmpxchg(&head_map->bounce_page, NULL, tmp_page))
+--- a/drivers/vdpa/vdpa_user/vduse_dev.c
++++ b/drivers/vdpa/vdpa_user/vduse_dev.c
+@@ -887,7 +887,7 @@ static void *vduse_dev_alloc_coherent(st
+
+ *dma_addr = DMA_MAPPING_ERROR;
+
+- addr = alloc_pages_exact(size, flag);
++ addr = alloc_pages_exact(size, flag | __GFP_ZERO);
+ if (!addr)
+ return NULL;
+
--- /dev/null
+From stable+bounces-287972-greg=kroah.com@vger.kernel.org Wed Jul 22 19:15:43 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:54:31 -0400
+Subject: vduse: remove unused vaddr parameter of vduse_domain_free_coherent
+To: stable@vger.kernel.org
+Cc: "Eugenio Pérez" <eperezma@redhat.com>, "Michael S. Tsirkin" <mst@redhat.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260722165433.1853588-2-sashal@kernel.org>
+
+From: Eugenio Pérez <eperezma@redhat.com>
+
+[ Upstream commit 766e1749c0ef6a09651be9b8a8283d508c322b58 ]
+
+We will modify the function in next patches so let's clean it first.
+
+Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Message-Id: <20260119143306.1818855-9-eperezma@redhat.com>
+Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vdpa/vdpa_user/iova_domain.c | 3 +--
+ drivers/vdpa/vdpa_user/iova_domain.h | 3 +--
+ drivers/vdpa/vdpa_user/vduse_dev.c | 2 +-
+ 3 files changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -528,8 +528,7 @@ err:
+ }
+
+ void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
+- void *vaddr, dma_addr_t dma_addr,
+- unsigned long attrs)
++ dma_addr_t dma_addr, unsigned long attrs)
+ {
+ struct iova_domain *iovad = &domain->consistent_iovad;
+ struct vhost_iotlb_map *map;
+--- a/drivers/vdpa/vdpa_user/iova_domain.h
++++ b/drivers/vdpa/vdpa_user/iova_domain.h
+@@ -72,8 +72,7 @@ void *vduse_domain_alloc_coherent(struct
+ gfp_t flag, unsigned long attrs);
+
+ void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
+- void *vaddr, dma_addr_t dma_addr,
+- unsigned long attrs);
++ dma_addr_t dma_addr, unsigned long attrs);
+
+ void vduse_domain_reset_bounce_map(struct vduse_iova_domain *domain);
+
+--- a/drivers/vdpa/vdpa_user/vduse_dev.c
++++ b/drivers/vdpa/vdpa_user/vduse_dev.c
+@@ -904,7 +904,7 @@ static void vduse_dev_free_coherent(stru
+ struct vduse_dev *vdev = dev_to_vduse(dev);
+ struct vduse_iova_domain *domain = vdev->domain;
+
+- vduse_domain_free_coherent(domain, size, vaddr, dma_addr, attrs);
++ vduse_domain_free_coherent(domain, size, dma_addr, attrs);
+ }
+
+ static size_t vduse_dev_max_mapping_size(struct device *dev)
--- /dev/null
+From stable+bounces-287973-greg=kroah.com@vger.kernel.org Wed Jul 22 19:04:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:54:32 -0400
+Subject: vduse: take out allocations from vduse_dev_alloc_coherent
+To: stable@vger.kernel.org
+Cc: "Eugenio Pérez" <eperezma@redhat.com>, "Jason Wang" <jasowang@redhat.com>, "Michael S. Tsirkin" <mst@redhat.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260722165433.1853588-3-sashal@kernel.org>
+
+From: Eugenio Pérez <eperezma@redhat.com>
+
+[ Upstream commit 489d76520612abf9a4ede4344349105406c91a73 ]
+
+The function vduse_dev_alloc_coherent will be called under rwlock in
+next patches. Make it out of the lock to avoid increasing its fail
+rate.
+
+Acked-by: Jason Wang <jasowang@redhat.com>
+Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Message-Id: <20260119143306.1818855-10-eperezma@redhat.com>
+Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vdpa/vdpa_user/iova_domain.c | 24 +++++++-----------------
+ drivers/vdpa/vdpa_user/iova_domain.h | 5 ++---
+ drivers/vdpa/vdpa_user/vduse_dev.c | 14 ++++++++++----
+ 3 files changed, 19 insertions(+), 24 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -493,17 +493,15 @@ void vduse_domain_unmap_page(struct vdus
+ vduse_domain_free_iova(iovad, dma_addr, size);
+ }
+
+-void *vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
+- size_t size, dma_addr_t *dma_addr,
+- gfp_t flag, unsigned long attrs)
++dma_addr_t vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
++ size_t size, void *orig)
+ {
+ struct iova_domain *iovad = &domain->consistent_iovad;
+ unsigned long limit = domain->iova_limit;
+ dma_addr_t iova = vduse_domain_alloc_iova(iovad, size, limit);
+- void *orig = alloc_pages_exact(size, flag);
+
+- if (!iova || !orig)
+- goto err;
++ if (!iova)
++ return DMA_MAPPING_ERROR;
+
+ spin_lock(&domain->iotlb_lock);
+ if (vduse_iotlb_add_range(domain, (u64)iova, (u64)iova + size - 1,
+@@ -514,17 +512,12 @@ void *vduse_domain_alloc_coherent(struct
+ }
+ spin_unlock(&domain->iotlb_lock);
+
+- *dma_addr = iova;
++ return iova;
+
+- return orig;
+ err:
+- *dma_addr = DMA_MAPPING_ERROR;
+- if (orig)
+- free_pages_exact(orig, size);
+- if (iova)
+- vduse_domain_free_iova(iovad, iova, size);
++ vduse_domain_free_iova(iovad, iova, size);
+
+- return NULL;
++ return DMA_MAPPING_ERROR;
+ }
+
+ void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
+@@ -533,7 +526,6 @@ void vduse_domain_free_coherent(struct v
+ struct iova_domain *iovad = &domain->consistent_iovad;
+ struct vhost_iotlb_map *map;
+ struct vdpa_map_file *map_file;
+- phys_addr_t pa;
+
+ spin_lock(&domain->iotlb_lock);
+ map = vhost_iotlb_itree_first(domain->iotlb, (u64)dma_addr,
+@@ -545,12 +537,10 @@ void vduse_domain_free_coherent(struct v
+ map_file = (struct vdpa_map_file *)map->opaque;
+ fput(map_file->file);
+ kfree(map_file);
+- pa = map->addr;
+ vhost_iotlb_map_free(domain->iotlb, map);
+ spin_unlock(&domain->iotlb_lock);
+
+ vduse_domain_free_iova(iovad, dma_addr, size);
+- free_pages_exact(phys_to_virt(pa), size);
+ }
+
+ static vm_fault_t vduse_domain_mmap_fault(struct vm_fault *vmf)
+--- a/drivers/vdpa/vdpa_user/iova_domain.h
++++ b/drivers/vdpa/vdpa_user/iova_domain.h
+@@ -67,9 +67,8 @@ void vduse_domain_unmap_page(struct vdus
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction dir, unsigned long attrs);
+
+-void *vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
+- size_t size, dma_addr_t *dma_addr,
+- gfp_t flag, unsigned long attrs);
++dma_addr_t vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
++ size_t size, void *orig);
+
+ void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
+ dma_addr_t dma_addr, unsigned long attrs);
+--- a/drivers/vdpa/vdpa_user/vduse_dev.c
++++ b/drivers/vdpa/vdpa_user/vduse_dev.c
+@@ -883,18 +883,23 @@ static void *vduse_dev_alloc_coherent(st
+ {
+ struct vduse_dev *vdev = dev_to_vduse(dev);
+ struct vduse_iova_domain *domain = vdev->domain;
+- unsigned long iova;
+ void *addr;
+
+ *dma_addr = DMA_MAPPING_ERROR;
+- addr = vduse_domain_alloc_coherent(domain, size,
+- (dma_addr_t *)&iova, flag, attrs);
++
++ addr = alloc_pages_exact(size, flag);
+ if (!addr)
+ return NULL;
+
+- *dma_addr = (dma_addr_t)iova;
++ *dma_addr = vduse_domain_alloc_coherent(domain, size, addr);
++ if (*dma_addr == DMA_MAPPING_ERROR)
++ goto err;
+
+ return addr;
++
++err:
++ free_pages_exact(addr, size);
++ return NULL;
+ }
+
+ static void vduse_dev_free_coherent(struct device *dev, size_t size,
+@@ -905,6 +910,7 @@ static void vduse_dev_free_coherent(stru
+ struct vduse_iova_domain *domain = vdev->domain;
+
+ vduse_domain_free_coherent(domain, size, dma_addr, attrs);
++ free_pages_exact(vaddr, size);
+ }
+
+ static size_t vduse_dev_max_mapping_size(struct device *dev)
--- /dev/null
+From stable+bounces-287971-greg=kroah.com@vger.kernel.org Wed Jul 22 19:04:36 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:54:30 -0400
+Subject: vduse: Use fixed 4KB bounce pages for non-4KB page size
+To: stable@vger.kernel.org
+Cc: Sheng Zhao <sheng.zhao@bytedance.com>, "Michael S. Tsirkin" <mst@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722165433.1853588-1-sashal@kernel.org>
+
+From: Sheng Zhao <sheng.zhao@bytedance.com>
+
+[ Upstream commit 3fc3068e7247c94dec08e93fea422a1bb649bfe5 ]
+
+The allocation granularity of bounce pages is PAGE_SIZE. This may cause
+even small IO requests to occupy an entire bounce page exclusively. The
+kind of memory waste will be more significant when PAGE_SIZE is larger
+than 4KB (e.g. arm64 with 64KB pages).
+
+So, optimize it by using fixed 4KB bounce maps and iova allocation
+granularity. A single IO request occupies at least a 4KB bounce page
+instead of the entire memory page of PAGE_SIZE.
+
+Signed-off-by: Sheng Zhao <sheng.zhao@bytedance.com>
+Message-Id: <20250925113516.60305-1-sheng.zhao@bytedance.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vdpa/vdpa_user/iova_domain.c | 130 +++++++++++++++++++++++------------
+ drivers/vdpa/vdpa_user/iova_domain.h | 5 +
+ 2 files changed, 94 insertions(+), 41 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -103,19 +103,38 @@ void vduse_domain_clear_map(struct vduse
+ static int vduse_domain_map_bounce_page(struct vduse_iova_domain *domain,
+ u64 iova, u64 size, u64 paddr)
+ {
+- struct vduse_bounce_map *map;
++ struct vduse_bounce_map *map, *head_map;
++ struct page *tmp_page;
+ u64 last = iova + size - 1;
+
+ while (iova <= last) {
+- map = &domain->bounce_maps[iova >> PAGE_SHIFT];
++ /*
++ * When PAGE_SIZE is larger than 4KB, multiple adjacent bounce_maps will
++ * point to the same memory page of PAGE_SIZE. Since bounce_maps originate
++ * from IO requests, we may not be able to guarantee that the orig_phys
++ * values of all IO requests within the same 64KB memory page are contiguous.
++ * Therefore, we need to store them separately.
++ *
++ * Bounce pages are allocated on demand. As a result, it may occur that
++ * multiple bounce pages corresponding to the same 64KB memory page attempt
++ * to allocate memory simultaneously, so we use cmpxchg to handle this
++ * concurrency.
++ */
++ map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT];
+ if (!map->bounce_page) {
+- map->bounce_page = alloc_page(GFP_ATOMIC);
+- if (!map->bounce_page)
+- return -ENOMEM;
++ head_map = &domain->bounce_maps[(iova & PAGE_MASK) >> BOUNCE_MAP_SHIFT];
++ if (!head_map->bounce_page) {
++ tmp_page = alloc_page(GFP_ATOMIC);
++ if (!tmp_page)
++ return -ENOMEM;
++ if (cmpxchg(&head_map->bounce_page, NULL, tmp_page))
++ __free_page(tmp_page);
++ }
++ map->bounce_page = head_map->bounce_page;
+ }
+ map->orig_phys = paddr;
+- paddr += PAGE_SIZE;
+- iova += PAGE_SIZE;
++ paddr += BOUNCE_MAP_SIZE;
++ iova += BOUNCE_MAP_SIZE;
+ }
+ return 0;
+ }
+@@ -127,12 +146,17 @@ static void vduse_domain_unmap_bounce_pa
+ u64 last = iova + size - 1;
+
+ while (iova <= last) {
+- map = &domain->bounce_maps[iova >> PAGE_SHIFT];
++ map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT];
+ map->orig_phys = INVALID_PHYS_ADDR;
+- iova += PAGE_SIZE;
++ iova += BOUNCE_MAP_SIZE;
+ }
+ }
+
++static unsigned int offset_in_bounce_page(dma_addr_t addr)
++{
++ return (addr & ~BOUNCE_MAP_MASK);
++}
++
+ static void do_bounce(phys_addr_t orig, void *addr, size_t size,
+ enum dma_data_direction dir)
+ {
+@@ -163,7 +187,7 @@ static void vduse_domain_bounce(struct v
+ {
+ struct vduse_bounce_map *map;
+ struct page *page;
+- unsigned int offset;
++ unsigned int offset, head_offset;
+ void *addr;
+ size_t sz;
+
+@@ -171,9 +195,10 @@ static void vduse_domain_bounce(struct v
+ return;
+
+ while (size) {
+- map = &domain->bounce_maps[iova >> PAGE_SHIFT];
+- offset = offset_in_page(iova);
+- sz = min_t(size_t, PAGE_SIZE - offset, size);
++ map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT];
++ head_offset = offset_in_page(iova);
++ offset = offset_in_bounce_page(iova);
++ sz = min_t(size_t, BOUNCE_MAP_SIZE - offset, size);
+
+ if (WARN_ON(!map->bounce_page ||
+ map->orig_phys == INVALID_PHYS_ADDR))
+@@ -183,7 +208,7 @@ static void vduse_domain_bounce(struct v
+ map->user_bounce_page : map->bounce_page;
+
+ addr = kmap_local_page(page);
+- do_bounce(map->orig_phys + offset, addr + offset, sz, dir);
++ do_bounce(map->orig_phys + offset, addr + head_offset, sz, dir);
+ kunmap_local(addr);
+ size -= sz;
+ iova += sz;
+@@ -218,7 +243,7 @@ vduse_domain_get_bounce_page(struct vdus
+ struct page *page = NULL;
+
+ read_lock(&domain->bounce_lock);
+- map = &domain->bounce_maps[iova >> PAGE_SHIFT];
++ map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT];
+ if (domain->user_bounce_pages || !map->bounce_page)
+ goto out;
+
+@@ -236,7 +261,7 @@ vduse_domain_free_kernel_bounce_pages(st
+ struct vduse_bounce_map *map;
+ unsigned long pfn, bounce_pfns;
+
+- bounce_pfns = domain->bounce_size >> PAGE_SHIFT;
++ bounce_pfns = domain->bounce_size >> BOUNCE_MAP_SHIFT;
+
+ for (pfn = 0; pfn < bounce_pfns; pfn++) {
+ map = &domain->bounce_maps[pfn];
+@@ -246,7 +271,8 @@ vduse_domain_free_kernel_bounce_pages(st
+ if (!map->bounce_page)
+ continue;
+
+- __free_page(map->bounce_page);
++ if (!((pfn << BOUNCE_MAP_SHIFT) & ~PAGE_MASK))
++ __free_page(map->bounce_page);
+ map->bounce_page = NULL;
+ }
+ }
+@@ -254,8 +280,12 @@ vduse_domain_free_kernel_bounce_pages(st
+ int vduse_domain_add_user_bounce_pages(struct vduse_iova_domain *domain,
+ struct page **pages, int count)
+ {
+- struct vduse_bounce_map *map;
+- int i, ret;
++ struct vduse_bounce_map *map, *head_map;
++ int i, j, ret;
++ int inner_pages = PAGE_SIZE / BOUNCE_MAP_SIZE;
++ int bounce_pfns = domain->bounce_size >> BOUNCE_MAP_SHIFT;
++ struct page *head_page = NULL;
++ bool need_copy;
+
+ /* Now we don't support partial mapping */
+ if (count != (domain->bounce_size >> PAGE_SHIFT))
+@@ -267,16 +297,23 @@ int vduse_domain_add_user_bounce_pages(s
+ goto out;
+
+ for (i = 0; i < count; i++) {
+- map = &domain->bounce_maps[i];
+- if (map->bounce_page) {
++ need_copy = false;
++ head_map = &domain->bounce_maps[(i * inner_pages)];
++ head_page = head_map->bounce_page;
++ for (j = 0; j < inner_pages; j++) {
++ if ((i * inner_pages + j) >= bounce_pfns)
++ break;
++ map = &domain->bounce_maps[(i * inner_pages + j)];
+ /* Copy kernel page to user page if it's in use */
+- if (map->orig_phys != INVALID_PHYS_ADDR)
+- memcpy_to_page(pages[i], 0,
+- page_address(map->bounce_page),
+- PAGE_SIZE);
++ if ((head_page) && (map->orig_phys != INVALID_PHYS_ADDR))
++ need_copy = true;
++ map->user_bounce_page = pages[i];
+ }
+- map->user_bounce_page = pages[i];
+ get_page(pages[i]);
++ if ((head_page) && (need_copy))
++ memcpy_to_page(pages[i], 0,
++ page_address(head_page),
++ PAGE_SIZE);
+ }
+ domain->user_bounce_pages = true;
+ ret = 0;
+@@ -288,8 +325,12 @@ out:
+
+ void vduse_domain_remove_user_bounce_pages(struct vduse_iova_domain *domain)
+ {
+- struct vduse_bounce_map *map;
+- unsigned long i, count;
++ struct vduse_bounce_map *map, *head_map;
++ unsigned long i, j, count;
++ int inner_pages = PAGE_SIZE / BOUNCE_MAP_SIZE;
++ int bounce_pfns = domain->bounce_size >> BOUNCE_MAP_SHIFT;
++ struct page *head_page = NULL;
++ bool need_copy;
+
+ write_lock(&domain->bounce_lock);
+ if (!domain->user_bounce_pages)
+@@ -297,20 +338,27 @@ void vduse_domain_remove_user_bounce_pag
+
+ count = domain->bounce_size >> PAGE_SHIFT;
+ for (i = 0; i < count; i++) {
+- struct page *page = NULL;
+-
+- map = &domain->bounce_maps[i];
+- if (WARN_ON(!map->user_bounce_page))
++ need_copy = false;
++ head_map = &domain->bounce_maps[(i * inner_pages)];
++ if (WARN_ON(!head_map->user_bounce_page))
+ continue;
++ head_page = head_map->user_bounce_page;
+
+- /* Copy user page to kernel page if it's in use */
+- if (map->orig_phys != INVALID_PHYS_ADDR) {
+- page = map->bounce_page;
+- memcpy_from_page(page_address(page),
+- map->user_bounce_page, 0, PAGE_SIZE);
++ for (j = 0; j < inner_pages; j++) {
++ if ((i * inner_pages + j) >= bounce_pfns)
++ break;
++ map = &domain->bounce_maps[(i * inner_pages + j)];
++ if (WARN_ON(!map->user_bounce_page))
++ continue;
++ /* Copy user page to kernel page if it's in use */
++ if ((map->orig_phys != INVALID_PHYS_ADDR) && (head_map->bounce_page))
++ need_copy = true;
++ map->user_bounce_page = NULL;
+ }
+- put_page(map->user_bounce_page);
+- map->user_bounce_page = NULL;
++ if (need_copy)
++ memcpy_from_page(page_address(head_map->bounce_page),
++ head_page, 0, PAGE_SIZE);
++ put_page(head_page);
+ }
+ domain->user_bounce_pages = false;
+ out:
+@@ -581,7 +629,7 @@ vduse_domain_create(unsigned long iova_l
+ unsigned long pfn, bounce_pfns;
+ int ret;
+
+- bounce_pfns = PAGE_ALIGN(bounce_size) >> PAGE_SHIFT;
++ bounce_pfns = PAGE_ALIGN(bounce_size) >> BOUNCE_MAP_SHIFT;
+ if (iova_limit <= bounce_size)
+ return NULL;
+
+@@ -613,7 +661,7 @@ vduse_domain_create(unsigned long iova_l
+ rwlock_init(&domain->bounce_lock);
+ spin_lock_init(&domain->iotlb_lock);
+ init_iova_domain(&domain->stream_iovad,
+- PAGE_SIZE, IOVA_START_PFN);
++ BOUNCE_MAP_SIZE, IOVA_START_PFN);
+ ret = iova_domain_init_rcaches(&domain->stream_iovad);
+ if (ret)
+ goto err_iovad_stream;
+--- a/drivers/vdpa/vdpa_user/iova_domain.h
++++ b/drivers/vdpa/vdpa_user/iova_domain.h
+@@ -19,6 +19,11 @@
+
+ #define INVALID_PHYS_ADDR (~(phys_addr_t)0)
+
++#define BOUNCE_MAP_SHIFT 12
++#define BOUNCE_MAP_SIZE (1 << BOUNCE_MAP_SHIFT)
++#define BOUNCE_MAP_MASK (~(BOUNCE_MAP_SIZE - 1))
++#define BOUNCE_MAP_ALIGN(addr) (((addr) + BOUNCE_MAP_SIZE - 1) & ~(BOUNCE_MAP_SIZE - 1))
++
+ struct vduse_bounce_map {
+ struct page *bounce_page;
+ struct page *user_bounce_page;
--- /dev/null
+From stable+bounces-287657-greg=kroah.com@vger.kernel.org Wed Jul 22 02:19:43 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 19:59:01 -0400
+Subject: VFS/audit: introduce kern_path_parent() for audit
+To: stable@vger.kernel.org
+Cc: NeilBrown <neil@brown.name>, Christian Brauner <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721235904.525962-1-sashal@kernel.org>
+
+From: NeilBrown <neil@brown.name>
+
+[ Upstream commit 76a53de6f7ff0641570364234fb4489f4d4fc8e9 ]
+
+audit_alloc_mark() and audit_get_nd() both need to perform a path
+lookup getting the parent dentry (which must exist) and the final
+target (following a LAST_NORM name) which sometimes doesn't need to
+exist.
+
+They don't need the parent to be locked, but use kern_path_locked() or
+kern_path_locked_negative() anyway. This is somewhat misleading to the
+casual reader.
+
+This patch introduces a more targeted function, kern_path_parent(),
+which returns not holding locks. On success the "path" will
+be set to the parent, which must be found, and the return value is the
+dentry of the target, which might be negative.
+
+This will clear the way to rename kern_path_locked() which is
+otherwise only used to prepare for removing something.
+
+It also allows us to remove kern_path_locked_negative(), which is
+transformed into the new kern_path_parent().
+
+Signed-off-by: NeilBrown <neil@brown.name>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+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>
+---
+ fs/namei.c | 43 +++++++++++++++++++++++++++++++++++++++++++
+ include/linux/namei.h | 1 +
+ kernel/audit.h | 4 ++--
+ kernel/audit_fsnotify.c | 9 +++------
+ kernel/audit_watch.c | 9 ++++++---
+ 5 files changed, 55 insertions(+), 11 deletions(-)
+
+--- a/fs/namei.c
++++ b/fs/namei.c
+@@ -2705,6 +2705,49 @@ static struct dentry *__kern_path_locked
+ return d;
+ }
+
++/**
++ * kern_path_parent: lookup path returning parent and target
++ * @name: path name
++ * @path: path to store parent in
++ *
++ * The path @name should end with a normal component, not "." or ".." or "/".
++ * A lookup is performed and if successful the parent information
++ * is store in @parent and the dentry is returned.
++ *
++ * The dentry maybe negative, the parent will be positive.
++ *
++ * Returns: dentry or error.
++ */
++struct dentry *kern_path_parent(const char *name, struct path *path)
++{
++ struct filename *filename = getname_kernel(name);
++ struct path parent_path;
++ struct dentry *d;
++ struct qstr last;
++ int type, error;
++
++ error = filename_parentat(AT_FDCWD, filename, 0, &parent_path, &last, &type);
++ if (error) {
++ d = ERR_PTR(error);
++ goto out;
++ }
++ if (unlikely(type != LAST_NORM)) {
++ path_put(&parent_path);
++ d = ERR_PTR(-EINVAL);
++ goto out;
++ }
++
++ d = lookup_one_len_unlocked(last.name, parent_path.dentry, last.len);
++ if (IS_ERR(d)) {
++ path_put(&parent_path);
++ goto out;
++ }
++ *path = parent_path;
++out:
++ putname(filename);
++ return d;
++}
++
+ struct dentry *kern_path_locked(const char *name, struct path *path)
+ {
+ struct filename *filename = getname_kernel(name);
+--- a/include/linux/namei.h
++++ b/include/linux/namei.h
+@@ -51,6 +51,7 @@ struct dentry *lookup_one_qstr_excl(cons
+ struct dentry *base,
+ unsigned int flags);
+ extern int kern_path(const char *, unsigned, struct path *);
++struct dentry *kern_path_parent(const char *name, struct path *parent);
+
+ extern struct dentry *kern_path_create(int, const char *, struct path *, unsigned int);
+ extern struct dentry *user_path_create(int, const char __user *, struct path *, unsigned int);
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -279,8 +279,8 @@ extern struct audit_fsnotify_mark *audit
+ 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,
+- unsigned long ino, dev_t dev);
++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_exe_compare(struct task_struct *tsk,
+ struct audit_fsnotify_mark *mark);
+--- a/kernel/audit_fsnotify.c
++++ b/kernel/audit_fsnotify.c
+@@ -57,7 +57,7 @@ char *audit_mark_path(struct audit_fsnot
+ return mark->path;
+ }
+
+-int audit_mark_compare(struct audit_fsnotify_mark *mark, unsigned long ino, dev_t dev)
++int audit_mark_compare(struct audit_fsnotify_mark *mark, u64 ino, dev_t dev)
+ {
+ if (mark->ino == AUDIT_INO_UNSET)
+ return 0;
+@@ -76,17 +76,14 @@ struct audit_fsnotify_mark *audit_alloc_
+ struct audit_fsnotify_mark *audit_mark;
+ struct path path;
+ struct dentry *dentry;
+- struct inode *inode;
+ int ret;
+
+ if (pathname[0] != '/' || pathname[len-1] == '/')
+ return ERR_PTR(-EINVAL);
+
+- dentry = kern_path_locked(pathname, &path);
++ dentry = kern_path_parent(pathname, &path);
+ if (IS_ERR(dentry))
+ return ERR_CAST(dentry); /* returning an error */
+- inode = path.dentry->d_inode;
+- inode_unlock(inode);
+
+ audit_mark = kzalloc(sizeof(*audit_mark), GFP_KERNEL);
+ if (unlikely(!audit_mark)) {
+@@ -100,7 +97,7 @@ struct audit_fsnotify_mark *audit_alloc_
+ audit_update_mark(audit_mark, dentry->d_inode);
+ audit_mark->rule = krule;
+
+- ret = fsnotify_add_inode_mark(&audit_mark->mark, inode, 0);
++ ret = fsnotify_add_inode_mark(&audit_mark->mark, path.dentry->d_inode, 0);
+ if (ret < 0) {
+ audit_mark->path = NULL;
+ fsnotify_put_mark(&audit_mark->mark);
+--- 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,
+- unsigned long ino, unsigned invalidating)
++ u64 ino, unsigned int invalidating)
+ {
+ struct audit_watch *owatch, *nwatch, *nextw;
+ struct audit_krule *r, *nextr;
+@@ -347,15 +347,18 @@ static void audit_remove_parent_watches(
+ /* Get path information necessary for adding watches. */
+ static int audit_get_nd(struct audit_watch *watch, struct path *parent)
+ {
+- struct dentry *d = kern_path_locked(watch->path, parent);
++ struct dentry *d;
++
++ d = kern_path_parent(watch->path, parent);
+ if (IS_ERR(d))
+ return PTR_ERR(d);
++
+ if (d_is_positive(d)) {
+ /* update watch filter fields */
+ watch->dev = d->d_sb->s_dev;
+ watch->ino = d_backing_inode(d)->i_ino;
+ }
+- inode_unlock(d_backing_inode(parent->dentry));
++
+ dput(d);
+ return 0;
+ }
--- /dev/null
+From stable+bounces-289739-greg=kroah.com@vger.kernel.org Tue Jul 28 01:36:40 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:31 -0400
+Subject: wifi: libertas_tf: fix use-after-free in lbtf_free_adapter()
+To: stable@vger.kernel.org
+Cc: Maoyi Xie <maoyixie.tju@gmail.com>, Johannes Berg <johannes.berg@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233431.2424901-1-sashal@kernel.org>
+
+From: Maoyi Xie <maoyixie.tju@gmail.com>
+
+[ Upstream commit aa6dcd5c8dd9ba1d7d0f60093bcda41c0d6d438d ]
+
+lbtf_free_adapter() calls timer_delete(&priv->command_timer), which does
+not wait for a running command_timer_fn() callback. lbtf_free_adapter()
+runs on the teardown path right before ieee80211_free_hw() frees priv,
+both in lbtf_remove_card() and in the probe error path. command_timer is
+armed by mod_timer() in lbtf_cmd() whenever a firmware command is sent.
+command_timer_fn() dereferences priv. If a command times out as the
+device is removed, command_timer_fn() runs concurrently with teardown and
+dereferences priv after it has been freed.
+
+This is the same use-after-free that commit 03cc8f90d053 ("wifi: libertas:
+fix use-after-free in lbs_free_adapter()") fixed in the sibling libertas
+driver. The libertas_tf variant has the identical pattern and was left
+unchanged. Use timer_delete_sync() so any in-flight callback completes
+before priv is freed.
+
+Fixes: 06b16ae53192 ("libertas_tf: main.c, data paths and mac80211 handlers")
+Cc: stable@vger.kernel.org
+Signed-off-by: Maoyi Xie <maoyixie.tju@gmail.com>
+Link: https://patch.msgid.link/178211481807.2212567.8773346114561900100@maoyixie.com
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/wireless/marvell/libertas_tf/main.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/wireless/marvell/libertas_tf/main.c
++++ b/drivers/net/wireless/marvell/libertas_tf/main.c
+@@ -174,7 +174,7 @@ static void lbtf_free_adapter(struct lbt
+ {
+ lbtf_deb_enter(LBTF_DEB_MAIN);
+ lbtf_free_cmd_buffer(priv);
+- del_timer(&priv->command_timer);
++ timer_delete_sync(&priv->command_timer);
+ lbtf_deb_leave(LBTF_DEB_MAIN);
+ }
+
--- /dev/null
+From stable+bounces-289480-greg=kroah.com@vger.kernel.org Mon Jul 27 14:24:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:19:52 -0400
+Subject: xfrm: nat_keepalive: avoid double free on send error
+To: stable@vger.kernel.org
+Cc: Qianyu Luo <qianyuluo3@gmail.com>, Yuan Tan <yuantan098@gmail.com>, Xin Liu <bird@lzu.edu.cn>, Ren Wei <n05ec@lzu.edu.cn>, Eyal Birger <eyal.birger@gmail.com>, Steffen Klassert <steffen.klassert@secunet.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727121952.3778327-2-sashal@kernel.org>
+
+From: Qianyu Luo <qianyuluo3@gmail.com>
+
+[ Upstream commit 226f4a490d1a938fc838d8f8c46a4eca864c0d78 ]
+
+nat_keepalive_send() frees the keepalive skb whenever the IPv4 or IPv6
+send helper reports an error.
+
+That cleanup is only correct before the skb is handed to the output
+path. Once ip_build_and_send_pkt() or ip6_xmit() takes ownership, the
+networking stack may already have consumed the skb before returning an
+error, so freeing it again is unsafe.
+
+Handle the pre-handoff failure cases inside nat_keepalive_send_ipv4()
+and nat_keepalive_send_ipv6(), where the caller still owns the skb, and
+keep nat_keepalive_send() responsible only for family dispatch and the
+unsupported-family cleanup path.
+
+Fixes: f531d13bdfe3 ("xfrm: support sending NAT keepalives in ESP in UDP states")
+Cc: stable@vger.kernel.org
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Signed-off-by: Qianyu Luo <qianyuluo3@gmail.com>
+Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
+Reviewed-by: Eyal Birger <eyal.birger@gmail.com>
+Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/xfrm/xfrm_nat_keepalive.c | 15 +++++++++------
+ 1 file changed, 9 insertions(+), 6 deletions(-)
+
+--- a/net/xfrm/xfrm_nat_keepalive.c
++++ b/net/xfrm/xfrm_nat_keepalive.c
+@@ -55,8 +55,10 @@ static int nat_keepalive_send_ipv4(struc
+ ka->encap_sport, sock_net_uid(net, NULL));
+
+ rt = ip_route_output_key(net, &fl4);
+- if (IS_ERR(rt))
++ if (IS_ERR(rt)) {
++ kfree_skb(skb);
+ return PTR_ERR(rt);
++ }
+
+ skb_dst_set(skb, &rt->dst);
+
+@@ -101,6 +103,7 @@ static int nat_keepalive_send_ipv6(struc
+ dst = ipv6_stub->ipv6_dst_lookup_flow(net, sk, &fl6, NULL);
+ if (IS_ERR(dst)) {
+ local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
++ kfree_skb(skb);
+ return PTR_ERR(dst);
+ }
+
+@@ -118,7 +121,6 @@ static void nat_keepalive_send(struct na
+ sizeof(struct ipv6hdr)) +
+ sizeof(struct udphdr);
+ const u8 nat_ka_payload = 0xFF;
+- int err = -EAFNOSUPPORT;
+ struct sk_buff *skb;
+ struct udphdr *uh;
+
+@@ -140,16 +142,17 @@ static void nat_keepalive_send(struct na
+
+ switch (ka->family) {
+ case AF_INET:
+- err = nat_keepalive_send_ipv4(skb, ka);
++ nat_keepalive_send_ipv4(skb, ka);
+ break;
+ #if IS_ENABLED(CONFIG_IPV6)
+ case AF_INET6:
+- err = nat_keepalive_send_ipv6(skb, ka, uh);
++ nat_keepalive_send_ipv6(skb, ka, uh);
+ break;
+ #endif
+- }
+- if (err)
++ default:
+ kfree_skb(skb);
++ break;
++ }
+ }
+
+ struct nat_keepalive_work_ctx {
--- /dev/null
+From stable+bounces-289479-greg=kroah.com@vger.kernel.org Mon Jul 27 14:19:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 08:19:51 -0400
+Subject: xfrm: Use nested-BH locking for nat_keepalive_sk_ipv[46]
+To: stable@vger.kernel.org
+Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>, Steffen Klassert <steffen.klassert@secunet.com>, Herbert Xu <herbert@gondor.apana.org.au>, Paolo Abeni <pabeni@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727121952.3778327-1-sashal@kernel.org>
+
+From: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+
+[ Upstream commit 9c607d4b6589d4d380a85784514bcf4cceee1e11 ]
+
+nat_keepalive_sk_ipv[46] is a per-CPU variable and relies on disabled BH
+for its locking. Without per-CPU locking in local_bh_disable() on
+PREEMPT_RT this data structure requires explicit locking.
+
+Use sock_bh_locked which has a sock pointer and a local_lock_t. Use
+local_lock_nested_bh() for locking. This change adds only lockdep
+coverage and does not alter the functional behaviour for !PREEMPT_RT.
+
+Cc: Steffen Klassert <steffen.klassert@secunet.com>
+Cc: Herbert Xu <herbert@gondor.apana.org.au>
+Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+Link: https://patch.msgid.link/20250512092736.229935-7-bigeasy@linutronix.de
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Stable-dep-of: 226f4a490d1a ("xfrm: nat_keepalive: avoid double free on send error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/xfrm/xfrm_nat_keepalive.c | 30 ++++++++++++++++++++----------
+ 1 file changed, 20 insertions(+), 10 deletions(-)
+
+--- a/net/xfrm/xfrm_nat_keepalive.c
++++ b/net/xfrm/xfrm_nat_keepalive.c
+@@ -9,9 +9,13 @@
+ #include <net/ip6_checksum.h>
+ #include <net/xfrm.h>
+
+-static DEFINE_PER_CPU(struct sock *, nat_keepalive_sk_ipv4);
++static DEFINE_PER_CPU(struct sock_bh_locked, nat_keepalive_sk_ipv4) = {
++ .bh_lock = INIT_LOCAL_LOCK(bh_lock),
++};
+ #if IS_ENABLED(CONFIG_IPV6)
+-static DEFINE_PER_CPU(struct sock *, nat_keepalive_sk_ipv6);
++static DEFINE_PER_CPU(struct sock_bh_locked, nat_keepalive_sk_ipv6) = {
++ .bh_lock = INIT_LOCAL_LOCK(bh_lock),
++};
+ #endif
+
+ struct nat_keepalive {
+@@ -56,10 +60,12 @@ static int nat_keepalive_send_ipv4(struc
+
+ skb_dst_set(skb, &rt->dst);
+
+- sk = *this_cpu_ptr(&nat_keepalive_sk_ipv4);
++ local_lock_nested_bh(&nat_keepalive_sk_ipv4.bh_lock);
++ sk = this_cpu_read(nat_keepalive_sk_ipv4.sock);
+ sock_net_set(sk, net);
+ err = ip_build_and_send_pkt(skb, sk, fl4.saddr, fl4.daddr, NULL, tos);
+ sock_net_set(sk, &init_net);
++ local_unlock_nested_bh(&nat_keepalive_sk_ipv4.bh_lock);
+ return err;
+ }
+
+@@ -89,15 +95,19 @@ static int nat_keepalive_send_ipv6(struc
+ fl6.fl6_sport = ka->encap_sport;
+ fl6.fl6_dport = ka->encap_dport;
+
+- sk = *this_cpu_ptr(&nat_keepalive_sk_ipv6);
++ local_lock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
++ sk = this_cpu_read(nat_keepalive_sk_ipv6.sock);
+ sock_net_set(sk, net);
+ dst = ipv6_stub->ipv6_dst_lookup_flow(net, sk, &fl6, NULL);
+- if (IS_ERR(dst))
++ if (IS_ERR(dst)) {
++ local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
+ return PTR_ERR(dst);
++ }
+
+ skb_dst_set(skb, dst);
+ err = ipv6_stub->ip6_xmit(sk, skb, &fl6, skb->mark, NULL, 0, 0);
+ sock_net_set(sk, &init_net);
++ local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
+ return err;
+ }
+ #endif
+@@ -202,7 +212,7 @@ static void nat_keepalive_work(struct wo
+ (ctx.next_run - ctx.now) * HZ);
+ }
+
+-static int nat_keepalive_sk_init(struct sock * __percpu *socks,
++static int nat_keepalive_sk_init(struct sock_bh_locked __percpu *socks,
+ unsigned short family)
+ {
+ struct sock *sk;
+@@ -214,22 +224,22 @@ static int nat_keepalive_sk_init(struct
+ if (err < 0)
+ goto err;
+
+- *per_cpu_ptr(socks, i) = sk;
++ per_cpu_ptr(socks, i)->sock = sk;
+ }
+
+ return 0;
+ err:
+ for_each_possible_cpu(i)
+- inet_ctl_sock_destroy(*per_cpu_ptr(socks, i));
++ inet_ctl_sock_destroy(per_cpu_ptr(socks, i)->sock);
+ return err;
+ }
+
+-static void nat_keepalive_sk_fini(struct sock * __percpu *socks)
++static void nat_keepalive_sk_fini(struct sock_bh_locked __percpu *socks)
+ {
+ int i;
+
+ for_each_possible_cpu(i)
+- inet_ctl_sock_destroy(*per_cpu_ptr(socks, i));
++ inet_ctl_sock_destroy(per_cpu_ptr(socks, i)->sock);
+ }
+
+ void xfrm_nat_keepalive_state_updated(struct xfrm_state *x)
--- /dev/null
+From stable+bounces-287872-greg=kroah.com@vger.kernel.org Wed Jul 22 16:03:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 09:55:01 -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: <20260722135501.1366960-2-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 | 182 +++++++++++++++++++++++++++++-----------------
+ fs/xfs/scrub/trace.h | 28 ++++---
+ 2 files changed, 135 insertions(+), 75 deletions(-)
+
+--- a/fs/xfs/scrub/cow_repair.c
++++ b/fs/xfs/scrub/cow_repair.c
+@@ -73,12 +73,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.
+@@ -315,22 +309,21 @@ out_pag:
+ 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;
+
+@@ -343,8 +336,8 @@ xrep_cow_alloc(
+
+ xfs_refcount_alloc_cow_extent(sc->tp, args.fsbno, args.len);
+
+- repl->fsbno = args.fsbno;
+- repl->len = args.len;
++ del->br_startblock = args.fsbno;
++ del->br_blockcount = args.len;
+ return 0;
+ }
+
+@@ -358,19 +351,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))
+- goto bad;
+-
+- if (got->br_startoff > startoff)
++ if (!xfs_iext_lookup_extent(ip, ifp, badoff, icur, got))
+ goto bad;
++ memcpy(rep, got, sizeof(*rep));
+
+- if (got->br_blockcount == 0)
++ if (got->br_startoff > badoff)
+ goto bad;
+
+ if (isnullstartblock(got->br_startblock))
+@@ -379,6 +372,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);
+@@ -389,46 +400,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;
++ }
+ }
+
+ /*
+@@ -442,30 +499,27 @@ 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);
+- error = xrep_cow_alloc(sc, alloc_len, &repl);
++ error = xrep_cow_alloc(sc, &rep);
+ if (error)
+ return error;
+
+@@ -473,7 +527,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);
+@@ -481,12 +535,12 @@ xrep_cow_replace_range(
+ return error;
+
+ /* Note the old CoW staging extents; we'll reap them all later. */
+- 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
+@@ -2553,9 +2553,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)
+@@ -2563,28 +2563,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,
--- /dev/null
+From stable+bounces-287870-greg=kroah.com@vger.kernel.org Wed Jul 22 16:18:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 09:55:00 -0400
+Subject: xfs: factor out xfs_attr3_leaf_init
+To: stable@vger.kernel.org
+Cc: Long Li <leo.lilong@huawei.com>, "Darrick J. Wong" <djwong@kernel.org>, Carlos Maiolino <cem@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722135501.1366960-1-sashal@kernel.org>
+
+From: Long Li <leo.lilong@huawei.com>
+
+[ Upstream commit e65bb55d7f8c2041c8fdb73cd29b0b4cad4ed847 ]
+
+Factor out wrapper xfs_attr3_leaf_init function, which exported for
+external use.
+
+Reviewed-by: Darrick J. Wong <djwong@kernel.org>
+Signed-off-by: Long Li <leo.lilong@huawei.com>
+Signed-off-by: Carlos Maiolino <cem@kernel.org>
+Stable-dep-of: a1caeeadbf57 ("xfs: don't replace the wrong part of the cow fork")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/xfs/libxfs/xfs_attr_leaf.c | 22 ++++++++++++++++++++++
+ fs/xfs/libxfs/xfs_attr_leaf.h | 3 +++
+ 2 files changed, 25 insertions(+)
+
+--- a/fs/xfs/libxfs/xfs_attr_leaf.c
++++ b/fs/xfs/libxfs/xfs_attr_leaf.c
+@@ -1330,6 +1330,28 @@ xfs_attr3_leaf_create(
+ }
+
+ /*
++ * Reinitialize an existing attr fork block as an empty leaf, and attach
++ * the buffer to tp.
++ */
++int
++xfs_attr3_leaf_init(
++ struct xfs_trans *tp,
++ struct xfs_inode *dp,
++ xfs_dablk_t blkno)
++{
++ struct xfs_buf *bp = NULL;
++ struct xfs_da_args args = {
++ .trans = tp,
++ .dp = dp,
++ .owner = dp->i_ino,
++ .geo = dp->i_mount->m_attr_geo,
++ };
++
++ ASSERT(tp != NULL);
++
++ return xfs_attr3_leaf_create(&args, blkno, &bp);
++}
++/*
+ * Split the leaf node, rebalance, then add the new entry.
+ *
+ * Returns 0 if the entry was added, 1 if a further split is needed or a
+--- a/fs/xfs/libxfs/xfs_attr_leaf.h
++++ b/fs/xfs/libxfs/xfs_attr_leaf.h
+@@ -86,6 +86,9 @@ int xfs_attr3_leaf_list_int(struct xfs_b
+ /*
+ * Routines used for shrinking the Btree.
+ */
++
++int xfs_attr3_leaf_init(struct xfs_trans *tp, struct xfs_inode *dp,
++ xfs_dablk_t blkno);
+ int xfs_attr3_leaf_toosmall(struct xfs_da_state *state, int *retval);
+ void xfs_attr3_leaf_unbalance(struct xfs_da_state *state,
+ struct xfs_da_state_blk *drop_blk,