From ee7863e43228a3143398dc5bbb943c9a735a8fca Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 31 Mar 2026 15:52:55 +0530 Subject: [PATCH 001/479] arm64: tegra: Remove fallback compatible for GPCDMA Remove the fallback compatible string "nvidia,tegra186-gpcdma" for GPCDMA in Tegra264. Tegra186 compatible cannot work on Tegra264 because of the register offset changes and absence of the reset property. Fixes: 65ef237e4810 ("arm64: tegra: Add Tegra264 support") Signed-off-by: Akhil R Reviewed-by: Jon Hunter Signed-off-by: Thierry Reding --- arch/arm64/boot/dts/nvidia/tegra264.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/nvidia/tegra264.dtsi b/arch/arm64/boot/dts/nvidia/tegra264.dtsi index 2d8e7e37830f..3dfdd7bb28a9 100644 --- a/arch/arm64/boot/dts/nvidia/tegra264.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra264.dtsi @@ -3208,7 +3208,7 @@ agic_page5: interrupt-controller@99b0000 { }; gpcdma: dma-controller@8400000 { - compatible = "nvidia,tegra264-gpcdma", "nvidia,tegra186-gpcdma"; + compatible = "nvidia,tegra264-gpcdma"; reg = <0x0 0x08400000 0x0 0x210000>; interrupts = , , From e81f1079f9000892cf54b23a9572ad5d86036fca Mon Sep 17 00:00:00 2001 From: Catalin Iacob Date: Mon, 8 Jun 2026 17:29:16 +0300 Subject: [PATCH 002/479] scsi: core: Remove export for scsi_device_from_queue() Commit 1cea5180f2f8 ("block: remove pktcdvd driver") left behind an export that is now dead code. Remove it and move the declaration of scsi_device_from_queue() to drivers/scsi/scsi_priv.h. Signed-off-by: Catalin Iacob Link: https://patch.msgid.link/20260608-remove-pktcdvd-references-v4-1-72f88b04cc87@gmail.com Signed-off-by: Martin K. Petersen --- drivers/scsi/scsi_lib.c | 8 -------- drivers/scsi/scsi_priv.h | 1 + include/scsi/scsi_device.h | 1 - 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 85eef401925a..b67f0dc79499 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -2224,14 +2224,6 @@ struct scsi_device *scsi_device_from_queue(struct request_queue *q) return sdev; } -/* - * pktcdvd should have been integrated into the SCSI layers, but for historical - * reasons like the old IDE driver it isn't. This export allows it to safely - * probe if a given device is a SCSI one and only attach to that. - */ -#ifdef CONFIG_CDROM_PKTCDVD_MODULE -EXPORT_SYMBOL_GPL(scsi_device_from_queue); -#endif /** * scsi_block_requests - Utility function used by low-level drivers to prevent diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h index 7a193cc04e5b..37e5601be2b8 100644 --- a/drivers/scsi/scsi_priv.h +++ b/drivers/scsi/scsi_priv.h @@ -102,6 +102,7 @@ void scsi_eh_done(struct scsi_cmnd *scmd); /* scsi_lib.c */ extern void scsi_device_unbusy(struct scsi_device *sdev, struct scsi_cmnd *cmd); +extern struct scsi_device *scsi_device_from_queue(struct request_queue *q); extern void scsi_queue_insert(struct scsi_cmnd *cmd, enum scsi_qc_status reason); extern void scsi_io_completion(struct scsi_cmnd *, unsigned int); diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h index 029f5115b2ea..8694eeadd753 100644 --- a/include/scsi/scsi_device.h +++ b/include/scsi/scsi_device.h @@ -408,7 +408,6 @@ void scsi_attach_vpd(struct scsi_device *sdev); void scsi_cdl_check(struct scsi_device *sdev); int scsi_cdl_enable(struct scsi_device *sdev, bool enable); -extern struct scsi_device *scsi_device_from_queue(struct request_queue *q); extern int __must_check scsi_device_get(struct scsi_device *); extern void scsi_device_put(struct scsi_device *); extern struct scsi_device *scsi_device_lookup(struct Scsi_Host *, From ca978f8a93d4d36841839bf2847d29b88c2591d6 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Thu, 11 Jun 2026 08:30:45 -0400 Subject: [PATCH 003/479] scsi: xen: scsiback: Free unsubmitted command instead of double-putting it scsiback_get_pend_req() obtains a command tag and returns a vscsibk_pend whose embedded se_cmd has only been memset to 0, so its cmd_kref is 0; the se_cmd is initialised (kref_init() via target_init_cmd()) only later, in scsiback_cmd_exec(), on the successful VSCSIIF_ACT_SCSI_CDB path. The two error paths in scsiback_do_cmd_fn() taken before the command is submitted -- a failed scsiback_gnttab_data_map() and an unknown ring_req.act -- call transport_generic_free_cmd(&pending_req->se_cmd, 0), which kref_put()s a refcount of 0. That underflows it ("refcount_t: underflow; use-after-free") and, as the release function is not run, leaks the command tag. Impact: a pvSCSI guest can leak every command tag of a LUN's session, stopping the LUN, by submitting requests with a bad grant reference or an unknown request type; under panic_on_warn the refcount underflow panics the host. Add a helper that just returns the tag with target_free_tag() and sends the error response. It frees the tag while the v2p reference still pins the session, and snapshots the response fields beforehand because freeing the tag can let another ring reuse the pending_req slot. Fixes: 2dbcdf33dbf6 ("xen-scsiback: Convert to percpu_ida tag allocation") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Juergen Gross Link: https://patch.msgid.link/20260611123046.2323342-2-michael.bommarito@gmail.com Signed-off-by: Martin K. Petersen --- drivers/xen/xen-scsiback.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/drivers/xen/xen-scsiback.c b/drivers/xen/xen-scsiback.c index e33f95c91b09..f324732eba7f 100644 --- a/drivers/xen/xen-scsiback.c +++ b/drivers/xen/xen-scsiback.c @@ -611,6 +611,25 @@ static void scsiback_disconnect(struct vscsibk_info *info) xenbus_unmap_ring_vfree(info->dev, info->ring.sring); } +/* + * Send the error response for a request that did not reach the target core + * and return its tag. Free the tag before the response drops the v2p + * reference that keeps the session alive, and snapshot what the response + * needs since returning the tag can let the slot be reused. + */ +static void scsiback_resp_and_free(struct vscsibk_pend *pending_req, + int32_t result) +{ + struct vscsibk_info *info = pending_req->info; + struct v2p_entry *v2p = pending_req->v2p; + struct se_session *se_sess = v2p->tpg->tpg_nexus->tvn_se_sess; + u16 rqid = pending_req->rqid; + + target_free_tag(se_sess, &pending_req->se_cmd); + scsiback_send_response(info, NULL, result, 0, rqid); + kref_put(&v2p->kref, scsiback_free_translation_entry); +} + static void scsiback_device_action(struct vscsibk_pend *pending_req, enum tcm_tmreq_table act, int tag) { @@ -792,9 +811,8 @@ static int scsiback_do_cmd_fn(struct vscsibk_info *info, case VSCSIIF_ACT_SCSI_CDB: if (scsiback_gnttab_data_map(&ring_req, pending_req)) { scsiback_fast_flush_area(pending_req); - scsiback_do_resp_with_sense(NULL, - DID_ERROR << 16, 0, pending_req); - transport_generic_free_cmd(&pending_req->se_cmd, 0); + scsiback_resp_and_free(pending_req, + DID_ERROR << 16); } else { scsiback_cmd_exec(pending_req); } @@ -808,9 +826,7 @@ static int scsiback_do_cmd_fn(struct vscsibk_info *info, break; default: pr_err_ratelimited("invalid request\n"); - scsiback_do_resp_with_sense(NULL, DID_ERROR << 16, 0, - pending_req); - transport_generic_free_cmd(&pending_req->se_cmd, 0); + scsiback_resp_and_free(pending_req, DID_ERROR << 16); break; } From 66aefc277ebb796ec285d550305535dc3fc0179f Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Thu, 11 Jun 2026 08:30:46 -0400 Subject: [PATCH 004/479] scsi: xen: scsiback: Free the command tag on the TMR submit-failure path scsiback_device_action() obtains a command tag in scsiback_get_pend_req() and submits a task-management request with target_submit_tmr(). When target_submit_tmr() fails it returns < 0 and scsiback jumps to the err: label, which sends a response but frees nothing, leaking the tag. Impact: a pvSCSI guest can leak the command tags of a LUN's session, stopping the LUN, by issuing VSCSIIF_ACT_SCSI_ABORT or RESET requests whenever target_submit_tmr() fails. transport_generic_free_cmd() cannot be used here. By the time target_submit_tmr() returns an error it has already run __target_init_cmd() (so se_cmd->cmd_kref is one, not zero), and on its target_get_sess_cmd() error path it has freed se_cmd->se_tmr_req via core_tmr_release_req() while leaving SCF_SCSI_TMR_CDB set and the pointer dangling. Letting the command release run target_free_cmd_mem() would then double-free se_tmr_req. Use the same helper, which returns just the tag, on this path too. Fixes: 2dbcdf33dbf6 ("xen-scsiback: Convert to percpu_ida tag allocation") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Juergen Gross Link: https://patch.msgid.link/20260611123046.2323342-3-michael.bommarito@gmail.com Signed-off-by: Martin K. Petersen --- drivers/xen/xen-scsiback.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/xen/xen-scsiback.c b/drivers/xen/xen-scsiback.c index f324732eba7f..c7036e0e41bd 100644 --- a/drivers/xen/xen-scsiback.c +++ b/drivers/xen/xen-scsiback.c @@ -658,7 +658,7 @@ static void scsiback_device_action(struct vscsibk_pend *pending_req, return; err: - scsiback_do_resp_with_sense(NULL, err, 0, pending_req); + scsiback_resp_and_free(pending_req, err); } /* From 57a6ed0b41677ccc5e28cc0976e495c1dfa33747 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 11 Jun 2026 14:55:56 +0200 Subject: [PATCH 005/479] scsi: bfa: Reduce kernel stack usage in bfa_fcs_lport_fdmi_build_portattr_block() bfa_fcs_fdmi_get_portattr() gets inlined into multiple places and has two fairly large variables on the stack, to the point of causing a warning in some randconfig builds: drivers/scsi/bfa/bfa_fcs_lport.c:2198:1: error: stack frame size (1560) exceeds limit (1280) in 'bfa_fcs_lport_fdmi_build_portattr_block' [-Werror,-Wframe-larger-than] 2198 | bfa_fcs_lport_fdmi_build_portattr_block(struct bfa_fcs_lport_fdmi_s *fdmi, | ^ drivers/scsi/bfa/bfa_fcs_lport.c:1856:1: error: stack frame size (1600) exceeds limit (1280) in 'bfa_fcs_lport_fdmi_build_rhba_pyld' [-Werror,-Wframe-larger-than] 1856 | bfa_fcs_lport_fdmi_build_rhba_pyld(struct bfa_fcs_lport_fdmi_s *fdmi, u8 *pyld) | ^ Mark the inner function as noinline_for_stack to keep it separate from the other variables and prevent multiple copies of the same variable to get inlined here. Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260611125601.3385418-1-arnd@kernel.org Signed-off-by: Martin K. Petersen --- drivers/scsi/bfa/bfa_fcs_lport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/bfa/bfa_fcs_lport.c b/drivers/scsi/bfa/bfa_fcs_lport.c index 2df399c537c1..8c9d423129c0 100644 --- a/drivers/scsi/bfa/bfa_fcs_lport.c +++ b/drivers/scsi/bfa/bfa_fcs_lport.c @@ -2627,7 +2627,7 @@ bfa_fcs_fdmi_get_hbaattr(struct bfa_fcs_lport_fdmi_s *fdmi, } -static void +static noinline_for_stack void bfa_fcs_fdmi_get_portattr(struct bfa_fcs_lport_fdmi_s *fdmi, struct bfa_fcs_fdmi_port_attr_s *port_attr) { From 7258770e5814f15e8308ebda82ac9acf6964ba8e Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Mon, 15 Jun 2026 19:16:25 +0100 Subject: [PATCH 006/479] KVM: arm64: vgic: Handle race between interrupt affinity change and LPI disabling Hyunwoo Kim reports some really bad races should the following situation occur: - LPI-I is pending in vcpu-B's AP list - vcpu-A writes to vcpu-B's RD to disable its LPIs - vcpu-C moves I from B to C If the last two race nicely enough, vgic_prune_ap_list() can drop the irq and AP list locks, reacquire them, and in the interval the irq has been freed. UAF follows. The fix is two-fold: - Before dropping the irq and ap_list locks, take a reference on the irq - Do not try to handle migration of the pending bit: there is no expectation that this state is retained, as per the architecture With that, we're sure that the interrupt is still around, and we safely remove it from the AP list as it has no target at this stage (unless another interrupt fires, but that's another story). Reported-by: Hyunwoo Kim Tested-by: Hyunwoo Kim Link: https://lore.kernel.org/r/ailsCnyoS82r_QRz@v4bel Link: https://patch.msgid.link/20260615181625.3029352-1-maz@kernel.org Fixes: 5dd4b924e390a ("KVM: arm/arm64: vgic: Add refcounting for IRQs") Signed-off-by: Marc Zyngier Cc: stable@vger.kernel.org --- arch/arm64/kvm/vgic/vgic.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c index 5a4768d8cd4f..70a161383e5a 100644 --- a/arch/arm64/kvm/vgic/vgic.c +++ b/arch/arm64/kvm/vgic/vgic.c @@ -203,6 +203,7 @@ void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu) list_for_each_entry_safe(irq, tmp, &vgic_cpu->ap_list_head, ap_list) { if (irq_is_lpi(vcpu->kvm, irq->intid)) { raw_spin_lock(&irq->irq_lock); + irq->pending_latch = false; list_del(&irq->ap_list); irq->vcpu = NULL; raw_spin_unlock(&irq->irq_lock); @@ -792,7 +793,11 @@ static void vgic_prune_ap_list(struct kvm_vcpu *vcpu) continue; } - /* This interrupt looks like it has to be migrated. */ + /* + * This interrupt looks like it has to be migrated, + * make sure it is kept alive while locks are dropped. + */ + vgic_get_irq_ref(irq); raw_spin_unlock(&irq->irq_lock); raw_spin_unlock(&vgic_cpu->ap_list_lock); @@ -836,6 +841,8 @@ static void vgic_prune_ap_list(struct kvm_vcpu *vcpu) raw_spin_unlock(&vcpuB->arch.vgic_cpu.ap_list_lock); raw_spin_unlock(&vcpuA->arch.vgic_cpu.ap_list_lock); + deleted_lpis |= vgic_put_irq_norelease(vcpu->kvm, irq); + if (target_vcpu_needs_kick) { kvm_make_request(KVM_REQ_IRQ_PENDING, target_vcpu); kvm_vcpu_kick(target_vcpu); From 0074b82cdfcb5fd13710a0ac308ade68ac6f6fbe Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Fri, 5 Jun 2026 05:59:15 +0900 Subject: [PATCH 007/479] KVM: arm64: vgic: Check the interrupt is still ours before migrating it vgic_prune_ap_list() drops both ap_list_lock and irq_lock while migrating an interrupt to another vCPU. After reacquiring the locks it only checks that the affinity is unchanged (target_vcpu == vgic_target_oracle(irq)) before moving the interrupt, which assumes that an interrupt whose affinity is preserved is still queued on this vCPU's ap_list. That assumption no longer holds if the interrupt is taken off the ap_list while the locks are dropped. vgic_flush_pending_lpis() removes the interrupt from the list and sets irq->vcpu to NULL, but leaves enabled/pending/target_vcpu untouched. As the interrupt is still enabled and pending, vgic_target_oracle() returns the same target_vcpu, so the affinity check passes and list_del() is run a second time on an entry that has already been removed. Also check that the interrupt is still assigned to this vCPU (irq->vcpu == vcpu) before moving it. Fixes: 0919e84c0fc1 ("KVM: arm/arm64: vgic-new: Add IRQ sync/flush framework") Signed-off-by: Hyunwoo Kim Link: https://patch.msgid.link/aiHnI1mu6SGQrgnz@v4bel Signed-off-by: Marc Zyngier Cc: stable@vger.kernel.org --- arch/arm64/kvm/vgic/vgic.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c index 70a161383e5a..ccb7e3a90cd0 100644 --- a/arch/arm64/kvm/vgic/vgic.c +++ b/arch/arm64/kvm/vgic/vgic.c @@ -820,15 +820,16 @@ static void vgic_prune_ap_list(struct kvm_vcpu *vcpu) raw_spin_lock(&irq->irq_lock); /* - * If the affinity has been preserved, move the - * interrupt around. Otherwise, it means things have - * changed while the interrupt was unlocked, and we - * need to replay this. + * If the interrupt is still ours and its affinity has + * been preserved, move it around. Otherwise, it means + * things have changed while the interrupt was unlocked + * (it may even have been taken off the list with its + * affinity left untouched), and we need to replay this. * * In all cases, we cannot trust the list not to have * changed, so we restart from the beginning. */ - if (target_vcpu == vgic_target_oracle(irq)) { + if (irq->vcpu == vcpu && target_vcpu == vgic_target_oracle(irq)) { struct vgic_cpu *new_cpu = &target_vcpu->arch.vgic_cpu; list_del(&irq->ap_list); From ff1022c3de46753eb7eba2f6efd990569e66ff95 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 17 Jun 2026 12:08:21 +0800 Subject: [PATCH 008/479] KVM: arm64: nv: Fix SPSR_EL2 restore in kvm_hyp_handle_mops() kvm_hyp_handle_mops() resets the single-step state machine as part of rewinding state for a MOPS exception by modifying vcpu_cpsr() and writing the result directly into hardware. In the case of nested virtualization, vcpu_cpsr() is a synthetic value such that the rest of KVM can deal with vEL2 cleanly. That means the value requires translation before being written into hardware, which is unfortunately missing from the MOPS handler. Fix it by directly modifying SPSR_EL2 and avoiding the synthetic state altogether, which will be resynchronized on the next 'full' exit back to KVM. Fixes: 2de451a329cf ("KVM: arm64: Add handler for MOPS exceptions") Reported-by: Zhong Wang Reported-by: Xuanqing Shi Link: https://lore.kernel.org/all/ajE4lHQevXNHpl1M@Air.local/ Cc: stable@vger.kernel.org Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20260617040820.2194831-2-bestswngs@gmail.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/include/hyp/switch.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/arch/arm64/kvm/hyp/include/hyp/switch.h b/arch/arm64/kvm/hyp/include/hyp/switch.h index 161bb2a3e1d9..d56371b189bf 100644 --- a/arch/arm64/kvm/hyp/include/hyp/switch.h +++ b/arch/arm64/kvm/hyp/include/hyp/switch.h @@ -446,16 +446,19 @@ static inline bool __populate_fault_info(struct kvm_vcpu *vcpu) static inline bool kvm_hyp_handle_mops(struct kvm_vcpu *vcpu, u64 *exit_code) { + u64 spsr; + *vcpu_pc(vcpu) = read_sysreg_el2(SYS_ELR); arm64_mops_reset_regs(vcpu_gp_regs(vcpu), vcpu->arch.fault.esr_el2); write_sysreg_el2(*vcpu_pc(vcpu), SYS_ELR); /* * Finish potential single step before executing the prologue - * instruction. + * instruction. Modify the hardware SPSR_EL2 directly, as vcpu_cpsr() + * may hold a synthetic (vEL2) value for a guest hypervisor. */ - *vcpu_cpsr(vcpu) &= ~DBG_SPSR_SS; - write_sysreg_el2(*vcpu_cpsr(vcpu), SYS_SPSR); + spsr = read_sysreg_el2(SYS_SPSR); + write_sysreg_el2(spsr & ~DBG_SPSR_SS, SYS_SPSR); return true; } From 9f1667098c6ae7ec81a9a56859cfdacb822aa0d0 Mon Sep 17 00:00:00 2001 From: Oliver Upton Date: Sun, 14 Jun 2026 22:13:24 -0700 Subject: [PATCH 009/479] KVM: arm64: nv: Drop bogus WARN for write to ZCR_EL2 It is entirely possible for a guest to write to the ZCR_EL2 sysreg alias while in a nested context, as it is expected if FEAT_NV2 is advertised to the L1 hypervisor. Get rid of the bogus WARN which, since the hyp vectors were installed at this point, has the effect of a hyp_panic... Cc: stable@vger.kernel.org Fixes: 0cfc85b8f5cf ("KVM: arm64: nv: Load guest FP state for ZCR_EL2 trap") Signed-off-by: Oliver Upton Link: https://patch.msgid.link/20260615051324.830045-1-oupton@kernel.org Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/include/hyp/switch.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm64/kvm/hyp/include/hyp/switch.h b/arch/arm64/kvm/hyp/include/hyp/switch.h index d56371b189bf..ea22d1b50512 100644 --- a/arch/arm64/kvm/hyp/include/hyp/switch.h +++ b/arch/arm64/kvm/hyp/include/hyp/switch.h @@ -598,8 +598,6 @@ static inline bool kvm_hyp_handle_fpsimd(struct kvm_vcpu *vcpu, u64 *exit_code) return false; break; case ESR_ELx_EC_SYS64: - if (WARN_ON_ONCE(!is_hyp_ctxt(vcpu))) - return false; fallthrough; case ESR_ELx_EC_SVE: if (!sve_guest) From e2cb1f4578625e71f461d5c1ce70984193389cbb Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Mon, 15 Jun 2026 14:11:16 +0100 Subject: [PATCH 010/479] KVM: arm64: nv: Write ESR_EL2 for injected nested SError exceptions kvm_inject_el2_exception() writes ESR_EL2 for synchronous exceptions but not for SError. enter_exception64() does not write ESR_ELx for any exception type, so the constructed syndrome is dropped. A guest L2 hypervisor taking a nested SError observes stale ESR_EL2. This affects both kvm_inject_nested_serror() and the EASE path in kvm_inject_nested_sea(). Write ESR_EL2 for except_type_serror, matching except_type_sync. Fixes: 77ee70a07357 ("KVM: arm64: nv: Honor SError exception routing / masking") Reported-by: sashiko Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260615131116.390977-1-tabba@google.com Signed-off-by: Marc Zyngier Cc: stable@vger.kernel.org --- arch/arm64/kvm/emulate-nested.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c index e688bc5139c1..76c3e6c1144c 100644 --- a/arch/arm64/kvm/emulate-nested.c +++ b/arch/arm64/kvm/emulate-nested.c @@ -2826,6 +2826,7 @@ static void kvm_inject_el2_exception(struct kvm_vcpu *vcpu, u64 esr_el2, break; case except_type_serror: kvm_pend_exception(vcpu, EXCEPT_AA64_EL2_SERR); + vcpu_write_sys_reg(vcpu, esr_el2, ESR_EL2); break; default: WARN_ONCE(1, "Unsupported EL2 exception injection %d\n", type); From ec40342aaca8162bc8ab2607076535ebab1838b8 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 12 Jun 2026 12:34:14 +0100 Subject: [PATCH 011/479] KVM: arm64: Sync SPSR_EL1 when injecting an exception into a pVM When pKVM injects a synchronous exception into a protected guest, it re-enters without restoring the guest's EL1 sysregs and writes the EL1 exception registers to hardware by hand: ESR_EL1 and ELR_EL1, but not SPSR_EL1. enter_exception64() sets SPSR_EL1 (the interrupted PSTATE) only in memory, so the guest's handler reads a stale SPSR_EL1 and restores the wrong PSTATE on eret. Write SPSR_EL1 alongside the other exception registers. Fixes: 6c30bfb18d0b ("KVM: arm64: Add handlers for protected VM System Registers") Reported-by: sashiko Signed-off-by: Fuad Tabba Acked-by: Will Deacon Link: https://patch.msgid.link/20260612113414.1022901-1-tabba@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/nvhe/sys_regs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c index 8c3fbb413a06..1a7d5cd16d72 100644 --- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c +++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c @@ -268,6 +268,7 @@ static void inject_sync64(struct kvm_vcpu *vcpu, u64 esr) write_sysreg_el1(esr, SYS_ESR); write_sysreg_el1(read_sysreg_el2(SYS_ELR), SYS_ELR); + write_sysreg_el1(read_sysreg_el2(SYS_SPSR), SYS_SPSR); write_sysreg_el2(*vcpu_pc(vcpu), SYS_ELR); write_sysreg_el2(*vcpu_cpsr(vcpu), SYS_SPSR); } From 0e8e955b9bcf84a70f20079390e19971fec1586d Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Wed, 17 Jun 2026 15:49:07 +0100 Subject: [PATCH 012/479] KVM: arm64: nv: Fix PSTATE construction on illegal exception return kvm_check_illegal_exception_return() sourced the flags {N,Z,C,V} and masks {D,A,I,F} of the resulting PSTATE from the current PSTATE, but R_VWJHB takes them from the SPSR being returned to and leaves PSTATE.{EL,SP,nRW} (and EXLOCK when FEAT_GCS) unchanged. PAN, ALLINT and PM were not applied at all. Build the PSTATE by taking those fields from the SPSR while preserving EL, SP, nRW and EXLOCK from the current PSTATE, then set IL. Fixes: 47f3a2fc765a ("KVM: arm64: nv: Support virtual EL2 exceptions") Suggested-by: Marc Zyngier Link: https://lore.kernel.org/all/86wlvxs5r0.wl-maz@kernel.org/ Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260617144907.2972095-1-tabba@google.com [maz: tidied things a bit] Signed-off-by: Marc Zyngier --- arch/arm64/kvm/emulate-nested.c | 36 ++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c index 76c3e6c1144c..96ebe7e3b408 100644 --- a/arch/arm64/kvm/emulate-nested.c +++ b/arch/arm64/kvm/emulate-nested.c @@ -2746,17 +2746,33 @@ static u64 kvm_check_illegal_exception_return(struct kvm_vcpu *vcpu, u64 spsr) (spsr & PSR_MODE32_BIT) || (vcpu_el2_tge_is_set(vcpu) && (mode == PSR_MODE_EL1t || mode == PSR_MODE_EL1h))) { - /* - * The guest is playing with our nerves. Preserve EL, SP, - * masks, flags from the existing PSTATE, and set IL. - * The HW will then generate an Illegal State Exception - * immediately after ERET. - */ - spsr = *vcpu_cpsr(vcpu); + u64 mask; - spsr &= (PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT | - PSR_N_BIT | PSR_Z_BIT | PSR_C_BIT | PSR_V_BIT | - PSR_MODE_MASK | PSR_MODE32_BIT); + /* + * On an illegal exception return, the flags and masks are + * taken from the SPSR while PSTATE.{EL,SP,nRW} and, if + * FEAT_GCS, PSTATE.EXLOCK are unchanged (R_VWJHB). Set IL + * so the HW generates an Illegal State Exception right + * after ERET. + */ + mask = PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT | + PSR_N_BIT | PSR_Z_BIT | PSR_C_BIT | PSR_V_BIT; + + if (kvm_has_feat(vcpu->kvm, ID_AA64MMFR1_EL1, PAN, IMP)) + mask |= PSR_PAN_BIT; + if (kvm_has_feat(vcpu->kvm, ID_AA64PFR1_EL1, NMI, IMP)) + mask |= ALLINT_ALLINT; + /* FEAT_SPE_EXC and FEAT_TRBE_EXC also gate PSTATE.PM one day... */ + if (kvm_has_feat(vcpu->kvm, ID_AA64DFR1_EL1, EBEP, IMP)) + mask |= BIT_ULL(32); /* SPSR_ELx.PM */ + + spsr &= mask; + + mask = PSR_MODE_MASK | PSR_MODE32_BIT; + if (kvm_has_feat(vcpu->kvm, ID_AA64PFR1_EL1, GCS, IMP)) + mask |= BIT_ULL(34); /* PSTATE.EXLOCK */ + + spsr |= *vcpu_cpsr(vcpu) & mask; spsr |= PSR_IL_BIT; } From fcda30b134d262a1b1826555352bd7349051486e Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Wed, 17 Jun 2026 14:12:36 +0100 Subject: [PATCH 013/479] KVM: arm64: Add Fuad Tabba as a reviewer I have been working on KVM/arm64 for a couple of years, mostly on pKVM, and am currently upstreaming protected guest support, with more to come later. I already review KVM/arm64 patches more broadly, and am happy to continue doing so in an official capacity. Add myself to the KVM/arm64 reviewer list. Signed-off-by: Fuad Tabba Acked-by: Oliver Upton Link: https://patch.msgid.link/20260617131237.2842619-1-tabba@google.com Signed-off-by: Marc Zyngier --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index c2c6d79275c6..30cba9f7491e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14060,6 +14060,7 @@ F: virt/kvm/* KERNEL VIRTUAL MACHINE FOR ARM64 (KVM/arm64) M: Marc Zyngier M: Oliver Upton +R: Fuad Tabba R: Joey Gouly R: Steffen Eiden R: Suzuki K Poulose From e60d3f9b4c89ffff5472ddc2829d5e29bcb153fe Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 20 Jun 2026 21:23:14 +0200 Subject: [PATCH 014/479] drm/tegra: Fix a strange error handling path The resource freed at the 'put_aux' label is "sor->aux->dev". However, this resource is taken after devm_tegra_pmc_get(), so there is no point to release it in this error handling path. This is harmless because put_device() will be called with a NULL pointer, but this is confusing. So, fix the logic and return directly. Signed-off-by: Christophe JAILLET Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index a76095838133..6479f852e16a 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -3733,10 +3733,8 @@ static int tegra_sor_probe(struct platform_device *pdev) sor->num_settings = sor->soc->num_settings; sor->pmc = devm_tegra_pmc_get(&pdev->dev); - if (IS_ERR(sor->pmc)) { - err = PTR_ERR(sor->pmc); - goto put_aux; - } + if (IS_ERR(sor->pmc)) + return PTR_ERR(sor->pmc); np = of_parse_phandle(pdev->dev.of_node, "nvidia,dpaux", 0); if (np) { From 813e034925814858cc52e7de321ec4848314e15d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 18 Jun 2026 16:36:50 +0200 Subject: [PATCH 015/479] soc/tegra: pmc: fix #ifdef block in header When build testing on ARM without the PMC driver, the other drivers fail to link: ld.lld: error: undefined symbol: tegra_pmc_core_domain_state_synced >>> referenced by regulators-tegra30.c >>> drivers/soc/tegra/regulators-tegra30.o:(tegra30_regulator_balance_voltage) in archive vmlinux.a >>> referenced by regulators-tegra20.c >>> drivers/soc/tegra/regulators-tegra20.o:(tegra20_core_rtc_update) in archive vmlinux.a Adapt the checks in the header to cover both cases on other architectures and without PMC. Fixes: 8318af5dd29c ("soc/tegra: pmc: Move legacy code behind CONFIG_ARM guard") Signed-off-by: Arnd Bergmann Signed-off-by: Thierry Reding --- include/soc/tegra/pmc.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/soc/tegra/pmc.h b/include/soc/tegra/pmc.h index 4bcbf19d75ac..53f6d02889db 100644 --- a/include/soc/tegra/pmc.h +++ b/include/soc/tegra/pmc.h @@ -210,7 +210,6 @@ tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id) bool tegra_pmc_cpu_is_powered(unsigned int cpuid); int tegra_pmc_cpu_power_on(unsigned int cpuid); int tegra_pmc_cpu_remove_clamping(unsigned int cpuid); -bool tegra_pmc_core_domain_state_synced(void); #if defined(CONFIG_SOC_TEGRA_PMC) && defined(CONFIG_PM_SLEEP) enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void); @@ -230,6 +229,10 @@ static inline void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) { } #endif +#endif + +#if defined(CONFIG_ARM) && defined(CONFIG_SOC_TEGRA_PMC) +bool tegra_pmc_core_domain_state_synced(void); #else /* needed for COMPILE_TEST */ static inline bool tegra_pmc_core_domain_state_synced(void) From 265d5d4032c5f6eb089a6e6241d37fdbde7da180 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 4 Jun 2026 03:36:17 -0700 Subject: [PATCH 016/479] soc/tegra: fuse: Fix spurious straps warning on SMCCC platforms My Grace host started to show this warning: WARNING: drivers/soc/tegra/fuse/tegra-apbmisc.c:120 at tegra_read_straps tegra30_fuse_add_randomness tegra30_fuse_init tegra_fuse_probe tegra_read_straps() warns when the static "chipid" cache is still zero, using it as a proxy for "APBMISC has been initialised". However chipid is only ever populated lazily by tegra_read_chipid() when it reads the APBMISC register. Guard on apbmisc_base instead, which is set unconditionally in tegra_init_apbmisc_resources() for all platforms and is already the sentinel used by tegra_read_chipid(). Fixes: 8b8ee2e56f95 ("soc/tegra: Use ARM SMCCC to get chip ID, revision, and platform info") Signed-off-by: Breno Leitao Signed-off-by: Thierry Reding --- drivers/soc/tegra/fuse/tegra-apbmisc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/tegra/fuse/tegra-apbmisc.c b/drivers/soc/tegra/fuse/tegra-apbmisc.c index 87ae63a7e52d..7aba7c58bad0 100644 --- a/drivers/soc/tegra/fuse/tegra-apbmisc.c +++ b/drivers/soc/tegra/fuse/tegra-apbmisc.c @@ -117,7 +117,7 @@ bool tegra_is_silicon(void) u32 tegra_read_straps(void) { - WARN(!chipid, "Tegra ABP MISC not yet available\n"); + WARN(!apbmisc_base, "Tegra ABP MISC not yet available\n"); return strapping; } From 2684e02bac41c5220f6c1ab2bdcc957b71812977 Mon Sep 17 00:00:00 2001 From: Oliver Upton Date: Thu, 18 Jun 2026 16:42:02 -0700 Subject: [PATCH 017/479] KVM: arm64: nv: Respect read-only PFN when mapping L1 VNCR KVM currently maps the L1 VNCR into the host stage-1 by relying entirely on the permissions of the guest stage-1. At the same time, it is entirely possible that the backing PFN is read-only (e.g. RO memslot), meaning that the L1 VNCR should use at most a read-only mapping. Cache the writability of the PFN in the VNCR TLB and use it to constrain the resulting fixmap permissions. Promote VNCR permission faults to an SEA in the case where the guest attempts to write to a read-only endpoint. Conveniently, this also plugs a page leak found by Sashiko [*] resulting from the early return for a read-only PFN. Cc: stable@vger.kernel.org Fixes: 2a359e072596 ("KVM: arm64: nv: Handle mapping of VNCR_EL2 at EL2") Link: https://lore.kernel.org/kvm/20260608082603.16AEC1F00893@smtp.kernel.org/ Signed-off-by: Oliver Upton Link: https://patch.msgid.link/20260618234207.1063941-2-oupton@kernel.org Signed-off-by: Marc Zyngier --- arch/arm64/kvm/nested.c | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index 3a5571c3c114..903ccabca78c 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -24,6 +24,7 @@ struct vncr_tlb { struct s1_walk_result wr; u64 hpa; + bool hpa_writable; /* -1 when not mapped on a CPU */ int cpu; @@ -1401,7 +1402,7 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem) if (!*is_gmem) { pfn = __kvm_faultin_pfn(memslot, gfn, write_fault ? FOLL_WRITE : 0, &writable, &page); - if (is_error_noslot_pfn(pfn) || (write_fault && !writable)) + if (is_error_noslot_pfn(pfn)) return -EFAULT; } else { ret = kvm_gmem_get_pfn(vcpu->kvm, memslot, gfn, &pfn, &page, NULL); @@ -1410,6 +1411,8 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem) write_fault, false, false); return ret; } + + writable = !(memslot->flags & KVM_MEM_READONLY); } scoped_guard(write_lock, &vcpu->kvm->mmu_lock) { @@ -1420,28 +1423,41 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem) vt->gva = va; vt->hpa = pfn << PAGE_SHIFT; + vt->hpa_writable = writable; vt->valid = true; vt->cpu = -1; kvm_make_request(KVM_REQ_MAP_L1_VNCR_EL2, vcpu); - kvm_release_faultin_page(vcpu->kvm, page, false, vt->wr.pw); + kvm_release_faultin_page(vcpu->kvm, page, false, vt->wr.pw && vt->hpa_writable); } - if (vt->wr.pw) + if (vt->wr.pw && vt->hpa_writable) mark_page_dirty(vcpu->kvm, gfn); return 0; } -static void inject_vncr_perm(struct kvm_vcpu *vcpu) +static void handle_vncr_perm(struct kvm_vcpu *vcpu) { struct vncr_tlb *vt = vcpu->arch.vncr_tlb; u64 esr = kvm_vcpu_get_esr(vcpu); + u64 fsc; + + /* + * Promote to an external abort if the stage-1 permits writes but the + * HPA is read-only (e.g. RO memslot). + */ + if (kvm_is_write_fault(vcpu) && vt->wr.pw && !vt->hpa_writable) + fsc = ESR_ELx_FSC_EXTABT; + /* + * Otherwise, inject a permission fault using the guest's translation + * level rather than the host's. + */ + else + fsc = ESR_ELx_FSC_PERM_L(vt->wr.level); - /* Adjust the fault level to reflect that of the guest's */ esr &= ~ESR_ELx_FSC; - esr |= FIELD_PREP(ESR_ELx_FSC, - ESR_ELx_FSC_PERM_L(vt->wr.level)); + esr |= FIELD_PREP(ESR_ELx_FSC, fsc); kvm_inject_nested_sync(vcpu, esr); } @@ -1475,7 +1491,7 @@ int kvm_handle_vncr_abort(struct kvm_vcpu *vcpu) return kvm_handle_guest_sea(vcpu); if (esr_fsc_is_permission_fault(esr)) { - inject_vncr_perm(vcpu); + handle_vncr_perm(vcpu); } else if (esr_fsc_is_translation_fault(esr)) { bool valid, is_gmem = false; int ret; @@ -1523,7 +1539,7 @@ int kvm_handle_vncr_abort(struct kvm_vcpu *vcpu) break; case -EPERM: /* Hack to deal with POE until we get kernel support */ - inject_vncr_perm(vcpu); + handle_vncr_perm(vcpu); break; case 0: break; @@ -1567,7 +1583,7 @@ static void kvm_map_l1_vncr(struct kvm_vcpu *vcpu) vt->cpu = smp_processor_id(); - if (vt->wr.pw && vt->wr.pr) + if (vt->hpa_writable && vt->wr.pw && vt->wr.pr) prot = PAGE_KERNEL; else if (vt->wr.pr) prot = PAGE_KERNEL_RO; From 9f3e83345a56280efffe235c65593c7e544c0fcc Mon Sep 17 00:00:00 2001 From: Oliver Upton Date: Thu, 18 Jun 2026 16:42:03 -0700 Subject: [PATCH 018/479] KVM: arm64: nv: Inject SEA if kvm_translate_vncr() can't resolve PFN kvm_handle_vncr_abort() assumes that s1_walk_result conveys an abort when kvm_translate_vncr() returns -EFAULT. This is not always the case as it's possible to encounter 'late' failures on the output of S1 translation, e.g. a GFN outside of the memslots. Fix it by preparing an external abort before returning from kvm_translate_vncr(). Get rid of the BUG_ON() in the fault injection path while at it. Cc: stable@vger.kernel.org Fixes: 2a359e072596 ("KVM: arm64: nv: Handle mapping of VNCR_EL2 at EL2") Signed-off-by: Oliver Upton Link: https://patch.msgid.link/20260618234207.1063941-3-oupton@kernel.org Signed-off-by: Marc Zyngier --- arch/arm64/include/asm/kvm_nested.h | 8 ++++++++ arch/arm64/kvm/at.c | 8 -------- arch/arm64/kvm/nested.c | 10 ++++++---- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/arch/arm64/include/asm/kvm_nested.h b/arch/arm64/include/asm/kvm_nested.h index dc2957662ff2..cbdaaa2a2903 100644 --- a/arch/arm64/include/asm/kvm_nested.h +++ b/arch/arm64/include/asm/kvm_nested.h @@ -388,6 +388,14 @@ struct s1_walk_result { bool failed; }; +static inline void fail_s1_walk(struct s1_walk_result *wr, u8 fst, bool s1ptw) +{ + wr->fst = fst; + wr->ptw = s1ptw; + wr->s2 = s1ptw; + wr->failed = true; +} + int __kvm_translate_va(struct kvm_vcpu *vcpu, struct s1_walk_info *wi, struct s1_walk_result *wr, u64 va); int __kvm_find_s1_desc_level(struct kvm_vcpu *vcpu, u64 va, u64 ipa, diff --git a/arch/arm64/kvm/at.c b/arch/arm64/kvm/at.c index 30e6fa8ac07c..8263c648207b 100644 --- a/arch/arm64/kvm/at.c +++ b/arch/arm64/kvm/at.c @@ -11,14 +11,6 @@ #include #include -static void fail_s1_walk(struct s1_walk_result *wr, u8 fst, bool s1ptw) -{ - wr->fst = fst; - wr->ptw = s1ptw; - wr->s2 = s1ptw; - wr->failed = true; -} - #define S1_MMU_DISABLED (-127) static int get_ia_size(struct s1_walk_info *wi) diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index 903ccabca78c..53dea9c3f14f 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -1395,15 +1395,19 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem) gfn = vt->wr.pa >> PAGE_SHIFT; memslot = gfn_to_memslot(vcpu->kvm, gfn); - if (!memslot) + if (!memslot) { + fail_s1_walk(&vt->wr, ESR_ELx_FSC_EXTABT, false); return -EFAULT; + } *is_gmem = kvm_slot_has_gmem(memslot); if (!*is_gmem) { pfn = __kvm_faultin_pfn(memslot, gfn, write_fault ? FOLL_WRITE : 0, &writable, &page); - if (is_error_noslot_pfn(pfn)) + if (is_error_noslot_pfn(pfn)) { + fail_s1_walk(&vt->wr, ESR_ELx_FSC_EXTABT, false); return -EFAULT; + } } else { ret = kvm_gmem_get_pfn(vcpu->kvm, memslot, gfn, &pfn, &page, NULL); if (ret) { @@ -1530,8 +1534,6 @@ int kvm_handle_vncr_abort(struct kvm_vcpu *vcpu) * Translation failed, inject the corresponding * exception back to EL2. */ - BUG_ON(!vt->wr.failed); - esr &= ~ESR_ELx_FSC; esr |= FIELD_PREP(ESR_ELx_FSC, vt->wr.fst); From bb645aa0a4caeaf7f9cd32e9a948594d434c1a8f Mon Sep 17 00:00:00 2001 From: Oliver Upton Date: Thu, 18 Jun 2026 16:42:04 -0700 Subject: [PATCH 019/479] KVM: arm64: nv: Re-translate VNCR before injecting abort KVM faults in the VNCR page with FOLL_WRITE whenever the guest aborts for a write, similar to how a regular stage-2 mapping is handled. It is entirely possible that the guest reads from the VNCR before writing to it, in which case the PFN could only be read-only. Invalidate the VNCR TLB and re-fetch the translation upon taking a VNCR abort, allowing the host mapping to be faulted in for write the second time around. Interestingly enough, this also satisfies the ordering requirements of FEAT_ETS2/3 between descriptor updates and MMU faults. Cc: stable@vger.kernel.org Fixes: 2a359e072596 ("KVM: arm64: nv: Handle mapping of VNCR_EL2 at EL2") Reported-by: Sashiko Signed-off-by: Oliver Upton Link: https://patch.msgid.link/20260618234207.1063941-4-oupton@kernel.org Signed-off-by: Marc Zyngier --- arch/arm64/kvm/nested.c | 115 +++++++++++++++------------------------- 1 file changed, 44 insertions(+), 71 deletions(-) diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index 53dea9c3f14f..7fffd86eee94 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -1466,88 +1466,61 @@ static void handle_vncr_perm(struct kvm_vcpu *vcpu) kvm_inject_nested_sync(vcpu, esr); } -static bool kvm_vncr_tlb_lookup(struct kvm_vcpu *vcpu) -{ - struct vncr_tlb *vt = vcpu->arch.vncr_tlb; - - lockdep_assert_held_read(&vcpu->kvm->mmu_lock); - - if (!vt->valid) - return false; - - if (read_vncr_el2(vcpu) != vt->gva) - return false; - - if (vt->wr.nG) - return get_asid_by_regime(vcpu, TR_EL20) == vt->wr.asid; - - return true; -} - int kvm_handle_vncr_abort(struct kvm_vcpu *vcpu) { struct vncr_tlb *vt = vcpu->arch.vncr_tlb; u64 esr = kvm_vcpu_get_esr(vcpu); + bool is_gmem = false; + bool perm; + int ret; WARN_ON_ONCE(!(esr & ESR_ELx_VNCR)); if (kvm_vcpu_abt_issea(vcpu)) return kvm_handle_guest_sea(vcpu); - if (esr_fsc_is_permission_fault(esr)) { - handle_vncr_perm(vcpu); - } else if (esr_fsc_is_translation_fault(esr)) { - bool valid, is_gmem = false; - int ret; - - scoped_guard(read_lock, &vcpu->kvm->mmu_lock) - valid = kvm_vncr_tlb_lookup(vcpu); - - if (!valid) - ret = kvm_translate_vncr(vcpu, &is_gmem); - else - ret = -EPERM; - - switch (ret) { - case -EAGAIN: - /* Let's try again... */ - break; - case -ENOMEM: - /* - * For guest_memfd, this indicates that it failed to - * create a folio to back the memory. Inform userspace. - */ - if (is_gmem) - return 0; - /* Otherwise, let's try again... */ - break; - case -EFAULT: - case -EIO: - case -EHWPOISON: - if (is_gmem) - return 0; - fallthrough; - case -EINVAL: - case -ENOENT: - case -EACCES: - /* - * Translation failed, inject the corresponding - * exception back to EL2. - */ - esr &= ~ESR_ELx_FSC; - esr |= FIELD_PREP(ESR_ELx_FSC, vt->wr.fst); - - kvm_inject_nested_sync(vcpu, esr); - break; - case -EPERM: - /* Hack to deal with POE until we get kernel support */ - handle_vncr_perm(vcpu); - break; - case 0: - break; - } - } else { + if (!esr_fsc_is_translation_fault(esr) && !esr_fsc_is_permission_fault(esr)) { WARN_ONCE(1, "Unhandled VNCR abort, ESR=%llx\n", esr); + return 1; + } + + ret = kvm_translate_vncr(vcpu, &is_gmem); + switch (ret) { + case -EAGAIN: + /* Let's try again... */ + return 1; + case -ENOMEM: + /* + * For guest_memfd, this indicates that it failed to + * create a folio to back the memory. Inform userspace. + */ + if (is_gmem) + return 0; + /* Otherwise, let's try again... */ + break; + case -EFAULT: + case -EIO: + case -EHWPOISON: + if (is_gmem) + return 0; + fallthrough; + case -EINVAL: + case -ENOENT: + case -EACCES: + /* + * Translation failed, inject the corresponding + * exception back to EL2. + */ + esr &= ~ESR_ELx_FSC; + esr |= FIELD_PREP(ESR_ELx_FSC, vt->wr.fst); + + kvm_inject_nested_sync(vcpu, esr); + break; + case 0: + perm = kvm_is_write_fault(vcpu) ? vt->wr.pw && vt->hpa_writable : vt->wr.pr; + if (!perm) + handle_vncr_perm(vcpu); + break; } return 1; From 4bd7dbe0b2243e6aa735cae4d5e1ff988b30b2a6 Mon Sep 17 00:00:00 2001 From: Oliver Upton Date: Thu, 18 Jun 2026 16:42:05 -0700 Subject: [PATCH 020/479] KVM: arm64: nv: Inject SEA if guest VNCR isn't normal memory When constructing an L1 VNCR mapping, KVM unconditionally uses cacheable memory attributes, even if the underlying PFN isn't memory. This gets particularly hairy if the endpoint doesn't support cacheable memory attributes, potentially throwing an SError on writeback... While KVM does permit cacheable memory attributes on certain PFNMAP VMAs, kvm_translate_vncr() isn't currently grabbing the VMA. So do the simpler thing for now and just reject everything that isn't memory. Cc: stable@vger.kernel.org Fixes: 2a359e072596 ("KVM: arm64: nv: Handle mapping of VNCR_EL2 at EL2") Signed-off-by: Oliver Upton Link: https://patch.msgid.link/20260618234207.1063941-5-oupton@kernel.org Signed-off-by: Marc Zyngier --- arch/arm64/kvm/nested.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index 7fffd86eee94..d4c9a9b05e3f 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -1419,6 +1419,17 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem) writable = !(memslot->flags & KVM_MEM_READONLY); } + /* + * FIXME: This check is too restrictive as KVM allows cacheable memory + * attributes for PFNMAP VMAs that have cacheable attributes in host + * stage-1. + */ + if (!pfn_is_map_memory(pfn)) { + kvm_release_faultin_page(vcpu->kvm, page, true, false); + fail_s1_walk(&vt->wr, ESR_ELx_FSC_EXTABT, false); + return -EINVAL; + } + scoped_guard(write_lock, &vcpu->kvm->mmu_lock) { if (mmu_invalidate_retry(vcpu->kvm, mmu_seq)) { kvm_release_faultin_page(vcpu->kvm, page, true, false); From 265b58aba51b6aaaad81678fbc57fcdb2d4ed480 Mon Sep 17 00:00:00 2001 From: Oliver Upton Date: Thu, 18 Jun 2026 16:42:06 -0700 Subject: [PATCH 021/479] KVM: arm64: nv: Mark VM as bugged for unexpected VNCR abort KVM is unlikely to resolve an unexpected VNCR abort, meaning that returning to the guest will likely leave the vCPU stuck in an abort loop. Bug the VM and exit to userspace instead. Signed-off-by: Oliver Upton Link: https://patch.msgid.link/20260618234207.1063941-6-oupton@kernel.org Signed-off-by: Marc Zyngier --- arch/arm64/kvm/nested.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index d4c9a9b05e3f..94df26de6990 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -1491,8 +1491,8 @@ int kvm_handle_vncr_abort(struct kvm_vcpu *vcpu) return kvm_handle_guest_sea(vcpu); if (!esr_fsc_is_translation_fault(esr) && !esr_fsc_is_permission_fault(esr)) { - WARN_ONCE(1, "Unhandled VNCR abort, ESR=%llx\n", esr); - return 1; + KVM_BUG(1, vcpu->kvm, "Unhandled VNCR abort, ESR=%llx\n", esr); + return -EIO; } ret = kvm_translate_vncr(vcpu, &is_gmem); From daa71eca24fdfb43029830bd57ddaddf70c59b23 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Thu, 18 Jun 2026 13:16:37 +0100 Subject: [PATCH 022/479] KVM: arm64: Set ESR_ELx.IL for injected undefined exceptions at EL2 inject_undef64() constructs an ESR with EC=0 (Unknown) but does not set IL. The architecture mandates IL=1 for EC=0 unconditionally (ARM DDI 0487, ESR_ELx.IL description), so the injected syndrome is one that conforming hardware cannot produce. Set ESR_ELx_IL in the constructed syndrome. Fixes: e5d40a5a97c1 ("KVM: arm64: pkvm: Add a generic synchronous exception injection primitive") Reported-by: sashiko Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260618121643.4105064-2-tabba@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/nvhe/sys_regs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c index 1a7d5cd16d72..b1411fb54139 100644 --- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c +++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c @@ -279,7 +279,7 @@ static void inject_sync64(struct kvm_vcpu *vcpu, u64 esr) */ static void inject_undef64(struct kvm_vcpu *vcpu) { - inject_sync64(vcpu, (ESR_ELx_EC_UNKNOWN << ESR_ELx_EC_SHIFT)); + inject_sync64(vcpu, (ESR_ELx_EC_UNKNOWN << ESR_ELx_EC_SHIFT) | ESR_ELx_IL); } static u64 read_id_reg(const struct kvm_vcpu *vcpu, From 1d695dc827957e9570d1b56abac1250d2d13bf0c Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Thu, 18 Jun 2026 13:16:38 +0100 Subject: [PATCH 023/479] KVM: arm64: Unconditionally set IL for injected undefined exceptions inject_undef64() derives IL from the triggering trap's instruction length (kvm_vcpu_trap_il_is32bit()), but the IL of the injected exception is fixed by its EC, not by the triggering instruction. The architecture mandates IL=1 for EC=0 (Unknown) unconditionally, so the conditional is wrong. The undef-injection paths are not reached from 16-bit instructions, so there is no functional change today, but the logic should not rely on that. Set ESR_ELx_IL unconditionally. Fixes: aa8eff9bfbd5 ("arm64: KVM: fault injection into a guest") Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260618121643.4105064-3-tabba@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/inject_fault.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/arch/arm64/kvm/inject_fault.c b/arch/arm64/kvm/inject_fault.c index 89982bd3345f..9dfae1bcdf99 100644 --- a/arch/arm64/kvm/inject_fault.c +++ b/arch/arm64/kvm/inject_fault.c @@ -170,14 +170,7 @@ void kvm_inject_sync(struct kvm_vcpu *vcpu, u64 esr) static void inject_undef64(struct kvm_vcpu *vcpu) { - u64 esr = (ESR_ELx_EC_UNKNOWN << ESR_ELx_EC_SHIFT); - - /* - * Build an unknown exception, depending on the instruction - * set. - */ - if (kvm_vcpu_trap_il_is32bit(vcpu)) - esr |= ESR_ELx_IL; + u64 esr = (ESR_ELx_EC_UNKNOWN << ESR_ELx_EC_SHIFT) | ESR_ELx_IL; kvm_inject_sync(vcpu, esr); } From add40af98b34764ff5603dce297160fde12d784c Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Thu, 18 Jun 2026 13:16:39 +0100 Subject: [PATCH 024/479] KVM: arm64: Unconditionally set IL for injected abort exceptions inject_abt64() derives IL from the triggering trap's instruction length (kvm_vcpu_trap_il_is32bit()), but the IL of the injected abort is fixed by its EC, not by the triggering instruction. The architecture mandates IL=1 for Instruction Aborts unconditionally and for Data Aborts with ISV=0, and this function never sets ISV (the FSC is always EXTABT or SEA_TTW). For a 16-bit T32 trap (a 32-bit EL0 task under an AArch64 EL1 guest) the trap has IL=0, so the abort is injected with the wrong IL. Set ESR_ELx_IL unconditionally. Fixes: aa8eff9bfbd5 ("arm64: KVM: fault injection into a guest") Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260618121643.4105064-4-tabba@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/inject_fault.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/arch/arm64/kvm/inject_fault.c b/arch/arm64/kvm/inject_fault.c index 9dfae1bcdf99..444d219b0217 100644 --- a/arch/arm64/kvm/inject_fault.c +++ b/arch/arm64/kvm/inject_fault.c @@ -138,11 +138,10 @@ static void inject_abt64(struct kvm_vcpu *vcpu, bool is_iabt, unsigned long addr pend_sync_exception(vcpu); /* - * Build an {i,d}abort, depending on the level and the - * instruction set. Report an external synchronous abort. + * Build an {i,d}abort, depending on the level. + * Report an external synchronous abort. */ - if (kvm_vcpu_trap_il_is32bit(vcpu)) - esr |= ESR_ELx_IL; + esr |= ESR_ELx_IL; /* * Here, the guest runs in AArch64 mode when in EL1. If we get From a52d6d68ad30374dd794bff300d8538e35ee49a8 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Thu, 18 Jun 2026 13:16:40 +0100 Subject: [PATCH 025/479] KVM: arm64: Set IL for injected FPAC exceptions during ERET emulation The FPAC syndrome constructed during nested ERET emulation does not set IL. For FPAC (EC=0x1C), IL reflects the instruction length. ERET and its authenticated variants are always A64 32-bit instructions, so IL must be 1. Fixes: 213b3d1ea161 ("KVM: arm64: nv: Handle ERETA[AB] instructions") Suggested-by: Marc Zyngier Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260618121643.4105064-5-tabba@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/emulate-nested.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c index 96ebe7e3b408..a15b2f41a12c 100644 --- a/arch/arm64/kvm/emulate-nested.c +++ b/arch/arm64/kvm/emulate-nested.c @@ -2800,7 +2800,7 @@ void kvm_emulate_nested_eret(struct kvm_vcpu *vcpu) * ERET handling, and the guest will have a little surprise. */ if (kvm_has_pauth(vcpu->kvm, FPACCOMBINE) && !(spsr & PSR_IL_BIT)) { - esr &= ESR_ELx_ERET_ISS_ERETA; + esr &= (ESR_ELx_ERET_ISS_ERETA | ESR_ELx_IL); esr |= FIELD_PREP(ESR_ELx_EC_MASK, ESR_ELx_EC_FPAC); kvm_inject_nested_sync(vcpu, esr); return; From 7514f1785d526207af8512cc6ccb1c35c5c61767 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Thu, 18 Jun 2026 13:16:41 +0100 Subject: [PATCH 026/479] KVM: arm64: Set IL for emulated SError injection kvm_inject_serror_esr() constructs an SError syndrome without IL. The architecture mandates IL=1 for SError unconditionally. Fixes: f6e2262dfa1a ("KVM: arm64: Populate ESR_ELx.EC for emulated SError injection") Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260618121643.4105064-6-tabba@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/inject_fault.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kvm/inject_fault.c b/arch/arm64/kvm/inject_fault.c index 444d219b0217..d6c4fc16f879 100644 --- a/arch/arm64/kvm/inject_fault.c +++ b/arch/arm64/kvm/inject_fault.c @@ -381,7 +381,7 @@ int kvm_inject_serror_esr(struct kvm_vcpu *vcpu, u64 esr) */ if (!serror_is_masked(vcpu)) { pend_serror_exception(vcpu); - esr |= FIELD_PREP(ESR_ELx_EC_MASK, ESR_ELx_EC_SERROR); + esr |= FIELD_PREP(ESR_ELx_EC_MASK, ESR_ELx_EC_SERROR) | ESR_ELx_IL; vcpu_write_sys_reg(vcpu, esr, exception_esr_elx(vcpu)); return 1; } From a69412287a33c931dca9e48d30c0dbf8cde0ffe6 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Thu, 18 Jun 2026 13:16:42 +0100 Subject: [PATCH 027/479] KVM: arm64: Set IL for nested SError injection kvm_inject_nested_serror() constructs an SError syndrome without IL. The architecture mandates IL=1 for SError unconditionally. Fixes: 77ee70a07357 ("KVM: arm64: nv: Honor SError exception routing / masking") Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260618121643.4105064-7-tabba@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/emulate-nested.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c index a15b2f41a12c..3c82f392845d 100644 --- a/arch/arm64/kvm/emulate-nested.c +++ b/arch/arm64/kvm/emulate-nested.c @@ -2967,6 +2967,6 @@ int kvm_inject_nested_serror(struct kvm_vcpu *vcpu, u64 esr) * vSError injection. Manually populate EC for an emulated SError * exception. */ - esr |= FIELD_PREP(ESR_ELx_EC_MASK, ESR_ELx_EC_SERROR); + esr |= FIELD_PREP(ESR_ELx_EC_MASK, ESR_ELx_EC_SERROR) | ESR_ELx_IL; return kvm_inject_nested(vcpu, esr, except_type_serror); } From cbe2278aa3dd6832c544782c6cfed1fbc1f71a42 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Thu, 18 Jun 2026 13:16:43 +0100 Subject: [PATCH 028/479] KVM: arm64: Set IL in fake ESR for pKVM memory sharing exit __pkvm_memshare_page_req() constructs a fake DABT ESR_EL2 to exit to the host without setting IL. The ESR has ISV=0, so IL must be 1 per the architecture. The host does not read IL on this path, but the constructed syndrome should still be architecturally valid. Set ESR_ELx_IL. Fixes: 03313efed5e2 ("KVM: arm64: Implement the MEM_SHARE hypercall for protected VMs") Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260618121643.4105064-8-tabba@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/nvhe/pkvm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index 3b2c4fbc34d8..24d6f164129a 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -1056,7 +1056,8 @@ static u64 __pkvm_memshare_page_req(struct kvm_vcpu *vcpu, u64 ipa) /* Fake up a data abort (level 3 translation fault on write) */ vcpu->arch.fault.esr_el2 = (ESR_ELx_EC_DABT_LOW << ESR_ELx_EC_SHIFT) | - ESR_ELx_WNR | ESR_ELx_FSC_FAULT | + ESR_ELx_IL | ESR_ELx_WNR | + ESR_ELx_FSC_FAULT | FIELD_PREP(ESR_ELx_FSC_LEVEL, 3); /* Shuffle the IPA around into the HPFAR */ From d098bb75d14fde2f12155f1a95ec0168160867ce Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Sun, 21 Jun 2026 21:31:55 +0000 Subject: [PATCH 029/479] KVM: arm64: account pKVM reclaim against the VM mm Protected guest faults charge long term pins to the VM's mm. Teardown can run later from file release, where current->mm may be unrelated. Drop the charge from kvm->mm instead. Fixes: 4e6e03f9eadd ("KVM: arm64: Hook up reclaim hypercall to pkvm_pgtable_stage2_destroy()") Signed-off-by: Bradley Morgan Reviewed-by: Fuad Tabba Tested-by: Fuad Tabba Link: https://patch.msgid.link/20260621213155.6019-1-include@grrlz.net Signed-off-by: Marc Zyngier Cc: stable@vger.kernel.org --- arch/arm64/kvm/pkvm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c index 053e4f733e4b..428723b1b0f5 100644 --- a/arch/arm64/kvm/pkvm.c +++ b/arch/arm64/kvm/pkvm.c @@ -352,7 +352,7 @@ static int __pkvm_pgtable_stage2_reclaim(struct kvm_pgtable *pgt, u64 start, u64 page = pfn_to_page(mapping->pfn); WARN_ON_ONCE(mapping->nr_pages != 1); unpin_user_pages_dirty_lock(&page, 1, true); - account_locked_vm(current->mm, 1, false); + account_locked_vm(kvm->mm, 1, false); pkvm_mapping_remove(mapping, &pgt->pkvm_mappings); kfree(mapping); } From 314c243b201b678fa89226b1eaea51a71340454e Mon Sep 17 00:00:00 2001 From: Jens Wiklander Date: Mon, 8 Jun 2026 10:21:50 +0200 Subject: [PATCH 030/479] MAINTAINERS: .mailmap: update Jens Wiklander's email address Update Jens Wiklander's email address to @kernel.org. Cc: Arnd Bergmann Cc: Sumit Garg Cc: Greg Kroah-Hartman Signed-off-by: Jens Wiklander --- .mailmap | 1 + MAINTAINERS | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.mailmap b/.mailmap index 0b9298a55d2d..4c30a9bea996 100644 --- a/.mailmap +++ b/.mailmap @@ -396,6 +396,7 @@ Jens Axboe Jens Axboe Jens Axboe Jens Osterkamp +Jens Wiklander Jernej Skrabec Jesper Dangaard Brouer Jesper Dangaard Brouer diff --git a/MAINTAINERS b/MAINTAINERS index c8d4b913f26c..0e42b30877bd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19959,7 +19959,7 @@ W: http://www.onsemi.com F: drivers/net/phy/ncn* OP-TEE DRIVER -M: Jens Wiklander +M: Jens Wiklander L: op-tee@lists.trustedfirmware.org (moderated for non-subscribers) S: Maintained F: Documentation/ABI/testing/sysfs-bus-optee-devices @@ -23305,7 +23305,7 @@ F: Documentation/devicetree/bindings/media/allwinner,sun8i-a83t-de2-rotate.yaml F: drivers/media/platform/sunxi/sun8i-rotate/ RPMB SUBSYSTEM -M: Jens Wiklander +M: Jens Wiklander L: linux-kernel@vger.kernel.org S: Supported F: drivers/misc/rpmb-core.c @@ -26223,7 +26223,7 @@ F: drivers/media/i2c/tw9910.c F: include/media/i2c/tw9910.h TEE SUBSYSTEM -M: Jens Wiklander +M: Jens Wiklander R: Sumit Garg L: op-tee@lists.trustedfirmware.org (moderated for non-subscribers) S: Maintained From 4cd5de72b6f8951cef6b45c177a582824bc13d46 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Wed, 24 Jun 2026 02:27:43 -0700 Subject: [PATCH 031/479] sched_ext: Avoid flooding the log with deprecation warnings The deprecation notices for direct p->scx.slice/dsq_vtime writes and for ops->cpu_acquire/release() use plain pr_warn(), so they repeat on every scheduler (re)load and can flood the kernel log. The slice/dsq_vtime notice is the worst offender: it is emitted from the BPF verifier's btf_struct_access callback, which is re-evaluated as the verifier explores program paths, so a single scheduler load can print it many times -- hundreds of lines on some hosts, dozens within the same second. Switch both notices to pr_warn_ratelimited() so each deprecation is still reported but bursts no longer spam the log, and add the missing newline to the slice/dsq_vtime message. Signed-off-by: Breno Leitao Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 691d53fe0f64..d62b93f48a85 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -6988,7 +6988,7 @@ static int validate_ops(struct scx_sched *sch, const struct sched_ext_ops *ops) * run past the BPF allocation. Skip for cid-form. */ if (!sch->is_cid_type && (ops->cpu_acquire || ops->cpu_release)) - pr_warn("ops->cpu_acquire/release() are deprecated, use sched_switch TP instead\n"); + pr_warn_ratelimited("ops->cpu_acquire/release() are deprecated, use sched_switch TP instead\n"); /* * Sub-scheduler support is tied to the cid-form struct_ops. A sub-sched @@ -7806,7 +7806,7 @@ static int bpf_scx_btf_struct_access(struct bpf_verifier_log *log, off + size <= offsetofend(struct task_struct, scx.slice)) || (off >= offsetof(struct task_struct, scx.dsq_vtime) && off + size <= offsetofend(struct task_struct, scx.dsq_vtime))) { - pr_warn("sched_ext: Writing directly to p->scx.slice/dsq_vtime is deprecated, use scx_bpf_task_set_slice/dsq_vtime()"); + pr_warn_ratelimited("sched_ext: Writing directly to p->scx.slice/dsq_vtime is deprecated, use scx_bpf_task_set_slice/dsq_vtime()\n"); return SCALAR_VALUE; } From 46d65096ce8d278abf4528e254878c14ddd0b459 Mon Sep 17 00:00:00 2001 From: Doehyun Baek Date: Sat, 20 Jun 2026 12:27:51 +0000 Subject: [PATCH 032/479] Docs/admin-guide/cgroup-v2: fix memory.stat doc details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix minor cgroup v2 memory.stat documentation issues. Correct the vmalloc per-node marker now that vmalloc uses the native NR_VMALLOC node stat, and document zswap_incomp as a byte-valued memory amount instead of as a page counter. Fixes: c466412c73c3 ("mm: memcontrol: switch to native NR_VMALLOC vmstat counter") Fixes: 5ad41a38c364 ("mm: zswap: add per-memcg stat for incompressible pages") Signed-off-by: Doehyun Baek Reviewed-by: Nhat Pham Acked-by: Michal Koutný Signed-off-by: Tejun Heo --- Documentation/admin-guide/cgroup-v2.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst index 993446ab66d0..ce6741f78f4f 100644 --- a/Documentation/admin-guide/cgroup-v2.rst +++ b/Documentation/admin-guide/cgroup-v2.rst @@ -1570,7 +1570,7 @@ The following nested keys are defined. sock (npn) Amount of memory used in network transmission buffers - vmalloc (npn) + vmalloc Amount of memory used for vmap backed memory. shmem @@ -1735,7 +1735,7 @@ The following nested keys are defined. Number of pages written from zswap to swap. zswap_incomp - Number of incompressible pages currently stored in zswap + Amount of memory used by incompressible pages currently stored in zswap without compression. These pages could not be compressed to a size smaller than PAGE_SIZE, so they are stored as-is. From 5771e79e461e72140b752496b69275b9e6bd1a75 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Mon, 8 Jun 2026 18:55:57 +0200 Subject: [PATCH 033/479] sched_ext: Reset dsq_vtime and slice when a task leaves SCX When a task leaves the BPF scheduler's control, p->scx.dsq_vtime and p->scx.slice keep whatever values they last held. The slice value is core-managed and is refilled on the next enqueue, but dsq_vtime is owned by the BPF scheduler and is never cleared by the core, so a task that leaves SCX and later returns carries a stale dsq_vtime across the round-trip. The stale values are also visible to other SCX schedulers that inspect the scx fields of non-SCX tasks. Fix this by resetting both dsq_vtime and slice in scx_disable_task(), after ops.disable(), so the BPF scheduler can still observe the task's final values and non-SCX tasks do not retain stale SCX state. Signed-off-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index d62b93f48a85..5efc97437c0a 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -3647,6 +3647,13 @@ static void scx_disable_task(struct scx_sched *sch, struct task_struct *p) SCX_CALL_OP_TASK(sch, disable, rq, p); scx_set_task_state(p, SCX_TASK_READY); + /* + * Reset the SCX-managed fields when @p leaves the BPF scheduler's + * control, after ops.disable() has observed their final values. + */ + p->scx.dsq_vtime = 0; + p->scx.slice = 0; + /* * Verify the task is not in BPF scheduler's custody. If flag * transitions are consistent, the flag should always be clear From 5c94a3ab6ed94ff1257631a49893a535098be0b6 Mon Sep 17 00:00:00 2001 From: Kuba Piecuch Date: Fri, 19 Jun 2026 13:23:59 +0000 Subject: [PATCH 034/479] sched_ext: Check remote rq eligibility under task's rq lock task_can_run_on_remote_rq() operates under the assumption that p->migration_disabled is stable, i.e. if the kernel observed is_migration_disabled(p) == true, then the BPF scheduler must have also been able to see this when dispatching the task, and it's the BPF scheduler's fault that it tried to dispatch a task with migration disabled to a CPU other than the task's current CPU. This assumption does not always hold. It's possible that the BPF scheduler saw is_migration_disabled(p) == false, while the kernel observes is_migration_disabled(p) == true in dispatch_to_local_dsq() -> task_can_run_on_remote_rq(). The crucial thing here is that with CONFIG_PREEMPT_RCU, migration is disabled while a task is executing a BPF program. So, if there's a situation where the BPF scheduler checks a task while it's not executing a BPF program, while the kernel checks it while it is executing one, the BPF scheduler will be killed through no fault of its own. Consider the following scenario: 1. SCX task @p is executing on CPU A and CPU A gets preempted by a higher-priority scheduling class. On entry to __schedule(), p->migration_disabled == 0. 2. In put_prev_task_scx() @p is enqueued on the BPF scheduler's internal data structures, making it available for other CPUs to dispatch. 3. CPU B enters ops.dispatch(), pops @p from the BPF scheduler's data structures, checks is_migration_disabled(p) which returns false, and dispatches @p to CPU B's local DSQ. 4. On CPU A, @p hasn't been switched out yet. Execution reaches trace_sched_switch() which enters a BPF program, as the BPF scheduler hooks into the sched_switch tracepoint to detect idle->fair transitions. On entry into the BPF program, @p disables migration. 5. CPU B enters finish_dispatch() -> dispatch_to_local_dsq() -> task_can_run_on_remote_rq() which observes is_migration_disabled(p) == true, triggering scx_error(). This all happens while holding CPU B's rq lock, so it's not synchronized with @p switching out. This patch fixes this by moving the call to task_can_run_on_remote_rq() after @p's rq lock is acquired in dispatch_to_local_dsq(). This way, we synchronize with @p switching out, since @p holds its rq lock all the way until it's switched out. Thus, any BPF programs that are called between put_prev_task_scx() and the end of the context switch are guaranteed to have finished and cannot influence p->migration_disabled. Also add a lockdep assertion in task_can_run_on_remote_rq() which ensures the task rq lock is held if enforce == true. Signed-off-by: Kuba Piecuch Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 24 ++++++++++++++++-------- kernel/sched/ext/internal.h | 23 +++++++++++++---------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 5efc97437c0a..5418e6357249 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2307,6 +2307,7 @@ static void move_remote_task_to_local_dsq(struct task_struct *p, u64 enq_flags, * no to the BPF scheduler initiated migrations while offline. * * The caller must ensure that @p and @rq are on different CPUs. + * If enforce == true, caller must hold @p's rq lock. */ static bool task_can_run_on_remote_rq(struct scx_sched *sch, struct task_struct *p, struct rq *rq, @@ -2314,6 +2315,14 @@ static bool task_can_run_on_remote_rq(struct scx_sched *sch, { s32 cpu = cpu_of(rq); + /* + * To prevent races with @p still running on its old CPU while switching + * out, make sure we're holding @p's rq lock so as not to risk + * erroneously killing the BPF scheduler. + */ + if (enforce) + lockdep_assert_rq_held(task_rq(p)); + WARN_ON_ONCE(task_cpu(p) == cpu); /* @@ -2581,13 +2590,6 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq, return; } - if (src_rq != dst_rq && - unlikely(!task_can_run_on_remote_rq(sch, p, dst_rq, true))) { - dispatch_enqueue(sch, rq, find_global_dsq(sch, task_cpu(p)), p, - enq_flags | SCX_ENQ_CLEAR_OPSS | SCX_ENQ_GDSQ_FALLBACK); - return; - } - /* * @p is on a possibly remote @src_rq which we need to lock to move the * task. If dequeue is in progress, it'd be locking @src_rq and waiting @@ -2614,6 +2616,7 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq, /* task_rq couldn't have changed if we're still the holding cpu */ if (likely(p->scx.holding_cpu == raw_smp_processor_id()) && !WARN_ON_ONCE(src_rq != task_rq(p))) { + bool fallback = false; /* * If @p is staying on the same rq, there's no need to go * through the full deactivate/activate cycle. Optimize by @@ -2623,6 +2626,11 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq, p->scx.holding_cpu = -1; dispatch_enqueue(sch, dst_rq, &dst_rq->scx.local_dsq, p, enq_flags); + } else if (unlikely(!task_can_run_on_remote_rq(sch, p, dst_rq, true))) { + p->scx.holding_cpu = -1; + fallback = true; + dispatch_enqueue(sch, src_rq, find_global_dsq(sch, task_cpu(p)), + p, enq_flags | SCX_ENQ_GDSQ_FALLBACK); } else { move_remote_task_to_local_dsq(p, enq_flags, src_rq, dst_rq); @@ -2631,7 +2639,7 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq, } /* if the destination CPU is idle, wake it up */ - if (sched_class_above(p->sched_class, dst_rq->curr->sched_class)) + if (!fallback && sched_class_above(p->sched_class, dst_rq->curr->sched_class)) resched_curr(dst_rq); } diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 145272cb4d8a..673059fa9d72 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -1469,21 +1469,24 @@ static const char *scx_enable_state_str[] = { * The sched_ext core uses a "lock dancing" protocol coordinated by * p->scx.holding_cpu. When moving a task to a different rq: * - * 1. Verify task can be moved (CPU affinity, migration_disabled, etc.) - * 2. Set p->scx.holding_cpu to the current CPU - * 3. Set task state to %SCX_OPSS_NONE; dequeue waits while DISPATCHING + * 1. Set p->scx.holding_cpu to the current CPU + * 2. Set task state to %SCX_OPSS_NONE; dequeue waits while DISPATCHING * is set, so clearing DISPATCHING first prevents the circular wait * (safe to lock the rq we need) - * 4. Unlock the current CPU's rq - * 5. Lock src_rq (where the task currently lives) - * 6. Verify p->scx.holding_cpu == current CPU, if not, dequeue won the + * 3. Unlock the current CPU's rq + * 4. Lock src_rq (where the task currently lives) + * 5. Verify p->scx.holding_cpu == current CPU, if not, dequeue won the * race (dequeue clears holding_cpu to -1 when it takes the task), in * this case migration is aborted - * 7. If src_rq == dst_rq: clear holding_cpu and enqueue directly + * 6. If src_rq == dst_rq: clear holding_cpu and enqueue directly * into dst_rq's local DSQ (no lock swap needed) - * 8. Otherwise: call move_remote_task_to_local_dsq(), which releases - * src_rq, locks dst_rq, and performs the deactivate/activate - * migration cycle (dst_rq is held on return) + * 7. Otherwise, verify under src_rq lock that the task can be moved to dst_rq + * (CPU affinity, migration_disabled, etc.). If not, clear holding_cpu, + * leave the task on src_rq, and enqueue it on the fallback DSQ. + * 8. Otherwise (i.e. if the task can be moved to dst_rq), call + * move_remote_task_to_local_dsq(), which releases src_rq, locks dst_rq, + * and performs the deactivate/activate migration cycle + * (dst_rq is held on return) * 9. Unlock dst_rq and re-lock the current CPU's rq to restore * the lock state expected by the caller * From 115d1ce989747045bd7745c7ab020982660c7e42 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 24 Jun 2026 12:40:51 -1000 Subject: [PATCH 035/479] sched_ext: Annotate ksyncs with __rcu in alloc/free_kick_syncs() scx_kick_syncs is a per-CPU __rcu pointer, so per_cpu_ptr() returns struct scx_kick_syncs __rcu **. alloc_kick_syncs() and free_kick_syncs() stored it in a plain struct scx_kick_syncs **ksyncs, which sparse flags as an __rcu address-space mismatch. Annotate ksyncs to match. Its accesses already go through rcu_*_pointer(). Fixes: 987e00035c0e ("sched_ext: Rename pnt_seq to kick_sync") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606122315.pbnDHP0n-lkp@intel.com/ Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 5418e6357249..aecbb021d6d7 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -5687,7 +5687,7 @@ static void free_kick_syncs(void) int cpu; for_each_possible_cpu(cpu) { - struct scx_kick_syncs **ksyncs = per_cpu_ptr(&scx_kick_syncs, cpu); + struct scx_kick_syncs __rcu **ksyncs = per_cpu_ptr(&scx_kick_syncs, cpu); struct scx_kick_syncs *to_free; to_free = rcu_replace_pointer(*ksyncs, NULL, true); @@ -6668,7 +6668,7 @@ static int alloc_kick_syncs(void) * can exceed percpu allocator limits on large machines. */ for_each_possible_cpu(cpu) { - struct scx_kick_syncs **ksyncs = per_cpu_ptr(&scx_kick_syncs, cpu); + struct scx_kick_syncs __rcu **ksyncs = per_cpu_ptr(&scx_kick_syncs, cpu); struct scx_kick_syncs *new_ksyncs; WARN_ON_ONCE(rcu_access_pointer(*ksyncs)); From 0dfa1e960f86e032007882b032c5cc7d14ebe73e Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Wed, 21 Jan 2026 16:15:34 +0530 Subject: [PATCH 036/479] arm64: tegra: Fix CPU compatible string to cortex-a78ae on Tegra234 The Tegra234 SoC uses Cortex-A78AE cores, not Cortex-A78. Update the compatible string for all CPU nodes to match the actual hardware. Tegra234 hardware reports: # head /proc/cpuinfo | egrep 'implementer|part' CPU implementer : 0x41 CPU part : 0xd42 Which maps to (from arch/arm64/include/asm/cputype.h): #define ARM_CPU_IMP_ARM 0x41 #define ARM_CPU_PART_CORTEX_A78AE 0xD42 Fixes: a12cf5c339b08 ("arm64: tegra: Describe Tegra234 CPU hierarchy") Signed-off-by: Sumit Gupta Signed-off-by: Thierry Reding --- arch/arm64/boot/dts/nvidia/tegra234.dtsi | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/arch/arm64/boot/dts/nvidia/tegra234.dtsi b/arch/arm64/boot/dts/nvidia/tegra234.dtsi index 8e0c51e496e2..820670dd6042 100644 --- a/arch/arm64/boot/dts/nvidia/tegra234.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra234.dtsi @@ -5355,7 +5355,7 @@ cpus { #size-cells = <0>; cpu0_0: cpu@0 { - compatible = "arm,cortex-a78"; + compatible = "arm,cortex-a78ae"; device_type = "cpu"; reg = <0x00000>; @@ -5374,7 +5374,7 @@ cpu0_0: cpu@0 { }; cpu0_1: cpu@100 { - compatible = "arm,cortex-a78"; + compatible = "arm,cortex-a78ae"; device_type = "cpu"; reg = <0x00100>; @@ -5393,7 +5393,7 @@ cpu0_1: cpu@100 { }; cpu0_2: cpu@200 { - compatible = "arm,cortex-a78"; + compatible = "arm,cortex-a78ae"; device_type = "cpu"; reg = <0x00200>; @@ -5412,7 +5412,7 @@ cpu0_2: cpu@200 { }; cpu0_3: cpu@300 { - compatible = "arm,cortex-a78"; + compatible = "arm,cortex-a78ae"; device_type = "cpu"; reg = <0x00300>; @@ -5431,7 +5431,7 @@ cpu0_3: cpu@300 { }; cpu1_0: cpu@10000 { - compatible = "arm,cortex-a78"; + compatible = "arm,cortex-a78ae"; device_type = "cpu"; reg = <0x10000>; @@ -5450,7 +5450,7 @@ cpu1_0: cpu@10000 { }; cpu1_1: cpu@10100 { - compatible = "arm,cortex-a78"; + compatible = "arm,cortex-a78ae"; device_type = "cpu"; reg = <0x10100>; @@ -5469,7 +5469,7 @@ cpu1_1: cpu@10100 { }; cpu1_2: cpu@10200 { - compatible = "arm,cortex-a78"; + compatible = "arm,cortex-a78ae"; device_type = "cpu"; reg = <0x10200>; @@ -5488,7 +5488,7 @@ cpu1_2: cpu@10200 { }; cpu1_3: cpu@10300 { - compatible = "arm,cortex-a78"; + compatible = "arm,cortex-a78ae"; device_type = "cpu"; reg = <0x10300>; @@ -5507,7 +5507,7 @@ cpu1_3: cpu@10300 { }; cpu2_0: cpu@20000 { - compatible = "arm,cortex-a78"; + compatible = "arm,cortex-a78ae"; device_type = "cpu"; reg = <0x20000>; @@ -5526,7 +5526,7 @@ cpu2_0: cpu@20000 { }; cpu2_1: cpu@20100 { - compatible = "arm,cortex-a78"; + compatible = "arm,cortex-a78ae"; device_type = "cpu"; reg = <0x20100>; @@ -5545,7 +5545,7 @@ cpu2_1: cpu@20100 { }; cpu2_2: cpu@20200 { - compatible = "arm,cortex-a78"; + compatible = "arm,cortex-a78ae"; device_type = "cpu"; reg = <0x20200>; @@ -5564,7 +5564,7 @@ cpu2_2: cpu@20200 { }; cpu2_3: cpu@20300 { - compatible = "arm,cortex-a78"; + compatible = "arm,cortex-a78ae"; device_type = "cpu"; reg = <0x20300>; From 806a66f926c2b6652aeb88983d01f25081b41a73 Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Wed, 21 Jan 2026 16:15:35 +0530 Subject: [PATCH 037/479] arm64: tegra: Fix CPU1 node unit-address on Tegra264 Fix the unit-address of cpu1 node to match its reg property value. Fixes: f6d1890e5f4d ("arm64: tegra: Add device tree for Tegra264") Signed-off-by: Sumit Gupta Signed-off-by: Thierry Reding --- arch/arm64/boot/dts/nvidia/tegra264.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/nvidia/tegra264.dtsi b/arch/arm64/boot/dts/nvidia/tegra264.dtsi index 3dfdd7bb28a9..2d2cb1a3d95c 100644 --- a/arch/arm64/boot/dts/nvidia/tegra264.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra264.dtsi @@ -4070,7 +4070,7 @@ cpu0: cpu@0 { d-cache-sets = <256>; }; - cpu1: cpu@1 { + cpu1: cpu@10000 { compatible = "arm,neoverse-v3ae"; device_type = "cpu"; reg = <0x10000>; From 6860b467f569f732b11cbc588ae7e195e90e7e23 Mon Sep 17 00:00:00 2001 From: Petr Wozniak Date: Sun, 21 Jun 2026 12:03:26 +0200 Subject: [PATCH 038/479] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm() validate_xmit_xfrm() returns NULL both when a packet is dropped and when it is stolen by async crypto (-EINPROGRESS from ->xmit()). Callers cannot distinguish the two cases. f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.") changed the semantics of a NULL return from "dropped" to "stolen or dropped", but __dev_queue_xmit() was not updated. On virtual/bridge interfaces (noqueue qdisc) __dev_queue_xmit() initialises rc=-ENOMEM and jumps to out: when skb is NULL, returning -ENOMEM to the caller even though the packet will be delivered correctly via xfrm_dev_resume(). Return ERR_PTR(-EINPROGRESS) from validate_xmit_xfrm() for the async case so callers can tell it apart from a real drop. Update __dev_queue_xmit() to handle ERR_PTR(-EINPROGRESS) from validate_xmit_skb() correctly. Update validate_xmit_skb_list() to use IS_ERR_OR_NULL() so that ERR_PTR(-EINPROGRESS) is not mistakenly added to the transmitted list. Fixes: f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.") Suggested-by: Sabrina Dubroca Signed-off-by: Petr Wozniak Signed-off-by: Steffen Klassert --- net/core/dev.c | 10 ++++++++-- net/xfrm/xfrm_device.c | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index 4b3d5cfdf6e0..5933c5dab09e 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -4018,6 +4018,9 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb, return NULL; } +/* Returns the skb on success, NULL if dropped, or ERR_PTR(-EINPROGRESS) + * if stolen by async xfrm crypto (delivered via xfrm_dev_resume()). + */ static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again) { netdev_features_t features; @@ -4089,7 +4092,7 @@ struct sk_buff *validate_xmit_skb_list(struct sk_buff *skb, struct net_device *d skb->prev = skb; skb = validate_xmit_skb(skb, dev, again); - if (!skb) + if (IS_ERR_OR_NULL(skb)) continue; if (!head) @@ -4860,8 +4863,11 @@ int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev) goto recursion_alert; skb = validate_xmit_skb(skb, dev, &again); - if (!skb) + if (IS_ERR_OR_NULL(skb)) { + if (PTR_ERR(skb) == -EINPROGRESS) + rc = NET_XMIT_SUCCESS; goto out; + } HARD_TX_LOCK(dev, txq, cpu); diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c index 630f3dd31cc5..19c77f09acc9 100644 --- a/net/xfrm/xfrm_device.c +++ b/net/xfrm/xfrm_device.c @@ -182,7 +182,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur err = x->type_offload->xmit(x, skb, esp_features); if (err) { if (err == -EINPROGRESS) - return NULL; + return ERR_PTR(-EINPROGRESS); XFRM_INC_STATS(xs_net(x), LINUX_MIB_XFRMOUTSTATEPROTOERROR); kfree_skb(skb); @@ -224,7 +224,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur pskb = skb2; } - return skb; + return skb ? skb : ERR_PTR(-EINPROGRESS); } EXPORT_SYMBOL_GPL(validate_xmit_xfrm); From 3f4c3919baf0944ad96580467c302bc6c7758b00 Mon Sep 17 00:00:00 2001 From: Petr Wozniak Date: Sun, 21 Jun 2026 12:03:27 +0200 Subject: [PATCH 039/479] xfrm: fix stale skb->prev after async crypto steals a GSO segment skb_gso_segment() leaves the segment list head with ->prev pointing at the last segment, an invariant validate_xmit_skb_list() relies on when it sets its tail pointer (tail = skb->prev). When validate_xmit_xfrm() walks a GSO list and some segments are stolen by async crypto (->xmit() returns -EINPROGRESS), those segments are unlinked from the list but the head ->prev is never updated. If the last segment is the one stolen, the returned head still has ->prev pointing at it, even though it is now owned by the crypto engine and may be freed. validate_xmit_skb_list() later does tail->next = skb, writing through that stale pointer -- a use-after-free. Repoint skb->prev at the last retained segment before returning. Fixes: f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.") Signed-off-by: Petr Wozniak Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_device.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c index 19c77f09acc9..aec1e1184a71 100644 --- a/net/xfrm/xfrm_device.c +++ b/net/xfrm/xfrm_device.c @@ -224,6 +224,14 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur pskb = skb2; } + /* skb_gso_segment() set skb->prev to the last segment, but async + * crypto may have stolen it above without updating ->prev. Repoint + * it at the last retained segment so validate_xmit_skb_list() does + * not chain onto a segment now owned by the crypto engine. + */ + if (skb) + skb->prev = pskb; + return skb ? skb : ERR_PTR(-EINPROGRESS); } EXPORT_SYMBOL_GPL(validate_xmit_xfrm); From 53716a4d745f1dac7aff33f3d1494b701eb2f888 Mon Sep 17 00:00:00 2001 From: Seth Forshee Date: Tue, 2 Jun 2026 21:54:06 +0000 Subject: [PATCH 040/479] firmware: arm_ffa: Respect firmware advertised RX/TX buffer size limits FFA_FEATURES reports the minimum size and alignment boundary required for RXTX_MAP. In FF-A v1.2 and later it can also report a maximum buffer size, with zero meaning that no maximum is enforced. The driver only used the minimum value and then rounded it up to PAGE_SIZE before invoking RXTX_MAP after commit 83210251fd70 ("firmware: arm_ffa: Use the correct buffer size during RXTX_MAP"). On systems where PAGE_SIZE is larger than the advertised minimum, this can exceed a non-zero maximum reported by firmware. Older implementations do not advertise a maximum and may also reject the rounded-up size. Decode the maximum size and clamp the page-aligned minimum to it when it is present. If no maximum is advertised and RXTX_MAP rejects the rounded size with INVALID_PARAMETERS, retry with the advertised minimum size. Record drv_info->rxtx_bufsz only after RXTX_MAP succeeds so it reflects the size registered with firmware. While there, also update RXTX_MAP_MIN_BUFSZ() to use FIELD_GET() for consistency. Fixes: 83210251fd70 ("firmware: arm_ffa: Use the correct buffer size during RXTX_MAP") Suggested-by: Sudeep Holla Signed-off-by: Seth Forshee Link: https://patch.msgid.link/20260602-b4-ffa-rxtx-map-fixes-v2-1-7cb06508da84@nvidia.com (sudeep.holla: Minor rewording subject and commit message) Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 0f468362c288..bc2685331b27 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -59,7 +60,9 @@ (FIELD_PREP(SENDER_ID_MASK, (s)) | FIELD_PREP(RECEIVER_ID_MASK, (r))) #define RXTX_MAP_MIN_BUFSZ_MASK GENMASK(1, 0) -#define RXTX_MAP_MIN_BUFSZ(x) ((x) & RXTX_MAP_MIN_BUFSZ_MASK) +#define RXTX_MAP_MAX_BUFSZ_MASK GENMASK(31, 16) +#define RXTX_MAP_MIN_BUFSZ(x) (FIELD_GET(RXTX_MAP_MIN_BUFSZ_MASK, (x))) +#define RXTX_MAP_MAX_BUFSZ(x) (FIELD_GET(RXTX_MAP_MAX_BUFSZ_MASK, (x))) #define FFA_MAX_NOTIFICATIONS 64 @@ -2101,7 +2104,7 @@ static int ffa_probe(struct platform_device *pdev) { int ret; u32 buf_sz; - size_t rxtx_bufsz = SZ_4K; + size_t rxtx_min_bufsz = SZ_4K, rxtx_max_bufsz = 0, rxtx_bufsz; if (IS_BUILTIN(CONFIG_ARM_FFA_TRANSPORT) && is_protected_kvm_enabled() && !is_pkvm_initialized()) @@ -2132,15 +2135,18 @@ static int ffa_probe(struct platform_device *pdev) ret = ffa_features(FFA_FN_NATIVE(RXTX_MAP), 0, &buf_sz, NULL); if (!ret) { if (RXTX_MAP_MIN_BUFSZ(buf_sz) == 1) - rxtx_bufsz = SZ_64K; + rxtx_min_bufsz = SZ_64K; else if (RXTX_MAP_MIN_BUFSZ(buf_sz) == 2) - rxtx_bufsz = SZ_16K; + rxtx_min_bufsz = SZ_16K; else - rxtx_bufsz = SZ_4K; + rxtx_min_bufsz = SZ_4K; + + rxtx_max_bufsz = RXTX_MAP_MAX_BUFSZ(buf_sz) * SZ_4K; + if (rxtx_max_bufsz != 0 && rxtx_max_bufsz < rxtx_min_bufsz) + rxtx_max_bufsz = rxtx_min_bufsz; } - rxtx_bufsz = PAGE_ALIGN(rxtx_bufsz); - drv_info->rxtx_bufsz = rxtx_bufsz; + rxtx_bufsz = min_not_zero(PAGE_ALIGN(rxtx_min_bufsz), rxtx_max_bufsz); drv_info->rx_buffer = alloc_pages_exact(rxtx_bufsz, GFP_KERNEL); if (!drv_info->rx_buffer) { ret = -ENOMEM; @@ -2156,10 +2162,17 @@ static int ffa_probe(struct platform_device *pdev) ret = ffa_rxtx_map(virt_to_phys(drv_info->tx_buffer), virt_to_phys(drv_info->rx_buffer), rxtx_bufsz / FFA_PAGE_SIZE); + if (ret == -EINVAL && !rxtx_max_bufsz && rxtx_min_bufsz < rxtx_bufsz) { + rxtx_bufsz = rxtx_min_bufsz; + ret = ffa_rxtx_map(virt_to_phys(drv_info->tx_buffer), + virt_to_phys(drv_info->rx_buffer), + rxtx_bufsz / FFA_PAGE_SIZE); + } if (ret) { pr_err("failed to register FFA RxTx buffers\n"); goto free_pages; } + drv_info->rxtx_bufsz = rxtx_bufsz; mutex_init(&drv_info->rx_lock); mutex_init(&drv_info->tx_lock); From d2e52d610b9b09694261632340b801a421e0b0c5 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sat, 6 Jun 2026 16:01:55 -0400 Subject: [PATCH 041/479] IB/mad: Drop unmatched RMPP responses before reassembly Kernel-handled RMPP receive processing starts reassembly for active DATA responses before the response is matched to an outstanding send. The normal match happens later, after ib_process_rmpp_recv_wc() has either assembled a complete message or consumed the segment. That ordering lets an unsolicited response that routes to a kernel RMPP agent by the high TID bits allocate or extend RMPP receive state before the full TID and source address are checked against a real request. A reordered burst can therefore reach the receive-side insertion path even though the response would not match any send. For kernel-handled RMPP DATA responses, require the existing ib_find_send_mad() match before entering RMPP reassembly. The matcher already checks the full TID, management class and source address/GID against the agent wait, backlog and in-flight send lists. If there is no match, drop the response without creating RMPP state. This leaves the RMPP window behavior unchanged and only rejects responses that have no corresponding request. Fixes: fa619a77046b ("[PATCH] IB: Add RMPP implementation") Assisted-by: Codex:gpt-5-5-xhigh Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/3170ff3bc389a930bb1641f2caa394a0b2241579.1780774907.git.michael.bommarito@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/mad.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/infiniband/core/mad.c b/drivers/infiniband/core/mad.c index 8d19613179e3..e0b3b36b8b14 100644 --- a/drivers/infiniband/core/mad.c +++ b/drivers/infiniband/core/mad.c @@ -2031,6 +2031,24 @@ void ib_mark_mad_done(struct ib_mad_send_wr_private *mad_send_wr) change_mad_state(mad_send_wr, IB_MAD_STATE_EARLY_RESP); } +static bool is_kernel_rmpp_data_response(struct ib_mad_agent_private *agent, + struct ib_mad_recv_wc *mad_recv_wc) +{ + const struct ib_mad_hdr *mad_hdr = &mad_recv_wc->recv_buf.mad->mad_hdr; + struct ib_rmpp_mad *rmpp_mad; + + if (!ib_mad_kernel_rmpp_agent(&agent->agent) || + !ib_response_mad(mad_hdr) || + !ib_is_mad_class_rmpp(mad_hdr->mgmt_class)) + return false; + + rmpp_mad = (struct ib_rmpp_mad *)mad_recv_wc->recv_buf.mad; + + return (ib_get_rmpp_flags(&rmpp_mad->rmpp_hdr) & + IB_MGMT_RMPP_FLAG_ACTIVE) && + rmpp_mad->rmpp_hdr.rmpp_type == IB_MGMT_RMPP_TYPE_DATA; +} + static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv, struct ib_mad_recv_wc *mad_recv_wc) { @@ -2050,6 +2068,18 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv, } list_add(&mad_recv_wc->recv_buf.list, &mad_recv_wc->rmpp_list); + if (is_kernel_rmpp_data_response(mad_agent_priv, mad_recv_wc)) { + spin_lock_irqsave(&mad_agent_priv->lock, flags); + mad_send_wr = ib_find_send_mad(mad_agent_priv, mad_recv_wc); + spin_unlock_irqrestore(&mad_agent_priv->lock, flags); + + if (!mad_send_wr) { + ib_free_recv_mad(mad_recv_wc); + deref_mad_agent(mad_agent_priv); + return; + } + } + if (ib_mad_kernel_rmpp_agent(&mad_agent_priv->agent)) { mad_recv_wc = ib_process_rmpp_recv_wc(mad_agent_priv, mad_recv_wc); From cceca8cb776b03e83985578f84bd730c30d57f70 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 13 Jun 2026 23:31:52 -0700 Subject: [PATCH 042/479] mtd: rawnand: ndfc: fix gcc uninitialized var Now that this can be built with COMPILE_TEST, an unassigned variable was found. Set to 0 to fix the W=1 error under GCC. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606141301.iyVdFgl7-lkp@intel.com/ Signed-off-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/ndfc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/ndfc.c b/drivers/mtd/nand/raw/ndfc.c index a937ca3eeff5..a48274297d3b 100644 --- a/drivers/mtd/nand/raw/ndfc.c +++ b/drivers/mtd/nand/raw/ndfc.c @@ -188,7 +188,7 @@ static int ndfc_probe(struct platform_device *ofdev) const __be32 *reg; u32 ccr; u32 cs; - int err, len; + int err, len = 0; /* Read the reg property to get the chip select */ reg = of_get_property(ofdev->dev.of_node, "reg", &len); From 75c0c09541b49daa08fddbc2c18c2232f4eab7d8 Mon Sep 17 00:00:00 2001 From: Harshit Mogalapalli Date: Sun, 14 Jun 2026 01:10:52 -0700 Subject: [PATCH 043/479] mtd: virt_concat: fix use-after-free in mtd_virt_concat_destroy_joins() mtd_concat_destroy() frees item->concat so calling mtd_virt_concat_put_mtd_devices(item->concat) leads to a use after free. Fix this by moving mtd_virt_concat_put_mtd_devices() before mtd_concat_destroy() Fixes: 43db6366fc2d ("mtd: Add driver for concatenating devices") Signed-off-by: Harshit Mogalapalli Reviewed-by: Luca Ceresoli Signed-off-by: Miquel Raynal --- drivers/mtd/mtd_virt_concat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c index 37075ead0f33..a3fb96788e9d 100644 --- a/drivers/mtd/mtd_virt_concat.c +++ b/drivers/mtd/mtd_virt_concat.c @@ -75,8 +75,8 @@ void mtd_virt_concat_destroy_joins(void) if (item->concat) { mtd_device_unregister(mtd); kfree(mtd->name); - mtd_concat_destroy(mtd); mtd_virt_concat_put_mtd_devices(item->concat); + mtd_concat_destroy(mtd); } } } From 82d9a2b45b170f0c52ac61e0e3e23f212cd065f0 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 15 Jun 2026 14:32:32 +0800 Subject: [PATCH 044/479] mtd: nand: ecc-mtk: handle ECC clock enable failures mtk_ecc_get() gets a reference to the ECC platform device, obtains the provider state and then enables the ECC clock before initializing the hardware. The clk_prepare_enable() return value is currently ignored. If enabling the clock fails, the code still touches the ECC registers and returns a live ECC handle to the caller. The provider device reference acquired by of_find_device_by_node() is also kept even though the handle setup failed. Propagate the clock enable error and drop the provider device reference on that failure path. Signed-off-by: Pengpeng Hou Signed-off-by: Miquel Raynal --- drivers/mtd/nand/ecc-mtk.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/ecc-mtk.c b/drivers/mtd/nand/ecc-mtk.c index c75bb8b80cc1..39be2e3e4ee4 100644 --- a/drivers/mtd/nand/ecc-mtk.c +++ b/drivers/mtd/nand/ecc-mtk.c @@ -265,6 +265,7 @@ static struct mtk_ecc *mtk_ecc_get(struct device_node *np) { struct platform_device *pdev; struct mtk_ecc *ecc; + int ret; pdev = of_find_device_by_node(np); if (!pdev) @@ -276,7 +277,12 @@ static struct mtk_ecc *mtk_ecc_get(struct device_node *np) return ERR_PTR(-EPROBE_DEFER); } - clk_prepare_enable(ecc->clk); + ret = clk_prepare_enable(ecc->clk); + if (ret) { + put_device(&pdev->dev); + return ERR_PTR(ret); + } + mtk_ecc_hw_init(ecc); return ecc; From 1a8b2951ba3c35824ff4df0ea7d9f7a554c964a2 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 15 Jun 2026 14:33:32 +0800 Subject: [PATCH 045/479] mtd: rawnand: ingenic: handle ECC clock enable failures ingenic_ecc_get() obtains a provider device reference and then enables the ECC clock before returning the ECC handle. The clk_prepare_enable() return value is currently ignored. If enabling the clock fails, the function still returns the ECC handle and keeps the provider device reference even though the acquire operation did not complete. Return the clock enable error and drop the provider device reference on that failure path. Signed-off-by: Pengpeng Hou Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/ingenic/ingenic_ecc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/ingenic/ingenic_ecc.c b/drivers/mtd/nand/raw/ingenic/ingenic_ecc.c index 525c34c281b6..beb033705cf3 100644 --- a/drivers/mtd/nand/raw/ingenic/ingenic_ecc.c +++ b/drivers/mtd/nand/raw/ingenic/ingenic_ecc.c @@ -67,6 +67,7 @@ static struct ingenic_ecc *ingenic_ecc_get(struct device_node *np) { struct platform_device *pdev; struct ingenic_ecc *ecc; + int ret; pdev = of_find_device_by_node(np); if (!pdev) @@ -78,7 +79,11 @@ static struct ingenic_ecc *ingenic_ecc_get(struct device_node *np) } ecc = platform_get_drvdata(pdev); - clk_prepare_enable(ecc->clk); + ret = clk_prepare_enable(ecc->clk); + if (ret) { + put_device(&pdev->dev); + return ERR_PTR(ret); + } return ecc; } From 4b45d7836b9526b8776af5f29219615be9417230 Mon Sep 17 00:00:00 2001 From: Harshit Mogalapalli Date: Tue, 16 Jun 2026 03:14:39 -0700 Subject: [PATCH 046/479] mtd: virt_concat: fix use-after-free in mtd_virt_concat_destroy() mtd_concat_destroy() frees item->concat so calling mtd_virt_concat_put_mtd_devices(item->concat) after that leads to a use-after-free. Fix it by moving mtd_virt_concat_put_mtd_devices() before mtd_concat_destroy(). Fixes: 43db6366fc2d ("mtd: Add driver for concatenating devices") Signed-off-by: Harshit Mogalapalli Signed-off-by: Miquel Raynal --- drivers/mtd/mtd_virt_concat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c index a3fb96788e9d..c872a8827718 100644 --- a/drivers/mtd/mtd_virt_concat.c +++ b/drivers/mtd/mtd_virt_concat.c @@ -126,8 +126,8 @@ int mtd_virt_concat_destroy(struct mtd_info *mtd) if (concat->mtd.name) { del_mtd_device(&concat->mtd); kfree(concat->mtd.name); - mtd_concat_destroy(&concat->mtd); mtd_virt_concat_put_mtd_devices(item->concat); + mtd_concat_destroy(&concat->mtd); } for (idx = 0; idx < item->count; idx++) From 5d7a08125e69630dc1168d3e9ca5626ded3da807 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 18 Jun 2026 02:37:49 +0800 Subject: [PATCH 047/479] mtd: spinand: initialize ret in regular page reads spinand_mtd_regular_page_read() returns ret after iterating over the requested pages. If the request contains no data or OOB bytes, the iterator does not run and ret is not assigned. Initialize it to 0 for the empty request path. Signed-off-by: Ruoyu Wang Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index f86786344d52..74bb5ee83b31 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -822,7 +822,7 @@ static int spinand_mtd_regular_page_read(struct mtd_info *mtd, loff_t from, bool disable_ecc = false; bool ecc_failed = false; unsigned int retry_mode = 0; - int ret; + int ret = 0; old_stats = mtd->ecc_stats; From 97f9e509d839f7e5b9a32105ae322e5223c86786 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 18 Jun 2026 22:58:27 +0200 Subject: [PATCH 048/479] mtd: rawnand: ndfc: add CONFIG_OF dependency When compile-testing on x86 without CONFIG_OF, the ndfc driver produces a harmless warning: drivers/mtd/nand/raw/ndfc.c: In function 'ndfc_probe': include/linux/dev_printk.h:154:31: error: 'len' is used uninitialized [-Werror=uninitialized] 154 | dev_printk_index_wrap(_dev_err, KERN_ERR, dev, dev_fmt(fmt), ##__VA_ARGS__) | ^ drivers/mtd/nand/raw/ndfc.c:196:17: note: in expansion of macro 'dev_err' 196 | dev_err(&ofdev->dev, "unable read reg property (%d)\n", len); Limit compile-testing to configurations with CONFIG_OF to trivially avoid this. The driver will still be built in allmodconfig and many randconfig builds. Fixes: 4f2692a5383e ("mtd: rawnand: ndfc: use ioread32be/iowrite32be and allow COMPILE_TEST") Signed-off-by: Arnd Bergmann Reviewed-by: Rosen Penev Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index 64b8b99a3a68..1f4053e531fd 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -72,6 +72,7 @@ config MTD_NAND_AU1550 config MTD_NAND_NDFC tristate "IBM/MCC 4xx NAND controller" depends on 44x || COMPILE_TEST + depends on OF select MTD_NAND_ECC_SW_HAMMING select MTD_NAND_ECC_SW_HAMMING_SMC help From ae9d8058f13238cad5f5d16f676da91187684581 Mon Sep 17 00:00:00 2001 From: Nikolay Ivchenko Date: Sat, 20 Jun 2026 20:06:03 +0300 Subject: [PATCH 049/479] mtd: mtdpart: fix uninitialized erasesize on MTDPART_OFS_RETAIN error path When parsing partition layouts, if a partition requested with MTDPART_OFS_RETAIN runs out of space, the allocator jumps directly to 'out_register' to preserve partition numbering. However, this jump bypasses child->erasesize initialization, leaving it at zero. When add_mtd_device() is later called on this child, the registration fails and triggers a WARN_ON() due to the zero ->erasesize. Fix this by zeroing out child->part.offset and child->part.size, and initializing child->erasesize to parent->erasesize. This is the exact same pattern already used just a few lines below in the "out of reach" error check (child->part.offset >= parent_size) to safely register a disabled partition. Reported-by: syzbot+3ae80219c633aca5431c@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3ae80219c633aca5431c Signed-off-by: Nikolay Ivchenko Signed-off-by: Miquel Raynal --- drivers/mtd/mtdpart.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 795a94e6b482..7f23f8a1b59c 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -118,6 +118,9 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent, part->name, parent_size - child->part.offset, child->part.size); /* register to preserve ordering */ + child->part.offset = 0; + child->part.size = 0; + child->erasesize = parent->erasesize; goto out_register; } } From a74e31d0a2d52c65d9a6647c87b54bf78c0d9317 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Sun, 21 Jun 2026 04:26:25 +0530 Subject: [PATCH 050/479] mtd: mtdpart: validate partition bounds in mtd_add_partition() mtd_add_partition() checks that 'length' is positive but does not validate that 'offset + length' fits within the parent partition's size. A userspace caller using the BLKPG_ADD_PARTITION ioctl can supply a crafted large 'length' value that passes the length <= 0 check, causing add_mtd_device() to fire a WARN_ON() when it detects the oversized partition. Fix this by adding explicit bounds checks before allocate_partition() is called: - Reject negative or out-of-range offsets. - Use u64 arithmetic to safely check offset + length <= parent_size, avoiding potential signed integer overflow. Reported-by: syzbot+3ae80219c633aca5431c@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3ae80219c633aca5431c Signed-off-by: Deepanshu Kartikey Signed-off-by: Miquel Raynal --- drivers/mtd/mtdpart.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 7f23f8a1b59c..4b41550fd374 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -267,6 +267,11 @@ int mtd_add_partition(struct mtd_info *parent, const char *name, if (length <= 0) return -EINVAL; + if (offset < 0 || offset >= (long long)parent_size) + return -EINVAL; + + if ((u64)offset + (u64)length > parent_size) + return -EINVAL; memset(&part, 0, sizeof(part)); part.name = name; part.size = length; From 66fb31358108d10245b9e4ef0eef3e7d9747055e Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 23 Jun 2026 09:53:17 +0800 Subject: [PATCH 051/479] mtd: mtdswap: remove debugfs stats file on teardown mtdswap_add_debugfs() creates an mtdswap_stats debugfs file under the per-MTD debugfs directory, but mtdswap_remove_dev() never removes it before freeing the mtdswap_dev. Store the returned dentry and remove it during device teardown before the driver-private state is freed. Fixes: a32159024620 ("mtd: Add mtdswap block driver") Signed-off-by: Pengpeng Hou Signed-off-by: Miquel Raynal --- drivers/mtd/mtdswap.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/mtdswap.c b/drivers/mtd/mtdswap.c index 866933fc8426..f33f753f0a9f 100644 --- a/drivers/mtd/mtdswap.c +++ b/drivers/mtd/mtdswap.c @@ -125,6 +125,7 @@ struct mtdswap_dev { char *page_buf; char *oob_buf; + struct dentry *debugfs_stats; }; struct mtdswap_oobdata { @@ -1262,7 +1263,8 @@ static int mtdswap_add_debugfs(struct mtdswap_dev *d) if (IS_ERR_OR_NULL(root)) return -1; - debugfs_create_file("mtdswap_stats", S_IRUSR, root, d, &mtdswap_fops); + d->debugfs_stats = debugfs_create_file("mtdswap_stats", 0400, root, + d, &mtdswap_fops); return 0; } @@ -1463,6 +1465,7 @@ static void mtdswap_remove_dev(struct mtd_blktrans_dev *dev) { struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + debugfs_remove(d->debugfs_stats); del_mtd_blktrans_dev(dev); mtdswap_cleanup(d); kfree(d); From 16f7ec8d5dc100eafd2c8e06cd30340a30b104a1 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 23 Jun 2026 21:57:29 +0800 Subject: [PATCH 052/479] mtd: nand: mtk-ecc: stop on ECC idle timeouts mtk_ecc_wait_idle() logs when the encoder or decoder does not become idle, but returns void. Callers can therefore configure a non-idle ECC engine or read parity bytes after an unconfirmed encoder idle state. Return the idle poll result and propagate it from the enable and encode paths that require the engine to be idle before continuing. Fixes: 1d6b1e464950 ("mtd: mediatek: driver for MTK Smart Device") Signed-off-by: Pengpeng Hou Signed-off-by: Miquel Raynal --- drivers/mtd/nand/ecc-mtk.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/nand/ecc-mtk.c b/drivers/mtd/nand/ecc-mtk.c index 39be2e3e4ee4..66f0985ef7cd 100644 --- a/drivers/mtd/nand/ecc-mtk.c +++ b/drivers/mtd/nand/ecc-mtk.c @@ -123,8 +123,8 @@ static int mt7622_ecc_regs[] = { [ECC_DECIRQ_STA] = 0x144, }; -static inline void mtk_ecc_wait_idle(struct mtk_ecc *ecc, - enum mtk_ecc_operation op) +static inline int mtk_ecc_wait_idle(struct mtk_ecc *ecc, + enum mtk_ecc_operation op) { struct device *dev = ecc->dev; u32 val; @@ -136,6 +136,8 @@ static inline void mtk_ecc_wait_idle(struct mtk_ecc *ecc, if (ret) dev_warn(dev, "%s NOT idle\n", op == ECC_ENCODE ? "encoder" : "decoder"); + + return ret; } static irqreturn_t mtk_ecc_irq(int irq, void *id) @@ -318,7 +320,11 @@ int mtk_ecc_enable(struct mtk_ecc *ecc, struct mtk_ecc_config *config) return ret; } - mtk_ecc_wait_idle(ecc, op); + ret = mtk_ecc_wait_idle(ecc, op); + if (ret) { + mutex_unlock(&ecc->lock); + return ret; + } ret = mtk_ecc_config(ecc, config); if (ret) { @@ -418,7 +424,9 @@ int mtk_ecc_encode(struct mtk_ecc *ecc, struct mtk_ecc_config *config, if (ret) goto timeout; - mtk_ecc_wait_idle(ecc, ECC_ENCODE); + ret = mtk_ecc_wait_idle(ecc, ECC_ENCODE); + if (ret) + goto timeout; /* Program ECC bytes to OOB: per sector oob = FDM + ECC + SPARE */ len = (config->strength * ecc->caps->parity_bits + 7) >> 3; From cb36d81e751173c4b59b5e561c596c925144ea48 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 25 Jun 2026 13:23:56 -1000 Subject: [PATCH 053/479] sched_ext: Pin parent scx_sched across a child sub-scheduler's lifetime A child sub-scheduler dereferences its parent scx_sched throughout its life, e.g., in scx_sub_disable() which reparents the child's tasks and calls parent->ops.sub_detach() after unlinking from the parent. However, the parent is pinned only through parent->sub_kset, which is dropped during disable. The parent scx_sched can be RCU-freed while a child is still disabling. Take a direct reference on the parent in scx_alloc_and_add_sched(), dropped in scx_sched_free_rcu_work(), so a parent always outlives its descendants. Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index aecbb021d6d7..3b2e13bc924b 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -4916,6 +4916,8 @@ static void scx_sched_free_rcu_work(struct work_struct *work) cgroup_put(sch_cgroup(sch)); if (sch->sub_kset) kobject_put(&sch->sub_kset->kobj); + if (scx_parent(sch)) + kobject_put(&scx_parent(sch)->kobj); #endif /* CONFIG_EXT_SUB_SCHED */ for_each_possible_cpu(cpu) { @@ -6863,12 +6865,19 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd, INIT_LIST_HEAD(&sch->children); INIT_LIST_HEAD(&sch->sibling); - if (parent) + if (parent) { + /* + * Pin @parent for @sch's lifetime. The kobject hierarchy pins + * it only via @parent->sub_kset, which is dropped during + * disable. Released in scx_sched_free_rcu_work(). + */ + kobject_get(&parent->kobj); ret = kobject_init_and_add(&sch->kobj, &scx_ktype, &parent->sub_kset->kobj, "sub-%llu", cgroup_id(cgrp)); - else + } else { ret = kobject_init_and_add(&sch->kobj, &scx_ktype, NULL, "root"); + } if (ret < 0) { RCU_INIT_POINTER(ops->priv, NULL); From cf1afec09e9f004a62c54c471863209ed249fca7 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Mon, 8 Jun 2026 11:33:53 +0100 Subject: [PATCH 054/479] btrfs: validate properties before setting them We set the xattr and then attempt to apply the property. If the apply fails we then attempt to delete the xattr to avoid an inconsistency. However we don't verify if the deletion succeed, so if it fails we leave an inconsistency between the state in the btree and the in-memory inode. Address this by validating first if we can apply the property, then set the xattr, then apply the property, and this last step should not fail since the validation succeeded before - assert that it does not fail but leave code to attempt to delete the xattr if it happens, and then abort the transaction only if the xattr delete failed. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/props.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/props.c b/fs/btrfs/props.c index adc956432d2f..bb77d46376d4 100644 --- a/fs/btrfs/props.c +++ b/fs/btrfs/props.c @@ -127,14 +127,24 @@ int btrfs_set_prop(struct btrfs_trans_handle *trans, struct btrfs_inode *inode, return ret; } + ret = handler->validate(inode, value, value_len); + if (ret) + return ret; ret = btrfs_setxattr(trans, &inode->vfs_inode, handler->xattr_name, value, value_len, flags); if (ret) return ret; ret = handler->apply(inode, value, value_len); - if (ret) { - btrfs_setxattr(trans, &inode->vfs_inode, handler->xattr_name, NULL, - 0, flags); + /* We validated before, so it should not fail here. */ + ASSERT(ret == 0); + if (unlikely(ret)) { + int ret2; + + /* Try to delete xattr, if not possible abort transaction. */ + ret2 = btrfs_setxattr(trans, &inode->vfs_inode, handler->xattr_name, + NULL, 0, flags); + if (unlikely(ret2)) + btrfs_abort_transaction(trans, ret2); return ret; } From 9d78a98796f215d9973e1e53871b2d63420f3608 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Mon, 8 Jun 2026 11:48:58 +0100 Subject: [PATCH 055/479] btrfs: fix transaction abort logic in btrfs_fileattr_set() There's no need to abort the transaction if we failed to set or delete a property, as we haven't done any change. However we need to abort if we set a property or delete a property and then fail to update the inode item, as that would leave the inode's state in subvolume tree inconsistent. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/ioctl.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 9d47d16394fc..81e87bc39828 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -289,6 +289,7 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, int ret; const char *comp = NULL; u32 inode_flags; + bool prop_set = false; if (btrfs_root_readonly(root)) return -EROFS; @@ -401,16 +402,15 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, if (comp) { ret = btrfs_set_prop(trans, inode, "btrfs.compression", comp, strlen(comp), 0); - if (unlikely(ret)) { - btrfs_abort_transaction(trans, ret); + if (ret) goto out_end_trans; - } + prop_set = true; } else { ret = btrfs_set_prop(trans, inode, "btrfs.compression", NULL, 0, 0); - if (unlikely(ret && ret != -ENODATA)) { - btrfs_abort_transaction(trans, ret); + prop_set = (ret == 0); + /* If ret == -ENODATA ignore and proceed to update inode item. */ + if (ret && ret != -ENODATA) goto out_end_trans; - } } update_flags: @@ -420,6 +420,12 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, inode_inc_iversion(&inode->vfs_inode); inode_set_ctime_current(&inode->vfs_inode); ret = btrfs_update_inode(trans, inode); + /* + * If we set a property or deleted one, we must abort if we fail to + * update the inode, to avoid persisting an inconsistent state. + */ + if (unlikely(ret && prop_set)) + btrfs_abort_transaction(trans, ret); out_end_trans: btrfs_end_transaction(trans); From a2d8d5647ed854e38f941741aea45b9eb15a6350 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Wed, 10 Jun 2026 10:29:26 -0700 Subject: [PATCH 056/479] btrfs: reject free space cache with more entries than pages When loading a v1 free space cache, __load_free_space_cache() takes num_entries and num_bitmaps straight from the on-disk btrfs_free_space_header. That header is stored in the tree_root under a key with type 0, which the tree-checker has no case for, so neither count is validated before the load trusts it. The load loops num_entries times and maps the next page whenever the current one runs out, going through io_ctl_check_crc() -> io_ctl_map_page(), which does io_ctl->pages[io_ctl->index++]. But pages[] is allocated in io_ctl_init() from the cache inode's i_size, not from num_entries: num_pages = DIV_ROUND_UP(i_size_read(inode), PAGE_SIZE); io_ctl->pages = kcalloc(num_pages, sizeof(struct page *), GFP_NOFS); So if num_entries claims more records than the pages can hold, io_ctl->index runs off the end of pages[]. The write side never hits this because io_ctl_add_entry() and io_ctl_add_bitmap() both stop once io_ctl->index >= io_ctl->num_pages; the read side just never had the same check. To trigger it, take a clean cache (num_entries = here), set num_entries in the header to 0x10000, and fix up the leaf checksum so it still passes the tree-checker. The cache inode has i_size = 65536, so num_pages is 16 and pages[] is a 16-pointer (kmalloc-128) array. The load now tries to read 65536 entries, io_ctl->index walks up to 16, and pages[16] is read past the array: BUG: KASAN: slab-out-of-bounds in io_ctl_check_crc (fs/btrfs/free-space-cache.c:420 fs/btrfs/free-space-cache.c:565) Read of size 8 at addr ffff88800c833a80 by task kworker/u8:3/58 io_ctl_check_crc (fs/btrfs/free-space-cache.c:420 fs/btrfs/free-space-cache.c:565) __load_free_space_cache (fs/btrfs/free-space-cache.c:655 fs/btrfs/free-space-cache.c:820) load_free_space_cache (fs/btrfs/free-space-cache.c:1017) caching_thread (fs/btrfs/block-group.c:880) btrfs_work_helper (fs/btrfs/async-thread.c:312) process_one_work worker_thread kthread ret_from_fork free-space-cache.c:420 is io_ctl_map_page(), inlined into io_ctl_check_crc() at line 565, which is why that is the frame KASAN names. The out-of-bounds slot is then treated as a struct page and handed to crc32c(), so the bad read turns into a GP fault. Add the missing check to io_ctl_check_crc(), which is where both the entry loop and the bitmap loop end up. When num_entries is too large the load now fails like any corrupt cache: __load_free_space_cache() drops it and rebuilds the free space from the extent tree, so a valid cache is never rejected. Reported-by: Weiming Shi Fixes: 5b0e95bf607d ("Btrfs: inline checksums into the disk free space cache") Link: https://lore.kernel.org/linux-btrfs/CAPpSM+RMPByMCKXvM5QFKToxsyNccfuFLWMdD0mfd0wh2Ja62w@mail.gmail.com/ Assisted-by: Claude:claude-opus-4-8 Reviewed-by: Qu Wenruo Signed-off-by: Xiang Mei Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/free-space-cache.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/btrfs/free-space-cache.c b/fs/btrfs/free-space-cache.c index 6009b1477232..e2af75a205ea 100644 --- a/fs/btrfs/free-space-cache.c +++ b/fs/btrfs/free-space-cache.c @@ -551,6 +551,9 @@ static int io_ctl_check_crc(struct btrfs_io_ctl *io_ctl, int index) u32 crc = ~(u32)0; unsigned offset = 0; + if (index >= io_ctl->num_pages) + return -EIO; + if (index == 0) offset = sizeof(u32) * io_ctl->num_pages; From ce6050bafb4e33377dc17fcc357736bfc351180c Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 11 Jun 2026 15:16:21 +0100 Subject: [PATCH 057/479] btrfs: fix root leak if its reloc root is unexpected in merge_reloc_roots() If we have an unexpected reloc_root for our root, we jump to the out label but never drop the reference we obtained for root, resulting in a leak. Add a missing btrfs_put_root() call. Fixes: 24213fa46c70 ("btrfs: do proper error handling in merge_reloc_roots") Reviewed-by: Qu Wenruo Reviewed-by: Johannes Thumshirn Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/relocation.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c index fb85bc8b345c..9447d009a9a0 100644 --- a/fs/btrfs/relocation.c +++ b/fs/btrfs/relocation.c @@ -1912,6 +1912,7 @@ void merge_reloc_roots(struct reloc_control *rc) * corruption, e.g. bad reloc tree key offset. */ ret = -EINVAL; + btrfs_put_root(root); goto out; } ret = merge_reloc_root(rc, root); From 3dcd50730814e5220072d2b26d0587af6bfb6dbe Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Thu, 11 Jun 2026 14:40:17 -0700 Subject: [PATCH 058/479] btrfs: fallback to transaction csum tree on a commit root csum miss We have been running with commit root csums enabled for some time and have noticed a slight uptick in zero csum errors. Investigating those revealed that they were same transaction reads of extents that were just relocated, but the extent map generation was long ago. It turns out that relocation intentionally does not update the extent generation (replace_file_extents()), but must write a new csum since the data has moved, so we must account for this with commit root csum reading. Luckily this is a short lived condition: after the relocation transaction the commit root will once again have the csum. So we can add a generic fallback to the lookup to try again with the transaction csum root. Fixes: f07b855c56b1 ("btrfs: try to search for data csums in commit root") Reviewed-by: Filipe Manana Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- fs/btrfs/file-item.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c index 9f6454e9db81..cf50fd623f41 100644 --- a/fs/btrfs/file-item.c +++ b/fs/btrfs/file-item.c @@ -358,6 +358,7 @@ int btrfs_lookup_bio_sums(struct btrfs_bio *bbio) const unsigned int nblocks = orig_len >> fs_info->sectorsize_bits; int ret = 0; u32 bio_offset = 0; + bool using_commit_root = false; if ((inode->flags & BTRFS_INODE_NODATASUM) || test_bit(BTRFS_FS_STATE_NO_DATA_CSUMS, &fs_info->fs_state)) @@ -431,6 +432,7 @@ int btrfs_lookup_bio_sums(struct btrfs_bio *bbio) * from across transactions. */ if (bbio->csum_search_commit_root) { + using_commit_root = true; path->search_commit_root = true; path->skip_locking = true; down_read(&fs_info->commit_root_sem); @@ -463,6 +465,28 @@ int btrfs_lookup_bio_sums(struct btrfs_bio *bbio) * assume this is the case. */ if (count == 0) { + /* + * If an extent is relocated in the current transaction + * then relocation writes a new csum without updating + * the extent map generation. Until the next commit, we + * will see a hole in that case, so we need to fallback + * to searching the transaction csum root. + * + * Note that a commit root lookup of a referenced extent can + * only miss, not return a stale csum. A freed extent's csum + * is deleted in the same transaction and its bytenr is not + * reusable until that transaction has committed and the + * extent is unpinned. + */ + if (using_commit_root) { + up_read(&fs_info->commit_root_sem); + using_commit_root = false; + path->search_commit_root = false; + path->skip_locking = false; + btrfs_release_path(path); + continue; + } + memset(csum_dst, 0, csum_size); count = 1; @@ -481,7 +505,7 @@ int btrfs_lookup_bio_sums(struct btrfs_bio *bbio) bio_offset += count * sectorsize; } - if (bbio->csum_search_commit_root) + if (using_commit_root) up_read(&fs_info->commit_root_sem); return ret; } From 1cb15b153c636096740519f62040ec6dc3e50aef Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 8 Jun 2026 08:23:49 +0930 Subject: [PATCH 059/479] btrfs: lzo: add error message for invalid headers Inside btrfs we always pair -EUCLEAN error with an error message to indicate which data is corrupted. However there are 3 cases inside lzo decompression where there is no error message for corrupted headers. Add those missing error messages to show exactly where the corruption is. Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/lzo.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/lzo.c b/fs/btrfs/lzo.c index 6e4aa22853ab..1531adb117d1 100644 --- a/fs/btrfs/lzo.c +++ b/fs/btrfs/lzo.c @@ -552,17 +552,26 @@ int lzo_decompress(struct list_head *ws, const u8 *data_in, size_t max_segment_len = workspace_buf_length(fs_info); int ret; - if (unlikely(srclen < LZO_LEN || srclen > max_segment_len + LZO_LEN * 2)) + if (unlikely(srclen < LZO_LEN || srclen > max_segment_len + LZO_LEN * 2)) { + btrfs_err(fs_info, "invalid lzo header length, has %zu expect (%u, %zu)", + srclen, LZO_LEN, max_segment_len + LZO_LEN * 2); return -EUCLEAN; + } in_len = get_unaligned_le32(data_in); - if (unlikely(in_len != srclen)) + if (unlikely(in_len != srclen)) { + btrfs_err(fs_info, "invalid lzo header length, has %zu expect %zu", + in_len, srclen); return -EUCLEAN; + } data_in += LZO_LEN; in_len = get_unaligned_le32(data_in); - if (unlikely(in_len != srclen - LZO_LEN * 2)) + if (unlikely(in_len != srclen - LZO_LEN * 2)) { + btrfs_err(fs_info, "invalid lzo segment length, has %zu expect %zu", + in_len, srclen - LZO_LEN * 2); return -EUCLEAN; + } data_in += LZO_LEN; out_len = sectorsize; From 3dc22abc21f5892406c09202fa2627196cc96967 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Thu, 11 Jun 2026 07:56:12 +0930 Subject: [PATCH 060/479] btrfs: tree-checker: validate INODE_REF's namelen [BUG] A crafted btrfs image can trigger the following crash: BUG: unable to handle page fault for address: ffffd1dc42884000 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page CPU: 9 UID: 0 PID: 1034 Comm: poc Not tainted 7.1.0-rc4-custom+ #383 PREEMPT(full) 46af0a92938a63be7132e0dfd71e62327c51d5c2 Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS unknown 02/02/2022 RIP: 0010:memcpy+0xc/0x10 Call Trace: read_extent_buffer+0xe4/0x100 [btrfs 3cf0785dd58fec8c5ff84633b772f17ce1f92a8f] btrfs_get_name+0x15e/0x1e0 [btrfs 3cf0785dd58fec8c5ff84633b772f17ce1f92a8f] reconnect_path+0x165/0x390 exportfs_decode_fh_raw+0x337/0x400 ? drop_caches_sysctl_handler+0xb0/0xb0 ---[ end trace 0000000000000000 ]--- RIP: 0010:memcpy+0xc/0x10 Kernel panic - not syncing: Fatal exception [CAUSE] TThe crafted image has the following corrupted INODE_REF item: item 9 key (258 INODE_REF 257) itemoff 11544 itemsize 4106 index 2 namelen 4096 name: d\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 The itemsize matches the namelen, but the namelen is 4096, way larger than normal name length limit (BTRFS_NAME_LEN, 255). Meanwhile the memory of the @name is only 255 byte sized, this will cause out-of-boundary access, and cause the above crash. [FIX] Add extra namelen verification for INODE_REF, just like what we have done in ROOT_REF checks. Now the crafted image can be rejected gracefully: BTRFS critical (device dm-2): corrupt leaf: root=5 block=30572544 slot=14 ino=259, invalid inode ref name length, has 4096 expect [1, 255] BTRFS error (device dm-2): read time tree block corruption detected on logical 30572544 mirror 2 Reported-by: Xiang Mei Link: https://lore.kernel.org/linux-btrfs/aik0hEV6ehKx6Ldv@Air.local/ Acked-by: Weiming Shi Signed-off-by: Weiming Shi [ Rebase, add a Link: tag, add an simple cause analyze ] Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index cb3e676a81cc..0ce91396b517 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -1923,6 +1923,12 @@ static int check_inode_ref(struct extent_buffer *leaf, iref = (struct btrfs_inode_ref *)ptr; namelen = btrfs_inode_ref_name_len(leaf, iref); + if (unlikely(namelen == 0 || namelen > BTRFS_NAME_LEN)) { + inode_ref_err(leaf, slot, + "invalid inode ref name length, has %u expect [1, %u]", + namelen, BTRFS_NAME_LEN); + return -EUCLEAN; + } if (unlikely(ptr + sizeof(*iref) + namelen > end)) { inode_ref_err(leaf, slot, "inode ref overflow, ptr %lu end %lu namelen %u", From 9e37d187e1e62bd3d450429203f98402cefb3ef2 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Mon, 22 Jun 2026 10:22:24 +0200 Subject: [PATCH 061/479] btrfs: decentralize transaction aborts in create_reloc_root() Decentralize transaction aborts in create_reloc_root(), so that it is obvious which call failed and what caused the transaction abort. Reviewed-by: Filipe Manana Signed-off-by: Johannes Thumshirn Signed-off-by: David Sterba --- fs/btrfs/relocation.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c index 9447d009a9a0..0d63d117db59 100644 --- a/fs/btrfs/relocation.c +++ b/fs/btrfs/relocation.c @@ -719,21 +719,19 @@ static struct btrfs_root *create_reloc_root(struct btrfs_trans_handle *trans, ret = btrfs_insert_root(trans, fs_info->tree_root, &root_key, root_item); - if (ret) - goto abort; + if (unlikely(ret)) { + btrfs_abort_transaction(trans, ret); + return ERR_PTR(ret); + } reloc_root = btrfs_read_tree_root(fs_info->tree_root, &root_key); if (IS_ERR(reloc_root)) { - ret = PTR_ERR(reloc_root); - goto abort; + btrfs_abort_transaction(trans, PTR_ERR(reloc_root)); + return ERR_CAST(reloc_root); } set_bit(BTRFS_ROOT_SHAREABLE, &reloc_root->state); btrfs_set_root_last_trans(reloc_root, trans->transid); return reloc_root; - -abort: - btrfs_abort_transaction(trans, ret); - return ERR_PTR(ret); } /* From c0041b502e579a5c52e5cae918b90678f03faddd Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sun, 21 Jun 2026 18:41:48 +0930 Subject: [PATCH 062/479] btrfs: print-tree: print header owner as signed When dumping a tree block, btrfs_header::owner is printed as unsigned, which can result in numbers that are hard to read, e.g.: BTRFS info (device loop0): leaf 8908800 gen 16 total ptrs 28 free space 1676 owner 18446744073709551607 For the above output, 18446744073709551607 is (s64)-9, the root id of data reloc tree. Despite those predefined root ids that are already negative, existing subvolume trees will not have any negative values, as subvolume trees can only utilize the lower 48 bits, so there will be no output change for existing subvolumes, thus no extra confusion. Reviewed-by: Filipe Manana Reviewed-by: Sun YangKai Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/print-tree.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/print-tree.c b/fs/btrfs/print-tree.c index 87e60a2d4bd8..53e726119ca7 100644 --- a/fs/btrfs/print-tree.c +++ b/fs/btrfs/print-tree.c @@ -449,9 +449,9 @@ void btrfs_print_leaf(const struct extent_buffer *l) nr = btrfs_header_nritems(l); btrfs_info(fs_info, - "leaf %llu gen %llu total ptrs %d free space %d owner %llu", + "leaf %llu gen %llu total ptrs %d free space %d owner %lld", btrfs_header_bytenr(l), btrfs_header_generation(l), nr, - btrfs_leaf_free_space(l), btrfs_header_owner(l)); + btrfs_leaf_free_space(l), (s64)btrfs_header_owner(l)); print_eb_refs_lock(l); for (i = 0 ; i < nr ; i++) { char key_buf[KEY_TYPE_BUF_SIZE]; @@ -600,10 +600,10 @@ void btrfs_print_tree(const struct extent_buffer *c, bool follow) return; } btrfs_info(fs_info, - "node %llu level %d gen %llu total ptrs %d free spc %u owner %llu", + "node %llu level %d gen %llu total ptrs %d free spc %u owner %lld", btrfs_header_bytenr(c), level, btrfs_header_generation(c), nr, (u32)BTRFS_NODEPTRS_PER_BLOCK(fs_info) - nr, - btrfs_header_owner(c)); + (s64)btrfs_header_owner(c)); print_eb_refs_lock(c); for (i = 0; i < nr; i++) { btrfs_node_key_to_cpu(c, &key, i); From 1006b2f57f77325bfbf5bd36685efe60334fa360 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Tue, 30 Jun 2026 11:18:13 +0800 Subject: [PATCH 063/479] erofs: use more informative s_id for file-backed mounts For file-backed mounts, set sb->s_id to the MAJOR:MINOR of sb->s_dev (which fstat() will return) so that kernel messages and the sysfs name are more informative rather than just "erofs: (device erofs): ...". Reviewed-by: Hongbo Li Signed-off-by: Gao Xiang --- fs/erofs/super.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/fs/erofs/super.c b/fs/erofs/super.c index 86fa5c6a0c70..c5881bb8d52b 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -595,17 +595,6 @@ static const struct export_operations erofs_export_ops = { .get_parent = erofs_get_parent, }; -static void erofs_set_sysfs_name(struct super_block *sb) -{ - struct erofs_sb_info *sbi = EROFS_SB(sb); - - if (erofs_is_fileio_mode(sbi)) - super_set_sysfs_name_generic(sb, "%s", - bdi_dev_name(sb->s_bdi)); - else - super_set_sysfs_name_id(sb); -} - static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) { struct inode *inode; @@ -657,12 +646,14 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) err = super_setup_bdi(sb); if (err) return err; + + snprintf(sb->s_id, sizeof(sb->s_id), + "%u:%u", MAJOR(sb->s_dev), MINOR(sb->s_dev)); } else { if (!sb_set_blocksize(sb, PAGE_SIZE)) { errorfc(fc, "failed to set initial blksize"); return -EINVAL; } - sbi->dif0.dax_dev = fs_dax_get_by_bdev(sb->s_bdev, &sbi->dif0.dax_part_off, NULL, NULL); } @@ -740,7 +731,7 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) if (err) return err; - erofs_set_sysfs_name(sb); + super_set_sysfs_name_id(sb); err = erofs_register_sysfs(sb); if (err) return err; From 8ae5f8e4836667fcaffdf2e3c6068b0a8b364dd8 Mon Sep 17 00:00:00 2001 From: Unnathi Chalicheemala Date: Wed, 17 Jun 2026 16:35:00 -0700 Subject: [PATCH 064/479] firmware: arm_ffa: Fix NULL dereference in ffa_partition_info_get() ffa_partition_info_get() passes uuid_str directly to uuid_parse() without a NULL check. When a caller passes NULL, uuid_parse() -> __uuid_parse() -> uuid_is_valid() dereferences the pointer, causing a kernel panic: | Unable to handle kernel NULL pointer dereference at virtual address | 0000000000000040 | pc : uuid_parse+0x40/0xac | lr : ffa_partition_info_get+0x1c/0x94 [arm_ffa] Add a NULL guard before uuid_parse() so a NULL argument returns -ENODEV instead of crashing. Callers are expected to always supply a valid partition UUID, so NULL is not a supported input. Fixes: d0c0bce83122 ("firmware: arm_ffa: Setup in-kernel users of FFA partitions") Signed-off-by: Unnathi Chalicheemala Link: https://patch.msgid.link/20260617-ffa_partition_nullptr_fix-v2-1-bc801b4ce34c@oss.qualcomm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index bc2685331b27..d475ff83132d 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -1142,7 +1142,7 @@ static int ffa_partition_info_get(const char *uuid_str, uuid_t uuid; struct ffa_partition_info *pbuf; - if (uuid_parse(uuid_str, &uuid)) { + if (!uuid_str || uuid_parse(uuid_str, &uuid)) { pr_err("invalid uuid (%s)\n", uuid_str); return -ENODEV; } From 05e5ffde9b666ee6d3ec225efeda9c038fa74ac8 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 3 Jun 2026 17:17:51 +0200 Subject: [PATCH 065/479] firmware: arm_scmi: Grammar s/may needed/may be needed/ Fix grammar in the help text for the ARM_SCMI_POWER_CONTROL symbol. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/5180d04abfb8e3074a321e2eb73bacfdd61c30c5.1780499850.git.geert+renesas@glider.be Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/arm_scmi/Kconfig b/drivers/firmware/arm_scmi/Kconfig index e3fb36825978..783c24a20e29 100644 --- a/drivers/firmware/arm_scmi/Kconfig +++ b/drivers/firmware/arm_scmi/Kconfig @@ -96,7 +96,7 @@ config ARM_SCMI_POWER_CONTROL firmware. This driver can also be built as a module. If so, the module will be - called scmi_power_control. Note this may needed early in boot to catch - early shutdown/reboot SCMI requests. + called scmi_power_control. Note this may be needed early in boot to + catch early shutdown/reboot SCMI requests. endmenu From 226f4a490d1a938fc838d8f8c46a4eca864c0d78 Mon Sep 17 00:00:00 2001 From: Qianyu Luo Date: Thu, 25 Jun 2026 13:55:08 +0800 Subject: [PATCH 066/479] xfrm: nat_keepalive: avoid double free on send error 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 --- net/xfrm/xfrm_nat_keepalive.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c index 458931062a04..eb1b6f67739e 100644 --- a/net/xfrm/xfrm_nat_keepalive.c +++ b/net/xfrm/xfrm_nat_keepalive.c @@ -55,8 +55,10 @@ static int nat_keepalive_send_ipv4(struct sk_buff *skb, 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(struct sk_buff *skb, dst = ip6_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 nat_keepalive *ka) 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 nat_keepalive *ka) 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 { From b7d9c359e5cf867f7eb23df3bb1c6b9e58af24da Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Mon, 29 Jun 2026 12:55:48 -1000 Subject: [PATCH 067/479] sched_ext: Don't warn on core-sched forced idle in put_prev_task_scx() put_prev_task_scx() warns when a runnable task drops to a lower sched_class without SCX_OPS_ENQ_LAST, on the assumption that balance_one() would have kept it running. Core scheduling breaks that: a forced-idle SMT sibling reschedules through the core_pick fast path in pick_next_task(), which skips pick_task_scx() and thus balance_one(), so a runnable task can drop to idle with ENQ_LAST unset. Gate the warning on sched_cpu_cookie_match(): a cookie mismatch means core scheduling forced the idle, while a match (or core scheduling off) still catches a genuine missing-ENQ_LAST drop. Fixes: 7c65ae81ea86 ("sched_ext: Don't call put_prev_task_scx() before picking the next task") Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 3b2e13bc924b..e75e2fd5ab7e 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -3090,9 +3090,14 @@ static void put_prev_task_scx(struct rq *rq, struct task_struct *p, * sched_class, %SCX_OPS_ENQ_LAST must be set. Tell * ops.enqueue() that @p is the only one available for this cpu, * which should trigger an explicit follow-up scheduling event. + * + * Core scheduling can force this CPU idle while @p stays + * runnable. @p's cookie then won't match the core's, so skip + * the warning in that case. */ if (next && sched_class_above(&ext_sched_class, next->sched_class)) { - WARN_ON_ONCE(!(sch->ops.flags & SCX_OPS_ENQ_LAST)); + WARN_ON_ONCE(sched_cpu_cookie_match(rq, p) && + !(sch->ops.flags & SCX_OPS_ENQ_LAST)); do_enqueue_task(rq, p, SCX_ENQ_LAST, -1); } else { do_enqueue_task(rq, p, 0, -1); From f5ad2ead846e3a00d040d64c7eaf67b65629f51d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 12 Jun 2026 22:15:59 +0200 Subject: [PATCH 068/479] RDMa/mlx5: Avoid frame overflow warning Building mlx5 on s390 shows a rather high stack usage that can exceed the warning limit when that is set to a lower but still reasonable value: drivers/infiniband/hw/mlx5/wr.c:1051:5: error: stack frame size (1328) exceeds limit (1280) in 'mlx5_ib_post_send' [-Werror,-Wframe-larger-than] The problem here is 'struct ib_reg_wr' on the stack of handle_reg_mr_integrity(), which gets inlined into mlx5_ib_post_send() along with a number of smaller functions. Keeping the inner function out of line like gcc does avoids the warning and reduces the total stack usage in other functions called from mlx5_ib_post_send(), though handle_reg_mr_integrity() itself still has the same problem as before. Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260612201611.4127750-1-arnd@kernel.org Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/mlx5/wr.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/infiniband/hw/mlx5/wr.c b/drivers/infiniband/hw/mlx5/wr.c index 9947feb7fb8a..fca9e1d9d5e9 100644 --- a/drivers/infiniband/hw/mlx5/wr.c +++ b/drivers/infiniband/hw/mlx5/wr.c @@ -840,13 +840,15 @@ static int handle_psv(struct mlx5_ib_dev *dev, struct mlx5_ib_qp *qp, return err; } -static int handle_reg_mr_integrity(struct mlx5_ib_dev *dev, - struct mlx5_ib_qp *qp, - const struct ib_send_wr *wr, - struct mlx5_wqe_ctrl_seg **ctrl, void **seg, - int *size, void **cur_edge, - unsigned int *idx, int nreq, u8 fence, - u8 next_fence) +static noinline_for_stack int handle_reg_mr_integrity(struct mlx5_ib_dev *dev, + struct mlx5_ib_qp *qp, + const struct ib_send_wr *wr, + struct mlx5_wqe_ctrl_seg **ctrl, + void **seg, + int *size, void **cur_edge, + unsigned int *idx, int nreq, + u8 fence, + u8 next_fence) { struct mlx5_ib_mr *mr; struct mlx5_ib_mr *pi_mr; From 1ca22c6aa006b05143367268066fb74e32cfe66b Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Mon, 18 May 2026 02:58:37 +0000 Subject: [PATCH 069/479] reset: spacemit: k3: fix USB2 ahb reset According to SpacemiT K3's updated docs, the USB2 ahb reset and USB2 bus clock enable bit was wrongly swapped, the correct one should be: Register : APMU_USB_CLK_RES_CTRL bit[1] : usb2_port_bus_clk_en bit[0] : usb2_port_ahb_rstn Fixes: a0e0c2f8c5f3 ("reset: spacemit: k3: Decouple composite reset lines") Reported-by: Junzhong Pan Signed-off-by: Yixun Lan Reviewed-by: Philipp Zabel Signed-off-by: Philipp Zabel --- drivers/reset/spacemit/reset-spacemit-k3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/reset/spacemit/reset-spacemit-k3.c b/drivers/reset/spacemit/reset-spacemit-k3.c index 9841f5e057b2..2e87f320cf11 100644 --- a/drivers/reset/spacemit/reset-spacemit-k3.c +++ b/drivers/reset/spacemit/reset-spacemit-k3.c @@ -112,7 +112,7 @@ static const struct ccu_reset_data k3_apmu_resets[] = { [RESET_APMU_SDH0] = RESET_DATA(APMU_SDH0_CLK_RES_CTRL, 0, BIT(1)), [RESET_APMU_SDH1] = RESET_DATA(APMU_SDH1_CLK_RES_CTRL, 0, BIT(1)), [RESET_APMU_SDH2] = RESET_DATA(APMU_SDH2_CLK_RES_CTRL, 0, BIT(1)), - [RESET_APMU_USB2_AHB] = RESET_DATA(APMU_USB_CLK_RES_CTRL, 0, BIT(1)), + [RESET_APMU_USB2_AHB] = RESET_DATA(APMU_USB_CLK_RES_CTRL, 0, BIT(0)), [RESET_APMU_USB2_VCC] = RESET_DATA(APMU_USB_CLK_RES_CTRL, 0, BIT(2)), [RESET_APMU_USB2_PHY] = RESET_DATA(APMU_USB_CLK_RES_CTRL, 0, BIT(3)), [RESET_APMU_USB3_A_AHB] = RESET_DATA(APMU_USB_CLK_RES_CTRL, 0, BIT(5)), From ab45ecfab540653f1ff4a8d2f2da055c82cf1640 Mon Sep 17 00:00:00 2001 From: Tanmay Kathpalia Date: Sat, 27 Jun 2026 13:14:46 -0700 Subject: [PATCH 070/479] dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5 Add COMBOPHY_RESET definition at index 38 for the combo PHY reset control on Altera Agilex5 SoCs. This reset is used by peripherals such as the SD/eMMC controller that share the combo PHY. Signed-off-by: Tanmay Kathpalia Acked-by: Conor Dooley Reviewed-by: Philipp Zabel Signed-off-by: Philipp Zabel --- include/dt-bindings/reset/altr,rst-mgr-s10.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/dt-bindings/reset/altr,rst-mgr-s10.h b/include/dt-bindings/reset/altr,rst-mgr-s10.h index 04c4d0c6fd34..c2505b9eb63e 100644 --- a/include/dt-bindings/reset/altr,rst-mgr-s10.h +++ b/include/dt-bindings/reset/altr,rst-mgr-s10.h @@ -22,7 +22,7 @@ #define USB0_RESET 35 #define USB1_RESET 36 #define NAND_RESET 37 -/* 38 is empty */ +#define COMBOPHY_RESET 38 #define SDMMC_RESET 39 #define EMAC0_OCP_RESET 40 #define EMAC1_OCP_RESET 41 From 1a8c89f8c112c75e84ff9a140f969e372aed0c9a Mon Sep 17 00:00:00 2001 From: Zhao Dongdong Date: Wed, 17 Jun 2026 11:16:27 +0800 Subject: [PATCH 071/479] reset: sunxi: fix memory region leak on ioremap failure In sunxi_reset_init(), when ioremap() fails, the memory region obtained via request_mem_region() is not released, leading to a resource leak. Add an err_mem_region label to properly release the memory region before freeing the data structure. Fixes: 8f1ae77f4666 ("reset: Add Allwinner SoCs Reset Controller Driver") Cc: stable@vger.kernel.org Signed-off-by: Zhao Dongdong Reviewed-by: Philipp Zabel Acked-by: Jernej Skrabec Signed-off-by: Philipp Zabel --- drivers/reset/reset-sunxi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/reset/reset-sunxi.c b/drivers/reset/reset-sunxi.c index 2544de6576e4..2f6df7707cad 100644 --- a/drivers/reset/reset-sunxi.c +++ b/drivers/reset/reset-sunxi.c @@ -44,7 +44,7 @@ static int sunxi_reset_init(struct device_node *np) data->membase = ioremap(res.start, size); if (!data->membase) { ret = -ENOMEM; - goto err_alloc; + goto err_mem_region; } spin_lock_init(&data->lock); @@ -57,6 +57,8 @@ static int sunxi_reset_init(struct device_node *np) return reset_controller_register(&data->rcdev); +err_mem_region: + release_mem_region(res.start, size); err_alloc: kfree(data); return ret; From 71827776667f4e4677a4fa806bcfb24d4b8dd9d7 Mon Sep 17 00:00:00 2001 From: Robby Cai Date: Fri, 19 Jun 2026 15:31:15 +0800 Subject: [PATCH 072/479] reset: imx7: Correct polarity of MIPI CSI resets on i.MX8MQ On i.MX8MQ, the MIPI CSI reset lines are active-low and not self-clearing. Writing '0' asserts reset and it remains asserted until explicitly deasserted by software. This driver previously treated the MIPI CSI reset signals as active-high, which led to incorrect reset assert/deassert sequencing. This issue was exposed by commit 6d79bb8fd2aa ("media: imx8mq-mipi-csi2: Explicitly release reset"). Fix this by reflecting the correct reset polarity and ensuring proper reset handling. Fixes: c979dbf59987 ("reset: imx7: Add support for i.MX8MQ IP block variant") Cc: stable@vger.kernel.org # 6d79bb8fd2aa: media: imx8mq-mipi-csi2: Explicitly release reset Reviewed-by: Philipp Zabel Signed-off-by: Robby Cai Reviewed-by: Guoniu Zhou Reviewed-by: Frank Li Signed-off-by: Philipp Zabel --- drivers/reset/reset-imx7.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/reset/reset-imx7.c b/drivers/reset/reset-imx7.c index dd01fe11c5cb..a3cb8244d76a 100644 --- a/drivers/reset/reset-imx7.c +++ b/drivers/reset/reset-imx7.c @@ -236,6 +236,12 @@ static int imx8mq_reset_set(struct reset_controller_dev *rcdev, case IMX8MQ_RESET_PCIE_CTRL_APPS_EN: case IMX8MQ_RESET_PCIE2_CTRL_APPS_EN: + case IMX8MQ_RESET_MIPI_CSI1_CORE_RESET: + case IMX8MQ_RESET_MIPI_CSI1_PHY_REF_RESET: + case IMX8MQ_RESET_MIPI_CSI1_ESC_RESET: + case IMX8MQ_RESET_MIPI_CSI2_CORE_RESET: + case IMX8MQ_RESET_MIPI_CSI2_PHY_REF_RESET: + case IMX8MQ_RESET_MIPI_CSI2_ESC_RESET: case IMX8MQ_RESET_MIPI_DSI_PCLK_RESET_N: case IMX8MQ_RESET_MIPI_DSI_ESC_RESET_N: case IMX8MQ_RESET_MIPI_DSI_DPI_RESET_N: From c283e9ada7fcb7dd4b10592623086b2e6d2f9925 Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Sat, 27 Jun 2026 02:40:23 +0000 Subject: [PATCH 073/479] xfrm: fix sk_dst_cache double-free in xfrm_user_policy() xfrm_user_policy() clears the socket dst cache with __sk_dst_reset(), i.e. the non-atomic __sk_dst_set(sk, NULL): it reads sk_dst_cache with rcu_dereference_protected(), stores NULL and dst_release()s the old dst. That is only safe if no other thread modifies sk_dst_cache concurrently. For a connected UDP socket that does not hold: the transmit fast path (udp_sendmsg -> sk_dst_check -> sk_dst_reset) resets the cache locklessly with an atomic xchg(). A per-socket policy change racing a send can make both sides observe the same old dst and each dst_release() it, dropping the socket's single reference twice and freeing the xfrm_dst bundle while it is still referenced: BUG: KASAN: slab-use-after-free in dst_release Write of size 4 at addr ffff88801897b6c0 by task exploit/155 Call Trace: ... dst_release (... ./include/linux/rcuref.h:109) xfrm_user_policy (./include/net/sock.h:2239 ./include/net/sock.h:2256 net/xfrm/xfrm_state.c:3053) do_ip_setsockopt (net/ipv4/ip_sockglue.c:1347) ip_setsockopt (net/ipv4/ip_sockglue.c:1417) do_sock_setsockopt (net/socket.c:2368) __sys_setsockopt (net/socket.c:2393) __x64_sys_setsockopt (net/socket.c:2396) do_syscall_64 (arch/x86/entry/syscall_64.c:94) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Reachable by an unprivileged user via a user+network namespace. Use the atomic sk_dst_reset() so the cache is cleared and released with a single xchg(): whichever side wins releases the dst once, the other sees NULL and does nothing. Behaviour is otherwise unchanged. Fixes: 2b06cdf3e688 ("xfrm: Clear sk_dst_cache when applying per-socket policy.") Fixes: be8f8284cd89 ("net: xfrm: allow clearing socket xfrm policies.") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_state.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index c58cd024e3c6..08ba6805ddb3 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -3010,7 +3010,7 @@ int xfrm_user_policy(struct sock *sk, int optname, sockptr_t optval, int optlen) if (sockptr_is_null(optval) && !optlen) { xfrm_sk_policy_insert(sk, XFRM_POLICY_IN, NULL); xfrm_sk_policy_insert(sk, XFRM_POLICY_OUT, NULL); - __sk_dst_reset(sk); + sk_dst_reset(sk); return 0; } @@ -3050,7 +3050,7 @@ int xfrm_user_policy(struct sock *sk, int optname, sockptr_t optval, int optlen) if (err >= 0) { xfrm_sk_policy_insert(sk, err, pol); xfrm_pol_put(pol); - __sk_dst_reset(sk); + sk_dst_reset(sk); err = 0; } From c4a5f0071cc6d378a0e7151b60d85986aefac1f3 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 27 Jun 2026 11:50:58 +0800 Subject: [PATCH 074/479] xfrm: cache the offload ifindex for netlink dumps copy_to_user_state_extra() only holds a reference to the outer xfrm_state. That does not pin x->xso.dev. NETDEV_DOWN and NETDEV_UNREGISTER can race through xfrm_dev_state_flush(), xfrm_state_delete(), and xfrm_dev_state_free(), which clears xso->dev and drops the netdev reference before the GETSA dump reaches xso_to_xuo() and reads xso->dev->ifindex. The buggy scenario involves two paths, with each column showing the order within that path: XFRM_MSG_GETSA dump path: NETDEV teardown path: 1. xfrm_get_sa() gets xfrm_state 1. xfrm_dev_state_flush() finds x 2. copy_to_user_state_extra() sees 2. xfrm_state_delete() removes x x->xso.dev from the SAD 3. copy_user_offload() calls 3. xfrm_dev_state_free() clears xso_to_xuo() xso->dev 4. xso->dev->ifindex dereferences 4. netdev_put() drops the device a detached net_device reference Avoid following the live net_device from the dump paths. Cache the attached ifindex in xfrm_dev_offload when state or policy offload is bound to a device, and serialize that snapshot instead. This preserves the user-visible XFRMA_OFFLOAD_DEV value without depending on the embedded net_device lifetime. Validation reproduced this kernel report: Oops: general protection fault Call Trace: copy_to_user_state_extra+0xb8d/0x1370 [xfrm_user] ? __pfx_copy_to_user_state_extra+0x10/0x10 [xfrm_user] ? __asan_memset+0x23/0x50 ? srso_alias_return_thunk+0x5/0xfbef5 ? __alloc_skb+0x342/0x960 ? srso_alias_return_thunk+0x5/0xfbef5 ? __asan_memset+0x23/0x50 ? srso_alias_return_thunk+0x5/0xfbef5 ? __nlmsg_put+0x147/0x1b0 dump_one_state+0x1c7/0x3e0 [xfrm_user] xfrm_state_netlink+0xcb/0x130 [xfrm_user] ? __pfx_xfrm_state_netlink+0x10/0x10 [xfrm_user] ? srso_alias_return_thunk+0x5/0xfbef5 ? xfrm_user_state_lookup.constprop.0+0x230/0x310 [xfrm_user] xfrm_get_sa+0x102/0x250 [xfrm_user] ? __pfx_xfrm_get_sa+0x10/0x10 [xfrm_user] xfrm_user_rcv_msg+0x504/0xaa0 [xfrm_user] ? __pfx_xfrm_user_rcv_msg+0x10/0x10 [xfrm_user] ? srso_alias_return_thunk+0x5/0xfbef5 ? stack_trace_save+0x8e/0xc0 ? __pfx_stack_trace_save+0x10/0x10 netlink_rcv_skb+0x11f/0x350 ? __pfx_xfrm_user_rcv_msg+0x10/0x10 [xfrm_user] ? __pfx_netlink_rcv_skb+0x10/0x10 ? __pfx_mutex_lock+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 xfrm_netlink_rcv+0x65/0x80 [xfrm_user] netlink_unicast+0x600/0x870 ? __pfx_netlink_unicast+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_stack_trace_save+0x10/0x10 netlink_sendmsg+0x75d/0xc10 ? __pfx_netlink_sendmsg+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ____sys_sendmsg+0x77a/0x900 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_____sys_sendmsg+0x10/0x10 ? __pfx_copy_msghdr_from_user+0x10/0x10 ? release_sock+0x1a/0x1d0 ? srso_alias_return_thunk+0x5/0xfbef5 ? netlink_insert+0x143/0xec0 ___sys_sendmsg+0xff/0x180 ? __pfx____sys_sendmsg+0x10/0x10 ? _raw_spin_lock_irqsave+0x85/0xe0 ? do_getsockname+0xf9/0x170 ? srso_alias_return_thunk+0x5/0xfbef5 ? fdget+0x53/0x3b0 __sys_sendmsg+0x111/0x1a0 ? __pfx___sys_sendmsg+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __sys_getsockname+0x8c/0x100 do_syscall_64+0x102/0x5a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: 07b87f9eea0c ("xfrm: Fix unregister netdevice hang on hardware offload.") Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Signed-off-by: Steffen Klassert --- include/net/xfrm.h | 2 ++ net/xfrm/xfrm_device.c | 1 + net/xfrm/xfrm_state.c | 1 + net/xfrm/xfrm_user.c | 38 +++++++++++++++++++++++++++++--------- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/include/net/xfrm.h b/include/net/xfrm.h index 519a0156a05c..a6d69aaa6cd2 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -162,6 +162,8 @@ struct xfrm_dev_offload { */ struct net_device *real_dev; unsigned long offload_handle; + /* Snapshot the attached device index for dump paths. */ + int ifindex; u8 dir : 2; u8 type : 2; u8 flags : 2; diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c index aec1e1184a71..f153bf695b9d 100644 --- a/net/xfrm/xfrm_device.c +++ b/net/xfrm/xfrm_device.c @@ -321,6 +321,7 @@ int xfrm_dev_state_add(struct net *net, struct xfrm_state *x, } xso->dev = dev; + xso->ifindex = dev->ifindex; netdev_tracker_alloc(dev, &xso->dev_tracker, GFP_ATOMIC); if (xuo->flags & XFRM_OFFLOAD_INBOUND) diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 08ba6805ddb3..10e5a1a95fe3 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -1547,6 +1547,7 @@ xfrm_state_find(const xfrm_address_t *daddr, const xfrm_address_t *saddr, xso->type = XFRM_DEV_OFFLOAD_PACKET; xso->dir = xdo->dir; xso->dev = dev; + xso->ifindex = dev->ifindex; xso->flags = XFRM_DEV_OFFLOAD_FLAG_ACQ; netdev_hold(dev, &xso->dev_tracker, GFP_ATOMIC); error = dev->xfrmdev_ops->xdo_dev_state_add(dev, x, diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index 6384795ee6b2..0eb87fc998d1 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -1201,17 +1201,26 @@ static int copy_sec_ctx(struct xfrm_sec_ctx *s, struct sk_buff *skb) return 0; } -static void xso_to_xuo(const struct xfrm_dev_offload *xso, - struct xfrm_user_offload *xuo) +static void xso_to_xuo_ifindex(const struct xfrm_dev_offload *xso, int ifindex, + struct xfrm_user_offload *xuo) { - xuo->ifindex = xso->dev->ifindex; + xuo->ifindex = ifindex; if (xso->dir == XFRM_DEV_OFFLOAD_IN) xuo->flags = XFRM_OFFLOAD_INBOUND; if (xso->type == XFRM_DEV_OFFLOAD_PACKET) xuo->flags |= XFRM_OFFLOAD_PACKET; } -static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb) +#ifdef CONFIG_XFRM_MIGRATE +static void xso_to_xuo(const struct xfrm_dev_offload *xso, + struct xfrm_user_offload *xuo) +{ + xso_to_xuo_ifindex(xso, xso->dev->ifindex, xuo); +} +#endif + +static int copy_user_offload_ifindex(const struct xfrm_dev_offload *xso, + int ifindex, struct sk_buff *skb) { struct xfrm_user_offload *xuo; struct nlattr *attr; @@ -1222,11 +1231,22 @@ static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb) xuo = nla_data(attr); memset(xuo, 0, sizeof(*xuo)); - xso_to_xuo(xso, xuo); + xso_to_xuo_ifindex(xso, ifindex, xuo); return 0; } +static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb) +{ + return copy_user_offload_ifindex(xso, xso->dev->ifindex, skb); +} + +static int copy_user_state_offload(const struct xfrm_dev_offload *xso, + struct sk_buff *skb) +{ + return copy_user_offload_ifindex(xso, READ_ONCE(xso->ifindex), skb); +} + static bool xfrm_redact(void) { return IS_ENABLED(CONFIG_SECURITY) && @@ -1433,8 +1453,8 @@ static int copy_to_user_state_extra(struct xfrm_state *x, &x->replay); if (ret) goto out; - if(x->xso.dev) - ret = copy_user_offload(&x->xso, skb); + if (READ_ONCE(x->xso.dev)) + ret = copy_user_state_offload(&x->xso, skb); if (ret) goto out; if (x->if_id) { @@ -4046,8 +4066,8 @@ static inline unsigned int xfrm_sa_len(struct xfrm_state *x) l += nla_total_size(sizeof(*x->coaddr)); if (x->props.extra_flags) l += nla_total_size(sizeof(x->props.extra_flags)); - if (x->xso.dev) - l += nla_total_size(sizeof(struct xfrm_user_offload)); + if (READ_ONCE(x->xso.dev)) + l += nla_total_size(sizeof(struct xfrm_user_offload)); if (x->props.smark.v | x->props.smark.m) { l += nla_total_size(sizeof(x->props.smark.v)); l += nla_total_size(sizeof(x->props.smark.m)); From ea528f18231ec0f33317be57f8866913b19aba6e Mon Sep 17 00:00:00 2001 From: Antony Antony Date: Sat, 27 Jun 2026 10:23:43 +0200 Subject: [PATCH 075/479] xfrm: reject optional IPTFS templates in outbound policies syzbot reported a stack-out-of-bounds read in xfrm_state_find() which flows from xfrm_tmpl_resolve_one(). Commit 3d776e31c841 ("xfrm: Reject optional tunnel/BEET mode templates in outbound policies") disallowed optional tunnel and BEET in outbound policies to prevent this. Later when IPTFS added, it was not covered by that fix and can still trigger the out-of-bounds read; Extend the check to disallow optional IPTFS in outbound policies as well. IPTFS should be identical to tunnel mode. IN and FWD policies are not affected: xfrm_tmpl_resolve_one() is only reachable via the outbound path. Reproducer, before: ip link add dummy0 type dummy ip link set dummy0 up ip addr add 10.1.1.1/24 dev dummy0 ip xfrm policy add src 10.1.1.1/32 dst 10.1.1.2/32 dir out tmpl src fc00::dead:1 dst fc00::dead:2 proto esp reqid 1 mode iptfs level use tmpl src fc00::dead:1 dst fc00::dead:2 proto esp reqid 2 mode transport ping -W 1 -c 1 10.1.1.2 PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data. [ 64.168420] ================================================================== [ 64.169977] BUG: KASAN: stack-out-of-bounds in __xfrm6_addr_hash+0x11e/0x170 [ 64.169977] Read of size 4 at addr ffff88800e1ffd20 by task ping/2844 [ 64.169977] CPU: 2 UID: 0 PID: 2844 Comm: ping Not tainted 7.1.0-rc7-00180-geb23b588430a #98 PREEMPT(full) [ 64.169977] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 64.169977] Call Trace: [ 64.169977] [ 64.169977] dump_stack_lvl+0x47/0x70 [ 64.169977] ? __xfrm6_addr_hash+0x11e/0x170 [ 64.169977] print_report+0x152/0x4b0 [ 64.169977] ? ksys_mmap_pgoff+0x6d/0xa0 [ 64.169977] ? entry_SYSCALL_64_after_hwframe+0x76/0x7e [ 64.169977] ? rcu_read_unlock_sched+0xa/0x20 [ 64.169977] ? __virt_addr_valid+0x21b/0x230 [ 64.169977] ? __xfrm6_addr_hash+0x11e/0x170 [ 64.169977] kasan_report+0xa8/0xd0 [ 64.169977] ? __xfrm6_addr_hash+0x11e/0x170 [ 64.169977] __xfrm6_addr_hash+0x11e/0x170 [ 64.169977] __xfrm_dst_hash+0x24/0xc0 [ 64.169977] xfrm_state_find+0xa2d/0x2f90 [ 64.169977] ? __pfx_xfrm_state_find+0x10/0x10 [ 64.169977] ? __pfx_ftrace_graph_ret_addr+0x10/0x10 [ 64.169977] ? __pfx_ftrace_graph_ret_addr+0x10/0x10 [ 64.169977] xfrm_tmpl_resolve_one+0x210/0x570 [ 64.169977] ? __pfx_xfrm_tmpl_resolve_one+0x10/0x10 [ 64.169977] ? __pfx_stack_trace_consume_entry+0x10/0x10 [ 64.169977] ? kernel_text_address+0x5b/0x80 [ 64.169977] ? __kernel_text_address+0xe/0x30 [ 64.169977] ? unwind_get_return_address+0x5e/0x90 [ 64.169977] ? arch_stack_walk+0x8c/0xe0 [ 64.169977] xfrm_tmpl_resolve+0x130/0x200 [ 64.169977] ? __pfx_xfrm_tmpl_resolve+0x10/0x10 [ 64.169977] ? __pfx_xfrm_policy_inexact_lookup_rcu+0x10/0x10 [ 64.169977] ? __refcount_add_not_zero.constprop.0+0xb2/0x110 [ 64.169977] ? __pfx___refcount_add_not_zero.constprop.0+0x10/0x10 [ 64.169977] xfrm_resolve_and_create_bundle+0xd5/0x310 [ 64.169977] ? __pfx_xfrm_resolve_and_create_bundle+0x10/0x10 [ 64.169977] ? __pfx_xfrm_policy_lookup_bytype+0x10/0x10 [ 64.169977] ? __pfx_xfrm_policy_lookup_bytype+0x10/0x10 [ 64.169977] xfrm_lookup_with_ifid+0x3d8/0xb80 [ 64.169977] ? __pfx_xfrm_lookup_with_ifid+0x10/0x10 [ 64.169977] ? ip_route_output_key_hash+0xc6/0x110 [ 64.169977] ? kasan_save_track+0x10/0x30 [ 64.169977] xfrm_lookup_route+0x18/0xe0 [ 64.169977] ip4_datagram_release_cb+0x4c9/0x530 [ 64.169977] ? __pfx_ip4_datagram_release_cb+0x10/0x10 [ 64.169977] ? do_raw_spin_lock+0x71/0xc0 [ 64.169977] ? __pfx_do_raw_spin_lock+0x10/0x10 [ 64.169977] release_sock+0xb0/0x170 [ 64.169977] udp_connect+0x43/0x50 [ 64.169977] __sys_connect+0xa6/0x100 [ 64.169977] ? alloc_fd+0x2e9/0x300 [ 64.169977] ? __pfx___sys_connect+0x10/0x10 [ 64.169977] ? preempt_latency_start+0x1f/0x70 [ 64.169977] ? fd_install+0x7e/0x150 [ 64.169977] ? rcu_read_unlock_sched+0xa/0x20 [ 64.169977] ? __sys_socket+0xdf/0x130 [ 64.169977] ? __pfx___sys_socket+0x10/0x10 [ 64.169977] ? vma_refcount_put+0x43/0xa0 [ 64.169977] __x64_sys_connect+0x7e/0x90 [ 64.169977] do_syscall_64+0x11b/0x2b0 [ 64.169977] entry_SYSCALL_64_after_hwframe+0x76/0x7e [ 64.169977] RIP: 0033:0x7f4851ecb570 [ 64.169977] Code: 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 80 3d f9 ca 0d 00 00 74 17 b8 2a 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 58 c3 0f 1f 80 00 00 00 00 48 83 ec 18 89 54 [ 64.169977] RSP: 002b:00007ffc830e3498 EFLAGS: 00000202 ORIG_RAX: 000000000000002a [ 64.169977] RAX: ffffffffffffffda RBX: 00007ffc830e34d0 RCX: 00007f4851ecb570 [ 64.169977] RDX: 0000000000000010 RSI: 00007ffc830e34d0 RDI: 0000000000000005 [ 64.169977] RBP: 0000000000000000 R08: 0000000000000003 R09: 0000000000000000 [ 64.169977] R10: 0000000000000006 R11: 0000000000000202 R12: 0000000000000005 [ 64.169977] R13: 0000000000000000 R14: 00005619a863f340 R15: 0000000000000000 [ 64.169977] [ 64.169977] The buggy address belongs to stack of task ping/2844 [ 64.169977] and is located at offset 88 in frame: [ 64.169977] ip4_datagram_release_cb+0x0/0x530 [ 64.169977] This frame has 1 object: [ 64.169977] [32, 88) 'fl4' [ 64.169977] The buggy address belongs to the physical page: [ 64.169977] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0xe1ff [ 64.169977] flags: 0x4000000000000000(zone=1) [ 64.169977] raw: 4000000000000000 0000000000000000 ffffea0000387fc8 0000000000000000 [ 64.169977] raw: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000 [ 64.169977] page dumped because: kasan: bad access detected [ 64.169977] Memory state around the buggy address: [ 64.169977] ffff88800e1ffc00: f2 f2 00 00 f3 f3 00 00 00 00 00 00 00 00 00 00 [ 64.169977] ffff88800e1ffc80: 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 00 [ 64.169977] >ffff88800e1ffd00: 00 00 00 00 f3 f3 f3 f3 f3 00 00 00 00 00 00 00 [ 64.169977] ^ [ 64.169977] ffff88800e1ffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 [ 64.169977] ffff88800e1ffe00: f1 f1 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 64.169977] ================================================================== [ 64.245153] Disabling lock debugging due to kernel taint After the fix: ip xfrm policy add src 10.1.1.1/32 dst 10.1.1.2/32 dir out tmpl \ src fc00::dead:1 dst fc00::dead:2 proto esp reqid 1 mode iptfs \ level use tmpl src fc00::dead:1 dst fc00::dead:2 proto esp reqid 2 \ mode transport Error: Mode in optional template not allowed in outbound policy. Fixes: d1716d5a44c3 ("xfrm: add generic iptfs defines and functionality") Reported-by: syzbot+0ac4d84afe1066a1f3e9@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a3ceb94.43b4ff68.30a095.0004.GAE@google.com/T/ Signed-off-by: Antony Antony Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_user.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index 0eb87fc998d1..d6db63304ba6 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -2124,13 +2124,12 @@ static int validate_tmpl(int nr, struct xfrm_user_tmpl *ut, u16 family, switch (ut[i].mode) { case XFRM_MODE_TUNNEL: case XFRM_MODE_BEET: + case XFRM_MODE_IPTFS: if (ut[i].optional && dir == XFRM_POLICY_OUT) { NL_SET_ERR_MSG(extack, "Mode in optional template not allowed in outbound policy"); return -EINVAL; } break; - case XFRM_MODE_IPTFS: - break; default: if (ut[i].family != prev_family) { NL_SET_ERR_MSG(extack, "Mode in template doesn't support a family change"); From 18313833e2c6de222a4f6c072da759d0d5888528 Mon Sep 17 00:00:00 2001 From: Or Gerlitz Date: Wed, 17 Jun 2026 14:21:05 +0300 Subject: [PATCH 076/479] RDMA/cma: Fix hardware address comparison length in netevent callback The cited commit hardcoded the hardware address comparison len to ETH_ALEN. This breaks IPoIB, which uses 20-byte addresses. By truncating the memcmp, the CMA may incorrectly assume the target address is unchanged and fails to abort the stalled connection. Fix this by replacing ETH_ALEN with the dynamic neigh->dev->addr_len to correctly evaluate the full address regardless of the link layer. Fixes: 925d046e7e52 ("RDMA/core: Add a netevent notifier to cma") Signed-off-by: Or Gerlitz Link: https://patch.msgid.link/20260617-fix-cma-ipoib-v1-1-03f869344304@ddn.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/cma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/core/cma.c b/drivers/infiniband/core/cma.c index 9480d1a51c11..e88d3efb967b 100644 --- a/drivers/infiniband/core/cma.c +++ b/drivers/infiniband/core/cma.c @@ -5270,7 +5270,7 @@ static int cma_netevent_callback(struct notifier_block *self, list_for_each_entry(current_id, &ips_node->id_list, id_list_entry) { if (!memcmp(current_id->id.route.addr.dev_addr.dst_dev_addr, - neigh->ha, ETH_ALEN)) + neigh->ha, neigh->dev->addr_len)) continue; cma_id_get(current_id); if (!queue_work(cma_wq, ¤t_id->id.net_work)) From a846aecb931b4d65d5eafa92a0623545af46d4f2 Mon Sep 17 00:00:00 2001 From: Jacob Moroni Date: Wed, 17 Jun 2026 14:19:36 +0000 Subject: [PATCH 077/479] RDMA/irdma: Prevent rereg_mr for non-mem regions When a QP/CQ/SRQ is created, a two step process is used where the buffer is allocated in userspace and explicitly registered with the normal reg_mr mechanism prior to creating the actual QP/CQ/SRQ object. These special registrations are indicated via an ABI field so the driver knows that they do not have a valid mkey and to skip the actual CQP command submission. Since these are real MR objects from the core's perspective, it is possible for a user application to invoke rereg_mr on them and cause a real CQP op to be emitted with the zero-initialized mkey value of 0. Fix this by preventing rereg_mr on these special regions. Fixes: 5ac388db27c4 ("RDMA/irdma: Add support to re-register a memory region") Signed-off-by: Jacob Moroni Reviewed-by: David Hu Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/irdma/verbs.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/infiniband/hw/irdma/verbs.c b/drivers/infiniband/hw/irdma/verbs.c index cb54c7c8fcd8..23ab8286dc12 100644 --- a/drivers/infiniband/hw/irdma/verbs.c +++ b/drivers/infiniband/hw/irdma/verbs.c @@ -3791,6 +3791,9 @@ static struct ib_mr *irdma_rereg_user_mr(struct ib_mr *ib_mr, int flags, if (flags & ~(IB_MR_REREG_TRANS | IB_MR_REREG_PD | IB_MR_REREG_ACCESS)) return ERR_PTR(-EOPNOTSUPP); + if (iwmr->type != IRDMA_MEMREG_TYPE_MEM) + return ERR_PTR(-EINVAL); + ret = ib_umem_check_rereg(iwmr->region, flags, new_access); if (ret) return ERR_PTR(ret); From b9b0889071569d43623c260074e159cd8f26adb1 Mon Sep 17 00:00:00 2001 From: Jacob Moroni Date: Wed, 17 Jun 2026 16:40:13 +0000 Subject: [PATCH 078/479] RDMA/irdma: Prevent user-triggered null deref on QP create Previously, the user QP creation path would only attempt to populate iwqp->iwpbl if the user-provided req.user_wqe_bufs field was non-zero. The problem is that iwqp->iwpbl is unconditionally dereferenced later on in irdma_setup_virt_qp. While there was a check for iwqp->iwpbl != NULL, this check would only occur if req.user_wqe_bufs was non-zero. The end result is that a user could send a zero user_wqe_bufs value and trigger a null ptr deref. Fix this by unconditionally calling irdma_get_pbl and bailing if it fails, similar to the CQ and SRQ paths. Fixes: b48c24c2d710 ("RDMA/irdma: Implement device supported verb APIs") Link: https://patch.msgid.link/r/20260617164013.280790-1-jmoroni@google.com Signed-off-by: Jacob Moroni Reviewed-by: David Hu Signed-off-by: Jason Gunthorpe --- drivers/infiniband/hw/irdma/verbs.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/infiniband/hw/irdma/verbs.c b/drivers/infiniband/hw/irdma/verbs.c index 23ab8286dc12..be8c5cf12f7f 100644 --- a/drivers/infiniband/hw/irdma/verbs.c +++ b/drivers/infiniband/hw/irdma/verbs.c @@ -633,17 +633,16 @@ static int irdma_setup_umode_qp(struct ib_udata *udata, iwqp->ctx_info.qp_compl_ctx = req.user_compl_ctx; iwqp->user_mode = 1; - if (req.user_wqe_bufs) { - spin_lock_irqsave(&ucontext->qp_reg_mem_list_lock, flags); - iwqp->iwpbl = irdma_get_pbl((unsigned long)req.user_wqe_bufs, - &ucontext->qp_reg_mem_list); - spin_unlock_irqrestore(&ucontext->qp_reg_mem_list_lock, flags); - if (!iwqp->iwpbl) { - ret = -ENODATA; - ibdev_dbg(&iwdev->ibdev, "VERBS: no pbl info\n"); - return ret; - } + spin_lock_irqsave(&ucontext->qp_reg_mem_list_lock, flags); + iwqp->iwpbl = irdma_get_pbl((unsigned long)req.user_wqe_bufs, + &ucontext->qp_reg_mem_list); + spin_unlock_irqrestore(&ucontext->qp_reg_mem_list_lock, flags); + + if (!iwqp->iwpbl) { + ret = -ENODATA; + ibdev_dbg(&iwdev->ibdev, "VERBS: no pbl info\n"); + return ret; } if (!ucontext->use_raw_attrs) { From 2815a277c53e9a84784d6410cd55a9da5b33068d Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 18 Jun 2026 12:17:51 +0800 Subject: [PATCH 079/479] RDMA/erdma: initialize ret for empty receive WR lists erdma_post_recv() returns ret after walking the receive work request list. If the caller passes an empty list, the loop is skipped and ret is not assigned. Initialize ret to 0 so an empty receive work request list returns success instead of stack data. Fixes: 155055771704 ("RDMA/erdma: Add verbs implementation") Link: https://patch.msgid.link/r/20260618041752.481193-1-ruoyuw560@gmail.com Signed-off-by: Ruoyu Wang Reviewed-by: Cheng Xu Signed-off-by: Jason Gunthorpe --- drivers/infiniband/hw/erdma/erdma_qp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/erdma/erdma_qp.c b/drivers/infiniband/hw/erdma/erdma_qp.c index 25f6c49aec77..e002343832f7 100644 --- a/drivers/infiniband/hw/erdma/erdma_qp.c +++ b/drivers/infiniband/hw/erdma/erdma_qp.c @@ -734,7 +734,7 @@ int erdma_post_recv(struct ib_qp *ibqp, const struct ib_recv_wr *recv_wr, const struct ib_recv_wr *wr = recv_wr; struct erdma_qp *qp = to_eqp(ibqp); unsigned long flags; - int ret; + int ret = 0; spin_lock_irqsave(&qp->lock, flags); From 155fd5ce2382b0ffbec0d7ee7b3a6818a27a5aed Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 18 Jun 2026 12:17:52 +0800 Subject: [PATCH 080/479] RDMA/mana_ib: initialize err for empty send WR lists mana_ib_post_send() returns err after walking the send work request list. If the caller passes an empty list, the loop is skipped and err is not assigned. Initialize err to 0 so an empty send work request list returns success instead of stack data. Fixes: c8017f5b4856 ("RDMA/mana_ib: UD/GSI work requests") Link: https://patch.msgid.link/r/20260618041752.481193-2-ruoyuw560@gmail.com Signed-off-by: Ruoyu Wang Reviewed-by: Long Li Signed-off-by: Jason Gunthorpe --- drivers/infiniband/hw/mana/wr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/mana/wr.c b/drivers/infiniband/hw/mana/wr.c index 1813567d3b16..36a1d506f08f 100644 --- a/drivers/infiniband/hw/mana/wr.c +++ b/drivers/infiniband/hw/mana/wr.c @@ -144,7 +144,7 @@ static int mana_ib_post_send_ud(struct mana_ib_qp *qp, const struct ib_ud_wr *wr int mana_ib_post_send(struct ib_qp *ibqp, const struct ib_send_wr *wr, const struct ib_send_wr **bad_wr) { - int err; + int err = 0; struct mana_ib_qp *qp = container_of(ibqp, struct mana_ib_qp, ibqp); for (; wr; wr = wr->next) { From e939334ea7dd219f100f963dbb1cb43df520c20a Mon Sep 17 00:00:00 2001 From: Chenguang Zhao Date: Thu, 25 Jun 2026 10:01:48 +0800 Subject: [PATCH 081/479] RDMA/core: Fix memory leak in __ib_create_cq() on invalid cqe Move the zero CQE validation before rdma_zalloc_drv_obj() to avoid leaking the CQ object when returning -EINVAL. Fixes: a2917582887a ("RDMA/core: Reject zero CQE count") Link: https://patch.msgid.link/r/20260625020148.224537-1-zhaochenguang@kylinos.cn Signed-off-by: Chenguang Zhao Reviewed-by: Kalesh AP Signed-off-by: Jason Gunthorpe --- drivers/infiniband/core/verbs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/infiniband/core/verbs.c b/drivers/infiniband/core/verbs.c index 3b613b57e269..86811d31092c 100644 --- a/drivers/infiniband/core/verbs.c +++ b/drivers/infiniband/core/verbs.c @@ -2196,13 +2196,13 @@ struct ib_cq *__ib_create_cq(struct ib_device *device, struct ib_cq *cq; int ret; + if (WARN_ON_ONCE(!cq_attr->cqe)) + return ERR_PTR(-EINVAL); + cq = rdma_zalloc_drv_obj(device, ib_cq); if (!cq) return ERR_PTR(-ENOMEM); - if (WARN_ON_ONCE(!cq_attr->cqe)) - return ERR_PTR(-EINVAL); - cq->device = device; cq->comp_handler = comp_handler; cq->event_handler = event_handler; From 9f0f2d2121f16d420199a82ac5bbc242269133b3 Mon Sep 17 00:00:00 2001 From: Danila Chernetsov Date: Sat, 27 Jun 2026 09:59:51 +0000 Subject: [PATCH 082/479] RDMA/hns: Fix potential integer overflow in mhop hem cleanup In hns_roce_cleanup_mhop_hem_table(), the expression: obj = i * buf_chunk_size / table->obj_size; is evaluated using 32-bit unsigned arithmetic because 'buf_chunk_size' is u32 and the usual arithmetic conversions convert 'i' to unsigned int. The result is assigned to a u64 variable, but the multiplication may overflow before the assignment. For sufficiently large HEM tables, this produces an incorrect object index passed to hns_roce_table_mhop_put(). Cast 'i' to u64 before the multiplication so that the intermediate calculation is performed with 64-bit arithmetic. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: a25d13cbe816 ("RDMA/hns: Add the interfaces to support multi hop addressing for the contexts in hip08") Link: https://patch.msgid.link/r/20260627095951.51378-1-listdansp@mail.ru Signed-off-by: Danila Chernetsov Reviewed-by: Junxian Huang Signed-off-by: Jason Gunthorpe --- drivers/infiniband/hw/hns/hns_roce_hem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/hns/hns_roce_hem.c b/drivers/infiniband/hw/hns/hns_roce_hem.c index 7041a8e9134b..92edec4fa61b 100644 --- a/drivers/infiniband/hw/hns/hns_roce_hem.c +++ b/drivers/infiniband/hw/hns/hns_roce_hem.c @@ -836,7 +836,7 @@ static void hns_roce_cleanup_mhop_hem_table(struct hns_roce_dev *hr_dev, mhop.bt_chunk_size; for (i = 0; i < table->num_hem; ++i) { - obj = i * buf_chunk_size / table->obj_size; + obj = (u64)i * buf_chunk_size / table->obj_size; if (table->hem[i]) hns_roce_table_mhop_put(hr_dev, table, obj, 0); } From bb27fcc67c429d97f785c92c35a6c5adebb05d7f Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Tue, 30 Jun 2026 14:00:40 +0800 Subject: [PATCH 083/479] RDMA/siw: publish QP after initialization siw_create_qp() currently calls siw_qp_add() before the queues, CQ pointers, state, completion, and device list entry are ready. A QPN lookup can therefore reach a QP that is still being constructed. Move siw_qp_add() to the end of siw_create_qp(), after QP initialization and before adding the QP to the siw device list. Fixes: f29dd55b0236 ("rdma/siw: queue pair methods") Link: https://patch.msgid.link/r/20260630060040.966461-1-ruoyuw560@gmail.com Suggested-by: Bernard Metzler Signed-off-by: Ruoyu Wang Acked-by: Bernard Metzler Signed-off-by: Jason Gunthorpe --- drivers/infiniband/sw/siw/siw_verbs.c | 44 +++++++++++++++------------ 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/drivers/infiniband/sw/siw/siw_verbs.c b/drivers/infiniband/sw/siw/siw_verbs.c index b74ac85c1b8b..2349c08c32ed 100644 --- a/drivers/infiniband/sw/siw/siw_verbs.c +++ b/drivers/infiniband/sw/siw/siw_verbs.c @@ -318,6 +318,7 @@ int siw_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, struct siw_ucontext *uctx = rdma_udata_to_drv_context(udata, struct siw_ucontext, base_ucontext); + struct siw_uresp_create_qp uresp = {}; unsigned long flags; int num_sqe, num_rqe, rv = 0; size_t length; @@ -371,11 +372,6 @@ int siw_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, spin_lock_init(&qp->rq_lock); spin_lock_init(&qp->orq_lock); - rv = siw_qp_add(sdev, qp); - if (rv) - goto err_atomic; - - /* All queue indices are derived from modulo operations * on a free running 'get' (consumer) and 'put' (producer) * unsigned counter. Having queue sizes at power of two @@ -393,14 +389,14 @@ int siw_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, if (qp->sendq == NULL) { rv = -ENOMEM; - goto err_out_xa; + goto err_out; } if (attrs->sq_sig_type != IB_SIGNAL_REQ_WR) { if (attrs->sq_sig_type == IB_SIGNAL_ALL_WR) qp->attrs.flags |= SIW_SIGNAL_ALL_WR; else { rv = -EINVAL; - goto err_out_xa; + goto err_out; } } qp->pd = pd; @@ -426,7 +422,7 @@ int siw_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, if (qp->recvq == NULL) { rv = -ENOMEM; - goto err_out_xa; + goto err_out; } qp->attrs.rq_size = num_rqe; } @@ -441,11 +437,8 @@ int siw_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, qp->attrs.state = SIW_QP_STATE_IDLE; if (udata) { - struct siw_uresp_create_qp uresp = {}; - uresp.num_sqe = num_sqe; uresp.num_rqe = num_rqe; - uresp.qp_id = qp_id(qp); if (qp->sendq) { length = num_sqe * sizeof(struct siw_sqe); @@ -454,7 +447,7 @@ int siw_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, length, &uresp.sq_key); if (!qp->sq_entry) { rv = -ENOMEM; - goto err_out_xa; + goto err_out; } } @@ -466,9 +459,23 @@ int siw_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, if (!qp->rq_entry) { uresp.sq_key = SIW_INVAL_UOBJ_KEY; rv = -ENOMEM; - goto err_out_xa; + goto err_out; } } + } + qp->tx_cpu = siw_get_tx_cpu(sdev); + if (qp->tx_cpu < 0) { + rv = -EINVAL; + goto err_out; + } + init_completion(&qp->qp_free); + + rv = siw_qp_add(sdev, qp); + if (rv) + goto err_out_tx; + + if (udata) { + uresp.qp_id = qp_id(qp); if (udata->outlen < sizeof(uresp)) { rv = -EINVAL; @@ -478,22 +485,19 @@ int siw_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, if (rv) goto err_out_xa; } - qp->tx_cpu = siw_get_tx_cpu(sdev); - if (qp->tx_cpu < 0) { - rv = -EINVAL; - goto err_out_xa; - } + INIT_LIST_HEAD(&qp->devq); spin_lock_irqsave(&sdev->lock, flags); list_add_tail(&qp->devq, &sdev->qp_list); spin_unlock_irqrestore(&sdev->lock, flags); - init_completion(&qp->qp_free); - return 0; err_out_xa: xa_erase(&sdev->qp_xa, qp_id(qp)); +err_out_tx: + siw_put_tx_cpu(qp->tx_cpu); +err_out: if (uctx) { rdma_user_mmap_entry_remove(qp->sq_entry); rdma_user_mmap_entry_remove(qp->rq_entry); From b28d3d0841a6083f82dfa9fe940afcd748f2a1e2 Mon Sep 17 00:00:00 2001 From: Federico Lombardo Date: Thu, 2 Jul 2026 22:43:47 +0200 Subject: [PATCH 084/479] ALSA: hda/realtek: Enable mute LED quirk for HP Laptop 15-dw0xxx HP Laptop 15-dw0xxx with subsystem ID 103c:85f0 needs the ALC236_FIXUP_HP_MUTE_LED_COEFBIT2 quirk to correctly disable and enable the Audio Mute LED. Add the missing quirk entry. Signed-off-by: Federico Lombardo Link: https://patch.msgid.link/20260702204347.187524-1-lombardo.federico.lf@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index f7700713dc62..c54f55730fdc 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6992,6 +6992,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x856a, "HP Pavilion 15-cs1xxx", ALC295_FIXUP_HP_PAVILION_MUTE_LED_1B), SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), + SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x860f, "HP ZBook 15 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), From caa0ecbeff4f7fbf70f22bd8ca598918bffb1b78 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Fri, 26 Jun 2026 10:13:38 +0800 Subject: [PATCH 085/479] mtd: virt-concat: free duplicate generated name Every MTD registration runs mtd_virt_concat_create_join(). Once a virtual concat has already been registered, the function builds the same name again and takes the equal-name branch. That branch skips to the next item without freeing the newly allocated string. Free the temporary name before continuing. Fixes: 43db6366fc2d ("mtd: Add driver for concatenating devices") Cc: stable@vger.kernel.org Signed-off-by: Xu Rao Signed-off-by: Miquel Raynal --- drivers/mtd/mtd_virt_concat.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c index c872a8827718..da4277ced4d6 100644 --- a/drivers/mtd/mtd_virt_concat.c +++ b/drivers/mtd/mtd_virt_concat.c @@ -321,8 +321,10 @@ int mtd_virt_concat_create_join(void) if (concat->mtd.name) { ret = memcmp(concat->mtd.name, name, name_sz); - if (ret == 0) + if (ret == 0) { + kfree(name); continue; + } } mtd = mtd_concat_create(concat->subdev, concat->num_subdev, name); if (!mtd) { From 9d4af746af8ce27eefc2338b2feaa1e01f28b6c3 Mon Sep 17 00:00:00 2001 From: Xue Lei Date: Wed, 1 Jul 2026 20:10:42 +0800 Subject: [PATCH 086/479] mtd: fix double free and WARN_ON in add_mtd_device() error paths When device_register() or mtd_nvmem_add() fails inside add_mtd_device() for a partition, the error handling triggers mtd_release() via put_device() or device_unregister(). mtd_release() calls release_mtd_partition() which frees the mtd_info structure. However, callers such as mtd_add_partition() and add_mtd_partitions() also call free_partition() in their error paths, resulting in a double free. Additionally, release_mtd_partition() hits WARN_ON(!list_empty( &mtd->part.node)) because the partition node is still linked in the parent's partitions list when the release callback fires from the add_mtd_device() error path. Fix this by overriding dev->type and dev->release before put_device() in the error paths, so that device_release() invokes a no-op function instead of mtd_release(). For the mtd_nvmem_add() failure case, device_unregister() is replaced with device_del() to separate the device removal from the final kobject reference drop, allowing the override to take effect before put_device() is called. The callers' error paths (list_del + free_partition) remain the sole owners of mtd_info lifetime on add_mtd_device() failure, which is the expected contract. The normal partition teardown path is not affected: del_mtd_device() goes through kref_put() -> mtd_device_release() -> device_unregister() with dev->type still set to &mtd_devtype, so mtd_release() -> release_mtd_partition() continues to work correctly for the regular removal case. Reported-by: syzbot+e9c76b56dc05023b8117@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e9c76b56dc05023b8117 Fixes: 19bfa9ebebb5 ("mtd: use refcount to prevent corruption") Signed-off-by: Xue Lei Signed-off-by: Miquel Raynal --- drivers/mtd/mtdcore.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 576537774628..16629382a787 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -105,6 +105,15 @@ static void mtd_release(struct device *dev) device_destroy(&mtd_class, index + 1); } +/* + * No-op device release used in add_mtd_device() error paths. + * Prevents mtd_release() from being called via device_release(), + * which would free the mtd_info that the caller still manages. + */ +static void mtd_dev_release_nop(struct device *dev) +{ +} + static void mtd_device_release(struct kref *kref) { struct mtd_info *mtd = container_of(kref, struct mtd_info, refcnt); @@ -799,10 +808,8 @@ int add_mtd_device(struct mtd_info *mtd) mtd_check_of_node(mtd); of_node_get(mtd_get_of_node(mtd)); error = device_register(&mtd->dev); - if (error) { - put_device(&mtd->dev); + if (error) goto fail_added; - } /* Add the nvmem provider */ error = mtd_nvmem_add(mtd); @@ -840,8 +847,16 @@ int add_mtd_device(struct mtd_info *mtd) return 0; fail_nvmem_add: - device_unregister(&mtd->dev); + device_del(&mtd->dev); fail_added: + /* + * Clear type and set nop release to prevent mtd_release() -> + * release_mtd_partition() -> free_partition() from freeing mtd. + * The caller handles cleanup on failure. + */ + mtd->dev.type = NULL; + mtd->dev.release = mtd_dev_release_nop; + put_device(&mtd->dev); of_node_put(mtd_get_of_node(mtd)); fail_devname: idr_remove(&mtd_idr, i); From dbf590b662695b16fbf5917ef129697be4410ea9 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 3 Jul 2026 15:37:59 +0800 Subject: [PATCH 087/479] mtd: rawnand: lpc32xx_mlc: fail DMA transfers on timeout lpc32xx_xmit_dma() starts a DMA transfer and waits up to one second for its completion, but it ignores the wait result and returns success after unmapping the buffer. A timed out read can therefore return success with incomplete data, and a timed out write can continue the NAND operation without proof that the DMA payload reached the controller. Terminate the DMA channel on timeout, unmap the scatterlist through the existing cleanup path, and return -ETIMEDOUT to the NAND read/write callers. Initialize the shared cleanup-path result before using it for dmaengine_prep_slave_sg() failures. Fixes: 70f7cb78ec53 ("mtd: add LPC32xx MLC NAND driver") Cc: stable@vger.kernel.org Reviewed-by: Vladimir Zapolskiy Signed-off-by: Pengpeng Hou Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/lpc32xx_mlc.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/nand/raw/lpc32xx_mlc.c b/drivers/mtd/nand/raw/lpc32xx_mlc.c index 19b13ae536d4..8f6a89d9ba83 100644 --- a/drivers/mtd/nand/raw/lpc32xx_mlc.c +++ b/drivers/mtd/nand/raw/lpc32xx_mlc.c @@ -396,6 +396,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len, struct lpc32xx_nand_host *host = nand_get_controller_data(chip); struct dma_async_tx_descriptor *desc; int flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; + unsigned long time_left; int res; sg_init_one(&host->sgl, mem, len); @@ -410,6 +411,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len, flags); if (!desc) { dev_err(mtd->dev.parent, "Failed to prepare slave sg\n"); + res = -ENXIO; goto out1; } @@ -420,7 +422,13 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len, dmaengine_submit(desc); dma_async_issue_pending(host->dma_chan); - wait_for_completion_timeout(&host->comp_dma, msecs_to_jiffies(1000)); + time_left = wait_for_completion_timeout(&host->comp_dma, + msecs_to_jiffies(1000)); + if (!time_left) { + dmaengine_terminate_sync(host->dma_chan); + res = -ETIMEDOUT; + goto out1; + } dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, DMA_BIDIRECTIONAL); @@ -428,7 +436,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len, out1: dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, DMA_BIDIRECTIONAL); - return -ENXIO; + return res; } static int lpc32xx_read_page(struct nand_chip *chip, uint8_t *buf, From 17a8ce84964f243c8f89dc7353ac7e8d3137bc74 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 3 Jul 2026 15:39:43 +0800 Subject: [PATCH 088/479] mtd: rawnand: lpc32xx_slc: fail DMA transfer on completion timeout lpc32xx_xmit_dma() waits for the DMA completion callback but ignores wait_for_completion_timeout(). A timed out DMA transfer is therefore unmapped and reported as successful to the NAND read/write path. Return -ETIMEDOUT when the completion wait expires. Terminate the DMA channel before unmapping the scatterlist so the timed out transfer cannot continue to access the buffer after the error is returned. Fixes: 2944a44da09e ("mtd: add LPC32xx SLC NAND driver") Cc: stable@vger.kernel.org Reviewed-by: Vladimir Zapolskiy Signed-off-by: Pengpeng Hou Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/lpc32xx_slc.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/nand/raw/lpc32xx_slc.c b/drivers/mtd/nand/raw/lpc32xx_slc.c index 3ca30e7dce33..10c8080207f4 100644 --- a/drivers/mtd/nand/raw/lpc32xx_slc.c +++ b/drivers/mtd/nand/raw/lpc32xx_slc.c @@ -430,6 +430,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, dma_addr_t dma, struct dma_async_tx_descriptor *desc; int flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; int res; + unsigned long time_left; host->dma_slave_config.direction = dir; host->dma_slave_config.src_addr = dma; @@ -467,12 +468,19 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, dma_addr_t dma, dmaengine_submit(desc); dma_async_issue_pending(host->dma_chan); - wait_for_completion_timeout(&host->comp, msecs_to_jiffies(1000)); + time_left = wait_for_completion_timeout(&host->comp, + msecs_to_jiffies(1000)); + if (!time_left) { + dmaengine_terminate_sync(host->dma_chan); + res = -ETIMEDOUT; + } else { + res = 0; + } dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, DMA_BIDIRECTIONAL); - return 0; + return res; out1: dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, DMA_BIDIRECTIONAL); From d322e40f4edf92bf0ca329e5aa4ae1c0316feb38 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 3 Jul 2026 15:40:52 +0800 Subject: [PATCH 089/479] mtd: mchp23k256: use SPI match data for chip caps The driver stores chip capacity information in both the OF match table and the SPI id table. Probe currently uses of_device_get_match_data(), so a non-OF SPI modalias match falls back to mchp23k256_caps even when the SPI id table selected a different part. Use spi_get_device_match_data() so SPI id-table driver_data is consumed when OF match data is absent. This keeps the existing default fallback while avoiding the wrong MTD geometry for id-table-only matches. Fixes: 4379075a870b ("mtd: mchp23k256: Add support for mchp23lcv1024") Cc: stable@vger.kernel.org Signed-off-by: Pengpeng Hou Signed-off-by: Miquel Raynal --- drivers/mtd/devices/mchp23k256.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/devices/mchp23k256.c b/drivers/mtd/devices/mchp23k256.c index cef5f9677d39..66e058df4c32 100644 --- a/drivers/mtd/devices/mchp23k256.c +++ b/drivers/mtd/devices/mchp23k256.c @@ -188,7 +188,7 @@ static int mchp23k256_probe(struct spi_device *spi) data = dev_get_platdata(&spi->dev); - flash->caps = of_device_get_match_data(&spi->dev); + flash->caps = spi_get_device_match_data(spi); if (!flash->caps) flash->caps = &mchp23k256_caps; From f9a13e05a327080c3a1c8165adf9e678fb68fef2 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 3 Jul 2026 15:42:33 +0800 Subject: [PATCH 090/479] mtd: rawnand: fsl_ifc: return errors for failed page reads fsl_ifc_run_command() logs controller timeout and other non-OPC completion states in ctrl->nand_stat. fsl_ifc_read_page() then only increments the ECC failure counter for non-OPC status and still returns max_bitflips, which can be zero. Return -ETIMEDOUT when the command did not complete at all and -EIO for other non-OPC read completions so the NAND core does not treat a failed page read as a clean page. Fixes: 82771882d960 ("NAND Machine support for Integrated Flash Controller") Cc: stable@vger.kernel.org Signed-off-by: Pengpeng Hou Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/fsl_ifc_nand.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c index fad0334f759d..a88ac2cfaccd 100644 --- a/drivers/mtd/nand/raw/fsl_ifc_nand.c +++ b/drivers/mtd/nand/raw/fsl_ifc_nand.c @@ -684,8 +684,15 @@ static int fsl_ifc_read_page(struct nand_chip *chip, uint8_t *buf, return check_erased_page(chip, buf); } - if (ctrl->nand_stat != IFC_NAND_EVTER_STAT_OPC) + if (!ctrl->nand_stat) { mtd->ecc_stats.failed++; + return -ETIMEDOUT; + } + + if (ctrl->nand_stat != IFC_NAND_EVTER_STAT_OPC) { + mtd->ecc_stats.failed++; + return -EIO; + } return nctrl->max_bitflips; } From d03a19bd6c7f86b99ca8fb61a6ec2345cee1d9d6 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 3 Jul 2026 15:43:50 +0800 Subject: [PATCH 091/479] mtd: onenand: samsung: report DMA completion timeouts The S5PC110 OneNAND DMA helpers have bounded waits for transfer completion. The polling helper falls out of its timeout loop and returns success, and the IRQ helper ignores wait_for_completion_timeout(). Return -ETIMEDOUT when the DMA transfer-done bit or completion does not arrive before the timeout so callers can treat the buffer transfer as failed. Fixes: e23abf4b7743 ("mtd: OneNAND: S5PC110: Implement DMA interrupt method") Cc: stable@vger.kernel.org Signed-off-by: Pengpeng Hou Signed-off-by: Miquel Raynal --- drivers/mtd/nand/onenand/onenand_samsung.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/onenand/onenand_samsung.c b/drivers/mtd/nand/onenand/onenand_samsung.c index 6d6aa709a21f..b7b7758ce4d8 100644 --- a/drivers/mtd/nand/onenand/onenand_samsung.c +++ b/drivers/mtd/nand/onenand/onenand_samsung.c @@ -554,6 +554,9 @@ static int s5pc110_dma_poll(dma_addr_t dst, dma_addr_t src, size_t count, int di } while (!(status & S5PC110_DMA_TRANS_STATUS_TD) && time_before(jiffies, timeout)); + if (!(status & S5PC110_DMA_TRANS_STATUS_TD)) + return -ETIMEDOUT; + writel(S5PC110_DMA_TRANS_CMD_TDC, base + S5PC110_DMA_TRANS_CMD); return 0; @@ -608,7 +611,9 @@ static int s5pc110_dma_irq(dma_addr_t dst, dma_addr_t src, size_t count, int dir writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD); - wait_for_completion_timeout(&onenand->complete, msecs_to_jiffies(20)); + if (!wait_for_completion_timeout(&onenand->complete, + msecs_to_jiffies(20))) + return -ETIMEDOUT; return 0; } From 2b533e775aec580cf60074417f4ca00ac9cf3580 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 3 Jul 2026 13:35:47 +0200 Subject: [PATCH 092/479] Revert "mtd: maps: remove uclinux map driver" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The uclinux MTD map driver is still actively used on ColdFire (m68k) platforms. The ColdFire startup code in arch/m68k/coldfire/head.S copies an attached filesystem blob to _bss_stop, and the uclinux driver picks it up from there. Most ColdFire defconfigs enable CONFIG_MTD_UCLINUX, as the driver seems to serve as a replacement for CONFIG_BLK_DEV_INITRD on these non-MMU targets. The driver was removed as part of a series cleaning up AMD Élan specific drivers, but unlike the other three drivers in that series, this driver seemed to not be as Élan specific (nor unused) as initially thought. This reverts commit c584b8a7ad01a334a89732f3c5791ba14e58642b. Reported-by: Greg Ungerer Closes: https://lore.kernel.org/all/b55ac37f-4523-46d0-8e77-3a17d7235ffe@westnet.com.au/ Acked-by: Arnd Bergmann Signed-off-by: Miquel Raynal --- drivers/mtd/maps/Kconfig | 6 ++ drivers/mtd/maps/Makefile | 1 + drivers/mtd/maps/uclinux.c | 118 +++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 drivers/mtd/maps/uclinux.c diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index dce5e67ce3c2..f447902d707e 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -277,6 +277,12 @@ config MTD_PCMCIA_ANONYMOUS If unsure, say N. +config MTD_UCLINUX + bool "Generic uClinux RAM/ROM filesystem support" + depends on (MTD_RAM=y || MTD_ROM=y) && (!MMU || COLDFIRE) + help + Map driver to support image based filesystems for uClinux. + config MTD_PLATRAM tristate "Map driver for platform device RAM (mtd-ram)" select MTD_RAM diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index fbed278157f6..01745eca1f73 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_MTD_SUN_UFLASH) += sun_uflash.o obj-$(CONFIG_MTD_SCx200_DOCFLASH)+= scx200_docflash.o obj-$(CONFIG_MTD_SOLUTIONENGINE)+= solutionengine.o obj-$(CONFIG_MTD_PCI) += pci.o +obj-$(CONFIG_MTD_UCLINUX) += uclinux.o obj-$(CONFIG_MTD_SCB2_FLASH) += scb2_flash.o obj-$(CONFIG_MTD_PLATRAM) += plat-ram.o obj-$(CONFIG_MTD_VMU) += vmu-flash.o diff --git a/drivers/mtd/maps/uclinux.c b/drivers/mtd/maps/uclinux.c new file mode 100644 index 000000000000..de4c46318abb --- /dev/null +++ b/drivers/mtd/maps/uclinux.c @@ -0,0 +1,118 @@ +/****************************************************************************/ + +/* + * uclinux.c -- generic memory mapped MTD driver for uclinux + * + * (C) Copyright 2002, Greg Ungerer (gerg@snapgear.com) + * + * License: GPL + */ + +/****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************/ + +#ifdef CONFIG_MTD_ROM +#define MAP_NAME "rom" +#else +#define MAP_NAME "ram" +#endif + +static struct map_info uclinux_ram_map = { + .name = MAP_NAME, + .size = 0, +}; + +static unsigned long physaddr = -1; +module_param(physaddr, ulong, S_IRUGO); + +static struct mtd_info *uclinux_ram_mtdinfo; + +/****************************************************************************/ + +static const struct mtd_partition uclinux_romfs[] = { + { .name = "ROMfs" } +}; + +#define NUM_PARTITIONS ARRAY_SIZE(uclinux_romfs) + +/****************************************************************************/ + +static int uclinux_point(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, void **virt, resource_size_t *phys) +{ + struct map_info *map = mtd->priv; + *virt = map->virt + from; + if (phys) + *phys = map->phys + from; + *retlen = len; + return(0); +} + +/****************************************************************************/ + +static int __init uclinux_mtd_init(void) +{ + struct mtd_info *mtd; + struct map_info *mapp; + + mapp = &uclinux_ram_map; + + if (physaddr == -1) + mapp->phys = (resource_size_t)__bss_stop; + else + mapp->phys = physaddr; + + if (!mapp->size) + mapp->size = PAGE_ALIGN(ntohl(*((unsigned long *)(mapp->phys + 8)))); + mapp->bankwidth = 4; + + printk("uclinux[mtd]: probe address=0x%x size=0x%x\n", + (int) mapp->phys, (int) mapp->size); + + /* + * The filesystem is guaranteed to be in direct mapped memory. It is + * directly following the kernels own bss region. Following the same + * mechanism used by architectures setting up traditional initrds we + * use phys_to_virt to get the virtual address of its start. + */ + mapp->virt = phys_to_virt(mapp->phys); + + if (mapp->virt == 0) { + printk("uclinux[mtd]: no virtual mapping?\n"); + return(-EIO); + } + + simple_map_init(mapp); + + mtd = do_map_probe("map_" MAP_NAME, mapp); + if (!mtd) { + printk("uclinux[mtd]: failed to find a mapping?\n"); + return(-ENXIO); + } + + mtd->owner = THIS_MODULE; + mtd->_point = uclinux_point; + mtd->priv = mapp; + + uclinux_ram_mtdinfo = mtd; + mtd_device_register(mtd, uclinux_romfs, NUM_PARTITIONS); + + return(0); +} +device_initcall(uclinux_mtd_init); + +/****************************************************************************/ From cb89f0c1aed02eb233c4271f76f830b37e222ff6 Mon Sep 17 00:00:00 2001 From: Malaya Kumar Rout Date: Sat, 4 Jul 2026 16:27:36 +0530 Subject: [PATCH 093/479] selftests/alsa: Fix memory leak in find_controls error path In find_controls(), card_data is allocated with malloc() but when snd_ctl_open_lconf() fails, the code jumps to next_card without freeing the allocated memory. This results in a memory leak for each card where snd_ctl_open_lconf() fails. Add free(card_data) before goto next_card to ensure proper cleanup of the allocated memory in the error path. Fixes: 5aaf9efffc57 ("kselftest: alsa: Add simplistic test for ALSA mixer controls kselftest") Signed-off-by: Malaya Kumar Rout Link: https://patch.msgid.link/20260704105736.94874-1-malayarout91@gmail.com Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/mixer-test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index d4f845c32804..c5ae2a026ce9 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -84,6 +84,7 @@ static void find_controls(void) if (err < 0) { ksft_print_msg("Failed to get hctl for card %d: %s\n", card, snd_strerror(err)); + free(card_data); goto next_card; } From 742a87fa54ad7123bff41bd1aa149fef6929a7af Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 5 Jul 2026 16:07:13 +0800 Subject: [PATCH 094/479] ALSA: hda/realtek: add quirk for Framework F111:0010 Similar to commit 67c738152207 ("ALSA: hda/realtek: fix mic boost on Framework PTL") and previous quirks for Framework systems with Realtek codecs. 0010 is another platform with an ALC285 which needs the same quirk. Signed-off-by: Daniel Schaefer Link: https://patch.msgid.link/20260705-framework-audio-ssid-0x0010-v1-1-fffc8996ac18@frame.work Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index c54f55730fdc..b6b502450a95 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7923,6 +7923,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0xf111, 0x000c, "Framework Laptop", ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0xf111, 0x000f, "Framework Laptop 13 Pro PTL", ALC295_FIXUP_FRAMEWORK_LAPTOP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0xf111, 0x010f, "Framework Laptop 13 PTL", ALC295_FIXUP_FRAMEWORK_LAPTOP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0xf111, 0x0010, "Framework Laptop", ALC295_FIXUP_FRAMEWORK_LAPTOP_LIMIT_INT_MIC_BOOST), #if 0 /* Below is a quirk table taken from the old code. From aba30af07d4fe499b50209801eba9da8a815522f Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 5 Jul 2026 16:46:01 +0800 Subject: [PATCH 095/479] ALSA: usb-audio: caiaq: validate EP1 reply lengths usb_ep1_command_reply_dispatch() uses buf[0] as a command byte and then reads command-specific fixed items from the same URB buffer. Several paths use buf + 1, buf[1], buf[2], or buf + 3 without first proving that urb->actual_length contains those bytes. Add per-command length checks, use a payload length derived from the bytes after the command byte for the control-state copy, and reject short analog input payloads before the input helper reads fixed offsets from the EP1 reply. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260705084601.56400-1-pengpeng@iscas.ac.cn Signed-off-by: Takashi Iwai --- sound/usb/caiaq/device.c | 17 ++++++++++++++--- sound/usb/caiaq/input.c | 6 ++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index b20aae0caf60..a16e59248480 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -134,14 +134,22 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) struct device *dev = &urb->dev->dev; struct snd_usb_caiaqdev *cdev = urb->context; unsigned char *buf = urb->transfer_buffer; + unsigned int payload_len; + unsigned int copy_len; if (urb->status || !cdev) { dev_warn(dev, "received EP1 urb->status = %i\n", urb->status); return; } + if (urb->actual_length < 1) + return; + + payload_len = urb->actual_length - 1; switch(buf[0]) { case EP1_CMD_GET_DEVICE_INFO: + if (payload_len < sizeof(struct caiaq_device_spec)) + break; memcpy(&cdev->spec, buf+1, sizeof(struct caiaq_device_spec)); cdev->spec.fw_version = le16_to_cpu(cdev->spec.fw_version); dev_dbg(dev, "device spec (firmware %d): audio: %d in, %d out, " @@ -157,18 +165,21 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) wake_up(&cdev->ep1_wait_queue); break; case EP1_CMD_AUDIO_PARAMS: + if (payload_len < 1) + break; cdev->audio_parm_answer = buf[1]; wake_up(&cdev->ep1_wait_queue); break; case EP1_CMD_MIDI_READ: + if (urb->actual_length < 3 || urb->actual_length - 3 < buf[2]) + break; snd_usb_caiaq_midi_handle_input(cdev, buf[1], buf + 3, buf[2]); break; case EP1_CMD_READ_IO: if (cdev->chip.usb_id == USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ)) { - if (urb->actual_length > sizeof(cdev->control_state)) - urb->actual_length = sizeof(cdev->control_state); - memcpy(cdev->control_state, buf + 1, urb->actual_length); + copy_len = min_t(unsigned int, payload_len, sizeof(cdev->control_state)); + memcpy(cdev->control_state, buf + 1, copy_len); wake_up(&cdev->ep1_wait_queue); break; } diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index eabbf41fdfb2..8d924330c54c 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -203,6 +203,8 @@ static void snd_caiaq_input_read_analog(struct snd_usb_caiaqdev *cdev, switch (cdev->chip.usb_id) { case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2): + if (len < 6) + return; snd_caiaq_input_report_abs(cdev, ABS_X, buf, 2); snd_caiaq_input_report_abs(cdev, ABS_Y, buf, 0); snd_caiaq_input_report_abs(cdev, ABS_Z, buf, 1); @@ -210,11 +212,15 @@ static void snd_caiaq_input_read_analog(struct snd_usb_caiaqdev *cdev, case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + if (len < 6) + return; snd_caiaq_input_report_abs(cdev, ABS_X, buf, 0); snd_caiaq_input_report_abs(cdev, ABS_Y, buf, 1); snd_caiaq_input_report_abs(cdev, ABS_Z, buf, 2); break; case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + if (len < 16) + return; snd_caiaq_input_report_abs(cdev, ABS_HAT0X, buf, 4); snd_caiaq_input_report_abs(cdev, ABS_HAT0Y, buf, 2); snd_caiaq_input_report_abs(cdev, ABS_HAT1X, buf, 6); From 3cda0dfe8c651dcbb9e38977905d3d3b1750c4ab Mon Sep 17 00:00:00 2001 From: Aleksandrova Alyona Date: Wed, 24 Jun 2026 17:48:46 +0300 Subject: [PATCH 096/479] RDMA/irdma: Prevent overflows in memory contiguity checks irdma_check_mem_contiguous() and irdma_check_mr_contiguous() verify that PBL entries describe physically contiguous memory ranges. Both functions calculate byte offsets using 32-bit operands. For example, with 4 KiB pages, pg_size * pg_idx overflows 32-bit arithmetic when pg_idx reaches 1048576. In the level-2 check, PBLE_PER_PAGE is 512, so i * pg_size * PBLE_PER_PAGE overflows when i reaches 2048. These values are reachable in the driver. For MRs, palloc->total_cnt comes from iwmr->page_cnt, which is calculated by ib_umem_num_dma_blocks(). The MR size is limited by IRDMA_MAX_MR_SIZE, so a 4 GiB MR with 4 KiB pages can reach page_cnt of 1048576. PBLE resources do not exclude this value either: for gen3, the limit is based on avail_sds * MAX_PBLE_PER_SD, and MAX_PBLE_PER_SD is 0x40000, so 4 SDs are enough for 1048576 PBLEs. Cast one operand to u64 before the multiplications so that the offset calculations are performed in 64-bit arithmetic. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: b48c24c2d710 ("RDMA/irdma: Implement device supported verb APIs") Signed-off-by: Aleksandrova Alyona Link: https://patch.msgid.link/20260624144846.61242-1-aga@itb.spb.ru Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/irdma/verbs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/hw/irdma/verbs.c b/drivers/infiniband/hw/irdma/verbs.c index be8c5cf12f7f..b7388b41ed95 100644 --- a/drivers/infiniband/hw/irdma/verbs.c +++ b/drivers/infiniband/hw/irdma/verbs.c @@ -2801,7 +2801,7 @@ static bool irdma_check_mem_contiguous(u64 *arr, u32 npages, u32 pg_size) u32 pg_idx; for (pg_idx = 0; pg_idx < npages; pg_idx++) { - if ((*arr + (pg_size * pg_idx)) != arr[pg_idx]) + if ((*arr + ((u64)pg_size * pg_idx)) != arr[pg_idx]) return false; } @@ -2834,7 +2834,7 @@ static bool irdma_check_mr_contiguous(struct irdma_pble_alloc *palloc, for (i = 0; i < lvl2->leaf_cnt; i++, leaf++) { arr = leaf->addr; - if ((*start_addr + (i * pg_size * PBLE_PER_PAGE)) != *arr) + if ((*start_addr + ((u64)i * pg_size * PBLE_PER_PAGE)) != *arr) return false; ret = irdma_check_mem_contiguous(arr, leaf->cnt, pg_size); if (!ret) From 1002500f54b5f60faa6a03636c7865fd4db53ad6 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 28 Jun 2026 20:25:52 -0700 Subject: [PATCH 097/479] lib/crypto: md5: Remove support for md5_mod_init_arch() No definitions of md5_mod_init_arch() remain, so remove the code that handles it. Link: https://patch.msgid.link/20260629032552.26100-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/md5.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/crypto/md5.c b/lib/crypto/md5.c index 6bf130cfbbf9..3d2b017a0525 100644 --- a/lib/crypto/md5.c +++ b/lib/crypto/md5.c @@ -298,19 +298,5 @@ void hmac_md5_usingrawkey(const u8 *raw_key, size_t raw_key_len, } EXPORT_SYMBOL_GPL(hmac_md5_usingrawkey); -#ifdef md5_mod_init_arch -static int __init md5_mod_init(void) -{ - md5_mod_init_arch(); - return 0; -} -subsys_initcall(md5_mod_init); - -static void __exit md5_mod_exit(void) -{ -} -module_exit(md5_mod_exit); -#endif - MODULE_DESCRIPTION("MD5 and HMAC-MD5 library functions"); MODULE_LICENSE("GPL"); From 92ea163c773cb4d0d5eaf103ed80c49d6758b96f Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sat, 13 Jun 2026 18:16:33 -0400 Subject: [PATCH 098/479] NFSD: Prevent post-shutdown use-after-free in NFSD_CMD_UNLOCK_FILESYSTEM The NFSD_CMD_UNLOCK_FILESYSTEM netlink command runs nfsd4_cancel_copy_by_sb() before nfsd_mutex is held and before nn->nfsd_serv is confirmed set, the same pre-mutex ordering the procfs unlock_filesystem path carried. Once nfsd has shut down, nfs4_state_destroy_net() has freed nn->conf_id_hashtbl but left the pointer intact, so the cancel helper iterates freed slab memory as an array of struct list_head and then dereferences a bogus nfs4_client when it takes clp->async_lock. A local administrator holding CAP_SYS_ADMIN can reach this use-after-free by stopping the server and then issuing the command. Move the async COPY cancel into the nfsd_mutex section, after nn->nfsd_serv is confirmed, so every NFSv4 state-table walker on this path observes a running server. Async copies exist only while the server runs, so gating the cancel on nn->nfsd_serv loses nothing. Fixes: 327c5168eff2 ("NFSD: Add NFSD_CMD_UNLOCK_FILESYSTEM netlink command") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Link: https://patch.msgid.link/20260613-unlock-filesystem-uaf-v1-2-462b9bec8c84@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/nfsctl.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index d0486f4a47ba..fa92e31d19d6 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2340,14 +2340,15 @@ int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, if (error) return error; - nfsd4_cancel_copy_by_sb(net, path.dentry->d_sb); error = nlmsvc_unlock_all_by_sb(path.dentry->d_sb); mutex_lock(&nfsd_mutex); - if (nn->nfsd_serv) + if (nn->nfsd_serv) { + nfsd4_cancel_copy_by_sb(net, path.dentry->d_sb); nfsd4_revoke_states(nn, path.dentry->d_sb); - else + } else { error = -EINVAL; + } mutex_unlock(&nfsd_mutex); path_put(&path); From 2538bd3cd1ff5af655908469544ac7b7ae259386 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 27 Jun 2026 11:01:17 +0800 Subject: [PATCH 099/479] xfrm: clear mode callbacks after failed mode setup xfrm_state_gc_task can run long after a failed IPTFS state setup. In the reproduced case, __xfrm_init_state() cached x->mode_cbs, IPTFS setup returned -ENOMEM before publishing mode_data, and the temporary module reference from xfrm_get_mode_cbs() was dropped immediately. The dead state then kept x->mode_cbs until deferred GC ran after xfrm_iptfs had been unloaded. Clear x->mode_cbs when mode init or clone fails before publishing mode_data. Those states never installed mode-specific state or the long-term IPTFS module pin, so deferred GC has nothing mode-specific to destroy and must not retain a callback table pointer past the temporary lookup reference. The buggy scenario involves two paths, with each column showing the order within that path: failed setup path: 1. cache x->mode_cbs 2. mode setup fails before mode_data 3. drop the temporary module ref 4. dead state keeps x->mode_cbs cached GC/unload path: 1. xfrm_state_put() queues GC work 2. xfrm_iptfs unloads later 3. xfrm_state_gc_task runs 4. GC dereferences stale x->mode_cbs This also covers the failed clone path where clone_state() returns before publishing mode_data. Validation reproduced this kernel report: Kernel panic - not syncing: Fatal exception CONFIG_FAULT_INJECTION_STACKTRACE_FILTER=y failslab_stacktrace_filter matched xfrm_iptfs frames ack_error=-12 FAULT_INJECTION: forcing a failure BUG: unable to handle page fault Workqueue: events xfrm_state_gc_task RIP: xfrm_state_gc_task+0x142/0x650 Modules linked in: esp4_offload xfrm_user [last unloaded: xfrm_iptfs] Kernel panic - not syncing: Fatal exception Fixes: 4b3faf610cc6 ("xfrm: iptfs: add new iptfs xfrm mode impl") Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_state.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 10e5a1a95fe3..36a4f6793ede 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -2072,8 +2072,11 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig, x->mode_cbs = orig->mode_cbs; if (x->mode_cbs && x->mode_cbs->clone_state) { - if (x->mode_cbs->clone_state(x, orig)) + if (x->mode_cbs->clone_state(x, orig)) { + if (!x->mode_data) + x->mode_cbs = NULL; goto error; + } } x->props.reqid = m->new_reqid; @@ -3292,6 +3295,8 @@ int __xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack) if (x->mode_cbs->init_state) err = x->mode_cbs->init_state(x); module_put(x->mode_cbs->owner); + if (err && !x->mode_data) + x->mode_cbs = NULL; } error: return err; From 430ea57d6daf765e88f90046afbfd1e071cb7200 Mon Sep 17 00:00:00 2001 From: Chen YanJun Date: Wed, 1 Jul 2026 11:31:52 +0800 Subject: [PATCH 100/479] xfrm: iptfs: propagate SKBFL_SHARED_FRAG in iptfs_skb_add_frags() When iptfs_skb_add_frags() copies frag references from the source frag walk into a new SKB, it increments the page reference count via __skb_frag_ref() but does not propagate SKBFL_SHARED_FRAG to the destination SKB's skb_shinfo->flags. If the source SKB carries shared frags (e.g. from a page-pool backed receive path), the new inner SKB will appear to ESP as having privately owned frags. A subsequent esp_input() call for a nested transport-mode SA then takes the no-COW fast path and decrypts in place, writing over pages that are still referenced by the outer IPTFS SKB. This causes kernel-visible memory corruption and can trigger a panic. All other frag-transfer helpers in the kernel (skb_try_coalesce, skb_gro_receive, __pskb_copy_fclone, skb_shift, skb_segment) correctly propagate SKBFL_SHARED_FRAG; align iptfs_skb_add_frags() with this convention by setting the flag inside the loop immediately after __skb_frag_ref() and nr_frags++, so every exit path that attaches a frag unconditionally propagates SKBFL_SHARED_FRAG. Fixes: 5f2b6a909574 ("xfrm: iptfs: add skb-fragment sharing code") Signed-off-by: Chen YanJun Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_iptfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c index ad810d1f97c0..597aedeac26e 100644 --- a/net/xfrm/xfrm_iptfs.c +++ b/net/xfrm/xfrm_iptfs.c @@ -480,6 +480,7 @@ static int iptfs_skb_add_frags(struct sk_buff *skb, } __skb_frag_ref(tofrag); shinfo->nr_frags++; + shinfo->flags |= SKBFL_SHARED_FRAG; /* see if we are done */ fraglen = tofrag->len; From 136992de9bb91871084ae52d172610541c76e4d2 Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Thu, 2 Jul 2026 01:05:16 +0000 Subject: [PATCH 101/479] xfrm6: clear dst.dev on error to avoid double netdev_put in xfrm6_fill_dst() On the error path where in6_dev_get(dev) returns NULL, xfrm6_fill_dst() releases the device reference with netdev_put() but leaves xdst->u.dst.dev set. dst_destroy() later calls netdev_put(dst->dev) again, so the same net_device reference is released twice, underflowing its refcount (ref_tracker WARNING + "unregister_netdevice: waiting for to become free"). Clear xdst->u.dst.dev after the netdev_put(), the same way the XFRM device-offload paths xfrm_dev_state_add() and xfrm_dev_policy_add() in net/xfrm/xfrm_device.c NULL ->dev when releasing the reference on error. ref_tracker: reference already released. ref_tracker: allocated in: xfrm6_fill_dst (net/ipv6/xfrm6_policy.c:86) ... udpv6_sendmsg (net/ipv6/udp.c:1696) ... ref_tracker: freed in: xfrm6_fill_dst (net/ipv6/xfrm6_policy.c:90) ... WARNING: lib/ref_tracker.c:322 at ref_tracker_free+0x58b/0x780 dst_destroy (net/core/dst.c:115) rcu_core handle_softirqs ... Fixes: 84c4a9dfbf43 ("xfrm6: release dev before returning error") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) Signed-off-by: Steffen Klassert --- net/ipv6/xfrm6_policy.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/ipv6/xfrm6_policy.c b/net/ipv6/xfrm6_policy.c index 125ea9a5b8a0..3b749475f6ed 100644 --- a/net/ipv6/xfrm6_policy.c +++ b/net/ipv6/xfrm6_policy.c @@ -88,6 +88,7 @@ static int xfrm6_fill_dst(struct xfrm_dst *xdst, struct net_device *dev, xdst->u.rt6.rt6i_idev = in6_dev_get(dev); if (!xdst->u.rt6.rt6i_idev) { netdev_put(dev, &xdst->u.dst.dev_tracker); + xdst->u.dst.dev = NULL; return -ENODEV; } From f38f8cce2f7e79775b3db7e8a5eacda04ac908e4 Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Fri, 3 Jul 2026 05:19:32 +0000 Subject: [PATCH 102/479] xfrm: policy: preallocate inexact bins before xfrm_hash_rebuild reinsert xfrm_hash_rebuild()'s first loop preallocates the bins/chains the reinsert loop needs, so the reinsert (after hlist_del_rcu()) cannot allocate or fail. But its guard is inverted: it skips policies with prefixlen < threshold and preallocates for the rest. prefixlen < threshold is exactly when policy_hash_bysel() returns NULL and the reinsert takes the allocating xfrm_policy_inexact_insert() path. So the loop preallocates for the exact policies (which never allocate) and skips the inexact ones, whose bin/node is then allocated GFP_ATOMIC during reinsert. On failure the error path only WARN_ONCE()s and continues, leaving a poisoned bydst node; the next rebuild's hlist_del_rcu() dereferences LIST_POISON2 and takes a GPF. Reachable under memory pressure, deterministic via failslab. Invert the guard so preallocation covers exactly the reinserted policies; the reinsert then allocates nothing and cannot fail. Crash: Oops: general protection fault, probably for non-canonical address 0xfbd59c0000000024: 0000 [#1] SMP KASAN NOPTI KASAN: maybe wild-memory-access in range [0xdead...] ... Workqueue: events xfrm_hash_rebuild RIP: 0010:xfrm_hash_rebuild+0x5b3/0x1190 RAX: dead000000000122 (LIST_POISON2 + offset) ... Call Trace: hlist_del_rcu (include/linux/rculist.h:599) xfrm_hash_rebuild (net/xfrm/xfrm_policy.c:1365) process_one_work (kernel/workqueue.c:3322) worker_thread (kernel/workqueue.c:3486) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) ... Kernel panic - not syncing: Fatal exception in interrupt Fixes: 24969facd704 ("xfrm: policy: store inexact policies in an rhashtable") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) Reviewed-by: Florian Westphal Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_policy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c index 7ef861a0e823..932a313b9460 100644 --- a/net/xfrm/xfrm_policy.c +++ b/net/xfrm/xfrm_policy.c @@ -1329,8 +1329,8 @@ static void xfrm_hash_rebuild(struct work_struct *work) } } - if (policy->selector.prefixlen_d < dbits || - policy->selector.prefixlen_s < sbits) + if (policy->selector.prefixlen_d >= dbits && + policy->selector.prefixlen_s >= sbits) continue; bin = xfrm_policy_inexact_alloc_bin(policy, dir); From a707e4127c0f893c7a7703500ab56297a5bd2d51 Mon Sep 17 00:00:00 2001 From: Rafael Beims Date: Wed, 10 Jun 2026 12:00:18 -0300 Subject: [PATCH 103/479] wifi: mwifiex: fix roaming to different channel in host_mlme mode When host MLME is enabled, mwifiex_cfg80211_authenticate() transmits the authentication frame on a remain-on-channel (ROC) reservation so that the frame is sent on the target BSS's channel. The ROC is only configured when priv->auth_flag is zero. priv->auth_flag is set to HOST_MLME_AUTH_PENDING when the auth frame is queued and advances to HOST_MLME_AUTH_DONE once authentication completes. It is only cleared back to zero on a disconnect, deauth or timeout path; nothing clears it when an association succeeds. It therefore stays at HOST_MLME_AUTH_DONE for the whole connected session. When the station later roams to a BSS on a different channel, the next authentication finds auth_flag != 0, skips the ROC setup, and the auth frame is transmitted on the currently-associated channel instead of the target's channel. Authentication times out on the new AP and the device stays connected to the original AP. Gate the ROC setup on HOST_MLME_AUTH_PENDING instead of on auth_flag being completely clear. This re-arms the remain-on-channel for every new authentication attempt, while still suppressing a redundant ROC during the multi-frame SAE exchange, where auth_flag stays PENDING between the commit and confirm frames. This change was tested in 3 different devices: Verdin AM62 (IW412 SD-UART) - (16.92.21.p142) Verdin iMX8MM (W8997 SD-SD) - (16.68.1.p197) Verdin iMX8MP (W8997 SD-UART) - (16.92.21.p137) There following loop tests were performed: 1) force roaming between two AP's, one 5GHz and one 2.4GHz, same SSID. Use wpa_cli to trigger the roaming behavior, sleep 2s between iterations. 2) force a disconnection to AP 1 and a connection to AP 2, test scan. Use wpa_cli to trigger the connection changes, sleep 2s between iterations. Each test ran in each device for at least 3 hours. Fixes: 36995892c271 ("wifi: mwifiex: add host mlme for client mode") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Rafael Beims Reviewed-by: Francesco Dolcini Link: https://patch.msgid.link/20260610150021.1018611-1-rafael@beims.me Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c index c9daf893472f..abc703441c5d 100644 --- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c +++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c @@ -4334,7 +4334,7 @@ mwifiex_cfg80211_authenticate(struct wiphy *wiphy, return -EOPNOTSUPP; } - if (!priv->auth_flag) { + if (!(priv->auth_flag & HOST_MLME_AUTH_PENDING)) { ret = mwifiex_remain_on_chan_cfg(priv, HostCmd_ACT_GEN_SET, req->bss->channel, AUTH_TX_DEFAULT_WAIT_TIME); From 44494b0d1d16e76ae805817579eacc801b10ed37 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 11 Jun 2026 15:00:54 +0200 Subject: [PATCH 104/479] wifi: mac80211: allocate backup ieee80211_nan_sched_cfg off stack The ieee80211_nan_sched_cfg structure is too large to keep on the per thread stack: net/mac80211/nan.c:251:5: error: stack frame size (1560) exceeds limit (1536) in 'ieee80211_nan_set_local_sched' [-Werror,-Wframe-larger-than] 251 | int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata, Allocate this dynamically using kmalloc_obj() to reduce the stack usage of this function to a manageable 344 bytes for the same configuration. Fixes: 589c06e8fdee ("wifi: mac80211: add NAN local schedule support") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260611130100.3387714-1-arnd@kernel.org Signed-off-by: Johannes Berg --- net/mac80211/nan.c | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c index 1800bb96dd29..19e08661be43 100644 --- a/net/mac80211/nan.c +++ b/net/mac80211/nan.c @@ -253,9 +253,12 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata, { struct ieee80211_nan_channel *sched_idx_to_chan[IEEE80211_NAN_MAX_CHANNELS] = {}; struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched; - struct ieee80211_nan_sched_cfg backup_sched; + struct ieee80211_nan_sched_cfg *backup_sched __free(kfree) = kmalloc_obj(*backup_sched); int ret; + if (!backup_sched) + return -ENOMEM; + if (sched->n_channels > IEEE80211_NAN_MAX_CHANNELS) return -EOPNOTSUPP; @@ -275,13 +278,13 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata, bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS); - memcpy(backup_sched.schedule, sched_cfg->schedule, - sizeof(backup_sched.schedule)); - memcpy(backup_sched.channels, sched_cfg->channels, - sizeof(backup_sched.channels)); - memcpy(backup_sched.avail_blob, sched_cfg->avail_blob, - sizeof(backup_sched.avail_blob)); - backup_sched.avail_blob_len = sched_cfg->avail_blob_len; + memcpy(backup_sched->schedule, sched_cfg->schedule, + sizeof(backup_sched->schedule)); + memcpy(backup_sched->channels, sched_cfg->channels, + sizeof(backup_sched->channels)); + memcpy(backup_sched->avail_blob, sched_cfg->avail_blob, + sizeof(backup_sched->avail_blob)); + backup_sched->avail_blob_len = sched_cfg->avail_blob_len; memcpy(sched_cfg->avail_blob, sched->nan_avail_blob, sched->nan_avail_blob_len); @@ -380,17 +383,17 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata, if (!chan_def->chan) continue; - if (!cfg80211_chandef_identical(&backup_sched.channels[i].chanreq.oper, + if (!cfg80211_chandef_identical(&backup_sched->channels[i].chanreq.oper, chan_def)) ieee80211_nan_remove_channel(sdata, &sched_cfg->channels[i]); } /* Re-add all backed up channels */ - for (int i = 0; i < ARRAY_SIZE(backup_sched.channels); i++) { + for (int i = 0; i < ARRAY_SIZE(backup_sched->channels); i++) { struct ieee80211_nan_channel *chan = &sched_cfg->channels[i]; - *chan = backup_sched.channels[i]; + *chan = backup_sched->channels[i]; /* * For deferred update, no channels were removed and the channel @@ -421,11 +424,11 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata, } } - memcpy(sched_cfg->schedule, backup_sched.schedule, - sizeof(backup_sched.schedule)); - memcpy(sched_cfg->avail_blob, backup_sched.avail_blob, - sizeof(backup_sched.avail_blob)); - sched_cfg->avail_blob_len = backup_sched.avail_blob_len; + memcpy(sched_cfg->schedule, backup_sched->schedule, + sizeof(backup_sched->schedule)); + memcpy(sched_cfg->avail_blob, backup_sched->avail_blob, + sizeof(backup_sched->avail_blob)); + sched_cfg->avail_blob_len = backup_sched->avail_blob_len; sched_cfg->deferred = false; bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS); From d78a407bad6f500884a8606aea1a5a9207be4030 Mon Sep 17 00:00:00 2001 From: Rafael Beims Date: Fri, 12 Jun 2026 09:25:46 -0300 Subject: [PATCH 105/479] wifi: mwifiex: fix permanently busy scans after multiple roam iterations In order for the firmware to sleep, the driver has to confirm a previously received sleep request. The normal sequence of evets goes like this: EVENT_SLEEP -> adapter->ps_state = PS_STATE_PRE_SLEEP -> sleep-confirm -> SLEEP -> EVENT_AWAKE -> AWAKE. Before sending the sleep-confirm command, the driver must make sure there are no commands either running or waiting to be completed. mwifiex_ret_802_11_associate() unconditionally sets ps_state = PS_STATE_AWAKE when it processes the association command response, outside of the normal powersave management flow. If EVENT_SLEEP arrives while the association command is in flight, ps_state is PRE_SLEEP when the association command response is parsed, and the forced AWAKE overwrites it. The deferred sleep-confirm is never sent. A subsequent scan_start command is correctly acknowledged, but the firmware doesn't generate scan_result events. The scan request never finishes, and additional requests from userspace fail with -EBUSY. After testing on both IW412 and W8997, I could only trigger the bug on the IW412 and observed the firmwares behave differently. On the IW412 the firmware still sends EVENT_SLEEP while the authentication / association process is ongoing. A W8997 under the same conditions seems to suppress power-save for the duration of the association, so PRE_SLEEP never coincided with the association response even after extended periods of testing using the loops described below (>12hours). On the IW412, the delay between commands that triggers an EVENT_SLEEP was empirically determined to be ~20ms. This delay can naturally occur when the driver is outputting debugging information (debug_mask = 0x00000037), in which situation the busy scans issue is repeatable while running "test 1)" as described below. If the delay between commands is less than ~20ms, the firmware stays awake and the issue was not reproducible running the same test. The host_mlme=false path also behaves differently. In this case, the entire authentication / association transaction is executed by one command (HostCmd_CMD_802_11_ASSOCIATE), and the firmware doesn't emit EVENT_SLEEP while the command is running. Remove the assignment so the ps_state is only manipulated in the paths that are related to powersave event handling and on the main workqueue for correct sleep confirmation. The following loop tests were performed (with debugging output enabled): 1) force roaming between two AP's, one 5GHz and one 2.4GHz, same SSID. Use wpa_cli to trigger the roaming behavior, sleep 2s between iterations. 2) force a disconnection to AP 1 and a connection to AP 2, test scan. Use wpa_cli to trigger the connection changes, sleep 2s between iterations. Each test ran in each device for at least 3 hours. Fixes: 5e6e3a92b9a4 ("wireless: mwifiex: initial commit for Marvell mwifiex driver") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Rafael Beims Reviewed-by: Jeff Chen Link: https://patch.msgid.link/20260612122547.1586872-2-rafael@beims.me Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/join.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/wireless/marvell/mwifiex/join.c b/drivers/net/wireless/marvell/mwifiex/join.c index 5a1a0287c1d5..b48f7febaf03 100644 --- a/drivers/net/wireless/marvell/mwifiex/join.c +++ b/drivers/net/wireless/marvell/mwifiex/join.c @@ -736,7 +736,6 @@ int mwifiex_ret_802_11_associate(struct mwifiex_private *priv, /* Send a Media Connected event, according to the Spec */ priv->media_connected = true; - priv->adapter->ps_state = PS_STATE_AWAKE; priv->adapter->pps_uapsd_mode = false; priv->adapter->tx_lock_flag = false; From 536fb3d739d75a03cb318c0c6fe799425cfea501 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Fri, 19 Jun 2026 15:31:04 +0800 Subject: [PATCH 106/479] wifi: rt2x00: avoid full teardown before work setup in probe rt2x00lib_probe_dev() uses the full rt2x00lib_remove_dev() teardown for all probe failures. However, drv_data allocation and workqueue allocation can fail before intf_work, autowakeup_work and sleep_work have been initialized. Do not enter the full remove path until the probe has reached the point where those work items are set up. Return directly for drv_data allocation failure, and use a small early cleanup path for workqueue allocation failure. This issue was found by our static analysis tool and then confirmed by manual review of rt2x00lib_probe_dev() and rt2x00lib_remove_dev(). The early probe exits should not call a common teardown path that assumes the later work setup has already completed. A QEMU PoC forced alloc_ordered_workqueue() to fail before the work initializers are reached. The resulting fail path entered rt2x00lib_remove_dev(), and DEBUG_OBJECTS reported invalid work drains with rt2x00lib_probe_dev() and rt2x00lib_remove_dev() in the stack. Fixes: 1ebbc48520a0 ("rt2x00: Introduce concept of driver data in struct rt2x00_dev.") Fixes: 0439f5367c8d ("rt2x00: Move TX/RX work into dedicated workqueue") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260619073104.1809161-1-runyu.xiao@seu.edu.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/ralink/rt2x00/rt2x00dev.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c index 82fb230a73bb..4d94b7062f44 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c @@ -1388,7 +1388,7 @@ int rt2x00lib_probe_dev(struct rt2x00_dev *rt2x00dev) GFP_KERNEL); if (!rt2x00dev->drv_data) { retval = -ENOMEM; - goto exit; + return retval; } } @@ -1422,7 +1422,7 @@ int rt2x00lib_probe_dev(struct rt2x00_dev *rt2x00dev) alloc_ordered_workqueue("%s", 0, wiphy_name(rt2x00dev->hw->wiphy)); if (!rt2x00dev->workqueue) { retval = -ENOMEM; - goto exit; + goto exit_free_drv_data; } INIT_WORK(&rt2x00dev->intf_work, rt2x00lib_intf_scheduled); @@ -1494,6 +1494,14 @@ int rt2x00lib_probe_dev(struct rt2x00_dev *rt2x00dev) exit: rt2x00lib_remove_dev(rt2x00dev); + return retval; + +exit_free_drv_data: + clear_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags); + + kfree(rt2x00dev->drv_data); + rt2x00dev->drv_data = NULL; + return retval; } EXPORT_SYMBOL_GPL(rt2x00lib_probe_dev); From ea588e4c7484ea883d3dab308f1f2c2f0f51f7d6 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Mon, 6 Jul 2026 02:19:31 +0800 Subject: [PATCH 107/479] ALSA: usb-audio: Fix imbalance per-channel volume of sticky mixers I accidentally made an off-by-a-line mistake when mimicking other code paths that set all channels. The mistake breaks sticky mixers with multiple channels. I didn't realize this mistake at that time, as my device's mixer is single-channel. Fix it, so that per-channel volume of sticky mixers is balanced. Fixes: aa2f4addab44 ("ALSA: usb-audio: Set the value of potential sticky mixers to maximum") Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260706-uac-sticky-channels-fix-v1-1-92741c538283@rong.moe Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index b4c855c25eef..703c118f9d4e 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1473,8 +1473,8 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, if (!cval->cmask) { snd_usb_set_cur_mix_value(cval, 0, 0, cval->max); } else { + idx = 0; for (i = 0; i < MAX_CHANNELS; i++) { - idx = 0; if (cval->cmask & BIT(i)) { snd_usb_set_cur_mix_value(cval, i + 1, idx, cval->max); idx++; From c845febafd92b2056abc0af541c1ad85785f1353 Mon Sep 17 00:00:00 2001 From: Malaya Kumar Rout Date: Sun, 5 Jul 2026 22:40:38 +0530 Subject: [PATCH 108/479] selftests/alsa: Fix format specifier and function mismatch in mixer-test In the ctl_value_index_valid() function, when validating 64-bit integer controls (SND_CTL_ELEM_TYPE_INTEGER64), the error message for maximum value validation has two bugs: 1. Uses snd_ctl_elem_info_get_max() instead of the 64-bit variant snd_ctl_elem_info_get_max64() 2. Uses %ld format specifier instead of %lld for long long type This is inconsistent with the minimum value validation (line 335-339) which correctly uses get_min64() and %lld. The mismatch can cause: - Incorrect maximum values being reported on some architectures - Undefined behavior due to format specifier mismatch - Potential crashes when printing the error message Fix by using snd_ctl_elem_info_get_max64() and %lld format specifier to match the data type and be consistent with the minimum value check. Signed-off-by: Malaya Kumar Rout Link: https://patch.msgid.link/20260705171038.171966-1-malayarout91@gmail.com Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/mixer-test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index c5ae2a026ce9..a329f901c5ed 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -340,9 +340,9 @@ static bool ctl_value_index_valid(struct ctl_data *ctl, } if (int64_val > snd_ctl_elem_info_get_max64(ctl->info)) { - ksft_print_msg("%s.%d value %lld more than maximum %ld\n", + ksft_print_msg("%s.%d value %lld more than maximum %lld\n", ctl->name, index, int64_val, - snd_ctl_elem_info_get_max(ctl->info)); + snd_ctl_elem_info_get_max64(ctl->info)); return false; } From edf0730be33696a1bd142792830d392129e495cc Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 20 Jun 2026 00:25:42 +0800 Subject: [PATCH 109/479] wifi: cfg80211: cancel sched scan results work on unregister cfg80211_sched_scan_results() can queue rdev->sched_scan_res_wk from a driver result notification while a scheduled scan request is present. The work callback recovers the containing cfg80211_registered_device and then locks the wiphy and walks the scheduled-scan request list. wiphy_unregister() already makes the wiphy unreachable and drains rdev work items before cfg80211_dev_free() can release the object, but it does not drain sched_scan_res_wk. A queued or running result work item can therefore cross the unregister/free boundary and access freed rdev state. The buggy scenario involves two paths, with each column showing the order within that path: scheduled-scan result path: unregister/free path: 1. cfg80211_sched_scan_results() 1. interface teardown stops and queues rdev->sched_scan_res_wk. removes the scheduled scan request. 2. cfg80211_wq starts the work 2. wiphy_unregister() drains other item and recovers rdev. rdev work items. 3. The worker locks rdev->wiphy 3. cfg80211_dev_free() destroys and and walks rdev state. frees rdev. Cancel sched_scan_res_wk in wiphy_unregister() alongside the other rdev work items. cancel_work_sync() removes a pending result notification and waits for an already running callback, so cfg80211_dev_free() cannot free rdev while this work item is still active. Validation reproduced this kernel report: BUG: KASAN: use-after-free in cfg80211_sched_scan_results_wk+0x4a6/0x530 Workqueue: cfg80211 cfg80211_sched_scan_results_wk [cfg80211] Read of size 8 Call trace: dump_stack_lvl+0x66/0xa0 print_report+0xce/0x630 cfg80211_sched_scan_results_wk+0x4a6/0x530 srso_alias_return_thunk+0x5/0xfbef5 __virt_addr_valid+0x224/0x430 kasan_report+0xac/0xe0 lockdep_hardirqs_on_prepare+0xea/0x1a0 process_one_work+0x8d0/0x18f0 (kernel/workqueue.c:3212) lock_is_held_type+0x8f/0x100 worker_thread+0x5ad/0xfd0 __kthread_parkme+0xc6/0x200 kthread+0x31e/0x410 trace_hardirqs_on+0x1a/0x170 ret_from_fork+0x576/0x810 __switch_to+0x57e/0xe20 __switch_to_asm+0x33/0x70 ret_from_fork_asm+0x1a/0x30 Fixes: 807f8a8c3004 ("cfg80211/nl80211: add support for scheduled scans") Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260619162542.3878296-1-zzzccc427@gmail.com Signed-off-by: Johannes Berg --- net/wireless/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/wireless/core.c b/net/wireless/core.c index 3dcf63b04c41..2c729a7aca12 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -1335,6 +1335,7 @@ void wiphy_unregister(struct wiphy *wiphy) /* this has nothing to do now but make sure it's gone */ cancel_work_sync(&rdev->wiphy_work); + cancel_work_sync(&rdev->sched_scan_res_wk); cancel_work_sync(&rdev->rfkill_block); cancel_work_sync(&rdev->conn_work); flush_work(&rdev->event_work); From 0d388f62031dbabcba0f44bb91b59f10e88cac17 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Sat, 20 Jun 2026 12:22:39 +0530 Subject: [PATCH 110/479] wifi: ipw2100: fix potential memory leak in ipw2100_pci_init_one() The memory allocated in the ipw2100_alloc_device() function is not freed in some of the error paths in ipw2100_pci_init_one(). Fix that by converting the direct return into a goto to the error path return. The error path when pci_enable_device() fails cannot jump to fail, since at this point priv is not set, so perform error handling inline. Fixes: 2c86c275015c ("Add ipw2100 wireless driver.") Signed-off-by: Abdun Nihaal Link: https://patch.msgid.link/20260620065242.93798-1-nihaal@cse.iitm.ac.in Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/ipw2100.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intel/ipw2x00/ipw2100.c b/drivers/net/wireless/intel/ipw2x00/ipw2100.c index c11428485dcc..2b8a23865bfb 100644 --- a/drivers/net/wireless/intel/ipw2x00/ipw2100.c +++ b/drivers/net/wireless/intel/ipw2x00/ipw2100.c @@ -6157,6 +6157,8 @@ static int ipw2100_pci_init_one(struct pci_dev *pci_dev, if (err) { printk(KERN_WARNING DRV_NAME "Error calling pci_enable_device.\n"); + free_libipw(dev, 0); + pci_iounmap(pci_dev, ioaddr); return err; } @@ -6169,16 +6171,14 @@ static int ipw2100_pci_init_one(struct pci_dev *pci_dev, if (err) { printk(KERN_WARNING DRV_NAME "Error calling pci_set_dma_mask.\n"); - pci_disable_device(pci_dev); - return err; + goto fail; } err = pci_request_regions(pci_dev, DRV_NAME); if (err) { printk(KERN_WARNING DRV_NAME "Error calling pci_request_regions.\n"); - pci_disable_device(pci_dev); - return err; + goto fail; } /* We disable the RETRY_TIMEOUT register (0x41) to keep From c6659f66d4ee4841aafae5659d2ef5e4c5c63cb6 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 20 Jun 2026 21:48:56 +0200 Subject: [PATCH 111/479] wifi: cfg80211: Fix an error handling path in cfg80211_wext_siwscan() If the test against IEEE80211_MAX_SSID_LEN fails, then 'creq' leaks. Use the existing error handling path to fix it. Fixes: 2a5193119269 ("cfg80211/nl80211: scanning (and mac80211 update to use it)") Signed-off-by: Christophe JAILLET Link: https://patch.msgid.link/a1be7eea4da0da18f90589af252bb76a18a61978.1781984889.git.christophe.jaillet@wanadoo.fr Signed-off-by: Johannes Berg --- net/wireless/scan.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 05b7dc6b766c..38001684014d 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -3612,8 +3612,10 @@ int cfg80211_wext_siwscan(struct net_device *dev, /* translate "Scan for SSID" request */ if (wreq) { if (wrqu->data.flags & IW_SCAN_THIS_ESSID) { - if (wreq->essid_len > IEEE80211_MAX_SSID_LEN) - return -EINVAL; + if (wreq->essid_len > IEEE80211_MAX_SSID_LEN) { + err = -EINVAL; + goto out; + } memcpy(creq->req.ssids[0].ssid, wreq->essid, wreq->essid_len); creq->req.ssids[0].ssid_len = wreq->essid_len; From 10a2b430f8f06ae14b9590b6f6faa6b588ef0654 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sat, 20 Jun 2026 21:45:18 -0500 Subject: [PATCH 112/479] wifi: mac80211_hwsim: clamp virtio RX length before skb_put hwsim_virtio_rx_work() passes the virtqueue used-ring length reported by the device straight to skb_put() on a fixed-size receive skb. A backend reporting a length larger than the skb tailroom drives skb_put() past the buffer end and hits skb_over_panic() -- a host-triggerable guest panic (denial of service). Clamp the length to the skb's available room before skb_put(). A conforming device never reports more than the posted buffer size, so valid frames are unaffected; a truncated over-report then fails the length/header checks in hwsim_virtio_handle_cmd() and is dropped, so truncating rather than dropping here cannot be turned into a parsing problem. Fixes: 5d44fe7c9808 ("mac80211_hwsim: add frame transmission support over virtio") Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260620-b4-disp-474bee37-v1-1-1a4d37f3e2d4@proton.me Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/mac80211_hwsim_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 0dd8a6c85953..5c1718277599 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -7289,6 +7289,7 @@ static void hwsim_virtio_rx_work(struct work_struct *work) skb->data = skb->head; skb_reset_tail_pointer(skb); + len = min(len, skb_end_offset(skb)); skb_put(skb, len); hwsim_virtio_handle_cmd(skb); From 1d067abcd37062426c59ec73dbc4e87a63f33fea Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sun, 21 Jun 2026 02:35:31 -0700 Subject: [PATCH 113/479] wifi: mac80211: fix unsol_bcast_probe_resp double free on alloc failure ieee80211_set_unsol_bcast_probe_resp() calls kfree_rcu() on the old template before allocating the replacement. If the kzalloc() then fails, it returns -ENOMEM while link->u.ap.unsol_bcast_probe_resp still points at the object already queued for freeing. A later update or AP teardown re-queues that same rcu_head; the second free is caught by KASAN when the RCU sheaf is processed in softirq: BUG: KASAN: double-free in rcu_free_sheaf (mm/slub.c:5850) Free of addr ffff88800d06f300 by task exploit/145 ... __rcu_free_sheaf_prepare (mm/slub.c:2634 mm/slub.c:2940) rcu_free_sheaf (mm/slub.c:5850) rcu_core (kernel/rcu/tree.c:2617 kernel/rcu/tree.c:2869) handle_softirqs (kernel/softirq.c:622) The buggy address belongs to the cache kmalloc-128 of size 128 Queue the old object for kfree_rcu() only after the new one is published, matching ieee80211_set_probe_resp() and ieee80211_set_s1g_short_beacon(). Fixes: 3b1c256eb4ae ("wifi: mac80211: fixes in FILS discovery updates") Reported-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260621093532.884188-1-xmei5@asu.edu Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 3b58af59f7e4..932cf20785bc 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1179,8 +1179,6 @@ ieee80211_set_unsol_bcast_probe_resp(struct ieee80211_sub_if_data *sdata, link_conf->unsol_bcast_probe_resp_interval = params->interval; old = sdata_dereference(link->u.ap.unsol_bcast_probe_resp, sdata); - if (old) - kfree_rcu(old, rcu_head); if (params->tmpl && params->tmpl_len) { new = kzalloc(sizeof(*new) + params->tmpl_len, GFP_KERNEL); @@ -1193,6 +1191,9 @@ ieee80211_set_unsol_bcast_probe_resp(struct ieee80211_sub_if_data *sdata, RCU_INIT_POINTER(link->u.ap.unsol_bcast_probe_resp, NULL); } + if (old) + kfree_rcu(old, rcu_head); + *changed |= BSS_CHANGED_UNSOL_BCAST_PROBE_RESP; return 0; } From 286e52a799fa158bdbd77da1426c4d93f9a6e7ad Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sun, 21 Jun 2026 02:35:32 -0700 Subject: [PATCH 114/479] wifi: mac80211: fix fils_discovery double free on alloc failure ieee80211_set_fils_discovery() calls kfree_rcu() on the old template before allocating the replacement. If the kzalloc() then fails, it returns -ENOMEM while link->u.ap.fils_discovery still points at the object already queued for freeing. A later update or AP teardown (ieee80211_stop_ap()) re-queues that same rcu_head; the second free is caught by KASAN when the RCU sheaf is processed in softirq: BUG: KASAN: double-free in rcu_free_sheaf (mm/slub.c:5850) Free of addr ffff88800c065280 by task swapper/0/0 ... __rcu_free_sheaf_prepare (mm/slub.c:2634 mm/slub.c:2940) rcu_free_sheaf (mm/slub.c:5850) rcu_core (kernel/rcu/tree.c:2617 kernel/rcu/tree.c:2869) handle_softirqs (kernel/softirq.c:622) The buggy address belongs to the cache kmalloc-96 of size 96 Queue the old object for kfree_rcu() only after the new one is published, matching ieee80211_set_probe_resp() and ieee80211_set_s1g_short_beacon(). Fixes: 3b1c256eb4ae ("wifi: mac80211: fixes in FILS discovery updates") Reported-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260621093532.884188-2-xmei5@asu.edu Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 932cf20785bc..b00191e02a63 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1146,9 +1146,6 @@ static int ieee80211_set_fils_discovery(struct ieee80211_sub_if_data *sdata, fd->max_interval = params->max_interval; old = sdata_dereference(link->u.ap.fils_discovery, sdata); - if (old) - kfree_rcu(old, rcu_head); - if (params->tmpl && params->tmpl_len) { new = kzalloc(sizeof(*new) + params->tmpl_len, GFP_KERNEL); if (!new) @@ -1160,6 +1157,9 @@ static int ieee80211_set_fils_discovery(struct ieee80211_sub_if_data *sdata, RCU_INIT_POINTER(link->u.ap.fils_discovery, NULL); } + if (old) + kfree_rcu(old, rcu_head); + *changed |= BSS_CHANGED_FILS_DISCOVERY; return 0; } From aa6dcd5c8dd9ba1d7d0f60093bcda41c0d6d438d Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Mon, 22 Jun 2026 15:53:38 +0800 Subject: [PATCH 115/479] wifi: libertas_tf: fix use-after-free in lbtf_free_adapter() 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 --- drivers/net/wireless/marvell/libertas_tf/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/libertas_tf/main.c b/drivers/net/wireless/marvell/libertas_tf/main.c index fb20fe31cd36..42be6fa22f9c 100644 --- 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 lbtf_private *priv) { lbtf_deb_enter(LBTF_DEB_MAIN); lbtf_free_cmd_buffer(priv); - timer_delete(&priv->command_timer); + timer_delete_sync(&priv->command_timer); lbtf_deb_leave(LBTF_DEB_MAIN); } From 63c2391deefb31e1b801b7f32bd502ca4808639b Mon Sep 17 00:00:00 2001 From: Dawei Feng Date: Wed, 24 Jun 2026 16:53:43 +0800 Subject: [PATCH 116/479] wifi: libertas: fix memory leak in helper_firmware_cb() helper_firmware_cb() neglects to free the single-stage firmware image after a successful async load, leading to a memory leak in the USB firmware-download path. Fix this memory leak by calling release_firmware() immediately after lbs_fw_loaded() returns. 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 the current wireless tree. An x86_64 allyesconfig build showed no new warnings. As we do not have compatible Libertas USB hardware for exercising this firmware-download path, no runtime testing was able to be performed. Fixes: 1dfba3060fe7 ("libertas: move firmware lifetime handling to firmware.c") Signed-off-by: Dawei Feng Link: https://patch.msgid.link/20260624085343.575508-1-dawei.feng@seu.edu.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/firmware.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/marvell/libertas/firmware.c b/drivers/net/wireless/marvell/libertas/firmware.c index f124110944b7..9bf7d4c207b9 100644 --- a/drivers/net/wireless/marvell/libertas/firmware.c +++ b/drivers/net/wireless/marvell/libertas/firmware.c @@ -78,6 +78,7 @@ static void helper_firmware_cb(const struct firmware *firmware, void *context) } else { /* No main firmware needed for this helper --> success! */ lbs_fw_loaded(priv, 0, firmware, NULL); + release_firmware(firmware); } } From 23b493d9dc5f00bba59347bd8ec7b044e26392a0 Mon Sep 17 00:00:00 2001 From: Yousef Alhouseen Date: Sun, 28 Jun 2026 02:25:37 +0200 Subject: [PATCH 117/479] wifi: mac80211_hwsim: avoid treating MCS as legacy rate index Injected HT and VHT rates store an MCS value in rates[0].idx rather than an index into the legacy bitrate table. hwsim nevertheless passes these rates to ieee80211_get_tx_rate() while generating monitor frames and timestamps. A crafted injected frame can therefore read beyond the bitrate table. If the resulting bitrate is zero, mac80211_hwsim_write_tsf() also divides by zero, as observed by syzbot. Use ieee80211_get_tx_rate() only for legacy rates. The existing fallback continues to supply a conservative bitrate where hwsim does not yet calculate MCS rates. Reported-by: syzbot+21629c14aa749636db9d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=21629c14aa749636db9d Signed-off-by: Yousef Alhouseen Link: https://patch.msgid.link/20260628002537.23550-1-alhouseenyousef@gmail.com [drop wrong Fixes tag] Signed-off-by: Johannes Berg --- .../net/wireless/virtual/mac80211_hwsim_main.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 5c1718277599..956ff9b94526 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -1324,6 +1324,17 @@ static void mac80211_hwsim_set_tsf(struct ieee80211_hw *hw, } } +static struct ieee80211_rate * +mac80211_hwsim_get_tx_rate(struct ieee80211_hw *hw, + struct ieee80211_tx_info *info) +{ + if (info->control.rates[0].flags & + (IEEE80211_TX_RC_MCS | IEEE80211_TX_RC_VHT_MCS)) + return NULL; + + return ieee80211_get_tx_rate(hw, info); +} + static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw, struct sk_buff *tx_skb, struct ieee80211_channel *chan) @@ -1333,7 +1344,7 @@ static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw, struct hwsim_radiotap_hdr *hdr; u16 flags, bitrate; struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx_skb); - struct ieee80211_rate *txrate = ieee80211_get_tx_rate(hw, info); + struct ieee80211_rate *txrate = mac80211_hwsim_get_tx_rate(hw, info); if (!txrate) bitrate = 0; @@ -1603,7 +1614,7 @@ static void mac80211_hwsim_write_tsf(struct mac80211_hwsim_data *data, spin_lock_bh(&data->tsf_offset_lock); - txrate = ieee80211_get_tx_rate(data->hw, info); + txrate = mac80211_hwsim_get_tx_rate(data->hw, info); if (txrate) bitrate = txrate->bitrate; From 2c51457d930f723e5f2903af90f5847f7df53f42 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 27 Jun 2026 00:58:30 +0800 Subject: [PATCH 118/479] wifi: mac80211: free ack status frame on TX header build failure ieee80211_build_hdr() stores an ACK status frame before it has finished all validation and header construction. If a later error path is taken, the transmit skb is freed but the stored ACK status frame remains in local->ack_status_frames. This can happen for control port frames when the requested MLO link ID does not match the link selected for a non-MLO station. Repeated failures can fill the ACK status IDR and leave pending ACK frames until hardware teardown. Remove any stored ACK status frame before returning an error after it has been inserted into the IDR. Fixes: a729cff8ad51 ("mac80211: implement wifi TX status") Cc: stable@vger.kernel.org Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Xin Liu Assisted-by: Codex:gpt-5.4 Signed-off-by: Zhiling Zou Signed-off-by: Ren Wei Link: https://patch.msgid.link/9de0423da840e92084915b8f92e66a421245c4b8.1782462409.git.roxy520tt@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index c13b209fad47..91b14112e24f 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2607,6 +2607,18 @@ static u16 ieee80211_store_ack_skb(struct ieee80211_local *local, return info_id; } +static void ieee80211_remove_ack_skb(struct ieee80211_local *local, u16 info_id) +{ + struct sk_buff *ack_skb; + unsigned long flags; + + spin_lock_irqsave(&local->ack_status_lock, flags); + ack_skb = idr_remove(&local->ack_status_frames, info_id); + spin_unlock_irqrestore(&local->ack_status_lock, flags); + + kfree_skb(ack_skb); +} + /** * ieee80211_build_hdr - build 802.11 header in the given frame * @sdata: virtual interface to build the header for @@ -2982,7 +2994,8 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, if (ieee80211_skb_resize(sdata, skb, head_need, ENCRYPT_DATA)) { ieee80211_free_txskb(&local->hw, skb); skb = NULL; - return ERR_PTR(-ENOMEM); + ret = -ENOMEM; + goto free; } } @@ -3050,6 +3063,8 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, return skb; free: + if (info_id) + ieee80211_remove_ack_skb(local, info_id); kfree_skb(skb); return ERR_PTR(ret); } From aa2eb62525188269cdd402a583b9a8ed94657ff0 Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Sat, 27 Jun 2026 16:30:28 +0800 Subject: [PATCH 119/479] wifi: mac80211: defer link RX stats percpu free to RCU sta_remove_link() frees a removed MLO link's RX stats percpu buffer right away, but defers only the link container to RCU: sta_info_free_link(&alloc->info); kfree_rcu(alloc, rcu_head); The RX fast path reads link_sta under rcu_read_lock and writes the percpu stats. A reader that resolved link_sta before the removal keeps the pointer. The container stays alive from the kfree_rcu, so the read still works. But the percpu block it points to is already freed. This needs uses_rss. That is when pcpu_rx_stats exists. The full STA teardown frees the deflink stats only after synchronize_net(). The link removal path had no such barrier. The race is hard to win in practice, but the free should still wait for RCU. Free the link together with its data from a single RCU callback, so the percpu block is reclaimed only after readers drain. Fixes: c71420db653a ("wifi: mac80211: RCU-ify link STA pointers") Link: https://lore.kernel.org/r/20260626080158.3589711-1-maoyixie.tju@gmail.com Suggested-by: Johannes Berg Co-developed-by: Kaixuan Li Signed-off-by: Kaixuan Li Signed-off-by: Maoyi Xie Link: https://patch.msgid.link/20260627083028.3826810-1-maoyixie.tju@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/sta_info.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 02b587ff8504..22eba0e6e54c 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -355,6 +355,15 @@ static void sta_info_free_link(struct link_sta_info *link_sta) free_percpu(link_sta->pcpu_rx_stats); } +static void sta_link_free_rcu(struct rcu_head *head) +{ + struct sta_link_alloc *alloc = + container_of(head, struct sta_link_alloc, rcu_head); + + sta_info_free_link(&alloc->info); + kfree(alloc); +} + static void sta_accumulate_removed_link_stats(struct sta_info *sta, int link_id) { struct link_sta_info *link_sta = wiphy_dereference(sta->local->hw.wiphy, @@ -439,10 +448,8 @@ static void sta_remove_link(struct sta_info *sta, unsigned int link_id, RCU_INIT_POINTER(sta->link[link_id], NULL); RCU_INIT_POINTER(sta->sta.link[link_id], NULL); - if (alloc) { - sta_info_free_link(&alloc->info); - kfree_rcu(alloc, rcu_head); - } + if (alloc) + call_rcu(&alloc->rcu_head, sta_link_free_rcu); ieee80211_sta_recalc_aggregates(&sta->sta); } From ebd6d37fa94bee929e0b4c9ca19fdf9b1dcf6cea Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sat, 27 Jun 2026 17:05:10 -0700 Subject: [PATCH 120/479] wifi: p54: validate RX frame length in p54_rx_eeprom_readback() p54_rx_eeprom_readback() copies the requested EEPROM slice out of a device-supplied readback frame without checking that the skb actually holds that many bytes. Commit da1b9a55ff11 ("wifi: p54: prevent buffer-overflow in p54_rx_eeprom_readback()") closed the destination overflow by copying a fixed priv->eeprom_slice_size (and rejecting a mismatched advertised len), but the source side is still unbounded: nothing verifies the frame is long enough to supply that many bytes. A malicious USB device can send a short frame whose advertised len matches priv->eeprom_slice_size while the payload is truncated. The equality check passes and memcpy() reads past the end of the skb, leaking adjacent heap: BUG: KASAN: slab-out-of-bounds in p54_rx (drivers/net/wireless/intersil/p54/txrx.c:507) Read of size 1016 at addr ffff88800f077114 by task swapper/0/0 Call Trace: ... __asan_memcpy (mm/kasan/shadow.c:105) p54_rx (drivers/net/wireless/intersil/p54/txrx.c:507) p54u_rx_cb (drivers/net/wireless/intersil/p54/p54usb.c:163) __usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1657) dummy_timer (drivers/usb/gadget/udc/dummy_hcd.c:2005) ... The buggy address belongs to the object at ffff88800f0770c0 which belongs to the cache skbuff_small_head of size 704 The buggy address is located 84 bytes inside of allocated 704-byte region [ffff88800f0770c0, ffff88800f077380) Check that the slice fits in the skb before copying. Fixes: 7cb770729ba8 ("p54: move eeprom code into common library") Reported-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Acked-by: Christian Lamparter Link: https://patch.msgid.link/20260628000510.4152481-1-xmei5@asu.edu Signed-off-by: Johannes Berg --- drivers/net/wireless/intersil/p54/txrx.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/intersil/p54/txrx.c b/drivers/net/wireless/intersil/p54/txrx.c index 1294a1d6528e..9f491334c8d0 100644 --- a/drivers/net/wireless/intersil/p54/txrx.c +++ b/drivers/net/wireless/intersil/p54/txrx.c @@ -499,11 +499,19 @@ static void p54_rx_eeprom_readback(struct p54_common *priv, if (le16_to_cpu(eeprom->v2.len) != priv->eeprom_slice_size) return; + if (eeprom->v2.data + priv->eeprom_slice_size > + skb_tail_pointer(skb)) + return; + memcpy(priv->eeprom, eeprom->v2.data, priv->eeprom_slice_size); } else { if (le16_to_cpu(eeprom->v1.len) != priv->eeprom_slice_size) return; + if (eeprom->v1.data + priv->eeprom_slice_size > + skb_tail_pointer(skb)) + return; + memcpy(priv->eeprom, eeprom->v1.data, priv->eeprom_slice_size); } From 843fe9bc583b7686ca68312ac9319c9240a73c03 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 1 Jul 2026 13:34:14 +0800 Subject: [PATCH 121/479] wifi: rsi: avoid reading TKIP MIC keys for non-TKIP ciphers rsi_hal_load_key() copies tx_mic_key and rx_mic_key from data[16] and data[24] whenever key data is present. Those offsets are only part of the 32-byte TKIP key layout. Shorter keys used by other ciphers, such as CCMP, do not provide those bytes, so the unconditional copies can read past the supplied key buffer. Only copy the MIC keys for TKIP, and reject malformed TKIP keys that are shorter than the expected 32-byte layout. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260701053414.34015-1-pengpeng@iscas.ac.cn [drop useless length check] Signed-off-by: Johannes Berg --- drivers/net/wireless/rsi/rsi_91x_mgmt.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/rsi/rsi_91x_mgmt.c b/drivers/net/wireless/rsi/rsi_91x_mgmt.c index 7f2c1608f2ce..2ddf4d158bfe 100644 --- a/drivers/net/wireless/rsi/rsi_91x_mgmt.c +++ b/drivers/net/wireless/rsi/rsi_91x_mgmt.c @@ -848,8 +848,10 @@ int rsi_hal_load_key(struct rsi_common *common, } else { memcpy(&set_key->key[0][0], data, key_len); } - memcpy(set_key->tx_mic_key, &data[16], 8); - memcpy(set_key->rx_mic_key, &data[24], 8); + if (cipher == WLAN_CIPHER_SUITE_TKIP) { + memcpy(set_key->tx_mic_key, &data[16], 8); + memcpy(set_key->rx_mic_key, &data[24], 8); + } } else { memset(&set_key[FRAME_DESC_SZ], 0, frame_len - FRAME_DESC_SZ); } From 74e27cd1d98b546fdb276008a83708d062339661 Mon Sep 17 00:00:00 2001 From: Haofeng Li Date: Wed, 1 Jul 2026 17:33:27 +0800 Subject: [PATCH 122/479] wifi: cfg80211: validate EHT MLE before MLD ID read cfg80211_gen_new_ie() copies ML probe response elements from the parent frame when the parent EHT multi-link element has an MLD ID matching the nontransmitted BSSID index. The code only checked that the extension element had more than one byte before calling ieee80211_mle_get_mld_id(). That helper assumes a BASIC MLE with enough common info and documents that callers must first use ieee80211_mle_type_ok(). Attack chain: malicious AP sends a short EHT MLE in an MBSSID beacon. cfg80211_inform_bss_frame_data() stores the copied IE buffer. cfg80211_parse_mbssid_data() builds the nontransmitted BSS IE. cfg80211_gen_new_ie() sees the EHT MLE in the parent frame. ieee80211_mle_get_mld_id() then reads past the IE boundary. Validate the MLE type and size before reading the MLD ID. This matches the contract required by the MLE helper and rejects the short element before any internal MLE fields are accessed. Cc: stable@vger.kernel.org Fixes: 61dcfa8c2a8f ("wifi: cfg80211: copy multi-link element from the multi-link probe request's frame body to the generated elements") Signed-off-by: Haofeng Li Link: https://patch.msgid.link/20260701093327.2680709-1-lihaofeng@kylinos.cn Signed-off-by: Johannes Berg --- net/wireless/scan.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 38001684014d..e1c09040a5c8 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -326,8 +326,11 @@ cfg80211_gen_new_ie(const u8 *ie, size_t ielen, /* For ML probe response, match the MLE in the frame body with * MLD id being 'bssid_index' */ - if (parent->id == WLAN_EID_EXTENSION && parent->datalen > 1 && + if (parent->id == WLAN_EID_EXTENSION && parent->data[0] == WLAN_EID_EXT_EHT_MULTI_LINK && + ieee80211_mle_type_ok(parent->data + 1, + IEEE80211_ML_CONTROL_TYPE_BASIC, + parent->datalen - 1) && bssid_index == ieee80211_mle_get_mld_id(parent->data + 1)) { if (!cfg80211_copy_elem_with_frags(parent, ie, ielen, From 2b0eab425e1f658d8fe1df7590e3b9af5959505e Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Fri, 3 Jul 2026 13:55:23 +0530 Subject: [PATCH 123/479] wifi: cfg80211: convert pmsr_free_wk to wiphy_work to fix deadlock When a netlink socket that owns a PMSR session is closed, cfg80211_release_pmsr() clears the request's nl_portid and queues pmsr_free_wk to call cfg80211_pmsr_process_abort() asynchronously. If the interface tears down concurrently, cfg80211_pmsr_wdev_down() is called under wiphy_lock and calls cancel_work_sync(&pmsr_free_wk) to wait for any running work. The work function acquires wiphy_lock via guard(wiphy) before calling process_abort. This is a deadlock: wdev_down holds wiphy_lock and blocks inside cancel_work_sync(); pmsr_free_wk blocks trying to acquire that same wiphy_lock. Neither thread can proceed. The same deadlock is reachable from cfg80211_leave_locked(), which calls cfg80211_pmsr_wdev_down() for all interface types under wiphy_lock. Fix this by converting pmsr_free_wk from a plain work_struct to a wiphy_work. The wiphy_work dispatcher holds wiphy_lock when running work items, so the explicit guard(wiphy) in the work function is no longer needed. wiphy_work_cancel() can be called safely while holding wiphy_lock - since wiphy_lock prevents the work from running concurrently, wiphy_work_cancel() never blocks, eliminating the deadlock. Remove the cancel_work_sync() for pmsr_free_wk from the NETDEV_GOING_DOWN handler. cfg80211_leave(), called unconditionally just before it, already cancels any pending work under wiphy_lock via wiphy_work_cancel() inside cfg80211_pmsr_wdev_down(). Fixes: 6dccbc9f3e1d ("wifi: cfg80211: cancel pmsr_free_wk in cfg80211_pmsr_wdev_down") Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260703082523.2629324-1-peddolla.reddy@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 2 +- net/wireless/core.c | 3 +-- net/wireless/core.h | 2 +- net/wireless/pmsr.c | 8 +++----- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 8188ad200de5..3751a1d74765 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -7265,7 +7265,7 @@ struct wireless_dev { struct list_head pmsr_list; spinlock_t pmsr_lock; - struct work_struct pmsr_free_wk; + struct wiphy_work pmsr_free_wk; unsigned long unprot_beacon_reported; diff --git a/net/wireless/core.c b/net/wireless/core.c index 2c729a7aca12..082f0ee12f1b 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -1614,7 +1614,7 @@ void cfg80211_init_wdev(struct wireless_dev *wdev) INIT_LIST_HEAD(&wdev->mgmt_registrations); INIT_LIST_HEAD(&wdev->pmsr_list); spin_lock_init(&wdev->pmsr_lock); - INIT_WORK(&wdev->pmsr_free_wk, cfg80211_pmsr_free_wk); + wiphy_work_init(&wdev->pmsr_free_wk, cfg80211_pmsr_free_wk); #ifdef CONFIG_CFG80211_WEXT wdev->wext.default_key = -1; @@ -1748,7 +1748,6 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb, cfg80211_remove_links(wdev); /* since we just did cfg80211_leave() nothing to do there */ cancel_work_sync(&wdev->disconnect_wk); - cancel_work_sync(&wdev->pmsr_free_wk); break; case NETDEV_DOWN: wiphy_lock(&rdev->wiphy); diff --git a/net/wireless/core.h b/net/wireless/core.h index df47ed6208a5..f60c66b88677 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -586,7 +586,7 @@ cfg80211_get_6ghz_power_type(const u8 *elems, size_t elems_len, void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid); void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev); -void cfg80211_pmsr_free_wk(struct work_struct *work); +void cfg80211_pmsr_free_wk(struct wiphy *wiphy, struct wiphy_work *work); void cfg80211_remove_link(struct wireless_dev *wdev, unsigned int link_id); void cfg80211_remove_links(struct wireless_dev *wdev); diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index c8447448f3a5..2c8db33d9c30 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -807,13 +807,11 @@ static void cfg80211_pmsr_process_abort(struct wireless_dev *wdev) } } -void cfg80211_pmsr_free_wk(struct work_struct *work) +void cfg80211_pmsr_free_wk(struct wiphy *wiphy, struct wiphy_work *work) { struct wireless_dev *wdev = container_of(work, struct wireless_dev, pmsr_free_wk); - guard(wiphy)(wdev->wiphy); - cfg80211_pmsr_process_abort(wdev); } @@ -829,7 +827,7 @@ void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev) } spin_unlock_bh(&wdev->pmsr_lock); - cancel_work_sync(&wdev->pmsr_free_wk); + wiphy_work_cancel(wdev->wiphy, &wdev->pmsr_free_wk); if (found) cfg80211_pmsr_process_abort(wdev); @@ -844,7 +842,7 @@ void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid) list_for_each_entry(req, &wdev->pmsr_list, list) { if (req->nl_portid == portid) { req->nl_portid = 0; - schedule_work(&wdev->pmsr_free_wk); + wiphy_work_queue(wdev->wiphy, &wdev->pmsr_free_wk); } } spin_unlock_bh(&wdev->pmsr_lock); From 0a2581cbae9e442835f68d22044157db61cdf54d Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Fri, 3 Jul 2026 13:49:32 +0000 Subject: [PATCH 124/479] wifi: ralink: RT2X00: init EEPROM properly I have an hostapd setup with a 01:00.0 Network controller: Ralink corp. RT2790 Wireless 802.11n 1T/2R PCIe The setup work fine on 6.18.26-gentoo It breaks on 6.18.33-gentoo (and still broken on 6.18.37) I found an hint in dmesg: On 6.18.26-gentoo I see: May 31 15:48:45 trash01 kernel: ieee80211 phy0: rt2x00_set_rf: Info - RF chipset 0003 detected On 6.18.33-gentoo I see: May 31 15:22:57 trash01 kernel: ieee80211 phy0: rt2x00_set_rf: Info - RF chipset 0006 detected The RF chipset seems badly detected. The problem was the EEPROM which was badly initialized. Probably the origin was in some PCI change but unfortunately I couldn't play to bisect/reboot often the board with this card to do it. Signed-off-by: Corentin Labbe Acked-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260703134932.3786771-1-clabbe@baylibre.com Signed-off-by: Johannes Berg --- drivers/net/wireless/ralink/rt2x00/rt2400pci.c | 2 +- drivers/net/wireless/ralink/rt2x00/rt2500pci.c | 2 +- drivers/net/wireless/ralink/rt2x00/rt2800pci.c | 2 +- drivers/net/wireless/ralink/rt2x00/rt61pci.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ralink/rt2x00/rt2400pci.c b/drivers/net/wireless/ralink/rt2x00/rt2400pci.c index cac191304bf5..b846fe589c2b 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2400pci.c +++ b/drivers/net/wireless/ralink/rt2x00/rt2400pci.c @@ -1429,7 +1429,7 @@ static irqreturn_t rt2400pci_interrupt(int irq, void *dev_instance) */ static int rt2400pci_validate_eeprom(struct rt2x00_dev *rt2x00dev) { - struct eeprom_93cx6 eeprom; + struct eeprom_93cx6 eeprom = {}; u32 reg; u16 word; u8 *mac; diff --git a/drivers/net/wireless/ralink/rt2x00/rt2500pci.c b/drivers/net/wireless/ralink/rt2x00/rt2500pci.c index fc35b60e422c..be9df35acc33 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2500pci.c +++ b/drivers/net/wireless/ralink/rt2x00/rt2500pci.c @@ -1555,7 +1555,7 @@ static irqreturn_t rt2500pci_interrupt(int irq, void *dev_instance) */ static int rt2500pci_validate_eeprom(struct rt2x00_dev *rt2x00dev) { - struct eeprom_93cx6 eeprom; + struct eeprom_93cx6 eeprom = {}; u32 reg; u16 word; u8 *mac; diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800pci.c b/drivers/net/wireless/ralink/rt2x00/rt2800pci.c index 4fa14bb573ad..2596b9fcc7dd 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2800pci.c +++ b/drivers/net/wireless/ralink/rt2x00/rt2800pci.c @@ -108,7 +108,7 @@ static void rt2800pci_eepromregister_write(struct eeprom_93cx6 *eeprom) static int rt2800pci_read_eeprom_pci(struct rt2x00_dev *rt2x00dev) { - struct eeprom_93cx6 eeprom; + struct eeprom_93cx6 eeprom = {}; u32 reg; reg = rt2x00mmio_register_read(rt2x00dev, E2PROM_CSR); diff --git a/drivers/net/wireless/ralink/rt2x00/rt61pci.c b/drivers/net/wireless/ralink/rt2x00/rt61pci.c index 79e1fd0a1fbd..d4783658b2c5 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt61pci.c +++ b/drivers/net/wireless/ralink/rt2x00/rt61pci.c @@ -2298,7 +2298,7 @@ static irqreturn_t rt61pci_interrupt(int irq, void *dev_instance) */ static int rt61pci_validate_eeprom(struct rt2x00_dev *rt2x00dev) { - struct eeprom_93cx6 eeprom; + struct eeprom_93cx6 eeprom = {}; u32 reg; u16 word; u8 *mac; From 13ff543e0b2c713aedeaadadde686686e949dc78 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 09:11:40 +0800 Subject: [PATCH 125/479] wifi: libertas: reject short monitor TX frames In monitor mode, lbs_hard_start_xmit() casts skb->data to a radiotap TX header, skips that header, and then copies the 802.11 destination address from offset 4 in the remaining frame. The generic length check only rejects zero-length and oversized skbs, so a short monitor frame can be read past the end of the skb data. Require enough bytes for the radiotap TX header and the destination address field before using the monitor-mode header layout. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260704011140.37639-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/tx.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/wireless/marvell/libertas/tx.c b/drivers/net/wireless/marvell/libertas/tx.c index 27304a98787d..13d08022e414 100644 --- a/drivers/net/wireless/marvell/libertas/tx.c +++ b/drivers/net/wireless/marvell/libertas/tx.c @@ -117,6 +117,13 @@ netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) { struct tx_radiotap_hdr *rtap_hdr = (void *)skb->data; + if (skb->len < sizeof(*rtap_hdr) + 4 + ETH_ALEN) { + lbs_deb_tx("tx err: short monitor frame %u\n", skb->len); + dev->stats.tx_dropped++; + dev->stats.tx_errors++; + goto free; + } + /* set txpd fields from the radiotap header */ txpd->tx_control = cpu_to_le32(convert_radiotap_rate_to_mv(rtap_hdr->rate)); From d06a3e60c8fead962f08cf951eb1de7bd22dab76 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 09:12:30 +0800 Subject: [PATCH 126/479] wifi: rsi: bound background scan probe request copy rsi_send_bgscan_probe_req() allocates room for struct rsi_bgscan_probe plus MAX_BGSCAN_PROBE_REQ_LEN bytes, but copies the entire mac80211-generated probe request skb after the fixed header. The probe request length depends on scan IEs and is not checked against the fixed firmware buffer. Reject generated probe requests that do not fit the firmware command buffer before copying them into the skb. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260704011231.45593-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/rsi/rsi_91x_mgmt.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/rsi/rsi_91x_mgmt.c b/drivers/net/wireless/rsi/rsi_91x_mgmt.c index 2ddf4d158bfe..bb167f03367b 100644 --- a/drivers/net/wireless/rsi/rsi_91x_mgmt.c +++ b/drivers/net/wireless/rsi/rsi_91x_mgmt.c @@ -1913,6 +1913,12 @@ int rsi_send_bgscan_probe_req(struct rsi_common *common, return -ENOMEM; } + if (probereq_skb->len > MAX_BGSCAN_PROBE_REQ_LEN) { + dev_kfree_skb(probereq_skb); + dev_kfree_skb(skb); + return -EINVAL; + } + memcpy(&skb->data[frame_len], probereq_skb->data, probereq_skb->len); bgscan->probe_req_length = cpu_to_le16(probereq_skb->len); From 74ed3669f26803b1761c1f55403062bea44c3466 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 5 Jul 2026 16:35:19 +0800 Subject: [PATCH 127/479] wifi: libipw: fix key index receive bound checks libipw_rx() reads skb->data[hdrlen + 3] to extract the WEP key index in both the software-decrypt key selection path and the hardware-decrypted IV/ICV strip path. In both places the existing guard only checks skb->len >= hdrlen + 3, which proves bytes up to hdrlen + 2 but not the byte at hdrlen + 3. Require hdrlen + 4 bytes before reading that item in both paths. This is a local source-boundary check only; it does not change the key index semantics. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260705083519.23567-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/libipw_rx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c index b7bc94f7abd8..c8841f9b9ad9 100644 --- a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c +++ b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c @@ -414,7 +414,7 @@ int libipw_rx(struct libipw_device *ieee, struct sk_buff *skb, ieee->host_mc_decrypt : ieee->host_decrypt; if (can_be_decrypted) { - if (skb->len >= hdrlen + 3) { + if (skb->len >= hdrlen + 4) { /* Top two-bits of byte 3 are the key index */ keyidx = skb->data[hdrlen + 3] >> 6; } @@ -660,7 +660,7 @@ int libipw_rx(struct libipw_device *ieee, struct sk_buff *skb, int trimlen = 0; /* Top two-bits of byte 3 are the key index */ - if (skb->len >= hdrlen + 3) + if (skb->len >= hdrlen + 4) keyidx = skb->data[hdrlen + 3] >> 6; /* To strip off any security data which appears before the From 8ecdeb8b8a33b22c597299043c0dcfce50beb9ea Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 5 Jul 2026 16:48:24 +0800 Subject: [PATCH 128/479] wifi: rsi: validate beacon length before fixed buffer copy rsi_prepare_beacon() copies the mac80211 beacon frame after FRAME_DESC_SZ into a management skb whose usable tailroom may be smaller than MAX_MGMT_PKT_SIZE after alignment. Validate the beacon length against the actual tailroom before the copy and skb_put(). Leave ownership of the management skb with the caller on error, matching the existing rsi_send_beacon() cleanup path. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260705084824.68105-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/rsi/rsi_91x_hal.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/rsi/rsi_91x_hal.c b/drivers/net/wireless/rsi/rsi_91x_hal.c index a0c36144eb0b..501071104a9f 100644 --- a/drivers/net/wireless/rsi/rsi_91x_hal.c +++ b/drivers/net/wireless/rsi/rsi_91x_hal.c @@ -431,6 +431,7 @@ int rsi_prepare_beacon(struct rsi_common *common, struct sk_buff *skb) struct ieee80211_vif *vif; struct sk_buff *mac_bcn; u8 vap_id = 0, i; + unsigned int tailroom; u16 tim_offset = 0; for (i = 0; i < RSI_MAX_VIFS; i++) { @@ -480,6 +481,13 @@ int rsi_prepare_beacon(struct rsi_common *common, struct sk_buff *skb) if (mac_bcn->data[tim_offset + 2] == 0) bcn_frm->frame_info |= cpu_to_le16(RSI_DATA_DESC_DTIM_BEACON); + tailroom = skb_tailroom(skb); + if (tailroom < FRAME_DESC_SZ || + mac_bcn->len > tailroom - FRAME_DESC_SZ) { + dev_kfree_skb(mac_bcn); + return -EMSGSIZE; + } + memcpy(&skb->data[FRAME_DESC_SZ], mac_bcn->data, mac_bcn->len); skb_put(skb, mac_bcn->len + FRAME_DESC_SZ); From 07a95ec2b54774201fdf4ef7ffb0ca2ab19ed29c Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Wed, 10 Jun 2026 19:22:09 +0800 Subject: [PATCH 129/479] wifi: nl80211: free RNR data on MBSSID mismatch nl80211_parse_beacon() rejects EMA RNR data when there are fewer RNR entries than MBSSID entries. The rejected RNR allocation has not been attached to the beacon data yet, so free it before returning the error. Fixes: dbbb27e183b1 ("cfg80211: support RNR for EMA AP") Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260610112208.1308-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 53b4b3f76697..056388c04599 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -6803,8 +6803,10 @@ static int nl80211_parse_beacon(struct cfg80211_registered_device *rdev, if (IS_ERR(rnr)) return PTR_ERR(rnr); - if (rnr && rnr->cnt < bcn->mbssid_ies->cnt) + if (rnr && rnr->cnt < bcn->mbssid_ies->cnt) { + kfree(rnr); return -EINVAL; + } bcn->rnr_ies = rnr; } From 57d503ce32eccfa7650065ca4c560f7e29a2e676 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 00:19:45 +0800 Subject: [PATCH 130/479] wifi: mac80211: validate extension-frame layout before RX Extension frames only have the extension header at the regular 802.11 header offset. The generic RX path can still reach helpers and interface dispatch code that read regular header address fields before unsupported extension subtypes are dropped. mac80211 currently only handles S1G beacon extension frames. Drop other extension subtypes before they can reach regular-header RX processing. For S1G beacons, linearize the SKB with the management-frame path and require the fixed S1G beacon header, including optional fixed fields indicated by frame control, before generic RX dispatch. Route S1G beacons through the station/default-link RX path without regular-header station lookup. Avoid regular-header address reads in the mac80211 RX paths that process S1G extension beacons, including accept-frame, duplicate-detection, address-copy, and MLO address-translation paths. Also make ieee80211_get_bssid() length-safe before returning the S1G source-address pointer. Fixes: 09a740ce352e ("mac80211: receive and process S1G beacons") Cc: stable@vger.kernel.org Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260611161943.91069-5-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/rx.c | 34 ++++++++++++++++++++++++++++++++-- net/mac80211/util.c | 3 +++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index fb9a3574afe9..d9ea19be075d 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -1526,6 +1526,9 @@ ieee80211_rx_h_check_dup(struct ieee80211_rx_data *rx) if (status->flag & RX_FLAG_DUP_VALIDATED) return RX_CONTINUE; + if (ieee80211_is_ext(hdr->frame_control)) + return RX_CONTINUE; + /* * Drop duplicate 802.11 retransmissions * (IEEE 802.11-2012: 9.3.2.10 "Duplicate detection and recovery") @@ -4510,12 +4513,16 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx) struct ieee80211_hdr *hdr = (void *)skb->data; struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); u8 *bssid = ieee80211_get_bssid(hdr, skb->len, sdata->vif.type); - bool multicast = is_multicast_ether_addr(hdr->addr1) || - ieee80211_is_s1g_beacon(hdr->frame_control); + bool multicast; static const u8 nan_network_id[ETH_ALEN] __aligned(2) = { 0x51, 0x6F, 0x9A, 0x01, 0x00, 0x00 }; + if (ieee80211_is_s1g_beacon(hdr->frame_control)) + return sdata->vif.type == NL80211_IFTYPE_STATION && bssid; + + multicast = is_multicast_ether_addr(hdr->addr1); + switch (sdata->vif.type) { case NL80211_IFTYPE_STATION: if (!bssid && !sdata->u.mgd.use_4addr) @@ -5212,6 +5219,11 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx, hdr = (struct ieee80211_hdr *)rx->skb->data; } + if (ieee80211_is_s1g_beacon(hdr->frame_control)) { + ieee80211_invoke_rx_handlers(rx); + return true; + } + /* Store a copy of the pre-translated link addresses for SW crypto */ if (unlikely(is_unicast_ether_addr(hdr->addr1) && !ieee80211_is_data(hdr->frame_control))) @@ -5301,6 +5313,13 @@ static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx, struct sta_info *sta; int link_id = -1; + if (ieee80211_is_s1g_beacon(hdr->frame_control)) { + if (!ieee80211_rx_data_set_sta(rx, NULL, -1)) + return false; + + return ieee80211_prepare_and_rx_handle(rx, skb, consume); + } + /* * Look up link station first, in case there's a * chance that they might have a link address that @@ -5376,6 +5395,17 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, err = -ENOBUFS; else err = skb_linearize(skb); + } else if (ieee80211_is_s1g_beacon(fc)) { + size_t s1g_hdr_len = offsetof(struct ieee80211_ext, + u.s1g_beacon.variable) + + ieee80211_s1g_optional_len(fc); + + if (skb->len < s1g_hdr_len) + err = -ENOBUFS; + else + err = skb_linearize(skb); + } else if (ieee80211_is_ext(fc)) { + err = -EINVAL; } else { err = !pskb_may_pull(skb, ieee80211_hdrlen(fc)); } diff --git a/net/mac80211/util.c b/net/mac80211/util.c index f6d4ae4127c8..59f73dabe6e0 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -73,6 +73,9 @@ u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len, if (ieee80211_is_s1g_beacon(fc)) { struct ieee80211_ext *ext = (void *) hdr; + if (len < offsetofend(struct ieee80211_ext, u.s1g_beacon.sa)) + return NULL; + return ext->u.s1g_beacon.sa; } From 4e5a4641e7b4763656336b7891d01359aaf363cd Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 00:19:46 +0800 Subject: [PATCH 131/479] wifi: cfg80211: derive S1G beacon TSF from S1G fields cfg80211_inform_bss_frame_data() parses S1G beacons with the extension frame layout, but still reads the TSF from the regular probe response layout after the S1G branch. For S1G beacons that reads bytes at the regular management-frame timestamp offset instead of the S1G timestamp. Use the 32-bit S1G beacon timestamp and the S1G Beacon Compatibility element's TSF completion field when informing an S1G BSS. Keep the regular management-frame timestamp read in the non-S1G branch. Fixes: 9eaffe5078ca ("cfg80211: convert S1G beacon to scan results") Signed-off-by: Zhao Li Tested-by: Lachlan Hodges Reviewed-by: Lachlan Hodges Link: https://patch.msgid.link/20260611161943.91069-6-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/scan.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index e1c09040a5c8..5c97b5bd5d69 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -3314,14 +3314,15 @@ cfg80211_inform_bss_frame_data(struct wiphy *wiphy, bssid = ext->u.s1g_beacon.sa; capability = le16_to_cpu(compat->compat_info); beacon_interval = le16_to_cpu(compat->beacon_int); + tsf = le32_to_cpu(ext->u.s1g_beacon.timestamp); + tsf |= (u64)le32_to_cpu(compat->tsf_completion) << 32; } else { bssid = mgmt->bssid; beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int); capability = le16_to_cpu(mgmt->u.probe_resp.capab_info); + tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp); } - tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp); - if (ieee80211_is_probe_resp(mgmt->frame_control)) ftype = CFG80211_BSS_FTYPE_PRESP; else if (ext) From 293baeae9b2434a3e432629d7720b5603db2d77e Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 01:35:07 +0800 Subject: [PATCH 132/479] wifi: ieee80211: validate MLE common info length ieee80211_mle_common_size() uses the first common-info octet as the common information length for all known MLE types. However, ieee80211_mle_size_ok() only validates that octet for Basic, Probe Request, and TDLS MLEs. Reconfiguration MLEs also skipped the length octet when calculating the minimum common size, and Priority Access MLEs skipped validation of the advertised common information length. Account for the Reconfiguration common-info length octet and validate the advertised common information length for all known MLE types. Keep unknown-type handling unchanged. Fixes: 0f48b8b88aa9 ("wifi: ieee80211: add definitions for multi-link element") Cc: stable@vger.kernel.org Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260611173506.36838-2-enderaoelyther@gmail.com [remove now misleading comment] Signed-off-by: Johannes Berg --- include/linux/ieee80211-eht.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index 18f9c662cf4c..c109722b1969 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -857,7 +857,7 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) const struct ieee80211_multi_link_elem *mle = (const void *)data; u8 fixed = sizeof(*mle); u8 common = 0; - bool check_common_len = false; + u8 common_len; u16 control; if (!data || len < fixed) @@ -868,7 +868,6 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) switch (u16_get_bits(control, IEEE80211_ML_CONTROL_TYPE)) { case IEEE80211_ML_CONTROL_TYPE_BASIC: common += sizeof(struct ieee80211_mle_basic_common_info); - check_common_len = true; if (control & IEEE80211_MLC_BASIC_PRES_LINK_ID) common += 1; if (control & IEEE80211_MLC_BASIC_PRES_BSS_PARAM_CH_CNT) @@ -888,9 +887,9 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) common += sizeof(struct ieee80211_mle_preq_common_info); if (control & IEEE80211_MLC_PREQ_PRES_MLD_ID) common += 1; - check_common_len = true; break; case IEEE80211_ML_CONTROL_TYPE_RECONF: + common += 1; if (control & IEEE80211_MLC_RECONF_PRES_MLD_MAC_ADDR) common += ETH_ALEN; if (control & IEEE80211_MLC_RECONF_PRES_EML_CAPA) @@ -902,7 +901,6 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) break; case IEEE80211_ML_CONTROL_TYPE_TDLS: common += sizeof(struct ieee80211_mle_tdls_common_info); - check_common_len = true; break; case IEEE80211_ML_CONTROL_TYPE_PRIO_ACCESS: common = ETH_ALEN + 1; @@ -915,11 +913,9 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) if (len < fixed + common) return false; - if (!check_common_len) - return true; + common_len = mle->variable[0]; - /* if present, common length is the first octet there */ - return mle->variable[0] >= common; + return common_len >= common && common_len <= len - fixed; } /** From 7f4b01812323443b55e4c65381c9dc851ff009e3 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 21:18:55 +0800 Subject: [PATCH 133/479] wifi: nl80211: validate nested MBSSID IE blobs Validate each nested NL80211_ATTR_MBSSID_ELEMS entry as a well-formed information-element stream before storing it for beacon construction. RNR parsing already validates each nested blob with validate_ie_attr() before storing it. Apply the same syntactic IE validation to MBSSID entries before counting and copying their data and length pointers. Fixes: dc1e3cb8da8b ("nl80211: MBSSID and EMA support in AP mode") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612131854.43575-3-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 056388c04599..26b781770d4f 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -6510,7 +6510,8 @@ static int nl80211_parse_mbssid_config(struct wiphy *wiphy, } static struct cfg80211_mbssid_elems * -nl80211_parse_mbssid_elems(struct wiphy *wiphy, struct nlattr *attrs) +nl80211_parse_mbssid_elems(struct wiphy *wiphy, struct nlattr *attrs, + struct netlink_ext_ack *extack) { struct nlattr *nl_elems; struct cfg80211_mbssid_elems *elems; @@ -6521,6 +6522,12 @@ nl80211_parse_mbssid_elems(struct wiphy *wiphy, struct nlattr *attrs) return ERR_PTR(-EINVAL); nla_for_each_nested(nl_elems, attrs, rem_elems) { + int ret; + + ret = validate_ie_attr(nl_elems, extack); + if (ret) + return ERR_PTR(ret); + if (num_elems >= 255) return ERR_PTR(-EINVAL); num_elems++; @@ -6787,7 +6794,8 @@ static int nl80211_parse_beacon(struct cfg80211_registered_device *rdev, if (attrs[NL80211_ATTR_MBSSID_ELEMS]) { struct cfg80211_mbssid_elems *mbssid = nl80211_parse_mbssid_elems(&rdev->wiphy, - attrs[NL80211_ATTR_MBSSID_ELEMS]); + attrs[NL80211_ATTR_MBSSID_ELEMS], + extack); if (IS_ERR(mbssid)) return PTR_ERR(mbssid); From 172f06023669f0a96d32511669ff45c600731380 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 21:18:56 +0800 Subject: [PATCH 134/479] wifi: nl80211: constrain MBSSID TX link ID range MBSSID transmitted-profile link IDs are valid only in the range 0..IEEE80211_MLD_MAX_NUM_LINKS - 1. Constrain the nl80211 policy to reject out-of-range values during attribute validation. Fixes: 37523c3c47b3 ("wifi: nl80211: add link id of transmitted profile for MLO MBSSID") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612131854.43575-4-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 26b781770d4f..3e05db942a18 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -630,7 +630,7 @@ nl80211_mbssid_config_policy[NL80211_MBSSID_CONFIG_ATTR_MAX + 1] = { [NL80211_MBSSID_CONFIG_ATTR_TX_IFINDEX] = { .type = NLA_U32 }, [NL80211_MBSSID_CONFIG_ATTR_EMA] = { .type = NLA_FLAG }, [NL80211_MBSSID_CONFIG_ATTR_TX_LINK_ID] = - NLA_POLICY_MAX(NLA_U8, IEEE80211_MLD_MAX_NUM_LINKS), + NLA_POLICY_RANGE(NLA_U8, 0, IEEE80211_MLD_MAX_NUM_LINKS - 1), }; static const struct nla_policy From 41aa973eb05922848dded26875c55ef982ac1c49 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 21:36:57 +0800 Subject: [PATCH 135/479] wifi: cfg80211: validate PMSR measurement type data PMSR request parsing accepts missing or duplicated measurement type entries in NL80211_PMSR_REQ_ATTR_DATA. Track whether one measurement type was already provided, reject a second one immediately, and return an error if the request data block contains no measurement type at all. Fixes: 9bb7e0f24e7e7 ("cfg80211: add peer measurement with FTM initiator API") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612133656.92900-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/pmsr.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index 2c8db33d9c30..4962456fda30 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -310,6 +310,7 @@ static int pmsr_parse_peer(struct cfg80211_registered_device *rdev, { struct nlattr *tb[NL80211_PMSR_PEER_ATTR_MAX + 1]; struct nlattr *req[NL80211_PMSR_REQ_ATTR_MAX + 1]; + bool have_measurement_type = false; struct nlattr *treq; int err, rem; @@ -376,6 +377,14 @@ static int pmsr_parse_peer(struct cfg80211_registered_device *rdev, } nla_for_each_nested(treq, req[NL80211_PMSR_REQ_ATTR_DATA], rem) { + if (have_measurement_type) { + NL_SET_ERR_MSG_ATTR(info->extack, treq, + "multiple measurement types in request data"); + return -EINVAL; + } + + have_measurement_type = true; + switch (nla_type(treq)) { case NL80211_PMSR_TYPE_FTM: err = pmsr_parse_ftm(rdev, treq, out, info); @@ -385,10 +394,16 @@ static int pmsr_parse_peer(struct cfg80211_registered_device *rdev, "unsupported measurement type"); err = -EINVAL; } + if (err) + return err; } - if (err) - return err; + if (!have_measurement_type) { + NL_SET_ERR_MSG_ATTR(info->extack, + req[NL80211_PMSR_REQ_ATTR_DATA], + "missing measurement type in request data"); + return -EINVAL; + } return 0; } From 36230936468f0ba4930e94aef496fc229d4bb951 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 21:37:04 +0800 Subject: [PATCH 136/479] wifi: cfg80211: validate PMSR FTM preamble range PMSR FTM request parsing accepts preamble values outside the enumerated nl80211 preamble range. Reject out-of-range values before using them in the parser capability bit test using the policy. Fixes: 9bb7e0f24e7e7 ("cfg80211: add peer measurement with FTM initiator API") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612133703.93274-2-enderaoelyther@gmail.com [drop unnecessary check] Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 3e05db942a18..625c99cf70a3 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -461,7 +461,9 @@ nl80211_ftm_responder_policy[NL80211_FTM_RESP_ATTR_MAX + 1] = { static const struct nla_policy nl80211_pmsr_ftm_req_attr_policy[NL80211_PMSR_FTM_REQ_ATTR_MAX + 1] = { [NL80211_PMSR_FTM_REQ_ATTR_ASAP] = { .type = NLA_FLAG }, - [NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE] = { .type = NLA_U32 }, + [NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE] = + NLA_POLICY_RANGE(NLA_U32, NL80211_PREAMBLE_LEGACY, + NL80211_PREAMBLE_HE), [NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP] = NLA_POLICY_MAX(NLA_U8, 15), [NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD] = { .type = NLA_U16 }, From 69ef6a7ec277f16d216be8da2b3cbe872786c999 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 21:37:11 +0800 Subject: [PATCH 137/479] wifi: cfg80211: reject unsupported PMSR FTM location requests PMSR FTM location request flags are syntactically valid, but they must be rejected when the device capability does not advertise support for them. Return an error immediately after rejecting unsupported LCI or civic location request bits so the request cannot reach the driver. Fixes: 9bb7e0f24e7e7 ("cfg80211: add peer measurement with FTM initiator API") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612133710.93544-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/pmsr.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index 4962456fda30..34ecbc6644a3 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -125,6 +125,7 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, NL_SET_ERR_MSG_ATTR(info->extack, tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI], "FTM: LCI request not supported"); + return -EOPNOTSUPP; } out->ftm.request_civicloc = @@ -133,6 +134,7 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, NL_SET_ERR_MSG_ATTR(info->extack, tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC], "FTM: civic location request not supported"); + return -EOPNOTSUPP; } out->ftm.trigger_based = From 57c05ce14fea03df01288fe1250f49197e161710 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 21:37:18 +0800 Subject: [PATCH 138/479] wifi: cfg80211: reject empty PMSR peer lists A PMSR request with an empty peers array is not a useful request and weakens the cfg80211-to-driver contract by allowing start_pmsr() with no target peer. Reject empty peer lists before allocating the request object or calling into the driver. Fixes: 9bb7e0f24e7e7 ("cfg80211: add peer measurement with FTM initiator API") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612133717.93783-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/pmsr.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index 34ecbc6644a3..34c3625f7fd5 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -444,6 +444,11 @@ int nl80211_pmsr_start(struct sk_buff *skb, struct genl_info *info) } } + if (!count) { + NL_SET_ERR_MSG_ATTR(info->extack, peers, "No peers specified"); + return -EINVAL; + } + req = kzalloc_flex(*req, peers, count); if (!req) return -ENOMEM; From 035ed430ce6a2c35b01e211844a9f0a7643e57a4 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 23:24:41 +0800 Subject: [PATCH 139/479] wifi: mac80211: avoid non-S1G AID fallback for S1G assoc When assoc_data->s1g is set and no AID Response element is present, falling back to mgmt->u.assoc_resp.aid reads the non-S1G association-response layout. Keep the fallback for non-S1G only. If a successful S1G association response omits the AID Response element, abandon the association instead of proceeding with AID 0. Initialize aid to 0 for other S1G responses so the later mask and logging flow keeps a defined value without reading the non-S1G layout. Fixes: 2a8a6b7c4cb0 ("wifi: mac80211: handle station association response with S1G") Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612152440.25955-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/mlme.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 9e92337bb6f9..86c90b504cfd 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -7138,7 +7138,7 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, { struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data; - u16 capab_info, status_code, aid; + u16 capab_info, status_code, aid = 0; struct ieee80211_elems_parse_params parse_params = { .bss = NULL, .link_id = -1, @@ -7217,8 +7217,10 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, if (elems->aid_resp) aid = le16_to_cpu(elems->aid_resp->aid); - else + else if (!assoc_data->s1g) aid = le16_to_cpu(mgmt->u.assoc_resp.aid); + else if (status_code == WLAN_STATUS_SUCCESS) + goto abandon_assoc; /* * The 5 MSB of the AID field are reserved for a non-S1G STA. For From 4a360c6e18dfa9d70006c7247a6a8cc8dfe0d60f Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Sat, 13 Jun 2026 02:50:45 +0800 Subject: [PATCH 140/479] wifi: mac80211: validate deauth frame length before reason access ieee80211_rx_mgmt_deauth() reads the deauth reason code before checking that the fixed field is actually present in the received frame. Validate the deauth frame length first and only then read the reason code. Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260612185042.66260-6-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/mlme.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 86c90b504cfd..fa773f3b0541 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -5641,13 +5641,15 @@ static void ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt, size_t len) { struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; - u16 reason_code = le16_to_cpu(mgmt->u.deauth.reason_code); + u16 reason_code; lockdep_assert_wiphy(sdata->local->hw.wiphy); - if (len < 24 + 2) + if (len < offsetofend(struct ieee80211_mgmt, u.deauth.reason_code)) return; + reason_code = le16_to_cpu(mgmt->u.deauth.reason_code); + if (!ether_addr_equal(mgmt->bssid, mgmt->sa)) { ieee80211_tdls_handle_disconnect(sdata, mgmt->sa, reason_code); return; From 521f39ca93cc43ce1b3eae8d44201f8f55dd9151 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Mon, 1 Jun 2026 17:49:01 +0300 Subject: [PATCH 141/479] mmc: sdhci-of-dwcmshc: check bus clock enable result in the probe() method In the driver's probe() method, clk_disable_unprepare() for the bus clock is called on the error path even if the prior clk_prepare_enable() call has failed (and the same thing happens in the remove() method as well) -- that would cause the prepare/enable counter imbalance. Also, the same problem can happen in the driver's suspend() method; note that the resume() method does check the clk_prepare_enable()'s result -- let's be consistent and do that in probe() method as well. BTW, I don't know for sure what does the bus clock control -- if it affects the register accesses, the driver will likely cause (e.g. on ARM) a kernel oops if it fails to prepare/enable the bus clock in the probe() method... Found by Linux Verification Center (linuxtesting.org) with the Svace static analysis tool. Fixes: e438cf49b305 ("mmc: sdhci-of-dwcmshc: add SDHCI OF Synopsys DWC MSHC driver") Fixes: bccce2ec7790 ("mmc: sdhci-of-dwcmshc: add suspend/resume support") Signed-off-by: Sergey Shtylyov Acked-by: Adrian Hunter Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-dwcmshc.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index eef53455b8ee..c688f3eaf468 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -2433,13 +2433,16 @@ static int dwcmshc_probe(struct platform_device *pdev) return err; priv->bus_clk = devm_clk_get(dev, "bus"); - if (!IS_ERR(priv->bus_clk)) - clk_prepare_enable(priv->bus_clk); + if (!IS_ERR(priv->bus_clk)) { + err = clk_prepare_enable(priv->bus_clk); + if (err) + goto err_clk; + } } err = mmc_of_parse(host->mmc); if (err) - goto err_clk; + goto err_bus_clk; sdhci_get_of_property(pdev); @@ -2453,7 +2456,7 @@ static int dwcmshc_probe(struct platform_device *pdev) if (pltfm_data->init) { err = pltfm_data->init(&pdev->dev, host, priv); if (err) - goto err_clk; + goto err_bus_clk; } #ifdef CONFIG_ACPI @@ -2499,9 +2502,10 @@ static int dwcmshc_probe(struct platform_device *pdev) err_rpm: pm_runtime_disable(dev); pm_runtime_put_noidle(dev); +err_bus_clk: + clk_disable_unprepare(priv->bus_clk); err_clk: clk_disable_unprepare(pltfm_host->clk); - clk_disable_unprepare(priv->bus_clk); clk_bulk_disable_unprepare(priv->num_other_clks, priv->other_clks); return err; } From c35fac6481a1dbb2d114dd337b54a40799437546 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 2 Jun 2026 21:13:44 +0100 Subject: [PATCH 142/479] mmc: mmc_test: Fix __counted_by handling after kzalloc_flex() conversion Fix logic issues introduced by the kzalloc_flex() conversion in mmc_test_alloc_mem() due to interaction with the __counted_by annotation on the flexible array. Bounds-checking sanitizers rely on the counter field reflecting the allocated array size before any array access occurs. However, use mem->cnt both as the allocation size and as the runtime insertion index, causing incorrect indexing and potentially invalid bounds tracking. Initialize mem->cnt to the maximum allocated number of segments immediately after kzalloc_flex(), then use a separate local index variable to track successfully allocated entries. Update mem->cnt to the actual number of initialized elements before returning or entering the cleanup path. Also rewrite mmc_test_free_mem() to use a forward for-loop, improving readability and ensuring only initialized entries are freed. Fixes: c3126dccfd7b ("mmc: mmc_test: use kzalloc_flex") Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/core/mmc_test.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/core/mmc_test.c b/drivers/mmc/core/mmc_test.c index ab38e4c45a8d..4dc16649e61d 100644 --- a/drivers/mmc/core/mmc_test.c +++ b/drivers/mmc/core/mmc_test.c @@ -318,9 +318,9 @@ static void mmc_test_free_mem(struct mmc_test_mem *mem) { if (!mem) return; - while (mem->cnt--) - __free_pages(mem->arr[mem->cnt].page, - mem->arr[mem->cnt].order); + for (unsigned int i = 0; i < mem->cnt; i++) + __free_pages(mem->arr[i].page, + mem->arr[i].order); kfree(mem); } @@ -341,6 +341,7 @@ static struct mmc_test_mem *mmc_test_alloc_mem(unsigned long min_sz, unsigned long page_cnt = 0; unsigned long limit = nr_free_buffer_pages() >> 4; struct mmc_test_mem *mem; + unsigned int idx = 0; if (max_page_cnt > limit) max_page_cnt = limit; @@ -375,23 +376,26 @@ static struct mmc_test_mem *mmc_test_alloc_mem(unsigned long min_sz, goto out_free; break; } - mem->arr[mem->cnt].page = page; - mem->arr[mem->cnt].order = order; - mem->cnt += 1; + mem->arr[idx].page = page; + mem->arr[idx].order = order; + idx += 1; if (max_page_cnt <= (1UL << order)) break; max_page_cnt -= 1UL << order; page_cnt += 1UL << order; - if (mem->cnt >= max_segs) { + if (idx >= mem->cnt) { if (page_cnt < min_page_cnt) goto out_free; break; } } + mem->cnt = idx; + return mem; out_free: + mem->cnt = idx; mmc_test_free_mem(mem); return NULL; } From a3b5f242997a3be7404112fd48784881560aea57 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Fri, 12 Jun 2026 11:27:56 +0800 Subject: [PATCH 143/479] mmc: vub300: fix use-after-free on probe failure 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 --- drivers/mmc/host/vub300.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/vub300.c b/drivers/mmc/host/vub300.c index 6c3cb2f1c9d3..c1c21e95f5bf 100644 --- a/drivers/mmc/host/vub300.c +++ b/drivers/mmc/host/vub300.c @@ -2336,12 +2336,16 @@ static int vub300_probe(struct usb_interface *interface, 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); /* From ee5fb641c4ccac8406c668d3e947eb20ce44f233 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 17 Jun 2026 23:23:19 +0800 Subject: [PATCH 144/479] mmc: vub300: defer reset until cmd_mutex is unlocked vub300_cmndwork_thread() holds cmd_mutex while it sends a command and waits for the command response. If the response wait times out, __vub300_command_response() kills the command URBs and then synchronously resets the USB device through usb_reset_device(). That reset path re-enters the driver through vub300_pre_reset(), which also takes cmd_mutex. The worker therefore tries to acquire the same mutex recursively while it is still holding it from the command path. This issue was found by our static analysis tool and then manually reviewed against the current tree. The grounded PoC kept the real worker and timeout/reset carrier: vub300_cmndwork_thread() __vub300_command_response() usb_lock_device_for_reset() usb_reset_device() vub300_pre_reset() Lockdep reported the same-task recursive acquisition on cmd_mutex: WARNING: possible recursive locking detected ... (&test_vub300.cmd_mutex) ... at: usb_reset_device... [vuln_msv] ... (&test_vub300.cmd_mutex) ... at: vub300_cmndwork_thread+0x12/0x20 [vuln_msv] Workqueue: vub300_cmd_wq vub300_cmndwork_thread [vuln_msv] *** DEADLOCK *** Return a flag from __vub300_command_response() when the timeout path needs a device reset, then perform the reset after vub300_cmndwork_thread() has cleared the in-flight command state and dropped cmd_mutex. The reset is still attempted before mmc_request_done(), preserving the existing request completion ordering while avoiding the recursive lock. Fixes: 88095e7b473a ("mmc: Add new VUB300 USB-to-SD/SDIO/MMC driver") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Signed-off-by: Ulf Hansson --- drivers/mmc/host/vub300.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/drivers/mmc/host/vub300.c b/drivers/mmc/host/vub300.c index c1c21e95f5bf..2dae474dcd06 100644 --- a/drivers/mmc/host/vub300.c +++ b/drivers/mmc/host/vub300.c @@ -1586,7 +1586,7 @@ static int __command_write_data(struct vub300_mmc_host *vub300, return linear_length; } -static void __vub300_command_response(struct vub300_mmc_host *vub300, +static bool __vub300_command_response(struct vub300_mmc_host *vub300, struct mmc_command *cmd, struct mmc_data *data, int data_length) { @@ -1598,17 +1598,11 @@ static void __vub300_command_response(struct vub300_mmc_host *vub300, msecs_to_jiffies(msec_timeout)); if (respretval == 0) { /* TIMED OUT */ /* we don't know which of "out" and "res" if any failed */ - int result; vub300->usb_timed_out = 1; usb_kill_urb(vub300->command_out_urb); usb_kill_urb(vub300->command_res_urb); cmd->error = -ETIMEDOUT; - result = usb_lock_device_for_reset(vub300->udev, - vub300->interface); - if (result == 0) { - result = usb_reset_device(vub300->udev); - usb_unlock_device(vub300->udev); - } + return true; } else if (respretval < 0) { /* we don't know which of "out" and "res" if any failed */ usb_kill_urb(vub300->command_out_urb); @@ -1704,6 +1698,8 @@ static void __vub300_command_response(struct vub300_mmc_host *vub300, } else { cmd->error = -EINVAL; } + + return false; } static void construct_request_response(struct vub300_mmc_host *vub300, @@ -1749,6 +1745,7 @@ static void vub300_cmndwork_thread(struct work_struct *work) struct mmc_request *req = vub300->req; struct mmc_command *cmd = vub300->cmd; struct mmc_data *data = vub300->data; + bool reset_device; int data_length; mutex_lock(&vub300->cmd_mutex); init_completion(&vub300->command_complete); @@ -1771,7 +1768,8 @@ static void vub300_cmndwork_thread(struct work_struct *work) data_length = __command_read_data(vub300, cmd, data); else data_length = __command_write_data(vub300, cmd, data); - __vub300_command_response(vub300, cmd, data, data_length); + reset_device = __vub300_command_response(vub300, cmd, + data, data_length); vub300->req = NULL; vub300->cmd = NULL; vub300->data = NULL; @@ -1779,6 +1777,16 @@ static void vub300_cmndwork_thread(struct work_struct *work) if (cmd->error == -ENOMEDIUM) check_vub300_port_status(vub300); mutex_unlock(&vub300->cmd_mutex); + if (reset_device) { + int result; + + result = usb_lock_device_for_reset(vub300->udev, + vub300->interface); + if (result == 0) { + result = usb_reset_device(vub300->udev); + usb_unlock_device(vub300->udev); + } + } mmc_request_done(vub300->mmc, req); kref_put(&vub300->kref, vub300_delete); return; From c505d54c1a9e99d36d322ced6c9c71a1157424b0 Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Wed, 1 Jul 2026 16:44:26 +0200 Subject: [PATCH 145/479] dt-bindings: mmc: mtk-sd: Document extra clocks for MT8189 MT8189 SoC MMC Controller IP has 4 additional clocks. Describe them in the dt-bindings for this SoC. Fixes: 7514f64780a4 ("dt-bindings: mmc: mtk-sd: Add support for MT8189 SoC") Signed-off-by: Louis-Alexis Eyraud Acked-by: Conor Dooley Signed-off-by: Ulf Hansson --- .../devicetree/bindings/mmc/mtk-sd.yaml | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mmc/mtk-sd.yaml b/Documentation/devicetree/bindings/mmc/mtk-sd.yaml index eb3755bdfdf7..a4d032224dce 100644 --- a/Documentation/devicetree/bindings/mmc/mtk-sd.yaml +++ b/Documentation/devicetree/bindings/mmc/mtk-sd.yaml @@ -193,7 +193,6 @@ allOf: - mediatek,mt8183-mmc - mediatek,mt8186-mmc - mediatek,mt8188-mmc - - mediatek,mt8189-mmc - mediatek,mt8195-mmc - mediatek,mt8196-mmc - mediatek,mt8516-mmc @@ -348,6 +347,34 @@ allOf: - const: axi_cg - const: ahb_cg + - if: + properties: + compatible: + contains: + const: mediatek,mt8189-mmc + then: + properties: + clocks: + minItems: 6 + items: + - description: source clock + - description: HCLK which used for host + - description: independent source clock gate + - description: bus clock used for internal register access + - description: peripheral bus clock gate + - description: AXI bus clock gate + - description: crypto clock used for data encrypt/decrypt (optional) + clock-names: + minItems: 6 + items: + - const: source + - const: hclk + - const: source_cg + - const: bus_clk + - const: pclk_cg + - const: axi_cg + - const: crypto + unevaluatedProperties: false examples: From 718178f524b98bc920d74bc771aed823c8b81425 Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Thu, 2 Jul 2026 16:27:45 +0800 Subject: [PATCH 146/479] memstick: ms_block: reject a card that reports too many blocks msb_ftl_initialize() computes the zone count from the card block count with no bound: msb->zone_count = msb->block_count / MS_BLOCKS_IN_ZONE; ... for (i = 0; i < msb->zone_count; i++) msb->free_block_count[i] = MS_BLOCKS_IN_ZONE; msb->block_count is a card value. msb_read_boot_blocks() reads number_of_blocks from the card boot page and byte swaps it. free_block_count is a fixed int[MS_MAX_ZONES]. MS_MAX_ZONES is 16, so the valid indices are 0 to 15. The init loop above indexes it by zone_count. msb_mark_block_used() and msb_mark_block_unused() index it by pba / MS_BLOCKS_IN_ZONE, for pba up to block_count - 1. A card may report up to 65535 blocks. A block_count above 8192 (MS_MAX_ZONES * MS_BLOCKS_IN_ZONE) lets the pba index reach 16. That writes past free_block_count[] and corrupts struct msb_data. A larger count runs the init loop past the end too. A real Memory Stick has at most 16 zones. So it has at most 8192 blocks. msb_ftl_initialize() now rejects a card that reports more than MS_MAX_ZONES * MS_BLOCKS_IN_ZONE blocks. Fixes: 0ab30494bc4f ("memstick: add support for legacy memorysticks") Cc: stable@vger.kernel.org Signed-off-by: Maoyi Xie Signed-off-by: Ulf Hansson --- drivers/memstick/core/ms_block.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/memstick/core/ms_block.c b/drivers/memstick/core/ms_block.c index a01fe313558e..ce33907bfc24 100644 --- a/drivers/memstick/core/ms_block.c +++ b/drivers/memstick/core/ms_block.c @@ -1338,6 +1338,10 @@ static int msb_ftl_initialize(struct msb_data *msb) return 0; msb->zone_count = msb->block_count / MS_BLOCKS_IN_ZONE; + if (msb->block_count > MS_MAX_ZONES * MS_BLOCKS_IN_ZONE) { + pr_err("Too many blocks: %d\n", msb->block_count); + return -EINVAL; + } msb->logical_block_count = msb->zone_count * 496 - 2; msb->used_blocks_bitmap = bitmap_zalloc(msb->block_count, GFP_KERNEL); From 86152fef52cac15cd662ed3bfc7604fbfef378f0 Mon Sep 17 00:00:00 2001 From: Ao Sun Date: Mon, 6 Jul 2026 11:43:00 +0000 Subject: [PATCH 147/479] mmc: block: fix RPMB device unregister ordering Since commit 7852028a35f0 ("mmc: block: register RPMB partition with the RPMB subsystem"), each mmc RPMB partition is represented by two device objects: - the mmc-owned device (`rpmb->dev`, backing the legacy /dev/mmcblkXrpmb char device) and - the rpmb-core device (`rdev`, backing /dev/rpmbN). The child RPMB device holds a reference to its parent, so the parent's release callback cannot be invoked if the child device is still registered. Remove rpmb_dev_unregister() from the parent release handler and unregister the child RPMB device in the remove path before tearing down the parent device. Also delete the extra blank line between mmc_blk_remove_rpmb_part() and {. Fixes: 7852028a35f0 ("mmc: block: register RPMB partition with the RPMB subsystem") Cc: stable@vger.kernel.org Signed-off-by: Jiazi Li Signed-off-by: Ao Sun Reviewed-by: Avri Altman Signed-off-by: Ulf Hansson --- drivers/mmc/core/block.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 0274e8d07660..54a923ba4f1e 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -2715,7 +2715,6 @@ static void mmc_blk_rpmb_device_release(struct device *dev) { struct mmc_rpmb_data *rpmb = dev_get_drvdata(dev); - rpmb_dev_unregister(rpmb->rdev); mmc_blk_put(rpmb->md); ida_free(&mmc_rpmb_ida, rpmb->id); kfree(rpmb); @@ -2930,8 +2929,8 @@ static int mmc_blk_alloc_rpmb_part(struct mmc_card *card, } static void mmc_blk_remove_rpmb_part(struct mmc_rpmb_data *rpmb) - { + rpmb_dev_unregister(rpmb->rdev); cdev_device_del(&rpmb->chrdev, &rpmb->dev); put_device(&rpmb->dev); } From 3a07249981629ace483ebbef81ef6b34c2d2afec Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Sat, 27 Jun 2026 11:51:05 +0100 Subject: [PATCH 148/479] KVM: Move kvm_io_bus_get_dev() locking responsibilities to callers kvm_io_bus_get_dev() returns a device that is only matched by the address, and nothing else. This can cause a lifetime issue if the matched device is not the expected type, as by the time the caller can introspect the object, it might be gone (the srcu lock having been dropped). Given that there is only a single user of this helper, the simplest option is to move the locking responsibility to the caller, which can keep the srcu lock held for as long as it wants. Note that this aligns with other kvm_io_bus*() helpers, which already require the srcu lock to be held by the callers. Reported-by: Will Deacon Fixes: 8a39d00670f07 ("KVM: kvm_io_bus: Add kvm_io_bus_get_dev() call") Link: https://lore.kernel.org/all/20260626111344.802555-1-maz@kernel.org Cc: stable@vger.kernel.org Reviewed-by: Oliver Upton Link: https://patch.msgid.link/20260627105105.1005990-1-maz@kernel.org Signed-off-by: Marc Zyngier --- arch/arm64/kvm/vgic/vgic-its.c | 2 ++ virt/kvm/kvm_main.c | 16 +++++----------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 67d107e9a77d..c90abde39fb8 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -508,6 +508,8 @@ static struct vgic_its *__vgic_doorbell_to_its(struct kvm *kvm, gpa_t db) struct kvm_io_device *kvm_io_dev; struct vgic_io_device *iodev; + guard(srcu)(&kvm->srcu); + kvm_io_dev = kvm_io_bus_get_dev(kvm, KVM_MMIO_BUS, db); if (!kvm_io_dev) return ERR_PTR(-EINVAL); diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 89489996fbc1..5788eac0ab81 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -6068,25 +6068,19 @@ struct kvm_io_device *kvm_io_bus_get_dev(struct kvm *kvm, enum kvm_bus bus_idx, gpa_t addr) { struct kvm_io_bus *bus; - int dev_idx, srcu_idx; - struct kvm_io_device *iodev = NULL; + int dev_idx; - srcu_idx = srcu_read_lock(&kvm->srcu); + lockdep_assert_held(&kvm->srcu); bus = kvm_get_bus_srcu(kvm, bus_idx); if (!bus) - goto out_unlock; + return NULL; dev_idx = kvm_io_bus_get_first_dev(bus, addr, 1); if (dev_idx < 0) - goto out_unlock; + return NULL; - iodev = bus->range[dev_idx].dev; - -out_unlock: - srcu_read_unlock(&kvm->srcu, srcu_idx); - - return iodev; + return bus->range[dev_idx].dev; } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_io_bus_get_dev); From 100baf0184896f859290a684f864b8200d8ac872 Mon Sep 17 00:00:00 2001 From: Oliver Upton Date: Wed, 1 Jul 2026 16:16:19 -0700 Subject: [PATCH 149/479] KVM: arm64: Ensure level is always initialized when relaxing perms stage2_update_leaf_attrs() returns early before writing to @level if the table walker returned an error. At the same time, kvm_pgtable_stage2_relax_perms() uses the level as a TLBI TTL hint when the error was EAGAIN, indicating the vCPU raced with a table update and the TLB entry it hit is now stale. Fall back to an unknown TTL if none was provided by the walk. Cc: stable@vger.kernel.org Fixes: be097997a273 ("KVM: arm64: Always invalidate TLB for stage-2 permission faults") Signed-off-by: Oliver Upton Reviewed-by: Wei-Lin Chang Link: https://patch.msgid.link/20260701231620.3300204-2-oupton@kernel.org Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/pgtable.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c index 0c1defa5fb0f..386a7468136a 100644 --- a/arch/arm64/kvm/hyp/pgtable.c +++ b/arch/arm64/kvm/hyp/pgtable.c @@ -1356,7 +1356,7 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr, enum kvm_pgtable_prot prot, enum kvm_pgtable_walk_flags flags) { kvm_pte_t xn = 0, set = 0, clr = 0; - s8 level; + s8 level = TLBI_TTL_UNKNOWN; int ret; if (prot & KVM_PTE_LEAF_ATTR_HI_SW) From f35c08c092505f3a83ce097d94fe51eb8bc9c1b5 Mon Sep 17 00:00:00 2001 From: Oliver Upton Date: Wed, 1 Jul 2026 16:16:20 -0700 Subject: [PATCH 150/479] KVM: arm64: Only update XN attr when requested during S2 relaxation On systems without DIC, KVM lazily grants execute permission to stage-2 translations after taking an instruction abort due to a permission fault, allowing it to defer I-cache invalidations to the point they're absolutely required. If a data abort happens later down the line to such a translation, KVM will not request execute permissions as part of the S2 relaxation on the assumption that kvm_pgtable_stage2_relax_perms() does exactly what the name implies and adds the requested permissions to the pre-existing ones. Avoid taking unintended execute permission faults by only preparing the XN attribute if KVM_PGTABLE_PROT_X is set. Fixes: 2608563b466b ("KVM: arm64: Add support for FEAT_XNX stage-2 permissions") Signed-off-by: Oliver Upton Reviewed-by: Wei-Lin Chang Link: https://patch.msgid.link/20260701231620.3300204-3-oupton@kernel.org Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/pgtable.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c index 386a7468136a..8754c99c22f2 100644 --- a/arch/arm64/kvm/hyp/pgtable.c +++ b/arch/arm64/kvm/hyp/pgtable.c @@ -1368,12 +1368,14 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr, if (prot & KVM_PGTABLE_PROT_W) set |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W; - ret = stage2_set_xn_attr(prot, &xn); - if (ret) - return ret; + if (prot & KVM_PGTABLE_PROT_X) { + ret = stage2_set_xn_attr(prot, &xn); + if (ret) + return ret; - set |= xn & KVM_PTE_LEAF_ATTR_HI_S2_XN; - clr |= ~xn & KVM_PTE_LEAF_ATTR_HI_S2_XN; + set |= xn & KVM_PTE_LEAF_ATTR_HI_S2_XN; + clr |= ~xn & KVM_PTE_LEAF_ATTR_HI_S2_XN; + } ret = stage2_update_leaf_attrs(pgt, addr, 1, set, clr, NULL, &level, flags); if (!ret || ret == -EAGAIN) From 85f56708a443ec02a290878f00d79a1ff5110e41 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Mon, 6 Jul 2026 12:55:21 +0100 Subject: [PATCH 151/479] KVM: arm64: Fix sign-extension of MMIO loads A sign-extending load (LDRSB, LDRSH, LDRSW) from MMIO returns a zero-extended value to the guest. The architecture performs such a load as a memory read of the access size, then a sign-extension to the register width. For LDRSH (DDI 0487 M.b C6.2.225, with the Mem accessor at J1.2.3.111): data = Mem{16}(address, accdesc); X{regsize}(t) = SignExtend{regsize}(data); The byte order is handled inside the Mem accessor, keyed on the access size; the register width is separate, applied afterwards by SignExtend(). kvm_handle_mmio_return() runs these in the wrong order: it sign-extends the access-width data, then calls vcpu_data_host_to_guest(), which masks the value back to the access width (the size-keyed byte-order step). The mask drops the sign bits that sign-extension produced. Reorder so vcpu_data_host_to_guest() runs first, with the sign-extension to register width after it. trace_kvm_mmio() moves with it and now logs the access-width data before sign-extension. Fixes: b30070862edbd ("ARM64: KVM: MMIO support BE host running LE code") Reviewed-by: Oliver Upton Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260706115522.954913-2-fuad.tabba@linux.dev Signed-off-by: Marc Zyngier --- arch/arm64/kvm/mmio.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/arm64/kvm/mmio.c b/arch/arm64/kvm/mmio.c index e2285ed8c91d..d1c3a352d5a2 100644 --- a/arch/arm64/kvm/mmio.c +++ b/arch/arm64/kvm/mmio.c @@ -126,6 +126,10 @@ int kvm_handle_mmio_return(struct kvm_vcpu *vcpu) len = kvm_vcpu_dabt_get_as(vcpu); data = kvm_mmio_read_buf(run->mmio.data, len); + trace_kvm_mmio(KVM_TRACE_MMIO_READ, len, run->mmio.phys_addr, + &data); + data = vcpu_data_host_to_guest(vcpu, data, len); + if (kvm_vcpu_dabt_issext(vcpu) && len < sizeof(unsigned long)) { mask = 1U << ((len * 8) - 1); @@ -135,9 +139,6 @@ int kvm_handle_mmio_return(struct kvm_vcpu *vcpu) if (!kvm_vcpu_dabt_issf(vcpu)) data = data & 0xffffffff; - trace_kvm_mmio(KVM_TRACE_MMIO_READ, len, run->mmio.phys_addr, - &data); - data = vcpu_data_host_to_guest(vcpu, data, len); vcpu_set_reg(vcpu, kvm_vcpu_dabt_get_rd(vcpu), data); } From 65a38ddeaeed1a962bc1b4ca03c98201c877fea5 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Mon, 6 Jul 2026 12:55:22 +0100 Subject: [PATCH 152/479] KVM: arm64: selftests: Add MMIO sign-extending load test Add a test for sign-extending MMIO loads (LDRSB, LDRSH, LDRSW) into Xt and Wt destinations, with and without the sign bit set. The host supplies the MMIO data and checks the guest register holds the sign-extended value. Repeat the loads big-endian on a mixed-endian implementation. Issue those at EL0: SCTLR_EL1.EE would make an EL1 load big-endian but also walk the little-endian page tables big-endian, whereas SCTLR_EL1.E0E selects only EL0 data endianness and leaves the walk little-endian. Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260706115522.954913-3-fuad.tabba@linux.dev Signed-off-by: Marc Zyngier --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/arm64/mmio_sign_ext.c | 255 ++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 tools/testing/selftests/kvm/arm64/mmio_sign_ext.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 9118a5a51b89..0f5803a1092e 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -171,6 +171,7 @@ TEST_GEN_PROGS_arm64 += arm64/hello_el2 TEST_GEN_PROGS_arm64 += arm64/host_sve TEST_GEN_PROGS_arm64 += arm64/hypercalls TEST_GEN_PROGS_arm64 += arm64/external_aborts +TEST_GEN_PROGS_arm64 += arm64/mmio_sign_ext TEST_GEN_PROGS_arm64 += arm64/page_fault_test TEST_GEN_PROGS_arm64 += arm64/psci_test TEST_GEN_PROGS_arm64 += arm64/sea_to_user diff --git a/tools/testing/selftests/kvm/arm64/mmio_sign_ext.c b/tools/testing/selftests/kvm/arm64/mmio_sign_ext.c new file mode 100644 index 000000000000..25196f1e6322 --- /dev/null +++ b/tools/testing/selftests/kvm/arm64/mmio_sign_ext.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mmio_sign_ext - Test sign-extending MMIO load emulation (LDRSB/LDRSH/LDRSW) + * + * Copyright (c) 2026 Google LLC + * Author: Fuad Tabba + */ + +#include + +#include "processor.h" +#include "test_util.h" + +#define MMIO_ADDR 0x8000000ULL + +/* AP[1]: allow unprivileged (EL0) access to a mapping. */ +#define PTE_USER BIT(6) + +/* SPSR for ERET to EL0t with DAIF masked. */ +#define SPSR_EL0 (PSR_MODE_EL0t | PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT) + +struct mmio_test { + const char *name; + uint64_t data; /* access-width value, host byte order */ + uint8_t len; + uint64_t expected; /* sign-extended result; same for LE and BE */ +}; + +/* Paired 1:1, in order, with the loads in guest_loads_le() and el0_be_loads. */ +static const struct mmio_test tests[] = { + /* LDRSB Xt: byte sign-extended to 64 bits */ + { "LDRSB Xt 0xFF", 0xFF, 1, 0xFFFFFFFFFFFFFFFFULL }, + { "LDRSB Xt 0x7F", 0x7F, 1, 0x7FULL }, + + /* LDRSB Wt: byte sign-extended to 32 bits, upper 32 bits zeroed */ + { "LDRSB Wt 0xFF", 0xFF, 1, 0xFFFFFFFFULL }, + { "LDRSB Wt 0x7F", 0x7F, 1, 0x7FULL }, + + /* LDRSH Xt: halfword sign-extended to 64 bits */ + { "LDRSH Xt 0x8001", 0x8001, 2, 0xFFFFFFFFFFFF8001ULL }, + { "LDRSH Xt 0x7FFF", 0x7FFF, 2, 0x7FFFULL }, + + /* LDRSH Wt: halfword sign-extended to 32 bits, upper 32 bits zeroed */ + { "LDRSH Wt 0x8001", 0x8001, 2, 0xFFFF8001ULL }, + { "LDRSH Wt 0x7FFF", 0x7FFF, 2, 0x7FFFULL }, + + /* LDRSW Xt: word sign-extended to 64 bits (no Wt form) */ + { "LDRSW Xt 0x80000001", 0x80000001, 4, 0xFFFFFFFF80000001ULL }, + { "LDRSW Xt 0x7FFFFFFF", 0x7FFFFFFF, 4, 0x7FFFFFFFULL }, +}; + +/* Issue one sign-extending load from MMIO and report the result. */ +#define GUEST_LDRS(load) do { \ + uint64_t val; \ + \ + asm volatile(load : "=r"(val) : "r"(MMIO_ADDR) : "memory"); \ + GUEST_SYNC(val); \ +} while (0) + +/* Little-endian pass: loads issued at EL1. */ +static void guest_loads_le(void) +{ + GUEST_LDRS("ldrsb %0, [%1]"); + GUEST_LDRS("ldrsb %0, [%1]"); + GUEST_LDRS("ldrsb %w0, [%1]"); + GUEST_LDRS("ldrsb %w0, [%1]"); + GUEST_LDRS("ldrsh %0, [%1]"); + GUEST_LDRS("ldrsh %0, [%1]"); + GUEST_LDRS("ldrsh %w0, [%1]"); + GUEST_LDRS("ldrsh %w0, [%1]"); + GUEST_LDRS("ldrsw %0, [%1]"); + GUEST_LDRS("ldrsw %0, [%1]"); +} + +/* + * Run the big-endian loads at EL0, where SCTLR_EL1.E0E flips only the data + * endianness; at EL1, SCTLR_EL1.EE would also flip the page-table walk and + * fault on the little-endian tables. x0 holds MMIO_ADDR; results return in + * x19..x28 (tests[] order) via a single SVC. + */ +extern char el0_be_loads[]; +asm( +" .pushsection .text, \"ax\"\n" +" .global el0_be_loads\n" +"el0_be_loads:\n" +" ldrsb x19, [x0]\n" +" ldrsb x20, [x0]\n" +" ldrsb w21, [x0]\n" +" ldrsb w22, [x0]\n" +" ldrsh x23, [x0]\n" +" ldrsh x24, [x0]\n" +" ldrsh w25, [x0]\n" +" ldrsh w26, [x0]\n" +" ldrsw x27, [x0]\n" +" ldrsw x28, [x0]\n" +" svc #0\n" +" .popsection\n" +); + +/* EL1 handler for the EL0 SVC: report the results, then finish. */ +static void el0_svc_handler(struct ex_regs *regs) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tests); i++) + GUEST_SYNC(regs->regs[19 + i]); + + GUEST_DONE(); +} + +static bool guest_mixed_endian_el0(void) +{ + uint64_t mmfr0 = read_sysreg(id_aa64mmfr0_el1); + + return SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGEND, mmfr0) || + SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGENDEL0, mmfr0); +} + +static void guest_code(void) +{ + guest_loads_le(); + + if (guest_mixed_endian_el0()) { + write_sysreg(read_sysreg(sctlr_el1) | SCTLR_EL1_E0E, sctlr_el1); + isb(); + + asm volatile( + " msr elr_el1, %[pc]\n" + " msr spsr_el1, %[spsr]\n" + " mov x0, %[mmio]\n" + " isb\n" + " eret\n" + : + : [pc] "r"(el0_be_loads), + [spsr] "r"((uint64_t)SPSR_EL0), + [mmio] "r"(MMIO_ADDR) + : "x0", "memory"); + __builtin_unreachable(); /* el0_svc_handler ends the test */ + } + + GUEST_DONE(); +} + +static void handle_mmio(struct kvm_run *run, const struct mmio_test *t, bool be) +{ + int i; + + TEST_ASSERT_EQ(run->mmio.phys_addr, MMIO_ADDR); + TEST_ASSERT(!run->mmio.is_write, "Expected MMIO read for %s", t->name); + TEST_ASSERT_EQ(run->mmio.len, t->len); + + memset(run->mmio.data, 0, sizeof(run->mmio.data)); + if (be) { + /* The guest reads the device bytes most-significant first. */ + for (i = 0; i < t->len; i++) + run->mmio.data[i] = t->data >> (8 * (t->len - 1 - i)); + } else { + /* Works because arm64 KVM hosts are always little-endian. */ + memcpy(run->mmio.data, &t->data, t->len); + } +} + +static void expect_sync(struct kvm_vcpu *vcpu, struct ucall *uc, + const struct mmio_test *t) +{ + switch (get_ucall(vcpu, uc)) { + case UCALL_SYNC: + TEST_ASSERT(uc->args[1] == t->expected, + "%s: got %#lx, want %#lx", t->name, + (unsigned long)uc->args[1], (unsigned long)t->expected); + break; + case UCALL_ABORT: + REPORT_GUEST_ASSERT(*uc); + break; + default: + TEST_FAIL("Unexpected ucall for %s", t->name); + } +} + +/* OR PTE_USER into the leaf descriptors covering [gva, gva + len). */ +static void make_el0_accessible(struct kvm_vm *vm, uint64_t gva, uint64_t len) +{ + uint64_t addr; + + for (addr = gva & ~((uint64_t)vm->page_size - 1); addr < gva + len; + addr += vm->page_size) + *virt_get_pte_hva(vm, addr) |= PTE_USER; +} + +static bool vcpu_mixed_endian_el0(struct kvm_vcpu *vcpu) +{ + uint64_t mmfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1)); + + return SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGEND, mmfr0) || + SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGENDEL0, mmfr0); +} + +int main(void) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + struct ucall uc; + unsigned int i; + bool be; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code); + virt_map(vm, MMIO_ADDR, MMIO_ADDR, 1); + + vm_init_descriptor_tables(vm); + vcpu_init_descriptor_tables(vcpu); + vm_install_sync_handler(vm, VECTOR_SYNC_LOWER_64, ESR_ELx_EC_SVC64, + el0_svc_handler); + + be = vcpu_mixed_endian_el0(vcpu); + if (be) + make_el0_accessible(vm, MMIO_ADDR, vm->page_size); + + ksft_print_header(); + ksft_set_plan(ARRAY_SIZE(tests) * (be ? 2 : 1)); + + /* Little-endian pass: one load and one result per iteration. */ + for (i = 0; i < ARRAY_SIZE(tests); i++) { + const struct mmio_test *t = &tests[i]; + + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); + handle_mmio(vcpu->run, t, false); + + vcpu_run(vcpu); + expect_sync(vcpu, &uc, t); + + ksft_test_result_pass("%s\n", t->name); + } + + if (be) { + /* The EL0 stub issues all the loads, then reports the results. */ + for (i = 0; i < ARRAY_SIZE(tests); i++) { + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); + handle_mmio(vcpu->run, &tests[i], true); + } + for (i = 0; i < ARRAY_SIZE(tests); i++) { + vcpu_run(vcpu); + expect_sync(vcpu, &uc, &tests[i]); + ksft_test_result_pass("BE %s\n", tests[i].name); + } + } + + vcpu_run(vcpu); + TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_DONE, "Expected UCALL_DONE"); + + kvm_vm_free(vm); + + ksft_finished(); +} From b983c56426383e4a06fa5970c4e33cee879b1482 Mon Sep 17 00:00:00 2001 From: Farhad Alemi Date: Mon, 6 Jul 2026 10:20:23 +0200 Subject: [PATCH 153/479] cgroup/cpuset: rebind mm mempolicy to effective_mems, not mems_allowed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating a child cpuset where cpuset.mems is never set leads to a div/0 when a VMA mempolicy with MPOL_F_RELATIVE_NODES rebinds in response to a CPU hotplug event. Reproduction steps: 1) Create a cgroup w/ cpuset controls (do not set cpuset.mems) 2) Move the task into the child cpuset 3) Create a VMA mempolicy for that task with MPOL_F_RELATIVE_NODES 4) unplug and hotplug a cpu echo 0 > /sys/devices/system/cpu/cpu1/online echo 1 > /sys/devices/system/cpu/cpu1/online 5) mempolicy rebind does a div/0 in mpol_relative_nodemask on the call to __nodes_fold() The cpuset code passes (cs->mems_allowed) which is not guaranteed to have nodes to the rebind routine. Use cs->effective_mems instead, which is guaranteed to have a non-empty nodemask once we reach that code path. Link: https://lore.kernel.org/all/CA+0ovCiEz6SP_sn3kN4Tb+_oC=eHMXy_Ffj=usV3wREdQrUtww@mail.gmail.com/ Fixes: ae1c802382f7 ("cpuset: apply cs->effective_{cpus,mems}") Closes: https://lore.kernel.org/linux-mm/CA+0ovCgxbZkXa+OU8w3s84R3KNPNxxRfmsNR-udh+afQBbGNmw@mail.gmail.com/ Suggested-by: Gregory Price Suggested-by: Waiman Long Acked-by: Waiman Long Signed-off-by: Farhad Alemi Cc: Andrew Morton Cc: Alistair Popple Cc: Byungchul Park Cc: Gregory Price Cc: "Huang, Ying" Cc: Joshua Hahn Cc: Matthew Brost Cc: Rakie Kim Cc: Rasmus Villemoes Cc: Zi Yan Cc: Tejun Heo Cc: Ridong Chen Cc: Johannes Weiner Cc: "Michal Koutný" Cc: [ david: add a comment, slightly rephrase description ] Signed-off-by: David Hildenbrand (Arm) Signed-off-by: Tejun Heo --- kernel/cgroup/cpuset.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 591e3aa487fc..45944b3e31ca 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -2653,7 +2653,12 @@ void cpuset_update_tasks_nodemask(struct cpuset *cs) migrate = is_memory_migrate(cs); - mpol_rebind_mm(mm, &cs->mems_allowed); + /* + * For v1 we can have empty effective_mems, but we cannot + * attach any tasks (see cpuset_can_attach_check()). For v2, + * effective_mems is guaranteed to not be empty. + */ + mpol_rebind_mm(mm, &cs->effective_mems); if (migrate) cpuset_migrate_mm(mm, &cs->old_mems_allowed, &newmems); else From f3858d5b1432098c1936e03d6e03dd0e33facf60 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 6 Jul 2026 22:08:41 +0800 Subject: [PATCH 154/479] wifi: mac80211: free AP_VLAN bc_buf SKBs outside IRQ lock ieee80211_do_stop() removes AP_VLAN packets from the parent AP ps->bc_buf while holding ps->bc_buf.lock with IRQs disabled. It then calls ieee80211_free_txskb() before dropping the lock. ieee80211_free_txskb() is not just a passive SKB release. For SKBs with TX status state it can report a dropped frame through cfg80211/nl80211, and that path can reach netlink tap transmit. This is the same reason the pending queue cleanup in ieee80211_do_stop() already unlinks SKBs under the queue lock and frees them after IRQ state is restored. The buggy scenario involves two paths, with each column showing the order within that path: AP_VLAN management TX: AP_VLAN stop: 1. attach ACK-status state 1. clear the running state 2. queue a multicast SKB on 2. take ps->bc_buf.lock with IRQs parent ps->bc_buf disabled 3. unlink the AP_VLAN SKB 4. call ieee80211_free_txskb() Unlink matching AP_VLAN SKBs from ps->bc_buf under the existing lock, but move them to a local free queue. Drop the lock and restore IRQ state before calling ieee80211_free_txskb(). WARNING: kernel/softirq.c:430 at __local_bh_enable_ip Fixes: 397a7a24ef8c ("mac80211: free ps->bc_buf skbs on vlan device stop") Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260706140841.581566-1-zzzccc427@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 086272c3ec08..43460a705a6b 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -588,6 +588,7 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do WARN_ON(!list_empty(&sdata->u.ap.vlans)); } else if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) { /* remove all packets in parent bc_buf pointing to this dev */ + __skb_queue_head_init(&freeq); ps = &sdata->bss->ps; spin_lock_irqsave(&ps->bc_buf.lock, flags); @@ -595,10 +596,15 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do if (skb->dev == sdata->dev) { __skb_unlink(skb, &ps->bc_buf); local->total_ps_buffered--; - ieee80211_free_txskb(&local->hw, skb); + __skb_queue_tail(&freeq, skb); } } spin_unlock_irqrestore(&ps->bc_buf.lock, flags); + + skb_queue_walk_safe(&freeq, skb, tmp) { + __skb_unlink(skb, &freeq); + ieee80211_free_txskb(&local->hw, skb); + } } if (going_down) From 95fc02722edde02946d0d475221f2b2054d3d8ba Mon Sep 17 00:00:00 2001 From: Dawei Feng Date: Mon, 6 Jul 2026 22:35:07 +0800 Subject: [PATCH 155/479] wifi: mac80211: fix memory leak in ieee80211_register_hw() If kmemdup() fails while copying supported band structures, the error path jumps to fail_rate. This skips rate_control_deinitialize() and leaks the initialized local->rate_ctrl. Fix this by adding a fail_band label that shares the rate-control cleanup path before falling through to the remaining teardown. 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-rc7. An x86_64 allyesconfig build showed no new warnings. As we do not have a suitable mac80211 device/driver combination to test with, no runtime testing was able to be performed. Fixes: 09b4a4faf9d0 ("mac80211: introduce capability flags for VHT EXT NSS support") Cc: stable@vger.kernel.org Reviewed-by: Zilin Guan Signed-off-by: Dawei Feng Link: https://patch.msgid.link/20260706143507.146131-1-dawei.feng@seu.edu.cn Signed-off-by: Johannes Berg --- net/mac80211/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 90d295cc364f..eb1eaaf34612 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -1602,7 +1602,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) sband = kmemdup(sband, sizeof(*sband), GFP_KERNEL); if (!sband) { result = -ENOMEM; - goto fail_rate; + goto fail_band; } wiphy_dbg(hw->wiphy, "copying sband (band %d) due to VHT EXT NSS BW flag\n", @@ -1678,6 +1678,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) #endif wiphy_unregister(local->hw.wiphy); fail_wiphy_register: + fail_band: rtnl_lock(); rate_control_deinitialize(local); ieee80211_remove_interfaces(local); From 0c2ed186bbe14304415476d6707b747dddcd8583 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 6 Jul 2026 23:24:18 +0800 Subject: [PATCH 156/479] wifi: cfg80211: use wiphy work for socket owner autodisconnect nl80211_netlink_notify() walks the cfg80211 wireless device list when a NETLINK_GENERIC socket is released. If the socket owns a connection, the notifier queues the embedded wdev->disconnect_wk work item. That work is a plain work_struct today. NETDEV_GOING_DOWN cancels it, but a NETLINK_URELEASE notifier that already observed conn_owner_nlportid can queue it after that cancel returns. _cfg80211_unregister_wdev() then removes the wdev from the list and waits for RCU readers, but synchronize_net() does not drain work queued by such a reader. Make the autodisconnect work a wiphy_work instead. The callback already needs the wiphy mutex, and wiphy_work runs under that mutex. This lets teardown cancel pending autodisconnect work while holding the mutex, without a cancel_work_sync() vs. worker locking concern. Also cancel the wiphy work after list_del_rcu() and synchronize_net(). Any NETLINK_URELEASE notifier that had already reached the wdev list has then either queued the work and it is removed, or can no longer find the wdev. Fixes: bd2522b16884 ("cfg80211: NL80211_ATTR_SOCKET_OWNER support for CMD_CONNECT") Suggested-by: Johannes Berg Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260706152418.779226-1-zzzccc427@gmail.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 2 +- net/wireless/core.c | 10 ++++++---- net/wireless/core.h | 2 +- net/wireless/nl80211.c | 3 ++- net/wireless/sme.c | 6 ++---- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 3751a1d74765..f5abf1db7558 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -7228,7 +7228,7 @@ struct wireless_dev { enum ieee80211_bss_type conn_bss_type; u32 conn_owner_nlportid; - struct work_struct disconnect_wk; + struct wiphy_work disconnect_wk; u8 disconnect_bssid[ETH_ALEN]; struct list_head event_list; diff --git a/net/wireless/core.c b/net/wireless/core.c index 082f0ee12f1b..610238d723ff 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -1425,6 +1425,7 @@ static void _cfg80211_unregister_wdev(struct wireless_dev *wdev, list_del_rcu(&wdev->list); synchronize_net(); rdev->devlist_generation++; + wiphy_work_cancel(wdev->wiphy, &wdev->disconnect_wk); cfg80211_mlme_purge_registrations(wdev); @@ -1638,7 +1639,7 @@ void cfg80211_init_wdev(struct wireless_dev *wdev) wdev->iftype == NL80211_IFTYPE_ADHOC) && !wdev->use_4addr) wdev->netdev->priv_flags |= IFF_DONT_BRIDGE; - INIT_WORK(&wdev->disconnect_wk, cfg80211_autodisconnect_wk); + wiphy_work_init(&wdev->disconnect_wk, cfg80211_autodisconnect_wk); } void cfg80211_register_wdev(struct cfg80211_registered_device *rdev, @@ -1744,10 +1745,11 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb, break; case NETDEV_GOING_DOWN: cfg80211_leave(rdev, wdev, -1); - scoped_guard(wiphy, &rdev->wiphy) + scoped_guard(wiphy, &rdev->wiphy) { cfg80211_remove_links(wdev); - /* since we just did cfg80211_leave() nothing to do there */ - cancel_work_sync(&wdev->disconnect_wk); + /* since we just did cfg80211_leave() nothing to do there */ + wiphy_work_cancel(wdev->wiphy, &wdev->disconnect_wk); + } break; case NETDEV_DOWN: wiphy_lock(&rdev->wiphy); diff --git a/net/wireless/core.h b/net/wireless/core.h index f60c66b88677..ac6ce9f967ec 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -428,7 +428,7 @@ void __cfg80211_port_authorized(struct wireless_dev *wdev, const u8 *peer_addr, const u8 *td_bitmap, u8 td_bitmap_len); int cfg80211_mgd_wext_connect(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev); -void cfg80211_autodisconnect_wk(struct work_struct *work); +void cfg80211_autodisconnect_wk(struct wiphy *wiphy, struct wiphy_work *work); /* SME implementation */ void cfg80211_conn_work(struct work_struct *work); diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 625c99cf70a3..5adcb6bd0fc5 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -22954,7 +22954,8 @@ static int nl80211_netlink_notify(struct notifier_block * nb, wdev->nl_owner_dead = true; schedule_work(&rdev->destroy_work); } else if (wdev->conn_owner_nlportid == notify->portid) { - schedule_work(&wdev->disconnect_wk); + wiphy_work_queue(wdev->wiphy, + &wdev->disconnect_wk); } cfg80211_release_pmsr(wdev, notify->portid); diff --git a/net/wireless/sme.c b/net/wireless/sme.c index b451df3096dd..2a719b5c487e 100644 --- a/net/wireless/sme.c +++ b/net/wireless/sme.c @@ -1578,13 +1578,11 @@ int cfg80211_disconnect(struct cfg80211_registered_device *rdev, * Used to clean up after the connection / connection attempt owner socket * disconnects */ -void cfg80211_autodisconnect_wk(struct work_struct *work) +void cfg80211_autodisconnect_wk(struct wiphy *wiphy, struct wiphy_work *work) { struct wireless_dev *wdev = container_of(work, struct wireless_dev, disconnect_wk); - struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); - - guard(wiphy)(wdev->wiphy); + struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); if (wdev->conn_owner_nlportid) { switch (wdev->iftype) { From 4e72459683b5185568e9ffe2584a7b834f7902b5 Mon Sep 17 00:00:00 2001 From: Shahar Tzarfati Date: Mon, 6 Jul 2026 22:27:52 +0300 Subject: [PATCH 157/479] wifi: mac80211: recalculate rx_nss on IBSS peer capability update When IBSS peer capabilities change, rates_updated is set to true in ieee80211_update_sta_info(), but rx_nss is never recalculated. For peers with HT/VHT, this leaves rx_nss at 0 instead of the correct value, causing drivers to use incorrect rate scaling parameters. The root cause is that the commit below moved NSS initialisation out of rate_control_rate_init() into explicit call sites, but missing the rates_updated path in ieee80211_update_sta_info(). Fix this by calling ieee80211_sta_init_nss_bw_capa() before rate_control_rate_init() when peer capabilities are updated, consistent with the other IBSS call sites added by that commit. Fixes: e5ad38a9b261 ("wifi: mac80211: clean up STA NSS handling") Signed-off-by: Shahar Tzarfati Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260706222724.422adfd57b71.I5a47f65c5e38a221712f5203e5c8040304b382b5@changeid Signed-off-by: Johannes Berg --- net/mac80211/ibss.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c index d0fd6054f182..1e5414ee27c0 100644 --- a/net/mac80211/ibss.c +++ b/net/mac80211/ibss.c @@ -1029,8 +1029,8 @@ static void ieee80211_update_sta_info(struct ieee80211_sub_if_data *sdata, u32 changed = IEEE80211_RC_SUPP_RATES_CHANGED; u8 rx_nss = sta->sta.deflink.rx_nss; - /* Force rx_nss recalculation */ - sta->sta.deflink.rx_nss = 0; + ieee80211_sta_init_nss_bw_capa(&sta->deflink, + &sdata->deflink.conf->chanreq.oper); rate_control_rate_init(&sta->deflink); if (sta->sta.deflink.rx_nss != rx_nss) changed |= IEEE80211_RC_NSS_CHANGED; From d0e69d9afa59b93c30294eba89b1f15f69e91105 Mon Sep 17 00:00:00 2001 From: Pagadala Yesu Anjaneyulu Date: Mon, 6 Jul 2026 22:37:56 +0300 Subject: [PATCH 158/479] wifi: mac80211: ibss: wait for in-flight TX on disconnect While leaving an IBSS in ieee80211_ibss_disconnect() mac80211 flushes stations, turns the carrier off and immediately tells the driver to leave as well. While there may be synchronize_net() in station flush and in this code later, packets can still be transmitted due to cross-CPU race conditions after carrier off is set. Therefore, it's possible for a race to happen where a TX to the driver occurs while or after telling it to leave the IBSS. This can be confusing to drivers, and in the case of iwlwifi leads to an attempt to use invalid queues. Move netif_carrier_off() to occur before sta_info_flush() during IBSS disconnect, and add synchronize_net() if flushing didn't, so that the synchronize_net() always happens between turning the carrier off and telling the driver, avoiding this race. Signed-off-by: Pagadala Yesu Anjaneyulu Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260706223751.da1ce439cc93.If5cf482f87ab98ce66dd48724e24c81fed236d3f@changeid Signed-off-by: Johannes Berg --- net/mac80211/ibss.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c index 1e5414ee27c0..882f91abbb66 100644 --- a/net/mac80211/ibss.c +++ b/net/mac80211/ibss.c @@ -668,7 +668,9 @@ static void ieee80211_ibss_disconnect(struct ieee80211_sub_if_data *sdata) ifibss->state = IEEE80211_IBSS_MLME_SEARCH; - sta_info_flush(sdata, -1); + netif_carrier_off(sdata->dev); + if (!sta_info_flush(sdata, -1)) + synchronize_net(); spin_lock_bh(&ifibss->incomplete_lock); while (!list_empty(&ifibss->incomplete_stations)) { @@ -682,8 +684,6 @@ static void ieee80211_ibss_disconnect(struct ieee80211_sub_if_data *sdata) } spin_unlock_bh(&ifibss->incomplete_lock); - netif_carrier_off(sdata->dev); - sdata->vif.cfg.ibss_joined = false; sdata->vif.cfg.ibss_creator = false; sdata->vif.bss_conf.enable_beacon = false; @@ -710,7 +710,6 @@ static void ieee80211_csa_connection_drop_work(struct wiphy *wiphy, u.ibss.csa_connection_drop_work); ieee80211_ibss_disconnect(sdata); - synchronize_rcu(); skb_queue_purge(&sdata->skb_queue); /* trigger a scan to find another IBSS network to join */ @@ -1797,8 +1796,6 @@ int ieee80211_ibss_leave(struct ieee80211_sub_if_data *sdata) memset(&ifibss->ht_capa, 0, sizeof(ifibss->ht_capa)); memset(&ifibss->ht_capa_mask, 0, sizeof(ifibss->ht_capa_mask)); - synchronize_rcu(); - skb_queue_purge(&sdata->skb_queue); timer_delete_sync(&sdata->u.ibss.timer); From d5e4586546974179feca305a94e07fac3e9727fe Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Tue, 7 Jul 2026 10:53:34 +0800 Subject: [PATCH 159/479] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access cfg80211_rx_mlme_mgmt() and cfg80211_tx_mlme_mgmt() call tracepoints before rejecting frames shorter than the frame-control field. After that, they only require len >= 2 before dispatching into subtype handlers that assume their fixed fields are present. The frames that trip this are not shorter than 2 bytes; they are short relative to their subtype. mwifiex is a concrete in-tree example on the length side: mwifiex_process_mgmt_packet() only requires a 4-address ieee80211_hdr plus the 2-byte firmware length prefix before handing the frame to cfg80211_rx_mlme_mgmt(). After stripping the length prefix and removing addr4, pkt_len can be exactly 24: a bare 3-address management header with no reason-code body. The existing WARN_ON(len < 2) does not fire on such a frame, and cfg80211_process_deauth() then reads u.deauth.reason_code as a two-byte access starting at offset 24, immediately past the 24-byte buffer. Add a frame-control length gate, then validate each subtype's minimum frame size in an if/else-if chain that mirrors the dispatch logic. Trace only after the frame is known to be well-formed. Side effects of this change: - The WARN_ON(len < 2) is dropped. It only guarded the frame_control read, never the subtype fixed fields, and it does not fire on the frames that actually trigger the out-of-bounds read (which are >= 2). The len >= 2 check is kept as the guard before dereferencing frame_control, but without the warning: these are exported callbacks and a malformed frame from a driver should be dropped silently rather than backtraced. - cfg80211_tx_mlme_mgmt() previously routed every non-deauth subtype through disassociation handling; it now silently ignores unrecognised subtypes. Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260707025336.22557-1-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/mlme.c | 49 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c index 2a2c173058ba..acc749eee2fe 100644 --- a/net/wireless/mlme.c +++ b/net/wireless/mlme.c @@ -151,19 +151,35 @@ void cfg80211_rx_mlme_mgmt(struct net_device *dev, const u8 *buf, size_t len) { struct wireless_dev *wdev = dev->ieee80211_ptr; struct ieee80211_mgmt *mgmt = (void *)buf; + __le16 fc; lockdep_assert_wiphy(wdev->wiphy); - trace_cfg80211_rx_mlme_mgmt(dev, buf, len); - - if (WARN_ON(len < 2)) + if (len < sizeof(fc)) return; - if (ieee80211_is_auth(mgmt->frame_control)) + fc = mgmt->frame_control; + + if (ieee80211_is_auth(fc)) { + if (len < offsetofend(struct ieee80211_mgmt, u.auth.status_code)) + return; + } else if (ieee80211_is_deauth(fc)) { + if (len < offsetofend(struct ieee80211_mgmt, u.deauth.reason_code)) + return; + } else if (ieee80211_is_disassoc(fc)) { + if (len < offsetofend(struct ieee80211_mgmt, u.disassoc.reason_code)) + return; + } else { + return; + } + + trace_cfg80211_rx_mlme_mgmt(dev, buf, len); + + if (ieee80211_is_auth(fc)) cfg80211_process_auth(wdev, buf, len); - else if (ieee80211_is_deauth(mgmt->frame_control)) + else if (ieee80211_is_deauth(fc)) cfg80211_process_deauth(wdev, buf, len, false); - else if (ieee80211_is_disassoc(mgmt->frame_control)) + else cfg80211_process_disassoc(wdev, buf, len, false); } EXPORT_SYMBOL(cfg80211_rx_mlme_mgmt); @@ -216,15 +232,28 @@ void cfg80211_tx_mlme_mgmt(struct net_device *dev, const u8 *buf, size_t len, { struct wireless_dev *wdev = dev->ieee80211_ptr; struct ieee80211_mgmt *mgmt = (void *)buf; + __le16 fc; lockdep_assert_wiphy(wdev->wiphy); - trace_cfg80211_tx_mlme_mgmt(dev, buf, len, reconnect); - - if (WARN_ON(len < 2)) + if (len < sizeof(fc)) return; - if (ieee80211_is_deauth(mgmt->frame_control)) + fc = mgmt->frame_control; + + if (ieee80211_is_deauth(fc)) { + if (len < offsetofend(struct ieee80211_mgmt, u.deauth.reason_code)) + return; + } else if (ieee80211_is_disassoc(fc)) { + if (len < offsetofend(struct ieee80211_mgmt, u.disassoc.reason_code)) + return; + } else { + return; + } + + trace_cfg80211_tx_mlme_mgmt(dev, buf, len, reconnect); + + if (ieee80211_is_deauth(fc)) cfg80211_process_deauth(wdev, buf, len, reconnect); else cfg80211_process_disassoc(wdev, buf, len, reconnect); From b760113aeca2e9362d56bf9e9263373ffe6c8eb3 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Tue, 7 Jul 2026 10:53:35 +0800 Subject: [PATCH 160/479] wifi: cfg80211: validate assoc response length before status and IE access cfg80211_rx_assoc_resp() initialises the status and response-IE fields of cfg80211_connect_resp_params from the management frame before proving that the frame is long enough for those offsets. S1G and regular association responses also have different IE offsets, but the S1G path only patched resp_ie after the unsafe initialiser had already run. Defer resp_ie, resp_ie_len, and status to after the link-iteration loop. Use a bool to remember whether the frame is S1G, then validate the appropriate minimum length and set all three fields in a single if/else block. Funnel short-frame and SME-reject cleanup through a shared free_bss label for the abandon paths. Assisted-by: Codex:gpt-5.5 Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260707025336.22557-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/mlme.c | 56 ++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c index acc749eee2fe..7824b7ac2770 100644 --- a/net/wireless/mlme.c +++ b/net/wireless/mlme.c @@ -32,15 +32,11 @@ void cfg80211_rx_assoc_resp(struct net_device *dev, .timeout_reason = NL80211_TIMEOUT_UNSPECIFIED, .req_ie = data->req_ies, .req_ie_len = data->req_ies_len, - .resp_ie = mgmt->u.assoc_resp.variable, - .resp_ie_len = data->len - - offsetof(struct ieee80211_mgmt, - u.assoc_resp.variable), - .status = le16_to_cpu(mgmt->u.assoc_resp.status_code), .ap_mld_addr = data->ap_mld_addr, .assoc_encrypted = data->assoc_encrypted, }; unsigned int link_id; + bool is_s1g = false; for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) { cr.links[link_id].status = data->links[link_id].status; @@ -61,16 +57,32 @@ void cfg80211_rx_assoc_resp(struct net_device *dev, if (cr.links[link_id].bss->channel->band == NL80211_BAND_S1GHZ) { WARN_ON(link_id); - cr.resp_ie = (u8 *)&mgmt->u.s1g_assoc_resp.variable; - cr.resp_ie_len = data->len - - offsetof(struct ieee80211_mgmt, - u.s1g_assoc_resp.variable); + is_s1g = true; } if (cr.ap_mld_addr) cr.valid_links |= BIT(link_id); } + if (is_s1g) { + if (data->len < offsetof(struct ieee80211_mgmt, + u.s1g_assoc_resp.variable)) + goto free_bss; + cr.resp_ie = (u8 *)&mgmt->u.s1g_assoc_resp.variable; + cr.resp_ie_len = data->len - + offsetof(struct ieee80211_mgmt, + u.s1g_assoc_resp.variable); + } else { + if (data->len < offsetof(struct ieee80211_mgmt, + u.assoc_resp.variable)) + goto free_bss; + cr.resp_ie = mgmt->u.assoc_resp.variable; + cr.resp_ie_len = data->len - + offsetof(struct ieee80211_mgmt, + u.assoc_resp.variable); + } + cr.status = le16_to_cpu(mgmt->u.assoc_resp.status_code); + trace_cfg80211_send_rx_assoc(dev, data); /* @@ -79,22 +91,24 @@ void cfg80211_rx_assoc_resp(struct net_device *dev, * and got a reject -- we only try again with an assoc * frame instead of reassoc. */ - if (cfg80211_sme_rx_assoc_resp(wdev, cr.status)) { - for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) { - struct cfg80211_bss *bss = data->links[link_id].bss; - - if (!bss) - continue; - - cfg80211_unhold_bss(bss_from_pub(bss)); - cfg80211_put_bss(wiphy, bss); - } - return; - } + if (cfg80211_sme_rx_assoc_resp(wdev, cr.status)) + goto free_bss; nl80211_send_rx_assoc(rdev, dev, data); /* update current_bss etc., consumes the bss reference */ __cfg80211_connect_result(dev, &cr, cr.status == WLAN_STATUS_SUCCESS); + return; + +free_bss: + for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) { + struct cfg80211_bss *bss = data->links[link_id].bss; + + if (!bss) + continue; + + cfg80211_unhold_bss(bss_from_pub(bss)); + cfg80211_put_bss(wiphy, bss); + } } EXPORT_SYMBOL(cfg80211_rx_assoc_resp); From 5b4396e12cb10fa89ca0a272fae2b5408cfeaa91 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Thu, 2 Jul 2026 09:19:58 -0700 Subject: [PATCH 161/479] xfs: drop ASSERT(0) on unrecognized log item type The item type passed to ITEM_TYPE() comes from the on-disk log, so a fuzzed or crafted image can reach the "unrecognized type" path in xlog_recover_reorder_trans() and trip its ASSERT(0) on a CONFIG_XFS_DEBUG kernel. The -EFSCORRUPTED return handles it fine; drop the assert. Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Weiming Shi Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log_recover.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c index 5f984bf5698a..a1b373c68f0e 100644 --- a/fs/xfs/xfs_log_recover.c +++ b/fs/xfs/xfs_log_recover.c @@ -1912,7 +1912,6 @@ xlog_recover_reorder_trans( xfs_warn(log->l_mp, "%s: unrecognized type of log operation (%d)", __func__, ITEM_TYPE(item)); - ASSERT(0); /* * return the remaining items back to the transaction * item list so they can be freed in caller. From 34dd2253414d228fc9bf8a70b5f9f83107eda3e1 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Thu, 2 Jul 2026 09:19:59 -0700 Subject: [PATCH 162/479] xfs: splice unsorted log items back to the transaction after the loop On error, xlog_recover_reorder_trans() splices the leftover sort_list items back to trans->r_itemq inside the loop before breaking out. The loop tail already splices the per-fate lists back, so do sort_list there too, guarded by the assert that used to sit after the loop. No functional change. It drops the duplicated splice so the next patch can add another error case without repeating it. Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Weiming Shi Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log_recover.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c index a1b373c68f0e..103b2a79667b 100644 --- a/fs/xfs/xfs_log_recover.c +++ b/fs/xfs/xfs_log_recover.c @@ -1912,12 +1912,6 @@ xlog_recover_reorder_trans( xfs_warn(log->l_mp, "%s: unrecognized type of log operation (%d)", __func__, ITEM_TYPE(item)); - /* - * return the remaining items back to the transaction - * item list so they can be freed in caller. - */ - if (!list_empty(&sort_list)) - list_splice_init(&sort_list, &trans->r_itemq); error = -EFSCORRUPTED; break; } @@ -1945,7 +1939,15 @@ xlog_recover_reorder_trans( } } - ASSERT(list_empty(&sort_list)); + /* + * Return the remaining items back to the transaction item list so they + * can be freed in caller. This should only happen when we encounter + * an error. + */ + if (!list_empty(&sort_list)) { + ASSERT(error); + list_splice_init(&sort_list, &trans->r_itemq); + } if (!list_empty(&buffer_list)) list_splice(&buffer_list, &trans->r_itemq); if (!list_empty(&item_list)) From 2a665946e0407a05a3f81bd56a08553c446498e0 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Fri, 19 Jun 2026 14:44:01 +0800 Subject: [PATCH 163/479] wifi: brcmfmac: initialize SDIO data work before cleanup brcmf_sdio_probe() stores the newly allocated bus in sdiodev->bus before allocating the ordered workqueue. If that allocation fails, the function jumps to fail and calls brcmf_sdio_remove(). brcmf_sdio_remove() unconditionally cancels bus->datawork. Initialize the work item before the first failure path that can reach brcmf_sdio_remove(), so the cleanup path always observes a valid work object. This issue was found by our static analysis tool and then confirmed by manual review of the probe error path and the remove-time work drain. The problem pattern is an early setup failure that reaches a cleanup helper which cancels an embedded work item before its initializer has run. A QEMU PoC forced alloc_ordered_workqueue() to fail at the same point in brcmf_sdio_probe(), before INIT_WORK(&bus->datawork) is reached. The resulting fail path calls brcmf_sdio_remove(), and DEBUG_OBJECTS reports the invalid work drain with brcmf_sdio_probe() and brcmf_sdio_remove() in the stack. Fixes: 9982464379e8 ("brcmfmac: make sdio suspend wait for threads to freeze") Signed-off-by: Runyu Xiao Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260619064401.1048976-1-runyu.xiao@seu.edu.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c index 8fb595733b9c..b725c64e5b5c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c @@ -4465,6 +4465,7 @@ int brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev) bus->sdiodev = sdiodev; sdiodev->bus = bus; skb_queue_head_init(&bus->glom); + INIT_WORK(&bus->datawork, brcmf_sdio_dataworker); bus->txbound = BRCMF_TXBOUND; bus->rxbound = BRCMF_RXBOUND; bus->txminmax = BRCMF_TXMINMAX; @@ -4479,7 +4480,6 @@ int brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev) goto fail; } brcmf_sdiod_freezer_count(sdiodev); - INIT_WORK(&bus->datawork, brcmf_sdio_dataworker); bus->brcmf_wq = wq; /* attempt to attach to the dongle */ From 240c8d2c717b3f8153e7e877b22a82518d78dbdc Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Sat, 27 Jun 2026 21:13:13 +0800 Subject: [PATCH 164/479] wifi: brcmfmac: cyw: fix heap overflow on a short auth frame brcmf_notify_auth_frame_rx() takes the frame length from the firmware event and copies the frame body with the management header offset subtracted: u32 mgmt_frame_len = e->datalen - sizeof(struct brcmf_rx_mgmt_data); ... memcpy(&mgmt_frame->u, frame, mgmt_frame_len - offsetof(struct ieee80211_mgmt, u)); The only length check is e->datalen >= sizeof(*rxframe), so mgmt_frame_len can be anything from 0 up. offsetof(struct ieee80211_mgmt, u) is 24. When mgmt_frame_len is below that, the subtraction wraps as an unsigned value to a huge length. The memcpy then runs far past the kzalloc'd buffer. A malicious or malfunctioning AP can make the frame short during the external SAE auth exchange, so this is a remotely triggered heap overflow. Reject frames shorter than the management header offset before the copy. Fixes: 66f909308a7c ("wifi: brcmfmac: cyw: support external SAE authentication in station mode") Link: https://lore.kernel.org/r/178214417708.2368577.16740907093694208834@maoyixie.com Cc: stable@vger.kernel.org Co-developed-by: Kaixuan Li Signed-off-by: Kaixuan Li Signed-off-by: Maoyi Xie Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260627131313.3878893-1-maoyixie.tju@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c index ce09d44fa73c..873754be5174 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c @@ -293,6 +293,12 @@ brcmf_notify_auth_frame_rx(struct brcmf_if *ifp, return -EINVAL; } + if (mgmt_frame_len < offsetof(struct ieee80211_mgmt, u)) { + bphy_err(drvr, "Event %s (%d) frame too small. Ignore\n", + brcmf_fweh_event_name(e->event_code), e->event_code); + return -EINVAL; + } + wdev = &ifp->vif->wdev; WARN_ON(!wdev); From 8c6db30d79528279abbeb416e4f533f1f91b8724 Mon Sep 17 00:00:00 2001 From: Mostafa Saleh Date: Thu, 2 Jul 2026 10:38:40 +0000 Subject: [PATCH 165/479] KVM: arm64: Fix bounds checking in do_ffa_mem_reclaim() Sashiko (locally) reports out of bound write possiblity if SPMD returns an invalid data. While SPMD is considered trusted, pKVM does some basic checks, for offset to be less than or equal len. However, that is incorrect as even if the offset is smaller than len pKVM can still access out of bound memory in the next ffa_host_unshare_ranges(). Split this check into 2: 1- Check that the fixed portion of the descriptor fits. 2- After getting reg, check the variable array size addr_range_cnt fits. Also, drop the WARN_ONs as that will panic the kernel and in the next checks there are no WARNs, so that makes it consistent. Fixes: 0a9f15fd5674 ("KVM: arm64: pkvm: Add support for fragmented FF-A descriptors") Signed-off-by: Mostafa Saleh Reviewed-by: Vincent Donnefort Signed-off-by: Sebastian Ene Link: https://patch.msgid.link/20260702103848.1647249-4-sebastianene@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/nvhe/ffa.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/ffa.c b/arch/arm64/kvm/hyp/nvhe/ffa.c index 1af722771178..41cc4c1bafeb 100644 --- a/arch/arm64/kvm/hyp/nvhe/ffa.c +++ b/arch/arm64/kvm/hyp/nvhe/ffa.c @@ -607,8 +607,8 @@ static void do_ffa_mem_reclaim(struct arm_smccc_1_2_regs *res, * check that we end up with something that doesn't look _completely_ * bogus. */ - if (WARN_ON(offset > len || - fraglen > KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE)) { + if (offset + CONSTITUENTS_OFFSET(0) > len || + fraglen > KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE) { ret = FFA_RET_ABORTED; ffa_rx_release(res); goto out_unlock; @@ -636,11 +636,16 @@ static void do_ffa_mem_reclaim(struct arm_smccc_1_2_regs *res, ffa_rx_release(res); } + reg = (void *)buf + offset; + if (offset + CONSTITUENTS_OFFSET(reg->addr_range_cnt) > len) { + ret = FFA_RET_ABORTED; + goto out_unlock; + } + ffa_mem_reclaim(res, handle_lo, handle_hi, flags); if (res->a0 != FFA_SUCCESS) goto out_unlock; - reg = (void *)buf + offset; /* If the SPMD was happy, then we should be too. */ WARN_ON(ffa_host_unshare_ranges(reg->constituents, reg->addr_range_cnt)); From a6b49d27c17909608d54523220bb6f3498d4a1df Mon Sep 17 00:00:00 2001 From: Sebastian Ene Date: Thu, 2 Jul 2026 10:38:41 +0000 Subject: [PATCH 166/479] KVM: arm64: Validate the offset to the mem access descriptor Prevent the pKVM hypervisor from making assumptions that the endpoint memory access descriptor (EMAD) comes right after the FF-A memory region header. Prior to FF-A version 1.1 the header of the memory region didn't contain an offset to the endpoint memory access descriptor. The layout of a memory transaction looks like this from 1.1 onward: Type | Field name | Offset [ Header | ffa_mem_region | 0 EMAD 1 | ffa_mem_region_attributes) | ffa_mem_region.ep_mem_offset ] Verify that the offset to the first endpoint memory access descriptor is within the mailbox buffer bounds. Also, fix one hardcoded sizeof(struct ffa_mem_region_attributes) that should be replaced ffa_emad_size_get() for compatibility with FFA v1.0. Fixes: 42fb33dde42b ("KVM: arm64: Use FF-A 1.1 with pKVM") Signed-off-by: Mostafa Saleh Signed-off-by: Sebastian Ene Reviewed-by: Vincent Donnefort Link: https://patch.msgid.link/20260702103848.1647249-5-sebastianene@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/nvhe/ffa.c | 27 +++++++++++++++++++-------- include/linux/arm_ffa.h | 7 +++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/ffa.c b/arch/arm64/kvm/hyp/nvhe/ffa.c index 41cc4c1bafeb..2e7ab7e3319d 100644 --- a/arch/arm64/kvm/hyp/nvhe/ffa.c +++ b/arch/arm64/kvm/hyp/nvhe/ffa.c @@ -476,11 +476,12 @@ static void __do_ffa_mem_xfer(const u64 func_id, DECLARE_REG(u32, fraglen, ctxt, 2); DECLARE_REG(u64, addr_mbz, ctxt, 3); DECLARE_REG(u32, npages_mbz, ctxt, 4); + u32 offset, nr_ranges, checked_offset, em_mem_access_off; struct ffa_mem_region_attributes *ep_mem_access; struct ffa_composite_mem_region *reg; struct ffa_mem_region *buf; - u32 offset, nr_ranges, checked_offset; int ret = 0; + size_t mem_region_len = FFA_MEM_REGION_SZ(hyp_ffa_version); if (addr_mbz || npages_mbz || fraglen > len || fraglen > KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE) { @@ -488,8 +489,7 @@ static void __do_ffa_mem_xfer(const u64 func_id, goto out; } - if (fraglen < sizeof(struct ffa_mem_region) + - sizeof(struct ffa_mem_region_attributes)) { + if (fraglen < mem_region_len + ffa_emad_size_get(hyp_ffa_version)) { ret = FFA_RET_INVALID_PARAMETERS; goto out; } @@ -508,8 +508,13 @@ static void __do_ffa_mem_xfer(const u64 func_id, buf = hyp_buffers.tx; memcpy(buf, host_buffers.tx, fraglen); - ep_mem_access = (void *)buf + - ffa_mem_desc_offset(buf, 0, hyp_ffa_version); + em_mem_access_off = ffa_mem_desc_offset(buf, 0, hyp_ffa_version); + if ((u64)em_mem_access_off + ffa_emad_size_get(hyp_ffa_version) > fraglen) { + ret = FFA_RET_INVALID_PARAMETERS; + goto out_unlock; + } + + ep_mem_access = (void *)buf + em_mem_access_off; offset = ep_mem_access->composite_off; if (!offset || buf->ep_count != 1 || buf->sender_id != HOST_FFA_ID) { ret = FFA_RET_INVALID_PARAMETERS; @@ -574,9 +579,9 @@ static void do_ffa_mem_reclaim(struct arm_smccc_1_2_regs *res, DECLARE_REG(u32, handle_lo, ctxt, 1); DECLARE_REG(u32, handle_hi, ctxt, 2); DECLARE_REG(u32, flags, ctxt, 3); + u32 offset, len, fraglen, fragoff, em_mem_access_off; struct ffa_mem_region_attributes *ep_mem_access; struct ffa_composite_mem_region *reg; - u32 offset, len, fraglen, fragoff; struct ffa_mem_region *buf; int ret = 0; u64 handle; @@ -599,8 +604,14 @@ static void do_ffa_mem_reclaim(struct arm_smccc_1_2_regs *res, len = res->a1; fraglen = res->a2; - ep_mem_access = (void *)buf + - ffa_mem_desc_offset(buf, 0, hyp_ffa_version); + em_mem_access_off = ffa_mem_desc_offset(buf, 0, hyp_ffa_version); + if ((u64)em_mem_access_off + ffa_emad_size_get(hyp_ffa_version) > fraglen) { + ret = FFA_RET_INVALID_PARAMETERS; + ffa_rx_release(res); + goto out_unlock; + } + + ep_mem_access = (void *)buf + em_mem_access_off; offset = ep_mem_access->composite_off; /* * We can trust the SPMD to get this right, but let's at least diff --git a/include/linux/arm_ffa.h b/include/linux/arm_ffa.h index 81e603839c4a..3c91d4c4153c 100644 --- a/include/linux/arm_ffa.h +++ b/include/linux/arm_ffa.h @@ -421,6 +421,13 @@ struct ffa_mem_region { #define FFA_EMAD_HAS_IMPDEF_FIELD(version) ((version) >= FFA_VERSION_1_2) #define FFA_MEM_REGION_HAS_EP_MEM_OFFSET(version) ((version) > FFA_VERSION_1_0) +/* The layout changed from FFA_VERSION_1_0 and the region includes an + * ep_mem_offset. + */ +#define FFA_MEM_REGION_SZ(version) (!FFA_MEM_REGION_HAS_EP_MEM_OFFSET((version)) ?\ + offsetof(struct ffa_mem_region, ep_mem_offset) :\ + sizeof(struct ffa_mem_region)) + static inline u32 ffa_emad_size_get(u32 ffa_version) { u32 sz; From 6a7a181f6921db3d9aed1bba7e15547fefd7eedc Mon Sep 17 00:00:00 2001 From: Mostafa Saleh Date: Thu, 2 Jul 2026 10:38:42 +0000 Subject: [PATCH 167/479] KVM: arm64: Ensure FFA ranges are page aligned Harden the check for the constituent memory region page alignment to prevent over-sharing when the negotiated FFA_PAGE_SIZE size is smaller than the system PAGE_SIZE. At the moment we only check that the size of the range is page aligned, and truncate the address to the page boundary which can annotate more memory than needed as being used by the FF-A. Fixes: 436090001776 ("KVM: arm64: Handle FFA_MEM_SHARE calls from the host") Signed-off-by: Mostafa Saleh Reviewed-by: Vincent Donnefort Signed-off-by: Sebastian Ene Link: https://patch.msgid.link/20260702103848.1647249-6-sebastianene@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/nvhe/ffa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/ffa.c b/arch/arm64/kvm/hyp/nvhe/ffa.c index 2e7ab7e3319d..9c96e72e522e 100644 --- a/arch/arm64/kvm/hyp/nvhe/ffa.c +++ b/arch/arm64/kvm/hyp/nvhe/ffa.c @@ -352,7 +352,7 @@ static u32 __ffa_host_share_ranges(struct ffa_mem_region_addr_range *ranges, u64 sz = (u64)range->pg_cnt * FFA_PAGE_SIZE; u64 pfn = hyp_phys_to_pfn(range->address); - if (!PAGE_ALIGNED(sz)) + if (!PAGE_ALIGNED(sz | range->address)) break; if (__pkvm_host_share_ffa(pfn, sz / PAGE_SIZE)) @@ -372,7 +372,7 @@ static u32 __ffa_host_unshare_ranges(struct ffa_mem_region_addr_range *ranges, u64 sz = (u64)range->pg_cnt * FFA_PAGE_SIZE; u64 pfn = hyp_phys_to_pfn(range->address); - if (!PAGE_ALIGNED(sz)) + if (!PAGE_ALIGNED(sz | range->address)) break; if (__pkvm_host_unshare_ffa(pfn, sz / PAGE_SIZE)) From 2bd3c6c702f3a9e2bcb3b536b0fbbaa645005d71 Mon Sep 17 00:00:00 2001 From: Sebastian Ene Date: Thu, 2 Jul 2026 10:38:43 +0000 Subject: [PATCH 168/479] KVM: arm64: Zero out the stack initialized data in the FFA handler Don't leak hypervisor stack data when using the FFA_VERSION call. When the compiler doesn't support -ftrivial-auto-var-init=zero option we need to zero out the stack initialized variable before returning data to the host caller. Closes: https://lore.kernel.org/all/20260616160016.C62C81F000E9@smtp.kernel.org/ Reported-by: Sashiko AI Fixes: c9c012625e12 ("KVM: arm64: Trap FFA_VERSION host call in pKVM") Reviewed-by: Vincent Donnefort Link: https://lore.kernel.org/all/20260616160016.C62C81F000E9@smtp.kernel.org/ Signed-off-by: Sebastian Ene Link: https://patch.msgid.link/20260702103848.1647249-7-sebastianene@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/nvhe/ffa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kvm/hyp/nvhe/ffa.c b/arch/arm64/kvm/hyp/nvhe/ffa.c index 9c96e72e522e..a327c2bbb6b6 100644 --- a/arch/arm64/kvm/hyp/nvhe/ffa.c +++ b/arch/arm64/kvm/hyp/nvhe/ffa.c @@ -880,7 +880,7 @@ static void do_ffa_part_get(struct arm_smccc_1_2_regs *res, bool kvm_host_ffa_handler(struct kvm_cpu_context *host_ctxt, u32 func_id) { - struct arm_smccc_1_2_regs res; + struct arm_smccc_1_2_regs res = {0}; /* * There's no way we can tell what a non-standard SMC call might From cb8afea4655ff004fa7feee825d5c79783525383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?HE=20WEI=20=28=E3=82=AE=E3=82=AB=E3=82=AF=29?= Date: Tue, 7 Jul 2026 18:48:28 +0900 Subject: [PATCH 169/479] wifi: cfg80211: bound element ID read when checking non-inheritance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cfg80211_is_element_inherited() reads the first data octet of the candidate element (id = elem->data[0]) to look it up in an extension non-inheritance list. It does so after testing elem->id, but without verifying that the element actually has a data octet. A zero-length extension element (WLAN_EID_EXTENSION with length 0) therefore makes it read one octet past the end of the element. _ieee802_11_parse_elems_full() runs this check for every element of a frame once a non-inheritance context exists -- e.g. while parsing a per-STA profile of a Multi-Link element in a (re)association response, or a non-transmitted BSS profile -- so a crafted frame from an AP can trigger a one-octet slab-out-of-bounds read during element parsing: BUG: KASAN: slab-out-of-bounds in cfg80211_is_element_inherited Read of size 1 ... in net/wireless/scan.c Return early (treat the element as inherited) when an extension element carries no data, mirroring the existing handling of empty ID lists. The bug was found by fuzzing ieee802_11_parse_elems_full() under KASAN. Fixes: f7dacfb11475 ("cfg80211: support non-inheritance element") Signed-off-by: HE WEI (ギカク) Link: https://patch.msgid.link/20260707094828.16465-1-skyexpoc@gmail.com Signed-off-by: Johannes Berg --- net/wireless/scan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 5c97b5bd5d69..071083cc3367 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -205,7 +205,7 @@ bool cfg80211_is_element_inherited(const struct element *elem, return true; if (elem->id == WLAN_EID_EXTENSION) { - if (!ext_id_len) + if (!ext_id_len || !elem->datalen) return true; loop_len = ext_id_len; list = &non_inherit_elem->data[3 + id_len]; From 4d6941c0a8fd44bcc9693a1c8201a1d26dd21bc8 Mon Sep 17 00:00:00 2001 From: Andreas Ziegler Date: Wed, 24 Jun 2026 05:33:41 +0200 Subject: [PATCH 170/479] rtla: Fix missing unistd include MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compiling RTLA 7.1.x with GCC 16 and uClibc as standard library fails with these errors: src/common.c: In function ‘set_signals’: src/common.c:40:17: error: implicit declaration of function ‘alarm’ [-Wimplicit-function-declaration] 40 | alarm(params->duration); | ^~~~~ src/common.c: In function ‘common_apply_config’: src/common.c:187:44: error: implicit declaration of function ‘getpid’; did you mean ‘getpt’? [-Wimplicit-function-declaration] 187 | retval = sched_setaffinity(getpid(), sizeof(params->hk_cpu_set), | ^~~~~~ | getpt In file included from src/common.c:9: src/common.c: In function ‘run_tool’: src/common.c:262:19: error: implicit declaration of function ‘sysconf’; did you mean ‘sscanf’? [-Wimplicit-function-declaration] 262 | nr_cpus = get_nprocs_conf(); | ^~~~~~~~~~~~~~~ src/common.c:262:19: error: ‘_SC_NPROCESSORS_CONF’ undeclared (first use in this function) 262 | nr_cpus = get_nprocs_conf(); | ^~~~~~~~~~~~~~~ src/common.c:262:19: note: each undeclared identifier is reported only once for each function it appears in src/common.c:370:17: error: implicit declaration of function ‘sleep’ [-Wimplicit-function-declaration] 370 | sleep(1); | ^~~~~ Restore the missing unistd.h include. Fixes: 115b06a00875 ("tools/rtla: Consolidate nr_cpus usage across all tools") Signed-off-by: Andreas Ziegler Link: https://lore.kernel.org/r/20260624033342.129970-1-br025@umbiko.net [ capital letter in patch subject, remove EOL after Fixes ] Signed-off-by: Tomas Glozar --- tools/tracing/rtla/src/common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c index d0a8a6edbf0c..8c7f5e75b2ec 100644 --- a/tools/tracing/rtla/src/common.c +++ b/tools/tracing/rtla/src/common.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "common.h" From 3383ffb7ef937317361713ffcc21921a7848511a Mon Sep 17 00:00:00 2001 From: Mostafa Saleh Date: Thu, 2 Jul 2026 10:38:38 +0000 Subject: [PATCH 171/479] firmware: arm_ffa: Fix out-of-bound writes in ffa_setup_and_transmit() Sashiko (locally) reports multiple out-of-bound issues in ffa_setup_and_transmit: 1) Writing ep_mem_access->reserved can write out of bounds for FFA versions < 1.2 as ffa_emad_size_get() returns 16 bytes in that case while reserved has an offset of 24. Instead of zeroing fields, memset the struct to zero first based on the FFA version. 2) Make sure there is enough size to write constituents. While at it, convert the only sizeof() in the driver that uses a type instead of variable. Reviewed-by: Sudeep Holla Fixes: 111a833dc5cb ("firmware: arm_ffa: Set reserved/MBZ fields to zero in the memory descriptors") Signed-off-by: Mostafa Saleh Signed-off-by: Sebastian Ene Link: https://patch.msgid.link/20260702103848.1647249-2-sebastianene@google.com Signed-off-by: Marc Zyngier --- drivers/firmware/arm_ffa/driver.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index eb2782848283..b700b2e93e72 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -697,11 +697,10 @@ ffa_setup_and_transmit(u32 func_id, void *buffer, u32 max_fragsize, for (idx = 0; idx < args->nattrs; idx++) { ep_mem_access = buffer + ffa_mem_desc_offset(buffer, idx, drv_info->version); + memset(ep_mem_access, 0, ffa_emad_size_get(drv_info->version)); ep_mem_access->receiver = args->attrs[idx].receiver; ep_mem_access->attrs = args->attrs[idx].attrs; ep_mem_access->composite_off = composite_offset; - ep_mem_access->flag = 0; - ep_mem_access->reserved = 0; ffa_emad_impdef_value_init(drv_info->version, ep_mem_access->impdef_val, args->attrs[idx].impdef_val); @@ -741,7 +740,7 @@ ffa_setup_and_transmit(u32 func_id, void *buffer, u32 max_fragsize, constituents = buffer; } - if ((void *)constituents - buffer > max_fragsize) { + if ((void *)constituents + sizeof(*constituents) - buffer > max_fragsize) { pr_err("Memory Region Fragment > Tx Buffer size\n"); return -EFAULT; } @@ -750,7 +749,7 @@ ffa_setup_and_transmit(u32 func_id, void *buffer, u32 max_fragsize, constituents->pg_cnt = args->sg->length / FFA_PAGE_SIZE; constituents->reserved = 0; constituents++; - frag_len += sizeof(struct ffa_mem_region_addr_range); + frag_len += sizeof(*constituents); } while ((args->sg = sg_next(args->sg))); return ffa_transmit_fragment(func_id, addr, buf_sz, frag_len, From b4d961351aa84fdf0148783fb1f3a1391b8a0adb Mon Sep 17 00:00:00 2001 From: Sebastian Ene Date: Thu, 2 Jul 2026 10:38:39 +0000 Subject: [PATCH 172/479] firmware: arm_ffa: Fix Endpoint Memory Access Descriptor offset calculation Use the descriptor's `ep_mem_offset` to calculate the start of the endpoint memory access array and to comply with the FF-A spec instead of defaulting to `sizeof(struct ffa_mem_region)`. This requires moving `ffa_mem_region_additional_setup()` earlier in the setup flow. Also, add sanity checks to ensure the calculated descriptor offsets do not exceed `max_fragsize`. Fixes: 113580530ee7 ("firmware: arm_ffa: Update memory descriptor to support v1.1 format") Reviewed-by: Sudeep Holla Signed-off-by: Mostafa Saleh Signed-off-by: Sebastian Ene Link: https://patch.msgid.link/20260702103848.1647249-3-sebastianene@google.com Signed-off-by: Marc Zyngier --- drivers/firmware/arm_ffa/driver.c | 20 +++++++++++++++----- include/linux/arm_ffa.h | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index b700b2e93e72..0ad323decada 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -685,19 +685,30 @@ ffa_setup_and_transmit(u32 func_id, void *buffer, u32 max_fragsize, struct ffa_composite_mem_region *composite; struct ffa_mem_region_addr_range *constituents; struct ffa_mem_region_attributes *ep_mem_access; - u32 idx, frag_len, length, buf_sz = 0, num_entries = sg_nents(args->sg); + u32 idx, frag_len, length, buf_sz = 0, num_entries = sg_nents(args->sg), ep_offset; + u32 emad_end, emad_size = ffa_emad_size_get(drv_info->version); mem_region->tag = args->tag; mem_region->flags = args->flags; mem_region->sender_id = drv_info->vm_id; mem_region->attributes = ffa_memory_attributes_get(func_id); + + ffa_mem_region_additional_setup(drv_info->version, mem_region); composite_offset = ffa_mem_desc_offset(buffer, args->nattrs, drv_info->version); + if (composite_offset + sizeof(*composite) > max_fragsize) + return -ENXIO; for (idx = 0; idx < args->nattrs; idx++) { - ep_mem_access = buffer + - ffa_mem_desc_offset(buffer, idx, drv_info->version); - memset(ep_mem_access, 0, ffa_emad_size_get(drv_info->version)); + ep_offset = ffa_mem_desc_offset(buffer, idx, drv_info->version); + if (check_add_overflow(ep_offset, emad_size, &emad_end)) + return -ENXIO; + + if (emad_end > max_fragsize) + return -ENXIO; + + ep_mem_access = buffer + ep_offset; + memset(ep_mem_access, 0, emad_size); ep_mem_access->receiver = args->attrs[idx].receiver; ep_mem_access->attrs = args->attrs[idx].attrs; ep_mem_access->composite_off = composite_offset; @@ -707,7 +718,6 @@ ffa_setup_and_transmit(u32 func_id, void *buffer, u32 max_fragsize, } mem_region->handle = 0; mem_region->ep_count = args->nattrs; - ffa_mem_region_additional_setup(drv_info->version, mem_region); composite = buffer + composite_offset; composite->total_pg_cnt = ffa_get_num_pages_sg(args->sg); diff --git a/include/linux/arm_ffa.h b/include/linux/arm_ffa.h index 3c91d4c4153c..c6a9ffd564f7 100644 --- a/include/linux/arm_ffa.h +++ b/include/linux/arm_ffa.h @@ -452,7 +452,7 @@ ffa_mem_desc_offset(struct ffa_mem_region *buf, int count, u32 ffa_version) if (!FFA_MEM_REGION_HAS_EP_MEM_OFFSET(ffa_version)) offset += offsetof(struct ffa_mem_region, ep_mem_offset); else - offset += sizeof(struct ffa_mem_region); + offset += buf->ep_mem_offset; return offset; } From 335f7860846732477a9ce24f0db04dd253ba268c Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Mon, 6 Jul 2026 17:56:14 -0700 Subject: [PATCH 173/479] ALSA: hda/realtek: Fix headphone output on ASUS ROG Ally X The ASUS ROG Ally X (RC72LA, PCI SSID 0x1043:0x1eb3) with an ALC294 codec produces no sound from the headphone jack, and unplugging the headphones can leave the speakers muted too. The device uses ALC287_FIXUP_TAS2781_I2C, which chains into the ThinkPad fixup chain. alc285_fixup_thinkpad_x1_gen7() makes the headphone (pin 0x21) and speaker (pin 0x17) share DAC 0x03. That is correct for a ThinkPad, but here the shared DAC is exposed as a "Line Out" control that defaults to zero, so the headphones stay silent when plugged in. Add a dedicated fixup chain for the Ally X that sets up the TAS2781 amplifier and the headset jack without the ThinkPad DAC sharing, and routes the speaker to DAC 0x02 so the headphone and speaker get independent DACs. Also, fixup the wrong product code (it's RC72LA, not RCLA72). Signed-off-by: Matthew Schwartz Link: https://patch.msgid.link/20260707005615.52889-1-matthew.schwartz@linux.dev Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b6b502450a95..8d409ccacad4 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4117,6 +4117,8 @@ enum { ALC236_FIXUP_DELL_DUAL_CODECS, ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI, ALC287_FIXUP_TAS2781_I2C, + ALC287_FIXUP_ASUS_ALLY_X, + ALC287_FIXUP_ASUS_ALLY_X_I2C, ALC295_FIXUP_DELL_TAS2781_I2C, ALC245_FIXUP_TAS2781_SPI_2, ALC287_FIXUP_TXNW2781_I2C, @@ -6476,6 +6478,18 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK, }, + [ALC287_FIXUP_ASUS_ALLY_X] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC287_FIXUP_ASUS_ALLY_X_I2C, + }, + [ALC287_FIXUP_ASUS_ALLY_X_I2C] = { + .type = HDA_FIXUP_FUNC, + .v.func = tas2781_fixup_tias_i2c, + .chained = true, + .chain_id = ALC225_FIXUP_HEADSET_JACK, + }, [ALC245_FIXUP_TAS2781_SPI_2] = { .type = HDA_FIXUP_FUNC, .v.func = tas2781_fixup_spi, @@ -7461,7 +7475,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1e83, "ASUS GA605W", ALC285_FIXUP_ASUS_GU605_SPI_SPEAKER2_TO_DAC1), SND_PCI_QUIRK(0x1043, 0x1e8e, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA401), SND_PCI_QUIRK(0x1043, 0x1e93, "ASUS ExpertBook B9403CVAR", ALC294_FIXUP_ASUS_HPE), - SND_PCI_QUIRK(0x1043, 0x1eb3, "ASUS Ally RCLA72", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x1043, 0x1eb3, "ASUS Ally RC72LA", ALC287_FIXUP_ASUS_ALLY_X), SND_PCI_QUIRK(0x1043, 0x1ed3, "ASUS HN7306W", ALC287_FIXUP_CS35L41_I2C_2), HDA_CODEC_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1), SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC287_FIXUP_CS35L41_I2C_2), From 883d0cae6d5f840629c5bcacf029c6aa9a9106f4 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Mon, 6 Jul 2026 17:56:15 -0700 Subject: [PATCH 174/479] ALSA: hda/realtek: Fix headset mic on ASUS ROG Ally X The ASUS ROG Ally X (RC72LA, PCI SSID 0x1043:0x1eb3) leaves the combo jack microphone pin (0x19) unconfigured, so a headset microphone is neither detected nor captured. Only the internal microphone is available. Add a pin override that configures node 0x19 as an external headset mic, the same way the original ROG Ally does. The headphone output fixup chain is preserved, so speaker and headphone routing are unchanged. Signed-off-by: Matthew Schwartz Link: https://patch.msgid.link/20260707005615.52889-2-matthew.schwartz@linux.dev Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 8d409ccacad4..984868e3d8bb 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4118,6 +4118,7 @@ enum { ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI, ALC287_FIXUP_TAS2781_I2C, ALC287_FIXUP_ASUS_ALLY_X, + ALC287_FIXUP_ASUS_ALLY_X_SPEAKER, ALC287_FIXUP_ASUS_ALLY_X_I2C, ALC295_FIXUP_DELL_TAS2781_I2C, ALC245_FIXUP_TAS2781_SPI_2, @@ -6479,6 +6480,15 @@ static const struct hda_fixup alc269_fixups[] = { .chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK, }, [ALC287_FIXUP_ASUS_ALLY_X] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a11050 }, /* headset mic */ + { } + }, + .chained = true, + .chain_id = ALC287_FIXUP_ASUS_ALLY_X_SPEAKER, + }, + [ALC287_FIXUP_ASUS_ALLY_X_SPEAKER] = { .type = HDA_FIXUP_FUNC, .v.func = alc285_fixup_speaker2_to_dac1, .chained = true, From dba0bfded433d412506eb674d6af77cdca82e259 Mon Sep 17 00:00:00 2001 From: Longlong Xia Date: Mon, 6 Jul 2026 12:26:39 +0900 Subject: [PATCH 175/479] ALSA: firewire: Drop redundant mod_devicetable.h includes The ALSA FireWire driver headers include , which already provides struct ieee1394_device_id via . Drop the now-redundant direct includes from the per-device headers. Signed-off-by: Longlong Xia Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260706032639.273929-1-xialonglong2025@163.com --- sound/firewire/bebob/bebob.h | 1 - sound/firewire/dice/dice.h | 1 - sound/firewire/digi00x/digi00x.h | 1 - sound/firewire/fireface/ff.h | 1 - sound/firewire/fireworks/fireworks.h | 1 - sound/firewire/motu/motu.h | 1 - sound/firewire/oxfw/oxfw.h | 1 - sound/firewire/tascam/tascam.h | 1 - 8 files changed, 8 deletions(-) diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 4d73ecb30d79..41e019b9e24e 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index 7744ea6a0791..5abae26d2bba 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index 82b647d383c5..60d17a6fddd1 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 7e42f5778a8a..c9c35c67db27 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index c8d5879efe28..a8aadf754e72 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index c66be0a89ccf..7333a76b50aa 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 39ea9a6dde33..aeb83e1595b7 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index d07ffcb27be6..9c42c9b48f21 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include From f67be28fdf8b5d31ac1cc1152bb17250f9f8f513 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Tue, 7 Jul 2026 21:24:19 +0800 Subject: [PATCH 176/479] ALSA: hda: Fix cached processing coefficient verbs 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 --- sound/hda/core/regmap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/hda/core/regmap.c b/sound/hda/core/regmap.c index e7b866fc52c1..d6eb17aa9e08 100644 --- a/sound/hda/core/regmap.c +++ b/sound/hda/core/regmap.c @@ -214,7 +214,7 @@ static int hda_reg_read_coef(struct hdac_device *codec, unsigned int reg, 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 hdac_device *codec, unsigned int reg, 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); } From 8d187d4b33c262c0f3e44842553521151d8629e8 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 7 Jul 2026 17:29:35 +0100 Subject: [PATCH 177/479] KVM: arm64: Fix propagation of TLBI level in kvm_pgtable_stage2_relax_perms() Assigning the invalidation level (an s8 value) with TLBI_TTL_UNKNOWN (a 32bit signed value) is not ideal, to say the least. Instead of this, only pass TLBI_TTL_UNKNOWN to __kvm_tlb_flush_vmid_ipa_nsh() when we know for sure that we don't have a provided level. Fixes: 100baf0184896 ("KVM: arm64: Ensure level is always initialized when relaxing perms") Reported-by: Mark Brown Reviewed-by: Oliver Upton Link: https://lore.kernel.org/r/akztC7H2IsEKaq4i@sirena.org.uk Link: https://patch.msgid.link/20260707162935.1900874-1-maz@kernel.org Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/pgtable.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c index 8754c99c22f2..70dceb20dfad 100644 --- a/arch/arm64/kvm/hyp/pgtable.c +++ b/arch/arm64/kvm/hyp/pgtable.c @@ -1356,7 +1356,7 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr, enum kvm_pgtable_prot prot, enum kvm_pgtable_walk_flags flags) { kvm_pte_t xn = 0, set = 0, clr = 0; - s8 level = TLBI_TTL_UNKNOWN; + s8 level; int ret; if (prot & KVM_PTE_LEAF_ATTR_HI_SW) @@ -1379,7 +1379,8 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr, ret = stage2_update_leaf_attrs(pgt, addr, 1, set, clr, NULL, &level, flags); if (!ret || ret == -EAGAIN) - kvm_call_hyp(__kvm_tlb_flush_vmid_ipa_nsh, pgt->mmu, addr, level); + kvm_call_hyp(__kvm_tlb_flush_vmid_ipa_nsh, pgt->mmu, addr, + (ret == -EAGAIN) ? TLBI_TTL_UNKNOWN : level); return ret; } From 030db7005efe6a0705ddf07fced494c364a1c915 Mon Sep 17 00:00:00 2001 From: Liang Luo Date: Tue, 7 Jul 2026 17:45:38 +0800 Subject: [PATCH 178/479] sched_ext: Documentation: Fix ops table header reference The "Where to Look" and "ABI Instability" sections state that the ops table is defined in include/linux/sched/ext.h. However, struct sched_ext_ops is actually defined in kernel/sched/ext/internal.h, along with the SCX_OPS_* flags; include/linux/sched/ext.h holds the core data structures (struct sched_ext_entity, struct scx_dispatch_q, ...) and the DSQ constants. Point the ops table references to the correct header. Signed-off-by: Liang Luo Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- Documentation/scheduler/sched-ext.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Documentation/scheduler/sched-ext.rst b/Documentation/scheduler/sched-ext.rst index 4b1ffd03f516..2771ea4cc14a 100644 --- a/Documentation/scheduler/sched-ext.rst +++ b/Documentation/scheduler/sched-ext.rst @@ -493,8 +493,9 @@ a freshly woken up task gets on a CPU. Where to Look ============= -* ``include/linux/sched/ext.h`` defines the core data structures, ops table - and constants. +* ``include/linux/sched/ext.h`` defines the core data structures and + constants, while the ops table (``struct sched_ext_ops``) is defined in + ``kernel/sched/ext/internal.h``. * ``kernel/sched/ext/ext.c`` contains sched_ext core implementation and helpers. The functions prefixed with ``scx_bpf_`` can be called from the BPF @@ -555,7 +556,8 @@ ABI Instability =============== The APIs provided by sched_ext to BPF schedulers programs have no stability -guarantees. This includes the ops table callbacks and constants defined in +guarantees. This includes the ops table callbacks defined in +``kernel/sched/ext/internal.h`` and the constants defined in ``include/linux/sched/ext.h``, as well as the ``scx_bpf_`` kfuncs defined in ``kernel/sched/ext/ext.c`` and ``kernel/sched/ext/idle.c``. From 5aa31cb1dfb08f19408c9c33da71f46525f6cba9 Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Tue, 7 Jul 2026 18:21:46 +0800 Subject: [PATCH 179/479] Docs/admin-guide/cgroup-v2: drop stale misc interface file count The Miscellaneous controller documentation states it "provides 3 interface files", but misc_cg_files[] actually registers six (max, current, peak, capacity, events, events.local). Drop the stale count and let the file list that follows speak for itself. Signed-off-by: Guopeng Zhang Signed-off-by: Tejun Heo --- Documentation/admin-guide/cgroup-v2.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst index ce6741f78f4f..7ce60fefc7ad 100644 --- a/Documentation/admin-guide/cgroup-v2.rst +++ b/Documentation/admin-guide/cgroup-v2.rst @@ -2934,7 +2934,8 @@ include/linux/misc_cgroup.h. Misc Interface Files ~~~~~~~~~~~~~~~~~~~~ -Miscellaneous controller provides 3 interface files. If two misc resources (res_a and res_b) are registered then: +Miscellaneous controller provides the following interface files. If two misc +resources (res_a and res_b) are registered then: misc.capacity A read-only flat-keyed file shown only in the root cgroup. It shows From 451a7467570d9578b5f7fba9aa6e0ed1ac1fcfec Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Tue, 7 Jul 2026 18:21:47 +0800 Subject: [PATCH 180/479] Docs/admin-guide/cgroup-v1: document rdma.peak, rdma.events and rdma.events.local The v1 RDMA controller documentation only describes rdma.max and rdma.current, but the controller exposes three more files -- rdma.peak, rdma.events and rdma.events.local -- which are already documented for v2. Mirror the v2 wording so the v1 documentation matches the files actually visible on a v1 mount. Co-developed-by: Tao Cui Signed-off-by: Tao Cui Signed-off-by: Guopeng Zhang Signed-off-by: Tejun Heo --- Documentation/admin-guide/cgroup-v1/rdma.rst | 66 ++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/Documentation/admin-guide/cgroup-v1/rdma.rst b/Documentation/admin-guide/cgroup-v1/rdma.rst index e69369b7252e..abddf34d2667 100644 --- a/Documentation/admin-guide/cgroup-v1/rdma.rst +++ b/Documentation/admin-guide/cgroup-v1/rdma.rst @@ -9,6 +9,7 @@ RDMA Controller 1-2. Why RDMA controller needed? 1-3. How is RDMA controller implemented? 2. Usage Examples + 3. RDMA Interface Files 1. Overview =========== @@ -115,3 +116,68 @@ Following resources can be accounted by rdma controller. (d) Delete resource limit:: echo mlx4_0 hca_handle=max hca_object=max > /sys/fs/cgroup/rdma/1/rdma.max + +3. RDMA Interface Files +======================== + +The following interface files are available in each non-root RDMA cgroup. + + rdma.max + A read-write file which describes the configured resource limit + for an RDMA/IB device. See the Usage Examples above. + + rdma.current + A read-only file which describes the current resource usage. + + rdma.peak + A read-only nested-keyed file which shows the historical high + watermark of resource usage per device since the cgroup was created. + + An example for mlx4 and ocrdma device follows:: + + mlx4_0 hca_handle=1 hca_object=20 + ocrdma1 hca_handle=0 hca_object=23 + + rdma.events + A read-only nested-keyed file which exists on non-root cgroups + and contains the following keys: + + max + The number of times a process in this cgroup or its + descendants attempted an RDMA resource allocation that + was rejected because a rdma.max limit in the subtree + was reached. This is a hierarchical counter propagated + upward to all ancestor cgroups. A value change in this + file generates a file modified event. + + alloc_fail + The number of RDMA resource allocation attempts that + originated in this cgroup or its descendants and failed + due to a rdma.max limit being reached. This is a + hierarchical counter propagated upward. + + An example for mlx4 device follows:: + + mlx4_0 hca_handle.max=5 hca_handle.alloc_fail=3 hca_object.max=0 hca_object.alloc_fail=0 + + rdma.events.local + Similar to rdma.events but the fields are local to the cgroup, + i.e. not hierarchical. The file modified event generated on this + file reflects only the local events. + + The following nested keys are defined. + + max + The number of times a process in this cgroup or its + descendants attempted an RDMA resource allocation that + was rejected because this cgroup's own rdma.max limit + was reached. + + alloc_fail + The number of RDMA resource allocation attempts + originating from this cgroup that failed due to this + cgroup's or an ancestor's rdma.max limit. + + An example for mlx4 device follows:: + + mlx4_0 hca_handle.max=5 hca_handle.alloc_fail=0 hca_object.max=0 hca_object.alloc_fail=0 From 97fef602584458b100d383afed9d176c0bc689ab Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Tue, 7 Jul 2026 18:21:48 +0800 Subject: [PATCH 181/479] Docs/admin-guide/cgroup-v2: note blkcg_debug_stats gates io.latency stats The io.stat section says that enabling the io.latency controller exposes the depth, avg_lat and win stats in addition to the normal ones. However, these io.latency-specific stats are debug stats and are only emitted when the blkcg_debug_stats module parameter is enabled, which is disabled by default. Make this explicit so users do not expect these fields to appear in io.stat by default, and qualify the usage text that suggests using avg_lat to pick an io.latency target. Signed-off-by: Guopeng Zhang Signed-off-by: Tejun Heo --- Documentation/admin-guide/cgroup-v2.rst | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst index 7ce60fefc7ad..14b8c571c0d1 100644 --- a/Documentation/admin-guide/cgroup-v2.rst +++ b/Documentation/admin-guide/cgroup-v2.rst @@ -2257,10 +2257,11 @@ groups D and F will influence each other. Group G will influence nobody:: So the ideal way to configure this is to set io.latency in groups A, B, and C. Generally you do not want to set a value lower than the latency your device supports. Experiment to find the value that works best for your workload. -Start at higher than the expected latency for your device and watch the -avg_lat value in io.stat for your workload group to get an idea of the -latency you see during normal operation. Use the avg_lat value as a basis for -your real setting, setting at 10-15% higher than the value in io.stat. +Start at higher than the expected latency for your device and, with +blkcg_debug_stats enabled, watch the avg_lat value in io.stat for your +workload group to get an idea of the latency you see during normal operation. +Use the avg_lat value as a basis for your real setting, setting at 10-15% +higher than the value in io.stat. How IO Latency Throttling Works ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2298,7 +2299,9 @@ IO Latency Interface Files io.stat If the controller is enabled you will see extra stats in io.stat in - addition to the normal ones. + addition to the normal ones. These debug stats are only emitted when + the blkcg_debug_stats module parameter is enabled (it is disabled by + default). depth This is the current queue depth for the group. From d3386e17393bec1341cfeedb9d08d6846ccd6fb2 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Wed, 8 Jul 2026 11:18:45 +0800 Subject: [PATCH 182/479] erofs: relax sanity check for tail pclusters due to ztailpacking If the tail data can be inlined into the inode meta block, it should be converted into a regular tail pcluster. In principle, it should be converted into an uncompressed pcluster if there is not enough gain to use compression (map->m_llen < map->m_plen); but since there are various shipped images, relax the condition for ztailpacking tail pcluster fallback instead of reporting corruption incorrectly. Reported-and-tested-by: Yifan Zhao Reported-by: Alberto Salvia Novella Closes: https://github.com/erofs/erofs-utils/issues/51 Fixes: a5242d37c83a ("erofs: error out obviously illegal extents in advance") Signed-off-by: Gao Xiang --- fs/erofs/zmap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/erofs/zmap.c b/fs/erofs/zmap.c index bab521613552..5811556a7b71 100644 --- a/fs/erofs/zmap.c +++ b/fs/erofs/zmap.c @@ -732,7 +732,8 @@ static int z_erofs_map_sanity_check(struct inode *inode, map->m_algorithmformat, EROFS_I(inode)->nid); return -EFSCORRUPTED; } - if (EROFS_MAP_FULL(map->m_flags) && map->m_llen < map->m_plen) { + if (EROFS_MAP_FULL(map->m_flags) && map->m_llen < map->m_plen && + map->m_la + map->m_llen < inode->i_size) { erofs_err(inode->i_sb, "too much compressed data @ la %llu of nid %llu", map->m_la, EROFS_I(inode)->nid); return -EFSCORRUPTED; From 95e19a4d8dd3072d51209081a29467d887c84158 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Tue, 7 Jul 2026 21:39:39 +0800 Subject: [PATCH 183/479] erofs: get rid of erofs_is_ishare_inode() helper Just open-code it for simplicity since FS_ONDEMAND no longer exists. Signed-off-by: Gao Xiang --- fs/erofs/ishare.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c index 0868c12fc15b..a1a20a5ba548 100644 --- a/fs/erofs/ishare.c +++ b/fs/erofs/ishare.c @@ -12,12 +12,6 @@ static struct vfsmount *erofs_ishare_mnt; -static inline bool erofs_is_ishare_inode(struct inode *inode) -{ - /* assumed FS_ONDEMAND is excluded with FS_PAGE_CACHE_SHARE feature */ - return inode->i_sb->s_type == &erofs_anon_fs_type; -} - static int erofs_ishare_iget5_eq(struct inode *inode, void *data) { struct erofs_inode_fingerprint *fp1 = &EROFS_I(inode)->fingerprint; @@ -179,7 +173,7 @@ struct inode *erofs_real_inode(struct inode *inode, bool *need_iput) struct inode *realinode; *need_iput = false; - if (!erofs_is_ishare_inode(inode)) + if (inode->i_sb != erofs_ishare_mnt->mnt_sb) return inode; vi_share = EROFS_I(inode); From cd9993d22a577339a93dcb401574e874ea29b143 Mon Sep 17 00:00:00 2001 From: Bastian Blank Date: Sun, 5 Jul 2026 12:49:17 +0200 Subject: [PATCH 184/479] rtla: Also link in ctype.c rtla started to only link parts of the tools library. It now misses the ctype information used by all the related string operations. Just add another single file to make it build again. Signed-off-by: Bastian Blank Fixes: 48209d763c22 ("rtla: Add libsubcmd dependency") Link: https://lore.kernel.org/r/ako2S4mzIqWwYuas@steamhammer.waldi.eu.org [ remove duplicated spaces in commit message ] Signed-off-by: Tomas Glozar --- tools/tracing/rtla/Makefile | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tools/tracing/rtla/Makefile b/tools/tracing/rtla/Makefile index 60a102538988..387bc6cc18f0 100644 --- a/tools/tracing/rtla/Makefile +++ b/tools/tracing/rtla/Makefile @@ -45,6 +45,9 @@ else LIB_OUTPUT = $(CURDIR)/lib endif +LIB_CTYPE = $(LIB_OUTPUT)/ctype.o +LIB_CTYPE_SRC = $(srctree)/tools/lib/ctype.c + LIB_STRING = $(LIB_OUTPUT)/string.o LIB_STRING_SRC = $(srctree)/tools/lib/string.c @@ -117,12 +120,12 @@ tests/bpf/bpf_action_map.o: tests/bpf/bpf_action_map.c $(Q)echo "BPF skeleton support is disabled, skipping tests/bpf/bpf_action_map.o" endif -$(RTLA): $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) - $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS) +$(RTLA): $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) + $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS) -static: $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) +static: $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) $(eval LDFLAGS += -static) - $(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS) + $(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS) rtla.%: fixdep FORCE make -f $(srctree)/tools/build/Makefile.build dir=. $@ @@ -150,6 +153,9 @@ $(LIB_STR_ERROR_R): $(LIB_STR_ERROR_R_SRC) | $(LIB_OUTPUT) $(LIB_STRING): $(LIB_STRING_SRC) | $(LIB_OUTPUT) $(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $< +$(LIB_CTYPE): $(LIB_CTYPE_SRC) | $(LIB_OUTPUT) + $(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $< + libsubcmd-clean: $(call QUIET_CLEAN, libsubcmd) $(Q)$(RM) -r -- $(LIBSUBCMD_OUTPUT) From 2d184128f45afad7bfdc17d946339765555dbd38 Mon Sep 17 00:00:00 2001 From: Agustin Luzardo Date: Sun, 5 Jul 2026 15:42:27 -0300 Subject: [PATCH 185/479] ALSA: usb-audio: Add quirk for Redragon H510-PRO Wireless headset The device with USB ID 040b:0897 (Weltrend Semiconductor chipset, sold rebranded as the Redragon H510-PRO Wireless headset, reporting "XiiSound Technology Corporation" in its USB string descriptors) reports a constant value on GET_CUR for its PCM Playback Volume control while still supporting an actually tunable volume. This trips the sticky-value detection in check_sticky_volume_control(), which disables the mixer control entirely: usb 1-4: 5:0: sticky mixer values (0/100/1 => 80), disabling As a result, the device boots with playback volume effectively muted and provides no way to raise it through the normal ALSA/PipeWire mixer path. Apply QUIRK_FLAG_MIXER_GET_CUR_BROKEN so the sticky check marks the control as get_cur_broken and relies on the cached value instead of disabling the mixer control outright. Tested by backporting this quirk flag and the supporting get_cur_broken logic onto a Linux 7.1.2-zen kernel build that does not yet carry it, and confirming that after applying the flag the kernel log changes from usb 1-4: 5:0: sticky mixer values (0/100/1 => 80), disabling to usb 1-4: 5:0: broken mixer GET_CUR (0/100/1 => 80) with the control usable via the driver's cached value afterward. Signed-off-by: Agustin Luzardo Link: https://patch.msgid.link/20260705184227.113588-1-agustinluzardo09@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 1cb588691e16..2949a0d2d961 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2210,6 +2210,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x040b, 0x0897, /* Weltrend Semiconductor, sold as Redragon H510-PRO Wireless headset */ + QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x041e, 0x4080, /* Creative Live Cam VF0610 */ From 5c3f4b583c25911fad9890b463632761291f65ab Mon Sep 17 00:00:00 2001 From: Rohit Sinha Date: Tue, 7 Jul 2026 02:48:34 +0530 Subject: [PATCH 186/479] ALSA: hda/realtek - Add quirk for HP Victus 15-fa0xxx (MB 8A50) The mute LED on HP Victus 15-fa0xxx (board ID 8A50) uses ALC245 but requires a quirk to work. This patch enables the existing ALC245_FIXUP_HP_MUTE_LED_COEFBIT quirk for the device. Tested on my HP Victus 15-fa0xxx. The LED behaviour works as intended. Signed-off-by: Rohit Sinha Link: https://patch.msgid.link/20260706211834.384022-1-rohitsinha12043@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 984868e3d8bb..5d9cbf5b7b40 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7146,6 +7146,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8a36, "HP Pavilion Plus 14-eh0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8a3d, "HP Victus 15-fb0xxx (MB 8A3D)", ALC245_FIXUP_HP_MUTE_LED_V2_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8a4f, "HP Victus 15-fa0xxx (MB 8A4F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8a50, "HP Victus 15-fa0xxx (MB 8A50)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8a6e, "HP EDNA 360", ALC287_FIXUP_CS35L41_I2C_4), SND_PCI_QUIRK(0x103c, 0x8a74, "HP ProBook 440 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8a75, "HP ProBook 450 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED), From 0752734163a20c9185a12c0d0786995751ab7426 Mon Sep 17 00:00:00 2001 From: Kshamendra Kumar Mishra Date: Tue, 7 Jul 2026 22:07:44 +0530 Subject: [PATCH 187/479] ALSA: hda/realtek: Add mic mute LED quirk for HP Laptop 15-fd0xxx The HP Laptop 15-fd0xxx uses GPIO bit 0 to control the mic mute LED, but currently the quirk only enables the speaker mute LED (via coef bit). Switch to the existing ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO fixup which additionally registers a GPIO-based mic mute LED, mapping to the same verbs (SET_GPIO_DATA/SET_GPIO_DIRECTION bit 0) that the platform firmware uses. Signed-off-by: Kshamendra Kumar Mishra Link: https://patch.msgid.link/DJSOWKFBTJH8.2Z7EPWX514QXM@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 5d9cbf5b7b40..02cc3f6b378d 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7294,7 +7294,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8da8, "HP 16 Piston OmniBook X", ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX), SND_PCI_QUIRK(0x103c, 0x8dc9, "HP Laptop 15-fc0xxx", ALC236_FIXUP_HP_DMIC), SND_PCI_QUIRK(0x103c, 0x8dd4, "HP EliteStudio 8 AIO", ALC274_FIXUP_HP_AIO_BIND_DACS), - SND_PCI_QUIRK(0x103c, 0x8dd7, "HP Laptop 15-fd0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8dd7, "HP Laptop 15-fd0xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8de8, "HP Gemtree", ALC245_FIXUP_TAS2781_SPI_2), SND_PCI_QUIRK(0x103c, 0x8de9, "HP Gemtree", ALC245_FIXUP_TAS2781_SPI_2), SND_PCI_QUIRK(0x103c, 0x8dec, "HP EliteBook 640 G12", ALC236_FIXUP_HP_GPIO_LED), From 2094dab19d45c487285617b7b68913d0cc0c1211 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Thu, 2 Jul 2026 09:20:00 -0700 Subject: [PATCH 188/479] xfs: fail recovery on a committed log item with no regions If the first op of a transaction is a bare transaction header (len == sizeof(struct xfs_trans_header)), xlog_recover_add_to_trans() adds an item but no region, leaving it on r_itemq with ri_cnt == 0 and ri_buf == NULL. The header can be split across op records, so later ops may still add regions; the item is only invalid if the transaction commits with none. The runtime commit path never emits such a transaction, so this only happens on a crafted log. It came from an AI-assisted code audit of the recovery parser. xlog_recover_reorder_trans() calls ITEM_TYPE() on the item, which reads *(unsigned short *)item->ri_buf[0].iov_base and faults on the NULL ri_buf. Reject it there, before the commit handlers that also read ri_buf[0]. KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: 0010:xlog_recover_reorder_trans (fs/xfs/xfs_log_recover.c:1836) xlog_recover_commit_trans (fs/xfs/xfs_log_recover.c:2043) xlog_recover_process_data (fs/xfs/xfs_log_recover.c:2501) xlog_do_recovery_pass (fs/xfs/xfs_log_recover.c:3244) xlog_recover (fs/xfs/xfs_log_recover.c:3493) xfs_log_mount (fs/xfs/xfs_log.c:618) xfs_mountfs (fs/xfs/xfs_mount.c:1034) xfs_fs_fill_super (fs/xfs/xfs_super.c:1938) vfs_get_tree (fs/super.c:1695) path_mount (fs/namespace.c:4161) __x64_sys_mount (fs/namespace.c:4367) Fixes: 89cebc847729 ("xfs: validate transaction header length on log recovery") Cc: stable@vger.kernel.org # v4.3 Reported-by: Xiang Mei Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Weiming Shi Reviewed-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log_recover.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c index 103b2a79667b..fdb011e6ef60 100644 --- a/fs/xfs/xfs_log_recover.c +++ b/fs/xfs/xfs_log_recover.c @@ -1907,6 +1907,15 @@ xlog_recover_reorder_trans( list_for_each_entry_safe(item, n, &sort_list, ri_list) { enum xlog_recover_reorder fate = XLOG_REORDER_ITEM_LIST; + /* a committed item with no regions has a NULL ri_buf[0] */ + if (!item->ri_cnt || !item->ri_buf) { + xfs_warn(log->l_mp, + "%s: committed log item has no regions", + __func__); + error = -EFSCORRUPTED; + break; + } + item->ri_ops = xlog_find_item_ops(item); if (!item->ri_ops) { xfs_warn(log->l_mp, From d128ffd2baa708da0362b562f0b7f9df96e43653 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sun, 5 Jul 2026 12:23:58 +0800 Subject: [PATCH 189/479] xfs: tie zoned sysfs lifetime to zone info The zoned sysfs directory is currently registered as part of the generic per-mount sysfs setup, but the data exposed by nr_open_zones has a narrower lifetime. mp->m_zone_info is allocated by xfs_mount_zones() and freed by xfs_unmount_zones(), while the zoned sysfs kobject remained registered until xfs_mount_sysfs_del(). A read of nr_open_zones can therefore enter through the still-live sysfs kobject after xfs_unmount_zones() has freed mp->m_zone_info, leading to a use-after-free in nr_open_zones_show(). Make the zoned sysfs lifetime match the zone-info lifetime inside the zone allocator. Create the zoned sysfs directory from xfs_mount_zones() after the zone allocator has finished setting up, and remove it as the first step of xfs_unmount_zones(), before any zone allocator teardown can free m_zone_info. Sysfs removal deactivates the kernfs nodes and waits for active callbacks to drain before returning, so this also protects a reader that has already entered nr_open_zones_show() but has not yet dereferenced m_zone_info. Validation reproduced this kernel report: BUG: KASAN: slab-use-after-free in nr_open_zones_show+0x86/0x90 The buggy address belongs to the object at ffff88810b177800 which belongs to the cache kmalloc-1k of size 1024 The buggy address is located 160 bytes inside of freed 1024-byte region [ffff88810b177800, ffff88810b177c00) Read of size 4 Call trace: print_report+0xcd/0x620 nr_open_zones_show+0x86/0x90 (fs/xfs/xfs_sysfs.c:724) srso_alias_return_thunk+0x5/0xfbef5 __virt_addr_valid+0x20c/0x410 kasan_report+0xdd/0x110 sysfs_kf_seq_show+0x1bd/0x380 seq_read_iter+0x40f/0x11b0 lock_release+0xba/0x260 mark_held_locks+0x40/0x70 vfs_read+0x717/0xce0 __up_read+0x319/0x900 ksys_read+0xf8/0x1c0 do_user_addr_fault+0x3d0/0xbc0 trace_hardirqs_on_prepare+0x23/0xf0 do_syscall_64+0xc8/0x530 (arch/x86/entry/syscall_64.c:87) entry_SYSCALL_64_after_hwframe+0x74/0x7c Allocated by task stack: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_cache_noprof+0x205/0x460 xfs_mount_zones+0x34c/0x2650 xfs_mountfs+0x1b97/0x1eb0 xfs_fs_fill_super+0xf2b/0x18a0 get_tree_bdev_flags+0x310/0x590 vfs_get_tree+0x8d/0x2e0 __x64_sys_fsconfig+0x61c/0xbc0 do_syscall_64+0xc8/0x530 (arch/x86/entry/syscall_64.c:87) entry_SYSCALL_64_after_hwframe+0x74/0x7c Freed by task stack: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x20e/0x4c0 xfs_unmountfs+0x2fd/0x390 xfs_fs_put_super+0x60/0x110 generic_shutdown_super+0x143/0x4b0 kill_block_super+0x3b/0x90 xfs_kill_sb+0x12/0x50 deactivate_locked_super+0xa7/0x160 cleanup_mnt+0x218/0x420 task_work_run+0x11a/0x1f0 exit_to_user_mode_loop+0x13c/0x4f0 do_syscall_64+0x4a9/0x530 (arch/x86/entry/syscall_64.c:87) entry_SYSCALL_64_after_hwframe+0x74/0x7c Fixes: 62c89988dc19 ("xfs: expose the number of open zones in sysfs") Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_sysfs.c | 28 +++++++++++++++++----------- fs/xfs/xfs_sysfs.h | 2 ++ fs/xfs/xfs_zone_alloc.c | 8 ++++++++ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/fs/xfs/xfs_sysfs.c b/fs/xfs/xfs_sysfs.c index 676777064c2d..b62712187324 100644 --- a/fs/xfs/xfs_sysfs.c +++ b/fs/xfs/xfs_sysfs.c @@ -780,6 +780,23 @@ static const struct kobj_type xfs_zoned_ktype = { .default_groups = xfs_zoned_groups, }; +int +xfs_zoned_sysfs_init(struct xfs_mount *mp) +{ + if (!IS_ENABLED(CONFIG_XFS_RT) || !xfs_has_zoned(mp)) + return 0; + + return xfs_sysfs_init(&mp->m_zoned_kobj, &xfs_zoned_ktype, + &mp->m_kobj, "zoned"); +} + +void +xfs_zoned_sysfs_del(struct xfs_mount *mp) +{ + if (IS_ENABLED(CONFIG_XFS_RT) && xfs_has_zoned(mp)) + xfs_sysfs_del(&mp->m_zoned_kobj); +} + int xfs_mount_sysfs_init( struct xfs_mount *mp) @@ -820,14 +837,6 @@ xfs_mount_sysfs_init( if (error) goto out_remove_error_dir; - if (IS_ENABLED(CONFIG_XFS_RT) && xfs_has_zoned(mp)) { - /* .../xfs//zoned/ */ - error = xfs_sysfs_init(&mp->m_zoned_kobj, &xfs_zoned_ktype, - &mp->m_kobj, "zoned"); - if (error) - goto out_remove_error_dir; - } - return 0; out_remove_error_dir: @@ -846,9 +855,6 @@ xfs_mount_sysfs_del( struct xfs_error_cfg *cfg; int i, j; - if (IS_ENABLED(CONFIG_XFS_RT) && xfs_has_zoned(mp)) - xfs_sysfs_del(&mp->m_zoned_kobj); - for (i = 0; i < XFS_ERR_CLASS_MAX; i++) { for (j = 0; j < XFS_ERR_ERRNO_MAX; j++) { cfg = &mp->m_error_cfg[i][j]; diff --git a/fs/xfs/xfs_sysfs.h b/fs/xfs/xfs_sysfs.h index 1622fe80ad3e..25e5f8fae2f3 100644 --- a/fs/xfs/xfs_sysfs.h +++ b/fs/xfs/xfs_sysfs.h @@ -53,6 +53,8 @@ xfs_sysfs_del( } int xfs_mount_sysfs_init(struct xfs_mount *mp); +int xfs_zoned_sysfs_init(struct xfs_mount *mp); +void xfs_zoned_sysfs_del(struct xfs_mount *mp); void xfs_mount_sysfs_del(struct xfs_mount *mp); #endif /* __XFS_SYSFS_H__ */ diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index 08d8b34f467e..7d13fa7ab30a 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -21,6 +21,7 @@ #include "xfs_rtbitmap.h" #include "xfs_rtrmap_btree.h" #include "xfs_zone_alloc.h" +#include "xfs_sysfs.h" #include "xfs_zone_priv.h" #include "xfs_zones.h" #include "xfs_trace.h" @@ -1420,11 +1421,17 @@ xfs_mount_zones( if (error) goto out_free_zone_info; + error = xfs_zoned_sysfs_init(mp); + if (error) + goto out_zone_gc_unmount; + xfs_info(mp, "%u zones of %u blocks (%u max open zones)", mp->m_sb.sb_rgcount, iz.zone_capacity, mp->m_max_open_zones); trace_xfs_zones_mount(mp); return 0; +out_zone_gc_unmount: + xfs_zone_gc_unmount(mp); out_free_zone_info: xfs_free_zone_info(mp->m_zone_info); return error; @@ -1434,6 +1441,7 @@ void xfs_unmount_zones( struct xfs_mount *mp) { + xfs_zoned_sysfs_del(mp); xfs_zone_gc_unmount(mp); xfs_free_zone_info(mp->m_zone_info); } From f88caa121eac3b6a050262ac3de0c133ee17f7b2 Mon Sep 17 00:00:00 2001 From: Jinliang Zheng Date: Thu, 2 Jul 2026 19:56:11 +0800 Subject: [PATCH 190/479] xfs: use xfs_csn_t for xlog_cil_push_now() push_seq parameter The push_seq argument to xlog_cil_push_now() carries a CIL checkpoint sequence number, not a log sequence number (LSN). Change the parameter type from xfs_lsn_t to xfs_csn_t to correctly reflect its semantics and match the surrounding types. Both types are int64_t under the hood, so this is a type-annotation fix with no behavioural change. Signed-off-by: Jinliang Zheng Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log_cil.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_log_cil.c b/fs/xfs/xfs_log_cil.c index edc368938f30..639f875a8fb2 100644 --- a/fs/xfs/xfs_log_cil.c +++ b/fs/xfs/xfs_log_cil.c @@ -1710,7 +1710,7 @@ xlog_cil_push_background( static void xlog_cil_push_now( struct xlog *log, - xfs_lsn_t push_seq, + xfs_csn_t push_seq, bool async) { struct xfs_cil *cil = log->l_cilp; From 6aa38ef0eab32df5409b72d62509de6ba09cea50 Mon Sep 17 00:00:00 2001 From: Guoniu Zhou Date: Tue, 9 Jun 2026 14:26:40 +0800 Subject: [PATCH 191/479] dt-bindings: power: imx93: Add MIPI PHY power domain Add MIPI PHY power domain for shared PHY resources used by both MIPI DSI and CSI blocks. Signed-off-by: Guoniu Zhou Reviewed-by: Frank Li Acked-by: Krzysztof Kozlowski Reviewed-by: Peng Fan Fixes: e9aa77d413c9 ("soc: imx: add i.MX93 media blk ctrl driver") Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- include/dt-bindings/power/fsl,imx93-power.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/dt-bindings/power/fsl,imx93-power.h b/include/dt-bindings/power/fsl,imx93-power.h index 17f9f015bf7d..071221fe5c57 100644 --- a/include/dt-bindings/power/fsl,imx93-power.h +++ b/include/dt-bindings/power/fsl,imx93-power.h @@ -11,5 +11,6 @@ #define IMX93_MEDIABLK_PD_PXP 2 #define IMX93_MEDIABLK_PD_LCDIF 3 #define IMX93_MEDIABLK_PD_ISI 4 +#define IMX93_MEDIABLK_PD_MIPI_PHY 5 #endif From 99611233f8cda833169fa6487d5dacdf189e5cb0 Mon Sep 17 00:00:00 2001 From: Guoniu Zhou Date: Tue, 9 Jun 2026 14:26:41 +0800 Subject: [PATCH 192/479] pmdomain: imx93-blk-ctrl: Extract PHY as shared domain for DSI/CSI The MIPI DSI and CSI domains share control bits for clock and reset, which can lead to incorrect behavior if one domain disables the shared resource while the other is still active. To fix the issue, introduce a shared MIPI PHY power domain to own the common resources and make DSI and CSI its subdomains. This ensures the shared bits are properly managed and not disabled while still in use. Fixes: e9aa77d413c9 ("soc: imx: add i.MX93 media blk ctrl driver") Cc: stable@vger.kernel.org Signed-off-by: Guoniu Zhou Reviewed-by: Frank Li Reviewed-by: Peng Fan Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/imx93-blk-ctrl.c | 60 ++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/drivers/pmdomain/imx/imx93-blk-ctrl.c b/drivers/pmdomain/imx/imx93-blk-ctrl.c index 1afc78b034fa..243ce939ba68 100644 --- a/drivers/pmdomain/imx/imx93-blk-ctrl.c +++ b/drivers/pmdomain/imx/imx93-blk-ctrl.c @@ -48,6 +48,8 @@ #define PRIO(X) (X) +#define BLK_CTRL_NO_PARENT UINT_MAX + struct imx93_blk_ctrl_domain; struct imx93_blk_ctrl { @@ -68,12 +70,18 @@ struct imx93_blk_ctrl_qos { u32 cfg_prio; }; +struct imx93_blk_ctrl_subdomain_link { + struct generic_pm_domain *parent; + struct generic_pm_domain *subdomain; +}; + struct imx93_blk_ctrl_domain_data { const char *name; const char * const *clk_names; int num_clks; u32 rst_mask; u32 clk_mask; + u32 parent; int num_qos; struct imx93_blk_ctrl_qos qos[DOMAIN_MAX_QOS]; }; @@ -203,6 +211,13 @@ static void imx93_release_pm_genpd(void *data) pm_genpd_remove(genpd); } +static void imx93_release_subdomain(void *data) +{ + struct imx93_blk_ctrl_subdomain_link *link = data; + + pm_genpd_remove_subdomain(link->parent, link->subdomain); +} + static struct lock_class_key blk_ctrl_genpd_lock_class; static int imx93_blk_ctrl_probe(struct platform_device *pdev) @@ -302,6 +317,34 @@ static int imx93_blk_ctrl_probe(struct platform_device *pdev) bc->onecell_data.domains[i] = &domain->genpd; } + for (i = 0; i < bc_data->num_domains; i++) { + struct imx93_blk_ctrl_domain *domain = &bc->domains[i]; + const struct imx93_blk_ctrl_domain_data *data = domain->data; + struct imx93_blk_ctrl_subdomain_link *link; + + if (bc_data->skip_mask & BIT(i) || + data->parent == BLK_CTRL_NO_PARENT) + continue; + + link = devm_kzalloc(dev, sizeof(*link), GFP_KERNEL); + if (!link) + return -ENOMEM; + + link->parent = &bc->domains[data->parent].genpd; + link->subdomain = &domain->genpd; + + ret = pm_genpd_add_subdomain(&bc->domains[data->parent].genpd, + &domain->genpd); + if (ret) + return dev_err_probe(dev, ret, "failed to add subdomain %s\n", + domain->genpd.name); + + ret = devm_add_action_or_reset(dev, imx93_release_subdomain, link); + if (ret) + return dev_err_probe(dev, ret, + "failed to add subdomain release callback\n"); + } + ret = devm_pm_runtime_enable(dev); if (ret) return dev_err_probe(dev, ret, "failed to enable pm-runtime\n"); @@ -326,8 +369,9 @@ static const struct imx93_blk_ctrl_domain_data imx93_media_blk_ctl_domain_data[] .name = "mediablk-mipi-dsi", .clk_names = (const char *[]){ "dsi" }, .num_clks = 1, - .rst_mask = BIT(11) | BIT(12), - .clk_mask = BIT(11) | BIT(12), + .rst_mask = BIT(11), + .clk_mask = BIT(11), + .parent = IMX93_MEDIABLK_PD_MIPI_PHY, }, [IMX93_MEDIABLK_PD_MIPI_CSI] = { .name = "mediablk-mipi-csi", @@ -335,6 +379,7 @@ static const struct imx93_blk_ctrl_domain_data imx93_media_blk_ctl_domain_data[] .num_clks = 2, .rst_mask = BIT(9) | BIT(10), .clk_mask = BIT(9) | BIT(10), + .parent = IMX93_MEDIABLK_PD_MIPI_PHY, }, [IMX93_MEDIABLK_PD_PXP] = { .name = "mediablk-pxp", @@ -342,6 +387,7 @@ static const struct imx93_blk_ctrl_domain_data imx93_media_blk_ctl_domain_data[] .num_clks = 1, .rst_mask = BIT(7) | BIT(8), .clk_mask = BIT(7) | BIT(8), + .parent = BLK_CTRL_NO_PARENT, .num_qos = 2, .qos = { { @@ -363,6 +409,7 @@ static const struct imx93_blk_ctrl_domain_data imx93_media_blk_ctl_domain_data[] .num_clks = 2, .rst_mask = BIT(4) | BIT(5) | BIT(6), .clk_mask = BIT(4) | BIT(5) | BIT(6), + .parent = BLK_CTRL_NO_PARENT, .num_qos = 1, .qos = { { @@ -379,6 +426,7 @@ static const struct imx93_blk_ctrl_domain_data imx93_media_blk_ctl_domain_data[] .num_clks = 1, .rst_mask = BIT(2) | BIT(3), .clk_mask = BIT(2) | BIT(3), + .parent = BLK_CTRL_NO_PARENT, .num_qos = 4, .qos = { { @@ -404,6 +452,14 @@ static const struct imx93_blk_ctrl_domain_data imx93_media_blk_ctl_domain_data[] } } }, + [IMX93_MEDIABLK_PD_MIPI_PHY] = { + .name = "mediablk-mipi-phy", + .clk_names = NULL, + .num_clks = 0, + .rst_mask = BIT(12), + .clk_mask = BIT(12), + .parent = BLK_CTRL_NO_PARENT, + }, }; static const struct regmap_range imx93_media_blk_ctl_yes_ranges[] = { From 72422525f641f68bed6ca3389d29ee3f41fdea33 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Wed, 10 Jun 2026 22:39:10 +0800 Subject: [PATCH 193/479] pmdomain: imx: Fix i.MX8MP power notifier Using imx8mm_vpu_power_notifier() for i.MX8MP is wrong, as it ungates the VPU clocks to provide the ADB clock, which is necessary on i.MX8MM, but on i.MX8MP there is a separate gate (bit 3) for the NoC. So add imx8mp_vpu_power_notifier() for i.MX8MP. Fixes: a1a5f15f7f6cb ("soc: imx: imx8m-blk-ctrl: add i.MX8MP VPU blk ctrl") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Signed-off-by: Peng Fan Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/imx8m-blk-ctrl.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/drivers/pmdomain/imx/imx8m-blk-ctrl.c b/drivers/pmdomain/imx/imx8m-blk-ctrl.c index 19e992d2ee3b..e13a47eeed75 100644 --- a/drivers/pmdomain/imx/imx8m-blk-ctrl.c +++ b/drivers/pmdomain/imx/imx8m-blk-ctrl.c @@ -514,9 +514,34 @@ static const struct imx8m_blk_ctrl_domain_data imx8mp_vpu_blk_ctl_domain_data[] }, }; +static int imx8mp_vpu_power_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct imx8m_blk_ctrl *bc = container_of(nb, struct imx8m_blk_ctrl, + power_nb); + + if (action == GENPD_NOTIFY_ON) { + /* + * On power up we have no software backchannel to the GPC to + * wait for the ADB handshake to happen, so we just delay for a + * bit. On power down the GPC driver waits for the handshake. + */ + + udelay(5); + + /* set "fuse" bits to enable the VPUs */ + regmap_set_bits(bc->regmap, 0x8, 0xffffffff); + regmap_set_bits(bc->regmap, 0xc, 0xffffffff); + regmap_set_bits(bc->regmap, 0x10, 0xffffffff); + regmap_set_bits(bc->regmap, 0x14, 0xffffffff); + } + + return NOTIFY_OK; +} + static const struct imx8m_blk_ctrl_data imx8mp_vpu_blk_ctl_dev_data = { .max_reg = 0x18, - .power_notifier_fn = imx8mm_vpu_power_notifier, + .power_notifier_fn = imx8mp_vpu_power_notifier, .domains = imx8mp_vpu_blk_ctl_domain_data, .num_domains = ARRAY_SIZE(imx8mp_vpu_blk_ctl_domain_data), }; From 25e252bcf1593b420b12a7231d9dd64b885a2ae2 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Wed, 10 Jun 2026 22:39:11 +0800 Subject: [PATCH 194/479] pmdomain: imx: Fix i.MX8MP VC8000E power up sequence Per errata[1]: ERR050531: VPU_NOC power down handshake may hang during VC8000E/VPUMIX power up/down cycling. Description: VC8000E reset de-assertion edge and AXI clock may have a timing issue. Workaround: Set bit2 (vc8000e_clk_en) of BLK_CLK_EN_CSR to 0 to gate off both AXI clock and VC8000E clock sent to VC8000E and AXI clock sent to VPU_NOC m_v_2 interface during VC8000E power up(VC8000E reset is de-asserted by HW) Add a bool variable is_errata_err050531 in 'struct imx8m_blk_ctrl_domain_data' to represent whether the workaround is needed. If is_errata_err050531 is true, first clear the clk before powering up gpc, then enable the clk after powering up gpc. [1] https://www.nxp.com/webapp/Download?colCode=IMX8MP_1P33A Fixes: a1a5f15f7f6cb ("soc: imx: imx8m-blk-ctrl: add i.MX8MP VPU blk ctrl") Cc: stable@vger.kernel.org Signed-off-by: Peng Fan Reviewed-by: Frank Li Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/imx8m-blk-ctrl.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/pmdomain/imx/imx8m-blk-ctrl.c b/drivers/pmdomain/imx/imx8m-blk-ctrl.c index e13a47eeed75..99d100e1d923 100644 --- a/drivers/pmdomain/imx/imx8m-blk-ctrl.c +++ b/drivers/pmdomain/imx/imx8m-blk-ctrl.c @@ -54,6 +54,15 @@ struct imx8m_blk_ctrl_domain_data { * register. */ u32 mipi_phy_rst_mask; + + /* + * VC8000E reset de-assertion edge and AXI clock may have a timing issue. + * Workaround: Set bit2 (vc8000e_clk_en) of BLK_CLK_EN_CSR to 0 to gate off + * both AXI clock and VC8000E clock sent to VC8000E and AXI clock sent to + * VPU_NOC m_v_2 interface during VC8000E power up(VC8000E reset is + * de-asserted by HW) + */ + bool is_errata_err050531; }; #define DOMAIN_MAX_CLKS 4 @@ -108,7 +117,11 @@ static int imx8m_blk_ctrl_power_on(struct generic_pm_domain *genpd) dev_err(bc->dev, "failed to enable clocks\n"); goto bus_put; } - regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask); + + if (data->is_errata_err050531) + regmap_clear_bits(bc->regmap, BLK_CLK_EN, data->clk_mask); + else + regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask); /* power up upstream GPC domain */ ret = pm_runtime_get_sync(domain->power_dev); @@ -117,6 +130,9 @@ static int imx8m_blk_ctrl_power_on(struct generic_pm_domain *genpd) goto clk_disable; } + if (data->is_errata_err050531) + regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask); + /* wait for reset to propagate */ udelay(5); @@ -511,6 +527,7 @@ static const struct imx8m_blk_ctrl_domain_data imx8mp_vpu_blk_ctl_domain_data[] .clk_mask = BIT(2), .path_names = (const char *[]){"vc8000e"}, .num_paths = 1, + .is_errata_err050531 = true, }, }; From ed446e8aa894883c08892cfee69782fdf8f6c3ca Mon Sep 17 00:00:00 2001 From: leixiang Date: Mon, 22 Jun 2026 15:51:01 +0800 Subject: [PATCH 195/479] KVM: x86: Nullify irqfd->producer if updating IRTE for bypass fails Nullify irqfd->producer if updating the IRTE for bypass fails, as leaving a dangling pointer will result in a use-after-free if the irqfd is reachable through KVM's routing, but the producer is freed separately. E.g. for VFIO PCI, the producer is embedded in struct "vfio_pci_irq_ctx" and freed when the vector is disabled, which can happen independent of routing updates. Fixes: 77e1b8332d1d ("KVM: x86: Decouple device assignment from IRQ bypass") Cc: stable@vger.kernel.org Signed-off-by: leixiang Link: https://patch.msgid.link/1782119051448443.14545.seg@mailgw.kylinos.cn [sean: drop PPC change, massage changelog] Signed-off-by: Sean Christopherson --- arch/x86/kvm/irq.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/x86/kvm/irq.c b/arch/x86/kvm/irq.c index 8c62c6d4d5c1..cb8ac4b9b0d7 100644 --- a/arch/x86/kvm/irq.c +++ b/arch/x86/kvm/irq.c @@ -488,8 +488,10 @@ int kvm_arch_irq_bypass_add_producer(struct irq_bypass_consumer *cons, if (irqfd->irq_entry.type == KVM_IRQ_ROUTING_MSI) { ret = kvm_pi_update_irte(irqfd, &irqfd->irq_entry); - if (ret) + if (ret) { kvm->arch.nr_possible_bypass_irqs--; + irqfd->producer = NULL; + } } spin_unlock_irq(&kvm->irqfds.lock); From 9285e4070df2c40585c3d7ec9571faa7a2b97e17 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Wed, 24 Jun 2026 15:05:16 -0700 Subject: [PATCH 196/479] KVM: x86: Ignore pending PV EOI if the vCPU has since disabled PV EOIs Ignore KVM's internal "service pending PV EOI" request if the vCPU has disabled PV EOIs since the request was made. Asserting that PV EOIs are enabled can fail if reading guest memory in pv_eoi_get_user() fails, i.e. if pv_eoi_test_and_clr_pending() bails early, *and* the vCPU also disables PV EOIs. kernel BUG at arch/x86/kvm/lapic.c:3338! Oops: invalid opcode: 0000 [#1] SMP CPU: 4 UID: 1000 PID: 890 Comm: pv_eoi_test Not tainted 7.0.0-d585aa5894d8-vm #337 PREEMPT Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 RIP: 0010:kvm_lapic_sync_from_vapic+0x12b/0x140 [kvm] Call Trace: kvm_arch_vcpu_ioctl_run+0x1075/0x1c30 [kvm] kvm_vcpu_ioctl+0x2d5/0x980 [kvm] __x64_sys_ioctl+0x8a/0xd0 do_syscall_64+0xb5/0xb40 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Modules linked in: kvm_intel kvm irqbypass ---[ end trace 0000000000000000 ]--- Fixes: ae7a2a3fb6f8 ("KVM: host side for eoi optimization") Cc: stable@vger.kernel.org Reviewed-by: Kai Huang Link: https://patch.msgid.link/20260624220516.3033391-1-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/lapic.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c index 6f30bbdddb5a..38bba9a1114c 100644 --- a/arch/x86/kvm/lapic.c +++ b/arch/x86/kvm/lapic.c @@ -3371,6 +3371,12 @@ static void apic_sync_pv_eoi_from_guest(struct kvm_vcpu *vcpu, struct kvm_lapic *apic) { int vector; + + if (unlikely(!pv_eoi_enabled(vcpu))) { + __clear_bit(KVM_APIC_PV_EOI_PENDING, &vcpu->arch.apic_attention); + return; + } + /* * PV EOI state is derived from KVM_APIC_PV_EOI_PENDING in host * and KVM_PV_EOI_ENABLED in guest memory as follows: @@ -3382,8 +3388,6 @@ static void apic_sync_pv_eoi_from_guest(struct kvm_vcpu *vcpu, * KVM_APIC_PV_EOI_PENDING is set, KVM_PV_EOI_ENABLED is unset: * -> host enabled PV EOI, guest executed EOI. */ - BUG_ON(!pv_eoi_enabled(vcpu)); - if (pv_eoi_test_and_clr_pending(vcpu)) return; vector = apic_set_eoi(apic); From ebdac7554abb347ca4197be241116842161acd9b Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 12 Jun 2026 07:56:41 -0700 Subject: [PATCH 197/479] KVM: nVMX: Move vTPR vs. TPR Threshold consistency check into "normal" checks Move the off-by-default consistency check for vmcs12.tpr_threshold vs. the virtual APIC vTPR into the "normal" controls checks, as waiting until KVM has loaded some amount of state is unnecessary and actively dangerous. Specifically, failure to unwind vmcs01.GUEST_CR3 to KVM's value when EPT is disabled results in KVM running L1 with an L1-controlled CR3, not with KVM's CR3! Alternatively, KVM could simply reset the MMU to force a reload of vmcs01.GUEST_CR3, but the _only_ reason the check was shoved into a "late" flow was to wait until the vmcs12 pages were retrieved. Rather than build up more crusty code, simply access vTPR using a regular guest memory access (performance isn't a concern). To circumvent the restrictions that led to KVM deferring nested_get_vmcs12_pages(), (a) use a VM-scoped API to read guest memory so that it always hits non-SMM memslots (for RSM), and (b) skip the check (since its off-by-default anyways) when the vCPU doesn't want to run, i.e. when userspace is restoring/stuffing state. If reading guest memory fails, simply skip the consistency check, as KVM's de facto ABI is that VMX instruction accesses to non-existent memory get PCI Bus Error semantics, where reads return 0xFFs. And if vTPR=0xFF, then the vTPR is guaranteed to be greater than or equal to TPR_THRESHOLD. Fixes: 1100e4910ad2 ("KVM: nVMX: Add an off-by-default module param to WARN on missed consistency checks") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260612145642.452392-2-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/vmx/nested.c | 66 +++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c index 6957bb6f5cf7..4fc4349810a3 100644 --- a/arch/x86/kvm/vmx/nested.c +++ b/arch/x86/kvm/vmx/nested.c @@ -582,6 +582,9 @@ static int nested_vmx_check_msr_bitmap_controls(struct kvm_vcpu *vcpu, static int nested_vmx_check_tpr_shadow_controls(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12) { + gpa_t vtpr_gpa = vmcs12->virtual_apic_page_addr + APIC_TASKPRI; + u32 vtpr; + if (!nested_cpu_has(vmcs12, CPU_BASED_TPR_SHADOW)) return 0; @@ -591,6 +594,32 @@ static int nested_vmx_check_tpr_shadow_controls(struct kvm_vcpu *vcpu, if (CC(!nested_cpu_has_vid(vmcs12) && vmcs12->tpr_threshold >> 4)) return -EINVAL; + /* + * Do the illegal vTPR vs. TPR Threshold consistency check if and only + * if KVM is configured to WARN on missed consistency checks, otherwise + * it's a waste of time. KVM needs to rely on hardware to fully detect + * an illegal combination due to the vTPR being writable by L1 at all + * times (it's an in-memory value, not a VMCS field). I.e. even if the + * check passes now, it might fail at the actual VM-Enter. + * + * If reading guest memory fails, skip the check as KVM's de facto ABI + * for VMX instruction accesses to non-existent memory is to provide + * PCI Bus Error semantics (reads return 0xFFs), in which case the vTPR + * is guaranteed to greater than or equal to the threshold. + * + * Note! Deliberately use the VM-scoped API when reading guest memory, + * to ensure the read doesn't hit SMRAM when restoring L2 state on RSM, + * and only perform the check when in KVM_RUN, to avoid a false failure + * if userspace hasn't yet configured memslots during state restore. + */ + if (warn_on_missed_cc && vcpu->wants_to_run && + nested_cpu_has(vmcs12, CPU_BASED_TPR_SHADOW) && + !nested_cpu_has_vid(vmcs12) && + !nested_cpu_has2(vmcs12, SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES) && + !kvm_read_guest(vcpu->kvm, vtpr_gpa, &vtpr, sizeof(vtpr)) && + CC((vmcs12->tpr_threshold & GENMASK(3, 0)) > ((vtpr >> 4) & GENMASK(3, 0)))) + return -EINVAL; + return 0; } @@ -3104,38 +3133,6 @@ static int nested_vmx_check_controls(struct kvm_vcpu *vcpu, return 0; } -static int nested_vmx_check_controls_late(struct kvm_vcpu *vcpu, - struct vmcs12 *vmcs12) -{ - void *vapic = to_vmx(vcpu)->nested.virtual_apic_map.hva; - u32 vtpr = vapic ? (*(u32 *)(vapic + APIC_TASKPRI)) >> 4 : 0; - - /* - * Don't bother with the consistency checks if KVM isn't configured to - * WARN on missed consistency checks, as KVM needs to rely on hardware - * to fully detect an illegal vTPR vs. TRP Threshold combination due to - * the vTPR being writable by L1 at all times (it's an in-memory value, - * not a VMCS field). I.e. even if the check passes now, it might fail - * at the actual VM-Enter. - * - * Keying off the module param also allows treating an invalid vAPIC - * mapping as a consistency check failure without increasing the risk - * of breaking a "real" VM. - */ - if (!warn_on_missed_cc) - return 0; - - if ((exec_controls_get(to_vmx(vcpu)) & CPU_BASED_TPR_SHADOW) && - nested_cpu_has(vmcs12, CPU_BASED_TPR_SHADOW) && - !nested_cpu_has_vid(vmcs12) && - !nested_cpu_has2(vmcs12, SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES) && - (CC(!vapic) || - CC((vmcs12->tpr_threshold & GENMASK(3, 0)) > (vtpr & GENMASK(3, 0))))) - return -EINVAL; - - return 0; -} - static int nested_vmx_check_address_space_size(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12) { @@ -3685,11 +3682,6 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu, return NVMX_VMENTRY_KVM_INTERNAL_ERROR; } - if (nested_vmx_check_controls_late(vcpu, vmcs12)) { - vmx_switch_vmcs(vcpu, &vmx->vmcs01); - return NVMX_VMENTRY_VMFAIL; - } - if (nested_vmx_check_guest_state(vcpu, vmcs12, &entry_failure_code)) { exit_reason.basic = EXIT_REASON_INVALID_STATE; From 3e6dd2b9b7b2884743ed7a0873b8743cc0df6d40 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 12 Jun 2026 07:56:42 -0700 Subject: [PATCH 198/479] KVM: nVMX: Don't use vmcs01.GUEST_CR3 to snapshot L1's CR3 when EPT is disabled Add a dedicated field in "struct nested_vmx" to track L1's pre-VM-Enter CR3 instead of using vmcs01.GUEST_CR3, which isn't anywhere near as safe as the comment purports it to be. E.g. in addition to the warn_on_missed_cc bug (that was fixed by relocating the consistency check), if getting vmcs12 pages (during actual nested VM-Entry) fails and EPT is disabled (in KVM), KVM will return control to userspace with vmcs01.GUEST_CR3 holding a guest- controlled value. Alternatively, KVM could force a reload of vmcs01.GUEST_CR3 by resetting the MMU context in the error path, but as above, the safety of the vmcs01 approach is extremely questionable, e.g. it took all of ~4 months for the code to break. Fixes: 671ddc700fd0 ("KVM: nVMX: Don't leak L1 MMIO regions to L2") Cc: stable@vger.kernel.org Cc: Jim Mattson Link: https://patch.msgid.link/20260612145642.452392-3-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/vmx/nested.c | 21 ++++++++------------- arch/x86/kvm/vmx/vmx.h | 7 +++++++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c index 4fc4349810a3..bb0eb40b4448 100644 --- a/arch/x86/kvm/vmx/nested.c +++ b/arch/x86/kvm/vmx/nested.c @@ -3658,19 +3658,14 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu, &vmx->nested.pre_vmenter_ssp_tbl); /* - * Overwrite vmcs01.GUEST_CR3 with L1's CR3 if EPT is disabled. In the - * event of a "late" VM-Fail, i.e. a VM-Fail detected by hardware but - * not KVM, KVM must unwind its software model to the pre-VM-Entry host - * state. When EPT is disabled, GUEST_CR3 holds KVM's shadow CR3, not - * L1's "real" CR3, which causes nested_vmx_restore_host_state() to - * corrupt vcpu->arch.cr3. Stuffing vmcs01.GUEST_CR3 results in the - * unwind naturally setting arch.cr3 to the correct value. Smashing - * vmcs01.GUEST_CR3 is safe because nested VM-Exits, and the unwind, - * reset KVM's MMU, i.e. vmcs01.GUEST_CR3 is guaranteed to be - * overwritten with a shadow CR3 prior to re-entering L1. + * Stash L1's CR3, so that in the event of a "late" VM-Fail, i.e. a + * VM-Fail detected by hardware but not KVM, KVM can unwind its + * software model to the pre-VM-Entry host state. When EPT is + * disabled, GUEST_CR3 holds KVM's shadow CR3, not L1's "real" CR3, + * and so simply restoring from vmcs01.GUEST_CR3 would corrupt + * vcpu->arch.cr3. */ - if (!enable_ept) - vmcs_writel(GUEST_CR3, vcpu->arch.cr3); + vmx->nested.pre_vmenter_cr3 = kvm_read_cr3(vcpu); vmx_switch_vmcs(vcpu, &vmx->nested.vmcs02); @@ -4982,7 +4977,7 @@ static void nested_vmx_restore_host_state(struct kvm_vcpu *vcpu) vmx_set_cr4(vcpu, vmcs_readl(CR4_READ_SHADOW)); nested_ept_uninit_mmu_context(vcpu); - vcpu->arch.cr3 = vmcs_readl(GUEST_CR3); + vcpu->arch.cr3 = vmx->nested.pre_vmenter_cr3; kvm_register_mark_available(vcpu, VCPU_REG_CR3); /* diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h index de9de0d2016c..dc8517f15bc4 100644 --- a/arch/x86/kvm/vmx/vmx.h +++ b/arch/x86/kvm/vmx/vmx.h @@ -159,6 +159,13 @@ struct nested_vmx { bool has_preemption_timer_deadline; bool preemption_timer_expired; + /* + * Used to restore L1's CR3 if hardware detects a VM-Fail Consistency + * Check that KVM does not, in which case KVM needs to unwind CR3 back + * to its pre-VM-Enter state, NOT to vmcs01.HOST_CR3. + */ + unsigned long pre_vmenter_cr3; + /* * Used to snapshot MSRs that are conditionally loaded on VM-Enter in * order to propagate the guest's pre-VM-Enter value into vmcs02. For From 18d62044cda7a2b40f59d910659c0b0d6accad37 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 8 Jul 2026 10:43:27 +0200 Subject: [PATCH 199/479] sched_ext: Preserve rq tracking across local DSQ dispatch dispatch_to_local_dsq() can run from scx_bpf_dsq_move_to_local() while ops.dispatch() has recorded the current rq. Moving a task to a local DSQ may switch to the source or destination rq before synchronously invoking ops.dequeue() through the following path: SCX_CALL_OP(dispatch, rq) ops.dispatch() scx_bpf_dsq_move_to_local() scx_flush_dispatch_buf() finish_dispatch() dispatch_to_local_dsq() scx_dispatch_enqueue() local_dsq_post_enq() call_task_dequeue() SCX_CALL_OP_TASK(dequeue, locked_rq, ...) The nested callback saves the recorded rq and restores it on return. If the rq tracking does not follow the lock switch, update_locked_rq() can trigger the following lockdep assertion while restoring an rq which is no longer held: WARNING: kernel/sched/sched.h:1641 at call_task_dequeue+0x160/0x170 Call Trace: scx_dispatch_enqueue+0x2b0/0x460 dispatch_to_local_dsq+0x138/0x230 scx_flush_dispatch_buf+0x1af/0x220 scx_bpf_dsq_move_to_local___v2+0xe2/0x1c0 bpf__sched_ext_ops_dispatch+0x4b/0xa7 do_pick_task_scx+0x3b6/0x910 __pick_next_task+0x105/0x1f0 __schedule+0x3e7/0x1980 Introduce switch_rq_lock() to update the tracking state together with each rq lock handoff. Use it in dispatch_to_local_dsq(), move_remote_task_to_local_dsq() and the in-balance paths of scx_dsq_move(), ensuring that scx_locked_rq() consistently refers to the rq whose lock is actually held throughout the lock dance. Fixes: 7fb39e4eb4c3 ("sched_ext: Save and restore scx_locked_rq across SCX_CALL_OP") Cc: stable@vger.kernel.org # 7.1+ Signed-off-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index e75e2fd5ab7e..ee66dacfc92c 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -479,6 +479,18 @@ static bool rq_is_open(struct rq *rq, u64 enq_flags) */ DEFINE_PER_CPU(struct rq *, scx_locked_rq_state); +static void switch_rq_lock(struct rq *from, struct rq *to) +{ + bool tracked = scx_locked_rq() == from; + + if (tracked) + update_locked_rq(NULL); + raw_spin_rq_unlock(from); + raw_spin_rq_lock(to); + if (tracked) + update_locked_rq(to); +} + /* * Flipped on enable per sch->is_cid_type. Declared in internal.h so * subsystem inlines can read it. @@ -2274,8 +2286,7 @@ static void move_remote_task_to_local_dsq(struct task_struct *p, u64 enq_flags, deactivate_task(src_rq, p, 0); set_task_cpu(p, cpu_of(dst_rq)); - raw_spin_rq_unlock(src_rq); - raw_spin_rq_lock(dst_rq); + switch_rq_lock(src_rq, dst_rq); /* * We want to pass scx-specific enq_flags but activate_task() will @@ -2608,9 +2619,8 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq, /* switch to @src_rq lock */ if (locked_rq != src_rq) { - raw_spin_rq_unlock(locked_rq); + switch_rq_lock(locked_rq, src_rq); locked_rq = src_rq; - raw_spin_rq_lock(src_rq); } /* task_rq couldn't have changed if we're still the holding cpu */ @@ -2644,10 +2654,8 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq, } /* switch back to @rq lock */ - if (locked_rq != rq) { - raw_spin_rq_unlock(locked_rq); - raw_spin_rq_lock(rq); - } + if (locked_rq != rq) + switch_rq_lock(locked_rq, rq); } /** @@ -8825,10 +8833,8 @@ static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit, in_balance = this_rq->scx.flags & SCX_RQ_IN_BALANCE; if (in_balance) { - if (this_rq != src_rq) { - raw_spin_rq_unlock(this_rq); - raw_spin_rq_lock(src_rq); - } + if (this_rq != src_rq) + switch_rq_lock(this_rq, src_rq); } else { raw_spin_rq_lock(src_rq); } @@ -8860,10 +8866,8 @@ static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit, dispatched = true; out: if (in_balance) { - if (this_rq != locked_rq) { - raw_spin_rq_unlock(locked_rq); - raw_spin_rq_lock(this_rq); - } + if (this_rq != locked_rq) + switch_rq_lock(locked_rq, this_rq); } else { raw_spin_rq_unlock_irqrestore(locked_rq, flags); } From 4ec10f38ff901dc10503d57cbdcf941248419ac1 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 8 Jul 2026 09:46:48 +0200 Subject: [PATCH 200/479] sched_ext: Enable tick for finite slices on nohz_full set_next_task_scx() updates the tick dependency before __schedule() updates rq->curr. When switching from a non-EXT task, such as idle, to an EXT task with a finite slice, sched_update_tick_dependency() checks the outgoing task and can allow the tick to remain stopped. The dependency can also be lost without a slice-type transition. After a finite-slice task leaves the CPU idle, the enqueue path can clear the dependency against the idle rq->curr. SCX_RQ_CAN_STOP_TICK still records a finite slice, so another finite task skips the transition block and can run without the ticks needed to expire its slice. The reverse mismatch can also happen when the last finite-slice EXT task is dequeued: sub_nr_running() updates the dependency before rq->curr changes, so the outgoing task state can keep the dependency set after the CPU goes idle. Fix this by unconditionally enabling the scheduler tick whenever a finite-slice EXT task is selected on a nohz_full CPU. Moreover, when the last runnable EXT task leaves, ignore the outgoing EXT slice state so the generic scheduler can correctly re-evaluate and clear the tick dependency. Fixes: 22a920209ab6 ("sched_ext: Implement tickless support") Signed-off-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 47 +++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index ee66dacfc92c..2dc6977d7984 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2986,24 +2986,38 @@ static void set_next_task_scx(struct rq *rq, struct task_struct *p, bool first) /* * @p is getting newly scheduled or got kicked after someone updated its - * slice. Refresh whether tick can be stopped. See scx_can_stop_tick(). + * slice. Update SCX_RQ_CAN_STOP_TICK to reflect whether the tick can be + * stopped. See scx_can_stop_tick(). + * + * Moreover, refresh the load_avgs just when transitioning in and out of + * nohz. In the future, we might want to add a mechanism to update + * load_avgs periodically on tick-stopped CPUs. */ - if ((p->scx.slice == SCX_SLICE_INF) != - (bool)(rq->scx.flags & SCX_RQ_CAN_STOP_TICK)) { - if (p->scx.slice == SCX_SLICE_INF) + if (p->scx.slice == SCX_SLICE_INF) { + if (!(rq->scx.flags & SCX_RQ_CAN_STOP_TICK)) { + /* + * Bypass mode always assigns finite slices, so @p + * can't have an infinite slice while bypassing. + * Therefore, sched_update_tick_dependency() can safely + * evaluate the outgoing task. + */ rq->scx.flags |= SCX_RQ_CAN_STOP_TICK; - else - rq->scx.flags &= ~SCX_RQ_CAN_STOP_TICK; + sched_update_tick_dependency(rq); - sched_update_tick_dependency(rq); + update_other_load_avgs(rq); + } + } else { + if (rq->scx.flags & SCX_RQ_CAN_STOP_TICK) { + rq->scx.flags &= ~SCX_RQ_CAN_STOP_TICK; + update_other_load_avgs(rq); + } /* - * For now, let's refresh the load_avgs just when transitioning - * in and out of nohz. In the future, we might want to add a - * mechanism which calls the following periodically on - * tick-stopped CPUs. + * @rq still references the outgoing scheduling context. A finite + * slice is sufficient by itself to require the tick. */ - update_other_load_avgs(rq); + if (tick_nohz_full_cpu(cpu_of(rq))) + tick_nohz_dep_set_cpu(cpu_of(rq), TICK_DEP_BIT_SCHED); } } @@ -4329,6 +4343,15 @@ bool scx_can_stop_tick(struct rq *rq) if (p->sched_class != &ext_sched_class) return true; + /* + * @rq->curr may still reference an outgoing EXT task after it has been + * dequeued. If no EXT tasks are accounted on @rq, ignore its stale + * slice state. If another task is dispatched from a DSQ, + * set_next_task_scx() will update the dependency for the incoming task. + */ + if (!rq->scx.nr_running) + return true; + if (scx_bypassing(sch, cpu_of(rq))) return false; From cfe950d79f524e72bc263f2b153a6d905a75a794 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 8 Jul 2026 09:46:49 +0200 Subject: [PATCH 201/479] selftests/sched_ext: Verify nohz_full tick behavior Finite-slice EXT tasks need the periodic scheduler tick to expire their slices even when nohz_full is enabled. Add a regression test that selects a nohz_full CPU and exercises both infinite-to-finite and finite-to-finite slice transitions across an idle interval. For each finite task, verify that its ops.tick() callback is invoked. Skip the test when an allowed nohz_full CPU and a separate housekeeping CPU are not available. Signed-off-by: Andrea Righi Signed-off-by: Tejun Heo --- tools/testing/selftests/sched_ext/Makefile | 1 + .../selftests/sched_ext/nohz_tick.bpf.c | 65 ++++ tools/testing/selftests/sched_ext/nohz_tick.c | 347 ++++++++++++++++++ 3 files changed, 413 insertions(+) create mode 100644 tools/testing/selftests/sched_ext/nohz_tick.bpf.c create mode 100644 tools/testing/selftests/sched_ext/nohz_tick.c diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile index 5d2dffca0e91..3cfe90e0f34f 100644 --- a/tools/testing/selftests/sched_ext/Makefile +++ b/tools/testing/selftests/sched_ext/Makefile @@ -176,6 +176,7 @@ auto-test-targets := \ maybe_null \ minimal \ non_scx_kfunc_deny \ + nohz_tick \ numa \ allowed_cpus \ peek_dsq \ diff --git a/tools/testing/selftests/sched_ext/nohz_tick.bpf.c b/tools/testing/selftests/sched_ext/nohz_tick.bpf.c new file mode 100644 index 000000000000..6998c5dd6bcb --- /dev/null +++ b/tools/testing/selftests/sched_ext/nohz_tick.bpf.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES + * + * Exercise tick dependency transitions between infinite and finite slices. + */ +#include + +char _license[] SEC("license") = "GPL"; + +const volatile s32 test_cpu; +bool finite_phase; +u64 nr_inf_running; +u64 nr_finite_running; +u64 nr_finite_ticks; + +UEI_DEFINE(uei); + +s32 BPF_STRUCT_OPS(nohz_tick_select_cpu, struct task_struct *p, s32 prev_cpu, + u64 wake_flags) +{ + return prev_cpu; +} + +void BPF_STRUCT_OPS(nohz_tick_enqueue, struct task_struct *p, u64 enq_flags) +{ + u64 slice = finite_phase ? 1000000ULL : SCX_SLICE_INF; + + scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, slice, enq_flags); + if (enq_flags & SCX_ENQ_LAST) + scx_bpf_kick_cpu(test_cpu, SCX_KICK_IDLE); +} + +void BPF_STRUCT_OPS(nohz_tick_running, struct task_struct *p) +{ + if (bpf_get_smp_processor_id() != test_cpu) + return; + + if (finite_phase) + __sync_fetch_and_add(&nr_finite_running, 1); + else + __sync_fetch_and_add(&nr_inf_running, 1); +} + +void BPF_STRUCT_OPS(nohz_tick_tick, struct task_struct *p) +{ + if (bpf_get_smp_processor_id() == test_cpu && finite_phase) + __sync_fetch_and_add(&nr_finite_ticks, 1); +} + +void BPF_STRUCT_OPS(nohz_tick_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); +} + +SEC(".struct_ops.link") +struct sched_ext_ops nohz_tick_ops = { + .select_cpu = (void *)nohz_tick_select_cpu, + .enqueue = (void *)nohz_tick_enqueue, + .running = (void *)nohz_tick_running, + .tick = (void *)nohz_tick_tick, + .exit = (void *)nohz_tick_exit, + .name = "nohz_tick", + .timeout_ms = 1000U, +}; diff --git a/tools/testing/selftests/sched_ext/nohz_tick.c b/tools/testing/selftests/sched_ext/nohz_tick.c new file mode 100644 index 000000000000..028f54391c2c --- /dev/null +++ b/tools/testing/selftests/sched_ext/nohz_tick.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES + * + * Validate that a finite-slice EXT task restarts the scheduler tick when it + * follows an infinite-slice EXT task and an idle interval on a NOHZ_FULL CPU. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "nohz_tick.bpf.skel.h" +#include "scx_test.h" + +#ifndef SCHED_EXT +#define SCHED_EXT 7 +#endif + +#define MIN_FINITE_TICKS 3 +#define PHASE_TIMEOUT_MS 1000 + +struct nohz_tick_ctx { + struct nohz_tick *skel; + cpu_set_t original_mask; + int test_cpu; +}; + +static int first_allowed_cpu(const cpu_set_t *mask, int first, int last) +{ + int cpu; + + for (cpu = first; cpu <= last && cpu < CPU_SETSIZE; cpu++) + if (CPU_ISSET(cpu, mask)) + return cpu; + + return -1; +} + +static int find_nohz_full_cpu(const cpu_set_t *allowed) +{ + char buf[4096], *cur, *end; + FILE *file; + + file = fopen("/sys/devices/system/cpu/nohz_full", "r"); + if (!file) + return -1; + if (!fgets(buf, sizeof(buf), file)) { + fclose(file); + return -1; + } + fclose(file); + + cur = buf; + while (*cur) { + long first, last; + int cpu; + + while (*cur == ' ' || *cur == '\t' || *cur == ',') + cur++; + if (*cur < '0' || *cur > '9') + break; + + errno = 0; + first = strtol(cur, &end, 10); + if (errno || end == cur || first < 0 || first >= CPU_SETSIZE) + return -1; + cur = end; + last = first; + if (*cur == '-') { + cur++; + errno = 0; + last = strtol(cur, &end, 10); + if (errno || end == cur || last < first) + return -1; + cur = end; + } + + cpu = first_allowed_cpu(allowed, first, last); + if (cpu >= 0) + return cpu; + } + + return -1; +} + +static pid_t start_worker(int cpu) +{ + struct sched_param param = {}; + cpu_set_t mask; + pid_t parent; + pid_t pid; + + parent = getpid(); + pid = fork(); + if (pid != 0) + return pid; + if (prctl(PR_SET_PDEATHSIG, SIGKILL) || getppid() != parent) + _exit(1); + + /* + * Become EXT before touching the target so it stays idle until wakeup. + */ + if (sched_setscheduler(0, SCHED_EXT, ¶m)) + _exit(1); + + CPU_ZERO(&mask); + CPU_SET(cpu, &mask); + if (sched_setaffinity(0, sizeof(mask), &mask)) + _exit(1); + + for (;;) + asm volatile("" ::: "memory"); +} + +static void stop_worker(pid_t pid) +{ + if (pid <= 0) + return; + + kill(pid, SIGKILL); + waitpid(pid, NULL, 0); +} + +static int pause_worker(pid_t pid) +{ + int status; + + if (kill(pid, SIGSTOP)) + return -errno; + if (waitpid(pid, &status, WUNTRACED) != pid) + return -errno; + if (!WIFSTOPPED(status)) + return -ECHILD; + + return 0; +} + +static bool wait_for_counter(const u64 *counter, u64 value, int timeout_ms) +{ + int elapsed; + + for (elapsed = 0; elapsed < timeout_ms; elapsed++) { + if (__atomic_load_n(counter, __ATOMIC_RELAXED) >= value) + return true; + usleep(1000); + } + + return false; +} + +static enum scx_test_status setup(void **ctx_ptr) +{ + struct nohz_tick_ctx *ctx; + cpu_set_t controller_mask; + int cpu; + + ctx = calloc(1, sizeof(*ctx)); + SCX_FAIL_IF(!ctx, "Failed to allocate context"); + if (sched_getaffinity(0, sizeof(ctx->original_mask), + &ctx->original_mask)) { + free(ctx); + SCX_FAIL("Failed to get affinity (%d)", errno); + } + + cpu = find_nohz_full_cpu(&ctx->original_mask); + if (cpu < 0) { + fprintf(stderr, "SKIP: no allowed NOHZ_FULL CPU\n"); + free(ctx); + return SCX_TEST_SKIP; + } + + controller_mask = ctx->original_mask; + CPU_CLR(cpu, &controller_mask); + if (CPU_COUNT(&controller_mask) == 0) { + fprintf(stderr, "SKIP: no housekeeping CPU available\n"); + free(ctx); + return SCX_TEST_SKIP; + } + + ctx->test_cpu = cpu; + ctx->skel = nohz_tick__open(); + if (!ctx->skel) { + free(ctx); + SCX_FAIL("Failed to open skeleton"); + } + + SCX_ENUM_INIT(ctx->skel); + ctx->skel->rodata->test_cpu = cpu; + ctx->skel->struct_ops.nohz_tick_ops->flags |= SCX_OPS_SWITCH_PARTIAL | + SCX_OPS_ENQ_LAST; + if (nohz_tick__load(ctx->skel)) { + nohz_tick__destroy(ctx->skel); + free(ctx); + SCX_FAIL("Failed to load skeleton"); + } + + if (sched_setaffinity(0, sizeof(controller_mask), &controller_mask)) { + nohz_tick__destroy(ctx->skel); + free(ctx); + SCX_FAIL("Failed to move controller off CPU %d (%d)", cpu, errno); + } + + *ctx_ptr = ctx; + return SCX_TEST_PASS; +} + +static enum scx_test_status run(void *ctx_ptr) +{ + struct nohz_tick_ctx *ctx = ctx_ptr; + struct nohz_tick *skel = ctx->skel; + struct bpf_link *link = NULL; + enum scx_test_status status = SCX_TEST_FAIL; + pid_t finite_worker = -1; + pid_t inf_worker = -1; + u64 finite_running; + u64 finite_ticks; + int ret; + + link = bpf_map__attach_struct_ops(skel->maps.nohz_tick_ops); + if (!link) { + SCX_ERR("Failed to attach scheduler"); + goto out; + } + + /* + * Establish SCX_RQ_CAN_STOP_TICK with an infinite-slice task. + */ + inf_worker = start_worker(ctx->test_cpu); + if (inf_worker < 0) { + SCX_ERR("Failed to start infinite-slice worker (%d)", errno); + goto out; + } + if (!wait_for_counter(&skel->bss->nr_inf_running, 1, + PHASE_TIMEOUT_MS)) { + SCX_ERR("Infinite-slice worker was not scheduled"); + goto out; + } + + /* Block without exiting so the rq retains the infinite-slice state. */ + ret = pause_worker(inf_worker); + if (ret) { + SCX_ERR("Failed to stop infinite-slice worker (%d)", ret); + goto out; + } + + /* Let the target enter idle with its tick stopped. */ + usleep(100000); + + /* + * The next EXT task receives a finite slice and must restart the tick. + */ + __atomic_store_n(&skel->bss->finite_phase, true, __ATOMIC_RELEASE); + finite_worker = start_worker(ctx->test_cpu); + if (finite_worker < 0) { + SCX_ERR("Failed to start finite-slice worker (%d)", errno); + goto out; + } + if (!wait_for_counter(&skel->bss->nr_finite_running, 1, + PHASE_TIMEOUT_MS)) { + SCX_ERR("Finite-slice worker was not scheduled"); + goto out; + } + if (!wait_for_counter(&skel->bss->nr_finite_ticks, MIN_FINITE_TICKS, + PHASE_TIMEOUT_MS)) { + SCX_ERR("Finite-slice worker received only %llu scheduler ticks", + (unsigned long long)skel->bss->nr_finite_ticks); + goto out; + } + stop_worker(finite_worker); + finite_worker = -1; + + /* + * Leave the CPU idle after a finite-slice task. The next finite-slice + * task must restart the tick even though the slice type is unchanged. + */ + usleep(100000); + finite_running = __atomic_load_n(&skel->bss->nr_finite_running, + __ATOMIC_RELAXED); + finite_ticks = __atomic_load_n(&skel->bss->nr_finite_ticks, + __ATOMIC_RELAXED); + + finite_worker = start_worker(ctx->test_cpu); + if (finite_worker < 0) { + SCX_ERR("Failed to start second finite-slice worker (%d)", errno); + goto out; + } + if (!wait_for_counter(&skel->bss->nr_finite_running, + finite_running + 1, PHASE_TIMEOUT_MS)) { + SCX_ERR("Second finite-slice worker was not scheduled"); + goto out; + } + if (!wait_for_counter(&skel->bss->nr_finite_ticks, + finite_ticks + MIN_FINITE_TICKS, + PHASE_TIMEOUT_MS)) { + SCX_ERR("Second finite-slice worker received only %llu scheduler ticks", + (unsigned long long)(skel->bss->nr_finite_ticks - + finite_ticks)); + goto out; + } + + if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) { + SCX_ERR("Scheduler exited unexpectedly (kind=%llu code=%lld)", + (unsigned long long)skel->data->uei.kind, + (long long)skel->data->uei.exit_code); + goto out; + } + + fprintf(stderr, "CPU %d received %llu finite-slice ticks\n", + ctx->test_cpu, + (unsigned long long)skel->bss->nr_finite_ticks); + status = SCX_TEST_PASS; +out: + stop_worker(finite_worker); + stop_worker(inf_worker); + if (link) + bpf_link__destroy(link); + return status; +} + +static void cleanup(void *ctx_ptr) +{ + struct nohz_tick_ctx *ctx = ctx_ptr; + + sched_setaffinity(0, sizeof(ctx->original_mask), &ctx->original_mask); + nohz_tick__destroy(ctx->skel); + free(ctx); +} + +struct scx_test nohz_tick = { + .name = "nohz_tick", + .description = "Verify finite EXT slices restart the NOHZ_FULL tick", + .setup = setup, + .run = run, + .cleanup = cleanup, +}; +REGISTER_SCX_TEST(&nohz_tick) From 46aea2c64e110ed1878fa5363f86ddc8fd79c9c4 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Tue, 30 Jun 2026 18:54:12 -0400 Subject: [PATCH 202/479] scsi: ufs: core: tracing: Do not dereference pointers in TP_printk() The trace events in drivers/ufs/core/ufs_trace.h were converted to take a pointer to the hba structure as an argument for the tracepoint and then in TP_printk() the printing of the dev_name from the ring buffer was converted to using the dev dereferenced pointer from the hba saved pointer. This is not allowed as the TP_printk() is executed at the time the trace event is read from /sys/kernel/tracing/trace file. That can happen literally, seconds, minutes, hours, weeks, days, or even months later! There is no guarantee that the hba pointer will still exist by the time it is dereferenced when the "trace" file is read. Instead, save the device name from the hba pointer at the time the tracepoint is called and place it into the ring buffer event. Then the TP_printk() can read the name directly from the ring buffer and remove the possibility that it will read a freed pointer and crash the kernel. This was detected when testing the trace event code that looks for TP_printk() parameters doing illegal derferences[1] [1] https://lore.kernel.org/all/20260630184836.74d477b6@gandalf.local.home/ Cc: stable@vger.kernel.org Fixes: 583e518e7100 ("scsi: ufs: core: Add hba parameter to trace events") Signed-off-by: Steven Rostedt Reviewed-by: Peter Wang Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260630185412.283c26c5@gandalf.local.home Signed-off-by: Martin K. Petersen --- drivers/ufs/core/ufs_trace.h | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/drivers/ufs/core/ufs_trace.h b/drivers/ufs/core/ufs_trace.h index 309ae51b4906..377a3c54b9f5 100644 --- a/drivers/ufs/core/ufs_trace.h +++ b/drivers/ufs/core/ufs_trace.h @@ -89,16 +89,18 @@ TRACE_EVENT(ufshcd_clk_gating, TP_STRUCT__entry( __field(struct ufs_hba *, hba) + __string(dev_name, dev_name(hba->dev)) __field(int, state) ), TP_fast_assign( + __assign_str(dev_name); __entry->hba = hba; __entry->state = state; ), TP_printk("%s: gating state changed to %s", - dev_name(__entry->hba->dev), + __get_str(dev_name), __print_symbolic(__entry->state, UFSCHD_CLK_GATING_STATES)) ); @@ -111,6 +113,7 @@ TRACE_EVENT(ufshcd_clk_scaling, TP_STRUCT__entry( __field(struct ufs_hba *, hba) + __string(dev_name, dev_name(hba->dev)) __string(state, state) __string(clk, clk) __field(u32, prev_state) @@ -119,6 +122,7 @@ TRACE_EVENT(ufshcd_clk_scaling, TP_fast_assign( __entry->hba = hba; + __assign_str(dev_name); __assign_str(state); __assign_str(clk); __entry->prev_state = prev_state; @@ -126,7 +130,7 @@ TRACE_EVENT(ufshcd_clk_scaling, ), TP_printk("%s: %s %s from %u to %u Hz", - dev_name(__entry->hba->dev), __get_str(state), __get_str(clk), + __get_str(dev_name), __get_str(state), __get_str(clk), __entry->prev_state, __entry->curr_state) ); @@ -138,16 +142,18 @@ TRACE_EVENT(ufshcd_auto_bkops_state, TP_STRUCT__entry( __field(struct ufs_hba *, hba) + __string(dev_name, dev_name(hba->dev)) __string(state, state) ), TP_fast_assign( __entry->hba = hba; + __assign_str(dev_name); __assign_str(state); ), TP_printk("%s: auto bkops - %s", - dev_name(__entry->hba->dev), __get_str(state)) + __get_str(dev_name), __get_str(state)) ); DECLARE_EVENT_CLASS(ufshcd_profiling_template, @@ -158,6 +164,7 @@ DECLARE_EVENT_CLASS(ufshcd_profiling_template, TP_STRUCT__entry( __field(struct ufs_hba *, hba) + __string(dev_name, dev_name(hba->dev)) __string(profile_info, profile_info) __field(s64, time_us) __field(int, err) @@ -165,13 +172,14 @@ DECLARE_EVENT_CLASS(ufshcd_profiling_template, TP_fast_assign( __entry->hba = hba; + __assign_str(dev_name); __assign_str(profile_info); __entry->time_us = time_us; __entry->err = err; ), TP_printk("%s: %s: took %lld usecs, err %d", - dev_name(__entry->hba->dev), __get_str(profile_info), + __get_str(dev_name), __get_str(profile_info), __entry->time_us, __entry->err) ); @@ -200,6 +208,7 @@ DECLARE_EVENT_CLASS(ufshcd_template, __field(s64, usecs) __field(int, err) __field(struct ufs_hba *, hba) + __string(dev_name, dev_name(hba->dev)) __field(int, dev_state) __field(int, link_state) ), @@ -208,13 +217,14 @@ DECLARE_EVENT_CLASS(ufshcd_template, __entry->usecs = usecs; __entry->err = err; __entry->hba = hba; + __assign_str(dev_name); __entry->dev_state = dev_state; __entry->link_state = link_state; ), TP_printk( "%s: took %lld usecs, dev_state: %s, link_state: %s, err %d", - dev_name(__entry->hba->dev), + __get_str(dev_name), __entry->usecs, __print_symbolic(__entry->dev_state, UFS_PWR_MODES), __print_symbolic(__entry->link_state, UFS_LINK_STATES), @@ -279,6 +289,7 @@ TRACE_EVENT(ufshcd_command, TP_STRUCT__entry( __field(struct scsi_device *, sdev) __field(struct ufs_hba *, hba) + __string(dev_name, dev_name(&sdev->sdev_dev)) __field(enum ufs_trace_str_t, str_t) __field(unsigned int, tag) __field(u32, doorbell) @@ -291,6 +302,7 @@ TRACE_EVENT(ufshcd_command, ), TP_fast_assign( + __assign_str(dev_name); __entry->sdev = sdev; __entry->hba = hba; __entry->str_t = str_t; @@ -307,7 +319,7 @@ TRACE_EVENT(ufshcd_command, TP_printk( "%s: %s: tag: %u, DB: 0x%x, size: %d, IS: %u, LBA: %llu, opcode: 0x%x (%s), group_id: 0x%x, hwq_id: %d", show_ufs_cmd_trace_str(__entry->str_t), - dev_name(&__entry->sdev->sdev_dev), __entry->tag, + __get_str(dev_name), __entry->tag, __entry->doorbell, __entry->transfer_len, __entry->intr, __entry->lba, (u32)__entry->opcode, str_opcode(__entry->opcode), (u32)__entry->group_id, __entry->hwq_id @@ -322,6 +334,7 @@ TRACE_EVENT(ufshcd_uic_command, TP_STRUCT__entry( __field(struct ufs_hba *, hba) + __string(dev_name, dev_name(hba->dev)) __field(enum ufs_trace_str_t, str_t) __field(u32, cmd) __field(u32, arg1) @@ -331,6 +344,7 @@ TRACE_EVENT(ufshcd_uic_command, TP_fast_assign( __entry->hba = hba; + __assign_str(dev_name); __entry->str_t = str_t; __entry->cmd = cmd; __entry->arg1 = arg1; @@ -340,7 +354,7 @@ TRACE_EVENT(ufshcd_uic_command, TP_printk( "%s: %s: cmd: 0x%x, arg1: 0x%x, arg2: 0x%x, arg3: 0x%x", - show_ufs_cmd_trace_str(__entry->str_t), dev_name(__entry->hba->dev), + show_ufs_cmd_trace_str(__entry->str_t), __get_str(dev_name), __entry->cmd, __entry->arg1, __entry->arg2, __entry->arg3 ) ); @@ -353,6 +367,7 @@ TRACE_EVENT(ufshcd_upiu, TP_STRUCT__entry( __field(struct ufs_hba *, hba) + __string(dev_name, dev_name(hba->dev)) __field(enum ufs_trace_str_t, str_t) __array(unsigned char, hdr, 12) __array(unsigned char, tsf, 16) @@ -361,6 +376,7 @@ TRACE_EVENT(ufshcd_upiu, TP_fast_assign( __entry->hba = hba; + __assign_str(dev_name); __entry->str_t = str_t; memcpy(__entry->hdr, hdr, sizeof(__entry->hdr)); memcpy(__entry->tsf, tsf, sizeof(__entry->tsf)); @@ -369,7 +385,7 @@ TRACE_EVENT(ufshcd_upiu, TP_printk( "%s: %s: HDR:%s, %s:%s", - show_ufs_cmd_trace_str(__entry->str_t), dev_name(__entry->hba->dev), + show_ufs_cmd_trace_str(__entry->str_t), __get_str(dev_name), __print_hex(__entry->hdr, sizeof(__entry->hdr)), show_ufs_cmd_trace_tsf(__entry->tsf_t), __print_hex(__entry->tsf, sizeof(__entry->tsf)) @@ -384,16 +400,18 @@ TRACE_EVENT(ufshcd_exception_event, TP_STRUCT__entry( __field(struct ufs_hba *, hba) + __string(dev_name, dev_name(hba->dev)) __field(u16, status) ), TP_fast_assign( __entry->hba = hba; + __assign_str(dev_name); __entry->status = status; ), TP_printk("%s: status 0x%x", - dev_name(__entry->hba->dev), __entry->status + __get_str(dev_name), __entry->status ) ); From 1d3a742afeb761eaead774691bde1ced699e9a5d Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Tue, 7 Jul 2026 11:08:45 +0800 Subject: [PATCH 203/479] scsi: sg: Report request-table problems when any status is set SG_GET_REQUEST_TABLE reports per-request diagnostic state through sg_req_info::problem. The field is meant to indicate whether there is an error to report for a completed request. sg_fill_request_table() currently combines masked_status, host_status and driver_status with bitwise AND. This only reports a problem when all three status fields are non-zero at the same time. A normal target check condition, for example, has masked_status set while host_status and driver_status may both be zero, so the request is incorrectly reported as clean. Use the same condition as sg_new_read(), which sets SG_INFO_CHECK when any of the three status fields is non-zero. Cc: stable@vger.kernel.org Signed-off-by: Xu Rao Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/54B60C19F7DB8889+20260707030845.970018-1-raoxu@uniontech.com Signed-off-by: Martin K. Petersen --- drivers/scsi/sg.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c index 74cd4e8a61c2..5408f002e6c0 100644 --- a/drivers/scsi/sg.c +++ b/drivers/scsi/sg.c @@ -863,10 +863,9 @@ sg_fill_request_table(Sg_fd *sfp, sg_req_info_t *rinfo) if (val >= SG_MAX_QUEUE) break; rinfo[val].req_state = srp->done + 1; - rinfo[val].problem = - srp->header.masked_status & - srp->header.host_status & - srp->header.driver_status; + rinfo[val].problem = srp->header.masked_status || + srp->header.host_status || + srp->header.driver_status; if (srp->done) rinfo[val].duration = srp->header.duration; From 1bd28625e25be549ee7c47532e7c3ef91c682410 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Tue, 7 Jul 2026 12:23:02 +0530 Subject: [PATCH 204/479] scsi: lpfc: Fix memory leak in lpfc_sli4_driver_resource_setup() The memory allocated for mboxq using mempool_alloc() is not freed in some of the early exit error paths. Fix that by moving the mempool_free() call to an earlier point after last use. Fixes: d79c9e9d4b3d ("scsi: lpfc: Support dynamic unbounded SGL lists on G7 hardware.") Cc: stable@vger.kernel.org Signed-off-by: Abdun Nihaal Reviewed-by: Justin Tee Link: https://patch.msgid.link/20260707065304.949135-1-nihaal@cse.iitm.ac.in Signed-off-by: Martin K. Petersen --- drivers/scsi/lpfc/lpfc_init.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/scsi/lpfc/lpfc_init.c b/drivers/scsi/lpfc/lpfc_init.c index 82af59c913e9..23355f12fbff 100644 --- a/drivers/scsi/lpfc/lpfc_init.c +++ b/drivers/scsi/lpfc/lpfc_init.c @@ -8189,6 +8189,7 @@ lpfc_sli4_driver_resource_setup(struct lpfc_hba *phba) mempool_free(mboxq, phba->mbox_mem_pool); goto out_free_bsmbx; } + mempool_free(mboxq, phba->mbox_mem_pool); /* * 1 for cmd, 1 for rsp, NVME adds an extra one @@ -8311,8 +8312,6 @@ lpfc_sli4_driver_resource_setup(struct lpfc_hba *phba) goto out_free_sg_dma_buf; } - mempool_free(mboxq, phba->mbox_mem_pool); - /* Verify OAS is supported */ lpfc_sli4_oas_verify(phba); From 7b69729046a4c58f4cb457184e5ac4aaa179bff4 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Wed, 24 Jun 2026 14:19:10 +0800 Subject: [PATCH 205/479] KVM: s390: pci: Fix GISC refcount leak on AIF enable failure kvm_s390_gisc_register() registers the guest ISC before pinning the guest interrupt forwarding pages and allocating the AISB bit. If any of the later setup steps fails, the function unwinds the pinned pages and other local state, but does not unregister the GISC reference. Add the missing kvm_s390_gisc_unregister() to the error unwind path. Fixes: 3c5a1b6f0a18 ("KVM: s390: pci: provide routines for enabling/disabling interrupt forwarding") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Reviewed-by: Matthew Rosato Tested-by: Matthew Rosato Acked-by: Claudio Imbrenda Reviewed-by: Christian Borntraeger Signed-off-by: Claudio Imbrenda Message-ID: <20260624061910.2794734-1-haoxiang_li2024@163.com> Signed-off-by: Christian Borntraeger --- arch/s390/kvm/pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/s390/kvm/pci.c b/arch/s390/kvm/pci.c index 5b075c38998e..686113be0530 100644 --- a/arch/s390/kvm/pci.c +++ b/arch/s390/kvm/pci.c @@ -328,6 +328,7 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, unpin1: unpin_user_page(aibv_page); out: + kvm_s390_gisc_unregister(kvm, fib->fmt0.isc); return rc; } From 866d03de6def89c386cdfd457b28a1f566e02565 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Thu, 2 Jul 2026 17:23:59 +0200 Subject: [PATCH 206/479] KVM: s390: vsie: Avoid potential deadlock with real spaces The natural lock ordering is mmu_lock -> children_lock, but in gmap_create_shadow() the reverse order is used when handling shadowing of real address spaces. Convert the inner locking of kvm->mmu_lock to a trylock; return -EAGAIN if the lock is busy, and let the caller try again. This path is not expected to happen in real-life scenarios, so its performance is not important. Fixes: a2c17f9270cc ("KVM: s390: New gmap code") Signed-off-by: Claudio Imbrenda Reviewed-by: Christian Borntraeger Signed-off-by: Christian Borntraeger --- arch/s390/kvm/gmap.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/s390/kvm/gmap.c b/arch/s390/kvm/gmap.c index 298fbaecec28..8abb4f55b306 100644 --- a/arch/s390/kvm/gmap.c +++ b/arch/s390/kvm/gmap.c @@ -1374,8 +1374,13 @@ struct gmap *gmap_create_shadow(struct kvm_s390_mmu_cache *mc, struct gmap *pare /* Only allow one real-space gmap shadow. */ list_for_each_entry(sg, &parent->children, list) { if (sg->guest_asce.r) { - scoped_guard(write_lock, &parent->kvm->mmu_lock) + if (write_trylock(&parent->kvm->mmu_lock)) { gmap_unshadow(sg); + write_unlock(&parent->kvm->mmu_lock); + } else { + gmap_put(new); + return ERR_PTR(-EAGAIN); + } break; } } From 4d4a21e38f1b87a76b3e63d4f837ff4e9b52d5a6 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Thu, 2 Jul 2026 17:24:05 +0200 Subject: [PATCH 207/479] KVM: s390: Fix dat_crste_walk_range() early return If a walk entry handler for a lower level returns a value, dat_crste_walk_range() will not return immediately, but instead loop again and move to the next entry. This means that some entries are potentially skipped, and early return is ignored. Skipped entries might lead to all kinds of issues, given that the caller expects them to not be skipped. Early return is often used to interrupt a walk when a rescheduling is needed; if it is ignored it can lead to stalls. Fix by breaking from the loop immediately if the walk to a lower level returned non-zero. Fixes: 2db149a0a6c5 ("KVM: s390: KVM page table management functions: walks") Signed-off-by: Claudio Imbrenda Reviewed-by: Christian Borntraeger Signed-off-by: Christian Borntraeger --- arch/s390/kvm/dat.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/s390/kvm/dat.c b/arch/s390/kvm/dat.c index 5f1960ec982d..ed4259d17629 100644 --- a/arch/s390/kvm/dat.c +++ b/arch/s390/kvm/dat.c @@ -570,6 +570,8 @@ static long dat_crste_walk_range(gfn_t start, gfn_t end, struct crst_table *tabl else if (walk->ops->pte_entry) rc = dat_pte_walk_range(max(start, cur), min(end, next), dereference_pmd(crste.pmd), walk); + if (rc) + break; } } return rc; From 9489220fe0e69d2ca141e5062dd3ef3e2e55959f Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Thu, 2 Jul 2026 17:24:06 +0200 Subject: [PATCH 208/479] KVM: s390: Improve kvm_s390_vm_stop_migration() There is no need to clear cmma-dirty state if the VM is not using CMMA. Skip the CMMA-related code if CMMA is not in use. Fixes: 6cfd47f91f6a ("KVM: s390: Fix cmma dirty tracking") Fixes: 190df4a212a7 ("KVM: s390: CMMA tracking, ESSA emulation, migration mode") Signed-off-by: Claudio Imbrenda Reviewed-by: Christian Borntraeger Signed-off-by: Christian Borntraeger --- arch/s390/kvm/kvm-s390.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index 23c817595e28..150b5dd2170e 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -1280,8 +1280,10 @@ static int kvm_s390_vm_stop_migration(struct kvm *kvm) * PGSTEs might have cmma_d set. */ WRITE_ONCE(kvm->arch.migration_mode, 0); - if (kvm->arch.use_cmma) - kvm_s390_sync_request_broadcast(kvm, KVM_REQ_STOP_MIGRATION); + if (!kvm->arch.use_cmma) + return 0; + + kvm_s390_sync_request_broadcast(kvm, KVM_REQ_STOP_MIGRATION); /* Clear cmma_d on all existing PGSTEs and set cmma_dirty_pages to 0. */ gmap_set_cmma_all_clean(kvm->arch.gmap); atomic64_set(&kvm->arch.cmma_dirty_pages, 0); From 97272a5704bf13369bba43951ad9fe8f806ccc7f Mon Sep 17 00:00:00 2001 From: Kailang Yang Date: Thu, 9 Jul 2026 14:57:06 +0800 Subject: [PATCH 209/479] ALSA: hda/realtek - Fixed Headphone noise issue for Dell QCM1255 This platform booted with Ubuntu 24.04 with Pipewire audio server. So, it has pop noise with headphone. But it's normal with Pulseaudio server. This patch was the workaround. Connect the headphones to DAC 0x2. The popping sound will disappear. Signed-off-by: Kailang Yang Link: https://lore.kernel.org/34b990cb56914148ba02fa8e9d176479@realtek.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 02cc3f6b378d..2fc25be99ca6 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4172,6 +4172,7 @@ enum { ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX, ALC287_FIXUP_ACER_MICMUTE_LED, + ALC236_FIXUP_DELL_HP_POP_NOISE, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -6779,6 +6780,10 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC2XX_FIXUP_HEADSET_MIC, }, + [ALC236_FIXUP_DELL_HP_POP_NOISE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_invalidate_dacs, + }, }; static const struct hda_quirk alc269_fixup_tbl[] = { @@ -6930,6 +6935,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1028, 0x0cc3, "Dell Oasis 14 Low Weight MTL-U", ALC289_FIXUP_DELL_CS35L41_SPI_2), SND_PCI_QUIRK(0x1028, 0x0cc4, "Dell Oasis 16 MTL-H/U", ALC289_FIXUP_DELL_CS35L41_SPI_2), SND_PCI_QUIRK(0x1028, 0x0cc5, "Dell Oasis 14", ALC289_FIXUP_RTK_AMP_DUAL_SPK), + SND_PCI_QUIRK(0x1028, 0x0e6b, "Dell Pro QCM1255", ALC236_FIXUP_DELL_HP_POP_NOISE), + SND_PCI_QUIRK(0x1028, 0x0e6d, "Dell Pro Micro QCM1255", ALC236_FIXUP_DELL_HP_POP_NOISE), SND_PCI_QUIRK(0x1028, 0x164a, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2), From d595255241e5fec0c94adeebf2565524398e37c5 Mon Sep 17 00:00:00 2001 From: Eckhart Mohr Date: Wed, 8 Jul 2026 15:21:06 +0200 Subject: [PATCH 210/479] ALSA: hda/realtek: Add quirk for TongFang X6xx45xU Fix microphone detection on built in headphone jack for some devices. Signed-off-by: Eckhart Mohr Cc: stable@vger.kernel.org Signed-off-by: Werner Sembach Link: https://patch.msgid.link/20260708132135.102680-1-wse@tuxedocomputers.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 2fc25be99ca6..b47177d734c7 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7918,6 +7918,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1d05, 0x300f, "TongFang X6AR5xxY", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d05, 0x3019, "TongFang X6FR5xxY", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d05, 0x3031, "TongFang X6AR55xU", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1d05, 0x3034, "TongFang X6xx45xU", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d17, 0x3288, "Haier Boyue G42", ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS), SND_PCI_QUIRK(0x1d72, 0x1602, "RedmiBook", ALC255_FIXUP_XIAOMI_HEADSET_MIC), SND_PCI_QUIRK(0x1d72, 0x1701, "XiaomiNotebook Pro", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE), From bf1deecccf210d1dd84e85cd4a45070888583984 Mon Sep 17 00:00:00 2001 From: Steve Dunnagan Date: Wed, 1 Jul 2026 15:59:20 -0400 Subject: [PATCH 211/479] firmware: arm_scmi: Use 64-bit division for clock rate rounding SCMI clock range descriptors report rates as 64-bit values. When handling a range clock, scmi_clock_determine_rate() rounds the requested rate up to the next supported step using the SCMI RATE_STEP value. The current code uses div64_ul() for this calculation. Since div64_ul() takes an unsigned long divisor, the 64-bit RATE_STEP value can be truncated on 32-bit builds. In the worst case, a non-zero 64-bit step can be narrowed to zero before the division. Store RATE_STEP in a u64, reject a malformed zero step, and use DIV64_U64_ROUND_UP() so the divisor is handled as a 64-bit value. This does not change behavior for valid firmware reporting a non-zero step that fits in unsigned long. Tested on Xunlong Orange Pi 5 Plus / RK3588 with SCMI over SMC. SCMI clocks probed successfully before and after the change. SCMI-backed CPU clocks were exercised through cpufreq-dt by switching each CPU policy between its lowest and highest available OPP. Fixes: ecde921eb460 ("firmware: arm_scmi: Add clock determine_rate operation") Signed-off-by: Steve Dunnagan Link: https://patch.msgid.link/20260701195923.444270-1-sdunnaga@redhat.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/clock.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index 42e666a628c7..0278705d809e 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -718,7 +718,7 @@ static int scmi_clock_rate_set(const struct scmi_protocol_handle *ph, static int scmi_clock_determine_rate(const struct scmi_protocol_handle *ph, u32 clk_id, unsigned long *rate) { - u64 fmin, fmax, ftmp; + u64 fmin, fmax, ftmp, step; struct scmi_clock_info *clk; struct scmi_clock_desc *clkd; struct clock_info *ci = ph->get_priv(ph); @@ -749,11 +749,14 @@ static int scmi_clock_determine_rate(const struct scmi_protocol_handle *ph, return 0; } - ftmp = *rate - fmin; - ftmp += clkd->r.rates[RATE_STEP] - 1; /* to round up */ - ftmp = div64_ul(ftmp, clkd->r.rates[RATE_STEP]); + step = clkd->r.rates[RATE_STEP]; + if (!step) + return -EINVAL; - *rate = ftmp * clkd->r.rates[RATE_STEP] + fmin; + ftmp = *rate - fmin; + ftmp = DIV64_U64_ROUND_UP(ftmp, step); + + *rate = ftmp * step + fmin; return 0; } From a4447c0693830d5ecadd6e755cb7fdc55d86aacc Mon Sep 17 00:00:00 2001 From: Pushpendra Singh Date: Wed, 8 Jul 2026 12:53:39 +0530 Subject: [PATCH 212/479] firmware: arm_scmi: Rate-limit queue-full warnings in IRQ context The scmi_notify() function is called from interrupt context to queue received notification events onto a per-protocol kfifo. When the kfifo is full, it logs a warning via dev_warn() for every dropped event. Under conditions where the platform sends a burst of SCMI notifications faster than the deferred worker can drain the queue, this results in a flood of dev_warn() calls from IRQ context. Each call acquires the console lock and may execute blocking console writes, causing the CPU to be held in interrupt context for an extended period and leading to observable system stalls. Fix this by switching to dev_warn_ratelimited() to limit the frequency of log messages when the notification queue is full. This reduces console overhead in interrupt context and prevents CPU stalls caused by excessive logging, while still preserving diagnostic visibility. Fixes: bd31b249692e ("firmware: arm_scmi: Add notification dispatch and delivery") Signed-off-by: Pushpendra Singh Link: https://patch.msgid.link/20260708072339.3021140-1-pushpendra.singh@oss.qualcomm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/notify.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c index 40ec184eedae..0a192cf2deab 100644 --- a/drivers/firmware/arm_scmi/notify.c +++ b/drivers/firmware/arm_scmi/notify.c @@ -600,9 +600,9 @@ int scmi_notify(const struct scmi_handle *handle, u8 proto_id, u8 evt_id, return -EINVAL; } if (kfifo_avail(&r_evt->proto->equeue.kfifo) < sizeof(eh) + len) { - dev_warn(handle->dev, - "queue full, dropping proto_id:%d evt_id:%d ts:%lld\n", - proto_id, evt_id, ktime_to_ns(ts)); + dev_warn_ratelimited(handle->dev, + "queue full, dropping proto_id:%d evt_id:%d ts:%lld\n", + proto_id, evt_id, ktime_to_ns(ts)); return -ENOMEM; } From 3e3aa6da87d30a0064a17b836685cd43c90a3572 Mon Sep 17 00:00:00 2001 From: Matthew Rosato Date: Thu, 9 Jul 2026 09:54:04 -0400 Subject: [PATCH 213/479] KVM: s390: pci: Fix handling of AIF enable without AISB When a guest seeks to register IRQs without a summary bit specified, ensure that the associated GAITE then stores 0 for the guest AISB location instead of virt_to_phys(page_address(NULL)). Fixes: 3c5a1b6f0a18 ("KVM: s390: pci: provide routines for enabling/disabling interrupt forwarding") Cc: stable@vger.kernel.org Reviewed-by: Farhan Ali Signed-off-by: Matthew Rosato Signed-off-by: Christian Borntraeger --- arch/s390/kvm/pci.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/arch/s390/kvm/pci.c b/arch/s390/kvm/pci.c index 686113be0530..720bb58cabe2 100644 --- a/arch/s390/kvm/pci.c +++ b/arch/s390/kvm/pci.c @@ -300,9 +300,14 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, gaite->gisc = fib->fmt0.isc; gaite->count++; - gaite->aisbo = fib->fmt0.aisbo; - gaite->aisb = virt_to_phys(page_address(aisb_page) + (fib->fmt0.aisb & - ~PAGE_MASK)); + if (fib->fmt0.sum == 1) { + gaite->aisbo = fib->fmt0.aisbo; + gaite->aisb = virt_to_phys(page_address(aisb_page) + + (fib->fmt0.aisb & ~PAGE_MASK)); + } else { + gaite->aisbo = 0; + gaite->aisb = 0; + } aift->kzdev[zdev->aisb] = zdev->kzdev; spin_unlock_irq(&aift->gait_lock); From 8008d6b59a659982b9f10916bb8712bf93bc7c46 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 8 Jul 2026 22:26:51 -0400 Subject: [PATCH 214/479] lib/crypto: docs: Fix some sentence fragments Currently, the section about the library API for each algorithm begins with a noun phrase that was intended to serve as an elaboration on the title. It's better to use complete sentences. Suggested-by: Thomas Huth Reviewed-by: Thomas Huth Link: https://patch.msgid.link/20260709022651.44216-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- .../crypto/libcrypto-blockcipher.rst | 6 +-- Documentation/crypto/libcrypto-hash.rst | 38 ++++++++++--------- Documentation/crypto/libcrypto-signature.rst | 2 +- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/Documentation/crypto/libcrypto-blockcipher.rst b/Documentation/crypto/libcrypto-blockcipher.rst index dd5ce2f8b515..fd85e27fab8d 100644 --- a/Documentation/crypto/libcrypto-blockcipher.rst +++ b/Documentation/crypto/libcrypto-blockcipher.rst @@ -6,14 +6,14 @@ Block ciphers AES --- -Support for the AES block cipher. +This API provides support for the AES block cipher. .. kernel-doc:: include/crypto/aes.h DES --- -Support for the DES block cipher. This algorithm is obsolete and is supported -only for backwards compatibility. +This API provides support for the DES block cipher. This algorithm is obsolete +and is supported only for backwards compatibility. .. kernel-doc:: include/crypto/des.h diff --git a/Documentation/crypto/libcrypto-hash.rst b/Documentation/crypto/libcrypto-hash.rst index 4248e6fdc952..fa4c54236af6 100644 --- a/Documentation/crypto/libcrypto-hash.rst +++ b/Documentation/crypto/libcrypto-hash.rst @@ -6,81 +6,83 @@ Hash functions, MACs, and XOFs AES-CMAC and AES-XCBC-MAC ------------------------- -Support for the AES-CMAC and AES-XCBC-MAC message authentication codes. +This API provides support for the AES-CMAC and AES-XCBC-MAC message +authentication codes. .. kernel-doc:: include/crypto/aes-cbc-macs.h BLAKE2b ------- -Support for the BLAKE2b cryptographic hash function. +This API provides support for the BLAKE2b cryptographic hash function. .. kernel-doc:: include/crypto/blake2b.h BLAKE2s ------- -Support for the BLAKE2s cryptographic hash function. +This API provides support for the BLAKE2s cryptographic hash function. .. kernel-doc:: include/crypto/blake2s.h GHASH and POLYVAL ----------------- -Support for the GHASH and POLYVAL universal hash functions. These algorithms -are used only as internal components of other algorithms. +This API provides support for the GHASH and POLYVAL universal hash functions. +These algorithms are used only as internal components of other algorithms. .. kernel-doc:: include/crypto/gf128hash.h MD5 --- -Support for the MD5 cryptographic hash function and HMAC-MD5. This algorithm is -obsolete and is supported only for backwards compatibility. +This API provides support for the MD5 cryptographic hash function and HMAC-MD5. +This algorithm is obsolete and is supported only for backwards compatibility. .. kernel-doc:: include/crypto/md5.h NH -- -Support for the NH universal hash function. This algorithm is used only as an -internal component of other algorithms. +This API provides support for the NH universal hash function. This algorithm is +used only as an internal component of other algorithms. .. kernel-doc:: include/crypto/nh.h Poly1305 -------- -Support for the Poly1305 universal hash function. This algorithm is used only -as an internal component of other algorithms. +This API provides support for the Poly1305 universal hash function. This +algorithm is used only as an internal component of other algorithms. .. kernel-doc:: include/crypto/poly1305.h SHA-1 ----- -Support for the SHA-1 cryptographic hash function and HMAC-SHA1. This algorithm -is obsolete and is supported only for backwards compatibility. +This API provides support for the SHA-1 cryptographic hash function and +HMAC-SHA1. This algorithm is obsolete and is supported only for backwards +compatibility. .. kernel-doc:: include/crypto/sha1.h SHA-2 ----- -Support for the SHA-2 family of cryptographic hash functions, including SHA-224, -SHA-256, SHA-384, and SHA-512. This also includes their corresponding HMACs: -HMAC-SHA224, HMAC-SHA256, HMAC-SHA384, and HMAC-SHA512. +This API provides support for the SHA-2 family of cryptographic hash functions, +including SHA-224, SHA-256, SHA-384, and SHA-512. This also includes their +corresponding HMACs: HMAC-SHA224, HMAC-SHA256, HMAC-SHA384, and HMAC-SHA512. .. kernel-doc:: include/crypto/sha2.h SHA-3 ----- -The SHA-3 functions are documented in :ref:`sha3`. +The SHA-3 API is documented in :ref:`sha3`. SM3 --- -Support for the SM3 cryptographic hash function. +This API provides support for the SM3 cryptographic hash function. .. kernel-doc:: include/crypto/sm3.h diff --git a/Documentation/crypto/libcrypto-signature.rst b/Documentation/crypto/libcrypto-signature.rst index e80d59fa51b6..2a6dc793f0de 100644 --- a/Documentation/crypto/libcrypto-signature.rst +++ b/Documentation/crypto/libcrypto-signature.rst @@ -6,6 +6,6 @@ Digital signature algorithms ML-DSA ------ -Support for the ML-DSA digital signature algorithm. +This API provides support for the ML-DSA digital signature algorithm. .. kernel-doc:: include/crypto/mldsa.h From 9665e22579ee4324a14e035b5b29d3d1fdc929e2 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 8 Jul 2026 22:27:47 -0400 Subject: [PATCH 215/479] lib/crypto: docs: Improve introduction sentence Make it clear that lib/crypto/ is a kernel-internal library. It's easy for people to come across this page, especially the HTML version online, without that context. Reviewed-by: Thomas Huth Link: https://patch.msgid.link/20260709022747.44635-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- Documentation/crypto/libcrypto.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/crypto/libcrypto.rst b/Documentation/crypto/libcrypto.rst index a1557d45b0e5..0733e603d229 100644 --- a/Documentation/crypto/libcrypto.rst +++ b/Documentation/crypto/libcrypto.rst @@ -4,8 +4,9 @@ Crypto library ============== -``lib/crypto/`` provides faster and easier access to cryptographic algorithms -than the traditional crypto API. +The Linux kernel's crypto library (``lib/crypto/``) provides kernel-internal +users of cryptographic algorithms with faster and easier access to those +algorithms than the traditional kernel crypto API. Each cryptographic algorithm is supported via a set of dedicated functions. "Crypto agility", where needed, is left to calling code. From e073f1238ecaea366f53e98724c40b31856da56a Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 8 Jul 2026 22:29:54 -0400 Subject: [PATCH 216/479] crypto: aes - Fix conditions for selecting MAC dependencies Starting in commit 7137cbf2b5c9 ("crypto: aes - Add cmac, xcbc, and cbcmac algorithms using library"), the aes module (CRYPTO_AES) supports CBC based MACs using the corresponding library functions. To avoid including unneeded functionality, that support honors the existing CRYPTO_CMAC, CRYPTO_XCBC, and CRYPTO_CCM kconfig options. The dependencies are selected if at least one of those is enabled. However, the select statements don't correctly handle the case where CRYPTO_AES=y and (for example) CRYPTO_CMAC=m. In that case the dependencies get selected at level 'm', due to how the kconfig language works. That causes a linker error. Fix this by changing the selection conditions to use '!= n'. A similar issue also exists for CRYPTO_LIB_AES's conditional selection of CRYPTO_LIB_UTILS. The same '!= n' would work, but instead just make CRYPTO_LIB_AES always select CRYPTO_LIB_UTILS. CRYPTO_LIB_UTILS is lightweight, and it's needed by most AES modes and many other things. Fixes: 7137cbf2b5c9 ("crypto: aes - Add cmac, xcbc, and cbcmac algorithms using library") Fixes: 309a7e514da7 ("lib/crypto: aes: Add support for CBC-based MACs") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260709022954.45113-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- crypto/Kconfig | 4 ++-- lib/crypto/Kconfig | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crypto/Kconfig b/crypto/Kconfig index f1e372195273..b61401bd3ef6 100644 --- a/crypto/Kconfig +++ b/crypto/Kconfig @@ -358,8 +358,8 @@ config CRYPTO_AES tristate "AES (Advanced Encryption Standard)" select CRYPTO_ALGAPI select CRYPTO_LIB_AES - select CRYPTO_LIB_AES_CBC_MACS if CRYPTO_CMAC || CRYPTO_XCBC || CRYPTO_CCM - select CRYPTO_HASH if CRYPTO_CMAC || CRYPTO_XCBC || CRYPTO_CCM + select CRYPTO_LIB_AES_CBC_MACS if CRYPTO_CMAC != n || CRYPTO_XCBC != n || CRYPTO_CCM != n + select CRYPTO_HASH if CRYPTO_CMAC != n || CRYPTO_XCBC != n || CRYPTO_CCM != n help AES cipher algorithms (Rijndael)(FIPS-197, ISO/IEC 18033-3) diff --git a/lib/crypto/Kconfig b/lib/crypto/Kconfig index 591c1c2a7fb3..83d4c95e079e 100644 --- a/lib/crypto/Kconfig +++ b/lib/crypto/Kconfig @@ -8,8 +8,7 @@ config CRYPTO_LIB_UTILS config CRYPTO_LIB_AES tristate - # Select dependencies of modes that are part of libaes. - select CRYPTO_LIB_UTILS if CRYPTO_LIB_AES_CBC_MACS + select CRYPTO_LIB_UTILS config CRYPTO_LIB_AES_ARCH bool From f86a7c96406d406a66f3d7653eca86aa576fcf6b Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 9 Jul 2026 13:12:24 +0100 Subject: [PATCH 217/479] ALSA: hda/cirrus_scodec: Make Kconfig visible if KUNIT Make the Kconfig item for cirrus_scodec visible if CONFIG_KUNIT is enabled. This is so that its KUnit test can be enabled by KUnit scripts without requiring a large amount of irrelevant additional components. The general rule for KUNIT_ALL_TESTS is that it should only enable tests for components that are already selected. However, the UML environment does not support ACPI, which means the HDA codec drivers that use cirrus_scodec cannot be selected. But cirrus_scodec does not need ACPI. By making the Kconfig option visible if CONFIG_KUNIT, the KUnit test can be enabled with only the minimal set of functionality that is required for cirrus_scodec. This is still compliant with the KUNIT_ALL_TESTS rule "only tests for enabled modules" because by default cirrus_scodec will only be enabled if the drivers that use it are enabled. It must be intentionally enabled to force it to be included for testing. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260709121224.614350-1-rf@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig index 2a2e8804bf9e..1cfd83e251e4 100644 --- a/sound/hda/codecs/side-codecs/Kconfig +++ b/sound/hda/codecs/side-codecs/Kconfig @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only config SND_HDA_CIRRUS_SCODEC - tristate + tristate "Cirrus side-codec library" if KUNIT config SND_HDA_CIRRUS_SCODEC_KUNIT_TEST tristate "KUnit test for Cirrus side-codec library" if !KUNIT_ALL_TESTS From 8ca0016a9b799d8366b32da7e729fcf56627516f Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 9 Jul 2026 13:22:11 +0100 Subject: [PATCH 218/479] ALSA: hda: MAINTAINERS: Fix missing cirrus* file reference When the HDA source was reorganized some of the cirrus* files were moved into a new 'side-codecs' subdirectory. But MAINTAINERS wasn't updated to add a cirrus* file reference to cover these moved files. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260709122211.615785-1-rf@opensource.cirrus.com Signed-off-by: Takashi Iwai --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 4a8b0fd665ce..bf59602eec39 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6209,6 +6209,7 @@ F: include/dt-bindings/sound/cs* F: include/linux/mfd/cs42l43* F: include/sound/cs* F: sound/hda/codecs/cirrus* +F: sound/hda/codecs/side-codecs/cirrus* F: sound/hda/codecs/side-codecs/cs* F: sound/hda/codecs/side-codecs/hda_component* F: sound/soc/codecs/cs* From 7cbd0c4cebe4c9f678d15e6b9ba975e1155a107f Mon Sep 17 00:00:00 2001 From: Matt Bobrowski Date: Thu, 9 Jul 2026 02:53:16 +0000 Subject: [PATCH 219/479] bpf: Fix UAF in sock clone early bailouts Similar to recent commit 9b51a6155d14 ("bpf,fork: wipe ->bpf_storage before bailouts that access it"), sk_clone() performs an initial shallow copy of the socket field ->sk_bpf_storage via sock_copy() for the cloned socket newsk. If sk_clone() bails out early (e.g. if sk_filter_charge() fails) prior to calling bpf_sk_storage_clone(), newsk->sk_bpf_storage still points to the parent socket's BPF local storage. When newsk is subsequently freed via sk_free(), the deallocation path (__sk_destruct() -> bpf_sk_storage_free()) destroys the parent socket's BPF local storage, leading to a use-after-free (UAF) on the parent socket. Fix this by resetting newsk->sk_bpf_storage to NULL immediately after sock_copy() in sk_clone(), and remove the now redundant initialization from bpf_sk_storage_clone(). Fixes: 6ac99e8f23d4 ("bpf: Introduce bpf sk local storage") Fixes: f12dd75959b0 ("bpf: net: Set sk_bpf_storage back to NULL for cloned sk") Signed-off-by: Matt Bobrowski Signed-off-by: Daniel Borkmann Reviewed-by: Kuniyuki Iwashima Acked-by: Daniel Borkmann Link: https://lore.kernel.org/bpf/20260709025316.999913-1-mattbobrowski@google.com --- net/core/bpf_sk_storage.c | 2 -- net/core/sock.c | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/net/core/bpf_sk_storage.c b/net/core/bpf_sk_storage.c index ecd659f79fd4..1d295a8769fa 100644 --- a/net/core/bpf_sk_storage.c +++ b/net/core/bpf_sk_storage.c @@ -158,8 +158,6 @@ int bpf_sk_storage_clone(const struct sock *sk, struct sock *newsk) struct bpf_local_storage_elem *selem; int ret = 0; - RCU_INIT_POINTER(newsk->sk_bpf_storage, NULL); - rcu_read_lock_dont_migrate(); sk_storage = rcu_dereference(sk->sk_bpf_storage); diff --git a/net/core/sock.c b/net/core/sock.c index 8a59bfaa8096..498a57f34f5b 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -2492,6 +2492,9 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority, sock_copy(newsk, sk); newsk->sk_prot_creator = prot; +#ifdef CONFIG_BPF_SYSCALL + RCU_INIT_POINTER(newsk->sk_bpf_storage, NULL); +#endif /* SANITY */ if (likely(newsk->sk_net_refcnt)) { From 27d80e0f8b8dff97503fc0061754b1d3800cb961 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 7 Jul 2026 19:19:55 +0200 Subject: [PATCH 220/479] cpufreq: intel_pstate: Set non-turbo capacity to HWP_GUARANTEED_PERF() Setting cpu->capacity_perf to cpu->pstate.max_pstate_physical in the "no turbo" case is inconsistent with what happens elsewhere in the driver and causes arch_scale_cpu_capacity() to be incorrect. It also skews arch_scale_freq_capacity() which ends up differing from 1024 for the guaranteed P-state. Address that by setting capacity_perf to HWP_GUARANTEED_PERF() in the "no turbo" case. Fixes: 929ebc93ccaa ("cpufreq: intel_pstate: Set asymmetric CPU capacity on hybrid systems") Signed-off-by: Rafael J. Wysocki Tested-by: Ricardo Neri Cc: All applicable Link: https://patch.msgid.link/12928972.O9o76ZdvQC@rafael.j.wysocki --- drivers/cpufreq/intel_pstate.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 5a0eeb84d382..6e984c114d96 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -1058,12 +1058,14 @@ static void hybrid_clear_cpu_capacity(unsigned int cpunum) static void hybrid_get_capacity_perf(struct cpudata *cpu) { + u64 hwp_cap = READ_ONCE(cpu->hwp_cap_cached); + if (READ_ONCE(global.no_turbo)) { - cpu->capacity_perf = cpu->pstate.max_pstate_physical; + cpu->capacity_perf = HWP_GUARANTEED_PERF(hwp_cap); return; } - cpu->capacity_perf = HWP_HIGHEST_PERF(READ_ONCE(cpu->hwp_cap_cached)); + cpu->capacity_perf = HWP_HIGHEST_PERF(hwp_cap); } static void hybrid_set_capacity_of_cpus(void) From d2d5c129d07ea8eb91cd8a8633b5774116c4d171 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 7 Jul 2026 19:25:39 +0200 Subject: [PATCH 221/479] cpufreq: Make cpufreq_update_pressure() fall back to cpuinfo.max_freq If arch_scale_freq_ref() is not defined for a given arch (like x86, for example), cpufreq_update_pressure() will always set cpufreq_pressure to zero for all CPUs in the system, which is generally problematic on systems with asymmetric capacity [1]. However, in the absence of arch_scale_freq_ref(), it is reasonable to assume that cpuinfo.max_freq is the maximum sustainable frequency for the given cpufreq policy. Moreover, there are cases in which arch_scale_freq_ref() would need to be defined to return essentially the cpuinfo.max_freq value anyway (for example, intel_pstate on hybrid platforms). For the above reasons, update cpufreq_update_pressure() to fall back to using cpuinfo.max_freq as the reference frequency if zero is returned by arch_scale_freq_ref(). Fixes: 75d659317bb1 ("cpufreq: Add a cpufreq pressure feedback for the scheduler") Link: https://lore.kernel.org/lkml/CAKfTPtBuRLfYNnR4w--cFZYZy-R8gaPEgVwCcaMmbCcJ2H-muQ@mail.gmail.com/ [1] Signed-off-by: Rafael J. Wysocki Acked-by: Viresh Kumar Reviewed-by: Zhongqiu Han Tested-by: Ricardo Neri # cluster scheduling Acked-by: Vincent Guittot Link: https://patch.msgid.link/5086499.GXAFRqVoOG@rafael.j.wysocki --- drivers/cpufreq/cpufreq.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 507224c9ecd3..b898b6544069 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -2586,6 +2586,9 @@ static void cpufreq_update_pressure(struct cpufreq_policy *policy) cpu = cpumask_first(policy->related_cpus); max_freq = arch_scale_freq_ref(cpu); + if (!max_freq) + max_freq = policy->cpuinfo.max_freq; + capped_freq = policy->max; /* From db4e9defd2e8620abee04cfe5809c0bcd6ecf06a Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 9 Jul 2026 11:08:13 -1000 Subject: [PATCH 222/479] sched_ext: Record an error on errno-only sub-enable failure scx_sub_enable_workfn() has several failure paths that only return an errno (e.g. -ENOMEM from an allocation) and jump to err_disable without calling scx_error(). scx_flush_disable_work() runs the disable, and thus ops.exit(), only when an error has been recorded, so an errno-only failure leaves the half-initialized sub-scheduler linked. Record an error at the err_disable sink so every errno-only failure runs the disable path. Fixes: ebeca1f930ea ("sched_ext: Introduce cgroup sub-sched support") Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 2dc6977d7984..40cef77394ac 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -7746,6 +7746,12 @@ static void scx_sub_enable_workfn(struct kthread_work *work) percpu_up_write(&scx_fork_rwsem); err_disable: mutex_unlock(&scx_enable_mutex); + /* + * Some enable failures only return an errno (e.g. -ENOMEM from an + * allocation) without calling scx_error(). Record it so + * scx_flush_disable_work() runs the disable and ops.exit() fires. + */ + scx_error(sch, "scx_sub_enable() failed (%d)", ret); scx_flush_disable_work(sch); cmd->ret = 0; } From 49b3378a750cf85112e656d003145d4b5d0da232 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 9 Jul 2026 11:08:22 -1000 Subject: [PATCH 223/479] sched_ext: Fix premature ops->priv publication in scx_alloc_and_add_sched() scx_alloc_and_add_sched() publishes @sch through ops->priv before allocating the cgroup path. If that allocation fails, the unwind path clears ops->priv and frees @sch immediately. scx_prog_sched() callers can dereference ops->priv from RCU context the moment it is set, so freeing without a grace period can use-after-free a concurrent kfunc caller. Move the publication below the cgroup path allocation so that every failure path after publication frees @sch through kobject_put(), whose release path defers the freeing by a grace period. Fixes: 105dcd005be2 ("sched_ext: Introduce scx_prog_sched()") Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 40cef77394ac..aeee44e016ab 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -6878,11 +6878,6 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd, sch->ops = *cmd->ops; } - rcu_assign_pointer(ops->priv, sch); - - sch->kobj.kset = scx_kset; - INIT_LIST_HEAD(&sch->all); - #ifdef CONFIG_EXT_SUB_SCHED char *buf = kzalloc(PATH_MAX, GFP_KERNEL); if (!buf) { @@ -6900,7 +6895,19 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd, sch->cgrp = cgrp; INIT_LIST_HEAD(&sch->children); INIT_LIST_HEAD(&sch->sibling); +#endif /* CONFIG_EXT_SUB_SCHED */ + /* + * Publishing makes @sch visible to scx_prog_sched() readers. Failure + * paths after this point must free @sch through kobject_put() whose + * release path defers the actual freeing by an RCU grace period. + */ + rcu_assign_pointer(ops->priv, sch); + + sch->kobj.kset = scx_kset; + INIT_LIST_HEAD(&sch->all); + +#ifdef CONFIG_EXT_SUB_SCHED if (parent) { /* * Pin @parent for @sch's lifetime. The kobject hierarchy pins @@ -6955,7 +6962,6 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd, #ifdef CONFIG_EXT_SUB_SCHED err_free_lb_resched: - RCU_INIT_POINTER(ops->priv, NULL); free_cpumask_var(sch->bypass_lb_resched_cpumask); #endif err_free_lb_cpumask: From e6979d05c6a6fe79980f08d63f039f0b27c30a1c Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 9 Jul 2026 11:08:41 -1000 Subject: [PATCH 224/479] tools/sched_ext: scx - Fix cmask_subset(), cmask_equal() and cmask_weight() cmask_equal(), cmask_weight() and cmask_subset() bounded their word walks with CMASK_NR_WORDS(nr_cids), which pads by one word and can't tell the last word in use without @base. The walks could thus cover a slack word past the active range, which cmask_reframe() leaves non-zero: a stale bit there gave cmask_equal() a spurious mismatch, cmask_weight() an inflated count, and cmask_subset() a spurious violation. cmask_subset() could also read @b->bits[] one word past its allocation (within the arena's fault-recovered range, so harmless), and deviated from the kernel scx_cmask_subset() by failing any @a range that doesn't nest inside @b's even when the overhanging bits are all clear. Bound the cmask_equal() and cmask_weight() walks by the words the range actually spans, with early returns for empty ranges. Rewrite cmask_subset() to match the kernel semantics: scan @a's overhangs for set bits with cmask_next_set() and walk the words of the range intersection. cmask_subset() moves below cmask_next_set(), which it now uses. Padding bits don't need masking as every cmask helper keeps them clear. Fixes: a58e6b79b432 ("sched_ext: Add cmask, a base-windowed bitmap over cid space") Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- tools/sched_ext/include/scx/cid.bpf.h | 88 +++++++++++++++++---------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/tools/sched_ext/include/scx/cid.bpf.h b/tools/sched_ext/include/scx/cid.bpf.h index db247e42fb45..6b0b4e41b288 100644 --- a/tools/sched_ext/include/scx/cid.bpf.h +++ b/tools/sched_ext/include/scx/cid.bpf.h @@ -391,7 +391,9 @@ static __always_inline bool cmask_equal(const struct scx_cmask __arena *a, if (a->base != b->base || a->nr_cids != b->nr_cids) return false; - nr_words = CMASK_NR_WORDS(a->nr_cids); + if (a->nr_cids == 0) + return true; + nr_words = (a->base + a->nr_cids - 1) / 64 - a->base / 64 + 1; bpf_for(i, 0, CMASK_MAX_WORDS) { if (i >= nr_words) @@ -402,36 +404,6 @@ static __always_inline bool cmask_equal(const struct scx_cmask __arena *a, return true; } -/* - * True iff every bit set in @a is also set in @b over the intersection of - * their ranges. Bits of @a outside @b's range fail the test. - */ -static __always_inline bool cmask_subset(const struct scx_cmask __arena *a, - const struct scx_cmask __arena *b) -{ - u32 a_end = a->base + a->nr_cids; - u32 b_end = b->base + b->nr_cids; - u32 a_wbase = a->base / 64; - u32 b_wbase = b->base / 64; - u32 nr_words, i; - - /* any bit of @a outside @b's range is a subset violation */ - if (a->base < b->base || a_end > b_end) - return false; - - nr_words = CMASK_NR_WORDS(a->nr_cids); - bpf_for(i, 0, CMASK_MAX_WORDS) { - u32 wi_b; - - if (i >= nr_words) - break; - wi_b = a_wbase + i - b_wbase; - if (a->bits[i] & ~b->bits[wi_b]) - return false; - } - return true; -} - /** * cmask_next_set - find the first set bit at or after @cid * @m: cmask to search @@ -488,16 +460,66 @@ static __always_inline u32 cmask_first_set(const struct scx_cmask __arena *m) (cid) < (m)->base + (m)->nr_cids; \ (cid) = cmask_next_set((m), (cid) + 1)) +/* + * True iff every bit set in @a is also set in @b. Matches the kernel-side + * scx_cmask_subset(): ranges don't need to nest, and set bits of @a outside + * @b's range fail the test. + */ +static __always_inline bool cmask_subset(const struct scx_cmask __arena *a, + const struct scx_cmask __arena *b) +{ + u32 a_end = a->base + a->nr_cids; + u32 b_end = b->base + b->nr_cids; + u32 a_wbase = a->base / 64; + u32 b_wbase = b->base / 64; + u32 lo = a->base > b->base ? a->base : b->base; + u32 hi = a_end < b_end ? a_end : b_end; + u32 lo_word, hi_word, i; + + /* set bits of @a outside @b's range can't be in @b */ + if (a->base < b->base && + cmask_next_set(a, a->base) < (b->base < a_end ? b->base : a_end)) + return false; + if (a_end > b_end && + cmask_next_set(a, a->base > b_end ? a->base : b_end) < a_end) + return false; + + if (lo >= hi) + return true; + + /* + * Walk the words the range intersection spans. Plain word tests + * suffice: the scans above guarantee @a has no set bit outside @b's + * range and padding bits are kept clear by all cmask helpers. + */ + lo_word = lo / 64; + hi_word = (hi - 1) / 64; + + bpf_for(i, 0, CMASK_MAX_WORDS) { + u32 w = lo_word + i; + + if (w > hi_word) + break; + if (a->bits[w - a_wbase] & ~b->bits[w - b_wbase]) + return false; + } + return true; +} + /* * Population count over [base, base + nr_cids). Padding bits in the head/tail * words are guaranteed zero by the mutating helpers, so a flat popcount over - * all words is correct. + * the words the range spans is correct. */ static __always_inline u32 cmask_weight(const struct scx_cmask __arena *m) { - u32 nr_words = CMASK_NR_WORDS(m->nr_cids), i; + u32 nr_words, i; u32 count = 0; + if (!m->nr_cids) + return 0; + nr_words = (m->base + m->nr_cids - 1) / 64 - m->base / 64 + 1; + bpf_for(i, 0, CMASK_MAX_WORDS) { if (i >= nr_words) break; From 33cb713db0161b54f04fe830e062c9e102c29a04 Mon Sep 17 00:00:00 2001 From: Matthieu Buffet Date: Wed, 1 Jul 2026 23:46:27 +0200 Subject: [PATCH 225/479] landlock: Fix TCP Fast Open connection bypass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documentation of the socket_connect() LSM hook states that it controls connecting a socket to a remote address. It has not been the case since the addition of TCP Fast Open (RFC 7413) support, which allows opening a TCP connection (thus, setting a socket's destination address) via the MSG_FASTOPEN flag passed to sendto()/sendmsg()/sendmmsg(). The problem then got duplicated into MPTCP. Landlock did not take it into account when its TCP support was added, leaving a bypass of TCP connect policy. Ideally a call to the LSM hook would be added in the fastopen code path, in order to fix this generically. But connect() hooks are designed to run with the socket locked, unlike sendmsg() hooks. Closes: https://github.com/landlock-lsm/linux/issues/41 Fixes: fff69fb03dde ("landlock: Support network rules with TCP bind and connect") Signed-off-by: Matthieu Buffet Link: https://patch.msgid.link/20260701214628.33319-1-matthieu@buffet.re Cc: stable@vger.kernel.org [mic: Wrap commit message] Signed-off-by: Mickaël Salaün --- security/landlock/net.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/security/landlock/net.c b/security/landlock/net.c index cbff59ec3aba..46c17116fcf4 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -351,6 +351,14 @@ static int hook_socket_sendmsg(struct socket *const sock, access_mask_t access_request; int ret = 0; + if ((msg->msg_flags & MSG_FASTOPEN) && address && sk_is_tcp(sock->sk)) { + ret = current_check_access_socket( + sock, address, addrlen, LANDLOCK_ACCESS_NET_CONNECT_TCP, + true); + if (ret != 0) + return ret; + } + if (sk_is_udp(sock->sk)) access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP; else From f4b30e0b1d488e7ffd8ea28d1365b9ba8e551edb Mon Sep 17 00:00:00 2001 From: Matthieu Buffet Date: Wed, 1 Jul 2026 23:46:28 +0200 Subject: [PATCH 226/479] selftests/landlock: Add test for TCP fast open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enforce that TCP Fast Open is controlled by LANDLOCK_ACCESS_NET_CONNECT_TCP. Semantics of connect() and sendmsg(MSG_FASTOPEN) should be identical from Landlock's perspective. Also enforce error code consistency, since UDP sockets ignore the MSG_FASTOPEN flag while Unix sockets reject it. Signed-off-by: Matthieu Buffet Link: https://patch.msgid.link/20260701214628.33319-2-matthieu@buffet.re Cc: stable@vger.kernel.org [mic: Fix formatting] Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/net_test.c | 97 +++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 2ed1f76b7a8b..be2eb88092fb 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -1281,6 +1281,103 @@ TEST_F(protocol, connect_unspec) EXPECT_EQ(0, close(bind_fd)); } +TEST_F(protocol, tcp_fastopen) +{ + const bool restricted = variant->sandbox == TCP_SANDBOX && + variant->prot.type == SOCK_STREAM && + (variant->prot.protocol == IPPROTO_TCP || + variant->prot.protocol == IPPROTO_IP) && + (variant->prot.domain == AF_INET || + variant->prot.domain == AF_INET6); + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + int bind_fd, client_fd, status; + char buf; + pid_t child; + + bind_fd = socket_variant(&self->srv0); + ASSERT_LE(0, bind_fd); + EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0)); + if (self->srv0.protocol.type == SOCK_STREAM) + EXPECT_EQ(0, listen(bind_fd, backlog)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int connect_fd, ret; + + /* Closes listening socket for the child. */ + EXPECT_EQ(0, close(bind_fd)); + + connect_fd = socket_variant(&self->srv0); + ASSERT_LE(0, connect_fd); + + if (variant->sandbox == TCP_SANDBOX) { + const int ruleset_fd = landlock_create_ruleset( + &ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + /* Fast Open with no address. */ + ret = sendto_variant(connect_fd, NULL, NULL, 0, MSG_FASTOPEN); + if (self->srv0.protocol.domain == AF_UNIX) { + EXPECT_EQ(-ENOTCONN, ret); + } else if (self->srv0.protocol.type == SOCK_DGRAM) { + EXPECT_EQ(-EDESTADDRREQ, ret); + } else { + EXPECT_EQ(-EINVAL, ret); + } + + /* Fast Open to a denied address. */ + ret = sendto_variant(connect_fd, &self->srv0, "A", 1, + MSG_FASTOPEN); + if (restricted) { + EXPECT_EQ(-EACCES, ret); + } else if (self->srv0.protocol.domain == AF_UNIX && + self->srv0.protocol.type == SOCK_STREAM) { + EXPECT_EQ(-EOPNOTSUPP, ret); + } else { + EXPECT_EQ(0, ret); + } + + EXPECT_EQ(0, close(connect_fd)); + _exit(_metadata->exit_code); + return; + } + + client_fd = bind_fd; + if (!restricted && self->srv0.protocol.type == SOCK_STREAM && + self->srv0.protocol.domain != AF_UNIX) { + client_fd = accept(bind_fd, NULL, 0); + ASSERT_LE(0, client_fd); + } + + if (restricted) { + EXPECT_EQ(-1, read(client_fd, &buf, 1)); + EXPECT_EQ(ENOTCONN, errno); + } else if (self->srv0.protocol.domain == AF_UNIX && + self->srv0.protocol.type == SOCK_STREAM) { + EXPECT_EQ(-1, read(client_fd, &buf, 1)); + EXPECT_EQ(EINVAL, errno); + } else { + EXPECT_EQ(1, read(client_fd, &buf, 1)); + EXPECT_EQ('A', buf); + } + + EXPECT_EQ(child, waitpid(child, &status, 0)); + EXPECT_EQ(1, WIFEXITED(status)); + EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + if (client_fd != bind_fd) + EXPECT_LE(0, close(client_fd)); + + EXPECT_EQ(0, close(bind_fd)); +} + TEST_F(protocol, sendmsg_stream) { int srv0_fd, tmp_fd, client_fd, res; From 592a37889f97d60debf6a442684ba4d13435c817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 3 Jul 2026 16:17:09 +0200 Subject: [PATCH 227/479] landlock: Fix kernel-doc for the nested quiet layer flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kernel-doc emits "Excess struct member 'quiet' description in 'landlock_layer'" because "quiet" is a bitfield inside the named nested struct "flags", but its inline comment used the bare member name "@quiet:", which kernel-doc attributes to the enclosing landlock_layer. Use the canonical dotted notation "@flags.quiet:" so kernel-doc resolves the nested member, and include it in the generated documentation. Cc: Justin Suess Cc: Tingmao Wang Fixes: a260c0055665 ("landlock: Add a place for flags to layer rules") Link: https://patch.msgid.link/20260703141711.2016964-1-mic@digikod.net Signed-off-by: Mickaël Salaün --- security/landlock/ruleset.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 61f3c253d5c9..0437adf17428 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -35,8 +35,8 @@ struct landlock_layer { */ struct { /** - * @quiet: Suppresses denial logs for the object covered by this - * rule in this domain. For filesystem rules, this inherits + * @flags.quiet: Suppresses denial logs for the object covered by + * this rule in this domain. For filesystem rules, this inherits * down the file hierarchy. */ u8 quiet : 1; From 97c0e344e03818dbf3116e77d3d7fe81f1fbe795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Wed, 8 Jul 2026 13:06:33 +0200 Subject: [PATCH 228/479] landlock: Update formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following commit 99df2a8eba34 ("clang-format: fix formatting of guard() and scoped_guard() statements"), update scoped_guard() formatting. Also, see the related fix [1]. Cc: Günther Noack Cc: Miguel Ojeda Link: https://lore.kernel.org/r/20260708105713.2073335-1-mic@digikod.net [1] Link: https://patch.msgid.link/20260708110635.2083515-1-mic@digikod.net Reviewed-by: Günther Noack Signed-off-by: Mickaël Salaün --- security/landlock/task.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/security/landlock/task.c b/security/landlock/task.c index 7ddf211f75c3..360d226d0f51 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -95,8 +95,7 @@ static int hook_ptrace_access_check(struct task_struct *const child, if (!parent_subject) return 0; - scoped_guard(rcu) - { + scoped_guard(rcu) { const struct landlock_ruleset *const child_dom = landlock_get_task_domain(child); err = domain_ptrace(parent_subject->domain, child_dom); @@ -370,8 +369,7 @@ static int hook_task_kill(struct task_struct *const p, if (!subject) return 0; - scoped_guard(rcu) - { + scoped_guard(rcu) { is_scoped = domain_is_scoped(subject->domain, landlock_get_task_domain(p), signal_scope.scope); @@ -422,8 +420,7 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, if (task_tgid(tsk) == landlock_file(fown->file)->fown_tg) return 0; - scoped_guard(rcu) - { + scoped_guard(rcu) { is_scoped = domain_is_scoped(subject->domain, landlock_get_task_domain(tsk), signal_scope.scope); From d793186aa3bb833c878ae6826c87e62c843afaa3 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Thu, 9 Jul 2026 18:43:40 +0200 Subject: [PATCH 229/479] selftests/landlock: Fix screwed up pointers in the scoped_signal_test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scoped_signal_test uses pthread_join(..., (void **)&ret)) in a couple of places, i.e. the return value of the thread is stored in the shape of a "void *" into the memory location of &ret. Pointers are 64-bit on modern computers, but the ret variable is declared as a simple "enum thread_return" which is only 32 bits. So the pthread_join() will overflow the ret variable by 4 byte. The problem is very visible on big endian systems like s390x where the test is failing: The least significant byte that carries the return code of the thread is not written into the ret variable here, but somewhere else in the stack frame, so the comparison for the right return code is failing here. Fix it by getting rid of the enum and defining the THREAD_* constants and "ret" variables as proper "void *" pointers. This way we can also get rid of some ugly (void *) castings in a couple of spots. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260709164340.339656-1-thuth@redhat.com Cc: stable@vger.kernel.org Fixes: c8994965013e ("selftests/landlock: Test signal scoping for threads") [mic: Add clang-format markups] Signed-off-by: Mickaël Salaün --- .../selftests/landlock/scoped_signal_test.c | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c index f24f2c28f62e..3f0cd42a2afe 100644 --- a/tools/testing/selftests/landlock/scoped_signal_test.c +++ b/tools/testing/selftests/landlock/scoped_signal_test.c @@ -249,12 +249,12 @@ TEST_F(scoped_domains, check_access_signal) _metadata->exit_code = KSFT_FAIL; } -enum thread_return { - THREAD_INVALID = 0, - THREAD_SUCCESS = 1, - THREAD_ERROR = 2, - THREAD_TEST_FAILED = 3, -}; +/* clang-format off */ +#define THREAD_INVALID ((void *)0) +#define THREAD_SUCCESS ((void *)1) +#define THREAD_ERROR ((void *)2) +#define THREAD_TEST_FAILED ((void *)3) +/* clang-format on */ static void *thread_sync(void *arg) { @@ -262,15 +262,15 @@ static void *thread_sync(void *arg) char buf; if (read(pipe_read, &buf, 1) != 1) - return (void *)THREAD_ERROR; + return THREAD_ERROR; - return (void *)THREAD_SUCCESS; + return THREAD_SUCCESS; } TEST(signal_scoping_thread_before) { pthread_t no_sandbox_thread; - enum thread_return ret = THREAD_INVALID; + void *ret = THREAD_INVALID; int thread_pipe[2]; drop_caps(_metadata); @@ -285,7 +285,7 @@ TEST(signal_scoping_thread_before) EXPECT_EQ(0, pthread_kill(no_sandbox_thread, 0)); EXPECT_EQ(1, write(thread_pipe[1], ".", 1)); - EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret)); + EXPECT_EQ(0, pthread_join(no_sandbox_thread, &ret)); EXPECT_EQ(THREAD_SUCCESS, ret); EXPECT_EQ(0, close(thread_pipe[0])); @@ -295,7 +295,7 @@ TEST(signal_scoping_thread_before) TEST(signal_scoping_thread_after) { pthread_t scoped_thread; - enum thread_return ret = THREAD_INVALID; + void *ret = THREAD_INVALID; int thread_pipe[2]; drop_caps(_metadata); @@ -310,7 +310,7 @@ TEST(signal_scoping_thread_after) EXPECT_EQ(0, pthread_kill(scoped_thread, 0)); EXPECT_EQ(1, write(thread_pipe[1], ".", 1)); - EXPECT_EQ(0, pthread_join(scoped_thread, (void **)&ret)); + EXPECT_EQ(0, pthread_join(scoped_thread, &ret)); EXPECT_EQ(THREAD_SUCCESS, ret); EXPECT_EQ(0, close(thread_pipe[0])); @@ -327,20 +327,20 @@ void *thread_setuid(void *ptr) char buf; if (read(arg->pipe_read, &buf, 1) != 1) - return (void *)THREAD_ERROR; + return THREAD_ERROR; /* libc's setuid() should update all thread's credentials. */ if (getuid() != arg->new_uid) - return (void *)THREAD_TEST_FAILED; + return THREAD_TEST_FAILED; - return (void *)THREAD_SUCCESS; + return THREAD_SUCCESS; } TEST(signal_scoping_thread_setuid) { struct thread_setuid_args arg; pthread_t no_sandbox_thread; - enum thread_return ret = THREAD_INVALID; + void *ret = THREAD_INVALID; int pipe_parent[2]; int prev_uid; @@ -367,7 +367,7 @@ TEST(signal_scoping_thread_setuid) EXPECT_EQ(arg.new_uid, getuid()); EXPECT_EQ(1, write(pipe_parent[1], ".", 1)); - EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret)); + EXPECT_EQ(0, pthread_join(no_sandbox_thread, &ret)); EXPECT_EQ(THREAD_SUCCESS, ret); clear_cap(_metadata, CAP_SETUID); @@ -667,20 +667,20 @@ static void *thread_setown_scoped(void *arg) ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); if (ruleset_fd < 0) - return (void *)THREAD_ERROR; + return THREAD_ERROR; if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) || landlock_restrict_self(ruleset_fd, 0)) { close(ruleset_fd); - return (void *)THREAD_ERROR; + return THREAD_ERROR; } close(ruleset_fd); /* Makes this process group own the SIGIO source. */ if (fcntl(fd, F_SETSIG, SIGURG) || fcntl(fd, F_SETOWN, -getpgrp()) || fcntl(fd, F_SETFL, O_ASYNC)) - return (void *)THREAD_ERROR; + return THREAD_ERROR; - return (void *)THREAD_SUCCESS; + return THREAD_SUCCESS; } /* @@ -702,7 +702,7 @@ TEST(sigio_to_pgid_self) { int trigger[2]; pthread_t thread; - enum thread_return ret = THREAD_INVALID; + void *ret = THREAD_INVALID; int i; drop_caps(_metadata); @@ -722,7 +722,7 @@ TEST(sigio_to_pgid_self) */ ASSERT_EQ(0, pthread_create(&thread, NULL, thread_setown_scoped, &trigger[0])); - ASSERT_EQ(0, pthread_join(thread, (void **)&ret)); + ASSERT_EQ(0, pthread_join(thread, &ret)); ASSERT_EQ(THREAD_SUCCESS, ret); /* Fans SIGURG out to the process group. */ From 5ab1dc6d110db6bee167a32fd94c53ea0e7ad6d2 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Fri, 10 Jul 2026 10:16:42 +0200 Subject: [PATCH 230/479] selftests/landlock: Skip scoped_signal subtest with MSG_OOB if not available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MSG_OOB might be disabled in the kernel for unix sockets (by not selecting CONFIG_AF_UNIX_OOB), and in this case the related tests of the scoped_signal_test are currently failing. Add a runtime probe using socketpair() to detect MSG_OOB support and skip the test gracefully if it is unavailable. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260710081642.405916-1-thuth@redhat.com Cc: stable@vger.kernel.org Fixes: f34e9ce5f479 ("selftests/landlock: Test signal created by out-of-bound message") Signed-off-by: Mickaël Salaün --- .../selftests/landlock/scoped_signal_test.c | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c index 3f0cd42a2afe..2d37d0c06c06 100644 --- a/tools/testing/selftests/landlock/scoped_signal_test.c +++ b/tools/testing/selftests/landlock/scoped_signal_test.c @@ -400,6 +400,24 @@ static int setup_signal_handler(int signal) return sigaction(SIGURG, &sa, NULL); } +/* + * MSG_OOB might be disabled in the kernel via the CONFIG_AF_UNIX_OOB + * switch, so this function can be used for probing for its availability. + */ +static bool has_af_unix_oob(void) +{ + bool available = false; + int sp[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == 0) { + available = (send(sp[0], ".", 1, MSG_OOB) == 1); + close(sp[0]); + close(sp[1]); + } + + return available; +} + /* clang-format off */ FIXTURE(fown) {}; /* clang-format on */ @@ -462,6 +480,9 @@ TEST_F(fown, sigurg_socket) int pipe_parent[2], pipe_child[2]; pid_t child; + if (!has_af_unix_oob()) + SKIP(return, "CONFIG_AF_UNIX_OOB / MSG_OOB not available"); + memset(&server_address, 0, sizeof(server_address)); set_unix_address(&server_address, 0); From ec4215683e47424c9c4762fd3c60f552a3119142 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Mon, 6 Jul 2026 11:01:59 +0200 Subject: [PATCH 231/479] ppp: defer channel free to an RCU grace period to fix pppol2tp RX UAF pppol2tp_recv() runs in the L2TP UDP-encap softirq RX path: l2tp_udp_encap_recv() -> l2tp_recv_common() -> pppol2tp_recv() -> ppp_input(&po->chan) It runs under rcu_read_lock() holding only an l2tp_session reference and takes NO reference on the internal PPP channel (struct channel, chan->ppp) that ppp_input() dereferences. The pppox socket is SOCK_RCU_FREE, so 'po' and the embedded ppp_channel are RCU-safe. But the internal struct channel is a separate allocation that ppp_release_channel() frees with a plain kfree(): close(data socket) -> pppol2tp_release() -> pppox_unbind_sock() -> ppp_unregister_channel() -> ppp_release_channel() -> kfree(pch) For a channel that is bound (PPPIOCGCHAN) but not attached to a ppp unit (no PPPIOCCONNECT, pch->ppp == NULL) and not bridged, teardown skips both ppp_disconnect_channel()'s synchronize_net() and ppp_unbridge_channels()'s synchronize_rcu(), so the kfree() has no grace period. rcu_read_lock() in pppol2tp_recv() does not protect against a plain kfree(), so an in-flight ppp_input() on one CPU can dereference the channel just freed by close() on another CPU. The bug is reachable by an unprivileged user. Defer the channel free to an RCU callback via call_rcu() so the grace period fences any in-flight ppp_input(). The disconnect and unbridge teardown paths already fence with synchronize_net()/synchronize_rcu(); call_rcu() does the same here without stalling the close() path. Fixes: ee40fb2e1eb5 ("l2tp: protect sock pointer of struct pppol2tp_session with RCU") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Norbert Szetei Reviewed-by: Qingfang Deng Link: https://patch.msgid.link/E793FCF2-58DE-4387-A983-C7B4BC3158BD@doyensec.com Signed-off-by: Paolo Abeni --- drivers/net/ppp/ppp_generic.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c index 57c68efa5ff8..717c1d3aa953 100644 --- a/drivers/net/ppp/ppp_generic.c +++ b/drivers/net/ppp/ppp_generic.c @@ -184,6 +184,7 @@ struct channel { struct list_head clist; /* link in list of channels per unit */ spinlock_t upl; /* protects `ppp' and 'bridge' */ struct channel __rcu *bridge; /* "bridged" ppp channel */ + struct rcu_head rcu; /* for RCU-deferred free of the channel */ #ifdef CONFIG_PPP_MULTILINK u8 avail; /* flag used in multilink stuff */ u8 had_frag; /* >= 1 fragments have been sent */ @@ -3562,6 +3563,18 @@ ppp_disconnect_channel(struct channel *pch) return err; } +/* Purge after the grace period: a late ppp_input() may still queue an + * skb on pch->file.rq before the last RCU reader drains. + */ +static void ppp_release_channel_free(struct rcu_head *rcu) +{ + struct channel *pch = container_of(rcu, struct channel, rcu); + + skb_queue_purge(&pch->file.xq); + skb_queue_purge(&pch->file.rq); + kfree(pch); +} + /* * Drop a reference to a ppp channel and free its memory if the refcount reaches * zero. @@ -3581,9 +3594,7 @@ static void ppp_release_channel(struct channel *pch) pr_err("ppp: destroying undead channel %p !\n", pch); return; } - skb_queue_purge(&pch->file.xq); - skb_queue_purge(&pch->file.rq); - kfree(pch); + call_rcu(&pch->rcu, ppp_release_channel_free); } static void __exit ppp_cleanup(void) @@ -3596,6 +3607,7 @@ static void __exit ppp_cleanup(void) device_destroy(&ppp_class, MKDEV(PPP_MAJOR, 0)); class_unregister(&ppp_class); unregister_pernet_device(&ppp_net_ops); + rcu_barrier(); /* wait for RCU callbacks before module unload */ } /* From fe179677b6dcb4b658586038a811f87265e97777 Mon Sep 17 00:00:00 2001 From: Gautam Menghani Date: Mon, 15 Jun 2026 14:41:19 +0530 Subject: [PATCH 232/479] powerpc/pseries/Kconfig: Enable CONFIG_VPA_PMU to be used with KVM Currently, CONFIG_VPA_PMU is not enabled by default, and consequently cannot be used for KVM guests at all, unless explicitly enabled on host kernel. Mark CONFIG_VPA_PMU as "default m" to ensure it is available when KVM is being used. Cc: stable@vger.kernel.org # v6.13+ Suggested-by: Sean Christopherson Reviewed-by: Amit Machhiwal Reviewed-by: Harsh Prateek Bora Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Gautam Menghani [Maddy: Changed tag order] Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260615091120.84169-1-gautam@linux.ibm.com --- arch/powerpc/platforms/pseries/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/powerpc/platforms/pseries/Kconfig b/arch/powerpc/platforms/pseries/Kconfig index f7052b131a4c..74910ce3a541 100644 --- a/arch/powerpc/platforms/pseries/Kconfig +++ b/arch/powerpc/platforms/pseries/Kconfig @@ -154,6 +154,7 @@ config HV_PERF_CTRS config VPA_PMU tristate "VPA PMU events" depends on KVM_BOOK3S_64_HV && HV_PERF_CTRS + default m help Enable access to the VPA PMU counters via perf. This enables code that support measurement for KVM on PowerVM(KoP) feature. From 10229a0702b51d0295b53190d34a7de9aa20602c Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Tue, 7 Jul 2026 12:14:14 +0000 Subject: [PATCH 233/479] MAINTAINERS: Update SpacemiT SoC git tree repository Due to security concern, switch SpacemiT kernel SoC tree's repository from github.com to kernel.org Signed-off-by: Yixun Lan Link: https://lore.kernel.org/r/20260707-07-spacemit-git-repo-url-v1-1-137697316a4c@kernel.org Signed-off-by: Arnd Bergmann --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 4a8b0fd665ce..1c7242fd18fe 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23312,7 +23312,7 @@ L: spacemit@lists.linux.dev S: Maintained W: https://github.com/spacemit-com/linux/wiki C: irc://irc.libera.chat/spacemit -T: git https://github.com/spacemit-com/linux +T: https://git.kernel.org/pub/scm/linux/kernel/git/spacemit/linux.git F: arch/riscv/boot/dts/spacemit/ N: spacemit K: spacemit From f9eff167fefa6f222af87ca605ddd6b6494e390f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Sun, 5 Jul 2026 10:50:00 +0200 Subject: [PATCH 234/479] ARM: Don't let ARMv5 platforms select USE_OF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit USE_OF is already selected by ARM (unless ARCH_FOOTBRIDGE || ARCH_RPC || ARCH_SA1100; these all conflict with ARCH_MULTI_V5). So there is no need for an explicit select and it can be dropped. Signed-off-by: Uwe Kleine-König (The Capable Hub) Acked-by: Arnd Bergmann Link: https://lore.kernel.org/r/20260705085000.3510576-2-u.kleine-koenig@baylibre.com Signed-off-by: Arnd Bergmann --- arch/arm/mach-ixp4xx/Kconfig | 1 - arch/arm/mach-pxa/Kconfig | 3 --- 2 files changed, 4 deletions(-) diff --git a/arch/arm/mach-ixp4xx/Kconfig b/arch/arm/mach-ixp4xx/Kconfig index cb46802f5ce5..7f812020e082 100644 --- a/arch/arm/mach-ixp4xx/Kconfig +++ b/arch/arm/mach-ixp4xx/Kconfig @@ -14,6 +14,5 @@ menuconfig ARCH_IXP4XX select IXP4XX_TIMER select USB_EHCI_BIG_ENDIAN_DESC select USB_EHCI_BIG_ENDIAN_MMIO - select USE_OF help Support for Intel's IXP4XX (XScale) family of processors. diff --git a/arch/arm/mach-pxa/Kconfig b/arch/arm/mach-pxa/Kconfig index 66e26990e2c8..c478fb8a6f78 100644 --- a/arch/arm/mach-pxa/Kconfig +++ b/arch/arm/mach-pxa/Kconfig @@ -22,7 +22,6 @@ config MACH_PXA25X_DT select PINCTRL select POWER_SUPPLY select PXA25x - select USE_OF help Include support for Marvell PXA25x based platforms using the device tree. Needn't select any other machine while @@ -33,7 +32,6 @@ config MACH_PXA27X_DT select PINCTRL select POWER_SUPPLY select PXA27x - select USE_OF help Include support for Marvell PXA27x based platforms using the device tree. Needn't select any other machine while @@ -47,7 +45,6 @@ config MACH_PXA3XX_DT select PINCTRL select POWER_SUPPLY select PXA3xx - select USE_OF help Include support for Marvell PXA3xx based platforms using the device tree. Needn't select any other machine while From f2f152e94a67bc746afaf05a1b2702c195553112 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sat, 4 Jul 2026 10:14:21 -0700 Subject: [PATCH 235/479] ipv4: fib: free fib_alias with kfree_rcu() on insert error path fib_table_insert() publishes new_fa into the leaf's fa_list with fib_insert_alias() before calling the fib entry notifiers. When a notifier fails, the error path removes new_fa with fib_remove_alias() (hlist_del_rcu) and frees it right away with kmem_cache_free(). fib_table_lookup() walks that list under rcu_read_lock() only, so a concurrent lookup that already reached new_fa keeps reading it after the free: BUG: KASAN: slab-use-after-free in fib_table_lookup (net/ipv4/fib_trie.c:1601) Read of size 1 at addr ffff88810676d4eb by task exploit/297 Call Trace: fib_table_lookup (net/ipv4/fib_trie.c:1601) ip_route_output_key_hash_rcu (net/ipv4/route.c:2814) ip_route_output_key_hash (net/ipv4/route.c:2705) __ip4_datagram_connect (net/ipv4/datagram.c:49) udp_connect (net/ipv4/udp.c:2144) __sys_connect (net/socket.c:2167) __x64_sys_connect (net/socket.c:2173) do_syscall_64 entry_SYSCALL_64_after_hwframe which belongs to the cache ip_fib_alias of size 56 Triggering the error path needs CAP_NET_ADMIN and a registered fib notifier that can reject a route; a netdevsim device whose IPv4 FIB resource is exhausted is enough. Free new_fa with alias_free_mem_rcu(), as fib_table_delete() already does for a fib_alias removed from the trie. Fixes: a6c76c17df02 ("ipv4: Notify route after insertion to the routing table") Reported-by: Xiang Mei Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Weiming Shi Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260704171421.1786806-1-bestswngs@gmail.com Signed-off-by: Paolo Abeni --- net/ipv4/fib_trie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c index e11dc86ceda0..6badad29593b 100644 --- a/net/ipv4/fib_trie.c +++ b/net/ipv4/fib_trie.c @@ -1385,7 +1385,7 @@ int fib_table_insert(struct net *net, struct fib_table *tb, out_remove_new_fa: fib_remove_alias(t, tp, l, new_fa); out_free_new_fa: - kmem_cache_free(fn_alias_kmem, new_fa); + alias_free_mem_rcu(new_fa); out: fib_release_info(fi); err: From 4fa349156043dc119721d067329714179f501749 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sun, 5 Jul 2026 22:24:36 -0500 Subject: [PATCH 236/479] net/iucv: take a reference on the socket found in afiucv_hs_rcv() afiucv_hs_rcv() looks up the destination socket under iucv_sk_list.lock, drops the lock, and then passes the socket to the afiucv_hs_callback_*() handlers without holding a reference. AF_IUCV sockets are not RCU-protected and are freed synchronously by iucv_sock_kill() -> sock_put(), so a concurrent close can free the socket in the window between read_unlock() and the handler, which then dereferences freed memory (for example sk->sk_data_ready() in afiucv_hs_callback_syn()). Take a reference with sock_hold() while the socket is still on the list and release it with sock_put() once the handler has run. Fixes: 3881ac441f64 ("af_iucv: add HiperSockets transport") Signed-off-by: Bryam Vargas Reviewed-by: Hidayath Khan Link: https://patch.msgid.link/20260705-b4-disp-fc79c0dc-v1-1-d2cdcb57afa9@proton.me Signed-off-by: Paolo Abeni --- net/iucv/af_iucv.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c index fed240b453bd..b85fb9767dec 100644 --- a/net/iucv/af_iucv.c +++ b/net/iucv/af_iucv.c @@ -2089,6 +2089,8 @@ static int afiucv_hs_rcv(struct sk_buff *skb, struct net_device *dev, } } } + if (sk) + sock_hold(sk); read_unlock(&iucv_sk_list.lock); if (!iucv) sk = NULL; @@ -2138,6 +2140,8 @@ static int afiucv_hs_rcv(struct sk_buff *skb, struct net_device *dev, kfree_skb(skb); } + if (sk) + sock_put(sk); return err; } From 5d1a2240935ea47e2673d0ea17fdb058e4dc91dd Mon Sep 17 00:00:00 2001 From: Wyatt Feng Date: Sat, 13 Jun 2026 18:27:15 +0800 Subject: [PATCH 237/479] netfilter: xt_nat: reject unsupported target families xt_nat SNAT and DNAT target handlers assume IP-family conntrack state is present and can dereference a NULL pointer when instantiated from an unsupported family through nft_compat. A bridge-family compat rule can therefore trigger a NULL-dereference in nf_nat_setup_info(). Reject non-IP families in xt_nat_checkentry() so unsupported targets cannot be installed. Keep NFPROTO_INET allowed for valid inet NAT compat users and leave the runtime fast path unchanged. [ The crash was fixed via 9dbba7e694ec ("netfilter: nft_compat: ebtables emulation must reject non-bridge targets"), so this patch is no longer critical. Nevertheless, NAT is only relevant for ipv4/ipv6, so this extra family check is a good idea in any case. ] Fixes: c7232c9979cb ("netfilter: add protocol independent NAT core") Cc: stable@vger.kernel.org Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Zhengchuan Liang Reported-by: Xin Liu Assisted-by: Codex:GPT-5.4 Signed-off-by: Wyatt Feng Signed-off-by: Ren Wei Signed-off-by: Florian Westphal --- net/netfilter/xt_nat.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/net/netfilter/xt_nat.c b/net/netfilter/xt_nat.c index b4f7bbc3f3ca..51c7f7ce88d9 100644 --- a/net/netfilter/xt_nat.c +++ b/net/netfilter/xt_nat.c @@ -26,6 +26,15 @@ static int xt_nat_checkentry_v0(const struct xt_tgchk_param *par) static int xt_nat_checkentry(const struct xt_tgchk_param *par) { + switch (par->family) { + case NFPROTO_IPV4: + case NFPROTO_IPV6: + case NFPROTO_INET: + break; + default: + return -EINVAL; + } + return nf_ct_netns_get(par->net, par->family); } From b06163ce52ec0561f202fbfb9b08090cb61f512e Mon Sep 17 00:00:00 2001 From: Yizhou Zhao Date: Tue, 7 Jul 2026 19:00:14 +0800 Subject: [PATCH 238/479] netfilter: ecache: fix inverted time_after() check ecache_work_evict_list() redelivers DESTROY events for conntracks that were moved to the per-netns dying_list after event delivery failed. It sets a 10ms deadline: stop = jiffies + ECACHE_MAX_JIFFIES but then tests: time_after(stop, jiffies) This condition is true while the deadline is still in the future, so the worker returns STATE_RESTART after the first successful redelivery in the usual case. ecache_work() maps STATE_RESTART to delay 0, which turns the redelivery path into one dying conntrack per workqueue dispatch and makes the sent > 16 batching/cond_resched() path effectively unreachable. A conntrack netlink listener whose receive queue is congested can make DESTROY event delivery fail with -ENOBUFS. With sustained conntrack churn, entries then accumulate on the dying_list and are only drained at the degraded one-entry-per-dispatch rate once delivery succeeds again, wasting CPU on back-to-back workqueue reschedules and prolonging conntrack memory/resource pressure. In a KASAN QEMU test with CONFIG_NF_CONNTRACK_EVENTS=y and nf_conntrack.enable_hooks=1, a congested DESTROY listener caused 8192 nf_ct_delete() calls to return false and move entries to the dying_list. After closing the listener, the unfixed kernel needed 7670 ecache_work() entries to destroy 7669 conntracks. With this change, the same 8192 entries were destroyed by 2 ecache_work() entries. Swap the comparison so the worker restarts only after the deadline has expired. Fixes: 2ed3bf188b33 ("netfilter: ecache: use dedicated list for event redelivery") Cc: stable@vger.kernel.org Reported-by: Yizhou Zhao Reported-by: Yuxiang Yang Reported-by: Ao Wang Reported-by: Xuewei Feng Reported-by: Qi Li Reported-by: Ke Xu Assisted-by: Claude-Code:GLM-5.2 Signed-off-by: Yizhou Zhao Signed-off-by: Florian Westphal --- net/netfilter/nf_conntrack_ecache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/nf_conntrack_ecache.c b/net/netfilter/nf_conntrack_ecache.c index 9df159448b89..cc8d8e85169f 100644 --- a/net/netfilter/nf_conntrack_ecache.c +++ b/net/netfilter/nf_conntrack_ecache.c @@ -77,7 +77,7 @@ static enum retry_state ecache_work_evict_list(struct nf_conntrack_net *cnet) hlist_nulls_del_rcu(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnnode); hlist_nulls_add_head(&ct->tuplehash[IP_CT_DIR_REPLY].hnnode, &evicted_list); - if (time_after(stop, jiffies)) { + if (time_after(jiffies, stop)) { ret = STATE_RESTART; break; } From 86f3ce81dd2b4b0aa2c3016c989a943e4b1b643d Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Wed, 8 Jul 2026 18:11:50 +0000 Subject: [PATCH 239/479] netfilter: bridge: fix stale prevhdr pointer in br_ip6_fragment() br_ip6_fragment() gets prevhdr, a pointer into the skb head, from ip6_find_1stfragopt(), then calls skb_checksum_help(). For a cloned skb skb_checksum_help() reallocates the head via pskb_expand_head(), leaving prevhdr dangling. It is later dereferenced in ip6_frag_next(), causing a use-after-free write. Save prevhdr's offset before skb_checksum_help() and recompute it after, like commit ef0efcd3bd3f ("ipv6: Fix dangling pointer when ipv6 fragment"). BUG: KASAN: slab-use-after-free in ip6_frag_next (net/ipv6/ip6_output.c:857) Write of size 1 at addr ffff888013ff5016 by task exploit/141 Call Trace: ... kasan_report (mm/kasan/report.c:595) ip6_frag_next (net/ipv6/ip6_output.c:857) br_ip6_fragment (net/ipv6/netfilter.c:212) nf_ct_bridge_post (net/bridge/netfilter/nf_conntrack_bridge.c:407) nf_hook_slow (net/netfilter/core.c:619) br_forward_finish (net/bridge/br_forward.c:66) __br_forward (net/bridge/br_forward.c:115) maybe_deliver (net/bridge/br_forward.c:191) br_flood (net/bridge/br_forward.c:245) br_handle_frame_finish (net/bridge/br_input.c:229) br_handle_frame (net/bridge/br_input.c:442) ... packet_sendmsg (net/packet/af_packet.c:3114) ... do_syscall_64 (arch/x86/entry/syscall_64.c:94) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Kernel panic - not syncing: Fatal exception in interrupt Fixes: 764dd163ac92 ("netfilter: nf_conntrack_bridge: add support for IPv6") Cc: stable@vger.kernel.org Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) Signed-off-by: Florian Westphal --- net/ipv6/netfilter.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/ipv6/netfilter.c b/net/ipv6/netfilter.c index 6d80f85e55fa..a7025ec87035 100644 --- a/net/ipv6/netfilter.c +++ b/net/ipv6/netfilter.c @@ -120,7 +120,7 @@ int br_ip6_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, ktime_t tstamp = skb->tstamp; struct ip6_frag_state state; u8 *prevhdr, nexthdr = 0; - unsigned int mtu, hlen; + unsigned int mtu, hlen, nexthdr_offset; int hroom, err = 0; __be32 frag_id; @@ -129,6 +129,7 @@ int br_ip6_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, goto blackhole; hlen = err; nexthdr = *prevhdr; + nexthdr_offset = prevhdr - skb_network_header(skb); mtu = skb->dev->mtu; if (frag_max_size > mtu || @@ -147,6 +148,7 @@ int br_ip6_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, (err = skb_checksum_help(skb))) goto blackhole; + prevhdr = skb_network_header(skb) + nexthdr_offset; hroom = LL_RESERVED_SPACE(skb->dev); if (skb_has_frag_list(skb)) { unsigned int first_len = skb_pagelen(skb); From f62c41b4910e65da396ec9a8c40c1fe7fe82e449 Mon Sep 17 00:00:00 2001 From: Yizhou Zhao Date: Wed, 8 Jul 2026 13:27:28 +0800 Subject: [PATCH 240/479] netfilter: nf_conncount: fix zone comparison in tuple dedup The "already exists" dedup logic in __nf_conncount_add() decides whether a connection has already been counted and can be skipped instead of incrementing the connlimit count. It compares the conntrack zone of a list entry with the zone of the connection being added using nf_ct_zone_id() and nf_ct_zone_equal(), passing conn->zone.dir or zone->dir as the direction argument. Those helpers take enum ip_conntrack_dir values: IP_CT_DIR_ORIGINAL is 0 and IP_CT_DIR_REPLY is 1. However, zone->dir is a u8 bitmask: NF_CT_ZONE_DIR_ORIG is 1, NF_CT_ZONE_DIR_REPL is 2 and NF_CT_DEFAULT_ZONE_DIR is 3. Passing that bitmask as the enum direction shifts the meaning of every non-zero value. An ORIG-only zone passes 1 and is tested as REPLY, while REPL-only and default zones pass 2 or 3 and test bits beyond the valid direction range. In those cases nf_ct_zone_id() can fall back to NF_CT_DEFAULT_ZONE_ID instead of using the real zone id, so different zones can be treated as equal and dedup collapses to tuple equality alone. nf_conncount stores and compares the original-direction tuple for a connection. If an skb already has an attached conntrack entry, get_ct_or_tuple_from_skb() explicitly copies ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, regardless of the packet's ctinfo. Therefore the zone comparison in the tuple dedup path must use IP_CT_DIR_ORIGINAL as well; the zone direction bitmask describes where a zone id applies, not which direction this conncount tuple represents. Fix the two dedup comparisons by passing IP_CT_DIR_ORIGINAL directly. Do not special-case NF_CT_DEFAULT_ZONE_DIR and do not compare raw zone ids: using the existing helpers with IP_CT_DIR_ORIGINAL preserves the direction-aware NF_CT_DEFAULT_ZONE_ID fallback. A default bidirectional zone contains the ORIG bit, so it naturally returns the real zone id; reply-only zones continue to fall back for original-direction tuple comparisons. Fixes: 21ba8847f857 ("netfilter: nf_conncount: Fix garbage collection with zones") Fixes: b36e4523d4d5 ("netfilter: nf_conncount: fix garbage collection confirm race") Cc: stable@vger.kernel.org Reported-by: Yizhou Zhao Reported-by: Yuxiang Yang Reported-by: Ao Wang Reported-by: Xuewei Feng Reported-by: Qi Li Reported-by: Ke Xu Assisted-by: Claude-Code:GLM-5.2 Signed-off-by: Yizhou Zhao Signed-off-by: Florian Westphal --- net/netfilter/nf_conncount.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index 91582069f6d2..e9ea6d9466e7 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -211,8 +211,8 @@ static int __nf_conncount_add(struct net *net, /* Not found, but might be about to be confirmed */ if (PTR_ERR(found) == -EAGAIN) { if (nf_ct_tuple_equal(&conn->tuple, &tuple) && - nf_ct_zone_id(&conn->zone, conn->zone.dir) == - nf_ct_zone_id(zone, zone->dir)) + nf_ct_zone_id(&conn->zone, IP_CT_DIR_ORIGINAL) == + nf_ct_zone_id(zone, IP_CT_DIR_ORIGINAL)) goto out_put; /* already exists */ } else { collect++; @@ -223,7 +223,7 @@ static int __nf_conncount_add(struct net *net, found_ct = nf_ct_tuplehash_to_ctrack(found); if (nf_ct_tuple_equal(&conn->tuple, &tuple) && - nf_ct_zone_equal(found_ct, zone, zone->dir)) { + nf_ct_zone_equal(found_ct, zone, IP_CT_DIR_ORIGINAL)) { /* * We should not see tuples twice unless someone hooks * this into a table without "-p tcp --syn". From bd0bdfae1cf0e064b317cd62b3bc29326684cdc6 Mon Sep 17 00:00:00 2001 From: Zhengyang Chen Date: Mon, 22 Jun 2026 18:10:27 +0800 Subject: [PATCH 241/479] selftests: netfilter: add bridge tunnel flowtable regression Add a nft_flowtable.sh regression test for the bridge direct-xmit plus IPIP/IP6IP6 underlay configuration that reproduces the reachable DIRECT+tunnel tuple combination exercised by the flowtable fix. The test reuses the existing bridge and tunnel topology, installs flow rules for the tunnel egress and bridge reply path, verifies IPv4 and IPv6 forwarding, and checks the flowtable counters after the transfer. Signed-off-by: Zhengyang Chen Signed-off-by: Ren Wei Signed-off-by: Florian Westphal --- .../selftests/net/netfilter/nft_flowtable.sh | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh b/tools/testing/selftests/net/netfilter/nft_flowtable.sh index 08ad07500e8a..fb1c59d45567 100755 --- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh +++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh @@ -736,6 +736,61 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "on bridge"; then ret=1 fi +if ip -net "$nsr1" link show tun0 > /dev/null 2>&1 && + ip -net "$nsr2" link show tun0 > /dev/null 2>&1; then + ip -net "$nsr1" route change default via 192.168.100.2 + ip -net "$nsr2" route change default via 192.168.100.1 + ip -6 -net "$nsr1" route delete default + ip -6 -net "$nsr1" route add default via fee1:3::2 + ip -6 -net "$nsr2" route delete default + ip -6 -net "$nsr2" route add default via fee1:3::1 + ip -net "$ns2" route add default via 10.0.2.1 + ip -6 -net "$ns2" route add default via dead:2::1 + + ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif "tun0" tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' + ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif "tun6" tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' + ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif "veth0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept' + ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif "br0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept' + ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif "tun0" accept' + ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif "tun6" accept' + + ip netns exec "$nsr1" nft reset counters table inet filter >/dev/null + + if test_tcp_forwarding "$ns1" "$ns2" 1 4 10.0.2.99 12345; then + check_counters "bridge + IPIP tunnel" + else + echo "FAIL: flow offload for ns1/ns2 with bridge + IPIP tunnel" 1>&2 + ip netns exec "$nsr1" nft list ruleset + ret=1 + fi + + if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then + check_counters "bridge + IP6IP6 tunnel" + else + echo "FAIL: flow offload for ns1/ns2 with bridge + IP6IP6 tunnel" 1>&2 + ip netns exec "$nsr1" nft list ruleset + ret=1 + fi + + ip -net "$nsr1" route change default via 192.168.10.2 + ip -net "$nsr2" route change default via 192.168.10.1 + ip -net "$ns2" route del default via 10.0.2.1 + ip -6 -net "$nsr1" route delete default + ip -6 -net "$nsr1" route add default via fee1:2::2 + ip -6 -net "$nsr2" route delete default + ip -6 -net "$nsr2" route add default via fee1:2::1 + ip -6 -net "$ns2" route del default via dead:2::1 +else + echo "SKIP: bridge + tunnel flowtable regression (tun0 missing)" + [ "$ret" -eq 0 ] && ret=$ksft_skip +fi + # Another test: # Add bridge interface br0 to Router1, with NAT and VLAN. From 90941d9c925d66a482c9121919ec3546a6988c16 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 9 Jul 2026 13:40:25 +0200 Subject: [PATCH 242/479] netfilter: flowtable: use correct direction to set up tunnel route The layer 2 encapsulation and layer 3 tunnel information in the xmit path is taken from the other tuple, because the tunnel information that is included in the tuple for hashtable lookups is also used to perform the egress encapsulation in the transmit path. This patch uses the correct direction when setting up the tunnel, the original proposed patch to address this fix uses the reversed direction. While at it, remove the redundant check to call dst_release() to drop the reference on the dst that was obtained from the forward path, which is not useful in the direct xmit path unless tunneling is performed. Fixes: fa7395c02d95 ("netfilter: flowtable: support IPIP tunnel with direct xmit") Cc: stable@vger.kernel.org Signed-off-by: Pablo Neira Ayuso Signed-off-by: Florian Westphal --- net/netfilter/nf_flow_table_core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c index 2a829b5e8240..b66e65439341 100644 --- a/net/netfilter/nf_flow_table_core.c +++ b/net/netfilter/nf_flow_table_core.c @@ -127,18 +127,18 @@ static int flow_offload_fill_route(struct flow_offload *flow, switch (route->tuple[dir].xmit_type) { case FLOW_OFFLOAD_XMIT_DIRECT: - if (flow_tuple->tun_num) { + if (route->tuple[!dir].in.num_tuns) { flow_tuple->dst_cache = dst; flow_tuple->dst_cookie = flow_offload_dst_cookie(flow_tuple); + } else { + dst_release(dst); } memcpy(flow_tuple->out.h_dest, route->tuple[dir].out.h_dest, ETH_ALEN); memcpy(flow_tuple->out.h_source, route->tuple[dir].out.h_source, ETH_ALEN); flow_tuple->out.ifidx = route->tuple[dir].out.ifindex; - if (!flow_tuple->tun_num) - dst_release(dst); break; case FLOW_OFFLOAD_XMIT_XFRM: case FLOW_OFFLOAD_XMIT_NEIGH: From a2f57827bf7c695b8c72dc4511cae8e86582369d Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Wed, 8 Jul 2026 16:21:30 +0200 Subject: [PATCH 243/479] ipvs: reload ip header after head reallocation __ip_vs_get_out_rt() calls skb_ensure_writable() which may reallocate skb->head. Fixes: 8d8e20e2d7bb ("ipvs: Decrement ttl") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-sonnet-4-6 Acked-by: Julian Anastasov Signed-off-by: Florian Westphal --- net/netfilter/ipvs/ip_vs_xmit.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index ce542ed4b013..9fef4335da13 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -736,13 +736,11 @@ int ip_vs_bypass_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh) { - struct iphdr *iph = ip_hdr(skb); - - if (__ip_vs_get_out_rt(cp->ipvs, cp->af, skb, NULL, iph->daddr, + if (__ip_vs_get_out_rt(cp->ipvs, cp->af, skb, NULL, ip_hdr(skb)->daddr, IP_VS_RT_MODE_NON_LOCAL, NULL, ipvsh) < 0) goto tx_error; - ip_send_check(iph); + ip_send_check(ip_hdr(skb)); /* Another hack: avoid icmp_send in ip_fragment */ skb->ignore_df = 1; From b3fe4cbd583895987935a9bdad01c8f9d3a02310 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Wed, 8 Jul 2026 21:03:15 +0300 Subject: [PATCH 244/479] ipvs: fix more places with wrong ipv6 transport offsets Sashiko reports for more incorrect IPv6 transport offsets. The app code for TCP was assuming IPv4 network header even after the ipvsh argument was provided. This can cause problems with apps over IPv6. As for the only official app in the kernel tree (FTP) this problem is harmless because we use Netfilter to mangle the FTP ports and we do not adjust the TCP seq numbers. Also, provide correct offset of the ICMPV6 header in ip_vs_out_icmp_v6() for correct checksum checks when the IPv6 packet has extension headers. Fixes: d12e12299a69 ("ipvs: add ipv6 support to ftp") Fixes: 2a3b791e6e11 ("IPVS: Add/adjust Netfilter hook functions and helpers for v6") Cc: stable@vger.kernel.org Link: https://sashiko.dev/#/patchset/20260706101624.69471-1-zhaoyz24%40mails.tsinghua.edu.cn Signed-off-by: Julian Anastasov Signed-off-by: Florian Westphal --- net/netfilter/ipvs/ip_vs_app.c | 10 ++++------ net/netfilter/ipvs/ip_vs_core.c | 3 +-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_app.c b/net/netfilter/ipvs/ip_vs_app.c index d54d7da58334..b0e00be85cb1 100644 --- a/net/netfilter/ipvs/ip_vs_app.c +++ b/net/netfilter/ipvs/ip_vs_app.c @@ -361,14 +361,13 @@ static inline int app_tcp_pkt_out(struct ip_vs_conn *cp, struct sk_buff *skb, struct ip_vs_iphdr *ipvsh) { int diff; - const unsigned int tcp_offset = ip_hdrlen(skb); struct tcphdr *th; __u32 seq; - if (skb_ensure_writable(skb, tcp_offset + sizeof(*th))) + if (skb_ensure_writable(skb, ipvsh->len + sizeof(*th))) return 0; - th = (struct tcphdr *)(skb_network_header(skb) + tcp_offset); + th = (struct tcphdr *)(skb_network_header(skb) + ipvsh->len); /* * Remember seq number in case this pkt gets resized @@ -438,14 +437,13 @@ static inline int app_tcp_pkt_in(struct ip_vs_conn *cp, struct sk_buff *skb, struct ip_vs_iphdr *ipvsh) { int diff; - const unsigned int tcp_offset = ip_hdrlen(skb); struct tcphdr *th; __u32 seq; - if (skb_ensure_writable(skb, tcp_offset + sizeof(*th))) + if (skb_ensure_writable(skb, ipvsh->len + sizeof(*th))) return 0; - th = (struct tcphdr *)(skb_network_header(skb) + tcp_offset); + th = (struct tcphdr *)(skb_network_header(skb) + ipvsh->len); /* * Remember seq number in case this pkt gets resized diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index 35cbe821c259..bafab93451d0 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -1219,8 +1219,7 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, snet.in6 = ciph.saddr.in6; offset = ciph.len; return handle_response_icmp(AF_INET6, skb, &snet, ciph.protocol, cp, - pp, offset, sizeof(struct ipv6hdr), - hooknum); + pp, offset, ipvsh->len, hooknum); } #endif From f468c48d488d0ea2df3422b3e1dfafae1611e853 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 10 Jul 2026 12:44:41 +0200 Subject: [PATCH 245/479] netfilter: xt_physdev: masks are not c-strings ... and must not be subjected to the 'nul terminated' constraint. If the interface name is 15 characters long, the mask is 16-bytes '0xff' (to cover for \0) and the valid device name is rejected. Fixes: 8df772afc9d0 ("netfilter: x_physdev: reject empty or not-nul terminated device names") Cc: stable@vger.kernel.org Closes: https://bugs.launchpad.net/neutron/+bug/2159935 Signed-off-by: Florian Westphal --- net/netfilter/xt_physdev.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/net/netfilter/xt_physdev.c b/net/netfilter/xt_physdev.c index dd98f758176c..a388881c68d4 100644 --- a/net/netfilter/xt_physdev.c +++ b/net/netfilter/xt_physdev.c @@ -130,11 +130,6 @@ static int physdev_mt_check(const struct xt_mtchk_param *par) if (X(physoutdev)) return -ENAMETOOLONG; } - - if (X(in_mask)) - return -ENAMETOOLONG; - if (X(out_mask)) - return -ENAMETOOLONG; #undef X if (!brnf_probed) { From 1cd23ca80784223fa2204e16203f754da4e821f8 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Fri, 3 Jul 2026 20:35:46 -0700 Subject: [PATCH 246/479] sctp: validate STALE_COOKIE cause length before reading staleness When an ERROR chunk with a STALE_COOKIE cause is received in the COOKIE_ECHOED state, sctp_sf_do_5_2_6_stale() reads the 4-byte Measure of Staleness that follows the cause header: err = (struct sctp_errhdr *)(chunk->skb->data); stale = ntohl(*(__be32 *)((u8 *)err + sizeof(*err))); err is the first cause in the chunk, not the STALE_COOKIE cause that caused the dispatch, and nothing guarantees the staleness field is present. sctp_walk_errors() only requires a cause to be as long as the 4-byte header, so for a STALE_COOKIE cause of length 4 the read runs past the cause, and for a minimal ERROR chunk past skb->tail. The value is echoed to the peer in the Cookie Preservative of the reply INIT, leaking uninitialized memory. sctp_sf_cookie_echoed_err() already walks to the STALE_COOKIE cause, so check its length there and pass it to sctp_sf_do_5_2_6_stale(), which reads that cause instead of the first one. A STALE_COOKIE cause too short to hold the staleness field is discarded. The read is reachable by any peer that can drive an association into COOKIE_ECHOED, including an unprivileged process using a raw SCTP socket in a user and network namespace. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Xiang Mei Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Weiming Shi Acked-by: Xin Long Link: https://patch.msgid.link/20260704033545.2438373-2-bestswngs@gmail.com Signed-off-by: Paolo Abeni --- net/sctp/sm_statefuns.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index d23d935e128e..3893b44448b3 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -74,7 +74,8 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale( const struct sctp_association *asoc, const union sctp_subtype type, void *arg, - struct sctp_cmd_seq *commands); + struct sctp_cmd_seq *commands, + struct sctp_errhdr *err); static enum sctp_disposition sctp_sf_shut_8_4_5( struct net *net, const struct sctp_endpoint *ep, @@ -2529,9 +2530,15 @@ enum sctp_disposition sctp_sf_cookie_echoed_err( * errors. */ sctp_walk_errors(err, chunk->chunk_hdr) { - if (SCTP_ERROR_STALE_COOKIE == err->cause) - return sctp_sf_do_5_2_6_stale(net, ep, asoc, type, - arg, commands); + if (err->cause != SCTP_ERROR_STALE_COOKIE) + continue; + /* The staleness is only meaningful if the cause is long + * enough to hold it; a shorter one is malformed. + */ + if (ntohs(err->length) < sizeof(*err) + sizeof(__be32)) + break; + return sctp_sf_do_5_2_6_stale(net, ep, asoc, type, + arg, commands, err); } /* It is possible to have malformed error causes, and that @@ -2573,13 +2580,13 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale( const struct sctp_association *asoc, const union sctp_subtype type, void *arg, - struct sctp_cmd_seq *commands) + struct sctp_cmd_seq *commands, + struct sctp_errhdr *err) { int attempts = asoc->init_err_counter + 1; - struct sctp_chunk *chunk = arg, *reply; struct sctp_cookie_preserve_param bht; struct sctp_bind_addr *bp; - struct sctp_errhdr *err; + struct sctp_chunk *reply; u32 stale; if (attempts > asoc->max_init_attempts) { @@ -2590,8 +2597,6 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale( return SCTP_DISPOSITION_DELETE_TCB; } - err = (struct sctp_errhdr *)(chunk->skb->data); - /* When calculating the time extension, an implementation * SHOULD use the RTT information measured based on the * previous COOKIE ECHO / ERROR exchange, and should add no From 6ee4140788234a6fabf59e6a50e38cdb936008cd Mon Sep 17 00:00:00 2001 From: Atish Patra Date: Tue, 2 Jun 2026 15:36:32 -0700 Subject: [PATCH 247/479] KVM: SEV: Do not allow intra-host migration/mirroring of SNP VMs The intra-host migration/mirroring feature is not fully implemented for SEV-SNP VMs. The proper migration requires additional SNP-specific state such as guest_req_mutex, guest_req_buf, and guest_resp_buf to be transferred or initialized on the destination. The SNP VM mirroring requires vmsa features to be copied as well otherwise ASID would be bound to SNP range while VM is detected as a SEV VM. Reject SNP source VMs in migration/mirroring until proper SNP state transfer is implemented. Fixes: 1dfe571c12cf ("KVM: SEV: Add initial SEV-SNP support") Reported-by: Chris Mason Reported-by: Sashiko Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Tom Lendacky Signed-off-by: Atish Patra Link: https://patch.msgid.link/20260602-sev_snp_fixes-v3-1-24bfd3ae047c@meta.com Cc: stable@vger.kernel.org [sean: let lines poke past 80 chars, tag for stable] Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 427229347876..944aaea6501f 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -2129,8 +2129,9 @@ int sev_vm_move_enc_context_from(struct kvm *kvm, unsigned int source_fd) if (ret) return ret; + /* Do not allow SNP VM migration until additional state transfer is implemented */ if (kvm->arch.vm_type != source_kvm->arch.vm_type || - sev_guest(kvm) || !sev_guest(source_kvm)) { + sev_guest(kvm) || !sev_guest(source_kvm) || sev_snp_guest(source_kvm)) { ret = -EINVAL; goto out_unlock; } @@ -2851,8 +2852,9 @@ int sev_vm_copy_enc_context_from(struct kvm *kvm, unsigned int source_fd) * disallow out-of-band SEV/SEV-ES init if the target is already an * SEV guest, or if vCPUs have been created. KVM relies on vCPUs being * created after SEV/SEV-ES initialization, e.g. to init intercepts. + * Also do not allow SNP VM mirroring until additional state transfer is implemented. */ - if (sev_guest(kvm) || !sev_guest(source_kvm) || + if (sev_guest(kvm) || !sev_guest(source_kvm) || sev_snp_guest(source_kvm) || is_mirroring_enc_context(source_kvm) || kvm->created_vcpus) { ret = -EINVAL; goto e_unlock; From df371f2c62446c5fc5c5c1c91b47c219bb0d2100 Mon Sep 17 00:00:00 2001 From: Atish Patra Date: Tue, 2 Jun 2026 15:36:33 -0700 Subject: [PATCH 248/479] KVM: selftests: Verify SNP VMs are rejected from migration and mirroring Migration and mirroring of SEV-SNP VMs are not supported yet. Add two selftests that verify KVM rejects intra-host migration and mirroring when the source VM is an SNP VM, so the restriction stays enforced until proper SNP state transfer is implemented. Signed-off-by: Atish Patra Link: https://patch.msgid.link/20260602-sev_snp_fixes-v3-2-24bfd3ae047c@meta.com Signed-off-by: Sean Christopherson --- .../selftests/kvm/x86/sev_migrate_tests.c | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tools/testing/selftests/kvm/x86/sev_migrate_tests.c b/tools/testing/selftests/kvm/x86/sev_migrate_tests.c index 42bc023d5193..d59abb198d86 100644 --- a/tools/testing/selftests/kvm/x86/sev_migrate_tests.c +++ b/tools/testing/selftests/kvm/x86/sev_migrate_tests.c @@ -313,6 +313,49 @@ static void test_sev_mirror_parameters(void) kvm_vm_free(vm_no_vcpu); } +static void test_sev_snp_migrate_reject(void) +{ + struct kvm_vm *src_vm, *dst_vm; + int ret; + + src_vm = vm_create_barebones_type(KVM_X86_SNP_VM); + snp_vm_init(src_vm); + __vm_vcpu_add(src_vm, 0); + vm_sev_launch(src_vm, snp_default_policy(), NULL); + + dst_vm = vm_create_barebones_type(KVM_X86_SNP_VM); + __vm_vcpu_add(dst_vm, 0); + + ret = __sev_migrate_from(dst_vm, src_vm); + TEST_ASSERT(ret == -1 && errno == EINVAL, + "SNP VM migration should be rejected. ret: %d, errno: %d", + ret, errno); + + kvm_vm_free(src_vm); + kvm_vm_free(dst_vm); +} + +static void test_sev_snp_mirror_reject(void) +{ + struct kvm_vm *src_vm, *dst_vm; + int ret; + + src_vm = vm_create_barebones_type(KVM_X86_SNP_VM); + snp_vm_init(src_vm); + __vm_vcpu_add(src_vm, 0); + vm_sev_launch(src_vm, snp_default_policy(), NULL); + + dst_vm = aux_vm_create(false); + + ret = __sev_mirror_create(dst_vm, src_vm); + TEST_ASSERT(ret == -1 && errno == EINVAL, + "SNP VM mirroring should be rejected. ret: %d, errno: %d", + ret, errno); + + kvm_vm_free(src_vm); + kvm_vm_free(dst_vm); +} + static void test_sev_move_copy(void) { struct kvm_vm *dst_vm, *dst2_vm, *dst3_vm, *sev_vm, *mirror_vm, @@ -384,12 +427,16 @@ int main(int argc, char *argv[]) test_sev_migrate_parameters(); if (kvm_has_cap(KVM_CAP_VM_COPY_ENC_CONTEXT_FROM)) test_sev_move_copy(); + if (kvm_cpu_has(X86_FEATURE_SEV_SNP)) + test_sev_snp_migrate_reject(); } if (kvm_has_cap(KVM_CAP_VM_COPY_ENC_CONTEXT_FROM)) { test_sev_mirror(/* es= */ false); if (have_sev_es) test_sev_mirror(/* es= */ true); test_sev_mirror_parameters(); + if (kvm_cpu_has(X86_FEATURE_SEV_SNP)) + test_sev_snp_mirror_reject(); } return 0; } From 745df2052cf94b8e3348da59924d2eb0e24d4bb7 Mon Sep 17 00:00:00 2001 From: Gary Yang Date: Fri, 26 Jun 2026 17:20:15 +0800 Subject: [PATCH 249/479] MAINTAINERS: Update maintainer and git tree for CIX SoC Peter Chen has left CIX Technology. Take over maintenance of the CIX SoC and Update the git tree URL accordingly. Signed-off-by: Gary Yang Reviewed-by: Fugang Duan Acked-by: Peter Chen Signed-off-by: Arnd Bergmann --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3fd6b8e4cff6..bc08a73e2055 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2767,12 +2767,12 @@ F: arch/arm/mach-ep93xx/ F: drivers/iio/adc/ep93xx_adc.c ARM/CIX SOC SUPPORT -M: Peter Chen +M: Gary Yang M: Fugang Duan R: CIX Linux Kernel Upstream Group L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained -T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/cix.git +T: git https://github.com/cixtech/linux-mainline.git F: Documentation/devicetree/bindings/arm/cix.yaml F: Documentation/devicetree/bindings/mailbox/cix,sky1-mbox.yaml F: arch/arm64/boot/dts/cix/ From cfbebb55e5127dc162e73fa8956000055a78606c Mon Sep 17 00:00:00 2001 From: Binbin Wu Date: Fri, 10 Jul 2026 11:53:23 +0800 Subject: [PATCH 250/479] KVM: TDX: Reject concurrent change to CPUID entry count Reject KVM_TDX_INIT_VM if userspace changes cpuid.nent between the initial read and the subsequent copy of the initialization data. tdx_td_init() first reads user_data->cpuid.nent to size the flexible kvm_tdx_init_vm copy. The copied structure also contains cpuid.nent, and that field can differ from the value used to size the allocation if userspace modifies the input concurrently. setup_tdparams_cpuids() later passes init_vm->cpuid.nent to kvm_find_cpuid_entry2(), which uses it as the array bound for the copied entries. Require the copied count to match the value used to size the allocation so that CPUID parsing cannot access beyond the entries actually copied. Fixes: 0bd0a4a1428b ("KVM: TDX: Replace kmalloc + copy_from_user with memdup_user in tdx_td_init()") Reported-by: Sashiko:gemini-3.1-pro-preview Cc: Signed-off-by: Binbin Wu Reviewed-by: Xiaoyao Li Reviewed-by: Thorsten Blum Link: https://patch.msgid.link/20260710035324.3170534-1-binbin.wu@linux.intel.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/vmx/tdx.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index 989ab29b8c6f..545b03d9d10b 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -2797,7 +2797,11 @@ static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd) goto out; } - if (init_vm->cpuid.padding) { + /* + * Reject the request if userspace changes cpuid.nent between the + * initial read and the subsequent copy. + */ + if (init_vm->cpuid.padding || init_vm->cpuid.nent != nr_user_entries) { ret = -EINVAL; goto out; } From 22a78be4123dce81d72c963a98b774b7d5e1f8e5 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 8 Jul 2026 15:32:39 -0400 Subject: [PATCH 251/479] selftests/ftrace: Fix reading enabled_functions in add_remove_fprobe_module test The add_remove_fprobe_module test checks the number of functions added to the enabled_functions file to make sure that the functions added or removed is as expected. The issue is that it expects this file to be empty on start up. Now that systemd uses BPF that attaches to functions via ftrace, this file is not empty in several systems: # cat /sys/kernel/tracing/enabled_functions bpf_lsm_file_open (1) R D M tramp: ftrace_regs_caller+0x0/0x61 (call_direct_funcs+0x0/0x50) direct(jmp)-->bpf_trampoline_6442529439+0x0/0xe9 Change the test to read the number of lines in enabled_functions at the start of the test and subtract that from the value of the count for the checks within the test. Link: https://lore.kernel.org/r/20260708153239.055d56dd@gandalf.local.home Signed-off-by: Steven Rostedt Acked-by: Masami Hiramatsu (Google) Signed-off-by: Shuah Khan --- .../dynevent/add_remove_fprobe_module.tc | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc index 2915206777b6..89660a9adf44 100644 --- a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc +++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc @@ -16,23 +16,32 @@ echo > dynamic_events FUNC1='foo_bar*' FUNC2='vfs_read' +:;: "Save enabled functions count" ;: +ecount=`cat enabled_functions | wc -l` + +count_enabled_functions() { + count=`cat enabled_functions | wc -l` + count=$(($count-$ecount)) + echo $count +} + :;: "Add an event on the test module" ;: echo "f:test1 $FUNC1" >> dynamic_events echo 1 > events/fprobes/test1/enable :;: "Ensure it is enabled" ;: -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $funcs -ne 0 :;: "Check the enabled_functions is cleared on unloading" ;: rmmod trace-events-sample -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $funcs -eq 0 :;: "Check it is kept clean" ;: modprobe trace-events-sample echo 1 > events/fprobes/test1/enable || echo "OK" -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $funcs -eq 0 :;: "Add another event not on the test module" ;: @@ -40,19 +49,19 @@ echo "f:test2 $FUNC2" >> dynamic_events echo 1 > events/fprobes/test2/enable :;: "Ensure it is enabled" ;: -ofuncs=`cat enabled_functions | wc -l` +ofuncs=`count_enabled_functions` test $ofuncs -ne 0 :;: "Disable and remove the first event" echo 0 > events/fprobes/test1/enable echo "-:fprobes/test1" >> dynamic_events -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $ofuncs -eq $funcs :;: "Disable and remove other events" ;: echo 0 > events/fprobes/enable echo > dynamic_events -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $funcs -eq 0 rmmod trace-events-sample @@ -63,12 +72,12 @@ echo "f:test1 $FUNC1" >> dynamic_events echo 1 > events/fprobes/test1/enable echo "f:test2 $FUNC2" >> dynamic_events echo 1 > events/fprobes/test2/enable -ofuncs=`cat enabled_functions | wc -l` +ofuncs=`count_enabled_functions` test $ofuncs -ne 0 :;: "Unload module (ftrace entry should be removed)" ;: rmmod trace-events-sample -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $funcs -ne 0 test $ofuncs -ne $funcs @@ -77,7 +86,7 @@ echo 0 > events/fprobes/test2/enable echo "-:fprobes/test2" >> dynamic_events :;: "Ensure ftrace is disabled." ;: -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $funcs -eq 0 echo 0 > events/fprobes/enable From c5c413534d40eb4c982f1794e62813f926aba747 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 8 Jul 2026 16:34:36 -0400 Subject: [PATCH 252/479] selftests/tracing: Have trigger-hist-poll.tc use sched_process_exit Currently trigger-hist-poll.tc uses sched_process_free to test the polling of the histogram file. The way it does that is to run sleep, then execute the poll.c code that polls on the sched_process_free for up to 4 seconds to test that when sleep triggers the sched_process_free trace event, it will update the histogram and wake the poll.c code up. The issue is that sched_process_free trace event is called by delayed_put_task_struct() which is called after a RCU grace period has ended. If CONFIG_RCU_LAZY is enabled, RCU callbacks are batched together and do not execute right away. This causes the delayed_put_task_struct() to be called after the poll.c function finishes and it will report an error that it did not wake up on the event. That's because the event didn't trigger during its wait time. Use sched_process_exit instead, which is called when a process exits and doesn't depend on RCU callbacks that may be delayed. Link: https://lore.kernel.org/r/20260708163436.058cc3df@gandalf.local.home Signed-off-by: Steven Rostedt Acked-by: Masami Hiramatsu (Google) Signed-off-by: Shuah Khan --- .../selftests/ftrace/test.d/trigger/trigger-hist-poll.tc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc b/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc index 8d275e3238d9..04eb8546fc07 100644 --- a/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc +++ b/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc @@ -1,7 +1,7 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0 # description: event trigger - test poll wait on histogram -# requires: set_event events/sched/sched_process_free/trigger events/sched/sched_process_free/hist +# requires: set_event events/sched/sched_process_exit/trigger events/sched/sched_process_exit/hist # flags: instance POLL=${FTRACETEST_ROOT}/poll @@ -11,7 +11,7 @@ if [ ! -x ${POLL} ]; then exit_unresolved fi -EVENT=events/sched/sched_process_free/ +EVENT=events/sched/sched_process_exit/ # Check poll ops is supported. Before implementing poll on hist file, it # returns soon with POLLIN | POLLOUT, but not POLLPRI. From 5142c56651578abc346d6c17f3fb919b9ffbb317 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 8 Jul 2026 11:54:58 +0200 Subject: [PATCH 253/479] bug: fix warning suppressions with kunit built as module CONFIG_KUNIT is a tristate symbol but the warning suppression code in lib/bug.c is only built if it's built-in due to it using a plain #ifdef, rendering warning suppressions broken for kunit build as loadable module. kunit_is_suppressed_warning() already has a stub for when kunit is disabled so drop that guard entirely. Link: https://lore.kernel.org/r/20260708095459.12111-1-bartosz.golaszewski@oss.qualcomm.com Suggested-by: Albert Esteve Cc: stable@vger.kernel.org Fixes: 85347718ab0d ("bug/kunit: Core support for suppressing warning backtraces") Signed-off-by: Bartosz Golaszewski Reviewed-by: Albert Esteve Reviewed-by: David Gow Signed-off-by: Shuah Khan --- lib/bug.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/bug.c b/lib/bug.c index 292420f45811..7c1c2c27f58e 100644 --- a/lib/bug.c +++ b/lib/bug.c @@ -219,14 +219,12 @@ static enum bug_trap_type __report_bug(struct bug_entry *bug, unsigned long buga no_cut = bug->flags & BUGFLAG_NO_CUT_HERE; has_args = bug->flags & BUGFLAG_ARGS; -#ifdef CONFIG_KUNIT /* * Before the once logic so suppressed warnings do not consume * the single-fire budget of WARN_ON_ONCE(). */ if (warning && kunit_is_suppressed_warning(true)) return BUG_TRAP_TYPE_WARN; -#endif disable_trace_on_warning(); From 0e2f4ab68a89fad42e0f5a9ff4b740738e7aa1d6 Mon Sep 17 00:00:00 2001 From: Kuba Piecuch Date: Fri, 10 Jul 2026 14:43:41 +0000 Subject: [PATCH 254/479] sched_ext: Skip ops.set_weight() for disabled tasks When switching a task's sched_class away from sched_ext, we get the following sequence of events in __sched_setscheduler(): sched_change_begin() switched_from_scx() scx_disable_task(p) ops.disable(p) __setscheduler_params() set_load_weight() reweight_task_scx(p) ops.set_weight(p) p->sched_class = next_class; sched_change_end() ... Notably, ops.set_weight() is called _after_ ops.disable(). This violates the expected semantics of the callbacks, the expectation being that ops.disable() can only be followed by ops.exit_task() or ops.enable(). Skipping the weight adjustment for disabled tasks should be harmless since the weight will be recalculated in scx_enable_task() if the task ever rejoins SCX. Fixes: 637b0682821b ("sched: Fold sched_class::switch{ing,ed}_{to,from}() into the change pattern") Cc: stable@vger.kernel.org # v6.19+ Signed-off-by: Kuba Piecuch Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index aeee44e016ab..e3fa7b2fac9d 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -3967,6 +3967,17 @@ static void reweight_task_scx(struct rq *rq, struct task_struct *p, if (task_dead_and_done(p)) return; + /* + * When switching sched_class away from SCX, reweight_task_scx() + * is called _after_ scx_disable_task(). Skip calling ops.set_weight() + * since the BPF scheduler may have already forgotten the task in + * ops.disable(). + * p->scx.weight will be recalculated in scx_enable_task() if the task + * ever returns to SCX class. + */ + if (scx_get_task_state(p) != SCX_TASK_ENABLED) + return; + p->scx.weight = sched_weight_to_cgroup(scale_load_down(lw->weight)); if (SCX_HAS_OP(sch, set_weight)) SCX_CALL_OP_TASK(sch, set_weight, rq, p, p->scx.weight); From 460511c11f0d67e62c6526b191b205eafc8033b2 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 10 Jul 2026 15:26:04 -0700 Subject: [PATCH 255/479] arc: validate DT CPU map strings before parsing them arc_get_cpu_map() fetches the possible-cpus or present-cpus property from the flat DT and immediately passes the raw pointer to cpulist_parse(). That parser expects a NUL-terminated text buffer, but this path does not prove that the DT property is terminated within its declared bounds. Reject unterminated CPU-map properties before handing them to cpulist_parse(). Changes since v1: - fold the NUL-termination check into the initial lookup test, as suggested by Vineet Gupta Signed-off-by: Pengpeng Hou Signed-off-by: Vineet Gupta --- arch/arc/kernel/smp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/arc/kernel/smp.c b/arch/arc/kernel/smp.c index b2f2c59279a6..2d99dffed0ce 100644 --- a/arch/arc/kernel/smp.c +++ b/arch/arc/kernel/smp.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -43,9 +44,10 @@ static int __init arc_get_cpu_map(const char *name, struct cpumask *cpumask) { unsigned long dt_root = of_get_flat_dt_root(); const char *buf; + int len; - buf = of_get_flat_dt_prop(dt_root, name, NULL); - if (!buf) + buf = of_get_flat_dt_prop(dt_root, name, &len); + if (!buf || !memchr(buf, '\0', len)) return -EINVAL; if (cpulist_parse(buf, cpumask)) From 76f38ad1b39cf0321f7ecb7ea37b46f61a0f3d75 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 10 Jul 2026 15:26:11 -0700 Subject: [PATCH 256/479] ARC: configs: Drop redundant I2C_DESIGNWARE_PLATFORM I2C_DESIGNWARE_PLATFORM is default=y via I2C_DESIGNWARE_CORE, which is enabled. No impact on include/generated/autoconf.h. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Vineet Gupta --- arch/arc/configs/axs101_defconfig | 1 - arch/arc/configs/axs103_defconfig | 1 - arch/arc/configs/axs103_smp_defconfig | 1 - arch/arc/configs/tb10x_defconfig | 1 - 4 files changed, 4 deletions(-) diff --git a/arch/arc/configs/axs101_defconfig b/arch/arc/configs/axs101_defconfig index f930396d9dae..870e5291b7db 100644 --- a/arch/arc/configs/axs101_defconfig +++ b/arch/arc/configs/axs101_defconfig @@ -67,7 +67,6 @@ CONFIG_SERIAL_OF_PLATFORM=y CONFIG_I2C=y CONFIG_I2C_CHARDEV=y CONFIG_I2C_DESIGNWARE_CORE=y -CONFIG_I2C_DESIGNWARE_PLATFORM=y # CONFIG_HWMON is not set CONFIG_DRM=m CONFIG_DRM_I2C_ADV7511=m diff --git a/arch/arc/configs/axs103_defconfig b/arch/arc/configs/axs103_defconfig index 6b779dee5ea0..d45e4d335998 100644 --- a/arch/arc/configs/axs103_defconfig +++ b/arch/arc/configs/axs103_defconfig @@ -67,7 +67,6 @@ CONFIG_SERIAL_OF_PLATFORM=y CONFIG_I2C=y CONFIG_I2C_CHARDEV=y CONFIG_I2C_DESIGNWARE_CORE=y -CONFIG_I2C_DESIGNWARE_PLATFORM=y # CONFIG_HWMON is not set CONFIG_FB=y CONFIG_FRAMEBUFFER_CONSOLE=y diff --git a/arch/arc/configs/axs103_smp_defconfig b/arch/arc/configs/axs103_smp_defconfig index a89b50d5369d..f986c0205f13 100644 --- a/arch/arc/configs/axs103_smp_defconfig +++ b/arch/arc/configs/axs103_smp_defconfig @@ -67,7 +67,6 @@ CONFIG_SERIAL_OF_PLATFORM=y CONFIG_I2C=y CONFIG_I2C_CHARDEV=y CONFIG_I2C_DESIGNWARE_CORE=y -CONFIG_I2C_DESIGNWARE_PLATFORM=y # CONFIG_HWMON is not set CONFIG_DRM=m CONFIG_DRM_I2C_ADV7511=m diff --git a/arch/arc/configs/tb10x_defconfig b/arch/arc/configs/tb10x_defconfig index 865fbc19ef03..6e396a9ddb8b 100644 --- a/arch/arc/configs/tb10x_defconfig +++ b/arch/arc/configs/tb10x_defconfig @@ -61,7 +61,6 @@ CONFIG_SERIAL_8250_DW=y CONFIG_I2C=y # CONFIG_I2C_COMPAT is not set CONFIG_I2C_DESIGNWARE_CORE=y -CONFIG_I2C_DESIGNWARE_PLATFORM=y CONFIG_GPIO_SYSFS=y # CONFIG_HWMON is not set # CONFIG_USB_SUPPORT is not set From 1d0e25c1ddf2063c499264fb2ba0fa6a3e4f8a00 Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Sat, 27 Jun 2026 12:57:25 +0200 Subject: [PATCH 257/479] udmabuf: Ensure to perform cache synchronisation in begin_cpu_udmabuf() The message of commit 504e2b4ab97a ("dma-buf/udmabuf: skip redundant cpu sync to fix cacheline EEXIST warning") says: > The CPU sync at map/unmap time is also redundant for udmabuf: > begin_cpu_udmabuf() and end_cpu_udmabuf() already perform explicit > cache synchronization via dma_sync_sgtable_for_cpu/device() when CPU > access is requested through the dma-buf interface. This, however, does not apply to the first time begin_cpu_udmabuf() is called on an udmabuf, in which case the implementation previously relied on get_sg_table() to perform the cache synchronisation. Ensure to call dma_sync_sgtable_for_cpu() in that case as well. Fixes: 504e2b4ab97a ("dma-buf/udmabuf: skip redundant cpu sync to fix cacheline EEXIST warning") Signed-off-by: Robert Mader Reviewed-by: Mikhail Gavrilov Signed-off-by: Vivek Kasireddy Link: https://patch.msgid.link/20260627105725.9083-1-robert.mader@collabora.com --- drivers/dma-buf/udmabuf.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/dma-buf/udmabuf.c b/drivers/dma-buf/udmabuf.c index bced421c0d65..08f57bc1294d 100644 --- a/drivers/dma-buf/udmabuf.c +++ b/drivers/dma-buf/udmabuf.c @@ -224,21 +224,22 @@ static int begin_cpu_udmabuf(struct dma_buf *buf, { struct udmabuf *ubuf = buf->priv; struct device *dev = ubuf->device->this_device; - int ret = 0; if (!ubuf->sg) { ubuf->sg = get_sg_table(dev, buf, direction); if (IS_ERR(ubuf->sg)) { + int ret; + ret = PTR_ERR(ubuf->sg); ubuf->sg = NULL; + return ret; } else { ubuf->sg_dir = direction; } - } else { - dma_sync_sgtable_for_cpu(dev, ubuf->sg, direction); } - return ret; + dma_sync_sgtable_for_cpu(dev, ubuf->sg, direction); + return 0; } static int end_cpu_udmabuf(struct dma_buf *buf, From 7410d11460eb90d6c9281162ccc6a128534d897d Mon Sep 17 00:00:00 2001 From: James Raphael Tiovalen Date: Sun, 5 Jul 2026 19:36:29 +0800 Subject: [PATCH 258/479] macsec: fix promiscuity refcount leak in macsec_dev_open() When a MACsec interface with IFF_PROMISC set is brought up on top of a device that has hardware offload enabled, macsec_dev_open() first calls dev_set_promiscuity(real_dev, 1) and then propagates the open to the offload device. If that propagation fails, the error path jumps to the clear_allmulti label, which only reverts allmulti and the unicast address. The promiscuity taken on the lower device is never dropped, so real_dev is left permanently stuck in promiscuous mode. Its promiscuity count can no longer be balanced from software. Add a clear_promisc label that drops the promiscuity reference and route the two offload failure paths to it. The dev_set_promiscuity() failure itself still jumps to clear_allmulti, since on that failure the count was not incremented. Fixes: 3cf3227a21d1 ("net: macsec: hardware offloading infrastructure") Cc: stable@vger.kernel.org Signed-off-by: James Raphael Tiovalen Reviewed-by: Sabrina Dubroca Link: https://patch.msgid.link/20260705113629.187490-1-jamestiotio@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/macsec.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c index dd89282f0179..ee0e2eb7dbc6 100644 --- a/drivers/net/macsec.c +++ b/drivers/net/macsec.c @@ -3615,19 +3615,22 @@ static int macsec_dev_open(struct net_device *dev) ops = macsec_get_ops(netdev_priv(dev), &ctx); if (!ops) { err = -EOPNOTSUPP; - goto clear_allmulti; + goto clear_promisc; } ctx.secy = &macsec->secy; err = macsec_offload(ops->mdo_dev_open, &ctx); if (err) - goto clear_allmulti; + goto clear_promisc; } if (netif_carrier_ok(real_dev)) netif_carrier_on(dev); return 0; +clear_promisc: + if (dev->flags & IFF_PROMISC) + dev_set_promiscuity(real_dev, -1); clear_allmulti: if (dev->flags & IFF_ALLMULTI) dev_set_allmulti(real_dev, -1); From 3f1f755366687d051174739fb99f7d560202f60b Mon Sep 17 00:00:00 2001 From: Asim Viladi Oglu Manizada Date: Mon, 6 Jul 2026 09:44:10 +0000 Subject: [PATCH 259/479] net: openvswitch: reject oversized nested action attrs Open vSwitch stores generated flow actions as nlattrs, whose nla_len field is u16. Commit a1e64addf3ff ("net: openvswitch: remove misbehaving actions length check") allowed the total sw_flow_actions stream to grow beyond 64 KiB, which is valid, but also removed the last guard preventing a generated nested action attribute from exceeding U16_MAX. An oversized generated container can thus be closed with a truncated nla_len. A later dump or teardown then walks a structurally different stream than the one that was validated. In particular, an oversized nested CLONE/CT action may cause subsequent bytes in the generated stream to be interpreted as independent actions. Keep the larger total-action-stream behavior, but make nested action close reject generated containers that do not fit in nla_len, and return the error through all callers. For recursive SAMPLE, CLONE, DEC_TTL, and CHECK_PKT_LEN builders, trim resource-owning action-list tails in reverse construction order before discarding failed wrappers, so resources copied into the rejected tails are released before the wrappers are removed. Most failed outer wrappers are discarded by truncating actions_len after child resources have been released. CHECK_PKT_LEN also trims its parent after branch resources are gone. SET/TUNNEL close failures unwind their known tun_dst ownership directly, and SET_TO_MASKED has no external ownership and truncates on close failure. Fixes: a1e64addf3ff ("net: openvswitch: remove misbehaving actions length check") Cc: stable@vger.kernel.org Assisted-by: avom-custom-harness:gpt-5.5-qwen3.6-mod-mix Signed-off-by: Asim Viladi Oglu Manizada Reviewed-by: Eelco Chaudron Reviewed-by: Aaron Conole Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/20260706094336.38639-1-manizada@pm.me Signed-off-by: Paolo Abeni --- net/openvswitch/flow_netlink.c | 201 +++++++++++++++++++++++++-------- 1 file changed, 157 insertions(+), 44 deletions(-) diff --git a/net/openvswitch/flow_netlink.c b/net/openvswitch/flow_netlink.c index 13052408a132..d8079dee700e 100644 --- a/net/openvswitch/flow_netlink.c +++ b/net/openvswitch/flow_netlink.c @@ -2496,13 +2496,56 @@ static inline int add_nested_action_start(struct sw_flow_actions **sfa, return used; } -static inline void add_nested_action_end(struct sw_flow_actions *sfa, - int st_offset) +static inline int add_nested_action_end(struct sw_flow_actions *sfa, + int st_offset) { - struct nlattr *a = (struct nlattr *) ((unsigned char *)sfa->actions + - st_offset); + struct nlattr *a; + u32 attr_len; - a->nla_len = sfa->actions_len - st_offset; + if (WARN_ON_ONCE(st_offset < 0 || + (u32)st_offset > sfa->actions_len)) + return -EINVAL; + + attr_len = sfa->actions_len - (u32)st_offset; + if (WARN_ON_ONCE(attr_len < NLA_HDRLEN)) + return -EINVAL; + + if (attr_len > U16_MAX) + return -EMSGSIZE; + + a = (struct nlattr *)((u8 *)sfa->actions + st_offset); + a->nla_len = attr_len; + return 0; +} + +/* Free the generated action-list tail at @start and truncate it. + * If @nested, @start points to its containing nlattr header. + */ +static void ovs_nla_trim(struct sw_flow_actions *sfa, int start, bool nested) +{ + const struct nlattr *actions; + u32 len; + + if (start < 0) + return; + + if (WARN_ON_ONCE((u32)start > sfa->actions_len)) + return; + + actions = (const struct nlattr *)((u8 *)sfa->actions + start); + len = sfa->actions_len - (u32)start; + + if (nested) { + if (len < NLA_HDRLEN) + goto out; + + actions = (const struct nlattr *)((u8 *)actions + NLA_HDRLEN); + len -= NLA_HDRLEN; + } + + ovs_nla_free_nested_actions(actions, len); +out: + sfa->actions_len = start; } static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr, @@ -2522,6 +2565,7 @@ static int validate_and_copy_sample(struct net *net, const struct nlattr *attr, const struct nlattr *attrs[OVS_SAMPLE_ATTR_MAX + 1]; const struct nlattr *probability, *actions; const struct nlattr *a; + int actions_start; int rem, start, err; struct sample_arg arg; @@ -2565,18 +2609,27 @@ static int validate_and_copy_sample(struct net *net, const struct nlattr *attr, err = ovs_nla_add_action(sfa, OVS_SAMPLE_ATTR_ARG, &arg, sizeof(arg), log); if (err) - return err; + goto err; + actions_start = (*sfa)->actions_len; err = __ovs_nla_copy_actions(net, actions, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (err) + goto err_free; return 0; + +err_free: + ovs_nla_trim(*sfa, actions_start, false); +err: + (*sfa)->actions_len = start; + return err; } static int validate_and_copy_dec_ttl(struct net *net, @@ -2624,18 +2677,31 @@ static int validate_and_copy_dec_ttl(struct net *net, return start; action_start = add_nested_action_start(sfa, OVS_DEC_TTL_ATTR_ACTION, log); - if (action_start < 0) - return action_start; + if (action_start < 0) { + err = action_start; + goto err; + } err = __ovs_nla_copy_actions(net, actions, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; - add_nested_action_end(*sfa, action_start); - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, action_start); + if (err) + goto err_free; + + err = add_nested_action_end(*sfa, start); + if (err) + goto err_free; return 0; + +err_free: + ovs_nla_trim(*sfa, action_start, true); +err: + (*sfa)->actions_len = start; + return err; } static int validate_and_copy_clone(struct net *net, @@ -2646,6 +2712,7 @@ static int validate_and_copy_clone(struct net *net, u32 mpls_label_count, bool log, bool last, u32 depth) { + int actions_start; int start, err; u32 exec; @@ -2661,17 +2728,26 @@ static int validate_and_copy_clone(struct net *net, err = ovs_nla_add_action(sfa, OVS_CLONE_ATTR_EXEC, &exec, sizeof(exec), log); if (err) - return err; + goto err; + actions_start = (*sfa)->actions_len; err = __ovs_nla_copy_actions(net, attr, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (err) + goto err_free; return 0; + +err_free: + ovs_nla_trim(*sfa, actions_start, false); +err: + (*sfa)->actions_len = start; + return err; } void ovs_match_init(struct sw_flow_match *match, @@ -2763,20 +2839,20 @@ static int validate_and_copy_set_tun(const struct nlattr *attr, tun_dst = metadata_dst_alloc(key.tun_opts_len, METADATA_IP_TUNNEL, GFP_KERNEL); - if (!tun_dst) - return -ENOMEM; + if (!tun_dst) { + err = -ENOMEM; + goto err; + } err = dst_cache_init(&tun_dst->u.tun_info.dst_cache, GFP_KERNEL); - if (err) { - dst_release((struct dst_entry *)tun_dst); - return err; - } + if (err) + goto err_free_tun_dst; a = __add_action(sfa, OVS_KEY_ATTR_TUNNEL_INFO, NULL, sizeof(*ovs_tun), log); if (IS_ERR(a)) { - dst_release((struct dst_entry *)tun_dst); - return PTR_ERR(a); + err = PTR_ERR(a); + goto err_free_tun_dst; } ovs_tun = nla_data(a); @@ -2797,8 +2873,16 @@ static int validate_and_copy_set_tun(const struct nlattr *attr, ip_tunnel_info_opts_set(tun_info, TUN_METADATA_OPTS(&key, key.tun_opts_len), key.tun_opts_len, dst_opt_type); - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (WARN_ON_ONCE(err)) + goto err_free_tun_dst; + return 0; + +err_free_tun_dst: + dst_release((struct dst_entry *)tun_dst); +err: + (*sfa)->actions_len = start; return err; } @@ -2971,7 +3055,7 @@ static int validate_set(const struct nlattr *a, /* Convert non-masked non-tunnel set actions to masked set actions. */ if (!masked && key_type != OVS_KEY_ATTR_TUNNEL) { - int start, len = key_len * 2; + int err, start, len = key_len * 2; struct nlattr *at; *skip_copy = true; @@ -2983,8 +3067,11 @@ static int validate_set(const struct nlattr *a, return start; at = __add_action(sfa, key_type, NULL, len, log); - if (IS_ERR(at)) - return PTR_ERR(at); + if (IS_ERR(at)) { + err = PTR_ERR(at); + (*sfa)->actions_len = start; + return err; + } memcpy(nla_data(at), nla_data(ovs_key), key_len); /* Key. */ memset(nla_data(at) + key_len, 0xff, key_len); /* Mask. */ @@ -2994,7 +3081,11 @@ static int validate_set(const struct nlattr *a, mask->ipv6_label &= htonl(0x000FFFFF); } - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (WARN_ON_ONCE(err)) { + (*sfa)->actions_len = start; + return err; + } } return 0; @@ -3040,7 +3131,8 @@ static int validate_and_copy_check_pkt_len(struct net *net, const struct nlattr *acts_if_greater, *acts_if_lesser_eq; struct nlattr *a[OVS_CHECK_PKT_LEN_ATTR_MAX + 1]; struct check_pkt_len_arg arg; - int nested_acts_start; + int greater_acts_start = -1; + int lesser_acts_start = -1; int start, err; err = nla_parse_deprecated_strict(a, OVS_CHECK_PKT_LEN_ATTR_MAX, @@ -3075,37 +3167,58 @@ static int validate_and_copy_check_pkt_len(struct net *net, err = ovs_nla_add_action(sfa, OVS_CHECK_PKT_LEN_ATTR_ARG, &arg, sizeof(arg), log); if (err) - return err; + goto err_free; - nested_acts_start = add_nested_action_start(sfa, - OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_LESS_EQUAL, log); - if (nested_acts_start < 0) - return nested_acts_start; + lesser_acts_start = + add_nested_action_start(sfa, + OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_LESS_EQUAL, + log); + if (lesser_acts_start < 0) { + err = lesser_acts_start; + goto err_free; + } err = __ovs_nla_copy_actions(net, acts_if_lesser_eq, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; - add_nested_action_end(*sfa, nested_acts_start); + err = add_nested_action_end(*sfa, lesser_acts_start); + if (err) + goto err_free; - nested_acts_start = add_nested_action_start(sfa, - OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_GREATER, log); - if (nested_acts_start < 0) - return nested_acts_start; + greater_acts_start = + add_nested_action_start(sfa, + OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_GREATER, + log); + if (greater_acts_start < 0) { + err = greater_acts_start; + goto err_free; + } err = __ovs_nla_copy_actions(net, acts_if_greater, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; - add_nested_action_end(*sfa, nested_acts_start); - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, greater_acts_start); + if (err) + goto err_free; + + err = add_nested_action_end(*sfa, start); + if (err) + goto err_free; return 0; + +err_free: + ovs_nla_trim(*sfa, greater_acts_start, true); + ovs_nla_trim(*sfa, lesser_acts_start, true); + ovs_nla_trim(*sfa, start, false); + return err; } static int validate_psample(const struct nlattr *attr) From 1ecb29b084616ca423ca45ec6a6365da53411516 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Tue, 9 Jun 2026 20:32:47 -0700 Subject: [PATCH 260/479] x86/cpu: Remove Makefile rule for removed UMC CPU support Support for UMC CPUs was removed in 7d328c5de43a ("x86/cpu: Remove CPU_SUP_UMC_32 support"), but a Makefile rule for the support code remained. Remove it. Fixes: 7d328c5de43a ("x86/cpu: Remove CPU_SUP_UMC_32 support") Signed-off-by: Ethan Nelson-Moore Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Ahmed S. Darwish Link: https://patch.msgid.link/20260610033252.164571-1-enelsonmoore@gmail.com --- arch/x86/kernel/cpu/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/x86/kernel/cpu/Makefile b/arch/x86/kernel/cpu/Makefile index d2e8a849f180..5b1070ec85d9 100644 --- a/arch/x86/kernel/cpu/Makefile +++ b/arch/x86/kernel/cpu/Makefile @@ -46,7 +46,6 @@ obj-$(CONFIG_CPU_SUP_HYGON) += hygon.o obj-$(CONFIG_CPU_SUP_CYRIX_32) += cyrix.o obj-$(CONFIG_CPU_SUP_CENTAUR) += centaur.o obj-$(CONFIG_CPU_SUP_TRANSMETA_32) += transmeta.o -obj-$(CONFIG_CPU_SUP_UMC_32) += umc.o obj-$(CONFIG_CPU_SUP_ZHAOXIN) += zhaoxin.o obj-$(CONFIG_CPU_SUP_VORTEX_32) += vortex.o From d130041a7b96f79cd4c7079a6c2431a6db4c9619 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 21 Jun 2026 19:00:10 +0200 Subject: [PATCH 261/479] x86/boot: Reject too long acpi_rsdp= values cmdline_find_option() returns the full length of the parsed acpi_rsdp= value. get_cmdline_acpi_rsdp() then silently truncates values which do not fit in the val[] buffer. Prevent boot_kstrtoul() from parsing a truncated value and then the kernel from silently using the wrong RSDP address, see discussion in Link:. Issue a warning so that the user is aware that s/he supplied a malformed value and can get feedback instead of silent crashes. [ bp: Make commit message more precise. ] Fixes: 3c98e71b42a7 ("x86/boot: Add "acpi_rsdp=" early parsing") Signed-off-by: Thorsten Blum Signed-off-by: Borislav Petkov (AMD) Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/20260617130417.36651-4-thorsten.blum@linux.dev --- arch/x86/boot/compressed/acpi.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/x86/boot/compressed/acpi.c b/arch/x86/boot/compressed/acpi.c index f196b1d1ddf8..aed27604c11f 100644 --- a/arch/x86/boot/compressed/acpi.c +++ b/arch/x86/boot/compressed/acpi.c @@ -184,10 +184,15 @@ static unsigned long get_cmdline_acpi_rsdp(void) char val[MAX_ADDR_LEN] = { }; int ret; - ret = cmdline_find_option("acpi_rsdp", val, MAX_ADDR_LEN); + ret = cmdline_find_option("acpi_rsdp", val, sizeof(val)); if (ret < 0) return 0; + if (ret >= sizeof(val)) { + warn("acpi_rsdp= value too long; ignoring"); + return 0; + } + if (boot_kstrtoul(val, 16, &addr)) return 0; #endif From 9202ee546b0cd71004eed7598546efe4660097da Mon Sep 17 00:00:00 2001 From: Andrey Albershteyn Date: Thu, 9 Jul 2026 17:24:07 +0200 Subject: [PATCH 262/479] xfs: fix null pointer dereference in tracepoint If dfp is not NULL we exit early here, when dfp is NULL it's allocated in xfs_defer_alloc() but not assigned. The tracepoint tries to dereference members of dfp struct. Signed-off-by: Andrey Albershteyn Cc: stable@vger.kernel.org # v6.8 Fixes: 3f3cec031099c3 ("xfs: force small EFIs for reaping btree extents") Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_defer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c index c6909716b041..89501e8bd2f8 100644 --- a/fs/xfs/libxfs/xfs_defer.c +++ b/fs/xfs/libxfs/xfs_defer.c @@ -878,7 +878,7 @@ xfs_defer_add_barrier( if (dfp) return; - xfs_defer_alloc(&tp->t_dfops, &xfs_barrier_defer_type); + dfp = xfs_defer_alloc(&tp->t_dfops, &xfs_barrier_defer_type); trace_xfs_defer_add_item(tp->t_mountp, dfp, NULL); } From d04a179085c262c9ed577d0a4cbc6482ff1fd9a3 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Thu, 11 Jun 2026 13:42:26 -0500 Subject: [PATCH 263/479] scsi: target: Bound PR-OUT TransportID parsing to the received buffer core_scsi3_decode_spec_i_port() and core_scsi3_emulate_register_and_move() hand the raw PERSISTENT RESERVE OUT parameter buffer to target_parse_pr_out_transport_id() without telling it how many bytes are valid. For an iSCSI TransportID (FORMAT CODE 01b), iscsi_parse_pr_out_transport_id() locates the ",i,0x" ISID separator with an unbounded strstr() (and on the error path prints the name with a further unbounded "%s"). An initiator can submit a TransportID whose iSCSI name contains neither a ",i,0x" substring nor a NUL terminator, filling the parameter list to its end, so the scan runs off the end of the buffer. When the parameter list spans more than one page the buffer is a multi-page vmap (transport_kmap_data_sg()), so the over-read walks into the trailing vmalloc guard page and oopses (KASAN: vmalloc-out-of-bounds in strstr). It is reachable by any fabric that delivers a PR OUT to a device exported through an iSCSI TPG, including a guest via vhost-scsi. Pass the number of received bytes down to the parser and validate the iSCSI TransportID's own self-described length (ADDITIONAL LENGTH + 4) once, up front: reject it if it is below the spc4r17 minimum or larger than the received buffer, then bound the separator search, the ISID walk and the name copy by that length. This is the length check the callers already perform after the parse (core_scsi3_decode_spec_i_port() compares tid_len against tpdl, core_scsi3_emulate_register_and_move() validates it against data_length), moved ahead of the scan. Also drop the unbounded "%s" of the unterminated name. Add per-format explicit name-length checks before copying into i_str, rather than silently truncating with min_t: for FORMAT CODE 00b reject if the descriptor body (tid_len - 4 bytes) cannot fit in i_str[TRANSPORT_IQN_LEN]; for FORMAT CODE 01b reject if the name portion (from &buf[4] up to the separator) cannot fit. Both checks make the bounds intent explicit at each format branch. While here, also reject a FORMAT CODE 01b TransportID whose ",i,0x" separator sits at the very end of the descriptor: that leaves an empty ISID and points the returned port nexus pointer at buf + tid_len, one past the descriptor, which the registration code (__core_scsi3_locate_pr_reg(), __core_scsi3_alloc_registration()) then dereferences as the ISID string -- the same over-read of the parameter buffer for a malformed descriptor. Fixes: c66ac9db8d4a ("[SCSI] target: Add LIO target core v4.0.0-rc6") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Reviewed-by: John Garry Reviewed-by: David Disseldorp Link: https://patch.msgid.link/20260611-b4-disp-9f20739e-v6-1-f6630e2aae44@proton.me Signed-off-by: Martin K. Petersen --- drivers/target/target_core_fabric_lib.c | 89 +++++++++++++++++++------ drivers/target/target_core_internal.h | 3 +- drivers/target/target_core_pr.c | 4 +- 3 files changed, 73 insertions(+), 23 deletions(-) diff --git a/drivers/target/target_core_fabric_lib.c b/drivers/target/target_core_fabric_lib.c index 87c5d26a5089..2853b95b2c59 100644 --- a/drivers/target/target_core_fabric_lib.c +++ b/drivers/target/target_core_fabric_lib.c @@ -290,13 +290,24 @@ static void sbp_parse_pr_out_transport_id(char *buf, char *i_str) static bool iscsi_parse_pr_out_transport_id( struct se_portal_group *se_tpg, char *buf, + u32 buf_len, u32 *out_tid_len, char **port_nexus_ptr, char *i_str) { char *p; + u32 tid_len; int i; - u8 format_code = (buf[0] & 0xc0); + u8 format_code; + + /* + * The 4-byte iSCSI TransportID header (FORMAT CODE + 2-byte ADDITIONAL + * LENGTH) must be present before any of it can be parsed. + */ + if (buf_len < 4) + return false; + + format_code = buf[0] & 0xc0; /* * Check for FORMAT CODE 00b or 01b from spc4r17, section 7.5.4.6: * @@ -316,15 +327,17 @@ static bool iscsi_parse_pr_out_transport_id( return false; } /* - * If the caller wants the TransportID Length, we set that value for the - * entire iSCSI Tarnsport ID now. + * Reconstruct the self-described TransportID length from the ADDITIONAL + * LENGTH field plus the 4-byte header. Reject it if it is below the + * spc4r17 section 7.5.4.6 minimum (ADDITIONAL LENGTH shall be at least + * 20) or if it runs past the bytes actually received, so that every + * access below stays inside the TransportID. */ - if (out_tid_len) { - /* The shift works thanks to integer promotion rules */ - *out_tid_len = get_unaligned_be16(&buf[2]); - /* Add four bytes for iSCSI Transport ID header */ - *out_tid_len += 4; - } + tid_len = get_unaligned_be16(&buf[2]) + 4; + if (tid_len < 24 || tid_len > buf_len) + return false; + if (out_tid_len) + *out_tid_len = tid_len; /* * Check for ',i,0x' separator between iSCSI Name and iSCSI Initiator @@ -332,16 +345,32 @@ static bool iscsi_parse_pr_out_transport_id( * format. */ if (format_code == 0x40) { - p = strstr(&buf[4], ",i,0x"); + p = strnstr(&buf[4], ",i,0x", tid_len - 4); if (!p) { - pr_err("Unable to locate \",i,0x\" separator" - " for Initiator port identifier: %s\n", - &buf[4]); + pr_err("Unable to locate \",i,0x\" separator in iSCSI TransportID\n"); + return false; + } + /* + * The iSCSI name runs from &buf[4] up to the separator; reject it + * if it cannot fit in i_str[TRANSPORT_IQN_LEN]. + */ + if (p - &buf[4] >= TRANSPORT_IQN_LEN) { + pr_err("iSCSI Initiator port name too long in TransportID\n"); return false; } *p = '\0'; /* Terminate iSCSI Name */ p += 5; /* Skip over ",i,0x" separator */ + /* + * The ISID must follow the separator. A ",i,0x" sitting at the + * very end of the TransportID leaves no ISID and would point the + * port nexus at buf + tid_len, i.e. past the descriptor, which + * the registration code then reads as the ISID string. + */ + if (p >= buf + tid_len) { + pr_err("Missing ISID in iSCSI Initiator port TransportID\n"); + return false; + } *port_nexus_ptr = p; /* * Go ahead and do the lower case conversion of the received @@ -349,7 +378,7 @@ static bool iscsi_parse_pr_out_transport_id( * for comparison against the running iSCSI session's ISID from * iscsi_target.c:lio_sess_get_initiator_sid() */ - for (i = 0; i < 12; i++) { + for (i = 0; i < 12 && p < buf + tid_len; i++) { /* * The first ISCSI INITIATOR SESSION ID field byte * containing an ASCII null character terminates the @@ -367,10 +396,22 @@ static bool iscsi_parse_pr_out_transport_id( *p = tolower(*p); p++; } - } else + strscpy(i_str, &buf[4], TRANSPORT_IQN_LEN); + } else { *port_nexus_ptr = NULL; - - strscpy(i_str, &buf[4], TRANSPORT_IQN_LEN); + /* + * FORMAT CODE 00b: the name occupies buf[4..tid_len-1]. The + * declared length tid_len - 4 must fit in i_str[TRANSPORT_IQN_LEN]. + * (For 01b the same tid_len bound would be over-restrictive: the + * descriptor also carries the separator and ISID, so a legal + * <=223-byte name gives tid_len up to 244.) + */ + if (tid_len - 4 >= TRANSPORT_IQN_LEN) { + pr_err("iSCSI Initiator port name too long in TransportID\n"); + return false; + } + strscpy(i_str, &buf[4], tid_len - 4); + } return true; } @@ -420,8 +461,16 @@ int target_get_pr_transport_id(struct se_node_acl *nacl, } bool target_parse_pr_out_transport_id(struct se_portal_group *tpg, - char *buf, u32 *out_tid_len, char **port_nexus_ptr, char *i_str) + char *buf, u32 buf_len, u32 *out_tid_len, + char **port_nexus_ptr, char *i_str) { + /* + * The fixed-length SAS/SRP/FCP/SBP TransportIDs are 24 bytes; the iSCSI + * format is variable and bounds itself against buf_len below. + */ + if (tpg->proto_id != SCSI_PROTOCOL_ISCSI && buf_len < 24) + return false; + switch (tpg->proto_id) { case SCSI_PROTOCOL_SAS: /* @@ -440,8 +489,8 @@ bool target_parse_pr_out_transport_id(struct se_portal_group *tpg, sbp_parse_pr_out_transport_id(buf, i_str); break; case SCSI_PROTOCOL_ISCSI: - return iscsi_parse_pr_out_transport_id(tpg, buf, out_tid_len, - port_nexus_ptr, i_str); + return iscsi_parse_pr_out_transport_id(tpg, buf, buf_len, + out_tid_len, port_nexus_ptr, i_str); default: pr_err("Unknown proto_id: 0x%02x\n", tpg->proto_id); return false; diff --git a/drivers/target/target_core_internal.h b/drivers/target/target_core_internal.h index 763e6d26e187..f0886ea29034 100644 --- a/drivers/target/target_core_internal.h +++ b/drivers/target/target_core_internal.h @@ -104,7 +104,8 @@ int target_get_pr_transport_id(struct se_node_acl *nacl, struct t10_pr_registration *pr_reg, int *format_code, unsigned char *buf); bool target_parse_pr_out_transport_id(struct se_portal_group *tpg, - char *buf, u32 *out_tid_len, char **port_nexus_ptr, char *i_str); + char *buf, u32 buf_len, u32 *out_tid_len, + char **port_nexus_ptr, char *i_str); /* target_core_hba.c */ struct se_hba *core_alloc_hba(const char *, u32, u32); diff --git a/drivers/target/target_core_pr.c b/drivers/target/target_core_pr.c index 11790f2c5d80..0b19997c2edd 100644 --- a/drivers/target/target_core_pr.c +++ b/drivers/target/target_core_pr.c @@ -1573,7 +1573,7 @@ core_scsi3_decode_spec_i_port( iport_ptr = NULL; tid_found = target_parse_pr_out_transport_id(tmp_tpg, - ptr, &tid_len, &iport_ptr, i_str); + ptr, tpdl, &tid_len, &iport_ptr, i_str); if (!tid_found) continue; /* @@ -3285,7 +3285,7 @@ core_scsi3_emulate_pro_register_and_move(struct se_cmd *cmd, u64 res_key, goto out; } tid_found = target_parse_pr_out_transport_id(dest_se_tpg, - &buf[24], &tmp_tid_len, &iport_ptr, initiator_str); + &buf[24], tid_len, &tmp_tid_len, &iport_ptr, initiator_str); if (!tid_found) { pr_err("SPC-3 PR REGISTER_AND_MOVE: Unable to locate" " initiator_str from Transport ID\n"); From fda6a1f3c3d7047b5ce5654487649c2daa738bfc Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Wed, 10 Jun 2026 04:22:48 +0000 Subject: [PATCH 264/479] scsi: target: core: Fix iSCSI ISID use-after-free in REGISTER AND MOVE core_scsi3_emulate_pro_register_and_move() maps the PERSISTENT RESERVE OUT parameter list with transport_kmap_data_sg() and parses the destination TransportID with target_parse_pr_out_transport_id(). For an iSCSI TransportID (FORMAT CODE 01b), iscsi_parse_pr_out_transport_id() returns the ISID in iport_ptr as a raw pointer into that mapped buffer. The function then unmaps the buffer with transport_kunmap_data_sg() before dereferencing iport_ptr in strcmp(), __core_scsi3_locate_pr_reg() and core_scsi3_alloc_registration(). When the parameter list spans more than one page (PARAMETER LIST LENGTH > 4096), transport_kmap_data_sg() uses vmap() and transport_kunmap_data_sg() does vunmap(), so the kernel virtual address backing iport_ptr is torn down and every subsequent dereference is a use-after-free read of the unmapped region. Keep the parameter list mapped until iport_ptr is no longer needed: drop the early transport_kunmap_data_sg() and unmap once on the success path, right before returning. The error paths already unmap through the existing "if (buf) transport_kunmap_data_sg(cmd)" at the out: label, which now runs on every post-map error exit because buf is no longer cleared early. Only reads of the mapping happen while spinlocks are held; the map and unmap calls remain outside any lock. The sibling caller core_scsi3_decode_spec_i_port() already uses the buffer before unmapping it and is left unchanged. Fixes: 4949314c7283 ("target: Allow control CDBs with data > 1 page") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Reviewed-by: John Garry Reviewed-by: David Disseldorp Link: https://patch.msgid.link/20260610042245.35473-1-hexlabsecurity@proton.me Signed-off-by: Martin K. Petersen --- drivers/target/target_core_pr.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/target/target_core_pr.c b/drivers/target/target_core_pr.c index 0b19997c2edd..1a77b4bb62b0 100644 --- a/drivers/target/target_core_pr.c +++ b/drivers/target/target_core_pr.c @@ -3293,9 +3293,6 @@ core_scsi3_emulate_pro_register_and_move(struct se_cmd *cmd, u64 res_key, goto out; } - transport_kunmap_data_sg(cmd); - buf = NULL; - pr_debug("SPC-3 PR [%s] Extracted initiator %s identifier: %s" " %s\n", dest_tf_ops->fabric_name, (iport_ptr != NULL) ? "port" : "device", initiator_str, (iport_ptr != NULL) ? @@ -3532,6 +3529,11 @@ core_scsi3_emulate_pro_register_and_move(struct se_cmd *cmd, u64 res_key, core_scsi3_update_and_write_aptpl(cmd->se_dev, aptpl); core_scsi3_put_pr_reg(dest_pr_reg); + /* + * iport_ptr aliases the PR-OUT parameter list mapped above, so the + * buffer is unmapped only here on success (and at out: on error). + */ + transport_kunmap_data_sg(cmd); return 0; out: if (buf) From dccf3b1798b70f94e958b3d00b83010399e6fb05 Mon Sep 17 00:00:00 2001 From: David Jeffery Date: Mon, 15 Jun 2026 13:46:30 -0400 Subject: [PATCH 265/479] scsi: core: wake eh reliably when using scsi_schedule_eh Drivers which use the scsi_schedule_eh function to run the error handler currently risk the error handler thread never waking once all commands are timed out or inactive. There is no enforced memory order between setting the host into error recovery state and counting busy commands. This can result in a race with scsi_dec_host_busy where neither CPU sees both conditions of all commands inactive and the host error state to request waking the error handler. To fix this, run the scsi_schedule_eh's scsi_eh_wakeup from a new work item which will use rcu to ensure scsi_schedule_eh's call to scsi_host_busy will occur after the error state is globally visible and will be seen by any current scsi_dec_host_busy callers. Fixes: 6eb045e092ef ("scsi: core: avoid host-wide host_busy counter for scsi_mq") Signed-off-by: David Jeffery Link: https://patch.msgid.link/20260615174630.11492-1-djeffery@redhat.com Signed-off-by: Martin K. Petersen --- drivers/scsi/hosts.c | 2 ++ drivers/scsi/scsi_error.c | 22 +++++++++++++++++++++- drivers/scsi/scsi_priv.h | 1 + include/scsi/scsi_host.h | 3 +++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index e047747d4ecf..46cc8e3c79a2 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -357,6 +357,7 @@ static void scsi_host_dev_release(struct device *dev) /* Wait for functions invoked through call_rcu(&scmd->rcu, ...) */ rcu_barrier(); + cancel_work_sync(&shost->eh_work); if (shost->tmf_work_q) destroy_workqueue(shost->tmf_work_q); if (shost->ehandler) @@ -422,6 +423,7 @@ struct Scsi_Host *scsi_host_alloc(const struct scsi_host_template *sht, int priv INIT_LIST_HEAD(&shost->starved_list); init_waitqueue_head(&shost->host_wait); mutex_init(&shost->scan_mutex); + INIT_WORK(&shost->eh_work, scsi_rcu_eh_wakeup); index = ida_alloc(&host_index_ida, GFP_KERNEL); if (index < 0) { diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c index 147127fb4db9..453a2232452d 100644 --- a/drivers/scsi/scsi_error.c +++ b/drivers/scsi/scsi_error.c @@ -73,6 +73,26 @@ void scsi_eh_wakeup(struct Scsi_Host *shost, unsigned int busy) } } +void scsi_rcu_eh_wakeup(struct work_struct *work) +{ + struct Scsi_Host *shost = container_of(work, struct Scsi_Host, eh_work); + unsigned long flags; + unsigned int busy; + + /* + * Ensure any running scsi_dec_host_busy has completed its rcu section + * so changes to host state and host_eh_scheduled are visible to all + * future calls of scsi_dec_host_busy + */ + synchronize_rcu(); + + busy = scsi_host_busy(shost); + + spin_lock_irqsave(shost->host_lock, flags); + scsi_eh_wakeup(shost, busy); + spin_unlock_irqrestore(shost->host_lock, flags); +} + /** * scsi_schedule_eh - schedule EH for SCSI host * @shost: SCSI host to invoke error handling on. @@ -88,7 +108,7 @@ void scsi_schedule_eh(struct Scsi_Host *shost) if (scsi_host_set_state(shost, SHOST_RECOVERY) == 0 || scsi_host_set_state(shost, SHOST_CANCEL_RECOVERY) == 0) { shost->host_eh_scheduled++; - scsi_eh_wakeup(shost, scsi_host_busy(shost)); + queue_work(shost->tmf_work_q, &shost->eh_work); } spin_unlock_irqrestore(shost->host_lock, flags); diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h index 37e5601be2b8..3dbf2ca59536 100644 --- a/drivers/scsi/scsi_priv.h +++ b/drivers/scsi/scsi_priv.h @@ -91,6 +91,7 @@ extern enum blk_eh_timer_return scsi_timeout(struct request *req); extern int scsi_error_handler(void *host); extern enum scsi_disposition scsi_decide_disposition(struct scsi_cmnd *cmd); extern void scsi_eh_wakeup(struct Scsi_Host *shost, unsigned int busy); +extern void scsi_rcu_eh_wakeup(struct work_struct *work); extern void scsi_eh_scmd_add(struct scsi_cmnd *); void scsi_eh_ready_devs(struct Scsi_Host *shost, struct list_head *work_q, diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h index 7e2011830ba4..f6b286fa59f2 100644 --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h @@ -750,6 +750,9 @@ struct Scsi_Host { */ struct device *dma_dev; + /* Used for an rcu-synchronizing eh wakeup */ + struct work_struct eh_work; + /* Delay for runtime autosuspend */ int rpm_autosuspend_delay; From 9cb2d5291dbfe7bed565ead3337047dee9ed1064 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Mon, 22 Jun 2026 15:58:44 +0800 Subject: [PATCH 266/479] scsi: elx: efct: Fix I/O leak on unsupported additional CDB efct_dispatch_fcp_cmd() allocates an efct_io before dispatching an unsolicited FCP command. If the command has an unsupported additional CDB, the function returns -EIO before handing the IO to the SCSI layer. Free the allocated IO before returning from this error path. Fixes: f45ae6aac0a0 ("scsi: elx: efct: Unsolicited FC frame processing routines") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Reviewed-by: Daniel Wagner Link: https://patch.msgid.link/20260622075844.832871-1-haoxiang_li2024@163.com Signed-off-by: Martin K. Petersen --- drivers/scsi/elx/efct/efct_unsol.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/elx/efct/efct_unsol.c b/drivers/scsi/elx/efct/efct_unsol.c index e6addab66a60..6a871a59c909 100644 --- a/drivers/scsi/elx/efct/efct_unsol.c +++ b/drivers/scsi/elx/efct/efct_unsol.c @@ -385,6 +385,7 @@ efct_dispatch_fcp_cmd(struct efct_node *node, struct efc_hw_sequence *seq) if (cmnd->fc_flags & FCP_CFL_LEN_MASK) { efc_log_err(efct, "Additional CDB not supported\n"); + efct_scsi_io_free(io); return -EIO; } /* From 2c007acf7b31c39c08ce4959451ad00b19be4c1f Mon Sep 17 00:00:00 2001 From: WenTao Liang Date: Thu, 11 Jun 2026 13:30:37 +0800 Subject: [PATCH 267/479] scsi: elx: efct: Fix refcount leak in efct_hw_io_abort() When efct_hw_reqtag_alloc() fails in efct_hw_io_abort(), the error path returns -ENOSPC without releasing the reference obtained via kref_get_unless_zero() earlier in the function. All other error paths correctly drop the reference. This causes a permanent reference leak on the io_to_abort object. Additionally, the abort_in_progress flag is left set to true on this path, which means future abort attempts for the same I/O will immediately return -EINPROGRESS even though the abort was never submitted, effectively blocking recovery. Fix this by adding the missing kref_put() call and reset abort_in_progress to false, matching the cleanup done in the efct_hw_wq_write() failure path below. Cc: stable@vger.kernel.org Fixes: 63de51327a64 ("scsi: elx: efct: Hardware I/O and SGL initialization") Signed-off-by: WenTao Liang Reviewed-by: Daniel Wagner Link: https://patch.msgid.link/20260611053037.63756-1-vulab@iscas.ac.cn Signed-off-by: Martin K. Petersen --- drivers/scsi/elx/efct/efct_hw.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/scsi/elx/efct/efct_hw.c b/drivers/scsi/elx/efct/efct_hw.c index 1838032f6486..b79c6a7ea791 100644 --- a/drivers/scsi/elx/efct/efct_hw.c +++ b/drivers/scsi/elx/efct/efct_hw.c @@ -1997,6 +1997,8 @@ efct_hw_io_abort(struct efct_hw *hw, struct efct_hw_io *io_to_abort, wqcb = efct_hw_reqtag_alloc(hw, efct_hw_wq_process_abort, io_to_abort); if (!wqcb) { efc_log_err(hw->os, "can't allocate request tag\n"); + io_to_abort->abort_in_progress = false; + kref_put(&io_to_abort->ref, io_to_abort->release); return -ENOSPC; } From e166bafc483e927150cb9b5f286c9191ea0df84e Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Tue, 23 Jun 2026 00:00:28 +0800 Subject: [PATCH 268/479] scsi: hpsa: Fix DMA mapping leak on IOACCEL2 reset path If phys_disk->in_reset is set, the function returns directly without undoing the resources acquired for the command. Add the missing error cleanup by unmapping the IOACCEL2 SG chain block when needed, unmapping the SCSI command, and dropping the outstanding IOACCEL command count before returning. Fixes: c5dfd106414f ("scsi: hpsa: correct device resets") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Acked-by: Don Brace Link: https://patch.msgid.link/20260622160028.1240496-1-haoxiang_li2024@163.com Signed-off-by: Martin K. Petersen --- drivers/scsi/hpsa.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/scsi/hpsa.c b/drivers/scsi/hpsa.c index a1b116cd4723..8edad1830abe 100644 --- a/drivers/scsi/hpsa.c +++ b/drivers/scsi/hpsa.c @@ -5017,6 +5017,10 @@ static int hpsa_scsi_ioaccel2_queue_command(struct ctlr_info *h, if (phys_disk->in_reset) { cmd->result = DID_RESET << 16; + atomic_dec(&phys_disk->ioaccel_cmds_out); + scsi_dma_unmap(cmd); + if (use_sg > h->ioaccel_maxsg) + hpsa_unmap_ioaccel2_sg_chain_block(h, cp); return -1; } From 4bbc16a353a98023e5ddfca7c1fc0e49971cf4d0 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 12 Jul 2026 14:37:26 -0700 Subject: [PATCH 269/479] ata: sata_dwc_460ex: enable SATA interrupts only after IRQ handler is registered sata_dwc_enable_interrupts() is called before platform_get_irq() and ata_host_activate(), leaving the SATA controller's interrupt mask enabled without a registered handler. If a later step fails (irq request, phy init, etc.) or if the controller asserts an interrupt during probe, the irq line may fire with no handler, causing a spurious interrupt storm. Move sata_dwc_enable_interrupts() after ata_host_activate() so that interrupts are only unmasked once the handler is registered and the core is fully initialized. Fixes: 62936009f35a ("[libata] Add 460EX on-chip SATA driver, sata_dwc_460ex") Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Signed-off-by: Damien Le Moal --- drivers/ata/sata_dwc_460ex.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/ata/sata_dwc_460ex.c b/drivers/ata/sata_dwc_460ex.c index 4fc22ce4bd9a..e10eeb8ca030 100644 --- a/drivers/ata/sata_dwc_460ex.c +++ b/drivers/ata/sata_dwc_460ex.c @@ -1169,9 +1169,6 @@ static int sata_dwc_probe(struct platform_device *ofdev) /* Save dev for later use in dev_xxx() routines */ hsdev->dev = dev; - /* Enable SATA Interrupts */ - sata_dwc_enable_interrupts(hsdev); - /* Get SATA interrupt number */ irq = irq_of_parse_and_map(np, 0); if (!irq) { @@ -1204,6 +1201,8 @@ static int sata_dwc_probe(struct platform_device *ofdev) if (err) dev_err(dev, "failed to activate host"); + /* Enable SATA Interrupts */ + sata_dwc_enable_interrupts(hsdev); return 0; error_out: From a4af122106f73ea510bb35a9ea1dedd980fc0db7 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 12 Jul 2026 14:37:25 -0700 Subject: [PATCH 270/479] ata: sata_dwc_460ex: use platform_get_irq() Replace irq_of_parse_and_map() with platform_get_irq() in both sata_dwc_dma_init_old() and sata_dwc_probe(). This is the preferred way to obtain IRQs for platform devices and provides better error reporting. Remove the now-unnecessary #include . irq_of_parse_and_map() requires irq_dispose_mapping(), which is missing. Also fix unused variable when CONFIG_SATA_DWC_OLD_DMA is disabled. Fixes: 62936009f35a ("[libata] Add 460EX on-chip SATA driver, sata_dwc_460ex") Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Signed-off-by: Damien Le Moal --- drivers/ata/sata_dwc_460ex.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/drivers/ata/sata_dwc_460ex.c b/drivers/ata/sata_dwc_460ex.c index e10eeb8ca030..85c5e67e9175 100644 --- a/drivers/ata/sata_dwc_460ex.c +++ b/drivers/ata/sata_dwc_460ex.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -226,7 +225,6 @@ static int sata_dwc_dma_init_old(struct platform_device *pdev, struct sata_dwc_device *hsdev) { struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; hsdev->dma = devm_kzalloc(dev, sizeof(*hsdev->dma), GFP_KERNEL); if (!hsdev->dma) @@ -236,11 +234,9 @@ static int sata_dwc_dma_init_old(struct platform_device *pdev, hsdev->dma->id = pdev->id; /* Get SATA DMA interrupt number */ - hsdev->dma->irq = irq_of_parse_and_map(np, 1); - if (!hsdev->dma->irq) { - dev_err(dev, "no SATA DMA irq\n"); - return -ENODEV; - } + hsdev->dma->irq = platform_get_irq(pdev, 1); + if (hsdev->dma->irq < 0) + return hsdev->dma->irq; /* Get physical SATA DMA register base address */ hsdev->dma->regs = devm_platform_ioremap_resource(pdev, 1); @@ -1126,7 +1122,6 @@ static const struct ata_port_info sata_dwc_port_info[] = { static int sata_dwc_probe(struct platform_device *ofdev) { struct device *dev = &ofdev->dev; - struct device_node *np = dev->of_node; struct sata_dwc_device *hsdev; u32 idr, versionr; char *ver = (char *)&versionr; @@ -1170,14 +1165,12 @@ static int sata_dwc_probe(struct platform_device *ofdev) hsdev->dev = dev; /* Get SATA interrupt number */ - irq = irq_of_parse_and_map(np, 0); - if (!irq) { - dev_err(dev, "no SATA DMA irq\n"); - return -ENODEV; - } + irq = platform_get_irq(ofdev, 0); + if (irq < 0) + return irq; #ifdef CONFIG_SATA_DWC_OLD_DMA - if (!of_property_present(np, "dmas")) { + if (!of_property_present(dev->of_node, "dmas")) { err = sata_dwc_dma_init_old(ofdev, hsdev); if (err) return err; From 66c4e310ad71f41e41736d33dd8a1fb5eaaec7f3 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 12 Jul 2026 14:37:27 -0700 Subject: [PATCH 271/479] ata: sata_dwc_460ex: fix clear_interrupt_bit() clearing all pending interrupts clear_interrupt_bit() ignores the bit argument and performs a read-write-back of the entire INTPR register. If INTPR uses standard Write-1-to-Clear semantics, this clears every pending interrupt bit, not just the intended one. Coalesced interrupts (e.g. DMAT + NEWFP) would be cleared together, silently losing the second event. Write only the specific bit to clear so that other pending interrupts are preserved. Fixes: 62936009f35a ("[libata] Add 460EX on-chip SATA driver, sata_dwc_460ex") Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Signed-off-by: Damien Le Moal --- drivers/ata/sata_dwc_460ex.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/ata/sata_dwc_460ex.c b/drivers/ata/sata_dwc_460ex.c index 85c5e67e9175..bc543a408963 100644 --- a/drivers/ata/sata_dwc_460ex.c +++ b/drivers/ata/sata_dwc_460ex.c @@ -394,8 +394,7 @@ static void clear_serror(struct ata_port *ap) static void clear_interrupt_bit(struct sata_dwc_device *hsdev, u32 bit) { - sata_dwc_writel(&hsdev->sata_dwc_regs->intpr, - sata_dwc_readl(&hsdev->sata_dwc_regs->intpr)); + sata_dwc_writel(&hsdev->sata_dwc_regs->intpr, bit); } static u32 qcmd_tag_to_mask(u8 tag) From c2130f6553f4a5cbdc259de069600117a995f197 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 12 Jul 2026 14:37:28 -0700 Subject: [PATCH 272/479] ata: sata_dwc_460ex: fix infinite loop in NCQ tag completion bit-scanning The hand-rolled bit-scanning loop in the NCQ completion path has an infinite loop bug. When tag_mask has only high bits set (e.g. 0x80000000), the inner while loop left-shifts tag_mask until it overflows to 0. At that point !(0 & 1) is always true and 0 <<= 1 stays 0, causing an infinite loop in hardirq context with a spinlock held. Replace the open-coded bit-scanning with __ffs() which correctly finds the least significant set bit and is bounded by the width of the argument. Fixes: 62936009f35a ("[libata] Add 460EX on-chip SATA driver, sata_dwc_460ex") Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Signed-off-by: Damien Le Moal --- drivers/ata/sata_dwc_460ex.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/ata/sata_dwc_460ex.c b/drivers/ata/sata_dwc_460ex.c index bc543a408963..8e3fc713891a 100644 --- a/drivers/ata/sata_dwc_460ex.c +++ b/drivers/ata/sata_dwc_460ex.c @@ -607,14 +607,9 @@ static irqreturn_t sata_dwc_isr(int irq, void *dev_instance) status = ap->ops->sff_check_status(ap); dev_dbg(ap->dev, "%s ATA status register=0x%x\n", __func__, status); - tag = 0; while (tag_mask) { - while (!(tag_mask & 0x00000001)) { - tag++; - tag_mask <<= 1; - } - - tag_mask &= (~0x00000001); + tag = __ffs(tag_mask); + tag_mask &= ~(1U << tag); qc = ata_qc_from_tag(ap, tag); if (unlikely(!qc)) { dev_err(ap->dev, "failed to get qc"); From e4159045c2704dfe146f0ccb0445d9d074cd6882 Mon Sep 17 00:00:00 2001 From: Karol Wachowski Date: Fri, 10 Jul 2026 12:13:31 +0200 Subject: [PATCH 273/479] accel/ivpu: Fix wrong register read in LNL failure diagnostics diagnose_failure_lnl() read VPU_HW_BTRS_MTL_INTERRUPT_STAT instead of VPU_HW_BTRS_LNL_INTERRUPT_STAT, which on LNL and newer parts is a different register with a different bit layout, so failure diagnostics decoded the wrong register and reported a bogus error cause. Read the LNL interrupt status register instead. Fixes: 8a27ad81f7d3 ("accel/ivpu: Split IP and buttress code") Reviewed-by: Andrzej Kacprowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260710101331.1899505-1-karol.wachowski@linux.intel.com --- drivers/accel/ivpu/ivpu_hw_btrs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/ivpu/ivpu_hw_btrs.c b/drivers/accel/ivpu/ivpu_hw_btrs.c index dac935164e11..a17c829adb89 100644 --- a/drivers/accel/ivpu/ivpu_hw_btrs.c +++ b/drivers/accel/ivpu/ivpu_hw_btrs.c @@ -927,7 +927,7 @@ static void diagnose_failure_mtl(struct ivpu_device *vdev) static void diagnose_failure_lnl(struct ivpu_device *vdev) { - u32 reg = REGB_RD32(VPU_HW_BTRS_MTL_INTERRUPT_STAT) & BTRS_LNL_IRQ_MASK; + u32 reg = REGB_RD32(VPU_HW_BTRS_LNL_INTERRUPT_STAT) & BTRS_LNL_IRQ_MASK; if (REG_TEST_FLD(VPU_HW_BTRS_LNL_INTERRUPT_STAT, ATS_ERR, reg)) { ivpu_err(vdev, "ATS_ERR_LOG1 0x%08x ATS_ERR_LOG2 0x%08x\n", From 1572282de6d3377ca8605d48c50df3a8c08468e9 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Mon, 13 Jul 2026 16:35:29 +0800 Subject: [PATCH 274/479] erofs: hide "cache_strategy=" for plain filesystems "cache_strategy=" is meaningless and confusing on unencoded EROFS filesystems; gate it on compressed images only since it's now possible after commit 7cef3c834194 ("erofs: separate plain and compressed filesystems formally"). Signed-off-by: Gao Xiang --- fs/erofs/super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/erofs/super.c b/fs/erofs/super.c index c5881bb8d52b..9d8f862f309f 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -1029,7 +1029,7 @@ static int erofs_show_options(struct seq_file *seq, struct dentry *root) ",user_xattr" : ",nouser_xattr"); if (IS_ENABLED(CONFIG_EROFS_FS_POSIX_ACL)) seq_puts(seq, test_opt(opt, POSIX_ACL) ? ",acl" : ",noacl"); - if (IS_ENABLED(CONFIG_EROFS_FS_ZIP)) + if (IS_ENABLED(CONFIG_EROFS_FS_ZIP) && sbi->available_compr_algs) seq_printf(seq, ",cache_strategy=%s", erofs_param_cache_strategy[opt->cache_strategy].name); if (test_opt(opt, DAX_ALWAYS)) From 47b87f469a35b5ffc81c16eee6b13a9b6c8d55c6 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Mon, 1 Jun 2026 15:50:00 +0800 Subject: [PATCH 275/479] powerpc/spufs: fix out-of-bounds access in spufs_mem_mmap_access() spufs_mem_mmap_access() computes the local store offset as address - vma->vm_start, but bounds-checks it against vma->vm_end instead of the local store size. On 64-bit, offset is always well below vma->vm_end, so the clamp never fires and len stays unbounded against the LS_SIZE buffer returned by ctx->ops->get_ls(). Reject offsets at or beyond LS_SIZE and clamp len to the remaining space, mirroring the guard already used by spufs_mem_mmap_fault() and spufs_ps_fault(). Fixes: a352894d0705 ("spufs: use new vm_ops->access to allow local state access from gdb") Reported-by: Yuhao Jiang Cc: stable@vger.kernel.org Signed-off-by: Junrui Luo Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/SYBPR01MB7881EE775E8B51C09F5A29E7AF152@SYBPR01MB7881.ausprd01.prod.outlook.com --- arch/powerpc/platforms/cell/spufs/file.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/powerpc/platforms/cell/spufs/file.c b/arch/powerpc/platforms/cell/spufs/file.c index f6de8c1169d5..de7494748fec 100644 --- a/arch/powerpc/platforms/cell/spufs/file.c +++ b/arch/powerpc/platforms/cell/spufs/file.c @@ -268,10 +268,12 @@ static int spufs_mem_mmap_access(struct vm_area_struct *vma, if (write && !(vma->vm_flags & VM_WRITE)) return -EACCES; + if (offset >= LS_SIZE) + return -EFAULT; if (spu_acquire(ctx)) return -EINTR; - if ((offset + len) > vma->vm_end) - len = vma->vm_end - offset; + if ((offset + len) > LS_SIZE) + len = LS_SIZE - offset; local_store = ctx->ops->get_ls(ctx); if (write) memcpy_toio(local_store + offset, buf, len); From 93b47e66cc6d6c6382d44b44f5e7f6fc3a7b38c3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Jul 2026 11:56:06 +0200 Subject: [PATCH 276/479] ALSA: usb-audio: Skip DSD quirk for Musical Fidelity M6s DAC Salvador reported that the recent fix for applying the DSD quirk to Musical Fidelity devices broke for his M6s DAC model (2772:0502). Although this is basically a firmware bug, the model in question is fairly old, and no further firmware update can be expected, so it'd be better to address in the driver side. As an ad hoc workaround, skip the DSD quirk for this device by adding an empty quirk entry of 2772:0502; this essentially skips the later DSD quirk entry by the match with the vendor 2772. Fixes: da3a7efff64e ("ALSA: usb-audio: Update for native DSD support quirks") Reported-by: Salvador Blaya Closes: https://lore.kernel.org/CAOdyq+qFaqCh=tK_wNnA64hv5pQuA1Y09ANxQ=xK8yR-t4mf9Q@mail.gmail.com Tested-by: Salvador Blaya Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260709095614.1418838-1-tiwai@suse.de --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 2949a0d2d961..d98825f9697c 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2463,6 +2463,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_DSD_RAW), DEVICE_FLG(0x2708, 0x0002, /* Audient iD14 */ QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x2772, 0x0502, /* Musical Fidelity M6s DAC */ + 0), /* for avoiding QUIRK_FLAG_DSD_RAW with vendor match */ DEVICE_FLG(0x2912, 0x30c8, /* Audioengine D1 */ QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x2a70, 0x1881, /* OnePlus Technology (Shenzhen) Co., Ltd. BE02T */ From 07ae060d35900977a93d4a05ec995b06c85c2552 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 9 Jul 2026 17:12:06 +0100 Subject: [PATCH 277/479] ALSA: hda: cs35l56: Fail if wmfw file is missing Fail cs35l56_hda_fw_load() and log an error if it didn't find a wmfw file and the BIOS didn't patch the firmware already. Also remove a section of code from cs35l56_hda_request_firmware_files() that attempted to find a .bin if a .wmfw was not found. The CS35L56 ROM can only provide default audio in SoundWire mode. A wmfw is needed to enable I2S audio. Also none of the customer-specific .bin files are compatible with the ROM firmware. So a .wmfw file is always required. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260709161211.686498-1-rf@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/cs35l56_hda.c | 30 +++++++--------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/sound/hda/codecs/side-codecs/cs35l56_hda.c b/sound/hda/codecs/side-codecs/cs35l56_hda.c index a0ea08eb96a9..78c2cf387a00 100644 --- a/sound/hda/codecs/side-codecs/cs35l56_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l56_hda.c @@ -512,20 +512,6 @@ static void cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56, NULL, "bin"); return; } - - /* - * Check for system-specific bin files without wmfw before - * falling back to generic firmware - */ - if (amp_name) - cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename, - base_name, system_name, amp_name, "bin"); - if (!*coeff_firmware) - cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filename, - base_name, system_name, NULL, "bin"); - - if (*coeff_firmware) - return; } ret = cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_filename, @@ -616,13 +602,15 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) &wmfw_firmware, &wmfw_filename, &coeff_firmware, &coeff_filename); - /* - * If the BIOS didn't patch the firmware a bin file is mandatory to - * enable the ASP· - */ - if (!coeff_firmware && firmware_missing) { - dev_err(cs35l56->base.dev, ".bin file required but not found\n"); - goto err_fw_release; + /* If the BIOS didn't patch the firmware a wmfw and bin file are mandatory */ + if (firmware_missing) { + if (!wmfw_firmware) { + dev_err(cs35l56->base.dev, ".%s file required but not found\n", "wmfw"); + goto err_fw_release; + } else if (!coeff_firmware) { + dev_err(cs35l56->base.dev, ".%s file required but not found\n", "bin"); + goto err_fw_release; + } } mutex_lock(&cs35l56->base.irq_lock); From 9064637fb2a80b43105900a47d414997630e5b6b Mon Sep 17 00:00:00 2001 From: Chen Bowen Date: Fri, 10 Jul 2026 21:34:09 +0800 Subject: [PATCH 278/479] ALSA: hda/realtek: Fix speakers on MECHREVO WUJIE Series The internal speakers on the MECHREVO WUJIE Series are silent, while the headphone output works correctly. The BIOS reports NID 0x1b on the Realtek ALC233 codec as unconnected with pin configuration 0x411111f0. However, the pin is connected to an internal speaker. Overriding NID 0x1b with 0x90170150 makes the HDA generic parser detect both 0x14 and 0x1b as speaker outputs and restores internal speaker playback. Add a pin configuration fixup for the affected PCI SSID c011:1d05. Tested on a MECHREVO WUJIE Series laptop with board WUJIE Series-Lark4-LNL and firmware EM_LNL326_V1.0.23. Signed-off-by: Chen Bowen Link: https://patch.msgid.link/20260710133409.16641-1-hicbowen@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b47177d734c7..55d699d5afca 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -3942,6 +3942,7 @@ enum { ALC275_FIXUP_DELL_XPS, ALC293_FIXUP_LENOVO_SPK_NOISE, ALC233_FIXUP_LENOVO_LINE2_MIC_HOTKEY, + ALC233_FIXUP_WUJIE_SPEAKERS, ALC233_FIXUP_LENOVO_L2MH_LOW_ENLED, ALC255_FIXUP_DELL_SPK_NOISE, ALC225_FIXUP_DISABLE_MIC_VREF, @@ -4210,6 +4211,13 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, } static const struct hda_fixup alc269_fixups[] = { + [ALC233_FIXUP_WUJIE_SPEAKERS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x90170150 }, /* internal speaker */ + { } + }, + }, [ALC269_FIXUP_GPIO2] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_gpio2, @@ -7949,6 +7957,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged", ALC256_FIXUP_INTEL_NUC8_RUGGED), SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", ALC256_FIXUP_INTEL_NUC10), SND_PCI_QUIRK(0x8086, 0x3038, "Intel NUC 13", ALC295_FIXUP_CHROME_BOOK), + SND_PCI_QUIRK(0xc011, 0x1d05, "MECHREVO WUJIE Series", ALC233_FIXUP_WUJIE_SPEAKERS), SND_PCI_QUIRK(0xf111, 0x0001, "Framework Laptop", ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0xf111, 0x0006, "Framework Laptop", ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0xf111, 0x0009, "Framework Laptop", ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE), From d35dfb6329accfe1cfa0b57e35214b5cbbe0f9ae Mon Sep 17 00:00:00 2001 From: Damien Laine Date: Sun, 12 Jul 2026 23:37:08 +0200 Subject: [PATCH 279/479] ALSA: hda/realtek: Fix speakers on Legion Pro 7 16ARX8H with codec SSID 17aa:38a7 Some units of the Lenovo Legion Pro 7 16ARX8H (82WS) report codec subsystem ID 17aa:38a7 instead of 17aa:38a8. Since only 38a8 has a codec SSID quirk, these machines fall through to the PCI SSID match 17aa:386f (Legion Pro 7i 16IAX7) and get ALC287_FIXUP_CS35L41_I2C_2, which probes the Cirrus amplifiers of the Intel variant. The TI TAS2781 amplifier (ACPI TIAS2781:00) present on this AMD variant is never bound and the internal speakers remain silent. Add a codec SSID quirk for 17aa:38a7 pointing to ALC287_FIXUP_TAS2781_I2C, mirroring the existing 38a8 entry. Tested on a Legion Pro 7 16ARX8H (82WS, BIOS LPCN62WW): with the codec SSID overridden to 17aa:38a8 via the HDA patch loader, the TAS2781 amplifier binds and the internal speakers work. Cc: Signed-off-by: Damien Laine Link: https://patch.msgid.link/20260712213708.1835469-1-damien.laine@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 55d699d5afca..443bc92c5e4b 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7807,6 +7807,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_SPEAKER2_TO_DAC1), HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), From a4876f11aa1d076802676e23f8af500706e780e3 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 9 Jul 2026 10:46:32 +0200 Subject: [PATCH 280/479] pmdomain: mediatek: Fix possible nullptr KP in HWV cleanup/on-check Should probe fail for HW_VOTER type power domains, this driver was unconditionally trying to perform cleanup for DIRECT_CTL domains, but only after checking if the target domain is powered on... with the DIRECT_CTL scpsys_domain_is_on() code again. And there's more: the scpsys_domain_is_on() function is also being unconditionally used in the probe path, for any power domain that has flag MTK_SCPD_KEEP_DEFAULT_OFF! This bug was never experienced by anyone because the HWV domains never failed probe, and because none of those is declared with the aforementioned flag - but it's still something critical. In order to fix this, add a check for MTCMOS Type and, based on that, call the correct functions for an "is on" check, and also do the same for the cleanup path, calling the correct functions for the "power off" action. For the latter, since there's a call to pm_genpd_remove() right before calling power_off, be cautious and add a variation of the power off functions (with a _internal suffix) for those to get a pointer to scpsys_domain instead of one to generic_pm_domain as, even if that's still working, this is way too much fragile and would break at some point. Fixes: 88914db077b6 ("pmdomain: mediatek: Add support for Hardware Voter power domains") Signed-off-by: AngeloGioacchino Del Regno Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/mediatek/mtk-pm-domains.c | 40 ++++++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.c b/drivers/pmdomain/mediatek/mtk-pm-domains.c index e1cfd4223473..f0a6339affd7 100644 --- a/drivers/pmdomain/mediatek/mtk-pm-domains.c +++ b/drivers/pmdomain/mediatek/mtk-pm-domains.c @@ -393,9 +393,8 @@ static int scpsys_hwv_power_on(struct generic_pm_domain *genpd) return ret; }; -static int scpsys_hwv_power_off(struct generic_pm_domain *genpd) +static int scpsys_hwv_power_off_internal(struct scpsys_domain *pd) { - struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd); const struct scpsys_hwv_domain_data *hwv = pd->hwv_data; struct scpsys *scpsys = pd->scpsys; u32 val; @@ -464,6 +463,13 @@ static int scpsys_hwv_power_off(struct generic_pm_domain *genpd) return ret; }; +static int scpsys_hwv_power_off(struct generic_pm_domain *genpd) +{ + struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd); + + return scpsys_hwv_power_off_internal(pd); +} + static int scpsys_ctl_pwrseq_on(struct scpsys_domain *pd) { struct scpsys *scpsys = pd->scpsys; @@ -694,9 +700,8 @@ static int scpsys_power_on(struct generic_pm_domain *genpd) return ret; } -static int scpsys_power_off(struct generic_pm_domain *genpd) +static int scpsys_power_off_internal(struct scpsys_domain *pd) { - struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd); struct scpsys *scpsys = pd->scpsys; bool tmp; int ret; @@ -737,6 +742,13 @@ static int scpsys_power_off(struct generic_pm_domain *genpd) return 0; } +static int scpsys_power_off(struct generic_pm_domain *genpd) +{ + struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd); + + return scpsys_power_off_internal(pd); +} + static struct generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_node *node) { @@ -884,7 +896,14 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no * late_init time. */ if (MTK_SCPD_CAPS(pd, MTK_SCPD_KEEP_DEFAULT_OFF)) { - if (scpsys_domain_is_on(pd)) + bool domain_is_on; + + if (scpsys->soc_data->type == SCPSYS_MTCMOS_TYPE_HW_VOTER) + domain_is_on = scpsys_hwv_domain_is_enable_done(pd); + else + domain_is_on = scpsys_domain_is_on(pd); + + if (domain_is_on) dev_warn(scpsys->dev, "%pOF: A default off power domain has been ON\n", node); } else { @@ -973,6 +992,7 @@ static int scpsys_add_subdomain(struct scpsys *scpsys, struct device_node *paren static void scpsys_remove_one_domain(struct scpsys_domain *pd) { + struct scpsys *scpsys = pd->scpsys; int ret; /* @@ -984,8 +1004,14 @@ static void scpsys_remove_one_domain(struct scpsys_domain *pd) dev_err(pd->scpsys->dev, "failed to remove domain '%s' : %d - state may be inconsistent\n", pd->genpd.name, ret); - if (scpsys_domain_is_on(pd)) - scpsys_power_off(&pd->genpd); + + if (scpsys->soc_data->type == SCPSYS_MTCMOS_TYPE_HW_VOTER) { + if (scpsys_hwv_domain_is_enable_done(pd)) + scpsys_hwv_power_off_internal(pd); + } else { + if (scpsys_domain_is_on(pd)) + scpsys_power_off_internal(pd); + } clk_bulk_put(pd->num_clks, pd->clks); clk_bulk_put(pd->num_subsys_clks, pd->subsys_clks); From 9c66085e6dcfb44b70970aa4e323003fc7f2b738 Mon Sep 17 00:00:00 2001 From: Daniel Paziyski Date: Fri, 10 Jul 2026 15:40:54 +0200 Subject: [PATCH 281/479] KVM: x86: Fix null pointer deref due to dummy array in trace_kvm_inj_exception() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The trace_kvm_inj_exception tracepoint takes as arguments the exception vector, whether the exception has an error code (and subsequently, the error code), and whether it is being reinjected. Because '0' is a valid error code, KVM uses __print_symbolic() to format the error code as a string to avoid printing the error code entirely if the exception doesn't have an error code (see commit 21d4c575eb4a ("KVM: x86: Print error code in exception injection tracepoint iff valid"). KVM's abuse of __print_symbolic() was all fine and dandy, until commit 754e38d2d1ae ("tracing: Use explicit array size instead of sentinel elements in symbol printing") reworked the printing to avoid terminating the arrays with NULL/0 values, and missed KVM's clever use of not-quite empty array of symbols. BUG: kernel NULL pointer dereference, address: 0000000000000000 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page PGD 0 P4D 0 Oops: Oops: 0000 [#1] SMP CPU: 20 UID: 0 PID: 791 Comm: less Not tainted 7.2.0-rc2 #401 PREEMPT Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 RIP: 0010:strlen+0x0/0x20 Call Trace: trace_seq_puts+0x18/0x80 trace_print_symbols_seq+0x68/0xa0 trace_raw_output_kvm_inj_exception+0x64/0xf0 [kvm] s_show+0x47/0x110 seq_read_iter+0x2a5/0x4c0 seq_read+0xfd/0x130 vfs_read+0xb6/0x330 ? vfs_write+0x2f2/0x3f0 ksys_read+0x61/0xd0 do_syscall_64+0xb7/0x570 entry_SYSCALL_64_after_hwframe+0x4b/0x53 RIP: 0033:0x7ff283714862 Simply drop the dummy array entirely, so that __print_symbolic() generates a truly empty array. Signed-off-by: Daniel Paziyski Fixes: 754e38d2d1ae ("tracing: Use explicit array size instead of sentinel elements in symbol printing") Reviewed-by: Thomas Weißschuh Link: https://patch.msgid.link/20260710134055.16432-1-danielpaziyski@gmail.com [sean: massage changelog, add splat, add Fixes, cc stable] Signed-off-by: Sean Christopherson --- arch/x86/kvm/trace.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kvm/trace.h b/arch/x86/kvm/trace.h index 0db25bba17f6..93de876c318c 100644 --- a/arch/x86/kvm/trace.h +++ b/arch/x86/kvm/trace.h @@ -490,7 +490,7 @@ TRACE_EVENT(kvm_inj_exception, TP_printk("%s%s%s%s%s", __print_symbolic(__entry->exception, kvm_trace_sym_exc), !__entry->has_error ? "" : " (", - !__entry->has_error ? "" : __print_symbolic(__entry->error_code, { }), + !__entry->has_error ? "" : __print_symbolic(__entry->error_code), !__entry->has_error ? "" : ")", __entry->reinjected ? " [reinjected]" : "") ); From c90164ca0f7036942ba088eb7ea8d3f6c2352020 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sat, 4 Jul 2026 16:10:30 -0700 Subject: [PATCH 282/479] Bluetooth: qca: fix NVM tag length underflow in TLV parser In the TLV_TYPE_NVM branch of qca_tlv_check_data() the tag loop bound is "while (idx < length - sizeof(struct tlv_type_nvm))". "length" is a signed int from the firmware TLV header and sizeof(struct tlv_type_nvm) is a size_t (12), so "length" is converted to size_t and any firmware-supplied "length" < 12 makes the subtraction wrap to a huge value. The loop body then reads a 12-byte struct tlv_type_nvm past the end of the short vmalloc'd firmware buffer (and the EDL_TAG_ID_* handlers can write past it). Rewrite the bound as "idx + sizeof(struct tlv_type_nvm) <= length"; both operands are non-negative, so it no longer underflows and a "length" too small for one record correctly skips the loop. BUG: KASAN: vmalloc-out-of-bounds in qca_download_firmware.isra.0 (drivers/bluetooth/btqca.c:421) Read of size 2 at addr ffffc900000e5004 by task kworker/u9:0/52 Workqueue: hci0 hci_power_on Call Trace: ... kasan_report (mm/kasan/report.c:595) qca_download_firmware.isra.0 (drivers/bluetooth/btqca.c:421 drivers/bluetooth/btqca.c:617) qca_uart_setup (drivers/bluetooth/btqca.c:948) qca_setup (drivers/bluetooth/hci_qca.c:2029) hci_uart_setup (drivers/bluetooth/hci_ldisc.c:438) hci_dev_open_sync (net/bluetooth/hci_sync.c:5227) hci_power_on (net/bluetooth/hci_core.c:920) process_one_work (kernel/workqueue.c:3322) worker_thread (kernel/workqueue.c:3486) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) Fixes: 2e4edfa1e2bd ("Bluetooth: qca: add missing firmware sanity checks") Reported-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Reported-by: Weiming Shi Reviewed-by: Johan Hovold Acked-by: Bartosz Golaszewski Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btqca.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c index 04ebe290bc78..10c496eaea2c 100644 --- a/drivers/bluetooth/btqca.c +++ b/drivers/bluetooth/btqca.c @@ -415,7 +415,7 @@ static int qca_tlv_check_data(struct hci_dev *hdev, idx = 0; data = tlv->data; - while (idx < length - sizeof(struct tlv_type_nvm)) { + while (idx + sizeof(struct tlv_type_nvm) <= length) { tlv_nvm = (struct tlv_type_nvm *)(data + idx); tag_id = le16_to_cpu(tlv_nvm->tag_id); From 2bf282f8f715f5d05d6f4c49ffb3bd241c5e667e Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Tue, 7 Jul 2026 12:15:18 +0800 Subject: [PATCH 283/479] Bluetooth: MGMT: revalidate LOAD_CONN_PARAM queued update MGMT_OP_LOAD_CONN_PARAM queues conn_update_sync() when a single parameter update changes an existing LE central connection. The queued work currently stores a borrowed hci_conn_params entry from hdev->le_conn_params. A later LOAD_CONN_PARAM request can clear disabled parameters and free that entry before hci_cmd_sync_work() runs the queued callback. Do not keep the borrowed hci_conn_params pointer in queued work. Queue the hci_conn instead and hold a reference until the queued callback completes. When the work runs, revalidate that the connection is still present, look up the current hci_conn_params entry, and cancel the update if userspace removed that entry while the work was pending. Copy the interval values from the current params entry under hdev->lock, then drop the lock and keep using hci_le_conn_update_sync() to issue the update. Validation reproduced this kernel report: BUG: KASAN: slab-use-after-free in conn_update_sync+0x2a/0xf0 [bluetooth] Read of size 1 at addr ffff88810c697126 by task kworker/u17:0/377 Workqueue: hci0 hci_cmd_sync_work [bluetooth] Call Trace: dump_stack_lvl+0x66/0xa0 print_report+0xce/0x5f0 kasan_report+0xe0/0x110 conn_update_sync+0x2a/0xf0 [bluetooth] hci_cmd_sync_work+0x187/0x210 [bluetooth] process_one_work+0x4fd/0xbc0 worker_thread+0x2d8/0x570 kthread+0x1ad/0x1f0 ret_from_fork+0x3c9/0x540 ret_from_fork_asm+0x1a/0x30 Allocated by task 466: hci_conn_params_add+0xa6/0x240 [bluetooth] load_conn_param+0x4e1/0x850 [bluetooth] hci_sock_sendmsg+0x96b/0xf80 [bluetooth] Freed by task 474: kfree+0x313/0x590 hci_conn_params_clear_disabled+0x9b/0xc0 [bluetooth] load_conn_param+0x4bf/0x850 [bluetooth] hci_sock_sendmsg+0x96b/0xf80 [bluetooth] Fixes: 0ece498c27d8c ("Bluetooth: MGMT: Make MGMT_OP_LOAD_CONN_PARAM update existing connection") Suggested-by: Luiz Augusto von Dentz Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 733a4b70e10c..a0a491e5311e 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -7937,14 +7937,36 @@ static int remove_device(struct sock *sk, struct hci_dev *hdev, static int conn_update_sync(struct hci_dev *hdev, void *data) { - struct hci_conn_params *params = data; - struct hci_conn *conn; + struct hci_conn *conn = data; + struct hci_conn_params *params; + struct hci_conn_params local = {}; - conn = hci_conn_hash_lookup_le(hdev, ¶ms->addr, params->addr_type); - if (!conn) - return -ECANCELED; + hci_dev_lock(hdev); - return hci_le_conn_update_sync(hdev, conn, params); + if (!hci_conn_valid(hdev, conn) || conn->role != HCI_ROLE_MASTER) + goto cancel; + + params = hci_conn_params_lookup(hdev, &conn->dst, conn->dst_type); + if (!params) + goto cancel; + + local.conn_min_interval = params->conn_min_interval; + local.conn_max_interval = params->conn_max_interval; + local.conn_latency = params->conn_latency; + local.supervision_timeout = params->supervision_timeout; + + hci_dev_unlock(hdev); + + return hci_le_conn_update_sync(hdev, conn, &local); + +cancel: + hci_dev_unlock(hdev); + return -ECANCELED; +} + +static void conn_update_sync_destroy(struct hci_dev *hdev, void *data, int err) +{ + hci_conn_put(data); } static int load_conn_param(struct sock *sk, struct hci_dev *hdev, void *data, @@ -8054,9 +8076,13 @@ static int load_conn_param(struct sock *sk, struct hci_dev *hdev, void *data, (conn->le_conn_min_interval != min || conn->le_conn_max_interval != max || conn->le_conn_latency != latency || - conn->le_supv_timeout != timeout)) - hci_cmd_sync_queue(hdev, conn_update_sync, - hci_param, NULL); + conn->le_supv_timeout != timeout)) { + hci_conn_get(conn); + if (hci_cmd_sync_queue(hdev, conn_update_sync, + conn, + conn_update_sync_destroy) < 0) + hci_conn_put(conn); + } } } From 609c5b04a28dc1b0f3af6a7bc93055135b2d2059 Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Fri, 10 Jul 2026 23:10:03 +0545 Subject: [PATCH 284/479] Bluetooth: btrtl: validate firmware patch bounds rtlbt_parse_firmware() copies patch_length - 4 bytes before appending the firmware version. A malformed firmware patch shorter than the version field can make this subtraction underflow and turn the copy into an oversized read and write during Bluetooth setup. The existing patch_offset + patch_length check can also wrap on 32-bit architectures. Validate the patch length and range without arithmetic overflow before allocating or copying the patch. Fixes: db33c77dddc2 ("Bluetooth: btrtl: Create separate module for Realtek BT driver") Cc: stable@vger.kernel.org Signed-off-by: Laxman Acharya Padhya Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btrtl.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c index 49ecb18fea45..7f54d2d2d13a 100644 --- a/drivers/bluetooth/btrtl.c +++ b/drivers/bluetooth/btrtl.c @@ -797,8 +797,9 @@ static int rtlbt_parse_firmware(struct hci_dev *hdev, } BT_DBG("length=%x offset=%x index %d", patch_length, patch_offset, i); - min_size = patch_offset + patch_length; - if (btrtl_dev->fw_len < min_size) + if (patch_length < sizeof(epatch_info->fw_version) || + patch_offset > btrtl_dev->fw_len || + patch_length > btrtl_dev->fw_len - patch_offset) return -EINVAL; /* Copy the firmware into a new buffer and write the version at From d5efd6e4b8b0634af6843178fe1a7dd2b2178a3d Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 10 Jul 2026 11:23:40 +0300 Subject: [PATCH 285/479] Bluetooth: hci_sync: extend conn_hash lookup critical sections Using RCU-protected pointers outside the critical sections without refcount is incorrect and may result to UAF. Extend critical section to cover both hci_conn_hash lookup and use of the returned conn. Add surrounding rcu_read_lock() also when return value is not used, in preparation for RCU lockdep requirement to hci_lookup_le_connect(). This avoids concurrent deletion of the conn before we are done dereferencing it. Also, make sure to hold hdev->lock when accessing hdev->accept_list. Fixes: 6d0417e4e1cf ("Bluetooth: hci_conn: Fix not setting conn_timeout for Broadcast Receiver") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 44 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index c896d4edd013..7a60e34c91b6 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -1054,14 +1054,19 @@ static int hci_set_random_addr_sync(struct hci_dev *hdev, bdaddr_t *rpa) * In this kind of scenario skip the update and let the random * address be updated at the next cycle. */ + rcu_read_lock(); + if (bacmp(&hdev->random_addr, BDADDR_ANY) && (hci_dev_test_flag(hdev, HCI_LE_ADV) || hci_lookup_le_connect(hdev))) { bt_dev_dbg(hdev, "Deferring random address update"); hci_dev_set_flag(hdev, HCI_RPA_EXPIRED); + rcu_read_unlock(); return 0; } + rcu_read_unlock(); + return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_RANDOM_ADDR, 6, rpa, HCI_CMD_TIMEOUT); } @@ -2647,12 +2652,17 @@ static int hci_pause_addr_resolution(struct hci_dev *hdev) /* Cannot disable addr resolution if scanning is enabled or * when initiating an LE connection. */ + rcu_read_lock(); + if (hci_dev_test_flag(hdev, HCI_LE_SCAN) || hci_lookup_le_connect(hdev)) { + rcu_read_unlock(); bt_dev_err(hdev, "Command not allowed when scan/LE connect"); return -EPERM; } + rcu_read_unlock(); + /* Cannot disable addr resolution if advertising is enabled. */ err = hci_pause_advertising_sync(hdev); if (err) { @@ -2790,6 +2800,8 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev) if (hci_dev_test_flag(hdev, HCI_PA_SYNC)) { struct hci_conn *conn; + rcu_read_lock(); + conn = hci_conn_hash_lookup_create_pa_sync(hdev); if (conn) { struct conn_params pa; @@ -2799,6 +2811,8 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev) bacpy(&pa.addr, &conn->dst); pa.addr_type = conn->dst_type; + rcu_read_unlock(); + /* Clear first since there could be addresses left * behind. */ @@ -2808,6 +2822,8 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev) err = hci_le_add_accept_list_sync(hdev, &pa, &num_entries); goto done; + } else { + rcu_read_unlock(); } } @@ -2818,10 +2834,13 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev) * the controller. */ list_for_each_entry_safe(b, t, &hdev->le_accept_list, list) { - if (hci_conn_hash_lookup_le(hdev, &b->bdaddr, b->bdaddr_type)) - continue; + rcu_read_lock(); + + if (hci_conn_hash_lookup_le(hdev, &b->bdaddr, b->bdaddr_type)) { + rcu_read_unlock(); + continue; + } - /* Pointers not dereferenced, no locks needed */ pend_conn = hci_pend_le_action_lookup(&hdev->pend_le_conns, &b->bdaddr, b->bdaddr_type); @@ -2829,6 +2848,8 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev) &b->bdaddr, b->bdaddr_type); + rcu_read_unlock(); + /* If the device is not likely to connect or report, * remove it from the acceptlist. */ @@ -2955,6 +2976,8 @@ static int hci_le_set_ext_scan_param_sync(struct hci_dev *hdev, u8 type, if (sent) { struct hci_conn *conn; + rcu_read_lock(); + conn = hci_conn_hash_lookup_ba(hdev, PA_LINK, &sent->bdaddr); if (conn) { @@ -2979,8 +3002,12 @@ static int hci_le_set_ext_scan_param_sync(struct hci_dev *hdev, u8 type, phy++; } + rcu_read_unlock(); + if (num_phy) goto done; + } else { + rcu_read_unlock(); } } } @@ -3231,12 +3258,16 @@ int hci_update_passive_scan_sync(struct hci_dev *hdev) /* If there is at least one pending LE connection, we should * keep the background scan running. */ + bool exists; /* If controller is connecting, we should not start scanning * since some controllers are not able to scan and connect at * the same time. */ - if (hci_lookup_le_connect(hdev)) + rcu_read_lock(); + exists = hci_lookup_le_connect(hdev); + rcu_read_unlock(); + if (exists) return 0; bt_dev_dbg(hdev, "start background scanning"); @@ -3454,6 +3485,7 @@ int hci_write_fast_connectable_sync(struct hci_dev *hdev, bool enable) } static bool disconnected_accept_list_entries(struct hci_dev *hdev) + __must_hold(&hdev->lock) { struct bdaddr_list *b; @@ -3494,12 +3526,16 @@ int hci_update_scan_sync(struct hci_dev *hdev) if (hdev->scanning_paused) return 0; + hci_dev_lock(hdev); + if (hci_dev_test_flag(hdev, HCI_CONNECTABLE) || disconnected_accept_list_entries(hdev)) scan = SCAN_PAGE; else scan = SCAN_DISABLED; + hci_dev_unlock(hdev); + if (hci_dev_test_flag(hdev, HCI_DISCOVERABLE)) scan |= SCAN_INQUIRY; From 16cd66443957e4ad42155c6fec401012f600c6f8 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 10 Jul 2026 11:23:41 +0300 Subject: [PATCH 286/479] Bluetooth: mgmt: fix locking in unpair_device/disconnect_sync Dereferencing RCU-protected pointers outside critical sections is invalid and may lead to UAF. Take hdev->lock for hci_conn lookup and hci_abort_conn(). Don't use RCU to ensure the conn is fully initialized at this point. Fixes: 227a0cdf4a028 ("Bluetooth: MGMT: Fix not generating command complete for MGMT_OP_DISCONNECT") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index a0a491e5311e..f3437a3fe4a1 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -3091,6 +3091,8 @@ static int unpair_device_sync(struct hci_dev *hdev, void *data) struct mgmt_cp_unpair_device *cp = cmd->param; struct hci_conn *conn; + hci_dev_lock(hdev); + if (cp->addr.type == BDADDR_BREDR) conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->addr.bdaddr); @@ -3098,6 +3100,11 @@ static int unpair_device_sync(struct hci_dev *hdev, void *data) conn = hci_conn_hash_lookup_le(hdev, &cp->addr.bdaddr, le_addr_type(cp->addr.type)); + if (conn) + hci_conn_get(conn); + + hci_dev_unlock(hdev); + if (!conn) return 0; @@ -3105,6 +3112,7 @@ static int unpair_device_sync(struct hci_dev *hdev, void *data) * will clean up the connection no matter the error. */ hci_abort_conn(conn, HCI_ERROR_REMOTE_USER_TERM); + hci_conn_put(conn); return 0; } @@ -3252,6 +3260,8 @@ static int disconnect_sync(struct hci_dev *hdev, void *data) struct mgmt_cp_disconnect *cp = cmd->param; struct hci_conn *conn; + hci_dev_lock(hdev); + if (cp->addr.type == BDADDR_BREDR) conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->addr.bdaddr); @@ -3259,6 +3269,11 @@ static int disconnect_sync(struct hci_dev *hdev, void *data) conn = hci_conn_hash_lookup_le(hdev, &cp->addr.bdaddr, le_addr_type(cp->addr.type)); + if (conn) + hci_conn_get(conn); + + hci_dev_unlock(hdev); + if (!conn) return -ENOTCONN; @@ -3266,6 +3281,7 @@ static int disconnect_sync(struct hci_dev *hdev, void *data) * will clean up the connection no matter the error. */ hci_abort_conn(conn, HCI_ERROR_REMOTE_USER_TERM); + hci_conn_put(conn); return 0; } From da55f570191d5d72f10c607a7043b947eb05ea46 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 10 Jul 2026 11:23:42 +0300 Subject: [PATCH 287/479] Bluetooth: mgmt: hold reference for hci_conn in mgmt_pending_cmds Dereferencing RCU-protected pointers outside critical sections is invalid and may lead to UAF. Use of hci_conn in hci_sync callbacks also needs to hold refcount to avoid UAF. Take appropriate locks for hci_conn lookups, and take refcount for hci_conn pointers stored in mgmt_pending_cmd so that the pointer stays valid. When accessing conn->state, ensure hdev->lock is held to avoid data race. Fixes: 7b445e220db9 ("Bluetooth: MGMT: Fix holding hci_conn reference while command is queued") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index f3437a3fe4a1..23a1aded0ca8 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -7404,6 +7404,9 @@ static void get_conn_info_complete(struct hci_dev *hdev, void *data, int err) rp.max_tx_power = HCI_TX_POWER_INVALID; } + if (conn) + hci_conn_put(conn); + mgmt_cmd_complete(cmd->sk, cmd->hdev->id, MGMT_OP_GET_CONN_INFO, status, &rp, sizeof(rp)); @@ -7418,6 +7421,8 @@ static int get_conn_info_sync(struct hci_dev *hdev, void *data) int err; __le16 handle; + hci_dev_lock(hdev); + /* Make sure we are still connected */ if (cp->addr.type == BDADDR_BREDR) conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, @@ -7425,12 +7430,16 @@ static int get_conn_info_sync(struct hci_dev *hdev, void *data) else conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->addr.bdaddr); - if (!conn || conn->state != BT_CONNECTED) + if (!conn || conn->state != BT_CONNECTED) { + hci_dev_unlock(hdev); return MGMT_STATUS_NOT_CONNECTED; + } - cmd->user_data = conn; + cmd->user_data = hci_conn_get(conn); handle = cpu_to_le16(conn->handle); + hci_dev_unlock(hdev); + /* Refresh RSSI each time */ err = hci_read_rssi_sync(hdev, handle); @@ -7564,6 +7573,9 @@ static void get_clock_info_complete(struct hci_dev *hdev, void *data, int err) } complete: + if (conn) + hci_conn_put(conn); + mgmt_cmd_complete(cmd->sk, cmd->hdev->id, cmd->opcode, status, &rp, sizeof(rp)); @@ -7580,15 +7592,21 @@ static int get_clock_info_sync(struct hci_dev *hdev, void *data) memset(&hci_cp, 0, sizeof(hci_cp)); hci_read_clock_sync(hdev, &hci_cp); + hci_dev_lock(hdev); + /* Make sure connection still exists */ conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->addr.bdaddr); - if (!conn || conn->state != BT_CONNECTED) + if (!conn || conn->state != BT_CONNECTED) { + hci_dev_unlock(hdev); return MGMT_STATUS_NOT_CONNECTED; + } - cmd->user_data = conn; + cmd->user_data = hci_conn_get(conn); hci_cp.handle = cpu_to_le16(conn->handle); hci_cp.which = 0x01; /* Piconet clock */ + hci_dev_unlock(hdev); + return hci_read_clock_sync(hdev, &hci_cp); } From c363202ec841df36421ec280eea3d5f94f556143 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 10 Jul 2026 11:23:43 +0300 Subject: [PATCH 288/479] Bluetooth: hci_sync: hold hdev->lock for hci_conn_params lookups hci_conn_params_lookup requires hdev->lock be held, otherwise the list iteration or param access is not safe. Hold hdev->lock for params lookups in hci_sync. Fixes: c530569adc19 ("Bluetooth: hci_core: Introduce HCI_CONN_FLAG_PAST") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 7a60e34c91b6..532534bc601c 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -6701,6 +6701,8 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data) if (!hci_dev_test_flag(hdev, HCI_LE_SIMULTANEOUS_ROLES)) hci_pause_advertising_sync(hdev); + hci_dev_lock(hdev); + params = hci_conn_params_lookup(hdev, &conn->dst, conn->dst_type); if (params) { conn->le_conn_min_interval = params->conn_min_interval; @@ -6714,6 +6716,8 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data) conn->le_supv_timeout = hdev->le_supv_timeout; } + hci_dev_unlock(hdev); + /* If controller is scanning, we stop it since some controllers are * not able to scan and connect at the same time. Also set the * HCI_LE_SCAN_INTERRUPTED flag so that the command complete @@ -7271,13 +7275,13 @@ static void create_pa_complete(struct hci_dev *hdev, void *data, int err) } static int hci_le_past_params_sync(struct hci_dev *hdev, struct hci_conn *conn, - struct hci_conn *acl, struct bt_iso_qos *qos) + u16 acl_handle, struct bt_iso_qos *qos) { struct hci_cp_le_past_params cp; int err; memset(&cp, 0, sizeof(cp)); - cp.handle = cpu_to_le16(acl->handle); + cp.handle = cpu_to_le16(acl_handle); /* An HCI_LE_Periodic_Advertising_Sync_Transfer_Received event is sent * to the Host. HCI_LE_Periodic_Advertising_Report events will be * enabled with duplicate filtering enabled. @@ -7342,16 +7346,28 @@ static int hci_le_pa_create_sync(struct hci_dev *hdev, void *data) * 2. Check if that HCI_CONN_FLAG_PAST has been set which indicates that * user really intended to use PAST. */ + hci_dev_lock(hdev); + le = hci_conn_hash_lookup_le(hdev, &conn->dst, conn->dst_type); if (le) { struct hci_conn_params *params; + hci_conn_flags_t flags = 0; + u16 le_handle = le->handle; params = hci_conn_params_lookup(hdev, &le->dst, le->dst_type); - if (params && params->flags & HCI_CONN_FLAG_PAST) { - err = hci_le_past_params_sync(hdev, conn, le, qos); + if (params) + flags = params->flags; + + hci_dev_unlock(hdev); + + if (flags & HCI_CONN_FLAG_PAST) { + err = hci_le_past_params_sync(hdev, conn, le_handle, + qos); if (!err) goto done; } + } else { + hci_dev_unlock(hdev); } /* SID has not been set listen for HCI_EV_LE_EXT_ADV_REPORT to update From bf587a10c33e5571a299742e45bc18960b9912e7 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 9 Jul 2026 14:22:50 +0800 Subject: [PATCH 289/479] Bluetooth: hci_qca: Clear memdump state on invalid dump size qca_controller_memdump() allocates qca->qca_memdump before processing the first dump packet. For a sequence-zero packet it then disables IBS, marks memdump collection active, and reads the advertised dump size. If the controller reports a zero dump size, the error path frees the local qca_memdump object and returns without clearing qca->qca_memdump or undoing the collection state. A later memdump work item initializes its local pointer from qca->qca_memdump and skips allocation when that pointer is non-NULL, so it can operate on freed memory. The stale collection and IBS-disabled flags can also leave waiters or later transmit handling blocked behind an aborted dump. Clear the saved pointer and memdump state before returning from the invalid-size path, matching the cleanup used when hci_devcd_init() fails. A static analysis checker reported the stale memdump state, and manual source review confirmed the invalid-size failure path. Fixes: 06d3fdfcdf5c ("Bluetooth: hci_qca: Add qcom devcoredump support") Signed-off-by: Ruoyu Wang Reviewed-by: Paul Menzel Reviewed-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_qca.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index b2d1ee3a3d11..1222f97800f4 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -1087,6 +1087,10 @@ static void qca_controller_memdump(struct work_struct *work) if (!(qca_memdump->ram_dump_size)) { bt_dev_err(hu->hdev, "Rx invalid memdump size"); kfree(qca_memdump); + qca->qca_memdump = NULL; + qca->memdump_state = QCA_MEMDUMP_COLLECTED; + clear_and_wake_up_bit(QCA_MEMDUMP_COLLECTION, &qca->flags); + clear_bit(QCA_IBS_DISABLED, &qca->flags); kfree_skb(skb); mutex_unlock(&qca->hci_memdump_lock); return; From c1cec2bbbeb5922d42d28c6af1707c4f3f8647e3 Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Fri, 10 Jul 2026 14:47:31 +0500 Subject: [PATCH 290/479] Bluetooth: mgmt: Translate HCI reason in Device Disconnected event MGMT_EV_DEVICE_DISCONNECTED carries a reason field which is defined to be one of MGMT_DEV_DISCONN_* (0x00..0x05). hci_disconn_complete_evt() converts the HCI error with hci_to_mgmt_reason(), but two other paths pass the raw HCI error straight through: hci_cs_disconnect() -> cp->reason mgmt_connect_failed() -> status The latter is reached whenever the adapter is powered off or suspended: hci_disconnect_all_sync() aborts every link with HCI_ERROR_REMOTE_POWER_OFF, hci_disconnect_sync() deliberately does not wait for HCI_EV_DISCONN_COMPLETE for that reason, so that hci_abort_conn_sync() finishes the connection off through hci_conn_failed() instead. As a result userspace sees an out of range reason: @ MGMT Event: Device Disconnected (0x000c) plen 8 BR/EDR Address: 8C:A9:6F:2C:51:46 Reason: Reserved (0x15) bluetoothd: btd_bearer_disconnected() Unknown disconnection value: 21 bluetoothd: device_disconnected() Unknown disconnection value: 21 Export hci_to_mgmt_reason() and use it in both places, so that a power off is reported as MGMT_DEV_DISCONN_REMOTE rather than as the raw HCI_ERROR_REMOTE_POWER_OFF (0x15). Fixes: d47da6bd4cfa ("Bluetooth: hci_core: Fix sending MGMT_EV_CONNECT_FAILED") Fixes: 182ee45da083 ("Bluetooth: hci_sync: Rework hci_suspend_notifier") Signed-off-by: Mikhail Gavrilov Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_core.h | 1 + net/bluetooth/hci_event.c | 18 +----------------- net/bluetooth/mgmt.c | 19 ++++++++++++++++++- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 4ca09298e11a..e7133ff87fbf 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -2430,6 +2430,7 @@ void mgmt_new_link_key(struct hci_dev *hdev, struct link_key *key, bool persistent); void mgmt_device_connected(struct hci_dev *hdev, struct hci_conn *conn, u8 *name, u8 name_len); +u8 hci_to_mgmt_reason(u8 err); void mgmt_device_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, u8 addr_type, u8 reason, bool mgmt_connected); diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index b6d963ce26d0..741d658e9630 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -2763,7 +2763,7 @@ static void hci_cs_disconnect(struct hci_dev *hdev, u8 status) } mgmt_device_disconnected(hdev, &conn->dst, conn->type, conn->dst_type, - cp->reason, mgmt_conn); + hci_to_mgmt_reason(cp->reason), mgmt_conn); hci_disconn_cfm(conn, cp->reason); @@ -3381,22 +3381,6 @@ static void hci_conn_request_evt(struct hci_dev *hdev, void *data, hci_dev_unlock(hdev); } -static u8 hci_to_mgmt_reason(u8 err) -{ - switch (err) { - case HCI_ERROR_CONNECTION_TIMEOUT: - return MGMT_DEV_DISCONN_TIMEOUT; - case HCI_ERROR_REMOTE_USER_TERM: - case HCI_ERROR_REMOTE_LOW_RESOURCES: - case HCI_ERROR_REMOTE_POWER_OFF: - return MGMT_DEV_DISCONN_REMOTE; - case HCI_ERROR_LOCAL_HOST_TERM: - return MGMT_DEV_DISCONN_LOCAL_HOST; - default: - return MGMT_DEV_DISCONN_UNKNOWN; - } -} - static void hci_disconn_complete_evt(struct hci_dev *hdev, void *data, struct sk_buff *skb) { diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 23a1aded0ca8..1db10e0f617f 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -9908,6 +9908,22 @@ bool mgmt_powering_down(struct hci_dev *hdev) return false; } +u8 hci_to_mgmt_reason(u8 err) +{ + switch (err) { + case HCI_ERROR_CONNECTION_TIMEOUT: + return MGMT_DEV_DISCONN_TIMEOUT; + case HCI_ERROR_REMOTE_USER_TERM: + case HCI_ERROR_REMOTE_LOW_RESOURCES: + case HCI_ERROR_REMOTE_POWER_OFF: + return MGMT_DEV_DISCONN_REMOTE; + case HCI_ERROR_LOCAL_HOST_TERM: + return MGMT_DEV_DISCONN_LOCAL_HOST; + default: + return MGMT_DEV_DISCONN_UNKNOWN; + } +} + void mgmt_device_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, u8 addr_type, u8 reason, bool mgmt_connected) @@ -9969,7 +9985,8 @@ void mgmt_connect_failed(struct hci_dev *hdev, struct hci_conn *conn, u8 status) if (test_and_clear_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags)) { mgmt_device_disconnected(hdev, &conn->dst, conn->type, - conn->dst_type, status, true); + conn->dst_type, + hci_to_mgmt_reason(status), true); return; } From f52524da7084c1a54683ae9fbc73e93fff19dd64 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 13 Jul 2026 18:03:29 +0800 Subject: [PATCH 291/479] ALSA: hda: conexant: Remove mic bias threshold override 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 --- sound/hda/codecs/conexant.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index 3d92262763f6..40da2832ba66 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -162,9 +162,6 @@ static void cx_fixup_headset_recog(struct hda_codec *codec) { 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. */ From 5caf27a2bf7f86f92f03e851d252901b64ed498c Mon Sep 17 00:00:00 2001 From: Lianqin Hu Date: Mon, 13 Jul 2026 12:35:50 +0000 Subject: [PATCH 292/479] ALSA: usb-audio: Add delay quirk for iBasso DC-Elite Audio control requests that sets sampling frequency sometimes fail on this card. Adding delay between control messages eliminates that problem. usb 1-1: New USB device found, idVendor=2fc6, idProduct=f0b5 usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 1-1: Product: iBasso DC-Elite usb 1-1: Manufacturer: iBasso usb 1-1: SerialNumber: CTUA171130B Signed-off-by: Lianqin Hu Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/TYUPR06MB6217D8FF419F24378196FCEFD2FA2@TYUPR06MB6217.apcprd06.prod.outlook.com --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index d98825f9697c..41149561aa06 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2487,6 +2487,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_GET_CUR_BROKEN), DEVICE_FLG(0x2fc6, 0xf06b, /* MOONDROP Moonriver2 Ti */ QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x2fc6, 0xf0b5, /* iBasso DC-Elite */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x2fc6, 0xf0b7, /* iBasso DC07 Pro */ QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x30be, 0x0101, /* Schiit Hel */ From 806c00c23e3ce8eae397a40ced536ef88ae4e012 Mon Sep 17 00:00:00 2001 From: Fredric Cover Date: Sat, 11 Jul 2026 19:54:02 -0700 Subject: [PATCH 293/479] smb: client: use kvzalloc() for megabyte buffer in simple fallocate Currently in smb3_simple_fallocate_range(), a 1 MB buffer is allocated using kzalloc(). Under heavy memory fragmentation, a contiguous 1 MB block of physical memory (an order-8 allocation) may not be available, causing the allocation to fail. This failure was observed during xfstests generic/013 on a 4GB RAM test machine running fsstress: fsstress: page allocation failure: order:8, mode:0x40dc0(GFP_KERNEL|__GFP_ZERO|__GFP_COMP), nodemask=(null),cpuset=/,mems_allowed=0 Call Trace: dump_stack_lvl+0x5d/0x80 warn_alloc+0x163/0x190 __alloc_pages_slowpath.constprop.0+0x71b/0x12f0 __alloc_frozen_pages_noprof+0x2f6/0x340 alloc_pages_mpol+0xb6/0x170 ___kmalloc_large_node+0xb3/0xd0 __kmalloc_large_noprof+0x1e/0xc0 smb3_simple_falloc.isra.0+0x62b/0x960 cifs_fallocate+0xed/0x180 vfs_fallocate+0x165/0x3c0 __x64_sys_fallocate+0x48/0xa0 do_syscall_64+0xe1/0x640 entry_SYSCALL_64_after_hwframe+0x76/0x7e Node 0 Normal: 3375*4kB ... 7*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB Since this scratch buffer does not require physically contiguous memory, switch the allocation to kvzalloc(). This retains the performance benefits of kmalloc() under normal conditions, while gracefully falling back to virtually contiguous memory when physical allocation fails. Fixes: 966a3cb7c7db ("cifs: improve fallocate emulation") Cc: stable@vger.kernel.org Signed-off-by: Fredric Cover Tested-by: Fredric Cover Signed-off-by: Steve French --- fs/smb/client/smb2ops.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 6bce44b171e4..5f0e268aa644 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3595,7 +3595,7 @@ static int smb3_simple_fallocate_range(unsigned int xid, if (rc) goto out; - buf = kzalloc(1024 * 1024, GFP_KERNEL); + buf = kvzalloc(1024 * 1024, GFP_KERNEL); if (buf == NULL) { rc = -ENOMEM; goto out; @@ -3652,7 +3652,7 @@ static int smb3_simple_fallocate_range(unsigned int xid, out: kfree(out_data); - kfree(buf); + kvfree(buf); return rc; } From 5906d0e82e8e07d756f3ed1abfac8f8cea2c20dc Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 3 Jul 2026 13:32:55 +0800 Subject: [PATCH 294/479] smb/client: refresh allocation size after duplicate extents FSCTL_DUPLICATE_EXTENTS_TO_FILE changes the target file extents on the server, but the client does not refresh the target AllocationSize/i_blocks. Callers can observe or use the wrong st_blocks value immediately after the clone, before a later attribute revalidation corrects it. For example, create a reflinked file with a leading hole: xfs_io -f -c "pwrite -S 0x61 0 64k" src touch dst chmod 600 dst xfs_io -c "reflink src 0 1m 64k" dst mkswap dst swapon dst The file still has a hole after mkswap: /mnt/scratch/dst: [0..7]: allocated [8..2047]: hole [2048..2175]: allocated The server also reports only the allocated ranges: server dst size=1114112 blocks=144 but the client reported EOF-derived blocks: client dst size=1114112 blocks=2176 and swapon succeeded: swapon_result=success /mnt/scratch/dst 1.1M 0B -1 So EOF-derived i_blocks can let a sparse reflinked file pass the CIFS swapfile hole check. Fix this by querying FILE_ALL_INFORMATION on the target handle after a successful duplicate extents request. Update i_blocks from AllocationSize and keep the refreshed target inode attributes valid so a following stat does not immediately revalidate again. If the query fails, mark the cached inode attributes stale so a later getattr can refresh them. This also fixes the xfstests generic/370 regression introduced by the i_blocks accounting change, as tested on a Samba "vfs objects = btrfs" share. Fixes: 99cd0a6eeb6c ("smb/client: do not account EOF extension as allocation") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/cifsfs.c | 10 +++++++--- fs/smb/client/smb2ops.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 4df6ca03a8de..66b9104e7ca2 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -1490,9 +1490,13 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off, } } - /* force revalidate of size and timestamps of target file now - that target is updated on the server */ - CIFS_I(target_inode)->time = 0; + /* + * On success, duplicate_extents already updated the target inode attrs + * or marked them stale if the refresh failed. On failure, mark attrs + * stale because EOF may have changed before the clone failed. + */ + if (rc) + CIFS_I(target_inode)->time = 0; unlock: /* although unlocking in the reverse order from locking is not strictly necessary here it is a little cleaner to be consistent */ diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 5f0e268aa644..9bf57a014744 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -2193,10 +2193,14 @@ smb2_duplicate_extents(const unsigned int xid, u64 len, u64 dest_off) { int rc; + int qrc; unsigned int ret_data_len; struct inode *inode; + struct smb2_file_all_info file_inf; struct duplicate_extents_to_file dup_ext_buf; + struct timespec64 ts; struct cifs_tcon *tcon = tlink_tcon(trgtfile->tlink); + u64 asize; /* server fileays advertise duplicate extent support with this flag */ if ((le32_to_cpu(tcon->fsAttrInfo.Attributes) & @@ -2232,6 +2236,32 @@ smb2_duplicate_extents(const unsigned int xid, if (ret_data_len > 0) cifs_dbg(FYI, "Non-zero response length in duplicate extents\n"); + if (rc == 0) { + qrc = SMB2_query_info(xid, tcon, trgtfile->fid.persistent_fid, + trgtfile->fid.volatile_fid, &file_inf); + spin_lock(&inode->i_lock); + if (qrc == 0) { + asize = le64_to_cpu(file_inf.AllocationSize); + CIFS_I(inode)->time = jiffies; + if (file_inf.LastWriteTime) { + ts = cifs_NTtimeToUnix(file_inf.LastWriteTime); + inode_set_mtime_to_ts(inode, ts); + } + if (file_inf.ChangeTime) { + ts = cifs_NTtimeToUnix(file_inf.ChangeTime); + inode_set_ctime_to_ts(inode, ts); + } + if (file_inf.LastAccessTime) { + ts = cifs_NTtimeToUnix(file_inf.LastAccessTime); + inode_set_atime_to_ts(inode, ts); + } + inode->i_blocks = CIFS_INO_BLOCKS(asize); + } else { + CIFS_I(inode)->time = 0; /* force reval */ + } + spin_unlock(&inode->i_lock); + } + duplicate_extents_out: if (rc) trace_smb3_clone_err(xid, srcfile->fid.volatile_fid, From b09ae45d85dc816987a71db9eebc54b0ae288e94 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 3 Jul 2026 13:32:56 +0800 Subject: [PATCH 295/479] smb/client: handle overlapping allocated ranges in fallocate smb3_simple_fallocate_range() can skip holes when an allocated range returned by the server starts before the current fallocate offset. The skipped hole is not zero-filled, but fallocate still returns success. A later write to that hole may therefore fail with ENOSPC. The function queries allocated ranges so that it can preserve existing contents and write zeroes only into holes. However, the server may return a range that starts before the current fallocate offset. For example, assume the fallocate request is [100, 400) and the only allocated range returned by the server is [0, 200): Request: [100, 400) Server range: [ 0, 200) allocated Correct: [100, 200) allocated data, skip [200, 400) hole, zero-fill Current: [100, 300) skipped [300, 400) zero-filled afterwards The current code adds the full server range length, 200, to the current offset 100 and moves to 300. As a result, the hole in [200, 300) is skipped without being zero-filled. Fix this by advancing only over the part of the allocated range that overlaps the current fallocate offset. Ignore ranges that end before the current offset and reject ranges whose end offset overflows. This also prevents a malformed range length from causing an out-of-bounds zero-buffer read. Fixes: 966a3cb7c7db ("cifs: improve fallocate emulation") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/smb2ops.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 9bf57a014744..ddb28464ff81 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3611,6 +3611,7 @@ static int smb3_simple_fallocate_range(unsigned int xid, struct file_allocated_range_buffer in_data, *out_data = NULL, *tmp_data; u32 out_data_len; char *buf = NULL; + u64 range_start, range_len, range_end; loff_t l; int rc; @@ -3647,13 +3648,21 @@ static int smb3_simple_fallocate_range(unsigned int xid, goto out; } - if (off < le64_to_cpu(tmp_data->file_offset)) { + range_start = le64_to_cpu(tmp_data->file_offset); + range_len = le64_to_cpu(tmp_data->length); + if (check_add_overflow(range_start, range_len, &range_end) || + range_end > S64_MAX) { + rc = -EINVAL; + goto out; + } + + if (off < range_start) { /* * We are at a hole. Write until the end of the region * or until the next allocated data, * whichever comes next. */ - l = le64_to_cpu(tmp_data->file_offset) - off; + l = range_start - off; if (len < l) l = len; rc = smb3_simple_fallocate_write_range(xid, tcon, @@ -3670,11 +3679,13 @@ static int smb3_simple_fallocate_range(unsigned int xid, * until the end of the data or the end of the region * we are supposed to fallocate, whichever comes first. */ - l = le64_to_cpu(tmp_data->length); - if (len < l) - l = len; - off += l; - len -= l; + if (off < range_end) { + l = range_end - off; + if (len < l) + l = len; + off += l; + len -= l; + } tmp_data = &tmp_data[1]; out_data_len -= sizeof(struct file_allocated_range_buffer); From 9e4ec3be67af41ab859302d7109b34976efd9258 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 3 Jul 2026 13:32:57 +0800 Subject: [PATCH 296/479] smb/client: reduce fallocate zero buffer allocation The fallocate emulation allocates a 1 MiB zero-filled buffer even though each SMB2_write request is limited to SMB2_MAX_BUFFER_SIZE, which is 64 KiB. A high-order 1 MiB allocation is more likely to fail on a fragmented system. Allocate only the smaller of the requested range and SMB2_MAX_BUFFER_SIZE, and reuse that zero-filled buffer for every write request. Also reject a successful write that makes no progress to avoid looping indefinitely. This reduces the contiguous allocation required by fallocate emulation without changing the written data or range semantics. Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/smb2ops.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index ddb28464ff81..ce607915a483 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3573,7 +3573,7 @@ static int smb3_simple_fallocate_write_range(unsigned int xid, char *buf) { struct cifs_io_parms io_parms = {0}; - int nbytes; + unsigned int nbytes; int rc = 0; struct kvec iov[2]; @@ -3594,9 +3594,10 @@ static int smb3_simple_fallocate_write_range(unsigned int xid, rc = SMB2_write(xid, &io_parms, &nbytes, iov, 1); if (rc) break; + if (!nbytes) + return -EIO; if (nbytes > len) return -EINVAL; - buf += nbytes; off += nbytes; len -= nbytes; } @@ -3626,7 +3627,7 @@ static int smb3_simple_fallocate_range(unsigned int xid, if (rc) goto out; - buf = kvzalloc(1024 * 1024, GFP_KERNEL); + buf = kvzalloc(min_t(loff_t, len, SMB2_MAX_BUFFER_SIZE), GFP_KERNEL); if (buf == NULL) { rc = -ENOMEM; goto out; From 7a06d3b816d73448b4e38b83d65049f090b7b201 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 3 Jul 2026 13:32:58 +0800 Subject: [PATCH 297/479] smb/client: emulate small EOF-extending mode 0 fallocate ranges When a mode 0 fallocate extends EOF from 1G to 2G + 1M, the client currently sends SetEOF for 2G + 1M. This can make fallocate return success without allocating the requested range, or allocate extra space before that range. For example, on a fresh file: xfs_io -f \ -c "falloc 0 1G" \ -c "falloc 2G 1M" \ -c "truncate 3G" test The second fallocate should allocate [2G, 2G + 1M), leaving [1G, 2G) as a hole. Before this change, the result depended on the server allocation policy. With Samba "strict allocate = no", SetEOF could return success without allocating [2G, 2G + 1M). With "strict allocate = yes": # filefrag -v test [0, 1G) allocated [1G, 2G) allocated unexpectedly [2G, 2G + 1M) allocated SMB cannot allocate that arbitrary range, so write zeroes to small EOF-extending ranges instead. Limit this to 1 MiB to bound the client-side I/O cost. With "strict allocate = no", the requested range [2G, 2G + 1M) is allocated by the writes. With "strict allocate = yes": # filefrag -v test [0, 1G) allocated [1G, 2G) hole [2G, 2G + 1M) allocated This fixes the small EOF-extending range case exercised by generic/213. Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/smb2ops.c | 69 +++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index ce607915a483..3e32ad18e6c2 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3610,12 +3610,25 @@ static int smb3_simple_fallocate_range(unsigned int xid, loff_t off, loff_t len) { struct file_allocated_range_buffer in_data, *out_data = NULL, *tmp_data; + struct inode *inode = d_inode(cfile->dentry); u32 out_data_len; char *buf = NULL; u64 range_start, range_len, range_end; loff_t l; int rc; + buf = kvzalloc(min_t(loff_t, len, SMB2_MAX_BUFFER_SIZE), GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; + goto out; + } + + if (off >= i_size_read(inode)) { + rc = smb3_simple_fallocate_write_range(xid, tcon, cfile, + off, len, buf); + goto out; + } + in_data.file_offset = cpu_to_le64(off); in_data.length = cpu_to_le64(len); rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid, @@ -3627,12 +3640,6 @@ static int smb3_simple_fallocate_range(unsigned int xid, if (rc) goto out; - buf = kvzalloc(min_t(loff_t, len, SMB2_MAX_BUFFER_SIZE), GFP_KERNEL); - if (buf == NULL) { - rc = -ENOMEM; - goto out; - } - tmp_data = out_data; while (len) { /* @@ -3707,18 +3714,22 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, struct cifsFileInfo *cfile = file->private_data; long rc = -EOPNOTSUPP; unsigned int xid; - loff_t new_eof; + loff_t old_eof, new_eof; + struct smb2_file_all_info file_inf; + u64 asize; + int qrc; xid = get_xid(); inode = d_inode(cfile->dentry); cifsi = CIFS_I(inode); + old_eof = i_size_read(inode); trace_smb3_falloc_enter(xid, cfile->fid.persistent_fid, tcon->tid, tcon->ses->Suid, off, len); /* if file not oplocked can't be sure whether asking to extend size */ if (!CIFS_CACHE_READ(cifsi)) - if (keep_size == false) { + if (!keep_size) { trace_smb3_falloc_err(xid, cfile->fid.persistent_fid, tcon->tid, tcon->ses->Suid, off, len, rc); free_xid(xid); @@ -3728,11 +3739,51 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, /* * Extending the file */ - if ((keep_size == false) && i_size_read(inode) < off + len) { + if (!keep_size && old_eof < off + len) { rc = inode_newsize_ok(inode, off + len); if (rc) goto out; + /* + * A small range at or beyond EOF can be allocated by writing + * zeroes. For off > old_eof, this preserves the intervening + * hole instead of allocating from offset 0. + */ + if (off > old_eof || + (off == old_eof && old_eof != 0 && + (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE))) { + if (len > 1024 * 1024) { + rc = -EOPNOTSUPP; + goto out; + } + + rc = smb3_simple_fallocate_range(xid, tcon, cfile, + off, len); + if (rc) { + spin_lock(&inode->i_lock); + cifsi->time = 0; + spin_unlock(&inode->i_lock); + goto out; + } + + new_eof = off + len; + netfs_resize_file(&cifsi->netfs, new_eof, true); + cifs_setsize(inode, new_eof); + + qrc = SMB2_query_info(xid, tcon, + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, &file_inf); + spin_lock(&inode->i_lock); + if (qrc == 0) { + asize = le64_to_cpu(file_inf.AllocationSize); + inode->i_blocks = CIFS_INO_BLOCKS(asize); + } else { + cifsi->time = 0; + } + spin_unlock(&inode->i_lock); + goto out; + } + if (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE) smb2_set_sparse(xid, tcon, cfile, inode, false); From 5bd1d3dcc25a51663b3878cbc506e16be15af354 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 3 Jul 2026 13:32:59 +0800 Subject: [PATCH 298/479] smb/client: refresh allocation after EOF-extending fallocate Before this change, xfstests generic/496 was not supported on ksmbd: generic/496 ... [not run] fallocated swap not supported here ksmbd handles SetEOF as truncate, so EOF extension alone does not allocate backing blocks. A fallocated swapfile can therefore still look sparse to swapon. Request allocation for EOF-extending fallocate ranges that can be represented by FILE_ALLOCATION_INFORMATION, and refresh the allocation state afterwards. With this change, xfstests generic/496 and generic/701 pass on ksmbd. However, Samba "strict allocate = no" now exposes the real generic/701 failure: the old pass came from inflated local i_blocks, not from server allocation. generic/213 also fails in that case because an oversized allocation request may not return ENOSPC. Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/smb2ops.c | 43 ++++++++++++++++++++++++++++++++++++--- fs/smb/client/smb2pdu.c | 19 +++++++++++++++++ fs/smb/client/smb2proto.h | 3 +++ fs/smb/common/fscc.h | 5 +++++ fs/smb/server/smb2pdu.h | 4 ---- 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 3e32ad18e6c2..05f2ab6d345a 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3788,12 +3788,49 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, smb2_set_sparse(xid, tcon, cfile, inode, false); new_eof = off + len; + + qrc = SMB2_query_info(xid, tcon, + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, &file_inf); + if (qrc == 0) + asize = le64_to_cpu(file_inf.AllocationSize); + + /* + * FILE_ALLOCATION_INFORMATION can only describe allocation up to + * new_eof. Some servers may accept it without allocating blocks, + * so refresh AllocationSize before updating i_blocks. + */ + if (off == 0 || off == old_eof) { + if (qrc || asize < new_eof) { + rc = SMB2_set_allocation(xid, tcon, + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + cfile->pid, new_eof); + if (rc) + goto out; + } + } + rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, new_eof); - if (rc == 0) { - netfs_resize_file(&cifsi->netfs, new_eof, true); - cifs_setsize(inode, new_eof); + if (rc) + goto out; + + netfs_resize_file(&cifsi->netfs, new_eof, true); + cifs_setsize(inode, new_eof); + + qrc = SMB2_query_info(xid, tcon, + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, &file_inf); + spin_lock(&inode->i_lock); + if (qrc == 0) { + asize = le64_to_cpu(file_inf.AllocationSize); + if (asize >= new_eof) + inode->i_blocks = CIFS_INO_BLOCKS(asize); + } else { + cifsi->time = 0; } + spin_unlock(&inode->i_lock); goto out; } diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 8f83ab377db1..4ce165e40657 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -5949,6 +5949,25 @@ SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, 0, 1, &data, &size); } +int +SMB2_set_allocation(const unsigned int xid, struct cifs_tcon *tcon, + u64 persistent_fid, u64 volatile_fid, u32 pid, + loff_t allocation_size) +{ + struct smb2_file_alloc_info info; + void *data; + unsigned int size; + + info.AllocationSize = cpu_to_le64(allocation_size); + + data = &info; + size = sizeof(struct smb2_file_alloc_info); + + return send_set_info(xid, tcon, persistent_fid, volatile_fid, + pid, FILE_ALLOCATION_INFORMATION, SMB2_O_INFO_FILE, + 0, 1, &data, &size); +} + int SMB2_set_acl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, u64 volatile_fid, diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index e01effe45ae2..2e9f70096825 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -204,6 +204,9 @@ void SMB2_query_directory_free(struct smb_rqst *rqst); int SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, u64 volatile_fid, u32 pid, loff_t new_eof); +int SMB2_set_allocation(const unsigned int xid, struct cifs_tcon *tcon, + u64 persistent_fid, u64 volatile_fid, u32 pid, + loff_t allocation_size); int SMB2_set_info_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server, struct smb_rqst *rqst, u64 persistent_fid, u64 volatile_fid, u32 pid, u8 info_class, u8 info_type, diff --git a/fs/smb/common/fscc.h b/fs/smb/common/fscc.h index 859849a42fec..941db5a95564 100644 --- a/fs/smb/common/fscc.h +++ b/fs/smb/common/fscc.h @@ -283,6 +283,11 @@ struct smb2_file_eof_info { /* encoding of request for level 10 */ __le64 EndOfFile; /* new end of file value */ } __packed; /* level 20 Set */ +/* See MS-FSCC 2.4.4 */ +struct smb2_file_alloc_info { /* encoding of request for level 19 */ + __le64 AllocationSize; +} __packed; + /* See MS-FSCC 2.4.15 */ typedef struct { __le32 NextEntryOffset; diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h index c2512dbcdec8..aa06c8c905f1 100644 --- a/fs/smb/server/smb2pdu.h +++ b/fs/smb/server/smb2pdu.h @@ -212,10 +212,6 @@ struct smb2_file_ea_info { __le32 EASize; } __packed; -struct smb2_file_alloc_info { - __le64 AllocationSize; -} __packed; - struct smb2_file_disposition_info { __u8 DeletePending; } __packed; From 8986c932905ea508d66da421eb2eb6e676ace1fe Mon Sep 17 00:00:00 2001 From: Shoichiro Miyamoto Date: Sat, 11 Jul 2026 22:33:26 +0900 Subject: [PATCH 299/479] smb: client: reject overlapping data areas in SMB2 responses Commit 53b7c271f06b ("smb: client: restrict implied bcc[0] exemption to responses without data area") restricted the implied bcc[0] length exception to responses without a data area. However, the overlap handling in __smb2_calc_size() clears data_length, which can make an invalid response appear to have no data area and so qualify for the exception. Track data area overlap separately and reject such responses before applying the length compatibility exceptions. Fixes: 53b7c271f06b ("smb: client: restrict implied bcc[0] exemption to responses without data area") Cc: stable@vger.kernel.org Signed-off-by: Shoichiro Miyamoto Signed-off-by: Steve French --- fs/smb/client/smb2misc.c | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c index 6270b33147d2..9068175e57cd 100644 --- a/fs/smb/client/smb2misc.c +++ b/fs/smb/client/smb2misc.c @@ -19,7 +19,8 @@ #include "nterr.h" #include "cached_dir.h" -static unsigned int __smb2_calc_size(void *buf, bool *have_data); +static unsigned int __smb2_calc_size(void *buf, bool *have_data, + bool *data_area_overlap); static int check_smb2_hdr(struct smb2_hdr *shdr, __u64 mid) @@ -148,6 +149,7 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len, __u32 calc_len; /* calculated length */ __u64 mid; bool have_data; + bool data_area_overlap; /* If server is a channel, select the primary channel */ pserver = SERVER_IS_CHAN(server) ? server->primary_server : server; @@ -232,7 +234,12 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len, } have_data = false; - calc_len = __smb2_calc_size(buf, &have_data); + data_area_overlap = false; + calc_len = __smb2_calc_size(buf, &have_data, &data_area_overlap); + + /* Reject responses whose data area overlaps the fixed area. */ + if (data_area_overlap) + return 1; /* For SMB2_IOCTL, OutputOffset and OutputLength are optional, so might * be 0, and not a real miscalculation */ @@ -416,14 +423,15 @@ smb2_get_data_area_len(int *off, int *len, struct smb2_hdr *shdr) } /* - * Calculate the size of the SMB message based on the fixed header - * portion, the number of word parameters and the data portion of the message. - * If have_data is non-NULL, it is set to true when a non-empty data area was - * found (data_length > 0), allowing callers to distinguish the implied bcc[0] - * case (no data area) from an overreported data length. + * Calculate the size of the SMB message based on the fixed header, fixed + * parameter area, and variable data area. + * + * If have_data is not NULL, it is set when a non-empty data area is found. + * If data_area_overlap is not NULL, it is set when the data area overlaps + * the fixed area. */ static unsigned int -__smb2_calc_size(void *buf, bool *have_data) +__smb2_calc_size(void *buf, bool *have_data, bool *data_area_overlap) { struct smb2_pdu *pdu = buf; struct smb2_hdr *shdr = &pdu->hdr; @@ -432,6 +440,11 @@ __smb2_calc_size(void *buf, bool *have_data) /* Structure Size has already been checked to make sure it is 64 */ int len = le16_to_cpu(shdr->StructureSize); + if (have_data) + *have_data = false; + if (data_area_overlap) + *data_area_overlap = false; + /* * StructureSize2, ie length of fixed parameter area has already * been checked to make sure it is the correct length. @@ -454,7 +467,10 @@ __smb2_calc_size(void *buf, bool *have_data) if (offset + 1 < len) { cifs_dbg(VFS, "data area offset %d overlaps SMB2 header %d\n", offset + 1, len); + if (data_area_overlap) + *data_area_overlap = true; data_length = 0; + goto calc_size_exit; } else { len = offset + data_length; } @@ -469,7 +485,7 @@ __smb2_calc_size(void *buf, bool *have_data) unsigned int smb2_calc_size(void *buf) { - return __smb2_calc_size(buf, NULL); + return __smb2_calc_size(buf, NULL, NULL); } /* Note: caller must free return buffer */ From d489a5305b9d5480d6fb97d5636f5f4b1e0b3827 Mon Sep 17 00:00:00 2001 From: Jason Macnak Date: Thu, 25 Jun 2026 10:08:28 -0700 Subject: [PATCH 300/479] drm/virtio: Don't detach GEM from a non-created context Applies the same treatment as commit 7cf6dd467e87 ("drm/virtio: Don't attach GEM to a non-created context in gem_object_open()") to virtio_gpu_gem_object_close() to avoid trying to detach a resource that was never attached due to a context never being created when context_init is supported. Fixes: 086b9f27f0ab ("drm/virtio: Don't create a context with default param if context_init is supported") Cc: # v6.14+ Signed-off-by: Jason Macnak Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260625170828.3335431-1-natsu@google.com --- drivers/gpu/drm/virtio/virtgpu_gem.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_gem.c b/drivers/gpu/drm/virtio/virtgpu_gem.c index 435d37d36034..66c3f6f74e9c 100644 --- a/drivers/gpu/drm/virtio/virtgpu_gem.c +++ b/drivers/gpu/drm/virtio/virtgpu_gem.c @@ -139,13 +139,15 @@ void virtio_gpu_gem_object_close(struct drm_gem_object *obj, if (!vgdev->has_virgl_3d) return; - objs = virtio_gpu_array_alloc(1); - if (!objs) - return; - virtio_gpu_array_add_obj(objs, obj); + if (vfpriv->context_created) { + objs = virtio_gpu_array_alloc(1); + if (!objs) + return; + virtio_gpu_array_add_obj(objs, obj); - virtio_gpu_cmd_context_detach_resource(vgdev, vfpriv->ctx_id, - objs); + virtio_gpu_cmd_context_detach_resource(vgdev, vfpriv->ctx_id, + objs); + } virtio_gpu_notify(vgdev); } From d1b894c5bbb3fee0012bd14356286dc2384e8213 Mon Sep 17 00:00:00 2001 From: Ryosuke Yasuoka Date: Mon, 13 Jul 2026 22:01:00 +0900 Subject: [PATCH 301/479] drm/virtio: fix deadlock in display_info_cb by removing hotplug from dequeue worker A probe-time deadlock can occur between the dequeue worker and drm_client_register(). During probe, drm_client_register() holds clientlist_mutex and calls the fbdev hotplug callback, which triggers an atomic commit that ends up sleeping in virtio_gpu_queue_ctrl_sgs() waiting for virtqueue space. The dequeue worker that would free that space calls virtio_gpu_cmd_get_display_info_cb(), which invokes drm_kms_helper_hotplug_event() -> drm_client_dev_hotplug(), attempting to acquire the same clientlist_mutex. Since wake_up() is only called after the resp_cb loop, the probe thread is never woken and both threads deadlock. Fix this by removing the hotplug notification from virtio_gpu_cmd_get_display_info_cb(). The display data (outputs[i].info) is still updated synchronously in the callback. For the init path, drm_client_register() already fires an initial hotplug when the client is registered, which picks up the connector state updated by display_info_cb. For the runtime config_changed path, add a wait_event_timeout() in config_changed_work_func() so that display_info_cb updates the connector data before the hotplug notification is sent. Also replace drm_helper_hpd_irq_event() with drm_kms_helper_hotplug_event() since virtio-gpu never calls drm_kms_helper_poll_init() and thus drm_helper_hpd_irq_event() always returns false without doing anything. Fixes: 27655b9bb9f0 ("drm/client: Send hotplug event after registering a client") Closes: https://syzkaller.appspot.com/bug?id=d6dd6f86d3aaf7eebe7406e45c1c6e549453f224 Closes: https://syzkaller.appspot.com/bug?id=908bd910da5dd79b88de4cf7baf376cc873a922e Suggested-by: Dmitry Osipenko Signed-off-by: Ryosuke Yasuoka Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260713-virtiogpu_syzbot-v2-1-2958fa37d46d@redhat.com --- drivers/gpu/drm/virtio/virtgpu_kms.c | 5 ++++- drivers/gpu/drm/virtio/virtgpu_vq.c | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c index cfde9f573df6..b4329f28e976 100644 --- a/drivers/gpu/drm/virtio/virtgpu_kms.c +++ b/drivers/gpu/drm/virtio/virtgpu_kms.c @@ -49,7 +49,10 @@ static void virtio_gpu_config_changed_work_func(struct work_struct *work) virtio_gpu_cmd_get_edids(vgdev); virtio_gpu_cmd_get_display_info(vgdev); virtio_gpu_notify(vgdev); - drm_helper_hpd_irq_event(vgdev->ddev); + wait_event_timeout(vgdev->resp_wq, + !vgdev->display_info_pending, + 5 * HZ); + drm_kms_helper_hotplug_event(vgdev->ddev); } events_clear |= VIRTIO_GPU_EVENT_DISPLAY; } diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c index c8b9475a7472..e5e1af8b8e8a 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vq.c +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c @@ -840,9 +840,6 @@ static void virtio_gpu_cmd_get_display_info_cb(struct virtio_gpu_device *vgdev, vgdev->display_info_pending = false; spin_unlock(&vgdev->display_info_lock); wake_up(&vgdev->resp_wq); - - if (!drm_helper_hpd_irq_event(vgdev->ddev)) - drm_kms_helper_hotplug_event(vgdev->ddev); } static void virtio_gpu_cmd_get_capset_info_cb(struct virtio_gpu_device *vgdev, From b6ea9680f8c101967caf9981c2980b80b818ccbf Mon Sep 17 00:00:00 2001 From: Vivian Wang Date: Mon, 13 Jul 2026 11:29:52 -0600 Subject: [PATCH 302/479] riscv: mm: Make mark_new_valid_map() stuff depend on 64BIT && MMU None of the code relating to mark_new_valid_map() does anything useful without CONFIG_64BIT=y && CONFIG_MMU=y, because the new_valid_map_cpus_check code is only used if CONFIG_64BIT, and the exception codes checked there can only happen with CONFIG_MMU=y. Therefore, make these conditional on CONFIG_64BIT=y && CONFIG_MMU=y to simplify programming, since we do not have to handle CONFIG_MMU=n when changing this code in the future. This also removes some unused code on the entry path for CONFIG_MMU=n. Signed-off-by: Vivian Wang Link: https://patch.msgid.link/20260713-mark-after-vmemmap-populate-v6-1-b945ceba29d4@iscas.ac.cn Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/cacheflush.h | 2 +- arch/riscv/kernel/entry.S | 2 +- arch/riscv/mm/init.c | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/riscv/include/asm/cacheflush.h b/arch/riscv/include/asm/cacheflush.h index 8cfe59483a8f..58e787fad029 100644 --- a/arch/riscv/include/asm/cacheflush.h +++ b/arch/riscv/include/asm/cacheflush.h @@ -40,7 +40,7 @@ do { \ flush_icache_mm(vma->vm_mm, 0); \ } while (0) -#ifdef CONFIG_64BIT +#if defined(CONFIG_64BIT) && defined(CONFIG_MMU) /* This is accessed in assembly code. cpumask_var_t would be too complex. */ extern DECLARE_BITMAP(new_valid_map_cpus, NR_CPUS); extern char _end[]; diff --git a/arch/riscv/kernel/entry.S b/arch/riscv/kernel/entry.S index 08df724e13b9..d799c4e56f80 100644 --- a/arch/riscv/kernel/entry.S +++ b/arch/riscv/kernel/entry.S @@ -137,7 +137,7 @@ SYM_CODE_START(handle_exception) .Lrestore_kernel_tpsp: csrr tp, CSR_SCRATCH -#ifdef CONFIG_64BIT +#if defined(CONFIG_64BIT) && defined(CONFIG_MMU) /* * The RISC-V kernel does not flush TLBs on all CPUS after each new * vmalloc mapping or kfence_unprotect(), which may result in diff --git a/arch/riscv/mm/init.c b/arch/riscv/mm/init.c index 5b1b3c88b4d1..3e450890be07 100644 --- a/arch/riscv/mm/init.c +++ b/arch/riscv/mm/init.c @@ -37,7 +37,9 @@ #include "../kernel/head.h" +#if defined(CONFIG_64BIT) && defined(CONFIG_MMU) DECLARE_BITMAP(new_valid_map_cpus, NR_CPUS); +#endif struct kernel_mapping kernel_map __ro_after_init; EXPORT_SYMBOL(kernel_map); From 4edd70ee6a7d0408a4e3ac921185779e7605f29c Mon Sep 17 00:00:00 2001 From: Vivian Wang Date: Mon, 13 Jul 2026 11:29:52 -0600 Subject: [PATCH 303/479] mm/sparse-vmemmap: flush_cache_vmap() after hotplugging vmemmap section_activate() does not flush TLB after populating new vmemmap pages. On most architectures, this is okay. However it is a problem on RISC-V since there the TLB caching non-present entries is permitted, which causes spurious faults on some hardwares. This seems to be most easily reproduced with DEBUG_VM=y and PAGE_POISONING=y, which causes these newly mapped struct pages to be poisoned i.e. written to immediately after mapping. Extend the RISC-V flush_cache_vmap() to also handle the vmemmap range, and call it after hotplugging vmemmap, which gets the possible spurious fault handled in the exception handler. At least for now, the only other architecture with both SPARSEMEM_VMEMMAP and flush_cache_vmap() is PowerPC, which has a similar problem with newly valid PTEs. But there flush_cache_vmap() is just a ptesync. So it should be safe to do this for generic code while having minimal performance impact. Suggested-by: Muchun Song Signed-off-by: Vivian Wang Reviewed-by: Muchun Song Acked-by: David Hildenbrand (Arm) Link: https://patch.msgid.link/20260713-mark-after-vmemmap-populate-v6-2-b945ceba29d4@iscas.ac.cn Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/cacheflush.h | 3 ++- mm/sparse-vmemmap.c | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/riscv/include/asm/cacheflush.h b/arch/riscv/include/asm/cacheflush.h index 58e787fad029..c2b0a2928f06 100644 --- a/arch/riscv/include/asm/cacheflush.h +++ b/arch/riscv/include/asm/cacheflush.h @@ -56,7 +56,8 @@ static inline void mark_new_valid_map(void) #define flush_cache_vmap flush_cache_vmap static inline void flush_cache_vmap(unsigned long start, unsigned long end) { - if (is_vmalloc_or_module_addr((void *)start)) + if (is_vmalloc_or_module_addr((void *)start) || + (start >= VMEMMAP_START && end <= VMEMMAP_END)) mark_new_valid_map(); } #define flush_cache_vmap_early(start, end) local_flush_tlb_kernel_range(start, end) diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c index 99e2be39671b..ebd3ac997f64 100644 --- a/mm/sparse-vmemmap.c +++ b/mm/sparse-vmemmap.c @@ -564,6 +564,8 @@ struct page * __meminit __populate_section_memmap(unsigned long pfn, if (r < 0) return NULL; + flush_cache_vmap(start, end); + return pfn_to_page(pfn); } From 3a2694bf6ac8e47b3814293e80343f58fc72937f Mon Sep 17 00:00:00 2001 From: Rui Qi Date: Mon, 6 Jul 2026 21:04:14 +0800 Subject: [PATCH 304/479] riscv: Gate FUNCTION_ALIGNMENT_4B on DYNAMIC_FTRACE The FUNCTION_ALIGNMENT_4B select forces the whole kernel to be built with -fmin-function-alignment=4. This alignment is only needed so the patchable-function-entry NOPs, which arch/riscv/Makefile emits under CONFIG_DYNAMIC_FTRACE, can be patched reliably on RISCV_ISA_C=y builds where compressed instructions otherwise allow 2-byte function alignment. The select is currently gated on HAVE_DYNAMIC_FTRACE, a capability bit that is selected whenever the toolchain supports dynamic ftrace, rather than on whether tracing is actually enabled. As a result every RISCV_ISA_C=y build gets 4-byte function alignment across the entire kernel even when function tracing is disabled, needlessly growing the kernel image and wasting instruction cache for a feature that is not in use. Gate the select on DYNAMIC_FTRACE instead, matching the condition under which arch/riscv/Makefile emits -fpatchable-function-entry, so the alignment is only applied when it is actually needed. Fixes: c41bf4326c7b ("riscv: ftrace: align patchable functions to 4 Byte boundary") Signed-off-by: Rui Qi Link: https://patch.msgid.link/20260706130415.463682-1-qirui.001@bytedance.com Signed-off-by: Paul Walmsley --- arch/riscv/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index c0a6992933e4..f7028caaeae0 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -157,7 +157,7 @@ config RISCV select HAVE_DEBUG_KMEMLEAK select HAVE_DMA_CONTIGUOUS if MMU select HAVE_DYNAMIC_FTRACE if MMU && (CLANG_SUPPORTS_DYNAMIC_FTRACE || GCC_SUPPORTS_DYNAMIC_FTRACE) - select FUNCTION_ALIGNMENT_4B if HAVE_DYNAMIC_FTRACE && RISCV_ISA_C + select FUNCTION_ALIGNMENT_4B if DYNAMIC_FTRACE && RISCV_ISA_C select HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS if HAVE_DYNAMIC_FTRACE_WITH_CALL_OPS select HAVE_DYNAMIC_FTRACE_WITH_CALL_OPS if (DYNAMIC_FTRACE_WITH_ARGS && !CFI) select HAVE_DYNAMIC_FTRACE_WITH_ARGS if HAVE_DYNAMIC_FTRACE From 6dc3934152d1cc48b0395264d0061ebb4aed359e Mon Sep 17 00:00:00 2001 From: Yunhui Cui Date: Fri, 3 Jul 2026 20:28:30 +0800 Subject: [PATCH 305/479] riscv: io: avoid null-pointer arithmetic in PIO helpers When port I/O is not supported, exposing the port-string helpers is both unnecessary and can make clang diagnose null-pointer arithmetic from the PCI_IOBASE based address expression. Keep the MMIO string helpers available as before, but only provide the port I/O variants when CONFIG_HAS_IOPORT is enabled. Signed-off-by: Yunhui Cui Reviewed-by: Arnd Bergmann Link: https://patch.msgid.link/20260703122832.15984-2-cuiyunhui@bytedance.com Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/io.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/riscv/include/asm/io.h b/arch/riscv/include/asm/io.h index 09bb5f57a9d3..92d5f831f349 100644 --- a/arch/riscv/include/asm/io.h +++ b/arch/riscv/include/asm/io.h @@ -102,12 +102,14 @@ __io_reads_ins(reads, u32, l, __io_br(), __io_ar(addr)) #define readsw(addr, buffer, count) __readsw(addr, buffer, count) #define readsl(addr, buffer, count) __readsl(addr, buffer, count) +#ifdef CONFIG_HAS_IOPORT __io_reads_ins(ins, u8, b, __io_pbr(), __io_par(addr)) __io_reads_ins(ins, u16, w, __io_pbr(), __io_par(addr)) __io_reads_ins(ins, u32, l, __io_pbr(), __io_par(addr)) #define insb(addr, buffer, count) __insb(PCI_IOBASE + (addr), buffer, count) #define insw(addr, buffer, count) __insw(PCI_IOBASE + (addr), buffer, count) #define insl(addr, buffer, count) __insl(PCI_IOBASE + (addr), buffer, count) +#endif __io_writes_outs(writes, u8, b, __io_bw(), __io_aw()) __io_writes_outs(writes, u16, w, __io_bw(), __io_aw()) @@ -116,26 +118,32 @@ __io_writes_outs(writes, u32, l, __io_bw(), __io_aw()) #define writesw(addr, buffer, count) __writesw(addr, buffer, count) #define writesl(addr, buffer, count) __writesl(addr, buffer, count) +#ifdef CONFIG_HAS_IOPORT __io_writes_outs(outs, u8, b, __io_pbw(), __io_paw()) __io_writes_outs(outs, u16, w, __io_pbw(), __io_paw()) __io_writes_outs(outs, u32, l, __io_pbw(), __io_paw()) #define outsb(addr, buffer, count) __outsb(PCI_IOBASE + (addr), buffer, count) #define outsw(addr, buffer, count) __outsw(PCI_IOBASE + (addr), buffer, count) #define outsl(addr, buffer, count) __outsl(PCI_IOBASE + (addr), buffer, count) +#endif #ifdef CONFIG_64BIT __io_reads_ins(reads, u64, q, __io_br(), __io_ar(addr)) #define readsq(addr, buffer, count) __readsq(addr, buffer, count) +#ifdef CONFIG_HAS_IOPORT __io_reads_ins(ins, u64, q, __io_pbr(), __io_par(addr)) #define insq(addr, buffer, count) __insq(PCI_IOBASE + (addr), buffer, count) +#endif __io_writes_outs(writes, u64, q, __io_bw(), __io_aw()) #define writesq(addr, buffer, count) __writesq(addr, buffer, count) +#ifdef CONFIG_HAS_IOPORT __io_writes_outs(outs, u64, q, __io_pbr(), __io_paw()) #define outsq(addr, buffer, count) __outsq(PCI_IOBASE + (addr), buffer, count) #endif +#endif #include From ad6dcfa023762e37962f77ee48e752b7570e9440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 1 Jul 2026 11:21:22 +0200 Subject: [PATCH 306/479] riscv: vdso: Do not use LTO for the vDSO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With LTO enabled the compiler assumes that the vDSO functions are not used and optimizes them away completely. Currently this happens to __vdso_clock_getres(), __vdso_clock_gettime(), __vdso_getrandom(), __vdso_gettimeofday() and __vdso_riscv_hwprobe(). Disable LTO for the vDSO, as these functions are hand-optimized anyways. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606301855.WvkSC4kD-lkp@intel.com/ Fixes: 021d23428bdb ("RISC-V: build: Allow LTO to be selected") Cc: stable@vger.kernel.org Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260701-riscv-vdso-lto-v1-1-89db0cd82077@linutronix.de Signed-off-by: Paul Walmsley --- arch/riscv/kernel/vdso/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/riscv/kernel/vdso/Makefile b/arch/riscv/kernel/vdso/Makefile index a842dc034571..43ee881f6c6f 100644 --- a/arch/riscv/kernel/vdso/Makefile +++ b/arch/riscv/kernel/vdso/Makefile @@ -69,9 +69,9 @@ CPPFLAGS_$(vdso_lds) += -DHAS_VGETTIMEOFDAY endif # Disable -pg to prevent insert call site -CFLAGS_REMOVE_vgettimeofday.o = $(CC_FLAGS_FTRACE) $(CC_FLAGS_SCS) -CFLAGS_REMOVE_getrandom.o = $(CC_FLAGS_FTRACE) $(CC_FLAGS_SCS) -CFLAGS_REMOVE_hwprobe.o = $(CC_FLAGS_FTRACE) $(CC_FLAGS_SCS) +CFLAGS_REMOVE_vgettimeofday.o = $(CC_FLAGS_FTRACE) $(CC_FLAGS_SCS) $(CC_FLAGS_LTO) +CFLAGS_REMOVE_getrandom.o = $(CC_FLAGS_FTRACE) $(CC_FLAGS_SCS) $(CC_FLAGS_LTO) +CFLAGS_REMOVE_hwprobe.o = $(CC_FLAGS_FTRACE) $(CC_FLAGS_SCS) $(CC_FLAGS_LTO) # Force dependency $(obj)/$(vdso_o): $(obj)/$(vdso_so) From a82f1bb8191aec98a971a2196136016ef70c0880 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 5 Jul 2026 16:02:25 +0800 Subject: [PATCH 307/479] drm/i915/gt: use correct selftest config symbol intel_engine_user.c checks CONFIG_DRM_I915_SELFTESTS before running the engine UABI isolation check. Kconfig defines DRM_I915_SELFTEST, without the trailing "S", and the rest of i915 uses CONFIG_DRM_I915_SELFTEST. Because CONFIG_DRM_I915_SELFTESTS is not backed by any Kconfig symbol, the IS_ENABLED() test is always false. Use the existing selftest symbol so the debug/selftest guarded path can be reached when selftests are enabled. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the Kconfig definition and the inconsistent guard in intel_engine_user.c. Fixes: 750e76b4f9f6 ("drm/i915/gt: Move the [class][inst] lookup for engines onto the GT") Signed-off-by: Pengpeng Hou Signed-off-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20260705080225.436-1-pengpeng@iscas.ac.cn (cherry picked from commit 14a2012a490258f3f93857bc4f1b203405964be7) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/i915/gt/intel_engine_user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/gt/intel_engine_user.c b/drivers/gpu/drm/i915/gt/intel_engine_user.c index be4bbff1a57c..d5190e11b270 100644 --- a/drivers/gpu/drm/i915/gt/intel_engine_user.c +++ b/drivers/gpu/drm/i915/gt/intel_engine_user.c @@ -259,7 +259,7 @@ void intel_engines_driver_register(struct drm_i915_private *i915) p = &prev->rb_right; } - if (IS_ENABLED(CONFIG_DRM_I915_SELFTESTS) && + if (IS_ENABLED(CONFIG_DRM_I915_SELFTEST) && IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM)) { struct intel_engine_cs *engine; unsigned int isolation; From 6fa6ee724d8dadf392139e242ac936b5da730c4b Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 10 Jul 2026 18:04:22 +0200 Subject: [PATCH 308/479] arm64: dts: renesas: ironhide: Describe inline ECC carveouts The DBSC5 DRAM controller protects DRAM content using inline ECC. The inline ECC utilizes areas of DRAM for its operation, which are in the DRAM address range, but must not be accessed or modified. Describe the inline ECC carveout areas used by the DBSC5 controller on this hardware as reserved-memory, which must not be accessed. Include DRAM areas which are unprotected by ECC as well, those are parts of the DRAM which directly precede the ECC carveout. In case of high DRAM utilization, unless the inline ECC carveouts are properly reserved, Linux may use and corrupt the memory used by the DBSC5 DRAM controller for inline ECC, which would lead to the system becoming unstable. Fixes: ad142a4ef710 ("arm64: dts: renesas: r8a78000: Add initial Ironhide board support") Cc: stable@vger.kernel.org Signed-off-by: Marek Vasut Tested-by: Geert Uytterhoeven Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260710160450.64967-1-marek.vasut+renesas@mailbox.org Signed-off-by: Geert Uytterhoeven --- .../boot/dts/renesas/r8a78000-ironhide.dts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r8a78000-ironhide.dts b/arch/arm64/boot/dts/renesas/r8a78000-ironhide.dts index d2b3fc08954a..0ab303863155 100644 --- a/arch/arm64/boot/dts/renesas/r8a78000-ironhide.dts +++ b/arch/arm64/boot/dts/renesas/r8a78000-ironhide.dts @@ -107,6 +107,47 @@ tee@8c400000 { reg = <0x0 0x8c400000 0x0 0x02000000>; no-map; }; + + /* DRAM controller inline ECC areas */ + ecc@10cccc0000 { + reg = <0x10 0xcccc0000 0x0 0x33340000>; + no-map; + }; + + ecc@12cccc0000 { + reg = <0x12 0xcccc0000 0x0 0x33340000>; + no-map; + }; + + ecc@14cccc0000 { + reg = <0x14 0xcccc0000 0x0 0x33340000>; + no-map; + }; + + ecc@16cccc0000 { + reg = <0x16 0xcccc0000 0x0 0x33340000>; + no-map; + }; + + ecc@18cccc0000 { + reg = <0x18 0xcccc0000 0x0 0x33340000>; + no-map; + }; + + ecc@1a66660000 { + reg = <0x1a 0x66660000 0x0 0x999a0000>; + no-map; + }; + + ecc@1c66660000 { + reg = <0x1c 0x66660000 0x0 0x999a0000>; + no-map; + }; + + ecc@1e66660000 { + reg = <0x1e 0x66660000 0x0 0x999a0000>; + no-map; + }; }; }; From 79adf48fb091e32ed94113d919908be20e09f5eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=B6lzl?= Date: Fri, 19 Jun 2026 11:00:35 +0200 Subject: [PATCH 309/479] can: vxcan: Kconfig: fix description stating no local echo provided MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Kconfig description of the vxcan kernel module erroneously states the the vxcan interface does not provide a local echo of sent can frames. However this behavior changed in commit 259bdba27e32 ("vxcan: enable local echo for sent CAN frames") and vxcan interfaces now provide a local echo. Change the description of the vxcan module in the Kconfig to reflect this change. Signed-off-by: Alexander Hölzl Acked-by: Oliver Hartkopp Link: https://patch.msgid.link/20260619090035.17769-1-alexander.hoelzl@gmx.net [mkl: rephrase patch description] Signed-off-by: Marc Kleine-Budde --- drivers/net/can/Kconfig | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index e4058708ae68..a8fad6fe5302 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -40,11 +40,8 @@ config CAN_VXCAN When one end receives the packet it appears on its pair and vice versa. The vxcan can be used for cross namespace communication. - In opposite to vcan loopback devices the vxcan only forwards CAN - frames to its pair and does *not* provide a local echo of sent - CAN frames. To disable a potential echo in af_can.c the vxcan driver - announces IFF_ECHO in the interface flags. To have a clean start - in each namespace the CAN GW hop counter is set to zero. + To have a clean start in each namespace the CAN GW hop counter is + set to zero. This driver can also be built as a module. If so, the module will be called vxcan. From c43122fef328a70045fe7621c06de6b2b8e19264 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 9 Jul 2026 16:41:59 +0000 Subject: [PATCH 310/479] can: esd_usb: kill anchored URBs before freeing netdevs esd_usb_disconnect() frees each CAN netdev with free_candev() inside its per-netdev loop and only calls unlink_all_urbs(dev) afterwards. The per-netdev private data (struct esd_usb_net_priv) is embedded in the net_device allocation returned by alloc_candev(), so once free_candev() has run, dev->nets[i] points to freed memory. unlink_all_urbs() then dereferences the freed dev->nets[i] to kill the per-netdev TX anchor (usb_kill_anchored_urbs(&priv->tx_submitted)), clear active_tx_jobs, and reset priv->tx_contexts[]. Reorder the teardown so the anchored URBs are killed before the netdevs are freed, matching other CAN/USB drivers in the same directory such as ems_usb, usb_8dev and mcba_usb, which unregister, then unlink, then free: unregister the netdevs first (which stops their TX queues), call unlink_all_urbs(dev) once, then free the netdevs. This issue was found by an in-house static analysis tool. Fixes: 96d8e90382dc ("can: Add driver for esd CAN-USB/2 device") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.5 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260709164159.497640-1-fanwu01@zju.edu.cn Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/esd_usb.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/can/usb/esd_usb.c b/drivers/net/can/usb/esd_usb.c index d257440fa01f..f41d4a0d140f 100644 --- a/drivers/net/can/usb/esd_usb.c +++ b/drivers/net/can/usb/esd_usb.c @@ -1390,10 +1390,13 @@ static void esd_usb_disconnect(struct usb_interface *intf) netdev = dev->nets[i]->netdev; netdev_info(netdev, "unregister\n"); unregister_netdev(netdev); - free_candev(netdev); } } unlink_all_urbs(dev); + for (i = 0; i < dev->net_count; i++) { + if (dev->nets[i]) + free_candev(dev->nets[i]->netdev); + } kfree(dev); } } From 2f4acd0fcd862e22eab45690ec2c08c80b6ef2e7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:03:44 -0700 Subject: [PATCH 311/479] xfs: resample the data fork mapping after cycling ILOCK xfs_reflink_fill_{cow_hole,delalloc} are both presented with an inode, a data fork mapping, and a cow fork mapping. Unfortunately, these two helpers cycle the ILOCK to grab a transaction, which means that the mappings are stale as soon as we reacquire the ILOCK. Currently we refresh the cow fork mapping by re-calling xfs_find_trim_cow_extent, but we don't refresh the data fork mapping beforehand, which means that the xfs_bmap_trim_cow in that function queries the refcount btree about the wrong physical blocks and returns an inaccurate value in *shared. If *shared is now false, the directio write proceeds with a stale data fork mapping. Fix this by querying the data fork mapping if the sequence counter changes across the ILOCK cycle. Cc: hch@lst.de Cc: stable@vger.kernel.org # v4.11 Fixes: 3c68d44a2b49a0 ("xfs: allocate direct I/O COW blocks in iomap_begin") Signed-off-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_reflink.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index a5c188b78138..480136136635 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -440,6 +440,7 @@ xfs_reflink_fill_cow_hole( struct xfs_mount *mp = ip->i_mount; struct xfs_trans *tp; xfs_filblks_t resaligned; + unsigned int seq_before = READ_ONCE(ip->i_df.if_seq); unsigned int dblocks = 0, rblocks = 0; int nimaps; int error; @@ -465,6 +466,22 @@ xfs_reflink_fill_cow_hole( *lockmode = XFS_ILOCK_EXCL; + /* + * The data fork mapping may have changed while we dropped the ILOCK + * (a racing O_DIRECT writer under IOLOCK_SHARED can complete a full + * CoW cycle including xfs_reflink_end_cow(), which remaps this offset + * and drops the refcount of the old shared block). Re-read it so the + * shared-status recheck below and the caller's in-place iomap both + * operate on the current mapping rather than a stale physical block. + */ + if (seq_before != READ_ONCE(ip->i_df.if_seq)) { + nimaps = 1; + error = xfs_bmapi_read(ip, imap->br_startoff, + imap->br_blockcount, imap, &nimaps, 0); + if (error) + goto out_trans_cancel; + } + error = xfs_find_trim_cow_extent(ip, imap, cmap, shared, &found); if (error || !*shared) goto out_trans_cancel; @@ -511,6 +528,8 @@ xfs_reflink_fill_delalloc( bool found; do { + unsigned int seq_before = READ_ONCE(ip->i_df.if_seq); + xfs_iunlock(ip, *lockmode); *lockmode = 0; @@ -521,6 +540,23 @@ xfs_reflink_fill_delalloc( *lockmode = XFS_ILOCK_EXCL; + /* + * The data fork mapping may have changed while we dropped the + * ILOCK (a racing O_DIRECT writer under IOLOCK_SHARED can + * complete a full CoW cycle including xfs_reflink_end_cow(), + * which remaps this offset and drops the refcount of the old + * shared block). Re-read it so the shared-status recheck + * below and the caller's in-place iomap both operate on the + * current mapping rather than a stale physical block. + */ + if (seq_before != READ_ONCE(ip->i_df.if_seq)) { + nimaps = 1; + error = xfs_bmapi_read(ip, imap->br_startoff, + imap->br_blockcount, imap, &nimaps, 0); + if (error) + goto out_trans_cancel; + } + error = xfs_find_trim_cow_extent(ip, imap, cmap, shared, &found); if (error || !*shared) From ffa0aa5b625fe0bed7463ac613f8b06676ff4542 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 13 Jul 2026 21:49:25 +0200 Subject: [PATCH 312/479] x86/boot: Validate console=uart8250 baud rate to fix early boot hang When the baud rate is empty, 0, invalid, or overflows to 0 when stored as an int, the system will hang during early boot because of a division by zero in early_serial_init(). Fall back to DEFAULT_BAUD when the resulting baud rate is 0 to prevent an early system hang. Fixes: ce0aa5dd20e4 ("x86, setup: Make the setup code also accept console=uart8250") Signed-off-by: Thorsten Blum Signed-off-by: Ingo Molnar Cc: "H. Peter Anvin" Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260713194924.126472-3-thorsten.blum@linux.dev --- arch/x86/boot/early_serial_console.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/arch/x86/boot/early_serial_console.c b/arch/x86/boot/early_serial_console.c index 023bf1c3de8b..5b83beab89e1 100644 --- a/arch/x86/boot/early_serial_console.c +++ b/arch/x86/boot/early_serial_console.c @@ -117,7 +117,7 @@ static unsigned int probe_baud(int port) static void parse_console_uart8250(void) { char optstr[64], *options; - int baud = DEFAULT_BAUD; + int baud; int port = 0; /* @@ -136,10 +136,13 @@ static void parse_console_uart8250(void) else return; - if (options && (options[0] == ',')) - baud = simple_strtoull(options + 1, &options, 0); - else + if (options && (options[0] == ',')) { + baud = simple_strtoull(options + 1, NULL, 0); + if (!baud) + baud = DEFAULT_BAUD; + } else { baud = probe_baud(port); + } if (port) early_serial_init(port, baud); From a1caeeadbf57ff86dfc3454398c46de86056a74e Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:04:08 -0700 Subject: [PATCH 313/479] xfs: don't replace the wrong part of the cow fork 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 --- fs/xfs/scrub/cow_repair.c | 203 ++++++++++++++++++++++++-------------- fs/xfs/scrub/trace.h | 28 +++--- 2 files changed, 148 insertions(+), 83 deletions(-) diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c index 0075b6d5a1b5..ca3405a26b64 100644 --- a/fs/xfs/scrub/cow_repair.c +++ b/fs/xfs/scrub/cow_repair.c @@ -80,12 +80,6 @@ struct xrep_cow { unsigned int next_bno; }; -/* CoW staging extent. */ -struct xrep_cow_extent { - xfs_fsblock_t fsbno; - xfs_extlen_t len; -}; - /* * Mark the part of the file range that corresponds to the given physical * space. Caller must ensure that the physical range is within xc->irec. @@ -401,22 +395,21 @@ xrep_cow_find_bad_rt( 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; @@ -428,8 +421,8 @@ xrep_cow_alloc( xfs_refcount_alloc_cow_extent(sc->tp, false, args.fsbno, args.len); - repl->fsbno = args.fsbno; - repl->len = args.len; + del->br_startblock = args.fsbno; + del->br_blockcount = args.len; return 0; } @@ -440,10 +433,12 @@ xrep_cow_alloc( STATIC int xrep_cow_alloc_rt( struct xfs_scrub *sc, - xfs_extlen_t maxlen, - struct xrep_cow_extent *repl) + struct xfs_bmbt_irec *del) { - xfs_rtxlen_t maxrtx = xfs_rtb_to_rtx(sc->mp, maxlen); + xfs_fsblock_t fsbno; + xfs_rtxlen_t maxrtx = + min(U32_MAX, xfs_blen_to_rtbxlen(sc->mp, del->br_blockcount)); + xfs_extlen_t len; int error; error = xfs_trans_reserve_more(sc->tp, 0, maxrtx); @@ -451,11 +446,14 @@ xrep_cow_alloc_rt( return error; error = xfs_rtallocate_rtgs(sc->tp, NULLRTBLOCK, 1, maxrtx, 1, false, - false, &repl->fsbno, &repl->len); + false, &fsbno, &len); if (error) return error; - xfs_refcount_alloc_cow_extent(sc->tp, true, repl->fsbno, repl->len); + xfs_refcount_alloc_cow_extent(sc->tp, true, fsbno, len); + + del->br_startblock = fsbno; + del->br_blockcount = len; return 0; } @@ -469,19 +467,19 @@ static inline int xrep_cow_find_mapping( struct xrep_cow *xc, struct xfs_iext_cursor *icur, - xfs_fileoff_t startoff, - struct xfs_bmbt_irec *got) + xfs_fileoff_t badoff, + xfs_extlen_t badlen, + struct xfs_bmbt_irec *got, + struct xfs_bmbt_irec *rep) { struct xfs_inode *ip = xc->sc->ip; struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK); - if (!xfs_iext_lookup_extent(ip, ifp, startoff, icur, got)) + if (!xfs_iext_lookup_extent(ip, ifp, badoff, icur, got)) goto bad; + memcpy(rep, got, sizeof(*rep)); - if (got->br_startoff > startoff) - goto bad; - - if (got->br_blockcount == 0) + if (got->br_startoff > badoff) goto bad; if (isnullstartblock(got->br_startblock)) @@ -490,6 +488,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); @@ -500,46 +516,92 @@ xrep_cow_find_mapping( #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; + + 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; } - - /* - * 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); } /* @@ -553,33 +615,30 @@ xrep_cow_replace_range( xfs_extlen_t *blockcount) { struct xfs_iext_cursor icur; - struct xrep_cow_extent repl; - struct xfs_bmbt_irec got; + struct xfs_bmbt_irec got, rep; struct xfs_scrub *sc = xc->sc; - xfs_fileoff_t nextoff; - xfs_extlen_t alloc_len; + xfs_fsblock_t old_fsbno; int error; /* - * Put the existing CoW fork mapping in @got. If @got ends before - * @rep, truncate @rep so we only replace one extent mapping at a time. + * Put the existing CoW fork mapping in @got, and put in @rep the + * contents of @got trimmed to @startoff/@blockcount. We only want + * to replace the bad region, and only one mapping at a time. */ - error = xrep_cow_find_mapping(xc, &icur, startoff, &got); + error = xrep_cow_find_mapping(xc, &icur, startoff, *blockcount, &got, + &rep); if (error) return error; - nextoff = min(startoff + *blockcount, - got.br_startoff + got.br_blockcount); + old_fsbno = rep.br_startblock; /* * Allocate a replacement extent. If we don't fill all the blocks, * shorten the quantity that will be deleted in this step. */ - alloc_len = min_t(xfs_fileoff_t, XFS_MAX_BMBT_EXTLEN, - nextoff - startoff); if (XFS_IS_REALTIME_INODE(sc->ip)) - error = xrep_cow_alloc_rt(sc, alloc_len, &repl); + error = xrep_cow_alloc_rt(sc, &rep); else - error = xrep_cow_alloc(sc, alloc_len, &repl); + error = xrep_cow_alloc(sc, &rep); if (error) return error; @@ -587,7 +646,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); @@ -596,15 +655,15 @@ xrep_cow_replace_range( /* Note the old CoW staging extents; we'll reap them all later. */ if (XFS_IS_REALTIME_INODE(sc->ip)) - error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks, - got.br_startblock, repl.len); + error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks, old_fsbno, + rep.br_blockcount); else - error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks, - got.br_startblock, repl.len); + error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks, old_fsbno, + rep.br_blockcount); if (error) return error; - *blockcount = repl.len; + *blockcount = rep.br_blockcount; return 0; } diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 1b7d9e07a27d..d48a6db5b5f3 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -2672,9 +2672,9 @@ TRACE_EVENT(xrep_cow_mark_file_range, ); TRACE_EVENT(xrep_cow_replace_mapping, - TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *irec, - xfs_fsblock_t new_startblock, xfs_extlen_t new_blockcount), - TP_ARGS(ip, irec, new_startblock, new_blockcount), + TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *got, + const struct xfs_bmbt_irec *rep), + TP_ARGS(ip, got, rep), TP_STRUCT__entry( __field(dev_t, dev) __field(xfs_ino_t, ino) @@ -2682,28 +2682,34 @@ TRACE_EVENT(xrep_cow_replace_mapping, __field(xfs_fileoff_t, startoff) __field(xfs_filblks_t, blockcount) __field(xfs_exntst_t, state) + __field(xfs_fileoff_t, new_startoff) __field(xfs_fsblock_t, new_startblock) __field(xfs_extlen_t, new_blockcount) + __field(xfs_exntst_t, new_state) ), TP_fast_assign( __entry->dev = ip->i_mount->m_super->s_dev; __entry->ino = I_INO(ip); - __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, From bcb0621204e6999bb8f3a6c7deb5c6abfe0f197b Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:04:24 -0700 Subject: [PATCH 314/479] xfs: make cow repair somewhat flaky when debugging knob enabled Introduce a new behavior for the cow fork repair code: if the debugging knob is enabled, we'll pick a random subrange of each cow fork mapping to mark as bad. This will exercise the xrep_cow_replace_mapping more thoroughly. Signed-off-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/cow_repair.c | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c index ca3405a26b64..511a761dca9f 100644 --- a/fs/xfs/scrub/cow_repair.c +++ b/fs/xfs/scrub/cow_repair.c @@ -224,6 +224,29 @@ xrep_cow_mark_missing_staging_rmap( xfs_gbno_to_fsb(cur->bc_group, rec_bno), rec_len); } +/* + * Trim the start and end of the current mapping by up to 1/4 of the length + * and mark that as "bad" to test the cow fork repair mechanism. + */ +static inline int +xrep_cow_debug_replacement( + struct xrep_cow *xc) +{ + xfs_fsblock_t fsbno = xc->irec.br_startblock; + xfs_extlen_t len = xc->irec.br_blockcount; + uint32_t trim; + + /* get_random_u32_below requires a nonzero argument */ + trim = len > 4 ? get_random_u32_below(len / 4) : 0; + len -= trim; + + trim = len > 4 ? get_random_u32_below(len / 4) : 0; + fsbno += trim; + len -= trim; + + return xrep_cow_mark_file_range(xc, fsbno, len); +} + /* * Find any part of the CoW fork mapping that isn't a single-owner CoW staging * extent and mark the corresponding part of the file range in the bitmap. @@ -293,8 +316,9 @@ xrep_cow_find_bad( * If userspace is forcing us to rebuild the CoW fork or someone turned * on the debugging knob, replace everything in the CoW fork. */ - if ((sc->sm->sm_flags & XFS_SCRUB_IFLAG_FORCE_REBUILD) || - XFS_TEST_ERROR(sc->mp, XFS_ERRTAG_FORCE_SCRUB_REPAIR)) + if (XFS_TEST_ERROR(sc->mp, XFS_ERRTAG_FORCE_SCRUB_REPAIR)) + error = xrep_cow_debug_replacement(xc); + else if (sc->sm->sm_flags & XFS_SCRUB_IFLAG_FORCE_REBUILD) error = xrep_cow_mark_file_range(xc, xc->irec.br_startblock, xc->irec.br_blockcount); @@ -375,8 +399,9 @@ xrep_cow_find_bad_rt( * turned on the debugging knob, replace everything in the * CoW fork and then scan for staging extents in the refcountbt. */ - if ((sc->sm->sm_flags & XFS_SCRUB_IFLAG_FORCE_REBUILD) || - XFS_TEST_ERROR(sc->mp, XFS_ERRTAG_FORCE_SCRUB_REPAIR)) + if (XFS_TEST_ERROR(sc->mp, XFS_ERRTAG_FORCE_SCRUB_REPAIR)) + error = xrep_cow_debug_replacement(xc); + else if (sc->sm->sm_flags & XFS_SCRUB_IFLAG_FORCE_REBUILD) error = xrep_cow_mark_file_range(xc, xc->irec.br_startblock, xc->irec.br_blockcount); From 60a1dde9d28948a88ed5a06cf0b6179b1d487f85 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:04:39 -0700 Subject: [PATCH 315/479] xfs: move cow_replace_mapping to xfs_bmap_util.c Move the actual details of (partially) replacing a COW fork mapping to xfs_bmap_util.c so that all the code doing hairy operations on subsets of bmbt_irecs are kept together. Signed-off-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/cow_repair.c | 95 +-------------------------------------- fs/xfs/scrub/trace.h | 41 ----------------- fs/xfs/xfs_bmap_util.c | 89 ++++++++++++++++++++++++++++++++++++ fs/xfs/xfs_bmap_util.h | 4 ++ fs/xfs/xfs_trace.h | 41 +++++++++++++++++ 5 files changed, 136 insertions(+), 134 deletions(-) diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c index 511a761dca9f..8dd9c0266e21 100644 --- a/fs/xfs/scrub/cow_repair.c +++ b/fs/xfs/scrub/cow_repair.c @@ -29,6 +29,7 @@ #include "xfs_rtalloc.h" #include "xfs_rtbitmap.h" #include "xfs_rtgroup.h" +#include "xfs_bmap_util.h" #include "scrub/xfs_scrub.h" #include "scrub/scrub.h" #include "scrub/common.h" @@ -537,98 +538,6 @@ xrep_cow_find_mapping( return -EFSCORRUPTED; } -#define REPLACE_LEFT_SIDE (1U << 0) -#define REPLACE_RIGHT_SIDE (1U << 1) - -/* - * 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, - struct xfs_bmbt_irec *got, - struct xfs_bmbt_irec *rep) -{ - 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(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, rep); - - 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: - /* - * Replacement matches the whole mapping, update the record. - */ - 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; - - 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; - } -} - /* * Replace the unwritten CoW staging extent backing the given file range with a * new space extent that isn't as problematic. @@ -671,7 +580,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, &rep); + xfs_bmap_replace_cow_mapping(sc->ip, &icur, &got, &rep); xfs_inode_set_cowblocks_tag(sc->ip); error = xfs_defer_finish(&sc->tp); diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index d48a6db5b5f3..d5d39d82749e 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -2671,47 +2671,6 @@ TRACE_EVENT(xrep_cow_mark_file_range, __entry->blockcount) ); -TRACE_EVENT(xrep_cow_replace_mapping, - 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) - __field(xfs_fsblock_t, startblock) - __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 = I_INO(ip); - __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_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_state) -); - TRACE_EVENT(xrep_cow_free_staging, TP_PROTO(const struct xfs_perag *pag, xfs_agblock_t agbno, xfs_extlen_t blockcount), diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 3b9f262f8e91..c88b9ade7389 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -1744,3 +1744,92 @@ xfs_swap_extents( xfs_trans_cancel(tp); goto out_unlock_ilock; } + +/* + * 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. + */ +void +xfs_bmap_replace_cow_mapping( + struct xfs_inode *ip, + struct xfs_iext_cursor *icur, + struct xfs_bmbt_irec *got, + struct xfs_bmbt_irec *rep) +{ + 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(rep->br_blockcount > 0); + ASSERT(!isnullstartblock(got->br_startblock)); + ASSERT(got->br_startoff <= rep->br_startoff); + ASSERT(got_endoff >= rep_endoff); + + trace_xfs_bmap_replace_cow_mapping(ip, got, rep); + + 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: + /* + * Replacement matches the whole mapping, update the record. + */ + 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; + + 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; + } +} diff --git a/fs/xfs/xfs_bmap_util.h b/fs/xfs/xfs_bmap_util.h index c477b3361630..eaaf094154b9 100644 --- a/fs/xfs/xfs_bmap_util.h +++ b/fs/xfs/xfs_bmap_util.h @@ -81,4 +81,8 @@ int xfs_bmap_count_blocks(struct xfs_trans *tp, struct xfs_inode *ip, int xfs_flush_unmap_range(struct xfs_inode *ip, xfs_off_t offset, xfs_off_t len); +void xfs_bmap_replace_cow_mapping(struct xfs_inode *ip, + struct xfs_iext_cursor *icur, struct xfs_bmbt_irec *got, + struct xfs_bmbt_irec *rep); + #endif /* __XFS_BMAP_UTIL_H__ */ diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index d478693674f9..aeb89ac53bf1 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -6439,6 +6439,47 @@ TRACE_EVENT(xfs_verify_media_error, __entry->error) ); +TRACE_EVENT(xfs_bmap_replace_cow_mapping, + 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) + __field(xfs_fsblock_t, startblock) + __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 = I_INO(ip); + __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_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_state) +); + #endif /* _TRACE_XFS_H */ #undef TRACE_INCLUDE_PATH From d766e4e5e85d829629c3ba503802fe1303d7b591 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:04:55 -0700 Subject: [PATCH 316/479] xfs: don't wrap around quota ids in dqiterate LOLLM noticed that q_id is an unsigned 32-bit variable. If it happens to be set to XFS_DQ_ID_MAX due to a filesystem that actually has a dquot for ID_MAX, then this addition will truncate to zero and the iteration starts over. Fix this by casting to u64. Cc: stable@vger.kernel.org # v6.8 Fixes: 21d7500929c8a0 ("xfs: improve dquot iteration for scrub") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dqiterate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/dqiterate.c b/fs/xfs/scrub/dqiterate.c index 10950e4bd4c3..079dc4e691a0 100644 --- a/fs/xfs/scrub/dqiterate.c +++ b/fs/xfs/scrub/dqiterate.c @@ -205,7 +205,7 @@ xchk_dquot_iter( if (error) return error; - cursor->id = dq->q_id + 1; + cursor->id = (uint64_t)dq->q_id + 1; *dqpp = dq; return 1; } From ee248157da501f0c02688fb64e5359f2832b0b01 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:05:10 -0700 Subject: [PATCH 317/479] xfs: use rtrefcount btree cursor in xchk_xref_is_rt_cow_staging LOLLM points out that we pass the wrong btree cursor here. We want the rtrefcount btree cursor, not the non-rt one. This is fairly benign since it only affects tracing data. Cc: stable@vger.kernel.org # v6.14 Fixes: 91683bb3f264c0 ("xfs: cross-reference checks with the rt refcount btree") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtrefcount.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index 0d10ce2910c2..4e7c540c8d23 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -607,7 +607,7 @@ xchk_xref_is_rt_cow_staging( /* CoW lookup returned a shared extent record? */ if (rc.rc_domain != XFS_REFC_DOMAIN_COW) - xchk_btree_xref_set_corrupt(sc, sc->sa.refc_cur, 0); + xchk_btree_xref_set_corrupt(sc, sc->sr.refc_cur, 0); /* Must be at least as long as what was passed in */ if (rc.rc_blockcount < len) From 881f2eb0fcdea2f46a50fa6a892fe9e3ccf19e01 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:05:26 -0700 Subject: [PATCH 318/479] xfs: use the rt version of the cow staging checker LOLLM also noticed that xchk_rtrmapbt_xref ought to be using the rtdev version of the "is this a cow extent?" helper function, not the datadev one. Cc: stable@vger.kernel.org # v6.14 Fixes: 91683bb3f264c0 ("xfs: cross-reference checks with the rt refcount btree") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtrmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/rtrmap.c b/fs/xfs/scrub/rtrmap.c index 043be93c7148..b3b2cf17ba2c 100644 --- a/fs/xfs/scrub/rtrmap.c +++ b/fs/xfs/scrub/rtrmap.c @@ -209,7 +209,7 @@ xchk_rtrmapbt_xref( xfs_rgbno_to_rtb(sc->sr.rtg, irec->rm_startblock), irec->rm_blockcount); if (irec->rm_owner == XFS_RMAP_OWN_COW) - xchk_xref_is_cow_staging(sc, irec->rm_startblock, + xchk_xref_is_rt_cow_staging(sc, irec->rm_startblock, irec->rm_blockcount); else xchk_rtrmapbt_xref_rtrefc(sc, irec); From 9af789fa274bc14b907f811f79fa988a6fc6d4e7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:05:41 -0700 Subject: [PATCH 319/479] xfs: write the rg superblock when fixing it The rtgroup superblock fixer should write the rtgroup superblock. LOLLM noticed this, oops. :/ Cc: stable@vger.kernel.org # v6.13 Fixes: 1433f8f9cead37 ("xfs: repair realtime group superblock") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rgsuper.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/xfs/scrub/rgsuper.c b/fs/xfs/scrub/rgsuper.c index 482f899a518a..3dad6e5da74e 100644 --- a/fs/xfs/scrub/rgsuper.c +++ b/fs/xfs/scrub/rgsuper.c @@ -80,9 +80,13 @@ int xrep_rgsuperblock( struct xfs_scrub *sc) { + struct xfs_buf *sb_bp; + ASSERT(rtg_rgno(sc->sr.rtg) == 0); + sb_bp = xfs_trans_getsb(sc->tp); xfs_log_sb(sc->tp); + xfs_log_rtsb(sc->tp, sb_bp); return 0; } #endif /* CONFIG_XFS_ONLINE_REPAIR */ From ea6e2d9de25d2095845e0cdf2274e581fd40d636 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:05:57 -0700 Subject: [PATCH 320/479] xfs: grab rtrmap btree when checking rgsuper LOLLM noticed that we aren't grabbing the rtrmap btree when we check the realtime group superblock. As a result, none of the cross-referencing checks have ever run. Fix this. Cc: stable@vger.kernel.org # v6.14 Fixes: 428e4884656db9 ("xfs: allow queued realtime intents to drain before scrubbing") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rgsuper.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/rgsuper.c b/fs/xfs/scrub/rgsuper.c index 3dad6e5da74e..2bd2c0351b35 100644 --- a/fs/xfs/scrub/rgsuper.c +++ b/fs/xfs/scrub/rgsuper.c @@ -23,6 +23,8 @@ int xchk_setup_rgsuperblock( struct xfs_scrub *sc) { + if (xchk_need_intent_drain(sc)) + xchk_fsgates_enable(sc, XCHK_FSGATES_DRAIN); return xchk_trans_alloc(sc, 0); } @@ -43,6 +45,7 @@ xchk_rgsuperblock( struct xfs_scrub *sc) { xfs_rgnumber_t rgno = sc->sm->sm_agno; + unsigned int flags; int error; /* @@ -63,7 +66,12 @@ xchk_rgsuperblock( if (!xchk_xref_process_error(sc, 0, 0, &error)) return error; - error = xchk_rtgroup_lock(sc, &sc->sr, XFS_RTGLOCK_BITMAP_SHARED); + if (xfs_has_rtrmapbt(sc->mp)) + flags = XFS_RTGLOCK_BITMAP | XFS_RTGLOCK_RMAP; + else + flags = XFS_RTGLOCK_BITMAP_SHARED; + + error = xchk_rtgroup_lock(sc, &sc->sr, flags); if (error) return error; From 540ddc626245f12f56326ee0c1601f71ebb41d64 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:06:12 -0700 Subject: [PATCH 321/479] xfs: set xfarray killable sort correctly LOLLM noticed that we *disable* interruptible sorts when the KILLABLE flag is set. This is backwards. Fix the incorrect logic, and rename the variable to make the connection more obvious. Cc: stable@vger.kernel.org # v6.10 Fixes: 271557de7cbfde ("xfs: reduce the rate of cond_resched calls inside scrub") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/scrub.h | 6 +++--- fs/xfs/scrub/xfarray.c | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/fs/xfs/scrub/scrub.h b/fs/xfs/scrub/scrub.h index a3f1abc91390..6d7d3523b71f 100644 --- a/fs/xfs/scrub/scrub.h +++ b/fs/xfs/scrub/scrub.h @@ -11,7 +11,7 @@ struct xfs_scrub; struct xchk_relax { unsigned long next_resched; unsigned int resched_nr; - bool interruptible; + bool killable; }; /* Yield to the scheduler at most 10x per second. */ @@ -21,7 +21,7 @@ struct xchk_relax { (struct xchk_relax){ \ .next_resched = XCHK_RELAX_NEXT, \ .resched_nr = 0, \ - .interruptible = true, \ + .killable = true, \ } /* @@ -45,7 +45,7 @@ static inline int xchk_maybe_relax(struct xchk_relax *widget) widget->next_resched = XCHK_RELAX_NEXT; } - if (widget->interruptible && fatal_signal_pending(current)) + if (widget->killable && fatal_signal_pending(current)) return -EINTR; return 0; diff --git a/fs/xfs/scrub/xfarray.c b/fs/xfs/scrub/xfarray.c index c7c4a71b6fa7..2ce24bfe4c0f 100644 --- a/fs/xfs/scrub/xfarray.c +++ b/fs/xfs/scrub/xfarray.c @@ -487,8 +487,7 @@ xfarray_sortinfo_alloc( xfarray_sortinfo_lo(si)[0] = 0; xfarray_sortinfo_hi(si)[0] = array->nr - 1; si->relax = INIT_XCHK_RELAX; - if (flags & XFARRAY_SORT_KILLABLE) - si->relax.interruptible = false; + si->relax.killable = !!(flags & XFARRAY_SORT_KILLABLE); trace_xfarray_sort(si, nr_bytes); *infop = si; From 5d72a68f2007ba2a968d6bf47dba3f4620bd182e Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:06:28 -0700 Subject: [PATCH 322/479] xfs: fix off-by-one error when calling xchk_xref_has_rt_owner LOLLM noticed an off-by-one error when computing the length of the rtrmap to cross-check. Cc: stable@vger.kernel.org # v6.14 Fixes: 037a44d8277adf ("xfs: cross-reference the realtime rmapbt") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtbitmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/rtbitmap.c b/fs/xfs/scrub/rtbitmap.c index de3f22f310f7..52c24d3d4be6 100644 --- a/fs/xfs/scrub/rtbitmap.c +++ b/fs/xfs/scrub/rtbitmap.c @@ -258,7 +258,7 @@ xchk_rtbitmap( * the last free extent we saw and the last possible extent in the rt * group. */ - last_rgbno = rtg->rtg_extents * mp->m_sb.sb_rextsize - 1; + last_rgbno = rtg->rtg_extents * mp->m_sb.sb_rextsize; if (rtb->next_free_rgbno < last_rgbno) xchk_xref_has_rt_owner(sc, rtb->next_free_rgbno, last_rgbno - rtb->next_free_rgbno); From 353a5900bc85234e7df05caac37698042dc26348 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:06:43 -0700 Subject: [PATCH 323/479] xfs: handle non-inode owners for rtrmap record checking LOLLM noticed that two helper functions in the rtrmapbt scrub code don't actually handle non-inode owners correctly -- CoW staging extents and rgsuperblock extents are not shareable, but they are mergeable. Fix these two helpers. Cc: stable@vger.kernel.org # v6.14 Fixes: 2d9a3e98053e8c ("xfs: allow overlapping rtrmapbt records for shared data extents") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtrmap.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/xfs/scrub/rtrmap.c b/fs/xfs/scrub/rtrmap.c index b3b2cf17ba2c..564d19a97a2f 100644 --- a/fs/xfs/scrub/rtrmap.c +++ b/fs/xfs/scrub/rtrmap.c @@ -87,6 +87,9 @@ xchk_rtrmapbt_is_shareable( return false; if (irec->rm_flags & XFS_RMAP_UNWRITTEN) return false; + if (irec->rm_owner == XFS_RMAP_OWN_COW || + irec->rm_owner == XFS_RMAP_OWN_FS) + return false; return true; } @@ -146,6 +149,9 @@ xchk_rtrmap_mergeable( return false; if (r1->rm_flags != r2->rm_flags) return false; + if (r1->rm_owner == XFS_RMAP_OWN_COW || + r1->rm_owner == XFS_RMAP_OWN_FS) + return true; return r1->rm_offset + r1->rm_blockcount == r2->rm_offset; } From ba150ce63453ccd74bae1404c1dfedbd01ecfd55 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:06:59 -0700 Subject: [PATCH 324/479] xfs: fully check the parent handle when it points to the rootdir LOLLM noticed that the directory tree path checking declares the path to be ok if the inumber in the parent pointer reaches the root directory. Unfortunately, it neglects to check that the generation is correct. Fix that by moving the generation check up. Cc: stable@vger.kernel.org # v6.10 Fixes: 928b721a11789a ("xfs: teach online scrub to find directory tree structure problems") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/xfs/scrub/dirtree.c b/fs/xfs/scrub/dirtree.c index c6a210f7508f..b2cf6e5439d9 100644 --- a/fs/xfs/scrub/dirtree.c +++ b/fs/xfs/scrub/dirtree.c @@ -383,6 +383,14 @@ xchk_dirpath_step_up( goto out_scanlock; } + /* The handle encoded in the parent pointer must match. */ + if (VFS_I(dp)->i_generation != be32_to_cpu(dl->pptr_rec.p_gen)) { + trace_xchk_dirpath_badgen(dl->sc, dp, path->path_nr, + path->nr_steps, &dl->xname, &dl->pptr_rec); + error = -EFSCORRUPTED; + goto out_scanlock; + } + /* We've reached the root directory; the path is ok. */ if (parent_ino == dl->root_ino) { xchk_dirpath_set_outcome(dl, path, XCHK_DIRPATH_OK); @@ -411,14 +419,6 @@ xchk_dirpath_step_up( goto out_scanlock; } - /* The handle encoded in the parent pointer must match. */ - if (VFS_I(dp)->i_generation != be32_to_cpu(dl->pptr_rec.p_gen)) { - trace_xchk_dirpath_badgen(dl->sc, dp, path->path_nr, - path->nr_steps, &dl->xname, &dl->pptr_rec); - error = -EFSCORRUPTED; - goto out_scanlock; - } - /* Parent pointer must point up to a directory. */ if (!S_ISDIR(VFS_I(dp)->i_mode)) { trace_xchk_dirpath_nondir_parent(dl->sc, dp, path->path_nr, From 15e38a9366b31d3d61081ead115f1dff59379e24 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:07:15 -0700 Subject: [PATCH 325/479] xfs: clamp timestamp nanoseconds correctly LOLLM noticed an off-by-one error in the nsec clamping; fix that so that we never have tv_nsec == 1e9. Cc: stable@vger.kernel.org # v6.8 Fixes: 2d295fe65776d1 ("xfs: repair inode records") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index 493dcf5cc6c1..5565e80691a6 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1757,7 +1757,7 @@ xrep_clamp_timestamp( struct xfs_inode *ip, struct timespec64 *ts) { - ts->tv_nsec = clamp_t(long, ts->tv_nsec, 0, NSEC_PER_SEC); + ts->tv_nsec = clamp_t(long, ts->tv_nsec, 0, NSEC_PER_SEC - 1); *ts = timestamp_truncate(*ts, VFS_I(ip)); } From 59c462b0f5cfa107794228051724b34ae9334168 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 13 Jul 2026 23:07:30 -0700 Subject: [PATCH 326/479] xfs: don't zap bmbt forks if they are MAXLEVELS tall LOLLM noticed a discrepancy between the bmbt level checks in the libxfs bmbt code vs. the inode repair code. We do actually allow a bmbt root that proclaims to have a height of XFS_BM_MAXLEVELS. Cc: stable@vger.kernel.org # v6.8 Fixes: e744cef2060559 ("xfs: zap broken inode forks") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index 5565e80691a6..3ec41c198351 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -921,7 +921,7 @@ xrep_dinode_bad_bmbt_fork( if (nrecs == 0 || xfs_bmdr_space_calc(nrecs) > dfork_size) return true; - if (level == 0 || level >= XFS_BM_MAXLEVELS(sc->mp, whichfork)) + if (level == 0 || level > XFS_BM_MAXLEVELS(sc->mp, whichfork)) return true; dmxr = xfs_bmdr_maxrecs(dfork_size, 0); From 1e5185c090589f4146d728ab36417d8a5419f127 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Thu, 7 May 2026 10:22:23 +0200 Subject: [PATCH 327/479] can: raw: add locking for raw flags bitfield With commit 890e5198a6e5 ("can: raw: use bitfields to store flags in struct raw_sock") the formerly separate integer values have been integrated into a single bitfield. This led to a read-modify-write operation when changing a flag in raw_setsockopt() which now needs a locking to prevent concurrent access. Instead of adding a lock/unlock hell in each of the flag manipulations this patch introduces a wrapper for a new raw_setsockopt_locked() function analogue to the isotp_setsockopt[_locked]() approach in net/can/isotp.c Fixes: 890e5198a6e5 ("can: raw: use bitfields to store flags in struct raw_sock") Reported-by: Eulgyu Kim Closes: https://lore.kernel.org/linux-can/20260503112200.22727-1-eulgyukim@snu.ac.kr/ Tested-by: Eulgyu Kim Signed-off-by: Oliver Hartkopp Reviewed-by: Vincent Mailhol Tested-by: Vincent Mailhol Link: https://patch.msgid.link/20260504111928.41856-1-socketcan@hartkopp.net [mkl: use Closes tag instead of Link] Signed-off-by: Marc Kleine-Budde --- net/can/raw.c | 66 +++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/net/can/raw.c b/net/can/raw.c index a26942e78e68..82d9c0499c95 100644 --- a/net/can/raw.c +++ b/net/can/raw.c @@ -562,8 +562,8 @@ static int raw_getname(struct socket *sock, struct sockaddr *uaddr, return RAW_MIN_NAMELEN; } -static int raw_setsockopt(struct socket *sock, int level, int optname, - sockptr_t optval, unsigned int optlen) +static int raw_setsockopt_locked(struct socket *sock, int optname, + sockptr_t optval, unsigned int optlen) { struct sock *sk = sock->sk; struct raw_sock *ro = raw_sk(sk); @@ -575,9 +575,6 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, int flag; int err = 0; - if (level != SOL_CAN_RAW) - return -EINVAL; - switch (optname) { case CAN_RAW_FILTER: if (optlen % sizeof(struct can_filter) != 0) @@ -598,17 +595,11 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, return -EFAULT; } - rtnl_lock(); - lock_sock(sk); - dev = ro->dev; - if (ro->bound && dev) { - if (dev->reg_state != NETREG_REGISTERED) { - if (count > 1) - kfree(filter); - err = -ENODEV; - goto out_fil; - } + if (ro->bound && dev && dev->reg_state != NETREG_REGISTERED) { + if (count > 1) + kfree(filter); + return -ENODEV; } if (ro->bound) { @@ -622,7 +613,7 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, if (err) { if (count > 1) kfree(filter); - goto out_fil; + return err; } /* remove old filter registrations */ @@ -642,11 +633,6 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, } ro->filter = filter; ro->count = count; - - out_fil: - release_sock(sk); - rtnl_unlock(); - break; case CAN_RAW_ERR_FILTER: @@ -658,16 +644,9 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, err_mask &= CAN_ERR_MASK; - rtnl_lock(); - lock_sock(sk); - dev = ro->dev; - if (ro->bound && dev) { - if (dev->reg_state != NETREG_REGISTERED) { - err = -ENODEV; - goto out_err; - } - } + if (ro->bound && dev && dev->reg_state != NETREG_REGISTERED) + return -ENODEV; /* remove current error mask */ if (ro->bound) { @@ -676,7 +655,7 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, err_mask); if (err) - goto out_err; + return err; /* remove old err_mask registration */ raw_disable_errfilter(sock_net(sk), dev, sk, @@ -685,11 +664,6 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, /* link new err_mask to the socket */ ro->err_mask = err_mask; - - out_err: - release_sock(sk); - rtnl_unlock(); - break; case CAN_RAW_LOOPBACK: @@ -769,6 +743,26 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, return err; } +static int raw_setsockopt(struct socket *sock, int level, int optname, + sockptr_t optval, unsigned int optlen) +{ + struct sock *sk = sock->sk; + int err; + + if (level != SOL_CAN_RAW) + return -EINVAL; + + rtnl_lock(); + lock_sock(sk); + + err = raw_setsockopt_locked(sock, optname, optval, optlen); + + release_sock(sk); + rtnl_unlock(); + + return err; +} + static int raw_getsockopt(struct socket *sock, int level, int optname, sockopt_t *opt) { From 0bcd59706aeda8a5d48ba656bab74cb26f2b423e Mon Sep 17 00:00:00 2001 From: Wang YuWei <1973615295@qq.com> Date: Mon, 6 Jul 2026 11:33:13 +0800 Subject: [PATCH 328/479] spi: dw-dma: Wait for controller idle before completing Tx dw_spi_dma_wait_tx_done() polls dw_spi_dma_tx_busy(), which only checks DW_SPI_SR_TF_EMPT. An empty TX FIFO merely means the last data word has been moved into the shift register; the transfer is not complete on the bus until DW_SPI_SR_BUSY is also cleared. As a result the wait can return while the controller is still shifting out the final word. Any caller that tears down or reconfigures the controller right after the transfer can then lose the tail of the transfer. The memory-operation path in spi-dw-core.c already waits for both DW_SPI_SR_BUSY == 0 and DW_SPI_SR_TF_EMPT == 1. Use the same completion condition in the DMA path so the transfer is guaranteed to be finished on the bus before the wait returns. Signed-off-by: Wang YuWei <1973615295@qq.com> Link: https://patch.msgid.link/tencent_4EA7B5C94669ED4C38A5F6C1C9126E5D9106@qq.com Signed-off-by: Mark Brown --- drivers/spi/spi-dw-dma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c index bd70a7ed8067..f7d848fec9ab 100644 --- a/drivers/spi/spi-dw-dma.c +++ b/drivers/spi/spi-dw-dma.c @@ -282,7 +282,8 @@ static int dw_spi_dma_wait(struct dw_spi *dws, unsigned int len, u32 speed) static inline bool dw_spi_dma_tx_busy(struct dw_spi *dws) { - return !(dw_readl(dws, DW_SPI_SR) & DW_SPI_SR_TF_EMPT); + return (dw_readl(dws, DW_SPI_SR) & + (DW_SPI_SR_BUSY | DW_SPI_SR_TF_EMPT)) != DW_SPI_SR_TF_EMPT; } static int dw_spi_dma_wait_tx_done(struct dw_spi *dws, From e919ca35a6e84b5adf085da9ffa1544c01d4ce1e Mon Sep 17 00:00:00 2001 From: Srikanth Boyapally Date: Wed, 8 Jul 2026 10:21:48 +0530 Subject: [PATCH 329/479] spi: cadence-quadspi: Fix indirect write timeout when DMA read mode is enabled When use_dma_read is enabled, the IRQ handler unconditionally overwrites irq_status with the return value of get_dma_status(). For write operations, DMA status returns 0 since no DMA read is in progress, causing irq_status to become 0. The subsequent completion signal is never triggered and the write operation times out with -ETIMEDOUT: cadence-qspi f1010000.spi: Indirect write timeout spi-nor spi0.1: operation failed with -110 Fix this by separating the DMA completion path from the write interrupt path. If get_dma_status() indicates DMA read completion, signal completion and return immediately. Otherwise, preserve the original irq_status so that write completion interrupts are correctly recognized and signalled. Fixes: aac733a96636 ("spi: cadence-qspi: Fix style and improve readability") Signed-off-by: Srikanth Boyapally Reviewed-by: Radhey Shyam Pandey Link: https://patch.msgid.link/20260708045148.2993313-1-srikanth.boyapally@amd.com Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-quadspi.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index aaba1a3ad577..ecb0be394696 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -382,12 +382,16 @@ static irqreturn_t cqspi_irq_handler(int this_irq, void *dev) /* Clear interrupt */ writel(irq_status, cqspi->iobase + CQSPI_REG_IRQSTATUS); - if (cqspi->use_dma_read && ddata && ddata->get_dma_status) - irq_status = ddata->get_dma_status(cqspi); - else if (cqspi->slow_sram) + if (cqspi->use_dma_read && ddata && ddata->get_dma_status) { + if (ddata->get_dma_status(cqspi)) { + complete(&cqspi->transfer_complete); + return IRQ_HANDLED; + } + } else if (cqspi->slow_sram) { irq_status &= CQSPI_IRQ_MASK_RD_SLOW_SRAM | CQSPI_IRQ_MASK_WR; - else + } else { irq_status &= CQSPI_IRQ_MASK_RD | CQSPI_IRQ_MASK_WR; + } if (irq_status) complete(&cqspi->transfer_complete); From f61289af2667a942f48859fe0b030894153ad1d2 Mon Sep 17 00:00:00 2001 From: Vidya Srinivas Date: Thu, 18 Jun 2026 23:48:37 +0530 Subject: [PATCH 330/479] drm/i915/display: Fix NV12 ceiling division for bigjoiner case Commit 16df4cc63c58 ("drm/i915/display: Use ceiling division for NV12 UV surface offset calculation") computes the UV (chroma) surface start/size as ceiling(half of Y plane start/size) directly from the U16.16 fixed-point source rectangle: x = fp_16_16_to_int_ceil(fp_16_16_div2(src.x1)); For a single pipe the source coordinates are integers, so this is correct. (UV start = ceiling(half of Y plane start)). With bigjoiner + a plane scaler the picture changes. The pipe boundary is a fixed integer destination pixel, but the plane's position and the scaler ratio are arbitrary, so drm_rect_clip_scaled() maps the seam back to a *fractional* per-pipe source. For a 1280->2407 upscaled NV12 plane crossing the seam: master src: width = 1204 * 1280/2407 = 640.265899, x1 = 0 joiner src: width = 1203 * 1280/2407 = 639.734115, x1 = 640.265884 The luma path floors this to an integer (src.x1 >> 16 = 640), but the UV path takes ceiling(640.265884 / 2) = ceil(320.13) = 321. The Y plane then starts at column 640 while the UV plane starts at 321*2 = 642, pushing the chroma read one column past the 640-wide chroma surface on the joiner secondary: [CRTC:382:pipe C] PLANE ATS fault [CRTC:382:pipe C][PLANE:267:plane 1C] fault (CTL=0x81009400, ...) The spec "Y plane start" is the integer pixel the luma surface actually programs (640), not the pre-floor fixed-point value (640.27). Convert the Y plane start/size to integer first - matching skl_check_main_surface() - and then apply the ceiling. This is a no-op for the integer (non-joiner) case and yields the correct, in-bounds chroma offset for the fractional joiner seam: before fix after fix master 1B: x=0 w=321 x=0 w=320 -> [0, 320) slave 1C: x=321 w=320 x=320 w=320 -> [320, 640) The two halves now tile the 640-wide chroma plane exactly and the ATS fault is gone. Assisted-by: GitHub-Copilot:Claude-Opus-4.8 Fixes: 16df4cc63c58 ("drm/i915/display: Use ceiling division for NV12 UV surface offset calculation") Signed-off-by: Vidya Srinivas Reviewed-by: Juha-Pekka Heikkila Signed-off-by: Uma Shankar Link: https://patch.msgid.link/20260618181837.687302-1-vidya.srinivas@intel.com (cherry picked from commit 0c59cc78241c10e5f02d92b28d811b0435e706a7) Signed-off-by: Rodrigo Vivi --- .../drm/i915/display/skl_universal_plane.c | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c index ad4bfff6903d..164b7d61c9a3 100644 --- a/drivers/gpu/drm/i915/display/skl_universal_plane.c +++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c @@ -2126,19 +2126,6 @@ static int skl_check_main_surface(struct intel_plane_state *plane_state) return 0; } - -/* Divide a U16.16 fixed-point value by 2, staying in fixed-point domain */ -static inline u32 fp_16_16_div2(u32 fp) -{ - return fp >> 1; -} - -/* Convert a U16.16 fixed-point value to integer, rounding up */ -static inline int fp_16_16_to_int_ceil(u32 fp) -{ - return DIV_ROUND_UP(fp, 1 << 16); -} - static int skl_check_nv12_aux_surface(struct intel_plane_state *plane_state) { struct intel_display *display = to_intel_display(plane_state); @@ -2154,14 +2141,20 @@ static int skl_check_nv12_aux_surface(struct intel_plane_state *plane_state) int max_height = intel_plane_max_height(plane, fb, uv_plane, rotation); /* - * LNL+ UV surface start/size = - * ceiling(half of Y plane start/size). Use ceiling division - * unconditionally; it is a no-op for even values. + * UV (chroma) start/size = ceiling(half of the *integer* Y plane + * start/size), i.e. the value the luma surface programs (src >> 16), + * not the raw U16.16. A bigjoiner seam mapped through the scaler can + * give a fractional luma src; ceiling that directly would round the + * chroma one column too far and read past the chroma surface. */ - int x = fp_16_16_to_int_ceil(fp_16_16_div2(plane_state->uapi.src.x1)); - int y = fp_16_16_to_int_ceil(fp_16_16_div2(plane_state->uapi.src.y1)); - int w = fp_16_16_to_int_ceil(fp_16_16_div2(drm_rect_width(&plane_state->uapi.src))); - int h = fp_16_16_to_int_ceil(fp_16_16_div2(drm_rect_height(&plane_state->uapi.src))); + int luma_x = plane_state->uapi.src.x1 >> 16; + int luma_y = plane_state->uapi.src.y1 >> 16; + int luma_w = drm_rect_width(&plane_state->uapi.src) >> 16; + int luma_h = drm_rect_height(&plane_state->uapi.src) >> 16; + int x = DIV_ROUND_UP(luma_x, 2); + int y = DIV_ROUND_UP(luma_y, 2); + int w = DIV_ROUND_UP(luma_x + luma_w, 2) - x; + int h = DIV_ROUND_UP(luma_y + luma_h, 2) - y; u32 offset; /* FIXME not quite sure how/if these apply to the chroma plane */ From 627b6c94b817c2ee00c854d102a5da08105ad0a7 Mon Sep 17 00:00:00 2001 From: Andi Shyti Date: Thu, 25 Jun 2026 18:32:21 +0200 Subject: [PATCH 331/479] CREDITS: Add Wolfram Sang Wolfram Sang has decided that a decade-plus of I2C was enough and is moving on to new things. Thank you, Wolfram, for your years of dedication and for keeping the bus in line. Your legacy is now officially cemented in the CREDITS file. Suggested-by: Sebastian Reichel Signed-off-by: Andi Shyti Cc: Wolfram Sang Reviewed-by: Wolfram Sang Link: https://lore.kernel.org/r/20260625163221.183414-1-andi.shyti@kernel.org --- CREDITS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CREDITS b/CREDITS index 84793a967a0b..91c51c14e993 100644 --- a/CREDITS +++ b/CREDITS @@ -3626,6 +3626,13 @@ S: 69 rue Dunois S: 75013 Paris S: France +N: Wolfram Sang +E: wsa@kernel.org +W: sang-engineering.com +P: rsa4096/140DE4CC14A029B6 3991 B1EA B9E2 6751 A4F7 645D 140D E4CC 14A0 29B6 +D: I2C Maintainer 2012 - 2026 +S: Berlin, Germany + N: Aleksa Sarai E: cyphar@cyphar.com W: https://www.cyphar.com/ From cb2fc37857693b55909fb77dc2c87cfbc1cdc476 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Mon, 13 Jul 2026 20:11:59 +0200 Subject: [PATCH 332/479] i2c: imx: fix locked bus on SMBus block-read of 0 (atomic) 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 --- drivers/i2c/busses/i2c-imx.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index 28313d0fad37..cfd1e63359e7 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -1415,6 +1415,7 @@ static int i2c_imx_atomic_read(struct imx_i2c_struct *i2c_imx, 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, false); if (result) @@ -1436,8 +1437,20 @@ static int i2c_imx_atomic_read(struct imx_i2c_struct *i2c_imx, */ 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); @@ -1485,7 +1498,7 @@ static int i2c_imx_atomic_read(struct imx_i2c_struct *i2c_imx, "<%s> read byte: B%d=0x%X\n", __func__, i, msgs->buf[i]); } - return 0; + return block_err; } static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, From 07fd9385f0d87dff4b34f355f68adf701080cb24 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Mon, 13 Jul 2026 20:12:00 +0200 Subject: [PATCH 333/479] i2c: imx: fix locked bus on SMBus block-read of 0 (IRQ) SMBus 3.1 6.5.7 allows a Block Read byte count of 0, but the interrupt-driven block-read state machine 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 of this i2c controller. Accept count=0: NACK the in-flight dummy byte (TXAK) and set msg->len to 2 so i2c_imx_isr_read_continue() emits STOP via its normal last-byte path. The dummy byte is discarded; block-read callers only consume buf[0..count-1]. Reading I2DR has likewise already armed the next byte on the count > I2C_SMBUS_BLOCK_MAX error path, so NACK it (TXAK) before aborting with -EPROTO; otherwise the failing transfer's STOP cannot complete and the bus stays held. The atomic path regressed earlier (v3.16) and is fixed separately; this patch covers only the v6.13 state-machine rework. Fixes: 5f5c2d4579ca ("i2c: imx: prevent rescheduling in non dma mode") Signed-off-by: Vincent Jardin Cc: # v6.13+ 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-2-073ac9e103a5@free.fr --- drivers/i2c/busses/i2c-imx.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index cfd1e63359e7..d5e6e2eca3b3 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -1061,11 +1061,28 @@ static inline enum imx_i2c_state i2c_imx_isr_read_continue(struct imx_i2c_struct static inline void i2c_imx_isr_read_block_data_len(struct imx_i2c_struct *i2c_imx) { u8 len = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); + unsigned int temp; 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. + * So NACK it (TXAK) to not hold the bus. + */ + temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); + temp |= I2CR_TXAK; + imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); + + if (len == 0) { + i2c_imx->msg->buf[i2c_imx->msg_buf_idx++] = 0; + i2c_imx->msg->len = 2; + return; + } + i2c_imx->isr_result = -EPROTO; i2c_imx->state = IMX_I2C_STATE_FAILED; wake_up(&i2c_imx->queue); + return; } i2c_imx->msg->len += len; i2c_imx->msg->buf[i2c_imx->msg_buf_idx++] = len; From 9db20d23aac7916ff49be409a4bfd48fe7cbbfb4 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Fri, 10 Jul 2026 15:21:13 +0800 Subject: [PATCH 334/479] i2c: spacemit: fix spurious IRQ handling returning IRQ_HANDLED When the interrupt status register reads zero, the handler should return IRQ_NONE instead of IRQ_HANDLED. What the return value actually feeds into is the spurious interrupt accounting in note_interrupt(): falsely claiming IRQ_HANDLED defeats the "irq XX: nobody cared" detection, so a stuck interrupt source would never be caught. Fixes: 5ea558473fa3 ("i2c: spacemit: add support for SpacemiT K1 SoC") Signed-off-by: Pei Xiao Cc: # v6.15+ Reviewed-by: Troy Mitchell Reviewed-by: Mukesh Savaliya Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/ef8b623f45d4e430721e46572c2598d882044aed.1783667875.git.xiaopei01@kylinos.cn --- drivers/i2c/busses/i2c-k1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index 9152cf436bea..51a0c3d80fc9 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -596,7 +596,7 @@ static irqreturn_t spacemit_i2c_irq_handler(int irq, void *devid) status = readl(i2c->base + SPACEMIT_ISR); if (!status) - return IRQ_HANDLED; + return IRQ_NONE; i2c->status = status; From 0bc7c196f3ab855da0879283ba633c4c51ddfd81 Mon Sep 17 00:00:00 2001 From: Honglei Huang Date: Wed, 1 Jul 2026 14:27:58 +0800 Subject: [PATCH 335/479] drm/gpusvm: free the whole IOVA reservation on unmap dma_iova_try_alloc() reserves IOVA for the entire range, but in a mixed range only the system pages are linked (their total size is state_offset) while device pages never touch the IOVA state. dma_iova_destroy() with state_offset only frees the linked part, permanently leaking the IOVA reserved for the device pages and eventually exhausting the IOVA space. Unlink the linked system-page portion and free the whole reserved IOVA instead. On the get_pages() error path state_offset is 0 (no page linked, dma_addr[0] unpopulated), so skip the unlink and just free the reservation; this also avoids reading the uninitialized dma_addr[0].dir there. Allocate the dma_addr array with the zeroing kvzalloc_objs() so every entry has a well-defined value. This issue was found by Sashiko AI review. Fixes: 37ad039fb367 ("drm/gpusvm: Use dma-map IOVA alloc, link, and sync API in GPU SVM") Cc: stable@vger.kernel.org Reviewed-by: Matthew Brost Signed-off-by: Honglei Huang Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260701062800.409248-2-honghuan@amd.com --- drivers/gpu/drm/drm_gpusvm.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index 958cb605aedd..3145d55cd860 100644 --- a/drivers/gpu/drm/drm_gpusvm.c +++ b/drivers/gpu/drm/drm_gpusvm.c @@ -1146,10 +1146,19 @@ static void __drm_gpusvm_unmap_pages(struct drm_gpusvm *gpusvm, }; bool use_iova = dma_use_iova(&svm_pages->state); - if (use_iova) - dma_iova_destroy(dev, &svm_pages->state, - svm_pages->state_offset, - svm_pages->dma_addr[0].dir, 0); + /* + * IOVA is reserved for the whole range but only the linked + * system pages (state_offset bytes) need unlinking; free the + * entire reservation to avoid leaking the device-page part. + * On the error path state_offset is 0, so just free it. + */ + if (use_iova) { + if (svm_pages->state_offset) + dma_iova_unlink(dev, &svm_pages->state, 0, + svm_pages->state_offset, + svm_pages->dma_addr[0].dir, 0); + dma_iova_free(dev, &svm_pages->state); + } for (i = 0, j = 0; i < npages; j++) { struct drm_pagemap_addr *addr = &svm_pages->dma_addr[j]; @@ -1486,7 +1495,7 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm, /* Unlock and restart mapping to allocate memory. */ drm_gpusvm_notifier_unlock(gpusvm); svm_pages->dma_addr = - kvmalloc_objs(*svm_pages->dma_addr, npages); + kvzalloc_objs(*svm_pages->dma_addr, npages); if (!svm_pages->dma_addr) { err = -ENOMEM; goto err_free; From ea2f9985aa4adeef89e9523be295633bb3d0874a Mon Sep 17 00:00:00 2001 From: Honglei Huang Date: Wed, 1 Jul 2026 14:27:59 +0800 Subject: [PATCH 336/479] drm/gpusvm: do not route system pages to device_unmap() on IOVA unmap In a mixed range: ctx->allow_mixed dpagemap is not NULL while some entries are system pages. The unmap loop used: dma_unmap_page(...); else if (dpagemap && dpagemap->ops->device_unmap) dpagemap->ops->device_unmap(...); When use_iova is true the first condition is false for system pages, so they fall through to device_unmap() and a system DMA address is handed to the device specific unmap callback, risking invalid accesses or state corruption. Key the branch off addr->proto instead: system pages only need an explicit dma_unmap_page() in the non IOVA case, IOVA system pages are already torn down by the single dma_iova_destroy(), and only genuine device pages reach device_unmap(). This issue was found by Sashiko AI review. Fixes: 37ad039fb367 ("drm/gpusvm: Use dma-map IOVA alloc, link, and sync API in GPU SVM") Cc: stable@vger.kernel.org Reviewed-by: Matthew Brost Signed-off-by: Honglei Huang Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260701062800.409248-3-honghuan@amd.com --- drivers/gpu/drm/drm_gpusvm.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index 3145d55cd860..44bb19658dd6 100644 --- a/drivers/gpu/drm/drm_gpusvm.c +++ b/drivers/gpu/drm/drm_gpusvm.c @@ -1163,12 +1163,18 @@ static void __drm_gpusvm_unmap_pages(struct drm_gpusvm *gpusvm, for (i = 0, j = 0; i < npages; j++) { struct drm_pagemap_addr *addr = &svm_pages->dma_addr[j]; - if (!use_iova && addr->proto == DRM_INTERCONNECT_SYSTEM) - dma_unmap_page(dev, - addr->addr, - PAGE_SIZE << addr->order, - addr->dir); - else if (dpagemap && dpagemap->ops->device_unmap) + if (addr->proto == DRM_INTERCONNECT_SYSTEM) { + /* + * Linked IOVA pages were already torn down by + * the dma_iova_unlink()/dma_iova_free() above; + * only the non-IOVA mappings need unmap here. + */ + if (!use_iova) + dma_unmap_page(dev, + addr->addr, + PAGE_SIZE << addr->order, + addr->dir); + } else if (dpagemap && dpagemap->ops->device_unmap) dpagemap->ops->device_unmap(dpagemap, dev, addr); i += 1 << addr->order; From 7f708f51e3955bda0d77a0b67ab9bea6c97fea99 Mon Sep 17 00:00:00 2001 From: Honglei Huang Date: Wed, 1 Jul 2026 14:28:00 +0800 Subject: [PATCH 337/479] drm/gpusvm: publish dpagemap early to avoid device mapping leak on error drm_gpusvm_get_pages() only stored the local dpagemap into svm_pages->dpagemap on the success path. If a later page failed (e.g. -EOPNOTSUPP when ctx->allow_mixed is false) and jumped to err_unmap, svm_pages->dpagemap was still NULL, so __drm_gpusvm_unmap_pages() skipped device_unmap() and leaked the device mappings already created. Assign svm_pages->dpagemap when the first device page is mapped so the err_unmap path can device_unmap() those mappings. This issue was found by Sashiko AI review. Fixes: f70da6f99d4f ("drm/gpusvm: pull out drm_gpusvm_pages substructure") Cc: stable@vger.kernel.org Reviewed-by: Matthew Brost Signed-off-by: Honglei Huang Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260701062800.409248-4-honghuan@amd.com --- drivers/gpu/drm/drm_gpusvm.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index 44bb19658dd6..9a06ff7d2608 100644 --- a/drivers/gpu/drm/drm_gpusvm.c +++ b/drivers/gpu/drm/drm_gpusvm.c @@ -1544,6 +1544,16 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm, err = -EAGAIN; goto err_unmap; } + + /* + * Set the dpagemap as soon as the first + * device page is mapped so the err_unmap path + * can device_unmap() the device mappings that + * have already been created. + */ + drm_pagemap_get(dpagemap); + drm_pagemap_put(svm_pages->dpagemap); + svm_pages->dpagemap = dpagemap; } svm_pages->dma_addr[j] = dpagemap->ops->device_map(dpagemap, @@ -1611,12 +1621,8 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm, goto err_unmap; } - if (pagemap) { + if (pagemap) flags.has_devmem_pages = true; - drm_pagemap_get(dpagemap); - drm_pagemap_put(svm_pages->dpagemap); - svm_pages->dpagemap = dpagemap; - } /* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */ WRITE_ONCE(svm_pages->flags.__flags, flags.__flags); From f1596ba3e6b390aa0fef8466afce44efecf39d8d Mon Sep 17 00:00:00 2001 From: Jaeyeong Lee Date: Sun, 12 Jul 2026 14:27:12 +0000 Subject: [PATCH 338/479] io_uring/kbuf: free the replaced iovec after a successful grow The provided-buffer validation fix deferred freeing a cached iovec until validation completed. However, the deferred free uses arg->iovs. After a grow, that points to the newly allocated array. Without a grow, it points to the cached array that remains in use. This leaves the caller with a dangling iovec in both cases and can result in repeated frees. Only free org_iovs when arg->iovs actually replaced it. Fixes: cd053d788c3f ("io_uring: fix dangling iovec after provided-buffer bundle grow failure") Assisted-by: Codex:gpt-5.3-codex-spark Signed-off-by: Jaeyeong Lee Link: https://patch.msgid.link/20260712142612.188695595-iostreampy@proton.me Signed-off-by: Jens Axboe --- io_uring/kbuf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io_uring/kbuf.c b/io_uring/kbuf.c index b6b969b55e12..de0129bceaba 100644 --- a/io_uring/kbuf.c +++ b/io_uring/kbuf.c @@ -328,8 +328,8 @@ static int io_ring_buffers_peek(struct io_kiocb *req, struct buf_sel_arg *arg, buf = io_ring_head_to_buf(br, ++head, bl->mask); } while (--nr_iovs); - if (arg->mode & KBUF_MODE_FREE) - kfree(arg->iovs); + if (arg->iovs != org_iovs && (arg->mode & KBUF_MODE_FREE)) + kfree(org_iovs); if (head == tail) req->flags |= REQ_F_BL_EMPTY; From cc609376e9a43166a2fba2aef6c5f9ea262ce722 Mon Sep 17 00:00:00 2001 From: Yi Xie Date: Tue, 14 Jul 2026 11:03:06 +0800 Subject: [PATCH 339/479] io_uring/fs: check unused sqe fields for unlinkat Zero check unused SQE fields addr3 and pad2 for unlinkat. They're not needed now, but could be used sometime in the future. Signed-off-by: Yi Xie Reviewed-by: Gabriel Krisman Bertazi Link: https://patch.msgid.link/20260714030306.64820-1-xieyi@kylinos.cn Signed-off-by: Jens Axboe --- io_uring/fs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/io_uring/fs.c b/io_uring/fs.c index d0580c754bf8..26ea841a22e7 100644 --- a/io_uring/fs.c +++ b/io_uring/fs.c @@ -110,7 +110,8 @@ int io_unlinkat_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) const char __user *fname; int err; - if (sqe->off || sqe->len || sqe->buf_index || sqe->splice_fd_in) + if (sqe->off || sqe->len || sqe->buf_index || sqe->splice_fd_in || + sqe->addr3 || sqe->__pad2[0]) return -EINVAL; if (unlikely(req->flags & REQ_F_FIXED_FILE)) return -EBADF; From 71356737a7a55c76fee847563e3d33f8e6dc6b6d Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Tue, 14 Jul 2026 23:08:08 +0800 Subject: [PATCH 340/479] i2c: mlxbf: Fix use-after-free in mlxbf_i2c_init_resource() If devm_platform_get_and_ioremap_resource() returns an error, mlxbf_i2c_init_resource() frees tmp_res before reading tmp_res->io to get the error code. This results in a use-after-free. Save the error code before freeing tmp_res. Fixes: b5b5b32081cd ("i2c: mlxbf: I2C SMBus driver for Mellanox BlueField SoC") Signed-off-by: Xuanqiang Luo Cc: # v5.10+ Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260714150808.85045-1-xuanqiang.luo@linux.dev --- drivers/i2c/busses/i2c-mlxbf.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-mlxbf.c b/drivers/i2c/busses/i2c-mlxbf.c index 6c1cfe9ec8ac..e33512b25353 100644 --- a/drivers/i2c/busses/i2c-mlxbf.c +++ b/drivers/i2c/busses/i2c-mlxbf.c @@ -1051,8 +1051,10 @@ static int mlxbf_i2c_init_resource(struct platform_device *pdev, tmp_res->io = devm_platform_get_and_ioremap_resource(pdev, type, &tmp_res->params); if (IS_ERR(tmp_res->io)) { + int ret = PTR_ERR(tmp_res->io); + devm_kfree(dev, tmp_res); - return PTR_ERR(tmp_res->io); + return ret; } tmp_res->type = type; From 9fe595fad54d4ac6a402edb3f60bec859d52cea6 Mon Sep 17 00:00:00 2001 From: Ondrej Mosnacek Date: Tue, 14 Jul 2026 14:57:59 +0200 Subject: [PATCH 341/479] selinux: fix incorrect execmem checks on overlayfs The commit fixing the overlayfs mmap() and mprotect() access checks failed to skip the execmem check in __file_map_prot_check() for the case where the "mounter check" is being performed. This check should be performed only against the credentials of the task that is calling mmap()/mprotect(), since it doesn't pertain to the file itself, but rather just gates the ability of the calling task to get an executable memory mapping in general. The purpose of the "mounter check" is to guard against using an overlayfs mount to gain file access that would otherwise be denied to the mounter. For execmem this is not relevant, as there is no further file access granted based on it (notice that the file's context is not used as the target in the check), so checking it also against the mounter credentials would be incorrect. Fix this by passing a boolean to [__]file_map_prot_check() and selinux_mmap_file_common() that indicates if we are doing the "mounter check" and skiping the execmem check in that case. Since this boolean also indicates if we use current_cred() or the mounter cred as the subject, also remove the "cred" argument from these functions and determine it based on the boolean and the file struct. Cc: stable@vger.kernel.org Fixes: 82544d36b172 ("selinux: fix overlayfs mmap() and mprotect() access checks") Signed-off-by: Ondrej Mosnacek Reviewed-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 42 +++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 70a3388c047d..8d6945edae7a 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3969,9 +3969,9 @@ static int selinux_file_ioctl_compat(struct file *file, unsigned int cmd, static int default_noexec __ro_after_init; -static int __file_map_prot_check(const struct cred *cred, - const struct file *file, unsigned long prot, - bool shared, bool bf_user_file) +static int __file_map_prot_check(const struct file *file, unsigned long prot, + bool shared, bool mounter_check, + bool bf_user_file) { struct inode *inode = NULL; bool prot_exec = prot & PROT_EXEC; @@ -3984,10 +3984,10 @@ static int __file_map_prot_check(const struct cred *cred, inode = file_inode(file); } - if (default_noexec && prot_exec && + if (!mounter_check && default_noexec && prot_exec && (!file || IS_PRIVATE(inode) || (!shared && prot_write))) { int rc; - u32 sid = cred_sid(cred); + u32 sid = current_sid(); /* * We are making executable an anonymous mapping or a private @@ -4000,6 +4000,8 @@ static int __file_map_prot_check(const struct cred *cred, } if (file) { + const struct cred *cred = mounter_check ? + file->f_cred : current_cred(); /* "read" always possible, "write" only if shared */ u32 av = FILE__READ; if (shared && prot_write) @@ -4013,11 +4015,11 @@ static int __file_map_prot_check(const struct cred *cred, return 0; } -static inline int file_map_prot_check(const struct cred *cred, - const struct file *file, - unsigned long prot, bool shared) +static inline int file_map_prot_check(const struct file *file, + unsigned long prot, bool shared, + bool mounter_check) { - return __file_map_prot_check(cred, file, prot, shared, false); + return __file_map_prot_check(file, prot, shared, mounter_check, false); } static int selinux_mmap_addr(unsigned long addr) @@ -4033,12 +4035,14 @@ static int selinux_mmap_addr(unsigned long addr) return rc; } -static int selinux_mmap_file_common(const struct cred *cred, struct file *file, - unsigned long prot, bool shared) +static int selinux_mmap_file_common(struct file *file, unsigned long prot, + bool shared, bool mounter_check) { if (file) { int rc; struct common_audit_data ad; + const struct cred *cred = mounter_check ? + file->f_cred : current_cred(); ad.type = LSM_AUDIT_DATA_FILE; ad.u.file = file; @@ -4047,15 +4051,16 @@ static int selinux_mmap_file_common(const struct cred *cred, struct file *file, return rc; } - return file_map_prot_check(cred, file, prot, shared); + return file_map_prot_check(file, prot, shared, mounter_check); } static int selinux_mmap_file(struct file *file, unsigned long reqprot __always_unused, unsigned long prot, unsigned long flags) { - return selinux_mmap_file_common(current_cred(), file, prot, - (flags & MAP_TYPE) == MAP_SHARED); + return selinux_mmap_file_common(file, prot, + (flags & MAP_TYPE) == MAP_SHARED, + false); } /** @@ -4087,8 +4092,9 @@ static int selinux_mmap_backing_file(struct vm_area_struct *vma, if (vma->vm_flags & VM_EXEC) prot |= PROT_EXEC; - return selinux_mmap_file_common(backing_file->f_cred, backing_file, - prot, vma->vm_flags & VM_SHARED); + return selinux_mmap_file_common(backing_file, prot, + vma->vm_flags & VM_SHARED, + true); } static int selinux_file_mprotect(struct vm_area_struct *vma, @@ -4149,11 +4155,11 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, } } - rc = __file_map_prot_check(cred, file, prot, shared, backing_file); + rc = __file_map_prot_check(file, prot, shared, false, backing_file); if (rc) return rc; if (backing_file) { - rc = file_map_prot_check(file->f_cred, file, prot, shared); + rc = file_map_prot_check(file, prot, shared, true); if (rc) return rc; } From deb35336b5bfed5db9231b5348bc1514db930797 Mon Sep 17 00:00:00 2001 From: Roman Vivchar Date: Thu, 9 Jul 2026 16:31:29 +0300 Subject: [PATCH 342/479] i2c: mediatek: fix WRRD for SoCs without auto_restart option MediaTek mt65xx family SoCs have no auto restart, however, they still support the WRRD mode in the hardware. Because auto_restart is set to 0, the WRRD mode will be never enabled, leading to read errors. Fix this by removing auto_restart check from the WRRD enable path. Fixes: b49218365280 ("i2c: mediatek: fix potential incorrect use of I2C_MASTER_WRRD") Signed-off-by: Roman Vivchar Cc: # v6.18+ Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260709-6572-6595-i2c-v2-1-b2fb8510d1d3@protonmail.com --- drivers/i2c/busses/i2c-mt65xx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-mt65xx.c b/drivers/i2c/busses/i2c-mt65xx.c index 126040ca05f1..307925fb78e3 100644 --- a/drivers/i2c/busses/i2c-mt65xx.c +++ b/drivers/i2c/busses/i2c-mt65xx.c @@ -1258,7 +1258,7 @@ static int mtk_i2c_transfer(struct i2c_adapter *adap, i2c->auto_restart = i2c->dev_comp->auto_restart; /* checking if we can skip restart and optimize using WRRD mode */ - if (i2c->auto_restart && num == 2) { + if (num == 2) { if (!(msgs[0].flags & I2C_M_RD) && (msgs[1].flags & I2C_M_RD) && msgs[0].addr == msgs[1].addr) { i2c->auto_restart = 0; From 261c1fe3327ad24508f54552c6366e3e4db82c15 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Mon, 13 Jul 2026 19:30:28 +0200 Subject: [PATCH 343/479] accel/amdxdna: reject user command submission without a command BO amdxdna_drm_submit_execbuf() passes the user-supplied command BO handle straight into amdxdna_cmd_submit() with drv_cmd == NULL. When the handle is AMDXDNA_INVALID_BO_HANDLE (0), the block that fetches job->cmd_bo is skipped, leaving it NULL, and no check rejects it on the user path (the !job->cmd_bo guard lives inside the != INVALID branch). The job is then armed and pushed to the DRM scheduler. aie2_sched_job_run() takes the drv_cmd == NULL path and calls amdxdna_cmd_set_state(job->cmd_bo) -> amdxdna_gem_vmap(NULL) -> to_gobj(NULL)->dev, a NULL pointer dereference in the drm_sched worker. A process with access to the accel node on a system with a probed AMD NPU can trigger a kernel oops with a single AMDXDNA_EXEC_CMD ioctl (cmd_handles = 0). Only internal driver commands (SYNC_DEBUG_BO / ATTACH_DEBUG_BO) legitimately pass AMDXDNA_INVALID_BO_HANDLE, and they always set drv_cmd. Reject the invalid handle for user submissions (drv_cmd == NULL) at the submit choke point so every user path is covered. Fixes: aac243092b70 ("accel/amdxdna: Add command execution") Cc: stable@vger.kernel.org Found by 0sec automated security-research tooling (https://0sec.ai). Assisted-by: 0sec:claude-opus-4-8 Signed-off-by: Doruk Tan Ozturk Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260713173030.87541-2-doruk@0sec.ai --- drivers/accel/amdxdna/amdxdna_ctx.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/accel/amdxdna/amdxdna_ctx.c b/drivers/accel/amdxdna/amdxdna_ctx.c index 8f8df9d04ec5..a5c8c2c4de6d 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.c +++ b/drivers/accel/amdxdna/amdxdna_ctx.c @@ -603,6 +603,16 @@ int amdxdna_cmd_submit(struct amdxdna_client *client, ret = -EINVAL; goto free_job; } + } else if (!drv_cmd) { + /* + * Only internal driver commands (drv_cmd != NULL) may omit a + * command BO. A user command submission with the invalid handle + * would leave job->cmd_bo NULL and later fault when the scheduler + * dereferences it in amdxdna_cmd_set_state(). + */ + XDNA_DBG(xdna, "Command BO handle required for user submission"); + ret = -EINVAL; + goto free_job; } ret = amdxdna_arg_bos_lookup(client, job, arg_bo_hdls, arg_bo_cnt); From 38953513d7313992676d4136cd425cdb70c6278e Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Mon, 13 Jul 2026 19:30:29 +0200 Subject: [PATCH 344/479] accel/amdxdna: reject command submission on devices without a submit op amdxdna_cmd_submit() calls xdna->dev_info->ops->cmd_submit() unconditionally, but only aie2_dev_ops defines that callback. aie4_vf_ops (the AIE4 SR-IOV virtual function) does not, so a user AMDXDNA_EXEC_CMD ioctl on an AIE4 device reaches a NULL function-pointer call and oopses the kernel. AIE4 submits work through a mapped user queue and doorbell, not this ioctl path. Reject the submission early with -EOPNOTSUPP when the device provides no cmd_submit op, so the shared EXEC ioctl is a clean no-op on such devices. Fixes: aac243092b70 ("accel/amdxdna: Add command execution") Cc: stable@vger.kernel.org Found by 0sec automated security-research tooling (https://0sec.ai). Assisted-by: 0sec:claude-opus-4-8 Signed-off-by: Doruk Tan Ozturk Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260713173030.87541-3-doruk@0sec.ai --- drivers/accel/amdxdna/amdxdna_ctx.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/accel/amdxdna/amdxdna_ctx.c b/drivers/accel/amdxdna/amdxdna_ctx.c index a5c8c2c4de6d..bdbd3db12a6c 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.c +++ b/drivers/accel/amdxdna/amdxdna_ctx.c @@ -590,6 +590,10 @@ int amdxdna_cmd_submit(struct amdxdna_client *client, int ret, idx; XDNA_DBG(xdna, "Command BO hdl %d, Arg BO count %d", cmd_bo_hdl, arg_bo_cnt); + + if (!xdna->dev_info->ops->cmd_submit) + return -EOPNOTSUPP; + job = kzalloc_flex(*job, bos, arg_bo_cnt); if (!job) return -ENOMEM; From e4bf6eb4c7b61db1cf24487e14e6ae8755e61e3d Mon Sep 17 00:00:00 2001 From: Aurelien Jarno Date: Tue, 23 Jun 2026 22:40:57 +0200 Subject: [PATCH 345/479] arch/riscv: vdso: remove CFI landing pad from rt_sigreturn When CONFIG_RISCV_USER_CFI is enabled, the CFI version of the vDSO, has a CFI landing pad instruction at the start of __vdso_rt_sigreturn. This breaks libgcc's unwinding code which matches on the first two instructions. Other unwinders that rely on similar instruction matching may also be affected. Since __vdso_rt_sigreturn is reached as part of signal-return handling rather than via an indirect call/jump from userspace, it does not need a CFI landing pad. Remove it and restore the instruction sequence expected by existing unwinding code. This matches what was done on arm64 in commit 9a964285572b ("arm64: vdso: Don't prefix sigreturn trampoline with a BTI C instruction") for a similar issue. Cc: stable@vger.kernel.org Fixes: 37f57bd3faea ("arch/riscv: compile vdso with landing pad and shadow stack note") Co-authored-by: Joel Stanley Signed-off-by: Aurelien Jarno Signed-off-by: Joel Stanley Link: https://patch.msgid.link/20260623204058.498120-1-aurelien@aurel32.net [pjw@kernel.org: fixed comment style] Signed-off-by: Paul Walmsley --- arch/riscv/kernel/vdso/rt_sigreturn.S | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/arch/riscv/kernel/vdso/rt_sigreturn.S b/arch/riscv/kernel/vdso/rt_sigreturn.S index e82987dc3739..d6f96b1abe40 100644 --- a/arch/riscv/kernel/vdso/rt_sigreturn.S +++ b/arch/riscv/kernel/vdso/rt_sigreturn.S @@ -7,11 +7,19 @@ #include #include +/* + * WARNING: Do NOT add a CFI landing pad at the start of this function. + * Unwinders such as libgcc identify the sigreturn trampoline by matching the + * instruction sequence. Adding a landing pad here would break unwinding from + * signal handlers. + * + * This trampoline is used only for signal return and not via an indirect + * call/jump from userspace, so adding CFI landing pad is unnecessary. + */ .text SYM_FUNC_START(__vdso_rt_sigreturn) .cfi_startproc .cfi_signal_frame - vdso_lpad li a7, __NR_rt_sigreturn ecall .cfi_endproc From 25957f7c3dac3265332d766b71233e3622f17e14 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 3 Jun 2026 21:33:09 -0700 Subject: [PATCH 346/479] powerpc/85xx: Add fsl,ifc to common device ids Add fsl,ifc to mpc85xx_common_ids so that of_platform_bus_probe creates a platform device for the IFC node even without 'simple-bus' in its compatible property. On P1010 and similar platforms the IFC node is a direct child of the root, so it must be explicitly matched to be populated. Fixes: 0bf51cc9e9e5 ("powerpc: dts: mpc85xx: remove "simple-bus" compatible from ifc node") Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260604043309.91280-1-rosenp@gmail.com --- arch/powerpc/platforms/85xx/common.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/powerpc/platforms/85xx/common.c b/arch/powerpc/platforms/85xx/common.c index 757811155587..c11deb2f50ed 100644 --- a/arch/powerpc/platforms/85xx/common.c +++ b/arch/powerpc/platforms/85xx/common.c @@ -42,6 +42,8 @@ static const struct of_device_id mpc85xx_common_ids[] __initconst = { { .compatible = "fsl,qoriq-pcie-v2.3", }, { .compatible = "fsl,qoriq-pcie-v2.2", }, { .compatible = "fsl,fman", }, + /* IFC NAND and NOR controllers */ + { .compatible = "fsl,ifc", }, {}, }; From c1c1ffa490fc33591e90852ed0d38804dd20bc36 Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Fri, 5 Jun 2026 18:13:29 +0530 Subject: [PATCH 347/479] powerpc/vtime: Initialize starttime at boot for native accounting It was observed that /proc/stat had very large value for one ore more CPUs. It was more visible after recent code simplifications around cpustats. System has 240 CPUs. cat /proc/uptime; 194.18 46500.55 cat /proc/stat cpu 5966 39 837032887 4650070 164 185 100 0 0 0 cpu0 108 0 837030890 19109 24 4 23 0 0 0 Since uptime is 194s, system time of each CPU can't be more than 19400. Sum of system time of all CPUs can't be more than 19400*240 4656000. In fact huge value is close to mftb(). Note mftb doesn't reset on powerVM when the LPAR restart. It only resets when whole system resets. The same issue exists for kexec too. This happens since starttime is not setup at init time. Once it is set then subsequent vtime_delta will return the right delta. Fix it by initializing the starttime during CPU initialization. This fixes the large times seen. cat /proc/uptime; cat /proc/stat 15.78 3694.63 cpu 6035 35 1347 369479 23 144 49 0 0 0 cpu0 19 0 38 1508 0 1 14 0 0 0 Now, system time is reported as expected. Fixes: cf9efce0ce31 ("powerpc: Account time using timebase rather than PURR") Reviewed-by: Christophe Leroy (CS GROUP) Suggested-by: Christophe Leroy (CS GROUP) Signed-off-by: Shrikanth Hegde Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260605124329.377533-1-sshegde@linux.ibm.com --- arch/powerpc/kernel/time.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/kernel/time.c b/arch/powerpc/kernel/time.c index 3460d1a5a97c..11145c40183d 100644 --- a/arch/powerpc/kernel/time.c +++ b/arch/powerpc/kernel/time.c @@ -377,7 +377,6 @@ void vtime_task_switch(struct task_struct *prev) } } -#ifdef CONFIG_NO_HZ_COMMON /** * vtime_reset - Fast forward vtime entry clocks * @@ -394,6 +393,7 @@ void vtime_reset(void) #endif } +#ifdef CONFIG_NO_HZ_COMMON /** * vtime_dyntick_start - Inform vtime about entry to idle-dynticks * @@ -933,6 +933,7 @@ static void __init set_decrementer_max(void) static void __init init_decrementer_clockevent(void) { register_decrementer_clockevent(smp_processor_id()); + vtime_reset(); } void secondary_cpu_time_init(void) @@ -948,6 +949,7 @@ void secondary_cpu_time_init(void) /* FIME: Should make unrelated change to move snapshot_timebase * call here ! */ register_decrementer_clockevent(smp_processor_id()); + vtime_reset(); } /* From d610d3ab18197d87618da11ec5fe8b3cebf32208 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Mon, 15 Jun 2026 16:37:26 -0700 Subject: [PATCH 348/479] powerpc/uaccess: correct check for CONFIG_PPC_E500 in mask_user_address() mask_user_address() incorrectly checks for CONFIG_E500 instead of CONFIG_PPC_E500, causing mask_user_address_isel() to not be used on E500 hardware. Fix the check to use the correct name. Fixes: 861574d51bbd ("powerpc/uaccess: Implement masked user access") Cc: stable@vger.kernel.org # 7.0+ Signed-off-by: Ethan Nelson-Moore Fixes: 861574d51bbd ("powerpc/uaccess: Implement masked user access") Reviewed-by: Christophe Leroy (CS GROUP) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260615233729.29386-1-enelsonmoore@gmail.com --- arch/powerpc/include/asm/uaccess.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/include/asm/uaccess.h b/arch/powerpc/include/asm/uaccess.h index 7b8c56962c31..49039074b33f 100644 --- a/arch/powerpc/include/asm/uaccess.h +++ b/arch/powerpc/include/asm/uaccess.h @@ -537,7 +537,7 @@ static inline void __user *mask_user_address(const void __user *ptr) if (IS_ENABLED(CONFIG_PPC64)) return mask_user_address_simple(ptr); - if (IS_ENABLED(CONFIG_E500)) + if (IS_ENABLED(CONFIG_PPC_E500)) return mask_user_address_isel(ptr); if (TASK_SIZE <= UL(SZ_2G) && border >= UL(SZ_2G)) return mask_user_address_simple(ptr); From bd83c98b988d2c560531084e296dbfb530aff829 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 14 Jun 2026 16:23:56 +0200 Subject: [PATCH 349/479] powerpc/pseries: fix memory leak on krealloc failure in papr_init When krealloc() fails, free the original esi_buf before returning to avoid a memory leak. Fixes: 3c14b73454cf ("powerpc/pseries: Interface to represent PAPR firmware attributes") Cc: stable@vger.kernel.org Signed-off-by: Thorsten Blum Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260614142356.658212-2-thorsten.blum@linux.dev --- arch/powerpc/platforms/pseries/papr_platform_attributes.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/arch/powerpc/platforms/pseries/papr_platform_attributes.c b/arch/powerpc/platforms/pseries/papr_platform_attributes.c index c6159870de0e..9c3758aa54c6 100644 --- a/arch/powerpc/platforms/pseries/papr_platform_attributes.c +++ b/arch/powerpc/platforms/pseries/papr_platform_attributes.c @@ -271,11 +271,9 @@ static int __init papr_init(void) esi_buf_size = ESI_HDR_SIZE + (CURR_MAX_ESI_ATTRS * max_esi_attrs); temp_esi_buf = krealloc(esi_buf, esi_buf_size, GFP_KERNEL); - if (temp_esi_buf) - esi_buf = temp_esi_buf; - else - return -ENOMEM; - + if (!temp_esi_buf) + goto out_free_esi_buf; + esi_buf = temp_esi_buf; goto retry; } From e4de1b9cb3b5c981e4fe9bca253a7fb9161f5acd Mon Sep 17 00:00:00 2001 From: Amit Machhiwal Date: Sun, 14 Jun 2026 23:04:37 +0530 Subject: [PATCH 350/479] powerpc/dt_cpu_ftrs: Set CPU_FTR_P11_PVR for Power11 and later processors When using device tree CPU features (dt-cpu-ftrs), the kernel bypasses the traditional cputable-based CPU identification and instead derives CPU features from the device tree's "ibm,powerpc-cpu-features" node provided by firmware. However, CPU_FTR_P11_PVR is a kernel-internal feature flag used to identify Power11 and later processors, and is not represented in the device tree's ISA feature set. While ISA v3.1 support (indicated by CPU_FTR_ARCH_31) is present on both Power10 and Power11, the CPU_FTR_P11_PVR flag is specifically needed by code that must distinguish between Power10 and Power11 processors. Without this flag set, code that checks for Power11 using cpu_has_feature(CPU_FTR_P11_PVR) will incorrectly return false on Power11+ systems using dt-cpu-ftrs, leading to incorrect behavior. This issue manifests specifically in powernv environments (bare-metal or QEMU TCG with powernv machine type), where skiboot/OPAL firmware provides the "ibm,powerpc-cpu-features" node, causing the kernel to use dt-cpu-ftrs. The issue does not affect pseries guests, where SLOF firmware does not provide this node, causing the kernel to fall back to the traditional cputable path (identify_cpu) which correctly sets CPU_FTR_P11_PVR during PVR-based CPU identification. In powernv TCG guests, the missing flag causes KVM code to trigger warnings when attempting to create KVM guests, as cpu_features shows 0x000c00eb8f4fb187 (missing bit 53) instead of the correct 0x002c00eb8f4fb187 (with bit 53 set). Fix this by setting CPU_FTR_P11_PVR for all processors with PVR >= PVR_POWER11 when ISA v3.1 support is detected in cpufeatures_setup_start(). This approach ensures forward compatibility with future processor generations. Fixes: 96e266e3bcd6 ("KVM: PPC: Book3S HV: Add Power11 capability support for Nested PAPR guests") Cc: stable@vger.kernel.org # v6.13+ Signed-off-by: Amit Machhiwal Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Christophe Leroy (CS GROUP) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260614173437.26352-1-amachhiw@linux.ibm.com --- arch/powerpc/kernel/dt_cpu_ftrs.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/powerpc/kernel/dt_cpu_ftrs.c b/arch/powerpc/kernel/dt_cpu_ftrs.c index 3af6c06af02f..e5853daa6a48 100644 --- a/arch/powerpc/kernel/dt_cpu_ftrs.c +++ b/arch/powerpc/kernel/dt_cpu_ftrs.c @@ -704,6 +704,15 @@ static void __init cpufeatures_setup_start(u32 isa) if (isa >= ISA_V3_1) { cur_cpu_spec->cpu_features |= CPU_FTR_ARCH_31; cur_cpu_spec->cpu_user_features2 |= PPC_FEATURE2_ARCH_3_1; + + /* + * CPU_FTR_P11_PVR is a kernel-internal flag to identify + * Power11 and later processors. While ISA v3.1 is supported + * by Power10+, this flag specifically indicates Power11+ + * for code that needs to distinguish between P10 and P11. + */ + if (PVR_VER(mfspr(SPRN_PVR)) >= PVR_POWER11) + cur_cpu_spec->cpu_features |= CPU_FTR_P11_PVR; } } From a2c02aa0c6ca3ec9fab6f1c99912a440c7b8bfdb Mon Sep 17 00:00:00 2001 From: "Christophe Leroy (CS GROUP)" Date: Fri, 19 Jun 2026 14:08:28 +0200 Subject: [PATCH 351/479] powerpc: Remove dead non-preemption code Since commit 7dadeaa6e851 ("sched: Further restrict the preemption modes"), powerpc always has CONFIG_PREEMPTION because only CONFIG_PREEMPT and CONFIG_PREEMPT_LAZY are possible, even in dynamic preemption mode (see sched_dynamic_mode). As a consequence, need_irq_preemption() is always true and can be removed. And because commit bee25f97ad24 ("powerpc: Enable GENERIC_ENTRY feature") includes linux/irq-entry-common.h which already declares sk_dynamic_irqentry_exit_cond_resched static key, asm/preempt.h becauses useless and can be removed. Signed-off-by: Christophe Leroy (CS GROUP) Reviewed-by: Shrikanth Hegde Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/2bf10a0afffefb6aca44bf2f864cc17471a80e31.1781870889.git.chleroy@kernel.org --- arch/powerpc/include/asm/preempt.h | 16 ---------------- arch/powerpc/lib/vmx-helper.c | 2 +- 2 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 arch/powerpc/include/asm/preempt.h diff --git a/arch/powerpc/include/asm/preempt.h b/arch/powerpc/include/asm/preempt.h deleted file mode 100644 index 000e2b9681f3..000000000000 --- a/arch/powerpc/include/asm/preempt.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __ASM_POWERPC_PREEMPT_H -#define __ASM_POWERPC_PREEMPT_H - -#include - -#if defined(CONFIG_PREEMPT_DYNAMIC) -#include -DECLARE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched); -#define need_irq_preemption() \ - (static_branch_unlikely(&sk_dynamic_irqentry_exit_cond_resched)) -#else -#define need_irq_preemption() (IS_ENABLED(CONFIG_PREEMPTION)) -#endif - -#endif /* __ASM_POWERPC_PREEMPT_H */ diff --git a/arch/powerpc/lib/vmx-helper.c b/arch/powerpc/lib/vmx-helper.c index 57e897b60db8..cc9fb72cb4eb 100644 --- a/arch/powerpc/lib/vmx-helper.c +++ b/arch/powerpc/lib/vmx-helper.c @@ -46,7 +46,7 @@ int exit_vmx_usercopy(void) * set and we are preemptible. The hack here is to schedule a * decrementer to fire here and reschedule for us if necessary. */ - if (need_irq_preemption() && need_resched()) + if (need_resched()) set_dec(1); return 0; } From b8de511f0e80a2ab9d23df73d147423968ebf102 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 14 Jul 2026 14:21:20 +0200 Subject: [PATCH 352/479] smb/client: Use EXPORT_SYMBOL_IF_KUNIT() to export symbols Replace EXPORT_SYMBOL_FOR_MODULES() with EXPORT_SYMBOL_IF_KUNIT() to mark the symbols as visible only if CONFIG_KUNIT is enabled. Kunit test should import the namespace EXPORTED_FOR_KUNIT_TESTING to use these marked symbols. This is the standard way for all KUnit tests. Signed-off-by: Andy Shevchenko Reviewed-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/smb1maperror.c | 24 ++++++++++++------------ fs/smb/client/smb1maperror_test.c | 1 + 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/fs/smb/client/smb1maperror.c b/fs/smb/client/smb1maperror.c index 74530088d17d..ab3d09613c91 100644 --- a/fs/smb/client/smb1maperror.c +++ b/fs/smb/client/smb1maperror.c @@ -10,6 +10,9 @@ */ #include + +#include + #include "cifsproto.h" #include "smb1proto.h" #include "smberr.h" @@ -239,48 +242,45 @@ int __init smb1_init_maperror(void) } #if IS_ENABLED(CONFIG_SMB1_KUNIT_TESTS) -#define EXPORT_SYMBOL_FOR_SMB_TEST(sym) \ - EXPORT_SYMBOL_FOR_MODULES(sym, "smb1maperror_test") - const struct ntstatus_to_dos_err * search_ntstatus_to_dos_map_test(__u32 ntstatus) { return search_ntstatus_to_dos_map(ntstatus); } -EXPORT_SYMBOL_FOR_SMB_TEST(search_ntstatus_to_dos_map_test); +EXPORT_SYMBOL_IF_KUNIT(search_ntstatus_to_dos_map_test); const struct ntstatus_to_dos_err * ntstatus_to_dos_map_test = ntstatus_to_dos_map; -EXPORT_SYMBOL_FOR_SMB_TEST(ntstatus_to_dos_map_test); +EXPORT_SYMBOL_IF_KUNIT(ntstatus_to_dos_map_test); unsigned int ntstatus_to_dos_num = ARRAY_SIZE(ntstatus_to_dos_map); -EXPORT_SYMBOL_FOR_SMB_TEST(ntstatus_to_dos_num); +EXPORT_SYMBOL_IF_KUNIT(ntstatus_to_dos_num); const struct smb_to_posix_error * search_mapping_table_ERRDOS_test(__u16 smb_err) { return search_mapping_table_ERRDOS(smb_err); } -EXPORT_SYMBOL_FOR_SMB_TEST(search_mapping_table_ERRDOS_test); +EXPORT_SYMBOL_IF_KUNIT(search_mapping_table_ERRDOS_test); const struct smb_to_posix_error * mapping_table_ERRDOS_test = mapping_table_ERRDOS; -EXPORT_SYMBOL_FOR_SMB_TEST(mapping_table_ERRDOS_test); +EXPORT_SYMBOL_IF_KUNIT(mapping_table_ERRDOS_test); unsigned int mapping_table_ERRDOS_num = ARRAY_SIZE(mapping_table_ERRDOS); -EXPORT_SYMBOL_FOR_SMB_TEST(mapping_table_ERRDOS_num); +EXPORT_SYMBOL_IF_KUNIT(mapping_table_ERRDOS_num); const struct smb_to_posix_error * search_mapping_table_ERRSRV_test(__u16 smb_err) { return search_mapping_table_ERRSRV(smb_err); } -EXPORT_SYMBOL_FOR_SMB_TEST(search_mapping_table_ERRSRV_test); +EXPORT_SYMBOL_IF_KUNIT(search_mapping_table_ERRSRV_test); const struct smb_to_posix_error * mapping_table_ERRSRV_test = mapping_table_ERRSRV; -EXPORT_SYMBOL_FOR_SMB_TEST(mapping_table_ERRSRV_test); +EXPORT_SYMBOL_IF_KUNIT(mapping_table_ERRSRV_test); unsigned int mapping_table_ERRSRV_num = ARRAY_SIZE(mapping_table_ERRSRV); -EXPORT_SYMBOL_FOR_SMB_TEST(mapping_table_ERRSRV_num); +EXPORT_SYMBOL_IF_KUNIT(mapping_table_ERRSRV_num); #endif diff --git a/fs/smb/client/smb1maperror_test.c b/fs/smb/client/smb1maperror_test.c index 2caaf11228ef..903c46f71291 100644 --- a/fs/smb/client/smb1maperror_test.c +++ b/fs/smb/client/smb1maperror_test.c @@ -75,3 +75,4 @@ kunit_test_suite(maperror_suite); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("KUnit tests of SMB1 maperror"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); From e2e08effef2cd5d5d27b44d6239718ada614eb62 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 14 Jul 2026 15:53:33 +0200 Subject: [PATCH 353/479] smb/client: Use EXPORT_SYMBOL_IF_KUNIT() to export symbols in SMB2 Replace EXPORT_SYMBOL_FOR_MODULES() with EXPORT_SYMBOL_IF_KUNIT() to mark the symbols as visible only if CONFIG_KUNIT is enabled. Kunit test should import the namespace EXPORTED_FOR_KUNIT_TESTING to use these marked symbols. This is the standard way for all KUnit tests. Signed-off-by: Andy Shevchenko Reviewed-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/smb2maperror.c | 12 ++++++------ fs/smb/client/smb2maperror_test.c | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/smb2maperror.c b/fs/smb/client/smb2maperror.c index 9ed21f7b618c..d86f2460d0e5 100644 --- a/fs/smb/client/smb2maperror.c +++ b/fs/smb/client/smb2maperror.c @@ -8,6 +8,9 @@ * */ #include + +#include + #include "cifsproto.h" #include "cifs_debug.h" #include "smb2proto.h" @@ -109,18 +112,15 @@ int __init smb2_init_maperror(void) } #if IS_ENABLED(CONFIG_SMB_KUNIT_TESTS) -#define EXPORT_SYMBOL_FOR_SMB_TEST(sym) \ - EXPORT_SYMBOL_FOR_MODULES(sym, "smb2maperror_test") - const struct status_to_posix_error *smb2_get_err_map_test(__u32 smb2_status) { return smb2_get_err_map(smb2_status); } -EXPORT_SYMBOL_FOR_SMB_TEST(smb2_get_err_map_test); +EXPORT_SYMBOL_IF_KUNIT(smb2_get_err_map_test); const struct status_to_posix_error *smb2_error_map_table_test = smb2_error_map_table; -EXPORT_SYMBOL_FOR_SMB_TEST(smb2_error_map_table_test); +EXPORT_SYMBOL_IF_KUNIT(smb2_error_map_table_test); unsigned int smb2_error_map_num = ARRAY_SIZE(smb2_error_map_table); -EXPORT_SYMBOL_FOR_SMB_TEST(smb2_error_map_num); +EXPORT_SYMBOL_IF_KUNIT(smb2_error_map_num); #endif diff --git a/fs/smb/client/smb2maperror_test.c b/fs/smb/client/smb2maperror_test.c index 0f8a44a5ed3c..44dc5e899cad 100644 --- a/fs/smb/client/smb2maperror_test.c +++ b/fs/smb/client/smb2maperror_test.c @@ -47,3 +47,4 @@ kunit_test_suite(maperror_suite); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("KUnit tests of SMB2 maperror"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); From d7d2adcd022baade5cab65ca492ce63421ce3a6e Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Tue, 14 Jul 2026 17:38:34 -0500 Subject: [PATCH 354/479] smb/client: flush dirty data before punching a hole Punching a hole after a large buffered write may leave the range reported as data. Reproduce it with: xfs_io -f \ -c "pwrite -b 3m -S 0x61 0 3m" \ -c "fpunch 1m 1m" \ -c "seek -h 0" \ -c "seek -d 1m" \ /mnt/test/repro Punching 1 MiB at offset 1 MiB should produce: 0 1 MiB 2 MiB 3 MiB | DATA | HOLE | DATA | EOF Instead, the entire file is reported as data. SEEK_HOLE(0) returns EOF, and SEEK_DATA(1M) returns 1M. This happens because a dirty folio spanning the punched range can be written back after the punch and refill the hole. Fix this by flushing and waiting for dirty data in the punched range before invalidating the page cache and issuing FSCTL_SET_ZERO_DATA. The xfstests generic/539 pass against Samba/ksmbd with this change. Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Signed-off-by: Steve French --- fs/smb/client/smb2ops.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 05f2ab6d345a..cbd51a08e97e 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3519,6 +3519,15 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon, goto out; filemap_invalidate_lock(inode->i_mapping); + /* + * Flush dirty data first, otherwise a dirty folio spanning the punched + * range may be written back after the ioctl and refill the hole. + */ + rc = filemap_write_and_wait_range(inode->i_mapping, offset, + offset + len - 1); + if (rc < 0) + goto unlock; + /* * We implement the punch hole through ioctl, so we need remove the page * caches first, otherwise the data may be inconsistent with the server. From 5b7b3b6595ee77d01c7463757baed114786094dd Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Thu, 2 Jul 2026 14:48:15 -0700 Subject: [PATCH 355/479] drm/ttm: Account for NULL and handle pages in ttm_pool_backup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pages in ttm_pool_backup can be NULL or backup handles (ttm_backup_page_ptr_is_handle()), neither of which can be passed to set_pages_array_wb() or freed. Add a dedicated WB pass before the dma/purge loop that walks allocations using the same i += num_pages stride, skipping NULL and handle entries, and calls set_pages_array_wb() once per contiguous run of real pages. Apply the same NULL/handle guard to the dma/purge loop. Fixes the following oops: Oops: general protection fault, kernel NULL pointer dereference 0x0: 0000 [#1] SMP NOPTI RIP: 0010:__cpa_process_fault+0xf8/0x770 RSP: 0018:ffffc90000a87718 EFLAGS: 00010287 RAX: 0000000000000000 RBX: ffffc90000a87868 RCX: 0000000000000000 RDX: 0000000000001000 RSI: 0005088000000000 RDI: ffffffff827c5f34 RBP: 0005088000000000 R08: ffffc90000a877cb R09: ffffc90000a877d0 R10: 0000000000000000 R11: 000000000000001b R12: 000ffffffffff000 R13: ffffc90000a87868 R14: ffffc90000a87868 R15: ffff88815b882ae0 FS: 0000000000000000(0000) GS:ffff8884ec840000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f930b844000 CR3: 000000000262e003 CR4: 0000000008f70ef0 PKRU: 55555554 Call Trace: __change_page_attr_set_clr+0x989/0xe90 ? __purge_vmap_area_lazy+0x6c/0x3a0 ? _vm_unmap_aliases+0x250/0x2a0 set_pages_array_wb+0x7f/0x120 ttm_pool_backup+0x4c9/0x5b0 [ttm] ? dma_resv_wait_timeout+0x3b/0xf0 ttm_tt_backup+0x32/0x60 [ttm] ttm_bo_shrink+0x66/0x110 [ttm] xe_bo_shrink_purge+0x12b/0x1b0 [xe] xe_bo_shrink+0xbb/0x270 [xe] __xe_shrinker_walk+0xf7/0x160 [xe] xe_shrinker_walk+0x9d/0xc0 [xe] xe_shrinker_scan+0x11f/0x210 [xe] do_shrink_slab+0x13b/0x270 shrink_slab+0xf1/0x400 shrink_node+0x352/0x8a0 balance_pgdat+0x32c/0x700 kswapd+0x205/0x2f0 ? __pfx_autoremove_wake_function+0x10/0x10 ? __pfx_kswapd+0x10/0x10 kthread+0xd1/0x110 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x1b1/0x200 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Cc: Christian Koenig Cc: Huang Rui Cc: Matthew Auld Cc: Matthew Brost Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: Thomas Zimmermann Cc: David Airlie Cc: Simona Vetter Cc: Thomas Hellström Cc: dri-devel@lists.freedesktop.org Cc: linux-kernel@vger.kernel.org Cc: stable@vger.kernel.org Fixes: b63d715b8090 ("drm/ttm/pool, drm/ttm/tt: Provide a helper to shrink pages") Cc: stable@vger.kernel.org Assisted-by: GitHub_Copilot:claude-opus-4.8 Signed-off-by: Matthew Brost Reviewed-by: Thomas Hellström Link: https://patch.msgid.link/20260702214815.4009271-1-matthew.brost@intel.com --- drivers/gpu/drm/ttm/ttm_pool.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/ttm/ttm_pool.c b/drivers/gpu/drm/ttm/ttm_pool.c index 278bbe7a11ad..46983e7de7a3 100644 --- a/drivers/gpu/drm/ttm/ttm_pool.c +++ b/drivers/gpu/drm/ttm/ttm_pool.c @@ -1051,9 +1051,31 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt, return -EBUSY; #ifdef CONFIG_X86 - /* Anything returned to the system needs to be cached. */ - if (tt->caching != ttm_cached) - set_pages_array_wb(tt->pages, tt->num_pages); + /* Anything returned to the system needs to be cached. Walk allocations + * skipping NULL pages and issue set_pages_array_wb() per contiguous run. + */ + if (tt->caching != ttm_cached) { + pgoff_t run_start = 0, run_count = 0; + + for (i = 0; i < tt->num_pages; i += num_pages) { + page = tt->pages[i]; + if (unlikely(!page || ttm_backup_page_ptr_is_handle(page))) { + if (run_count) { + set_pages_array_wb(&tt->pages[run_start], + run_count); + run_count = 0; + } + num_pages = 1; + continue; + } + num_pages = 1UL << ttm_pool_page_order(pool, page); + if (!run_count) + run_start = i; + run_count += num_pages; + } + if (run_count) + set_pages_array_wb(&tt->pages[run_start], run_count); + } #endif if (tt->dma_address || flags->purge) { @@ -1061,7 +1083,7 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt, unsigned int order; page = tt->pages[i]; - if (unlikely(!page)) { + if (unlikely(!page || ttm_backup_page_ptr_is_handle(page))) { num_pages = 1; continue; } @@ -1104,6 +1126,10 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt, if (unlikely(!page)) continue; + /* Already-handled entry from a previous attempt. */ + if (unlikely(ttm_backup_page_ptr_is_handle(page))) + continue; + ttm_pool_split_for_swap(pool, page); shandle = ttm_backup_backup_page(backup, page, flags->writeback, i, From fe3ff273767ef22fe8a7cb3816f264927c190e50 Mon Sep 17 00:00:00 2001 From: Michal Luczaj Date: Tue, 7 Jul 2026 06:23:56 +0200 Subject: [PATCH 356/479] selftests/bpf: Ensure UDP sockets are bound Update sockmap_basic tests to bind sockets before they are used. This accommodates the recent change in sockmap that rejects unbound UDP sockets. Signed-off-by: Michal Luczaj Reviewed-by: Kuniyuki Iwashima Reviewed-by: Jakub Sitnicki Link: https://lore.kernel.org/bpf/20260707-sockmap-lookup-udp-leak-v4-1-f878346f27ab@rbox.co Signed-off-by: Kumar Kartikeya Dwivedi --- tools/testing/selftests/bpf/prog_tests/sockmap_basic.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c index cb3229711f93..2d22a9058a8e 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c @@ -853,7 +853,7 @@ static void test_sockmap_many_socket(void) return; } - udp = xsocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); + udp = socket_loopback(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK); if (udp < 0) { close(dgram); close(tcp); @@ -922,7 +922,7 @@ static void test_sockmap_many_maps(void) return; } - udp = xsocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); + udp = socket_loopback(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK); if (udp < 0) { close(dgram); close(tcp); @@ -993,7 +993,7 @@ static void test_sockmap_same_sock(void) return; } - udp = xsocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); + udp = socket_loopback(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK); if (udp < 0) { close(dgram); close(tcp); From 66efd3368ae10d05e08fbe6425b50fdec7186ac7 Mon Sep 17 00:00:00 2001 From: Michal Luczaj Date: Tue, 7 Jul 2026 06:23:57 +0200 Subject: [PATCH 357/479] bpf, sockmap: Reject unhashed UDP sockets on sockmap update UDP sockets get SOCK_RCU_FREE set when (auto-)bound. This means sk_is_refcounted(unbound) = true, while sk_is_refcounted(bound) = false. Because sockmap accepts unbound UDP sockets, a BPF program can increment a socket's refcount via lookup. If the socket is subsequently bound, the transition from unbound to bound causes bpf_sk_release() to skip the decrement of the refcount, causing a memory leak. unreferenced object 0xffff88810bc2eb40 (size 1984): comm "test_progs", pid 2451, jiffies 4295320596 hex dump (first 32 bytes): 7f 00 00 01 7f 00 00 01 d2 04 1b b7 04 d2 00 00 ................ 02 00 01 40 00 00 00 00 00 00 00 00 00 00 00 00 ...@............ backtrace (crc bdee079d): kmem_cache_alloc_noprof+0x557/0x660 sk_prot_alloc+0x69/0x240 sk_alloc+0x30/0x460 inet_create+0x2ce/0xf80 __sock_create+0x25b/0x5c0 __sys_socket+0x119/0x1d0 __x64_sys_socket+0x72/0xd0 do_syscall_64+0xa1/0x5f0 entry_SYSCALL_64_after_hwframe+0x76/0x7e Instead of special-casing for refcounted sockets, reject unhashed UDP sockets during sockmap updates, as there is no benefit to supporting those. This effectively reverts the commit under Fixes, with two exceptions: 1. sock_map_sk_state_allowed() maintains a fall-through `return true`. 2. In the spirit of commit b8b8315e39ff ("bpf, sockmap: Remove unhash handler for BPF sockmap usage"), the proto::unhash BPF handler is not reintroduced. Historical note: this issue is related to commit 67312adc96b5 ("bpf: reject unhashed sockets in bpf_sk_assign"). Fixes: 0c48eefae712 ("sock_map: Lift socket state restriction for datagram sockets") Suggested-by: Kuniyuki Iwashima Signed-off-by: Michal Luczaj Reviewed-by: Kuniyuki Iwashima Reviewed-by: Jakub Sitnicki Reviewed-by: John Fastabend Link: https://lore.kernel.org/bpf/20260707-sockmap-lookup-udp-leak-v4-2-f878346f27ab@rbox.co Signed-off-by: Kumar Kartikeya Dwivedi --- net/core/sock_map.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/core/sock_map.c b/net/core/sock_map.c index c60ba6d292f9..9efbd8ca7db8 100644 --- a/net/core/sock_map.c +++ b/net/core/sock_map.c @@ -542,6 +542,8 @@ static bool sock_map_sk_state_allowed(const struct sock *sk) { if (sk_is_tcp(sk)) return (1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_LISTEN); + if (sk_is_udp(sk)) + return sk_hashed(sk); if (sk_is_stream_unix(sk)) return (1 << READ_ONCE(sk->sk_state)) & TCPF_ESTABLISHED; if (sk_is_vsock(sk) && From 30581eda4a07ff15db623612cac578e81869e96f Mon Sep 17 00:00:00 2001 From: Michal Luczaj Date: Tue, 7 Jul 2026 06:23:58 +0200 Subject: [PATCH 358/479] selftests/bpf: Adapt sockmap update error handling Update sockmap_listen to accommodate the recent change in sockmap that rejects unbound UDP sockets. TCP: Reject unbound and bound (unless established or listening). UDP: Accept only bound sockets. While at it, migrate to ASSERT_* and enforce reverse xmas tree. Signed-off-by: Michal Luczaj Reviewed-by: Kuniyuki Iwashima Reviewed-by: Jakub Sitnicki Link: https://lore.kernel.org/bpf/20260707-sockmap-lookup-udp-leak-v4-3-f878346f27ab@rbox.co Signed-off-by: Kumar Kartikeya Dwivedi --- .../selftests/bpf/prog_tests/sockmap_listen.c | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c b/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c index cc0c68bab907..1c96a3cf4b97 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c @@ -53,8 +53,8 @@ static void test_insert_opened(struct test_sockmap_listen *skel __always_unused, int family, int sotype, int mapfd) { u32 key = 0; - u64 value; int err, s; + u64 value; s = xsocket(family, sotype, 0); if (s == -1) @@ -63,11 +63,8 @@ static void test_insert_opened(struct test_sockmap_listen *skel __always_unused, errno = 0; value = s; err = bpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); - if (sotype == SOCK_STREAM) { - if (!err || errno != EOPNOTSUPP) - FAIL_ERRNO("map_update: expected EOPNOTSUPP"); - } else if (err) - FAIL_ERRNO("map_update: expected success"); + ASSERT_ERR(err, "map_update"); + ASSERT_EQ(errno, EOPNOTSUPP, "errno"); xclose(s); } @@ -77,8 +74,8 @@ static void test_insert_bound(struct test_sockmap_listen *skel __always_unused, struct sockaddr_storage addr; socklen_t len = 0; u32 key = 0; - u64 value; int err, s; + u64 value; init_addr_loopback(family, &addr, &len); @@ -93,8 +90,12 @@ static void test_insert_bound(struct test_sockmap_listen *skel __always_unused, errno = 0; value = s; err = bpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); - if (!err || errno != EOPNOTSUPP) - FAIL_ERRNO("map_update: expected EOPNOTSUPP"); + if (sotype == SOCK_STREAM) { + ASSERT_ERR(err, "map_update"); + ASSERT_EQ(errno, EOPNOTSUPP, "errno"); + } else { + ASSERT_OK(err, "map_update"); + } close: xclose(s); } @@ -1289,7 +1290,7 @@ static void test_ops(struct test_sockmap_listen *skel, struct bpf_map *map, /* insert */ TEST(test_insert_invalid), TEST(test_insert_opened), - TEST(test_insert_bound, SOCK_STREAM), + TEST(test_insert_bound), TEST(test_insert), /* delete */ TEST(test_delete_after_insert), From 203b06932777b9ad5085319389dea566f5c2ca63 Mon Sep 17 00:00:00 2001 From: Michal Luczaj Date: Tue, 7 Jul 2026 06:23:59 +0200 Subject: [PATCH 359/479] selftests/bpf: Fail unbound UDP on sockmap update sockmap now rejects unbound UDP sockets. Adjust test_maps. While at it, check socket()'s return value. This effectively reverts commit c39aa2159974 ("bpf, selftests: Fix test_maps now that sockmap supports UDP"). Signed-off-by: Michal Luczaj Reviewed-by: Kuniyuki Iwashima Reviewed-by: Jakub Sitnicki Link: https://lore.kernel.org/bpf/20260707-sockmap-lookup-udp-leak-v4-4-f878346f27ab@rbox.co Signed-off-by: Kumar Kartikeya Dwivedi --- tools/testing/selftests/bpf/test_maps.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/bpf/test_maps.c b/tools/testing/selftests/bpf/test_maps.c index c32da7bd8be2..6a2641ee7897 100644 --- a/tools/testing/selftests/bpf/test_maps.c +++ b/tools/testing/selftests/bpf/test_maps.c @@ -759,16 +759,15 @@ static void test_sockmap(unsigned int tasks, void *data) goto out_sockmap; } - /* Test update with unsupported UDP socket */ + /* Test update with unsupported unbound UDP socket */ udp = socket(AF_INET, SOCK_DGRAM, 0); - i = 0; - err = bpf_map_update_elem(fd, &i, &udp, BPF_ANY); - if (err) { - printf("Failed socket update SOCK_DGRAM '%i:%i'\n", - i, udp); + CHECK(udp < 0, "socket(AF_INET, SOCK_DGRAM)", "errno:%d\n", errno); + err = bpf_map_update_elem(fd, &(int){0}, &udp, BPF_ANY); + close(udp); + if (!err) { + printf("Unexpectedly succeeded unbound UDP update '0:%i'\n", udp); goto out_sockmap; } - close(udp); /* Test update without programs */ for (i = 0; i < 6; i++) { From 899160e2774d9952e9f2770b38f701ff1906c0b2 Mon Sep 17 00:00:00 2001 From: Luke Wang Date: Wed, 15 Jul 2026 15:18:11 +0800 Subject: [PATCH 360/479] mmc: sdhci-esdhc-imx: remove unnecessary mmc_card_wake_sdio_irq check for tuning save/restore The tuning save/restore during system PM is conditioned on mmc_card_wake_sdio_irq(), but this check is unrelated to whether tuning values need to be preserved. The actual requirement is that the card keeps power during suspend and the controller is a uSDHC. SDIO devices using out-of-band GPIO wakeup maintain power during suspend but do not set the SDIO IRQ wake flag. In this case the tuning delay values are not saved/restored. Remove the unnecessary mmc_card_wake_sdio_irq() condition from both the suspend save and resume restore paths. Fixes: c63d25cdc59a ("mmc: sdhci-esdhc-imx: Save tuning value when card stays powered in suspend") Acked-by: Adrian Hunter Reviewed-by: Frank Li Reviewed-by: Haibo Chen Signed-off-by: Luke Wang Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc-imx.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 18ecddd6df6f..6526d65538de 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -2064,8 +2064,7 @@ static int sdhci_esdhc_suspend(struct device *dev) * to save the tuning delay value just in case the usdhc * lost power during system PM. */ - if (mmc_card_keep_power(host->mmc) && mmc_card_wake_sdio_irq(host->mmc) && - esdhc_is_usdhc(imx_data)) + if (mmc_card_keep_power(host->mmc) && esdhc_is_usdhc(imx_data)) sdhc_esdhc_tuning_save(host); if (device_may_wakeup(dev)) { @@ -2124,8 +2123,7 @@ static int sdhci_esdhc_resume(struct device *dev) * restore the saved tuning delay value for the device which keep * power during system PM. */ - if (mmc_card_keep_power(host->mmc) && mmc_card_wake_sdio_irq(host->mmc) && - esdhc_is_usdhc(imx_data)) + if (mmc_card_keep_power(host->mmc) && esdhc_is_usdhc(imx_data)) sdhc_esdhc_tuning_restore(host); pm_runtime_put_autosuspend(dev); From 2439becd91bad6883b135044f85f83a0538b96a6 Mon Sep 17 00:00:00 2001 From: Luke Wang Date: Wed, 15 Jul 2026 15:18:12 +0800 Subject: [PATCH 361/479] mmc: sdhci-esdhc-imx: restore DLL override for DDR modes on resume sdhci_esdhc_imx_hwinit() unconditionally clears ESDHC_DLL_CTRL by writing zero. For SDIO devices that keep power during system suspend and operate in DDR mode, the card remains in DDR timing while the host DLL override configuration is lost. Extract the DLL override setup from esdhc_set_uhs_signaling() into a helper esdhc_set_dll_override(), and call it on the resume path when the card kept power and is using a DDR timing mode. Fixes: 676a83855614 ("mmc: host: sdhci-esdhc-imx: refactor the system PM logic") Acked-by: Adrian Hunter Reviewed-by: Frank Li Reviewed-by: Haibo Chen Signed-off-by: Luke Wang Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc-imx.c | 39 +++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 6526d65538de..7230d70e02ae 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -1349,6 +1349,23 @@ static int esdhc_change_pinstate(struct sdhci_host *host, return pinctrl_select_state(imx_data->pinctrl, pinctrl); } +static void esdhc_set_dll_override(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host); + struct esdhc_platform_data *boarddata = &imx_data->boarddata; + u32 v; + + if (!boarddata->delay_line) + return; + + v = boarddata->delay_line << ESDHC_DLL_OVERRIDE_VAL_SHIFT | + (1 << ESDHC_DLL_OVERRIDE_EN_SHIFT); + if (is_imx53_esdhc(imx_data)) + v <<= 1; + writel(v, host->ioaddr + ESDHC_DLL_CTRL); +} + /* * For HS400 eMMC, there is a data_strobe line. This signal is generated * by the device and used for data output and CRC status response output @@ -1404,7 +1421,6 @@ static void esdhc_set_uhs_signaling(struct sdhci_host *host, unsigned timing) u32 m; struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host); - struct esdhc_platform_data *boarddata = &imx_data->boarddata; /* disable ddr mode and disable HS400 mode */ m = readl(host->ioaddr + ESDHC_MIX_CTRL); @@ -1425,15 +1441,7 @@ static void esdhc_set_uhs_signaling(struct sdhci_host *host, unsigned timing) m |= ESDHC_MIX_CTRL_DDREN; writel(m, host->ioaddr + ESDHC_MIX_CTRL); imx_data->is_ddr = 1; - if (boarddata->delay_line) { - u32 v; - v = boarddata->delay_line << - ESDHC_DLL_OVERRIDE_VAL_SHIFT | - (1 << ESDHC_DLL_OVERRIDE_EN_SHIFT); - if (is_imx53_esdhc(imx_data)) - v <<= 1; - writel(v, host->ioaddr + ESDHC_DLL_CTRL); - } + esdhc_set_dll_override(host); break; case MMC_TIMING_MMC_HS400: m |= ESDHC_MIX_CTRL_DDREN | ESDHC_MIX_CTRL_HS400_EN; @@ -2123,9 +2131,18 @@ static int sdhci_esdhc_resume(struct device *dev) * restore the saved tuning delay value for the device which keep * power during system PM. */ - if (mmc_card_keep_power(host->mmc) && esdhc_is_usdhc(imx_data)) + if (mmc_card_keep_power(host->mmc) && esdhc_is_usdhc(imx_data)) { sdhc_esdhc_tuning_restore(host); + /* + * Restore DLL override for DDR modes. hwinit unconditionally + * clears ESDHC_DLL_CTRL, but the card is still in DDR mode. + */ + if (host->timing == MMC_TIMING_UHS_DDR50 || + host->timing == MMC_TIMING_MMC_DDR52) + esdhc_set_dll_override(host); + } + pm_runtime_put_autosuspend(dev); return ret; From 5adc14cd4b905629d5b9163b3a416dcab24c7ce2 Mon Sep 17 00:00:00 2001 From: Luke Wang Date: Wed, 15 Jul 2026 15:18:13 +0800 Subject: [PATCH 362/479] mmc: sdhci-esdhc-imx: fix esdhc_change_pinstate() to allow default state restore esdhc_change_pinstate() checks for pins_100mhz and pins_200mhz at the top of the function and returns -EINVAL if either is not defined. This prevents the default case from ever being reached, which means devices with a sleep pinctrl state but without high-speed pin states (100mhz/ 200mhz) can never restore their default pin configuration. Move the IS_ERR checks for pins_100mhz and pins_200mhz into their respective switch cases. Fixes: 676a83855614 ("mmc: host: sdhci-esdhc-imx: refactor the system PM logic") Acked-by: Adrian Hunter Reviewed-by: Frank Li Signed-off-by: Luke Wang Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc-imx.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 7230d70e02ae..ead4685d621a 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -1326,19 +1326,21 @@ static int esdhc_change_pinstate(struct sdhci_host *host, dev_dbg(mmc_dev(host->mmc), "change pinctrl state for uhs %d\n", uhs); - if (IS_ERR(imx_data->pinctrl) || - IS_ERR(imx_data->pins_100mhz) || - IS_ERR(imx_data->pins_200mhz)) + if (IS_ERR(imx_data->pinctrl)) return -EINVAL; switch (uhs) { case MMC_TIMING_UHS_SDR50: case MMC_TIMING_UHS_DDR50: + if (IS_ERR(imx_data->pins_100mhz)) + return -EINVAL; pinctrl = imx_data->pins_100mhz; break; case MMC_TIMING_UHS_SDR104: case MMC_TIMING_MMC_HS200: case MMC_TIMING_MMC_HS400: + if (IS_ERR(imx_data->pins_200mhz)) + return -EINVAL; pinctrl = imx_data->pins_200mhz; break; default: From 1db87818bde3d2295613660879378b43a70d31f8 Mon Sep 17 00:00:00 2001 From: Luke Wang Date: Wed, 15 Jul 2026 15:18:14 +0800 Subject: [PATCH 363/479] mmc: sdhci-esdhc-imx: restore pinctrl before restoring ios timing on resume SDIO devices such as WiFi may keep power during suspend, so the MMC core skips full card re-initialization on resume and directly restores the host controller's ios timing to match the card. For DDR mode, pm_runtime_force_resume() sets DDR_EN before the pin configuration is restored from sleep state. This is related to the SoC IP integration: switching pinctrl setting (changing alt from GPIO to USDHC) impacts the internal loopback path. If pinctrl configures the pad to GPIO function, once DDR_EN is set, the DLL delay will be fixed based on the GPIO function loopback path. When the pinctrl is later changed to USDHC function, the internal loopback path changes, making the original fixed sample point no longer suitable for the current loopback path. This causes persistent read CRC errors on subsequent data transfers. SD/eMMC running in DDR mode are unaffected as they are fully re-initialized from legacy timing after resume. Fix this by restoring the pinctrl state based on current timing mode using esdhc_change_pinstate() before pm_runtime_force_resume(). This ensures the correct pin configuration (e.g., 100/200MHz for UHS modes) is applied before DDR_EN is set. Only restore for non-wakeup devices since wakeup devices kept their active pin state during suspend. Fixes: 676a83855614 ("mmc: host: sdhci-esdhc-imx: refactor the system PM logic") Acked-by: Adrian Hunter Reviewed-by: Haibo Chen Signed-off-by: Luke Wang Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc-imx.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index ead4685d621a..a54da5757acc 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -2115,6 +2115,12 @@ static int sdhci_esdhc_resume(struct device *dev) struct pltfm_imx_data *imx_data = sdhci_pltfm_priv(pltfm_host); int ret; + if (!device_may_wakeup(dev)) { + ret = esdhc_change_pinstate(host, host->timing); + if (ret) + dev_warn(dev, "Failed to restore pinctrl state\n"); + } + pm_runtime_force_resume(dev); ret = mmc_gpio_set_cd_wake(host->mmc, false); From 9d87eaf985cef9581b6ed99b461b38e8cd666480 Mon Sep 17 00:00:00 2001 From: Luke Wang Date: Wed, 15 Jul 2026 15:18:15 +0800 Subject: [PATCH 364/479] mmc: sdhci-esdhc-imx: disable irq during suspend to fix unhandled interrupt When using WIFI out-of-band wakeup, an "irq xxx: nobody cared" warning occurs. This happens because the usdhc interrupt is not disabled during system suspend when device_may_wakeup() returns false. The sequence of events leading to this issue: 1. System enters suspend without disabling usdhc interrupt (because device_may_wakeup() returns false for usdhc device) 2. WIFI out-of-band wakeup triggers system resume via GPIO interrupt 3. WIFI sends a Card interrupt before usdhc has fully resumed 4. usdhc is still in runtime suspend state and cannot handle the interrupt properly 5. The unhandled interrupt triggers "nobody cared" warning Fix this by unconditionally disabling the usdhc interrupt during suspend and re-enabling it during resume, regardless of the wakeup capability. This ensures no interrupts are processed during the suspend/resume transition. Fixes: 676a83855614 ("mmc: host: sdhci-esdhc-imx: refactor the system PM logic") Acked-by: Adrian Hunter Reviewed-by: Haibo Chen Signed-off-by: Luke Wang Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc-imx.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index a54da5757acc..d980dea9cad1 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -2077,9 +2077,10 @@ static int sdhci_esdhc_suspend(struct device *dev) if (mmc_card_keep_power(host->mmc) && esdhc_is_usdhc(imx_data)) sdhc_esdhc_tuning_save(host); + /* The irqs of imx are not shared. It is safe to disable */ + disable_irq(host->irq); + if (device_may_wakeup(dev)) { - /* The irqs of imx are not shared. It is safe to disable */ - disable_irq(host->irq); ret = sdhci_enable_irq_wakeups(host); if (!ret) dev_warn(dev, "Failed to enable irq wakeup\n"); @@ -2130,10 +2131,10 @@ static int sdhci_esdhc_resume(struct device *dev) /* re-initialize hw state in case it's lost in low power mode */ sdhci_esdhc_imx_hwinit(host); - if (host->irq_wake_enabled) { + if (host->irq_wake_enabled) sdhci_disable_irq_wakeups(host); - enable_irq(host->irq); - } + + enable_irq(host->irq); /* * restore the saved tuning delay value for the device which keep From 8da5930144712412d85e7f868693d96ec5c2018c Mon Sep 17 00:00:00 2001 From: Luke Wang Date: Wed, 15 Jul 2026 15:18:16 +0800 Subject: [PATCH 365/479] mmc: sdhci-esdhc-imx: use pm_runtime_resume_and_get() in suspend Replace pm_runtime_get_sync() with pm_runtime_resume_and_get() to simplify error handling. pm_runtime_resume_and_get() automatically drops the usage counter on failure, avoiding the need for a separate pm_runtime_put_noidle() call. If it fails, the device is unclocked and accessing hardware registers would cause a kernel panic, so return the error immediately. Fixes: 676a83855614 ("mmc: host: sdhci-esdhc-imx: refactor the system PM logic") Acked-by: Adrian Hunter Signed-off-by: Luke Wang Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc-imx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index d980dea9cad1..7818f351a58a 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -2061,7 +2061,9 @@ static int sdhci_esdhc_suspend(struct device *dev) * 2, make sure the pm_runtime_force_resume() in sdhci_esdhc_resume() really * invoke its ->runtime_resume callback (needs_force_resume = 1). */ - pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; if ((imx_data->socdata->flags & ESDHC_FLAG_STATE_LOST_IN_LPMODE) && (host->tuning_mode != SDHCI_TUNING_MODE_1)) { From 6aa00a43bbd3d994558a55586351757cebbff236 Mon Sep 17 00:00:00 2001 From: Luke Wang Date: Wed, 15 Jul 2026 15:18:17 +0800 Subject: [PATCH 366/479] mmc: sdhci-esdhc-imx: make non-fatal errors non-blocking in suspend Make pinctrl_pm_select_sleep_state() and mmc_gpio_set_cd_wake() failures non-fatal in the suspend path. These failures only mean slightly higher power consumption or missing CD wakeup capability, but should not block system suspend. Also change the function to always return 0 on the success path instead of propagating non-fatal warning return values. Fixes: 676a83855614 ("mmc: host: sdhci-esdhc-imx: refactor the system PM logic") Acked-by: Adrian Hunter Signed-off-by: Luke Wang Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc-imx.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 7818f351a58a..290a3172931b 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -2083,8 +2083,7 @@ static int sdhci_esdhc_suspend(struct device *dev) disable_irq(host->irq); if (device_may_wakeup(dev)) { - ret = sdhci_enable_irq_wakeups(host); - if (!ret) + if (!sdhci_enable_irq_wakeups(host)) dev_warn(dev, "Failed to enable irq wakeup\n"); } else { /* @@ -2095,12 +2094,12 @@ static int sdhci_esdhc_suspend(struct device *dev) * other function like GPIO function to save power in PM, * which finally block the SDIO wakeup function. */ - ret = pinctrl_pm_select_sleep_state(dev); - if (ret) - return ret; + if (pinctrl_pm_select_sleep_state(dev)) + dev_warn(dev, "Failed to select sleep pinctrl state\n"); } - ret = mmc_gpio_set_cd_wake(host->mmc, true); + if (mmc_gpio_set_cd_wake(host->mmc, true)) + dev_warn(dev, "Failed to enable cd wake\n"); /* * Make sure invoke runtime_suspend to gate off clock. @@ -2108,7 +2107,7 @@ static int sdhci_esdhc_suspend(struct device *dev) */ pm_runtime_force_suspend(dev); - return ret; + return 0; } static int sdhci_esdhc_resume(struct device *dev) From e27c946b589c53520409a0956b33d52ef7a0898f Mon Sep 17 00:00:00 2001 From: Luke Wang Date: Wed, 15 Jul 2026 15:18:18 +0800 Subject: [PATCH 367/479] mmc: sdhci-esdhc-imx: fix resume error handling Check pm_runtime_force_resume() return value in resume. If it fails (clock enable failure), return immediately since accessing hardware registers on an unclocked device would cause a kernel panic. The early return intentionally skips enable_irq() and sdhci_disable_irq_wakeups() because the IRQ handler reads SDHCI_INT_STATUS, which would also fault without clocks. The PM runtime usage counter leak only affects this already-broken device instance and is an acceptable tradeoff to preserve system stability. Remove the return value check for mmc_gpio_set_cd_wake(host->mmc, false) since disable_irq_wake() called internally always returns 0. Also return 0 explicitly on the success path instead of propagating stale return values. Fixes: 676a83855614 ("mmc: host: sdhci-esdhc-imx: refactor the system PM logic") Acked-by: Adrian Hunter Reviewed-by: Frank Li Signed-off-by: Luke Wang Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc-imx.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 290a3172931b..18f4905c15b9 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -2123,12 +2123,12 @@ static int sdhci_esdhc_resume(struct device *dev) dev_warn(dev, "Failed to restore pinctrl state\n"); } - pm_runtime_force_resume(dev); - - ret = mmc_gpio_set_cd_wake(host->mmc, false); + ret = pm_runtime_force_resume(dev); if (ret) return ret; + mmc_gpio_set_cd_wake(host->mmc, false); + /* re-initialize hw state in case it's lost in low power mode */ sdhci_esdhc_imx_hwinit(host); @@ -2155,7 +2155,7 @@ static int sdhci_esdhc_resume(struct device *dev) pm_runtime_put_autosuspend(dev); - return ret; + return 0; } static int sdhci_esdhc_runtime_suspend(struct device *dev) From 04af4efde58a4a4ef4feab7360c46af9ec0b83a0 Mon Sep 17 00:00:00 2001 From: Mattia Meleleo Date: Wed, 8 Jul 2026 18:55:00 +0200 Subject: [PATCH 368/479] bpf, sockmap: Account for receive queue in FIONREAD without a verdict program tcp_bpf_ioctl() answers SIOCINQ from psock->msg_tot_len, which only counts bytes in ingress_msg. Without a stream/skb verdict program nothing is diverted there: data stays in sk_receive_queue, so FIONREAD returns 0 even though read() returns data. Add tcp_inq() to the reported value when the psock has no verdict program. The two queues are disjoint, so bytes redirected into ingress_msg from other sockets stay correctly accounted through msg_tot_len. Remove unused sk_psock_msg_inq(). Fixes: 929e30f93125 ("bpf, sockmap: Fix FIONREAD for sockmap") Signed-off-by: Mattia Meleleo Reviewed-by: Jiayuan Chen Reviewed-by: Emil Tsalapatis Reviewed-by: John Fastabend Link: https://lore.kernel.org/bpf/20260708-fionread-no-verdict-v3-1-b4ee31b3af53@coralogix.com Signed-off-by: Kumar Kartikeya Dwivedi --- include/linux/skmsg.h | 14 -------------- net/ipv4/tcp_bpf.c | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/include/linux/skmsg.h b/include/linux/skmsg.h index a8553401b1c9..d5e35f24738d 100644 --- a/include/linux/skmsg.h +++ b/include/linux/skmsg.h @@ -551,20 +551,6 @@ static inline void psock_progs_drop(struct sk_psock_progs *progs) psock_set_prog(&progs->skb_verdict, NULL); } -/* for tcp only, sk is locked */ -static inline ssize_t sk_psock_msg_inq(struct sock *sk) -{ - struct sk_psock *psock; - ssize_t inq = 0; - - psock = sk_psock_get(sk); - if (likely(psock)) { - inq = sk_psock_get_msg_len_nolock(psock); - sk_psock_put(sk, psock); - } - return inq; -} - /* for udp only, sk is not locked */ static inline ssize_t sk_msg_first_len(struct sock *sk) { diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c index cc0bd73f36b6..8e905b50dead 100644 --- a/net/ipv4/tcp_bpf.c +++ b/net/ipv4/tcp_bpf.c @@ -334,6 +334,7 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg) { + struct sk_psock *psock; bool slow; if (cmd != SIOCINQ) @@ -344,7 +345,21 @@ static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg) return -EINVAL; slow = lock_sock_fast(sk); - *karg = sk_psock_msg_inq(sk); + psock = sk_psock_get(sk); + if (unlikely(!psock)) { + unlock_sock_fast(sk, slow); + return tcp_ioctl(sk, cmd, karg); + } + *karg = sk_psock_get_msg_len_nolock(psock); + /* Without a verdict program, ingress data is never diverted to + * ingress_msg: it stays in sk_receive_queue and is read through + * the fallback to tcp_recvmsg(), so account for it like + * tcp_ioctl() does. + */ + if (!READ_ONCE(psock->progs.stream_verdict) && + !READ_ONCE(psock->progs.skb_verdict)) + *karg += tcp_inq(sk); + sk_psock_put(sk, psock); unlock_sock_fast(sk, slow); return 0; From a42f05cc4c1d49e27bbc6f65415aa76274ef7736 Mon Sep 17 00:00:00 2001 From: Mattia Meleleo Date: Wed, 8 Jul 2026 18:55:01 +0200 Subject: [PATCH 369/479] selftests/bpf: Test FIONREAD on a sockmap socket without a verdict program Add a test validating that FIONREAD on a TCP socket in a sockmap without a verdict program reports data pending in sk_receive_queue. Signed-off-by: Mattia Meleleo Reviewed-by: Jiayuan Chen Reviewed-by: Emil Tsalapatis Reviewed-by: John Fastabend Link: https://lore.kernel.org/bpf/20260708-fionread-no-verdict-v3-2-b4ee31b3af53@coralogix.com Signed-off-by: Kumar Kartikeya Dwivedi --- .../selftests/bpf/prog_tests/sockmap_basic.c | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c index 2d22a9058a8e..e5fc038d747b 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c @@ -1373,6 +1373,43 @@ static void test_sockmap_multi_channels(int sotype) test_sockmap_pass_prog__destroy(skel); } +/* A socket in a sockmap without a verdict program keeps its ingress data + * in sk_receive_queue: FIONREAD must account for it. + */ +static void test_sockmap_no_verdict_fionread(void) +{ + int err, map, zero = 0, sent, avail; + int c0 = -1, c1 = -1, p0 = -1, p1 = -1; + struct test_sockmap_pass_prog *skel; + char buf[256] = "0123456789"; + + skel = test_sockmap_pass_prog__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + map = bpf_map__fd(skel->maps.sock_map_rx); + + err = create_socket_pairs(AF_INET, SOCK_STREAM, &c0, &c1, &p0, &p1); + if (!ASSERT_OK(err, "create_socket_pairs()")) + goto out; + + err = bpf_map_update_elem(map, &zero, &c1, BPF_NOEXIST); + if (!ASSERT_OK(err, "bpf_map_update_elem(c1)")) + goto out_close; + + sent = xsend(p1, &buf, sizeof(buf), 0); + ASSERT_EQ(sent, sizeof(buf), "xsend(p1)"); + avail = wait_for_fionread(c1, sizeof(buf), IO_TIMEOUT_SEC); + ASSERT_EQ(avail, sizeof(buf), "ioctl(FIONREAD)"); + +out_close: + close(c0); + close(p0); + close(c1); + close(p1); +out: + test_sockmap_pass_prog__destroy(skel); +} + void test_sockmap_basic(void) { if (test__start_subtest("sockmap create_update_free")) @@ -1415,6 +1452,8 @@ void test_sockmap_basic(void) test_sockmap_skb_verdict_shutdown(); if (test__start_subtest("sockmap skb_verdict fionread")) test_sockmap_skb_verdict_fionread(true); + if (test__start_subtest("sockmap no_verdict fionread")) + test_sockmap_no_verdict_fionread(); if (test__start_subtest("sockmap skb_verdict fionread on drop")) test_sockmap_skb_verdict_fionread(false); if (test__start_subtest("sockmap skb_verdict change tail")) From fd4cfa8c8f9a17cdec0539334d28754bc1d8a5d9 Mon Sep 17 00:00:00 2001 From: Sun Jian Date: Tue, 14 Jul 2026 02:38:45 -0700 Subject: [PATCH 370/479] bpf: Reject negative const offsets for buffer pointers The verifier rejects variable offsets for PTR_TO_TP_BUFFER and PTR_TO_BUF accesses, but it currently accepts a constant negative offset produced by pointer arithmetic. Commit 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") moved constant pointer offsets from reg->off to reg->var_off. However, __check_buffer_access() continued to check only the instruction offset. An access with reg->var_off equal to -8 and an instruction offset of zero therefore passes verification. For writable raw tracepoints, the access end is also calculated from the unsigned reg->var_off.value. An eight-byte access starting at -8 wraps the calculated end to zero, allowing the program to load and attach without increasing max_tp_access. After ensuring that reg->var_off is constant, calculate the effective access start using signed arithmetic and reject it when it is negative. Use the validated start to calculate the access end for both PTR_TO_TP_BUFFER and PTR_TO_BUF. Fixes: 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") Signed-off-by: Sun Jian Acked-by: Shung-Hsi Yu Cc: stable@vger.kernel.org # 5.2.0 Link: https://patch.msgid.link/20260714093846.18159-2-sun.jian.kdev@gmail.com Signed-off-by: Eduard Zingerman --- kernel/bpf/verifier.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 6515d4d3c003..9f1333676365 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -5326,14 +5326,11 @@ static int check_max_stack_depth(struct bpf_verifier_env *env) static int __check_buffer_access(struct bpf_verifier_env *env, const char *buf_info, const struct bpf_reg_state *reg, - argno_t argno, int off, int size) + argno_t argno, int off, int size, + u32 *access_end) { - if (off < 0) { - verbose(env, - "%s invalid %s buffer access: off=%d, size=%d\n", - reg_arg_name(env, argno), buf_info, off, size); - return -EACCES; - } + s64 start; + if (!tnum_is_const(reg->var_off)) { char tn_buf[48]; @@ -5344,6 +5341,15 @@ static int __check_buffer_access(struct bpf_verifier_env *env, return -EACCES; } + start = (s64)reg->var_off.value + off; + if (start < 0) { + verbose(env, + "%s invalid negative %s buffer offset: off=%d, var_off=%lld\n", + reg_arg_name(env, argno), buf_info, off, (s64)reg->var_off.value); + return -EACCES; + } + + *access_end = start + size; return 0; } @@ -5351,14 +5357,14 @@ static int check_tp_buffer_access(struct bpf_verifier_env *env, const struct bpf_reg_state *reg, argno_t argno, int off, int size) { + u32 access_end; int err; - err = __check_buffer_access(env, "tracepoint", reg, argno, off, size); + err = __check_buffer_access(env, "tracepoint", reg, argno, off, size, &access_end); if (err) return err; - env->prog->aux->max_tp_access = max(reg->var_off.value + off + size, - env->prog->aux->max_tp_access); + env->prog->aux->max_tp_access = max(access_end, env->prog->aux->max_tp_access); return 0; } @@ -5370,13 +5376,14 @@ static int check_buffer_access(struct bpf_verifier_env *env, u32 *max_access) { const char *buf_info = type_is_rdonly_mem(reg->type) ? "rdonly" : "rdwr"; + u32 access_end; int err; - err = __check_buffer_access(env, buf_info, reg, argno, off, size); + err = __check_buffer_access(env, buf_info, reg, argno, off, size, &access_end); if (err) return err; - *max_access = max(reg->var_off.value + off + size, *max_access); + *max_access = max(access_end, *max_access); return 0; } From 6f59deb32efa4673fc3b1fef9f3a0da48e9d8494 Mon Sep 17 00:00:00 2001 From: Sun Jian Date: Tue, 14 Jul 2026 02:38:46 -0700 Subject: [PATCH 371/479] selftests/bpf: Cover negative buffer pointer offsets Add verifier coverage for constant negative offsets on PTR_TO_TP_BUFFER and PTR_TO_BUF pointers. Both programs adjust the buffer pointer by -8 and access it at offset zero, so the negative effective start must be rejected at load time. Switch the raw tracepoint writable attach checks from nbd_send_request to bpf_testmod_test_writable_bare_tp, avoiding a dependency on the NBD tracepoint. Keep the existing past-end case and add a case with a negative var_off compensated by a positive instruction offset. The effective start remains non-negative, so the program loads, but its access end exceeds the writable context size and bpf_raw_tracepoint_open() must return -EINVAL. Cc: stable@vger.kernel.org # 5.2.0 Signed-off-by: Sun Jian Acked-by: Shung-Hsi Yu Link: https://patch.msgid.link/20260714093846.18159-3-sun.jian.kdev@gmail.com Signed-off-by: Eduard Zingerman --- .../raw_tp_writable_reject_bad_access.c | 57 +++++++++++++++++++ .../raw_tp_writable_reject_nbd_invalid.c | 43 -------------- .../selftests/bpf/prog_tests/verifier.c | 2 + .../selftests/bpf/progs/verifier_ptr_to_buf.c | 27 +++++++++ .../bpf/progs/verifier_raw_tp_writable.c | 16 ++++++ 5 files changed, 102 insertions(+), 43 deletions(-) create mode 100644 tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c delete mode 100644 tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c create mode 100644 tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c diff --git a/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c b/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c new file mode 100644 index 000000000000..b8538fc4fc3f --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include "test_kmods/bpf_testmod.h" +#include "bpf_util.h" + +static void check_attach_reject(const struct bpf_insn *program, size_t prog_len) +{ + LIBBPF_OPTS(bpf_prog_load_opts, opts); + char error[4096]; + int bpf_fd, tp_fd; + + opts.log_level = 2; + opts.log_buf = error; + opts.log_size = sizeof(error); + + bpf_fd = bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE, NULL, "GPL v2", + program, prog_len, &opts); + if (!ASSERT_GE(bpf_fd, 0, "prog_load")) + return; + + tp_fd = bpf_raw_tracepoint_open("bpf_testmod_test_writable_bare_tp", bpf_fd); + ASSERT_EQ(tp_fd, -EINVAL, "bpf_raw_tracepoint_open"); + if (tp_fd >= 0) + close(tp_fd); + + close(bpf_fd); +} + +void test_raw_tp_writable_reject_bad_access(void) +{ + const struct bpf_insn program[] = { + /* r6 is our tp buffer */ + BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0), + /* one byte beyond the end of the writable context */ + BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6, + sizeof(struct bpf_testmod_test_writable_ctx)), + BPF_EXIT_INSN(), + }; + + const struct bpf_insn negative_var_off_program[] = { + BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0), + /* make var_off negative, but keep the effective access offset non-negative */ + BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, -8), + /* one byte beyond the end of the writable context */ + BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6, + sizeof(struct bpf_testmod_test_writable_ctx) + 8), + BPF_EXIT_INSN(), + }; + + if (test__start_subtest("past_end")) + check_attach_reject(program, ARRAY_SIZE(program)); + + if (test__start_subtest("negative_var_off_past_end")) + check_attach_reject(negative_var_off_program, + ARRAY_SIZE(negative_var_off_program)); +} diff --git a/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c b/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c deleted file mode 100644 index 216b0dfac0fe..000000000000 --- a/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#include -#include -#include "bpf_util.h" - -void test_raw_tp_writable_reject_nbd_invalid(void) -{ - __u32 duration = 0; - char error[4096]; - int bpf_fd = -1, tp_fd = -1; - - const struct bpf_insn program[] = { - /* r6 is our tp buffer */ - BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0), - /* one byte beyond the end of the nbd_request struct */ - BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6, - sizeof(struct nbd_request)), - BPF_EXIT_INSN(), - }; - - LIBBPF_OPTS(bpf_prog_load_opts, opts, - .log_level = 2, - .log_buf = error, - .log_size = sizeof(error), - ); - - bpf_fd = bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE, NULL, "GPL v2", - program, ARRAY_SIZE(program), - &opts); - if (CHECK(bpf_fd < 0, "bpf_raw_tracepoint_writable load", - "failed: %d errno %d\n", bpf_fd, errno)) - return; - - tp_fd = bpf_raw_tracepoint_open("nbd_send_request", bpf_fd); - if (CHECK(tp_fd >= 0, "bpf_raw_tracepoint_writable open", - "erroneously succeeded\n")) - goto out_bpffd; - - close(tp_fd); -out_bpffd: - close(bpf_fd); -} diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c index 8a3d69e2453c..be97f6887f0e 100644 --- a/tools/testing/selftests/bpf/prog_tests/verifier.c +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c @@ -78,6 +78,7 @@ #include "verifier_precision.skel.h" #include "verifier_prevent_map_lookup.skel.h" #include "verifier_private_stack.skel.h" +#include "verifier_ptr_to_buf.skel.h" #include "verifier_raw_stack.skel.h" #include "verifier_raw_tp_writable.skel.h" #include "verifier_reg_equal.skel.h" @@ -230,6 +231,7 @@ void test_verifier_or_jmp32_k(void) { RUN(verifier_or_jmp32_k); } void test_verifier_precision(void) { RUN(verifier_precision); } void test_verifier_prevent_map_lookup(void) { RUN(verifier_prevent_map_lookup); } void test_verifier_private_stack(void) { RUN(verifier_private_stack); } +void test_verifier_ptr_to_buf(void) { RUN(verifier_ptr_to_buf); } void test_verifier_raw_stack(void) { RUN(verifier_raw_stack); } void test_verifier_raw_tp_writable(void) { RUN(verifier_raw_tp_writable); } void test_verifier_reg_equal(void) { RUN(verifier_reg_equal); } diff --git a/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c b/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c new file mode 100644 index 000000000000..12cf24db46a0 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include "bpf_misc.h" + +SEC("iter/bpf_map_elem") +__description("PTR_TO_BUF: reject negative const offset") +__failure +__msg("invalid negative rdwr buffer offset") +__naked void ptr_to_buf_reject_negative_const_offset(void) +{ + asm volatile ("r0 = 0; \ + r2 = *(u64 *)(r1 + %[value_off]); \ + if r2 == 0 goto l0_%=; \ + r2 += -8; \ + r0 = *(u64 *)(r2 + 0); \ +l0_%=: \ + exit; \ + " + : + : __imm_const(value_off, + offsetof(struct bpf_iter__bpf_map_elem, value)) + : __clobber_all); +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c b/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c index 14a0172e2141..4055a6443bc2 100644 --- a/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c +++ b/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c @@ -47,4 +47,20 @@ l0_%=: /* shift the buffer pointer to a variable location */\ : __clobber_all); } +SEC("raw_tracepoint.w") +__description("raw_tracepoint_writable: reject negative const offset") +__failure +__msg("invalid negative tracepoint buffer offset") +__naked void tracepoint_writable_reject_negative_const_offset(void) +{ + asm volatile (" \ + r6 = *(u64 *)(r1 + 0); \ + r6 += -8; \ + r0 = *(u64 *)(r6 + 0); \ + exit; \ +" : + : + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; From 2f2312c422fd2695da772cecb30c69994b795964 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Tue, 14 Jul 2026 09:03:06 -0700 Subject: [PATCH 372/479] KVM: nVMX: Put vmcs12 pages if nested VM-Enter fails due to invalid guest state Put all vmcs12 pages if KVM synthesizes a nested VM-Exit due to invalid guest while emulating VMLAUNCH or VMRESUME. The invalid guest state path doesn't use nested_vmx_vmexit() as that API is intended to be used if and only if L2 is active, and the open coded equivalent neglects to put the vmcs12 pages. Failure to put the vmcs12 pages leaks any pinned pages (and/or mappings) if L1 retries VMLAUNCH/VMRESUME. Note, the !from_vmenter scenario doesn't suffer the same problem, as vmx_get_nested_state_pages() only gets/pins/maps the vmcs12 pages if L2 is active, i.e. if a "full" VM-Exit is guaranteed before KVM will retry getting vmcs12 pages. Fixes: 96c66e87deee ("KVM/nVMX: Use kvm_vcpu_map when mapping the virtual APIC page") Fixes: 3278e0492554 ("KVM/nVMX: Use kvm_vcpu_map when mapping the posted interrupt descriptor table") Fixes: fe1911aa443e ("KVM: nVMX: Use kvm_vcpu_map() to get/pin vmcs12's APIC-access page") Reported-by: Minh Nguyen Cc: stable@vger.kernel.org Signed-off-by: Sean Christopherson Signed-off-by: Paolo Bonzini --- arch/x86/kvm/vmx/nested.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c index bb0eb40b4448..220d42ebc82e 100644 --- a/arch/x86/kvm/vmx/nested.c +++ b/arch/x86/kvm/vmx/nested.c @@ -3761,6 +3761,8 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu, if (!from_vmentry) return NVMX_VMENTRY_VMEXIT; + nested_put_vmcs12_pages(vcpu); + load_vmcs12_host_state(vcpu, vmcs12); vmcs12->vm_exit_reason = exit_reason.full; if (enable_shadow_vmcs || nested_vmx_is_evmptr12_valid(vmx)) From 4a2c8cbe9bcba170706fdf08b1c84b6cbcf5b044 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Tue, 14 Jul 2026 18:30:55 +0200 Subject: [PATCH 373/479] drm/panthor: return error on truncated firmware panthor_fw_load() detects truncated firmware images, but jumps to the common cleanup path without setting ret. If no previous error was recorded, the function can return 0 and treat the invalid firmware as successfully loaded. Set ret to -EINVAL before leaving the truncated-image path. Fixes: 2718d91816ee ("drm/panthor: Add the FW logical block") Cc: stable@vger.kernel.org Signed-off-by: Osama Abdelkader Reviewed-by: Liviu Dudau Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20260714163056.22329-1-osama.abdelkader@gmail.com Signed-off-by: Liviu Dudau --- drivers/gpu/drm/panthor/panthor_fw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c index de8e6689a869..90f59d782a80 100644 --- a/drivers/gpu/drm/panthor/panthor_fw.c +++ b/drivers/gpu/drm/panthor/panthor_fw.c @@ -829,6 +829,7 @@ static int panthor_fw_load(struct panthor_device *ptdev) } if (hdr.size > iter.size) { + ret = -EINVAL; drm_err(&ptdev->base, "Firmware image is truncated\n"); goto out; } From 022e901333c3054656a640794e842bab7af5a75c Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 13 Jul 2026 16:29:12 +0800 Subject: [PATCH 374/479] drm/panthor: Check debugfs GEM lock initialization drmm_mutex_init() can fail while registering the managed cleanup action. When that happens, drmm_add_action_or_reset() destroys the mutex before returning the error. Continuing initialization would therefore leave the debugfs GEM object list with an unusable lock. Propagate the error as is already done for the other managed mutexes in panthor_device_init(). Fixes: a3707f53eb3f ("drm/panthor: show device-wide list of DRM GEM objects over DebugFS") Signed-off-by: Linmao Li Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260713082912.321021-1-lilinmao@kylinos.cn Signed-off-by: Liviu Dudau --- drivers/gpu/drm/panthor/panthor_device.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_device.c b/drivers/gpu/drm/panthor/panthor_device.c index 0b25abebb803..9687c59de350 100644 --- a/drivers/gpu/drm/panthor/panthor_device.c +++ b/drivers/gpu/drm/panthor/panthor_device.c @@ -182,7 +182,10 @@ int panthor_device_init(struct panthor_device *ptdev) return ret; #ifdef CONFIG_DEBUG_FS - drmm_mutex_init(&ptdev->base, &ptdev->gems.lock); + ret = drmm_mutex_init(&ptdev->base, &ptdev->gems.lock); + if (ret) + return ret; + INIT_LIST_HEAD(&ptdev->gems.node); #endif From ddb44baed257560f192b145ed36cf8c0a412de47 Mon Sep 17 00:00:00 2001 From: Jhonraushan Date: Wed, 15 Jul 2026 13:12:06 +0530 Subject: [PATCH 375/479] accel/ivpu: Reject firmware log with size smaller than header fw_log_from_bo() validates the tracing buffer header_size and that the log fits within the BO, but never checks that log->size is at least log->header_size. fw_log_print_buffer() then computes: u32 data_size = log->size - log->header_size; which underflows to a near-U32_MAX value when firmware reports a log whose size is smaller than its header. That huge data_size defeats the log_start/log_end bounds clamps added by commit dd1311bcf0e6 ("accel/ivpu: Add bounds checks for firmware log indices"), so fw_log_print_lines() reads far past the small real data region of the BO. A size of 0 also makes fw_log_from_bo() advance the offset by 0, causing the callers to loop forever on the same header. Reject logs whose size is smaller than the header (which also rejects size == 0). Fixes: d4e4257afa6e ("accel/ivpu: Add firmware tracing support") Cc: stable@vger.kernel.org Signed-off-by: Jhonraushan Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260715074206.867712-1-raushan.jhon@gmail.com --- drivers/accel/ivpu/ivpu_fw_log.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/accel/ivpu/ivpu_fw_log.c b/drivers/accel/ivpu/ivpu_fw_log.c index 275baf844b56..716467aa3156 100644 --- a/drivers/accel/ivpu/ivpu_fw_log.c +++ b/drivers/accel/ivpu/ivpu_fw_log.c @@ -43,6 +43,10 @@ static int fw_log_from_bo(struct ivpu_device *vdev, struct ivpu_bo *bo, u32 *off ivpu_dbg(vdev, FW_BOOT, "Invalid header size 0x%x\n", log->header_size); return -EINVAL; } + if (log->size < log->header_size) { + ivpu_dbg(vdev, FW_BOOT, "Invalid log size 0x%x\n", log->size); + return -EINVAL; + } if ((char *)log + log->size > (char *)ivpu_bo_vaddr(bo) + ivpu_bo_size(bo)) { ivpu_dbg(vdev, FW_BOOT, "Invalid log size 0x%x\n", log->size); return -EINVAL; From 4bb06b60d982355e22647b3d12d6619419f8c1fa Mon Sep 17 00:00:00 2001 From: Vasily Gorbik Date: Wed, 8 Jul 2026 12:02:14 +0200 Subject: [PATCH 376/479] s390/checksum: Fix csum_partial() without vector facility Currently csum_partial() calls csum_copy() with copy=false and dst=NULL. On machines without the vector facility, csum_copy() falls back to cksm(dst, ...), causing the checksum to be calculated from address zero instead of the source buffer. The VX implementation already checksums data loaded from src. Make the fallback do the same by passing src to cksm(). Fixes: dcd3e1de9d17 ("s390/checksum: provide csum_partial_copy_nocheck()") Reviewed-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/lib/csum-partial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/lib/csum-partial.c b/arch/s390/lib/csum-partial.c index 458abd9bac70..9d74ceff136c 100644 --- a/arch/s390/lib/csum-partial.c +++ b/arch/s390/lib/csum-partial.c @@ -23,7 +23,7 @@ static __always_inline __wsum csum_copy(void *dst, const void *src, int len, __w if (!cpu_has_vx()) { if (copy) memcpy(dst, src, len); - return cksm(dst, len, sum); + return cksm(src, len, sum); } kernel_fpu_begin(&vxstate, KERNEL_VXR_V16V23); fpu_vlvgf(16, (__force u32)sum, 1); From 49145bce539117db4b6e9e83c0e5ef528e361050 Mon Sep 17 00:00:00 2001 From: Sumanth Korikkar Date: Mon, 6 Jul 2026 12:46:31 +0200 Subject: [PATCH 377/479] s390/perf_cpum_cf: Add missing array_index_nospec() to __hw_perf_event_init() ev variable is userspace controlled via event->attr.config and used as an array index after bounds checking, but without speculation barriers. Add the missing array_index_nospec() call to prevent speculative execution. Cc: stable@vger.kernel.org Fixes: 212188a596d1 ("[S390] perf: add support for s390x CPU counters") Signed-off-by: Sumanth Korikkar Reviewed-by: Ilya Leoshkevich Acked-by: Thomas Richter Signed-off-by: Vasily Gorbik --- arch/s390/kernel/perf_cpum_cf.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/s390/kernel/perf_cpum_cf.c b/arch/s390/kernel/perf_cpum_cf.c index 7aa655664ecc..2076ac22e2c4 100644 --- a/arch/s390/kernel/perf_cpum_cf.c +++ b/arch/s390/kernel/perf_cpum_cf.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -768,6 +769,7 @@ static int __hw_perf_event_init(struct perf_event *event, unsigned int type) if (!is_userspace_event(ev)) { if (ev >= ARRAY_SIZE(cpumf_generic_events_user)) return -EOPNOTSUPP; + ev = array_index_nospec(ev, ARRAY_SIZE(cpumf_generic_events_user)); ev = cpumf_generic_events_user[ev]; } } else if (!attr->exclude_kernel && attr->exclude_user) { @@ -778,6 +780,7 @@ static int __hw_perf_event_init(struct perf_event *event, unsigned int type) if (!is_userspace_event(ev)) { if (ev >= ARRAY_SIZE(cpumf_generic_events_basic)) return -EOPNOTSUPP; + ev = array_index_nospec(ev, ARRAY_SIZE(cpumf_generic_events_basic)); ev = cpumf_generic_events_basic[ev]; } } From 5caae1deee89a6582c761d5dcd4b924b744426cc Mon Sep 17 00:00:00 2001 From: Mark Harris Date: Mon, 13 Jul 2026 17:30:56 -0700 Subject: [PATCH 378/479] riscv: hwprobe: Avoid uninitialized read in hwprobe_get_cpus() When cpusetsize < cpumask_size(), hwprobe_get_cpus() did not fully initialize its copy of the cpu mask, which could cause non-deterministic results from the riscv_hwprobe syscall on a system with more than 8 CPUs when the supplied cpu mask is empty. Address this by fully initializing the cpu mask. Fixes: e178bf146e4b ("RISC-V: hwprobe: Introduce which-cpus flag") Signed-off-by: Mark Harris Reviewed-by: Nam Cao Reviewed-by: Michael Ellerman Link: https://patch.msgid.link/20260714003056.73707-1-mark.hsj@gmail.com Signed-off-by: Paul Walmsley --- arch/riscv/kernel/sys_hwprobe.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c index 1659d31fd288..caf6762427c8 100644 --- a/arch/riscv/kernel/sys_hwprobe.c +++ b/arch/riscv/kernel/sys_hwprobe.c @@ -450,6 +450,7 @@ static int hwprobe_get_cpus(struct riscv_hwprobe __user *pairs, if (cpusetsize > cpumask_size()) cpusetsize = cpumask_size(); + cpumask_clear(&cpus); ret = copy_from_user(&cpus, cpus_user, cpusetsize); if (ret) return -EFAULT; From 691b052139c94ee6640ac39e0b764dd3867897c0 Mon Sep 17 00:00:00 2001 From: Mike Waychison Date: Wed, 15 Jul 2026 15:29:50 -0400 Subject: [PATCH 379/479] block: fix race in blk_time_get_ns() returning 0 blk_time_get_ns() populates the per-plug cached timestamp and then returns it by re-reading the field: if (!plug->cur_ktime) { plug->cur_ktime = ktime_get_ns(); current->flags |= PF_BLOCK_TS; } return plug->cur_ktime; This is problematic when the compiler emits the final "return plug->cur_ktime" as a reload from memory, after PF_BLOCK_TS has already been set. Since the cached timestamp is now invalidated from finish_task_switch() (fad156c2af22 "block: invalidate cached plug timestamp after task switch"), a task preempted between setting PF_BLOCK_TS and that reload has plug->cur_ktime zeroed by blk_plug_invalidate_ts() when it is scheduled back in. The reload then returns 0. A 0 handed back here is stored as a start timestamp -- e.g. blk_account_io_start() writes it to rq->start_time_ns -- and later subtracted from "now". blk_account_io_done() then adds (now - 0), i.e. roughly the system uptime, to the per-group nsecs[] counters. On an otherwise idle, healthy device this appears as sudden ~uptime-sized jumps in the diskstats time fields (write_ticks/discard_ticks/time_in_queue). The solution is to be explicit in our reads and writes to this field that is preemption volatile. We also add a barrier() to ensure that any setting of PF_BLOCK_TS is ordered to happen after the cur_ktime update. This issue was discovered using AI-assisted kprobes looking for paths that were leaking zeroed timestamps in a live system, based on the observation that we were sometimes seeing uptime-sized jumps in kernel exported counters. This was flagged by NodeDiskIOSaturation prometheus alerts that started firing on all hosts post 7.1.3 kernel upgrade, due to node-exporter now exporting a nonsensical node_disk_io_time_weighted_seconds_total. Fixes: fad156c2af22 ("block: invalidate cached plug timestamp after task switch") Cc: stable@vger.kernel.org Signed-off-by: Mike Waychison Assisted-by: Claude:claude-opus-4.8 Link: https://patch.msgid.link/20260715192950.2488921-1-mike@waychison.com Signed-off-by: Jens Axboe --- block/blk.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/block/blk.h b/block/blk.h index fb95d3c58950..eaac05815cb0 100644 --- a/block/blk.h +++ b/block/blk.h @@ -717,6 +717,7 @@ static inline int req_ref_read(struct request *req) static inline u64 blk_time_get_ns(void) { struct blk_plug *plug = current->plug; + u64 now; if (!plug || !in_task()) return ktime_get_ns(); @@ -725,12 +726,18 @@ static inline u64 blk_time_get_ns(void) * 0 could very well be a valid time, but rather than flag "this is * a valid timestamp" separately, just accept that we'll do an extra * ktime_get_ns() if we just happen to get 0 as the current time. + * + * cur_ktime can be zeroed by pre-emption the moment PF_BLOCK_TS is set. */ - if (!plug->cur_ktime) { - plug->cur_ktime = ktime_get_ns(); + now = READ_ONCE(plug->cur_ktime); + if (!now) { + now = ktime_get_ns(); + WRITE_ONCE(plug->cur_ktime, now); + /* Ensure PF_BLOCK_TS is set after cur_ktime. */ + barrier(); current->flags |= PF_BLOCK_TS; } - return plug->cur_ktime; + return now; } static inline ktime_t blk_time_get(void) From c4f4c0fc551cbcdccbbbc2d874d7d6440c7b4983 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 15 Jul 2026 21:39:57 +0200 Subject: [PATCH 380/479] loop: remove manually added partitions on detach Commit 267ec4d7223a ("loop: fix partition scan race between udev and loop_reread_partitions()") stopped disk_force_media_change() from setting GD_NEED_PART_SCAN because loop devices with LO_FLAGS_PARTSCAN rescan partitions explicitly. However, partitions can also be added manually with BLKPG while LO_FLAGS_PARTSCAN is clear. When such a loop device is detached, __loop_clr_fd() skips bdev_disk_changed(). Without GD_NEED_PART_SCAN, reopening the unbound device no longer performs the previous lazy cleanup, leaving dead partition devices behind. A subsequent LOOP_CONFIGURE can then fail its partition scan with -EBUSY, as seen in blktests loop/009 after loop/008. Call bdev_disk_changed() unconditionally during __loop_clr_fd(). The disk capacity is already zero and the release path holds open_mutex, so this drops all partitions without rescanning the detached backing file. The new blktests loop/013 case covers this sequence by adding a partition with BLKPG without LO_FLAGS_PARTSCAN, detaching the loop device, and checking that the partition is gone when the device is reopened. Fixes: 267ec4d7223a ("loop: fix partition scan race between udev and loop_reread_partitions()") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-lkp/202607150754.b660f5b9-lkp@intel.com Signed-off-by: Daan De Meyer Link: https://patch.msgid.link/20260715-b4-loop-partition-cleanup-v1-1-b9f59910cd1e@amutable.com Signed-off-by: Jens Axboe --- drivers/block/loop.c | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/drivers/block/loop.c b/drivers/block/loop.c index 310de0463beb..1faecef33009 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -1113,6 +1113,7 @@ static void __loop_clr_fd(struct loop_device *lo) struct queue_limits lim; struct file *filp; gfp_t gfp = lo->old_gfp_mask; + int err; spin_lock_irq(&lo->lo_lock); filp = lo->lo_backing_file; @@ -1146,26 +1147,21 @@ static void __loop_clr_fd(struct loop_device *lo) disk_force_media_change(lo->lo_disk); - if (lo->lo_flags & LO_FLAGS_PARTSCAN) { - int err; - - /* - * open_mutex has been held already in release path, so don't - * acquire it if this function is called in such case. - * - * If the reread partition isn't from release path, lo_refcnt - * must be at least one and it can only become zero when the - * current holder is released. - */ - err = bdev_disk_changed(lo->lo_disk, false); - if (err) - pr_warn("%s: partition scan of loop%d failed (rc=%d)\n", - __func__, lo->lo_number, err); - /* Device is gone, no point in returning error */ - } + /* + * Remove all partitions, including partitions added manually with + * BLKPG, which may exist even if LO_FLAGS_PARTSCAN is not set. + * + * open_mutex has been held already in release path, so don't acquire + * it here. + */ + err = bdev_disk_changed(lo->lo_disk, false); + if (err) + pr_warn("%s: partition scan of loop%d failed (rc=%d)\n", + __func__, lo->lo_number, err); + /* Device is gone, no point in returning error */ /* - * lo->lo_state is set to Lo_unbound here after above partscan has + * lo->lo_state is set to Lo_unbound here after removing partitions has * finished. There cannot be anybody else entering __loop_clr_fd() as * Lo_rundown state protects us from all the other places trying to * change the 'lo' device. From dbbca20764382b4d411ec2918f4e278ffe547acc Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Wed, 15 Jul 2026 21:24:07 +0800 Subject: [PATCH 381/479] blk-cgroup: fix leaks and online flag on radix_tree_insert failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When radix_tree_insert() fails in blkg_create(), the error path has two issues: 1. blkg->online is set to true unconditionally, even when the blkg was never fully inserted. Move the assignment inside the success block. 2. The error path calls blkg_put() without first calling percpu_ref_kill(). Because the refcount is still in percpu mode, percpu_ref_put() only does this_cpu_sub() without checking for zero, so blkg_release() is never triggered. This permanently leaks the blkg memory, its percpu iostat, policy data, the parent blkg reference, and the cgroup css reference — the latter preventing the cgroup from ever being destroyed. Fix by replacing blkg_put() with percpu_ref_kill(), matching the pattern used in blkg_destroy(). Acked-by: Tejun Heo Signed-off-by: Tao Cui Link: https://patch.msgid.link/20260715132407.1469777-1-cui.tao@linux.dev Signed-off-by: Jens Axboe --- block/blk-cgroup.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c index d2a1f5903f24..d9676126c5b5 100644 --- a/block/blk-cgroup.c +++ b/block/blk-cgroup.c @@ -434,15 +434,15 @@ static struct blkcg_gq *blkg_create(struct blkcg *blkcg, struct gendisk *disk, blkg->pd[i]->online = true; } } + blkg->online = true; } - blkg->online = true; spin_unlock(&blkcg->lock); if (!ret) return blkg; /* @blkg failed fully initialized, use the usual release path */ - blkg_put(blkg); + percpu_ref_kill(&blkg->refcnt); return ERR_PTR(ret); err_free_blkg: From d35e236282992e583ed9c1b7ab52e9ef1b873585 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 14 Jul 2026 07:48:06 -0400 Subject: [PATCH 382/479] partitions: aix: bound the lvd scan to one sector aix_partition() reads the logical-volume descriptor array as a single sector and then scans it: if (numlvs && (d = read_part_sector(state, vgda_sector + 1, §))) { struct lvd *p = (struct lvd *)d; ... for (i = 0; foundlvs < numlvs && i < state->limit; i++) { lvip[i].pps_per_lv = be16_to_cpu(p[i].num_lps); p points at a single 512-byte sector, which holds SECTOR_SIZE / sizeof(struct lvd) = 16 entries, but the loop runs until foundlvs reaches the on-disk numlvs or i reaches state->limit (DISK_MAX_PARTS, 256). numlvs is an on-disk __be16 read straight from the volume group descriptor and is not validated, so a crafted AIX image with numlvs larger than 16 and lvd entries whose num_lps fields are zero (so foundlvs never advances) drives the loop to read p[i] well past the end of the read sector buffer. Commit d97a86c170b4 ("partitions: aix.c: off by one bug") hardened the matching write of lvip[lv_ix] in 2014 but left this read loop unbounded. Bound the scan to the number of struct lvd entries that fit in the sector that was actually read. Fixes: 6ceea22bbbc8 ("partitions: add aix lvm partition support files") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/20260714114806.3761553-1-michael.bommarito@gmail.com Signed-off-by: Jens Axboe --- block/partitions/aix.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/block/partitions/aix.c b/block/partitions/aix.c index f3c4174e003e..689837deba27 100644 --- a/block/partitions/aix.c +++ b/block/partitions/aix.c @@ -208,7 +208,14 @@ int aix_partition(struct parsed_partitions *state) if (n) { int foundlvs = 0; - for (i = 0; foundlvs < numlvs && i < state->limit; i += 1) { + /* + * The lvd array was read as a single sector; only the + * struct lvd entries that fit in it are valid. Bound the + * scan so an on-disk numlvs larger than that cannot walk + * the read buffer out of bounds. + */ + for (i = 0; foundlvs < numlvs && i < state->limit && + i < SECTOR_SIZE / (int)sizeof(struct lvd); i++) { lvip[i].pps_per_lv = be16_to_cpu(p[i].num_lps); if (lvip[i].pps_per_lv) foundlvs += 1; From daff723f2d4c618d5f9186a990f47bf75878abeb Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 14 Jul 2026 18:37:39 +0930 Subject: [PATCH 383/479] block: do not warn when doing greedy allocation in folio_alloc_greedy() During one of my local btrfs fstests runs, folio_alloc() inside folio_alloc_greedy() triggered an allocation failure report when trying to allocate an order-4 folio. The kernel is from the latest development branch, which is utilizing the IOMAP_DIO_BOUNCE flag for direct writes when the inode requires checksum. Unfortunately I didn't save the full log, only the function and the order. When the IOMAP_DIO_BOUNCE flag is utilized, we will hit the following call chain: bio_iov_iter_bounce_write() |- folio_alloc_greedy() |- folio_alloc(gfp | __GFP_NORETRY, get_order(*size)); However __GFP_NORETRY will still emit an allocation failure report when it fails. And folio_alloc_greedy() will retry with a smaller order anyway, there is no point in emitting that allocation failure report. Append the __GFP_NOWARN flag to folio_alloc() for the larger-order folio attempts. Fixes: 8dd5e7c75d7b ("block: add helpers to bounce buffer an iov_iter into bios") Signed-off-by: Qu Wenruo Link: https://patch.msgid.link/d10571445ee505d95ba6eaad7558fc1f556d2921.1784020005.git.wqu@suse.com Signed-off-by: Jens Axboe --- block/bio.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/block/bio.c b/block/bio.c index f2a5f4d0a967..5ac954c70dd0 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1285,7 +1285,8 @@ static struct folio *folio_alloc_greedy(gfp_t gfp, size_t *size, struct folio *folio; while (*size > minsize) { - folio = folio_alloc(gfp | __GFP_NORETRY, get_order(*size)); + folio = folio_alloc(gfp | __GFP_NORETRY | __GFP_NOWARN, + get_order(*size)); if (folio) return folio; *size = rounddown_pow_of_two(*size - 1); From 25f744ffa0c8e799e06250ce2e618367b166b0d4 Mon Sep 17 00:00:00 2001 From: Nikunj A Dadhania Date: Wed, 15 Jul 2026 06:35:06 +0000 Subject: [PATCH 384/479] KVM: SVM: Bump asid_generation on CPU online to avoid ASID collision after hotplug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a vCPU stays scheduled out (or blocked) while the last pCPU it ran on goes through a hotplug cycle (online->offline->online), and the vCPU then resumes execution on the same pCPU, then it is possible for it to run with an ASID that has now been assigned to a different vCPU, resulting in stale TLB translations being used. svm_enable_virtualization_cpu() resets asid_generation to 1 and sets next_asid to max_asid + 1 on every CPU online event, including hotplug cycles. Because next_asid starts beyond the pool boundary, the first call to new_asid() after an online event always wraps the pool, incrementing asid_generation to 2 and assigning ASIDs starting from min_asid. Consider two vCPUs from different VMs, vCPU-A pinned to CPU-X holding asid_generation=2 and ASID=N from before the hotplug event: 1. CPU-X goes offline and back online: asid_generation resets to 1, next_asid = max_asid + 1. 2. One or more vCPUs migrate to CPU-X and call new_asid(), wrapping the pool and consuming ASIDs starting from min_asid. Eventually vCPU-B from a different VM is assigned asid_generation=2, ASID=N — the same ASID that vCPU-A held before the hotplug. 3. vCPU-A enters pre_svm_run() on CPU-X: current_vmcb->cpu is unchanged so the migration branch is skipped. Its saved asid_generation=2 matches sd->asid_generation=2, so the generation check silently passes and vCPU-A continues running with ASID=N — the same ASID just freshly assigned to vCPU-B. Both vCPUs from different VMs now run on CPU-X with the same ASID, causing them to share NPT TLB entries and producing stale translations. The collision manifests as a KVM internal error (Suberror: 1, emulation failure). The NPT page fault reports a faulting GPA far outside the VM's physical memory range — a sign of stale TLB translations being used. KVM falls back to instruction emulation, which fails on FPU/XSave instructions (XRSTOR, STMXCSR) that the emulator does not implement. Fix this by incrementing asid_generation instead of resetting it to 1 in svm_enable_virtualization_cpu(). On module load, asid_generation starts at 0 (memset) and the increment produces 1, identical to the old behaviour. On subsequent hotplug cycles the generation advances beyond any value a vCPU previously observed on this CPU, so the generation check in pre_svm_run() reliably forces new_asid() on every vCPU after every hotplug cycle. Fixes: 774c47f1d78e ("[PATCH] KVM: cpu hotplug support") Reported-by: Chandrakanth Silveru Tested-by: Srikanth Aithal Reviewed-by: K Prateek Nayak Reviewed-by: Tom Lendacky Signed-off-by: Nikunj A Dadhania Message-ID: <20260715063506.672432-1-nikunj@amd.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/svm.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index 4d2bacd00ec4..d0971685034b 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -571,7 +571,12 @@ static int svm_enable_virtualization_cpu(void) return r; sd = per_cpu_ptr(&svm_data, me); - sd->asid_generation = 1; + /* + * Bump the current asid_generation value to ensure any vCPU that + * previously ran on this CPU sees a stale generation and is forced + * to acquire a new ASID, preventing a latent ASID collision. + */ + sd->asid_generation++; sd->max_asid = cpuid_ebx(SVM_CPUID_FUNC) - 1; sd->next_asid = sd->max_asid + 1; sd->min_asid = max_sev_asid + 1; From e4e8af62adab2fdcca230006f829407a953070cd Mon Sep 17 00:00:00 2001 From: Shuhao Fu Date: Thu, 7 May 2026 10:22:26 +0200 Subject: [PATCH 385/479] can: j1939: fix lockless local-destination check j1939_priv.ents[].nusers is documented as protected by priv->lock, and its updates already happen under that lock. j1939_can_recv() also reads it under read_lock_bh(). However, j1939_session_skb_queue() and j1939_tp_send() still read priv->ents[da].nusers without taking the lock. Those transport-side checks decide whether to set J1939_ECU_LOCAL_DST, so they can race with j1939_local_ecu_get() and j1939_local_ecu_put() while userspace is binding or releasing sockets concurrently with TP traffic. This can misclassify TP/ETP sessions as local or remote and take the wrong transport path. Fix both transport paths by routing the destination-locality check through a helper that reads ents[].nusers under read_lock_bh(&priv->lock). Fixes: 9d71dd0c7009 ("can: add support of SAE J1939 protocol") Signed-off-by: Shuhao Fu Tested-by: Oleksij Rempel Acked-by: Oleksij Rempel Link: https://patch.msgid.link/20260419140614.GA4041240@chcpu16 Signed-off-by: Marc Kleine-Budde --- net/can/j1939/transport.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c index df93d57907da..8a31cb23bc76 100644 --- a/net/can/j1939/transport.c +++ b/net/can/j1939/transport.c @@ -351,6 +351,18 @@ static void j1939_session_skb_drop_old(struct j1939_session *session) } } +static bool j1939_address_is_local(struct j1939_priv *priv, u8 addr) +{ + bool local = false; + + read_lock_bh(&priv->lock); + if (j1939_address_is_unicast(addr) && priv->ents[addr].nusers) + local = true; + read_unlock_bh(&priv->lock); + + return local; +} + void j1939_session_skb_queue(struct j1939_session *session, struct sk_buff *skb) { @@ -359,8 +371,7 @@ void j1939_session_skb_queue(struct j1939_session *session, j1939_ac_fixup(priv, skb); - if (j1939_address_is_unicast(skcb->addr.da) && - priv->ents[skcb->addr.da].nusers) + if (j1939_address_is_local(priv, skcb->addr.da)) skcb->flags |= J1939_ECU_LOCAL_DST; skcb->flags |= J1939_ECU_LOCAL_SRC; @@ -2038,8 +2049,7 @@ struct j1939_session *j1939_tp_send(struct j1939_priv *priv, return ERR_PTR(ret); /* fix DST flags, it may be used there soon */ - if (j1939_address_is_unicast(skcb->addr.da) && - priv->ents[skcb->addr.da].nusers) + if (j1939_address_is_local(priv, skcb->addr.da)) skcb->flags |= J1939_ECU_LOCAL_DST; /* src is always local, I'm sending ... */ From d83762005c13269fffa51620399fc8d09095c4f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Grosjean?= Date: Thu, 7 May 2026 10:22:27 +0200 Subject: [PATCH 386/479] can: peak: Modification of references to email accounts being deleted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following the sale of PEAK-System France by HMS-Networks, this update is intended to change all my @hms-networks.com email addresses to my new @peak-system.fr address. Signed-off-by: Stéphane Grosjean Link: https://patch.msgid.link/20260410124251.40506-1-stephane.grosjean@free.fr Signed-off-by: Marc Kleine-Budde --- .mailmap | 4 ++-- drivers/net/can/peak_canfd/peak_canfd.c | 2 +- drivers/net/can/peak_canfd/peak_canfd_user.h | 2 +- drivers/net/can/peak_canfd/peak_pciefd_main.c | 4 ++-- drivers/net/can/sja1000/peak_pci.c | 4 ++-- drivers/net/can/sja1000/peak_pcmcia.c | 4 ++-- drivers/net/can/usb/peak_usb/pcan_usb.c | 2 +- drivers/net/can/usb/peak_usb/pcan_usb_core.c | 4 ++-- drivers/net/can/usb/peak_usb/pcan_usb_core.h | 2 +- drivers/net/can/usb/peak_usb/pcan_usb_fd.c | 2 +- drivers/net/can/usb/peak_usb/pcan_usb_pro.c | 2 +- drivers/net/can/usb/peak_usb/pcan_usb_pro.h | 2 +- include/linux/can/dev/peak_canfd.h | 2 +- 13 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.mailmap b/.mailmap index 12f3acdebd72..918fcd6575b1 100644 --- a/.mailmap +++ b/.mailmap @@ -825,8 +825,8 @@ Sriram Yagnaraman Stanislav Fomichev Stanislav Fomichev Stefan Wahren -Stéphane Grosjean -Stéphane Grosjean +Stéphane Grosjean +Stéphane Grosjean Stéphane Witzmann Stephen Hemminger Stephen Hemminger diff --git a/drivers/net/can/peak_canfd/peak_canfd.c b/drivers/net/can/peak_canfd/peak_canfd.c index 06cb2629f66a..4fd1aefb780f 100644 --- a/drivers/net/can/peak_canfd/peak_canfd.c +++ b/drivers/net/can/peak_canfd/peak_canfd.c @@ -2,7 +2,7 @@ /* Copyright (C) 2007, 2011 Wolfgang Grandegger * * Copyright (C) 2016-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #include diff --git a/drivers/net/can/peak_canfd/peak_canfd_user.h b/drivers/net/can/peak_canfd/peak_canfd_user.h index 60c6542028cf..dc0ecb566a85 100644 --- a/drivers/net/can/peak_canfd/peak_canfd_user.h +++ b/drivers/net/can/peak_canfd/peak_canfd_user.h @@ -2,7 +2,7 @@ /* CAN driver for PEAK System micro-CAN based adapters * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #ifndef PEAK_CANFD_USER_H #define PEAK_CANFD_USER_H diff --git a/drivers/net/can/peak_canfd/peak_pciefd_main.c b/drivers/net/can/peak_canfd/peak_pciefd_main.c index 93558e33bc02..7c749301ea84 100644 --- a/drivers/net/can/peak_canfd/peak_pciefd_main.c +++ b/drivers/net/can/peak_canfd/peak_pciefd_main.c @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_pci.c: * * Copyright (C) 2001-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #include @@ -19,7 +19,7 @@ #include "peak_canfd_user.h" -MODULE_AUTHOR("Stéphane Grosjean "); +MODULE_AUTHOR("Stéphane Grosjean "); MODULE_DESCRIPTION("Socket-CAN driver for PEAK PCAN PCIe/M.2 FD family cards"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/sja1000/peak_pci.c b/drivers/net/can/sja1000/peak_pci.c index 4cc4a1581dd1..69c61ccf621d 100644 --- a/drivers/net/can/sja1000/peak_pci.c +++ b/drivers/net/can/sja1000/peak_pci.c @@ -5,7 +5,7 @@ * Derived from the PCAN project file driver/src/pcan_pci.c: * * Copyright (C) 2001-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #include @@ -22,7 +22,7 @@ #include "sja1000.h" -MODULE_AUTHOR("Stéphane Grosjean "); +MODULE_AUTHOR("Stéphane Grosjean "); MODULE_DESCRIPTION("Socket-CAN driver for PEAK PCAN PCI family cards"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/sja1000/peak_pcmcia.c b/drivers/net/can/sja1000/peak_pcmcia.c index 42a77d435b39..c3c2aa21da47 100644 --- a/drivers/net/can/sja1000/peak_pcmcia.c +++ b/drivers/net/can/sja1000/peak_pcmcia.c @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_pccard.c * * Copyright (C) 2006-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #include #include @@ -19,7 +19,7 @@ #include #include "sja1000.h" -MODULE_AUTHOR("Stéphane Grosjean "); +MODULE_AUTHOR("Stéphane Grosjean "); MODULE_DESCRIPTION("CAN driver for PEAK-System PCAN-PC Cards"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/usb/peak_usb/pcan_usb.c b/drivers/net/can/usb/peak_usb/pcan_usb.c index 9278a1522aae..8fd058c32856 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb.c @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_usb.c * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean * * Many thanks to Klaus Hitschler */ diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_core.c b/drivers/net/can/usb/peak_usb/pcan_usb_core.c index cf48bb26d46d..c7933d1acc99 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_core.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb_core.c @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_usb_core.c * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean * * Many thanks to Klaus Hitschler */ @@ -24,7 +24,7 @@ #include "pcan_usb_core.h" -MODULE_AUTHOR("Stéphane Grosjean "); +MODULE_AUTHOR("Stéphane Grosjean "); MODULE_DESCRIPTION("CAN driver for PEAK-System USB adapters"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_core.h b/drivers/net/can/usb/peak_usb/pcan_usb_core.h index d1c1897d47b9..65999f04f4b7 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_core.h +++ b/drivers/net/can/usb/peak_usb/pcan_usb_core.h @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_usb_core.c * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean * * Many thanks to Klaus Hitschler */ diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c index eb4f5884ad73..ef9fd693e9bd 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c @@ -3,7 +3,7 @@ * CAN driver for PEAK System PCAN-USB FD / PCAN-USB Pro FD adapter * * Copyright (C) 2013-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #include #include diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_pro.c b/drivers/net/can/usb/peak_usb/pcan_usb_pro.c index 4bfa8d0fbb32..aefcded8e12a 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_pro.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb_pro.c @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_usbpro.c * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #include #include diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_pro.h b/drivers/net/can/usb/peak_usb/pcan_usb_pro.h index 162c7546d3a8..d669c9e610c7 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_pro.h +++ b/drivers/net/can/usb/peak_usb/pcan_usb_pro.h @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_usbpro_fw.h * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #ifndef PCAN_USB_PRO_H #define PCAN_USB_PRO_H diff --git a/include/linux/can/dev/peak_canfd.h b/include/linux/can/dev/peak_canfd.h index d3788a3d0942..056e0efa649f 100644 --- a/include/linux/can/dev/peak_canfd.h +++ b/include/linux/can/dev/peak_canfd.h @@ -3,7 +3,7 @@ * CAN driver for PEAK System micro-CAN based adapters * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #ifndef PUCAN_H #define PUCAN_H From 68973f9db76144825e4f35dfdc80fb8279eb2d57 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Tue, 14 Jul 2026 18:55:23 +0200 Subject: [PATCH 387/479] can: bcm: defer rx_op deallocation to workqueue to fix thrtimer UAF Commit f1b4e32aca08 ("can: bcm: use call_rcu() instead of costly synchronize_rcu()") replaced synchronize_rcu() in bcm_delete_rx_op() with call_rcu() and introduced the RX_NO_AUTOTIMER flag. However, this flag check was omitted for thrtimer in the packet rx fast-path. During BCM RX operation teardown, a concurrent RCU reader (bcm_rx_handler) can race and re-arm thrtimer via bcm_rx_update_and_send() after call_rcu() has been scheduled. Once the RCU grace period elapses, bcm_op is freed. The subsequently firing thrtimer then dereferences the deallocated op, causing a UAF. Adding flag checks to the rx fast-path (bcm_rx_update_and_send) does not fully close the TOCTOU race and introduces latency for every CAN frame. Conversely, calling hrtimer_cancel() directly inside the RCU callback (softirq context) is fatal as hrtimer_cancel() can sleep, triggering a "scheduling while atomic" panic. Resolve this by deferring the timer cancellation and memory free to a dedicated unbound workqueue (bcm_wq). The RCU callback now queues a work item to bcm_wq, which safely cancels both timers and deallocates memory in sleepable process context. A dedicated workqueue is used to prevent system-wide WQ saturation and is cleanly flushed/destroyed on module unload to avoid rmmod page faults. Since the deferred work can now outlive the calling context by an unbounded amount, also take a reference on op->sk when it is assigned and drop it only once the deferred work has cancelled both timers, so a socket can no longer be freed out from under a still-armed timer whose callback (bcm_send_to_user()) dereferences op->sk. Fixes: f1b4e32aca08 ("can: bcm: use call_rcu() instead of costly synchronize_rcu()") Tested-by: Feng Xue Tested-by: Oliver Hartkopp Signed-off-by: Lee Jones Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260714-bcm_fixes-v15-1-562f7e3e42da@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/bcm.c | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/net/can/bcm.c b/net/can/bcm.c index a4bef2c48a55..bdf53241bd7b 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -58,6 +58,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,8 @@ MODULE_ALIAS("can-proto-2"); #define BCM_MIN_NAMELEN CAN_REQUIRED_SIZE(struct sockaddr_can, can_ifindex) +static struct workqueue_struct *bcm_wq; + /* * easy access to the first 64 bit of can(fd)_frame payload. cp->data is * 64 bit aligned so the offset has to be multiples of 8 which is ensured @@ -105,6 +108,7 @@ static inline u64 get_u64(const struct canfd_frame *cp, int offset) struct bcm_op { struct list_head list; struct rcu_head rcu; + struct work_struct work; int ifindex; canid_t can_id; u32 flags; @@ -793,9 +797,12 @@ static struct bcm_op *bcm_find_op(struct list_head *ops, return NULL; } -static void bcm_free_op_rcu(struct rcu_head *rcu_head) +static void bcm_free_op_work(struct work_struct *work) { - struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu); + struct bcm_op *op = container_of(work, struct bcm_op, work); + + hrtimer_cancel(&op->timer); + hrtimer_cancel(&op->thrtimer); if ((op->frames) && (op->frames != &op->sframe)) kfree(op->frames); @@ -803,9 +810,23 @@ static void bcm_free_op_rcu(struct rcu_head *rcu_head) if ((op->last_frames) && (op->last_frames != &op->last_sframe)) kfree(op->last_frames); + /* the last possible access to op->timer/op->thrtimer has now + * happened above via hrtimer_cancel() - op->sk is no longer + * needed by any pending timer callback, so drop our reference + */ + sock_put(op->sk); + kfree(op); } +static void bcm_free_op_rcu(struct rcu_head *rcu_head) +{ + struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu); + + INIT_WORK(&op->work, bcm_free_op_work); + queue_work(bcm_wq, &op->work); +} + static void bcm_remove_op(struct bcm_op *op) { hrtimer_cancel(&op->timer); @@ -1060,6 +1081,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, /* bcm_can_tx / bcm_tx_timeout_handler needs this */ op->sk = sk; + sock_hold(sk); op->ifindex = ifindex; /* initialize uninitialized (kzalloc) structure */ @@ -1221,6 +1243,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, /* bcm_can_tx / bcm_tx_timeout_handler needs this */ op->sk = sk; + sock_hold(sk); op->ifindex = ifindex; /* ifindex for timeout events w/o previous frame reception */ @@ -1839,11 +1862,15 @@ static int __init bcm_module_init(void) { int err; + bcm_wq = alloc_workqueue("can-bcm-wq", WQ_UNBOUND, 0); + if (!bcm_wq) + return -ENOMEM; + pr_info("can: broadcast manager protocol\n"); err = register_pernet_subsys(&canbcm_pernet_ops); if (err) - return err; + goto register_pernet_failed; err = register_netdevice_notifier(&canbcm_notifier); if (err) @@ -1861,6 +1888,8 @@ static int __init bcm_module_init(void) unregister_netdevice_notifier(&canbcm_notifier); register_notifier_failed: unregister_pernet_subsys(&canbcm_pernet_ops); +register_pernet_failed: + destroy_workqueue(bcm_wq); return err; } @@ -1869,6 +1898,8 @@ static void __exit bcm_module_exit(void) can_proto_unregister(&bcm_can_proto); unregister_netdevice_notifier(&canbcm_notifier); unregister_pernet_subsys(&canbcm_pernet_ops); + rcu_barrier(); + destroy_workqueue(bcm_wq); } module_init(bcm_module_init); From d9b091d9d22fee81ec53fb55d2032951993ceadb Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Tue, 14 Jul 2026 18:55:24 +0200 Subject: [PATCH 388/479] can: bcm: fix lockless bound/ifindex race and silent RX_SETUP failure bcm_sendmsg() reads bo->ifindex and checks bo->bound before taking lock_sock(), while bcm_notify(), bcm_connect() and bcm_release() all mutate both fields under that same lock. Because the lockless reads and the locked writes are unordered with respect to each other, a racing bcm_notify() (device unregister) or bcm_connect() (concurrent bind on another thread sharing the socket) can make bcm_sendmsg() observe an inconsistent combination, e.g. a stale bound=1 together with the now-cleared ifindex=0, silently turning a socket bound to a specific CAN interface into one that also matches "any" interface. Keep the lockless bo->bound check purely as a fast-path reject, and move the ifindex read (and a bo->bound re-check) into the locked section, where every writer already serializes. This removes the possibility of observing the two fields torn against each other, rather than trying to fix it with more READ_ONCE()/WRITE_ONCE() pairs on two independently updated fields. Annotate the now-purely-lockless bo->bound accesses consistently across all its write sites. Also fix bcm_rx_setup() silently returning success when the target device disappears concurrently instead of reporting -ENODEV, so a broken RX op is no longer left registered as if it had succeeded. Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") Reported-by: Ginger Closes: https://lore.kernel.org/linux-can/CAGp+u1aBK8QVjsvAxM2Ldzep4rEbsP9x_pV3At4g=h1kVEtyhA@mail.gmail.com/ Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260714-bcm_fixes-v15-2-562f7e3e42da@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/bcm.c | 65 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/net/can/bcm.c b/net/can/bcm.c index bdf53241bd7b..b612135b017d 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -1323,6 +1323,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, op->rx_reg_dev = dev; dev_put(dev); + } else { + /* the requested device is gone - do not + * silently succeed without registering + */ + err = -ENODEV; } } else @@ -1396,12 +1401,13 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) { struct sock *sk = sock->sk; struct bcm_sock *bo = bcm_sk(sk); - int ifindex = bo->ifindex; /* default ifindex for this bcm_op */ + int ifindex; struct bcm_msg_head msg_head; int cfsiz; int ret; /* read bytes or error codes as return value */ - if (!bo->bound) + /* Lockless fast-path check for bound socket */ + if (!READ_ONCE(bo->bound)) return -ENOTCONN; /* check for valid message length from userspace */ @@ -1417,17 +1423,38 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) if ((size - MHSIZ) % cfsiz) return -EINVAL; + lock_sock(sk); + + /* Re-validate under the socket lock: a concurrent bcm_notify() + * may have unbound this socket (device removal) after the + * lockless fast-path check above. bo->ifindex is only ever + * mutated under lock_sock(), so reading it here - instead of + * before taking the lock - guarantees it can't be observed + * torn against bo->bound. + */ + if (!bo->bound) { + ret = -ENOTCONN; + goto out_release; + } + + /* default ifindex for this bcm_op */ + ifindex = bo->ifindex; + /* check for alternative ifindex for this bcm_op */ if (!ifindex && msg->msg_name) { /* no bound device as default => check msg_name */ DECLARE_SOCKADDR(struct sockaddr_can *, addr, msg->msg_name); - if (msg->msg_namelen < BCM_MIN_NAMELEN) - return -EINVAL; + if (msg->msg_namelen < BCM_MIN_NAMELEN) { + ret = -EINVAL; + goto out_release; + } - if (addr->can_family != AF_CAN) - return -EINVAL; + if (addr->can_family != AF_CAN) { + ret = -EINVAL; + goto out_release; + } /* ifindex from sendto() */ ifindex = addr->can_ifindex; @@ -1436,20 +1463,21 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) struct net_device *dev; dev = dev_get_by_index(sock_net(sk), ifindex); - if (!dev) - return -ENODEV; + if (!dev) { + ret = -ENODEV; + goto out_release; + } if (dev->type != ARPHRD_CAN) { dev_put(dev); - return -ENODEV; + ret = -ENODEV; + goto out_release; } dev_put(dev); } } - lock_sock(sk); - switch (msg_head.opcode) { case TX_SETUP: @@ -1499,6 +1527,7 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) break; } +out_release: release_sock(sk); return ret; @@ -1535,7 +1564,12 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, bo->bcm_proc_read = NULL; } #endif - bo->bound = 0; + /* Paired with the lockless fast-path check in + * bcm_sendmsg(); bo->ifindex itself is only ever + * accessed under lock_sock() so it needs no + * annotation. + */ + WRITE_ONCE(bo->bound, 0); bo->ifindex = 0; notify_enodev = 1; } @@ -1676,7 +1710,7 @@ static int bcm_release(struct socket *sock) /* remove device reference */ if (bo->bound) { - bo->bound = 0; + WRITE_ONCE(bo->bound, 0); bo->ifindex = 0; } @@ -1746,7 +1780,10 @@ static int bcm_connect(struct socket *sock, struct sockaddr_unsized *uaddr, int } #endif /* CONFIG_PROC_FS */ - bo->bound = 1; + /* bo->ifindex above is fully assigned before this point; pairs + * with the lockless fast-path check in bcm_sendmsg() + */ + WRITE_ONCE(bo->bound, 1); fail: release_sock(sk); From 749179c2e25b95d22499ed29096b3e02d6dfd2b4 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Tue, 14 Jul 2026 18:55:25 +0200 Subject: [PATCH 389/479] can: bcm: add locking when updating filter and timer values KCSAN detected a simultaneous access to timer values that can be overwritten in bcm_rx_setup() when updating timer and filter content while bcm_rx_handler(), bcm_rx_timeout_handler() or bcm_rx_thr_handler() run concurrently on incoming CAN traffic. Protect the timer (ival1/ival2/kt_ival1/kt_ival2/kt_lastmsg) and filter (nframes/flags/frames/last_frames) updates in bcm_rx_setup() with a new per-op bcm_rx_update_lock, taken with the matching scope in the RX handlers. memcpy_from_msg() is staged into a temporary buffer before the lock is taken, since it can sleep and must not run under a spinlock. hrtimer_cancel() is always called without bcm_rx_update_lock held, since bcm_rx_timeout_handler()/bcm_rx_thr_handler() take the same lock and a running callback would otherwise deadlock against the canceller. Also close a related race: bcm_rx_setup() cleared the RTR flag in the stored reply frame's can_id as a separate, unprotected step after the frame content was already installed, so a concurrent bcm_rx_handler() could transmit a stale reply with CAN_RTR_FLAG still set. Fold that normalization into the initial frame preparation instead (on the staged buffer for updates, directly on op->frames pre-registration for new ops), so the installed frame is always atomically self-consistent. bcm_rx_handler()'s RX_RTR_FRAME check now takes a lock-protected snapshot of op->flags before deciding whether to call bcm_can_tx(), but does not hold the lock across that call. Also take a lock-protected snapshot of the currframe in bcm_can_tx() to avoid partly overwrites by content updates in bcm_tx_setup(). Finally check if a TX_RESET_MULTI_IDX/SETTIMER might have reset op->currframe between the two locked sections in bcm_can_tx(). Omit calling hrtimer_forward() with zero interval in bcm_rx_thr_handler(). kt_ival2 may have been concurrently cleared by bcm_rx_setup() before it cancels this timer, so check kt_ival2 inside the bcm_rx_update_lock. Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates") Reported-by: syzbot+75e5e4ae00c3b4bb544e@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-can/6975d5cf.a00a0220.33ccc7.0022.GAE@google.com/ Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260714-bcm_fixes-v15-3-562f7e3e42da@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/bcm.c | 180 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 135 insertions(+), 45 deletions(-) diff --git a/net/can/bcm.c b/net/can/bcm.c index b612135b017d..1e5f8d65d351 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -129,6 +129,7 @@ struct bcm_op { struct sock *sk; struct net_device *rx_reg_dev; spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */ + spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */ }; struct bcm_sock { @@ -293,22 +294,28 @@ static int bcm_proc_show(struct seq_file *m, void *v) * bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface * of the given bcm tx op */ -static void bcm_can_tx(struct bcm_op *op) +static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf) { struct sk_buff *skb; struct can_skb_ext *csx; struct net_device *dev; - struct canfd_frame *cf; + struct canfd_frame cframe; + bool cyclic = !cf; + unsigned int idx = 0; int err; /* no target device? => exit */ if (!op->ifindex) return; - /* read currframe under lock protection */ - spin_lock_bh(&op->bcm_tx_lock); - cf = op->frames + op->cfsiz * op->currframe; - spin_unlock_bh(&op->bcm_tx_lock); + if (cyclic) { + /* read currframe under lock protection */ + spin_lock_bh(&op->bcm_tx_lock); + idx = op->currframe; + memcpy(&cframe, op->frames + op->cfsiz * idx, op->cfsiz); + cf = &cframe; + spin_unlock_bh(&op->bcm_tx_lock); + } dev = dev_get_by_index(sock_net(op->sk), op->ifindex); if (!dev) { @@ -341,14 +348,20 @@ static void bcm_can_tx(struct bcm_op *op) if (!err) op->frames_abs++; - op->currframe++; + /* only advance the cyclic sequence if nothing reset currframe while + * we were sending - a concurrent TX_RESET_MULTI_IDX means this + * frame's bookkeeping belongs to a sequence that no longer exists + */ + if (!cyclic || op->currframe == idx) { + op->currframe++; - /* reached last frame? */ - if (op->currframe >= op->nframes) - op->currframe = 0; + /* reached last frame? */ + if (op->currframe >= op->nframes) + op->currframe = 0; - if (op->count > 0) - op->count--; + if (op->count > 0) + op->count--; + } spin_unlock_bh(&op->bcm_tx_lock); out: @@ -461,7 +474,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) struct bcm_msg_head msg_head; if (op->kt_ival1 && (op->count > 0)) { - bcm_can_tx(op); + bcm_can_tx(op, NULL); if (!op->count && (op->flags & TX_COUNTEVT)) { /* create notification to user */ @@ -478,7 +491,7 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) } } else if (op->kt_ival2) { - bcm_can_tx(op); + bcm_can_tx(op, NULL); } return bcm_tx_set_expiry(op, &op->timer) ? @@ -622,6 +635,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer) struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer); struct bcm_msg_head msg_head; + spin_lock_bh(&op->bcm_rx_update_lock); + /* if user wants to be informed, when cyclic CAN-Messages come back */ if ((op->flags & RX_ANNOUNCE_RESUME) && op->last_frames) { /* clear received CAN frames to indicate 'nothing received' */ @@ -638,6 +653,8 @@ static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer) msg_head.can_id = op->can_id; msg_head.nframes = 0; + spin_unlock_bh(&op->bcm_rx_update_lock); + bcm_send_to_user(op, &msg_head, NULL, 0); return HRTIMER_NORESTART; @@ -686,15 +703,26 @@ static int bcm_rx_thr_flush(struct bcm_op *op) static enum hrtimer_restart bcm_rx_thr_handler(struct hrtimer *hrtimer) { struct bcm_op *op = container_of(hrtimer, struct bcm_op, thrtimer); + enum hrtimer_restart ret; - if (bcm_rx_thr_flush(op)) { + spin_lock_bh(&op->bcm_rx_update_lock); + + /* kt_ival2 may have been concurrently cleared by bcm_rx_setup() + * before it cancels this timer - never forward with a zero + * interval in that case. + */ + if (bcm_rx_thr_flush(op) && op->kt_ival2) { hrtimer_forward_now(hrtimer, op->kt_ival2); - return HRTIMER_RESTART; + ret = HRTIMER_RESTART; } else { /* rearm throttle handling */ op->kt_lastmsg = 0; - return HRTIMER_NORESTART; + ret = HRTIMER_NORESTART; } + + spin_unlock_bh(&op->bcm_rx_update_lock); + + return ret; } /* @@ -704,8 +732,10 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) { struct bcm_op *op = (struct bcm_op *)data; const struct canfd_frame *rxframe = (struct canfd_frame *)skb->data; + struct canfd_frame rtrframe; unsigned int i; unsigned char traffic_flags; + bool rtr_frame; if (op->can_id != rxframe->can_id) return; @@ -729,9 +759,18 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) /* update statistics */ op->frames_abs++; - if (op->flags & RX_RTR_FRAME) { + /* snapshot the flag under lock: op->flags/op->frames may be updated + * concurrently by bcm_rx_setup(). + */ + spin_lock_bh(&op->bcm_rx_update_lock); + rtr_frame = op->flags & RX_RTR_FRAME; + if (rtr_frame) + memcpy(&rtrframe, op->frames, op->cfsiz); + spin_unlock_bh(&op->bcm_rx_update_lock); + + if (rtr_frame) { /* send reply for RTR-request (placed in op->frames[0]) */ - bcm_can_tx(op); + bcm_can_tx(op, &rtrframe); return; } @@ -743,6 +782,8 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) traffic_flags |= RX_OWN; } + spin_lock_bh(&op->bcm_rx_update_lock); + if (op->flags & RX_FILTER_ID) { /* the easiest case */ bcm_rx_update_and_send(op, op->last_frames, rxframe, @@ -778,6 +819,8 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) rx_starttimer: bcm_rx_starttimer(op); + + spin_unlock_bh(&op->bcm_rx_update_lock); } /* @@ -1116,7 +1159,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, } if (op->flags & TX_ANNOUNCE) - bcm_can_tx(op); + bcm_can_tx(op, NULL); if (op->flags & STARTTIMER) bcm_tx_start_timer(op); @@ -1130,6 +1173,24 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, return err; } +static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, + struct bcm_op *op, void *new_frames) +{ + /* funny feature in RX(!)_SETUP only for RTR-mode: + * copy can_id into frame BUT without RTR-flag to + * prevent a full-load-loopback-test ... ;-] + * normalize this on the staged buffer, before it is + * ever installed into op->frames. + */ + if (msg_head->flags & RX_RTR_FRAME) { + struct canfd_frame *frame0 = new_frames; + + if ((msg_head->flags & TX_CP_CAN_ID) || + frame0->can_id == op->can_id) + frame0->can_id = op->can_id & ~CAN_RTR_FLAG; + } +} + /* * bcm_rx_setup - create or update a bcm rx op (for bcm_sendmsg) */ @@ -1164,6 +1225,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, /* check the given can_id */ op = bcm_find_op(&bo->rx_ops, msg_head, ifindex); if (op) { + void *new_frames = NULL; + /* update existing BCM operation */ /* @@ -1175,19 +1238,48 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, return -E2BIG; if (msg_head->nframes) { - /* update CAN frames content */ - err = memcpy_from_msg(op->frames, msg, - msg_head->nframes * op->cfsiz); - if (err < 0) - return err; + /* get new CAN frames content before locking */ + new_frames = kmalloc(msg_head->nframes * op->cfsiz, + GFP_KERNEL); + if (!new_frames) + return -ENOMEM; - /* clear last_frames to indicate 'nothing received' */ - memset(op->last_frames, 0, msg_head->nframes * op->cfsiz); + err = memcpy_from_msg(new_frames, msg, + msg_head->nframes * op->cfsiz); + if (err < 0) { + kfree(new_frames); + return err; + } + + bcm_rx_setup_rtr_check(msg_head, op, new_frames); } + spin_lock_bh(&op->bcm_rx_update_lock); op->nframes = msg_head->nframes; op->flags = msg_head->flags; + if (msg_head->nframes) { + /* update CAN frames content */ + memcpy(op->frames, new_frames, + msg_head->nframes * op->cfsiz); + + /* clear last_frames to indicate 'nothing received' */ + memset(op->last_frames, 0, + msg_head->nframes * op->cfsiz); + } + + if (msg_head->flags & SETTIMER) { + op->ival1 = msg_head->ival1; + op->ival2 = msg_head->ival2; + op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); + op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); + op->kt_lastmsg = 0; + } + spin_unlock_bh(&op->bcm_rx_update_lock); + + /* free temporary frames / kfree(NULL) is safe */ + kfree(new_frames); + /* Only an update -> do not call can_rx_register() */ do_rx_register = 0; @@ -1198,6 +1290,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, return -ENOMEM; spin_lock_init(&op->bcm_tx_lock); + spin_lock_init(&op->bcm_rx_update_lock); op->can_id = msg_head->can_id; op->nframes = msg_head->nframes; op->cfsiz = CFSIZ(msg_head->flags); @@ -1239,6 +1332,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, kfree(op); return err; } + + bcm_rx_setup_rtr_check(msg_head, op, op->frames); } /* bcm_can_tx / bcm_tx_timeout_handler needs this */ @@ -1266,29 +1361,22 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, /* check flags */ if (op->flags & RX_RTR_FRAME) { - struct canfd_frame *frame0 = op->frames; - /* no timers in RTR-mode */ hrtimer_cancel(&op->thrtimer); hrtimer_cancel(&op->timer); - - /* - * funny feature in RX(!)_SETUP only for RTR-mode: - * copy can_id into frame BUT without RTR-flag to - * prevent a full-load-loopback-test ... ;-] - */ - if ((op->flags & TX_CP_CAN_ID) || - (frame0->can_id == op->can_id)) - frame0->can_id = op->can_id & ~CAN_RTR_FLAG; - } else { if (op->flags & SETTIMER) { - /* set timer value */ - op->ival1 = msg_head->ival1; - op->ival2 = msg_head->ival2; - op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); - op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); + /* set timers (locked) for newly created op */ + if (do_rx_register) { + spin_lock_bh(&op->bcm_rx_update_lock); + op->ival1 = msg_head->ival1; + op->ival2 = msg_head->ival2; + op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); + op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); + op->kt_lastmsg = 0; + spin_unlock_bh(&op->bcm_rx_update_lock); + } /* disable an active timer due to zero value? */ if (!op->kt_ival1) @@ -1298,9 +1386,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, * In any case cancel the throttle timer, flush * potentially blocked msgs and reset throttle handling */ - op->kt_lastmsg = 0; hrtimer_cancel(&op->thrtimer); + + spin_lock_bh(&op->bcm_rx_update_lock); bcm_rx_thr_flush(op); + spin_unlock_bh(&op->bcm_rx_update_lock); } if ((op->flags & STARTTIMER) && op->kt_ival1) From e6c24ba95fc3f1b5e1dcd28b1c6e59ef61a9daa5 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Tue, 14 Jul 2026 18:55:26 +0200 Subject: [PATCH 390/479] can: bcm: fix CAN frame rx/tx statistics KCSAN detected a data race within the bcm_rx_handler() when two CAN frames have been simultaneously received and processed in a single rx op by two different CPUs. Use atomic operations with (signed) long data types to access the statistics in the hot path to fix the KCSAN complaint. Additionally simplify the update and check of statistics overflow by using the atomic operations in separate bcm_update_[rx|tx]_stats() functions. The rx variant runs under bcm_rx_update_lock to prevent races when resetting the two rx counters; the tx variant runs under bcm_tx_lock and only needs to guard its own counter's overflow. As the rx path resets its values already at LONG_MAX / 100, there is no conflict between the two locking domains (bcm_rx_update_lock vs. bcm_tx_lock) even for ops that use both paths. The rx statistics update and the frames_filtered update in bcm_rx_changed() were previously performed in two separate bcm_rx_update_lock sections. For an rx op subscribed on all interfaces (ifindex == 0), bcm_rx_handler() can run concurrently on different CPUs, so a counter reset by one CPU between these two sections could leave frames_filtered larger than frames_abs on another CPU, producing a bogus (even negative) reduction percentage in procfs. Update the statistics in the same critical section as bcm_rx_changed() to close this gap, which also removes the now unneeded extra lock/unlock pair around the traffic_flags calculation. Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260714-bcm_fixes-v15-4-562f7e3e42da@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/bcm.c | 71 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/net/can/bcm.c b/net/can/bcm.c index 1e5f8d65d351..03c98e4cc677 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -112,7 +112,7 @@ struct bcm_op { int ifindex; canid_t can_id; u32 flags; - unsigned long frames_abs, frames_filtered; + atomic_long_t frames_abs, frames_filtered; struct bcm_timeval ival1, ival2; struct hrtimer timer, thrtimer; ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg; @@ -229,10 +229,13 @@ static int bcm_proc_show(struct seq_file *m, void *v) list_for_each_entry_rcu(op, &bo->rx_ops, list) { - unsigned long reduction; + long reduction, frames_filtered, frames_abs; + + frames_filtered = atomic_long_read(&op->frames_filtered); + frames_abs = atomic_long_read(&op->frames_abs); /* print only active entries & prevent division by zero */ - if (!op->frames_abs) + if (!frames_abs) continue; seq_printf(m, "rx_op: %03X %-5s ", op->can_id, @@ -254,9 +257,9 @@ static int bcm_proc_show(struct seq_file *m, void *v) (long long)ktime_to_us(op->kt_ival2)); seq_printf(m, "# recv %ld (%ld) => reduction: ", - op->frames_filtered, op->frames_abs); + frames_filtered, frames_abs); - reduction = 100 - (op->frames_filtered * 100) / op->frames_abs; + reduction = 100 - (frames_filtered * 100) / frames_abs; seq_printf(m, "%s%ld%%\n", (reduction == 100) ? "near " : "", reduction); @@ -280,7 +283,8 @@ static int bcm_proc_show(struct seq_file *m, void *v) seq_printf(m, "t2=%lld ", (long long)ktime_to_us(op->kt_ival2)); - seq_printf(m, "# sent %ld\n", op->frames_abs); + seq_printf(m, "# sent %ld\n", + atomic_long_read(&op->frames_abs)); } seq_putc(m, '\n'); @@ -290,6 +294,24 @@ static int bcm_proc_show(struct seq_file *m, void *v) } #endif /* CONFIG_PROC_FS */ +static void bcm_update_rx_stats(struct bcm_op *op) +{ + /* prevent overflow of the reduction% calculation in bcm_proc_show() */ + if (atomic_long_inc_return(&op->frames_abs) > LONG_MAX / 100) { + atomic_long_set(&op->frames_filtered, 0); + atomic_long_set(&op->frames_abs, 0); + } +} + +static void bcm_update_tx_stats(struct bcm_op *op) +{ + /* tx_op has no reduction% calculation - use the full range and + * just keep the displayed counter non-negative on overflow + */ + if (atomic_long_inc_return(&op->frames_abs) == LONG_MAX) + atomic_long_set(&op->frames_abs, 0); +} + /* * bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface * of the given bcm tx op @@ -346,7 +368,7 @@ static void bcm_can_tx(struct bcm_op *op, struct canfd_frame *cf) spin_lock_bh(&op->bcm_tx_lock); if (!err) - op->frames_abs++; + bcm_update_tx_stats(op); /* only advance the cyclic sequence if nothing reset currframe while * we were sending - a concurrent TX_RESET_MULTI_IDX means this @@ -505,12 +527,9 @@ static void bcm_rx_changed(struct bcm_op *op, struct canfd_frame *data) { struct bcm_msg_head head; - /* update statistics */ - op->frames_filtered++; - - /* prevent statistics overflow */ - if (op->frames_filtered > ULONG_MAX/100) - op->frames_filtered = op->frames_abs = 0; + /* update statistics (frames_filtered <= frames_abs) */ + if (atomic_long_read(&op->frames_abs)) + atomic_long_inc(&op->frames_filtered); /* this element is not throttled anymore */ data->flags &= ~RX_THR; @@ -756,24 +775,30 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) op->rx_stamp = skb->tstamp; /* save originator for recvfrom() */ op->rx_ifindex = skb->dev->ifindex; - /* update statistics */ - op->frames_abs++; - /* snapshot the flag under lock: op->flags/op->frames may be updated - * concurrently by bcm_rx_setup(). - */ + /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */ spin_lock_bh(&op->bcm_rx_update_lock); - rtr_frame = op->flags & RX_RTR_FRAME; - if (rtr_frame) - memcpy(&rtrframe, op->frames, op->cfsiz); - spin_unlock_bh(&op->bcm_rx_update_lock); + rtr_frame = op->flags & RX_RTR_FRAME; if (rtr_frame) { + bcm_update_rx_stats(op); + /* snapshot RTR content under lock */ + memcpy(&rtrframe, op->frames, op->cfsiz); + spin_unlock_bh(&op->bcm_rx_update_lock); + /* send reply for RTR-request (placed in op->frames[0]) */ bcm_can_tx(op, &rtrframe); return; } + /* update statistics in the same critical section as bcm_rx_changed() + * below: frames_filtered must never be checked/incremented against a + * frames_abs snapshot from a concurrent bcm_rx_handler() call on + * another CPU for the same (wildcard) op, or frames_filtered can end + * up larger than frames_abs. + */ + bcm_update_rx_stats(op); + /* compute flags to distinguish between own/local/remote CAN traffic */ traffic_flags = 0; if (skb->sk) { @@ -782,8 +807,6 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) traffic_flags |= RX_OWN; } - spin_lock_bh(&op->bcm_rx_update_lock); - if (op->flags & RX_FILTER_ID) { /* the easiest case */ bcm_rx_update_and_send(op, op->last_frames, rxframe, From 7b2c3eabc4dafc062a25e10711154f2107526a78 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Tue, 14 Jul 2026 18:55:27 +0200 Subject: [PATCH 391/479] can: bcm: add missing rcu list annotations and operations sashiko-bot remarked the missing use of list_add_rcu() in bcm_[rx|tx]_setup() to have a proper initialized bcm_op structure when bcm_proc_show() traverses the bcm_op's under rcu_read_lock(). To cover all initial settings of the bcm_op's the list_add_rcu() calls are moved to the end of the setup code. While at it, also fix the mirroring removal side: bcm_release() called bcm_remove_op() - which frees the op via call_rcu() - on ops that were still linked in bo->tx_ops/bo->rx_ops, without list_del_rcu() first. Unlink each op with list_del_rcu() before handing it to bcm_remove_op(), matching the existing pattern in bcm_delete_tx_op()/bcm_delete_rx_op(). Reported-by: sashiko-reviews@lists.linux.dev Closes: https://lore.kernel.org/linux-can/20260610094654.A1FFE1F00893@smtp.kernel.org/ Fixes: dac5e6249159 ("can: bcm: add missing rcu read protection for procfs content") Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260714-bcm_fixes-v15-5-562f7e3e42da@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/bcm.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/net/can/bcm.c b/net/can/bcm.c index 03c98e4cc677..5c1e83eeb4ff 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -265,7 +265,7 @@ static int bcm_proc_show(struct seq_file *m, void *v) (reduction == 100) ? "near " : "", reduction); } - list_for_each_entry(op, &bo->tx_ops, list) { + list_for_each_entry_rcu(op, &bo->tx_ops, list) { seq_printf(m, "tx_op: %03X %s ", op->can_id, bcm_proc_getifname(net, ifname, op->ifindex)); @@ -1017,6 +1017,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, struct bcm_sock *bo = bcm_sk(sk); struct bcm_op *op; struct canfd_frame *cf; + bool add_op_to_list = false; unsigned int i; int err; @@ -1158,8 +1159,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, hrtimer_setup(&op->thrtimer, hrtimer_dummy_timeout, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); - /* add this bcm_op to the list of the tx_ops */ - list_add(&op->list, &bo->tx_ops); + add_op_to_list = true; } /* if ((op = bcm_find_op(&bo->tx_ops, msg_head->can_id, ifindex))) */ @@ -1181,6 +1181,10 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, op->flags |= TX_ANNOUNCE; } + /* add this bcm_op to the list of the tx_ops? */ + if (add_op_to_list) + list_add_rcu(&op->list, &bo->tx_ops); + if (op->flags & TX_ANNOUNCE) bcm_can_tx(op, NULL); @@ -1373,9 +1377,6 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, hrtimer_setup(&op->thrtimer, bcm_rx_thr_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); - /* add this bcm_op to the list of the rx_ops */ - list_add(&op->list, &bo->rx_ops); - /* call can_rx_register() */ do_rx_register = 1; @@ -1449,10 +1450,12 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, bcm_rx_handler, op, "bcm", sk); if (err) { /* this bcm rx op is broken -> remove it */ - list_del_rcu(&op->list); bcm_remove_op(op); return err; } + + /* add this bcm_op to the list of the rx_ops */ + list_add_rcu(&op->list, &bo->rx_ops); } return msg_head->nframes * op->cfsiz + MHSIZ; @@ -1786,8 +1789,10 @@ static int bcm_release(struct socket *sock) remove_proc_entry(bo->procname, net->can.bcmproc_dir); #endif /* CONFIG_PROC_FS */ - list_for_each_entry_safe(op, next, &bo->tx_ops, list) + list_for_each_entry_safe(op, next, &bo->tx_ops, list) { + list_del_rcu(&op->list); bcm_remove_op(op); + } list_for_each_entry_safe(op, next, &bo->rx_ops, list) { /* @@ -1818,8 +1823,10 @@ static int bcm_release(struct socket *sock) synchronize_rcu(); - list_for_each_entry_safe(op, next, &bo->rx_ops, list) + list_for_each_entry_safe(op, next, &bo->rx_ops, list) { + list_del_rcu(&op->list); bcm_remove_op(op); + } /* remove device reference */ if (bo->bound) { From 12ce799f7ab1e05bd8fbf79e46f403bfe5597ebc Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Tue, 14 Jul 2026 18:55:28 +0200 Subject: [PATCH 392/479] can: bcm: extend bcm_tx_lock usage for data and timer updates Stage new CAN frame content for an existing tx op into a kmalloc()'d buffer and validate it there, mirroring the approach already used in bcm_rx_setup(). Only copy the validated data into op->frames while holding op->bcm_tx_lock, so bcm_can_tx() and bcm_tx_timeout_handler() can no longer observe a partially updated or unvalidated frame. Add a missing error path for memcpy_from_msg() when copying CAN frame data from userspace. Also move the kt_ival1/kt_ival2/ival1/ival2 updates in bcm_tx_setup() under op->bcm_tx_lock, and read kt_ival1/kt_ival2/count under the same lock in bcm_tx_set_expiry() and bcm_tx_timeout_handler(), closing the torn 64-bit ktime_t read on 32-bit platforms. Fixes: c2aba69d0c36 ("can: bcm: add locking for bcm_op runtime updates") Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260714-bcm_fixes-v15-6-562f7e3e42da@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/bcm.c | 108 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 31 deletions(-) diff --git a/net/can/bcm.c b/net/can/bcm.c index 5c1e83eeb4ff..68a62f605432 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -128,7 +128,7 @@ struct bcm_op { struct canfd_frame last_sframe; struct sock *sk; struct net_device *rx_reg_dev; - spinlock_t bcm_tx_lock; /* protect currframe/count in runtime updates */ + spinlock_t bcm_tx_lock; /* protect tx data and timer updates */ spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */ }; @@ -472,12 +472,18 @@ static bool bcm_tx_set_expiry(struct bcm_op *op, struct hrtimer *hrt) { ktime_t ival; + spin_lock_bh(&op->bcm_tx_lock); + if (op->kt_ival1 && op->count) ival = op->kt_ival1; - else if (op->kt_ival2) + else if (op->kt_ival2) { ival = op->kt_ival2; - else + } else { + spin_unlock_bh(&op->bcm_tx_lock); return false; + } + + spin_unlock_bh(&op->bcm_tx_lock); hrtimer_set_expires(hrt, ktime_add(ktime_get(), ival)); return true; @@ -494,25 +500,47 @@ static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer) { struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer); struct bcm_msg_head msg_head; + bool tx_ival1, tx_ival2; + + /* snapshot kt_ival1/kt_ival2/count under lock to avoid torn + * ktime_t reads racing with concurrent bcm_tx_setup() updates + */ + spin_lock_bh(&op->bcm_tx_lock); + tx_ival1 = op->kt_ival1 && (op->count > 0); + tx_ival2 = !!op->kt_ival2; + spin_unlock_bh(&op->bcm_tx_lock); + + if (tx_ival1) { + u32 flags, count; + struct bcm_timeval ival1, ival2; - if (op->kt_ival1 && (op->count > 0)) { bcm_can_tx(op, NULL); - if (!op->count && (op->flags & TX_COUNTEVT)) { + /* snapshot variables under lock to avoid torn reads racing + * with concurrent bcm_tx_setup() updates + */ + spin_lock_bh(&op->bcm_tx_lock); + flags = op->flags; + count = op->count; + ival1 = op->ival1; + ival2 = op->ival2; + spin_unlock_bh(&op->bcm_tx_lock); + + if (!count && (flags & TX_COUNTEVT)) { /* create notification to user */ memset(&msg_head, 0, sizeof(msg_head)); msg_head.opcode = TX_EXPIRED; - msg_head.flags = op->flags; - msg_head.count = op->count; - msg_head.ival1 = op->ival1; - msg_head.ival2 = op->ival2; + msg_head.flags = flags; + msg_head.count = count; + msg_head.ival1 = ival1; + msg_head.ival2 = ival2; msg_head.can_id = op->can_id; msg_head.nframes = 0; bcm_send_to_user(op, &msg_head, NULL, 0); } - } else if (op->kt_ival2) { + } else if (tx_ival2) { bcm_can_tx(op, NULL); } @@ -1036,6 +1064,8 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, /* check the given can_id */ op = bcm_find_op(&bo->tx_ops, msg_head, ifindex); if (op) { + void *new_frames; + /* update existing BCM operation */ /* @@ -1046,11 +1076,23 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, if (msg_head->nframes > op->nframes) return -E2BIG; - /* update CAN frames content */ + /* get new CAN frames content into a staging buffer before + * locking: validate and normalize the frames there so that + * bcm_can_tx() / bcm_tx_timeout_handler() never observe a + * partially updated or unvalidated frame in op->frames + */ + new_frames = kmalloc(msg_head->nframes * op->cfsiz, GFP_KERNEL); + if (!new_frames) + return -ENOMEM; + for (i = 0; i < msg_head->nframes; i++) { - cf = op->frames + op->cfsiz * i; + cf = new_frames + op->cfsiz * i; err = memcpy_from_msg((u8 *)cf, msg, op->cfsiz); + if (err < 0) { + kfree(new_frames); + return err; + } if (op->flags & CAN_FD_FRAME) { if (cf->len > 64) @@ -1060,37 +1102,39 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, err = -EINVAL; } - if (err < 0) + if (err < 0) { + kfree(new_frames); return err; + } if (msg_head->flags & TX_CP_CAN_ID) { /* copy can_id into frame */ cf->can_id = msg_head->can_id; } } + + spin_lock_bh(&op->bcm_tx_lock); + + /* update CAN frames content */ + memcpy(op->frames, new_frames, msg_head->nframes * op->cfsiz); + op->flags = msg_head->flags; - /* only lock for unlikely count/nframes/currframe changes */ if (op->nframes != msg_head->nframes || - op->flags & TX_RESET_MULTI_IDX || - op->flags & SETTIMER) { - - spin_lock_bh(&op->bcm_tx_lock); - - if (op->nframes != msg_head->nframes || - op->flags & TX_RESET_MULTI_IDX) { - /* potentially update changed nframes */ - op->nframes = msg_head->nframes; - /* restart multiple frame transmission */ - op->currframe = 0; - } - - if (op->flags & SETTIMER) - op->count = msg_head->count; - - spin_unlock_bh(&op->bcm_tx_lock); + op->flags & TX_RESET_MULTI_IDX) { + /* potentially update changed nframes */ + op->nframes = msg_head->nframes; + /* restart multiple frame transmission */ + op->currframe = 0; } + if (op->flags & SETTIMER) + op->count = msg_head->count; + + spin_unlock_bh(&op->bcm_tx_lock); + + kfree(new_frames); + } else { /* insert new BCM operation for the given can_id */ @@ -1165,10 +1209,12 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, if (op->flags & SETTIMER) { /* set timer values */ + spin_lock_bh(&op->bcm_tx_lock); op->ival1 = msg_head->ival1; op->ival2 = msg_head->ival2; op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); + spin_unlock_bh(&op->bcm_tx_lock); /* disable an active timer due to zero values? */ if (!op->kt_ival1 && !op->kt_ival2) From 62ec41f364648be79d54d94d0d240ee326948afd Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Tue, 14 Jul 2026 18:55:29 +0200 Subject: [PATCH 393/479] can: bcm: validate frame length in bcm_rx_setup() for RTR replies bcm_tx_setup() validates cf->len against the CAN/CAN FD DLC limits before installing frames for TX_SETUP, but bcm_rx_setup() never did the same for the RTR-reply frame configured via RX_SETUP with RX_RTR_FRAME. Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260714-bcm_fixes-v15-7-562f7e3e42da@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/bcm.c | 59 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/net/can/bcm.c b/net/can/bcm.c index 68a62f605432..2d9c9cd74536 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -1246,22 +1246,37 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, return err; } -static void bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, - struct bcm_op *op, void *new_frames) +static int bcm_rx_setup_rtr_check(struct bcm_msg_head *msg_head, + struct bcm_op *op, void *new_frames) { + struct canfd_frame *frame0 = new_frames; + + if (!(msg_head->flags & RX_RTR_FRAME)) + return 0; + + /* this frame is sent out as-is by bcm_can_tx() whenever a matching + * remote request is received, so validate its length the same way + * bcm_tx_setup() validates TX_SETUP frames before installing it + */ + if (msg_head->flags & CAN_FD_FRAME) { + if (frame0->len > 64) + return -EINVAL; + } else { + if (frame0->len > 8) + return -EINVAL; + } + /* funny feature in RX(!)_SETUP only for RTR-mode: * copy can_id into frame BUT without RTR-flag to * prevent a full-load-loopback-test ... ;-] * normalize this on the staged buffer, before it is * ever installed into op->frames. */ - if (msg_head->flags & RX_RTR_FRAME) { - struct canfd_frame *frame0 = new_frames; + if ((msg_head->flags & TX_CP_CAN_ID) || + frame0->can_id == op->can_id) + frame0->can_id = op->can_id & ~CAN_RTR_FLAG; - if ((msg_head->flags & TX_CP_CAN_ID) || - frame0->can_id == op->can_id) - frame0->can_id = op->can_id & ~CAN_RTR_FLAG; - } + return 0; } /* @@ -1324,7 +1339,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, return err; } - bcm_rx_setup_rtr_check(msg_head, op, new_frames); + err = bcm_rx_setup_rtr_check(msg_head, op, new_frames); + if (err < 0) { + kfree(new_frames); + return err; + } } spin_lock_bh(&op->bcm_rx_update_lock); @@ -1397,16 +1416,12 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, if (msg_head->nframes) { err = memcpy_from_msg(op->frames, msg, msg_head->nframes * op->cfsiz); - if (err < 0) { - if (op->frames != &op->sframe) - kfree(op->frames); - if (op->last_frames != &op->last_sframe) - kfree(op->last_frames); - kfree(op); - return err; - } + if (err < 0) + goto free_op; - bcm_rx_setup_rtr_check(msg_head, op, op->frames); + err = bcm_rx_setup_rtr_check(msg_head, op, op->frames); + if (err < 0) + goto free_op; } /* bcm_can_tx / bcm_tx_timeout_handler needs this */ @@ -1505,6 +1520,14 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, } return msg_head->nframes * op->cfsiz + MHSIZ; + +free_op: + if (op->frames != &op->sframe) + kfree(op->frames); + if (op->last_frames != &op->last_sframe) + kfree(op->last_frames); + kfree(op); + return err; } /* From d59948293ea34b6337ce2b5febab8510de70048c Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Tue, 14 Jul 2026 18:55:30 +0200 Subject: [PATCH 394/479] can: bcm: add missing device refcount for CAN filter removal sashiko-bot remarked a problem with a concurrent device unregistration in isotp.c which also is present in the bcm.c code. A former fix for raw.c commit c275a176e4b6 ("can: raw: add missing refcount for memory leak fix") introduced a netdevice_tracker which solves the issue for bcm.c too. bcm_release(), bcm_delete_rx_op() and bcm_notifier() relied on dev_get_by_index(ifindex) to re-find the device for an rx_op before unregistering its filter. If a concurrent NETDEV_UNREGISTER has already unlisted the device from the ifindex table, that lookup fails and can_rx_unregister() is silently skipped, leaving a stale CAN filter pointing at the soon-to-be-freed bcm_op/socket. Hold a netdev_hold()/netdev_put() tracked reference on op->rx_reg_dev from the moment the rx filter is registered in bcm_rx_setup() until it is unregistered in bcm_rx_unreg(), and use that reference directly in bcm_release() and bcm_delete_rx_op() instead of re-looking the device up by ifindex. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260707094716.63578-1-socketcan@hartkopp.net Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260714-bcm_fixes-v15-8-562f7e3e42da@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/bcm.c | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/net/can/bcm.c b/net/can/bcm.c index 2d9c9cd74536..25842061800b 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -128,6 +128,7 @@ struct bcm_op { struct canfd_frame last_sframe; struct sock *sk; struct net_device *rx_reg_dev; + netdevice_tracker rx_reg_dev_tracker; spinlock_t bcm_tx_lock; /* protect tx data and timer updates */ spinlock_t bcm_rx_update_lock; /* protect filter/timer data updates */ }; @@ -937,6 +938,7 @@ static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op) /* mark as removed subscription */ op->rx_reg_dev = NULL; + netdev_put(dev, &op->rx_reg_dev_tracker); } else printk(KERN_ERR "can-bcm: bcm_rx_unreg: registered device " "mismatch %p %p\n", op->rx_reg_dev, dev); @@ -967,17 +969,14 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh, * Only remove subscriptions that had not * been removed due to NETDEV_UNREGISTER * in bcm_notifier() + * + * op->rx_reg_dev is a tracked reference taken + * when the subscription was registered, so it + * stays valid here even if a concurrent + * NETDEV_UNREGISTER already unlisted the dev. */ - if (op->rx_reg_dev) { - struct net_device *dev; - - dev = dev_get_by_index(sock_net(op->sk), - op->ifindex); - if (dev) { - bcm_rx_unreg(dev, op); - dev_put(dev); - } - } + if (op->rx_reg_dev) + bcm_rx_unreg(op->rx_reg_dev, op); } else can_rx_unregister(sock_net(op->sk), NULL, op->can_id, @@ -1496,7 +1495,17 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, bcm_rx_handler, op, "bcm", sk); - op->rx_reg_dev = dev; + /* keep a tracked reference so that a later + * unregister can safely reach the device even + * if a concurrent NETDEV_UNREGISTER has + * already unlisted it by ifindex + */ + if (!err) { + op->rx_reg_dev = dev; + netdev_hold(dev, + &op->rx_reg_dev_tracker, + GFP_KERNEL); + } dev_put(dev); } else { /* the requested device is gone - do not @@ -1873,16 +1882,14 @@ static int bcm_release(struct socket *sock) * Only remove subscriptions that had not * been removed due to NETDEV_UNREGISTER * in bcm_notifier() + * + * op->rx_reg_dev is a tracked reference taken + * when the subscription was registered, so it + * stays valid here even if a concurrent + * NETDEV_UNREGISTER already unlisted the device. */ - if (op->rx_reg_dev) { - struct net_device *dev; - - dev = dev_get_by_index(net, op->ifindex); - if (dev) { - bcm_rx_unreg(dev, op); - dev_put(dev); - } - } + if (op->rx_reg_dev) + bcm_rx_unreg(op->rx_reg_dev, op); } else can_rx_unregister(net, NULL, op->can_id, REGMASK(op->can_id), From 3b762c0d950383ab7a002686c9136b9aa55d2d70 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Tue, 14 Jul 2026 18:55:31 +0200 Subject: [PATCH 395/479] can: bcm: fix stale rx/tx ops after device removal RX: an RX_SETUP update(!) for an existing op skipped can_rx_register() unconditionally, even when a concurrent NETDEV_UNREGISTER had already torn down its registration (op->rx_reg_dev == NULL). This silently did not re-enable frame delivery for that updated filter. bcm_rx_setup() now re-registers in that case, while leaving rx_ops with ifindex = 0 (all CAN devices) which never carry a tracked rx_reg_dev registered as-is. TX: bcm_notify() only handled bo->rx_ops on NETDEV_UNREGISTER, leaving tx_ops with an active cyclic transmission re-arming its hrtimer indefinitely to execute bcm_tx_timeout_handler(). Cancelling the hrtimer prevents the runaway timer and any injection into a later reused ifindex, since nothing else calls bcm_can_tx() for the op until an explicit TX_SETUP update re-arms it. Unlike bcm_rx_unreg(), which clears the tracked rx_reg_dev for rx_ops, the ifindex is intentionally left unchanged for tx_ops. bcm_tx_setup() always rejects ifindex 0, so clearing it would strand the op: neither a later TX_SETUP (bcm_find_op()) nor TX_DELETE (bcm_delete_tx_op()) could ever find it again, since both require an exact ifindex match. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-can/20260708094536.DDF821F00A3A@smtp.kernel.org/ Closes: https://lore.kernel.org/linux-can/20260708154039.347ED1F000E9@smtp.kernel.org/ Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260714-bcm_fixes-v15-9-562f7e3e42da@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/bcm.c | 54 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/net/can/bcm.c b/net/can/bcm.c index 25842061800b..a53dba6ab8b8 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -1287,6 +1287,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, struct bcm_sock *bo = bcm_sk(sk); struct bcm_op *op; int do_rx_register; + int new_op = 0; int err = 0; if ((msg_head->flags & RX_FILTER_ID) || (!(msg_head->nframes))) { @@ -1371,8 +1372,15 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, /* free temporary frames / kfree(NULL) is safe */ kfree(new_frames); - /* Only an update -> do not call can_rx_register() */ - do_rx_register = 0; + /* Don't register a new CAN filter for the rx_op update unless + * a concurrent NETDEV_UNREGISTER notifier already tore down + * the previous registration. In this case the receiver needs + * to be re-registered here so that this update doesn't + * silently stop delivering frames for the given ifindex. + * Ops with ifindex = 0 (all CAN interfaces) never carry a + * tracked rx_reg_dev and stay registered as-is. + */ + do_rx_register = (ifindex && !op->rx_reg_dev) ? 1 : 0; } else { /* insert new BCM operation for the given can_id */ @@ -1439,6 +1447,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, /* call can_rx_register() */ do_rx_register = 1; + new_op = 1; } /* if ((op = bcm_find_op(&bo->rx_ops, msg_head->can_id, ifindex))) */ @@ -1452,7 +1461,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, if (op->flags & SETTIMER) { /* set timers (locked) for newly created op */ - if (do_rx_register) { + if (new_op) { spin_lock_bh(&op->bcm_rx_update_lock); op->ival1 = msg_head->ival1; op->ival2 = msg_head->ival2; @@ -1482,7 +1491,10 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, HRTIMER_MODE_REL_SOFT); } - /* now we can register for can_ids, if we added a new bcm_op */ + /* now we can register for can_ids, if we added a new bcm_op + * or need to re-register after a NETDEV_UNREGISTER tore down + * the previous registration of an existing op + */ if (do_rx_register) { if (ifindex) { struct net_device *dev; @@ -1514,18 +1526,32 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, err = -ENODEV; } - } else + } else { err = can_rx_register(sock_net(sk), NULL, op->can_id, REGMASK(op->can_id), bcm_rx_handler, op, "bcm", sk); + } + if (err) { - /* this bcm rx op is broken -> remove it */ - bcm_remove_op(op); + /* newly created bcm rx op is broken -> remove it */ + if (new_op) { + bcm_remove_op(op); + return err; + } + + /* an existing op just stays unregistered. + * Cancel op->timer and (defensively) op->thrtimer. + * Other settings can't be reached until the next + * successful RX_SETUP. + */ + hrtimer_cancel(&op->timer); + hrtimer_cancel(&op->thrtimer); return err; } - /* add this bcm_op to the list of the rx_ops */ - list_add_rcu(&op->list, &bo->rx_ops); + /* add a new bcm_op to the list of the rx_ops */ + if (new_op) + list_add_rcu(&op->list, &bo->rx_ops); } return msg_head->nframes * op->cfsiz + MHSIZ; @@ -1745,11 +1771,19 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, case NETDEV_UNREGISTER: lock_sock(sk); - /* remove device specific receive entries */ + /* rx_ops: remove device specific receive entries */ list_for_each_entry(op, &bo->rx_ops, list) if (op->rx_reg_dev == dev) bcm_rx_unreg(dev, op); + /* tx_ops: stop device specific cyclic transmissions on the + * vanishing ifindex. Cancelling the timer is enough to stop + * cyclic bcm_can_tx() calls as there is no re-arming. + */ + list_for_each_entry(op, &bo->tx_ops, list) + if (op->ifindex == dev->ifindex) + hrtimer_cancel(&op->timer); + /* remove device reference, if this is our bound device */ if (bo->bound && bo->ifindex == dev->ifindex) { #if IS_ENABLED(CONFIG_PROC_FS) From 58fd6cbc8541216af1d7ed272ea7ac2b66d50fd8 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Tue, 14 Jul 2026 18:55:32 +0200 Subject: [PATCH 396/479] can: bcm: fix data race on rx_stamp/rx_ifindex in bcm_rx_handler() For an rx op subscribed on all interfaces (ifindex == 0), the same op is registered once in the shared per-netns wildcard filter list, so bcm_rx_handler() can run concurrently on different CPUs for frames arriving on different net devices. op->rx_stamp and op->rx_ifindex were written before bcm_rx_update_lock was taken, allowing concurrent writers to race each other - including a torn store of the 64-bit rx_stamp on 32-bit platforms. Beyond a torn store bcm_send_to_user() must report the timestamp/ifindex of the very same frame whose content it is delivering. So the assignment is placed in the same unbroken bcm_rx_update_lock section as the content comparison. As a side effect, the RTR-request frame feature (which never reach bcm_send_to_user()) no longer updates rx_stamp/rx_ifindex, since only the notification path needs them. Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-can/20260707145135.5BC831F00A3A@smtp.kernel.org/ Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260714-bcm_fixes-v15-10-562f7e3e42da@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/bcm.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/net/can/bcm.c b/net/can/bcm.c index a53dba6ab8b8..f213a0b37791 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -800,11 +800,6 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) /* disable timeout */ hrtimer_cancel(&op->timer); - /* save rx timestamp */ - op->rx_stamp = skb->tstamp; - /* save originator for recvfrom() */ - op->rx_ifindex = skb->dev->ifindex; - /* op->flags/op->frames may be updated concurrently by bcm_rx_setup() */ spin_lock_bh(&op->bcm_rx_update_lock); @@ -836,6 +831,14 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) traffic_flags |= RX_OWN; } + /* save rx timestamp and originator for recvfrom() under lock. + * For an op subscribed on all interfaces (ifindex == 0) + * bcm_rx_handler() can run concurrently on different CPUs so + * the CAN content and the meta data must be bundled correctly. + */ + op->rx_stamp = skb->tstamp; + op->rx_ifindex = skb->dev->ifindex; + if (op->flags & RX_FILTER_ID) { /* the easiest case */ bcm_rx_update_and_send(op, op->last_frames, rxframe, From 2f5976f54a04e9f18b25283036ac3136be453b17 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Tue, 14 Jul 2026 18:55:33 +0200 Subject: [PATCH 397/479] can: bcm: track a single source interface for ANYDEV timeout/throttle ops An ANYDEV rx op (ifindex == 0) with an active RX timeout and/or throttle timer has no defined semantics when matching frames arrive from several interfaces: bcm_rx_handler() can run concurrently for the same op on different CPUs, racing hrtimer_cancel()/ bcm_rx_starttimer() against bcm_rx_timeout_handler() and causing spurious RX_TIMEOUT notifications and last_frames corruption. The same concurrency lets throttled multiplex frames from different interfaces clobber the single rx_ifindex/rx_stamp fields shared by the op. Add op->if_detected to track the first interface that delivers a matching frame while a timeout/throttle timer is configured, and reject frames from any other interface for that op. The claim is decided in bcm_rx_handler() before hrtimer_cancel() touches op->timer, so a rejected frame can never disturb the claimed interface's watchdog. RTR-mode ops are excluded via RX_RTR_FRAME, independent of kt_ival1/kt_ival2, since those may briefly hold a stale value from an earlier non-RTR configuration. The claim is released in bcm_notify() on NETDEV_UNREGISTER and in bcm_rx_setup() when SETTIMER reconfigures the timer values. A (re-)claim is only possible on CAN devices in NETREG_REGISTERED dev->reg_state to cover the release in bcm_notify() where reg_state becomes NETREG_UNREGISTERING until synchronize_net(). Fixes: ffd980f976e7 ("[CAN]: Add broadcast manager (bcm) protocol") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-can/20260709105031.1A39C1F000E9@smtp.kernel.org/ Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260714-bcm_fixes-v15-11-562f7e3e42da@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/bcm.c | 49 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/net/can/bcm.c b/net/can/bcm.c index f213a0b37791..3d637a1e0ac1 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -117,6 +117,7 @@ struct bcm_op { struct hrtimer timer, thrtimer; ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg; int rx_ifindex; + int if_detected; /* first received ifindex in ANYDEV rx_op mode */ int cfsiz; u32 count; u32 nframes; @@ -797,6 +798,33 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) return; } + /* An ANYDEV op with an active RX timeout and/or throttle timer + * tracks a single source interface: claim the first interface that + * delivers a matching frame and reject frames from any other one, + * before hrtimer_cancel() below can touch op->timer - this avoids + * racing bcm_rx_timeout_handler() across concurrent interfaces. + * RX_RTR_FRAME ops are excluded, as kt_ival1/kt_ival2 may briefly + * hold a stale value from an earlier non-RTR configuration. + */ + if (!op->ifindex) { + spin_lock_bh(&op->bcm_rx_update_lock); + + if (!(op->flags & RX_RTR_FRAME) && + (op->kt_ival1 || op->kt_ival2)) { + /* don't claim to vanishing interface */ + if (!op->if_detected && + READ_ONCE(skb->dev->reg_state) == NETREG_REGISTERED) + op->if_detected = skb->dev->ifindex; + + if (op->if_detected != skb->dev->ifindex) { + spin_unlock_bh(&op->bcm_rx_update_lock); + return; + } + } + + spin_unlock_bh(&op->bcm_rx_update_lock); + } + /* disable timeout */ hrtimer_cancel(&op->timer); @@ -831,10 +859,9 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) traffic_flags |= RX_OWN; } - /* save rx timestamp and originator for recvfrom() under lock. - * For an op subscribed on all interfaces (ifindex == 0) - * bcm_rx_handler() can run concurrently on different CPUs so - * the CAN content and the meta data must be bundled correctly. + /* save rx timestamp and originator for recvfrom() under lock: an + * ANYDEV op without an active timer can still run concurrently on + * different CPUs, so content and meta data must be bundled here. */ op->rx_stamp = skb->tstamp; op->rx_ifindex = skb->dev->ifindex; @@ -1369,6 +1396,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1); op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2); op->kt_lastmsg = 0; + op->if_detected = 0; /* reclaim ifindex in ANYDEV mode */ } spin_unlock_bh(&op->bcm_rx_update_lock); @@ -1775,10 +1803,21 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, lock_sock(sk); /* rx_ops: remove device specific receive entries */ - list_for_each_entry(op, &bo->rx_ops, list) + list_for_each_entry(op, &bo->rx_ops, list) { if (op->rx_reg_dev == dev) bcm_rx_unreg(dev, op); + /* release an ANYDEV op's claim (see bcm_rx_handler()) + * on this now confirmed-gone interface. + */ + if (!op->ifindex) { + spin_lock_bh(&op->bcm_rx_update_lock); + if (op->if_detected == dev->ifindex) + op->if_detected = 0; + spin_unlock_bh(&op->bcm_rx_update_lock); + } + } + /* tx_ops: stop device specific cyclic transmissions on the * vanishing ifindex. Cancelling the timer is enough to stop * cyclic bcm_can_tx() calls as there is no re-arming. From 9b1a02e0d980ac6b0e36a90378f847062f81d7e4 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Sun, 12 Jul 2026 19:59:41 +0200 Subject: [PATCH 398/479] can: isotp: use unconditional synchronize_rcu() in isotp_release() isotp_notify() unregisters the (RCU) CAN filters via can_rx_unregister() and clears so->bound without waiting for a grace period. isotp_release() uses so->bound to decide whether it needs to call synchronize_rcu() before cancelling so->rxtimer, so when NETDEV_UNREGISTER runs first it skips that synchronize_rcu() and can cancel the timer while an in-flight isotp_rcv() is still executing and about to re-arm it via isotp_send_fc(), leading to a use-after-free timer callback on the freed socket. sakisho-bot remarked a problem with rtnl_lock held in isotp_notify(), therefore make isotp_release() always call synchronize_rcu() before cancelling the timers, regardless of so->bound. This still closes the original race (isotp_notify() clearing so->bound without waiting for in-flight isotp_rcv() callers before isotp_release() cancels the RX timer) without adding any RCU wait to the netdevice notifier path. Fixes: 14a4696bc311 ("can: isotp: isotp_release(): omit unintended hrtimer restart on socket release") Closes: https://lore.kernel.org/linux-can/20260707085210.6B6C01F000E9@smtp.kernel.org/ Reported-by: Nico Yip Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260712-isotp-fixes-v10-1-793a1b1ce17f@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/isotp.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/can/isotp.c b/net/can/isotp.c index c48b4a818297..d30937345bcd 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -1237,11 +1237,18 @@ static int isotp_release(struct socket *sock) SINGLE_MASK(so->txid), isotp_rcv_echo, sk); dev_put(dev); - synchronize_rcu(); } } } + /* Always wait for a grace period before touching the timers below. + * A concurrent NETDEV_UNREGISTER may have already unregistered our + * filters and cleared so->bound in isotp_notify() without waiting + * for in-flight isotp_rcv() callers to finish, so this call must not + * be skipped just because so->bound is already 0 here. + */ + synchronize_rcu(); + hrtimer_cancel(&so->txfrtimer); hrtimer_cancel(&so->txtimer); hrtimer_cancel(&so->rxtimer); From 20bab8b88baac140ca3701116e1d486c7f51e311 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Sun, 12 Jul 2026 19:59:42 +0200 Subject: [PATCH 399/479] can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER isotp_release() looked up the bound network device via dev_get_by_index() using the stored ifindex. During device unregistration the device is unlisted from the ifindex hash before the NETDEV_UNREGISTER notifier chain runs, so a concurrent isotp_release() could find no device, skip can_rx_unregister() entirely, and still proceed to free the socket. Since isotp_release() had already removed itself from the isotp notifier list at that point, isotp_notify() would never get a chance to clean up either, leaving a stale CAN filter that keeps pointing at the freed socket. Fix this the same way raw.c already does: hold a tracked reference to the bound net_device in the socket (so->dev/so->dev_tracker) from bind() onward instead of re-resolving it from the ifindex, and serialize bind()/release() with rtnl_lock() so that so->dev is always consistent with what the NETDEV_UNREGISTER notifier sees. so->dev stays valid regardless of ifindex-hash unlisting, and is only ever cleared by whichever of isotp_release()/isotp_notify() gets there first, so the filter is always removed exactly once. isotp_bind() now rejects a (re)bind with -EAGAIN while so->[tx|rx].state isn't ISOTP_IDLE yet, so a timer left running by a prior NETDEV_UNREGISTER can't act on a newly bound so->ifindex. Both checks share the same lock_sock() section, so there is no window in which a concurrent isotp_notify() clearing so->bound could be missed. Fixes: e057dd3fc20f ("can: add ISO 15765-2:2016 transport protocol") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-can/20260707101420.47F261F000E9@smtp.kernel.org/ Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260712-isotp-fixes-v10-2-793a1b1ce17f@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/isotp.c | 85 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 27 deletions(-) diff --git a/net/can/isotp.c b/net/can/isotp.c index d30937345bcd..44c044eb83e1 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -152,6 +152,8 @@ struct isotp_sock { struct sock sk; int bound; int ifindex; + struct net_device *dev; + netdevice_tracker dev_tracker; canid_t txid; canid_t rxid; ktime_t tx_gap; @@ -978,6 +980,14 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) goto err_event_drop; } + /* so->bound is only checked once above - a wakeup may have + * unbound/rebound the socket meanwhile, so re-validate it + */ + if (!so->bound) { + err = -EADDRNOTAVAIL; + goto err_out_drop; + } + /* PDU size > default => try max_pdu_size */ if (size > so->tx.buflen && so->tx.buflen < max_pdu_size) { u8 *newbuf = kmalloc(max_pdu_size, GFP_KERNEL); @@ -1219,28 +1229,30 @@ static int isotp_release(struct socket *sock) list_del(&so->notifier); spin_unlock(&isotp_notifier_lock); + rtnl_lock(); lock_sock(sk); - /* remove current filters & unregister */ - if (so->bound) { - if (so->ifindex) { - struct net_device *dev; + /* remove current filters & unregister + * tracked reference so->dev is taken at bind() time with rtnl_lock + */ + if (so->bound && so->dev) { + if (isotp_register_rxid(so)) + can_rx_unregister(net, so->dev, so->rxid, + SINGLE_MASK(so->rxid), + isotp_rcv, sk); - dev = dev_get_by_index(net, so->ifindex); - if (dev) { - if (isotp_register_rxid(so)) - can_rx_unregister(net, dev, so->rxid, - SINGLE_MASK(so->rxid), - isotp_rcv, sk); - - can_rx_unregister(net, dev, so->txid, - SINGLE_MASK(so->txid), - isotp_rcv_echo, sk); - dev_put(dev); - } - } + can_rx_unregister(net, so->dev, so->txid, + SINGLE_MASK(so->txid), + isotp_rcv_echo, sk); + netdev_put(so->dev, &so->dev_tracker); } + so->ifindex = 0; + so->bound = 0; + so->dev = NULL; + + rtnl_unlock(); + /* Always wait for a grace period before touching the timers below. * A concurrent NETDEV_UNREGISTER may have already unregistered our * filters and cleared so->bound in isotp_notify() without waiting @@ -1253,9 +1265,6 @@ static int isotp_release(struct socket *sock) hrtimer_cancel(&so->txtimer); hrtimer_cancel(&so->rxtimer); - so->ifindex = 0; - so->bound = 0; - sock_orphan(sk); sock->sk = NULL; @@ -1310,6 +1319,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l if (!addr->can_ifindex) return -ENODEV; + rtnl_lock(); lock_sock(sk); if (so->bound) { @@ -1317,6 +1327,17 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l goto out; } + /* A transmission or reception that outlived a previous binding + * (unbound by NETDEV_UNREGISTER) may still be draining; the FC/echo + * and RX watchdog timers bound how long this takes. Checked together + * with so->bound in the same lock_sock() section above, so there is + * no window in which a concurrent isotp_notify() could be missed. + */ + if (so->tx.state != ISOTP_IDLE || so->rx.state != ISOTP_IDLE) { + err = -EAGAIN; + goto out; + } + /* ensure different CAN IDs when the rx_id is to be registered */ if (isotp_register_rxid(so) && rx_id == tx_id) { err = -EADDRNOTAVAIL; @@ -1329,14 +1350,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l goto out; } if (dev->type != ARPHRD_CAN) { - dev_put(dev); err = -ENODEV; - goto out; + goto out_put_dev; } if (READ_ONCE(dev->mtu) < so->ll.mtu) { - dev_put(dev); err = -EINVAL; - goto out; + goto out_put_dev; } if (!(dev->flags & IFF_UP)) notify_enetdown = 1; @@ -1354,16 +1373,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id), isotp_rcv_echo, sk, "isotpe", sk); - dev_put(dev); - /* switch to new settings */ so->ifindex = ifindex; so->rxid = rx_id; so->txid = tx_id; so->bound = 1; + /* bind() ok -> hold a reference for so->dev so that isotp_release() + * can safely reach the device later, even if a concurrent + * NETDEV_UNREGISTER has already unlisted it by ifindex. + */ + so->dev = dev; + netdev_hold(so->dev, &so->dev_tracker, GFP_KERNEL); + +out_put_dev: + /* remove potential reference from dev_get_by_index() */ + dev_put(dev); out: release_sock(sk); + rtnl_unlock(); if (notify_enetdown) { sk->sk_err = ENETDOWN; @@ -1566,7 +1594,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, if (!net_eq(dev_net(dev), sock_net(sk))) return; - if (so->ifindex != dev->ifindex) + if (so->dev != dev) return; switch (msg) { @@ -1582,10 +1610,12 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, can_rx_unregister(dev_net(dev), dev, so->txid, SINGLE_MASK(so->txid), isotp_rcv_echo, sk); + netdev_put(so->dev, &so->dev_tracker); } so->ifindex = 0; so->bound = 0; + so->dev = NULL; release_sock(sk); sk->sk_err = ENODEV; @@ -1645,6 +1675,7 @@ static int isotp_init(struct sock *sk) so->ifindex = 0; so->bound = 0; + so->dev = NULL; so->opt.flags = CAN_ISOTP_DEFAULT_FLAGS; so->opt.ext_address = CAN_ISOTP_DEFAULT_EXT_ADDRESS; From cf070fe33bfbd1a4c21236078fadb35dd223a157 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Sun, 12 Jul 2026 19:59:43 +0200 Subject: [PATCH 400/479] can: isotp: serialize TX state transitions under so->rx_lock The TX state machine (so->tx.state) is driven from three contexts: sendmsg() claiming and progressing a transfer, the RX path consuming Flow Control/echo frames, and two hrtimers timing out a stalled transfer. Mixing a lock-free cmpxchg() claim in sendmsg() with hrtimer_cancel() calls made under so->rx_lock elsewhere left windows where a frame or timer callback could act on a state that had already moved on, corrupting an unrelated transfer. so->rx_lock now covers the full lifecycle of a TX claim: sendmsg() takes it to check so->tx.state is ISOTP_IDLE, switch it to ISOTP_SENDING, bump so->tx_gen and drain the previous transfer's timers - all as one critical section. isotp_rcv_fc()/isotp_rcv_cf() already run under this lock via isotp_rcv(), and isotp_rcv_echo() now takes it itself, so none of them can ever observe a transfer mid-claim. This also means a transfer can no longer be handed to sendmsg()'s cleanup paths (signal or send error) while another thread is concurrently claiming or finishing it, so those paths can cancel timers and reset the state unconditionally. isotp_release() claims the socket the same way, so a racing sendmsg() sees a consistent ISOTP_SHUTDOWN and skips arming its timer or sending. Only the hrtimer callbacks stay outside so->rx_lock, since they run under so->rx_lock's cancellation elsewhere and taking it themselves would deadlock. so->tx_gen lets them recognize whether the transfer they timed out is still the one currently active, so they don't report an error against a transfer that has since completed or been superseded. Fixes: e057dd3fc20f ("can: add ISO 15765-2:2016 transport protocol") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-can/20260710142146.BDAE61F000E9@smtp.kernel.org/ Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260712-isotp-fixes-v10-3-793a1b1ce17f@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/isotp.c | 202 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 160 insertions(+), 42 deletions(-) diff --git a/net/can/isotp.c b/net/can/isotp.c index 44c044eb83e1..54becaf6898f 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -158,7 +158,7 @@ struct isotp_sock { canid_t rxid; ktime_t tx_gap; ktime_t lastrxcf_tstamp; - struct hrtimer rxtimer, txtimer, txfrtimer; + struct hrtimer rxtimer, txtimer, txfrtimer, echotimer; struct can_isotp_options opt; struct can_isotp_fc_options rxfc, txfc; struct can_isotp_ll_options ll; @@ -166,6 +166,7 @@ struct isotp_sock { u32 force_tx_stmin; u32 force_rx_stmin; u32 cfecho; /* consecutive frame echo tag */ + u32 tx_gen; /* generation, bumped per new tx transfer */ struct tpcon rx, tx; struct list_head notifier; wait_queue_head_t wait; @@ -378,6 +379,15 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) hrtimer_cancel(&so->txtimer); + /* isotp_tx_timeout() may have given up on this job while + * hrtimer_cancel() above waited for it to finish; so->rx_lock + * (held by our caller isotp_rcv()) rules out a concurrent claim, + * so a plain recheck is enough here. + */ + if (so->tx.state != ISOTP_WAIT_FC && + so->tx.state != ISOTP_WAIT_FIRST_FC) + return 1; + if ((cf->len < ae + FC_CONTENT_SZ) || ((so->opt.flags & ISOTP_CHECK_PADDING) && check_pad(so, cf, ae + FC_CONTENT_SZ, so->opt.rxpad_content))) { @@ -424,7 +434,7 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) so->tx.bs = 0; so->tx.state = ISOTP_SENDING; /* send CF frame and enable echo timeout handling */ - hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), HRTIMER_MODE_REL_SOFT); isotp_send_cframe(so); break; @@ -577,6 +587,14 @@ static int isotp_rcv_cf(struct sock *sk, struct canfd_frame *cf, int ae, hrtimer_cancel(&so->rxtimer); + /* isotp_rx_timer_handler() may have raced us for so->rx.state + * while hrtimer_cancel() above waited for it to finish, already + * reporting ETIMEDOUT and resetting the reception; don't process + * this CF into a reassembly that has already been given up on. + */ + if (so->rx.state != ISOTP_WAIT_DATA) + return 1; + /* CFs are never longer than the FF */ if (cf->len > so->rx.ll_dl) return 1; @@ -872,20 +890,36 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) struct canfd_frame *cf = (struct canfd_frame *)skb->data; /* only handle my own local echo CF/SF skb's (no FF!) */ - if (skb->sk != sk || so->cfecho != *(u32 *)cf->data) + if (skb->sk != sk) return; + /* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock + * (no isotp_rcv() caller here), so take it ourselves + */ + spin_lock(&so->rx_lock); + + /* so->cfecho may since belong to a new transfer; recheck under lock */ + if (so->cfecho != *(u32 *)cf->data) + goto out_unlock; + /* cancel local echo timeout */ - hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->echotimer); /* local echo skb with consecutive frame has been consumed */ so->cfecho = 0; + /* claiming a transfer also takes so->rx_lock, so a plain recheck + * is enough: so->tx.state can't have flipped to ISOTP_SENDING for + * a new claim while we're still in here + */ + if (so->tx.state != ISOTP_SENDING) + goto out_unlock; + if (so->tx.idx >= so->tx.len) { /* we are done */ so->tx.state = ISOTP_IDLE; wake_up_interruptible(&so->wait); - return; + goto out_unlock; } if (so->txfc.bs && so->tx.bs >= so->txfc.bs) { @@ -893,53 +927,83 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) so->tx.state = ISOTP_WAIT_FC; hrtimer_start(&so->txtimer, ktime_set(ISOTP_FC_TIMEOUT, 0), HRTIMER_MODE_REL_SOFT); - return; + goto out_unlock; } /* no gap between data frames needed => use burst mode */ if (!so->tx_gap) { /* enable echo timeout handling */ - hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), HRTIMER_MODE_REL_SOFT); isotp_send_cframe(so); - return; + goto out_unlock; } /* start timer to send next consecutive frame with correct delay */ hrtimer_start(&so->txfrtimer, so->tx_gap, HRTIMER_MODE_REL_SOFT); + +out_unlock: + spin_unlock(&so->rx_lock); } -static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) +/* shared by so->txtimer's and so->echotimer's callbacks. Both timers get + * cancelled under so->rx_lock elsewhere, so this must stay lock-free to + * avoid deadlocking with that; uses so->tx_gen instead to avoid tainting + * a new transfer with an error from the one that just timed out. + */ +static enum hrtimer_restart isotp_tx_timeout(struct isotp_sock *so) { - struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, - txtimer); struct sock *sk = &so->sk; + u32 gen = READ_ONCE(so->tx_gen); + u32 old_state = READ_ONCE(so->tx.state); /* don't handle timeouts in IDLE or SHUTDOWN state */ - if (so->tx.state == ISOTP_IDLE || so->tx.state == ISOTP_SHUTDOWN) + if (old_state == ISOTP_IDLE || old_state == ISOTP_SHUTDOWN) + return HRTIMER_NORESTART; + + /* only claim the timeout if the state is still unchanged */ + if (cmpxchg(&so->tx.state, old_state, ISOTP_IDLE) != old_state) return HRTIMER_NORESTART; /* we did not get any flow control or echo frame in time */ - /* report 'communication error on send' */ - sk->sk_err = ECOMM; - if (!sock_flag(sk, SOCK_DEAD)) - sk_error_report(sk); + if (READ_ONCE(so->tx_gen) == gen) { + /* report 'communication error on send' */ + sk->sk_err = ECOMM; + if (!sock_flag(sk, SOCK_DEAD)) + sk_error_report(sk); + } - /* reset tx state */ - so->tx.state = ISOTP_IDLE; wake_up_interruptible(&so->wait); return HRTIMER_NORESTART; } +/* so->txtimer: fires when a Flow Control frame does not arrive in time */ +static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) +{ + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + txtimer); + + return isotp_tx_timeout(so); +} + +/* so->echotimer: fires when a sent CF/SF's local echo does not arrive */ +static enum hrtimer_restart isotp_echo_timer_handler(struct hrtimer *hrtimer) +{ + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + echotimer); + + return isotp_tx_timeout(so); +} + static enum hrtimer_restart isotp_txfr_timer_handler(struct hrtimer *hrtimer) { struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, txfrtimer); /* start echo timeout handling and cover below protocol error */ - hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), HRTIMER_MODE_REL_SOFT); /* cfecho should be consumed by isotp_rcv_echo() here */ @@ -960,13 +1024,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; int wait_tx_done = (so->opt.flags & CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0; s64 hrtimer_sec = ISOTP_ECHO_TIMEOUT; + struct hrtimer *tx_hrt = &so->echotimer; + u32 new_state = ISOTP_SENDING; int off; int err; if (!so->bound || so->tx.state == ISOTP_SHUTDOWN) return -EADDRNOTAVAIL; - while (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE) { + /* claim the socket under so->rx_lock: this serializes the claim + * with the RX path and with sendmsg()'s own error paths below, so + * none of them can ever see a transfer mid-claim + */ + for (;;) { + spin_lock_bh(&so->rx_lock); + if (READ_ONCE(so->tx.state) == ISOTP_IDLE) + break; + spin_unlock_bh(&so->rx_lock); + /* we do not support multiple buffers - for now */ if (msg->msg_flags & MSG_DONTWAIT) return -EAGAIN; @@ -975,11 +1050,23 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) return -EADDRNOTAVAIL; /* wait for complete transmission of current pdu */ - err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); + err = wait_event_interruptible(so->wait, + so->tx.state == ISOTP_IDLE); if (err) - goto err_event_drop; + return err; } + /* new transfer: bump so->tx_gen and drain the old one's timers, + * still under the so->rx_lock we just claimed the socket with + */ + WRITE_ONCE(so->tx.state, ISOTP_SENDING); + WRITE_ONCE(so->tx_gen, READ_ONCE(so->tx_gen) + 1); + hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->echotimer); + hrtimer_cancel(&so->txfrtimer); + so->cfecho = 0; + spin_unlock_bh(&so->rx_lock); + /* so->bound is only checked once above - a wakeup may have * unbound/rebound the socket meanwhile, so re-validate it */ @@ -1096,18 +1183,33 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) so->cfecho = *(u32 *)cf->data; } else { /* standard flow control check */ - so->tx.state = ISOTP_WAIT_FIRST_FC; + new_state = ISOTP_WAIT_FIRST_FC; /* start timeout for FC */ hrtimer_sec = ISOTP_FC_TIMEOUT; + tx_hrt = &so->txtimer; /* no CF echo tag for isotp_rcv_echo() (FF-mode) */ so->cfecho = 0; } } - hrtimer_start(&so->txtimer, ktime_set(hrtimer_sec, 0), + spin_lock_bh(&so->rx_lock); + if (so->tx.state == ISOTP_SHUTDOWN) { + /* isotp_release() has since taken over and already drained + * our timers - don't send into a socket that's going away + */ + spin_unlock_bh(&so->rx_lock); + kfree_skb(skb); + dev_put(dev); + wake_up_interruptible(&so->wait); + return -EADDRNOTAVAIL; + } + /* WAIT_FIRST_FC for standard FF, else stays ISOTP_SENDING */ + so->tx.state = new_state; + hrtimer_start(tx_hrt, ktime_set(hrtimer_sec, 0), HRTIMER_MODE_REL_SOFT); + spin_unlock_bh(&so->rx_lock); /* send the first or only CAN frame */ cf->flags = so->ll.tx_flags; @@ -1120,13 +1222,10 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) pr_notice_once("can-isotp: %s: can_send_ret %pe\n", __func__, ERR_PTR(err)); + spin_lock_bh(&so->rx_lock); /* no transmission -> no timeout monitoring */ - hrtimer_cancel(&so->txtimer); - - /* reset consecutive frame echo tag */ - so->cfecho = 0; - - goto err_out_drop; + hrtimer_cancel(tx_hrt); + goto err_out_drop_locked; } if (wait_tx_done) { @@ -1142,14 +1241,21 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) return size; +err_out_drop: + /* claimed but nothing sent yet - no timer to cancel */ + spin_lock_bh(&so->rx_lock); + goto err_out_drop_locked; err_event_drop: - /* got signal: force tx state machine to be idle */ - so->tx.state = ISOTP_IDLE; + /* interrupted waiting on our own transfer - drain its timers */ + spin_lock_bh(&so->rx_lock); hrtimer_cancel(&so->txfrtimer); hrtimer_cancel(&so->txtimer); -err_out_drop: - /* drop this PDU and unlock a potential wait queue */ + hrtimer_cancel(&so->echotimer); +err_out_drop_locked: + /* release the claim; so->rx_lock still held from above */ + so->cfecho = 0; so->tx.state = ISOTP_IDLE; + spin_unlock_bh(&so->rx_lock); wake_up_interruptible(&so->wait); return err; @@ -1211,13 +1317,20 @@ static int isotp_release(struct socket *sock) so = isotp_sk(sk); net = sock_net(sk); - /* wait for complete transmission of current pdu */ - while (wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0 && - cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE) + /* best-effort: wait for a running pdu to finish, but don't block on + * it forever - give up after the first signal + */ + while (so->tx.state != ISOTP_IDLE && + wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0) ; - /* force state machines to be idle also when a signal occurred */ + /* claim the socket under so->rx_lock like sendmsg() does, so its + * claim can't race the forced ISOTP_SHUTDOWN below; force it + * unconditionally, even when a signal cut the wait above short + */ + spin_lock_bh(&so->rx_lock); so->tx.state = ISOTP_SHUTDOWN; + spin_unlock_bh(&so->rx_lock); so->rx.state = ISOTP_IDLE; spin_lock(&isotp_notifier_lock); @@ -1263,6 +1376,7 @@ static int isotp_release(struct socket *sock) hrtimer_cancel(&so->txfrtimer); hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->echotimer); hrtimer_cancel(&so->rxtimer); sock_orphan(sk); @@ -1702,10 +1816,14 @@ static int isotp_init(struct sock *sk) so->rx.buflen = ARRAY_SIZE(so->rx.sbuf); so->tx.buflen = ARRAY_SIZE(so->tx.sbuf); - hrtimer_setup(&so->rxtimer, isotp_rx_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); - hrtimer_setup(&so->txtimer, isotp_tx_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); - hrtimer_setup(&so->txfrtimer, isotp_txfr_timer_handler, CLOCK_MONOTONIC, - HRTIMER_MODE_REL_SOFT); + hrtimer_setup(&so->rxtimer, isotp_rx_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + hrtimer_setup(&so->txtimer, isotp_tx_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + hrtimer_setup(&so->echotimer, isotp_echo_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + hrtimer_setup(&so->txfrtimer, isotp_txfr_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); init_waitqueue_head(&so->wait); spin_lock_init(&so->rx_lock); From bd2df8dc72201f626d66ca97f2afc8b7f2740713 Mon Sep 17 00:00:00 2001 From: Jackie Liu Date: Wed, 15 Jul 2026 15:35:18 +0800 Subject: [PATCH 401/479] block: free copied pages when blk_rq_map_kern() fails bio_copy_kern() allocates pages that are normally freed by the bio completion callback. If blk_rq_append_bio() rejects the bio, however, blk_rq_map_kern() only drops the bio reference. Since bio_put() does not free pages referenced by the bio vectors, those pages leak. This can happen when the bio exceeds the queue segment constraints or when a later mapping cannot be merged into a request built by earlier calls. Track whether the buffer was copied and free those pages before dropping the rejected bio. Fixes: 3a5a39276d2a ("block: allow blk_rq_map_kern to append to requests") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Jackie Liu Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260715073518.96042-1-liu.yun@linux.dev Signed-off-by: Jens Axboe --- block/blk-map.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/block/blk-map.c b/block/blk-map.c index 768549f19f97..d1d6bbe0ecf1 100644 --- a/block/blk-map.c +++ b/block/blk-map.c @@ -653,6 +653,7 @@ int blk_rq_map_kern(struct request *rq, void *kbuf, unsigned int len, gfp_t gfp_mask) { unsigned long addr = (unsigned long) kbuf; + bool do_copy; struct bio *bio; int ret; @@ -661,7 +662,8 @@ int blk_rq_map_kern(struct request *rq, void *kbuf, unsigned int len, if (!len || !kbuf) return -EINVAL; - if (!blk_rq_aligned(rq->q, addr, len) || object_is_on_stack(kbuf)) + do_copy = !blk_rq_aligned(rq->q, addr, len) || object_is_on_stack(kbuf); + if (do_copy) bio = bio_copy_kern(rq, kbuf, len, gfp_mask); else bio = bio_map_kern(rq, kbuf, len, gfp_mask); @@ -670,8 +672,11 @@ int blk_rq_map_kern(struct request *rq, void *kbuf, unsigned int len, return PTR_ERR(bio); ret = blk_rq_append_bio(rq, bio); - if (unlikely(ret)) + if (unlikely(ret)) { + if (do_copy) + bio_free_pages(bio); blk_mq_map_bio_put(bio); + } return ret; } EXPORT_SYMBOL(blk_rq_map_kern); From 4ff58d6bc9dd4c7fe6092d0ab5677a79ebc788af Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 16 Jul 2026 18:22:37 +0900 Subject: [PATCH 402/479] block: serialize elevator changes for the same queue using a writer lock When elevator_change() is called concurrently for the same queue, the elevator_change_done() function runs concurrently as well. This function adds or deletes kobjects for the debugfs entry of the queue. Then the concurrent calls cause memory corruption of the kobjects and result in a process hang. The core part of the elevator switch is protected by queue freeze and q->elevator_lock. However, since the commit 559dc11143eb ("block: move elv_register[unregister]_queue out of elevator_lock"), the elevator_change_done() is not serialized. Hence the memory corruption and the hang. The failures are observed when udev-worker writes to a sysfs queue/scheduler attribute file while the blktests test case block/005 writes to the same attribute file. The failure also can be recreated by running two processes that write to the same queue/scheduler file concurrently. The failure is observed since another commit 370ac285f23a ("block: avoid cpu_hotplug_lock depedency on freeze_lock"). This commit changed the behavior of queue freeze and it unveiled the failure. Fix the failure by changing elv_iosched_store() to acquire update_nr_hwq_lock as the writer lock instead of the reader lock. This serializes the whole elevator switch steps, including the elevator_change_done() call. Fixes: 559dc11143eb ("block: move elv_register[unregister]_queue out of elevator_lock") Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Nilay Shroff Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260716092237.1305030-1-shinichiro.kawasaki@wdc.com Signed-off-by: Jens Axboe --- block/elevator.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/block/elevator.c b/block/elevator.c index 3bcd37c2aa34..2161b6eea680 100644 --- a/block/elevator.c +++ b/block/elevator.c @@ -812,8 +812,13 @@ ssize_t elv_iosched_store(struct gendisk *disk, const char *buf, * reference during concurrent disk deletion: * update_nr_hwq_lock -> kn->active (via del_gendisk -> kobject_del) * kn->active -> update_nr_hwq_lock (via this sysfs write path) + * + * Use the writer lock instead of the reader lock of update_nr_hwq_lock + * to serialize the two-stage elevator switch steps in + * elevator_change(): the core switch step under the elevator lock and + * the elevator_change_done() step outside the elevator lock. */ - if (!down_read_trylock(&set->update_nr_hwq_lock)) { + if (!down_write_trylock(&set->update_nr_hwq_lock)) { ret = -EBUSY; goto out; } @@ -824,7 +829,7 @@ ssize_t elv_iosched_store(struct gendisk *disk, const char *buf, } else { ret = -ENOENT; } - up_read(&set->update_nr_hwq_lock); + up_write(&set->update_nr_hwq_lock); out: if (ctx.type) From f94de432646ebec7857ba2624fe082e779b52572 Mon Sep 17 00:00:00 2001 From: Jackie Liu Date: Wed, 15 Jul 2026 15:33:41 +0800 Subject: [PATCH 403/479] block: show operation in error injection rules Rules listed through the error_injection debugfs file omit the block operation they match. As a result, rules that differ only in operation are indistinguishable even though op is mandatory when adding a rule. Signed-off-by: Jackie Liu Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260715073341.95129-1-liu.yun@linux.dev Signed-off-by: Jens Axboe --- block/error-injection.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/block/error-injection.c b/block/error-injection.c index cfb83138960c..e14bc4b723ef 100644 --- a/block/error-injection.c +++ b/block/error-injection.c @@ -276,9 +276,10 @@ static int blk_error_injection_show(struct seq_file *s, void *private) rcu_read_lock(); list_for_each_entry_rcu(inj, &disk->error_injection_list, entry) { - seq_printf(s, "%llu:%llu status=%s,chance=%u", - inj->start, inj->end, - blk_status_to_tag(inj->status), inj->chance); + seq_printf(s, "%llu:%llu op=%s,status=%s,chance=%u", + inj->start, inj->end, + blk_op_str(inj->op), + blk_status_to_tag(inj->status), inj->chance); seq_putc(s, '\n'); } rcu_read_unlock(); From 447cfed6d700bfbd5a7120f8ea5821a0e1191667 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Thu, 9 Jul 2026 10:01:45 +0800 Subject: [PATCH 404/479] block: try slab allocation in bio_alloc_bioset() before mempool When the per-CPU bio cache is enabled but empty, bio_alloc_percpu_cache() returns NULL and bio_alloc_bioset() falls straight through to the mempool fallback: if (unlikely(!bio)) { if (!(saved_gfp & __GFP_DIRECT_RECLAIM)) return NULL; ... } For non-sleeping allocations (no __GFP_DIRECT_RECLAIM) this returns NULL without ever attempting a slab allocation, even when there is plenty of free memory. Commit b520c4eef83d ("block: split bio_alloc_bioset more clearly into a fast and slowpath") introduced this. Before it, a percpu cache miss fell through to mempool_alloc(), which attempted the underlying slab allocation first and only failed when that slab allocation failed. The restructuring dropped the slab attempt that non-sleeping callers of a cache-enabled bioset (such as the default fs_bio_set used by bio_alloc()) relied on. Try a slab allocation with optimistic GFP_ flags before falling back to the mempool whenever the bio is still NULL, so both the cache-empty and non-cache paths share the same slab attempt. This restores the previous behavior for non-sleeping allocations. Fixes: b520c4eef83d ("block: split bio_alloc_bioset more clearly into a fast and slowpath") Suggested-by: Christoph Hellwig Signed-off-by: Joseph Qi Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260709020145.4011533-1-joseph.qi@linux.alibaba.com Signed-off-by: Jens Axboe --- block/bio.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/block/bio.c b/block/bio.c index 5ac954c70dd0..de56d3f10c56 100644 --- a/block/bio.c +++ b/block/bio.c @@ -555,6 +555,14 @@ struct bio *bio_alloc_bioset(struct block_device *bdev, unsigned short nr_vecs, bio = bio_alloc_percpu_cache(bs); } else { opf &= ~REQ_ALLOC_CACHE; + } + + /* + * For a bioset without a percpu cache, or when the percpu cache was + * empty, try a slab allocation with optimistic GFP_ flags before + * falling back to the mempool. + */ + if (!bio) { p = kmem_cache_alloc(bs->bio_slab, gfp); if (p) bio = p + bs->front_pad; From 4f221efff9b32fb4206f8db240a2b35a7d27696d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 16 Jul 2026 11:12:45 +0200 Subject: [PATCH 405/479] block: handle huge zero folios in bio_free_folios When CONFIG_PERSISTENT_HUGE_ZERO_FOLIO is enabled, iomap_dio_zero() can add a huge zero folio to a zeroing bio, which needs special treatment in bio_free_folios by also checking is_huge_zero_folio() in addition to is_zero_folio(). Fixes: 8dd5e7c75d7b ("block: add helpers to bounce buffer an iov_iter into bios") Signed-off-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Link: https://patch.msgid.link/20260716091306.316625-2-hch@lst.de Signed-off-by: Jens Axboe --- block/bio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/bio.c b/block/bio.c index de56d3f10c56..ebb6d3e53ac3 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1311,7 +1311,7 @@ static void bio_free_folios(struct bio *bio) bio_for_each_bvec_all(bv, bio, i) { struct folio *folio = bvec_folio(bv); - if (!is_zero_folio(folio)) + if (!is_zero_folio(folio) && !is_huge_zero_folio(folio)) folio_put(folio); } } From df308a14585649f7683a560f5c94978ab4f8224d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 16 Jul 2026 11:12:46 +0200 Subject: [PATCH 406/479] block: fix aligning of bounced dio read bios bio_iov_iter_align_down expects the "normal" biovec layout from vector 0, while bio_iov_iter_bounce_read abuses vector 0 for a bounce buffer allocation. Pass an explicit bvec to bio_iov_iter_align_down to deal with this case to avoid a double unpin. Additionally we need to free the folio if no bio_vec could be added, and adjust the size of the first bio_vec that contains the bounce buffer when the I/O size is aligned down. Fixes: e7b8b3c5b2a6 ("block: align down bounces bios") Reported-by: 0wnerD1ed Signed-off-by: Christoph Hellwig Tested-by: 0wnerD1ed Reviewed-by: Keith Busch Link: https://patch.msgid.link/20260716091306.316625-3-hch@lst.de Signed-off-by: Jens Axboe --- block/bio.c | 51 ++++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/block/bio.c b/block/bio.c index ebb6d3e53ac3..6a2f6fc3413e 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1199,7 +1199,7 @@ void bio_iov_bvec_set(struct bio *bio, const struct iov_iter *iter) * for the next iteration. */ static int bio_iov_iter_align_down(struct bio *bio, struct iov_iter *iter, - unsigned len_align_mask) + struct bio_vec *bv, unsigned len_align_mask) { size_t nbytes = bio->bi_iter.bi_size & len_align_mask; @@ -1208,23 +1208,16 @@ static int bio_iov_iter_align_down(struct bio *bio, struct iov_iter *iter, iov_iter_revert(iter, nbytes); bio->bi_iter.bi_size -= nbytes; - do { - struct bio_vec *bv = &bio->bi_io_vec[bio->bi_vcnt - 1]; - - if (nbytes < bv->bv_len) { - bv->bv_len -= nbytes; - break; - } - + while (nbytes >= bv->bv_len) { if (bio_flagged(bio, BIO_PAGE_PINNED)) unpin_user_page(bv->bv_page); - bio->bi_vcnt--; + if (!--bio->bi_vcnt) + return -EFAULT; nbytes -= bv->bv_len; - } while (nbytes); - - if (!bio->bi_vcnt) - return -EFAULT; + bv--; + } + bv->bv_len -= nbytes; return 0; } @@ -1284,7 +1277,8 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter, if (is_pci_p2pdma_page(bio->bi_io_vec->bv_page)) bio->bi_opf |= REQ_NOMERGE; - return bio_iov_iter_align_down(bio, iter, len_align_mask); + return bio_iov_iter_align_down(bio, iter, + &bio->bi_io_vec[bio->bi_vcnt - 1], len_align_mask); } static struct folio *folio_alloc_greedy(gfp_t gfp, size_t *size, @@ -1369,7 +1363,8 @@ static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter, if (!bio->bi_iter.bi_size) return -ENOMEM; - return bio_iov_iter_align_down(bio, iter, minsize - 1); + return bio_iov_iter_align_down(bio, iter, + &bio->bi_io_vec[bio->bi_vcnt - 1], minsize - 1); } static int bio_iov_iter_bounce_read(struct bio *bio, struct iov_iter *iter, @@ -1377,21 +1372,18 @@ static int bio_iov_iter_bounce_read(struct bio *bio, struct iov_iter *iter, { size_t len = min3(iov_iter_count(iter), maxlen, SZ_1M); struct folio *folio; + ssize_t ret; folio = folio_alloc_greedy(GFP_KERNEL, &len, minsize); if (!folio) return -ENOMEM; do { - ssize_t ret; - ret = iov_iter_extract_bvecs(iter, bio->bi_io_vec + 1, len, &bio->bi_vcnt, bio->bi_max_vecs - 1, 0); if (ret <= 0) { - if (!bio->bi_vcnt) { - folio_put(folio); - return ret; - } + if (!bio->bi_vcnt) + goto out_folio_put; break; } len -= ret; @@ -1407,7 +1399,20 @@ static int bio_iov_iter_bounce_read(struct bio *bio, struct iov_iter *iter, bvec_set_folio(&bio->bi_io_vec[0], folio, bio->bi_iter.bi_size, 0); if (iov_iter_extract_will_pin(iter)) bio_set_flag(bio, BIO_PAGE_PINNED); - return bio_iov_iter_align_down(bio, iter, minsize - 1); + + /* The first vec stores the bounce buffer, so do not subtract 1 here. */ + ret = bio_iov_iter_align_down(bio, iter, + &bio->bi_io_vec[bio->bi_vcnt], minsize - 1); + if (ret) + goto out_folio_put; + + /* Update the bounc buffer bv_len to the aligned down size. */ + bio->bi_io_vec[0].bv_len = bio->bi_iter.bi_size; + return 0; + +out_folio_put: + folio_put(folio); + return ret; } /** From 7bc597ce74bab4153b2009c92eccf889e9d74044 Mon Sep 17 00:00:00 2001 From: Himal Prasad Ghimiray Date: Wed, 24 Jun 2026 23:19:44 +0530 Subject: [PATCH 407/479] drm/xe/vm: Fix BO prefetch with CONSULT_MEM_ADVISE_PREF_LOC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When prefetch region is DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC for a BO VMA, the code used it as an index into region_to_mem_type[], causing an out-of-bounds access since the value is -1. Resolve the preferred location for BO VMAs directly: local VRAM on dGFX (using the BO's tile placement) or system memory on iGPU. Discovered using AI-assisted static analysis confirmed by Intel Product Security. v2: -Fix null dereference Reported-by: Martin Hodo Fixes: c1bb69a2e8e2 ("drm/xe/svm: Consult madvise preferred location in prefetch") Cc: Matthew Brost Cc: stable@vger.kernel.org Reviewed-by: Matthew Brost Link: https://patchwork.freedesktop.org/patch/msgid/20260624174943.2808767-2-himal.prasad.ghimiray@intel.com Signed-off-by: Himal Prasad Ghimiray (cherry picked from commit d9a4906ac03be9f6ed3f3b45c56c866b867fd75b) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_vm.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index 080c2fff0e95..32ded13491ca 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -3255,11 +3255,26 @@ static int op_lock_and_prep(struct drm_exec *exec, struct xe_vm *vm, .request_decompress = false, .check_purged = true, }); - if (!err && !xe_vma_has_no_bo(vma)) - err = xe_bo_migrate(xe_vma_bo(vma), - region_to_mem_type[region], - NULL, - exec); + if (!err && !xe_vma_has_no_bo(vma)) { + struct xe_bo *bo = xe_vma_bo(vma); + u32 mem_type; + + if (region == DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC) { + unsigned int i; + + mem_type = XE_PL_TT; + for (i = 0; i < bo->placement.num_placement; i++) { + if (mem_type_is_vram(bo->placements[i].mem_type)) { + mem_type = bo->placements[i].mem_type; + break; + } + } + } else { + mem_type = region_to_mem_type[region]; + } + + err = xe_bo_migrate(bo, mem_type, NULL, exec); + } break; } default: From 62775525a27c3b0d56382e08ba81ee2d322058b6 Mon Sep 17 00:00:00 2001 From: Nitin Gote Date: Sat, 11 Jul 2026 00:40:28 +0530 Subject: [PATCH 408/479] drm/xe: Hold a dma-buf reference for imported BOs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An imported dma-buf BO is created as a ttm_bo_type_sg BO whose reservation object is the exporter's dma_buf->resv. The importer, however, only takes a dma-buf reference after a successful dma_buf_dynamic_attach(). Until then nothing keeps the exporter alive, so if the exporter is freed while the BO still references its resv, a later access to that resv is a use-after-free: Oops: general protection fault, probably for non-canonical address 0x6b6b6b6b6b6b6b9c Workqueue: ttm ttm_bo_delayed_delete [ttm] RIP: 0010:mutex_can_spin_on_owner+0x3f/0xc0 This can be reached on two paths: - dma_buf_dynamic_attach() fails, or - ttm_bo_init_reserved() fails during BO creation. In both cases the BO already has bo->base.resv pointing at the exporter resv, and sg BOs are always torn down via ttm_bo_delayed_delete(), which locks bo->base.resv asynchronously - potentially after the exporter has been freed. Take the dma-buf reference in xe_bo_init_locked(), before ttm_bo_init_reserved(), so it also covers a creation failure there, and release it in xe_ttm_bo_destroy(). The reference is held for the whole BO lifetime, keeping the shared resv alive on every path. v2: - Reworked the fix to avoid creating the imported sg BO before dma_buf_dynamic_attach() succeeds. - Attach with importer_priv == NULL and make invalidate_mappings ignore incomplete imports. v3: - Dropped the xe-side reordering approach since importer_priv must be valid when dma_buf_dynamic_attach() publishes the attachment. - Per Christian's suggestion on the v1 thread, keyed the check on import_attach rather than removing the sg guard entirely. - Fixes both xe and amdgpu in a single TTM patch. v4: - Moved import_attach check to after dma_resv_copy_fences() so fences are copied before returning for successful imports (Thomas). - Removed exporter-alive claim from commit message (Thomas). v5: - Add drm/xe patch to keep imported sg BOs off the LRU before attach succeeds; the TTM fix alone is not sufficient for xe if the BO is already LRU-visible. (Thomas) v4 patch: https://patchwork.freedesktop.org/patch/736663/?series=169129&rev=2 - Patch 1 (drm/ttm) carries Christian's Reviewed-by from v4. v6: - Reworked the fix based on Thomas' suggestion. Instead of the TTM resv individualization (v1-v5) plus the xe off-LRU/placement handling (v5), just hold a dma-buf reference for the imported BO lifetime so the shared resv can never be freed while the BO still references it. Single xe patch, no TTM change. (Thomas) - Take the reference in xe_bo_init_locked() before ttm_bo_init_reserved() so a TTM creation failure is covered too (Thomas). - Dropped the v5 series (drm/ttm + drm/xe off-LRU); the off-LRU approach also regressed in CI BAT via ttm_bo_pipeline_gutting() creating a ghost BO that outlived the exporter. Link to v5: https://patchwork.freedesktop.org/series/169984/ v7: - Move changelog above --- so it stays in the commit message. - Reorder changelog entries oldest-to-newest. (Thomas) Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/8023 Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Cc: stable@vger.kernel.org Cc: Thomas Hellstrom Cc: Christian Konig Cc: Matthew Auld Suggested-by: Thomas Hellstrom Assisted-by: GitHub_Copilot:claude-sonnet-4.6 Reviewed-by: Thomas Hellström Signed-off-by: Nitin Gote Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260710191027.260160-2-nitin.r.gote@intel.com (cherry picked from commit 3516f3fae6be35642f8f06f8a218da6425c0306a) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_bo.c | 24 ++++++++++++++++++++---- drivers/gpu/drm/xe/xe_bo.h | 3 ++- drivers/gpu/drm/xe/xe_bo_types.h | 2 ++ drivers/gpu/drm/xe/xe_dma_buf.c | 2 +- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index 4c80bac67622..ddbaf4242c79 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -1349,7 +1349,7 @@ int xe_bo_notifier_prepare_pinned(struct xe_bo *bo) backup = xe_bo_init_locked(xe, NULL, NULL, bo->ttm.base.resv, NULL, xe_bo_size(bo), DRM_XE_GEM_CPU_CACHING_WB, ttm_bo_type_kernel, XE_BO_FLAG_SYSTEM | XE_BO_FLAG_NEEDS_CPU_ACCESS | - XE_BO_FLAG_PINNED, &exec); + XE_BO_FLAG_PINNED, NULL, &exec); if (IS_ERR(backup)) { drm_exec_retry_on_contention(&exec); ret = PTR_ERR(backup); @@ -1490,7 +1490,7 @@ int xe_bo_evict_pinned(struct xe_bo *bo) xe_bo_size(bo), DRM_XE_GEM_CPU_CACHING_WB, ttm_bo_type_kernel, XE_BO_FLAG_SYSTEM | XE_BO_FLAG_NEEDS_CPU_ACCESS | - XE_BO_FLAG_PINNED, &exec); + XE_BO_FLAG_PINNED, NULL, &exec); if (IS_ERR(backup)) { drm_exec_retry_on_contention(&exec); ret = PTR_ERR(backup); @@ -1826,6 +1826,8 @@ static void xe_ttm_bo_destroy(struct ttm_buffer_object *ttm_bo) if (bo->ttm.base.import_attach) drm_prime_gem_destroy(&bo->ttm.base, NULL); + if (bo->dma_buf) + dma_buf_put(bo->dma_buf); drm_gem_object_release(&bo->ttm.base); xe_assert(xe, list_empty(&ttm_bo->base.gpuva.list)); @@ -2283,6 +2285,8 @@ void xe_bo_free(struct xe_bo *bo) * @cpu_caching: The cpu caching used for system memory backing store. * @type: The TTM buffer object type. * @flags: XE_BO_FLAG_ flags. + * @dma_buf: The dma-buf to reference for the BO lifetime (imported BOs), + * or NULL. * @exec: The drm_exec transaction to use for exhaustive eviction. * * Initialize or create an xe buffer object. On failure, any allocated buffer @@ -2294,7 +2298,8 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo, struct xe_tile *tile, struct dma_resv *resv, struct ttm_lru_bulk_move *bulk, size_t size, u16 cpu_caching, enum ttm_bo_type type, - u32 flags, struct drm_exec *exec) + u32 flags, struct dma_buf *dma_buf, + struct drm_exec *exec) { struct ttm_operation_ctx ctx = { .interruptible = true, @@ -2383,6 +2388,17 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo, placement = (type == ttm_bo_type_sg || bo->flags & XE_BO_FLAG_DEFER_BACKING) ? &sys_placement : &bo->placement; + + /* + * For imported BOs, keep the exporter dma-buf alive for the BO + * lifetime. Taken before ttm_bo_init_reserved() to also cover a + * creation failure there. Released in xe_ttm_bo_destroy(). + */ + if (dma_buf) { + get_dma_buf(dma_buf); + bo->dma_buf = dma_buf; + } + err = ttm_bo_init_reserved(&xe->ttm, &bo->ttm, type, placement, alignment, &ctx, NULL, resv, xe_ttm_bo_destroy); @@ -2500,7 +2516,7 @@ __xe_bo_create_locked(struct xe_device *xe, vm && !xe_vm_in_fault_mode(vm) && flags & XE_BO_FLAG_USER ? &vm->lru_bulk_move : NULL, size, - cpu_caching, type, flags, exec); + cpu_caching, type, flags, NULL, exec); if (IS_ERR(bo)) return bo; diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h index 6340317f7d2e..7ae1d9ac0574 100644 --- a/drivers/gpu/drm/xe/xe_bo.h +++ b/drivers/gpu/drm/xe/xe_bo.h @@ -118,7 +118,8 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo, struct xe_tile *tile, struct dma_resv *resv, struct ttm_lru_bulk_move *bulk, size_t size, u16 cpu_caching, enum ttm_bo_type type, - u32 flags, struct drm_exec *exec); + u32 flags, struct dma_buf *dma_buf, + struct drm_exec *exec); struct xe_bo *xe_bo_create_locked(struct xe_device *xe, struct xe_tile *tile, struct xe_vm *vm, size_t size, enum ttm_bo_type type, u32 flags, diff --git a/drivers/gpu/drm/xe/xe_bo_types.h b/drivers/gpu/drm/xe/xe_bo_types.h index fcc63ae3f455..e45f24301050 100644 --- a/drivers/gpu/drm/xe/xe_bo_types.h +++ b/drivers/gpu/drm/xe/xe_bo_types.h @@ -36,6 +36,8 @@ struct xe_bo { struct xe_bo *backup_obj; /** @parent_obj: Ref to parent bo if this a backup_obj */ struct xe_bo *parent_obj; + /** @dma_buf: Imported dma-buf ref to keep its resv alive. */ + struct dma_buf *dma_buf; /** @flags: flags for this buffer object */ u32 flags; /** @vm: VM this BO is attached to, for extobj this will be NULL */ diff --git a/drivers/gpu/drm/xe/xe_dma_buf.c b/drivers/gpu/drm/xe/xe_dma_buf.c index 8a920e58245c..bf0728838ead 100644 --- a/drivers/gpu/drm/xe/xe_dma_buf.c +++ b/drivers/gpu/drm/xe/xe_dma_buf.c @@ -302,7 +302,7 @@ xe_dma_buf_create_obj(struct drm_device *dev, struct dma_buf *dma_buf) bo = xe_bo_init_locked(xe, NULL, NULL, resv, NULL, dma_buf->size, 0, /* Will require 1way or 2way for vm_bind */ - ttm_bo_type_sg, XE_BO_FLAG_SYSTEM, &exec); + ttm_bo_type_sg, XE_BO_FLAG_SYSTEM, dma_buf, &exec); drm_exec_retry_on_contention(&exec); if (IS_ERR(bo)) { ret = PTR_ERR(bo); From c473761f8178760b915633332908409c73bfdb9e Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Tue, 14 Jul 2026 08:54:17 +0300 Subject: [PATCH 409/479] drm/xe/nvm: fix writable override for CRI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The witable override should be set when FDO_MODE bit is enabled. Fix the comparison to distingush this case from legacy systems where bit should be disabled to have override. Cc: stable@vger.kernel.org Fixes: 9dde74fd9e65 ("drm/xe/nvm: enable cri platform") Signed-off-by: Alexander Usyskin Reviewed-by: Rodrigo Vivi Link: https://patch.msgid.link/20260714-cri_nvm_fdo_flip-v2-1-14580e71b58e@intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 2007be18d2318a59748da5da1b8968042213d5f1) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_nvm.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_nvm.c b/drivers/gpu/drm/xe/xe_nvm.c index 33487e91f366..1ea67eaeae24 100644 --- a/drivers/gpu/drm/xe/xe_nvm.c +++ b/drivers/gpu/drm/xe/xe_nvm.c @@ -60,35 +60,40 @@ static bool xe_nvm_writable_override(struct xe_device *xe) struct xe_mmio *mmio = xe_root_tile_mmio(xe); bool writable_override; struct xe_reg reg; - u32 test_bit; + u32 test_bit, test_val; switch (xe->info.platform) { case XE_CRESCENTISLAND: reg = PCODE_SCRATCH(0); test_bit = FDO_MODE; + test_val = FDO_MODE; break; case XE_BATTLEMAGE: reg = HECI_FWSTS2(DG2_GSC_HECI2_BASE); test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE; + test_val = 0; break; case XE_PVC: reg = HECI_FWSTS2(PVC_GSC_HECI2_BASE); test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE; + test_val = 0; break; case XE_DG2: reg = HECI_FWSTS2(DG2_GSC_HECI2_BASE); test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE; + test_val = 0; break; case XE_DG1: reg = HECI_FWSTS2(DG1_GSC_HECI2_BASE); test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE; + test_val = 0; break; default: drm_err(&xe->drm, "Unknown platform\n"); return true; } - writable_override = !(xe_mmio_read32(mmio, reg) & test_bit); + writable_override = (xe_mmio_read32(mmio, reg) & test_bit) == test_val; if (writable_override) drm_info(&xe->drm, "NVM access overridden by jumper\n"); return writable_override; From 56441f9e08ad68697295b8835266d2bc48ab59b5 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Mon, 13 Jul 2026 23:24:40 -0700 Subject: [PATCH 410/479] drm/xe/vf: Fix VF CCS attach/detach race with in-flight BO moves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xe_bo_move() attaches VF CCS read/write batch buffers (BBs) to a BO after it transitions NULL/SYSTEM -> TT, and detaches them after it transitions TT -> SYSTEM. Both operations were done synchronously on the CPU immediately after building the move's copy/clear fence, without waiting for that fence to signal. This creates two races with VF migration: - Attach happens too late relative to the copy job it is meant to protect. If the copy job is submitted before the CCS BBs are attached, a VF migration event that pauses execution mid-copy can observe partially copied CCS metadata without the attach state needed to correctly save/restore it. - Detach happens too early relative to the copy job that moves data out of TT. The CCS BBs are torn down right after the copy fence is obtained, while the actual blit may still be in flight. A VF migration event that pauses execution mid-copy can then race the save/restore path against the still-running blit, and the CCS BBs it would need to make sense of the paused state have already been removed. Fix both races: - Move the attach call to before the copy/clear job is submitted, so the CCS BBs are already registered by the time the copy runs. On attach failure, unwind and bail out of the move. xe_migrate_ccs_rw_copy() now takes the destination resource explicitly, since bo->ttm.resource is not updated to the new resource until after the move commits. - Detach only after explicitly waiting for the copy fence to signal, instead of tearing down the CCS BBs immediately after obtaining it. While here, also fix xe_sriov_vf_ccs_attach_bo() to properly unwind and propagate errors: the per-context loop previously never broke out on error, silently discarding earlier failures. Unwind by clearing each attached context directly via xe_migrate_ccs_rw_copy_clear() instead of reusing xe_sriov_vf_ccs_detach_bo(), which requires both contexts to be attached before it will clean up either one. Fixes: 864690cf4dd6 ("drm/xe/vf: Attach and detach CCS copy commands with BO") Cc: Michal Wajdeczko Cc: Matthew Auld Cc: MichaÅ‚ Winiarski Cc: Satyanarayana K V P Assisted-by: GitHub_Copilot:claude-sonnet-5 Signed-off-by: Matthew Brost Acked-by: Satyanarayana K V P Reviewed-by: Matthew Auld Link: https://patch.msgid.link/20260714062440.3421225-1-matthew.brost@intel.com (cherry picked from commit d45ad0aa7a1eb5d7288b5ed948b05695611dc39e) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_bo.c | 34 +++++++++++++++++++--------- drivers/gpu/drm/xe/xe_migrate.c | 5 +++- drivers/gpu/drm/xe/xe_migrate.h | 1 + drivers/gpu/drm/xe/xe_sriov_vf_ccs.c | 20 ++++++++++++++-- drivers/gpu/drm/xe/xe_sriov_vf_ccs.h | 3 ++- 5 files changed, 48 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index ddbaf4242c79..7ed76349075f 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -1102,6 +1102,21 @@ static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict, xe_pm_runtime_get_noresume(xe); } + /* + * Attach CCS BBs before submitting the copy job below so a VF + * migration racing the copy sees valid, up to date attach state. + */ + if (IS_VF_CCS_READY(xe) && + ((move_lacks_source && new_mem->mem_type == XE_PL_TT) || + (old_mem_type == XE_PL_SYSTEM && new_mem->mem_type == XE_PL_TT)) && + handle_system_ccs) { + ret = xe_sriov_vf_ccs_attach_bo(bo, new_mem); + if (ret) { + xe_pm_runtime_put(xe); + goto out; + } + } + if (move_lacks_source) { u32 flags = 0; @@ -1139,22 +1154,19 @@ static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict, ttm_bo_move_null(ttm_bo, new_mem); } - dma_fence_put(fence); - xe_pm_runtime_put(xe); - /* - * CCS meta data is migrated from TT -> SMEM. So, let us detach the - * BBs from BO as it is no longer needed. + * Detach must wait for the copy above to complete: a VF migration + * racing an in-flight copy must still see valid CCS BBs, so don't + * tear them down until the copy fence has signaled. */ if (IS_VF_CCS_READY(xe) && old_mem_type == XE_PL_TT && - new_mem->mem_type == XE_PL_SYSTEM) + new_mem->mem_type == XE_PL_SYSTEM) { + dma_fence_wait(fence, false); xe_sriov_vf_ccs_detach_bo(bo); + } - if (IS_VF_CCS_READY(xe) && - ((move_lacks_source && new_mem->mem_type == XE_PL_TT) || - (old_mem_type == XE_PL_SYSTEM && new_mem->mem_type == XE_PL_TT)) && - handle_system_ccs) - ret = xe_sriov_vf_ccs_attach_bo(bo); + dma_fence_put(fence); + xe_pm_runtime_put(xe); out: if ((!ttm_bo->resource || ttm_bo->resource->mem_type == XE_PL_SYSTEM) && diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c index 9428dd5e7760..7d28290e7d1c 100644 --- a/drivers/gpu/drm/xe/xe_migrate.c +++ b/drivers/gpu/drm/xe/xe_migrate.c @@ -1166,6 +1166,8 @@ static int emit_flush_invalidate(u32 *dw, int i, u32 flags) * @tile: Tile whose migration context to be used. * @q : Execution to be used along with migration context. * @src_bo: The buffer object @src is currently bound to. + * @new_mem: The (not yet committed) destination resource @src_bo is being + * moved into; src_bo->ttm.resource is still the old resource. * @read_write : Creates BB commands for CCS read/write. * * Creates batch buffer instructions to copy CCS metadata from CCS pool to @@ -1177,12 +1179,13 @@ static int emit_flush_invalidate(u32 *dw, int i, u32 flags) */ int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q, struct xe_bo *src_bo, + struct ttm_resource *new_mem, enum xe_sriov_vf_ccs_rw_ctxs read_write) { bool src_is_pltt = read_write == XE_SRIOV_VF_CCS_READ_CTX; bool dst_is_pltt = read_write == XE_SRIOV_VF_CCS_WRITE_CTX; - struct ttm_resource *src = src_bo->ttm.resource; + struct ttm_resource *src = new_mem; struct xe_migrate *m = tile->migrate; struct xe_gt *gt = tile->primary_gt; u32 batch_size, batch_size_allocated; diff --git a/drivers/gpu/drm/xe/xe_migrate.h b/drivers/gpu/drm/xe/xe_migrate.h index 965c45889c72..78e5b63f3ebe 100644 --- a/drivers/gpu/drm/xe/xe_migrate.h +++ b/drivers/gpu/drm/xe/xe_migrate.h @@ -138,6 +138,7 @@ struct dma_fence *xe_migrate_resolve(struct xe_migrate *m, int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q, struct xe_bo *src_bo, + struct ttm_resource *new_mem, enum xe_sriov_vf_ccs_rw_ctxs read_write); void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo, diff --git a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c index 09b99fb2608b..6787564629c6 100644 --- a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c +++ b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c @@ -404,6 +404,8 @@ void xe_sriov_vf_ccs_rw_update_bb_addr(struct xe_sriov_vf_ccs_ctx *ctx) /** * xe_sriov_vf_ccs_attach_bo - Insert CCS read write commands in the BO. * @bo: the &buffer object to which batch buffer commands will be added. + * @new_mem: the (not yet committed) destination resource @bo is being moved + * into; bo->ttm.resource is still the old resource at this point. * * This function shall be called only by VF. It inserts the PTEs and copy * command instructions in the BO by calling xe_migrate_ccs_rw_copy() @@ -411,7 +413,7 @@ void xe_sriov_vf_ccs_rw_update_bb_addr(struct xe_sriov_vf_ccs_ctx *ctx) * * Returns: 0 if successful, negative error code on failure. */ -int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo) +int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo, struct ttm_resource *new_mem) { struct xe_device *xe = xe_bo_device(bo); enum xe_sriov_vf_ccs_rw_ctxs ctx_id; @@ -430,7 +432,21 @@ int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo) xe_assert(xe, !bb); ctx = &xe->sriov.vf.ccs.contexts[ctx_id]; - err = xe_migrate_ccs_rw_copy(tile, ctx->mig_q, bo, ctx_id); + err = xe_migrate_ccs_rw_copy(tile, ctx->mig_q, bo, new_mem, ctx_id); + if (err) + goto err_unwind; + } + return 0; + +err_unwind: + /* + * Clean up any contexts already attached. Can't reuse + * xe_sriov_vf_ccs_detach_bo() here as it requires both contexts + * attached before cleaning up either one. + */ + for_each_ccs_rw_ctx(ctx_id) { + if (bo->bb_ccs[ctx_id]) + xe_migrate_ccs_rw_copy_clear(bo, ctx_id); } return err; } diff --git a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.h b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.h index 00e58b36c510..e1034d852104 100644 --- a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.h +++ b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.h @@ -11,11 +11,12 @@ #include "xe_sriov_vf_ccs_types.h" struct drm_printer; +struct ttm_resource; struct xe_device; struct xe_bo; int xe_sriov_vf_ccs_init(struct xe_device *xe); -int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo); +int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo, struct ttm_resource *new_mem); int xe_sriov_vf_ccs_detach_bo(struct xe_bo *bo); int xe_sriov_vf_ccs_register_context(struct xe_device *xe); void xe_sriov_vf_ccs_rebase(struct xe_device *xe); From ad87e2476b3b246580f407afc8ffa91d621bc849 Mon Sep 17 00:00:00 2001 From: Daniele Ceraolo Spurio Date: Mon, 13 Jul 2026 15:17:59 -0700 Subject: [PATCH 411/479] drm/xe/wopcm: fix WOPCM size for LNL+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starting on LNL the WOPCM size is 8MB instead of 4, so we need to avoid using the [0, 8MB) range of the GGTT as that can be unaccessible from the microcontrollers. Note that the proper long-term fix here is to read the WOPCM size from the HW, but that is a more serious rework that would be difficult to backport, so we can do that as a follow-up. Fixes: 9c57bc08652a ("drm/xe/lnl: Drop force_probe requirement") Signed-off-by: Daniele Ceraolo Spurio Cc: Rodrigo Vivi Cc: Shuicheng Lin Cc: Matt Roper Reviewed-by: Shuicheng Lin Link: https://patch.msgid.link/20260713221758.3285744-2-daniele.ceraolospurio@intel.com (cherry picked from commit 3033b0b24ed0e2f5e56bdd4d9c183417c365a45b) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_wopcm.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_wopcm.c b/drivers/gpu/drm/xe/xe_wopcm.c index 900daf1d1b1b..fe65ed246775 100644 --- a/drivers/gpu/drm/xe/xe_wopcm.c +++ b/drivers/gpu/drm/xe/xe_wopcm.c @@ -49,9 +49,9 @@ */ /* Default WOPCM size is 2MB from Gen11, 1MB on previous platforms */ -/* FIXME: Larger size require for 2 tile PVC, do a proper probe sooner or later */ +/* FIXME: Larger size require for some platforms, do a proper probe sooner or later */ #define DGFX_WOPCM_SIZE SZ_4M -/* FIXME: Larger size require for MTL, do a proper probe sooner or later */ +#define LNL_WOPCM_SIZE SZ_8M #define MTL_WOPCM_SIZE SZ_4M #define WOPCM_SIZE SZ_2M @@ -179,9 +179,14 @@ static int __wopcm_init_regs(struct xe_device *xe, struct xe_gt *gt, u32 xe_wopcm_size(struct xe_device *xe) { - return IS_DGFX(xe) ? DGFX_WOPCM_SIZE : - xe->info.platform == XE_METEORLAKE ? MTL_WOPCM_SIZE : - WOPCM_SIZE; + if (xe->info.platform >= XE_LUNARLAKE) + return LNL_WOPCM_SIZE; + else if (IS_DGFX(xe)) + return DGFX_WOPCM_SIZE; + else if (xe->info.platform == XE_METEORLAKE) + return MTL_WOPCM_SIZE; + else + return WOPCM_SIZE; } static u32 max_wopcm_size(struct xe_device *xe) From 6384271ac1ac0099198d15df79212a19ebdb929d Mon Sep 17 00:00:00 2001 From: Zongyao Bai Date: Tue, 14 Jul 2026 23:24:32 +0000 Subject: [PATCH 412/479] drm/xe/pt: Reset current_op in xe_pt_update_ops_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xe_pt_update_ops_init() fails to reset current_op to 0. On the vm_bind path, ops_execute() calls xe_pt_update_ops_prepare() inside the xe_validation_guard() / drm_exec_until_all_locked() loop. When that loop retries due to lock contention or OOM eviction (drm_exec_retry_on_contention() / xe_validation_retry_on_oom()), xe_pt_update_ops_prepare() runs again on the same vops, and each call to bind_op_prepare() increments current_op without resetting it. After N retries current_op exceeds the array size allocated by xe_vma_ops_alloc(), causing an out-of-bounds write into SLUB-poisoned memory and a subsequent UAF crash in xe_migrate_update_pgtables_cpu() when reading the corrupted pt_op->bind. Also reset needs_svm_lock and needs_invalidation which are derived in the same prepare pass and would otherwise cause wrong migrate ops selection and redundant TLB invalidation on retry. Fix this by resetting current_op, needs_svm_lock and needs_invalidation in xe_pt_update_ops_init(). v2 (Matt): - Add details in commit message. - Add Fixes tag and Cc to stable@vger.kernel.org Fixes: e8babb280b5e ("drm/xe: Convert multiple bind ops into single job") Suggested-by: Matthew Auld Cc: stable@vger.kernel.org Assisted-by: GitHub-Copilot:claude-sonnet-4.6 Signed-off-by: Zongyao Bai Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260714232433.2737533-1-zongyao.bai@intel.com (cherry picked from commit 046045543e530605c441063535e7dca0075369a6) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_pt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c index e787c0c27c42..39c9c8f0ea2d 100644 --- a/drivers/gpu/drm/xe/xe_pt.c +++ b/drivers/gpu/drm/xe/xe_pt.c @@ -2365,8 +2365,11 @@ static void xe_pt_update_ops_init(struct xe_vm_pgtable_update_ops *pt_update_ops) { init_llist_head(&pt_update_ops->deferred); + pt_update_ops->current_op = 0; pt_update_ops->start = ~0x0ull; pt_update_ops->last = 0x0ull; + pt_update_ops->needs_svm_lock = false; + pt_update_ops->needs_invalidation = false; xe_page_reclaim_list_init(&pt_update_ops->prl); } From 299bc6d50b1bed7d1f408391736712f01a0855e2 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Tue, 14 Jul 2026 12:14:02 +0530 Subject: [PATCH 413/479] drm/xe/guc: Keep scheduler timeline name alive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scheduler keeps a pointer to the timeline name, but q->name is freed with the exec queue while scheduler fences can still reference it. Store the name in struct xe_guc_exec_queue so it shares the scheduler's RCU-deferred lifetime. Fixes: 6bd90e700b42 ("drm/xe: Make dma-fences compliant with the safe access rules") Cc: Thomas Hellström Cc: Rodrigo Vivi Cc: Himal Prasad Ghimiray Cc: Matthew Brost Signed-off-by: Arvind Yadav Reviewed-by: Tvrtko Ursulin Acked-by: Matthew Brost Link: https://patch.msgid.link/20260714064402.2457257-1-arvind.yadav@intel.com Signed-off-by: Tejas Upadhyay (cherry picked from commit 41075f0eb5dcbd3b065d15f15ef7bbe9315188e8) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_guc_exec_queue_types.h | 5 +++++ drivers/gpu/drm/xe/xe_guc_submit.c | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h b/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h index e5e53b421f29..cda14d954e57 100644 --- a/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h +++ b/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h @@ -10,6 +10,7 @@ #include #include "xe_gpu_scheduler_types.h" +#include "xe_hw_fence_types.h" struct dma_fence; struct xe_exec_queue; @@ -24,6 +25,10 @@ struct xe_guc_exec_queue { struct rcu_head rcu; /** @sched: GPU scheduler for this xe_exec_queue */ struct xe_gpu_scheduler sched; + /** + * @name: Scheduler timeline name, kept with @sched until RCU free. + */ + char name[MAX_FENCE_NAME_LEN]; /** @entity: Scheduler entity for this xe_exec_queue */ struct xe_sched_entity entity; /** diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index f5c3d8a97ec6..9109f21d367f 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -1955,6 +1955,8 @@ static int guc_exec_queue_init(struct xe_exec_queue *q) xe_exec_queue_assign_name(q, q->guc->id); + strscpy(ge->name, q->name, sizeof(ge->name)); + /* * Use primary queue's submit_wq for all secondary queues of a * multi queue group. This serialization avoids any locking around @@ -1969,7 +1971,7 @@ static int guc_exec_queue_init(struct xe_exec_queue *q) err = xe_sched_init(&ge->sched, &drm_sched_ops, &xe_sched_ops, submit_wq, xe_lrc_ring_size() / MAX_JOB_SIZE_BYTES, 64, timeout, guc_to_gt(guc)->ordered_wq, NULL, - q->name, gt_to_xe(q->gt)->drm.dev); + ge->name, gt_to_xe(q->gt)->drm.dev); if (err) goto err_release_id; From 9b7e60184f4b22e893d4ae95234d5f26261a430c Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Thu, 16 Jul 2026 11:56:24 +0530 Subject: [PATCH 414/479] drm/xe/guc: Hold device ref until queue teardown completes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GuC exec queue destruction can run asynchronously. If the final device put happens from a destroy worker, drmm cleanup can end up draining the same workqueue and deadlock. Hold a drm_device reference for the queue lifetime and drop it after queue teardown completes. This keeps drmm cleanup from running while async destroy work is still pending. Move GuC destroy work to a module-lifetime Xe workqueue and flush it on PCI remove so hot-unbind/rebind still waits for pending destroy work. With queue-held device refs, guc_submit_sw_fini() cannot run with live GuC IDs. Replace the fini wait with an assertion and remove the unused fini_wq. v2: - Rebase v3: - Switch to queue-lifetime drm_dev_get()/drm_dev_put() model. (Matt) - Queue async teardown on system_dfl_wq instead of xe->destroy_wq. (Matt) - Drop separate deferred drm_dev_put worker. - Remove stale drain_workqueue(xe->destroy_wq) from guc_submit_sw_fini(). v4: - Replace the guc_submit_sw_fini() wait with an assertion and remove the now-unused fini_wq. (sashiko) v5: - Move destroy work to a module-lifetime Xe workqueue instead of system_dfl_wq. (Matt) - Flush the module-lifetime destroy workqueue during PCI remove to preserve the old device-remove wait semantics. v6: - Keep SVM pagemap destroy work on the per-device destroy_wq to avoid letting it outlive the xe_device/drm_device. (Sashiko) - Use WQ_MEM_RECLAIM for xe->destroy_wq because SVM pagemap destroy work can be queued from the reclaim path. v7: - Drop the per-device xe->destroy_wq and use the module-level destroy WQ for SVM pagemap destroy as well. (Matt) - Rename xe_exec_queue_destroy_wq_*() helpers to xe_destroy_wq_*() helpers because the WQ is no longer exec-queue specific. (Matt) v8: - Rebase. v9: - Keep SVM pagemap destroy work on the per-device WQ_MEM_RECLAIM destroy_wq because it can be queued from reclaim and embeds the dev_pagemap used by devres teardown. (Sashiko) - Keep the module-level destroy WQ GuC-only and drop WQ_MEM_RECLAIM from it. - Update the module-WQ kdoc to document the GuC/SVM split. v10: - Keep xe->destroy_wq per-cpu while adding WQ_MEM_RECLAIM to fix the workqueue allocation warning. v11: - Drop the SVM pagemap destroy comment as it was revision-specific. (Thomas) v12: - Rebase. Fixes: 2d2be279f1ca ("drm/xe: fix UAF around queue destruction") Cc: Thomas Hellström Cc: Rodrigo Vivi Cc: Himal Prasad Ghimiray Cc: Tejas Upadhyay Reviewed-by: Matthew Brost Signed-off-by: Arvind Yadav Link: https://patch.msgid.link/20260716062624.211396-1-arvind.yadav@intel.com Signed-off-by: Tejas Upadhyay (cherry picked from commit da1124abac689cc2b1d8995e5f0a816f8a122edb) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_device.c | 2 +- drivers/gpu/drm/xe/xe_device_types.h | 2 +- drivers/gpu/drm/xe/xe_guc_submit.c | 66 ++++++++++++++++------------ drivers/gpu/drm/xe/xe_guc_types.h | 2 - drivers/gpu/drm/xe/xe_module.c | 49 +++++++++++++++++++++ drivers/gpu/drm/xe/xe_module.h | 5 +++ drivers/gpu/drm/xe/xe_pci.c | 6 +++ 7 files changed, 100 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c index abe25aedeead..f3eb83644a6f 100644 --- a/drivers/gpu/drm/xe/xe_device.c +++ b/drivers/gpu/drm/xe/xe_device.c @@ -580,7 +580,7 @@ int xe_device_init_early(struct xe_device *xe) WQ_MEM_RECLAIM); xe->ordered_wq = alloc_ordered_workqueue("xe-ordered-wq", 0); xe->unordered_wq = alloc_workqueue("xe-unordered-wq", WQ_PERCPU, 0); - xe->destroy_wq = alloc_workqueue("xe-destroy-wq", WQ_PERCPU, 0); + xe->destroy_wq = alloc_workqueue("xe-destroy-wq", WQ_PERCPU | WQ_MEM_RECLAIM, 0); if (!xe->ordered_wq || !xe->unordered_wq || !xe->preempt_fence_wq || !xe->destroy_wq) { /* diff --git a/drivers/gpu/drm/xe/xe_device_types.h b/drivers/gpu/drm/xe/xe_device_types.h index 32dd2ffbc796..aad10899d9ac 100644 --- a/drivers/gpu/drm/xe/xe_device_types.h +++ b/drivers/gpu/drm/xe/xe_device_types.h @@ -355,7 +355,7 @@ struct xe_device { /** @unordered_wq: used to serialize unordered work */ struct workqueue_struct *unordered_wq; - /** @destroy_wq: used to serialize user destroy work, like queue */ + /** @destroy_wq: used to serialize SVM pagemap destroy work */ struct workqueue_struct *destroy_wq; /** @tiles: device tiles */ diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index 9109f21d367f..1c92b96e4c32 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -10,6 +10,7 @@ #include #include +#include #include #include "abi/guc_actions_abi.h" @@ -37,6 +38,7 @@ #include "xe_macros.h" #include "xe_map.h" #include "xe_mocs.h" +#include "xe_module.h" #include "xe_pm.h" #include "xe_ring_ops_types.h" #include "xe_sched_job.h" @@ -232,17 +234,9 @@ static bool exec_queue_killed_or_banned_or_wedged(struct xe_exec_queue *q) static void guc_submit_sw_fini(struct drm_device *drm, void *arg) { struct xe_guc *guc = arg; - struct xe_device *xe = guc_to_xe(guc); struct xe_gt *gt = guc_to_gt(guc); - int ret; - ret = wait_event_timeout(guc->submission_state.fini_wq, - xa_empty(&guc->submission_state.exec_queue_lookup), - HZ * 5); - - drain_workqueue(xe->destroy_wq); - - xe_gt_assert(gt, ret); + xe_gt_assert(gt, xa_empty(&guc->submission_state.exec_queue_lookup)); xa_destroy(&guc->submission_state.exec_queue_lookup); } @@ -319,8 +313,6 @@ int xe_guc_submit_init(struct xe_guc *guc, unsigned int num_ids) xa_init(&guc->submission_state.exec_queue_lookup); - init_waitqueue_head(&guc->submission_state.fini_wq); - primelockdep(guc); guc->submission_state.initialized = true; @@ -411,9 +403,6 @@ static void __release_guc_id(struct xe_guc *guc, struct xe_exec_queue *q, xe_guc_id_mgr_release_locked(&guc->submission_state.idm, q->guc->id, q->width); - if (xa_empty(&guc->submission_state.exec_queue_lookup)) - wake_up(&guc->submission_state.fini_wq); - mutex_unlock(&guc->submission_state.lock); } @@ -1685,6 +1674,7 @@ static void guc_exec_queue_fini(struct xe_exec_queue *q) { struct xe_guc_exec_queue *ge = q->guc; struct xe_guc *guc = exec_queue_to_guc(q); + struct drm_device *drm = &guc_to_xe(guc)->drm; if (xe_exec_queue_is_multi_queue_secondary(q)) { struct xe_exec_queue_group *group = q->multi_queue.group; @@ -1703,36 +1693,52 @@ static void guc_exec_queue_fini(struct xe_exec_queue *q) * (timeline name). */ kfree_rcu(ge, rcu); + + drm_dev_put(drm); +} + +static void guc_exec_queue_do_destroy(struct xe_exec_queue *q) +{ + struct xe_guc_exec_queue *ge = q->guc; + struct xe_guc *guc = exec_queue_to_guc(q); + struct xe_device *xe = guc_to_xe(guc); + struct drm_device *drm = &xe->drm; + + /* + * guc_exec_queue_fini() drops the queue's drm_device ref. + * Keep the device alive until the PM-runtime guard unwinds. + */ + drm_dev_get(drm); + + scoped_guard(xe_pm_runtime, xe) { + trace_xe_exec_queue_destroy(q); + + /* Confirm no work left behind accessing device structures */ + cancel_delayed_work_sync(&ge->sched.base.work_tdr); + + xe_exec_queue_fini(q); + } + + drm_dev_put(drm); } static void __guc_exec_queue_destroy_async(struct work_struct *w) { struct xe_guc_exec_queue *ge = container_of(w, struct xe_guc_exec_queue, destroy_async); - struct xe_exec_queue *q = ge->q; - struct xe_guc *guc = exec_queue_to_guc(q); - guard(xe_pm_runtime)(guc_to_xe(guc)); - trace_xe_exec_queue_destroy(q); - - /* Confirm no work left behind accessing device structures */ - cancel_delayed_work_sync(&ge->sched.base.work_tdr); - - xe_exec_queue_fini(q); + guc_exec_queue_do_destroy(ge->q); } static void guc_exec_queue_destroy_async(struct xe_exec_queue *q) { - struct xe_guc *guc = exec_queue_to_guc(q); - struct xe_device *xe = guc_to_xe(guc); - INIT_WORK(&q->guc->destroy_async, __guc_exec_queue_destroy_async); /* We must block on kernel engines so slabs are empty on driver unload */ if (q->flags & EXEC_QUEUE_FLAG_PERMANENT || exec_queue_wedged(q)) - __guc_exec_queue_destroy_async(&q->guc->destroy_async); + guc_exec_queue_do_destroy(q); else - queue_work(xe->destroy_wq, &q->guc->destroy_async); + xe_destroy_wq_queue(&q->guc->destroy_async); } static void __guc_exec_queue_destroy(struct xe_guc *guc, struct xe_exec_queue *q) @@ -1927,6 +1933,7 @@ static int guc_exec_queue_init(struct xe_exec_queue *q) { struct xe_gpu_scheduler *sched; struct xe_guc *guc = exec_queue_to_guc(q); + struct drm_device *drm = &guc_to_xe(guc)->drm; struct workqueue_struct *submit_wq = NULL; struct xe_guc_exec_queue *ge; long timeout; @@ -1938,6 +1945,8 @@ static int guc_exec_queue_init(struct xe_exec_queue *q) if (!ge) return -ENOMEM; + drm_dev_get(drm); + q->guc = ge; ge->q = q; init_rcu_head(&ge->rcu); @@ -2016,6 +2025,7 @@ static int guc_exec_queue_init(struct xe_exec_queue *q) release_guc_id(guc, q); err_free: kfree(ge); + drm_dev_put(drm); return err; } diff --git a/drivers/gpu/drm/xe/xe_guc_types.h b/drivers/gpu/drm/xe/xe_guc_types.h index c7b9642b41ba..31a2acb63ac3 100644 --- a/drivers/gpu/drm/xe/xe_guc_types.h +++ b/drivers/gpu/drm/xe/xe_guc_types.h @@ -100,8 +100,6 @@ struct xe_guc { * even initialized - before that not even the lock is valid */ bool initialized; - /** @submission_state.fini_wq: submit fini wait queue */ - wait_queue_head_t fini_wq; } submission_state; /** @hwconfig: Hardware config state */ diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c index 4cb578182912..99347f216ec8 100644 --- a/drivers/gpu/drm/xe/xe_module.c +++ b/drivers/gpu/drm/xe/xe_module.c @@ -7,6 +7,7 @@ #include #include +#include #include @@ -91,6 +92,50 @@ static int xe_check_nomodeset(void) return 0; } +static struct workqueue_struct *xe_destroy_wq; + +static int __init xe_destroy_wq_module_init(void) +{ + xe_destroy_wq = alloc_workqueue("xe-guc-destroy-wq", WQ_UNBOUND, 0); + if (!xe_destroy_wq) + return -ENOMEM; + return 0; +} + +static void xe_destroy_wq_module_exit(void) +{ + if (xe_destroy_wq) + destroy_workqueue(xe_destroy_wq); + xe_destroy_wq = NULL; +} + +/** + * xe_destroy_wq_queue() - Queue work on the destroy workqueue + * @work: work item to queue + * + * The destroy workqueue has module lifetime and is used for GuC exec queue + * teardown that can outlive a single xe_device. SVM pagemap destroy uses the + * per-device xe->destroy_wq instead. + * + * Return: %true if @work was queued, %false if it was already pending. + */ +bool xe_destroy_wq_queue(struct work_struct *work) +{ + return queue_work(xe_destroy_wq, work); +} + +/** + * xe_destroy_wq_flush() - Flush the destroy workqueue + * + * Drains all pending destroy work. Called from PCI remove to ensure + * teardown ordering before the device is destroyed. + */ +void xe_destroy_wq_flush(void) +{ + if (xe_destroy_wq) + flush_workqueue(xe_destroy_wq); +} + struct init_funcs { int (*init)(void); void (*exit)(void); @@ -112,6 +157,10 @@ static const struct init_funcs init_funcs[] = { .init = xe_sched_job_module_init, .exit = xe_sched_job_module_exit, }, + { + .init = xe_destroy_wq_module_init, + .exit = xe_destroy_wq_module_exit, + }, { .init = xe_register_pci_driver, .exit = xe_unregister_pci_driver, diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h index 79cb9639c0f3..e8e54f701cf3 100644 --- a/drivers/gpu/drm/xe/xe_module.h +++ b/drivers/gpu/drm/xe/xe_module.h @@ -8,6 +8,8 @@ #include +struct work_struct; + /* Module modprobe variables */ struct xe_modparam { bool force_execlist; @@ -27,5 +29,8 @@ struct xe_modparam { extern struct xe_modparam xe_modparam; +bool xe_destroy_wq_queue(struct work_struct *work); +void xe_destroy_wq_flush(void); + #endif diff --git a/drivers/gpu/drm/xe/xe_pci.c b/drivers/gpu/drm/xe/xe_pci.c index 3165686e3e04..2bd601eb088b 100644 --- a/drivers/gpu/drm/xe/xe_pci.c +++ b/drivers/gpu/drm/xe/xe_pci.c @@ -1048,6 +1048,12 @@ static void xe_pci_remove(struct pci_dev *pdev) return; xe_device_remove(xe); + + /* + * Preserve remove-time flush after moving destroy work to module + * lifetime. + */ + xe_destroy_wq_flush(); xe_pm_fini(xe); } From 130910bac905a42225f93841e338bbea4a431b1a Mon Sep 17 00:00:00 2001 From: Satyanarayana K V P Date: Tue, 14 Jul 2026 11:03:00 +0530 Subject: [PATCH 415/479] drm/xe/pf: Disable display in admin only PF mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Admin-only PF mode does not expose media or 3D execution capabilities to userspace, so display pipelines cannot receive rendered content. Fixes: d88c4bac8c2a ("drm/xe/pf: Restrict device query responses in admin-only PF mode") Signed-off-by: Satyanarayana K V P Cc: Michal Wajdeczko Cc: Piotr Piórkowski Cc: MichaÅ‚ Winiarski Cc: Rodrigo Vivi Reviewed-by: Piotr Piórkowski Link: https://patch.msgid.link/20260714053259.504308-2-satyanarayana.k.v.p@intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 7ef55ae582eba2b0a7a7441bd3b9aefd38a26bb9) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_device.c | 2 -- drivers/gpu/drm/xe/xe_pci.c | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c index f3eb83644a6f..dcb48caa485d 100644 --- a/drivers/gpu/drm/xe/xe_device.c +++ b/drivers/gpu/drm/xe/xe_device.c @@ -426,7 +426,6 @@ static const struct drm_ioctl_desc xe_ioctls_admin_only[] = { static const struct drm_driver admin_only_driver = { .driver_features = - XE_DISPLAY_DRIVER_FEATURES | DRIVER_GEM | DRIVER_RENDER | DRIVER_GEM_GPUVA, .open = xe_file_open, .postclose = xe_file_close, @@ -438,7 +437,6 @@ static const struct drm_driver admin_only_driver = { .major = DRIVER_MAJOR, .minor = DRIVER_MINOR, .patchlevel = DRIVER_PATCHLEVEL, - XE_DISPLAY_DRIVER_OPS, }; /** diff --git a/drivers/gpu/drm/xe/xe_pci.c b/drivers/gpu/drm/xe/xe_pci.c index 2bd601eb088b..9cd873708136 100644 --- a/drivers/gpu/drm/xe/xe_pci.c +++ b/drivers/gpu/drm/xe/xe_pci.c @@ -788,7 +788,8 @@ static int xe_info_init_early(struct xe_device *xe, xe->info.probe_display = IS_ENABLED(CONFIG_DRM_XE_DISPLAY) && xe_modparam.probe_display && - desc->has_display; + desc->has_display && + !xe_device_is_admin_only(xe); xe->info.force_execlist = xe_modparam.force_execlist; xe_assert(xe, desc->max_gt_per_tile > 0); From 1c0ae3df692ea2a4ce992f786346154e75a3f0d5 Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Thu, 9 Jul 2026 17:05:30 +0200 Subject: [PATCH 416/479] ksmbd: fix integer overflow in set_file_allocation_info() set_file_allocation_info() converts the client-supplied FILE_ALLOCATION_INFORMATION::AllocationSize into a 512-byte block count with: alloc_blks = (le64_to_cpu(file_alloc_info->AllocationSize) + 511) >> 9; AllocationSize is a fully client-controlled __le64 field; the only validation performed by the caller (smb2_set_info_file(), case FILE_ALLOCATION_INFORMATION) is that the fixed buffer is at least sizeof(struct smb2_file_alloc_info) == 8 bytes. The value itself is never range-checked before this arithmetic. When AllocationSize is close to U64_MAX (e.g. 0xffffffffffffffff), "AllocationSize + 511" wraps around mod 2^64 to a small number (0xffffffffffffffff + 511 = 510), so alloc_blks becomes 0. Since any existing regular file has stat.blocks > 0, the function then takes the "shrink" branch and calls: ksmbd_vfs_truncate(work, fp, alloc_blks * 512); /* == 0 */ silently truncating the file to size 0, even though the client asked to grow the allocation to (what looks like) the maximum possible size. The trailing "if (size < alloc_blks * 512) i_size_write(inode, size);" restore is guarded by a comparison that is never true once alloc_blks == 0, so the truncation is not undone. This lets an authenticated SMB client that already holds an open handle with FILE_WRITE_DATA on a file silently truncate that same file to size 0 via a single crafted SET_INFO(FILE_ALLOCATION_INFORMATION) request advertising a near-U64_MAX AllocationSize, even though the request asks to grow the file's allocation rather than shrink it. This is a functional/data-loss bug, not a privilege-boundary violation: the same client could already truncate the file via FILE_END_OF_FILE_INFORMATION or a plain write. Fix it by validating AllocationSize against MAX_LFS_FILESIZE, the same upper bound the VFS itself uses to reject unrepresentable file sizes, before doing the "+511" rounding, and rejecting oversized values with -EINVAL. Bounding AllocationSize to MAX_LFS_FILESIZE - 511 guarantees the "+511" addition cannot wrap, and that the subsequent "alloc_blks * 512" values passed to vfs_fallocate() and ksmbd_vfs_truncate() stay within a representable loff_t as well. No legitimate SMB client asks for an allocation size anywhere near 2^64 bytes, so this only rejects a value that was previously silently misinterpreted as zero. Runtime-verified on a v6.19 KASAN test stand: sending SET_INFO (FILE_ALLOCATION_INFORMATION) with AllocationSize = 0xffffffffffffffff against ksmbd now returns -EINVAL and leaves the target file's size unchanged, where the unpatched kernel truncated it from 4096 to 0 bytes. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Hashimov Assisted-by: AuditCode-AI:2026.07 Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/smb2pdu.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index b73167785e87..320b46db9377 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -6781,6 +6781,7 @@ static int set_file_allocation_info(struct ksmbd_work *work, */ loff_t alloc_blks; + u64 alloc_size; struct inode *inode; struct kstat stat; int rc; @@ -6796,7 +6797,19 @@ static int set_file_allocation_info(struct ksmbd_work *work, if (rc) return rc; - alloc_blks = (le64_to_cpu(file_alloc_info->AllocationSize) + 511) >> 9; + /* + * AllocationSize is fully client-controlled (the caller only + * validates the fixed 8-byte buffer length). Reject values that + * would overflow the "round up to 512-byte blocks" conversion + * below instead of silently wrapping it to a tiny block count, + * which would truncate the file to a size the client never + * asked for. + */ + alloc_size = le64_to_cpu(file_alloc_info->AllocationSize); + if (alloc_size > MAX_LFS_FILESIZE - 511) + return -EINVAL; + + alloc_blks = (alloc_size + 511) >> 9; inode = file_inode(fp->filp); if (alloc_blks > stat.blocks) { From aa5d8f3f96aa11a4a54ce993c11ce8af11c546f9 Mon Sep 17 00:00:00 2001 From: Qihang Date: Thu, 9 Jul 2026 22:49:55 +0800 Subject: [PATCH 417/479] ksmbd: pin conn during async oplock break notification smb2_oplock_break_noti() and smb2_lease_break_noti() store a ksmbd_conn pointer in an async ksmbd_work and then queue that work on ksmbd-io. The work only increments conn->r_count, which prevents teardown from passing the pending-request wait after the increment, but it does not pin the struct ksmbd_conn object. If connection teardown races with an oplock break notification, the last conn reference can be dropped before the queued worker finishes. The worker then uses the freed conn in ksmbd_conn_write() and ksmbd_conn_r_count_dec(). Take a real conn reference when publishing the conn pointer to the async work item, and drop it after the notification work has decremented r_count. Apply the same lifetime rule to lease break notification, which uses the same work->conn pattern. Fixes: 3aa660c05924 ("ksmbd: prevent connection release during oplock break notification") Signed-off-by: Qihang Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/oplock.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c index 3c55ae5d6a11..79787099afdc 100644 --- a/fs/smb/server/oplock.c +++ b/fs/smb/server/oplock.c @@ -875,6 +875,7 @@ static void __smb2_oplock_break_noti(struct work_struct *wk) out: ksmbd_free_work_struct(work); ksmbd_conn_r_count_dec(conn); + ksmbd_conn_put(conn); } /** @@ -910,7 +911,7 @@ static int smb2_oplock_break_noti(struct oplock_info *opinfo) br_info->open_trunc = opinfo->open_trunc; work->request_buf = (char *)br_info; - work->conn = conn; + work->conn = ksmbd_conn_get(conn); work->sess = opinfo->sess; ksmbd_conn_r_count_inc(conn); @@ -985,6 +986,7 @@ static void __smb2_lease_break_noti(struct work_struct *wk) out: ksmbd_free_work_struct(work); ksmbd_conn_r_count_dec(conn); + ksmbd_conn_put(conn); } /** @@ -1034,7 +1036,7 @@ static int smb2_lease_break_noti(struct oplock_info *opinfo, bool wait_ack, memcpy(br_info->lease_key, lease->lease_key, SMB2_LEASE_KEY_SIZE); work->request_buf = (char *)br_info; - work->conn = conn; + work->conn = ksmbd_conn_get(conn); work->sess = opinfo->sess; ksmbd_conn_r_count_inc(conn); From b078f39af3818292687623dd433d996aef5e69c9 Mon Sep 17 00:00:00 2001 From: Gil Portnoy Date: Fri, 10 Jul 2026 20:23:34 +0900 Subject: [PATCH 418/479] ksmbd: zero the smb2_read alignment tail to avoid an infoleak Commit 6b9a2e09d4cc ("ksmbd: avoid zeroing the read buffer in smb2_read()") switched the SMB2 READ payload buffer from kvzalloc() to kvmalloc(), on the premise that only the nbytes actually read are ever transmitted, so the ALIGN(length, 8) tail need not be initialized. That premise does not hold for a compound response. ksmbd_vfs_read() fills only nbytes, leaving [nbytes, ALIGN(length, 8)) uninitialized. The aux payload is pinned as the last response iov with iov_len == nbytes, but when the READ is a member of a compound, init_chained_smb2_rsp() 8-byte-aligns the previous member by extending that same iov: new_len = ALIGN(len, 8); work->iov[work->iov_idx].iov_len += (new_len - len); inc_rfc1001_len(work->response_buf, new_len - len); so up to 7 uninitialized bytes of the kvmalloc()'d slab tail are sent to the client. When the read length is small the buffer is served from a general kmalloc slab, so those bytes can be stale kernel-heap contents, including pointer values -- an information leak usable to defeat KASLR. An authenticated client triggers it with a compound request containing a READ whose returned nbytes is not 8-aligned (for example [READ, CLOSE] with a 1-byte read). Zero only the alignment tail after the read, preserving the bulk no-zeroing optimization of 6b9a2e09d4cc. Fixes: 6b9a2e09d4cc ("ksmbd: avoid zeroing the read buffer in smb2_read()") Cc: stable@vger.kernel.org Signed-off-by: Gil Portnoy Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/smb2pdu.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 320b46db9377..c407425cbba0 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -7446,6 +7446,15 @@ int smb2_read(struct ksmbd_work *work) goto out; } + /* + * ksmbd_vfs_read() fills only nbytes; the [nbytes, ALIGN(nbytes, 8)) + * tail of the un-zeroed buffer is transmitted as compound-response + * alignment padding, leaking uninitialized kernel memory to the + * client. Zero just that tail. + */ + if (nbytes & 7) + memset(aux_payload_buf + nbytes, 0, ALIGN(nbytes, 8) - nbytes); + if ((nbytes == 0 && length != 0) || nbytes < mincount) { kvfree(aux_payload_buf); rsp->hdr.Status = STATUS_END_OF_FILE; From 08b9452a338db7b7f9560342a1fff30f7a58a226 Mon Sep 17 00:00:00 2001 From: Gil Portnoy Date: Fri, 10 Jul 2026 20:27:10 +0900 Subject: [PATCH 419/479] ksmbd: fix memory leak of xattr_stream_name in smb2_rename() On an SMB2 SET_INFO(FileRenameInformation) whose target names an alternate data stream, smb2_rename() obtains a formatted stream-name string from ksmbd_vfs_xattr_stream_name(), which allocates it with kasprintf() and returns it through an out-param: rc = ksmbd_vfs_xattr_stream_name(stream_name, &xattr_stream_name, ...); if (rc) goto out; rc = ksmbd_vfs_setxattr(..., xattr_stream_name, ...); if (rc < 0) { ... goto out; } goto out; xattr_stream_name is declared inside the alternate-data-stream block, but the out: label is outside that block and frees only new_name, so it cannot release xattr_stream_name. ksmbd_vfs_setxattr() takes a const char * and only reads the name, so it does not take ownership either. Both the setxattr-failure and the success path therefore leak the kasprintf()'d string. An authenticated client with a writable share can leak kernel memory on every stream rename, exhausting kernel memory over time. Free xattr_stream_name after its use, before the block's goto out. The two earlier goto out paths never assign the variable, so there is no double-free. Signed-off-by: Gil Portnoy Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/smb2pdu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index c407425cbba0..c8acf9f04e45 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -6597,9 +6597,8 @@ static int smb2_rename(struct ksmbd_work *work, pr_err("failed to store stream name in xattr: %d\n", rc); rc = -EINVAL; - goto out; } - + kfree(xattr_stream_name); goto out; } From 610346149d047a52a92c9a0eb329dd565b8f92c5 Mon Sep 17 00:00:00 2001 From: Gil Portnoy Date: Sun, 12 Jul 2026 00:00:00 +0000 Subject: [PATCH 420/479] ksmbd: fix stack buffer overflow in multichannel session-key copy Commit 4b706360ffb7 ("ksmbd: fix multichannel binding and enforce channel limit") moved the binding-path session key out of the session-wide sess->sess_key (CIFS_KEY_SIZE = 40) into a new per-channel buffer, and sized both that buffer and the on-stack copy used during binding with SMB2_NTLMV2_SESSKEY_SIZE (16): struct channel { char sess_key[SMB2_NTLMV2_SESSKEY_SIZE]; /* 16 */ ... }; ntlm_authenticate() / krb5_authenticate(): char channel_key[SMB2_NTLMV2_SESSKEY_SIZE] = {}; /* 16 */ char *auth_key = conn->binding ? channel_key : sess->sess_key; The two writers that fill this destination still bound the copy length against CIFS_KEY_SIZE (40), not against the 16-byte buffer: ksmbd_decode_ntlmssp_auth_blob() (NTLM key exchange): if (sess_key_len > CIFS_KEY_SIZE) /* 40 */ return -EINVAL; arc4_crypt(ctx_arc4, sess_key, (char *)authblob + sess_key_off, sess_key_len); ksmbd_krb5_authenticate(): if (resp->session_key_len > sizeof(sess->sess_key)) /* 40 */ ... memcpy(sess_key, resp->payload, resp->session_key_len); On a binding SESSION_SETUP, auth_key points at the 16-byte channel_key, so a client that supplies an NTLM EncryptedRandomSessionKey of up to 40 bytes (with NTLMSSP_NEGOTIATE_KEY_EXCH), or a Kerberos ticket whose session key is longer than 16 bytes (a normal AES256 key is 32), writes past the 16-byte stack buffer -- up to a 24-byte kernel stack overflow. KASAN reports it as a stack-out-of-bounds write in arc4_crypt() called from ksmbd_decode_ntlmssp_auth_blob(). The destinations must be able to hold the full session key the length checks already permit. Size the per-channel key buffer and the two on-stack channel_key buffers with CIFS_KEY_SIZE, matching sess->sess_key. Fixes: 4b706360ffb7 ("ksmbd: fix multichannel binding and enforce channel limit") Signed-off-by: Gil Portnoy Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/mgmt/user_session.h | 2 +- fs/smb/server/smb2pdu.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h index 8893a9aaede7..4637a8c8436d 100644 --- a/fs/smb/server/mgmt/user_session.h +++ b/fs/smb/server/mgmt/user_session.h @@ -19,7 +19,7 @@ struct ksmbd_file_table; struct channel { - char sess_key[SMB2_NTLMV2_SESSKEY_SIZE]; + char sess_key[CIFS_KEY_SIZE]; __u8 smb3signingkey[SMB3_SIGN_KEY_SIZE]; struct ksmbd_conn *conn; }; diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index c8acf9f04e45..8686b9a4a696 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -1689,7 +1689,7 @@ static int ntlm_authenticate(struct ksmbd_work *work, struct ksmbd_conn *conn = work->conn; struct ksmbd_session *sess = work->sess; struct ksmbd_user *user; - char channel_key[SMB2_NTLMV2_SESSKEY_SIZE] = {}; + char channel_key[CIFS_KEY_SIZE] = {}; char *auth_key = conn->binding ? channel_key : sess->sess_key; u64 prev_id; bool binding = conn->binding; @@ -1826,7 +1826,7 @@ static int krb5_authenticate(struct ksmbd_work *work, struct ksmbd_conn *conn = work->conn; struct ksmbd_session *sess = work->sess; char *in_blob, *out_blob; - char channel_key[SMB2_NTLMV2_SESSKEY_SIZE] = {}; + char channel_key[CIFS_KEY_SIZE] = {}; char *auth_key = conn->binding ? channel_key : sess->sess_key; u64 prev_sess_id; bool binding = conn->binding; From b10665730fbf6b5e45fe422badcbbc3f0df96ef5 Mon Sep 17 00:00:00 2001 From: Gil Portnoy Date: Sun, 12 Jul 2026 00:00:00 +0000 Subject: [PATCH 421/479] ksmbd: remove stale channels from all sessions on teardown ksmbd_sessions_deregister() removes a connection's channels from other sessions' channel lists only while conn->binding is still set: if (conn->binding) { hash_for_each_safe(sessions_table, ...) ksmbd_chann_del(conn, sess); } conn->binding is a transient flag: it is cleared once a binding SESSION_SETUP completes, and also by a subsequent non-binding SESSION_SETUP on the same connection (a reauthentication on a bound channel, or a new SessionId==0 setup). A connection that has bound a channel into another session's ksmbd_chann_list and then clears conn->binding leaves that channel behind when it disconnects: the channel, whose chann->conn points at the now freed struct ksmbd_conn, stays on the owner session's list. When the owning connection later tears down, the second loop dereferences the stale channel: xa_for_each(&sess->ksmbd_chann_list, chann_id, chann) if (chann->conn != conn) ksmbd_conn_set_exiting(chann->conn); /* freed */ which is a use-after-free write into the freed ksmbd_conn (the same stale channel is also walked by show_proc_session() through /proc). The session is leaked as well, because its channel list never empties. Remove the conn->binding gate so a connection always removes its channels from every session on teardown. Fixes: faf8578c77f3 ("ksmbd: find bound sessions during reauthentication") Signed-off-by: Gil Portnoy Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/mgmt/user_session.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c index d6331184ebfc..f99c86284ba3 100644 --- a/fs/smb/server/mgmt/user_session.c +++ b/fs/smb/server/mgmt/user_session.c @@ -457,22 +457,19 @@ void ksmbd_sessions_deregister(struct ksmbd_conn *conn) { struct ksmbd_session *sess; unsigned long id; + struct hlist_node *tmp; + int bkt; down_write(&sessions_table_lock); - if (conn->binding) { - int bkt; - struct hlist_node *tmp; - - hash_for_each_safe(sessions_table, bkt, tmp, sess, hlist) { - if (!ksmbd_chann_del(conn, sess) && - xa_empty(&sess->ksmbd_chann_list)) { - hash_del(&sess->hlist); - down_write(&conn->session_lock); - xa_erase(&conn->sessions, sess->id); - up_write(&conn->session_lock); - if (atomic_dec_and_test(&sess->refcnt)) - ksmbd_session_destroy(sess); - } + hash_for_each_safe(sessions_table, bkt, tmp, sess, hlist) { + if (!ksmbd_chann_del(conn, sess) && + xa_empty(&sess->ksmbd_chann_list)) { + hash_del(&sess->hlist); + down_write(&conn->session_lock); + xa_erase(&conn->sessions, sess->id); + up_write(&conn->session_lock); + if (atomic_dec_and_test(&sess->refcnt)) + ksmbd_session_destroy(sess); } } From 35754558f6c21d0fa0985dceb3387071a3348aff Mon Sep 17 00:00:00 2001 From: Gil Portnoy Date: Mon, 13 Jul 2026 09:20:09 +0900 Subject: [PATCH 422/479] ksmbd: lock the binding preauth session in smb3_preauth_hash_rsp smb3_preauth_hash_rsp() computes the SMB3.1.1 preauth integrity hash on the response path. For a binding SESSION_SETUP it looks up the per-connection preauth_session and reads its Preauth_HashValue. smb2_sess_setup() frees that preauth_session under ksmbd_conn_lock(). Two SMB2 requests on one connection can run concurrently, so an unlocked lookup and hash can use a preauth_session after another worker frees it. Take ksmbd_conn_lock() before selecting conn->binding and hold it across the selected preauth hash lookup and update. This preserves the existing hash selection while preventing the lookup-to-use lifetime race. Fixes: 1c5daa2ea924 ("ksmbd: handle channel binding with a different user") Signed-off-by: Gil Portnoy Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/smb2pdu.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 8686b9a4a696..bec692bca1ca 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -9808,22 +9808,21 @@ void smb3_preauth_hash_rsp(struct ksmbd_work *work) } if (le16_to_cpu(rsp->Command) == SMB2_SESSION_SETUP_HE && sess) { - __u8 *hash_value; + ksmbd_conn_lock(conn); if (conn->binding) { struct preauth_session *preauth_sess; preauth_sess = ksmbd_preauth_session_lookup(conn, sess->id); - if (!preauth_sess) - return; - hash_value = preauth_sess->Preauth_HashValue; - } else { - hash_value = sess->Preauth_HashValue; - if (!hash_value) - return; + if (preauth_sess) + ksmbd_gen_preauth_integrity_hash(conn, + work->response_buf, + preauth_sess->Preauth_HashValue); + } else if (sess->Preauth_HashValue) { + ksmbd_gen_preauth_integrity_hash(conn, work->response_buf, + sess->Preauth_HashValue); } - ksmbd_gen_preauth_integrity_hash(conn, work->response_buf, - hash_value); + ksmbd_conn_unlock(conn); } } From 15b38176fd1530372905c602fde51fe89ec8c877 Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Mon, 13 Jul 2026 21:55:10 +0000 Subject: [PATCH 423/479] ksmbd: validate compound request size before reading StructureSize2 When ksmbd validates a compound (chained) SMB2 request, ksmbd_smb2_check_message() reads pdu->StructureSize2 without first checking that the compound element is large enough to contain it. StructureSize2 is a 2-byte field at offset 64 (__SMB2_HEADER_STRUCTURE_SIZE) from the start of each element. The compound-walking logic only guarantees that a full 64-byte SMB2 header is present for the trailing element: when NextCommand is 0, len is reduced to the number of bytes remaining after next_smb2_rcv_hdr_off. A remote client can craft a compound request whose last element has exactly 64 bytes, so the 2-byte StructureSize2 read at offset 64 extends one byte past the receive buffer, producing a slab-out-of-bounds read. BUG: KASAN: slab-out-of-bounds in ksmbd_smb2_check_message (fs/smb/server/smb2misc.c:402) Read of size 2 at addr ffff888012ae31ac by task kworker/0:1/14 The buggy address is located 172 bytes inside of allocated 173-byte region Workqueue: ksmbd-io handle_ksmbd_work Call Trace: ... kasan_report (mm/kasan/report.c:595) ksmbd_smb2_check_message (fs/smb/server/smb2misc.c:402) handle_ksmbd_work (fs/smb/server/server.c:119) process_one_work (kernel/workqueue.c:3314) worker_thread (kernel/workqueue.c:3397) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) Reject any compound element that is too small to hold StructureSize2 before dereferencing it. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/smb2misc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/smb/server/smb2misc.c b/fs/smb/server/smb2misc.c index c0c4edd092c2..9f3629c86291 100644 --- a/fs/smb/server/smb2misc.c +++ b/fs/smb/server/smb2misc.c @@ -407,6 +407,11 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work) return 1; } + if (len < __SMB2_HEADER_STRUCTURE_SIZE + sizeof(__le16)) { + ksmbd_debug(SMB, "Message is too small for StructureSize2\n"); + return 1; + } + if (smb2_req_struct_sizes[command] != pdu->StructureSize2) { if (!(command == SMB2_OPLOCK_BREAK_HE && (le16_to_cpu(pdu->StructureSize2) == OP_BREAK_STRUCT_SIZE_20 || From 50dce2e2f84b56d8b4b406d97a1543709e8a87f5 Mon Sep 17 00:00:00 2001 From: Abhishek Ojha Date: Wed, 15 Jul 2026 13:04:08 -0400 Subject: [PATCH 424/479] regulator: ltc3676: Fix incorrect IRQSTAT bit offsets The LTC3676_IRQSTAT_* bit definitions do not match the IRQSTAT (Interrupt Request Status) register layout documented in Table 15 of the LTC3676/LTC3676-1 datasheet: bit 0 - Pushbutton Status Active bit 1 - Hard Reset Occurred bit 2 - PGOOD Timeout Occurred bit 3 - Undervoltage Warning bit 4 - Undervoltage Standby (Fault) Occurred bit 5 - Overtemperature Warning bit 6 - Overtemperature Standby (Fault) Occurred bit 7 - Reserved The driver instead defines these starting at bit 3, one bit higher than the datasheet specifies, which causes ltc3676_regulator_isr() to check the wrong status bits and misreport (or miss) PGOOD timeout, undervoltage and thermal warning/fault conditions. Fix the bit offsets to match the datasheet. Fixes: 37b918a034fe ("regulator: Add LTC3676 support") Cc: stable@vger.kernel.org Signed-off-by: Abhishek Ojha Link: https://patch.msgid.link/20260715170408.295552-1-Abhishek.ojha@savoirfairelinux.com Signed-off-by: Mark Brown --- drivers/regulator/ltc3676.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/regulator/ltc3676.c b/drivers/regulator/ltc3676.c index 73d511eb1c1d..768cdce0d4ec 100644 --- a/drivers/regulator/ltc3676.c +++ b/drivers/regulator/ltc3676.c @@ -45,11 +45,11 @@ #define LTC3676_DVBxA_REF_SELECT BIT(5) #define LTC3676_DVBxB_PGOOD_MASK BIT(5) -#define LTC3676_IRQSTAT_PGOOD_TIMEOUT BIT(3) -#define LTC3676_IRQSTAT_UNDERVOLT_WARN BIT(4) -#define LTC3676_IRQSTAT_UNDERVOLT_FAULT BIT(5) -#define LTC3676_IRQSTAT_THERMAL_WARN BIT(6) -#define LTC3676_IRQSTAT_THERMAL_FAULT BIT(7) +#define LTC3676_IRQSTAT_PGOOD_TIMEOUT BIT(2) +#define LTC3676_IRQSTAT_UNDERVOLT_WARN BIT(3) +#define LTC3676_IRQSTAT_UNDERVOLT_FAULT BIT(4) +#define LTC3676_IRQSTAT_THERMAL_WARN BIT(5) +#define LTC3676_IRQSTAT_THERMAL_FAULT BIT(6) enum ltc3676_reg { LTC3676_SW1, From e89978c1cff54e265345c66e1177d19ea5a8bc00 Mon Sep 17 00:00:00 2001 From: Vinod Govindapillai Date: Mon, 15 Jun 2026 23:33:49 +0300 Subject: [PATCH 425/479] drm/i915/wm: clear the plane ddb_y entries on plane disable The UV/Y plane DDB entriess are never cleared on sk_wm_plane_disable_noatomic() and can leave stale DDB state for NV12 planes on pre-Gen11 devices Fixes: d34b59d5ba41 ("drm/i915: Add skl_wm_plane_disable_noatomic()") Assisted-by: Copilot:claude-sonnet-4.6 Signed-off-by: Vinod Govindapillai Reviewed-by: Suraj Kandpal Link: https://patch.msgid.link/20260615203355.218578-2-vinod.govindapillai@intel.com (cherry picked from commit 60f68a6ba298fd1e971a2d91576304bee89a16fc) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/i915/display/skl_watermark.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/skl_watermark.c b/drivers/gpu/drm/i915/display/skl_watermark.c index 5a3677ea25b0..a4ce21d4c024 100644 --- a/drivers/gpu/drm/i915/display/skl_watermark.c +++ b/drivers/gpu/drm/i915/display/skl_watermark.c @@ -3856,7 +3856,7 @@ void skl_wm_plane_disable_noatomic(struct intel_crtc *crtc, return; skl_ddb_entry_init(&crtc_state->wm.skl.plane_ddb[plane->id], 0, 0); - skl_ddb_entry_init(&crtc_state->wm.skl.plane_ddb[plane->id], 0, 0); + skl_ddb_entry_init(&crtc_state->wm.skl.plane_ddb_y[plane->id], 0, 0); crtc_state->wm.skl.plane_min_ddb[plane->id] = 0; crtc_state->wm.skl.plane_interim_ddb[plane->id] = 0; From 612978b83f45bf7018815209db5395d759db6f26 Mon Sep 17 00:00:00 2001 From: Emre Cecanpunar Date: Wed, 15 Jul 2026 01:04:30 +0300 Subject: [PATCH 426/479] drm/i915/selftests: Fix GT PM sort comparators Compare the sampled clock values instead of their addresses. Comparing addresses leaves the samples unsorted, preventing the code from discarding the minimum and maximum samples. Fixes: 1a5392479207 ("drm/i915/selftests: Measure CS_TIMESTAMP") Signed-off-by: Emre Cecanpunar Signed-off-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20260714220430.238433-1-emreleno@gmail.com (cherry picked from commit 682ea2d28d18bb06f9fc663cb5ab7e80dc0e606a) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/i915/gt/selftest_gt_pm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/gt/selftest_gt_pm.c b/drivers/gpu/drm/i915/gt/selftest_gt_pm.c index 33351deeea4f..07eaf71955c4 100644 --- a/drivers/gpu/drm/i915/gt/selftest_gt_pm.c +++ b/drivers/gpu/drm/i915/gt/selftest_gt_pm.c @@ -16,9 +16,9 @@ static int cmp_u64(const void *A, const void *B) { const u64 *a = A, *b = B; - if (a < b) + if (*a < *b) return -1; - else if (a > b) + else if (*a > *b) return 1; else return 0; @@ -28,9 +28,9 @@ static int cmp_u32(const void *A, const void *B) { const u32 *a = A, *b = B; - if (a < b) + if (*a < *b) return -1; - else if (a > b) + else if (*a > *b) return 1; else return 0; From 266cddf7bd0f6c79b6c0633aef742a22bf70265b Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Wed, 3 Jun 2026 17:37:49 +0900 Subject: [PATCH 427/479] gpu: host1x: Fix use-after-free in host1x_bo_clear_cached_mappings __host1x_bo_unpin() drops the last reference to the mapping and frees it, so we can't dereference mapping afterwards. The cache itself outlives the mapping, so use the cache local variable instead. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-tegra/ah6ErK6f4kVudVIA@stanley.mountain/T/#u Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260603-host1x-bocache-leak-fix-v1-1-494101dbfd30@nvidia.com --- drivers/gpu/host1x/bus.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c index e3884096c2fe..ea3b584990c9 100644 --- a/drivers/gpu/host1x/bus.c +++ b/drivers/gpu/host1x/bus.c @@ -1012,10 +1012,10 @@ void host1x_bo_clear_cached_mappings(struct host1x_bo *bo) if (WARN_ON(!cache)) continue; - mutex_lock(&mapping->cache->lock); + mutex_lock(&cache->lock); WARN_ON(kref_read(&mapping->ref) != 1); __host1x_bo_unpin(&mapping->ref); - mutex_unlock(&mapping->cache->lock); + mutex_unlock(&cache->lock); } } EXPORT_SYMBOL(host1x_bo_clear_cached_mappings); From 3917b1012ee2fef6da16d7450d4267dcb3e93363 Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Mon, 13 Jul 2026 16:52:22 -0700 Subject: [PATCH 428/479] bpf: Fix tracing of kfuncs with implicit args A kfunc marked with KF_IMPLICIT_ARGS flag takes implicit arguments (such as bpf_prog_aux) that the verifier injects at load time. resolve_btfids strips those from the kfunc's BTF-visible prototype and keeps the real kernel ABI in a counterpart _impl prototype [1]. fentry/fexit/fmod_ret/fsession programs may attach to the BPF kernel functions, including those with implicit args. However bpf_check_attach_target() and bpf_check_attach_btf_id_multi() extract the struct btf_func_model from the wrong BTF prototype of the kfunc. The btf_func_model is later read to construct the trampoline, which then causes the injected implicit argument to be clobbered and the kfunc dereferencing garbage. Add btf_attach_func_proto() to resolve the real ABI prototype of the kfunc the way the call site does: by looking up the _impl prototype for a KF_IMPLICIT_ARGS kfunc. Use it at both attach-target model construction sites. To enable this, make two supporting changes: * pass bpf_verifier_log instead of bpf_verifier_env to find_kfunc_impl_proto(), so it can be reused from the attach path * add btf_kfunc_check_flag() to test a flag across all of a kfunc's hook sets, because a program attaching to a kfunc is not in the kfunc's call-set KF_IMPLICIT_ARGS must be consistent across the sets, so btf_kfunc_check_flag() returns -EINVAL on inconsistency. btf_kfunc_check_flag() reads the kfunc's flags from the target's kfunc_set_tab. For a module BTF that table is stable only after the module is live, so take a module reference around the read, mirroring how the kfunc call path gates the same lookup with btf_try_get_module(). The remaining call sites of btf_distill_func_proto() are safe as is. The BPF_TRACE_ITER case distills a registered iterator's prototype, and bpf_struct_ops_desc_init() distills the function-pointer members of a struct_ops type. Neither is a kfunc, and so can't have implicit arguments. [1] https://lore.kernel.org/all/20260120222638.3976562-1-ihor.solodrai@linux.dev/ Fixes: 64e1360524b9 ("bpf: Verifier support for KF_IMPLICIT_ARGS") Reported-by: Tejun Heo Signed-off-by: Ihor Solodrai Link: https://github.com/sched-ext/scx/issues/3687#issuecomment-4906694106 Link: https://patch.msgid.link/20260713235223.1639022-2-ihor.solodrai@linux.dev Signed-off-by: Eduard Zingerman --- include/linux/btf.h | 1 + kernel/bpf/btf.c | 29 +++++++++++++++++++ kernel/bpf/verifier.c | 66 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 83 insertions(+), 13 deletions(-) diff --git a/include/linux/btf.h b/include/linux/btf.h index 240401d9b25b..c09b7994de4e 100644 --- a/include/linux/btf.h +++ b/include/linux/btf.h @@ -578,6 +578,7 @@ const char *btf_str_by_offset(const struct btf *btf, u32 offset); struct btf *btf_parse_vmlinux(void); struct btf *bpf_prog_get_target_btf(const struct bpf_prog *prog); u32 *btf_kfunc_flags(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_prog *prog); +int btf_kfunc_check_flag(const struct btf *btf, u32 kfunc_btf_id, u32 flag); bool btf_kfunc_is_allowed(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_prog *prog); u32 *btf_kfunc_is_modify_return(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_prog *prog); diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 64572f85edc8..c4673a54c4ba 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -9114,6 +9114,35 @@ u32 *btf_kfunc_flags(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_p return btf_kfunc_id_set_contains(btf, hook, kfunc_btf_id); } +/* + * Check a single KF_* @flag on a kfunc across all of its hook sets. + * Returns: + * * 1 if @flag is set + * * 0 if @flag is not set + * * -EINVAL if @flag is set inconsistently across the sets + * * -ENOENT if kfunc_btf_id is not a registered kfunc + */ +int btf_kfunc_check_flag(const struct btf *btf, u32 kfunc_btf_id, u32 flag) +{ + enum btf_kfunc_hook hook; + int res = -ENOENT; + bool is_set; + u32 *flags; + + for (hook = 0; hook < BTF_KFUNC_HOOK_MAX; hook++) { + flags = btf_kfunc_id_set_contains(btf, hook, kfunc_btf_id); + if (!flags) + continue; + is_set = *flags & flag; + if (res < 0) + res = is_set; + else if (res != is_set) + return -EINVAL; + } + + return res; +} + u32 *btf_kfunc_is_modify_return(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_prog *prog) { diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 9f1333676365..99444eae917e 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -2584,24 +2584,25 @@ static struct btf *find_kfunc_desc_btf(struct bpf_verifier_env *env, s16 offset) #define KF_IMPL_SUFFIX "_impl" -static const struct btf_type *find_kfunc_impl_proto(struct bpf_verifier_env *env, +static const struct btf_type *find_kfunc_impl_proto(struct bpf_verifier_log *log, struct btf *btf, const char *func_name) { - char *buf = env->tmp_str_buf; const struct btf_type *func; + char buf[KSYM_NAME_LEN]; s32 impl_id; int len; - len = snprintf(buf, TMP_STR_BUF_LEN, "%s%s", func_name, KF_IMPL_SUFFIX); - if (len < 0 || len >= TMP_STR_BUF_LEN) { - verbose(env, "function name %s%s is too long\n", func_name, KF_IMPL_SUFFIX); + len = snprintf(buf, sizeof(buf), "%s%s", func_name, KF_IMPL_SUFFIX); + if (len < 0 || len >= sizeof(buf)) { + bpf_log(log, "function name %s%s is too long\n", + func_name, KF_IMPL_SUFFIX); return NULL; } impl_id = btf_find_by_name_kind(btf, buf, BTF_KIND_FUNC); if (impl_id <= 0) { - verbose(env, "cannot find function %s in BTF\n", buf); + bpf_log(log, "cannot find function %s in BTF\n", buf); return NULL; } @@ -2653,7 +2654,7 @@ static int fetch_kfunc_meta(struct bpf_verifier_env *env, * can be found through the counterpart _impl kfunc. */ if (kfunc_flags && (*kfunc_flags & KF_IMPLICIT_ARGS)) - func_proto = find_kfunc_impl_proto(env, btf, func_name); + func_proto = find_kfunc_impl_proto(&env->log, btf, func_name); else func_proto = btf_type_by_id(btf, func->type); @@ -18880,6 +18881,47 @@ static int btf_id_allow_sleepable(u32 btf_id, unsigned long addr, const struct b return -EINVAL; } +/* + * Resolve the prototype describing a trace target's real ABI. A + * KF_IMPLICIT_ARGS kfunc has its injected args stripped from the public + * prototype, so use the _impl prototype; other targets use their own. + */ +static const struct btf_type * +btf_attach_func_proto(struct bpf_verifier_log *log, struct btf *btf, u32 func_id) +{ + const struct btf_type *func; + struct module *mod = NULL; + const char *name; + int implicit; + + func = btf_type_by_id(btf, func_id); + if (!func || !btf_type_is_func(func)) + return NULL; + name = btf_name_by_offset(btf, func->name_off); + + /* + * btf_kfunc_check_flag() reads kfunc_set_tab, which for a module is + * stable only once it is live; hold a module ref across the read to + * exclude a concurrent module load. + */ + if (btf_is_module(btf)) { + mod = btf_try_get_module(btf); + if (!mod) + return NULL; + } + implicit = btf_kfunc_check_flag(btf, func_id, KF_IMPLICIT_ARGS); + module_put(mod); + + if (implicit == -EINVAL) { + bpf_log(log, "kfunc %s has inconsistent KF_IMPLICIT_ARGS\n", name); + return NULL; + } + if (implicit > 0) + return find_kfunc_impl_proto(log, btf, name); + + return btf_type_by_id(btf, func->type); +} + int bpf_check_attach_target(struct bpf_verifier_log *log, const struct bpf_prog *prog, const struct bpf_prog *tgt_prog, @@ -19128,8 +19170,8 @@ int bpf_check_attach_target(struct bpf_verifier_log *log, if (prog_extension && btf_check_type_match(log, prog, btf, t)) return -EINVAL; - t = btf_type_by_id(btf, t->type); - if (!btf_type_is_func_proto(t)) + t = btf_attach_func_proto(log, btf, btf_id); + if (!t || !btf_type_is_func_proto(t)) return -EINVAL; if ((prog->aux->saved_dst_prog_type || prog->aux->saved_dst_attach_type) && @@ -19412,10 +19454,8 @@ int bpf_check_attach_btf_id_multi(struct btf *btf, struct bpf_prog *prog, u32 bt tname = btf_name_by_offset(btf, t->name_off); if (!tname) return -EINVAL; - if (!btf_type_is_func(t)) - return -EINVAL; - t = btf_type_by_id(btf, t->type); - if (!btf_type_is_func_proto(t)) + t = btf_attach_func_proto(NULL, btf, btf_id); + if (!t || !btf_type_is_func_proto(t)) return -EINVAL; err = btf_distill_func_proto(NULL, btf, t, tname, &tgt_info->fmodel); if (err < 0) From 0af15f3057a60a038864d47fa4b54d1c3c3c654c Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 13 Jul 2026 16:52:23 -0700 Subject: [PATCH 429/479] selftests/bpf: Cover tracing implicit kfunc args KF_IMPLICIT_ARGS kfuncs have a BPF-call prototype and a real kernel target prototype. Add a tracing selftest that attaches fentry and fexit programs to bpf_kfunc_implicit_arg(), runs a syscall BPF program that calls it, and checks that the tracing context exposes both the explicit argument and the implicit prog aux pointer. Co-developed-by: Ihor Solodrai Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Ihor Solodrai Link: https://patch.msgid.link/20260713235223.1639022-3-ihor.solodrai@linux.dev Signed-off-by: Eduard Zingerman --- .../prog_tests/kfunc_implicit_args_tracing.c | 36 +++++++++ .../bpf/progs/kfunc_implicit_args_tracing.c | 77 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/kfunc_implicit_args_tracing.c create mode 100644 tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c diff --git a/tools/testing/selftests/bpf/prog_tests/kfunc_implicit_args_tracing.c b/tools/testing/selftests/bpf/prog_tests/kfunc_implicit_args_tracing.c new file mode 100644 index 000000000000..61cc5aaba025 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/kfunc_implicit_args_tracing.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#include +#include "kfunc_implicit_args_tracing.skel.h" + +void test_kfunc_implicit_args_tracing(void) +{ + struct kfunc_implicit_args_tracing *skel; + LIBBPF_OPTS(bpf_test_run_opts, topts); + int err, fd; + + skel = kfunc_implicit_args_tracing__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + + err = kfunc_implicit_args_tracing__attach(skel); + if (!ASSERT_OK(err, "attach")) + goto cleanup; + + fd = bpf_program__fd(skel->progs.trigger_implicit_arg); + err = bpf_prog_test_run_opts(fd, &topts); + if (!ASSERT_OK(err, "test_run")) + goto cleanup; + + ASSERT_EQ(topts.retval, 5, "kfunc_retval"); + ASSERT_EQ(skel->bss->fentry_arg_cnt, 2, "fentry_arg_cnt"); + ASSERT_NEQ(skel->bss->fentry_aux_arg, 0, "fentry_aux_arg"); + ASSERT_EQ(skel->bss->fentry_result, 1, "fentry_result"); + ASSERT_EQ(skel->bss->fexit_arg_cnt, 2, "fexit_arg_cnt"); + ASSERT_NEQ(skel->bss->fexit_aux_arg, 0, "fexit_aux_arg"); + ASSERT_EQ(skel->bss->fexit_result, 1, "fexit_result"); + +cleanup: + kfunc_implicit_args_tracing__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c b/tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c new file mode 100644 index 000000000000..995f8b8b5b9e --- /dev/null +++ b/tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#include +#include +#include +#include + +extern int bpf_kfunc_implicit_arg(int a) __weak __ksym; + +char _license[] SEC("license") = "GPL"; + +/* Shared arg checks; reports arg count and aux, returns 1 on success. */ +static __always_inline __u64 +check_implicit_args(void *ctx, __u64 *arg_cnt, __u64 *aux_arg) +{ + __u64 a = 0, aux = 0, z = 0; + __u64 result; + __s64 err; + + *arg_cnt = bpf_get_func_arg_cnt(ctx); + result = *arg_cnt == 2; + + err = bpf_get_func_arg(ctx, 0, &a); + result &= err == 0 && (int)a == 5; + + err = bpf_get_func_arg(ctx, 1, &aux); + *aux_arg = aux; + result &= err == 0 && aux != 0; + + err = bpf_get_func_arg(ctx, 2, &z); + result &= err == -EINVAL; + + return result; +} + +__u64 fentry_result; +__u64 fentry_arg_cnt; +__u64 fentry_aux_arg; + +SEC("fentry/bpf_kfunc_implicit_arg") +int BPF_PROG(trace_implicit_arg_fentry) +{ + __u64 ret = 0; + __s64 err; + + fentry_result = check_implicit_args(ctx, &fentry_arg_cnt, &fentry_aux_arg); + + err = bpf_get_func_ret(ctx, &ret); + fentry_result &= err == -EOPNOTSUPP; + + return 0; +} + +__u64 fexit_result; +__u64 fexit_arg_cnt; +__u64 fexit_aux_arg; + +SEC("fexit/bpf_kfunc_implicit_arg") +int BPF_PROG(trace_implicit_arg_fexit) +{ + __u64 ret = 0; + __s64 err; + + fexit_result = check_implicit_args(ctx, &fexit_arg_cnt, &fexit_aux_arg); + + err = bpf_get_func_ret(ctx, &ret); + fexit_result &= err == 0 && ret == 5; + + return 0; +} + +SEC("syscall") +int trigger_implicit_arg(void *ctx) +{ + return bpf_kfunc_implicit_arg(5); +} From faebb7ba1ac65fa5810b640df02ce04e509fdc11 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Thu, 16 Jul 2026 08:13:05 -0700 Subject: [PATCH 430/479] accel/amdxdna: Fix use-after-free of mm_struct in job scheduler amdxdna_cmd_submit() stores current->mm in job->mm without holding any reference. aie2_sched_job_run() later access job->mm from the DRM scheduler worker thread. With only a raw pointer and no structural reference, the mm_struct can be freed before the scheduler runs the job. Fix this by calling mmgrab() to hold a structural mm_count reference for the lifetime of the job, paired with mmdrop() in every cleanup path. Fixes: aac243092b70 ("accel/amdxdna: Add command execution") Reviewed-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260716151305.1595780-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_ctx.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/accel/amdxdna/amdxdna_ctx.c b/drivers/accel/amdxdna/amdxdna_ctx.c index bdbd3db12a6c..31a414c3f0d9 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.c +++ b/drivers/accel/amdxdna/amdxdna_ctx.c @@ -577,6 +577,7 @@ void amdxdna_sched_job_cleanup(struct amdxdna_sched_job *job) amdxdna_arg_bos_put(job); amdxdna_gem_put_obj(job->cmd_bo); dma_fence_put(job->fence); + mmdrop(job->mm); } int amdxdna_cmd_submit(struct amdxdna_client *client, @@ -642,6 +643,7 @@ int amdxdna_cmd_submit(struct amdxdna_client *client, job->hwctx = hwctx; job->mm = current->mm; + mmgrab(job->mm); job->fence = amdxdna_fence_create(hwctx); if (!job->fence) { @@ -676,6 +678,8 @@ int amdxdna_cmd_submit(struct amdxdna_client *client, cmd_put: amdxdna_gem_put_obj(job->cmd_bo); free_job: + if (job->mm) + mmdrop(job->mm); kfree(job); return ret; } From df6134b527a88b3e65ba6ae5073664af091d5fd2 Mon Sep 17 00:00:00 2001 From: Zhiping Zhang Date: Thu, 2 Jul 2026 15:24:58 -0700 Subject: [PATCH 431/479] net/mlx5: free mlx5_st_idx_data on final dealloc Workloads that repeatedly allocate and release mkeys carrying TPH steering-tag hints (e.g. churning RDMA MRs) leak one struct mlx5_st_idx_data per cycle; kmemleak flags it as unreferenced and the kmalloc slab grows over time. When the last reference to an ST table entry is dropped, mlx5_st_dealloc_index() removed the entry from idx_xa but the backing mlx5_st_idx_data allocation was never freed. Free idx_data after the xa_erase() so the lifetime of the bookkeeping struct matches the lifetime of the ST entry it tracks. Cc: stable@vger.kernel.org Fixes: 888a7776f4fb ("net/mlx5: Add support for device steering tag") Reviewed-by: Michael Gur Signed-off-by: Zhiping Zhang Reviewed-by: Leon Romanovsky Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260702222507.1234467-1-zhipingz@meta.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mellanox/mlx5/core/lib/st.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c index 997be91f0a13..7cedc348790d 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c @@ -175,6 +175,7 @@ int mlx5_st_dealloc_index(struct mlx5_core_dev *dev, u16 st_index) if (refcount_dec_and_test(&idx_data->usecount)) { xa_erase(&st->idx_xa, st_index); + kfree(idx_data); /* We leave PCI config space as was before, no mkey will refer to it */ } From 2c1931a81122c3cdc4c89448fe0442c69e21c0d5 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 10 Jul 2026 18:13:17 +0000 Subject: [PATCH 432/479] tcp: fix TIME_WAIT socket reference leak on PSP policy failure Release the TIME_WAIT socket reference and jump to discard_it upon PSP policy failure in both IPv4 and IPv6 receive paths. This prevents a memory leak of tcp_tw_bucket structures. Fixes: 659a2899a57d ("tcp: add datapath logic for PSP with inline key exchange") Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/20260710181317.4060230-1-edumazet@google.com Signed-off-by: Paolo Abeni --- net/ipv4/tcp_ipv4.c | 6 ++++-- net/ipv6/tcp_ipv6.c | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 209ef7522508..4a46da375043 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -2318,8 +2318,10 @@ int tcp_v4_rcv(struct sk_buff *skb) } drop_reason = psp_twsk_rx_policy_check(inet_twsk(sk), skb); - if (drop_reason) - break; + if (drop_reason) { + inet_twsk_put(inet_twsk(sk)); + goto discard_it; + } } /* to ACK */ fallthrough; diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index ebe161d72fbd..522ba45ce9b7 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -1977,8 +1977,10 @@ INDIRECT_CALLABLE_SCOPE int tcp_v6_rcv(struct sk_buff *skb) } drop_reason = psp_twsk_rx_policy_check(inet_twsk(sk), skb); - if (drop_reason) - break; + if (drop_reason) { + inet_twsk_put(inet_twsk(sk)); + goto discard_it; + } } /* to ACK */ fallthrough; From d2e914a4a0d0f753dbae830264850d044026167c Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Fri, 10 Jul 2026 21:36:25 +0200 Subject: [PATCH 433/479] dpll: fix NULL pointer dereference in dpll_msg_add_pin_ref_sync() When a dpll_pin is shared across multiple dpll_device instances and those devices are being unregistered (e.g. during driver module removal), a NULL pointer dereference can occur in dpll_msg_add_pin_ref_sync(). This happens under the following conditions: - A pin is registered with two or more dpll devices (dpll_A, dpll_B) - The pin has ref_sync pairs with other pins - During unregistration of dpll_A's pins, a ref_sync partner pin is unregistered first, removing it from dpll_A->pin_refs - But since the partner pin is still registered with dpll_B, its dpll_refs is not empty, so dpll_pin_ref_sync_pair_del() does NOT run and the partner stays in the pin's ref_sync_pins xarray - When the pin itself is then unregistered from dpll_A, the delete notification calls dpll_msg_add_pin_ref_sync() which finds the partner in ref_sync_pins, passes dpll_pin_available() (partner is still registered with dpll_B), but dpll_pin_on_dpll_priv(dpll_A, partner) returns NULL because partner was already removed from dpll_A->pin_refs - The NULL priv pointer is passed to the driver's ref_sync_get callback, which dereferences it BUG: kernel NULL pointer dereference, address: 0000000000000034 Oops: Oops: 0000 [#1] SMP NOPTI RIP: 0010:zl3073x_dpll_input_pin_ref_sync_get+0x73/0x80 [zl3073x] Call Trace: dpll_msg_add_pin_ref_sync+0xb8/0x200 dpll_cmd_pin_get_one+0x3b6/0x4b0 dpll_pin_event_send+0x72/0x140 __dpll_pin_unregister+0x5a/0x2b0 dpll_pin_unregister+0x49/0x70 Fix this by skipping ref_sync pins whose priv pointer cannot be resolved for the current dpll device. Fixes: 58256a26bfb3 ("dpll: add reference sync get/set") Signed-off-by: Ivan Vecera Reviewed-by: Vadim Fedorenko Reviewed-by: Jiri Pirko Link: https://patch.msgid.link/20260710193625.1378822-1-ivecera@redhat.com Signed-off-by: Paolo Abeni --- drivers/dpll/dpll_netlink.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index bf729cde796a..5703667593a7 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -567,6 +567,9 @@ dpll_msg_add_pin_ref_sync(struct sk_buff *msg, struct dpll_pin *pin, if (!dpll_pin_available(ref_sync_pin)) continue; ref_sync_pin_priv = dpll_pin_on_dpll_priv(dpll, ref_sync_pin); + /* Pin may have been unregistered from this dpll already */ + if (!ref_sync_pin_priv) + continue; if (WARN_ON(!ops->ref_sync_get)) return -EOPNOTSUPP; ret = ops->ref_sync_get(pin, pin_priv, ref_sync_pin, From f1f5c8a3955f8fda3f84ed883ac8daa1847e724c Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 11 Jul 2026 11:05:37 -0400 Subject: [PATCH 434/479] net/sched: act_tunnel_key: Defer dst_release to RCU callback Fix a race-condition use-after-free in tunnel_key_release_params(). The function releases the metadata_dst of the old params synchronously via dst_release() while deferring the params struct free with kfree_rcu(). A concurrent tunnel_key_act() reader on the datapath may still hold the old params pointer (under rcu_read_lock_bh) and proceed to call dst_clone(¶ms->tcft_enc_metadata->dst) after the writer's dst_release has already pushed the dst's rcuref to RCUREF_DEAD. zdi-disclosures@trendmicro.com produced a poc which i (and Victor) verified that KASAN reports: ================================================================== BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:112 BUG: KASAN: slab-use-after-free in atomic_sub_return_release include/linux/atomic/atomic-instrumented.h:326 BUG: KASAN: slab-use-after-free in __rcuref_put include/linux/rcuref.h:109 BUG: KASAN: slab-use-after-free in rcuref_put include/linux/rcuref.h:173 BUG: KASAN: slab-use-after-free in dst_release+0x5b/0x370 net/core/dst.c:168 Write of size 4 at addr ffff88806158de40 by task poc/9388 CPU: 0 UID: 0 PID: 9388 Comm: poc Tainted: G W 7.1.0-rc7 #7 PREEMPT(lazy) Tainted: [W]=WARN Hardware name: QEMU Ubuntu 25.10 PC v2 (i440FX + PIIX, + 10.1 machine, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: __dump_stack lib/dump_stack.c:94 dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 print_report+0x139/0x4ad mm/kasan/report.c:482 kasan_report+0xe4/0x1d0 mm/kasan/report.c:595 check_region_inline mm/kasan/generic.c:186 kasan_check_range+0x125/0x200 mm/kasan/generic.c:200 instrument_atomic_read_write include/linux/instrumented.h:112 atomic_sub_return_release include/linux/atomic/atomic-instrumented.h:326 __rcuref_put include/linux/rcuref.h:109 rcuref_put include/linux/rcuref.h:173 dst_release+0x5b/0x370 net/core/dst.c:168 refdst_drop include/net/dst.h:272 skb_dst_drop include/net/dst.h:284 skb_release_head_state+0x293/0x400 net/core/skbuff.c:1163 skb_release_all net/core/skbuff.c:1187 [..] Allocated by task 9391: kasan_save_stack+0x30/0x50 mm/kasan/common.c:57 kasan_save_track+0x14/0x30 mm/kasan/common.c:78 poison_kmalloc_redzone mm/kasan/common.c:398 __kasan_kmalloc+0x9a/0xb0 mm/kasan/common.c:415 kasan_kmalloc include/linux/kasan.h:263 __do_kmalloc_node mm/slub.c:5296 __kmalloc_noprof+0x2f1/0x830 mm/slub.c:5308 kmalloc_noprof include/linux/slab.h:954 kzalloc_noprof include/linux/slab.h:1188 offload_action_alloc+0x2f/0x130 net/core/flow_offload.c:35 tcf_action_offload_add_ex+0x1ba/0x880 net/sched/act_api.c:258 tcf_action_offload_add net/sched/act_api.c:293 tcf_action_init+0x66e/0xa20 net/sched/act_api.c:1547 tcf_action_add+0xf6/0x5d0 net/sched/act_api.c:2101 [..] Freed by task 9391: kasan_save_stack+0x30/0x50 mm/kasan/common.c:57 kasan_save_track+0x14/0x30 mm/kasan/common.c:78 kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584 poison_slab_object mm/kasan/common.c:253 __kasan_slab_free+0x6b/0x90 mm/kasan/common.c:285 kasan_slab_free include/linux/kasan.h:235 slab_free_hook mm/slub.c:2689 slab_free mm/slub.c:6251 kfree+0x21f/0x6b0 mm/slub.c:6566 tcf_action_offload_add_ex+0x4ad/0x880 net/sched/act_api.c:284 tcf_action_offload_add net/sched/act_api.c:293 tcf_action_init+0x66e/0xa20 net/sched/act_api.c:1547 tcf_action_add+0xf6/0x5d0 net/sched/act_api.c:2101 The buggy address belongs to the object at ffff88806158de00 which belongs to the cache kmalloc-256 of size 256 The buggy address is located 64 bytes inside of freed 256-byte region [ffff88806158de00, ffff88806158df00) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff88806158d600 pfn:0x6158c head: order:1 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0 flags: 0x4fff00000000240(workingset|head|node=1|zone=1|lastcpupid=0x7ff) page_type: f5(slab) raw: 04fff00000000240 ffff88801c841b40 ffffea0001856290 ffffea0001856190 raw: ffff88806158d600 0000000800100009 00000000f5000000 0000000000000000 head: 04fff00000000240 ffff88801c841b40 ffffea0001856290 ffffea0001856190 head: ffff88806158d600 0000000800100009 00000000f5000000 0000000000000000 head: 04fff00000000001 ffffffffffffff81 00000000ffffffff 00000000ffffffff head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000002 page dumped because: kasan: bad access detected page_owner tracks the page as allocated page last allocated via order 1, migratetype Unmovable, gfp_mask 0xd2820(GFP_ATOMIC|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 9391, tgid 9378 (poc), ts 123227323196, free_ts 0 set_page_owner include/linux/page_owner.h:32 post_alloc_hook+0xfe/0x140 mm/page_alloc.c:1853 prep_new_page mm/page_alloc.c:1861 get_page_from_freelist+0x110c/0x2fc0 mm/page_alloc.c:3941 __alloc_frozen_pages_noprof+0x263/0x2bc0 mm/page_alloc.c:5221 alloc_slab_page mm/slub.c:3278 allocate_slab mm/slub.c:3467 new_slab+0xa6/0x690 mm/slub.c:3525 refill_objects+0x271/0x420 mm/slub.c:7272 refill_sheaf mm/slub.c:2816 __pcs_replace_empty_main+0x373/0x630 mm/slub.c:4652 alloc_from_pcs mm/slub.c:4750 slab_alloc_node mm/slub.c:4884 __do_kmalloc_node mm/slub.c:5295 __kmalloc_noprof+0x66d/0x830 mm/slub.c:5308 kmalloc_noprof include/linux/slab.h:954 metadata_dst_alloc+0x26/0x90 net/core/dst.c:298 tun_rx_dst include/net/dst_metadata.h:144 __ip_tun_set_dst include/net/dst_metadata.h:208 tunnel_key_init+0xb01/0x1b90 net/sched/act_tunnel_key.c:451 tcf_action_init_1+0x46b/0x6c0 net/sched/act_api.c:1428 tcf_action_init+0x448/0xa20 net/sched/act_api.c:1503 tcf_action_add+0xf6/0x5d0 net/sched/act_api.c:2101 [..] ================================================================== Fix by moving dst_release() into a custom RCU callback that runs after the grace period, matching the lifetime of the containing params struct. Readers in the datapath therefore always find a live rcuref when calling dst_clone(). Fixes: 9174c3df1cd18 ("net/sched: act_tunnel_key: fix memory leak in case of action replace") Reported-by: zdi-disclosures@trendmicro.com Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Reviewed-by: Davide Caratti Link: https://patch.msgid.link/20260711150537.7946-1-jhs@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/act_tunnel_key.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/net/sched/act_tunnel_key.c b/net/sched/act_tunnel_key.c index 876b30c5709e..b14807761d82 100644 --- a/net/sched/act_tunnel_key.c +++ b/net/sched/act_tunnel_key.c @@ -342,14 +342,20 @@ static const struct nla_policy tunnel_key_policy[TCA_TUNNEL_KEY_MAX + 1] = { [TCA_TUNNEL_KEY_ENC_TTL] = { .type = NLA_U8 }, }; +static void tunnel_key_release_params_rcu(struct rcu_head *head) +{ + struct tcf_tunnel_key_params *p = container_of(head, typeof(*p), rcu); + + if (p->tcft_action == TCA_TUNNEL_KEY_ACT_SET) + dst_release(&p->tcft_enc_metadata->dst); + kfree(p); +} + static void tunnel_key_release_params(struct tcf_tunnel_key_params *p) { if (!p) return; - if (p->tcft_action == TCA_TUNNEL_KEY_ACT_SET) - dst_release(&p->tcft_enc_metadata->dst); - - kfree_rcu(p, rcu); + call_rcu(&p->rcu, tunnel_key_release_params_rcu); } static int tunnel_key_init(struct net *net, struct nlattr *nla, From e0b5252a59383b77d1b8dbeda00b7184dd95f4d3 Mon Sep 17 00:00:00 2001 From: Xin Long Date: Fri, 10 Jul 2026 14:12:35 -0400 Subject: [PATCH 435/479] sctp: fix auth_hmacs array size in struct sctp_cookie The auth_hmacs array in struct sctp_cookie is supposed to store a complete SCTP_AUTH_HMAC_ALGO parameter, which consists of a struct sctp_paramhdr followed by N HMAC identifiers. However, the array size was calculated using an extra 2 bytes instead of sizeof(struct sctp_paramhdr), which is 4 bytes. When four HMAC identifiers are configured, the HMAC-ALGO parameter stored in the endpoint is larger than the auth_hmacs buffer in the cookie. As a result, sctp_association_init() copies beyond the end of auth_hmacs when initializing the association, corrupting the adjacent auth_chunks field. This can lead to an invalid HMAC identifier being accepted and later cause an out-of-bounds read in sctp_auth_get_hmac(). Fix the array size calculation by including the full SCTP parameter header size. Fixes: 1f485649f529 ("[SCTP]: Implement SCTP-AUTH internals") Reported-by: Yuan Tan Reported-by: Xin Liu Reported-by: Zihan Xi Reported-by: Ren Wei Signed-off-by: Xin Long Link: https://patch.msgid.link/634a0de0d5de29532915e6d47c92a0cbc206e03f.1783707155.git.lucien.xin@gmail.com Signed-off-by: Paolo Abeni --- include/net/sctp/structs.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/net/sctp/structs.h b/include/net/sctp/structs.h index affee44bd38e..cccc662561aa 100644 --- a/include/net/sctp/structs.h +++ b/include/net/sctp/structs.h @@ -312,7 +312,8 @@ struct sctp_cookie { __u8 auth_random[sizeof(struct sctp_paramhdr) + SCTP_AUTH_RANDOM_LENGTH]; - __u8 auth_hmacs[SCTP_AUTH_NUM_HMACS * sizeof(__u16) + 2]; + __u8 auth_hmacs[sizeof(struct sctp_paramhdr) + + SCTP_AUTH_NUM_HMACS * sizeof(__u16)]; __u8 auth_chunks[sizeof(struct sctp_paramhdr) + SCTP_AUTH_MAX_CHUNKS]; /* This is a shim for my peer's INIT packet, followed by From 1cb8553c02e93e5a150cebd42f9ee3db0ece4707 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Sun, 12 Jul 2026 00:37:16 +0800 Subject: [PATCH 436/479] bnxt_en: Handle partially initialized auxiliary devices bnxt_aux_devices_init() calls auxiliary_device_init() before all fields used by bnxt_aux_dev_release() are initialized. After auxiliary_device_init() succeeds, later errors must unwind with auxiliary_device_uninit(), which invokes the release callback. The release callback assumes that aux_priv->id, aux_priv->edev, edev->net and edev->ulp_tbl are all populated. If allocation fails after auxiliary_device_init(), the release path can otherwise dereference or clear partially initialized state. Allocate and attach the bnxt_en_dev and ULP table before calling auxiliary_device_init(), so the release callback only sees a fully initialized auxiliary private object. If auxiliary_device_init() itself fails, free those allocations directly because device_initialize() has not run and the release callback will not be invoked. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 194fad5b2781 ("bnxt_en: Refactor bnxt_rdma_aux_device_init/uninit functions") Signed-off-by: Ruoyu Wang Reviewed-by: Pavan Chebbi Link: https://patch.msgid.link/20260711163716.3996929-1-ruoyuw560@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt_ulp.c | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ulp.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ulp.c index 5c751933da6a..a515c368bac0 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ulp.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ulp.c @@ -566,6 +566,18 @@ void bnxt_aux_devices_init(struct bnxt *bp) if (!aux_priv) goto next_auxdev; + edev = kzalloc_obj(*edev); + if (!edev) + goto aux_priv_free; + aux_priv->edev = edev; + bnxt_set_edev_info(edev, bp); + + ulp = kzalloc_obj(*ulp); + if (!ulp) + goto edev_free; + edev->ulp_tbl = ulp; + aux_priv->id = idx; + aux_dev = &aux_priv->aux_dev; aux_dev->id = bp->auxdev_id; aux_dev->name = bnxt_aux_devices[idx].name; @@ -573,37 +585,26 @@ void bnxt_aux_devices_init(struct bnxt *bp) aux_dev->dev.release = bnxt_aux_dev_release; rc = auxiliary_device_init(aux_dev); - if (rc) { - kfree(aux_priv); - goto next_auxdev; - } + if (rc) + goto ulp_free; bp->aux_priv[idx] = aux_priv; /* From this point, all cleanup will happen via the .release * callback & any error unwinding will need to include a call * to auxiliary_device_uninit. */ - edev = kzalloc_obj(*edev); - if (!edev) - goto aux_dev_uninit; - - aux_priv->edev = edev; - bnxt_set_edev_info(edev, bp); - - ulp = kzalloc_obj(*ulp); - if (!ulp) - goto aux_dev_uninit; - - edev->ulp_tbl = ulp; bp->edev[idx] = edev; if (idx == BNXT_AUXDEV_RDMA) bp->ulp_num_msix_want = bnxt_set_dflt_ulp_msix(bp); - aux_priv->id = idx; bnxt_auxdev_set_state(bp, idx, BNXT_ADEV_STATE_INIT); continue; -aux_dev_uninit: - auxiliary_device_uninit(aux_dev); +ulp_free: + kfree(ulp); +edev_free: + kfree(edev); +aux_priv_free: + kfree(aux_priv); next_auxdev: if (idx == BNXT_AUXDEV_RDMA) bp->flags &= ~BNXT_FLAG_ROCE_CAP; From 04aeddf2dadd0eb7ad016a766dcbe9c983311f09 Mon Sep 17 00:00:00 2001 From: Andre Carvalho Date: Fri, 10 Jul 2026 23:19:17 +0100 Subject: [PATCH 437/479] selftests: netconsole: only restore MAC when it changed on resume The "mac" bind mode reactivation downs the interface, restores the saved MAC and renames it to trigger a target resume. This assumes the recreated interface comes back with a different MAC, which is true under MACAddressPolicy=none (as on the Netdev CI) but not when MACs are persistent. In the persistent case netconsole resumes the target on its own, and the down/restore/rename flow instead drops it and fails the test. Guard the block on the MAC having actually changed so the test passes under both policies. Fixes: 6ecc08329bab ("selftests: netconsole: validate target resume") Reported-by: Matthieu Baerts (NGI0) Closes: https://lore.kernel.org/netdev/f398373e-2cb4-4649-a491-9763df94d98b@kernel.org/ Signed-off-by: Andre Carvalho Tested-by: Matthieu Baerts (NGI0) Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260710-netcons-mac-reload-v1-1-3fb1bcc70b4a@gmail.com Signed-off-by: Paolo Abeni --- .../testing/selftests/drivers/net/netconsole/netcons_resume.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh index cb59cf436dd0..d9111f2102bc 100755 --- a/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh +++ b/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh @@ -44,7 +44,8 @@ function trigger_reactivation() { # Restore MACs ip netns exec "${NAMESPACE}" ip link set "${DSTIF}" \ address "${SAVED_DSTMAC}" - if [ "${BINDMODE}" == "mac" ]; then + if [ "${BINDMODE}" == "mac" ] && + [ "$(mac_get "${SRCIF}")" != "${SAVED_SRCMAC}" ]; then ip link set dev "${SRCIF}" down ip link set dev "${SRCIF}" address "${SAVED_SRCMAC}" # Rename device in order to trigger target resume, as initial From 2c72eb6286347d05a885412fb076993bd5286b53 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Sun, 12 Jul 2026 21:03:43 +0800 Subject: [PATCH 438/479] llc: fix SAP refcount leak when creating incoming sockets llc_sap_add_socket() takes a SAP reference for each socket added to a SAP, and llc_sap_remove_socket() releases it. llc_create_incoming_sock() takes an additional SAP reference after adding the child socket. This extra reference was balanced by an explicit llc_sap_put() in llc_ui_release() until commit 3100aa9d74db ("llc: fix SAP reference counting w.r.t. socket handling") removed that put. The corresponding hold in the accept path was left behind. When such a child socket is removed, only the reference taken by llc_sap_add_socket() is released. The extra reference keeps the SAP alive after its last socket is removed. Remove the obsolete hold. Fixes: 3100aa9d74db ("llc: fix SAP reference counting w.r.t. socket handling") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260712130343.518797-1-xuanqiang.luo@linux.dev Signed-off-by: Paolo Abeni --- net/llc/llc_conn.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/llc/llc_conn.c b/net/llc/llc_conn.c index e8f427375c68..260460d50f54 100644 --- a/net/llc/llc_conn.c +++ b/net/llc/llc_conn.c @@ -767,7 +767,6 @@ static struct sock *llc_create_incoming_sock(struct sock *sk, newllc->dev = dev; dev_hold(dev); llc_sap_add_socket(llc->sap, newsk); - llc_sap_hold(llc->sap); out: return newsk; } From 56d96fededd61192cd7cc8d2b0f36adfd59036c3 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sat, 11 Jul 2026 04:50:00 -0700 Subject: [PATCH 439/479] mpls: fix NULL deref in mpls_valid_fib_dump_req() on CONFIG_INET=n On CONFIG_INET=n builds, mpls_valid_fib_dump_req() walks the parsed attribute table itself instead of calling ip_valid_fib_dump_req(). The RTA_OIF arm passes tb[RTA_OIF] to nla_get_u32() without checking it is present, so an RTM_GETROUTE dump for AF_MPLS with strict checking and no RTA_OIF hits a NULL dereference. RTM_GETROUTE is RTNL_KIND_GET, which rtnetlink_rcv_msg() permits without CAP_NET_ADMIN, so an unprivileged user can trigger it. Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: 0010:mpls_valid_fib_dump_req (net/mpls/af_mpls.c:2189) Call Trace: mpls_dump_routes (net/mpls/af_mpls.c:2236) netlink_dump (net/netlink/af_netlink.c:2331) __netlink_dump_start (net/netlink/af_netlink.c:2446) rtnetlink_rcv_msg (net/core/rtnetlink.c:7033) netlink_rcv_skb (net/netlink/af_netlink.c:2556) netlink_unicast (net/netlink/af_netlink.c:1345) netlink_sendmsg (net/netlink/af_netlink.c:1900) __sock_sendmsg (net/socket.c:790) ____sys_sendmsg (net/socket.c:2684) ___sys_sendmsg (net/socket.c:2738) __sys_sendmsg (net/socket.c:2770) do_syscall_64 (arch/x86/entry/syscall_64.c:94) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Skip unset attributes, as ip_valid_fib_dump_req() does. Fixes: 196cfebf8972 ("net/mpls: Handle kernel side filtering of route dumps") Assisted-by: Claude:claude-opus-4-8 Reported-by: Xiang Mei Signed-off-by: Weiming Shi Reviewed-by: David Ahern Link: https://patch.msgid.link/20260711114958.1009619-3-bestswngs@gmail.com Signed-off-by: Paolo Abeni --- net/mpls/af_mpls.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c index ca504d9626cf..318cb7e2ac5f 100644 --- a/net/mpls/af_mpls.c +++ b/net/mpls/af_mpls.c @@ -2186,6 +2186,9 @@ static int mpls_valid_fib_dump_req(struct net *net, const struct nlmsghdr *nlh, int ifindex; if (i == RTA_OIF) { + if (!tb[i]) + continue; + ifindex = nla_get_u32(tb[i]); filter->dev = dev_get_by_index_rcu(net, ifindex); if (!filter->dev) From 3afc64c61ce906a04f073ca350b46de10e8302f9 Mon Sep 17 00:00:00 2001 From: Woraphat Khiaodaeng Date: Fri, 17 Jul 2026 22:45:37 +0700 Subject: [PATCH 440/479] io_uring/bpf-ops: reject re-registration of an already-bound ops io_install_bpf() only rejects a second registration on the ctx side (ctx->bpf_ops) and sets the per-map back-pointer ops->priv unconditionally. The struct_ops link path never advances a map past BPF_STRUCT_OPS_STATE_READY, so the same io_uring_bpf_ops map can be registered more than once, and bpf_io_reg() re-resolves the target ring via fget(ops->ring_fd) on every call. A caller can therefore point the same ring_fd at a different io_ring_ctx between two BPF_LINK_CREATE calls. The second registration passes the ctx->bpf_ops check (the new ctx has none) and overwrites ops->priv, orphaning the first ctx. Teardown (io_eject_bpf()/bpf_io_unreg()) only reaches a ctx through ops->priv, so the orphaned ctx is never torn down: its ctx->loop_step keeps pointing into the struct_ops trampoline, which is freed once the map is gone. A later io_uring_enter() on the orphaned ring then calls the dangling ctx->loop_step from io_run_loop() -- a use-after-free of freed executable memory, reachable by a task with CAP_BPF + CAP_PERFMON. Reject registration when ops->priv is already set, as hid_bpf_reg() does for its struct_ops. Cc: stable@vger.kernel.org Fixes: 98f37634b12b ("io_uring/bpf-ops: implement bpf ops registration") Signed-off-by: Woraphat Khiaodaeng Reviewed-by: Gabriel Krisman Bertazi Reviewed-by: Pavel Begunkov Link: https://patch.msgid.link/20260717154537.129736-1-worapat.kd2@gmail.com Signed-off-by: Jens Axboe --- io_uring/bpf-ops.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/io_uring/bpf-ops.c b/io_uring/bpf-ops.c index 5a50f0675fe5..cf2bd068e331 100644 --- a/io_uring/bpf-ops.c +++ b/io_uring/bpf-ops.c @@ -168,6 +168,8 @@ static int io_install_bpf(struct io_ring_ctx *ctx, struct io_uring_bpf_ops *ops) if (ctx->bpf_ops) return -EBUSY; + if (ops->priv) + return -EBUSY; if (WARN_ON_ONCE(!ops->loop_step)) return -EINVAL; From 29c57db1629e7a3cddfe1a4b13e72682acfef38e Mon Sep 17 00:00:00 2001 From: Roman Li Date: Wed, 13 May 2026 21:49:15 -0400 Subject: [PATCH 441/479] drm/amdgpu/discovery: Fix device family for DCN42 GC 11.7.0 and 11.7.1 should map to AMDGPU_FAMILY_GC_11_5_4 for DCN42. Fixes: cf591e67c095 ("drm/amdgpu: add support for GC IP version 11.7.0") Fixes: a928d8d81ec5 ("drm/amdgpu: add support for GC IP version 11.7.1") Signed-off-by: Roman Li Acked-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit f8ee6447e7ec1d75d6663c817e45566dd01f440b) --- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index 7b9bb998906d..2860f12915c0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -3137,9 +3137,11 @@ int amdgpu_discovery_set_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + adev->family = AMDGPU_FAMILY_GC_11_5_0; + break; case IP_VERSION(11, 7, 0): case IP_VERSION(11, 7, 1): - adev->family = AMDGPU_FAMILY_GC_11_5_0; + adev->family = AMDGPU_FAMILY_GC_11_5_4; break; case IP_VERSION(12, 0, 0): case IP_VERSION(12, 0, 1): From fbbbd98f200f11e7f9b66ca7f2d18546be8b254e Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Thu, 4 Jun 2026 11:36:09 -0400 Subject: [PATCH 442/479] drm/amd/display: Fix DCN42B null registers & register masks [why] DCN42B is missing some register masks, which are causing errors in dmesg. [how] Make DCN42B reuse the DCN42 register lists, and add the missing defines manually. Fixes: 64142f9d51af ("drm/amd/display: Fix DCN42 null registers & register masks") Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Signed-off-by: Alex Deucher (cherry picked from commit b7d69145907cdefcbd39a70a31eefd30919af9f1) --- .../dc/resource/dcn42b/dcn42b_resource.c | 20 ++- .../dc/resource/dcn42b/dcn42b_resource.h | 116 +----------------- 2 files changed, 20 insertions(+), 116 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 527d17f29f3b..1a3b9e942caa 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -22,6 +22,7 @@ #include "dcn35/dcn35_resource.h" #include "dcn321/dcn321_resource.h" #include "dcn401/dcn401_resource.h" +#include "dcn42/dcn42_resource.h" #include "dcn42/dcn42_resource_fpu.h" #include "dcn10/dcn10_ipp.h" @@ -116,6 +117,23 @@ #define regAPG9_APG_DBG_GEN_CONTROL 0x38ae #define regAPG9_APG_DBG_GEN_CONTROL_BASE_IDX 2 +#define regHUBP0_HUBPREQ_DEBUG_DB 0x05f8 +#define regHUBP0_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP0_HUBPREQ_DEBUG 0x05f9 +#define regHUBP0_HUBPREQ_DEBUG_BASE_IDX 2 +#define regHUBP1_HUBPREQ_DEBUG_DB 0x06d4 +#define regHUBP1_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP1_HUBPREQ_DEBUG 0x06d5 +#define regHUBP1_HUBPREQ_DEBUG_BASE_IDX 2 +#define regHUBP2_HUBPREQ_DEBUG_DB 0x07b0 +#define regHUBP2_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP2_HUBPREQ_DEBUG 0x07b1 +#define regHUBP2_HUBPREQ_DEBUG_BASE_IDX 2 +#define regHUBP3_HUBPREQ_DEBUG_DB 0x088c +#define regHUBP3_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP3_HUBPREQ_DEBUG 0x088d +#define regHUBP3_HUBPREQ_DEBUG_BASE_IDX 2 + enum dcn401_clk_src_array_id { DCN401_CLK_SRC_PLL0, DCN401_CLK_SRC_PLL1, @@ -461,7 +479,7 @@ static const struct dcn_optc_mask optc_mask = { OPTC_COMMON_MASK_SH_LIST_DCN42B(_MASK)}; #define hubp_regs_init(id) \ - HUBP_REG_LIST_DCN42B_RI(id) + HUBP_REG_LIST_DCN42_RI(id) static struct dcn_hubp2_registers hubp_regs[4]; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h index 2da3e3c8304a..2824a0e1acc9 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h @@ -344,7 +344,6 @@ * DCCG_SRII(PHASE, DP_DTO, 3), * DCCG_SRII(MODULO, DP_DTO, 3), * SR(DSCCLK3_DTO_PARAM), - * SR(HDMISTREAMCLK_CNTL), * SR(SYMCLKD_CLOCK_ENABLE), * SR(SYMCLKE_CLOCK_ENABLE) */ @@ -360,6 +359,7 @@ SR(PHYBSYMCLK_CLOCK_CNTL), \ SR(PHYCSYMCLK_CLOCK_CNTL), \ SR(DPSTREAMCLK_CNTL), \ + SR(HDMISTREAMCLK_CNTL), \ SR(SYMCLK32_SE_CNTL), \ SR(SYMCLK32_LE_CNTL), \ DCCG_SRII(PIXEL_RATE_CNTL, OTG, 0), \ @@ -542,120 +542,6 @@ SRI_ARR(DC_ABM1_ACE_OFFSET_SLOPE_DATA, ABM, id), \ SRI_ARR(DC_ABM1_ACE_PWL_CNTL, ABM, id) -/* HUBP */ -/* Not in DCN42B: HUBPREQ_DEBUG_DB and HUBPREQ_DEBUG */ -#define HUBP_REG_LIST_DCN42B_RI(id) \ - SRI_ARR(DCN_DMDATA_VM_CNTL, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_3, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_4, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_5, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_6, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_5, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_6, HUBPREQ, id), \ - HUBP_REG_LIST_DCN_VM_RI(id), \ - SRI_ARR(PREFETCH_SETTINGS, HUBPREQ, id), \ - SRI_ARR(PREFETCH_SETTINGS_C, HUBPREQ, id), \ - SRI_ARR(DCN_VM_SYSTEM_APERTURE_LOW_ADDR, HUBPREQ, id), \ - SRI_ARR(DCN_VM_SYSTEM_APERTURE_HIGH_ADDR, HUBPREQ, id), \ - SRI_ARR(CURSOR_SETTINGS, HUBPREQ, id), \ - SRI_ARR(CURSOR_SURFACE_ADDRESS_HIGH, CURSOR0_, id), \ - SRI_ARR(CURSOR_SURFACE_ADDRESS, CURSOR0_, id), \ - SRI_ARR(CURSOR_SIZE, CURSOR0_, id), \ - SRI_ARR(CURSOR_CONTROL, CURSOR0_, id), \ - SRI_ARR(CURSOR_POSITION, CURSOR0_, id), \ - SRI_ARR(CURSOR_HOT_SPOT, CURSOR0_, id), \ - SRI_ARR(CURSOR_DST_OFFSET, CURSOR0_, id), \ - SRI_ARR(DMDATA_ADDRESS_HIGH, CURSOR0_, id), \ - SRI_ARR(DMDATA_ADDRESS_LOW, CURSOR0_, id), \ - SRI_ARR(DMDATA_CNTL, CURSOR0_, id), \ - SRI_ARR(DMDATA_SW_CNTL, CURSOR0_, id), \ - SRI_ARR(DMDATA_QOS_CNTL, CURSOR0_, id), \ - SRI_ARR(DMDATA_SW_DATA, CURSOR0_, id), \ - SRI_ARR(DMDATA_STATUS, CURSOR0_, id), \ - SRI_ARR(FLIP_PARAMETERS_0, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_1, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_2, HUBPREQ, id), \ - SRI_ARR(DCN_CUR1_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_CUR1_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(DCSURF_FLIP_CONTROL2, HUBPREQ, id), \ - SRI_ARR(VMID_SETTINGS_0, HUBPREQ, id), \ - SRI_ARR(DCHUBP_CNTL, HUBP, id), \ - SRI_ARR(DCSURF_ADDR_CONFIG, HUBP, id), \ - SRI_ARR(DCSURF_TILING_CONFIG, HUBP, id), \ - SRI_ARR(DCSURF_SURFACE_PITCH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_PITCH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_CONFIG, HUBP, id), \ - SRI_ARR(DCSURF_FLIP_CONTROL, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_DIMENSION, HUBP, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_START, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_DIMENSION, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_START, HUBP, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_DIMENSION_C, HUBP, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_START_C, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_DIMENSION_C, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_START_C, HUBP, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_CONTROL, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_FLIP_INTERRUPT, HUBPREQ, id), \ - SRI_ARR(HUBPRET_CONTROL, HUBPRET, id), \ - SRI_ARR(HUBPRET_READ_LINE_STATUS, HUBPRET, id), \ - SRI_ARR(DCN_EXPANSION_MODE, HUBPREQ, id), \ - SRI_ARR(DCHUBP_REQ_SIZE_CONFIG, HUBP, id), \ - SRI_ARR(DCHUBP_REQ_SIZE_CONFIG_C, HUBP, id), \ - SRI_ARR(BLANK_OFFSET_0, HUBPREQ, id), \ - SRI_ARR(BLANK_OFFSET_1, HUBPREQ, id), \ - SRI_ARR(DST_DIMENSIONS, HUBPREQ, id), \ - SRI_ARR(DST_AFTER_SCALER, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_0, HUBPREQ, id), \ - SRI_ARR(REF_FREQ_TO_PIX_FREQ, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_1, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_3, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_4, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_5, HUBPREQ, id), \ - SRI_ARR(PER_LINE_DELIVERY_PRE, HUBPREQ, id), \ - SRI_ARR(PER_LINE_DELIVERY, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_2, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_4, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_6, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_7, HUBPREQ, id), \ - SRI_ARR(DCN_TTU_QOS_WM, HUBPREQ, id), \ - SRI_ARR(DCN_GLOBAL_TTU_CNTL, HUBPREQ, id), \ - SRI_ARR(DCN_SURF0_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_SURF0_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(DCN_SURF1_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_SURF1_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(DCN_CUR0_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_CUR0_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(HUBP_CLK_CNTL, HUBP, id), \ - SRI_ARR(HUBPRET_READ_LINE_VALUE, HUBPRET, id), \ - SRI_ARR(DCHUBP_MALL_CONFIG, HUBP, id), \ - SRI_ARR(DCHUBP_VMPG_CONFIG, HUBP, id), \ - SRI_ARR(UCLK_PSTATE_FORCE, HUBPREQ, id), \ - SRI_ARR(HUBP_3DLUT_DLG_PARAM, CURSOR0_, id), \ - HUBP_3DLUT_FL_REG_LIST_DCN401(id) struct dcn42b_resource_pool { struct resource_pool base; }; From 2b0386d4293920e690c0e017708f999b93cc729b Mon Sep 17 00:00:00 2001 From: Yongqiang Sun Date: Mon, 6 Jul 2026 15:15:07 -0400 Subject: [PATCH 443/479] drm/amdkfd: fix 32-bit overflow in CWSR total size calculation total_cwsr_size was computed in 32-bit before being used as a BO/SVM allocation size. With large ctx_save_restore_area_size and debug_memory_size multiplied by the XCC count, the product can wrap, yielding an undersized CWSR save area that firmware later overruns. Promote total_cwsr_size to u64 and use check_add_overflow()/ check_mul_overflow() in both kfd_queue_acquire_buffers() and kfd_queue_release_buffers(). Signed-off-by: Yongqiang Sun Reviewed-by: Philip Yang Signed-off-by: Alex Deucher (cherry picked from commit 319f7e13423ae3f486b9aea82f9ad2d6af0ee608) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_queue.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c index 28354a4e5dd5..98a5512b701b 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c @@ -23,6 +23,7 @@ */ #include +#include #include "kfd_priv.h" #include "kfd_topology.h" #include "kfd_svm.h" @@ -235,7 +236,7 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope struct kfd_topology_device *topo_dev; u64 expected_queue_size; struct amdgpu_vm *vm; - u32 total_cwsr_size; + u64 total_cwsr_size; int err; topo_dev = kfd_topology_device_by_id(pdd->dev->id); @@ -308,8 +309,14 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope goto out_err_unreserve; } - total_cwsr_size = (properties->ctx_save_restore_area_size + - topo_dev->node_props.debug_memory_size) * NUM_XCC(pdd->dev->xcc_mask); + total_cwsr_size = (u64)properties->ctx_save_restore_area_size + + topo_dev->node_props.debug_memory_size; + if (check_mul_overflow(total_cwsr_size, + NUM_XCC(pdd->dev->xcc_mask), + &total_cwsr_size)) { + err = -EINVAL; + goto out_err_unreserve; + } total_cwsr_size = ALIGN(total_cwsr_size, PAGE_SIZE); err = kfd_queue_buffer_get(vm, (void *)properties->ctx_save_restore_area_address, @@ -344,7 +351,7 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope int kfd_queue_release_buffers(struct kfd_process_device *pdd, struct queue_properties *properties) { struct kfd_topology_device *topo_dev; - u32 total_cwsr_size; + u64 total_cwsr_size; kfd_queue_buffer_put(&properties->wptr_bo); kfd_queue_buffer_put(&properties->rptr_bo); @@ -355,8 +362,12 @@ int kfd_queue_release_buffers(struct kfd_process_device *pdd, struct queue_prope topo_dev = kfd_topology_device_by_id(pdd->dev->id); if (!topo_dev) return -EINVAL; - total_cwsr_size = (properties->ctx_save_restore_area_size + - topo_dev->node_props.debug_memory_size) * NUM_XCC(pdd->dev->xcc_mask); + total_cwsr_size = (u64)properties->ctx_save_restore_area_size + + topo_dev->node_props.debug_memory_size; + if (check_mul_overflow(total_cwsr_size, + NUM_XCC(pdd->dev->xcc_mask), + &total_cwsr_size)) + return -EINVAL; total_cwsr_size = ALIGN(total_cwsr_size, PAGE_SIZE); kfd_queue_buffer_svm_put(pdd, properties->ctx_save_restore_area_address, total_cwsr_size); From 9fb646bc4d87f62bcbf0a7ea326430eb802c475c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Fri, 29 May 2026 11:09:08 +0200 Subject: [PATCH 444/479] drm/amd/display: Fix preferred link rate for NUTMEG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When there is a preferred link rate setting, it needs to be applied to both the current and initial link rate. This was regressed by a "coding style" fix, which caused the current link rate to not respect the preferred value. This commit restores the functionality of NUTMEG, the DP bridge encoder found on old APUs such as Kaveri. Fixes: a62346043a89 ("drm/amd/display: Fix coding style issue") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5465 Cc: Chuanyu Tseng Reviewed-by: Fangzhi Zuo Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit e78b0a367f8690b682029d90e75308dc84ed51de) Cc: stable@vger.kernel.org --- .../drm/amd/display/dc/link/protocols/link_dp_capability.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c index 47abb4066709..4079f128bade 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c @@ -750,8 +750,10 @@ static bool decide_dp_link_settings(struct dc_link *link, struct dc_link_setting if (req_bw > dp_link_bandwidth_kbps(link, &link->verified_link_cap)) return false; - if (link->preferred_link_setting.link_rate != LINK_RATE_UNKNOWN) + if (link->preferred_link_setting.link_rate != LINK_RATE_UNKNOWN) { initial_link_setting.link_rate = link->preferred_link_setting.link_rate; + current_link_setting.link_rate = link->preferred_link_setting.link_rate; + } /* search for the minimum link setting that: * 1. is supported according to the link training result From e39b7cf5c62e027af166772e46382356ecb45c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Fri, 29 May 2026 11:09:09 +0200 Subject: [PATCH 445/479] drm/amd/display: Add dp_skip_rbr flag for NUTMEG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No functional changes. Just clean up a conceptual mismatch. Based on feedback on the NUTMEG code in DC, the preferred_link_setting is meant to force the DP link to a specific setting, meaning both the link rate and lane count should be locked to an exact value. What NUTMEG needs is a lower bound on the link rate, which is not the same concept. Implement this as a HW workaround flag instead. Suggested-by: Wenjing Liu Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit 871ceb853841bcaa4e6cec3723b16c4887a760be) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/dc.h | 2 ++ drivers/gpu/drm/amd/display/dc/link/link_detection.c | 2 +- .../drm/amd/display/dc/link/protocols/link_dp_capability.c | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 82d02ebbd829..ebdcbfa7db80 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1815,6 +1815,8 @@ struct dc_scratch_space { bool dp_skip_DID2; bool dp_skip_reset_segment; bool dp_skip_fs_144hz; + /* Some DP bridges don't work with RBR and must use HBR. */ + bool dp_skip_rbr; bool dp_mot_reset_segment; /* Some USB4 docks do not handle turning off MST DSC once it has been enabled. */ bool dpia_mst_dsc_always_on; diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c index 7d8951fecd57..29dbb5e410d8 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c @@ -623,7 +623,7 @@ static bool detect_dp(struct dc_link *link, link->dpcd_caps.sink_count.bits.SINK_COUNT = 1; /* NUTMEG requires that we use HBR, doesn't work with RBR. */ if (link->dpcd_caps.branch_dev_id == DP_BRANCH_DEVICE_ID_00001A) - link->preferred_link_setting.link_rate = LINK_RATE_HIGH; + link->wa_flags.dp_skip_rbr = true; } return true; diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c index 4079f128bade..1cd17a0272bc 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c @@ -750,9 +750,9 @@ static bool decide_dp_link_settings(struct dc_link *link, struct dc_link_setting if (req_bw > dp_link_bandwidth_kbps(link, &link->verified_link_cap)) return false; - if (link->preferred_link_setting.link_rate != LINK_RATE_UNKNOWN) { - initial_link_setting.link_rate = link->preferred_link_setting.link_rate; - current_link_setting.link_rate = link->preferred_link_setting.link_rate; + if (link->wa_flags.dp_skip_rbr) { + initial_link_setting.link_rate = LINK_RATE_HIGH; + current_link_setting.link_rate = LINK_RATE_HIGH; } /* search for the minimum link setting that: From 9b3aa1dec7c364b0d7b171dcc00ab1092e362aef Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Wed, 24 Jun 2026 16:54:30 -0400 Subject: [PATCH 446/479] drm/amd/display: Fix 8K Mode Not Parsed by EDID [why] The 8K120/8K240 timings live in DisplayID extension blocks 2 and 3 of this EDID. The EDID is a 4-block (512-byte) HDMI 2.1 EDID that uses HF-EEODB. drm core reads and parses this correctly, but amdgpu rebuilds its own copy. Only 2 of 4 blocks were copied into sink->dc_edid, that leads to drm_edid_connector_add_modes() never sees blocks 2 and 3. [how] Directly populate edid_blob_ptr with a blob whose length is the full, and HF-EEODB-aware size. Reviewed-by: Sun peng (Leo) Li Signed-off-by: Fangzhi Zuo Signed-off-by: George Zhang Signed-off-by: Alex Deucher (cherry picked from commit 11a90eaf5c808ba800249dda0d481c35d0888589) --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index c6f94eb71ffa..6be7f6edd0b2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -1201,11 +1201,25 @@ enum dc_edid_status dm_helpers_read_local_edid( continue; edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() - if (!edid || - edid->extensions >= sizeof(sink->dc_edid.raw_edid) / EDID_LENGTH) + /* + * Use the length of the EDID property blob populated by + * drm_edid_connector_update() above. It reflects the true number + * of EDID blocks, including any HDMI Forum EDID Extension Override + * Data Block (HF-EEODB) count, which the raw byte 0x7e extension + * count can hide (e.g. HDMI 8K sinks). + */ + if (!edid || !connector->edid_blob_ptr || + connector->edid_blob_ptr->length > sizeof(sink->dc_edid.raw_edid)) return EDID_BAD_INPUT; - sink->dc_edid.length = EDID_LENGTH * (edid->extensions + 1); + /* + * FIXME: amdgpu_dm today does not consider the HF-EEODB, which + * may contain additional mode info for sinks. This is a + * workaround until dc_edid is refactored out from DC into + * amdgpu_dm's ownership, allowing amdgpu_dm to use drm_edid + * directly + */ + sink->dc_edid.length = connector->edid_blob_ptr->length; memmove(sink->dc_edid.raw_edid, (uint8_t *)edid, sink->dc_edid.length); /* We don't need the original edid anymore */ From f1b5d8f9cc54ae8a2567ac126867ae488e1bf625 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 29 Jun 2026 15:27:00 -0500 Subject: [PATCH 447/479] drm/amd/display: Fix backlight max_brightness to match exported range [Why] FWTS autobrightness fails on eDP panels because actual_brightness can read higher than the advertised max_brightness (e.g. 63576 vs 62451). The conversion helpers expose the firmware PWM range to userspace as [0..max]. But max_brightness is advertised as (max - min), which is smaller. So reading the level can return a value above max_brightness. This regressed in commit 4b61b8a39051 ("drm/amd/display: Add debugging message for brightness caps"), which changed max_brightness to (max - min) and undid commit 8dbd72cb7900 ("drm/amd/display: Export full brightness range to userspace"). [How] Advertise max_brightness as max, and scale the initial AC/DC brightness against max too. Update the KUnit expectations to match. Fixes: 4b61b8a39051 ("drm/amd/display: Add debugging message for brightness caps") Reviewed-by: Alex Hung Signed-off-by: Mario Limonciello Signed-off-by: George Zhang Signed-off-by: Alex Deucher (cherry picked from commit bd9e2b5b0473c75abc0f4134dfe79ecbfb16610d) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 18145d78334f..ff337de44f7a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -5563,11 +5563,11 @@ amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) caps = &dm->backlight_caps[aconnector->bl_idx]; if (get_brightness_range(caps, &min, &max)) { if (power_supply_is_system_supplied() > 0) - props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, 100); + props.brightness = DIV_ROUND_CLOSEST(max * caps->ac_level, 100); else - props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, 100); + props.brightness = DIV_ROUND_CLOSEST(max * caps->dc_level, 100); /* min is zero, so max needs to be adjusted */ - props.max_brightness = max - min; + props.max_brightness = max; drm_dbg(drm, "Backlight caps: min: %d, max: %d, ac %d, dc %d\n", min, max, caps->ac_level, caps->dc_level); } else From 5b1250efc17058595d2776f3c1e53e258f0968ae Mon Sep 17 00:00:00 2001 From: Dmytro Laktyushkin Date: Fri, 3 Jul 2026 10:08:37 -0400 Subject: [PATCH 448/479] drm/amd/display: fix dcn42 det allocation order set_pipe_unlock_order needs to be set to true for the pipes to be unlocked in correct order to avoid det overallocation Reviewed-by: Taimur Hassan Signed-off-by: Dmytro Laktyushkin Signed-off-by: George Zhang Signed-off-by: Alex Deucher (cherry picked from commit 198663d035cc439eb48844a2da66f6ae1b0de303) --- drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c index 7de12b16d7ad..a8241036def2 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c @@ -2142,6 +2142,7 @@ static bool dcn42_resource_construct( dc->config.use_pipe_ctx_sync_logic = true; dc->config.dc_mode_clk_limit_support = false; dc->config.enable_windowed_mpo_odm = true; + dc->config.set_pipe_unlock_order = true; /* Need to ensure DET gets freed before allocating */ /* Use psp mailbox to enable assr */ dc->config.use_assr_psp_message = true; /* dcn42 and afterward always support external panel replay */ From f9ad396aa803c34513ac4c93bce42435dfa96b68 Mon Sep 17 00:00:00 2001 From: Dmytro Laktyushkin Date: Fri, 3 Jul 2026 13:01:12 -0400 Subject: [PATCH 449/479] drm/amd/display: fix dcn42b det allocation order set_pipe_unlock_order needs to be set to true for the pipes to be unlocked in correct order to avoid det overallocation Reviewed-by: Charlene Liu Signed-off-by: Dmytro Laktyushkin Signed-off-by: George Zhang Signed-off-by: Alex Deucher (cherry picked from commit 183bbded999a70c5996e8f399fa8790568d71112) --- drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 1a3b9e942caa..e9c33e3466ea 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -2105,6 +2105,7 @@ static bool dcn42b_resource_construct( dc->config.use_pipe_ctx_sync_logic = true; dc->config.dc_mode_clk_limit_support = false; dc->config.enable_windowed_mpo_odm = true; + dc->config.set_pipe_unlock_order = true; /* Need to ensure DET gets freed before allocating */ /* Use psp mailbox to enable assr */ dc->config.use_assr_psp_message = true; /* dcn42 and afterward always support external panel replay */ From 5d75ec2e5f1736c2f10c7d6f4565bf1bf29f29a7 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Fri, 10 Jul 2026 16:45:42 +0800 Subject: [PATCH 450/479] drm/amdgpu/userq: fix indefinite fence wait during GPU reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pre_reset only force-completes fences of MAPPED queues. A queue in any other state (e.g. mid-eviction) keeps its last_fence pending; after a GPU reset that fence never signals, so the eviction/suspend worker and process teardown (amdgpu_evf_mgr_flush_suspend) wait on it forever and wedge the machine: INFO: task kworker/6:28 blocked for more than 120 seconds. Workqueue: events amdgpu_eviction_fence_suspend_worker [amdgpu] Call Trace: dma_fence_wait_timeout+0x7e/0x130 amdgpu_userq_evict+0x67/0x140 [amdgpu] amdgpu_eviction_fence_suspend_worker+0xd8/0x160 [amdgpu] process_scheduled_works+0xa6/0x420 Force-complete every queue's fence regardless of state. The unmap and mark-hung step stays gated on MAPPED, since unmapping a queue that is not mapped is invalid. Fixes: 290f46cf5726 ("drm/amdgpu: Implement user queue reset functionality") Reviewed-by: Christian König Signed-off-by: Jesse Zhang Signed-off-by: Alex Deucher (cherry picked from commit 9102b39fa924dcc3dc75a3137bfa9633c40b88c0) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index d854343b3734..572f2949cb64 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -1376,16 +1376,19 @@ void amdgpu_userq_pre_reset(struct amdgpu_device *adev) /* TODO: We probably need a new lock for the queue state */ xa_for_each(&adev->userq_doorbell_xa, queue_id, queue) { - if (queue->state != AMDGPU_USERQ_STATE_MAPPED) - continue; - - userq_funcs = adev->userq_funcs[queue->queue_type]; - userq_funcs->unmap(queue); - /* just mark all queues as hung at this point. - * if unmap succeeds, we could map again - * in amdgpu_userq_post_reset() if vram is not lost + if (queue->state == AMDGPU_USERQ_STATE_MAPPED) { + userq_funcs = adev->userq_funcs[queue->queue_type]; + userq_funcs->unmap(queue); + /* just mark all queues as hung at this point. + * if unmap succeeds, we could map again + * in amdgpu_userq_post_reset() if vram is not lost + */ + queue->state = AMDGPU_USERQ_STATE_HUNG; + } + /* Force-complete any pending fence regardless of queue state so + * that eviction/suspend and queue teardown waiters don't block + * forever on a fence that will never signal after the reset. */ - queue->state = AMDGPU_USERQ_STATE_HUNG; amdgpu_userq_fence_driver_force_completion(queue); } } From a2f895f3c852063258d62e9f74b081de07ca95df Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Wed, 1 Jul 2026 18:53:21 +0800 Subject: [PATCH 451/479] drm/amdgpu: fix bo->pin leaking in amdgpu_bo_create_reserved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_bo_create_reserved() only allocates a new BO when *bo_ptr (struct amdgpu_bo **bo_ptr as input parameter) is NULL, it simply skips creation when *bo_ptr is non-NULL. But it unconditionally reserves, pins, gart allocates and maps the BO afterwards. When the same non-NULL BO pointer is passed in again, for example firmware buffers that live in adev and are re-loaded on every resume / cp_resume / start under AMDGPU_FW_LOAD_DIRECT, amdgpu_bo_pin() just increases pin_count unconditionally, however the matching teardown only unpins once, so pin_count never drops to zero, so TTM is not able to move, swap or evict a BO, causing BO leaks. This commit fixes this issue by only pinning the bo once at creation, and repeated calls no longer take additional pin references. Signed-off-by: Zhu Lingshan Reviewed-by: Alex Deucher Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 3ddc0ae76202c447b6aec61e907b852bc94671cf) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_object.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c index f98bfba59a2c..718937777e53 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c @@ -276,10 +276,12 @@ int amdgpu_bo_create_reserved(struct amdgpu_device *adev, goto error_free; } - r = amdgpu_bo_pin(*bo_ptr, domain); - if (r) { - dev_err(adev->dev, "(%d) kernel bo pin failed\n", r); - goto error_unreserve; + if (free) { + r = amdgpu_bo_pin(*bo_ptr, domain); + if (r) { + dev_err(adev->dev, "(%d) kernel bo pin failed\n", r); + goto error_unreserve; + } } r = amdgpu_ttm_alloc_gart(&(*bo_ptr)->tbo); @@ -302,7 +304,8 @@ int amdgpu_bo_create_reserved(struct amdgpu_device *adev, return 0; error_unpin: - amdgpu_bo_unpin(*bo_ptr); + if (free) + amdgpu_bo_unpin(*bo_ptr); error_unreserve: amdgpu_bo_unreserve(*bo_ptr); From db7e8108809a2245f0a17ba323f027cac0941ffb Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 8 Jul 2026 14:35:14 -0500 Subject: [PATCH 452/479] drm/amdgpu: Fix VFCT bus number matching with soft filter On systems where PCI bus renumbering occurs (e.g. pci=realloc, resource conflicts), the runtime bus number may differ from the BIOS POST bus number recorded in the VFCT table. This causes amdgpu_acpi_vfct_bios() to fail finding the VBIOS even though the correct device entry exists. Introduce amdgpu_acpi_vfct_match() which treats the bus number as a soft filter: vendor/device/function identity is the hard requirement, while exact bus match is the preferred path. When bus numbers disagree but device identity matches, accept the VFCT entry and log a dev_notice for diagnostics. Reported-by: Oz Tiram Closes: https://lore.kernel.org/amd-gfx/20260621173211.28443-1-oz@shift-computing.de/ Reviewed-by: Alex Deucher Link: https://patch.msgid.link/20260708193518.702584-2-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 11c141672045ffc0187aa604f2c0f597bc334fb2) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c | 45 +++++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c index aa039e148a5e..fbe350a45871 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c @@ -372,6 +372,45 @@ static bool amdgpu_read_disabled_bios(struct amdgpu_device *adev) } #ifdef CONFIG_ACPI +/** + * amdgpu_acpi_vfct_match() - Check if a VFCT entry matches the device + * @adev: AMDGPU device + * @vhdr: VFCT image header to check + * + * VFCT entries contain the PCI bus number as recorded during BIOS POST. + * On systems where the kernel renumbers PCI buses (e.g. pci=realloc or + * resource conflicts), the runtime bus number may differ from the POST + * value. Match by device identity (vendor + device + function) and use + * the bus number as a preference: exact bus match is preferred, but when + * the bus numbers disagree we accept the entry if the device identity + * matches. + * + * Returns: 0 on match, -ENODEV on no match + */ +static int amdgpu_acpi_vfct_match(struct amdgpu_device *adev, + VFCT_IMAGE_HEADER *vhdr) +{ + /* Vendor and device IDs must always match */ + if (vhdr->VendorID != adev->pdev->vendor || + vhdr->DeviceID != adev->pdev->device) + return -ENODEV; + + if (vhdr->PCIDevice != PCI_SLOT(adev->pdev->devfn) || + vhdr->PCIFunction != PCI_FUNC(adev->pdev->devfn)) + return -ENODEV; + + /* Exact bus number match - preferred */ + if (vhdr->PCIBus == adev->pdev->bus->number) + return 0; + + /* Bus mismatch but device identity matches (PCI renumbering case) */ + dev_notice(adev->dev, + "VFCT bus number mismatch: table %u != runtime %u, matching by device identity (vendor 0x%04x device 0x%04x)\n", + vhdr->PCIBus, adev->pdev->bus->number, + adev->pdev->vendor, adev->pdev->device); + return 0; +} + static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) { struct acpi_table_header *hdr; @@ -407,11 +446,7 @@ static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) } if (vhdr->ImageLength && - vhdr->PCIBus == adev->pdev->bus->number && - vhdr->PCIDevice == PCI_SLOT(adev->pdev->devfn) && - vhdr->PCIFunction == PCI_FUNC(adev->pdev->devfn) && - vhdr->VendorID == adev->pdev->vendor && - vhdr->DeviceID == adev->pdev->device) { + !amdgpu_acpi_vfct_match(adev, vhdr)) { adev->bios = kmemdup(&vbios->VbiosContent, vhdr->ImageLength, GFP_KERNEL); From 65bff26617607c1331283232016c0e89088c5b78 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 8 Jul 2026 14:35:15 -0500 Subject: [PATCH 453/479] drm/amdgpu: Release VFCT ACPI table reference amdgpu_acpi_vfct_bios() fetches the VFCT table with acpi_get_table() but never releases it. acpi_get_table() takes a reference on the table (incrementing its validation_count and mapping it on the 0->1 transition); without a paired acpi_put_table() the mapping is leaked on every call, whether or not a matching VBIOS image is found. Route all exit paths after the table is acquired through a common acpi_put_table(). The VBIOS image is copied out with kmemdup() before the table is released, so it remains valid for the caller. Reviewed-by: Alex Deucher Link: https://patch.msgid.link/20260708193518.702584-3-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit ca5988682b4cba4cd125a0fa99b2de1239164ae4) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c index fbe350a45871..8525c45ab209 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c @@ -417,13 +417,14 @@ static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) acpi_size tbl_size; UEFI_ACPI_VFCT *vfct; unsigned int offset; + bool r = false; if (!ACPI_SUCCESS(acpi_get_table("VFCT", 1, &hdr))) return false; tbl_size = hdr->length; if (tbl_size < sizeof(UEFI_ACPI_VFCT)) { dev_info(adev->dev, "ACPI VFCT table present but broken (too short #1),skipping\n"); - return false; + goto out; } vfct = (UEFI_ACPI_VFCT *)hdr; @@ -436,13 +437,13 @@ static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) offset += sizeof(VFCT_IMAGE_HEADER); if (offset > tbl_size) { dev_info(adev->dev, "ACPI VFCT image header truncated,skipping\n"); - return false; + goto out; } offset += vhdr->ImageLength; if (offset > tbl_size) { dev_info(adev->dev, "ACPI VFCT image truncated,skipping\n"); - return false; + goto out; } if (vhdr->ImageLength && @@ -453,15 +454,19 @@ static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) if (!check_atom_bios(adev, vhdr->ImageLength)) { amdgpu_bios_release(adev); - return false; + goto out; } adev->bios_size = vhdr->ImageLength; - return true; + r = true; + goto out; } } dev_info(adev->dev, "ACPI VFCT table present but broken (too short #2),skipping\n"); - return false; + +out: + acpi_put_table(hdr); + return r; } #else static inline bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) From ea9d70db278957e1e81e4aea58b4b131ba262cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sat, 11 Jul 2026 13:21:07 +0200 Subject: [PATCH 454/479] drm/amdgpu: Print vmid, pasid and more task info in devcoredump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are in the dmesg logs but are missing from devcoredumps. Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit fed7aa36d79802c3e02acd05aeae8b0a877e47c2) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 7 ++++++- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index e77db76b48b8..acab3d94a51a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -355,10 +355,14 @@ amdgpu_devcoredump_format(char *buffer, size_t count, struct amdgpu_coredump_inf drm_printf(&p, "kernel: %s\n", init_utsname()->release); drm_printf(&p, "module: " KBUILD_MODNAME "\n"); drm_printf(&p, "time: %ptSp\n", &coredump->reset_time); + drm_printf(&p, "pasid: %u\n", coredump->pasid); + drm_printf(&p, "vmid: %u\n", coredump->vmid); if (coredump->reset_task_info.task.pid) - drm_printf(&p, "process_name: %s PID: %d\n", + drm_printf(&p, "process_name: %s TGID: %d thread: %s PID: %d\n", coredump->reset_task_info.process_name, + coredump->reset_task_info.tgid, + coredump->reset_task_info.task.comm, coredump->reset_task_info.task.pid); /* SOC Information */ @@ -562,6 +566,7 @@ void amdgpu_coredump(struct amdgpu_device *adev, bool skip_vram_check, amdgpu_vm_put_task_info(ti); } coredump->pasid = job->pasid; + coredump->vmid = job->vmid; coredump->num_ibs = job->num_ibs; for (i = 0; i < job->num_ibs; ++i) { coredump->ibs[i].gpu_addr = job->ibs[i].gpu_addr; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h index 2371e20fc68b..63f27337c09a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.h @@ -63,6 +63,7 @@ struct amdgpu_coredump_info { char *formatted; unsigned int pasid; + unsigned int vmid; int num_ibs; struct amdgpu_coredump_ib_info ibs[] __counted_by(num_ibs); }; From b5eab15944b6b140f003af6e639a25f5c3a8ed0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sat, 11 Jul 2026 13:21:08 +0200 Subject: [PATCH 455/479] drm/amdgpu: Reserve space for IB contents in devcoredumps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the contents of IBs are abruptly cut off and don't show the full contents. This patch makes sure to reserve space for those contents too so they may be printed. Signed-off-by: Timur Kristóf Acked-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 4e2c0821509fed754e8c31d5053d152fbb3484a5) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index acab3d94a51a..6480a344006d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -234,6 +234,9 @@ amdgpu_devcoredump_print_ibs(struct drm_printer *p, drm_printf(p, "\nIB #%d 0x%llx %d dw\n", i, coredump->ibs[i].gpu_addr, coredump->ibs[i].ib_size_dw); + + for (int j = 0; j < coredump->ibs[i].ib_size_dw; j++) + drm_printf(p, "0xffffffff\n"); } return; } From 54d4dee9f89e1dac1a6c2618de0bb2e3f35cc2f0 Mon Sep 17 00:00:00 2001 From: yanglinlin Date: Mon, 13 Jul 2026 11:12:28 +0800 Subject: [PATCH 456/479] drm/amd/display: fix __udivdi3 link error When compiling the AMDGPU display driver for 32-bit architectures, the linker reports undefined reference to `__udivdi3` in functions get_dp_dto_frequency_100hz() and dcn401_get_dp_dto_frequency_100hz(). This is because the code uses 64-bit division (/) on 32-bit systems, which GCC cannot handle directly and instead tries to call the missing __udivdi3 helper function. Replace the raw division with div_u64(), the kernel's standard 64-bit division helper, to avoid the link error. Signed-off-by: Linlin Yang Reported-by: k2ci Signed-off-by: Alex Deucher (cherry picked from commit 0421fc6ab3a8514e99156ff3c2cee13ee9af3fa7) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c b/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c index 7c293917e6fd..ecb8493ec523 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dce_clock_source.c @@ -1229,9 +1229,9 @@ static bool get_dp_dto_frequency_100hz( */ modulo_hz = REG_READ(MODULO[inst]); if (modulo_hz) { - temp = div_u64((uint64_t)clock_hz * dp_dto_ref_khz * 10, modulo_hz); - ASSERT(temp / 100 <= 0xFFFFFFFFUL); - *pixel_clk_100hz = (unsigned int)(temp / 100); + temp = clock_hz * dp_dto_ref_khz * 10; + ASSERT(temp <= UINT_MAX * modulo_hz * 100ULL); + *pixel_clk_100hz = div_u64(temp, modulo_hz * 100); } else *pixel_clk_100hz = 0; } else { @@ -1285,13 +1285,12 @@ static bool dcn401_get_dp_dto_frequency_100hz(const struct clock_source *clock_s * - target pix_clk_hz = (DPDTO INTEGER * DPDTO MODULO + DPDTO PHASE) */ temp = (unsigned long long)dp_dto_integer * modulo_hz + phase_hz; - - if (temp / 100 > 0xFFFFFFFFUL) { + if (temp > (UINT_MAX * 100ULL)) { /* pixel rate 100hz should never be this high, if it is, throw an assert and return 0 */ BREAK_TO_DEBUGGER(); *pixel_clk_100hz = 0; } else { - *pixel_clk_100hz = (unsigned int)(temp / 100); + *pixel_clk_100hz = div_u64(temp, 100); } return true; From 85371c5ef502d10add72eab38711e191dccea981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Mon, 13 Jul 2026 08:14:43 +0200 Subject: [PATCH 457/479] drm/amd/pm/ci: Don't disable MCLK DPM on Bonaire 0x6658 (R7 260X) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old radeon driver has a documented workaround in ci_dpm.c which claims that Bonaire 0x6658 with old memory controller firmware is unstable with MCLK DPM, so as a precaution I disabled MCLK DPM on this ASIC in amdgpu. Note that the old MC firmware is not actually used with amdgpu, but in theory it's possible that the VBIOS sets up the ASIC with an old MC firmware that is already running when amdgpu initializes (in which case amdgpu doesn't load its own firmware). What I expected to happen is that the GPU would simply use its maximum memory clock, and indeed this is what seemed to happen according to amdgpu_pm_info which reads the current MCLK value from the SMU. However, some users reported a huge perf regression and upon a closer look it seems that the GPU seems to not actually use the highest MCLK value, despite the SMU reporting that it does. Let's not disable MCLK DPM on Bonaire 0x6658 (R7 260X). Keep MCLK DPM disabled on R9 M380 in the 2015 iMac because that still hangs if we enable it. Fixes: 9851f29cb06c ("drm/amd/pm/ci: Disable MCLK DPM on problematic CI ASICs") Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit d34acad064ee7d82bd18f5d87592c422d4d323ac) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c index 1d6e30269d56..4d553be56396 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/hwmgr.c @@ -106,11 +106,8 @@ int hwmgr_early_init(struct pp_hwmgr *hwmgr) hwmgr->od_enabled = false; switch (hwmgr->chip_id) { case CHIP_BONAIRE: - /* R9 M380 in iMac 2015: SMU hangs when enabling MCLK DPM - * R7 260X cards with old MC ucode: MCLK DPM is unstable - */ - if (adev->pdev->subsystem_vendor == 0x106B || - adev->pdev->device == 0x6658) { + /* R9 M380 in iMac 2015: SMU hangs when enabling MCLK DPM */ + if (adev->pdev->subsystem_vendor == 0x106B) { dev_info(adev->dev, "disabling MCLK DPM on quirky ASIC"); adev->pm.pp_feature &= ~PP_MCLK_DPM_MASK; hwmgr->feature_mask &= ~PP_MCLK_DPM_MASK; From cea54c52d82dd9948126b7518af51ca1de094933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sat, 11 Jul 2026 13:34:35 +0200 Subject: [PATCH 458/479] drm/amd/display: Set native cursor mode for disabled CRTCs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Always set native cursor mode when the CRTC is disabled, to make sure it doesn't cause atomic commits to fail when they are trying to disable the CRTC. Fixes: 41af6215cdbc ("drm/amd/display: Reject cursor plane on DCE when scaled differently than primary") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5432 Cc: Leo Li Cc: Michel Dänzer Signed-off-by: Timur Kristóf Tested-by: Viktor Jägersküpper Signed-off-by: Alex Deucher (cherry picked from commit 2f79f0130f828cf26fe2dcf45291821616af7b47) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index ff337de44f7a..fe6eea28d119 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -12814,10 +12814,15 @@ static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev, /* Overlay cursor not supported on HW before DCN * DCN401/420 does not have the cursor-on-scaled-plane or cursor-on-yuv-plane restrictions * as previous DCN generations, so enable native mode on DCN401/420 + * + * Always set native cursor mode when the CRTC is disabled, + * to make sure it doesn't cause atomic commits to fail when + * they are trying to disable the CRTC. */ if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 0, 1) || amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0) || - amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1)) { + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 1) || + !dm_crtc_state->base.enable) { *cursor_mode = DM_CURSOR_NATIVE_MODE; return 0; } From d340cba0df4cf327c7e89c7c1a4e79d4771d7dd5 Mon Sep 17 00:00:00 2001 From: Andriy Korud Date: Fri, 10 Jul 2026 12:52:26 +0200 Subject: [PATCH 459/479] drm/amd/display: dce100: skip non-DP stream encoders for DP MST On DCE8-class ASICs (e.g. Bonaire), the resource pool contains digital DIG stream encoders plus one analog DAC encoder. When assigning a stream encoder for a second DisplayPort MST stream, if the preferred digital encoder is already acquired, dce100_find_first_free_match_stream_enc_for_link() falls back to the first free pool entry. That entry may be the analog encoder, whose funcs table lacks DP hooks such as dp_set_stream_attribute. The subsequent atomic commit then dereferences NULL function pointers in link_set_dpms_on() and crashes. Skip encoders without dp_set_stream_attribute when the stream uses a DP signal (including MST). Use dc_is_dp_signal(stream->signal) for the MST fallback path instead of checking only the link connector signal. Tested on: - GPU: AMD Radeon R7 260X (Bonaire / DCE8) - Board: Supermicro C9X299-PG300 - Setup: DP MST daisy chain, hotplug second monitor or have it connected on boot - Kernel: 7.1.3 (issue observed since 6.19) - Result: kernel oops without patch; dual monitors stable with patch Signed-off-by: Andriy Korud Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5162 Signed-off-by: Alex Deucher (cherry picked from commit 28ec64943e3ee4d9b8d30cea61e380f1429953a8) Cc: stable@vger.kernel.org --- .../drm/amd/display/dc/resource/dce100/dce100_resource.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c index b92d4f378d60..97ea22af5d2b 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c @@ -992,6 +992,11 @@ struct stream_encoder *dce100_find_first_free_match_stream_enc_for_link( for (i = 0; i < pool->stream_enc_count; i++) { if (!res_ctx->is_stream_enc_acquired[i] && pool->stream_enc[i]) { + /* DP/MST needs a digital encoder; skip analog/no-DP encoders */ + if (dc_is_dp_signal(stream->signal) && + (!pool->stream_enc[i]->funcs || + !pool->stream_enc[i]->funcs->dp_set_stream_attribute)) + continue; /* Store first available for MST second display * in daisy chain use case */ @@ -1014,7 +1019,7 @@ struct stream_encoder *dce100_find_first_free_match_stream_enc_for_link( * required for non DP connectors. */ - if (j >= 0 && link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT) + if (j >= 0 && dc_is_dp_signal(stream->signal)) return pool->stream_enc[j]; return NULL; From f8922d5a946699fc2bdc7660e6778bd6726bf8b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sun, 12 Jul 2026 19:39:26 +0200 Subject: [PATCH 460/479] drm/amd/pm/si: Don't schedule thermal work when queue isn't initialized MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When DPM is turned off with the amdgpu.dpm=0 module parameter, the thermal work queue isn't initialized so we shouldn't schedule any work on it. Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit bd018d36171a695952c6d391471c279c9e05c8b2) --- drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c index 8079da7c5335..9d837d23d3a7 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -7689,7 +7689,7 @@ static int si_dpm_process_interrupt(struct amdgpu_device *adev, break; } - if (queue_thermal) + if (queue_thermal && amdgpu_dpm) schedule_work(&adev->pm.dpm.thermal.work); return 0; From 86b6cf8387a4a28754fa251c79e1abfc2e57b534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sun, 12 Jul 2026 19:39:27 +0200 Subject: [PATCH 461/479] drm/amd/pm/si: Fix AC/DC switch notification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There were two mistakes in the previous implementation: The check for ATOM_PP_PLATFORM_CAP_HARDWAREDC should be inverted. We recently learned that the kernel should send PPSMC_MSG_RunningOnAC when the flag is set, and not the other way around. The clocks also need to be recomputed, because the code in the si_apply_state_adjust_rules() function selects different limits on AC and DC. Fixes: 2d071f6457af ("drm/amd/pm/si: Notify the SMC when switching to AC") Tested-by: Jeremy Klarenbeek Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit 358dd0a9ce66d898fa934887385327547d599d88) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c index 9d837d23d3a7..012227c70600 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -3892,13 +3892,16 @@ static void si_notify_hw_of_powersource(void *handle) { struct amdgpu_device *adev = (struct amdgpu_device *)handle; - /* Check if the platform already manages the AC/DC switch via dedicated GPIO. */ - if (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_HARDWAREDC) - return; - - /* The SMU automatically notices DC, but needs to be notified when switching to AC. */ - if (adev->pm.ac_power) + /* + * Check if the platform already manages the AC/DC switch via dedicated GPIO. + * Otherwise SMU automatically notices DC, but needs to be notified of AC. + */ + if (adev->pm.ac_power && + (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_HARDWAREDC)) amdgpu_si_send_msg_to_smc(adev, PPSMC_MSG_RunningOnAC); + + /* Recompute clocks with updated max_limits. */ + amdgpu_legacy_dpm_compute_clocks(adev); } static PPSMC_Result si_send_msg_to_smc_with_parameter(struct amdgpu_device *adev, From b2ff0595c31cce4303957dd0058e14c89eb70152 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 3 Jun 2026 15:41:28 -0400 Subject: [PATCH 462/479] drm/amdgpu: always emit the job vm fence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need the fence to reemit the gds switch or spm update after a queue reset. Fixes: a17ef941212b ("drm/amdgpu: rework ring reset backup and reemit v9") Cc: timur.kristof@gmail.com Cc: christian.koenig@amd.com Reviewed-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit bc639a9eadc75822f7f15a4315c198a4b5513bd2) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index bb99b7c3a010..f224d33c4bc4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -855,12 +855,10 @@ void amdgpu_vm_flush(struct amdgpu_ring *ring, struct amdgpu_job *job, job->oa_size); } - if (vm_flush_needed || pasid_mapping_needed || cleaner_shader_needed) { - amdgpu_fence_emit(ring, job->hw_vm_fence, 0); - fence = &job->hw_vm_fence->base; - /* get a ref for the job */ - dma_fence_get(fence); - } + amdgpu_fence_emit(ring, job->hw_vm_fence, 0); + fence = &job->hw_vm_fence->base; + /* get a ref for the job */ + dma_fence_get(fence); if (vm_flush_needed) { mutex_lock(&id_mgr->lock); From 0148ac33547b9af1c5a7f3bb6e5baffcb6e9fac2 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 8 Jul 2026 22:15:20 -0500 Subject: [PATCH 463/479] drm/amdgpu: Disable PCIe dynamic speed switching on Ryzen Pinnacle Ridge AMD Ryzen Pinnacle Ridge (Zen+, family 0x17 model 0x08) CPUs have PCI controllers that don't support PCIe dynamic speed switching, causing system freezes during GPU initialization when enabled. Disable dynamic speed switching when this CPU is detected. Assisted-by: Claude:sonnet Fixes: 466a7d115326 ("drm/amd: Use the first non-dGPU PCI device for BW limits") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5436 Reviewed-by: Lijo Lazar Link: https://patch.msgid.link/20260709031520.841611-1-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 9ceb4e034a327a04155f32f1cd1a5031dfa5fe02) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 53335ca96b1d..e5f26e5892ba 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1323,6 +1323,15 @@ static bool amdgpu_device_pcie_dynamic_switching_supported(struct amdgpu_device if (c->x86_vendor == X86_VENDOR_INTEL) return false; + + /* + * AMD Ryzen Pinnacle Ridge (Zen+, family 0x17 model 0x08) CPUs don't + * support PCIe dynamic speed switching. + * https://gitlab.freedesktop.org/drm/amd/-/work_items/5436 + */ + if (c->x86_vendor == X86_VENDOR_AMD && c->x86 == 0x17 && + c->x86_model == 0x08) + return false; #endif return true; } From ec917f19ff1077709e1c4aa7926c7451106080d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sun, 12 Jul 2026 19:39:28 +0200 Subject: [PATCH 464/479] drm/amd/pm/smu7: Fix AC/DC switch notification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There were two mistakes in the previous implementation: The check for AutomaticDCTransition should be inverted. We recently learned that the kernel should send PPSMC_MSG_RunningOnAC when the flag is set, and not the other way around. The clocks also need to be recomputed, because the code in the smu7_apply_state_adjust_rules() function selects different limits on AC and DC. Fixes: 96da0d86614e ("drm/amd/pm/smu7: Notify SMU7 of DC->AC switch") Signed-off-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit 516f8fc30a1b56af03f39e93c18707d13419fb1f) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c index 95bf187f02a5..bc82ba2e4c9b 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c @@ -5857,15 +5857,19 @@ static int smu7_power_off_asic(struct pp_hwmgr *hwmgr) static void smu7_notify_ac_dc(struct pp_hwmgr *hwmgr) { struct amdgpu_device *adev = (struct amdgpu_device *)(hwmgr->adev); + const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; - /* Check if the platform already manages the AC/DC switch via dedicated GPIO. */ - if (phm_cap_enabled(hwmgr->platform_descriptor.platformCaps, + /* + * Check if the platform already manages the AC/DC switch via dedicated GPIO. + * Otherwise SMU automatically notices DC, but needs to be notified of AC. + */ + if (adev->pm.ac_power && + phm_cap_enabled(hwmgr->platform_descriptor.platformCaps, PHM_PlatformCaps_AutomaticDCTransition)) - return; - - /* The SMU automatically notices DC, but needs to be notified when switching to AC. */ - if (adev->pm.ac_power) smum_send_msg_to_smc(hwmgr, PPSMC_MSG_RunningOnAC, NULL); + + /* Recompute clocks with updated max_limits. */ + pp_funcs->pm_compute_clocks(adev->powerplay.pp_handle); } static const struct pp_hwmgr_func smu7_hwmgr_funcs = { From 79f408cc06525c6cd01d1a5bf83086a1beede49b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Sat, 11 Jul 2026 13:49:58 +0200 Subject: [PATCH 465/479] drm/amdgpu/ttm: Consider concurrent VM flushes for buffer entities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow using multiple SDMA schedulers only on GPUs where we are allowed to do concurrent VM flushes. This consideration is necessary because all GART windows are mapped in VMID 0 (the kernel VMID) so each buffer entity would flush VMID 0 concurrently. Practically this means that we can't use multiple SDMA engines for TTM on GFX6-8 and Navi 1x. Fixes: 01c836788b37 ("drm/amdgpu: pass all the sdma scheds to amdgpu_mman") Fixes: e4029f7a9474 ("drm/amdgpu: only use working sdma schedulers for ttm") Cc: Pierre-Eric Pelloux-Prayer Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit a8171229bc836607fbc225d323ebc4d14489cfbb) --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 025625e7e800..eb8bbfc7e6d9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2684,12 +2684,22 @@ void amdgpu_sdma_set_buffer_funcs_scheds(struct amdgpu_device *adev, return; } - /* Navi1x's workaround requires us to limit to a single SDMA sched - * for ttm. - */ hub = &adev->vmhub[AMDGPU_GFXHUB(0)]; - adev->mman.num_buffer_funcs_scheds = hub->sdma_invalidation_workaround ? - 1 : n; + + /* + * Allow using multiple SDMA schedulers only on GPUs where + * we are allowed to do concurrent VM flushes. + * This consideration is necessary because all GART windows + * are mapped in VMID 0 (the kernel VMID) so each buffer + * entity would flush VMID 0 concurrently. + * + * Also consider the SDMA invalidation workaround on + * Navi 1x GPUs, which also prevents us from using + * multiple SDMA engines on VMID 0 at the same time. + */ + adev->mman.num_buffer_funcs_scheds = + (adev->vm_manager.concurrent_flush && + !hub->sdma_invalidation_workaround) ? n : 1; } #if defined(CONFIG_DEBUG_FS) From b240f792ae02e9b687eafff934a39e57d1e45365 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 18:51:15 +0800 Subject: [PATCH 466/479] drm/amdkfd: free MQD managers on DQM init failures The change referenced by the Fixes tag releases the HIQ SDMA MQD trunk buffer when device_queue_manager_init() fails after it has been allocated. However, the same failure path can also be reached after init_mqd_managers() has succeeded. At that point dqm->mqd_mgrs[] contains per-type MQD manager objects owned by the device queue manager. The normal teardown path frees those objects from uninitialize(), but the initialization error path only frees dqm itself. Free the MQD managers from the initialization error path as well. This is safe for earlier failures because dqm is zeroed when allocated and init_mqd_managers() clears the entries it rolls back internally. Fixes: b7cccc8286bb ("drm/amdkfd: fix a memory leak in device_queue_manager_init()") Signed-off-by: Guangshuo Li Reviewed-by: Mukul Joshi Reviewed-by: Felix Kuehling Signed-off-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit 1fff2e07b6670bc5b8f7344a8708c136259cb176) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 678ec611a4f2..67137679a901 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -3103,6 +3103,7 @@ static void deallocate_hiq_sdma_mqd(struct kfd_node *dev, struct device_queue_manager *device_queue_manager_init(struct kfd_node *dev) { struct device_queue_manager *dqm; + int i; pr_debug("Loading device queue manager\n"); @@ -3231,6 +3232,9 @@ struct device_queue_manager *device_queue_manager_init(struct kfd_node *dev) deallocate_hiq_sdma_mqd(dev, &dqm->hiq_sdma_mqd); out_free: + for (i = 0; i < KFD_MQD_TYPE_MAX; i++) + kfree(dqm->mqd_mgrs[i]); + kfree(dqm); return NULL; } From bad177fa75e607e396cd57daaaed881450d7a471 Mon Sep 17 00:00:00 2001 From: Alessandro Rinaldi Date: Fri, 26 Jun 2026 16:36:00 +0200 Subject: [PATCH 467/479] drm/amd/display: Force PWM backlight on Lenovo Legion 5 15ARH05 The Lenovo Legion 5 15ARH05 (Renoir) ships a BOE 0x08DF eDP panel that advertises AUX/DPCD backlight control, so amdgpu's automatic detection (amdgpu_backlight == -1) selects AUX. On this panel the AUX backlight path has no effect: brightness writes are accepted but the panel level never changes, the display is stuck at a fixed brightness and max_brightness is reported as a bogus 511000. As a result neither the desktop brightness slider nor the brightness hotkeys do anything. Forcing PWM backlight (amdgpu.backlight=0) restores working control: max_brightness becomes 65535 and the level tracks writes. This has long been applied by users as a manual kernel-parameter workaround. Extend the generic panel backlight quirk with a force_pwm flag, add an entry for the Legion 5 15ARH05 / BOE 0x08DF panel, and have amdgpu disable AUX backlight (use PWM) when the quirk matches and the user lets the driver auto-select the backlight type. Signed-off-by: Alessandro Rinaldi Tested-by: Alessandro Rinaldi Reviewed-by: George Zhang Signed-off-by: Alex Deucher (cherry picked from commit 81b39f43e7e53589491e2eef6bad5389626b4b9c) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 7 +++++-- drivers/gpu/drm/drm_panel_backlight_quirks.c | 9 +++++++++ include/drm/drm_utils.h | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index fe6eea28d119..6210a1ff7cc2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4069,6 +4069,8 @@ static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) caps->ext_caps = &aconnector->dc_link->dpcd_sink_ext_caps; caps->aux_support = false; + panel_backlight_quirk = drm_get_panel_backlight_quirk(aconnector->drm_edid); + if (caps->ext_caps->bits.oled == 1 /* * || @@ -4081,6 +4083,9 @@ static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) caps->aux_support = false; else if (amdgpu_backlight == 1) caps->aux_support = true; + else if (!IS_ERR_OR_NULL(panel_backlight_quirk) && + panel_backlight_quirk->force_pwm) + caps->aux_support = false; if (caps->aux_support) aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; @@ -4096,8 +4101,6 @@ static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) else caps->aux_min_input_signal = 1; - panel_backlight_quirk = - drm_get_panel_backlight_quirk(aconnector->drm_edid); if (!IS_ERR_OR_NULL(panel_backlight_quirk)) { if (panel_backlight_quirk->min_brightness) { caps->min_input_signal = diff --git a/drivers/gpu/drm/drm_panel_backlight_quirks.c b/drivers/gpu/drm/drm_panel_backlight_quirks.c index f85cb293a3db..e417c7533053 100644 --- a/drivers/gpu/drm/drm_panel_backlight_quirks.c +++ b/drivers/gpu/drm/drm_panel_backlight_quirks.c @@ -20,6 +20,15 @@ struct drm_get_panel_backlight_quirk { }; static const struct drm_get_panel_backlight_quirk drm_panel_min_backlight_quirks[] = { + /* Lenovo Legion 5 15ARH05, AUX backlight non-functional, force PWM */ + { + .dmi_match.field = DMI_SYS_VENDOR, + .dmi_match.value = "LENOVO", + .dmi_match_other.field = DMI_PRODUCT_VERSION, + .dmi_match_other.value = "Lenovo Legion 5 15ARH05", + .ident.panel_id = drm_edid_encode_panel_id('B', 'O', 'E', 0x08df), + .quirk = { .force_pwm = true, }, + }, /* 13 inch matte panel */ { .dmi_match.field = DMI_BOARD_VENDOR, diff --git a/include/drm/drm_utils.h b/include/drm/drm_utils.h index 6a46f755daba..7e077484c5bb 100644 --- a/include/drm/drm_utils.h +++ b/include/drm/drm_utils.h @@ -19,6 +19,7 @@ int drm_get_panel_orientation_quirk(int width, int height); struct drm_panel_backlight_quirk { u16 min_brightness; u32 brightness_mask; + bool force_pwm; }; const struct drm_panel_backlight_quirk * From 9fa26b9eed6195bf840f39ac183b9a6237548755 Mon Sep 17 00:00:00 2001 From: WenTao Liang Date: Sun, 28 Jun 2026 15:27:40 +0800 Subject: [PATCH 468/479] drm/amd/display: set new_stream to NULL after release In dm_update_crtc_state(), the skip_modeset path releases new_stream via dc_stream_release() but does not set the pointer to NULL. If a later error (e.g., color management failure) triggers the fail label, the error path calls dc_stream_release() again on the same dangling pointer, causing a double release and potential use-after-free. Fix this by setting new_stream to NULL after the initial release. Fixes: 9b690ef3c704 ("drm/amd/display: Avoid full modeset when not required") Signed-off-by: WenTao Liang Reviewed-by: George Zhang Signed-off-by: Alex Deucher (cherry picked from commit 99f3af19073b3ddbfd96e789124cce12c4277b28) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 6210a1ff7cc2..b1e6d735cf02 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -12101,6 +12101,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, /* Release extra reference */ if (new_stream) dc_stream_release(new_stream); + new_stream = NULL; /* * We want to do dc stream updates that do not require a From 46c3c32ba655c94b885b75b5adb8e481612126bf Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 25 Jun 2026 14:06:47 +0800 Subject: [PATCH 469/479] drm/amd/display: wire DCN42B mcache programming callback DCN42B enables DML2 and DML21 by default and defines dcn42b_prepare_mcache_programming(), but the resource function table only wires the callback when CONFIG_DRM_AMD_DC_DML21 is defined. There is no in-tree Kconfig symbol named DRM_AMD_DC_DML21, so the preprocessor always removes the callback entry. Sibling DCN42 and DCN401 resource tables wire their prepare_mcache_programming callbacks unconditionally, and the core DC code already checks whether the callback pointer is present before calling it. Remove the stale guard so DCN42B exposes the callback relation that its source and DML21 build world already provide. This is an RFC patch draft from static conditional callback legality auditing. It needs AMD display maintainer review before submission as a final fix. Signed-off-by: Pengpeng Hou Reviewed-by: George Zhang Signed-off-by: Alex Deucher (cherry picked from commit 85453fb4ff726e1ddb9984ee83dca260903c5353) --- .../gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index e9c33e3466ea..94e166c0a9b0 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -1900,9 +1900,7 @@ static struct resource_funcs dcn42b_res_pool_funcs = { .update_soc_for_wm_a = dcn30_update_soc_for_wm_a, .add_phantom_pipes = dcn32_add_phantom_pipes, .calculate_mall_ways_from_bytes = dcn32_calculate_mall_ways_from_bytes, -#ifdef CONFIG_DRM_AMD_DC_DML21 .prepare_mcache_programming = dcn42b_prepare_mcache_programming, -#endif .build_pipe_pix_clk_params = dcn42b_build_pipe_pix_clk_params, .get_power_profile = dcn401_get_power_profile, .get_vstartup_for_pipe = dcn401_get_vstartup_for_pipe, From 75c8746b9d0a0317d8a58aa0efa2ff2f7359908b Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 13 Jul 2026 14:53:13 -0500 Subject: [PATCH 470/479] drm/amd: Create a device link between APU display and XHCI devices Some AMD APU multi-function devices expose an integrated USB xHCI controller. In some circumstances (such as larger VRAM), the PM core can resume can fail when the xHCI controller is resuming in parallel with the GPU/display function. On affected systems, the xHCI controller can complete pci_pm_resume and start resuming USB devices while the GPU is still in its much longer resume path. This race condition leads to USB device resume failures followed by: xhci_hcd ...: xHCI host not responding to stop endpoint command xhci_hcd ...: HC died; cleaning up Create a device link from any xHCI controller sharing the same PCIe root port as the APU display function. The link uses DL_FLAG_STATELESS and DL_FLAG_PM_RUNTIME to ensure the GPU completes its resume before the xHCI controller begins resuming USB devices. This device link is done specifically in amdgpu so that if the platform firmware has been modified such that this issue doesn't happen the version can be detected and the workaround skipped. Suggested-by: Aaron Ma Reported-by: mrh@frame.work Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221073 Acked-by: Alex Deucher Tested-by: Mark Pearson Tested-by: Alexander F Tested-by: Francis DB Link: https://patch.msgid.link/20260713195313.1739762-1-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 07c93d7eeb0d990bc1b8e3b1eafa464bc9feee97) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c | 12 +++++ drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h | 12 ++++- .../drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c | 45 +++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c index 762ec3cede96..3969a7670482 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c +++ b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c @@ -1367,6 +1367,14 @@ static void smu_feature_cap_init(struct smu_context *smu) bitmap_zero(fea_cap->cap_map, SMU_FEATURE_CAP_ID__COUNT); } +static int smu_set_power_dep(struct smu_context *smu, bool enable) +{ + if (!smu->ppt_funcs->set_power_dep) + return 0; + + return smu->ppt_funcs->set_power_dep(smu, enable); +} + static int smu_sw_init(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -1428,6 +1436,8 @@ static int smu_sw_init(struct amdgpu_ip_block *ip_block) if (!smu->ppt_funcs->get_fan_control_mode) smu->adev->pm.no_fan = true; + smu_set_power_dep(smu, true); + return 0; } @@ -1450,6 +1460,8 @@ static int smu_sw_fini(struct amdgpu_ip_block *ip_block) smu_fini_microcode(smu); + smu_set_power_dep(smu, false); + return 0; } diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h index d76e0b005308..e3a89e9a9df4 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h @@ -749,6 +749,9 @@ struct smu_context { bool pm_enabled; bool is_apu; + /* Power dependency link from an integrated xHCI controller to the GPU */ + struct device_link *usb_power_link; + uint32_t smc_driver_if_version; uint32_t smc_fw_if_version; uint32_t smc_fw_version; @@ -1648,12 +1651,19 @@ struct pptable_funcs { int (*ras_send_msg)(struct smu_context *smu, enum smu_message_type msg, uint32_t param, uint32_t *read_arg); - /** * @get_ras_smu_drv: Get RAS smu driver interface * Return: ras_smu_drv * */ int (*get_ras_smu_drv)(struct smu_context *smu, const struct ras_smu_drv **ras_smu_drv); + + /** + * @set_power_dep: Create or destroy a power dependency link + * from an integrated xHCI controller to the GPU so that the GPU is + * resumed before the USB controller during PM resume. @enable is true + * to create the link and false to tear it down. + */ + int (*set_power_dep)(struct smu_context *smu, bool enable); }; typedef enum { diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c index 75719c47a41e..3d73f2050bbe 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c @@ -1701,6 +1701,50 @@ static int smu_v14_0_0_restore_user_od_settings(struct smu_context *smu) return 0; } +/* + * Link any xHCI controller sharing the GPU's PCIe root port as a consumer + * of the GPU so the GPU resumes first, avoiding an xHCI resume race. + */ +static int smu_v14_0_0_set_power_dep(struct smu_context *smu, bool enable) +{ + struct amdgpu_device *adev = smu->adev; + struct pci_dev *gpu_pdev = adev->pdev; + struct pci_dev *root_port, *usb_pdev = NULL; + struct device_link *link; + + if (!enable) { + if (smu->usb_power_link) { + device_link_del(smu->usb_power_link); + smu->usb_power_link = NULL; + } + return 0; + } + + root_port = pcie_find_root_port(gpu_pdev); + while ((usb_pdev = pci_get_class(PCI_CLASS_SERIAL_USB_XHCI, usb_pdev))) { + struct pci_dev *usb_root; + + usb_root = pcie_find_root_port(usb_pdev); + if (usb_root != root_port) + continue; + + /* Create device link: USB (consumer) depends on GPU (supplier) */ + link = device_link_add(&usb_pdev->dev, &gpu_pdev->dev, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME); + if (link) { + smu->usb_power_link = link; + drm_info(adev_to_drm(adev), "USB controller %s D0 power state depends on %s\n", + pci_name(usb_pdev), pci_name(gpu_pdev)); + /* Only create one link for the first USB controller found */ + break; + } + } + + pci_dev_put(usb_pdev); + + return 0; +} + static const struct pptable_funcs smu_v14_0_0_ppt_funcs = { .check_fw_status = smu_v14_0_check_fw_status, .check_fw_version = smu_cmn_check_fw_version, @@ -1734,6 +1778,7 @@ static const struct pptable_funcs smu_v14_0_0_ppt_funcs = { .dpm_set_umsch_mm_enable = smu_v14_0_0_set_umsch_mm_enable, .get_dpm_clock_table = smu_v14_0_common_get_dpm_table, .set_mall_enable = smu_v14_0_common_set_mall_enable, + .set_power_dep = smu_v14_0_0_set_power_dep, }; static void smu_v14_0_0_init_msg_ctl(struct smu_context *smu) From 8382cd234981ae36299bb66a10bac2cd8ff1b99d Mon Sep 17 00:00:00 2001 From: Leo Li Date: Fri, 12 Jun 2026 13:29:31 -0400 Subject: [PATCH 471/479] drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock [Why] On DCN, vblank events were delivered from VSTARTUP/VUPDATE (dm_crtc_high_irq/dm_vupdate_high_irq) and pageflip completion from GRPH_PFLIP (dm_pflip_high_irq). These signals can be masked by hardware by a few things: * DPG - DCN can Dynamically Power Gate parts of the display pipe when a self-refresh capable eDP is connected. DPG is engaged when there's enough static frames (detected through drm_vblank_off). Once gated, even though the OTG (output timing generator) is still enabled, VSTARTUP and GRPH_FLIP are masked. * GSL - Driver can use the Global Sync Lock to block HW from latching onto double-buffered registers during programming, to prevent HW from latching onto a partially programmed state. This will mask VSTARTUP, GRPH_FLIP, and VUPDATE. See dcn20_pipe_control_lock(). * MALL - A DCN accessible cache introduced in DCN32+ DGPUs that can store fb data to allow for longer DRAM sleep. When scanning out from MALL, VSTARTUP is masked. When masked, events are never delivered, which can show up as flip_done timeouts in the wild. However, there is an interrupt source on DCN that is never masked: VUPDATE_NO_LOCK. It's simply an unmasked variant of VUPDATE, which fires while the OTG is active, at the exact point hardware latches double-buffered registers. It is therefore the natural single signal for delivering both vblank and flip-completion events on DCN, and the correct point to timestamp both VRR and non-VRR vblanks. DCE's interrupt sources are different, it does not have an unmaskable VUPDATE_NO_LOCK. The only unmaskable DCE interrupt is VLINE0, but it can only be programmed as a vline offset from vsync_start, making it unsuitable for VRR. Thus, we keep DCE untouched and use the existing mix of interrupt sources. [How] For DCN1 and newer only: * Factor the body of dm_crtc_high_irq() into dm_crtc_high_irq_handler() and drive it from dm_vupdate_high_irq() (VUPDATE_NO_LOCK). DCE keeps using dm_crtc_high_irq() (VSTARTUP) and dm_pflip_high_irq() (GRPH_PFLIP) unchanged. * Stop registering VSTARTUP (crtc_irq) and GRPH_PFLIP (pageflip_irq) on DCN, and stop enabling them in amdgpu_dm_crtc_set_vblank() / manage_dm_interrupts(). Enable VUPDATE whenever vblank is enabled on DCN (previously only in VRR mode). The secure-display vline0 interrupt is left untouched. * VUPDATE_NO_LOCK does not early-fire on an immediate (tearing / async) flip, since HW latches the new address right away. Deliver the flip completion event immediately after programming such flips in amdgpu_dm_commit_planes(), and clear pflip_status so the next vupdate handler does not double-send. v2: Do not gate VUPDATE_NO_LOCK on DCN in dm_handle_vrr_transition() Also toggle VUPDATE_NO_LOCK on DCN in dm_gpureset_toggle_interrupts() Re-cook vblank event count and timestamp for immediate flips Fixes: 9b47278cec98 ("drm/amd/display: temp w/a for dGPU to enter idle optimizations") Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/3787 Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/4141 Assisted-by: Copilot:claude-opus-4.8 Co-developed-by: Matthew Schwartz Signed-off-by: Matthew Schwartz Tested-by: Mario Limonciello (AMD) Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Leo Li Signed-off-by: Alex Deucher (cherry picked from commit c87e6635d2db02c88ae8d09529362da672d34770) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 347 ++++++++++-------- .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 70 ++-- 2 files changed, 228 insertions(+), 189 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index b1e6d735cf02..39c0ea1f50b1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -580,89 +580,25 @@ static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, queue_work(system_percpu_wq, &offload_work->work); } -static void dm_vupdate_high_irq(void *interrupt_params) -{ - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct amdgpu_crtc *acrtc; - struct drm_device *drm_dev; - struct drm_vblank_crtc *vblank; - ktime_t frame_duration_ns, previous_timestamp; - unsigned long flags; - int vrr_active; - - acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE); - - if (acrtc) { - vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); - drm_dev = acrtc->base.dev; - vblank = drm_crtc_vblank_crtc(&acrtc->base); - previous_timestamp = atomic64_read(&irq_params->previous_timestamp); - frame_duration_ns = vblank->time - previous_timestamp; - - if (frame_duration_ns > 0) { - trace_amdgpu_refresh_rate_track(acrtc->base.index, - frame_duration_ns, - ktime_divns(NSEC_PER_SEC, frame_duration_ns)); - atomic64_set(&irq_params->previous_timestamp, vblank->time); - } - - drm_dbg_vbl(drm_dev, - "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id, - vrr_active); - - /* Core vblank handling is done here after end of front-porch in - * vrr mode, as vblank timestamping will give valid results - * while now done after front-porch. This will also deliver - * page-flip completion events that have been queued to us - * if a pageflip happened inside front-porch. - */ - if (vrr_active && acrtc->dm_irq_params.stream) { - bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; - bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; - bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state - == VRR_STATE_ACTIVE_VARIABLE; - - amdgpu_dm_crtc_handle_vblank(acrtc); - - /* BTR processing for pre-DCE12 ASICs */ - if (adev->family < AMDGPU_FAMILY_AI) { - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - mod_freesync_handle_v_update( - adev->dm.freesync_module, - acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params); - - if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { - schedule_dc_vmin_vmax(adev, - acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params.adjust); - } - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); - } - } - } -} - /** - * dm_crtc_high_irq() - Handles CRTC interrupt - * @interrupt_params: used for determining the CRTC instance + * dm_crtc_high_irq_handler() - Common OTG vblank/flip event handling + * @adev: amdgpu device + * @acrtc: the CRTC to service * - * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK - * event handler. + * Performs writeback completion, vblank event handling, CRC processing, VRR BTR + * updates and pageflip completion delivery. + * + * On DCN this is driven by VUPDATE_NO_LOCK (the register latch point) from + * dm_vupdate_high_irq(); on DCE it is driven by VLINE0 at the start of vblank + * from dm_crtc_high_irq(). */ -static void dm_crtc_high_irq(void *interrupt_params) +static void dm_crtc_high_irq_handler(struct amdgpu_device *adev, + struct amdgpu_crtc *acrtc) { - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; struct drm_writeback_job *job; - struct amdgpu_crtc *acrtc; unsigned long flags; int vrr_active; - - acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK); - if (!acrtc) - return; + bool is_dcn = amdgpu_ip_version(adev, DCE_HWIP, 0) != 0; if (acrtc->wb_conn) { spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); @@ -699,12 +635,17 @@ static void dm_crtc_high_irq(void *interrupt_params) vrr_active, acrtc->dm_irq_params.active_planes); /** - * Core vblank handling at start of front-porch is only possible - * in non-vrr mode, as only there vblank timestamping will give - * valid results while done in front-porch. Otherwise defer it - * to dm_vupdate_high_irq after end of front-porch. + * Core vblank handling. + * + * On DCN this handler runs at VUPDATE_NO_LOCK, the register latch + * point, which is the correct place to timestamp both VRR and non-VRR + * vblanks. + * + * On DCE this handler runs at the start of front-porch, where only + * non-VRR timestamping is valid; VRR vblank is deferred to + * dm_vupdate_high_irq() after end of front-porch. */ - if (!vrr_active) + if (is_dcn || !vrr_active) amdgpu_dm_crtc_handle_vblank(acrtc); /** @@ -737,18 +678,16 @@ static void dm_crtc_high_irq(void *interrupt_params) } /* - * If there aren't any active_planes then DCH HUBP may be clock-gated. - * In that case, pageflip completion interrupts won't fire and pageflip - * completion events won't get delivered. Prevent this by sending - * pending pageflip events from here if a flip is still pending. + * Deliver pageflip completion events (DCN only). * - * If any planes are enabled, use dm_pflip_high_irq() instead, to - * avoid race conditions between flip programming and completion, - * which could cause too early flip completion events. + * Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch + * point. Deliver any pending pageflip completion event from here. + * + * NOTE: This can deliver an event for a flip that was armed but not yet + * programmed into HW; that race is closed in a follow-up change by + * checking the programmed flip status. */ - if (adev->family >= AMDGPU_FAMILY_RV && - acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && - acrtc->dm_irq_params.active_planes == 0) { + if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED) { if (acrtc->event) { drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); acrtc->event = NULL; @@ -760,6 +699,104 @@ static void dm_crtc_high_irq(void *interrupt_params) spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); } +static void dm_vupdate_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct amdgpu_crtc *acrtc; + struct drm_device *drm_dev; + struct drm_vblank_crtc *vblank; + ktime_t frame_duration_ns, previous_timestamp; + unsigned long flags; + int vrr_active; + + acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE); + if (!acrtc) + return; + + vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); + drm_dev = acrtc->base.dev; + vblank = drm_crtc_vblank_crtc(&acrtc->base); + previous_timestamp = atomic64_read(&irq_params->previous_timestamp); + frame_duration_ns = vblank->time - previous_timestamp; + + if (frame_duration_ns > 0) { + trace_amdgpu_refresh_rate_track(acrtc->base.index, + frame_duration_ns, + ktime_divns(NSEC_PER_SEC, frame_duration_ns)); + atomic64_set(&irq_params->previous_timestamp, vblank->time); + } + + drm_dbg_vbl(drm_dev, + "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id, + vrr_active); + + /* + * On DCN, VUPDATE_NO_LOCK is the single OTG interrupt used to deliver + * vblank and pageflip completion events; VSTARTUP and GRPH_PFLIP are + * not used. Run the full handler here. + */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) { + dm_crtc_high_irq_handler(adev, acrtc); + return; + } + + /* DCE only below. */ + + /* Core vblank handling is done here after end of front-porch in + * vrr mode, as vblank timestamping will give valid results + * while now done after front-porch. This will also deliver + * page-flip completion events that have been queued to us + * if a pageflip happened inside front-porch. + */ + if (vrr_active && acrtc->dm_irq_params.stream) { + bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; + bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; + bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state + == VRR_STATE_ACTIVE_VARIABLE; + + amdgpu_dm_crtc_handle_vblank(acrtc); + + /* BTR processing for pre-DCE12 ASICs */ + if (adev->family < AMDGPU_FAMILY_AI) { + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + mod_freesync_handle_v_update( + adev->dm.freesync_module, + acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params); + + if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { + schedule_dc_vmin_vmax(adev, + acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params.adjust); + } + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); + } + } +} + +/** + * dm_crtc_high_irq() - Handles CRTC interrupt + * @interrupt_params: used for determining the CRTC instance + * + * Handles the CRTC/VSYNC interrupt by notifying DRM's VBLANK event handler. + * + * Used on DCE (VLINE0, set to vblank start). On DCN the equivalent handling is + * driven by VUPDATE_NO_LOCK in dm_vupdate_high_irq(). + */ +static void dm_crtc_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct amdgpu_crtc *acrtc; + + acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK); + if (!acrtc) + return; + + dm_crtc_high_irq_handler(adev, acrtc); +} + #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) /** * dm_dcn_vertical_interrupt0_high_irq() - Handles OTG Vertical interrupt0 for @@ -3298,6 +3335,13 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, */ if (!dc_interrupt_set(adev->dm.dc, irq_source, enable)) drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis"); + + } else if (acrtc && state->stream_status[i].plane_count != 0) { + /* DCN only needs to toggle VUPDATE_NO_LOCK */ + rc = amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, enable); + if (rc) + drm_warn(adev_to_drm(adev), "Failed to %sable vupdate interrupt\n", + enable ? "en" : "dis"); } } @@ -4866,38 +4910,6 @@ static int dcn10_register_irq_handlers(struct amdgpu_device *adev) * for acknowledging and handling. */ - /* Use VSTARTUP interrupt */ - for (i = DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP; - i <= DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP + adev->mode_info.num_crtc - 1; - i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->crtc_irq); - - if (r) { - drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 || - int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) { - drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_crtc_high_irq, c_irq_params)) - return -ENOMEM; - } - /* Use otg vertical line interrupt */ #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) { @@ -4969,37 +4981,6 @@ static int dcn10_register_irq_handlers(struct amdgpu_device *adev) return -ENOMEM; } - /* Use GRPH_PFLIP interrupt */ - for (i = DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT; - i <= DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT + dc->caps.max_otg_num - 1; - i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->pageflip_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST || - int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) { - drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_pflip_high_irq, c_irq_params)) - return -ENOMEM; - } - /* HPD */ r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DC_HPD1_INT, &adev->hpd_irq); @@ -9710,14 +9691,22 @@ static void manage_dm_interrupts(struct amdgpu_device *adev, drm_crtc_vblank_on_config(&acrtc->base, &config); - /* Allow RX6xxx, RX7700, RX7800 GPUs to call amdgpu_irq_get.*/ + /* + * Since pflip_high_irq is no longer registered for DCN, grab an + * extra reference to vupdate irq instead to workaround this + * issue: + * https://gitlab.freedesktop.org/drm/amd/-/work_items/3936 + * + * The callbacks to drm_vblank_on/off should really take care of + * this though. + */ switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { case IP_VERSION(3, 0, 0): case IP_VERSION(3, 0, 2): case IP_VERSION(3, 0, 3): case IP_VERSION(3, 2, 0): - if (amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type)) - drm_err(dev, "DM_IRQ: Cannot get pageflip irq!\n"); + if (amdgpu_irq_get(adev, &adev->vupdate_irq, irq_type)) + drm_err(dev, "DM_IRQ: Cannot get vupdate irq!\n"); #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) if (amdgpu_irq_get(adev, &adev->vline0_irq, irq_type)) drm_err(dev, "DM_IRQ: Cannot get vline0 irq!\n"); @@ -9735,8 +9724,8 @@ static void manage_dm_interrupts(struct amdgpu_device *adev, if (amdgpu_irq_put(adev, &adev->vline0_irq, irq_type)) drm_err(dev, "DM_IRQ: Cannot put vline0 irq!\n"); #endif - if (amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type)) - drm_err(dev, "DM_IRQ: Cannot put pageflip irq!\n"); + if (amdgpu_irq_put(adev, &adev->vupdate_irq, irq_type)) + drm_err(dev, "DM_IRQ: Cannot put vupdate irq!\n"); } drm_crtc_vblank_off(&acrtc->base); @@ -9749,6 +9738,10 @@ static void dm_update_pflip_irq_state(struct amdgpu_device *adev, int irq_type = amdgpu_display_crtc_idx_to_irq_type(adev, acrtc->crtc_id); + /* GRPH_PFLIP is not used on DCN; nothing to reapply. */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) + return; + /** * This reads the current state for the IRQ and force reapplies * the setting to hardware. @@ -10081,9 +10074,13 @@ static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, struct dm_crtc_state *old_state, struct dm_crtc_state *new_state) { + struct amdgpu_device *adev = drm_to_adev(new_state->base.crtc->dev); bool old_vrr_active = amdgpu_dm_crtc_vrr_active(old_state); bool new_vrr_active = amdgpu_dm_crtc_vrr_active(new_state); + /* Only DCE gates vupdate on VRR, keep it enabled for DCN */ + bool vrr_gates_vupdate = amdgpu_ip_version(adev, DCE_HWIP, 0) == 0; + if (!old_vrr_active && new_vrr_active) { /* Transition VRR inactive -> active: * While VRR is active, we must not disable vblank irq, as a @@ -10093,7 +10090,8 @@ static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, * We also need vupdate irq for the actual core vblank handling * at end of vblank. */ - WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0); + if (vrr_gates_vupdate) + WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, true) != 0); WARN_ON(drm_crtc_vblank_get(new_state->base.crtc) != 0); drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR off->on: Get vblank ref\n", __func__, new_state->base.crtc->base.id); @@ -10109,7 +10107,8 @@ static void amdgpu_dm_handle_vrr_transition(struct amdgpu_display_manager *dm, /* Transition VRR active -> inactive: * Allow vblank irq disable again for fixed refresh rate. */ - WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0); + if (vrr_gates_vupdate) + WARN_ON(amdgpu_dm_crtc_set_vupdate_irq(new_state->base.crtc, false) != 0); drm_crtc_vblank_put(new_state->base.crtc); drm_dbg_driver(new_state->base.crtc->dev, "%s: crtc=%u VRR on->off: Drop vblank ref\n", __func__, new_state->base.crtc->base.id); @@ -10280,6 +10279,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, bool vrr_active = amdgpu_dm_crtc_vrr_active(acrtc_state); bool cursor_update = false; bool pflip_present = false; + bool immediate_flip = false; bool dirty_rects_changed = false; bool updated_planes_and_streams = false; struct { @@ -10444,6 +10444,8 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, acrtc_state->update_type == UPDATE_TYPE_FAST && get_mem_type(old_plane_state->fb) == get_mem_type(fb); + immediate_flip |= bundle->flip_addrs[planes_count].flip_immediate; + timestamp_ns = ktime_get_ns(); bundle->flip_addrs[planes_count].flip_timestamp_in_us = div_u64(timestamp_ns, 1000); bundle->surface_updates[planes_count].flip_addr = &bundle->flip_addrs[planes_count]; @@ -10636,6 +10638,29 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, acrtc_state->cursor_mode == DM_CURSOR_NATIVE_MODE) amdgpu_dm_commit_cursors(state); + /* + * On DCN, flip completion is normally delivered from VUPDATE_NO_LOCK. + * However, an immediate (tearing / async) flip is latched by HW right + * away and does not wait for the next vupdate, so deliver its + * completion event here after programming. + * + * On DCE, GRPH_PFLIP already fires immediately for immediate flips, so + * this is DCN-only. + */ + if (immediate_flip && amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) { + spin_lock_irqsave(&pcrtc->dev->event_lock, flags); + if (acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc_attach->event) { + drm_crtc_accurate_vblank_count(&acrtc_attach->base); + drm_crtc_send_vblank_event(&acrtc_attach->base, + acrtc_attach->event); + acrtc_attach->event = NULL; + drm_crtc_vblank_put(&acrtc_attach->base); + acrtc_attach->pflip_status = AMDGPU_FLIP_NONE; + } + spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); + } + cleanup: kfree(bundle); } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 3dcedaa67ed8..b43cd68cde67 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -274,7 +274,14 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable) drm_crtc_vblank_restore(crtc); } - if (dc_supports_vrr(dm->dc->ctx->dce_version)) { + /* + * On DCN, VUPDATE_NO_LOCK is the single OTG interrupt used to deliver + * vblank and pageflip completion events, so enable it whenever vblank + * is enabled. On DCE, vupdate is only needed in VRR mode. + */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) { + rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, enable); + } else if (dc_supports_vrr(dm->dc->ctx->dce_version)) { if (enable) { /* vblank irq on -> Only need vupdate irq in vrr mode */ if (amdgpu_dm_crtc_vrr_active(acrtc_state)) @@ -285,39 +292,46 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable) } } - if (rc) - return rc; - - /* crtc vblank or vstartup interrupt */ - if (enable) { - rc = amdgpu_irq_get(adev, &adev->crtc_irq, irq_type); - drm_dbg_vbl(crtc->dev, "Get crtc_irq ret=%d\n", rc); - } else { - rc = amdgpu_irq_put(adev, &adev->crtc_irq, irq_type); - drm_dbg_vbl(crtc->dev, "Put crtc_irq ret=%d\n", rc); - } - if (rc) return rc; /* - * hubp surface flip interrupt - * - * We have no guarantee that the frontend index maps to the same - * backend index - some even map to more than one. - * - * TODO: Use a different interrupt or check DC itself for the mapping. + * VLINE0 (crtc_irq) and GRPH_PFLIP (pageflip_irq) are only used on + * DCE. On DCN, vblank and pageflip completion are delivered from + * VUPDATE_NO_LOCK (enabled above), so don't touch them here. */ - if (enable) { - rc = amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type); - drm_dbg_vbl(crtc->dev, "Get pageflip_irq ret=%d\n", rc); - } else { - rc = amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type); - drm_dbg_vbl(crtc->dev, "Put pageflip_irq ret=%d\n", rc); - } + if (amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { + /* crtc vblank or vstartup interrupt */ + if (enable) { + rc = amdgpu_irq_get(adev, &adev->crtc_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Get crtc_irq ret=%d\n", rc); + } else { + rc = amdgpu_irq_put(adev, &adev->crtc_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Put crtc_irq ret=%d\n", rc); + } - if (rc) - return rc; + if (rc) + return rc; + + /* + * hubp surface flip interrupt + * + * We have no guarantee that the frontend index maps to the same + * backend index - some even map to more than one. + * + * TODO: Use a different interrupt or check DC itself for the mapping. + */ + if (enable) { + rc = amdgpu_irq_get(adev, &adev->pageflip_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Get pageflip_irq ret=%d\n", rc); + } else { + rc = amdgpu_irq_put(adev, &adev->pageflip_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Put pageflip_irq ret=%d\n", rc); + } + + if (rc) + return rc; + } #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) /* crtc vline0 interrupt, only available on DCN+ */ From 48ab86360af117123eb1b15e38f068acf3826400 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Fri, 12 Jun 2026 15:49:03 -0400 Subject: [PATCH 472/479] drm/amd/display: check GRPH_FLIP status before sending event [Why] After unifying DCN interrupt sources under VUPDATE_NO_LOCK, we have two remaining issues to clean up: 1. On DCN, flip completion is now delivered from VUPDATE_NO_LOCK (dm_crtc_high_irq_handler) instead of GRPH_PFLIP. But VUPDATE_NO_LOCK fires every frame, regardless of whether a flip has latched. 2. There is a window during commit where a flip is armed (pflip_status = SUBMITTED) but not yet programmed into HW. If the VUPDATE_NO_LOCK fires in that window, its handler would deliver a flip event to userspace before HW has latched to it. If userspace then renders to what it believes is now the back buffer (but HW is still latched to it!), it will cause display corruption. This issue seemed to have been introduced by: commit 1159898a88db ("drm/amd/display: Handle commit plane with no FB.") Enabling replay or psr extended the duration of this window, and hence made corruption more likely to be observed. [How] * Move acrtc->event/pflip_status arming to after update_planes_and_stream_adapter() has programmed the flip into HW. This closes the window where pflip_status is SUBMITTED but the flip is not yet programmed. * Add dc_get_flip_pending_on_otg(), which reads the HUBP flip-pending status straight from HW for the pipe(s) bound to an OTG instance. It is keyed only by otg_inst and does not take or mutate a dc_plane_state, so it is safe to call from the OTG interrupt handler without racing a concurrent commit that may be modifying plane state. * Optimistically query for flip-pending after programming, in the event that HW latched to the new fb between programming start and arming event. If it latched, send the vblank event immediately, rather than wait for the next vblank IRQ. * In the VUPDATE_NO_LOCK handler, only deliver flip completion once dc_get_flip_pending_on_otg() reports the flip is no longer pending. Otherwise leave the flip armed and retry on the next vupdate. * For DCE, maintain the existing behavior of arming flips before programming, and relying on GRPH_FLIP to fire at HW latch. v2: * Drop flip_programmed completion object, instead move event/pflip_status arming after programming. * For DCN, optimistically query for flip pending immediately after programming, and if it latched, send event right away. v3: * Fix event timestamps on optimistic flip latch detection, where it's possible for it to run *before* the vupdate IRQ updates the timestamp. * Add more docstrings for DCN vblank handling. * Clean up if conditions in dm_arm_vblank_event(). * Code style cleanup on braces surrounding multi-line statements. Fixes: 9b47278cec98 ("drm/amd/display: temp w/a for dGPU to enter idle optimizations") Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/3787 Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/4141 Assisted-by: Copilot:claude-opus-4.8 Tested-by: Mario Limonciello (AMD) Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Leo Li Signed-off-by: Alex Deucher (cherry picked from commit f64a9be5653689ff43e148cd8a6483077488c8e5) Cc: stable@vger.kernel.org # 8382cd234981: drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock Cc: stable@vger.kernel.org --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 191 ++++++++++++++---- drivers/gpu/drm/amd/display/dc/core/dc.c | 45 +++++ drivers/gpu/drm/amd/display/dc/dc.h | 1 + 3 files changed, 197 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 39c0ea1f50b1..10105341e6db 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -681,13 +681,30 @@ static void dm_crtc_high_irq_handler(struct amdgpu_device *adev, * Deliver pageflip completion events (DCN only). * * Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch - * point. Deliver any pending pageflip completion event from here. + * point. Deliver any pending pageflip completion event from here, + * once HW has consumed the new address (the OTG no longer reports a + * pending flip). * - * NOTE: This can deliver an event for a flip that was armed but not yet - * programmed into HW; that race is closed in a follow-up change by - * checking the programmed flip status. + * Also handle the case here where there aren't any active planes and + * DCN HUBP may be clock-gated, so the flip-pending status may be + * undefined. */ - if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED) { + if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc->event) { + + if (!dc_get_flip_pending_on_otg(adev->dm.dc, acrtc->otg_inst)) { + drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); + acrtc->event = NULL; + drm_crtc_vblank_put(&acrtc->base); + acrtc->pflip_status = AMDGPU_FLIP_NONE; + } + /* + * If the flip is still pending, leave it armed and + * retry on the next vupdate. + */ + } else if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc->dm_irq_params.active_planes == 0) { + if (acrtc->event) { drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); acrtc->event = NULL; @@ -10257,6 +10274,28 @@ static void amdgpu_dm_enable_self_refresh(struct amdgpu_display_manager *dm, } } +static void dm_arm_vblank_event(struct amdgpu_crtc *acrtc, + struct dm_crtc_state *acrtc_state, + bool pflip_update, + bool cursor_update) +{ + assert_spin_locked(&acrtc->base.dev->event_lock); + + if (!acrtc->base.state->event || acrtc_state->active_planes == 0) + return; + + if (pflip_update) { + drm_crtc_vblank_get(&acrtc->base); + WARN_ON(acrtc->pflip_status != AMDGPU_FLIP_NONE); + /* Arm flip completion handling and event delivery after programming. */ + prepare_flip_isr(acrtc); + } else if (cursor_update) { + drm_crtc_vblank_get(&acrtc->base); + acrtc->event = acrtc->base.state->event; + acrtc->base.state->event = NULL; + } +} + static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, struct drm_device *dev, struct amdgpu_display_manager *dm, @@ -10280,6 +10319,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, bool cursor_update = false; bool pflip_present = false; bool immediate_flip = false; + bool flip_latched_during_prog = false; bool dirty_rects_changed = false; bool updated_planes_and_streams = false; struct { @@ -10514,39 +10554,24 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, usleep_range(1000, 1100); } - /** - * Prepare the flip event for the pageflip interrupt to handle. - * - * This only works in the case where we've already turned on the - * appropriate hardware blocks (eg. HUBP) so in the transition case - * from 0 -> n planes we have to skip a hardware generated event - * and rely on sending it from software. - */ - if (acrtc_attach->base.state->event && - acrtc_state->active_planes > 0) { - drm_crtc_vblank_get(pcrtc); - - spin_lock_irqsave(&pcrtc->dev->event_lock, flags); - - WARN_ON(acrtc_attach->pflip_status != AMDGPU_FLIP_NONE); - prepare_flip_isr(acrtc_attach); - - spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); - } - if (acrtc_state->stream) { if (acrtc_state->freesync_vrr_info_changed) bundle->stream_update.vrr_infopacket = &acrtc_state->stream->vrr_infopacket; } - } else if (cursor_update && acrtc_state->active_planes > 0) { - spin_lock_irqsave(&pcrtc->dev->event_lock, flags); - if (acrtc_attach->base.state->event) { - drm_crtc_vblank_get(pcrtc); - acrtc_attach->event = acrtc_attach->base.state->event; - acrtc_attach->base.state->event = NULL; + } + + /* + * DCE depends on a combination of GRPH_FLIP, VLINE0, and VUPDATE for + * event delivery. Only GRPH_FLIP handler can send pflip events, and it + * only fires if HW latched to the flip. Maintain legacy behavior by + * arming event before programming. + */ + if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) == 0) { + scoped_guard(spinlock_irqsave, &pcrtc->dev->event_lock) { + dm_arm_vblank_event(acrtc_attach, acrtc_state, + pflip_present, cursor_update); } - spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); } /* Update the planes if changed or disable if we don't have any. */ @@ -10639,17 +10664,103 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, amdgpu_dm_commit_cursors(state); /* - * On DCN, flip completion is normally delivered from VUPDATE_NO_LOCK. - * However, an immediate (tearing / async) flip is latched by HW right - * away and does not wait for the next vupdate, so deliver its - * completion event here after programming. + * DCN specific vblank handling + * ============================ * - * On DCE, GRPH_PFLIP already fires immediately for immediate flips, so - * this is DCN-only. + * With the event_lock held, arm the vblank event, and determine whether + * deliver it immediately, or in VUPDATE_NO_LOCK IRQ (i.e. HW latch + * point) handler. Do this *after* programming so that the IRQ handler + * will not deliver the event before HW laches onto the programmed + * values: + * + * Commit thread IRQ handler HW + * ----------------------------------------------------------------- + * arm_vblank_event() + * vupdate() + * vupdate_handler() + * cook_timestamp() + * # prev flip already latched, + * # so flip_latched == true. + * if event_armed && flip_latched: + * send_vblank_event() + * # sent before latch, **BAD!** + * hw_program() + * vupdate() + * **latch** + * + * There's a consequence of arming after: it's possible for HW to latch + * between start of HW programming and acrtc->event/pflip_status arming. + * When this happens, the IRQ handler will send the event on the next + * immediate latch point, even though HW has already latched. This is + * handled by optimistically checking for HW latch after programming, + * and if latched, send the event immediately: + * + * Commit thread IRQ handler HW + * ----------------------------------------------------------------- + * hw_program() + * vupdate() + * **latch** + * vupdate_handler() + * cook_timestamp() + * # event_armed == false + * # **no event sent!** + * arm_vblank_event() + * if flip_latched: + * **send_vblank_event()** + * disarm_vblank_event() + * + * The IRQ handler is expected to cook the timestamp, but we need to + * cook the timestamp before optimistic sending as well. That's because + * the following sequence is possible: + * + * Commit thread IRQ handler HW + * ----------------------------------------------------------------- + * hw_program() + * arm_vblank_event() + * vupdate() + * **latch** + * if flip_latched: + * # Need cook before send! + * **cook_timestamp()** + * send_vblank_event() + * disarm_vblank_event() + * vupdate_handler() + * cook_timestamp() + * # event_armed == false + * # no event sent! + * + * Cooking twice is OK, since DRM scanout accurate timestamps report A) + * the previous vactive start if currently in vactive, or B) the next + * vactive start if currently in vblank (see &get_vblank_counter). 'A)' + * is what we want for the optimistic send, and for 'B)', we'll cook a + * timestamp no later than the next IRQ handler run. + * + * The more correct fix is to wrap programming and arming with the + * event_lock and thus serializing it with the IRQ handler. However, + * there are various sleep-waits within + * update_planes_and_stream_adapter() that makes spin locking illegal. + * And on full updates, it can take 1-2 frame-times to return (see + * commit_planes_for_stream). + * + * On DCE, GRPH_PFLIP IRQ is used and takes care of this. */ - if (immediate_flip && amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) { + if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) { spin_lock_irqsave(&pcrtc->dev->event_lock, flags); - if (acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED && + + if (updated_planes_and_streams) { + flip_latched_during_prog = + !dc_get_flip_pending_on_otg(dm->dc, acrtc_attach->otg_inst); + } + + dm_arm_vblank_event(acrtc_attach, acrtc_state, + pflip_present, cursor_update); + + /* + * Deliver the event immediately on immediate flip, or on a + * update that has already latched. + */ + if ((immediate_flip || flip_latched_during_prog) && + acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED && acrtc_attach->event) { drm_crtc_accurate_vblank_count(&acrtc_attach->base); drm_crtc_send_vblank_event(&acrtc_attach->base, diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 175106cce5a4..e25b94b65dac 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -6165,6 +6165,51 @@ void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src) dal_irq_service_ack(dc->res_pool->irqs, src); } +/* + * dc_get_flip_pending_on_otg() - Check if a GRPH_FLIP is still pending on OTG + * + * @dc: display core context @otg_inst: OTG instance to query + * + * Reads the HUBP flip-pending status for the pipe(s) bound to @otg_inst, + * returning true if any of them has not yet latched its programmed surface + * address. + * + * Unlike dc_plane_get_status(), this does not take or mutate a dc_plane_state, + * so it is safe to call from interrupt context without racing a concurrent + * commit that may be updating plane state. + * + * Return: true if a flip is still pending on the OTG, false otherwise. + */ +bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst) +{ + bool flip_pending = false; + int i; + + if (!dc || !dc->current_state) + return false; + + dc_exit_ips_for_hw_access(dc); + + for (i = 0; i < dc->res_pool->pipe_count; i++) { + struct pipe_ctx *pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i]; + struct hubp *hubp = pipe_ctx->plane_res.hubp; + + if (!pipe_ctx->plane_state || !pipe_ctx->stream_res.tg) + continue; + + if (pipe_ctx->stream_res.tg->inst != otg_inst) + continue; + + if (hubp && hubp->funcs->hubp_is_flip_pending && + hubp->funcs->hubp_is_flip_pending(hubp)) { + flip_pending = true; + break; + } + } + + return flip_pending; +} + void dc_power_down_on_boot(struct dc *dc) { if (dc->ctx->dce_environment != DCE_ENV_VIRTUAL_HW && diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index ebdcbfa7db80..1dc85f6b6689 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -2883,6 +2883,7 @@ enum dc_irq_source dc_interrupt_to_irq_source( uint32_t ext_id); bool dc_interrupt_set(struct dc *dc, enum dc_irq_source src, bool enable); void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src); +bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst); enum dc_irq_source dc_get_hpd_irq_source_at_index( struct dc *dc, uint32_t link_index); From f39283eab44fb7e304677b2aa1d8c38f151b6566 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Mon, 4 May 2026 14:09:49 -0400 Subject: [PATCH 473/479] Revert "drm/amd/display: Restore 5s vbl offdelay for NV3x+ DGPUs" Now that proper fixes have been found, let's revert this workaround. This reverts commit a1fc7bf6677eb547167cb72b3bcafdc34b976692. Tested-by: Mario Limonciello (AMD) Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Leo Li Signed-off-by: Alex Deucher (cherry picked from commit f64a9be5653689ff43e148cd8a6483077488c8e5) Cc: stable@vger.kernel.org # 8382cd234981: drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock Cc: stable@vger.kernel.org # 48ab86360af1: drm/amd/display: check GRPH_FLIP status before sending event Cc: stable@vger.kernel.org --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 10105341e6db..9c564cd5edee 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -9675,21 +9675,9 @@ static void manage_dm_interrupts(struct amdgpu_device *adev, if (acrtc_state) { timing = &acrtc_state->stream->timing; - if (amdgpu_ip_version(adev, DCE_HWIP, 0) >= - IP_VERSION(3, 2, 0) && - !(adev->flags & AMD_IS_APU)) { - /* - * DGPUs NV3x and newer that support idle optimizations - * experience intermittent flip-done timeouts on cursor - * updates. Restore 5s offdelay behavior for now. - * - * Discussion on the issue: - * https://lore.kernel.org/amd-gfx/20260217191632.1243826-1-sysdadmin@m1k.cloud/ - */ - config.offdelay_ms = 5000; - config.disable_immediate = false; - } else if (amdgpu_ip_version(adev, DCE_HWIP, 0) < - IP_VERSION(3, 5, 0)) { + if (amdgpu_ip_version(adev, DCE_HWIP, 0) < + IP_VERSION(3, 5, 0) || + !(adev->flags & AMD_IS_APU)) { /* * Older HW and DGPU have issues with instant off; * use a 2 frame offdelay. From 1590cf0329716306e948a8fc29f1d3ee87d3989f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 19 Jul 2026 13:54:41 -0700 Subject: [PATCH 474/479] Linux 7.2-rc4 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bfb47ad8cb9a..11539c3fd405 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 2 SUBLEVEL = 0 -EXTRAVERSION = -rc3 +EXTRAVERSION = -rc4 NAME = Baby Opossum Posse # *DOCUMENTATION* From 7fa44519a2a3a9da715669a7f359f08e49911e2f Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Fri, 17 Jul 2026 15:25:03 +0200 Subject: [PATCH 475/479] ASoC: aw88399: extract shared device library Extract the device-level functions from aw88399.c into a new shared library module (aw88399-lib.c) with a shared header at include/sound/aw88399.h, following the pattern established by CS35L41 (cs35l41-lib.c / include/sound/cs35l41.h) for chips that need both ASoC and HDA drivers. The shared header at include/sound/aw88399.h contains the register definitions, bit-field masks, hardware constants, device enums, the struct aw88399 definition, and the library function declarations. The ASoC-private header at sound/soc/codecs/aw88399.h is reduced to ASoC-specific definitions (PCM formats/rates, ALSA kcontrol helpers, calibration constants) and includes the shared header. The library contains the chip initialization, firmware loading, playback start/stop sequences, and all their internal dependencies (PLL checks, DSP management, volume control, calibration, CRC verification, etc.). The ASoC codec driver retains the ALSA controls, DAPM widgets, codec probe/remove, calibration service, and I2C bus driver registration. A new Kconfig symbol SND_SOC_AW88399_LIB is introduced. SND_SOC_AW88399 (the existing ASoC codec) selects it, ensuring no change for current users. The HDA side codec driver (introduced later in this series) selects the library without pulling in the full ASoC codec module. This avoids a build-time dependency on the full ASoC driver and follows the established pattern used by CS35L41 (SND_SOC_CS35L41_LIB) for chips with both ASoC and HDA drivers. Some library functions (DSP control, volume setting, mute, calibration updates, profile management, and status helpers) are used internally by the library's start/stop sequences but are also called directly by the ASoC driver's remaining code. These are exported from the library so the ASoC module can access them. This is a pure code movement with no functional changes. The moved functions are identical to their originals in aw88399.c. Tested-by: Nadim Kobeissi Tested-by: Xia Yun'an Tested-by: Munzir Taha Signed-off-by: Marco Giunta Link: https://patch.msgid.link/DS7PR19MB772415C485FAF74297673FD7FCC62@DS7PR19MB7724.namprd19.prod.outlook.com Signed-off-by: Mark Brown --- include/sound/aw88399.h | 618 ++++++++++++++ sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/aw88399-lib.c | 1381 ++++++++++++++++++++++++++++++++ sound/soc/codecs/aw88399.c | 1349 ------------------------------- sound/soc/codecs/aw88399.h | 582 +------------- 6 files changed, 2008 insertions(+), 1929 deletions(-) create mode 100644 include/sound/aw88399.h create mode 100644 sound/soc/codecs/aw88399-lib.c diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h new file mode 100644 index 000000000000..3a2153f0ee92 --- /dev/null +++ b/include/sound/aw88399.h @@ -0,0 +1,618 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * linux/sound/aw88399.h -- Platform data for AW88399 + * + * Copyright (c) 2023 AWINIC Technology CO., LTD + * + * Author: Weidong Wang + */ + +#ifndef __SOUND_AW88399_H +#define __SOUND_AW88399_H + +#include +#include + +/* registers list */ +#define AW88399_ID_REG (0x00) +#define AW88399_SYSST_REG (0x01) +#define AW88399_SYSINT_REG (0x02) +#define AW88399_SYSINTM_REG (0x03) +#define AW88399_SYSCTRL_REG (0x04) +#define AW88399_SYSCTRL2_REG (0x05) +#define AW88399_I2SCTRL1_REG (0x06) +#define AW88399_I2SCTRL2_REG (0x07) +#define AW88399_I2SCTRL3_REG (0x08) +#define AW88399_DACCFG1_REG (0x09) +#define AW88399_DACCFG2_REG (0x0A) +#define AW88399_DACCFG3_REG (0x0B) +#define AW88399_DACCFG4_REG (0x0C) +#define AW88399_DACCFG5_REG (0x0D) +#define AW88399_DACCFG6_REG (0x0E) +#define AW88399_DACCFG7_REG (0x0F) +#define AW88399_MPDCFG1_REG (0x10) +#define AW88399_MPDCFG2_REG (0x11) +#define AW88399_MPDCFG3_REG (0x12) +#define AW88399_MPDCFG4_REG (0x13) +#define AW88399_PWMCTRL1_REG (0x14) +#define AW88399_PWMCTRL2_REG (0x15) +#define AW88399_PWMCTRL3_REG (0x16) +#define AW88399_I2SCFG1_REG (0x17) +#define AW88399_DBGCTRL_REG (0x18) +#define AW88399_HAGCST_REG (0x20) +#define AW88399_VBAT_REG (0x21) +#define AW88399_TEMP_REG (0x22) +#define AW88399_PVDD_REG (0x23) +#define AW88399_ISNDAT_REG (0x24) +#define AW88399_VSNDAT_REG (0x25) +#define AW88399_I2SINT_REG (0x26) +#define AW88399_I2SCAPCNT_REG (0x27) +#define AW88399_ANASTA1_REG (0x28) +#define AW88399_ANASTA2_REG (0x29) +#define AW88399_ANASTA3_REG (0x2A) +#define AW88399_TESTDET_REG (0x2B) +#define AW88399_DSMCFG1_REG (0x30) +#define AW88399_DSMCFG2_REG (0x31) +#define AW88399_DSMCFG3_REG (0x32) +#define AW88399_DSMCFG4_REG (0x33) +#define AW88399_DSMCFG5_REG (0x34) +#define AW88399_DSMCFG6_REG (0x35) +#define AW88399_DSMCFG7_REG (0x36) +#define AW88399_DSMCFG8_REG (0x37) +#define AW88399_TESTIN_REG (0x38) +#define AW88399_TESTOUT_REG (0x39) +#define AW88399_MEMTEST_REG (0x3A) +#define AW88399_VSNCTRL1_REG (0x3B) +#define AW88399_ISNCTRL1_REG (0x3C) +#define AW88399_ISNCTRL2_REG (0x3D) +#define AW88399_DSPMADD_REG (0x40) +#define AW88399_DSPMDAT_REG (0x41) +#define AW88399_WDT_REG (0x42) +#define AW88399_ACR1_REG (0x43) +#define AW88399_ACR2_REG (0x44) +#define AW88399_ASR1_REG (0x45) +#define AW88399_ASR2_REG (0x46) +#define AW88399_DSPCFG_REG (0x47) +#define AW88399_ASR3_REG (0x48) +#define AW88399_ASR4_REG (0x49) +#define AW88399_DSPVCALB_REG (0x4A) +#define AW88399_CRCCTRL_REG (0x4B) +#define AW88399_DSPDBG1_REG (0x4C) +#define AW88399_DSPDBG2_REG (0x4D) +#define AW88399_DSPDBG3_REG (0x4E) +#define AW88399_PLLCTRL1_REG (0x50) +#define AW88399_PLLCTRL2_REG (0x51) +#define AW88399_PLLCTRL3_REG (0x52) +#define AW88399_CDACTRL1_REG (0x53) +#define AW88399_CDACTRL2_REG (0x54) +#define AW88399_CDACTRL3_REG (0x55) +#define AW88399_SADCCTRL1_REG (0x56) +#define AW88399_SADCCTRL2_REG (0x57) +#define AW88399_BOPCTRL1_REG (0x58) +#define AW88399_BOPCTRL2_REG (0x5A) +#define AW88399_BOPCTRL3_REG (0x5B) +#define AW88399_BOPCTRL4_REG (0x5C) +#define AW88399_BOPCTRL5_REG (0x5D) +#define AW88399_BOPCTRL6_REG (0x5E) +#define AW88399_BOPCTRL7_REG (0x5F) +#define AW88399_BSTCTRL1_REG (0x60) +#define AW88399_BSTCTRL2_REG (0x61) +#define AW88399_BSTCTRL3_REG (0x62) +#define AW88399_BSTCTRL4_REG (0x63) +#define AW88399_BSTCTRL5_REG (0x64) +#define AW88399_BSTCTRL6_REG (0x65) +#define AW88399_BSTCTRL7_REG (0x66) +#define AW88399_BSTCTRL8_REG (0x67) +#define AW88399_BSTCTRL9_REG (0x68) +#define AW88399_BSTCTRL10_REG (0x69) +#define AW88399_CPCTRL_REG (0x6A) +#define AW88399_EFWH_REG (0x6C) +#define AW88399_EFWM2_REG (0x6D) +#define AW88399_EFWM1_REG (0x6E) +#define AW88399_EFWL_REG (0x6F) +#define AW88399_TESTCTRL1_REG (0x70) +#define AW88399_TESTCTRL2_REG (0x71) +#define AW88399_EFCTRL1_REG (0x72) +#define AW88399_EFCTRL2_REG (0x73) +#define AW88399_EFRH4_REG (0x74) +#define AW88399_EFRH3_REG (0x75) +#define AW88399_EFRH2_REG (0x76) +#define AW88399_EFRH1_REG (0x77) +#define AW88399_EFRL4_REG (0x78) +#define AW88399_EFRL3_REG (0x79) +#define AW88399_EFRL2_REG (0x7A) +#define AW88399_EFRL1_REG (0x7B) +#define AW88399_TM_REG (0x7C) +#define AW88399_TM2_REG (0x7D) + +#define AW88399_REG_MAX (0x7E) +#define AW88399_MUTE_VOL (1023) + +#define AW88399_DSP_CFG_ADDR (0x9B00) +#define AW88399_DSP_REG_CFG_ADPZ_RA (0x9B68) +#define AW88399_DSP_FW_ADDR (0x8980) +#define AW88399_DSP_ROM_CHECK_ADDR (0x1F40) +#define AW88399_DSP_ROM_CHECK_DATA (0x4638) + +#define AW88399_CALI_RE_HBITS_MASK (~(0xFFFF0000)) +#define AW88399_CALI_RE_HBITS_SHIFT (16) + +#define AW88399_CALI_RE_LBITS_MASK (~(0xFFFF)) +#define AW88399_CALI_RE_LBITS_SHIFT (0) + +#define AW88399_I2STXEN_START_BIT (9) +#define AW88399_I2STXEN_BITS_LEN (1) +#define AW88399_I2STXEN_MASK \ + (~(((1<> (shift)) +#define AW88399_SHOW_RE_TO_DSP_RE(re, shift) (((re) << shift) / (1000)) +#define AW88399_CRC_CHECK_PASS_VAL (0x4) + +#define AW88399_CRC_CFG_BASE_ADDR (0xD80) +#define AW88399_CRC_FW_BASE_ADDR (0x4C0) +#define AW88399_ACF_FILE "aw88399_acf.bin" +#define AW88399_DEV_SYSST_CHECK_MAX (10) +#define AW88399_CHIP_ID 0x2183 + +#define AW88399_START_RETRIES (5) +#define AW88399_START_WORK_DELAY_MS (0) + +enum { + AW_EF_AND_CHECK = 0, + AW_EF_OR_CHECK, +}; + +enum { + AW88399_DEV_VDSEL_DAC = 0, + AW88399_DEV_VDSEL_VSENSE = 32, +}; + +enum { + AW88399_DSP_CRC_NA = 0, + AW88399_DSP_CRC_OK = 1, +}; + +enum { + AW88399_DSP_FW_UPDATE_OFF = 0, + AW88399_DSP_FW_UPDATE_ON = 1, +}; + +enum { + AW88399_FORCE_UPDATE_OFF = 0, + AW88399_FORCE_UPDATE_ON = 1, +}; + +enum { + AW88399_1000_US = 1000, + AW88399_2000_US = 2000, + AW88399_3000_US = 3000, + AW88399_4000_US = 4000, +}; + +enum AW88399_DEV_STATUS { + AW88399_DEV_PW_OFF = 0, + AW88399_DEV_PW_ON, +}; + +enum AW88399_DEV_FW_STATUS { + AW88399_DEV_FW_FAILED = 0, + AW88399_DEV_FW_OK, +}; + +enum AW88399_DEV_MEMCLK { + AW88399_DEV_MEMCLK_OSC = 0, + AW88399_DEV_MEMCLK_PLL = 1, +}; + +enum AW88399_DEV_DSP_CFG { + AW88399_DEV_DSP_WORK = 0, + AW88399_DEV_DSP_BYPASS = 1, +}; + +enum { + AW88399_NOT_RCV_MODE = 0, + AW88399_RCV_MODE = 1, +}; + +enum { + AW88399_SYNC_START = 0, + AW88399_ASYNC_START, +}; + +struct aw_device; +struct aw_container; +struct aw_cali_desc; +struct gpio_desc; +struct i2c_client; +struct regmap; + +struct aw88399 { + struct aw_device *aw_pa; + struct mutex lock; + struct gpio_desc *reset_gpio; + struct delayed_work start_work; + struct regmap *regmap; + struct aw_container *aw_cfg; + + unsigned int check_val; + unsigned int crc_init_val; + unsigned int vcalb_init_val; + unsigned int dither_st; +}; + +int aw_dev_check_syspll(struct aw_device *aw_dev); +void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable); +int aw_dev_get_dsp_status(struct aw_device *aw_dev); +int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value); +int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc); +int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name); +void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute); +void aw88399_hw_reset(struct aw88399 *aw88399); +int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap); +extern const struct regmap_config aw88399_remap_config; +int aw88399_request_firmware_file(struct aw88399 *aw88399); +void aw88399_start(struct aw88399 *aw88399, bool sync_start); +void aw88399_startup_work(struct work_struct *work); +int aw88399_stop(struct aw_device *aw_dev); + +#endif /* __SOUND_AW88399_H */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 252f683be3c1..18e34899566e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -726,6 +726,10 @@ config SND_SOC_AW87390 sound quality, which is a new high efficiency, low noise, constant large volume, 6th Smart K audio amplifier. +config SND_SOC_AW88399_LIB + tristate + select SND_SOC_AW88395_LIB + config SND_SOC_AW88399 tristate "Soc Audio for awinic aw88399" depends on I2C @@ -733,6 +737,7 @@ config SND_SOC_AW88399 select REGMAP_I2C select GPIOLIB select SND_SOC_AW88395_LIB + select SND_SOC_AW88399_LIB help This option enables support for aw88399 Smart PA. The awinic AW88399 is an I2S/TDM input, high efficiency diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index aa0396e5b575..d2a689006d69 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -55,6 +55,7 @@ snd-soc-aw88395-y := aw88395/aw88395.o snd-soc-aw88166-y := aw88166.o snd-soc-aw88261-y := aw88261.o snd-soc-aw88399-y := aw88399.o +snd-soc-aw88399-lib-y := aw88399-lib.o snd-soc-bd28623-y := bd28623.o snd-soc-bt-sco-y := bt-sco.o snd-soc-chv3-codec-y := chv3-codec.o @@ -496,6 +497,7 @@ obj-$(CONFIG_SND_SOC_AW88395) +=snd-soc-aw88395.o obj-$(CONFIG_SND_SOC_AW88166) +=snd-soc-aw88166.o obj-$(CONFIG_SND_SOC_AW88261) +=snd-soc-aw88261.o obj-$(CONFIG_SND_SOC_AW88399) += snd-soc-aw88399.o +obj-$(CONFIG_SND_SOC_AW88399_LIB) += snd-soc-aw88399-lib.o obj-$(CONFIG_SND_SOC_BD28623) += snd-soc-bd28623.o obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o obj-$(CONFIG_SND_SOC_CHV3_CODEC) += snd-soc-chv3-codec.o diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c new file mode 100644 index 000000000000..258d6efbf590 --- /dev/null +++ b/sound/soc/codecs/aw88399-lib.c @@ -0,0 +1,1381 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// aw88399-lib.c -- AW88399 Common functions for ASoC and HDA audio drivers +// +// Copyright (c) 2023 AWINIC Technology CO., LTD +// +// Author: Weidong Wang +// + +#include +#include +#include +#include +#include +#include +#include +#include "aw88395/aw88395_device.h" + +const struct regmap_config aw88399_remap_config = { + .val_bits = 16, + .reg_bits = 8, + .max_register = AW88399_REG_MAX, + .reg_format_endian = REGMAP_ENDIAN_LITTLE, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; +EXPORT_SYMBOL_GPL(aw88399_remap_config); + +static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) +{ + int ret; + + if (pwd) + ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, + ~AW88399_PWDN_MASK, AW88399_PWDN_POWER_DOWN_VALUE); + else + ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, + ~AW88399_PWDN_MASK, AW88399_PWDN_WORKING_VALUE); + + if (ret) + dev_dbg(aw_dev->dev, "%s failed", __func__); +} + +static void aw_dev_get_int_status(struct aw_device *aw_dev, unsigned short *int_status) +{ + unsigned int reg_val; + int ret; + + ret = regmap_read(aw_dev->regmap, AW88399_SYSINT_REG, ®_val); + if (ret) + dev_err(aw_dev->dev, "read interrupt reg fail, ret=%d", ret); + else + *int_status = reg_val; + + dev_dbg(aw_dev->dev, "read interrupt reg=0x%04x", *int_status); +} + +static void aw_dev_clear_int_status(struct aw_device *aw_dev) +{ + u16 int_status; + + /* read int status and clear */ + aw_dev_get_int_status(aw_dev, &int_status); + /* make sure int status is clear */ + aw_dev_get_int_status(aw_dev, &int_status); + if (int_status) + dev_dbg(aw_dev->dev, "int status(%d) is not cleaned.\n", int_status); +} + +static int aw_dev_get_iis_status(struct aw_device *aw_dev) +{ + unsigned int reg_val; + int ret; + + ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); + if (ret) + return ret; + if ((reg_val & AW88399_BIT_PLL_CHECK) != AW88399_BIT_PLL_CHECK) { + dev_err(aw_dev->dev, "check pll lock fail, reg_val:0x%04x", reg_val); + return -EINVAL; + } + + return 0; +} + +static int aw_dev_check_mode1_pll(struct aw_device *aw_dev) +{ + int ret, i; + + for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { + ret = aw_dev_get_iis_status(aw_dev); + if (ret) { + dev_err(aw_dev->dev, "mode1 iis signal check error"); + usleep_range(AW88399_2000_US, AW88399_2000_US + 10); + } else { + return 0; + } + } + + return -EPERM; +} + +static int aw_dev_check_mode2_pll(struct aw_device *aw_dev) +{ + unsigned int reg_val; + int ret, i; + + ret = regmap_read(aw_dev->regmap, AW88399_PLLCTRL2_REG, ®_val); + if (ret) + return ret; + + reg_val &= (~AW88399_CCO_MUX_MASK); + if (reg_val == AW88399_CCO_MUX_DIVIDED_VALUE) { + dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); + return -EPERM; + } + + /* change mode2 */ + ret = regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, + ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_DIVIDED_VALUE); + if (ret) + return ret; + + for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { + ret = aw_dev_get_iis_status(aw_dev); + if (ret) { + dev_err(aw_dev->dev, "mode2 iis signal check error"); + usleep_range(AW88399_2000_US, AW88399_2000_US + 10); + } else { + break; + } + } + + /* change mode1 */ + regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, + ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_BYPASS_VALUE); + if (ret == 0) { + usleep_range(AW88399_2000_US, AW88399_2000_US + 10); + for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { + ret = aw_dev_get_iis_status(aw_dev); + if (ret) { + dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); + usleep_range(AW88399_2000_US, AW88399_2000_US + 10); + } else { + break; + } + } + } + + return ret; +} + +int aw_dev_check_syspll(struct aw_device *aw_dev) +{ + int ret; + + ret = aw_dev_check_mode1_pll(aw_dev); + if (ret) { + dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); + ret = aw_dev_check_mode2_pll(aw_dev); + if (ret) { + dev_err(aw_dev->dev, "mode2 check iis failed"); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(aw_dev_check_syspll); + +static int aw_dev_check_sysst(struct aw_device *aw_dev) +{ + unsigned int check_val; + unsigned int reg_val; + int ret, i; + + ret = regmap_read(aw_dev->regmap, AW88399_PWMCTRL3_REG, ®_val); + if (ret) + return ret; + + if (reg_val & (~AW88399_NOISE_GATE_EN_MASK)) + check_val = AW88399_BIT_SYSST_NOSWS_CHECK; + else + check_val = AW88399_BIT_SYSST_SWS_CHECK; + + for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { + ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); + if (ret) + return ret; + + if ((reg_val & (~AW88399_BIT_SYSST_CHECK_MASK) & check_val) != check_val) { + dev_err(aw_dev->dev, "check sysst fail, cnt=%d, reg_val=0x%04x, check:0x%x", + i, reg_val, AW88399_BIT_SYSST_NOSWS_CHECK); + usleep_range(AW88399_2000_US, AW88399_2000_US + 10); + } else { + return 0; + } + } + + return -EPERM; +} + +static void aw_dev_amppd(struct aw_device *aw_dev, bool amppd) +{ + int ret; + + if (amppd) + ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, + ~AW88399_AMPPD_MASK, AW88399_AMPPD_POWER_DOWN_VALUE); + else + ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, + ~AW88399_AMPPD_MASK, AW88399_AMPPD_WORKING_VALUE); + + if (ret) + dev_dbg(aw_dev->dev, "%s failed", __func__); +} + +void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) +{ + int ret; + + if (is_enable) + ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, + ~AW88399_DSPBY_MASK, AW88399_DSPBY_WORKING_VALUE); + else + ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, + ~AW88399_DSPBY_MASK, AW88399_DSPBY_BYPASS_VALUE); + + if (ret) + dev_dbg(aw_dev->dev, "%s failed\n", __func__); +} +EXPORT_SYMBOL_GPL(aw_dev_dsp_enable); + +static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) +{ + uint16_t icalkh_val, icalkl_val, icalk_val; + struct aw_device *aw_dev = aw88399->aw_pa; + unsigned int reg_val; + int ret; + + ret = regmap_read(aw_dev->regmap, AW88399_EFRH4_REG, ®_val); + if (ret) + return ret; + icalkh_val = reg_val & (~AW88399_EF_ISN_GESLP_H_MASK); + + ret = regmap_read(aw_dev->regmap, AW88399_EFRL4_REG, ®_val); + if (ret) + return ret; + icalkl_val = reg_val & (~AW88399_EF_ISN_GESLP_L_MASK); + + if (aw88399->check_val == AW_EF_AND_CHECK) + icalk_val = icalkh_val & icalkl_val; + else + icalk_val = icalkh_val | icalkl_val; + + if (icalk_val & (~AW88399_EF_ISN_GESLP_SIGN_MASK)) + icalk_val = icalk_val | AW88399_EF_ISN_GESLP_SIGN_NEG; + *icalk = (int16_t)icalk_val; + + return 0; +} + +static int aw88399_dev_get_vcalk(struct aw88399 *aw88399, int16_t *vcalk) +{ + uint16_t vcalkh_val, vcalkl_val, vcalk_val; + struct aw_device *aw_dev = aw88399->aw_pa; + unsigned int reg_val; + int ret; + + ret = regmap_read(aw_dev->regmap, AW88399_EFRH3_REG, ®_val); + if (ret) + return ret; + + vcalkh_val = reg_val & (~AW88399_EF_VSN_GESLP_H_MASK); + + ret = regmap_read(aw_dev->regmap, AW88399_EFRL3_REG, ®_val); + if (ret) + return ret; + + vcalkl_val = reg_val & (~AW88399_EF_VSN_GESLP_L_MASK); + + if (aw88399->check_val == AW_EF_AND_CHECK) + vcalk_val = vcalkh_val & vcalkl_val; + else + vcalk_val = vcalkh_val | vcalkl_val; + + if (vcalk_val & AW88399_EF_VSN_GESLP_SIGN_MASK) + vcalk_val = vcalk_val | AW88399_EF_VSN_GESLP_SIGN_NEG; + *vcalk = (int16_t)vcalk_val; + + return 0; +} + +static int aw88399_dev_get_internal_vcalk(struct aw88399 *aw88399, int16_t *vcalk) +{ + uint16_t vcalkh_val, vcalkl_val, vcalk_val; + struct aw_device *aw_dev = aw88399->aw_pa; + unsigned int reg_val; + int ret; + + ret = regmap_read(aw_dev->regmap, AW88399_EFRH2_REG, ®_val); + if (ret) + return ret; + vcalkh_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_H_MASK); + + ret = regmap_read(aw_dev->regmap, AW88399_EFRL2_REG, ®_val); + if (ret) + return ret; + vcalkl_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_L_MASK); + + if (aw88399->check_val == AW_EF_AND_CHECK) + vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) & + (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); + else + vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) | + (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); + + if (vcalk_val & (~AW88399_TEM4_SIGN_MASK)) + vcalk_val = vcalk_val | AW88399_TEM4_SIGN_NEG; + + *vcalk = (int16_t)vcalk_val; + + return 0; +} + +static int aw_dev_set_vcalb(struct aw88399 *aw88399) +{ + struct aw_device *aw_dev = aw88399->aw_pa; + unsigned int vsense_select, vsense_value; + int32_t ical_k, vcal_k, vcalb; + int16_t icalk, vcalk; + uint16_t reg_val; + int ret; + + ret = regmap_read(aw_dev->regmap, AW88399_VSNCTRL1_REG, &vsense_value); + if (ret) + return ret; + + vsense_select = vsense_value & (~AW88399_VDSEL_MASK); + + ret = aw88399_dev_get_icalk(aw88399, &icalk); + if (ret) { + dev_err(aw_dev->dev, "get icalk failed\n"); + return ret; + } + + ical_k = icalk * AW88399_ICABLK_FACTOR + AW88399_CABL_BASE_VALUE; + + switch (vsense_select) { + case AW88399_DEV_VDSEL_VSENSE: + ret = aw88399_dev_get_vcalk(aw88399, &vcalk); + vcal_k = vcalk * AW88399_VCABLK_FACTOR + AW88399_CABL_BASE_VALUE; + vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_FACTOR / AW88399_ISCAL_FACTOR * + ical_k / vcal_k * aw88399->vcalb_init_val; + break; + case AW88399_DEV_VDSEL_DAC: + ret = aw88399_dev_get_internal_vcalk(aw88399, &vcalk); + vcal_k = vcalk * AW88399_VCABLK_DAC_FACTOR + AW88399_CABL_BASE_VALUE; + vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_DAC_FACTOR / + AW88399_ISCAL_DAC_FACTOR * ical_k / + vcal_k * aw88399->vcalb_init_val; + break; + default: + dev_err(aw_dev->dev, "%s: unsupported vsense\n", __func__); + ret = -EINVAL; + break; + } + if (ret) + return ret; + + vcalb = vcalb >> AW88399_VCALB_ADJ_FACTOR; + reg_val = (uint32_t)vcalb; + + regmap_write(aw_dev->regmap, AW88399_DSPVCALB_REG, reg_val); + + return 0; +} + +int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) +{ + struct aw_device *aw_dev = + container_of(cali_desc, struct aw_device, cali_desc); + uint16_t re_lbits, re_hbits; + u32 cali_re; + int ret; + + if ((aw_dev->cali_desc.cali_re >= AW88399_CALI_RE_MAX) || + (aw_dev->cali_desc.cali_re <= AW88399_CALI_RE_MIN)) + return -EINVAL; + + cali_re = AW88399_SHOW_RE_TO_DSP_RE((aw_dev->cali_desc.cali_re + + aw_dev->cali_desc.ra), AW88399_DSP_RE_SHIFT); + + re_hbits = (cali_re & (~AW88399_CALI_RE_HBITS_MASK)) >> AW88399_CALI_RE_HBITS_SHIFT; + re_lbits = (cali_re & (~AW88399_CALI_RE_LBITS_MASK)) >> AW88399_CALI_RE_LBITS_SHIFT; + + ret = regmap_write(aw_dev->regmap, AW88399_ACR1_REG, re_hbits); + if (ret) { + dev_err(aw_dev->dev, "set cali re error"); + return ret; + } + + ret = regmap_write(aw_dev->regmap, AW88399_ACR2_REG, re_lbits); + if (ret) + dev_err(aw_dev->dev, "set cali re error"); + + return ret; +} +EXPORT_SYMBOL_GPL(aw_dev_update_cali_re); + +static int aw_dev_fw_crc_check(struct aw_device *aw_dev) +{ + uint16_t check_val, fw_len_val; + unsigned int reg_val; + int ret; + + /* calculate fw_end_addr */ + fw_len_val = ((aw_dev->dsp_fw_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_FW_BASE_ADDR; + + /* write fw_end_addr to crc_end_addr */ + ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, + ~AW88399_CRC_END_ADDR_MASK, fw_len_val); + if (ret) + return ret; + /* enable fw crc check */ + ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, + ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_ENABLE_VALUE); + + usleep_range(AW88399_2000_US, AW88399_2000_US + 10); + + /* read crc check result */ + regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); + if (ret) + return ret; + + check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; + + /* disable fw crc check */ + ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, + ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_DISABLE_VALUE); + if (ret) + return ret; + + if (check_val != AW88399_CRC_CHECK_PASS_VAL) { + dev_err(aw_dev->dev, "%s failed, check_val 0x%x != 0x%x", + __func__, check_val, AW88399_CRC_CHECK_PASS_VAL); + ret = -EINVAL; + } + + return ret; +} + +static int aw_dev_cfg_crc_check(struct aw_device *aw_dev) +{ + uint16_t check_val, cfg_len_val; + unsigned int reg_val; + int ret; + + /* calculate cfg end addr */ + cfg_len_val = ((aw_dev->dsp_cfg_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_CFG_BASE_ADDR; + + /* write cfg_end_addr to crc_end_addr */ + ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, + ~AW88399_CRC_END_ADDR_MASK, cfg_len_val); + if (ret) + return ret; + + /* enable cfg crc check */ + ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, + ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_ENABLE_VALUE); + if (ret) + return ret; + + usleep_range(AW88399_1000_US, AW88399_1000_US + 10); + + /* read crc check result */ + ret = regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); + if (ret) + return ret; + + check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; + + /* disable cfg crc check */ + ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, + ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_DISABLE_VALUE); + if (ret) + return ret; + + if (check_val != AW88399_CRC_CHECK_PASS_VAL) { + dev_err(aw_dev->dev, "crc_check failed, check val 0x%x != 0x%x", + check_val, AW88399_CRC_CHECK_PASS_VAL); + ret = -EINVAL; + } + + return ret; +} + +static int aw_dev_hw_crc_check(struct aw88399 *aw88399) +{ + struct aw_device *aw_dev = aw88399->aw_pa; + int ret; + + ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, + ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_BYPASS_VALUE); + if (ret) + return ret; + + ret = aw_dev_fw_crc_check(aw_dev); + if (ret) { + dev_err(aw_dev->dev, "fw_crc_check failed\n"); + goto crc_check_failed; + } + + ret = aw_dev_cfg_crc_check(aw_dev); + if (ret) { + dev_err(aw_dev->dev, "cfg_crc_check failed\n"); + goto crc_check_failed; + } + + ret = regmap_write(aw_dev->regmap, AW88399_CRCCTRL_REG, aw88399->crc_init_val); + if (ret) + return ret; + + ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, + ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); + + return ret; + +crc_check_failed: + regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, + ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); + return ret; +} + +static void aw_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) +{ + int ret; + + if (flag) + ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCTRL3_REG, + ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_ENABLE_VALUE); + else + ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, + ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_DISABLE_VALUE); + + if (ret) + dev_dbg(aw_dev->dev, "%s failed", __func__); +} + +int aw_dev_get_dsp_status(struct aw_device *aw_dev) +{ + unsigned int reg_val; + int ret; + + ret = regmap_read(aw_dev->regmap, AW88399_WDT_REG, ®_val); + if (ret) + return ret; + if (!(reg_val & (~AW88399_WDT_CNT_MASK))) + return -EPERM; + + return 0; +} +EXPORT_SYMBOL_GPL(aw_dev_get_dsp_status); + +static int aw_dev_dsp_check(struct aw_device *aw_dev) +{ + int ret, i; + + switch (aw_dev->dsp_cfg) { + case AW88399_DEV_DSP_BYPASS: + dev_dbg(aw_dev->dev, "dsp bypass"); + ret = 0; + break; + case AW88399_DEV_DSP_WORK: + aw_dev_dsp_enable(aw_dev, false); + aw_dev_dsp_enable(aw_dev, true); + usleep_range(AW88399_1000_US, AW88399_1000_US + 10); + for (i = 0; i < AW88399_DEV_DSP_CHECK_MAX; i++) { + ret = aw_dev_get_dsp_status(aw_dev); + if (ret) { + dev_err(aw_dev->dev, "dsp wdt status error=%d", ret); + usleep_range(AW88399_2000_US, AW88399_2000_US + 10); + } + } + break; + default: + dev_err(aw_dev->dev, "unknown dsp cfg=%d", aw_dev->dsp_cfg); + ret = -EINVAL; + break; + } + + return ret; +} + +int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) +{ + struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; + unsigned int reg_value; + u16 real_value; + int ret; + + real_value = min((value + vol_desc->init_volume), (unsigned int)AW88399_MUTE_VOL); + + ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL2_REG, ®_value); + if (ret) + return ret; + + dev_dbg(aw_dev->dev, "value 0x%x , reg:0x%x", value, real_value); + + real_value = (real_value << AW88399_VOL_START_BIT) | (reg_value & AW88399_VOL_MASK); + + ret = regmap_write(aw_dev->regmap, AW88399_SYSCTRL2_REG, real_value); + + return ret; +} +EXPORT_SYMBOL_GPL(aw_dev_set_volume); + +static void aw_dev_fade_in(struct aw_device *aw_dev) +{ + struct aw_volume_desc *desc = &aw_dev->volume_desc; + u16 fade_in_vol = desc->ctl_volume; + int fade_step = aw_dev->fade_step; + int i; + + if (fade_step == 0 || aw_dev->fade_in_time == 0) { + aw_dev_set_volume(aw_dev, fade_in_vol); + return; + } + + for (i = AW88399_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { + aw_dev_set_volume(aw_dev, i); + usleep_range(aw_dev->fade_in_time, aw_dev->fade_in_time + 10); + } + + if (i != fade_in_vol) + aw_dev_set_volume(aw_dev, fade_in_vol); +} + +static void aw_dev_fade_out(struct aw_device *aw_dev) +{ + struct aw_volume_desc *desc = &aw_dev->volume_desc; + int fade_step = aw_dev->fade_step; + int i; + + if (fade_step == 0 || aw_dev->fade_out_time == 0) { + aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); + return; + } + + for (i = desc->ctl_volume; i <= AW88399_MUTE_VOL; i += fade_step) { + aw_dev_set_volume(aw_dev, i); + usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); + } + + if (i != AW88399_MUTE_VOL) { + aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); + usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); + } +} + +void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) +{ + if (is_mute) { + aw_dev_fade_out(aw_dev); + regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, + ~AW88399_HMUTE_MASK, AW88399_HMUTE_ENABLE_VALUE); + } else { + regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, + ~AW88399_HMUTE_MASK, AW88399_HMUTE_DISABLE_VALUE); + aw_dev_fade_in(aw_dev); + } +} +EXPORT_SYMBOL_GPL(aw88399_dev_mute); + +static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) +{ + struct aw_device *aw_dev = aw88399->aw_pa; + + if (dither) + regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, + ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_ENABLE_VALUE); + else + regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, + ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_DISABLE_VALUE); +} + +static int aw88399_dev_start(struct aw88399 *aw88399) +{ + struct aw_device *aw_dev = aw88399->aw_pa; + int ret; + + if (aw_dev->status == AW88399_DEV_PW_ON) { + dev_dbg(aw_dev->dev, "already power on"); + return 0; + } + + aw88399_dev_set_dither(aw88399, false); + + /* power on */ + aw_dev_pwd(aw_dev, false); + usleep_range(AW88399_2000_US, AW88399_2000_US + 10); + + ret = aw_dev_check_syspll(aw_dev); + if (ret) { + dev_err(aw_dev->dev, "pll check failed cannot start"); + goto pll_check_fail; + } + + /* amppd on */ + aw_dev_amppd(aw_dev, false); + usleep_range(AW88399_1000_US, AW88399_1000_US + 50); + + /* check i2s status */ + ret = aw_dev_check_sysst(aw_dev); + if (ret) { + dev_err(aw_dev->dev, "sysst check failed"); + goto sysst_check_fail; + } + + if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) { + ret = aw_dev_hw_crc_check(aw88399); + if (ret) { + dev_err(aw_dev->dev, "dsp crc check failed"); + goto crc_check_fail; + } + aw_dev_dsp_enable(aw_dev, false); + aw_dev_set_vcalb(aw88399); + aw_dev_update_cali_re(&aw_dev->cali_desc); + + ret = aw_dev_dsp_check(aw_dev); + if (ret) { + dev_err(aw_dev->dev, "dsp status check failed"); + goto dsp_check_fail; + } + } else { + dev_dbg(aw_dev->dev, "start pa with dsp bypass"); + } + + /* enable tx feedback */ + aw_dev_i2s_tx_enable(aw_dev, true); + + if (aw88399->dither_st == AW88399_DITHER_EN_ENABLE_VALUE) + aw88399_dev_set_dither(aw88399, true); + + /* close mute */ + aw88399_dev_mute(aw_dev, false); + /* clear inturrupt */ + aw_dev_clear_int_status(aw_dev); + aw_dev->status = AW88399_DEV_PW_ON; + + return 0; + +dsp_check_fail: +crc_check_fail: + aw_dev_dsp_enable(aw_dev, false); +sysst_check_fail: + aw_dev_clear_int_status(aw_dev); + aw_dev_amppd(aw_dev, true); +pll_check_fail: + aw_dev_pwd(aw_dev, true); + aw_dev->status = AW88399_DEV_PW_OFF; + + return ret; +} + +static int aw_dev_dsp_update_container(struct aw_device *aw_dev, + unsigned char *data, unsigned int len, unsigned short base) +{ + u32 tmp_len; + int i, ret; + + ret = regmap_write(aw_dev->regmap, AW88399_DSPMADD_REG, base); + if (ret) + return ret; + + for (i = 0; i < len; i += AW88399_MAX_RAM_WRITE_BYTE_SIZE) { + tmp_len = min(len - i, AW88399_MAX_RAM_WRITE_BYTE_SIZE); + ret = regmap_raw_write(aw_dev->regmap, AW88399_DSPMDAT_REG, + &data[i], tmp_len); + if (ret) + return ret; + } + + return 0; +} + +static int aw_dev_get_ra(struct aw_cali_desc *cali_desc) +{ + struct aw_device *aw_dev = + container_of(cali_desc, struct aw_device, cali_desc); + u32 dsp_ra; + int ret; + + ret = aw_dev_dsp_read(aw_dev, AW88399_DSP_REG_CFG_ADPZ_RA, + &dsp_ra, AW_DSP_32_DATA); + if (ret) { + dev_err(aw_dev->dev, "read ra error"); + return ret; + } + + cali_desc->ra = AW88399_DSP_RE_TO_SHOW_RE(dsp_ra, + AW88399_DSP_RE_SHIFT); + + return 0; +} + +static int aw_dev_dsp_update_cfg(struct aw_device *aw_dev, + unsigned char *data, unsigned int len) +{ + int ret; + + dev_dbg(aw_dev->dev, "dsp config len:%d", len); + + if (!len || !data) { + dev_err(aw_dev->dev, "dsp config data is null or len is 0"); + return -EINVAL; + } + + ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_CFG_ADDR); + if (ret) + return ret; + + aw_dev->dsp_cfg_len = len; + + ret = aw_dev_get_ra(&aw_dev->cali_desc); + + return ret; +} + +static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, + unsigned char *data, unsigned int len) +{ + int ret; + + dev_dbg(aw_dev->dev, "dsp firmware len:%d", len); + + if (!len || !data) { + dev_err(aw_dev->dev, "dsp firmware data is null or len is 0"); + return -EINVAL; + } + + aw_dev->dsp_fw_len = len; + ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_FW_ADDR); + + return ret; +} + +static int aw_dev_check_sram(struct aw_device *aw_dev) +{ + unsigned int reg_val; + + /* read dsp_rom_check_reg */ + aw_dev_dsp_read(aw_dev, AW88399_DSP_ROM_CHECK_ADDR, ®_val, AW_DSP_16_DATA); + if (reg_val != AW88399_DSP_ROM_CHECK_DATA) { + dev_err(aw_dev->dev, "check dsp rom failed, read[0x%x] != check[0x%x]", + reg_val, AW88399_DSP_ROM_CHECK_DATA); + return -EPERM; + } + + /* check dsp_cfg_base_addr */ + aw_dev_dsp_write(aw_dev, AW88399_DSP_CFG_ADDR, + AW88399_DSP_ODD_NUM_BIT_TEST, AW_DSP_16_DATA); + aw_dev_dsp_read(aw_dev, AW88399_DSP_CFG_ADDR, ®_val, AW_DSP_16_DATA); + if (reg_val != AW88399_DSP_ODD_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", + reg_val, AW88399_DSP_ODD_NUM_BIT_TEST); + return -EPERM; + } + + return 0; +} + +static void aw_dev_select_memclk(struct aw_device *aw_dev, unsigned char flag) +{ + int ret; + + switch (flag) { + case AW88399_DEV_MEMCLK_PLL: + ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, + ~AW88399_MEM_CLKSEL_MASK, + AW88399_MEM_CLKSEL_DAPHCLK_VALUE); + if (ret) + dev_err(aw_dev->dev, "memclk select pll failed"); + break; + case AW88399_DEV_MEMCLK_OSC: + ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, + ~AW88399_MEM_CLKSEL_MASK, + AW88399_MEM_CLKSEL_OSCCLK_VALUE); + if (ret) + dev_err(aw_dev->dev, "memclk select OSC failed"); + break; + default: + dev_err(aw_dev->dev, "unknown memclk config, flag=0x%x", flag); + break; + } +} + +static void aw_dev_get_cur_mode_st(struct aw_device *aw_dev) +{ + struct aw_profctrl_desc *profctrl_desc = &aw_dev->profctrl_desc; + unsigned int reg_val; + int ret; + + ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL_REG, ®_val); + if (ret) { + dev_dbg(aw_dev->dev, "%s failed", __func__); + return; + } + if ((reg_val & (~AW88399_RCV_MODE_MASK)) == AW88399_RCV_MODE_RECEIVER_VALUE) + profctrl_desc->cur_mode = AW88399_RCV_MODE; + else + profctrl_desc->cur_mode = AW88399_NOT_RCV_MODE; +} + +static int aw_dev_update_reg_container(struct aw88399 *aw88399, + unsigned char *data, unsigned int len) +{ + struct aw_device *aw_dev = aw88399->aw_pa; + struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; + u16 read_vol, reg_val; + int data_len, i, ret; + int16_t *reg_data; + u8 reg_addr; + + reg_data = (int16_t *)data; + data_len = len >> 1; + + if (data_len & 0x1) { + dev_err(aw_dev->dev, "data len:%d unsupported", data_len); + return -EINVAL; + } + + for (i = 0; i < data_len; i += 2) { + reg_addr = reg_data[i]; + reg_val = reg_data[i + 1]; + + if (reg_addr == AW88399_DSPVCALB_REG) { + aw88399->vcalb_init_val = reg_val; + continue; + } + + if (reg_addr == AW88399_SYSCTRL_REG) { + if (reg_val & (~AW88399_DSPBY_MASK)) + aw_dev->dsp_cfg = AW88399_DEV_DSP_BYPASS; + else + aw_dev->dsp_cfg = AW88399_DEV_DSP_WORK; + + reg_val &= (AW88399_HMUTE_MASK | AW88399_PWDN_MASK | + AW88399_DSPBY_MASK); + reg_val |= (AW88399_HMUTE_ENABLE_VALUE | AW88399_PWDN_POWER_DOWN_VALUE | + AW88399_DSPBY_BYPASS_VALUE); + } + + if (reg_addr == AW88399_I2SCTRL3_REG) { + reg_val &= AW88399_I2STXEN_MASK; + reg_val |= AW88399_I2STXEN_DISABLE_VALUE; + } + + if (reg_addr == AW88399_SYSCTRL2_REG) { + read_vol = (reg_val & (~AW88399_VOL_MASK)) >> + AW88399_VOL_START_BIT; + aw_dev->volume_desc.init_volume = read_vol; + } + + if (reg_addr == AW88399_DBGCTRL_REG) { + if ((reg_val & (~AW88399_EF_DBMD_MASK)) == AW88399_EF_DBMD_OR_VALUE) + aw88399->check_val = AW_EF_OR_CHECK; + else + aw88399->check_val = AW_EF_AND_CHECK; + + aw88399->dither_st = reg_val & (~AW88399_DITHER_EN_MASK); + } + + if (reg_addr == AW88399_CRCCTRL_REG) + aw88399->crc_init_val = reg_val; + + ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); + if (ret) + return ret; + } + + aw_dev_pwd(aw_dev, false); + usleep_range(AW88399_1000_US, AW88399_1000_US + 10); + + aw_dev_get_cur_mode_st(aw_dev); + + if (aw_dev->prof_cur != aw_dev->prof_index) + vol_desc->ctl_volume = 0; + else + aw_dev_set_volume(aw_dev, vol_desc->ctl_volume); + + return 0; +} + +static int aw_dev_reg_update(struct aw88399 *aw88399, + unsigned char *data, unsigned int len) +{ + int ret; + + if (!len || !data) { + dev_err(aw88399->aw_pa->dev, "reg data is null or len is 0"); + return -EINVAL; + } + + ret = aw_dev_update_reg_container(aw88399, data, len); + if (ret) + dev_err(aw88399->aw_pa->dev, "reg update failed"); + + return ret; +} + +int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) +{ + struct aw_prof_info *prof_info = &aw_dev->prof_info; + struct aw_prof_desc *prof_desc; + + if ((index >= aw_dev->prof_info.count) || (index < 0)) { + dev_err(aw_dev->dev, "index[%d] overflow count[%d]", + index, aw_dev->prof_info.count); + return -EINVAL; + } + + prof_desc = &aw_dev->prof_info.prof_desc[index]; + + *prof_name = prof_info->prof_name_list[prof_desc->id]; + + return 0; +} +EXPORT_SYMBOL_GPL(aw88399_dev_get_prof_name); + +static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, + struct aw_prof_desc **prof_desc) +{ + if ((index >= aw_dev->prof_info.count) || (index < 0)) { + dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", + __func__, index, aw_dev->prof_info.count); + return -EINVAL; + } + + *prof_desc = &aw_dev->prof_info.prof_desc[index]; + + return 0; +} + +static int aw88399_dev_fw_update(struct aw88399 *aw88399, bool up_dsp_fw_en, bool force_up_en) +{ + struct aw_device *aw_dev = aw88399->aw_pa; + struct aw_prof_desc *prof_index_desc; + struct aw_sec_data_desc *sec_desc; + char *prof_name; + int ret; + + if ((aw_dev->prof_cur == aw_dev->prof_index) && + (force_up_en == AW88399_FORCE_UPDATE_OFF)) { + dev_dbg(aw_dev->dev, "scene no change, not update"); + return 0; + } + + if (aw_dev->fw_status == AW88399_DEV_FW_FAILED) { + dev_err(aw_dev->dev, "fw status[%d] error", aw_dev->fw_status); + return -EPERM; + } + + ret = aw88399_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); + if (ret) + return ret; + + dev_dbg(aw_dev->dev, "start update %s", prof_name); + + ret = aw88399_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); + if (ret) + return ret; + + /* update reg */ + sec_desc = prof_index_desc->sec_desc; + ret = aw_dev_reg_update(aw88399, sec_desc[AW88395_DATA_TYPE_REG].data, + sec_desc[AW88395_DATA_TYPE_REG].len); + if (ret) { + dev_err(aw_dev->dev, "update reg failed"); + return ret; + } + + aw88399_dev_mute(aw_dev, true); + + if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) + aw_dev_dsp_enable(aw_dev, false); + + aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); + + ret = aw_dev_check_sram(aw_dev); + if (ret) { + dev_err(aw_dev->dev, "check sram failed"); + goto error; + } + + if (up_dsp_fw_en) { + dev_dbg(aw_dev->dev, "fw_ver: [%x]", prof_index_desc->fw_ver); + ret = aw_dev_dsp_update_fw(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_FW].data, + sec_desc[AW88395_DATA_TYPE_DSP_FW].len); + if (ret) { + dev_err(aw_dev->dev, "update dsp fw failed"); + goto error; + } + } + + /* update dsp config */ + ret = aw_dev_dsp_update_cfg(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_CFG].data, + sec_desc[AW88395_DATA_TYPE_DSP_CFG].len); + if (ret) { + dev_err(aw_dev->dev, "update dsp cfg failed"); + goto error; + } + + aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); + + aw_dev->prof_cur = aw_dev->prof_index; + + return 0; + +error: + aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); + return ret; +} + +static void aw88399_start_pa(struct aw88399 *aw88399) +{ + int ret, i; + + for (i = 0; i < AW88399_START_RETRIES; i++) { + ret = aw88399_dev_start(aw88399); + if (ret) { + dev_err(aw88399->aw_pa->dev, "aw88399 device start failed. retry = %d", i); + ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_ON, true); + if (ret) { + dev_err(aw88399->aw_pa->dev, "fw update failed"); + continue; + } + } else { + dev_dbg(aw88399->aw_pa->dev, "start success\n"); + break; + } + } +} + +void aw88399_startup_work(struct work_struct *work) +{ + struct aw88399 *aw88399 = + container_of(work, struct aw88399, start_work.work); + + mutex_lock(&aw88399->lock); + aw88399_start_pa(aw88399); + mutex_unlock(&aw88399->lock); +} +EXPORT_SYMBOL_GPL(aw88399_startup_work); + +void aw88399_start(struct aw88399 *aw88399, bool sync_start) +{ + int ret; + + if (aw88399->aw_pa->fw_status != AW88399_DEV_FW_OK) + return; + + if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) + return; + + ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_OFF, true); + if (ret) { + dev_err(aw88399->aw_pa->dev, "fw update failed."); + return; + } + + if (sync_start == AW88399_SYNC_START) + aw88399_start_pa(aw88399); + else + queue_delayed_work(system_dfl_wq, + &aw88399->start_work, + AW88399_START_WORK_DELAY_MS); +} +EXPORT_SYMBOL_GPL(aw88399_start); + +static int aw_dev_check_sysint(struct aw_device *aw_dev) +{ + u16 reg_val; + + aw_dev_get_int_status(aw_dev, ®_val); + if (reg_val & AW88399_BIT_SYSINT_CHECK) { + dev_err(aw_dev->dev, "pa stop check fail:0x%04x", reg_val); + return -EINVAL; + } + + return 0; +} + +int aw88399_stop(struct aw_device *aw_dev) +{ + struct aw_sec_data_desc *dsp_cfg = + &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_CFG]; + struct aw_sec_data_desc *dsp_fw = + &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_FW]; + int int_st; + + if (aw_dev->status == AW88399_DEV_PW_OFF) { + dev_dbg(aw_dev->dev, "already power off"); + return 0; + } + + aw_dev->status = AW88399_DEV_PW_OFF; + + aw88399_dev_mute(aw_dev, true); + usleep_range(AW88399_4000_US, AW88399_4000_US + 100); + + aw_dev_i2s_tx_enable(aw_dev, false); + usleep_range(AW88399_1000_US, AW88399_1000_US + 100); + + int_st = aw_dev_check_sysint(aw_dev); + + aw_dev_dsp_enable(aw_dev, false); + + aw_dev_amppd(aw_dev, true); + + if (int_st) { + aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); + aw_dev_dsp_update_fw(aw_dev, dsp_fw->data, dsp_fw->len); + aw_dev_dsp_update_cfg(aw_dev, dsp_cfg->data, dsp_cfg->len); + aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); + } + + aw_dev_pwd(aw_dev, true); + + return 0; +} +EXPORT_SYMBOL_GPL(aw88399_stop); + +static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) +{ + struct aw_device *aw_dev = aw88399->aw_pa; + int ret; + + ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); + if (ret) { + dev_err(aw_dev->dev, "aw_dev acf parse failed"); + return -EINVAL; + } + aw_dev->fade_in_time = AW88399_1000_US / 10; + aw_dev->fade_out_time = AW88399_1000_US >> 1; + aw_dev->prof_cur = aw_dev->prof_info.prof_desc[0].id; + aw_dev->prof_index = aw_dev->prof_info.prof_desc[0].id; + + ret = aw88399_dev_fw_update(aw88399, AW88399_FORCE_UPDATE_ON, AW88399_DSP_FW_UPDATE_ON); + if (ret) { + dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); + return ret; + } + + aw88399_dev_mute(aw_dev, true); + + /* close tx feedback */ + aw_dev_i2s_tx_enable(aw_dev, false); + usleep_range(AW88399_1000_US, AW88399_1000_US + 100); + + /* enable amppd */ + aw_dev_amppd(aw_dev, true); + + /* close dsp */ + aw_dev_dsp_enable(aw_dev, false); + /* set power down */ + aw_dev_pwd(aw_dev, true); + + return 0; +} + +int aw88399_request_firmware_file(struct aw88399 *aw88399) +{ + const struct firmware *cont = NULL; + int ret; + + aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; + + ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); + if (ret) { + dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); + return ret; + } + + dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", + AW88399_ACF_FILE, cont ? cont->size : 0); + + aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, + struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); + if (!aw88399->aw_cfg) { + release_firmware(cont); + return -ENOMEM; + } + aw88399->aw_cfg->len = (int)cont->size; + memcpy(aw88399->aw_cfg->data, cont->data, cont->size); + release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); + if (ret) { + dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); + return ret; + } + + mutex_lock(&aw88399->lock); + /* aw device init */ + ret = aw88399_dev_init(aw88399, aw88399->aw_cfg); + if (ret) + dev_err(aw88399->aw_pa->dev, "dev init failed"); + mutex_unlock(&aw88399->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(aw88399_request_firmware_file); + +void aw88399_hw_reset(struct aw88399 *aw88399) +{ + if (aw88399->reset_gpio) { + gpiod_set_value_cansleep(aw88399->reset_gpio, 1); + usleep_range(AW88399_1000_US, AW88399_1000_US + 10); + gpiod_set_value_cansleep(aw88399->reset_gpio, 0); + usleep_range(AW88399_1000_US, AW88399_1000_US + 10); + gpiod_set_value_cansleep(aw88399->reset_gpio, 1); + usleep_range(AW88399_1000_US, AW88399_1000_US + 10); + } +} +EXPORT_SYMBOL_GPL(aw88399_hw_reset); + +static void aw88399_parse_channel_dt(struct aw_device *aw_dev) +{ + struct device_node *np = aw_dev->dev->of_node; + u32 channel_value; + + of_property_read_u32(np, "awinic,audio-channel", &channel_value); + aw_dev->channel = channel_value; +} + +int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap) +{ + struct aw_device *aw_dev; + unsigned int chip_id; + int ret; + + ret = regmap_read(regmap, AW88399_ID_REG, &chip_id); + if (ret) { + dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); + return ret; + } + if (chip_id != AW88399_CHIP_ID) { + dev_err(&i2c->dev, "unsupported device"); + return -ENXIO; + } + dev_dbg(&i2c->dev, "chip id = %x\n", chip_id); + + aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); + if (!aw_dev) + return -ENOMEM; + aw88399->aw_pa = aw_dev; + + aw_dev->i2c = i2c; + aw_dev->dev = &i2c->dev; + aw_dev->regmap = regmap; + mutex_init(&aw_dev->dsp_lock); + + aw_dev->chip_id = chip_id; + aw_dev->acf = NULL; + aw_dev->prof_info.prof_desc = NULL; + aw_dev->prof_info.count = 0; + aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; + aw_dev->channel = AW88399_DEV_DEFAULT_CH; + aw_dev->fw_status = AW88399_DEV_FW_FAILED; + + aw_dev->fade_step = AW88399_VOLUME_STEP_DB; + aw_dev->volume_desc.ctl_volume = AW88399_VOL_DEFAULT_VALUE; + + aw88399_parse_channel_dt(aw_dev); + + return 0; +} +EXPORT_SYMBOL_GPL(aw88399_init); + +MODULE_DESCRIPTION("AW88399 common device library"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c index b2ec3503f7e2..13495e71ad0e 100644 --- a/sound/soc/codecs/aw88399.c +++ b/sound/soc/codecs/aw88399.c @@ -10,1217 +10,12 @@ #include #include #include -#include -#include #include #include #include #include "aw88399.h" #include "aw88395/aw88395_device.h" -static const struct regmap_config aw88399_remap_config = { - .val_bits = 16, - .reg_bits = 8, - .max_register = AW88399_REG_MAX, - .reg_format_endian = REGMAP_ENDIAN_LITTLE, - .val_format_endian = REGMAP_ENDIAN_BIG, -}; - -static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) -{ - int ret; - - if (pwd) - ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, - ~AW88399_PWDN_MASK, AW88399_PWDN_POWER_DOWN_VALUE); - else - ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, - ~AW88399_PWDN_MASK, AW88399_PWDN_WORKING_VALUE); - - if (ret) - dev_dbg(aw_dev->dev, "%s failed", __func__); -} - -static void aw_dev_get_int_status(struct aw_device *aw_dev, unsigned short *int_status) -{ - unsigned int reg_val; - int ret; - - ret = regmap_read(aw_dev->regmap, AW88399_SYSINT_REG, ®_val); - if (ret) - dev_err(aw_dev->dev, "read interrupt reg fail, ret=%d", ret); - else - *int_status = reg_val; - - dev_dbg(aw_dev->dev, "read interrupt reg=0x%04x", *int_status); -} - -static void aw_dev_clear_int_status(struct aw_device *aw_dev) -{ - u16 int_status; - - /* read int status and clear */ - aw_dev_get_int_status(aw_dev, &int_status); - /* make sure int status is clear */ - aw_dev_get_int_status(aw_dev, &int_status); - if (int_status) - dev_dbg(aw_dev->dev, "int status(%d) is not cleaned.\n", int_status); -} - -static int aw_dev_get_iis_status(struct aw_device *aw_dev) -{ - unsigned int reg_val; - int ret; - - ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); - if (ret) - return ret; - if ((reg_val & AW88399_BIT_PLL_CHECK) != AW88399_BIT_PLL_CHECK) { - dev_err(aw_dev->dev, "check pll lock fail, reg_val:0x%04x", reg_val); - return -EINVAL; - } - - return 0; -} - -static int aw_dev_check_mode1_pll(struct aw_device *aw_dev) -{ - int ret, i; - - for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { - ret = aw_dev_get_iis_status(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "mode1 iis signal check error"); - usleep_range(AW88399_2000_US, AW88399_2000_US + 10); - } else { - return 0; - } - } - - return -EPERM; -} - -static int aw_dev_check_mode2_pll(struct aw_device *aw_dev) -{ - unsigned int reg_val; - int ret, i; - - ret = regmap_read(aw_dev->regmap, AW88399_PLLCTRL2_REG, ®_val); - if (ret) - return ret; - - reg_val &= (~AW88399_CCO_MUX_MASK); - if (reg_val == AW88399_CCO_MUX_DIVIDED_VALUE) { - dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); - return -EPERM; - } - - /* change mode2 */ - ret = regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, - ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_DIVIDED_VALUE); - if (ret) - return ret; - - for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { - ret = aw_dev_get_iis_status(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "mode2 iis signal check error"); - usleep_range(AW88399_2000_US, AW88399_2000_US + 10); - } else { - break; - } - } - - /* change mode1 */ - regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, - ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_BYPASS_VALUE); - if (ret == 0) { - usleep_range(AW88399_2000_US, AW88399_2000_US + 10); - for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { - ret = aw_dev_get_iis_status(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); - usleep_range(AW88399_2000_US, AW88399_2000_US + 10); - } else { - break; - } - } - } - - return ret; -} - -static int aw_dev_check_syspll(struct aw_device *aw_dev) -{ - int ret; - - ret = aw_dev_check_mode1_pll(aw_dev); - if (ret) { - dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); - ret = aw_dev_check_mode2_pll(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "mode2 check iis failed"); - return ret; - } - } - - return 0; -} - -static int aw_dev_check_sysst(struct aw_device *aw_dev) -{ - unsigned int check_val; - unsigned int reg_val; - int ret, i; - - ret = regmap_read(aw_dev->regmap, AW88399_PWMCTRL3_REG, ®_val); - if (ret) - return ret; - - if (reg_val & (~AW88399_NOISE_GATE_EN_MASK)) - check_val = AW88399_BIT_SYSST_NOSWS_CHECK; - else - check_val = AW88399_BIT_SYSST_SWS_CHECK; - - for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { - ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); - if (ret) - return ret; - - if ((reg_val & (~AW88399_BIT_SYSST_CHECK_MASK) & check_val) != check_val) { - dev_err(aw_dev->dev, "check sysst fail, cnt=%d, reg_val=0x%04x, check:0x%x", - i, reg_val, AW88399_BIT_SYSST_NOSWS_CHECK); - usleep_range(AW88399_2000_US, AW88399_2000_US + 10); - } else { - return 0; - } - } - - return -EPERM; -} - -static void aw_dev_amppd(struct aw_device *aw_dev, bool amppd) -{ - int ret; - - if (amppd) - ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, - ~AW88399_AMPPD_MASK, AW88399_AMPPD_POWER_DOWN_VALUE); - else - ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, - ~AW88399_AMPPD_MASK, AW88399_AMPPD_WORKING_VALUE); - - if (ret) - dev_dbg(aw_dev->dev, "%s failed", __func__); -} - -static void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) -{ - int ret; - - if (is_enable) - ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, - ~AW88399_DSPBY_MASK, AW88399_DSPBY_WORKING_VALUE); - else - ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, - ~AW88399_DSPBY_MASK, AW88399_DSPBY_BYPASS_VALUE); - - if (ret) - dev_dbg(aw_dev->dev, "%s failed\n", __func__); -} - -static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) -{ - uint16_t icalkh_val, icalkl_val, icalk_val; - struct aw_device *aw_dev = aw88399->aw_pa; - unsigned int reg_val; - int ret; - - ret = regmap_read(aw_dev->regmap, AW88399_EFRH4_REG, ®_val); - if (ret) - return ret; - icalkh_val = reg_val & (~AW88399_EF_ISN_GESLP_H_MASK); - - ret = regmap_read(aw_dev->regmap, AW88399_EFRL4_REG, ®_val); - if (ret) - return ret; - icalkl_val = reg_val & (~AW88399_EF_ISN_GESLP_L_MASK); - - if (aw88399->check_val == AW_EF_AND_CHECK) - icalk_val = icalkh_val & icalkl_val; - else - icalk_val = icalkh_val | icalkl_val; - - if (icalk_val & (~AW88399_EF_ISN_GESLP_SIGN_MASK)) - icalk_val = icalk_val | AW88399_EF_ISN_GESLP_SIGN_NEG; - *icalk = (int16_t)icalk_val; - - return 0; -} - -static int aw88399_dev_get_vcalk(struct aw88399 *aw88399, int16_t *vcalk) -{ - uint16_t vcalkh_val, vcalkl_val, vcalk_val; - struct aw_device *aw_dev = aw88399->aw_pa; - unsigned int reg_val; - int ret; - - ret = regmap_read(aw_dev->regmap, AW88399_EFRH3_REG, ®_val); - if (ret) - return ret; - - vcalkh_val = reg_val & (~AW88399_EF_VSN_GESLP_H_MASK); - - ret = regmap_read(aw_dev->regmap, AW88399_EFRL3_REG, ®_val); - if (ret) - return ret; - - vcalkl_val = reg_val & (~AW88399_EF_VSN_GESLP_L_MASK); - - if (aw88399->check_val == AW_EF_AND_CHECK) - vcalk_val = vcalkh_val & vcalkl_val; - else - vcalk_val = vcalkh_val | vcalkl_val; - - if (vcalk_val & AW88399_EF_VSN_GESLP_SIGN_MASK) - vcalk_val = vcalk_val | AW88399_EF_VSN_GESLP_SIGN_NEG; - *vcalk = (int16_t)vcalk_val; - - return 0; -} - -static int aw88399_dev_get_internal_vcalk(struct aw88399 *aw88399, int16_t *vcalk) -{ - uint16_t vcalkh_val, vcalkl_val, vcalk_val; - struct aw_device *aw_dev = aw88399->aw_pa; - unsigned int reg_val; - int ret; - - ret = regmap_read(aw_dev->regmap, AW88399_EFRH2_REG, ®_val); - if (ret) - return ret; - vcalkh_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_H_MASK); - - ret = regmap_read(aw_dev->regmap, AW88399_EFRL2_REG, ®_val); - if (ret) - return ret; - vcalkl_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_L_MASK); - - if (aw88399->check_val == AW_EF_AND_CHECK) - vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) & - (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); - else - vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) | - (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); - - if (vcalk_val & (~AW88399_TEM4_SIGN_MASK)) - vcalk_val = vcalk_val | AW88399_TEM4_SIGN_NEG; - - *vcalk = (int16_t)vcalk_val; - - return 0; -} - -static int aw_dev_set_vcalb(struct aw88399 *aw88399) -{ - struct aw_device *aw_dev = aw88399->aw_pa; - unsigned int vsense_select, vsense_value; - int32_t ical_k, vcal_k, vcalb; - int16_t icalk, vcalk; - uint16_t reg_val; - int ret; - - ret = regmap_read(aw_dev->regmap, AW88399_VSNCTRL1_REG, &vsense_value); - if (ret) - return ret; - - vsense_select = vsense_value & (~AW88399_VDSEL_MASK); - - ret = aw88399_dev_get_icalk(aw88399, &icalk); - if (ret) { - dev_err(aw_dev->dev, "get icalk failed\n"); - return ret; - } - - ical_k = icalk * AW88399_ICABLK_FACTOR + AW88399_CABL_BASE_VALUE; - - switch (vsense_select) { - case AW88399_DEV_VDSEL_VSENSE: - ret = aw88399_dev_get_vcalk(aw88399, &vcalk); - vcal_k = vcalk * AW88399_VCABLK_FACTOR + AW88399_CABL_BASE_VALUE; - vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_FACTOR / AW88399_ISCAL_FACTOR * - ical_k / vcal_k * aw88399->vcalb_init_val; - break; - case AW88399_DEV_VDSEL_DAC: - ret = aw88399_dev_get_internal_vcalk(aw88399, &vcalk); - vcal_k = vcalk * AW88399_VCABLK_DAC_FACTOR + AW88399_CABL_BASE_VALUE; - vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_DAC_FACTOR / - AW88399_ISCAL_DAC_FACTOR * ical_k / - vcal_k * aw88399->vcalb_init_val; - break; - default: - dev_err(aw_dev->dev, "%s: unsupported vsense\n", __func__); - ret = -EINVAL; - break; - } - if (ret) - return ret; - - vcalb = vcalb >> AW88399_VCALB_ADJ_FACTOR; - reg_val = (uint32_t)vcalb; - - regmap_write(aw_dev->regmap, AW88399_DSPVCALB_REG, reg_val); - - return 0; -} - -static int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) -{ - struct aw_device *aw_dev = - container_of(cali_desc, struct aw_device, cali_desc); - uint16_t re_lbits, re_hbits; - u32 cali_re; - int ret; - - if ((aw_dev->cali_desc.cali_re >= AW88399_CALI_RE_MAX) || - (aw_dev->cali_desc.cali_re <= AW88399_CALI_RE_MIN)) - return -EINVAL; - - cali_re = AW88399_SHOW_RE_TO_DSP_RE((aw_dev->cali_desc.cali_re + - aw_dev->cali_desc.ra), AW88399_DSP_RE_SHIFT); - - re_hbits = (cali_re & (~AW88399_CALI_RE_HBITS_MASK)) >> AW88399_CALI_RE_HBITS_SHIFT; - re_lbits = (cali_re & (~AW88399_CALI_RE_LBITS_MASK)) >> AW88399_CALI_RE_LBITS_SHIFT; - - ret = regmap_write(aw_dev->regmap, AW88399_ACR1_REG, re_hbits); - if (ret) { - dev_err(aw_dev->dev, "set cali re error"); - return ret; - } - - ret = regmap_write(aw_dev->regmap, AW88399_ACR2_REG, re_lbits); - if (ret) - dev_err(aw_dev->dev, "set cali re error"); - - return ret; -} - -static int aw_dev_fw_crc_check(struct aw_device *aw_dev) -{ - uint16_t check_val, fw_len_val; - unsigned int reg_val; - int ret; - - /* calculate fw_end_addr */ - fw_len_val = ((aw_dev->dsp_fw_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_FW_BASE_ADDR; - - /* write fw_end_addr to crc_end_addr */ - ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, - ~AW88399_CRC_END_ADDR_MASK, fw_len_val); - if (ret) - return ret; - /* enable fw crc check */ - ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, - ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_ENABLE_VALUE); - - usleep_range(AW88399_2000_US, AW88399_2000_US + 10); - - /* read crc check result */ - regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); - if (ret) - return ret; - - check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; - - /* disable fw crc check */ - ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, - ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_DISABLE_VALUE); - if (ret) - return ret; - - if (check_val != AW88399_CRC_CHECK_PASS_VAL) { - dev_err(aw_dev->dev, "%s failed, check_val 0x%x != 0x%x", - __func__, check_val, AW88399_CRC_CHECK_PASS_VAL); - ret = -EINVAL; - } - - return ret; -} - -static int aw_dev_cfg_crc_check(struct aw_device *aw_dev) -{ - uint16_t check_val, cfg_len_val; - unsigned int reg_val; - int ret; - - /* calculate cfg end addr */ - cfg_len_val = ((aw_dev->dsp_cfg_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_CFG_BASE_ADDR; - - /* write cfg_end_addr to crc_end_addr */ - ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, - ~AW88399_CRC_END_ADDR_MASK, cfg_len_val); - if (ret) - return ret; - - /* enable cfg crc check */ - ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, - ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_ENABLE_VALUE); - if (ret) - return ret; - - usleep_range(AW88399_1000_US, AW88399_1000_US + 10); - - /* read crc check result */ - ret = regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); - if (ret) - return ret; - - check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; - - /* disable cfg crc check */ - ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, - ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_DISABLE_VALUE); - if (ret) - return ret; - - if (check_val != AW88399_CRC_CHECK_PASS_VAL) { - dev_err(aw_dev->dev, "crc_check failed, check val 0x%x != 0x%x", - check_val, AW88399_CRC_CHECK_PASS_VAL); - ret = -EINVAL; - } - - return ret; -} - -static int aw_dev_hw_crc_check(struct aw88399 *aw88399) -{ - struct aw_device *aw_dev = aw88399->aw_pa; - int ret; - - ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, - ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_BYPASS_VALUE); - if (ret) - return ret; - - ret = aw_dev_fw_crc_check(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "fw_crc_check failed\n"); - goto crc_check_failed; - } - - ret = aw_dev_cfg_crc_check(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "cfg_crc_check failed\n"); - goto crc_check_failed; - } - - ret = regmap_write(aw_dev->regmap, AW88399_CRCCTRL_REG, aw88399->crc_init_val); - if (ret) - return ret; - - ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, - ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); - - return ret; - -crc_check_failed: - regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, - ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); - return ret; -} - -static void aw_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) -{ - int ret; - - if (flag) - ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCTRL3_REG, - ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_ENABLE_VALUE); - else - ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, - ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_DISABLE_VALUE); - - if (ret) - dev_dbg(aw_dev->dev, "%s failed", __func__); -} - -static int aw_dev_get_dsp_status(struct aw_device *aw_dev) -{ - unsigned int reg_val; - int ret; - - ret = regmap_read(aw_dev->regmap, AW88399_WDT_REG, ®_val); - if (ret) - return ret; - if (!(reg_val & (~AW88399_WDT_CNT_MASK))) - return -EPERM; - - return 0; -} - -static int aw_dev_dsp_check(struct aw_device *aw_dev) -{ - int ret, i; - - switch (aw_dev->dsp_cfg) { - case AW88399_DEV_DSP_BYPASS: - dev_dbg(aw_dev->dev, "dsp bypass"); - ret = 0; - break; - case AW88399_DEV_DSP_WORK: - aw_dev_dsp_enable(aw_dev, false); - aw_dev_dsp_enable(aw_dev, true); - usleep_range(AW88399_1000_US, AW88399_1000_US + 10); - for (i = 0; i < AW88399_DEV_DSP_CHECK_MAX; i++) { - ret = aw_dev_get_dsp_status(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "dsp wdt status error=%d", ret); - usleep_range(AW88399_2000_US, AW88399_2000_US + 10); - } - } - break; - default: - dev_err(aw_dev->dev, "unknown dsp cfg=%d", aw_dev->dsp_cfg); - ret = -EINVAL; - break; - } - - return ret; -} - -static int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) -{ - struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; - unsigned int reg_value; - u16 real_value; - int ret; - - real_value = min((value + vol_desc->init_volume), (unsigned int)AW88399_MUTE_VOL); - - ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL2_REG, ®_value); - if (ret) - return ret; - - dev_dbg(aw_dev->dev, "value 0x%x , reg:0x%x", value, real_value); - - real_value = (real_value << AW88399_VOL_START_BIT) | (reg_value & AW88399_VOL_MASK); - - ret = regmap_write(aw_dev->regmap, AW88399_SYSCTRL2_REG, real_value); - - return ret; -} - -static void aw_dev_fade_in(struct aw_device *aw_dev) -{ - struct aw_volume_desc *desc = &aw_dev->volume_desc; - u16 fade_in_vol = desc->ctl_volume; - int fade_step = aw_dev->fade_step; - int i; - - if (fade_step == 0 || aw_dev->fade_in_time == 0) { - aw_dev_set_volume(aw_dev, fade_in_vol); - return; - } - - for (i = AW88399_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { - aw_dev_set_volume(aw_dev, i); - usleep_range(aw_dev->fade_in_time, aw_dev->fade_in_time + 10); - } - - if (i != fade_in_vol) - aw_dev_set_volume(aw_dev, fade_in_vol); -} - -static void aw_dev_fade_out(struct aw_device *aw_dev) -{ - struct aw_volume_desc *desc = &aw_dev->volume_desc; - int fade_step = aw_dev->fade_step; - int i; - - if (fade_step == 0 || aw_dev->fade_out_time == 0) { - aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); - return; - } - - for (i = desc->ctl_volume; i <= AW88399_MUTE_VOL; i += fade_step) { - aw_dev_set_volume(aw_dev, i); - usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); - } - - if (i != AW88399_MUTE_VOL) { - aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); - usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); - } -} - -static void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) -{ - if (is_mute) { - aw_dev_fade_out(aw_dev); - regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, - ~AW88399_HMUTE_MASK, AW88399_HMUTE_ENABLE_VALUE); - } else { - regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, - ~AW88399_HMUTE_MASK, AW88399_HMUTE_DISABLE_VALUE); - aw_dev_fade_in(aw_dev); - } -} - -static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) -{ - struct aw_device *aw_dev = aw88399->aw_pa; - - if (dither) - regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, - ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_ENABLE_VALUE); - else - regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, - ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_DISABLE_VALUE); -} - -static int aw88399_dev_start(struct aw88399 *aw88399) -{ - struct aw_device *aw_dev = aw88399->aw_pa; - int ret; - - if (aw_dev->status == AW88399_DEV_PW_ON) { - dev_dbg(aw_dev->dev, "already power on"); - return 0; - } - - aw88399_dev_set_dither(aw88399, false); - - /* power on */ - aw_dev_pwd(aw_dev, false); - usleep_range(AW88399_2000_US, AW88399_2000_US + 10); - - ret = aw_dev_check_syspll(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "pll check failed cannot start"); - goto pll_check_fail; - } - - /* amppd on */ - aw_dev_amppd(aw_dev, false); - usleep_range(AW88399_1000_US, AW88399_1000_US + 50); - - /* check i2s status */ - ret = aw_dev_check_sysst(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "sysst check failed"); - goto sysst_check_fail; - } - - if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) { - ret = aw_dev_hw_crc_check(aw88399); - if (ret) { - dev_err(aw_dev->dev, "dsp crc check failed"); - goto crc_check_fail; - } - aw_dev_dsp_enable(aw_dev, false); - aw_dev_set_vcalb(aw88399); - aw_dev_update_cali_re(&aw_dev->cali_desc); - - ret = aw_dev_dsp_check(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "dsp status check failed"); - goto dsp_check_fail; - } - } else { - dev_dbg(aw_dev->dev, "start pa with dsp bypass"); - } - - /* enable tx feedback */ - aw_dev_i2s_tx_enable(aw_dev, true); - - if (aw88399->dither_st == AW88399_DITHER_EN_ENABLE_VALUE) - aw88399_dev_set_dither(aw88399, true); - - /* close mute */ - aw88399_dev_mute(aw_dev, false); - /* clear inturrupt */ - aw_dev_clear_int_status(aw_dev); - aw_dev->status = AW88399_DEV_PW_ON; - - return 0; - -dsp_check_fail: -crc_check_fail: - aw_dev_dsp_enable(aw_dev, false); -sysst_check_fail: - aw_dev_clear_int_status(aw_dev); - aw_dev_amppd(aw_dev, true); -pll_check_fail: - aw_dev_pwd(aw_dev, true); - aw_dev->status = AW88399_DEV_PW_OFF; - - return ret; -} - -static int aw_dev_dsp_update_container(struct aw_device *aw_dev, - unsigned char *data, unsigned int len, unsigned short base) -{ - u32 tmp_len; - int i, ret; - - ret = regmap_write(aw_dev->regmap, AW88399_DSPMADD_REG, base); - if (ret) - return ret; - - for (i = 0; i < len; i += AW88399_MAX_RAM_WRITE_BYTE_SIZE) { - tmp_len = min(len - i, AW88399_MAX_RAM_WRITE_BYTE_SIZE); - ret = regmap_raw_write(aw_dev->regmap, AW88399_DSPMDAT_REG, - &data[i], tmp_len); - if (ret) - return ret; - } - - return 0; -} - -static int aw_dev_get_ra(struct aw_cali_desc *cali_desc) -{ - struct aw_device *aw_dev = - container_of(cali_desc, struct aw_device, cali_desc); - u32 dsp_ra; - int ret; - - ret = aw_dev_dsp_read(aw_dev, AW88399_DSP_REG_CFG_ADPZ_RA, - &dsp_ra, AW_DSP_32_DATA); - if (ret) { - dev_err(aw_dev->dev, "read ra error"); - return ret; - } - - cali_desc->ra = AW88399_DSP_RE_TO_SHOW_RE(dsp_ra, - AW88399_DSP_RE_SHIFT); - - return 0; -} - -static int aw_dev_dsp_update_cfg(struct aw_device *aw_dev, - unsigned char *data, unsigned int len) -{ - int ret; - - dev_dbg(aw_dev->dev, "dsp config len:%d", len); - - if (!len || !data) { - dev_err(aw_dev->dev, "dsp config data is null or len is 0"); - return -EINVAL; - } - - ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_CFG_ADDR); - if (ret) - return ret; - - aw_dev->dsp_cfg_len = len; - - ret = aw_dev_get_ra(&aw_dev->cali_desc); - - return ret; -} - -static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, - unsigned char *data, unsigned int len) -{ - int ret; - - dev_dbg(aw_dev->dev, "dsp firmware len:%d", len); - - if (!len || !data) { - dev_err(aw_dev->dev, "dsp firmware data is null or len is 0"); - return -EINVAL; - } - - aw_dev->dsp_fw_len = len; - ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_FW_ADDR); - - return ret; -} - -static int aw_dev_check_sram(struct aw_device *aw_dev) -{ - unsigned int reg_val; - - /* read dsp_rom_check_reg */ - aw_dev_dsp_read(aw_dev, AW88399_DSP_ROM_CHECK_ADDR, ®_val, AW_DSP_16_DATA); - if (reg_val != AW88399_DSP_ROM_CHECK_DATA) { - dev_err(aw_dev->dev, "check dsp rom failed, read[0x%x] != check[0x%x]", - reg_val, AW88399_DSP_ROM_CHECK_DATA); - return -EPERM; - } - - /* check dsp_cfg_base_addr */ - aw_dev_dsp_write(aw_dev, AW88399_DSP_CFG_ADDR, - AW88399_DSP_ODD_NUM_BIT_TEST, AW_DSP_16_DATA); - aw_dev_dsp_read(aw_dev, AW88399_DSP_CFG_ADDR, ®_val, AW_DSP_16_DATA); - if (reg_val != AW88399_DSP_ODD_NUM_BIT_TEST) { - dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", - reg_val, AW88399_DSP_ODD_NUM_BIT_TEST); - return -EPERM; - } - - return 0; -} - -static void aw_dev_select_memclk(struct aw_device *aw_dev, unsigned char flag) -{ - int ret; - - switch (flag) { - case AW88399_DEV_MEMCLK_PLL: - ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, - ~AW88399_MEM_CLKSEL_MASK, - AW88399_MEM_CLKSEL_DAPHCLK_VALUE); - if (ret) - dev_err(aw_dev->dev, "memclk select pll failed"); - break; - case AW88399_DEV_MEMCLK_OSC: - ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, - ~AW88399_MEM_CLKSEL_MASK, - AW88399_MEM_CLKSEL_OSCCLK_VALUE); - if (ret) - dev_err(aw_dev->dev, "memclk select OSC failed"); - break; - default: - dev_err(aw_dev->dev, "unknown memclk config, flag=0x%x", flag); - break; - } -} - -static void aw_dev_get_cur_mode_st(struct aw_device *aw_dev) -{ - struct aw_profctrl_desc *profctrl_desc = &aw_dev->profctrl_desc; - unsigned int reg_val; - int ret; - - ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL_REG, ®_val); - if (ret) { - dev_dbg(aw_dev->dev, "%s failed", __func__); - return; - } - if ((reg_val & (~AW88399_RCV_MODE_MASK)) == AW88399_RCV_MODE_RECEIVER_VALUE) - profctrl_desc->cur_mode = AW88399_RCV_MODE; - else - profctrl_desc->cur_mode = AW88399_NOT_RCV_MODE; -} - -static int aw_dev_update_reg_container(struct aw88399 *aw88399, - unsigned char *data, unsigned int len) -{ - struct aw_device *aw_dev = aw88399->aw_pa; - struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; - u16 read_vol, reg_val; - int data_len, i, ret; - int16_t *reg_data; - u8 reg_addr; - - reg_data = (int16_t *)data; - data_len = len >> 1; - - if (data_len & 0x1) { - dev_err(aw_dev->dev, "data len:%d unsupported", data_len); - return -EINVAL; - } - - for (i = 0; i < data_len; i += 2) { - reg_addr = reg_data[i]; - reg_val = reg_data[i + 1]; - - if (reg_addr == AW88399_DSPVCALB_REG) { - aw88399->vcalb_init_val = reg_val; - continue; - } - - if (reg_addr == AW88399_SYSCTRL_REG) { - if (reg_val & (~AW88399_DSPBY_MASK)) - aw_dev->dsp_cfg = AW88399_DEV_DSP_BYPASS; - else - aw_dev->dsp_cfg = AW88399_DEV_DSP_WORK; - - reg_val &= (AW88399_HMUTE_MASK | AW88399_PWDN_MASK | - AW88399_DSPBY_MASK); - reg_val |= (AW88399_HMUTE_ENABLE_VALUE | AW88399_PWDN_POWER_DOWN_VALUE | - AW88399_DSPBY_BYPASS_VALUE); - } - - if (reg_addr == AW88399_I2SCTRL3_REG) { - reg_val &= AW88399_I2STXEN_MASK; - reg_val |= AW88399_I2STXEN_DISABLE_VALUE; - } - - if (reg_addr == AW88399_SYSCTRL2_REG) { - read_vol = (reg_val & (~AW88399_VOL_MASK)) >> - AW88399_VOL_START_BIT; - aw_dev->volume_desc.init_volume = read_vol; - } - - if (reg_addr == AW88399_DBGCTRL_REG) { - if ((reg_val & (~AW88399_EF_DBMD_MASK)) == AW88399_EF_DBMD_OR_VALUE) - aw88399->check_val = AW_EF_OR_CHECK; - else - aw88399->check_val = AW_EF_AND_CHECK; - - aw88399->dither_st = reg_val & (~AW88399_DITHER_EN_MASK); - } - - if (reg_addr == AW88399_CRCCTRL_REG) - aw88399->crc_init_val = reg_val; - - ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); - if (ret) - return ret; - } - - aw_dev_pwd(aw_dev, false); - usleep_range(AW88399_1000_US, AW88399_1000_US + 10); - - aw_dev_get_cur_mode_st(aw_dev); - - if (aw_dev->prof_cur != aw_dev->prof_index) - vol_desc->ctl_volume = 0; - else - aw_dev_set_volume(aw_dev, vol_desc->ctl_volume); - - return 0; -} - -static int aw_dev_reg_update(struct aw88399 *aw88399, - unsigned char *data, unsigned int len) -{ - int ret; - - if (!len || !data) { - dev_err(aw88399->aw_pa->dev, "reg data is null or len is 0"); - return -EINVAL; - } - - ret = aw_dev_update_reg_container(aw88399, data, len); - if (ret) - dev_err(aw88399->aw_pa->dev, "reg update failed"); - - return ret; -} - -static int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) -{ - struct aw_prof_info *prof_info = &aw_dev->prof_info; - struct aw_prof_desc *prof_desc; - - if ((index >= aw_dev->prof_info.count) || (index < 0)) { - dev_err(aw_dev->dev, "index[%d] overflow count[%d]", - index, aw_dev->prof_info.count); - return -EINVAL; - } - - prof_desc = &aw_dev->prof_info.prof_desc[index]; - - *prof_name = prof_info->prof_name_list[prof_desc->id]; - - return 0; -} - -static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, - struct aw_prof_desc **prof_desc) -{ - if ((index >= aw_dev->prof_info.count) || (index < 0)) { - dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", - __func__, index, aw_dev->prof_info.count); - return -EINVAL; - } - - *prof_desc = &aw_dev->prof_info.prof_desc[index]; - - return 0; -} - -static int aw88399_dev_fw_update(struct aw88399 *aw88399, bool up_dsp_fw_en, bool force_up_en) -{ - struct aw_device *aw_dev = aw88399->aw_pa; - struct aw_prof_desc *prof_index_desc; - struct aw_sec_data_desc *sec_desc; - char *prof_name; - int ret; - - if ((aw_dev->prof_cur == aw_dev->prof_index) && - (force_up_en == AW88399_FORCE_UPDATE_OFF)) { - dev_dbg(aw_dev->dev, "scene no change, not update"); - return 0; - } - - if (aw_dev->fw_status == AW88399_DEV_FW_FAILED) { - dev_err(aw_dev->dev, "fw status[%d] error", aw_dev->fw_status); - return -EPERM; - } - - ret = aw88399_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); - if (ret) - return ret; - - dev_dbg(aw_dev->dev, "start update %s", prof_name); - - ret = aw88399_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); - if (ret) - return ret; - - /* update reg */ - sec_desc = prof_index_desc->sec_desc; - ret = aw_dev_reg_update(aw88399, sec_desc[AW88395_DATA_TYPE_REG].data, - sec_desc[AW88395_DATA_TYPE_REG].len); - if (ret) { - dev_err(aw_dev->dev, "update reg failed"); - return ret; - } - - aw88399_dev_mute(aw_dev, true); - - if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) - aw_dev_dsp_enable(aw_dev, false); - - aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); - - ret = aw_dev_check_sram(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "check sram failed"); - goto error; - } - - if (up_dsp_fw_en) { - dev_dbg(aw_dev->dev, "fw_ver: [%x]", prof_index_desc->fw_ver); - ret = aw_dev_dsp_update_fw(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_FW].data, - sec_desc[AW88395_DATA_TYPE_DSP_FW].len); - if (ret) { - dev_err(aw_dev->dev, "update dsp fw failed"); - goto error; - } - } - - /* update dsp config */ - ret = aw_dev_dsp_update_cfg(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_CFG].data, - sec_desc[AW88395_DATA_TYPE_DSP_CFG].len); - if (ret) { - dev_err(aw_dev->dev, "update dsp cfg failed"); - goto error; - } - - aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); - - aw_dev->prof_cur = aw_dev->prof_index; - - return 0; - -error: - aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); - return ret; -} - -static void aw88399_start_pa(struct aw88399 *aw88399) -{ - int ret, i; - - for (i = 0; i < AW88399_START_RETRIES; i++) { - ret = aw88399_dev_start(aw88399); - if (ret) { - dev_err(aw88399->aw_pa->dev, "aw88399 device start failed. retry = %d", i); - ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_ON, true); - if (ret) { - dev_err(aw88399->aw_pa->dev, "fw update failed"); - continue; - } - } else { - dev_dbg(aw88399->aw_pa->dev, "start success\n"); - break; - } - } -} - -static void aw88399_startup_work(struct work_struct *work) -{ - struct aw88399 *aw88399 = - container_of(work, struct aw88399, start_work.work); - - mutex_lock(&aw88399->lock); - aw88399_start_pa(aw88399); - mutex_unlock(&aw88399->lock); -} - -static void aw88399_start(struct aw88399 *aw88399, bool sync_start) -{ - int ret; - - if (aw88399->aw_pa->fw_status != AW88399_DEV_FW_OK) - return; - - if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) - return; - - ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_OFF, true); - if (ret) { - dev_err(aw88399->aw_pa->dev, "fw update failed."); - return; - } - - if (sync_start == AW88399_SYNC_START) - aw88399_start_pa(aw88399); - else - queue_delayed_work(system_dfl_wq, - &aw88399->start_work, - AW88399_START_WORK_DELAY_MS); -} - -static int aw_dev_check_sysint(struct aw_device *aw_dev) -{ - u16 reg_val; - - aw_dev_get_int_status(aw_dev, ®_val); - if (reg_val & AW88399_BIT_SYSINT_CHECK) { - dev_err(aw_dev->dev, "pa stop check fail:0x%04x", reg_val); - return -EINVAL; - } - - return 0; -} - -static int aw88399_stop(struct aw_device *aw_dev) -{ - struct aw_sec_data_desc *dsp_cfg = - &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_CFG]; - struct aw_sec_data_desc *dsp_fw = - &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_FW]; - int int_st; - - if (aw_dev->status == AW88399_DEV_PW_OFF) { - dev_dbg(aw_dev->dev, "already power off"); - return 0; - } - - aw_dev->status = AW88399_DEV_PW_OFF; - - aw88399_dev_mute(aw_dev, true); - usleep_range(AW88399_4000_US, AW88399_4000_US + 100); - - aw_dev_i2s_tx_enable(aw_dev, false); - usleep_range(AW88399_1000_US, AW88399_1000_US + 100); - - int_st = aw_dev_check_sysint(aw_dev); - - aw_dev_dsp_enable(aw_dev, false); - - aw_dev_amppd(aw_dev, true); - - if (int_st) { - aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); - aw_dev_dsp_update_fw(aw_dev, dsp_fw->data, dsp_fw->len); - aw_dev_dsp_update_cfg(aw_dev, dsp_cfg->data, dsp_cfg->len); - aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); - } - - aw_dev_pwd(aw_dev, true); - - return 0; -} - static struct snd_soc_dai_driver aw88399_dai[] = { { .name = "aw88399-aif", @@ -1869,86 +664,6 @@ static int aw88399_calib_set(struct snd_kcontrol *kcontrol, return 0; } -static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) -{ - struct aw_device *aw_dev = aw88399->aw_pa; - int ret; - - ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); - if (ret) { - dev_err(aw_dev->dev, "aw_dev acf parse failed"); - return -EINVAL; - } - aw_dev->fade_in_time = AW88399_1000_US / 10; - aw_dev->fade_out_time = AW88399_1000_US >> 1; - aw_dev->prof_cur = aw_dev->prof_info.prof_desc[0].id; - aw_dev->prof_index = aw_dev->prof_info.prof_desc[0].id; - - ret = aw88399_dev_fw_update(aw88399, AW88399_FORCE_UPDATE_ON, AW88399_DSP_FW_UPDATE_ON); - if (ret) { - dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); - return ret; - } - - aw88399_dev_mute(aw_dev, true); - - /* close tx feedback */ - aw_dev_i2s_tx_enable(aw_dev, false); - usleep_range(AW88399_1000_US, AW88399_1000_US + 100); - - /* enable amppd */ - aw_dev_amppd(aw_dev, true); - - /* close dsp */ - aw_dev_dsp_enable(aw_dev, false); - /* set power down */ - aw_dev_pwd(aw_dev, true); - - return 0; -} - -static int aw88399_request_firmware_file(struct aw88399 *aw88399) -{ - const struct firmware *cont = NULL; - int ret; - - aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; - - ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); - if (ret) { - dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); - return ret; - } - - dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", - AW88399_ACF_FILE, cont ? cont->size : 0); - - aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, - struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); - if (!aw88399->aw_cfg) { - release_firmware(cont); - return -ENOMEM; - } - aw88399->aw_cfg->len = (int)cont->size; - memcpy(aw88399->aw_cfg->data, cont->data, cont->size); - release_firmware(cont); - - ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); - if (ret) { - dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); - return ret; - } - - mutex_lock(&aw88399->lock); - /* aw device init */ - ret = aw88399_dev_init(aw88399, aw88399->aw_cfg); - if (ret) - dev_err(aw88399->aw_pa->dev, "dev init failed"); - mutex_unlock(&aw88399->lock); - - return ret; -} - static const struct snd_kcontrol_new aw88399_controls[] = { SOC_SINGLE_EXT("PCM Playback Volume", AW88399_SYSCTRL2_REG, 6, AW88399_MUTE_VOL, 0, aw88399_volume_get, @@ -2040,70 +755,6 @@ static const struct snd_soc_component_driver soc_codec_dev_aw88399 = { .num_controls = ARRAY_SIZE(aw88399_controls), }; -static void aw88399_hw_reset(struct aw88399 *aw88399) -{ - if (aw88399->reset_gpio) { - gpiod_set_value_cansleep(aw88399->reset_gpio, 1); - usleep_range(AW88399_1000_US, AW88399_1000_US + 10); - gpiod_set_value_cansleep(aw88399->reset_gpio, 0); - usleep_range(AW88399_1000_US, AW88399_1000_US + 10); - gpiod_set_value_cansleep(aw88399->reset_gpio, 1); - usleep_range(AW88399_1000_US, AW88399_1000_US + 10); - } -} - -static void aw88399_parse_channel_dt(struct aw_device *aw_dev) -{ - struct device_node *np = aw_dev->dev->of_node; - u32 channel_value; - - of_property_read_u32(np, "awinic,audio-channel", &channel_value); - aw_dev->channel = channel_value; -} - -static int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap) -{ - struct aw_device *aw_dev; - unsigned int chip_id; - int ret; - - ret = regmap_read(regmap, AW88399_ID_REG, &chip_id); - if (ret) { - dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); - return ret; - } - if (chip_id != AW88399_CHIP_ID) { - dev_err(&i2c->dev, "unsupported device"); - return -ENXIO; - } - dev_dbg(&i2c->dev, "chip id = %x\n", chip_id); - - aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); - if (!aw_dev) - return -ENOMEM; - aw88399->aw_pa = aw_dev; - - aw_dev->i2c = i2c; - aw_dev->dev = &i2c->dev; - aw_dev->regmap = regmap; - mutex_init(&aw_dev->dsp_lock); - - aw_dev->chip_id = chip_id; - aw_dev->acf = NULL; - aw_dev->prof_info.prof_desc = NULL; - aw_dev->prof_info.count = 0; - aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; - aw_dev->channel = AW88399_DEV_DEFAULT_CH; - aw_dev->fw_status = AW88399_DEV_FW_FAILED; - - aw_dev->fade_step = AW88399_VOLUME_STEP_DB; - aw_dev->volume_desc.ctl_volume = AW88399_VOL_DEFAULT_VALUE; - - aw88399_parse_channel_dt(aw_dev); - - return 0; -} - static int aw88399_i2c_probe(struct i2c_client *i2c) { struct aw88399 *aw88399; diff --git a/sound/soc/codecs/aw88399.h b/sound/soc/codecs/aw88399.h index b386f4836748..04123bf0ad84 100644 --- a/sound/soc/codecs/aw88399.h +++ b/sound/soc/codecs/aw88399.h @@ -10,512 +10,10 @@ #ifndef __AW88399_H__ #define __AW88399_H__ -/* registers list */ -#define AW88399_ID_REG (0x00) -#define AW88399_SYSST_REG (0x01) -#define AW88399_SYSINT_REG (0x02) -#define AW88399_SYSINTM_REG (0x03) -#define AW88399_SYSCTRL_REG (0x04) -#define AW88399_SYSCTRL2_REG (0x05) -#define AW88399_I2SCTRL1_REG (0x06) -#define AW88399_I2SCTRL2_REG (0x07) -#define AW88399_I2SCTRL3_REG (0x08) -#define AW88399_DACCFG1_REG (0x09) -#define AW88399_DACCFG2_REG (0x0A) -#define AW88399_DACCFG3_REG (0x0B) -#define AW88399_DACCFG4_REG (0x0C) -#define AW88399_DACCFG5_REG (0x0D) -#define AW88399_DACCFG6_REG (0x0E) -#define AW88399_DACCFG7_REG (0x0F) -#define AW88399_MPDCFG1_REG (0x10) -#define AW88399_MPDCFG2_REG (0x11) -#define AW88399_MPDCFG3_REG (0x12) -#define AW88399_MPDCFG4_REG (0x13) -#define AW88399_PWMCTRL1_REG (0x14) -#define AW88399_PWMCTRL2_REG (0x15) -#define AW88399_PWMCTRL3_REG (0x16) -#define AW88399_I2SCFG1_REG (0x17) -#define AW88399_DBGCTRL_REG (0x18) -#define AW88399_HAGCST_REG (0x20) -#define AW88399_VBAT_REG (0x21) -#define AW88399_TEMP_REG (0x22) -#define AW88399_PVDD_REG (0x23) -#define AW88399_ISNDAT_REG (0x24) -#define AW88399_VSNDAT_REG (0x25) -#define AW88399_I2SINT_REG (0x26) -#define AW88399_I2SCAPCNT_REG (0x27) -#define AW88399_ANASTA1_REG (0x28) -#define AW88399_ANASTA2_REG (0x29) -#define AW88399_ANASTA3_REG (0x2A) -#define AW88399_TESTDET_REG (0x2B) -#define AW88399_DSMCFG1_REG (0x30) -#define AW88399_DSMCFG2_REG (0x31) -#define AW88399_DSMCFG3_REG (0x32) -#define AW88399_DSMCFG4_REG (0x33) -#define AW88399_DSMCFG5_REG (0x34) -#define AW88399_DSMCFG6_REG (0x35) -#define AW88399_DSMCFG7_REG (0x36) -#define AW88399_DSMCFG8_REG (0x37) -#define AW88399_TESTIN_REG (0x38) -#define AW88399_TESTOUT_REG (0x39) -#define AW88399_MEMTEST_REG (0x3A) -#define AW88399_VSNCTRL1_REG (0x3B) -#define AW88399_ISNCTRL1_REG (0x3C) -#define AW88399_ISNCTRL2_REG (0x3D) -#define AW88399_DSPMADD_REG (0x40) -#define AW88399_DSPMDAT_REG (0x41) -#define AW88399_WDT_REG (0x42) -#define AW88399_ACR1_REG (0x43) -#define AW88399_ACR2_REG (0x44) -#define AW88399_ASR1_REG (0x45) -#define AW88399_ASR2_REG (0x46) -#define AW88399_DSPCFG_REG (0x47) -#define AW88399_ASR3_REG (0x48) -#define AW88399_ASR4_REG (0x49) -#define AW88399_DSPVCALB_REG (0x4A) -#define AW88399_CRCCTRL_REG (0x4B) -#define AW88399_DSPDBG1_REG (0x4C) -#define AW88399_DSPDBG2_REG (0x4D) -#define AW88399_DSPDBG3_REG (0x4E) -#define AW88399_PLLCTRL1_REG (0x50) -#define AW88399_PLLCTRL2_REG (0x51) -#define AW88399_PLLCTRL3_REG (0x52) -#define AW88399_CDACTRL1_REG (0x53) -#define AW88399_CDACTRL2_REG (0x54) -#define AW88399_CDACTRL3_REG (0x55) -#define AW88399_SADCCTRL1_REG (0x56) -#define AW88399_SADCCTRL2_REG (0x57) -#define AW88399_BOPCTRL1_REG (0x58) -#define AW88399_BOPCTRL2_REG (0x5A) -#define AW88399_BOPCTRL3_REG (0x5B) -#define AW88399_BOPCTRL4_REG (0x5C) -#define AW88399_BOPCTRL5_REG (0x5D) -#define AW88399_BOPCTRL6_REG (0x5E) -#define AW88399_BOPCTRL7_REG (0x5F) -#define AW88399_BSTCTRL1_REG (0x60) -#define AW88399_BSTCTRL2_REG (0x61) -#define AW88399_BSTCTRL3_REG (0x62) -#define AW88399_BSTCTRL4_REG (0x63) -#define AW88399_BSTCTRL5_REG (0x64) -#define AW88399_BSTCTRL6_REG (0x65) -#define AW88399_BSTCTRL7_REG (0x66) -#define AW88399_BSTCTRL8_REG (0x67) -#define AW88399_BSTCTRL9_REG (0x68) -#define AW88399_BSTCTRL10_REG (0x69) -#define AW88399_CPCTRL_REG (0x6A) -#define AW88399_EFWH_REG (0x6C) -#define AW88399_EFWM2_REG (0x6D) -#define AW88399_EFWM1_REG (0x6E) -#define AW88399_EFWL_REG (0x6F) -#define AW88399_TESTCTRL1_REG (0x70) -#define AW88399_TESTCTRL2_REG (0x71) -#define AW88399_EFCTRL1_REG (0x72) -#define AW88399_EFCTRL2_REG (0x73) -#define AW88399_EFRH4_REG (0x74) -#define AW88399_EFRH3_REG (0x75) -#define AW88399_EFRH2_REG (0x76) -#define AW88399_EFRH1_REG (0x77) -#define AW88399_EFRL4_REG (0x78) -#define AW88399_EFRL3_REG (0x79) -#define AW88399_EFRL2_REG (0x7A) -#define AW88399_EFRL1_REG (0x7B) -#define AW88399_TM_REG (0x7C) -#define AW88399_TM2_REG (0x7D) - -#define AW88399_REG_MAX (0x7E) -#define AW88399_MUTE_VOL (1023) - -#define AW88399_DSP_CFG_ADDR (0x9B00) -#define AW88399_DSP_REG_CFG_ADPZ_RA (0x9B68) -#define AW88399_DSP_FW_ADDR (0x8980) -#define AW88399_DSP_ROM_CHECK_ADDR (0x1F40) -#define AW88399_DSP_ROM_CHECK_DATA (0x4638) - -#define AW88399_CALI_RE_HBITS_MASK (~(0xFFFF0000)) -#define AW88399_CALI_RE_HBITS_SHIFT (16) - -#define AW88399_CALI_RE_LBITS_MASK (~(0xFFFF)) -#define AW88399_CALI_RE_LBITS_SHIFT (0) - -#define AW88399_I2STXEN_START_BIT (9) -#define AW88399_I2STXEN_BITS_LEN (1) -#define AW88399_I2STXEN_MASK \ - (~(((1<> (shift)) -#define AW88399_SHOW_RE_TO_DSP_RE(re, shift) (((re) << shift) / (1000)) -#define AW88399_CRC_CHECK_PASS_VAL (0x4) - -#define AW88399_CRC_CFG_BASE_ADDR (0xD80) -#define AW88399_CRC_FW_BASE_ADDR (0x4C0) -#define AW88399_ACF_FILE "aw88399_acf.bin" -#define AW88399_DEV_SYSST_CHECK_MAX (10) -#define AW88399_CHIP_ID 0x2183 +#include #define AW88399_I2C_NAME "aw88399" -#define AW88399_START_RETRIES (5) -#define AW88399_START_WORK_DELAY_MS (0) - #define AW88399_RATES (SNDRV_PCM_RATE_8000_48000 | \ SNDRV_PCM_RATE_96000) #define AW88399_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ @@ -550,80 +48,4 @@ .put = profile_set, \ } -enum { - AW_EF_AND_CHECK = 0, - AW_EF_OR_CHECK, -}; - -enum { - AW88399_DEV_VDSEL_DAC = 0, - AW88399_DEV_VDSEL_VSENSE = 32, -}; - -enum { - AW88399_DSP_CRC_NA = 0, - AW88399_DSP_CRC_OK = 1, -}; - -enum { - AW88399_DSP_FW_UPDATE_OFF = 0, - AW88399_DSP_FW_UPDATE_ON = 1, -}; - -enum { - AW88399_FORCE_UPDATE_OFF = 0, - AW88399_FORCE_UPDATE_ON = 1, -}; - -enum { - AW88399_1000_US = 1000, - AW88399_2000_US = 2000, - AW88399_3000_US = 3000, - AW88399_4000_US = 4000, -}; - -enum AW88399_DEV_STATUS { - AW88399_DEV_PW_OFF = 0, - AW88399_DEV_PW_ON, -}; - -enum AW88399_DEV_FW_STATUS { - AW88399_DEV_FW_FAILED = 0, - AW88399_DEV_FW_OK, -}; - -enum AW88399_DEV_MEMCLK { - AW88399_DEV_MEMCLK_OSC = 0, - AW88399_DEV_MEMCLK_PLL = 1, -}; - -enum AW88399_DEV_DSP_CFG { - AW88399_DEV_DSP_WORK = 0, - AW88399_DEV_DSP_BYPASS = 1, -}; - -enum { - AW88399_NOT_RCV_MODE = 0, - AW88399_RCV_MODE = 1, -}; - -enum { - AW88399_SYNC_START = 0, - AW88399_ASYNC_START, -}; - -struct aw88399 { - struct aw_device *aw_pa; - struct mutex lock; - struct gpio_desc *reset_gpio; - struct delayed_work start_work; - struct regmap *regmap; - struct aw_container *aw_cfg; - - unsigned int check_val; - unsigned int crc_init_val; - unsigned int vcalb_init_val; - unsigned int dither_st; -}; - -#endif +#endif /* __AW88399_H__ */ From df5654d7306197087aef090377d196573bb4ee46 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Fri, 17 Jul 2026 15:25:04 +0200 Subject: [PATCH 476/479] ASoC: aw88399: derive channel from I2C address on ACPI systems Extend aw88399_parse_channel_dt to derive the audio channel from the I2C address when the Device Tree property "awinic,audio-channel" is absent. The original code calls of_property_read_u32 without checking the return value. On ACPI systems, the DT property is never present, and channel_value is used uninitialized in the assignment to aw_dev->channel. Add a fallback that computes the channel as (i2c_addr - 0x34), where 0x34 is the AW88399's base I2C address per the datasheet (valid range 0x34-0x37). This channel assignment may be subsequently overridden by the HDA side codec's property driver on systems that require it. No change on Device Tree systems where the property is present. Tested-by: Nadim Kobeissi Tested-by: Xia Yun'an Tested-by: Munzir Taha Co-developed-by: Yakov Till Signed-off-by: Yakov Till Signed-off-by: Marco Giunta Link: https://patch.msgid.link/DS7PR19MB772468BB9F4D6925DC4E8E3EFCC62@DS7PR19MB7724.namprd19.prod.outlook.com Signed-off-by: Mark Brown --- sound/soc/codecs/aw88399-lib.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index 258d6efbf590..b525695c96d1 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -1328,8 +1328,20 @@ static void aw88399_parse_channel_dt(struct aw_device *aw_dev) { struct device_node *np = aw_dev->dev->of_node; u32 channel_value; + int ret; - of_property_read_u32(np, "awinic,audio-channel", &channel_value); + ret = of_property_read_u32(np, "awinic,audio-channel", &channel_value); + if (ret) { + /* + * On ACPI systems, DT properties don't exist. Derive channel + * from I2C address: 0x34 -> channel 0 (left), 0x35 -> channel 1 (right) + */ + aw_dev->channel = aw_dev->i2c->addr - 0x34; + dev_dbg(aw_dev->dev, + "DT channel property not found, using I2C address-based channel %d (addr 0x%02x)\n", + aw_dev->channel, aw_dev->i2c->addr); + return; + } aw_dev->channel = channel_value; } From b4530a3e4895abfc308e2b45c0ea508f4dddd9f1 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Fri, 17 Jul 2026 15:25:05 +0200 Subject: [PATCH 477/479] ASoC: aw88399: add per-instance BSTS status bypass flag Add a bsts_unreliable flag to struct aw88399 that, when set, causes the startup status check (aw_dev_check_sysst) to skip the BSTS (boost startup finished) requirement. On some hardware, the BSTS bit in the SYSST register (0x01, bit 9) does not reliably assert even during normal audio playback. Register inspection on affected Lenovo Legion hardware shows both amplifiers reporting BSTS=0 on both channels despite clean audio output. Per the AW88399 datasheet, BSTS indicates boost startup completion. If BSTS never reliably sets to 1, the chip is never allowed to start by aw_dev_check_sysst, regardless of whether the boot failure is genuine. The new flag defaults to false via kzalloc, preserving the original check behavior for all existing users. No existing code path sets this flag; it will be set by the forthcoming HDA side codec property driver for affected hardware. Tested-by: Nadim Kobeissi Tested-by: Xia Yun'an Tested-by: Munzir Taha Signed-off-by: Marco Giunta Link: https://patch.msgid.link/DS7PR19MB77242B8E5BB8BFB5E69816E9FCC62@DS7PR19MB7724.namprd19.prod.outlook.com Signed-off-by: Mark Brown --- include/sound/aw88399.h | 1 + sound/soc/codecs/aw88399-lib.c | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h index 3a2153f0ee92..dee91b540b81 100644 --- a/include/sound/aw88399.h +++ b/include/sound/aw88399.h @@ -598,6 +598,7 @@ struct aw88399 { unsigned int crc_init_val; unsigned int vcalb_init_val; unsigned int dither_st; + bool bsts_unreliable; }; int aw_dev_check_syspll(struct aw_device *aw_dev); diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index b525695c96d1..2045c4171be0 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -167,8 +167,9 @@ int aw_dev_check_syspll(struct aw_device *aw_dev) } EXPORT_SYMBOL_GPL(aw_dev_check_syspll); -static int aw_dev_check_sysst(struct aw_device *aw_dev) +static int aw_dev_check_sysst(struct aw88399 *aw88399) { + struct aw_device *aw_dev = aw88399->aw_pa; unsigned int check_val; unsigned int reg_val; int ret, i; @@ -182,6 +183,14 @@ static int aw_dev_check_sysst(struct aw_device *aw_dev) else check_val = AW88399_BIT_SYSST_SWS_CHECK; + /* + * On some hardware the BSTS (boost-finished) status bit does not + * reliably assert even when audio output is working normally. + * Allow per-instance bypass when flagged by the side-codec driver. + */ + if (aw88399->bsts_unreliable) + check_val &= ~AW88399_BSTS_FINISHED_VALUE; + for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); if (ret) @@ -710,7 +719,7 @@ static int aw88399_dev_start(struct aw88399 *aw88399) usleep_range(AW88399_1000_US, AW88399_1000_US + 50); /* check i2s status */ - ret = aw_dev_check_sysst(aw_dev); + ret = aw_dev_check_sysst(aw88399); if (ret) { dev_err(aw_dev->dev, "sysst check failed"); goto sysst_check_fail; From b5e1d685fb9c9acef5942794a8dc07580c607e66 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Fri, 17 Jul 2026 15:25:06 +0200 Subject: [PATCH 478/479] ASoC: aw88399: add firmware reload flag for resume Add a fw_needs_reload flag to struct aw88399 that, when set, causes aw88399_start to perform a full DSP firmware upload instead of assuming the firmware binary is already present in memory. After system sleep, the AW88399 loses its memory contents. The existing start sequence assumes the firmware binary persists from initialization and only uploads register configuration and DSP config (AW88399_DSP_FW_UPDATE_OFF). When memory is empty, this causes the subsequent CRC check to fail, triggering the retry mechanism in aw88399_start_pa which re-uploads the firmware on the second attempt. While the retry mechanism recovers correctly, it produces misleading error-level log messages on every resume cycle. The fw_needs_reload flag allows the HDA side codec driver to signal that a full firmware reload is needed after resume, eliminating the spurious CRC failures. The flag defaults to false via kzalloc, preserving the original behavior for existing ASoC users. No existing code path sets this flag; it will be set by the HDA side codec driver's system suspend handler. Tested-by: Nadim Kobeissi Tested-by: Xia Yun'an Tested-by: Munzir Taha Signed-off-by: Marco Giunta Link: https://patch.msgid.link/DS7PR19MB77240CB79188C0B7AE243829FCC62@DS7PR19MB7724.namprd19.prod.outlook.com Signed-off-by: Mark Brown --- include/sound/aw88399.h | 1 + sound/soc/codecs/aw88399-lib.c | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h index dee91b540b81..3dbc37dc13d1 100644 --- a/include/sound/aw88399.h +++ b/include/sound/aw88399.h @@ -599,6 +599,7 @@ struct aw88399 { unsigned int vcalb_init_val; unsigned int dither_st; bool bsts_unreliable; + bool fw_needs_reload; }; int aw_dev_check_syspll(struct aw_device *aw_dev); diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index 2045c4171be0..094a37b66fb5 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -1171,12 +1171,15 @@ void aw88399_start(struct aw88399 *aw88399, bool sync_start) if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) return; - ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_OFF, true); + ret = aw88399_dev_fw_update(aw88399, aw88399->fw_needs_reload ? + AW88399_DSP_FW_UPDATE_ON : AW88399_DSP_FW_UPDATE_OFF, true); if (ret) { dev_err(aw88399->aw_pa->dev, "fw update failed."); return; } + aw88399->fw_needs_reload = false; + if (sync_start == AW88399_SYNC_START) aw88399_start_pa(aw88399); else From 15c9fdb3c39d5073463ffa746cc64ad9682c875f Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Fri, 17 Jul 2026 15:25:07 +0200 Subject: [PATCH 479/479] ASoC: aw88399: add channel setter for HDA side codec Add aw88399_dev_set_channel() to the shared library so that the HDA side codec driver can set the amplifier's channel assignment without including the aw88395 device header directly. The AW88399's struct aw_device is defined in aw88395_device.h, which lives under sound/soc/codecs/aw88395/. Without this accessor, the HDA driver would need a cross-subsystem relative include path to access the channel field. Providing a setter in the library keeps the interface clean and avoids coupling the HDA driver to ASoC-internal headers. Tested-by: Nadim Kobeissi Tested-by: Xia Yun'an Tested-by: Munzir Taha Signed-off-by: Marco Giunta Link: https://patch.msgid.link/DS7PR19MB7724E8A1AD36D1E623FA2A0AFCC62@DS7PR19MB7724.namprd19.prod.outlook.com Signed-off-by: Mark Brown --- include/sound/aw88399.h | 1 + sound/soc/codecs/aw88399-lib.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h index 3dbc37dc13d1..a1e0de6be8ea 100644 --- a/include/sound/aw88399.h +++ b/include/sound/aw88399.h @@ -609,6 +609,7 @@ int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value); int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc); int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name); void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute); +void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel); void aw88399_hw_reset(struct aw88399 *aw88399); int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap); extern const struct regmap_config aw88399_remap_config; diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index 094a37b66fb5..5c7982891def 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -1401,5 +1401,11 @@ int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap } EXPORT_SYMBOL_GPL(aw88399_init); +void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel) +{ + aw88399->aw_pa->channel = channel; +} +EXPORT_SYMBOL_GPL(aw88399_dev_set_channel); + MODULE_DESCRIPTION("AW88399 common device library"); MODULE_LICENSE("GPL");