From: Greg Kroah-Hartman Date: Thu, 30 Jul 2026 12:50:11 +0000 (+0200) Subject: 6.12-stable patches X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0864f15ad7765ca30da3c14593491dba4d124a63;p=thirdparty%2Fkernel%2Fstable-queue.git 6.12-stable patches added patches: afs-drop-the-net-parameter-from-afs_unuse_cell.patch afs-fix-afs_server-ref-accounting.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 afs-improve-server-refcount-active-count-tracing.patch afs-make-afs_lookup_cell-take-a-trace-note.patch afs-simplify-cell-record-handling.patch afs-use-the-per-peer-app-data-provided-by-rxrpc.patch alsa-hda-conexant-remove-mic-bias-threshold-override.patch alsa-hda-fix-cached-processing-coefficient-verbs.patch arm64-dts-qcom-correct-rbr-opp-entry.patch arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.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 asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch audit-use-unsigned-int-instead-of-unsigned.patch audit-widen-ino-fields-to-u64.patch block-add-helper-add_disk_final.patch block-remove-redundant-gd_need_part_scan-in-add_disk_final.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 bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.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 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 dm-integrity-fix-leaking-uninitialized-kernel-memory.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 fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch fbcon-use-correct-type-for-vc_resize-return-value.patch fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch gpu-move-drm-buddy-allocator-one-level-up-part-two.patch gve-fix-header-buffer-corruption-with-header-split-and-hw-gro.patch i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch iommufd-avoid-partial-fault-group-delivery-in-iommufd_fault_fops_read.patch iommufd-break-the-loop-on-failure-in-iommufd_fault_fops_read.patch iommufd-reject-invalid-read-count-in-iommufd_fault_fops_read.patch ipmi-fix-refcount-leak-in-i_ipmi_request.patch landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch landlock-prepare-to-use-credential-instead-of-domain-for-fowner.patch locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch mm-prepare-to-move-subsection_map_init-to-mm-sparse-vmemmap.c.patch mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch mmc-vub300-fix-use-after-free-on-disconnect.patch mmc-vub300-fix-use-after-free-on-probe-failure.patch mmc-vub300-rename-probe-error-labels.patch mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch net-macb-drop-in-flight-tx-skbs-on-close.patch net-mana-validate-the-packet-length-reported-by-the-nic.patch net-mlx5-hws-fix-matcher-leak-on-resize-target-setup-failure.patch net-mlx5-hws-rearrange-to-prevent-forward-declaration.patch netfilter-bitwise-rename-some-boolean-operation-functions.patch netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch netfilter-nf_tables-remove-register-tracking-infrastructure.patch netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch nfsd-pass-nfsd_file-to-nfsd_iter_read.patch nvmet-auth-reject-short-auth_receive-buffers.patch nvmet-introduce-nvmet_req_transfer_len.patch octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch octeontx2-annotate-mmio-regions-as-__iomem.patch octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch remoteproc-xlnx-check-remote-core-state.patch rxrpc-allow-the-app-to-store-private-data-on-peer-structs.patch rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch rxrpc-fix-cpu-time-starvation-in-i-o-thread.patch rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch rxrpc-fix-socket-notification-race.patch rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch tcp-decrement-tcp_md5_needed-static-branch.patch tcp-defer-md5sig_info-kfree-past-rcu-grace-period-in-tcp_connect.patch thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch thunderbolt-remove-service-debugfs-entries-during-unregister.patch thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch usb-gadget-fsl-udc-fix-dev_printk-device.patch usb-gadget-use-str_enable_disable-like-helpers.patch vduse-avoid-leaking-information-to-userspace.patch vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch vfs-audit-introduce-kern_path_parent-for-audit.patch wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch xfrm-nat_keepalive-avoid-double-free-on-send-error.patch xfrm-use-nested-bh-locking-for-nat_keepalive_sk_ipv.patch xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch xfs-factor-out-xfs_attr3_leaf_init.patch --- diff --git a/queue-6.12/afs-drop-the-net-parameter-from-afs_unuse_cell.patch b/queue-6.12/afs-drop-the-net-parameter-from-afs_unuse_cell.patch new file mode 100644 index 0000000000..f9d8255dcc --- /dev/null +++ b/queue-6.12/afs-drop-the-net-parameter-from-afs_unuse_cell.patch @@ -0,0 +1,210 @@ +From stable+bounces-290946-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:52 2026 +From: Sasha Levin +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 , Marc Dionne , linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin +Message-ID: <20260730010137.1700032-3-sashal@kernel.org> + +From: David Howells + +[ Upstream commit 469c82b558628482002e4819d35b7670f0a989fd ] + +Remove the redundant net parameter to afs_unuse_cell() as cell->net can be +used instead. + +Signed-off-by: David Howells +cc: Marc Dionne +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 ") \ diff --git a/queue-6.12/afs-fix-afs_server-ref-accounting.patch b/queue-6.12/afs-fix-afs_server-ref-accounting.patch new file mode 100644 index 0000000000..62a770e207 --- /dev/null +++ b/queue-6.12/afs-fix-afs_server-ref-accounting.patch @@ -0,0 +1,1095 @@ +From stable+bounces-290952-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:14 2026 +From: Sasha Levin +Date: Wed, 29 Jul 2026 21:01:35 -0400 +Subject: afs: Fix afs_server ref accounting +To: stable@vger.kernel.org +Cc: David Howells , Marc Dionne , linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin +Message-ID: <20260730010137.1700032-6-sashal@kernel.org> + +From: David Howells + +[ 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 +cc: Marc Dionne +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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") \ diff --git a/queue-6.12/afs-fix-dynamic-lookup-to-fail-on-cell-lookup-failure.patch b/queue-6.12/afs-fix-dynamic-lookup-to-fail-on-cell-lookup-failure.patch new file mode 100644 index 0000000000..04ac3a0982 --- /dev/null +++ b/queue-6.12/afs-fix-dynamic-lookup-to-fail-on-cell-lookup-failure.patch @@ -0,0 +1,347 @@ +From stable+bounces-290954-greg=kroah.com@vger.kernel.org Thu Jul 30 03:05:15 2026 +From: Sasha Levin +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 , Markus Suvanto , Marc Dionne , linux-afs@lists.infradead.org, Christian Brauner , Sasha Levin +Message-ID: <20260730010137.1700032-8-sashal@kernel.org> + +From: David Howells + +[ 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 +Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220685 +Signed-off-by: David Howells +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 +cc: Marc Dionne +cc: linux-afs@lists.infradead.org +Signed-off-by: Christian Brauner +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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)) diff --git a/queue-6.12/afs-fix-lack-of-locking-around-modifications-of-net-cells_dyn_ino.patch b/queue-6.12/afs-fix-lack-of-locking-around-modifications-of-net-cells_dyn_ino.patch new file mode 100644 index 0000000000..88dc318295 --- /dev/null +++ b/queue-6.12/afs-fix-lack-of-locking-around-modifications-of-net-cells_dyn_ino.patch @@ -0,0 +1,77 @@ +From stable+bounces-290959-greg=kroah.com@vger.kernel.org Thu Jul 30 03:06:42 2026 +From: Sasha Levin +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 , Marc Dionne , linux-afs@lists.infradead.org, "Christian Brauner (Amutable)" , Sasha Levin +Message-ID: <20260730010140.1700208-8-sashal@kernel.org> + +From: David Howells + +[ 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 +Link: https://patch.msgid.link/20260622090856.2746629-19-dhowells@redhat.com +cc: Marc Dionne +cc: linux-afs@lists.infradead.org +Signed-off-by: Christian Brauner (Amutable) +Stable-dep-of: 26f17ce6fa3f ("afs: Fix premature cell exposure through /afs") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.12/afs-improve-server-refcount-active-count-tracing.patch b/queue-6.12/afs-improve-server-refcount-active-count-tracing.patch new file mode 100644 index 0000000000..e482b3556b --- /dev/null +++ b/queue-6.12/afs-improve-server-refcount-active-count-tracing.patch @@ -0,0 +1,181 @@ +From stable+bounces-290944-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:25 2026 +From: Sasha Levin +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 , Marc Dionne , linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin +Message-ID: <20260730010137.1700032-1-sashal@kernel.org> + +From: David Howells + +[ 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 +cc: Marc Dionne +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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, diff --git a/queue-6.12/afs-make-afs_lookup_cell-take-a-trace-note.patch b/queue-6.12/afs-make-afs_lookup_cell-take-a-trace-note.patch new file mode 100644 index 0000000000..1cffd08e48 --- /dev/null +++ b/queue-6.12/afs-make-afs_lookup_cell-take-a-trace-note.patch @@ -0,0 +1,176 @@ +From stable+bounces-290945-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:38 2026 +From: Sasha Levin +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 , Marc Dionne , linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin +Message-ID: <20260730010137.1700032-2-sashal@kernel.org> + +From: David Howells + +[ 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 +cc: Marc Dionne +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 ") \ diff --git a/queue-6.12/afs-simplify-cell-record-handling.patch b/queue-6.12/afs-simplify-cell-record-handling.patch new file mode 100644 index 0000000000..61406307a8 --- /dev/null +++ b/queue-6.12/afs-simplify-cell-record-handling.patch @@ -0,0 +1,842 @@ +From stable+bounces-290953-greg=kroah.com@vger.kernel.org Thu Jul 30 03:02:18 2026 +From: Sasha Levin +Date: Wed, 29 Jul 2026 21:01:36 -0400 +Subject: afs: Simplify cell record handling +To: stable@vger.kernel.org +Cc: David Howells , Marc Dionne , linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin +Message-ID: <20260730010137.1700032-7-sashal@kernel.org> + +From: David Howells + +[ 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 +cc: Marc Dionne +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 ") \ diff --git a/queue-6.12/afs-use-the-per-peer-app-data-provided-by-rxrpc.patch b/queue-6.12/afs-use-the-per-peer-app-data-provided-by-rxrpc.patch new file mode 100644 index 0000000000..4d8431c75a --- /dev/null +++ b/queue-6.12/afs-use-the-per-peer-app-data-provided-by-rxrpc.patch @@ -0,0 +1,521 @@ +From stable+bounces-290950-greg=kroah.com@vger.kernel.org Thu Jul 30 03:04:15 2026 +From: Sasha Levin +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 , Marc Dionne , linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, Sasha Levin +Message-ID: <20260730010137.1700032-5-sashal@kernel.org> + +From: David Howells + +[ 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 +cc: Marc Dionne +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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); + diff --git a/queue-6.12/alsa-hda-conexant-remove-mic-bias-threshold-override.patch b/queue-6.12/alsa-hda-conexant-remove-mic-bias-threshold-override.patch new file mode 100644 index 0000000000..9a993419f8 --- /dev/null +++ b/queue-6.12/alsa-hda-conexant-remove-mic-bias-threshold-override.patch @@ -0,0 +1,47 @@ +From stable+bounces-287797-greg=kroah.com@vger.kernel.org Wed Jul 22 12:57:35 2026 +From: Sasha Levin +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 , Takashi Iwai , Sasha Levin +Message-ID: <20260722105104.989130-1-sashal@kernel.org> + +From: Zhang Heng + +[ 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 +Link: https://patch.msgid.link/20260713100329.306892-1-zhangheng@kylinos.cn +Signed-off-by: Takashi Iwai +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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. + */ diff --git a/queue-6.12/alsa-hda-fix-cached-processing-coefficient-verbs.patch b/queue-6.12/alsa-hda-fix-cached-processing-coefficient-verbs.patch new file mode 100644 index 0000000000..726c4a2bb8 --- /dev/null +++ b/queue-6.12/alsa-hda-fix-cached-processing-coefficient-verbs.patch @@ -0,0 +1,62 @@ +From stable+bounces-287802-greg=kroah.com@vger.kernel.org Wed Jul 22 13:03:12 2026 +From: Sasha Levin +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 , Takashi Iwai , Sasha Levin +Message-ID: <20260722110122.1006393-1-sashal@kernel.org> + +From: Xu Rao + +[ 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 +Link: https://patch.msgid.link/DB9023BF2920BA99+20260707132419.1731342-1-raoxu@uniontech.com +Signed-off-by: Takashi Iwai +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } diff --git a/queue-6.12/arm64-dts-qcom-correct-rbr-opp-entry.patch b/queue-6.12/arm64-dts-qcom-correct-rbr-opp-entry.patch new file mode 100644 index 0000000000..56c9dab5e0 --- /dev/null +++ b/queue-6.12/arm64-dts-qcom-correct-rbr-opp-entry.patch @@ -0,0 +1,309 @@ +From stable+bounces-288120-greg=kroah.com@vger.kernel.org Thu Jul 23 01:53:22 2026 +From: Sasha Levin +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 , Bjorn Andersson , Sasha Levin +Message-ID: <20260722234700.2612312-1-sashal@kernel.org> + +From: Dmitry Baryshkov + +[ 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 +Link: https://lore.kernel.org/r/20260304-msm-fix-rbr-v1-1-b9eba986eaef@oss.qualcomm.com +Signed-off-by: Bjorn Andersson +Stable-dep-of: c17e22094667 ("arm64: dts: qcom: hamoa: Fix OPP tables for all DisplayPort controllers") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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>; + }; + diff --git a/queue-6.12/arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch b/queue-6.12/arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch new file mode 100644 index 0000000000..c57be20503 --- /dev/null +++ b/queue-6.12/arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch @@ -0,0 +1,176 @@ +From stable+bounces-288119-greg=kroah.com@vger.kernel.org Thu Jul 23 01:53:30 2026 +From: Sasha Levin +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 , Dmitry Baryshkov , Konrad Dybcio , Bjorn Andersson , Sasha Levin +Message-ID: <20260722234700.2612312-2-sashal@kernel.org> + +From: Abel Vesa + +[ 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 +Reviewed-by: Konrad Dybcio +Reviewed-by: Dmitry Baryshkov +Signed-off-by: Abel Vesa +Link: https://lore.kernel.org/r/20260323-hamoa-fix-dp3-opp-table-v3-1-a823776bd1b0@oss.qualcomm.com +Signed-off-by: Bjorn Andersson +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 { diff --git a/queue-6.12/asoc-mediatek-mt8183-afe-pcm-shorten-memif_data-table-using-macros.patch b/queue-6.12/asoc-mediatek-mt8183-afe-pcm-shorten-memif_data-table-using-macros.patch new file mode 100644 index 0000000000..bb7fe79956 --- /dev/null +++ b/queue-6.12/asoc-mediatek-mt8183-afe-pcm-shorten-memif_data-table-using-macros.patch @@ -0,0 +1,285 @@ +From stable+bounces-288164-greg=kroah.com@vger.kernel.org Thu Jul 23 04:29:19 2026 +From: Sasha Levin +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 , AngeloGioacchino Del Regno , Mark Brown , Sasha Levin +Message-ID: <20260723022909.2845199-1-sashal@kernel.org> + +From: Chen-Yu Tsai + +[ 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 +Reviewed-by: AngeloGioacchino Del Regno +Link: https://patch.msgid.link/20250515073825.4155297-2-wenst@chromium.org +Signed-off-by: Mark Brown +Stable-dep-of: f0334fbfd107 ("ASoC: mediatek: mt8183: Check runtime resume during probe") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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] = { diff --git a/queue-6.12/asoc-mediatek-mt8183-afe-pcm-support-32-bit-dma-addresses.patch b/queue-6.12/asoc-mediatek-mt8183-afe-pcm-support-32-bit-dma-addresses.patch new file mode 100644 index 0000000000..a736c90403 --- /dev/null +++ b/queue-6.12/asoc-mediatek-mt8183-afe-pcm-support-32-bit-dma-addresses.patch @@ -0,0 +1,80 @@ +From stable+bounces-288165-greg=kroah.com@vger.kernel.org Thu Jul 23 04:29:19 2026 +From: Sasha Levin +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 , AngeloGioacchino Del Regno , Mark Brown , Sasha Levin +Message-ID: <20260723022909.2845199-2-sashal@kernel.org> + +From: Chen-Yu Tsai + +[ 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 +Signed-off-by: Chen-Yu Tsai +Link: https://patch.msgid.link/20250612074901.4023253-8-wenst@chromium.org +Signed-off-by: Mark Brown +Stable-dep-of: f0334fbfd107 ("ASoC: mediatek: mt8183: Check runtime resume during probe") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + + #include ++#include + #include + #include + #include +@@ -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; diff --git a/queue-6.12/asoc-mediatek-mt8183-afe-pcm-use-local-dev-pointer-in-driver-callbacks.patch b/queue-6.12/asoc-mediatek-mt8183-afe-pcm-use-local-dev-pointer-in-driver-callbacks.patch new file mode 100644 index 0000000000..5329a4a749 --- /dev/null +++ b/queue-6.12/asoc-mediatek-mt8183-afe-pcm-use-local-dev-pointer-in-driver-callbacks.patch @@ -0,0 +1,144 @@ +From stable+bounces-288166-greg=kroah.com@vger.kernel.org Thu Jul 23 04:30:44 2026 +From: Sasha Levin +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 , AngeloGioacchino Del Regno , Mark Brown , Sasha Levin +Message-ID: <20260723022909.2845199-3-sashal@kernel.org> + +From: Chen-Yu Tsai + +[ 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 +Signed-off-by: Chen-Yu Tsai +Link: https://patch.msgid.link/20250612074901.4023253-10-wenst@chromium.org +Signed-off-by: Mark Brown +Stable-dep-of: f0334fbfd107 ("ASoC: mediatek: mt8183: Check runtime resume during probe") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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[] = { diff --git a/queue-6.12/asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch b/queue-6.12/asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch new file mode 100644 index 0000000000..a70c15f15c --- /dev/null +++ b/queue-6.12/asoc-mediatek-mt8183-check-runtime-resume-during-probe.patch @@ -0,0 +1,60 @@ +From stable+bounces-288167-greg=kroah.com@vger.kernel.org Thu Jul 23 04:30:48 2026 +From: Sasha Levin +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" , "Mark Brown" , "Sasha Levin" +Message-ID: <20260723022909.2845199-4-sashal@kernel.org> + +From: Cássio Gabriel + +[ 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 +Link: https://patch.msgid.link/20260527-asoc-mt8183-probe-cleanup-v1-2-4f4f5593c8d1@gmail.com +Signed-off-by: Mark Brown +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + diff --git a/queue-6.12/asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch b/queue-6.12/asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch new file mode 100644 index 0000000000..2f0056a47f --- /dev/null +++ b/queue-6.12/asoc-mediatek-mt8192-afe-pcm-simplify-probe-with-local-dev-variable.patch @@ -0,0 +1,94 @@ +From stable+bounces-288152-greg=kroah.com@vger.kernel.org Thu Jul 23 03:18:39 2026 +From: Sasha Levin +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 , Mark Brown , Sasha Levin +Message-ID: <20260723011715.2780615-1-sashal@kernel.org> + +From: Tang Bin + +[ Upstream commit 01981565c764c554cc96e2d30a71c42975171416 ] + +Simplify the function mt8192_afe_pcm_dev_probe() by +using local 'dev' instead of '&pdev->dev'. + +Signed-off-by: Tang Bin +Link: https://patch.msgid.link/20241025080026.2393-1-tangbin@cmss.chinamobile.com +Signed-off-by: Mark Brown +Stable-dep-of: e24d5dde56a5 ("ASoC: mediatek: mt8192: Check runtime resume during probe") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch b/queue-6.12/asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch new file mode 100644 index 0000000000..2de70491e0 --- /dev/null +++ b/queue-6.12/asoc-mediatek-mt8192-check-runtime-resume-during-probe.patch @@ -0,0 +1,62 @@ +From stable+bounces-288151-greg=kroah.com@vger.kernel.org Thu Jul 23 03:18:34 2026 +From: Sasha Levin +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" , "Mark Brown" , "Sasha Levin" +Message-ID: <20260723011715.2780615-2-sashal@kernel.org> + +From: Cássio Gabriel + +[ 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 +Link: https://patch.msgid.link/20260527-asoc-mt8192-probe-cleanup-v1-2-1bb834d05b72@gmail.com +Signed-off-by: Mark Brown +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + diff --git a/queue-6.12/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch b/queue-6.12/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch new file mode 100644 index 0000000000..a4fa3244ef --- /dev/null +++ b/queue-6.12/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch @@ -0,0 +1,100 @@ +From stable+bounces-290226-greg=kroah.com@vger.kernel.org Wed Jul 29 04:31:55 2026 +From: Sasha Levin +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 , Damien Le Moal , Niklas Cassel , Sasha Levin +Message-ID: <20260729022948.1112469-1-sashal@kernel.org> + +From: Bryam Vargas + +[ 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 +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 +Reviewed-by: Niklas Cassel +Signed-off-by: Damien Le Moal +[ adapted `kzalloc_flex()` allocation to `kzalloc(struct_size(...), GFP_KERNEL)` and adjusted context offsets. ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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, diff --git a/queue-6.12/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch b/queue-6.12/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch new file mode 100644 index 0000000000..c8b10e24cd --- /dev/null +++ b/queue-6.12/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch @@ -0,0 +1,364 @@ +From stable+bounces-287660-greg=kroah.com@vger.kernel.org Wed Jul 22 02:00:45 2026 +From: Sasha Levin +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 , stable@kernel.org, Waiman Long , Richard Guy Briggs , Nathan Chancellor , Paul Moore , Sasha Levin +Message-ID: <20260721235904.525962-4-sashal@kernel.org> + +From: Ricardo Robaina + +[ 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: + + 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 + + +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 +Acked-by: Richard Guy Briggs +Signed-off-by: Nathan Chancellor +Signed-off-by: Ricardo Robaina +[PM: move link metadata into the msg, apply fix from NC] +Signed-off-by: Paul Moore +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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)) { diff --git a/queue-6.12/audit-use-unsigned-int-instead-of-unsigned.patch b/queue-6.12/audit-use-unsigned-int-instead-of-unsigned.patch new file mode 100644 index 0000000000..c74761a65f --- /dev/null +++ b/queue-6.12/audit-use-unsigned-int-instead-of-unsigned.patch @@ -0,0 +1,190 @@ +From stable+bounces-287659-greg=kroah.com@vger.kernel.org Wed Jul 22 02:19:46 2026 +From: Sasha Levin +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 , Paul Moore , Sasha Levin +Message-ID: <20260721235904.525962-3-sashal@kernel.org> + +From: Ricardo Robaina + +[ 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 +Signed-off-by: Paul Moore +Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + +-unsigned compat_dir_class[] = { ++unsigned int compat_dir_class[] = { + #include + ~0U + }; + +-unsigned compat_read_class[] = { ++unsigned int compat_read_class[] = { + #include + ~0U + }; + +-unsigned compat_write_class[] = { ++unsigned int compat_write_class[] = { + #include + ~0U + }; + +-unsigned compat_chattr_class[] = { ++unsigned int compat_chattr_class[] = { + #include + ~0U + }; + +-unsigned compat_signal_class[] = { ++unsigned int compat_signal_class[] = { + #include + ~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 diff --git a/queue-6.12/audit-widen-ino-fields-to-u64.patch b/queue-6.12/audit-widen-ino-fields-to-u64.patch new file mode 100644 index 0000000000..3be00e97b0 --- /dev/null +++ b/queue-6.12/audit-widen-ino-fields-to-u64.patch @@ -0,0 +1,159 @@ +From stable+bounces-287658-greg=kroah.com@vger.kernel.org Wed Jul 22 02:00:42 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 19:59:02 -0400 +Subject: audit: widen ino fields to u64 +To: stable@vger.kernel.org +Cc: Jeff Layton , Paul Moore , Christian Brauner , Sasha Levin +Message-ID: <20260721235904.525962-2-sashal@kernel.org> + +From: Jeff Layton + +[ 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 +Link: https://patch.msgid.link/20260304-iino-u64-v3-2-2257ad83d372@kernel.org +Acked-by: Paul Moore +Signed-off-by: Christian Brauner +Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + +-#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), diff --git a/queue-6.12/block-add-helper-add_disk_final.patch b/queue-6.12/block-add-helper-add_disk_final.patch new file mode 100644 index 0000000000..ad88116e29 --- /dev/null +++ b/queue-6.12/block-add-helper-add_disk_final.patch @@ -0,0 +1,100 @@ +From stable+bounces-289610-greg=kroah.com@vger.kernel.org Mon Jul 27 18:06:59 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 11:59:27 -0400 +Subject: block: add helper add_disk_final() +To: stable@vger.kernel.org +Cc: Ming Lei , Hannes Reinecke , Nilay Shroff , Christoph Hellwig , Jens Axboe , Sasha Levin +Message-ID: <20260727155928.1732187-1-sashal@kernel.org> + +From: Ming Lei + +[ 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 +Reviewed-by: Hannes Reinecke +Reviewed-by: Nilay Shroff +Reviewed-by: Christoph Hellwig +Link: https://lore.kernel.org/r/20250505141805.2751237-8-ming.lei@redhat.com +Signed-off-by: Jens Axboe +Stable-dep-of: 181bb9c9eae4 ("block: remove redundant GD_NEED_PART_SCAN in add_disk_final()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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: diff --git a/queue-6.12/block-remove-redundant-gd_need_part_scan-in-add_disk_final.patch b/queue-6.12/block-remove-redundant-gd_need_part_scan-in-add_disk_final.patch new file mode 100644 index 0000000000..2d50973b5a --- /dev/null +++ b/queue-6.12/block-remove-redundant-gd_need_part_scan-in-add_disk_final.patch @@ -0,0 +1,65 @@ +From stable+bounces-289611-greg=kroah.com@vger.kernel.org Mon Jul 27 18:07:02 2026 +From: Sasha Levin +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 , Christoph Hellwig , Jens Axboe , Sasha Levin +Message-ID: <20260727155928.1732187-2-sashal@kernel.org> + +From: Connor Williamson + +[ 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 +Reviewed-by: Christoph Hellwig +Link: https://patch.msgid.link/20260615130715.53693-1-connordw@amazon.com +Signed-off-by: Jens Axboe +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/bluetooth-add-pa_link-to-distinguish-big-sync-and-pa-sync-connections.patch b/queue-6.12/bluetooth-add-pa_link-to-distinguish-big-sync-and-pa-sync-connections.patch new file mode 100644 index 0000000000..b4953c9c21 --- /dev/null +++ b/queue-6.12/bluetooth-add-pa_link-to-distinguish-big-sync-and-pa-sync-connections.patch @@ -0,0 +1,386 @@ +From stable+bounces-290941-greg=kroah.com@vger.kernel.org Thu Jul 30 03:00:26 2026 +From: Sasha Levin +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 , Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260730005817.1696365-1-sashal@kernel.org> + +From: Yang Li + +[ 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 +Signed-off-by: Luiz Augusto von Dentz +Stable-dep-of: 9d4b01a0bf8d ("Bluetooth: hci_core: Fix not accounting for BIS/CIS/PA links separately") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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: diff --git a/queue-6.12/bluetooth-hci_core-fix-not-accounting-for-bis-cis-pa-links-separately.patch b/queue-6.12/bluetooth-hci_core-fix-not-accounting-for-bis-cis-pa-links-separately.patch new file mode 100644 index 0000000000..3333a9c0f1 --- /dev/null +++ b/queue-6.12/bluetooth-hci_core-fix-not-accounting-for-bis-cis-pa-links-separately.patch @@ -0,0 +1,100 @@ +From stable+bounces-290942-greg=kroah.com@vger.kernel.org Thu Jul 30 03:00:41 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260730005817.1696365-2-sashal@kernel.org> + +From: Luiz Augusto von Dentz + +[ 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 +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.12/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch b/queue-6.12/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch new file mode 100644 index 0000000000..cdfbe67a25 --- /dev/null +++ b/queue-6.12/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch @@ -0,0 +1,97 @@ +From stable+bounces-290094-greg=kroah.com@vger.kernel.org Tue Jul 28 19:53:38 2026 +From: Sasha Levin +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 , "Masami Hiramatsu (Google)" , Sasha Levin +Message-ID: <20260728170708.183603-2-sashal@kernel.org> + +From: Breno Leitao + +[ 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 +Signed-off-by: Masami Hiramatsu (Google) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + diff --git a/queue-6.12/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch b/queue-6.12/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch new file mode 100644 index 0000000000..03b6e499b8 --- /dev/null +++ b/queue-6.12/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch @@ -0,0 +1,165 @@ +From stable+bounces-290093-greg=kroah.com@vger.kernel.org Tue Jul 28 19:53:27 2026 +From: Sasha Levin +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 , "Masami Hiramatsu (Google)" , Sasha Levin +Message-ID: <20260728170708.183603-1-sashal@kernel.org> + +From: Breno Leitao + +[ 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 +Signed-off-by: Masami Hiramatsu (Google) +Stable-dep-of: dec4d8118c17 ("bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.12/bpf-refactor-acquire-release-_reference_state.patch b/queue-6.12/bpf-refactor-acquire-release-_reference_state.patch new file mode 100644 index 0000000000..ea2841921d --- /dev/null +++ b/queue-6.12/bpf-refactor-acquire-release-_reference_state.patch @@ -0,0 +1,116 @@ +From stable+bounces-289595-greg=kroah.com@vger.kernel.org Mon Jul 27 17:52:10 2026 +From: Sasha Levin +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 , Eduard Zingerman , Alexei Starovoitov , Sasha Levin +Message-ID: <20260727154103.1515347-1-sashal@kernel.org> + +From: Kumar Kartikeya Dwivedi + +[ 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 +Signed-off-by: Kumar Kartikeya Dwivedi +Link: https://lore.kernel.org/r/20241204030400.208005-3-memxor@gmail.com +Signed-off-by: Alexei Starovoitov +Stable-dep-of: 5e0b273e0a62 ("bpf: Reset register bounds before narrowing retval range in check_mem_access()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) { diff --git a/queue-6.12/bpf-refactor-check_ctx_access.patch b/queue-6.12/bpf-refactor-check_ctx_access.patch new file mode 100644 index 0000000000..8de02f3b86 --- /dev/null +++ b/queue-6.12/bpf-refactor-check_ctx_access.patch @@ -0,0 +1,84 @@ +From stable+bounces-289596-greg=kroah.com@vger.kernel.org Mon Jul 27 17:49:26 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 11:41:02 -0400 +Subject: bpf: Refactor check_ctx_access() +To: stable@vger.kernel.org +Cc: Amery Hung , Alexei Starovoitov , Sasha Levin +Message-ID: <20260727154103.1515347-2-sashal@kernel.org> + +From: Amery Hung + +[ 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 +Link: https://lore.kernel.org/r/20250221175644.1822383-1-ameryhung@gmail.com +Signed-off-by: Alexei Starovoitov +Stable-dep-of: 5e0b273e0a62 ("bpf: Reset register bounds before narrowing retval range in check_mem_access()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) { diff --git a/queue-6.12/bpf-reset-register-bounds-before-narrowing-retval-range-in-check_mem_access.patch b/queue-6.12/bpf-reset-register-bounds-before-narrowing-retval-range-in-check_mem_access.patch new file mode 100644 index 0000000000..519de4ea0d --- /dev/null +++ b/queue-6.12/bpf-reset-register-bounds-before-narrowing-retval-range-in-check_mem_access.patch @@ -0,0 +1,51 @@ +From stable+bounces-289597-greg=kroah.com@vger.kernel.org Mon Jul 27 17:52:29 2026 +From: Sasha Levin +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 , Eduard Zingerman , Alexei Starovoitov , Sasha Levin +Message-ID: <20260727154103.1515347-3-sashal@kernel.org> + +From: Tristan Madani + +[ 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 +Acked-by: Eduard Zingerman +Link: https://lore.kernel.org/r/20260622230123.3695446-2-tristmd@gmail.com +Signed-off-by: Alexei Starovoitov +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.12/cleanup-add-a-scoped-version-of-class.patch b/queue-6.12/cleanup-add-a-scoped-version-of-class.patch new file mode 100644 index 0000000000..f2501a8570 --- /dev/null +++ b/queue-6.12/cleanup-add-a-scoped-version-of-class.patch @@ -0,0 +1,45 @@ +From stable+bounces-289715-greg=kroah.com@vger.kernel.org Tue Jul 28 00:25:07 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260727222109.2119761-1-sashal@kernel.org> + +From: Christian Brauner + +[ 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 +Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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): diff --git a/queue-6.12/cleanup-fix-scoped_class.patch b/queue-6.12/cleanup-fix-scoped_class.patch new file mode 100644 index 0000000000..35af75a32c --- /dev/null +++ b/queue-6.12/cleanup-fix-scoped_class.patch @@ -0,0 +1,57 @@ +From stable+bounces-289716-greg=kroah.com@vger.kernel.org Tue Jul 28 00:21:17 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 18:21:06 -0400 +Subject: cleanup: fix scoped_class() +To: stable@vger.kernel.org +Cc: Christian Brauner , Jens Axboe , Sasha Levin +Message-ID: <20260727222109.2119761-2-sashal@kernel.org> + +From: Christian Brauner + +[ 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 +Signed-off-by: Christian Brauner +Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 diff --git a/queue-6.12/cred-add-kernel_cred-helper.patch b/queue-6.12/cred-add-kernel_cred-helper.patch new file mode 100644 index 0000000000..e0405fdc7f --- /dev/null +++ b/queue-6.12/cred-add-kernel_cred-helper.patch @@ -0,0 +1,48 @@ +From stable+bounces-289717-greg=kroah.com@vger.kernel.org Tue Jul 28 00:25:14 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 18:21:07 -0400 +Subject: cred: add kernel_cred() helper +To: stable@vger.kernel.org +Cc: Christian Brauner , Jens Axboe , Sasha Levin +Message-ID: <20260727222109.2119761-3-sashal@kernel.org> + +From: Christian Brauner + +[ 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 +Signed-off-by: Christian Brauner +Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 *); diff --git a/queue-6.12/cred-add-scoped_with_kernel_creds.patch b/queue-6.12/cred-add-scoped_with_kernel_creds.patch new file mode 100644 index 0000000000..5e4aae72a7 --- /dev/null +++ b/queue-6.12/cred-add-scoped_with_kernel_creds.patch @@ -0,0 +1,47 @@ +From stable+bounces-289718-greg=kroah.com@vger.kernel.org Tue Jul 28 00:21:17 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 18:21:08 -0400 +Subject: cred: add scoped_with_kernel_creds() +To: stable@vger.kernel.org +Cc: Christian Brauner , Jens Axboe , Sasha Levin +Message-ID: <20260727222109.2119761-4-sashal@kernel.org> + +From: Christian Brauner + +[ 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 +Signed-off-by: Christian Brauner +Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 diff --git a/queue-6.12/dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch b/queue-6.12/dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch new file mode 100644 index 0000000000..d48fb99cd0 --- /dev/null +++ b/queue-6.12/dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch @@ -0,0 +1,72 @@ +From stable+bounces-289719-greg=kroah.com@vger.kernel.org Tue Jul 28 00:21:18 2026 +From: Sasha Levin +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 , Mikulas Patocka , Ondrej Kozina , Sasha Levin +Message-ID: <20260727222109.2119761-5-sashal@kernel.org> + +From: Ingo Blechschmidt + +[ 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 +Signed-off-by: Mikulas Patocka +Cc: stable@vger.kernel.org +Tested-by: Ondrej Kozina +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; diff --git a/queue-6.12/dm-integrity-fix-leaking-uninitialized-kernel-memory.patch b/queue-6.12/dm-integrity-fix-leaking-uninitialized-kernel-memory.patch new file mode 100644 index 0000000000..87d73a477d --- /dev/null +++ b/queue-6.12/dm-integrity-fix-leaking-uninitialized-kernel-memory.patch @@ -0,0 +1,37 @@ +From stable+bounces-289643-greg=kroah.com@vger.kernel.org Mon Jul 27 20:43:55 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260727183957.1545701-1-sashal@kernel.org> + +From: Mikulas Patocka + +[ 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 +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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; diff --git a/queue-6.12/dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch b/queue-6.12/dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch new file mode 100644 index 0000000000..2f5825eb9b --- /dev/null +++ b/queue-6.12/dma-dw-edma-fix-build-warning-in-dw_edma_pcie_probe.patch @@ -0,0 +1,177 @@ +From stable+bounces-289277-greg=kroah.com@vger.kernel.org Sun Jul 26 15:22:06 2026 +From: Sasha Levin +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 , Abinash Singh , Manivannan Sadhasivam , Vinod Koul , Sasha Levin +Message-ID: <20260726132154.327006-1-sashal@kernel.org> + +From: Abinash Singh + +[ 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 +Acked-by: Manivannan Sadhasivam +Link: https://lore.kernel.org/r/20250705160055.808165-1-abinashsinghlalotra@gmail.com +Signed-off-by: Vinod Koul +Stable-dep-of: 11d7cfe0c119 ("dmaengine: dw-edma-pcie: Reject devices without driver data") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } + diff --git a/queue-6.12/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch b/queue-6.12/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch new file mode 100644 index 0000000000..14a9992638 --- /dev/null +++ b/queue-6.12/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch @@ -0,0 +1,55 @@ +From stable+bounces-289280-greg=kroah.com@vger.kernel.org Sun Jul 26 15:22:13 2026 +From: Sasha Levin +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 , Manivannan Sadhasivam , Vinod Koul , Sasha Levin +Message-ID: <20260726132154.327006-2-sashal@kernel.org> + +From: Krzysztof Kozlowski + +[ 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 +Reviewed-by: Manivannan Sadhasivam +Link: https://patch.msgid.link/20251208020729.4654-2-krzysztof.kozlowski@oss.qualcomm.com +Signed-off-by: Vinod Koul +Stable-dep-of: 11d7cfe0c119 ("dmaengine: dw-edma-pcie: Reject devices without driver data") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; + diff --git a/queue-6.12/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch b/queue-6.12/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch new file mode 100644 index 0000000000..1e5c868765 --- /dev/null +++ b/queue-6.12/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch @@ -0,0 +1,43 @@ +From stable+bounces-289279-greg=kroah.com@vger.kernel.org Sun Jul 26 15:22:18 2026 +From: Sasha Levin +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 , Frank Li , Vinod Koul , Sasha Levin +Message-ID: <20260726132154.327006-3-sashal@kernel.org> + +From: Koichiro Den + +[ 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 +Reviewed-by: Frank Li +Link: https://patch.msgid.link/20260521142153.2957432-3-den@valinux.co.jp +Signed-off-by: Vinod Koul +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.12/fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch b/queue-6.12/fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch new file mode 100644 index 0000000000..0a31c636eb --- /dev/null +++ b/queue-6.12/fbcon-rename-struct-fbcon_ops-to-struct-fbcon_par.patch @@ -0,0 +1,2460 @@ +From stable+bounces-287902-greg=kroah.com@vger.kernel.org Wed Jul 22 16:52:24 2026 +From: Sasha Levin +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 , Sam Ravnborg , Sasha Levin +Message-ID: <20260722144842.1601797-1-sashal@kernel.org> + +From: Thomas Zimmermann + +[ 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 +Reviewed-by: Sam Ravnborg +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } + } diff --git a/queue-6.12/fbcon-use-correct-type-for-vc_resize-return-value.patch b/queue-6.12/fbcon-use-correct-type-for-vc_resize-return-value.patch new file mode 100644 index 0000000000..13ec5018f2 --- /dev/null +++ b/queue-6.12/fbcon-use-correct-type-for-vc_resize-return-value.patch @@ -0,0 +1,45 @@ +From stable+bounces-287900-greg=kroah.com@vger.kernel.org Wed Jul 22 17:03:58 2026 +From: Sasha Levin +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 , Thomas Zimmermann , Helge Deller , Sasha Levin +Message-ID: <20260722144842.1601797-2-sashal@kernel.org> + +From: Jiacheng Yu + +[ 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 +Reviewed-by: Thomas Zimmermann +Signed-off-by: Helge Deller +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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]; + diff --git a/queue-6.12/fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch b/queue-6.12/fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch new file mode 100644 index 0000000000..4d4bc480ae --- /dev/null +++ b/queue-6.12/fpga-dfl-afu-validate-dma-mapping-length-in-afu_dma_map_region.patch @@ -0,0 +1,46 @@ +From stable+bounces-281853-greg=kroah.com@vger.kernel.org Tue Jul 21 21:04:18 2026 +From: Sasha Levin +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 , Xu Yilun , Xu Yilun , Sasha Levin +Message-ID: <20260721184906.383124-1-sashal@kernel.org> + +From: Sebastian Alba Vives + +[ 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 +Reviewed-by: Xu Yilun +Link: https://lore.kernel.org/r/20260518190742.61426-3-sebasjosue84@gmail.com +Signed-off-by: Xu Yilun +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; diff --git a/queue-6.12/fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch b/queue-6.12/fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch new file mode 100644 index 0000000000..8a4ffa3902 --- /dev/null +++ b/queue-6.12/fs-resctrl-fix-double-add-of-pseudo-locked-region-s-rmid-to-free-list.patch @@ -0,0 +1,60 @@ +From stable+bounces-290227-greg=kroah.com@vger.kernel.org Wed Jul 29 04:30:01 2026 +From: Sasha Levin +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 , "Borislav Petkov (AMD)" , stable@kernel.org, Sasha Levin +Message-ID: <20260729022952.1112657-1-sashal@kernel.org> + +From: Reinette Chatre + +[ 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 +Signed-off-by: Borislav Petkov (AMD) +Cc: +Link: https://patch.msgid.link/551432dd7e624a862b8e58314c38aaba0afff3e9.1783377598.git.reinette.chatre@intel.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch b/queue-6.12/gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch new file mode 100644 index 0000000000..43914436ba --- /dev/null +++ b/queue-6.12/gpio-mt7621-avoid-corruption-of-shared-interrupt-trigger-state.patch @@ -0,0 +1,44 @@ +From stable+bounces-290047-greg=kroah.com@vger.kernel.org Tue Jul 28 18:13:03 2026 +From: Sasha Levin +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 , Sashiko , Bartosz Golaszewski , Sasha Levin +Message-ID: <20260728154327.4192271-1-sashal@kernel.org> + +From: Sergio Paracuellos + +[ 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 +Fixes: 4ba9c3afda41 ("gpio: mt7621: Add a driver for MT7621") +Signed-off-by: Sergio Paracuellos +Link: https://patch.msgid.link/20260626060112.2498324-2-sergio.paracuellos@gmail.com +Signed-off-by: Bartosz Golaszewski +[ 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 +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.12/gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch b/queue-6.12/gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch new file mode 100644 index 0000000000..ce29f8df97 --- /dev/null +++ b/queue-6.12/gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch @@ -0,0 +1,140 @@ +From stable+bounces-288440-greg=kroah.com@vger.kernel.org Thu Jul 23 19:08:01 2026 +From: Sasha Levin +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" , "Christian König" , "Matthew Auld" , "Timur Kristóf" , "John Olender" , "Sasha Levin" +Message-ID: <20260723165444.3392251-2-sashal@kernel.org> + +From: Arunpravin Paneer Selvam + +[ 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 +Cc: Matthew Auld +Cc: Christian König +Cc: Timur Kristóf +Cc: stable@vger.kernel.org +Reviewed-by: Matthew Auld +Tested-by: John Olender +Signed-off-by: Arunpravin Paneer Selvam +Link: https://patch.msgid.link/20260709131050.1022759-1-Arunpravin.PaneerSelvam@amd.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } + } diff --git a/queue-6.12/gpu-move-drm-buddy-allocator-one-level-up-part-two.patch b/queue-6.12/gpu-move-drm-buddy-allocator-one-level-up-part-two.patch new file mode 100644 index 0000000000..5ce3eed3b7 --- /dev/null +++ b/queue-6.12/gpu-move-drm-buddy-allocator-one-level-up-part-two.patch @@ -0,0 +1,6105 @@ +From stable+bounces-288441-greg=kroah.com@vger.kernel.org Thu Jul 23 18:58:13 2026 +From: Sasha Levin +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 , Dave Airlie , Sasha Levin +Message-ID: <20260723165444.3392251-1-sashal@kernel.org> + +From: Joel Fernandes + +[ 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 +Reviewed-by: Dave Airlie +[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 +Stable-dep-of: 56bc6384314f ("gpu/buddy: bail out of try_harder when alignment cannot be honoured") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++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 + #include ++#include + + #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 ++#include + + 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 ++ ++#include + #include + #include + #include + ++#include + #include +- +-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_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 + #include +-#include ++#include + + #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 ++#include + #include + + #include +@@ -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 + #include + ++#include + #include + + #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 + #include + +-#include ++#include + + #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 +-#include +-#include +-#include +-#include +- +-#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 +- +-#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 +- */ +- +-#include +- +-#include +-#include +-#include +- +-#include +- +-#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 + #include + +-#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 + +-#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 ++#include + + 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 ++#include + + #include + #include +@@ -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 ++#include + #include + + 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 ++ */ ++ ++#include ++ ++#include ++#include ++#include ++ ++#include ++ ++#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 ++#include ++#include ++#include ++#include ++ ++#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 ++ ++#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 +-#include +-#include +-#include +-#include ++#include + +-#include ++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 ++#include ++#include ++#include ++#include ++ ++#include ++ ++#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 diff --git a/queue-6.12/gve-fix-header-buffer-corruption-with-header-split-and-hw-gro.patch b/queue-6.12/gve-fix-header-buffer-corruption-with-header-split-and-hw-gro.patch new file mode 100644 index 0000000000..8b348545f0 --- /dev/null +++ b/queue-6.12/gve-fix-header-buffer-corruption-with-header-split-and-hw-gro.patch @@ -0,0 +1,143 @@ +From stable+bounces-289989-greg=kroah.com@vger.kernel.org Tue Jul 28 15:21:31 2026 +From: Sasha Levin +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 , Praveen Kaligineedi , Jordan Rhee , Harshitha Ramamurthy , Joshua Washington , Eric Dumazet , Jakub Kicinski , Sasha Levin +Message-ID: <20260728132004.3954154-2-sashal@kernel.org> + +From: Ankit Garg + +[ 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 +Reviewed-by: Praveen Kaligineedi +Reviewed-by: Jordan Rhee +Reviewed-by: Harshitha Ramamurthy +Signed-off-by: Joshua Washington +Reviewed-by: Eric Dumazet +Link: https://patch.msgid.link/20260617013208.3781453-1-joshwash@google.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; diff --git a/queue-6.12/i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch b/queue-6.12/i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch new file mode 100644 index 0000000000..3c34d214b1 --- /dev/null +++ b/queue-6.12/i2c-davinci-unregister-cpufreq-notifier-on-probe-failure.patch @@ -0,0 +1,50 @@ +From stable+bounces-282938-greg=kroah.com@vger.kernel.org Tue Jul 21 21:57:25 2026 +From: Sasha Levin +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 , Bartosz Golaszewski , Andi Shyti , Sasha Levin +Message-ID: <20260721193603.410310-1-sashal@kernel.org> + +From: Haoxiang Li + +[ 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 +Cc: # v2.6.36+ +Reviewed-by: Bartosz Golaszewski +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260610030513.2651018-1-haoxiang_li2024@163.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch b/queue-6.12/i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch new file mode 100644 index 0000000000..027a1b3513 --- /dev/null +++ b/queue-6.12/i2c-i801-fix-hardware-state-machine-corruption-in-error-path.patch @@ -0,0 +1,68 @@ +From stable+bounces-287675-greg=kroah.com@vger.kernel.org Wed Jul 22 04:46:00 2026 +From: Sasha Levin +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 , Sasha Levin +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: # v6.3+ +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260512093534.348655-1-w15303746062@163.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch b/queue-6.12/i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch new file mode 100644 index 0000000000..12e8c96808 --- /dev/null +++ b/queue-6.12/i2c-imx-fix-locked-bus-on-smbus-block-read-of-0-atomic.patch @@ -0,0 +1,87 @@ +From stable+bounces-289477-greg=kroah.com@vger.kernel.org Mon Jul 27 14:19:47 2026 +From: Sasha Levin +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 , Oleksij Rempel , Carlos Song , Stefan Eichenberger , Andi Shyti , Sasha Levin +Message-ID: <20260727121938.3777594-2-sashal@kernel.org> + +From: Vincent Jardin + +[ 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 +Cc: # v3.16+ +Acked-by: Oleksij Rempel +Acked-by: Carlos Song +Reviewed-by: Stefan Eichenberger +Signed-off-by: Andi Shyti +Link: https://lore.kernel.org/r/20260713-for-upstream-i2c-lx2160-fix-v1-v3-1-073ac9e103a5@free.fr +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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, diff --git a/queue-6.12/i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch b/queue-6.12/i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch new file mode 100644 index 0000000000..7dc1000ac9 --- /dev/null +++ b/queue-6.12/i2c-imx-separate-atomic-dma-and-non-dma-use-case.patch @@ -0,0 +1,194 @@ +From stable+bounces-289476-greg=kroah.com@vger.kernel.org Mon Jul 27 14:26:20 2026 +From: Sasha Levin +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 , Frank Li , Oleksij Rempel , Andi Shyti , Sasha Levin +Message-ID: <20260727121938.3777594-1-sashal@kernel.org> + +From: Stefan Eichenberger + +[ 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 +Reviewed-by: Frank Li +Acked-by: Oleksij Rempel +Signed-off-by: Andi Shyti +Stable-dep-of: cb2fc3785769 ("i2c: imx: fix locked bus on SMBus block-read of 0 (atomic)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; diff --git a/queue-6.12/iommufd-avoid-partial-fault-group-delivery-in-iommufd_fault_fops_read.patch b/queue-6.12/iommufd-avoid-partial-fault-group-delivery-in-iommufd_fault_fops_read.patch new file mode 100644 index 0000000000..e581ef58d2 --- /dev/null +++ b/queue-6.12/iommufd-avoid-partial-fault-group-delivery-in-iommufd_fault_fops_read.patch @@ -0,0 +1,74 @@ +From stable+bounces-279661-greg=kroah.com@vger.kernel.org Tue Jul 21 19:25:28 2026 +From: Sasha Levin +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 , Pranjal Shrivastava , Kevin Tian , Jason Gunthorpe , Sasha Levin +Message-ID: <20260721171353.111431-3-sashal@kernel.org> + +From: Nicolin Chen + +[ 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 +Reviewed-by: Pranjal Shrivastava +Reviewed-by: Kevin Tian +Signed-off-by: Jason Gunthorpe +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + diff --git a/queue-6.12/iommufd-break-the-loop-on-failure-in-iommufd_fault_fops_read.patch b/queue-6.12/iommufd-break-the-loop-on-failure-in-iommufd_fault_fops_read.patch new file mode 100644 index 0000000000..82a3bc32f8 --- /dev/null +++ b/queue-6.12/iommufd-break-the-loop-on-failure-in-iommufd_fault_fops_read.patch @@ -0,0 +1,43 @@ +From stable+bounces-279638-greg=kroah.com@vger.kernel.org Tue Jul 21 20:37:42 2026 +From: Sasha Levin +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 , Pranjal Shrivastava , Kevin Tian , Jason Gunthorpe , Sasha Levin +Message-ID: <20260721171311.108330-1-sashal@kernel.org> + +From: Nicolin Chen + +[ 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 +Reviewed-by: Pranjal Shrivastava +Reviewed-by: Kevin Tian +Signed-off-by: Jason Gunthorpe +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + diff --git a/queue-6.12/iommufd-reject-invalid-read-count-in-iommufd_fault_fops_read.patch b/queue-6.12/iommufd-reject-invalid-read-count-in-iommufd_fault_fops_read.patch new file mode 100644 index 0000000000..7b614a9aa2 --- /dev/null +++ b/queue-6.12/iommufd-reject-invalid-read-count-in-iommufd_fault_fops_read.patch @@ -0,0 +1,44 @@ +From stable+bounces-279627-greg=kroah.com@vger.kernel.org Tue Jul 21 19:24:01 2026 +From: Sasha Levin +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 , Pranjal Shrivastava , Kevin Tian , Jason Gunthorpe , Sasha Levin +Message-ID: <20260721171250.107567-1-sashal@kernel.org> + +From: Nicolin Chen + +[ 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 +Reviewed-by: Pranjal Shrivastava +Reviewed-by: Kevin Tian +Signed-off-by: Jason Gunthorpe +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; + } + diff --git a/queue-6.12/ipmi-fix-refcount-leak-in-i_ipmi_request.patch b/queue-6.12/ipmi-fix-refcount-leak-in-i_ipmi_request.patch new file mode 100644 index 0000000000..c4fe41df73 --- /dev/null +++ b/queue-6.12/ipmi-fix-refcount-leak-in-i_ipmi_request.patch @@ -0,0 +1,60 @@ +From stable+bounces-290096-greg=kroah.com@vger.kernel.org Tue Jul 28 19:41:00 2026 +From: Sasha Levin +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 , Corey Minyard , Sasha Levin +Message-ID: <20260728170722.184404-1-sashal@kernel.org> + +From: Wentao Liang + +[ 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 +Message-ID: <20260603120634.3758747-1-vulab@iscas.ac.cn> +Signed-off-by: Corey Minyard +[ changed `free_ipmi_user` to `free_user` in the two added `kref_put()` calls ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch b/queue-6.12/landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch new file mode 100644 index 0000000000..ec23511f0d --- /dev/null +++ b/queue-6.12/landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch @@ -0,0 +1,164 @@ +From stable+bounces-288959-greg=kroah.com@vger.kernel.org Fri Jul 24 19:00:37 2026 +From: Sasha Levin +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" , "Günther Noack" , "Mickaël Salaün" , "Sasha Levin" +Message-ID: <20260724164456.469658-2-sashal@kernel.org> + +From: Bryam Vargas + +[ 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 +Reviewed-by: Günther Noack +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 +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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, diff --git a/queue-6.12/landlock-prepare-to-use-credential-instead-of-domain-for-fowner.patch b/queue-6.12/landlock-prepare-to-use-credential-instead-of-domain-for-fowner.patch new file mode 100644 index 0000000000..850ca6fcbb --- /dev/null +++ b/queue-6.12/landlock-prepare-to-use-credential-instead-of-domain-for-fowner.patch @@ -0,0 +1,188 @@ +From stable+bounces-288958-greg=kroah.com@vger.kernel.org Fri Jul 24 19:00:37 2026 +From: Sasha Levin +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" , "Günther Noack" , "Sasha Levin" +Message-ID: <20260724164456.469658-1-sashal@kernel.org> + +From: Mickaël Salaün + +[ 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 +Link: https://lore.kernel.org/r/20250320190717.2287696-8-mic@digikod.net +Signed-off-by: Mickaël Salaün +Stable-dep-of: 4b80320ca7ed ("landlock: Fix LANDLOCK_SCOPE_SIGNAL bypass on the SIGIO path") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + * Copyright © 2018-2020 ANSSI ++ * Copyright © 2024-2025 Microsoft Corporation + */ + + #ifndef _SECURITY_LANDLOCK_FS_H +@@ -13,6 +14,7 @@ + #include + #include + ++#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; + diff --git a/queue-6.12/locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch b/queue-6.12/locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch new file mode 100644 index 0000000000..151429c006 --- /dev/null +++ b/queue-6.12/locking-rt-fix-the-incorrect-rcu-protection-in-rt_spin_unlock.patch @@ -0,0 +1,110 @@ +From stable+bounces-289807-greg=kroah.com@vger.kernel.org Tue Jul 28 04:46:24 2026 +From: Sasha Levin +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 , syzbot+000c800a02097aaa10ed@syzkaller.appspotmail.com, Jann Horn , Sebastian Andrzej Siewior , Al Viro , Sasha Levin +Message-ID: <20260728024607.3052461-1-sashal@kernel.org> + +From: Thomas Gleixner + +[ 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 +Signed-off-by: Thomas Gleixner +Reviewed-by: Sebastian Andrzej Siewior +Acked-by: Al Viro +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/87jyrud75z.ffs@fw13 +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + diff --git a/queue-6.12/mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch b/queue-6.12/mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch new file mode 100644 index 0000000000..42a25aef66 --- /dev/null +++ b/queue-6.12/mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch @@ -0,0 +1,88 @@ +From stable+bounces-288841-greg=kroah.com@vger.kernel.org Fri Jul 24 13:22:38 2026 +From: Sasha Levin +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 , syzbot+226c1f947186f8fef796@syzkaller.appspotmail.com, Muchun Song , David Hildenbrand , Oscar Salvador , Mina Almasry , Andrew Morton , Sasha Levin +Message-ID: <20260724110052.3984407-1-sashal@kernel.org> + +From: Deepanshu Kartikey + +[ 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 +Reviewed-by: Muchun Song +Cc: David Hildenbrand +Cc: Oscar Salvador +Cc: Mina Almasry +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch b/queue-6.12/mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch new file mode 100644 index 0000000000..ec3c5a2cbc --- /dev/null +++ b/queue-6.12/mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch @@ -0,0 +1,115 @@ +From stable+bounces-289041-greg=kroah.com@vger.kernel.org Sat Jul 25 03:37:49 2026 +From: Sasha Levin +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 , "Mike Rapoport (Microsoft)" , Oscar Salvador , "David Hildenbrand (Arm)" , "Liam R. Howlett" , "Aneesh Kumar K.V" , Joao Martins , Lorenzo Stoakes , Madhavan Srinivasan , Michael Ellerman , Michal Hocko , Nicholas Piggin , Suren Baghdasaryan , Vlastimil Babka , Andrew Morton , Sasha Levin +Message-ID: <20260725013741.1892750-1-sashal@kernel.org> + +From: Muchun Song + +[ 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 +Reviewed-by: Mike Rapoport (Microsoft) +Reviewed-by: Oscar Salvador +Acked-by: David Hildenbrand (Arm) +Acked-by: Liam R. Howlett +Cc: "Aneesh Kumar K.V" +Cc: Joao Martins +Cc: Lorenzo Stoakes +Cc: Madhavan Srinivasan +Cc: Michael Ellerman +Cc: Michal Hocko +Cc: Nicholas Piggin +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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)); + } diff --git a/queue-6.12/mm-prepare-to-move-subsection_map_init-to-mm-sparse-vmemmap.c.patch b/queue-6.12/mm-prepare-to-move-subsection_map_init-to-mm-sparse-vmemmap.c.patch new file mode 100644 index 0000000000..eacc1d9602 --- /dev/null +++ b/queue-6.12/mm-prepare-to-move-subsection_map_init-to-mm-sparse-vmemmap.c.patch @@ -0,0 +1,832 @@ +From stable+bounces-289228-greg=kroah.com@vger.kernel.org Sun Jul 26 14:03:06 2026 +From: Sasha Levin +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)" , "Lorenzo Stoakes (Oracle)" , "Mike Rapoport (Microsoft)" , Axel Rasmussen , Liam Howlett , Michal Hocko , Oscar Salvador , Sidhartha Kumar , Suren Baghdasaryan , Vlastimil Babka , Wei Xu , Yuanchu Xie , Andrew Morton , Sasha Levin +Message-ID: <20260726120046.4138231-1-sashal@kernel.org> + +From: "David Hildenbrand (Arm)" + +[ 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) +Reviewed-by: Lorenzo Stoakes (Oracle) +Reviewed-by: Mike Rapoport (Microsoft) +Cc: Axel Rasmussen +Cc: Liam Howlett +Cc: Michal Hocko +Cc: Oscar Salvador +Cc: Sidhartha Kumar +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: Wei Xu +Cc: Yuanchu Xie +Signed-off-by: Andrew Morton +Stable-dep-of: 721a73e30c9e ("mm/sparse-vmemmap: fix DAX vmemmap accounting with optimization") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + ++#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 */ diff --git a/queue-6.12/mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch b/queue-6.12/mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch new file mode 100644 index 0000000000..bba2722177 --- /dev/null +++ b/queue-6.12/mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch @@ -0,0 +1,112 @@ +From stable+bounces-289229-greg=kroah.com@vger.kernel.org Sun Jul 26 14:00:55 2026 +From: Sasha Levin +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 , "Mike Rapoport (Microsoft)" , Oscar Salvador , "David Hildenbrand (Arm)" , "Liam R. Howlett" , "Aneesh Kumar K.V" , Joao Martins , Lorenzo Stoakes , Madhavan Srinivasan , Michael Ellerman , Michal Hocko , Nicholas Piggin , Suren Baghdasaryan , Vlastimil Babka , Andrew Morton , Sasha Levin +Message-ID: <20260726120046.4138231-2-sashal@kernel.org> + +From: Muchun Song + +[ 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 +Acked-by: Mike Rapoport (Microsoft) +Acked-by: Oscar Salvador +Acked-by: David Hildenbrand (Arm) +Acked-by: Liam R. Howlett +Cc: "Aneesh Kumar K.V" +Cc: Joao Martins +Cc: Lorenzo Stoakes +Cc: Madhavan Srinivasan +Cc: Michael Ellerman +Cc: Michal Hocko +Cc: Nicholas Piggin +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } + diff --git a/queue-6.12/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch b/queue-6.12/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch new file mode 100644 index 0000000000..b1a2738473 --- /dev/null +++ b/queue-6.12/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch @@ -0,0 +1,171 @@ +From stable+bounces-288842-greg=kroah.com@vger.kernel.org Fri Jul 24 13:23:00 2026 +From: Sasha Levin +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 , "Mike Rapoport (Microsoft)" , Oscar Salvador , "David Hildenbrand (Arm)" , "Liam R. Howlett" , "Aneesh Kumar K.V" , Joao Martins , Lorenzo Stoakes , Madhavan Srinivasan , Michael Ellerman , Michal Hocko , Nicholas Piggin , Suren Baghdasaryan , Vlastimil Babka , Andrew Morton , Sasha Levin +Message-ID: <20260724110055.3984461-1-sashal@kernel.org> + +From: Muchun Song + +[ 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 +Acked-by: Mike Rapoport (Microsoft) +Acked-by: Oscar Salvador +Acked-by: David Hildenbrand (Arm) +Acked-by: Liam R. Howlett +Cc: "Aneesh Kumar K.V" +Cc: Joao Martins +Cc: Lorenzo Stoakes +Cc: Madhavan Srinivasan +Cc: Michael Ellerman +Cc: Michal Hocko +Cc: Nicholas Piggin +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; + } diff --git a/queue-6.12/mmc-vub300-fix-use-after-free-on-disconnect.patch b/queue-6.12/mmc-vub300-fix-use-after-free-on-disconnect.patch new file mode 100644 index 0000000000..09a464d234 --- /dev/null +++ b/queue-6.12/mmc-vub300-fix-use-after-free-on-disconnect.patch @@ -0,0 +1,90 @@ +From stable+bounces-289742-greg=kroah.com@vger.kernel.org Tue Jul 28 01:37:22 2026 +From: Sasha Levin +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 , Binbin Zhou , Ulf Hansson +Message-ID: <20260727233445.2426041-1-sashal@kernel.org> + +From: Johan Hovold + +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 +Signed-off-by: Johan Hovold +Signed-off-by: Ulf Hansson +(cherry picked from commit 8f4d20a710225ec7a565f6a0459862d3b1f32330) +Signed-off-by: Greg Kroah-Hartman +--- + 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 diff --git a/queue-6.12/mmc-vub300-fix-use-after-free-on-probe-failure.patch b/queue-6.12/mmc-vub300-fix-use-after-free-on-probe-failure.patch new file mode 100644 index 0000000000..0473f0fb85 --- /dev/null +++ b/queue-6.12/mmc-vub300-fix-use-after-free-on-probe-failure.patch @@ -0,0 +1,87 @@ +From stable+bounces-289744-greg=kroah.com@vger.kernel.org Tue Jul 28 01:38:01 2026 +From: Sasha Levin +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 , Johan Hovold , Ulf Hansson +Message-ID: <20260727233445.2426041-3-sashal@kernel.org> + +From: Guangshuo Li + +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 +Reviewed-by: Johan Hovold +Cc: stable@vger.kernel.org +Signed-off-by: Ulf Hansson +(cherry picked from commit a3b5f242997a3be7404112fd48784881560aea57) +Signed-off-by: Greg Kroah-Hartman +--- + 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); + /* diff --git a/queue-6.12/mmc-vub300-rename-probe-error-labels.patch b/queue-6.12/mmc-vub300-rename-probe-error-labels.patch new file mode 100644 index 0000000000..b8ccb85413 --- /dev/null +++ b/queue-6.12/mmc-vub300-rename-probe-error-labels.patch @@ -0,0 +1,79 @@ +From stable+bounces-289743-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:53 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 19:34:43 -0400 +Subject: mmc: vub300: rename probe error labels +To: stable@vger.kernel.org +Cc: Johan Hovold , Ulf Hansson +Message-ID: <20260727233445.2426041-2-sashal@kernel.org> + +From: Johan Hovold + +Error labels should be named after what they do. + +Rename the probe error labels. + +Signed-off-by: Johan Hovold +Signed-off-by: Ulf Hansson +(cherry picked from commit 5b8b35d6f4fa758dd5e8ae18526ea1c73f6787e0) +Signed-off-by: Greg Kroah-Hartman +--- + 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; + } + diff --git a/queue-6.12/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch b/queue-6.12/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch new file mode 100644 index 0000000000..680146c2cc --- /dev/null +++ b/queue-6.12/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch @@ -0,0 +1,58 @@ +From stable+bounces-289091-greg=kroah.com@vger.kernel.org Sat Jul 25 13:02:56 2026 +From: Sasha Levin +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 , Miquel Raynal , Sasha Levin +Message-ID: <20260725110246.1858961-1-sashal@kernel.org> + +From: Florian Fuchs + +[ 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 +Signed-off-by: Miquel Raynal +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; diff --git a/queue-6.12/net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch b/queue-6.12/net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch new file mode 100644 index 0000000000..ac81daeb3a --- /dev/null +++ b/queue-6.12/net-ip6_tunnel-require-cap_net_admin-in-the-device-netns-for-changelink.patch @@ -0,0 +1,48 @@ +From stable+bounces-289987-greg=kroah.com@vger.kernel.org Tue Jul 28 15:25:44 2026 +From: Sasha Levin +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 , Xiao Liang , Kuniyuki Iwashima , Jakub Kicinski , Sasha Levin +Message-ID: <20260728131953.3953668-1-sashal@kernel.org> + +From: Maoyi Xie + +[ 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 +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 +Reviewed-by: Kuniyuki Iwashima +Link: https://patch.msgid.link/20260612085941.3158249-5-maoyixie.tju@gmail.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; + diff --git a/queue-6.12/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch b/queue-6.12/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch new file mode 100644 index 0000000000..32f1ee517b --- /dev/null +++ b/queue-6.12/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch @@ -0,0 +1,91 @@ +From stable+bounces-290233-greg=kroah.com@vger.kernel.org Wed Jul 29 04:47:28 2026 +From: Sasha Levin +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 , Larysa Zaremba , Alex Elder , Jakub Kicinski , Sasha Levin +Message-ID: <20260729024004.1127220-1-sashal@kernel.org> + +From: Haoxiang Li + +[ 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 +Reviewed-by: Larysa Zaremba +Reviewed-by: Alex Elder +Link: https://patch.msgid.link/20260624065955.2822765-1-haoxiang_li2024@163.com +Signed-off-by: Jakub Kicinski +[ 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 +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } + diff --git a/queue-6.12/net-macb-drop-in-flight-tx-skbs-on-close.patch b/queue-6.12/net-macb-drop-in-flight-tx-skbs-on-close.patch new file mode 100644 index 0000000000..d87bf32b8f --- /dev/null +++ b/queue-6.12/net-macb-drop-in-flight-tx-skbs-on-close.patch @@ -0,0 +1,64 @@ +From stable+bounces-290228-greg=kroah.com@vger.kernel.org Wed Jul 29 04:30:20 2026 +From: Sasha Levin +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" , "Nicolai Buchwitz" , "Paolo Abeni" , "Sasha Levin" +Message-ID: <20260729023002.1113287-1-sashal@kernel.org> + +From: Théo Lebrun + +[ 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 +Signed-off-by: Théo Lebrun +Link: https://patch.msgid.link/20260702-macb-drop-tx-v4-1-1c833eebdbc8@bootlin.com +Signed-off-by: Paolo Abeni +[ 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 +Signed-off-by: Greg Kroah-Hartman +--- + 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, diff --git a/queue-6.12/net-mana-validate-the-packet-length-reported-by-the-nic.patch b/queue-6.12/net-mana-validate-the-packet-length-reported-by-the-nic.patch new file mode 100644 index 0000000000..30a7943ed7 --- /dev/null +++ b/queue-6.12/net-mana-validate-the-packet-length-reported-by-the-nic.patch @@ -0,0 +1,50 @@ +From stable+bounces-289819-greg=kroah.com@vger.kernel.org Tue Jul 28 05:02:11 2026 +From: Sasha Levin +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 , Haiyang Zhang , Paolo Abeni , Sasha Levin +Message-ID: <20260728030204.3123559-1-sashal@kernel.org> + +From: Dexuan Cui + +[ 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 +Signed-off-by: Dexuan Cui +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 +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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. diff --git a/queue-6.12/net-mlx5-hws-fix-matcher-leak-on-resize-target-setup-failure.patch b/queue-6.12/net-mlx5-hws-fix-matcher-leak-on-resize-target-setup-failure.patch new file mode 100644 index 0000000000..3c7831b9e3 --- /dev/null +++ b/queue-6.12/net-mlx5-hws-fix-matcher-leak-on-resize-target-setup-failure.patch @@ -0,0 +1,52 @@ +From stable+bounces-290182-greg=kroah.com@vger.kernel.org Wed Jul 29 00:18:36 2026 +From: Sasha Levin +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 , Yevgeny Kliteynik , Tariq Toukan , Paolo Abeni , Sasha Levin +Message-ID: <20260728221755.746200-2-sashal@kernel.org> + +From: Dawei Feng + +[ 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 +Reviewed-by: Yevgeny Kliteynik +Acked-by: Tariq Toukan +Link: https://patch.msgid.link/20260629064049.3852759-1-dawei.feng@seu.edu.cn +Signed-off-by: Paolo Abeni +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; + } + diff --git a/queue-6.12/net-mlx5-hws-rearrange-to-prevent-forward-declaration.patch b/queue-6.12/net-mlx5-hws-rearrange-to-prevent-forward-declaration.patch new file mode 100644 index 0000000000..6cc26df7fe --- /dev/null +++ b/queue-6.12/net-mlx5-hws-rearrange-to-prevent-forward-declaration.patch @@ -0,0 +1,405 @@ +From stable+bounces-290181-greg=kroah.com@vger.kernel.org Wed Jul 29 00:23:23 2026 +From: Sasha Levin +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 , Mark Bloch , Simon Horman , Jakub Kicinski , Sasha Levin +Message-ID: <20260728221755.746200-1-sashal@kernel.org> + +From: Yevgeny Kliteynik + +[ 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 +Signed-off-by: Mark Bloch +Reviewed-by: Simon Horman +Link: https://patch.msgid.link/20250703185431.445571-9-mbloch@nvidia.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: bb09d0e64eca ("net/mlx5: HWS, fix matcher leak on resize target setup failure") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) + { diff --git a/queue-6.12/netfilter-bitwise-rename-some-boolean-operation-functions.patch b/queue-6.12/netfilter-bitwise-rename-some-boolean-operation-functions.patch new file mode 100644 index 0000000000..7f9b0eeea2 --- /dev/null +++ b/queue-6.12/netfilter-bitwise-rename-some-boolean-operation-functions.patch @@ -0,0 +1,161 @@ +From stable+bounces-288424-greg=kroah.com@vger.kernel.org Thu Jul 23 19:05:45 2026 +From: Sasha Levin +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 , Pablo Neira Ayuso , Sasha Levin +Message-ID: <20260723165419.3390514-1-sashal@kernel.org> + +From: Jeremy Sowden + +[ 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 +Signed-off-by: Pablo Neira Ayuso +Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; diff --git a/queue-6.12/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch b/queue-6.12/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch new file mode 100644 index 0000000000..d575f2bc37 --- /dev/null +++ b/queue-6.12/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch @@ -0,0 +1,39 @@ +From stable+bounces-288358-greg=kroah.com@vger.kernel.org Thu Jul 23 16:39:44 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260723143430.3226511-1-sashal@kernel.org> + +From: Florian Westphal + +[ 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 +Stable-dep-of: e5e24a365a5e ("netfilter: nf_conntrack_sip: validate skb_dst() before accessing it") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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)); + diff --git a/queue-6.12/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch b/queue-6.12/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch new file mode 100644 index 0000000000..de29b66dee --- /dev/null +++ b/queue-6.12/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch @@ -0,0 +1,56 @@ +From stable+bounces-288359-greg=kroah.com@vger.kernel.org Thu Jul 23 16:39:45 2026 +From: Sasha Levin +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 , Ren Wei , Florian Westphal , Sasha Levin +Message-ID: <20260723143430.3226511-2-sashal@kernel.org> + +From: Pablo Neira Ayuso + +[ 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 +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Florian Westphal +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.12/netfilter-nf_tables-remove-register-tracking-infrastructure.patch b/queue-6.12/netfilter-nf_tables-remove-register-tracking-infrastructure.patch new file mode 100644 index 0000000000..c8117545a2 --- /dev/null +++ b/queue-6.12/netfilter-nf_tables-remove-register-tracking-infrastructure.patch @@ -0,0 +1,1645 @@ +From stable+bounces-288429-greg=kroah.com@vger.kernel.org Thu Jul 23 18:57:23 2026 +From: Sasha Levin +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 , Jakub Kicinski , Sasha Levin +Message-ID: <20260723165419.3390514-3-sashal@kernel.org> + +From: Florian Westphal + +[ 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 +Link: https://patch.msgid.link/20260224205048.4718-10-fw@strlen.de +Signed-off-by: Jakub Kicinski +Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 "); +--- 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 = { diff --git a/queue-6.12/netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch b/queue-6.12/netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch new file mode 100644 index 0000000000..09aca4733a --- /dev/null +++ b/queue-6.12/netfilter-nf_tables-remove-unused-nft_reduce_is_readonly.patch @@ -0,0 +1,39 @@ +From stable+bounces-288425-greg=kroah.com@vger.kernel.org Thu Jul 23 19:06:00 2026 +From: Sasha Levin +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 , Simon Horman , Pablo Neira Ayuso , Sasha Levin +Message-ID: <20260723165419.3390514-2-sashal@kernel.org> + +From: Yue Haibing + +[ Upstream commit bf6788742b8d6c73de441e088a71de7154f0d4aa ] + +Since commit 9e539c5b6d9c ("netfilter: nf_tables: disable expression +reduction infra") this is unused. + +Signed-off-by: Yue Haibing +Reviewed-by: Simon Horman +Signed-off-by: Pablo Neira Ayuso +Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch b/queue-6.12/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch new file mode 100644 index 0000000000..238443f5a4 --- /dev/null +++ b/queue-6.12/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch @@ -0,0 +1,100 @@ +From stable+bounces-288427-greg=kroah.com@vger.kernel.org Thu Jul 23 19:06:09 2026 +From: Sasha Levin +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 , Florian Westphal , Sasha Levin +Message-ID: <20260723165419.3390514-4-sashal@kernel.org> + +From: Theodor Arsenij Larionov-Trichkine + +[ 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 +Signed-off-by: Florian Westphal +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 = { diff --git a/queue-6.12/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch b/queue-6.12/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch new file mode 100644 index 0000000000..02351c5219 --- /dev/null +++ b/queue-6.12/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch @@ -0,0 +1,116 @@ +From stable+bounces-288530-greg=kroah.com@vger.kernel.org Thu Jul 23 22:01:08 2026 +From: Sasha Levin +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 , Jeff Layton , NeilBrown , Christoph Hellwig , Chuck Lever , Sasha Levin +Message-ID: <20260723195904.3685200-1-sashal@kernel.org> + +From: Mike Snitzer + +[ 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 +Reviewed-by: Jeff Layton +Reviewed-by: NeilBrown +Reviewed-by: Christoph Hellwig +Signed-off-by: Chuck Lever +Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/nvmet-auth-reject-short-auth_receive-buffers.patch b/queue-6.12/nvmet-auth-reject-short-auth_receive-buffers.patch new file mode 100644 index 0000000000..528929e930 --- /dev/null +++ b/queue-6.12/nvmet-auth-reject-short-auth_receive-buffers.patch @@ -0,0 +1,85 @@ +From stable+bounces-289491-greg=kroah.com@vger.kernel.org Mon Jul 27 14:49:33 2026 +From: Sasha Levin +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 , Hannes Reinecke , Keith Busch , Sasha Levin +Message-ID: <20260727124359.172046-2-sashal@kernel.org> + +From: Michael Bommarito + +[ 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 +Signed-off-by: Michael Bommarito +Signed-off-by: Keith Busch +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.12/nvmet-introduce-nvmet_req_transfer_len.patch b/queue-6.12/nvmet-introduce-nvmet_req_transfer_len.patch new file mode 100644 index 0000000000..2466336ed3 --- /dev/null +++ b/queue-6.12/nvmet-introduce-nvmet_req_transfer_len.patch @@ -0,0 +1,289 @@ +From stable+bounces-289490-greg=kroah.com@vger.kernel.org Mon Jul 27 14:49:29 2026 +From: Sasha Levin +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 , Christoph Hellwig , Rick Wertenbroek , Manivannan Sadhasivam , Keith Busch , Sasha Levin +Message-ID: <20260727124359.172046-1-sashal@kernel.org> + +From: Damien Le Moal + +[ 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 +Reviewed-by: Christoph Hellwig +Tested-by: Rick Wertenbroek +Tested-by: Manivannan Sadhasivam +Signed-off-by: Keith Busch +Stable-dep-of: 779575bc35c6 ("nvmet-auth: reject short AUTH_RECEIVE buffers") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch b/queue-6.12/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch new file mode 100644 index 0000000000..f0360fc2a8 --- /dev/null +++ b/queue-6.12/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch @@ -0,0 +1,78 @@ +From stable+bounces-290049-greg=kroah.com@vger.kernel.org Tue Jul 28 19:03:00 2026 +From: Sasha Levin +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 , Yuhao Jiang , Jakub Kicinski , Sasha Levin +Message-ID: <20260728154339.4192777-1-sashal@kernel.org> + +From: Junrui Luo + +[ 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 +Cc: stable@vger.kernel.org +Signed-off-by: Junrui Luo +Link: https://patch.msgid.link/SYBPR01MB78811656934E713B77DA6CEDAFE62@SYBPR01MB7881.ausprd01.prod.outlook.com +Signed-off-by: Jakub Kicinski +[ 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 +Signed-off-by: Greg Kroah-Hartman +--- + 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. + */ diff --git a/queue-6.12/octeontx2-annotate-mmio-regions-as-__iomem.patch b/queue-6.12/octeontx2-annotate-mmio-regions-as-__iomem.patch new file mode 100644 index 0000000000..2d706a6a67 --- /dev/null +++ b/queue-6.12/octeontx2-annotate-mmio-regions-as-__iomem.patch @@ -0,0 +1,99 @@ +From stable+bounces-288043-greg=kroah.com@vger.kernel.org Wed Jul 22 20:12:49 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 14:07:47 -0400 +Subject: octeontx2: Annotate mmio regions as __iomem +To: stable@vger.kernel.org +Cc: Subbaraya Sundeep , Jakub Kicinski , Sasha Levin +Message-ID: <20260722180748.2202109-1-sashal@kernel.org> + +From: Subbaraya Sundeep + +[ 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 +Link: https://patch.msgid.link/1749484309-3434-1-git-send-email-sbhatta@marvell.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: 0b352f04b9be ("octeontx2-vf: clear stale mailbox IRQ state before request_irq()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) { diff --git a/queue-6.12/octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch b/queue-6.12/octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch new file mode 100644 index 0000000000..27529e60b1 --- /dev/null +++ b/queue-6.12/octeontx2-pf-clear-stale-mailbox-irq-state-before-request_irq.patch @@ -0,0 +1,67 @@ +From stable+bounces-288045-greg=kroah.com@vger.kernel.org Wed Jul 22 20:13:00 2026 +From: Sasha Levin +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 , Simon Horman , Ratheesh Kannoth , Jakub Kicinski , Sasha Levin +Message-ID: <20260722180757.2202348-2-sashal@kernel.org> + +From: Runyu Xiao + +[ 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 +Reviewed-by: Simon Horman +Reviewed-by: Ratheesh Kannoth +Link: https://patch.msgid.link/20260611160014.3202224-2-runyu.xiao@seu.edu.cn +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.12/octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch b/queue-6.12/octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch new file mode 100644 index 0000000000..c592ffedbf --- /dev/null +++ b/queue-6.12/octeontx2-pf-fix-sqb-pointer-leak-on-init-failure.patch @@ -0,0 +1,79 @@ +From stable+bounces-290199-greg=kroah.com@vger.kernel.org Wed Jul 29 02:31:02 2026 +From: Sasha Levin +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 , Ratheesh Kannoth , Paolo Abeni , Sasha Levin +Message-ID: <20260729003053.889460-1-sashal@kernel.org> + +From: Dawei Feng + +[ 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 +Signed-off-by: Dawei Feng +Link: https://patch.msgid.link/20260630071625.349996-1-dawei.feng@seu.edu.cn +Signed-off-by: Paolo Abeni +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch b/queue-6.12/octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch new file mode 100644 index 0000000000..409f7c8c02 --- /dev/null +++ b/queue-6.12/octeontx2-vf-clear-stale-mailbox-irq-state-before-request_irq.patch @@ -0,0 +1,75 @@ +From stable+bounces-288042-greg=kroah.com@vger.kernel.org Wed Jul 22 20:12:46 2026 +From: Sasha Levin +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 , Simon Horman , Ratheesh Kannoth , Jakub Kicinski , Sasha Levin +Message-ID: <20260722180748.2202109-2-sashal@kernel.org> + +From: Runyu Xiao + +[ 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 +Reviewed-by: Simon Horman +Reviewed-by: Ratheesh Kannoth +Link: https://patch.msgid.link/20260611160014.3202224-3-runyu.xiao@seu.edu.cn +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; diff --git a/queue-6.12/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch b/queue-6.12/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch new file mode 100644 index 0000000000..7cdc6ebcbb --- /dev/null +++ b/queue-6.12/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch @@ -0,0 +1,105 @@ +From stable+bounces-289568-greg=kroah.com@vger.kernel.org Mon Jul 27 15:43:16 2026 +From: Sasha Levin +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 , Amir Goldstein , Miklos Szeredi , "Christian Brauner (Amutable)" , Sasha Levin +Message-ID: <20260727134026.589031-1-sashal@kernel.org> + +From: Souvik Banerjee + +[ 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 "#" 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 + - 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 +Link: https://patch.msgid.link/20260501232735.2610824-1-souvik@amlalabs.com +Reviewed-by: Amir Goldstein +Reviewed-by: Miklos Szeredi +Signed-off-by: Christian Brauner (Amutable) +[ adapted scoped credential and creation helpers to explicit credential, locking, lookup, and cleanup handling ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/remoteproc-xlnx-check-remote-core-state.patch b/queue-6.12/remoteproc-xlnx-check-remote-core-state.patch new file mode 100644 index 0000000000..04320f95c2 --- /dev/null +++ b/queue-6.12/remoteproc-xlnx-check-remote-core-state.patch @@ -0,0 +1,147 @@ +From stable+bounces-288601-greg=kroah.com@vger.kernel.org Fri Jul 24 03:25:06 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 21:24:56 -0400 +Subject: remoteproc: xlnx: Check remote core state +To: stable@vger.kernel.org +Cc: Tanmay Shah , Beleswar Padhi , Michal Simek , Mathieu Poirier , Sasha Levin +Message-ID: <20260724012456.3832700-1-sashal@kernel.org> + +From: Tanmay Shah + +[ 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 +Fixes: bca4b02ef92e ("remoteproc: xlnx: Add attach detach support") +Reviewed-by: Beleswar Padhi +Acked-by: Michal Simek +Cc: stable@vger.kernel.org +Link: https://lore.kernel.org/r/20260428221855.313752-1-tanmay.shah@amd.com +Signed-off-by: Mathieu Poirier +[ 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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 diff --git a/queue-6.12/rxrpc-allow-the-app-to-store-private-data-on-peer-structs.patch b/queue-6.12/rxrpc-allow-the-app-to-store-private-data-on-peer-structs.patch new file mode 100644 index 0000000000..4f57c417f6 --- /dev/null +++ b/queue-6.12/rxrpc-allow-the-app-to-store-private-data-on-peer-structs.patch @@ -0,0 +1,94 @@ +From stable+bounces-290948-greg=kroah.com@vger.kernel.org Thu Jul 30 03:03:13 2026 +From: Sasha Levin +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 , Marc Dionne , Jakub Kicinski , "David S. Miller" , Eric Dumazet , Paolo Abeni , Simon Horman , linux-afs@lists.infradead.org, linux-fsdevel@vger.kernel.org, netdev@vger.kernel.org, Sasha Levin +Message-ID: <20260730010137.1700032-4-sashal@kernel.org> + +From: David Howells + +[ 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 +cc: Marc Dionne +cc: Jakub Kicinski +cc: "David S. Miller" +cc: Eric Dumazet +cc: Paolo Abeni +cc: Simon Horman +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch b/queue-6.12/rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch new file mode 100644 index 0000000000..3aa1a1f0e6 --- /dev/null +++ b/queue-6.12/rxrpc-don-t-need-barrier-for-tx_bottom-and-acks_hard_ack.patch @@ -0,0 +1,82 @@ +From stable+bounces-287940-greg=kroah.com@vger.kernel.org Wed Jul 22 18:32:15 2026 +From: Sasha Levin +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 , Marc Dionne , linux-afs@lists.infradead.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722160805.1741176-2-sashal@kernel.org> + +From: David Howells + +[ 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 +cc: Marc Dionne +cc: linux-afs@lists.infradead.org +Link: https://patch.msgid.link/20241204074710.990092-18-dhowells@redhat.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/rxrpc-fix-cpu-time-starvation-in-i-o-thread.patch b/queue-6.12/rxrpc-fix-cpu-time-starvation-in-i-o-thread.patch new file mode 100644 index 0000000000..1ae0646ed6 --- /dev/null +++ b/queue-6.12/rxrpc-fix-cpu-time-starvation-in-i-o-thread.patch @@ -0,0 +1,403 @@ +From stable+bounces-287939-greg=kroah.com@vger.kernel.org Wed Jul 22 18:32:12 2026 +From: Sasha Levin +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 , Marc Dionne , linux-afs@lists.infradead.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722160805.1741176-1-sashal@kernel.org> + +From: David Howells + +[ 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 +cc: Marc Dionne +cc: linux-afs@lists.infradead.org +Link: https://patch.msgid.link/20241204074710.990092-14-dhowells@redhat.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } diff --git a/queue-6.12/rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch b/queue-6.12/rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch new file mode 100644 index 0000000000..c22b474a93 --- /dev/null +++ b/queue-6.12/rxrpc-fix-notification-vs-call-release-vs-recvmsg.patch @@ -0,0 +1,138 @@ +From stable+bounces-287942-greg=kroah.com@vger.kernel.org Wed Jul 22 18:32:08 2026 +From: Sasha Levin +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 , Jeffrey Altman , Marc Dionne , "Junvyyang, Tencent Zhuque Lab" , LePremierHomme , Simon Horman , linux-afs@lists.infradead.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722160805.1741176-4-sashal@kernel.org> + +From: David Howells + +[ 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 +Reviewed-by: Jeffrey Altman +cc: Marc Dionne +cc: Junvyyang, Tencent Zhuque Lab +cc: LePremierHomme +cc: Simon Horman +cc: linux-afs@lists.infradead.org +Link: https://patch.msgid.link/20250717074350.3767366-4-dhowells@redhat.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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(); + diff --git a/queue-6.12/rxrpc-fix-socket-notification-race.patch b/queue-6.12/rxrpc-fix-socket-notification-race.patch new file mode 100644 index 0000000000..cee415a423 --- /dev/null +++ b/queue-6.12/rxrpc-fix-socket-notification-race.patch @@ -0,0 +1,47 @@ +From stable+bounces-287943-greg=kroah.com@vger.kernel.org Wed Jul 22 18:10:20 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 12:08:05 -0400 +Subject: rxrpc: Fix socket notification race +To: stable@vger.kernel.org +Cc: David Howells , Marc Dionne , Jeffrey Altman , Simon Horman , linux-afs@lists.infradead.org, stable@kernel.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722160805.1741176-5-sashal@kernel.org> + +From: David Howells + +[ 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 +cc: Marc Dionne +cc: Jeffrey Altman +cc: Simon Horman +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 +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; diff --git a/queue-6.12/rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch b/queue-6.12/rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch new file mode 100644 index 0000000000..c13a3da5c0 --- /dev/null +++ b/queue-6.12/rxrpc-pull-out-certain-app-callback-funcs-into-an-ops-table.patch @@ -0,0 +1,294 @@ +From stable+bounces-287863-greg=kroah.com@vger.kernel.org Wed Jul 22 16:03:05 2026 +From: Sasha Levin +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 , Marc Dionne , Simon Horman , linux-afs@lists.infradead.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722135450.1365902-1-sashal@kernel.org> + +From: David Howells + +[ 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 +cc: Marc Dionne +cc: Simon Horman +cc: linux-afs@lists.infradead.org +Link: https://patch.msgid.link/20250411095303.2316168-3-dhowells@redhat.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: dc175389b18c ("rxrpc: serialize kernel accept preallocation with socket teardown") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.12/rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch b/queue-6.12/rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch new file mode 100644 index 0000000000..4476d09fd4 --- /dev/null +++ b/queue-6.12/rxrpc-serialize-kernel-accept-preallocation-with-socket-teardown.patch @@ -0,0 +1,79 @@ +From stable+bounces-287864-greg=kroah.com@vger.kernel.org Wed Jul 22 16:03:44 2026 +From: Sasha Levin +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 , Yuan Tan , Yifan Wu , Juefei Pu , Xin Liu , Ren Wei , David Howells , Marc Dionne , Jeffrey Altman , Simon Horman , linux-afs@lists.infradead.org, stable@kernel.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722135450.1365902-2-sashal@kernel.org> + +From: Li Daming + +[ 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 +Reported-by: Yifan Wu +Reported-by: Juefei Pu +Reported-by: Xin Liu +Signed-off-by: Li Daming +Signed-off-by: Ren Wei +Signed-off-by: David Howells +cc: Marc Dionne +cc: Jeffrey Altman +cc: Simon Horman +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 +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch b/queue-6.12/rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch new file mode 100644 index 0000000000..202e34ed7a --- /dev/null +++ b/queue-6.12/rxrpc-use-irq-disabling-spinlocks-between-app-and-i-o-thread.patch @@ -0,0 +1,473 @@ +From stable+bounces-287941-greg=kroah.com@vger.kernel.org Wed Jul 22 18:33:59 2026 +From: Sasha Levin +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 , Marc Dionne , linux-afs@lists.infradead.org, Jakub Kicinski , Sasha Levin +Message-ID: <20260722160805.1741176-3-sashal@kernel.org> + +From: David Howells + +[ 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 +cc: Marc Dionne +cc: linux-afs@lists.infradead.org +Signed-off-by: Jakub Kicinski +Stable-dep-of: e66f8f32f501 ("rxrpc: Fix socket notification race") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/series b/queue-6.12/series index ba8d2498fa..475426b4c7 100644 --- a/queue-6.12/series +++ b/queue-6.12/series @@ -462,3 +462,123 @@ gve-fix-rx-queue-stall-on-alloc-failure.patch 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 diff --git a/queue-6.12/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch b/queue-6.12/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch new file mode 100644 index 0000000000..c3149fd572 --- /dev/null +++ b/queue-6.12/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch @@ -0,0 +1,279 @@ +From stable+bounces-288527-greg=kroah.com@vger.kernel.org Thu Jul 23 22:00:59 2026 +From: Sasha Levin +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 , Jeff Layton , Anna Schumaker , Sasha Levin +Message-ID: <20260723195904.3685200-3-sashal@kernel.org> + +From: Chuck Lever + +[ 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 +Acked-by: Anna Schumaker +Signed-off-by: Chuck Lever +Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 diff --git a/queue-6.12/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch b/queue-6.12/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch new file mode 100644 index 0000000000..e03444d93b --- /dev/null +++ b/queue-6.12/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch @@ -0,0 +1,140 @@ +From stable+bounces-288528-greg=kroah.com@vger.kernel.org Thu Jul 23 22:01:01 2026 +From: Sasha Levin +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 , NeilBrown , Chuck Lever , Sasha Levin +Message-ID: <20260723195904.3685200-2-sashal@kernel.org> + +From: Jeff Layton + +[ 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 +Reviewed-by: NeilBrown +Signed-off-by: Chuck Lever +Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } diff --git a/queue-6.12/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch b/queue-6.12/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch new file mode 100644 index 0000000000..9f68399a39 --- /dev/null +++ b/queue-6.12/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch @@ -0,0 +1,142 @@ +From stable+bounces-288531-greg=kroah.com@vger.kernel.org Thu Jul 23 22:01:10 2026 +From: Sasha Levin +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 , Chris Mason , Jeff Layton , Sasha Levin +Message-ID: <20260723195904.3685200-5-sashal@kernel.org> + +From: Chuck Lever + +[ 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 +Fixes: 2eb2b9358181 ("SUNRPC: Convert svc_tcp_sendmsg to use bio_vecs directly") +Cc: stable@vger.kernel.org +Reviewed-by: Jeff Layton +Signed-off-by: Chuck Lever +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; + } + + /** diff --git a/queue-6.12/tcp-decrement-tcp_md5_needed-static-branch.patch b/queue-6.12/tcp-decrement-tcp_md5_needed-static-branch.patch new file mode 100644 index 0000000000..1b7665625e --- /dev/null +++ b/queue-6.12/tcp-decrement-tcp_md5_needed-static-branch.patch @@ -0,0 +1,44 @@ +From stable+bounces-289482-greg=kroah.com@vger.kernel.org Mon Jul 27 14:24:16 2026 +From: Sasha Levin +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 , Jakub Kicinski , Sasha Levin +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 +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 +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 diff --git a/queue-6.12/tcp-defer-md5sig_info-kfree-past-rcu-grace-period-in-tcp_connect.patch b/queue-6.12/tcp-defer-md5sig_info-kfree-past-rcu-grace-period-in-tcp_connect.patch new file mode 100644 index 0000000000..186d2eb057 --- /dev/null +++ b/queue-6.12/tcp-defer-md5sig_info-kfree-past-rcu-grace-period-in-tcp_connect.patch @@ -0,0 +1,113 @@ +From stable+bounces-289481-greg=kroah.com@vger.kernel.org Mon Jul 27 14:24:16 2026 +From: Sasha Levin +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 , Eric Dumazet , Dmitry Safonov , Qihang , Dmitry Safonov <0x7f454c46@gmail.com>, Jakub Kicinski , Sasha Levin +Message-ID: <20260727121956.3778569-1-sashal@kernel.org> + +From: Michael Bommarito + +[ 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 +Signed-off-by: Michael Bommarito +Reviewed-by: Dmitry Safonov +Reviewed-by: Eric Dumazet +[also credits to Qihang, who found that this races with tcp-diag] +Reported-by: Qihang +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 +Stable-dep-of: 6f6e860e370c ("tcp: Decrement tcp_md5_needed static branch") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 diff --git a/queue-6.12/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch b/queue-6.12/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch new file mode 100644 index 0000000000..2a19366fa9 --- /dev/null +++ b/queue-6.12/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch @@ -0,0 +1,42 @@ +From stable+bounces-289260-greg=kroah.com@vger.kernel.org Sun Jul 26 14:36:19 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260726123613.37376-1-sashal@kernel.org> + +From: Mika Westerberg + +[ 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 +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch b/queue-6.12/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch new file mode 100644 index 0000000000..b23599bf7b --- /dev/null +++ b/queue-6.12/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch @@ -0,0 +1,90 @@ +From stable+bounces-289263-greg=kroah.com@vger.kernel.org Sun Jul 26 14:36:20 2026 +From: Sasha Levin +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 , Mika Westerberg , Sasha Levin +Message-ID: <20260726123613.37376-4-sashal@kernel.org> + +From: Michael Bommarito + +[ 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 +Signed-off-by: Mika Westerberg +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); diff --git a/queue-6.12/thunderbolt-remove-service-debugfs-entries-during-unregister.patch b/queue-6.12/thunderbolt-remove-service-debugfs-entries-during-unregister.patch new file mode 100644 index 0000000000..0bce7b9cd6 --- /dev/null +++ b/queue-6.12/thunderbolt-remove-service-debugfs-entries-during-unregister.patch @@ -0,0 +1,76 @@ +From stable+bounces-289261-greg=kroah.com@vger.kernel.org Sun Jul 26 14:36:19 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260726123613.37376-2-sashal@kernel.org> + +From: Mika Westerberg + +[ 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 +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; + } + diff --git a/queue-6.12/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch b/queue-6.12/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch new file mode 100644 index 0000000000..ead1e29de1 --- /dev/null +++ b/queue-6.12/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch @@ -0,0 +1,337 @@ +From stable+bounces-289262-greg=kroah.com@vger.kernel.org Sun Jul 26 14:36:20 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260726123613.37376-3-sashal@kernel.org> + +From: Mika Westerberg + +[ 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 +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + * diff --git a/queue-6.12/tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch b/queue-6.12/tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch new file mode 100644 index 0000000000..3a27dbc986 --- /dev/null +++ b/queue-6.12/tipc-restrict-socket-queue-dumps-in-enqueue-tracepoints.patch @@ -0,0 +1,97 @@ +From stable+bounces-287970-greg=kroah.com@vger.kernel.org Wed Jul 22 18:56:34 2026 +From: Sasha Levin +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 , Tung Nguyen , Jakub Kicinski , Sasha Levin +Message-ID: <20260722165429.1853397-1-sashal@kernel.org> + +From: Li Xiasong + +[ 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: + + 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 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 + + +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 +Reviewed-by: Tung Nguyen +Link: https://patch.msgid.link/20260611135647.3666727-1-lixiasong1@huawei.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } diff --git a/queue-6.12/usb-gadget-fsl-udc-fix-dev_printk-device.patch b/queue-6.12/usb-gadget-fsl-udc-fix-dev_printk-device.patch new file mode 100644 index 0000000000..d113fa2cd0 --- /dev/null +++ b/queue-6.12/usb-gadget-fsl-udc-fix-dev_printk-device.patch @@ -0,0 +1,422 @@ +From stable+bounces-290986-greg=kroah.com@vger.kernel.org Thu Jul 30 04:31:09 2026 +From: Sasha Levin +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" , stable , "Uwe Kleine-König" , "Greg Kroah-Hartman" , "Sasha Levin" +Message-ID: <20260730023101.1859045-2-sashal@kernel.org> + +From: Johan Hovold + +[ 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 +Cc: Uwe Kleine-König +Signed-off-by: Johan Hovold +Acked-by: Uwe Kleine-König +Link: https://patch.msgid.link/20260702141536.90887-4-johan@kernel.org +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; diff --git a/queue-6.12/usb-gadget-use-str_enable_disable-like-helpers.patch b/queue-6.12/usb-gadget-use-str_enable_disable-like-helpers.patch new file mode 100644 index 0000000000..63d9c220da --- /dev/null +++ b/queue-6.12/usb-gadget-use-str_enable_disable-like-helpers.patch @@ -0,0 +1,262 @@ +From sashal@kernel.org Thu Jul 30 04:31:04 2026 +From: Sasha Levin +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 , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260730023101.1859045-1-sashal@kernel.org> + +From: Krzysztof Kozlowski + +[ 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 +Link: https://lore.kernel.org/r/20250114-str-enable-disable-usb-v1-5-c8405df47c19@linaro.org +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: c4dd150fceab ("USB: gadget: fsl-udc: fix dev_printk() device") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + #include ++#include + + #include + +@@ -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 + #include + #include ++#include + + #include + #include +@@ -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 + #include + #include ++#include + #include + #include + #include +@@ -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 + #include + #include ++#include + #include + #include + #include +@@ -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 + #include + #include ++#include + #include + #include + #include +@@ -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 + #include + #include ++#include + #include + #include + #include +@@ -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 + #include + #include ++#include + #include + #include + +@@ -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 + #include + #include ++#include + #include + #include + #include +@@ -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 + #include + #include ++#include + #include + #include + #include +@@ -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 + #include + #include ++#include + #include + #include + #include +@@ -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 + #include + #include ++#include + #include + #include + #include +@@ -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) { diff --git a/queue-6.12/vduse-avoid-leaking-information-to-userspace.patch b/queue-6.12/vduse-avoid-leaking-information-to-userspace.patch new file mode 100644 index 0000000000..5b981f0a33 --- /dev/null +++ b/queue-6.12/vduse-avoid-leaking-information-to-userspace.patch @@ -0,0 +1,53 @@ +From stable+bounces-287974-greg=kroah.com@vger.kernel.org Wed Jul 22 19:15:33 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 12:54:33 -0400 +Subject: VDUSE: avoid leaking information to userspace +To: stable@vger.kernel.org +Cc: "Jason Wang" , "Xie Yongji" , "Eugenio Pérez" , "Michael S. Tsirkin" , "Sasha Levin" +Message-ID: <20260722165433.1853588-4-sashal@kernel.org> + +From: Jason Wang + +[ 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 +Reviewed-by: Xie Yongji +Reviewed-by: Eugenio Pérez +Signed-off-by: Michael S. Tsirkin +Message-ID: <20260130050750.4050-1-jasowang@redhat.com> +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; + diff --git a/queue-6.12/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch b/queue-6.12/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch new file mode 100644 index 0000000000..eef1f206af --- /dev/null +++ b/queue-6.12/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch @@ -0,0 +1,61 @@ +From stable+bounces-287972-greg=kroah.com@vger.kernel.org Wed Jul 22 19:15:43 2026 +From: Sasha Levin +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" , "Michael S. Tsirkin" , "Sasha Levin" +Message-ID: <20260722165433.1853588-2-sashal@kernel.org> + +From: Eugenio Pérez + +[ Upstream commit 766e1749c0ef6a09651be9b8a8283d508c322b58 ] + +We will modify the function in next patches so let's clean it first. + +Signed-off-by: Eugenio Pérez +Signed-off-by: Michael S. Tsirkin +Message-Id: <20260119143306.1818855-9-eperezma@redhat.com> +Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.12/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch b/queue-6.12/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch new file mode 100644 index 0000000000..9699e5fb30 --- /dev/null +++ b/queue-6.12/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch @@ -0,0 +1,147 @@ +From stable+bounces-287973-greg=kroah.com@vger.kernel.org Wed Jul 22 19:04:38 2026 +From: Sasha Levin +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" , "Jason Wang" , "Michael S. Tsirkin" , "Sasha Levin" +Message-ID: <20260722165433.1853588-3-sashal@kernel.org> + +From: Eugenio Pérez + +[ 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 +Signed-off-by: Eugenio Pérez +Signed-off-by: Michael S. Tsirkin +Message-Id: <20260119143306.1818855-10-eperezma@redhat.com> +Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) diff --git a/queue-6.12/vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch b/queue-6.12/vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch new file mode 100644 index 0000000000..8cb25db613 --- /dev/null +++ b/queue-6.12/vduse-use-fixed-4kb-bounce-pages-for-non-4kb-page-size.patch @@ -0,0 +1,292 @@ +From stable+bounces-287971-greg=kroah.com@vger.kernel.org Wed Jul 22 19:04:36 2026 +From: Sasha Levin +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 , "Michael S. Tsirkin" , Sasha Levin +Message-ID: <20260722165433.1853588-1-sashal@kernel.org> + +From: Sheng Zhao + +[ 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 +Message-Id: <20250925113516.60305-1-sheng.zhao@bytedance.com> +Signed-off-by: Michael S. Tsirkin +Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; diff --git a/queue-6.12/vfs-audit-introduce-kern_path_parent-for-audit.patch b/queue-6.12/vfs-audit-introduce-kern_path_parent-for-audit.patch new file mode 100644 index 0000000000..8d2d5db57b --- /dev/null +++ b/queue-6.12/vfs-audit-introduce-kern_path_parent-for-audit.patch @@ -0,0 +1,191 @@ +From stable+bounces-287657-greg=kroah.com@vger.kernel.org Wed Jul 22 02:19:43 2026 +From: Sasha Levin +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 , Christian Brauner , Sasha Levin +Message-ID: <20260721235904.525962-1-sashal@kernel.org> + +From: NeilBrown + +[ 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 +Signed-off-by: Christian Brauner +Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; + } diff --git a/queue-6.12/wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch b/queue-6.12/wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch new file mode 100644 index 0000000000..e9d2536a16 --- /dev/null +++ b/queue-6.12/wifi-libertas_tf-fix-use-after-free-in-lbtf_free_adapter.patch @@ -0,0 +1,49 @@ +From stable+bounces-289739-greg=kroah.com@vger.kernel.org Tue Jul 28 01:36:40 2026 +From: Sasha Levin +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 , Johannes Berg , Sasha Levin +Message-ID: <20260727233431.2424901-1-sashal@kernel.org> + +From: Maoyi Xie + +[ 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 +Link: https://patch.msgid.link/178211481807.2212567.8773346114561900100@maoyixie.com +Signed-off-by: Johannes Berg +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } + diff --git a/queue-6.12/xfrm-nat_keepalive-avoid-double-free-on-send-error.patch b/queue-6.12/xfrm-nat_keepalive-avoid-double-free-on-send-error.patch new file mode 100644 index 0000000000..c65577d322 --- /dev/null +++ b/queue-6.12/xfrm-nat_keepalive-avoid-double-free-on-send-error.patch @@ -0,0 +1,91 @@ +From stable+bounces-289480-greg=kroah.com@vger.kernel.org Mon Jul 27 14:24:14 2026 +From: Sasha Levin +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 , Yuan Tan , Xin Liu , Ren Wei , Eyal Birger , Steffen Klassert , Sasha Levin +Message-ID: <20260727121952.3778327-2-sashal@kernel.org> + +From: Qianyu Luo + +[ 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 +Reported-by: Xin Liu +Signed-off-by: Qianyu Luo +Signed-off-by: Ren Wei +Reviewed-by: Eyal Birger +Signed-off-by: Steffen Klassert +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 { diff --git a/queue-6.12/xfrm-use-nested-bh-locking-for-nat_keepalive_sk_ipv.patch b/queue-6.12/xfrm-use-nested-bh-locking-for-nat_keepalive_sk_ipv.patch new file mode 100644 index 0000000000..a64140fb66 --- /dev/null +++ b/queue-6.12/xfrm-use-nested-bh-locking-for-nat_keepalive_sk_ipv.patch @@ -0,0 +1,122 @@ +From stable+bounces-289479-greg=kroah.com@vger.kernel.org Mon Jul 27 14:19:59 2026 +From: Sasha Levin +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 , Steffen Klassert , Herbert Xu , Paolo Abeni , Sasha Levin +Message-ID: <20260727121952.3778327-1-sashal@kernel.org> + +From: Sebastian Andrzej Siewior + +[ 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 +Cc: Herbert Xu +Signed-off-by: Sebastian Andrzej Siewior +Link: https://patch.msgid.link/20250512092736.229935-7-bigeasy@linutronix.de +Signed-off-by: Paolo Abeni +Stable-dep-of: 226f4a490d1a ("xfrm: nat_keepalive: avoid double free on send error") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + +-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) diff --git a/queue-6.12/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch b/queue-6.12/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch new file mode 100644 index 0000000000..984df290c0 --- /dev/null +++ b/queue-6.12/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch @@ -0,0 +1,387 @@ +From stable+bounces-287872-greg=kroah.com@vger.kernel.org Wed Jul 22 16:03:28 2026 +From: Sasha Levin +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" , Christoph Hellwig , Carlos Maiolino , Sasha Levin +Message-ID: <20260722135501.1366960-2-sashal@kernel.org> + +From: "Darrick J. Wong" + +[ 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" +Assisted-by: LOLLM # finding obvious bugs +Reviewed-by: Christoph Hellwig +Signed-off-by: Carlos Maiolino +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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, diff --git a/queue-6.12/xfs-factor-out-xfs_attr3_leaf_init.patch b/queue-6.12/xfs-factor-out-xfs_attr3_leaf_init.patch new file mode 100644 index 0000000000..5f31fccdfe --- /dev/null +++ b/queue-6.12/xfs-factor-out-xfs_attr3_leaf_init.patch @@ -0,0 +1,69 @@ +From stable+bounces-287870-greg=kroah.com@vger.kernel.org Wed Jul 22 16:18:10 2026 +From: Sasha Levin +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 , "Darrick J. Wong" , Carlos Maiolino , Sasha Levin +Message-ID: <20260722135501.1366960-1-sashal@kernel.org> + +From: Long Li + +[ Upstream commit e65bb55d7f8c2041c8fdb73cd29b0b4cad4ed847 ] + +Factor out wrapper xfs_attr3_leaf_init function, which exported for +external use. + +Reviewed-by: Darrick J. Wong +Signed-off-by: Long Li +Signed-off-by: Carlos Maiolino +Stable-dep-of: a1caeeadbf57 ("xfs: don't replace the wrong part of the cow fork") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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,