--- /dev/null
+From stable+bounces-273248-greg=kroah.com@vger.kernel.org Fri Jul 10 16:22:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Jul 2026 10:17:40 -0400
+Subject: ACPI: NFIT: core: Fix acpi_nfit_init() error cleanup
+To: stable@vger.kernel.org
+Cc: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>, Dave Jiang <dave.jiang@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260710141740.132102-1-sashal@kernel.org>
+
+From: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
+
+[ Upstream commit 38bf27511ef41bffebd157ec3eba41fc89ba59cd ]
+
+If acpi_nfit_init() fails after adding the acpi_desc object to the
+acpi_descs list, that object is never removed from that list because
+the acpi_nfit_shutdown() devm action is not added for the NFIT device
+in that case. Next, the acpi_nfit_init() failure causes
+acpi_nfit_probe() to fail, the acpi_desc object is freed, and a
+dangling pointer is left behind in the acpi_descs. Any subsequent
+ACPI Machine Check Exception will trigger nfit_handle_mce() which
+iterates over acpi_descs and so a use-after-free will occur.
+
+Moreover, if acpi_nfit_probe() returns 0 after installing a notify
+handler for the NFIT device and without allocating the acpi_desc
+object and setting the NFIT device's driver data pointer, the
+acpi_desc object will be allocated by acpi_nfit_update_notify()
+and acpi_nfit_init() will be called to initialize it. Regardless
+of whether or not acpi_nfit_init() fails in that case, the
+acpi_nfit_shutdown() devm action is not added for the NFIT device
+and acpi_desc is never removed from the acpi_descs list. If the
+acpi_desc object is freed subsequently on driver removal, any
+subsequent ACPI MCE will lead to a use-after-free like in the
+previous case.
+
+To address the first issue mentioned above, make acpi_nfit_probe()
+call acpi_nfit_shutdown() directly on acpi_nfit_init() failures and
+to address the other one, add a remove callback to the driver and
+make it call acpi_nfit_shutdown(). Also, since it is now possible to
+pass NULL to acpi_nfit_shutdown() or the acpi_desc object passed to it
+may not have been initialized, add checks against NULL for acpi_desc and
+its nvdimm_bus field to that function and make acpi_nfit_unregister()
+clear the latter after unregistering the NVDIMM bus.
+
+Fixes: a61fe6f7902e ("nfit, tools/testing/nvdimm: unify common init for acpi_nfit_desc")
+Fixes: fbabd829fe76 ("acpi, nfit: fix module unload vs workqueue shutdown race")
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+Cc: All applicable <stable@vger.kernel.org>
+Reviewed-by: Dave Jiang <dave.jiang@intel.com>
+Link: https://patch.msgid.link/1963615.tdWV9SEqCh@rafael.j.wysocki
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/acpi/nfit/core.c | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+--- a/drivers/acpi/nfit/core.c
++++ b/drivers/acpi/nfit/core.c
+@@ -3067,6 +3067,8 @@ static void acpi_nfit_unregister(void *d
+ struct acpi_nfit_desc *acpi_desc = data;
+
+ nvdimm_bus_unregister(acpi_desc->nvdimm_bus);
++ /* The nvdimm_bus object may have been freed, so clear the pointer. */
++ acpi_desc->nvdimm_bus = NULL;
+ }
+
+ int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, void *data, acpi_size sz)
+@@ -3290,7 +3292,10 @@ static void acpi_nfit_put_table(void *ta
+ void acpi_nfit_shutdown(void *data)
+ {
+ struct acpi_nfit_desc *acpi_desc = data;
+- struct device *bus_dev = to_nvdimm_bus_dev(acpi_desc->nvdimm_bus);
++ struct device *bus_dev;
++
++ if (!acpi_desc || !acpi_desc->nvdimm_bus)
++ return;
+
+ /*
+ * Destruct under acpi_desc_lock so that nfit_handle_mce does not
+@@ -3305,6 +3310,7 @@ void acpi_nfit_shutdown(void *data)
+ mutex_unlock(&acpi_desc->init_mutex);
+ cancel_delayed_work_sync(&acpi_desc->dwork);
+
++ bus_dev = to_nvdimm_bus_dev(acpi_desc->nvdimm_bus);
+ /*
+ * Bounce the nvdimm bus lock to make sure any in-flight
+ * acpi_nfit_ars_rescan() submissions have had a chance to
+@@ -3372,13 +3378,14 @@ static int acpi_nfit_add(struct acpi_dev
+ sz - sizeof(struct acpi_table_nfit));
+
+ if (rc)
+- return rc;
+- return devm_add_action_or_reset(dev, acpi_nfit_shutdown, acpi_desc);
++ acpi_nfit_shutdown(acpi_desc);
++
++ return rc;
+ }
+
+ static int acpi_nfit_remove(struct acpi_device *adev)
+ {
+- /* see acpi_nfit_unregister */
++ acpi_nfit_shutdown(dev_get_drvdata(&adev->dev));
+ return 0;
+ }
+
--- /dev/null
+From stable+bounces-274054-greg=kroah.com@vger.kernel.org Tue Jul 14 03:16:24 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 21:16:17 -0400
+Subject: ALSA: aoa: check snd_ctl_new1() return value
+To: stable@vger.kernel.org
+Cc: Zhao Dongdong <zhaodongdong@kylinos.cn>, Takashi Iwai <tiwai@suse.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714011617.2188420-1-sashal@kernel.org>
+
+From: Zhao Dongdong <zhaodongdong@kylinos.cn>
+
+[ Upstream commit 8df560fefe6fed6a20b7e06720eeaeccec349ac0 ]
+
+snd_ctl_new1() can return NULL when memory allocation fails. In
+layout.c, the function does not check the return value before
+dereferencing ctl->id.name or passing to aoa_snd_ctl_add(), which can
+lead to a NULL pointer dereference.
+
+Add NULL checks after snd_ctl_new1() calls and return early if any
+fails.
+
+Assisted-by: Opencode:DeepSeek-V4-Flash
+Cc: stable@vger.kernel.org
+Fixes: f3d9478b2ce4 ("[ALSA] snd-aoa: add snd-aoa")
+Signed-off-by: Zhao Dongdong <zhaodongdong@kylinos.cn>
+Link: https://patch.msgid.link/tencent_35F3A25FEEBF190A2E15ED787754C57E3708@qq.com
+Signed-off-by: Takashi Iwai <tiwai@suse.de>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/aoa/fabrics/layout.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/sound/aoa/fabrics/layout.c
++++ b/sound/aoa/fabrics/layout.c
+@@ -947,6 +947,8 @@ static void layout_attached_codec(struct
+ if (lineout == 1)
+ ldev->gpio.methods->set_lineout(codec->gpio, 1);
+ ctl = snd_ctl_new1(&lineout_ctl, codec->gpio);
++ if (!ctl)
++ return;
+ if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
+ strscpy(ctl->id.name,
+ "Headphone Switch", sizeof(ctl->id.name));
+@@ -961,6 +963,8 @@ static void layout_attached_codec(struct
+ if (ldev->have_lineout_detect) {
+ ctl = snd_ctl_new1(&lineout_detect_choice,
+ ldev);
++ if (!ctl)
++ return;
+ if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
+ strscpy(ctl->id.name,
+ "Headphone Detect Autoswitch",
+@@ -968,6 +972,8 @@ static void layout_attached_codec(struct
+ aoa_snd_ctl_add(ctl);
+ ctl = snd_ctl_new1(&lineout_detected,
+ ldev);
++ if (!ctl)
++ return;
+ if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
+ strscpy(ctl->id.name,
+ "Headphone Detected",
--- /dev/null
+From stable+bounces-278315-greg=kroah.com@vger.kernel.org Tue Jul 21 03:28:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 21:27:50 -0400
+Subject: audit: add audit_log_nf_skb helper function
+To: stable@vger.kernel.org
+Cc: Ricardo Robaina <rrobaina@redhat.com>, Florian Westphal <fw@strlen.de>, Paul Moore <paul@paul-moore.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721012751.3458464-1-sashal@kernel.org>
+
+From: Ricardo Robaina <rrobaina@redhat.com>
+
+[ Upstream commit f19590b07cb620be1fcd5474c49515e21a05d406 ]
+
+Netfilter code (net/netfilter/nft_log.c and net/netfilter/xt_AUDIT.c)
+have to be kept in sync. Both source files had duplicated versions of
+audit_ip4() and audit_ip6() functions, which can result in lack of
+consistency and/or duplicated work.
+
+This patch adds a helper function in audit.c that can be called by
+netfilter code commonly, aiming to improve maintainability and
+consistency.
+
+Suggested-by: Florian Westphal <fw@strlen.de>
+Suggested-by: Paul Moore <paul@paul-moore.com>
+Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
+Acked-by: Florian Westphal <fw@strlen.de>
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+Stable-dep-of: 65dfde57d1e2 ("audit: fix potential integer overflow in audit_log_n_hex()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/audit.h | 8 +++++
+ kernel/audit.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++
+ net/netfilter/nft_log.c | 58 ------------------------------------------
+ net/netfilter/xt_AUDIT.c | 58 ------------------------------------------
+ 4 files changed, 74 insertions(+), 114 deletions(-)
+
+--- a/include/linux/audit.h
++++ b/include/linux/audit.h
+@@ -177,6 +177,8 @@ extern void audit_log_lost(const ch
+
+ extern int audit_log_task_context(struct audit_buffer *ab);
+ extern void audit_log_task_info(struct audit_buffer *ab);
++extern int audit_log_nf_skb(struct audit_buffer *ab,
++ const struct sk_buff *skb, u8 nfproto);
+
+ extern int audit_update_lsm_rules(void);
+
+@@ -242,6 +244,12 @@ static inline int audit_log_task_context
+ static inline void audit_log_task_info(struct audit_buffer *ab)
+ { }
+
++static inline int audit_log_nf_skb(struct audit_buffer *ab,
++ const struct sk_buff *skb, u8 nfproto)
++{
++ return 0;
++}
++
+ static inline kuid_t audit_get_loginuid(struct task_struct *tsk)
+ {
+ return INVALID_UID;
+--- a/kernel/audit.c
++++ b/kernel/audit.c
+@@ -59,6 +59,8 @@
+ #include <linux/freezer.h>
+ #include <linux/pid_namespace.h>
+ #include <net/netns/generic.h>
++#include <net/ip.h>
++#include <net/ipv6.h>
+
+ #include "audit.h"
+
+@@ -2305,6 +2307,68 @@ void audit_log_path_denied(int type, con
+ audit_log_end(ab);
+ }
+
++int audit_log_nf_skb(struct audit_buffer *ab,
++ const struct sk_buff *skb, u8 nfproto)
++{
++ /* find the IP protocol in the case of NFPROTO_BRIDGE */
++ if (nfproto == NFPROTO_BRIDGE) {
++ switch (eth_hdr(skb)->h_proto) {
++ case htons(ETH_P_IP):
++ nfproto = NFPROTO_IPV4;
++ break;
++ case htons(ETH_P_IPV6):
++ nfproto = NFPROTO_IPV6;
++ break;
++ default:
++ goto unknown_proto;
++ }
++ }
++
++ switch (nfproto) {
++ case NFPROTO_IPV4: {
++ struct iphdr iph;
++ const struct iphdr *ih;
++
++ ih = skb_header_pointer(skb, skb_network_offset(skb),
++ sizeof(iph), &iph);
++ if (!ih)
++ return -ENOMEM;
++
++ audit_log_format(ab, " saddr=%pI4 daddr=%pI4 proto=%hhu",
++ &ih->saddr, &ih->daddr, ih->protocol);
++ break;
++ }
++ case NFPROTO_IPV6: {
++ struct ipv6hdr iph;
++ const struct ipv6hdr *ih;
++ u8 nexthdr;
++ __be16 frag_off;
++
++ ih = skb_header_pointer(skb, skb_network_offset(skb),
++ sizeof(iph), &iph);
++ if (!ih)
++ return -ENOMEM;
++
++ nexthdr = ih->nexthdr;
++ ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(iph),
++ &nexthdr, &frag_off);
++
++ audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu",
++ &ih->saddr, &ih->daddr, nexthdr);
++ break;
++ }
++ default:
++ goto unknown_proto;
++ }
++
++ return 0;
++
++unknown_proto:
++ audit_log_format(ab, " saddr=? daddr=? proto=?");
++ return -EPFNOSUPPORT;
++}
++EXPORT_SYMBOL(audit_log_nf_skb);
++
+ /* global counter which is incremented every time something logs in */
+ static atomic_t session_id = ATOMIC_INIT(0);
+
+--- a/net/netfilter/nft_log.c
++++ b/net/netfilter/nft_log.c
+@@ -26,46 +26,10 @@ struct nft_log {
+ char *prefix;
+ };
+
+-static bool audit_ip4(struct audit_buffer *ab, struct sk_buff *skb)
+-{
+- struct iphdr _iph;
+- const struct iphdr *ih;
+-
+- ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_iph), &_iph);
+- if (!ih)
+- return false;
+-
+- audit_log_format(ab, " saddr=%pI4 daddr=%pI4 proto=%hhu",
+- &ih->saddr, &ih->daddr, ih->protocol);
+-
+- return true;
+-}
+-
+-static bool audit_ip6(struct audit_buffer *ab, struct sk_buff *skb)
+-{
+- struct ipv6hdr _ip6h;
+- const struct ipv6hdr *ih;
+- u8 nexthdr;
+- __be16 frag_off;
+-
+- ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_ip6h), &_ip6h);
+- if (!ih)
+- return false;
+-
+- nexthdr = ih->nexthdr;
+- ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(_ip6h), &nexthdr, &frag_off);
+-
+- audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu",
+- &ih->saddr, &ih->daddr, nexthdr);
+-
+- return true;
+-}
+-
+ static void nft_log_eval_audit(const struct nft_pktinfo *pkt)
+ {
+ struct sk_buff *skb = pkt->skb;
+ struct audit_buffer *ab;
+- int fam = -1;
+
+ if (!audit_enabled)
+ return;
+@@ -76,27 +40,7 @@ static void nft_log_eval_audit(const str
+
+ audit_log_format(ab, "mark=%#x", skb->mark);
+
+- switch (nft_pf(pkt)) {
+- case NFPROTO_BRIDGE:
+- switch (eth_hdr(skb)->h_proto) {
+- case htons(ETH_P_IP):
+- fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1;
+- break;
+- case htons(ETH_P_IPV6):
+- fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1;
+- break;
+- }
+- break;
+- case NFPROTO_IPV4:
+- fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1;
+- break;
+- case NFPROTO_IPV6:
+- fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1;
+- break;
+- }
+-
+- if (fam == -1)
+- audit_log_format(ab, " saddr=? daddr=? proto=-1");
++ audit_log_nf_skb(ab, skb, nft_pf(pkt));
+
+ audit_log_end(ab);
+ }
+--- a/net/netfilter/xt_AUDIT.c
++++ b/net/netfilter/xt_AUDIT.c
+@@ -28,46 +28,10 @@ MODULE_ALIAS("ip6t_AUDIT");
+ MODULE_ALIAS("ebt_AUDIT");
+ MODULE_ALIAS("arpt_AUDIT");
+
+-static bool audit_ip4(struct audit_buffer *ab, struct sk_buff *skb)
+-{
+- struct iphdr _iph;
+- const struct iphdr *ih;
+-
+- ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_iph), &_iph);
+- if (!ih)
+- return false;
+-
+- audit_log_format(ab, " saddr=%pI4 daddr=%pI4 proto=%hhu",
+- &ih->saddr, &ih->daddr, ih->protocol);
+-
+- return true;
+-}
+-
+-static bool audit_ip6(struct audit_buffer *ab, struct sk_buff *skb)
+-{
+- struct ipv6hdr _ip6h;
+- const struct ipv6hdr *ih;
+- u8 nexthdr;
+- __be16 frag_off;
+-
+- ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_ip6h), &_ip6h);
+- if (!ih)
+- return false;
+-
+- nexthdr = ih->nexthdr;
+- ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(_ip6h), &nexthdr, &frag_off);
+-
+- audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu",
+- &ih->saddr, &ih->daddr, nexthdr);
+-
+- return true;
+-}
+-
+ static unsigned int
+ audit_tg(struct sk_buff *skb, const struct xt_action_param *par)
+ {
+ struct audit_buffer *ab;
+- int fam = -1;
+
+ if (audit_enabled == AUDIT_OFF)
+ goto errout;
+@@ -77,27 +41,7 @@ audit_tg(struct sk_buff *skb, const stru
+
+ audit_log_format(ab, "mark=%#x", skb->mark);
+
+- switch (xt_family(par)) {
+- case NFPROTO_BRIDGE:
+- switch (eth_hdr(skb)->h_proto) {
+- case htons(ETH_P_IP):
+- fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1;
+- break;
+- case htons(ETH_P_IPV6):
+- fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1;
+- break;
+- }
+- break;
+- case NFPROTO_IPV4:
+- fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1;
+- break;
+- case NFPROTO_IPV6:
+- fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1;
+- break;
+- }
+-
+- if (fam == -1)
+- audit_log_format(ab, " saddr=? daddr=? proto=-1");
++ audit_log_nf_skb(ab, skb, xt_family(par));
+
+ audit_log_end(ab);
+
--- /dev/null
+From stable+bounces-278316-greg=kroah.com@vger.kernel.org Tue Jul 21 03:28:03 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 21:27:51 -0400
+Subject: audit: fix potential integer overflow in audit_log_n_hex()
+To: stable@vger.kernel.org
+Cc: Ricardo Robaina <rrobaina@redhat.com>, Richard Guy Briggs <rgb@redhat.com>, Paul Moore <paul@paul-moore.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721012751.3458464-2-sashal@kernel.org>
+
+From: Ricardo Robaina <rrobaina@redhat.com>
+
+[ Upstream commit 65dfde57d1e29ce2b76fc23dd565eccd5c0bc0f0 ]
+
+The function calculates new_len as len << 1 for hex encoding. This
+has two overflow risks: the shift itself can overflow when len is
+large, and the result can be truncated when assigned to new_len
+(declared as int) from the size_t calculation.
+
+Fix by using check_shl_overflow() to catch shift overflow and
+changing new_len and loop counter i to size_t to prevent truncation.
+
+Cc: stable@vger.kernel.org
+Fixes: 168b7173959f ("AUDIT: Clean up logging of untrusted strings")
+Reviewed-by: Richard Guy Briggs <rgb@redhat.com>
+Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
+[PM: remove vertical whitspace noise]
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/audit.c | 11 +++++++++--
+ 1 file changed, 9 insertions(+), 2 deletions(-)
+
+--- a/kernel/audit.c
++++ b/kernel/audit.c
+@@ -61,6 +61,7 @@
+ #include <net/netns/generic.h>
+ #include <net/ip.h>
+ #include <net/ipv6.h>
++#include <linux/overflow.h>
+
+ #include "audit.h"
+
+@@ -2035,7 +2036,8 @@ void audit_log_format(struct audit_buffe
+ void audit_log_n_hex(struct audit_buffer *ab, const unsigned char *buf,
+ size_t len)
+ {
+- int i, avail, new_len;
++ int avail;
++ size_t i, new_len;
+ unsigned char *ptr;
+ struct sk_buff *skb;
+
+@@ -2045,7 +2047,12 @@ void audit_log_n_hex(struct audit_buffer
+ BUG_ON(!ab->skb);
+ skb = ab->skb;
+ avail = skb_tailroom(skb);
+- new_len = len<<1;
++
++ if (check_shl_overflow(len, 1, &new_len)) {
++ audit_log_format(ab, "?");
++ return;
++ }
++
+ if (new_len >= avail) {
+ /* Round the buffer request up to the next multiple */
+ new_len = AUDIT_BUFSIZ*(((new_len-avail)/AUDIT_BUFSIZ) + 1);
--- /dev/null
+From stable+bounces-274061-greg=kroah.com@vger.kernel.org Tue Jul 14 03:16:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 21:16:30 -0400
+Subject: bitops: make BYTES_TO_BITS() treewide-available
+To: stable@vger.kernel.org
+Cc: Alexander Lobakin <aleksander.lobakin@intel.com>, Andy Shevchenko <andriy.shevchenko@linux.intel.com>, Przemek Kitszel <przemyslaw.kitszel@intel.com>, Yury Norov <yury.norov@gmail.com>, "David S. Miller" <davem@davemloft.net>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714011631.2188967-1-sashal@kernel.org>
+
+From: Alexander Lobakin <aleksander.lobakin@intel.com>
+
+[ Upstream commit 7d8296b250f2eed73f1758607926d4d258dea5d4 ]
+
+Avoid open-coding that simple expression each time by moving
+BYTES_TO_BITS() from the probes code to <linux/bitops.h> to export
+it to the rest of the kernel.
+Simplify the macro while at it. `BITS_PER_LONG / sizeof(long)` always
+equals to %BITS_PER_BYTE, regardless of the target architecture.
+Do the same for the tools ecosystem as well (incl. its version of
+bitops.h). The previous implementation had its implicit type of long,
+while the new one is int, so adjust the format literal accordingly in
+the perf code.
+
+Suggested-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
+Acked-by: Yury Norov <yury.norov@gmail.com>
+Signed-off-by: Alexander Lobakin <aleksander.lobakin@intel.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Stable-dep-of: 55052184ac90 ("iio: common: st_sensors: honour channel endianness in read_axis_data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/bitops.h | 2 ++
+ kernel/trace/trace_probe.c | 2 --
+ tools/include/linux/bitops.h | 2 ++
+ tools/perf/util/probe-finder.c | 4 +---
+ 4 files changed, 5 insertions(+), 5 deletions(-)
+
+--- a/include/linux/bitops.h
++++ b/include/linux/bitops.h
+@@ -21,6 +21,8 @@
+ #define BITS_TO_U32(nr) __KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(u32))
+ #define BITS_TO_BYTES(nr) __KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(char))
+
++#define BYTES_TO_BITS(nb) ((nb) * BITS_PER_BYTE)
++
+ extern unsigned int __sw_hweight8(unsigned int w);
+ extern unsigned int __sw_hweight16(unsigned int w);
+ extern unsigned int __sw_hweight32(unsigned int w);
+--- a/kernel/trace/trace_probe.c
++++ b/kernel/trace/trace_probe.c
+@@ -535,8 +535,6 @@ parse_probe_arg(char *arg, const struct
+ return ret;
+ }
+
+-#define BYTES_TO_BITS(nb) ((BITS_PER_LONG * (nb)) / sizeof(long))
+-
+ /* Bitfield type needs to be parsed into a fetch function */
+ static int __parse_bitfield_probe_arg(const char *bf,
+ const struct fetch_type *t,
+--- a/tools/include/linux/bitops.h
++++ b/tools/include/linux/bitops.h
+@@ -20,6 +20,8 @@
+ #define BITS_TO_U32(nr) DIV_ROUND_UP(nr, BITS_PER_TYPE(u32))
+ #define BITS_TO_BYTES(nr) DIV_ROUND_UP(nr, BITS_PER_TYPE(char))
+
++#define BYTES_TO_BITS(nb) ((nb) * BITS_PER_BYTE)
++
+ extern unsigned int __sw_hweight8(unsigned int w);
+ extern unsigned int __sw_hweight16(unsigned int w);
+ extern unsigned int __sw_hweight32(unsigned int w);
+--- a/tools/perf/util/probe-finder.c
++++ b/tools/perf/util/probe-finder.c
+@@ -304,8 +304,6 @@ static_var:
+ return ret2;
+ }
+
+-#define BYTES_TO_BITS(nb) ((nb) * BITS_PER_LONG / sizeof(long))
+-
+ static int convert_variable_type(Dwarf_Die *vr_die,
+ struct probe_trace_arg *tvar,
+ const char *cast, bool user_access)
+@@ -335,7 +333,7 @@ static int convert_variable_type(Dwarf_D
+ total = dwarf_bytesize(vr_die);
+ if (boffs < 0 || total < 0)
+ return -ENOENT;
+- ret = snprintf(buf, 16, "b%d@%d/%zd", bsize, boffs,
++ ret = snprintf(buf, 16, "b%d@%d/%d", bsize, boffs,
+ BYTES_TO_BITS(total));
+ goto formatted;
+ }
--- /dev/null
+From stable+bounces-275091-greg=kroah.com@vger.kernel.org Thu Jul 16 01:56:04 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 19:55:18 -0400
+Subject: Bluetooth: 6lowpan: fix cyclic locking warning on netdev unregister
+To: stable@vger.kernel.org
+Cc: Pauli Virtanen <pav@iki.fi>, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715235519.525147-4-sashal@kernel.org>
+
+From: Pauli Virtanen <pav@iki.fi>
+
+[ Upstream commit 9707a015fe8f3ba8ec7c270f3b2b8efb38823d6b ]
+
+6lowpan.c has theoretically conflicting lock orderings, which lockdep
+complains about:
+
+ a) rtnl_lock > hdev->workqueue
+
+ from 6lowpan.c:delete_netdev -> rtnl_lock -> device_del
+ -> put_device(parent) -> hci_release_dev -> destroy_workqueue
+
+ b) hdev->workqueue > l2cap_conn->lock > chan->lock > rtnl_lock
+
+ from hci_rx_work -> 6lowpan.c:chan_ready_cb
+ -> lowpan_register_netdev, ifup -> rtnl_lock
+
+Actual deadlock appears not possible, as hci_rx_work is disabled and
+l2cap_conn flushed already on hdev unregister. Hence, do minimal thing
+to make lockdep happy by breaking chain a) by holding hdev refcount
+until after netdev put in 6lowpan.c.
+
+Fixes the lockdep complaint:
+WARNING: possible circular locking dependency detected.
+kworker/0:1/11 is trying to acquire lock:
+ffff8880023b3940 ((wq_completion)hci0#2){+.+.}-{0:0}, at: touch_wq_lockdep_map+0x8b/0x130
+but task is already holding lock:
+ffffffff95e4f9c0 (rtnl_mutex){+.+.}-{4:4}, at: lowpan_unregister_netdev+0xd/0x30
+Workqueue: events delete_netdev
+
+Signed-off-by: Pauli Virtanen <pav@iki.fi>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Stable-dep-of: 6fef032af009 ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/bluetooth/6lowpan.c | 25 +++++++++++++++++++++++--
+ 1 file changed, 23 insertions(+), 2 deletions(-)
+
+--- a/net/bluetooth/6lowpan.c
++++ b/net/bluetooth/6lowpan.c
+@@ -759,13 +759,33 @@ static inline struct l2cap_chan *chan_ne
+ return chan;
+ }
+
++static void unregister_dev(struct lowpan_btle_dev *dev)
++{
++ struct hci_dev *hdev = READ_ONCE(dev->hdev);
++
++ /* If netdev holds last reference to hci_dev (its parent device), this
++ * leads to theoretical cyclic locking on lowpan_unregister_netdev:
++ *
++ * rtnl_lock -> put_device(parent) -> hci_release_dev ->
++ * destroy_workqueue -> hci_rx_work -> l2cap_recv_acldata ->
++ * chan_ready_cb -> ifup -> rtnl_lock
++ *
++ * However, hci_rx_work is disabled in hci_unregister_dev, so this
++ * should not occur. Make lockdep happy by postponing hdev release after
++ * netdev put.
++ */
++ hci_dev_hold(hdev);
++ lowpan_unregister_netdev(dev->netdev);
++ hci_dev_put(hdev);
++}
++
+ static void delete_netdev(struct work_struct *work)
+ {
+ struct lowpan_btle_dev *entry = container_of(work,
+ struct lowpan_btle_dev,
+ delete_netdev);
+
+- lowpan_unregister_netdev(entry->netdev);
++ unregister_dev(entry);
+
+ /* The entry pointer is deleted by the netdev destructor. */
+ }
+@@ -1245,6 +1265,7 @@ static void disconnect_devices(void)
+ break;
+
+ new_dev->netdev = entry->netdev;
++ new_dev->hdev = entry->hdev;
+ INIT_LIST_HEAD(&new_dev->list);
+
+ list_add_rcu(&new_dev->list, &devices);
+@@ -1256,7 +1277,7 @@ static void disconnect_devices(void)
+ ifdown(entry->netdev);
+ BT_DBG("Unregistering netdev %s %p",
+ entry->netdev->name, entry->netdev);
+- lowpan_unregister_netdev(entry->netdev);
++ unregister_dev(entry);
+ kfree(entry);
+ }
+ }
--- /dev/null
+From stable+bounces-275112-greg=kroah.com@vger.kernel.org Thu Jul 16 03:16:04 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 21:15:55 -0400
+Subject: Bluetooth: hci_core: Enable buffer flow control for SCO/eSCO
+To: stable@vger.kernel.org
+Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Pauli Virtanen <pav@iki.fi>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716011555.683225-1-sashal@kernel.org>
+
+From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+
+[ Upstream commit 13218453521d75916dfed55efb8e809bfc03cb4b ]
+
+This enables buffer flow control for SCO/eSCO
+(see: Bluetooth Core 6.0 spec: 6.22. Synchronous Flow Control Enable),
+recently this has caused the following problem and is actually a nice
+addition for the likes of Socket TX complete:
+
+< HCI Command: Read Buffer Size (0x04|0x0005) plen 0
+> HCI Event: Command Complete (0x0e) plen 11
+ Read Buffer Size (0x04|0x0005) ncmd 1
+ Status: Success (0x00)
+ ACL MTU: 1021 ACL max packet: 5
+ SCO MTU: 240 SCO max packet: 8
+...
+< SCO Data TX: Handle 257 flags 0x00 dlen 120
+< SCO Data TX: Handle 257 flags 0x00 dlen 120
+< SCO Data TX: Handle 257 flags 0x00 dlen 120
+< SCO Data TX: Handle 257 flags 0x00 dlen 120
+< SCO Data TX: Handle 257 flags 0x00 dlen 120
+< SCO Data TX: Handle 257 flags 0x00 dlen 120
+< SCO Data TX: Handle 257 flags 0x00 dlen 120
+< SCO Data TX: Handle 257 flags 0x00 dlen 120
+< SCO Data TX: Handle 257 flags 0x00 dlen 120
+> HCI Event: Hardware Error (0x10) plen 1
+ Code: 0x0a
+
+To fix the code will now attempt to enable buffer flow control when
+HCI_QUIRK_SYNC_FLOWCTL_SUPPORTED is set by the driver:
+
+< HCI Command: Write Sync Fl.. (0x03|0x002f) plen 1
+ Flow control: Enabled (0x01)
+> HCI Event: Command Complete (0x0e) plen 4
+ Write Sync Flow Control Enable (0x03|0x002f) ncmd 1
+ Status: Success (0x00)
+
+On success then HCI_SCO_FLOWCTL would be set which indicates sco_cnt
+shall be used for flow contro.
+
+Fixes: 7fedd3bb6b77 ("Bluetooth: Prioritize SCO traffic")
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Tested-by: Pauli Virtanen <pav@iki.fi>
+Stable-dep-of: 12917f591cea ("Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/bluetooth/hci.h | 13 ++++++++
+ include/net/bluetooth/hci_core.h | 1
+ net/bluetooth/hci_core.c | 62 +++++++++++++++++----------------------
+ net/bluetooth/hci_event.c | 2 +
+ net/bluetooth/hci_sync.c | 24 +++++++++++++++
+ 5 files changed, 68 insertions(+), 34 deletions(-)
+
+--- a/include/net/bluetooth/hci.h
++++ b/include/net/bluetooth/hci.h
+@@ -228,6 +228,13 @@ enum {
+ */
+ HCI_QUIRK_WIDEBAND_SPEECH_SUPPORTED,
+
++ /* When this quirk is set consider Sync Flow Control as supported by
++ * the driver.
++ *
++ * This quirk must be set before hci_register_dev is called.
++ */
++ HCI_QUIRK_SYNC_FLOWCTL_SUPPORTED,
++
+ /* When this quirk is set, the controller has validated that
+ * LE states reported through the HCI_LE_READ_SUPPORTED_STATES are
+ * valid. This mechanism is necessary as many controllers have
+@@ -396,6 +403,7 @@ enum {
+ HCI_WIDEBAND_SPEECH_ENABLED,
+ HCI_EVENT_FILTER_CONFIGURED,
+ HCI_PA_SYNC,
++ HCI_SCO_FLOWCTL,
+
+ HCI_DUT_MODE,
+ HCI_VENDOR_DIAG,
+@@ -1538,6 +1546,11 @@ struct hci_rp_read_tx_power {
+ __s8 tx_power;
+ } __packed;
+
++#define HCI_OP_WRITE_SYNC_FLOWCTL 0x0c2f
++struct hci_cp_write_sync_flowctl {
++ __u8 enable;
++} __packed;
++
+ #define HCI_OP_READ_PAGE_SCAN_TYPE 0x0c46
+ struct hci_rp_read_page_scan_type {
+ __u8 status;
+--- a/include/net/bluetooth/hci_core.h
++++ b/include/net/bluetooth/hci_core.h
+@@ -1659,6 +1659,7 @@ void hci_conn_del_sysfs(struct hci_conn
+ #define lmp_hold_capable(dev) ((dev)->features[0][0] & LMP_HOLD)
+ #define lmp_sniff_capable(dev) ((dev)->features[0][0] & LMP_SNIFF)
+ #define lmp_park_capable(dev) ((dev)->features[0][1] & LMP_PARK)
++#define lmp_sco_capable(dev) ((dev)->features[0][1] & LMP_SCO)
+ #define lmp_inq_rssi_capable(dev) ((dev)->features[0][3] & LMP_RSSI_INQ)
+ #define lmp_esco_capable(dev) ((dev)->features[0][3] & LMP_ESCO)
+ #define lmp_bredr_capable(dev) (!((dev)->features[0][4] & LMP_NO_BREDR))
+--- a/net/bluetooth/hci_core.c
++++ b/net/bluetooth/hci_core.c
+@@ -3586,42 +3586,27 @@ static void __check_timeout(struct hci_d
+ }
+
+ /* Schedule SCO */
+-static void hci_sched_sco(struct hci_dev *hdev)
++static void hci_sched_sco(struct hci_dev *hdev, __u8 type)
+ {
+ struct hci_conn *conn;
+ struct sk_buff *skb;
+- int quote;
++ int quote, *cnt;
++ unsigned int pkts = hdev->sco_pkts;
+
+- BT_DBG("%s", hdev->name);
++ bt_dev_dbg(hdev, "type %u", type);
+
+- if (!hci_conn_num(hdev, SCO_LINK))
++ if (!hci_conn_num(hdev, type) || !pkts)
+ return;
+
+- while (hdev->sco_cnt && (conn = hci_low_sent(hdev, SCO_LINK, "e))) {
+- while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
+- BT_DBG("skb %p len %d", skb, skb->len);
+- hci_send_frame(hdev, skb);
+-
+- conn->sent++;
+- if (conn->sent == ~0)
+- conn->sent = 0;
+- }
+- }
+-}
+-
+-static void hci_sched_esco(struct hci_dev *hdev)
+-{
+- struct hci_conn *conn;
+- struct sk_buff *skb;
+- int quote;
+-
+- BT_DBG("%s", hdev->name);
+-
+- if (!hci_conn_num(hdev, ESCO_LINK))
+- return;
++ /* Use sco_pkts if flow control has not been enabled which will limit
++ * the amount of buffer sent in a row.
++ */
++ if (!hci_dev_test_flag(hdev, HCI_SCO_FLOWCTL))
++ cnt = &pkts;
++ else
++ cnt = &hdev->sco_cnt;
+
+- while (hdev->sco_cnt && (conn = hci_low_sent(hdev, ESCO_LINK,
+- "e))) {
++ while (*cnt && (conn = hci_low_sent(hdev, type, "e))) {
+ while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
+ BT_DBG("skb %p len %d", skb, skb->len);
+ hci_send_frame(hdev, skb);
+@@ -3629,8 +3614,17 @@ static void hci_sched_esco(struct hci_de
+ conn->sent++;
+ if (conn->sent == ~0)
+ conn->sent = 0;
++ (*cnt)--;
+ }
+ }
++
++ /* Rescheduled if all packets were sent and flow control is not enabled
++ * as there could be more packets queued that could not be sent and
++ * since no HCI_EV_NUM_COMP_PKTS event will be generated the reschedule
++ * needs to be forced.
++ */
++ if (!pkts && !hci_dev_test_flag(hdev, HCI_SCO_FLOWCTL))
++ queue_work(hdev->workqueue, &hdev->tx_work);
+ }
+
+ static void hci_sched_acl_pkt(struct hci_dev *hdev)
+@@ -3666,8 +3660,8 @@ static void hci_sched_acl_pkt(struct hci
+ chan->conn->sent++;
+
+ /* Send pending SCO packets right away */
+- hci_sched_sco(hdev);
+- hci_sched_esco(hdev);
++ hci_sched_sco(hdev, SCO_LINK);
++ hci_sched_sco(hdev, ESCO_LINK);
+ }
+ }
+
+@@ -3788,8 +3782,8 @@ static void hci_sched_le(struct hci_dev
+ chan->conn->sent++;
+
+ /* Send pending SCO packets right away */
+- hci_sched_sco(hdev);
+- hci_sched_esco(hdev);
++ hci_sched_sco(hdev, SCO_LINK);
++ hci_sched_sco(hdev, ESCO_LINK);
+ }
+ }
+
+@@ -3834,8 +3828,8 @@ static void hci_tx_work(struct work_stru
+
+ if (!hci_dev_test_flag(hdev, HCI_USER_CHANNEL)) {
+ /* Schedule queues and send stuff to HCI driver */
+- hci_sched_sco(hdev);
+- hci_sched_esco(hdev);
++ hci_sched_sco(hdev, SCO_LINK);
++ hci_sched_sco(hdev, ESCO_LINK);
+ hci_sched_iso(hdev);
+ hci_sched_acl(hdev);
+ hci_sched_le(hdev);
+--- a/net/bluetooth/hci_event.c
++++ b/net/bluetooth/hci_event.c
+@@ -4463,9 +4463,11 @@ static void hci_num_comp_pkts_evt(struct
+ break;
+
+ case SCO_LINK:
++ case ESCO_LINK:
+ hdev->sco_cnt += count;
+ if (hdev->sco_cnt > hdev->sco_pkts)
+ hdev->sco_cnt = hdev->sco_pkts;
++
+ break;
+
+ case ISO_LINK:
+--- a/net/bluetooth/hci_sync.c
++++ b/net/bluetooth/hci_sync.c
+@@ -3854,6 +3854,28 @@ static int hci_write_ca_timeout_sync(str
+ sizeof(param), ¶m, HCI_CMD_TIMEOUT);
+ }
+
++/* Enable SCO flow control if supported */
++static int hci_write_sync_flowctl_sync(struct hci_dev *hdev)
++{
++ struct hci_cp_write_sync_flowctl cp;
++ int err;
++
++ /* Check if the controller supports SCO and HCI_OP_WRITE_SYNC_FLOWCTL */
++ if (!lmp_sco_capable(hdev) || !(hdev->commands[10] & BIT(4)) ||
++ !test_bit(HCI_QUIRK_SYNC_FLOWCTL_SUPPORTED, &hdev->quirks))
++ return 0;
++
++ memset(&cp, 0, sizeof(cp));
++ cp.enable = 0x01;
++
++ err = __hci_cmd_sync_status(hdev, HCI_OP_WRITE_SYNC_FLOWCTL,
++ sizeof(cp), &cp, HCI_CMD_TIMEOUT);
++ if (!err)
++ hci_dev_set_flag(hdev, HCI_SCO_FLOWCTL);
++
++ return err;
++}
++
+ /* BR Controller init stage 2 command sequence */
+ static const struct hci_init_stage br_init2[] = {
+ /* HCI_OP_READ_BUFFER_SIZE */
+@@ -3872,6 +3894,8 @@ static const struct hci_init_stage br_in
+ HCI_INIT(hci_clear_event_filter_sync),
+ /* HCI_OP_WRITE_CA_TIMEOUT */
+ HCI_INIT(hci_write_ca_timeout_sync),
++ /* HCI_OP_WRITE_SYNC_FLOWCTL */
++ HCI_INIT(hci_write_sync_flowctl_sync),
+ {}
+ };
+
--- /dev/null
+From stable+bounces-275096-greg=kroah.com@vger.kernel.org Thu Jul 16 01:56:15 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 19:55:25 -0400
+Subject: Bluetooth: L2CAP: cancel pending_rx_work before taking conn->lock
+To: stable@vger.kernel.org
+Cc: Runyu Xiao <runyu.xiao@seu.edu.cn>, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715235525.525483-3-sashal@kernel.org>
+
+From: Runyu Xiao <runyu.xiao@seu.edu.cn>
+
+[ Upstream commit 2641a9e0a1dd4af2e21995470a21d55dd35e5203 ]
+
+l2cap_conn_del() takes conn->lock and then calls cancel_work_sync() for
+pending_rx_work. process_pending_rx() takes the same mutex, so teardown
+can deadlock against the worker it is flushing.
+
+This issue was found by our static analysis tool and then manually
+reviewed against the current tree.
+
+The grounded PoC kept the l2cap_conn_ready() -> queue_work(...,
+&conn->pending_rx_work) submit path, the l2cap_conn_del() ->
+cancel_work_sync(&conn->pending_rx_work) teardown path, and the
+process_pending_rx() -> mutex_lock(&conn->lock) worker edge. Lockdep
+reported:
+
+ WARNING: possible circular locking dependency detected
+ process_pending_rx+0x21/0x2a [vuln_msv]
+ l2cap_conn_del.constprop.0+0x3f/0x4e [vuln_msv]
+ *** DEADLOCK ***
+
+Cancel pending_rx_work before taking conn->lock, matching the existing
+lock-before-drain ordering used for the two delayed works in the same
+teardown path. The pending_rx queue is still purged after the work has
+been cancelled and conn->lock has been acquired.
+
+Fixes: 7ab56c3a6ecc ("Bluetooth: Fix deadlock in l2cap_conn_del()")
+Cc: stable@vger.kernel.org
+Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/bluetooth/l2cap_core.c | 10 ++--------
+ 1 file changed, 2 insertions(+), 8 deletions(-)
+
+--- a/net/bluetooth/l2cap_core.c
++++ b/net/bluetooth/l2cap_core.c
+@@ -1785,17 +1785,11 @@ static void l2cap_conn_del(struct hci_co
+ cancel_delayed_work_sync(&conn->info_timer);
+ cancel_work_sync(&conn->id_addr_update_work);
+
++ cancel_work_sync(&conn->pending_rx_work);
++
+ kfree_skb(conn->rx_skb);
+
+ skb_queue_purge(&conn->pending_rx);
+-
+- /* We can not call flush_work(&conn->pending_rx_work) here since we
+- * might block if we are running on a worker from the same workqueue
+- * pending_rx_work is waiting on.
+- */
+- if (work_pending(&conn->pending_rx_work))
+- cancel_work_sync(&conn->pending_rx_work);
+-
+ ida_destroy(&conn->tx_ida);
+
+ l2cap_unregister_all_users(conn);
--- /dev/null
+From stable+bounces-275095-greg=kroah.com@vger.kernel.org Thu Jul 16 01:56:12 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 19:55:24 -0400
+Subject: Bluetooth: L2CAP: Fix deadlock in l2cap_conn_del()
+To: stable@vger.kernel.org
+Cc: Hyunwoo Kim <imv4bel@gmail.com>, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715235525.525483-2-sashal@kernel.org>
+
+From: Hyunwoo Kim <imv4bel@gmail.com>
+
+[ Upstream commit 00fdebbbc557a2fc21321ff2eaa22fd70c078608 ]
+
+l2cap_conn_del() calls cancel_delayed_work_sync() for both info_timer
+and id_addr_timer while holding conn->lock. However, the work functions
+l2cap_info_timeout() and l2cap_conn_update_id_addr() both acquire
+conn->lock, creating a potential AB-BA deadlock if the work is already
+executing when l2cap_conn_del() takes the lock.
+
+Move the work cancellations before acquiring conn->lock and use
+disable_delayed_work_sync() to additionally prevent the works from
+being rearmed after cancellation, consistent with the pattern used in
+hci_conn_del().
+
+Fixes: ab4eedb790ca ("Bluetooth: L2CAP: Fix corrupted list in hci_chan_del")
+Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Stable-dep-of: 2641a9e0a1dd ("Bluetooth: L2CAP: cancel pending_rx_work before taking conn->lock")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/bluetooth/l2cap_core.c | 9 +++------
+ 1 file changed, 3 insertions(+), 6 deletions(-)
+
+--- a/net/bluetooth/l2cap_core.c
++++ b/net/bluetooth/l2cap_core.c
+@@ -1782,6 +1782,9 @@ static void l2cap_conn_del(struct hci_co
+
+ BT_DBG("hcon %p conn %p, err %d", hcon, conn, err);
+
++ cancel_delayed_work_sync(&conn->info_timer);
++ cancel_work_sync(&conn->id_addr_update_work);
++
+ kfree_skb(conn->rx_skb);
+
+ skb_queue_purge(&conn->pending_rx);
+@@ -1795,9 +1798,6 @@ static void l2cap_conn_del(struct hci_co
+
+ ida_destroy(&conn->tx_ida);
+
+- if (work_pending(&conn->id_addr_update_work))
+- cancel_work_sync(&conn->id_addr_update_work);
+-
+ l2cap_unregister_all_users(conn);
+
+ /* Force the connection to be immediately dropped */
+@@ -1822,9 +1822,6 @@ static void l2cap_conn_del(struct hci_co
+
+ hci_chan_del(conn->hchan);
+
+- if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT)
+- cancel_delayed_work_sync(&conn->info_timer);
+-
+ hcon->l2cap_data = NULL;
+ conn->hchan = NULL;
+ l2cap_conn_put(conn);
--- /dev/null
+From stable+bounces-275094-greg=kroah.com@vger.kernel.org Thu Jul 16 01:56:11 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 19:55:23 -0400
+Subject: Bluetooth: L2CAP: Fix not tracking outstanding TX ident
+To: stable@vger.kernel.org
+Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Paul Menzel <pmenzel@molgen.mpg.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715235525.525483-1-sashal@kernel.org>
+
+From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+
+[ Upstream commit 6c3ea155e5ee3e56606233acde8309afda66d483 ]
+
+This attempts to proper track outstanding request by using struct ida
+and allocating from it in l2cap_get_ident using ida_alloc_range which
+would reuse ids as they are free, then upon completion release
+the id using ida_free.
+
+This fixes the qualification test case L2CAP/COS/CED/BI-29-C which
+attempts to check if the host stack is able to work after 256 attempts
+to connect which requires Ident field to use the full range of possible
+values in order to pass the test.
+
+Link: https://github.com/bluez/bluez/issues/1829
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>
+Stable-dep-of: 2641a9e0a1dd ("Bluetooth: L2CAP: cancel pending_rx_work before taking conn->lock")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/bluetooth/l2cap.h | 3 --
+ net/bluetooth/l2cap_core.c | 53 ++++++++++++++++++++++++++++++------------
+ 2 files changed, 40 insertions(+), 16 deletions(-)
+
+--- a/include/net/bluetooth/l2cap.h
++++ b/include/net/bluetooth/l2cap.h
+@@ -653,8 +653,7 @@ struct l2cap_conn {
+
+ struct sk_buff *rx_skb;
+ __u32 rx_len;
+- __u8 tx_ident;
+- struct mutex ident_lock;
++ struct ida tx_ida;
+
+ struct sk_buff_head pending_rx;
+ struct work_struct pending_rx_work;
+--- a/net/bluetooth/l2cap_core.c
++++ b/net/bluetooth/l2cap_core.c
+@@ -958,26 +958,18 @@ int l2cap_chan_check_security(struct l2c
+ initiator);
+ }
+
+-static u8 l2cap_get_ident(struct l2cap_conn *conn)
++static int l2cap_get_ident(struct l2cap_conn *conn)
+ {
+- u8 id;
++ /* LE link does not support tools like l2ping so use the full range */
++ if (conn->hcon->type == LE_LINK)
++ return ida_alloc_range(&conn->tx_ida, 1, 255, GFP_ATOMIC);
+
+ /* Get next available identificator.
+ * 1 - 128 are used by kernel.
+ * 129 - 199 are reserved.
+ * 200 - 254 are used by utilities like l2ping, etc.
+ */
+-
+- mutex_lock(&conn->ident_lock);
+-
+- if (++conn->tx_ident > 128)
+- conn->tx_ident = 1;
+-
+- id = conn->tx_ident;
+-
+- mutex_unlock(&conn->ident_lock);
+-
+- return id;
++ return ida_alloc_range(&conn->tx_ida, 1, 128, GFP_ATOMIC);
+ }
+
+ static void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len,
+@@ -1801,6 +1793,8 @@ static void l2cap_conn_del(struct hci_co
+ if (work_pending(&conn->pending_rx_work))
+ cancel_work_sync(&conn->pending_rx_work);
+
++ ida_destroy(&conn->tx_ida);
++
+ if (work_pending(&conn->id_addr_update_work))
+ cancel_work_sync(&conn->id_addr_update_work);
+
+@@ -4875,12 +4869,41 @@ unlock:
+ return err;
+ }
+
++static void l2cap_put_ident(struct l2cap_conn *conn, u8 code, u8 id)
++{
++ int ret;
++
++ switch (code) {
++ case L2CAP_COMMAND_REJ:
++ case L2CAP_CONN_RSP:
++ case L2CAP_CONF_RSP:
++ case L2CAP_DISCONN_RSP:
++ case L2CAP_ECHO_RSP:
++ case L2CAP_INFO_RSP:
++ case L2CAP_CONN_PARAM_UPDATE_RSP:
++ case L2CAP_ECRED_CONN_RSP:
++ case L2CAP_ECRED_RECONF_RSP:
++ /* The remote may send bogus ids that would make ida_free
++ * generate warnings, so only free ids that are actually
++ * allocated: probing the exact id returns -ENOSPC when it
++ * is in use, otherwise the probe allocated it and freeing
++ * is safe either way. Only on -ENOMEM is the id known to
++ * be unallocated and the free must be skipped.
++ */
++ ret = ida_alloc_range(&conn->tx_ida, id, id, GFP_ATOMIC);
++ if (ret >= 0 || ret == -ENOSPC)
++ ida_free(&conn->tx_ida, id);
++ }
++}
++
+ static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn,
+ struct l2cap_cmd_hdr *cmd, u16 cmd_len,
+ u8 *data)
+ {
+ int err = 0;
+
++ l2cap_put_ident(conn, cmd->code, cmd->ident);
++
+ switch (cmd->code) {
+ case L2CAP_COMMAND_REJ:
+ l2cap_command_rej(conn, cmd, cmd_len, data);
+@@ -5602,6 +5625,8 @@ static inline int l2cap_le_sig_cmd(struc
+ {
+ int err = 0;
+
++ l2cap_put_ident(conn, cmd->code, cmd->ident);
++
+ switch (cmd->code) {
+ case L2CAP_COMMAND_REJ:
+ l2cap_le_command_rej(conn, cmd, cmd_len, data);
+@@ -7157,13 +7182,13 @@ static struct l2cap_conn *l2cap_conn_add
+ hci_dev_test_flag(hcon->hdev, HCI_FORCE_BREDR_SMP)))
+ conn->local_fixed_chan |= L2CAP_FC_SMP_BREDR;
+
+- mutex_init(&conn->ident_lock);
+ mutex_init(&conn->chan_lock);
+
+ INIT_LIST_HEAD(&conn->chan_l);
+ INIT_LIST_HEAD(&conn->users);
+
+ INIT_DELAYED_WORK(&conn->info_timer, l2cap_info_timeout);
++ ida_init(&conn->tx_ida);
+
+ skb_queue_head_init(&conn->pending_rx);
+ INIT_WORK(&conn->pending_rx_work, process_pending_rx);
--- /dev/null
+From stable+bounces-275090-greg=kroah.com@vger.kernel.org Thu Jul 16 01:55:35 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 19:55:17 -0400
+Subject: Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_connect()
+To: stable@vger.kernel.org
+Cc: Sungwoo Kim <iam@sung-woo.kim>, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715235519.525147-3-sashal@kernel.org>
+
+From: Sungwoo Kim <iam@sung-woo.kim>
+
+[ Upstream commit 4d7b41c0e43995b0e992b9f8903109275744b658 ]
+
+Extend a critical section to prevent chan from early freeing.
+Also make the l2cap_connect() return type void. Nothing is using the
+returned value but it is ugly to return a potentially freed pointer.
+Making it void will help with backports because earlier kernels did use
+the return value. Now the compile will break for kernels where this
+patch is not a complete fix.
+
+Call stack summary:
+
+[use]
+l2cap_bredr_sig_cmd
+ l2cap_connect
+ ┌ mutex_lock(&conn->chan_lock);
+ │ chan = pchan->ops->new_connection(pchan); <- alloc chan
+ │ __l2cap_chan_add(conn, chan);
+ │ l2cap_chan_hold(chan);
+ │ list_add(&chan->list, &conn->chan_l); ... (1)
+ â”” mutex_unlock(&conn->chan_lock);
+ chan->conf_state ... (4) <- use after free
+
+[free]
+l2cap_conn_del
+┌ mutex_lock(&conn->chan_lock);
+│ foreach chan in conn->chan_l: ... (2)
+│ l2cap_chan_put(chan);
+│ l2cap_chan_destroy
+│ kfree(chan) ... (3) <- chan freed
+â”” mutex_unlock(&conn->chan_lock);
+
+==================================================================
+BUG: KASAN: slab-use-after-free in instrument_atomic_read
+include/linux/instrumented.h:68 [inline]
+BUG: KASAN: slab-use-after-free in _test_bit
+include/asm-generic/bitops/instrumented-non-atomic.h:141 [inline]
+BUG: KASAN: slab-use-after-free in l2cap_connect+0xa67/0x11a0
+net/bluetooth/l2cap_core.c:4260
+Read of size 8 at addr ffff88810bf040a0 by task kworker/u3:1/311
+
+Fixes: 73ffa904b782 ("Bluetooth: Move conf_{req,rsp} stuff to struct l2cap_chan")
+Signed-off-by: Sungwoo Kim <iam@sung-woo.kim>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Stable-dep-of: 6fef032af009 ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/bluetooth/l2cap_core.c | 21 ++++++++++-----------
+ 1 file changed, 10 insertions(+), 11 deletions(-)
+
+--- a/net/bluetooth/l2cap_core.c
++++ b/net/bluetooth/l2cap_core.c
+@@ -3985,13 +3985,12 @@ static inline int l2cap_command_rej(stru
+ return 0;
+ }
+
+-static struct l2cap_chan *l2cap_connect(struct l2cap_conn *conn,
+- struct l2cap_cmd_hdr *cmd,
+- u8 *data, u8 rsp_code, u8 amp_id)
++static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
++ u8 *data, u8 rsp_code, u8 amp_id)
+ {
+ struct l2cap_conn_req *req = (struct l2cap_conn_req *) data;
+ struct l2cap_conn_rsp rsp;
+- struct l2cap_chan *chan = NULL, *pchan;
++ struct l2cap_chan *chan = NULL, *pchan = NULL;
+ int result, status = L2CAP_CS_NO_INFO;
+
+ u16 dcid = 0, scid = __le16_to_cpu(req->scid);
+@@ -4004,7 +4003,7 @@ static struct l2cap_chan *l2cap_connect(
+ &conn->hcon->dst, ACL_LINK);
+ if (!pchan) {
+ result = L2CAP_CR_BAD_PSM;
+- goto sendresp;
++ goto response;
+ }
+
+ mutex_lock(&conn->chan_lock);
+@@ -4092,17 +4091,15 @@ static struct l2cap_chan *l2cap_connect(
+ }
+
+ response:
+- l2cap_chan_unlock(pchan);
+- mutex_unlock(&conn->chan_lock);
+- l2cap_chan_put(pchan);
+-
+-sendresp:
+ rsp.scid = cpu_to_le16(scid);
+ rsp.dcid = cpu_to_le16(dcid);
+ rsp.result = cpu_to_le16(result);
+ rsp.status = cpu_to_le16(status);
+ l2cap_send_cmd(conn, cmd->ident, rsp_code, sizeof(rsp), &rsp);
+
++ if (!pchan)
++ return;
++
+ if (result == L2CAP_CR_PEND && status == L2CAP_CS_NO_INFO) {
+ struct l2cap_info_req info;
+ info.type = cpu_to_le16(L2CAP_IT_FEAT_MASK);
+@@ -4125,7 +4122,9 @@ sendresp:
+ chan->num_conf_req++;
+ }
+
+- return chan;
++ l2cap_chan_unlock(pchan);
++ mutex_unlock(&conn->chan_lock);
++ l2cap_chan_put(pchan);
+ }
+
+ static int l2cap_connect_req(struct l2cap_conn *conn,
--- /dev/null
+From stable+bounces-275092-greg=kroah.com@vger.kernel.org Thu Jul 16 01:56:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 19:55:19 -0400
+Subject: Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()
+To: stable@vger.kernel.org
+Cc: Siwei Zhang <oss@fourdim.xyz>, stable@kernel.org, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715235519.525147-5-sashal@kernel.org>
+
+From: Siwei Zhang <oss@fourdim.xyz>
+
+[ Upstream commit 6fef032af0092ed5ccb767239a9ac1bc38c08a40 ]
+
+l2cap_sock_new_connection_cb() returned l2cap_pi(sk)->chan after
+release_sock(parent). Once the parent lock is dropped the newly
+enqueued child socket sk is reachable via the accept queue, so another
+task can accept and free it before the callback dereferences sk,
+resulting in a use-after-free.
+
+Rework the ->new_connection() op so the core, rather than the callback,
+owns the child channel's lifetime. The op now receives a pre-allocated
+new_chan and returns an errno instead of allocating and returning a
+channel. l2cap_new_connection() allocates the child channel and links
+it into the conn list via __l2cap_chan_add() before invoking the
+callback, so the conn-list reference keeps the channel alive once
+release_sock(parent) exposes the socket to other tasks.
+
+Channel configuration that was duplicated in l2cap_sock_init() and the
+various new_connection callbacks is consolidated into
+l2cap_chan_set_defaults(), which now inherits from the parent channel
+when one is supplied.
+
+Fixes: 8ffb929098a5 ("Bluetooth: Remove parent socket usage from l2cap_core.c")
+Cc: stable@kernel.org
+Assisted-by: Claude:claude-opus-4-8
+Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/bluetooth/l2cap.h | 10 ++--
+ net/bluetooth/6lowpan.c | 18 -------
+ net/bluetooth/l2cap_core.c | 78 ++++++++++++++++++++++++++-----
+ net/bluetooth/l2cap_sock.c | 103 +++++++++++++++++++-----------------------
+ net/bluetooth/smp.c | 27 ++---------
+ 5 files changed, 127 insertions(+), 109 deletions(-)
+
+--- a/include/net/bluetooth/l2cap.h
++++ b/include/net/bluetooth/l2cap.h
+@@ -614,7 +614,8 @@ struct l2cap_chan {
+ struct l2cap_ops {
+ char *name;
+
+- struct l2cap_chan *(*new_connection) (struct l2cap_chan *chan);
++ int (*new_connection)(struct l2cap_chan *chan,
++ struct l2cap_chan *new_chan);
+ int (*recv) (struct l2cap_chan * chan,
+ struct sk_buff *skb);
+ void (*teardown) (struct l2cap_chan *chan, int err);
+@@ -878,9 +879,10 @@ static inline __u16 __next_seq(struct l2
+ return (seq + 1) % (chan->tx_win_max + 1);
+ }
+
+-static inline struct l2cap_chan *l2cap_chan_no_new_connection(struct l2cap_chan *chan)
++static inline int l2cap_chan_no_new_connection(struct l2cap_chan *chan,
++ struct l2cap_chan *new_chan)
+ {
+- return NULL;
++ return -EOPNOTSUPP;
+ }
+
+ static inline int l2cap_chan_no_recv(struct l2cap_chan *chan, struct sk_buff *skb)
+@@ -956,7 +958,7 @@ int l2cap_chan_send(struct l2cap_chan *c
+ void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
+ void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail);
+ int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
+-void l2cap_chan_set_defaults(struct l2cap_chan *chan);
++void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan);
+ int l2cap_ertm_init(struct l2cap_chan *chan);
+ void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan);
+ void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan);
+--- a/net/bluetooth/6lowpan.c
++++ b/net/bluetooth/6lowpan.c
+@@ -631,7 +631,7 @@ static struct l2cap_chan *chan_create(vo
+ if (!chan)
+ return NULL;
+
+- l2cap_chan_set_defaults(chan);
++ l2cap_chan_set_defaults(chan, NULL);
+
+ chan->chan_type = L2CAP_CHAN_CONN_ORIENTED;
+ chan->mode = L2CAP_MODE_LE_FLOWCTL;
+@@ -744,21 +744,6 @@ static inline void chan_ready_cb(struct
+ ifup(dev->netdev);
+ }
+
+-static inline struct l2cap_chan *chan_new_conn_cb(struct l2cap_chan *pchan)
+-{
+- struct l2cap_chan *chan;
+-
+- chan = chan_create();
+- if (!chan)
+- return NULL;
+-
+- chan->ops = pchan->ops;
+-
+- BT_DBG("chan %p pchan %p", chan, pchan);
+-
+- return chan;
+-}
+-
+ static void unregister_dev(struct lowpan_btle_dev *dev)
+ {
+ struct hci_dev *hdev = READ_ONCE(dev->hdev);
+@@ -900,7 +885,6 @@ static long chan_get_sndtimeo_cb(struct
+
+ static const struct l2cap_ops bt_6lowpan_chan_ops = {
+ .name = "L2CAP 6LoWPAN channel",
+- .new_connection = chan_new_conn_cb,
+ .recv = chan_recv_cb,
+ .close = chan_close_cb,
+ .state_change = chan_state_change_cb,
+--- a/net/bluetooth/l2cap_core.c
++++ b/net/bluetooth/l2cap_core.c
+@@ -523,7 +523,10 @@ void l2cap_chan_put(struct l2cap_chan *c
+ }
+ EXPORT_SYMBOL_GPL(l2cap_chan_put);
+
+-void l2cap_chan_set_defaults(struct l2cap_chan *chan)
++/* Initialise @chan with default values, inheriting from the parent channel
++ * @pchan when it is given.
++ */
++void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan)
+ {
+ chan->fcs = L2CAP_FCS_CRC16;
+ chan->max_tx = L2CAP_DEFAULT_MAX_TX;
+@@ -537,6 +540,31 @@ void l2cap_chan_set_defaults(struct l2ca
+ chan->retrans_timeout = L2CAP_DEFAULT_RETRANS_TO;
+ chan->monitor_timeout = L2CAP_DEFAULT_MONITOR_TO;
+
++ if (pchan) {
++ BT_DBG("chan %p pchan %p", chan, pchan);
++
++ chan->chan_type = pchan->chan_type;
++ chan->imtu = pchan->imtu;
++ chan->omtu = pchan->omtu;
++ chan->mode = pchan->mode;
++ chan->fcs = pchan->fcs;
++ chan->max_tx = pchan->max_tx;
++ chan->tx_win = pchan->tx_win;
++ chan->tx_win_max = pchan->tx_win_max;
++ chan->sec_level = pchan->sec_level;
++ chan->conf_state = pchan->conf_state;
++ chan->flags = pchan->flags;
++ chan->tx_credits = pchan->tx_credits;
++ chan->rx_credits = pchan->rx_credits;
++
++ if (chan->chan_type == L2CAP_CHAN_FIXED) {
++ chan->scid = pchan->scid;
++ chan->dcid = pchan->scid;
++ }
++
++ return;
++ }
++
+ chan->conf_state = 0;
+ set_bit(CONF_NOT_COMPLETE, &chan->conf_state);
+
+@@ -3985,6 +4013,38 @@ static inline int l2cap_command_rej(stru
+ return 0;
+ }
+
++/* Allocate and initialise a channel for an incoming connection.
++ *
++ * The channel inherits its configuration from @pchan and is linked into @conn
++ * before ->new_connection() runs, so the conn list reference keeps it alive if
++ * the callback exposes it (e.g. via the socket accept queue) before this
++ * returns. The l2cap_chan_create() reference is taken over by the subsystem on
++ * success and dropped here on failure.
++ */
++static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn,
++ struct l2cap_chan *pchan)
++{
++ struct l2cap_chan *chan;
++
++ chan = l2cap_chan_create();
++ if (!chan)
++ return NULL;
++
++ l2cap_chan_set_defaults(chan, pchan);
++ chan->ops = pchan->ops;
++
++ __l2cap_chan_add(conn, chan);
++
++ if (pchan->ops->new_connection &&
++ pchan->ops->new_connection(pchan, chan) < 0) {
++ l2cap_chan_del(chan, 0);
++ l2cap_chan_put(chan);
++ return NULL;
++ }
++
++ return chan;
++}
++
+ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
+ u8 *data, u8 rsp_code, u8 amp_id)
+ {
+@@ -4032,7 +4092,7 @@ static void l2cap_connect(struct l2cap_c
+ goto response;
+ }
+
+- chan = pchan->ops->new_connection(pchan);
++ chan = l2cap_new_connection(conn, pchan);
+ if (!chan)
+ goto response;
+
+@@ -4050,8 +4110,6 @@ static void l2cap_connect(struct l2cap_c
+ chan->psm = psm;
+ chan->dcid = scid;
+
+- __l2cap_chan_add(conn, chan);
+-
+ dcid = chan->scid;
+
+ __set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
+@@ -4957,7 +5015,7 @@ static int l2cap_le_connect_req(struct l
+ goto response_unlock;
+ }
+
+- chan = pchan->ops->new_connection(pchan);
++ chan = l2cap_new_connection(conn, pchan);
+ if (!chan) {
+ result = L2CAP_CR_LE_NO_MEM;
+ goto response_unlock;
+@@ -4972,8 +5030,6 @@ static int l2cap_le_connect_req(struct l
+ chan->omtu = mtu;
+ chan->remote_mps = mps;
+
+- __l2cap_chan_add(conn, chan);
+-
+ l2cap_le_flowctl_init(chan, __le16_to_cpu(req->credits));
+
+ dcid = chan->scid;
+@@ -5184,7 +5240,7 @@ static inline int l2cap_ecred_conn_req(s
+ continue;
+ }
+
+- chan = pchan->ops->new_connection(pchan);
++ chan = l2cap_new_connection(conn, pchan);
+ if (!chan) {
+ result = L2CAP_CR_LE_NO_MEM;
+ continue;
+@@ -5199,8 +5255,6 @@ static inline int l2cap_ecred_conn_req(s
+ chan->omtu = mtu;
+ chan->remote_mps = mps;
+
+- __l2cap_chan_add(conn, chan);
+-
+ l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
+
+ /* Init response */
+@@ -7486,14 +7540,12 @@ static void l2cap_connect_cfm(struct hci
+ goto next;
+
+ l2cap_chan_lock(pchan);
+- chan = pchan->ops->new_connection(pchan);
++ chan = l2cap_new_connection(conn, pchan);
+ if (chan) {
+ bacpy(&chan->src, &hcon->src);
+ bacpy(&chan->dst, &hcon->dst);
+ chan->src_type = bdaddr_src_type(hcon);
+ chan->dst_type = dst_type;
+-
+- __l2cap_chan_add(conn, chan);
+ }
+
+ l2cap_chan_unlock(pchan);
+--- a/net/bluetooth/l2cap_sock.c
++++ b/net/bluetooth/l2cap_sock.c
+@@ -45,7 +45,8 @@ static struct bt_sock_list l2cap_sk_list
+ static const struct proto_ops l2cap_sock_ops;
+ static void l2cap_sock_init(struct sock *sk, struct sock *parent);
+ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock,
+- int proto, gfp_t prio, int kern);
++ int proto, gfp_t prio, int kern,
++ struct l2cap_chan *chan);
+ static void l2cap_sock_cleanup_listen(struct sock *parent);
+
+ bool l2cap_is_socket(struct socket *sock)
+@@ -1240,6 +1241,23 @@ done:
+ return err;
+ }
+
++/* Release the sock's ref on chan and clear the pointer so that the ref is
++ * dropped exactly once even if both l2cap_sock_kill() and
++ * l2cap_sock_destruct() run. Setting chan->data to NULL first stops any other
++ * task from dereferencing the now-dead sock pointer.
++ */
++static void l2cap_sock_put_chan(struct sock *sk)
++{
++ struct l2cap_chan *chan = l2cap_pi(sk)->chan;
++
++ if (!chan)
++ return;
++
++ chan->data = NULL;
++ l2cap_pi(sk)->chan = NULL;
++ l2cap_chan_put(chan);
++}
++
+ /* Kill socket (only if zapped and orphan)
+ * Must be called on unlocked socket, with l2cap channel lock.
+ */
+@@ -1250,13 +1268,9 @@ static void l2cap_sock_kill(struct sock
+
+ BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state));
+
+- /* Sock is dead, so set chan data to NULL, avoid other task use invalid
+- * sock pointer.
+- */
+- l2cap_pi(sk)->chan->data = NULL;
+- /* Kill poor orphan */
++ l2cap_sock_put_chan(sk);
+
+- l2cap_chan_put(l2cap_pi(sk)->chan);
++ /* Kill poor orphan */
+ sock_set_flag(sk, SOCK_DEAD);
+ sock_put(sk);
+ }
+@@ -1500,12 +1514,13 @@ static void l2cap_sock_cleanup_listen(st
+ }
+ }
+
+-static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)
++static int l2cap_sock_new_connection_cb(struct l2cap_chan *chan,
++ struct l2cap_chan *new_chan)
+ {
+ struct sock *sk, *parent = chan->data;
+
+ if (!parent)
+- return NULL;
++ return -EINVAL;
+
+ lock_sock(parent);
+
+@@ -1513,25 +1528,28 @@ static struct l2cap_chan *l2cap_sock_new
+ if (sk_acceptq_is_full(parent)) {
+ BT_DBG("backlog full %d", parent->sk_ack_backlog);
+ release_sock(parent);
+- return NULL;
++ return -ENOBUFS;
+ }
+
+ sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP,
+- GFP_ATOMIC, 0);
++ GFP_ATOMIC, 0, new_chan);
+ if (!sk) {
+ release_sock(parent);
+- return NULL;
+- }
++ return -ENOMEM;
++ }
+
+ bt_sock_reclassify_lock(sk, BTPROTO_L2CAP);
+
+ l2cap_sock_init(sk, parent);
+
++ /* The conn list reference taken by l2cap_new_connection() keeps new_chan
++ * alive once release_sock() lets another task free this socket.
++ */
+ bt_accept_enqueue(parent, sk, false);
+
+ release_sock(parent);
+
+- return l2cap_pi(sk)->chan;
++ return 0;
+ }
+
+ static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
+@@ -1829,10 +1847,7 @@ static void l2cap_sock_destruct(struct s
+
+ BT_DBG("sk %p", sk);
+
+- if (l2cap_pi(sk)->chan) {
+- l2cap_pi(sk)->chan->data = NULL;
+- l2cap_chan_put(l2cap_pi(sk)->chan);
+- }
++ l2cap_sock_put_chan(sk);
+
+ list_for_each_entry_safe(rx_busy, next, &l2cap_pi(sk)->rx_busy, list) {
+ kfree_skb(rx_busy->skb);
+@@ -1864,30 +1879,12 @@ static void l2cap_sock_init(struct sock
+ BT_DBG("sk %p", sk);
+
+ if (parent) {
+- struct l2cap_chan *pchan = l2cap_pi(parent)->chan;
+-
+ sk->sk_type = parent->sk_type;
+ bt_sk(sk)->flags = bt_sk(parent)->flags;
+
+- chan->chan_type = pchan->chan_type;
+- chan->imtu = pchan->imtu;
+- chan->omtu = pchan->omtu;
+- chan->conf_state = pchan->conf_state;
+- chan->mode = pchan->mode;
+- chan->fcs = pchan->fcs;
+- chan->max_tx = pchan->max_tx;
+- chan->tx_win = pchan->tx_win;
+- chan->tx_win_max = pchan->tx_win_max;
+- chan->sec_level = pchan->sec_level;
+- chan->flags = pchan->flags;
+- chan->tx_credits = pchan->tx_credits;
+- chan->rx_credits = pchan->rx_credits;
+-
+- if (chan->chan_type == L2CAP_CHAN_FIXED) {
+- chan->scid = pchan->scid;
+- chan->dcid = pchan->scid;
+- }
+-
++ /* Channel configuration is inherited from the parent by
++ * l2cap_new_connection().
++ */
+ security_sk_clone(parent, sk);
+ } else {
+ switch (sk->sk_type) {
+@@ -1913,7 +1910,7 @@ static void l2cap_sock_init(struct sock
+ chan->mode = L2CAP_MODE_BASIC;
+ }
+
+- l2cap_chan_set_defaults(chan);
++ l2cap_chan_set_defaults(chan, NULL);
+ }
+
+ /* Default config options */
+@@ -1932,10 +1929,10 @@ static struct proto l2cap_proto = {
+ };
+
+ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock,
+- int proto, gfp_t prio, int kern)
++ int proto, gfp_t prio, int kern,
++ struct l2cap_chan *chan)
+ {
+ struct sock *sk;
+- struct l2cap_chan *chan;
+
+ sk = bt_sock_alloc(net, sock, &l2cap_proto, proto, prio, kern);
+ if (!sk)
+@@ -1946,16 +1943,7 @@ static struct sock *l2cap_sock_alloc(str
+
+ INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy);
+
+- chan = l2cap_chan_create();
+- if (!chan) {
+- sk_free(sk);
+- if (sock)
+- sock->sk = NULL;
+- return NULL;
+- }
+-
+- l2cap_chan_hold(chan);
+-
++ /* The sock takes ownership of the caller's reference on chan. */
+ l2cap_pi(sk)->chan = chan;
+
+ return sk;
+@@ -1965,6 +1953,7 @@ static int l2cap_sock_create(struct net
+ int kern)
+ {
+ struct sock *sk;
++ struct l2cap_chan *chan;
+
+ BT_DBG("sock %p", sock);
+
+@@ -1979,10 +1968,16 @@ static int l2cap_sock_create(struct net
+
+ sock->ops = &l2cap_sock_ops;
+
+- sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern);
+- if (!sk)
++ chan = l2cap_chan_create();
++ if (!chan)
+ return -ENOMEM;
+
++ sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern, chan);
++ if (!sk) {
++ l2cap_chan_put(chan);
++ return -ENOMEM;
++ }
++
+ l2cap_sock_init(sk, NULL);
+ bt_sock_link(&l2cap_sk_list, sk);
+ return 0;
+--- a/net/bluetooth/smp.c
++++ b/net/bluetooth/smp.c
+@@ -3228,34 +3228,19 @@ static const struct l2cap_ops smp_chan_o
+ .get_sndtimeo = l2cap_chan_no_get_sndtimeo,
+ };
+
+-static inline struct l2cap_chan *smp_new_conn_cb(struct l2cap_chan *pchan)
++static inline int smp_new_conn_cb(struct l2cap_chan *chan,
++ struct l2cap_chan *new_chan)
+ {
+- struct l2cap_chan *chan;
+-
+- BT_DBG("pchan %p", pchan);
+-
+- chan = l2cap_chan_create();
+- if (!chan)
+- return NULL;
+-
+- chan->chan_type = pchan->chan_type;
+- chan->ops = &smp_chan_ops;
+- chan->scid = pchan->scid;
+- chan->dcid = chan->scid;
+- chan->imtu = pchan->imtu;
+- chan->omtu = pchan->omtu;
+- chan->mode = pchan->mode;
++ new_chan->ops = &smp_chan_ops;
+
+ /* Other L2CAP channels may request SMP routines in order to
+ * change the security level. This means that the SMP channel
+ * lock must be considered in its own category to avoid lockdep
+ * warnings.
+ */
+- atomic_set(&chan->nesting, L2CAP_NESTING_SMP);
+-
+- BT_DBG("created chan %p", chan);
++ atomic_set(&new_chan->nesting, L2CAP_NESTING_SMP);
+
+- return chan;
++ return 0;
+ }
+
+ static const struct l2cap_ops smp_root_chan_ops = {
+@@ -3326,7 +3311,7 @@ create_chan:
+
+ l2cap_add_scid(chan, cid);
+
+- l2cap_chan_set_defaults(chan);
++ l2cap_chan_set_defaults(chan, NULL);
+
+ if (cid == L2CAP_CID_SMP) {
+ u8 bdaddr_type;
--- /dev/null
+From stable+bounces-275089-greg=kroah.com@vger.kernel.org Thu Jul 16 01:56:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 19:55:15 -0400
+Subject: Bluetooth: Make handle of hci_conn be unique
+To: stable@vger.kernel.org
+Cc: Ziyang Xuan <william.xuanziyang@huawei.com>, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715235519.525147-1-sashal@kernel.org>
+
+From: Ziyang Xuan <william.xuanziyang@huawei.com>
+
+[ Upstream commit 181a42edddf51d5d9697ecdf365d72ebeab5afb0 ]
+
+The handle of new hci_conn is always HCI_CONN_HANDLE_MAX + 1 if
+the handle of the first hci_conn entry in hci_dev->conn_hash->list
+is not HCI_CONN_HANDLE_MAX + 1. Use ida to manage the allocation of
+hci_conn->handle to make it be unique.
+
+Fixes: 9f78191cc9f1 ("Bluetooth: hci_conn: Always allocate unique handles")
+Signed-off-by: Ziyang Xuan <william.xuanziyang@huawei.com>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Stable-dep-of: 6fef032af009 ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/bluetooth/hci_core.h | 8 ++++-
+ net/bluetooth/amp.c | 3 --
+ net/bluetooth/hci_conn.c | 45 +++++++++++++++++++++++--------
+ net/bluetooth/hci_core.c | 3 ++
+ net/bluetooth/hci_event.c | 56 ++++++++++++++++++++++++---------------
+ 5 files changed, 79 insertions(+), 36 deletions(-)
+
+--- a/include/net/bluetooth/hci_core.h
++++ b/include/net/bluetooth/hci_core.h
+@@ -322,8 +322,8 @@ struct adv_monitor {
+
+ #define HCI_MAX_SHORT_NAME_LENGTH 10
+
+-#define HCI_CONN_HANDLE_UNSET 0xffff
+ #define HCI_CONN_HANDLE_MAX 0x0eff
++#define HCI_CONN_HANDLE_UNSET(_handle) (_handle > HCI_CONN_HANDLE_MAX)
+
+ /* Min encryption key size to match with SMP */
+ #define HCI_MIN_ENC_KEY_SIZE 7
+@@ -352,6 +352,8 @@ struct hci_dev {
+ struct srcu_struct srcu;
+ struct mutex lock;
+
++ struct ida unset_handle_ida;
++
+ const char *name;
+ unsigned long flags;
+ __u16 id;
+@@ -1332,7 +1334,9 @@ bool hci_iso_setup_path(struct hci_conn
+ int hci_le_create_cis(struct hci_conn *conn);
+
+ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
+- u8 role);
++ u8 role, u16 handle);
++struct hci_conn *hci_conn_add_unset(struct hci_dev *hdev, int type,
++ bdaddr_t *dst, u8 role);
+ int hci_conn_del(struct hci_conn *conn);
+ void hci_conn_hash_flush(struct hci_dev *hdev);
+ void hci_conn_check_pending(struct hci_dev *hdev);
+--- a/net/bluetooth/amp.c
++++ b/net/bluetooth/amp.c
+@@ -109,7 +109,7 @@ struct hci_conn *phylink_add(struct hci_
+ struct hci_conn *hcon;
+ u8 role = out ? HCI_ROLE_MASTER : HCI_ROLE_SLAVE;
+
+- hcon = hci_conn_add(hdev, AMP_LINK, dst, role);
++ hcon = hci_conn_add(hdev, AMP_LINK, dst, role, __next_handle(mgr));
+ if (!hcon)
+ return NULL;
+
+@@ -117,7 +117,6 @@ struct hci_conn *phylink_add(struct hci_
+
+ hcon->state = BT_CONNECT;
+ hcon->attempt++;
+- hcon->handle = __next_handle(mgr);
+ hcon->remote_id = remote_id;
+ hcon->amp_mgr = amp_mgr_get(mgr);
+
+--- a/net/bluetooth/hci_conn.c
++++ b/net/bluetooth/hci_conn.c
+@@ -151,6 +151,9 @@ static void hci_conn_cleanup(struct hci_
+
+ hci_conn_hash_del(hdev, conn);
+
++ if (HCI_CONN_HANDLE_UNSET(conn->handle))
++ ida_free(&hdev->unset_handle_ida, conn->handle);
++
+ if (conn->cleanup)
+ conn->cleanup(conn);
+
+@@ -980,12 +983,18 @@ static void cis_cleanup(struct hci_conn
+ hci_le_remove_cig(hdev, conn->iso_qos.cig);
+ }
+
++static int hci_conn_hash_alloc_unset(struct hci_dev *hdev)
++{
++ return ida_alloc_range(&hdev->unset_handle_ida, HCI_CONN_HANDLE_MAX + 1,
++ U16_MAX, GFP_ATOMIC);
++}
++
+ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
+- u8 role)
++ u8 role, u16 handle)
+ {
+ struct hci_conn *conn;
+
+- BT_DBG("%s dst %pMR", hdev->name, dst);
++ bt_dev_dbg(hdev, "dst %pMR handle 0x%4.4x", dst, handle);
+
+ conn = kzalloc(sizeof(*conn), GFP_KERNEL);
+ if (!conn)
+@@ -993,7 +1002,7 @@ struct hci_conn *hci_conn_add(struct hci
+
+ bacpy(&conn->dst, dst);
+ bacpy(&conn->src, &hdev->bdaddr);
+- conn->handle = HCI_CONN_HANDLE_UNSET;
++ conn->handle = handle;
+ conn->hdev = hdev;
+ conn->type = type;
+ conn->role = role;
+@@ -1077,6 +1086,20 @@ struct hci_conn *hci_conn_add(struct hci
+ return conn;
+ }
+
++struct hci_conn *hci_conn_add_unset(struct hci_dev *hdev, int type,
++ bdaddr_t *dst, u8 role)
++{
++ int handle;
++
++ bt_dev_dbg(hdev, "dst %pMR", dst);
++
++ handle = hci_conn_hash_alloc_unset(hdev);
++ if (unlikely(handle < 0))
++ return NULL;
++
++ return hci_conn_add(hdev, type, dst, role, handle);
++}
++
+ static bool hci_conn_unlink(struct hci_conn *conn)
+ {
+ if (!conn->link)
+@@ -1107,7 +1130,7 @@ int hci_conn_del(struct hci_conn *conn)
+ * yet at this point. Delete it now, otherwise it is
+ * possible for it to be stuck and can't be deleted.
+ */
+- if (link->handle == HCI_CONN_HANDLE_UNSET)
++ if (HCI_CONN_HANDLE_UNSET(link->handle))
+ hci_conn_del(link);
+ }
+
+@@ -1336,7 +1359,7 @@ struct hci_conn *hci_connect_le(struct h
+ if (conn) {
+ bacpy(&conn->dst, dst);
+ } else {
+- conn = hci_conn_add(hdev, LE_LINK, dst, role);
++ conn = hci_conn_add_unset(hdev, LE_LINK, dst, role);
+ if (!conn)
+ return ERR_PTR(-ENOMEM);
+ hci_conn_hold(conn);
+@@ -1503,7 +1526,7 @@ static struct hci_conn *hci_add_bis(stru
+ if (conn)
+ return ERR_PTR(-EADDRINUSE);
+
+- conn = hci_conn_add(hdev, ISO_LINK, dst, HCI_ROLE_MASTER);
++ conn = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_MASTER);
+ if (!conn)
+ return ERR_PTR(-ENOMEM);
+
+@@ -1548,7 +1571,7 @@ struct hci_conn *hci_connect_le_scan(str
+
+ BT_DBG("requesting refresh of dst_addr");
+
+- conn = hci_conn_add(hdev, LE_LINK, dst, HCI_ROLE_MASTER);
++ conn = hci_conn_add_unset(hdev, LE_LINK, dst, HCI_ROLE_MASTER);
+ if (!conn)
+ return ERR_PTR(-ENOMEM);
+
+@@ -1596,7 +1619,7 @@ struct hci_conn *hci_connect_acl(struct
+
+ acl = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst);
+ if (!acl) {
+- acl = hci_conn_add(hdev, ACL_LINK, dst, HCI_ROLE_MASTER);
++ acl = hci_conn_add_unset(hdev, ACL_LINK, dst, HCI_ROLE_MASTER);
+ if (!acl)
+ return ERR_PTR(-ENOMEM);
+ }
+@@ -1627,7 +1650,7 @@ struct hci_conn *hci_connect_sco(struct
+
+ sco = hci_conn_hash_lookup_ba(hdev, type, dst);
+ if (!sco) {
+- sco = hci_conn_add(hdev, type, dst, HCI_ROLE_MASTER);
++ sco = hci_conn_add_unset(hdev, type, dst, HCI_ROLE_MASTER);
+ if (!sco) {
+ hci_conn_drop(acl);
+ return ERR_PTR(-ENOMEM);
+@@ -1837,7 +1860,7 @@ struct hci_conn *hci_bind_cis(struct hci
+
+ cis = hci_conn_hash_lookup_cis(hdev, dst, dst_type);
+ if (!cis) {
+- cis = hci_conn_add(hdev, ISO_LINK, dst, HCI_ROLE_MASTER);
++ cis = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_MASTER);
+ if (!cis)
+ return ERR_PTR(-ENOMEM);
+ cis->cleanup = cis_cleanup;
+@@ -2247,7 +2270,7 @@ struct hci_conn *hci_connect_cis(struct
+ /* If LE is already connected and CIS handle is already set proceed to
+ * Create CIS immediately.
+ */
+- if (le->state == BT_CONNECTED && cis->handle != HCI_CONN_HANDLE_UNSET)
++ if (le->state == BT_CONNECTED && !HCI_CONN_HANDLE_UNSET(cis->handle))
+ hci_le_create_cis(le);
+
+ return cis;
+--- a/net/bluetooth/hci_core.c
++++ b/net/bluetooth/hci_core.c
+@@ -2525,6 +2525,8 @@ struct hci_dev *hci_alloc_dev_priv(int s
+ mutex_init(&hdev->lock);
+ mutex_init(&hdev->req_lock);
+
++ ida_init(&hdev->unset_handle_ida);
++
+ INIT_LIST_HEAD(&hdev->mesh_pending);
+ INIT_LIST_HEAD(&hdev->mgmt_pending);
+ INIT_LIST_HEAD(&hdev->reject_list);
+@@ -2784,6 +2786,7 @@ void hci_release_dev(struct hci_dev *hde
+ msft_release(hdev);
+ hci_dev_unlock(hdev);
+
++ ida_destroy(&hdev->unset_handle_ida);
+ ida_simple_remove(&hci_index_ida, hdev->id);
+ kfree_skb(hdev->sent_cmd);
+ kfree_skb(hdev->recv_event);
+--- a/net/bluetooth/hci_event.c
++++ b/net/bluetooth/hci_event.c
+@@ -2317,8 +2317,8 @@ static void hci_cs_create_conn(struct hc
+ }
+ } else {
+ if (!conn) {
+- conn = hci_conn_add(hdev, ACL_LINK, &cp->bdaddr,
+- HCI_ROLE_MASTER);
++ conn = hci_conn_add_unset(hdev, ACL_LINK, &cp->bdaddr,
++ HCI_ROLE_MASTER);
+ if (!conn)
+ bt_dev_err(hdev, "no memory for new connection");
+ }
+@@ -3160,8 +3160,8 @@ static void hci_conn_complete_evt(struct
+ hci_bdaddr_list_lookup_with_flags(&hdev->accept_list,
+ &ev->bdaddr,
+ BDADDR_BREDR)) {
+- conn = hci_conn_add(hdev, ev->link_type, &ev->bdaddr,
+- HCI_ROLE_SLAVE);
++ conn = hci_conn_add_unset(hdev, ev->link_type,
++ &ev->bdaddr, HCI_ROLE_SLAVE);
+ if (!conn) {
+ bt_dev_err(hdev, "no memory for new conn");
+ goto unlock;
+@@ -3185,20 +3185,24 @@ static void hci_conn_complete_evt(struct
+ * As the connection handle is set here for the first time, it indicates
+ * whether the connection is already set up.
+ */
+- if (conn->handle != HCI_CONN_HANDLE_UNSET) {
++ if (!HCI_CONN_HANDLE_UNSET(conn->handle)) {
+ bt_dev_err(hdev, "Ignoring HCI_Connection_Complete for existing connection");
+ goto unlock;
+ }
+
+ if (!status) {
+- conn->handle = __le16_to_cpu(ev->handle);
+- if (conn->handle > HCI_CONN_HANDLE_MAX) {
++ if (__le16_to_cpu(ev->handle) > HCI_CONN_HANDLE_MAX) {
+ bt_dev_err(hdev, "Invalid handle: 0x%4.4x > 0x%4.4x",
+- conn->handle, HCI_CONN_HANDLE_MAX);
++ __le16_to_cpu(ev->handle),
++ HCI_CONN_HANDLE_MAX);
+ status = HCI_ERROR_INVALID_PARAMETERS;
+ goto done;
+ }
+
++ if (HCI_CONN_HANDLE_UNSET(conn->handle))
++ ida_free(&hdev->unset_handle_ida, conn->handle);
++ conn->handle = __le16_to_cpu(ev->handle);
++
+ if (conn->type == ACL_LINK) {
+ conn->state = BT_CONFIG;
+ hci_conn_hold(conn);
+@@ -3355,8 +3359,8 @@ static void hci_conn_request_evt(struct
+ conn = hci_conn_hash_lookup_ba(hdev, ev->link_type,
+ &ev->bdaddr);
+ if (!conn) {
+- conn = hci_conn_add(hdev, ev->link_type, &ev->bdaddr,
+- HCI_ROLE_SLAVE);
++ conn = hci_conn_add_unset(hdev, ev->link_type, &ev->bdaddr,
++ HCI_ROLE_SLAVE);
+ if (!conn) {
+ bt_dev_err(hdev, "no memory for new connection");
+ goto unlock;
+@@ -3867,6 +3871,8 @@ static u8 hci_cc_le_set_cig_params(struc
+ conn->state == BT_CONNECTED)
+ continue;
+
++ if (HCI_CONN_HANDLE_UNSET(conn->handle))
++ ida_free(&hdev->unset_handle_ida, conn->handle);
+ conn->handle = __le16_to_cpu(rp->handle[i++]);
+
+ bt_dev_dbg(hdev, "%p handle 0x%4.4x link %p", conn,
+@@ -5050,22 +5056,26 @@ static void hci_sync_conn_complete_evt(s
+ * As the connection handle is set here for the first time, it indicates
+ * whether the connection is already set up.
+ */
+- if (conn->handle != HCI_CONN_HANDLE_UNSET) {
++ if (!HCI_CONN_HANDLE_UNSET(conn->handle)) {
+ bt_dev_err(hdev, "Ignoring HCI_Sync_Conn_Complete event for existing connection");
+ goto unlock;
+ }
+
+ switch (status) {
+ case 0x00:
+- conn->handle = __le16_to_cpu(ev->handle);
+- if (conn->handle > HCI_CONN_HANDLE_MAX) {
++ if (__le16_to_cpu(ev->handle) > HCI_CONN_HANDLE_MAX) {
+ bt_dev_err(hdev, "Invalid handle: 0x%4.4x > 0x%4.4x",
+- conn->handle, HCI_CONN_HANDLE_MAX);
++ __le16_to_cpu(ev->handle),
++ HCI_CONN_HANDLE_MAX);
+ status = HCI_ERROR_INVALID_PARAMETERS;
+ conn->state = BT_CLOSED;
+ break;
+ }
+
++ if (HCI_CONN_HANDLE_UNSET(conn->handle))
++ ida_free(&hdev->unset_handle_ida, conn->handle);
++ conn->handle = __le16_to_cpu(ev->handle);
++
+ conn->state = BT_CONNECTED;
+ conn->type = ev->link_type;
+
+@@ -5752,7 +5762,7 @@ static void le_conn_complete_evt(struct
+ if (status)
+ goto unlock;
+
+- conn = hci_conn_add(hdev, LE_LINK, bdaddr, role);
++ conn = hci_conn_add_unset(hdev, LE_LINK, bdaddr, role);
+ if (!conn) {
+ bt_dev_err(hdev, "no memory for new connection");
+ goto unlock;
+@@ -5790,7 +5800,7 @@ static void le_conn_complete_evt(struct
+ * As the connection handle is set here for the first time, it indicates
+ * whether the connection is already set up.
+ */
+- if (conn->handle != HCI_CONN_HANDLE_UNSET) {
++ if (!HCI_CONN_HANDLE_UNSET(conn->handle)) {
+ bt_dev_err(hdev, "Ignoring HCI_Connection_Complete for existing connection");
+ goto unlock;
+ }
+@@ -5848,6 +5858,8 @@ static void le_conn_complete_evt(struct
+ mgmt_device_connected(hdev, conn, NULL, 0);
+
+ conn->sec_level = BT_SECURITY_LOW;
++ if (HCI_CONN_HANDLE_UNSET(conn->handle))
++ ida_free(&hdev->unset_handle_ida, conn->handle);
+ conn->handle = handle;
+ conn->state = BT_CONFIG;
+
+@@ -6849,12 +6861,12 @@ static void hci_le_cis_req_evt(struct hc
+
+ cis = hci_conn_hash_lookup_handle(hdev, cis_handle);
+ if (!cis) {
+- cis = hci_conn_add(hdev, ISO_LINK, &acl->dst, HCI_ROLE_SLAVE);
++ cis = hci_conn_add(hdev, ISO_LINK, &acl->dst, HCI_ROLE_SLAVE,
++ cis_handle);
+ if (!cis) {
+ hci_le_reject_cis(hdev, ev->cis_handle);
+ goto unlock;
+ }
+- cis->handle = cis_handle;
+ }
+
+ cis->iso_qos.cig = ev->cig_id;
+@@ -6896,8 +6908,11 @@ static void hci_le_create_big_complete_e
+ goto unlock;
+ }
+
+- if (ev->num_bis)
++ if (ev->num_bis) {
++ if (HCI_CONN_HANDLE_UNSET(conn->handle))
++ ida_free(&hdev->unset_handle_ida, conn->handle);
+ conn->handle = __le16_to_cpu(ev->bis_handle[0]);
++ }
+
+ if (!ev->status) {
+ conn->state = BT_CONNECTED;
+@@ -6939,10 +6954,9 @@ static void hci_le_big_sync_established_
+ bis = hci_conn_hash_lookup_handle(hdev, handle);
+ if (!bis) {
+ bis = hci_conn_add(hdev, ISO_LINK, BDADDR_ANY,
+- HCI_ROLE_SLAVE);
++ HCI_ROLE_SLAVE, handle);
+ if (!bis)
+ continue;
+- bis->handle = handle;
+ }
+
+ bis->iso_qos.big = ev->handle;
--- /dev/null
+From stable+bounces-275093-greg=kroah.com@vger.kernel.org Thu Jul 16 01:55:39 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 19:55:16 -0400
+Subject: Bluetooth: Remove BT_HS
+To: stable@vger.kernel.org
+Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715235519.525147-2-sashal@kernel.org>
+
+From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+
+[ Upstream commit e7b02296fb400ee64822fbdd81a0718449066333 ]
+
+High Speed, Alternate MAC and PHY (AMP) extension, has been removed from
+Bluetooth Core specification on 5.3:
+
+https://www.bluetooth.com/blog/new-core-specification-v5-3-feature-enhancements/
+
+Fixes: 244bc377591c ("Bluetooth: Add BT_HS config option")
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Stable-dep-of: 6fef032af009 ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/bluetooth/hci.h | 1
+ include/net/bluetooth/l2cap.h | 42 -
+ net/bluetooth/Kconfig | 8
+ net/bluetooth/Makefile | 1
+ net/bluetooth/a2mp.c | 1054 -----------------------------------------
+ net/bluetooth/a2mp.h | 154 ------
+ net/bluetooth/amp.c | 590 -----------------------
+ net/bluetooth/amp.h | 61 --
+ net/bluetooth/hci_conn.c | 4
+ net/bluetooth/hci_event.c | 2
+ net/bluetooth/l2cap_core.c | 1069 ------------------------------------------
+ net/bluetooth/l2cap_sock.c | 18
+ net/bluetooth/mgmt.c | 73 --
+ 13 files changed, 20 insertions(+), 3057 deletions(-)
+ delete mode 100644 net/bluetooth/a2mp.c
+ delete mode 100644 net/bluetooth/a2mp.h
+ delete mode 100644 net/bluetooth/amp.c
+ delete mode 100644 net/bluetooth/amp.h
+
+--- a/include/net/bluetooth/hci.h
++++ b/include/net/bluetooth/hci.h
+@@ -382,7 +382,6 @@ enum {
+ HCI_LIMITED_PRIVACY,
+ HCI_RPA_EXPIRED,
+ HCI_RPA_RESOLVING,
+- HCI_HS_ENABLED,
+ HCI_LE_ENABLED,
+ HCI_ADVERTISING,
+ HCI_ADVERTISING_CONNECTABLE,
+--- a/include/net/bluetooth/l2cap.h
++++ b/include/net/bluetooth/l2cap.h
+@@ -60,8 +60,6 @@
+ #define L2CAP_WAIT_ACK_POLL_PERIOD msecs_to_jiffies(200)
+ #define L2CAP_WAIT_ACK_TIMEOUT msecs_to_jiffies(10000)
+
+-#define L2CAP_A2MP_DEFAULT_MTU 670
+-
+ /* L2CAP socket address */
+ struct sockaddr_l2 {
+ sa_family_t l2_family;
+@@ -110,12 +108,6 @@ struct l2cap_conninfo {
+ #define L2CAP_ECHO_RSP 0x09
+ #define L2CAP_INFO_REQ 0x0a
+ #define L2CAP_INFO_RSP 0x0b
+-#define L2CAP_CREATE_CHAN_REQ 0x0c
+-#define L2CAP_CREATE_CHAN_RSP 0x0d
+-#define L2CAP_MOVE_CHAN_REQ 0x0e
+-#define L2CAP_MOVE_CHAN_RSP 0x0f
+-#define L2CAP_MOVE_CHAN_CFM 0x10
+-#define L2CAP_MOVE_CHAN_CFM_RSP 0x11
+ #define L2CAP_CONN_PARAM_UPDATE_REQ 0x12
+ #define L2CAP_CONN_PARAM_UPDATE_RSP 0x13
+ #define L2CAP_LE_CONN_REQ 0x14
+@@ -145,7 +137,6 @@ struct l2cap_conninfo {
+ /* L2CAP fixed channels */
+ #define L2CAP_FC_SIG_BREDR 0x02
+ #define L2CAP_FC_CONNLESS 0x04
+-#define L2CAP_FC_A2MP 0x08
+ #define L2CAP_FC_ATT 0x10
+ #define L2CAP_FC_SIG_LE 0x20
+ #define L2CAP_FC_SMP_LE 0x40
+@@ -268,7 +259,6 @@ struct l2cap_conn_rsp {
+ /* channel identifier */
+ #define L2CAP_CID_SIGNALING 0x0001
+ #define L2CAP_CID_CONN_LESS 0x0002
+-#define L2CAP_CID_A2MP 0x0003
+ #define L2CAP_CID_ATT 0x0004
+ #define L2CAP_CID_LE_SIGNALING 0x0005
+ #define L2CAP_CID_SMP 0x0006
+@@ -283,7 +273,6 @@ struct l2cap_conn_rsp {
+ #define L2CAP_CR_BAD_PSM 0x0002
+ #define L2CAP_CR_SEC_BLOCK 0x0003
+ #define L2CAP_CR_NO_MEM 0x0004
+-#define L2CAP_CR_BAD_AMP 0x0005
+ #define L2CAP_CR_INVALID_SCID 0x0006
+ #define L2CAP_CR_SCID_IN_USE 0x0007
+
+@@ -405,29 +394,6 @@ struct l2cap_info_rsp {
+ __u8 data[];
+ } __packed;
+
+-struct l2cap_create_chan_req {
+- __le16 psm;
+- __le16 scid;
+- __u8 amp_id;
+-} __packed;
+-
+-struct l2cap_create_chan_rsp {
+- __le16 dcid;
+- __le16 scid;
+- __le16 result;
+- __le16 status;
+-} __packed;
+-
+-struct l2cap_move_chan_req {
+- __le16 icid;
+- __u8 dest_amp_id;
+-} __packed;
+-
+-struct l2cap_move_chan_rsp {
+- __le16 icid;
+- __le16 result;
+-} __packed;
+-
+ #define L2CAP_MR_SUCCESS 0x0000
+ #define L2CAP_MR_PEND 0x0001
+ #define L2CAP_MR_BAD_ID 0x0002
+@@ -542,8 +508,6 @@ struct l2cap_seq_list {
+
+ struct l2cap_chan {
+ struct l2cap_conn *conn;
+- struct hci_conn *hs_hcon;
+- struct hci_chan *hs_hchan;
+ struct kref kref;
+ atomic_t nesting;
+
+@@ -597,12 +561,6 @@ struct l2cap_chan {
+ unsigned long conn_state;
+ unsigned long flags;
+
+- __u8 remote_amp_id;
+- __u8 local_amp_id;
+- __u8 move_id;
+- __u8 move_state;
+- __u8 move_role;
+-
+ __u16 next_tx_seq;
+ __u16 expected_ack_seq;
+ __u16 expected_tx_seq;
+--- a/net/bluetooth/Kconfig
++++ b/net/bluetooth/Kconfig
+@@ -62,14 +62,6 @@ source "net/bluetooth/cmtp/Kconfig"
+
+ source "net/bluetooth/hidp/Kconfig"
+
+-config BT_HS
+- bool "Bluetooth High Speed (HS) features"
+- depends on BT_BREDR
+- help
+- Bluetooth High Speed includes support for off-loading
+- Bluetooth connections via 802.11 (wifi) physical layer
+- available with Bluetooth version 3.0 or later.
+-
+ config BT_LE
+ bool "Bluetooth Low Energy (LE) features"
+ depends on BT
+--- a/net/bluetooth/Makefile
++++ b/net/bluetooth/Makefile
+@@ -19,7 +19,6 @@ bluetooth-y := af_bluetooth.o hci_core.o
+
+ bluetooth-$(CONFIG_BT_BREDR) += sco.o
+ bluetooth-$(CONFIG_BT_LE) += iso.o
+-bluetooth-$(CONFIG_BT_HS) += a2mp.o amp.o
+ bluetooth-$(CONFIG_BT_LEDS) += leds.o
+ bluetooth-$(CONFIG_BT_MSFTEXT) += msft.o
+ bluetooth-$(CONFIG_BT_AOSPEXT) += aosp.o
+--- a/net/bluetooth/a2mp.c
++++ /dev/null
+@@ -1,1054 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0-only
+-/*
+- Copyright (c) 2010,2011 Code Aurora Forum. All rights reserved.
+- Copyright (c) 2011,2012 Intel Corp.
+-
+-*/
+-
+-#include <net/bluetooth/bluetooth.h>
+-#include <net/bluetooth/hci_core.h>
+-#include <net/bluetooth/l2cap.h>
+-
+-#include "hci_request.h"
+-#include "a2mp.h"
+-#include "amp.h"
+-
+-#define A2MP_FEAT_EXT 0x8000
+-
+-/* Global AMP Manager list */
+-static LIST_HEAD(amp_mgr_list);
+-static DEFINE_MUTEX(amp_mgr_list_lock);
+-
+-/* A2MP build & send command helper functions */
+-static struct a2mp_cmd *__a2mp_build(u8 code, u8 ident, u16 len, void *data)
+-{
+- struct a2mp_cmd *cmd;
+- int plen;
+-
+- plen = sizeof(*cmd) + len;
+- cmd = kzalloc(plen, GFP_KERNEL);
+- if (!cmd)
+- return NULL;
+-
+- cmd->code = code;
+- cmd->ident = ident;
+- cmd->len = cpu_to_le16(len);
+-
+- memcpy(cmd->data, data, len);
+-
+- return cmd;
+-}
+-
+-static void a2mp_send(struct amp_mgr *mgr, u8 code, u8 ident, u16 len, void *data)
+-{
+- struct l2cap_chan *chan = mgr->a2mp_chan;
+- struct a2mp_cmd *cmd;
+- u16 total_len = len + sizeof(*cmd);
+- struct kvec iv;
+- struct msghdr msg;
+-
+- cmd = __a2mp_build(code, ident, len, data);
+- if (!cmd)
+- return;
+-
+- iv.iov_base = cmd;
+- iv.iov_len = total_len;
+-
+- memset(&msg, 0, sizeof(msg));
+-
+- iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &iv, 1, total_len);
+-
+- l2cap_chan_send(chan, &msg, total_len);
+-
+- kfree(cmd);
+-}
+-
+-static u8 __next_ident(struct amp_mgr *mgr)
+-{
+- if (++mgr->ident == 0)
+- mgr->ident = 1;
+-
+- return mgr->ident;
+-}
+-
+-static struct amp_mgr *amp_mgr_lookup_by_state(u8 state)
+-{
+- struct amp_mgr *mgr;
+-
+- mutex_lock(&_mgr_list_lock);
+- list_for_each_entry(mgr, &_mgr_list, list) {
+- if (test_and_clear_bit(state, &mgr->state)) {
+- amp_mgr_get(mgr);
+- mutex_unlock(&_mgr_list_lock);
+- return mgr;
+- }
+- }
+- mutex_unlock(&_mgr_list_lock);
+-
+- return NULL;
+-}
+-
+-/* hci_dev_list shall be locked */
+-static void __a2mp_add_cl(struct amp_mgr *mgr, struct a2mp_cl *cl)
+-{
+- struct hci_dev *hdev;
+- int i = 1;
+-
+- cl[0].id = AMP_ID_BREDR;
+- cl[0].type = AMP_TYPE_BREDR;
+- cl[0].status = AMP_STATUS_BLUETOOTH_ONLY;
+-
+- list_for_each_entry(hdev, &hci_dev_list, list) {
+- if (hdev->dev_type == HCI_AMP) {
+- cl[i].id = hdev->id;
+- cl[i].type = hdev->amp_type;
+- if (test_bit(HCI_UP, &hdev->flags))
+- cl[i].status = hdev->amp_status;
+- else
+- cl[i].status = AMP_STATUS_POWERED_DOWN;
+- i++;
+- }
+- }
+-}
+-
+-/* Processing A2MP messages */
+-static int a2mp_command_rej(struct amp_mgr *mgr, struct sk_buff *skb,
+- struct a2mp_cmd *hdr)
+-{
+- struct a2mp_cmd_rej *rej = (void *) skb->data;
+-
+- if (le16_to_cpu(hdr->len) < sizeof(*rej))
+- return -EINVAL;
+-
+- BT_DBG("ident %u reason %d", hdr->ident, le16_to_cpu(rej->reason));
+-
+- skb_pull(skb, sizeof(*rej));
+-
+- return 0;
+-}
+-
+-static int a2mp_discover_req(struct amp_mgr *mgr, struct sk_buff *skb,
+- struct a2mp_cmd *hdr)
+-{
+- struct a2mp_discov_req *req = (void *) skb->data;
+- u16 len = le16_to_cpu(hdr->len);
+- struct a2mp_discov_rsp *rsp;
+- u16 ext_feat;
+- u8 num_ctrl;
+- struct hci_dev *hdev;
+-
+- if (len < sizeof(*req))
+- return -EINVAL;
+-
+- skb_pull(skb, sizeof(*req));
+-
+- ext_feat = le16_to_cpu(req->ext_feat);
+-
+- BT_DBG("mtu %d efm 0x%4.4x", le16_to_cpu(req->mtu), ext_feat);
+-
+- /* check that packet is not broken for now */
+- while (ext_feat & A2MP_FEAT_EXT) {
+- if (len < sizeof(ext_feat))
+- return -EINVAL;
+-
+- ext_feat = get_unaligned_le16(skb->data);
+- BT_DBG("efm 0x%4.4x", ext_feat);
+- len -= sizeof(ext_feat);
+- skb_pull(skb, sizeof(ext_feat));
+- }
+-
+- read_lock(&hci_dev_list_lock);
+-
+- /* at minimum the BR/EDR needs to be listed */
+- num_ctrl = 1;
+-
+- list_for_each_entry(hdev, &hci_dev_list, list) {
+- if (hdev->dev_type == HCI_AMP)
+- num_ctrl++;
+- }
+-
+- len = struct_size(rsp, cl, num_ctrl);
+- rsp = kmalloc(len, GFP_ATOMIC);
+- if (!rsp) {
+- read_unlock(&hci_dev_list_lock);
+- return -ENOMEM;
+- }
+-
+- rsp->mtu = cpu_to_le16(L2CAP_A2MP_DEFAULT_MTU);
+- rsp->ext_feat = 0;
+-
+- __a2mp_add_cl(mgr, rsp->cl);
+-
+- read_unlock(&hci_dev_list_lock);
+-
+- a2mp_send(mgr, A2MP_DISCOVER_RSP, hdr->ident, len, rsp);
+-
+- kfree(rsp);
+- return 0;
+-}
+-
+-static int a2mp_discover_rsp(struct amp_mgr *mgr, struct sk_buff *skb,
+- struct a2mp_cmd *hdr)
+-{
+- struct a2mp_discov_rsp *rsp = (void *) skb->data;
+- u16 len = le16_to_cpu(hdr->len);
+- struct a2mp_cl *cl;
+- u16 ext_feat;
+- bool found = false;
+-
+- if (len < sizeof(*rsp))
+- return -EINVAL;
+-
+- len -= sizeof(*rsp);
+- skb_pull(skb, sizeof(*rsp));
+-
+- ext_feat = le16_to_cpu(rsp->ext_feat);
+-
+- BT_DBG("mtu %d efm 0x%4.4x", le16_to_cpu(rsp->mtu), ext_feat);
+-
+- /* check that packet is not broken for now */
+- while (ext_feat & A2MP_FEAT_EXT) {
+- if (len < sizeof(ext_feat))
+- return -EINVAL;
+-
+- ext_feat = get_unaligned_le16(skb->data);
+- BT_DBG("efm 0x%4.4x", ext_feat);
+- len -= sizeof(ext_feat);
+- skb_pull(skb, sizeof(ext_feat));
+- }
+-
+- cl = (void *) skb->data;
+- while (len >= sizeof(*cl)) {
+- BT_DBG("Remote AMP id %u type %u status %u", cl->id, cl->type,
+- cl->status);
+-
+- if (cl->id != AMP_ID_BREDR && cl->type != AMP_TYPE_BREDR) {
+- struct a2mp_info_req req;
+-
+- found = true;
+-
+- memset(&req, 0, sizeof(req));
+-
+- req.id = cl->id;
+- a2mp_send(mgr, A2MP_GETINFO_REQ, __next_ident(mgr),
+- sizeof(req), &req);
+- }
+-
+- len -= sizeof(*cl);
+- cl = skb_pull(skb, sizeof(*cl));
+- }
+-
+- /* Fall back to L2CAP init sequence */
+- if (!found) {
+- struct l2cap_conn *conn = mgr->l2cap_conn;
+- struct l2cap_chan *chan;
+-
+- mutex_lock(&conn->chan_lock);
+-
+- list_for_each_entry(chan, &conn->chan_l, list) {
+-
+- BT_DBG("chan %p state %s", chan,
+- state_to_string(chan->state));
+-
+- if (chan->scid == L2CAP_CID_A2MP)
+- continue;
+-
+- l2cap_chan_lock(chan);
+-
+- if (chan->state == BT_CONNECT)
+- l2cap_send_conn_req(chan);
+-
+- l2cap_chan_unlock(chan);
+- }
+-
+- mutex_unlock(&conn->chan_lock);
+- }
+-
+- return 0;
+-}
+-
+-static int a2mp_change_notify(struct amp_mgr *mgr, struct sk_buff *skb,
+- struct a2mp_cmd *hdr)
+-{
+- struct a2mp_cl *cl = (void *) skb->data;
+-
+- while (skb->len >= sizeof(*cl)) {
+- BT_DBG("Controller id %u type %u status %u", cl->id, cl->type,
+- cl->status);
+- cl = skb_pull(skb, sizeof(*cl));
+- }
+-
+- /* TODO send A2MP_CHANGE_RSP */
+-
+- return 0;
+-}
+-
+-static void read_local_amp_info_complete(struct hci_dev *hdev, u8 status,
+- u16 opcode)
+-{
+- BT_DBG("%s status 0x%2.2x", hdev->name, status);
+-
+- a2mp_send_getinfo_rsp(hdev);
+-}
+-
+-static int a2mp_getinfo_req(struct amp_mgr *mgr, struct sk_buff *skb,
+- struct a2mp_cmd *hdr)
+-{
+- struct a2mp_info_req *req = (void *) skb->data;
+- struct hci_dev *hdev;
+- struct hci_request hreq;
+- int err = 0;
+-
+- if (le16_to_cpu(hdr->len) < sizeof(*req))
+- return -EINVAL;
+-
+- BT_DBG("id %u", req->id);
+-
+- hdev = hci_dev_get(req->id);
+- if (!hdev || hdev->dev_type != HCI_AMP) {
+- struct a2mp_info_rsp rsp;
+-
+- memset(&rsp, 0, sizeof(rsp));
+-
+- rsp.id = req->id;
+- rsp.status = A2MP_STATUS_INVALID_CTRL_ID;
+-
+- a2mp_send(mgr, A2MP_GETINFO_RSP, hdr->ident, sizeof(rsp),
+- &rsp);
+-
+- goto done;
+- }
+-
+- set_bit(READ_LOC_AMP_INFO, &mgr->state);
+- hci_req_init(&hreq, hdev);
+- hci_req_add(&hreq, HCI_OP_READ_LOCAL_AMP_INFO, 0, NULL);
+- err = hci_req_run(&hreq, read_local_amp_info_complete);
+- if (err < 0)
+- a2mp_send_getinfo_rsp(hdev);
+-
+-done:
+- if (hdev)
+- hci_dev_put(hdev);
+-
+- skb_pull(skb, sizeof(*req));
+- return 0;
+-}
+-
+-static int a2mp_getinfo_rsp(struct amp_mgr *mgr, struct sk_buff *skb,
+- struct a2mp_cmd *hdr)
+-{
+- struct a2mp_info_rsp *rsp = (struct a2mp_info_rsp *) skb->data;
+- struct a2mp_amp_assoc_req req;
+- struct amp_ctrl *ctrl;
+-
+- if (le16_to_cpu(hdr->len) < sizeof(*rsp))
+- return -EINVAL;
+-
+- BT_DBG("id %u status 0x%2.2x", rsp->id, rsp->status);
+-
+- if (rsp->status)
+- return -EINVAL;
+-
+- ctrl = amp_ctrl_add(mgr, rsp->id);
+- if (!ctrl)
+- return -ENOMEM;
+-
+- memset(&req, 0, sizeof(req));
+-
+- req.id = rsp->id;
+- a2mp_send(mgr, A2MP_GETAMPASSOC_REQ, __next_ident(mgr), sizeof(req),
+- &req);
+-
+- skb_pull(skb, sizeof(*rsp));
+- return 0;
+-}
+-
+-static int a2mp_getampassoc_req(struct amp_mgr *mgr, struct sk_buff *skb,
+- struct a2mp_cmd *hdr)
+-{
+- struct a2mp_amp_assoc_req *req = (void *) skb->data;
+- struct hci_dev *hdev;
+- struct amp_mgr *tmp;
+-
+- if (le16_to_cpu(hdr->len) < sizeof(*req))
+- return -EINVAL;
+-
+- BT_DBG("id %u", req->id);
+-
+- /* Make sure that other request is not processed */
+- tmp = amp_mgr_lookup_by_state(READ_LOC_AMP_ASSOC);
+-
+- hdev = hci_dev_get(req->id);
+- if (!hdev || hdev->amp_type == AMP_TYPE_BREDR || tmp) {
+- struct a2mp_amp_assoc_rsp rsp;
+-
+- memset(&rsp, 0, sizeof(rsp));
+- rsp.id = req->id;
+-
+- if (tmp) {
+- rsp.status = A2MP_STATUS_COLLISION_OCCURED;
+- amp_mgr_put(tmp);
+- } else {
+- rsp.status = A2MP_STATUS_INVALID_CTRL_ID;
+- }
+-
+- a2mp_send(mgr, A2MP_GETAMPASSOC_RSP, hdr->ident, sizeof(rsp),
+- &rsp);
+-
+- goto done;
+- }
+-
+- amp_read_loc_assoc(hdev, mgr);
+-
+-done:
+- if (hdev)
+- hci_dev_put(hdev);
+-
+- skb_pull(skb, sizeof(*req));
+- return 0;
+-}
+-
+-static int a2mp_getampassoc_rsp(struct amp_mgr *mgr, struct sk_buff *skb,
+- struct a2mp_cmd *hdr)
+-{
+- struct a2mp_amp_assoc_rsp *rsp = (void *) skb->data;
+- u16 len = le16_to_cpu(hdr->len);
+- struct hci_dev *hdev;
+- struct amp_ctrl *ctrl;
+- struct hci_conn *hcon;
+- size_t assoc_len;
+-
+- if (len < sizeof(*rsp))
+- return -EINVAL;
+-
+- assoc_len = len - sizeof(*rsp);
+-
+- BT_DBG("id %u status 0x%2.2x assoc len %zu", rsp->id, rsp->status,
+- assoc_len);
+-
+- if (rsp->status)
+- return -EINVAL;
+-
+- /* Save remote ASSOC data */
+- ctrl = amp_ctrl_lookup(mgr, rsp->id);
+- if (ctrl) {
+- u8 *assoc;
+-
+- assoc = kmemdup(rsp->amp_assoc, assoc_len, GFP_KERNEL);
+- if (!assoc) {
+- amp_ctrl_put(ctrl);
+- return -ENOMEM;
+- }
+-
+- ctrl->assoc = assoc;
+- ctrl->assoc_len = assoc_len;
+- ctrl->assoc_rem_len = assoc_len;
+- ctrl->assoc_len_so_far = 0;
+-
+- amp_ctrl_put(ctrl);
+- }
+-
+- /* Create Phys Link */
+- hdev = hci_dev_get(rsp->id);
+- if (!hdev)
+- return -EINVAL;
+-
+- hcon = phylink_add(hdev, mgr, rsp->id, true);
+- if (!hcon)
+- goto done;
+-
+- BT_DBG("Created hcon %p: loc:%u -> rem:%u", hcon, hdev->id, rsp->id);
+-
+- mgr->bredr_chan->remote_amp_id = rsp->id;
+-
+- amp_create_phylink(hdev, mgr, hcon);
+-
+-done:
+- hci_dev_put(hdev);
+- skb_pull(skb, len);
+- return 0;
+-}
+-
+-static int a2mp_createphyslink_req(struct amp_mgr *mgr, struct sk_buff *skb,
+- struct a2mp_cmd *hdr)
+-{
+- struct a2mp_physlink_req *req = (void *) skb->data;
+- struct a2mp_physlink_rsp rsp;
+- struct hci_dev *hdev;
+- struct hci_conn *hcon;
+- struct amp_ctrl *ctrl;
+-
+- if (le16_to_cpu(hdr->len) < sizeof(*req))
+- return -EINVAL;
+-
+- BT_DBG("local_id %u, remote_id %u", req->local_id, req->remote_id);
+-
+- memset(&rsp, 0, sizeof(rsp));
+-
+- rsp.local_id = req->remote_id;
+- rsp.remote_id = req->local_id;
+-
+- hdev = hci_dev_get(req->remote_id);
+- if (!hdev || hdev->amp_type == AMP_TYPE_BREDR) {
+- rsp.status = A2MP_STATUS_INVALID_CTRL_ID;
+- goto send_rsp;
+- }
+-
+- ctrl = amp_ctrl_lookup(mgr, rsp.remote_id);
+- if (!ctrl) {
+- ctrl = amp_ctrl_add(mgr, rsp.remote_id);
+- if (ctrl) {
+- amp_ctrl_get(ctrl);
+- } else {
+- rsp.status = A2MP_STATUS_UNABLE_START_LINK_CREATION;
+- goto send_rsp;
+- }
+- }
+-
+- if (ctrl) {
+- size_t assoc_len = le16_to_cpu(hdr->len) - sizeof(*req);
+- u8 *assoc;
+-
+- assoc = kmemdup(req->amp_assoc, assoc_len, GFP_KERNEL);
+- if (!assoc) {
+- amp_ctrl_put(ctrl);
+- hci_dev_put(hdev);
+- return -ENOMEM;
+- }
+-
+- ctrl->assoc = assoc;
+- ctrl->assoc_len = assoc_len;
+- ctrl->assoc_rem_len = assoc_len;
+- ctrl->assoc_len_so_far = 0;
+-
+- amp_ctrl_put(ctrl);
+- }
+-
+- hcon = phylink_add(hdev, mgr, req->local_id, false);
+- if (hcon) {
+- amp_accept_phylink(hdev, mgr, hcon);
+- rsp.status = A2MP_STATUS_SUCCESS;
+- } else {
+- rsp.status = A2MP_STATUS_UNABLE_START_LINK_CREATION;
+- }
+-
+-send_rsp:
+- if (hdev)
+- hci_dev_put(hdev);
+-
+- /* Reply error now and success after HCI Write Remote AMP Assoc
+- command complete with success status
+- */
+- if (rsp.status != A2MP_STATUS_SUCCESS) {
+- a2mp_send(mgr, A2MP_CREATEPHYSLINK_RSP, hdr->ident,
+- sizeof(rsp), &rsp);
+- } else {
+- set_bit(WRITE_REMOTE_AMP_ASSOC, &mgr->state);
+- mgr->ident = hdr->ident;
+- }
+-
+- skb_pull(skb, le16_to_cpu(hdr->len));
+- return 0;
+-}
+-
+-static int a2mp_discphyslink_req(struct amp_mgr *mgr, struct sk_buff *skb,
+- struct a2mp_cmd *hdr)
+-{
+- struct a2mp_physlink_req *req = (void *) skb->data;
+- struct a2mp_physlink_rsp rsp;
+- struct hci_dev *hdev;
+- struct hci_conn *hcon;
+-
+- if (le16_to_cpu(hdr->len) < sizeof(*req))
+- return -EINVAL;
+-
+- BT_DBG("local_id %u remote_id %u", req->local_id, req->remote_id);
+-
+- memset(&rsp, 0, sizeof(rsp));
+-
+- rsp.local_id = req->remote_id;
+- rsp.remote_id = req->local_id;
+- rsp.status = A2MP_STATUS_SUCCESS;
+-
+- hdev = hci_dev_get(req->remote_id);
+- if (!hdev) {
+- rsp.status = A2MP_STATUS_INVALID_CTRL_ID;
+- goto send_rsp;
+- }
+-
+- hcon = hci_conn_hash_lookup_ba(hdev, AMP_LINK,
+- &mgr->l2cap_conn->hcon->dst);
+- if (!hcon) {
+- bt_dev_err(hdev, "no phys link exist");
+- rsp.status = A2MP_STATUS_NO_PHYSICAL_LINK_EXISTS;
+- goto clean;
+- }
+-
+- /* TODO Disconnect Phys Link here */
+-
+-clean:
+- hci_dev_put(hdev);
+-
+-send_rsp:
+- a2mp_send(mgr, A2MP_DISCONNPHYSLINK_RSP, hdr->ident, sizeof(rsp), &rsp);
+-
+- skb_pull(skb, sizeof(*req));
+- return 0;
+-}
+-
+-static inline int a2mp_cmd_rsp(struct amp_mgr *mgr, struct sk_buff *skb,
+- struct a2mp_cmd *hdr)
+-{
+- BT_DBG("ident %u code 0x%2.2x", hdr->ident, hdr->code);
+-
+- skb_pull(skb, le16_to_cpu(hdr->len));
+- return 0;
+-}
+-
+-/* Handle A2MP signalling */
+-static int a2mp_chan_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
+-{
+- struct a2mp_cmd *hdr;
+- struct amp_mgr *mgr = chan->data;
+- int err = 0;
+-
+- amp_mgr_get(mgr);
+-
+- while (skb->len >= sizeof(*hdr)) {
+- u16 len;
+-
+- hdr = (void *) skb->data;
+- len = le16_to_cpu(hdr->len);
+-
+- BT_DBG("code 0x%2.2x id %u len %u", hdr->code, hdr->ident, len);
+-
+- skb_pull(skb, sizeof(*hdr));
+-
+- if (len > skb->len || !hdr->ident) {
+- err = -EINVAL;
+- break;
+- }
+-
+- mgr->ident = hdr->ident;
+-
+- switch (hdr->code) {
+- case A2MP_COMMAND_REJ:
+- a2mp_command_rej(mgr, skb, hdr);
+- break;
+-
+- case A2MP_DISCOVER_REQ:
+- err = a2mp_discover_req(mgr, skb, hdr);
+- break;
+-
+- case A2MP_CHANGE_NOTIFY:
+- err = a2mp_change_notify(mgr, skb, hdr);
+- break;
+-
+- case A2MP_GETINFO_REQ:
+- err = a2mp_getinfo_req(mgr, skb, hdr);
+- break;
+-
+- case A2MP_GETAMPASSOC_REQ:
+- err = a2mp_getampassoc_req(mgr, skb, hdr);
+- break;
+-
+- case A2MP_CREATEPHYSLINK_REQ:
+- err = a2mp_createphyslink_req(mgr, skb, hdr);
+- break;
+-
+- case A2MP_DISCONNPHYSLINK_REQ:
+- err = a2mp_discphyslink_req(mgr, skb, hdr);
+- break;
+-
+- case A2MP_DISCOVER_RSP:
+- err = a2mp_discover_rsp(mgr, skb, hdr);
+- break;
+-
+- case A2MP_GETINFO_RSP:
+- err = a2mp_getinfo_rsp(mgr, skb, hdr);
+- break;
+-
+- case A2MP_GETAMPASSOC_RSP:
+- err = a2mp_getampassoc_rsp(mgr, skb, hdr);
+- break;
+-
+- case A2MP_CHANGE_RSP:
+- case A2MP_CREATEPHYSLINK_RSP:
+- case A2MP_DISCONNPHYSLINK_RSP:
+- err = a2mp_cmd_rsp(mgr, skb, hdr);
+- break;
+-
+- default:
+- BT_ERR("Unknown A2MP sig cmd 0x%2.2x", hdr->code);
+- err = -EINVAL;
+- break;
+- }
+- }
+-
+- if (err) {
+- struct a2mp_cmd_rej rej;
+-
+- memset(&rej, 0, sizeof(rej));
+-
+- rej.reason = cpu_to_le16(0);
+- hdr = (void *) skb->data;
+-
+- BT_DBG("Send A2MP Rej: cmd 0x%2.2x err %d", hdr->code, err);
+-
+- a2mp_send(mgr, A2MP_COMMAND_REJ, hdr->ident, sizeof(rej),
+- &rej);
+- }
+-
+- /* Always free skb and return success error code to prevent
+- from sending L2CAP Disconnect over A2MP channel */
+- kfree_skb(skb);
+-
+- amp_mgr_put(mgr);
+-
+- return 0;
+-}
+-
+-static void a2mp_chan_close_cb(struct l2cap_chan *chan)
+-{
+- l2cap_chan_put(chan);
+-}
+-
+-static void a2mp_chan_state_change_cb(struct l2cap_chan *chan, int state,
+- int err)
+-{
+- struct amp_mgr *mgr = chan->data;
+-
+- if (!mgr)
+- return;
+-
+- BT_DBG("chan %p state %s", chan, state_to_string(state));
+-
+- chan->state = state;
+-
+- switch (state) {
+- case BT_CLOSED:
+- if (mgr)
+- amp_mgr_put(mgr);
+- break;
+- }
+-}
+-
+-static struct sk_buff *a2mp_chan_alloc_skb_cb(struct l2cap_chan *chan,
+- unsigned long hdr_len,
+- unsigned long len, int nb)
+-{
+- struct sk_buff *skb;
+-
+- skb = bt_skb_alloc(hdr_len + len, GFP_KERNEL);
+- if (!skb)
+- return ERR_PTR(-ENOMEM);
+-
+- return skb;
+-}
+-
+-static const struct l2cap_ops a2mp_chan_ops = {
+- .name = "L2CAP A2MP channel",
+- .recv = a2mp_chan_recv_cb,
+- .close = a2mp_chan_close_cb,
+- .state_change = a2mp_chan_state_change_cb,
+- .alloc_skb = a2mp_chan_alloc_skb_cb,
+-
+- /* Not implemented for A2MP */
+- .new_connection = l2cap_chan_no_new_connection,
+- .teardown = l2cap_chan_no_teardown,
+- .ready = l2cap_chan_no_ready,
+- .defer = l2cap_chan_no_defer,
+- .resume = l2cap_chan_no_resume,
+- .set_shutdown = l2cap_chan_no_set_shutdown,
+- .get_sndtimeo = l2cap_chan_no_get_sndtimeo,
+-};
+-
+-static struct l2cap_chan *a2mp_chan_open(struct l2cap_conn *conn, bool locked)
+-{
+- struct l2cap_chan *chan;
+- int err;
+-
+- chan = l2cap_chan_create();
+- if (!chan)
+- return NULL;
+-
+- BT_DBG("chan %p", chan);
+-
+- chan->chan_type = L2CAP_CHAN_FIXED;
+- chan->scid = L2CAP_CID_A2MP;
+- chan->dcid = L2CAP_CID_A2MP;
+- chan->omtu = L2CAP_A2MP_DEFAULT_MTU;
+- chan->imtu = L2CAP_A2MP_DEFAULT_MTU;
+- chan->flush_to = L2CAP_DEFAULT_FLUSH_TO;
+-
+- chan->ops = &a2mp_chan_ops;
+-
+- l2cap_chan_set_defaults(chan);
+- chan->remote_max_tx = chan->max_tx;
+- chan->remote_tx_win = chan->tx_win;
+-
+- chan->retrans_timeout = L2CAP_DEFAULT_RETRANS_TO;
+- chan->monitor_timeout = L2CAP_DEFAULT_MONITOR_TO;
+-
+- skb_queue_head_init(&chan->tx_q);
+-
+- chan->mode = L2CAP_MODE_ERTM;
+-
+- err = l2cap_ertm_init(chan);
+- if (err < 0) {
+- l2cap_chan_del(chan, 0);
+- return NULL;
+- }
+-
+- chan->conf_state = 0;
+-
+- if (locked)
+- __l2cap_chan_add(conn, chan);
+- else
+- l2cap_chan_add(conn, chan);
+-
+- chan->remote_mps = chan->omtu;
+- chan->mps = chan->omtu;
+-
+- chan->state = BT_CONNECTED;
+-
+- return chan;
+-}
+-
+-/* AMP Manager functions */
+-struct amp_mgr *amp_mgr_get(struct amp_mgr *mgr)
+-{
+- BT_DBG("mgr %p orig refcnt %d", mgr, kref_read(&mgr->kref));
+-
+- kref_get(&mgr->kref);
+-
+- return mgr;
+-}
+-
+-static void amp_mgr_destroy(struct kref *kref)
+-{
+- struct amp_mgr *mgr = container_of(kref, struct amp_mgr, kref);
+-
+- BT_DBG("mgr %p", mgr);
+-
+- mutex_lock(&_mgr_list_lock);
+- list_del(&mgr->list);
+- mutex_unlock(&_mgr_list_lock);
+-
+- amp_ctrl_list_flush(mgr);
+- kfree(mgr);
+-}
+-
+-int amp_mgr_put(struct amp_mgr *mgr)
+-{
+- BT_DBG("mgr %p orig refcnt %d", mgr, kref_read(&mgr->kref));
+-
+- return kref_put(&mgr->kref, &_mgr_destroy);
+-}
+-
+-static struct amp_mgr *amp_mgr_create(struct l2cap_conn *conn, bool locked)
+-{
+- struct amp_mgr *mgr;
+- struct l2cap_chan *chan;
+-
+- mgr = kzalloc(sizeof(*mgr), GFP_KERNEL);
+- if (!mgr)
+- return NULL;
+-
+- BT_DBG("conn %p mgr %p", conn, mgr);
+-
+- mgr->l2cap_conn = conn;
+-
+- chan = a2mp_chan_open(conn, locked);
+- if (!chan) {
+- kfree(mgr);
+- return NULL;
+- }
+-
+- mgr->a2mp_chan = chan;
+- chan->data = mgr;
+-
+- conn->hcon->amp_mgr = mgr;
+-
+- kref_init(&mgr->kref);
+-
+- /* Remote AMP ctrl list initialization */
+- INIT_LIST_HEAD(&mgr->amp_ctrls);
+- mutex_init(&mgr->amp_ctrls_lock);
+-
+- mutex_lock(&_mgr_list_lock);
+- list_add(&mgr->list, &_mgr_list);
+- mutex_unlock(&_mgr_list_lock);
+-
+- return mgr;
+-}
+-
+-struct l2cap_chan *a2mp_channel_create(struct l2cap_conn *conn,
+- struct sk_buff *skb)
+-{
+- struct amp_mgr *mgr;
+-
+- if (conn->hcon->type != ACL_LINK)
+- return NULL;
+-
+- mgr = amp_mgr_create(conn, false);
+- if (!mgr) {
+- BT_ERR("Could not create AMP manager");
+- return NULL;
+- }
+-
+- BT_DBG("mgr: %p chan %p", mgr, mgr->a2mp_chan);
+-
+- return mgr->a2mp_chan;
+-}
+-
+-void a2mp_send_getinfo_rsp(struct hci_dev *hdev)
+-{
+- struct amp_mgr *mgr;
+- struct a2mp_info_rsp rsp;
+-
+- mgr = amp_mgr_lookup_by_state(READ_LOC_AMP_INFO);
+- if (!mgr)
+- return;
+-
+- BT_DBG("%s mgr %p", hdev->name, mgr);
+-
+- memset(&rsp, 0, sizeof(rsp));
+-
+- rsp.id = hdev->id;
+- rsp.status = A2MP_STATUS_INVALID_CTRL_ID;
+-
+- if (hdev->amp_type != AMP_TYPE_BREDR) {
+- rsp.status = 0;
+- rsp.total_bw = cpu_to_le32(hdev->amp_total_bw);
+- rsp.max_bw = cpu_to_le32(hdev->amp_max_bw);
+- rsp.min_latency = cpu_to_le32(hdev->amp_min_latency);
+- rsp.pal_cap = cpu_to_le16(hdev->amp_pal_cap);
+- rsp.assoc_size = cpu_to_le16(hdev->amp_assoc_size);
+- }
+-
+- a2mp_send(mgr, A2MP_GETINFO_RSP, mgr->ident, sizeof(rsp), &rsp);
+- amp_mgr_put(mgr);
+-}
+-
+-void a2mp_send_getampassoc_rsp(struct hci_dev *hdev, u8 status)
+-{
+- struct amp_mgr *mgr;
+- struct amp_assoc *loc_assoc = &hdev->loc_assoc;
+- struct a2mp_amp_assoc_rsp *rsp;
+- size_t len;
+-
+- mgr = amp_mgr_lookup_by_state(READ_LOC_AMP_ASSOC);
+- if (!mgr)
+- return;
+-
+- BT_DBG("%s mgr %p", hdev->name, mgr);
+-
+- len = sizeof(struct a2mp_amp_assoc_rsp) + loc_assoc->len;
+- rsp = kzalloc(len, GFP_KERNEL);
+- if (!rsp) {
+- amp_mgr_put(mgr);
+- return;
+- }
+-
+- rsp->id = hdev->id;
+-
+- if (status) {
+- rsp->status = A2MP_STATUS_INVALID_CTRL_ID;
+- } else {
+- rsp->status = A2MP_STATUS_SUCCESS;
+- memcpy(rsp->amp_assoc, loc_assoc->data, loc_assoc->len);
+- }
+-
+- a2mp_send(mgr, A2MP_GETAMPASSOC_RSP, mgr->ident, len, rsp);
+- amp_mgr_put(mgr);
+- kfree(rsp);
+-}
+-
+-void a2mp_send_create_phy_link_req(struct hci_dev *hdev, u8 status)
+-{
+- struct amp_mgr *mgr;
+- struct amp_assoc *loc_assoc = &hdev->loc_assoc;
+- struct a2mp_physlink_req *req;
+- struct l2cap_chan *bredr_chan;
+- size_t len;
+-
+- mgr = amp_mgr_lookup_by_state(READ_LOC_AMP_ASSOC_FINAL);
+- if (!mgr)
+- return;
+-
+- len = sizeof(*req) + loc_assoc->len;
+-
+- BT_DBG("%s mgr %p assoc_len %zu", hdev->name, mgr, len);
+-
+- req = kzalloc(len, GFP_KERNEL);
+- if (!req) {
+- amp_mgr_put(mgr);
+- return;
+- }
+-
+- bredr_chan = mgr->bredr_chan;
+- if (!bredr_chan)
+- goto clean;
+-
+- req->local_id = hdev->id;
+- req->remote_id = bredr_chan->remote_amp_id;
+- memcpy(req->amp_assoc, loc_assoc->data, loc_assoc->len);
+-
+- a2mp_send(mgr, A2MP_CREATEPHYSLINK_REQ, __next_ident(mgr), len, req);
+-
+-clean:
+- amp_mgr_put(mgr);
+- kfree(req);
+-}
+-
+-void a2mp_send_create_phy_link_rsp(struct hci_dev *hdev, u8 status)
+-{
+- struct amp_mgr *mgr;
+- struct a2mp_physlink_rsp rsp;
+- struct hci_conn *hs_hcon;
+-
+- mgr = amp_mgr_lookup_by_state(WRITE_REMOTE_AMP_ASSOC);
+- if (!mgr)
+- return;
+-
+- memset(&rsp, 0, sizeof(rsp));
+-
+- hs_hcon = hci_conn_hash_lookup_state(hdev, AMP_LINK, BT_CONNECT);
+- if (!hs_hcon) {
+- rsp.status = A2MP_STATUS_UNABLE_START_LINK_CREATION;
+- } else {
+- rsp.remote_id = hs_hcon->remote_id;
+- rsp.status = A2MP_STATUS_SUCCESS;
+- }
+-
+- BT_DBG("%s mgr %p hs_hcon %p status %u", hdev->name, mgr, hs_hcon,
+- status);
+-
+- rsp.local_id = hdev->id;
+- a2mp_send(mgr, A2MP_CREATEPHYSLINK_RSP, mgr->ident, sizeof(rsp), &rsp);
+- amp_mgr_put(mgr);
+-}
+-
+-void a2mp_discover_amp(struct l2cap_chan *chan)
+-{
+- struct l2cap_conn *conn = chan->conn;
+- struct amp_mgr *mgr = conn->hcon->amp_mgr;
+- struct a2mp_discov_req req;
+-
+- BT_DBG("chan %p conn %p mgr %p", chan, conn, mgr);
+-
+- if (!mgr) {
+- mgr = amp_mgr_create(conn, true);
+- if (!mgr)
+- return;
+- }
+-
+- mgr->bredr_chan = chan;
+-
+- memset(&req, 0, sizeof(req));
+-
+- req.mtu = cpu_to_le16(L2CAP_A2MP_DEFAULT_MTU);
+- req.ext_feat = 0;
+- a2mp_send(mgr, A2MP_DISCOVER_REQ, 1, sizeof(req), &req);
+-}
+--- a/net/bluetooth/a2mp.h
++++ /dev/null
+@@ -1,154 +0,0 @@
+-/* SPDX-License-Identifier: GPL-2.0-only */
+-/*
+- Copyright (c) 2010,2011 Code Aurora Forum. All rights reserved.
+- Copyright (c) 2011,2012 Intel Corp.
+-
+-*/
+-
+-#ifndef __A2MP_H
+-#define __A2MP_H
+-
+-#include <net/bluetooth/l2cap.h>
+-
+-enum amp_mgr_state {
+- READ_LOC_AMP_INFO,
+- READ_LOC_AMP_ASSOC,
+- READ_LOC_AMP_ASSOC_FINAL,
+- WRITE_REMOTE_AMP_ASSOC,
+-};
+-
+-struct amp_mgr {
+- struct list_head list;
+- struct l2cap_conn *l2cap_conn;
+- struct l2cap_chan *a2mp_chan;
+- struct l2cap_chan *bredr_chan;
+- struct kref kref;
+- __u8 ident;
+- __u8 handle;
+- unsigned long state;
+- unsigned long flags;
+-
+- struct list_head amp_ctrls;
+- struct mutex amp_ctrls_lock;
+-};
+-
+-struct a2mp_cmd {
+- __u8 code;
+- __u8 ident;
+- __le16 len;
+- __u8 data[];
+-} __packed;
+-
+-/* A2MP command codes */
+-#define A2MP_COMMAND_REJ 0x01
+-struct a2mp_cmd_rej {
+- __le16 reason;
+- __u8 data[];
+-} __packed;
+-
+-#define A2MP_DISCOVER_REQ 0x02
+-struct a2mp_discov_req {
+- __le16 mtu;
+- __le16 ext_feat;
+-} __packed;
+-
+-struct a2mp_cl {
+- __u8 id;
+- __u8 type;
+- __u8 status;
+-} __packed;
+-
+-#define A2MP_DISCOVER_RSP 0x03
+-struct a2mp_discov_rsp {
+- __le16 mtu;
+- __le16 ext_feat;
+- struct a2mp_cl cl[];
+-} __packed;
+-
+-#define A2MP_CHANGE_NOTIFY 0x04
+-#define A2MP_CHANGE_RSP 0x05
+-
+-#define A2MP_GETINFO_REQ 0x06
+-struct a2mp_info_req {
+- __u8 id;
+-} __packed;
+-
+-#define A2MP_GETINFO_RSP 0x07
+-struct a2mp_info_rsp {
+- __u8 id;
+- __u8 status;
+- __le32 total_bw;
+- __le32 max_bw;
+- __le32 min_latency;
+- __le16 pal_cap;
+- __le16 assoc_size;
+-} __packed;
+-
+-#define A2MP_GETAMPASSOC_REQ 0x08
+-struct a2mp_amp_assoc_req {
+- __u8 id;
+-} __packed;
+-
+-#define A2MP_GETAMPASSOC_RSP 0x09
+-struct a2mp_amp_assoc_rsp {
+- __u8 id;
+- __u8 status;
+- __u8 amp_assoc[];
+-} __packed;
+-
+-#define A2MP_CREATEPHYSLINK_REQ 0x0A
+-#define A2MP_DISCONNPHYSLINK_REQ 0x0C
+-struct a2mp_physlink_req {
+- __u8 local_id;
+- __u8 remote_id;
+- __u8 amp_assoc[];
+-} __packed;
+-
+-#define A2MP_CREATEPHYSLINK_RSP 0x0B
+-#define A2MP_DISCONNPHYSLINK_RSP 0x0D
+-struct a2mp_physlink_rsp {
+- __u8 local_id;
+- __u8 remote_id;
+- __u8 status;
+-} __packed;
+-
+-/* A2MP response status */
+-#define A2MP_STATUS_SUCCESS 0x00
+-#define A2MP_STATUS_INVALID_CTRL_ID 0x01
+-#define A2MP_STATUS_UNABLE_START_LINK_CREATION 0x02
+-#define A2MP_STATUS_NO_PHYSICAL_LINK_EXISTS 0x02
+-#define A2MP_STATUS_COLLISION_OCCURED 0x03
+-#define A2MP_STATUS_DISCONN_REQ_RECVD 0x04
+-#define A2MP_STATUS_PHYS_LINK_EXISTS 0x05
+-#define A2MP_STATUS_SECURITY_VIOLATION 0x06
+-
+-struct amp_mgr *amp_mgr_get(struct amp_mgr *mgr);
+-
+-#if IS_ENABLED(CONFIG_BT_HS)
+-int amp_mgr_put(struct amp_mgr *mgr);
+-struct l2cap_chan *a2mp_channel_create(struct l2cap_conn *conn,
+- struct sk_buff *skb);
+-void a2mp_discover_amp(struct l2cap_chan *chan);
+-#else
+-static inline int amp_mgr_put(struct amp_mgr *mgr)
+-{
+- return 0;
+-}
+-
+-static inline struct l2cap_chan *a2mp_channel_create(struct l2cap_conn *conn,
+- struct sk_buff *skb)
+-{
+- return NULL;
+-}
+-
+-static inline void a2mp_discover_amp(struct l2cap_chan *chan)
+-{
+-}
+-#endif
+-
+-void a2mp_send_getinfo_rsp(struct hci_dev *hdev);
+-void a2mp_send_getampassoc_rsp(struct hci_dev *hdev, u8 status);
+-void a2mp_send_create_phy_link_req(struct hci_dev *hdev, u8 status);
+-void a2mp_send_create_phy_link_rsp(struct hci_dev *hdev, u8 status);
+-
+-#endif /* __A2MP_H */
+--- a/net/bluetooth/amp.c
++++ /dev/null
+@@ -1,590 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0-only
+-/*
+- Copyright (c) 2011,2012 Intel Corp.
+-
+-*/
+-
+-#include <net/bluetooth/bluetooth.h>
+-#include <net/bluetooth/hci.h>
+-#include <net/bluetooth/hci_core.h>
+-#include <crypto/hash.h>
+-
+-#include "hci_request.h"
+-#include "a2mp.h"
+-#include "amp.h"
+-
+-/* Remote AMP Controllers interface */
+-void amp_ctrl_get(struct amp_ctrl *ctrl)
+-{
+- BT_DBG("ctrl %p orig refcnt %d", ctrl,
+- kref_read(&ctrl->kref));
+-
+- kref_get(&ctrl->kref);
+-}
+-
+-static void amp_ctrl_destroy(struct kref *kref)
+-{
+- struct amp_ctrl *ctrl = container_of(kref, struct amp_ctrl, kref);
+-
+- BT_DBG("ctrl %p", ctrl);
+-
+- kfree(ctrl->assoc);
+- kfree(ctrl);
+-}
+-
+-int amp_ctrl_put(struct amp_ctrl *ctrl)
+-{
+- BT_DBG("ctrl %p orig refcnt %d", ctrl,
+- kref_read(&ctrl->kref));
+-
+- return kref_put(&ctrl->kref, &_ctrl_destroy);
+-}
+-
+-struct amp_ctrl *amp_ctrl_add(struct amp_mgr *mgr, u8 id)
+-{
+- struct amp_ctrl *ctrl;
+-
+- ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
+- if (!ctrl)
+- return NULL;
+-
+- kref_init(&ctrl->kref);
+- ctrl->id = id;
+-
+- mutex_lock(&mgr->amp_ctrls_lock);
+- list_add(&ctrl->list, &mgr->amp_ctrls);
+- mutex_unlock(&mgr->amp_ctrls_lock);
+-
+- BT_DBG("mgr %p ctrl %p", mgr, ctrl);
+-
+- return ctrl;
+-}
+-
+-void amp_ctrl_list_flush(struct amp_mgr *mgr)
+-{
+- struct amp_ctrl *ctrl, *n;
+-
+- BT_DBG("mgr %p", mgr);
+-
+- mutex_lock(&mgr->amp_ctrls_lock);
+- list_for_each_entry_safe(ctrl, n, &mgr->amp_ctrls, list) {
+- list_del(&ctrl->list);
+- amp_ctrl_put(ctrl);
+- }
+- mutex_unlock(&mgr->amp_ctrls_lock);
+-}
+-
+-struct amp_ctrl *amp_ctrl_lookup(struct amp_mgr *mgr, u8 id)
+-{
+- struct amp_ctrl *ctrl;
+-
+- BT_DBG("mgr %p id %u", mgr, id);
+-
+- mutex_lock(&mgr->amp_ctrls_lock);
+- list_for_each_entry(ctrl, &mgr->amp_ctrls, list) {
+- if (ctrl->id == id) {
+- amp_ctrl_get(ctrl);
+- mutex_unlock(&mgr->amp_ctrls_lock);
+- return ctrl;
+- }
+- }
+- mutex_unlock(&mgr->amp_ctrls_lock);
+-
+- return NULL;
+-}
+-
+-/* Physical Link interface */
+-static u8 __next_handle(struct amp_mgr *mgr)
+-{
+- if (++mgr->handle == 0)
+- mgr->handle = 1;
+-
+- return mgr->handle;
+-}
+-
+-struct hci_conn *phylink_add(struct hci_dev *hdev, struct amp_mgr *mgr,
+- u8 remote_id, bool out)
+-{
+- bdaddr_t *dst = &mgr->l2cap_conn->hcon->dst;
+- struct hci_conn *hcon;
+- u8 role = out ? HCI_ROLE_MASTER : HCI_ROLE_SLAVE;
+-
+- hcon = hci_conn_add(hdev, AMP_LINK, dst, role, __next_handle(mgr));
+- if (!hcon)
+- return NULL;
+-
+- BT_DBG("hcon %p dst %pMR", hcon, dst);
+-
+- hcon->state = BT_CONNECT;
+- hcon->attempt++;
+- hcon->remote_id = remote_id;
+- hcon->amp_mgr = amp_mgr_get(mgr);
+-
+- return hcon;
+-}
+-
+-/* AMP crypto key generation interface */
+-static int hmac_sha256(u8 *key, u8 ksize, char *plaintext, u8 psize, u8 *output)
+-{
+- struct crypto_shash *tfm;
+- struct shash_desc *shash;
+- int ret;
+-
+- if (!ksize)
+- return -EINVAL;
+-
+- tfm = crypto_alloc_shash("hmac(sha256)", 0, 0);
+- if (IS_ERR(tfm)) {
+- BT_DBG("crypto_alloc_ahash failed: err %ld", PTR_ERR(tfm));
+- return PTR_ERR(tfm);
+- }
+-
+- ret = crypto_shash_setkey(tfm, key, ksize);
+- if (ret) {
+- BT_DBG("crypto_ahash_setkey failed: err %d", ret);
+- goto failed;
+- }
+-
+- shash = kzalloc(sizeof(*shash) + crypto_shash_descsize(tfm),
+- GFP_KERNEL);
+- if (!shash) {
+- ret = -ENOMEM;
+- goto failed;
+- }
+-
+- shash->tfm = tfm;
+-
+- ret = crypto_shash_digest(shash, plaintext, psize, output);
+-
+- kfree(shash);
+-
+-failed:
+- crypto_free_shash(tfm);
+- return ret;
+-}
+-
+-int phylink_gen_key(struct hci_conn *conn, u8 *data, u8 *len, u8 *type)
+-{
+- struct hci_dev *hdev = conn->hdev;
+- struct link_key *key;
+- u8 keybuf[HCI_AMP_LINK_KEY_SIZE];
+- u8 gamp_key[HCI_AMP_LINK_KEY_SIZE];
+- int err;
+-
+- if (!hci_conn_check_link_mode(conn))
+- return -EACCES;
+-
+- BT_DBG("conn %p key_type %d", conn, conn->key_type);
+-
+- /* Legacy key */
+- if (conn->key_type < 3) {
+- bt_dev_err(hdev, "legacy key type %u", conn->key_type);
+- return -EACCES;
+- }
+-
+- *type = conn->key_type;
+- *len = HCI_AMP_LINK_KEY_SIZE;
+-
+- key = hci_find_link_key(hdev, &conn->dst);
+- if (!key) {
+- BT_DBG("No Link key for conn %p dst %pMR", conn, &conn->dst);
+- return -EACCES;
+- }
+-
+- /* BR/EDR Link Key concatenated together with itself */
+- memcpy(&keybuf[0], key->val, HCI_LINK_KEY_SIZE);
+- memcpy(&keybuf[HCI_LINK_KEY_SIZE], key->val, HCI_LINK_KEY_SIZE);
+-
+- /* Derive Generic AMP Link Key (gamp) */
+- err = hmac_sha256(keybuf, HCI_AMP_LINK_KEY_SIZE, "gamp", 4, gamp_key);
+- if (err) {
+- bt_dev_err(hdev, "could not derive Generic AMP Key: err %d", err);
+- return err;
+- }
+-
+- if (conn->key_type == HCI_LK_DEBUG_COMBINATION) {
+- BT_DBG("Use Generic AMP Key (gamp)");
+- memcpy(data, gamp_key, HCI_AMP_LINK_KEY_SIZE);
+- return err;
+- }
+-
+- /* Derive Dedicated AMP Link Key: "802b" is 802.11 PAL keyID */
+- return hmac_sha256(gamp_key, HCI_AMP_LINK_KEY_SIZE, "802b", 4, data);
+-}
+-
+-static void read_local_amp_assoc_complete(struct hci_dev *hdev, u8 status,
+- u16 opcode, struct sk_buff *skb)
+-{
+- struct hci_rp_read_local_amp_assoc *rp = (void *)skb->data;
+- struct amp_assoc *assoc = &hdev->loc_assoc;
+- size_t rem_len, frag_len;
+-
+- BT_DBG("%s status 0x%2.2x", hdev->name, rp->status);
+-
+- if (rp->status)
+- goto send_rsp;
+-
+- frag_len = skb->len - sizeof(*rp);
+- rem_len = __le16_to_cpu(rp->rem_len);
+-
+- if (rem_len > frag_len) {
+- BT_DBG("frag_len %zu rem_len %zu", frag_len, rem_len);
+-
+- memcpy(assoc->data + assoc->offset, rp->frag, frag_len);
+- assoc->offset += frag_len;
+-
+- /* Read other fragments */
+- amp_read_loc_assoc_frag(hdev, rp->phy_handle);
+-
+- return;
+- }
+-
+- memcpy(assoc->data + assoc->offset, rp->frag, rem_len);
+- assoc->len = assoc->offset + rem_len;
+- assoc->offset = 0;
+-
+-send_rsp:
+- /* Send A2MP Rsp when all fragments are received */
+- a2mp_send_getampassoc_rsp(hdev, rp->status);
+- a2mp_send_create_phy_link_req(hdev, rp->status);
+-}
+-
+-void amp_read_loc_assoc_frag(struct hci_dev *hdev, u8 phy_handle)
+-{
+- struct hci_cp_read_local_amp_assoc cp;
+- struct amp_assoc *loc_assoc = &hdev->loc_assoc;
+- struct hci_request req;
+- int err;
+-
+- BT_DBG("%s handle %u", hdev->name, phy_handle);
+-
+- cp.phy_handle = phy_handle;
+- cp.max_len = cpu_to_le16(hdev->amp_assoc_size);
+- cp.len_so_far = cpu_to_le16(loc_assoc->offset);
+-
+- hci_req_init(&req, hdev);
+- hci_req_add(&req, HCI_OP_READ_LOCAL_AMP_ASSOC, sizeof(cp), &cp);
+- err = hci_req_run_skb(&req, read_local_amp_assoc_complete);
+- if (err < 0)
+- a2mp_send_getampassoc_rsp(hdev, A2MP_STATUS_INVALID_CTRL_ID);
+-}
+-
+-void amp_read_loc_assoc(struct hci_dev *hdev, struct amp_mgr *mgr)
+-{
+- struct hci_cp_read_local_amp_assoc cp;
+- struct hci_request req;
+- int err;
+-
+- memset(&hdev->loc_assoc, 0, sizeof(struct amp_assoc));
+- memset(&cp, 0, sizeof(cp));
+-
+- cp.max_len = cpu_to_le16(hdev->amp_assoc_size);
+-
+- set_bit(READ_LOC_AMP_ASSOC, &mgr->state);
+- hci_req_init(&req, hdev);
+- hci_req_add(&req, HCI_OP_READ_LOCAL_AMP_ASSOC, sizeof(cp), &cp);
+- err = hci_req_run_skb(&req, read_local_amp_assoc_complete);
+- if (err < 0)
+- a2mp_send_getampassoc_rsp(hdev, A2MP_STATUS_INVALID_CTRL_ID);
+-}
+-
+-void amp_read_loc_assoc_final_data(struct hci_dev *hdev,
+- struct hci_conn *hcon)
+-{
+- struct hci_cp_read_local_amp_assoc cp;
+- struct amp_mgr *mgr = hcon->amp_mgr;
+- struct hci_request req;
+- int err;
+-
+- if (!mgr)
+- return;
+-
+- cp.phy_handle = hcon->handle;
+- cp.len_so_far = cpu_to_le16(0);
+- cp.max_len = cpu_to_le16(hdev->amp_assoc_size);
+-
+- set_bit(READ_LOC_AMP_ASSOC_FINAL, &mgr->state);
+-
+- /* Read Local AMP Assoc final link information data */
+- hci_req_init(&req, hdev);
+- hci_req_add(&req, HCI_OP_READ_LOCAL_AMP_ASSOC, sizeof(cp), &cp);
+- err = hci_req_run_skb(&req, read_local_amp_assoc_complete);
+- if (err < 0)
+- a2mp_send_getampassoc_rsp(hdev, A2MP_STATUS_INVALID_CTRL_ID);
+-}
+-
+-static void write_remote_amp_assoc_complete(struct hci_dev *hdev, u8 status,
+- u16 opcode, struct sk_buff *skb)
+-{
+- struct hci_rp_write_remote_amp_assoc *rp = (void *)skb->data;
+-
+- BT_DBG("%s status 0x%2.2x phy_handle 0x%2.2x",
+- hdev->name, rp->status, rp->phy_handle);
+-
+- if (rp->status)
+- return;
+-
+- amp_write_rem_assoc_continue(hdev, rp->phy_handle);
+-}
+-
+-/* Write AMP Assoc data fragments, returns true with last fragment written*/
+-static bool amp_write_rem_assoc_frag(struct hci_dev *hdev,
+- struct hci_conn *hcon)
+-{
+- struct hci_cp_write_remote_amp_assoc *cp;
+- struct amp_mgr *mgr = hcon->amp_mgr;
+- struct amp_ctrl *ctrl;
+- struct hci_request req;
+- u16 frag_len, len;
+-
+- ctrl = amp_ctrl_lookup(mgr, hcon->remote_id);
+- if (!ctrl)
+- return false;
+-
+- if (!ctrl->assoc_rem_len) {
+- BT_DBG("all fragments are written");
+- ctrl->assoc_rem_len = ctrl->assoc_len;
+- ctrl->assoc_len_so_far = 0;
+-
+- amp_ctrl_put(ctrl);
+- return true;
+- }
+-
+- frag_len = min_t(u16, 248, ctrl->assoc_rem_len);
+- len = frag_len + sizeof(*cp);
+-
+- cp = kzalloc(len, GFP_KERNEL);
+- if (!cp) {
+- amp_ctrl_put(ctrl);
+- return false;
+- }
+-
+- BT_DBG("hcon %p ctrl %p frag_len %u assoc_len %u rem_len %u",
+- hcon, ctrl, frag_len, ctrl->assoc_len, ctrl->assoc_rem_len);
+-
+- cp->phy_handle = hcon->handle;
+- cp->len_so_far = cpu_to_le16(ctrl->assoc_len_so_far);
+- cp->rem_len = cpu_to_le16(ctrl->assoc_rem_len);
+- memcpy(cp->frag, ctrl->assoc, frag_len);
+-
+- ctrl->assoc_len_so_far += frag_len;
+- ctrl->assoc_rem_len -= frag_len;
+-
+- amp_ctrl_put(ctrl);
+-
+- hci_req_init(&req, hdev);
+- hci_req_add(&req, HCI_OP_WRITE_REMOTE_AMP_ASSOC, len, cp);
+- hci_req_run_skb(&req, write_remote_amp_assoc_complete);
+-
+- kfree(cp);
+-
+- return false;
+-}
+-
+-void amp_write_rem_assoc_continue(struct hci_dev *hdev, u8 handle)
+-{
+- struct hci_conn *hcon;
+-
+- BT_DBG("%s phy handle 0x%2.2x", hdev->name, handle);
+-
+- hcon = hci_conn_hash_lookup_handle(hdev, handle);
+- if (!hcon)
+- return;
+-
+- /* Send A2MP create phylink rsp when all fragments are written */
+- if (amp_write_rem_assoc_frag(hdev, hcon))
+- a2mp_send_create_phy_link_rsp(hdev, 0);
+-}
+-
+-void amp_write_remote_assoc(struct hci_dev *hdev, u8 handle)
+-{
+- struct hci_conn *hcon;
+-
+- BT_DBG("%s phy handle 0x%2.2x", hdev->name, handle);
+-
+- hcon = hci_conn_hash_lookup_handle(hdev, handle);
+- if (!hcon)
+- return;
+-
+- BT_DBG("%s phy handle 0x%2.2x hcon %p", hdev->name, handle, hcon);
+-
+- amp_write_rem_assoc_frag(hdev, hcon);
+-}
+-
+-static void create_phylink_complete(struct hci_dev *hdev, u8 status,
+- u16 opcode)
+-{
+- struct hci_cp_create_phy_link *cp;
+-
+- BT_DBG("%s status 0x%2.2x", hdev->name, status);
+-
+- cp = hci_sent_cmd_data(hdev, HCI_OP_CREATE_PHY_LINK);
+- if (!cp)
+- return;
+-
+- hci_dev_lock(hdev);
+-
+- if (status) {
+- struct hci_conn *hcon;
+-
+- hcon = hci_conn_hash_lookup_handle(hdev, cp->phy_handle);
+- if (hcon)
+- hci_conn_del(hcon);
+- } else {
+- amp_write_remote_assoc(hdev, cp->phy_handle);
+- }
+-
+- hci_dev_unlock(hdev);
+-}
+-
+-void amp_create_phylink(struct hci_dev *hdev, struct amp_mgr *mgr,
+- struct hci_conn *hcon)
+-{
+- struct hci_cp_create_phy_link cp;
+- struct hci_request req;
+-
+- cp.phy_handle = hcon->handle;
+-
+- BT_DBG("%s hcon %p phy handle 0x%2.2x", hdev->name, hcon,
+- hcon->handle);
+-
+- if (phylink_gen_key(mgr->l2cap_conn->hcon, cp.key, &cp.key_len,
+- &cp.key_type)) {
+- BT_DBG("Cannot create link key");
+- return;
+- }
+-
+- hci_req_init(&req, hdev);
+- hci_req_add(&req, HCI_OP_CREATE_PHY_LINK, sizeof(cp), &cp);
+- hci_req_run(&req, create_phylink_complete);
+-}
+-
+-static void accept_phylink_complete(struct hci_dev *hdev, u8 status,
+- u16 opcode)
+-{
+- struct hci_cp_accept_phy_link *cp;
+-
+- BT_DBG("%s status 0x%2.2x", hdev->name, status);
+-
+- if (status)
+- return;
+-
+- cp = hci_sent_cmd_data(hdev, HCI_OP_ACCEPT_PHY_LINK);
+- if (!cp)
+- return;
+-
+- amp_write_remote_assoc(hdev, cp->phy_handle);
+-}
+-
+-void amp_accept_phylink(struct hci_dev *hdev, struct amp_mgr *mgr,
+- struct hci_conn *hcon)
+-{
+- struct hci_cp_accept_phy_link cp;
+- struct hci_request req;
+-
+- cp.phy_handle = hcon->handle;
+-
+- BT_DBG("%s hcon %p phy handle 0x%2.2x", hdev->name, hcon,
+- hcon->handle);
+-
+- if (phylink_gen_key(mgr->l2cap_conn->hcon, cp.key, &cp.key_len,
+- &cp.key_type)) {
+- BT_DBG("Cannot create link key");
+- return;
+- }
+-
+- hci_req_init(&req, hdev);
+- hci_req_add(&req, HCI_OP_ACCEPT_PHY_LINK, sizeof(cp), &cp);
+- hci_req_run(&req, accept_phylink_complete);
+-}
+-
+-void amp_physical_cfm(struct hci_conn *bredr_hcon, struct hci_conn *hs_hcon)
+-{
+- struct hci_dev *bredr_hdev = hci_dev_hold(bredr_hcon->hdev);
+- struct amp_mgr *mgr = hs_hcon->amp_mgr;
+- struct l2cap_chan *bredr_chan;
+-
+- BT_DBG("bredr_hcon %p hs_hcon %p mgr %p", bredr_hcon, hs_hcon, mgr);
+-
+- if (!bredr_hdev || !mgr || !mgr->bredr_chan)
+- return;
+-
+- bredr_chan = mgr->bredr_chan;
+-
+- l2cap_chan_lock(bredr_chan);
+-
+- set_bit(FLAG_EFS_ENABLE, &bredr_chan->flags);
+- bredr_chan->remote_amp_id = hs_hcon->remote_id;
+- bredr_chan->local_amp_id = hs_hcon->hdev->id;
+- bredr_chan->hs_hcon = hs_hcon;
+- bredr_chan->conn->mtu = hs_hcon->hdev->block_mtu;
+-
+- __l2cap_physical_cfm(bredr_chan, 0);
+-
+- l2cap_chan_unlock(bredr_chan);
+-
+- hci_dev_put(bredr_hdev);
+-}
+-
+-void amp_create_logical_link(struct l2cap_chan *chan)
+-{
+- struct hci_conn *hs_hcon = chan->hs_hcon;
+- struct hci_cp_create_accept_logical_link cp;
+- struct hci_dev *hdev;
+-
+- BT_DBG("chan %p hs_hcon %p dst %pMR", chan, hs_hcon,
+- &chan->conn->hcon->dst);
+-
+- if (!hs_hcon)
+- return;
+-
+- hdev = hci_dev_hold(chan->hs_hcon->hdev);
+- if (!hdev)
+- return;
+-
+- cp.phy_handle = hs_hcon->handle;
+-
+- cp.tx_flow_spec.id = chan->local_id;
+- cp.tx_flow_spec.stype = chan->local_stype;
+- cp.tx_flow_spec.msdu = cpu_to_le16(chan->local_msdu);
+- cp.tx_flow_spec.sdu_itime = cpu_to_le32(chan->local_sdu_itime);
+- cp.tx_flow_spec.acc_lat = cpu_to_le32(chan->local_acc_lat);
+- cp.tx_flow_spec.flush_to = cpu_to_le32(chan->local_flush_to);
+-
+- cp.rx_flow_spec.id = chan->remote_id;
+- cp.rx_flow_spec.stype = chan->remote_stype;
+- cp.rx_flow_spec.msdu = cpu_to_le16(chan->remote_msdu);
+- cp.rx_flow_spec.sdu_itime = cpu_to_le32(chan->remote_sdu_itime);
+- cp.rx_flow_spec.acc_lat = cpu_to_le32(chan->remote_acc_lat);
+- cp.rx_flow_spec.flush_to = cpu_to_le32(chan->remote_flush_to);
+-
+- if (hs_hcon->out)
+- hci_send_cmd(hdev, HCI_OP_CREATE_LOGICAL_LINK, sizeof(cp),
+- &cp);
+- else
+- hci_send_cmd(hdev, HCI_OP_ACCEPT_LOGICAL_LINK, sizeof(cp),
+- &cp);
+-
+- hci_dev_put(hdev);
+-}
+-
+-void amp_disconnect_logical_link(struct hci_chan *hchan)
+-{
+- struct hci_conn *hcon = hchan->conn;
+- struct hci_cp_disconn_logical_link cp;
+-
+- if (hcon->state != BT_CONNECTED) {
+- BT_DBG("hchan %p not connected", hchan);
+- return;
+- }
+-
+- cp.log_handle = cpu_to_le16(hchan->handle);
+- hci_send_cmd(hcon->hdev, HCI_OP_DISCONN_LOGICAL_LINK, sizeof(cp), &cp);
+-}
+-
+-void amp_destroy_logical_link(struct hci_chan *hchan, u8 reason)
+-{
+- BT_DBG("hchan %p", hchan);
+-
+- hci_chan_del(hchan);
+-}
+--- a/net/bluetooth/amp.h
++++ /dev/null
+@@ -1,61 +0,0 @@
+-/* SPDX-License-Identifier: GPL-2.0-only */
+-/*
+- Copyright (c) 2011,2012 Intel Corp.
+-
+-*/
+-
+-#ifndef __AMP_H
+-#define __AMP_H
+-
+-struct amp_ctrl {
+- struct list_head list;
+- struct kref kref;
+- __u8 id;
+- __u16 assoc_len_so_far;
+- __u16 assoc_rem_len;
+- __u16 assoc_len;
+- __u8 *assoc;
+-};
+-
+-int amp_ctrl_put(struct amp_ctrl *ctrl);
+-void amp_ctrl_get(struct amp_ctrl *ctrl);
+-struct amp_ctrl *amp_ctrl_add(struct amp_mgr *mgr, u8 id);
+-struct amp_ctrl *amp_ctrl_lookup(struct amp_mgr *mgr, u8 id);
+-void amp_ctrl_list_flush(struct amp_mgr *mgr);
+-
+-struct hci_conn *phylink_add(struct hci_dev *hdev, struct amp_mgr *mgr,
+- u8 remote_id, bool out);
+-
+-int phylink_gen_key(struct hci_conn *hcon, u8 *data, u8 *len, u8 *type);
+-
+-void amp_read_loc_info(struct hci_dev *hdev, struct amp_mgr *mgr);
+-void amp_read_loc_assoc_frag(struct hci_dev *hdev, u8 phy_handle);
+-void amp_read_loc_assoc(struct hci_dev *hdev, struct amp_mgr *mgr);
+-void amp_read_loc_assoc_final_data(struct hci_dev *hdev,
+- struct hci_conn *hcon);
+-void amp_create_phylink(struct hci_dev *hdev, struct amp_mgr *mgr,
+- struct hci_conn *hcon);
+-void amp_accept_phylink(struct hci_dev *hdev, struct amp_mgr *mgr,
+- struct hci_conn *hcon);
+-
+-#if IS_ENABLED(CONFIG_BT_HS)
+-void amp_create_logical_link(struct l2cap_chan *chan);
+-void amp_disconnect_logical_link(struct hci_chan *hchan);
+-#else
+-static inline void amp_create_logical_link(struct l2cap_chan *chan)
+-{
+-}
+-
+-static inline void amp_disconnect_logical_link(struct hci_chan *hchan)
+-{
+-}
+-#endif
+-
+-void amp_write_remote_assoc(struct hci_dev *hdev, u8 handle);
+-void amp_write_rem_assoc_continue(struct hci_dev *hdev, u8 handle);
+-void amp_physical_cfm(struct hci_conn *bredr_hcon, struct hci_conn *hs_hcon);
+-void amp_create_logical_link(struct l2cap_chan *chan);
+-void amp_disconnect_logical_link(struct hci_chan *hchan);
+-void amp_destroy_logical_link(struct hci_chan *hchan, u8 reason);
+-
+-#endif /* __AMP_H */
+--- a/net/bluetooth/hci_conn.c
++++ b/net/bluetooth/hci_conn.c
+@@ -35,7 +35,6 @@
+
+ #include "hci_request.h"
+ #include "smp.h"
+-#include "a2mp.h"
+ #include "eir.h"
+
+ struct sco_param {
+@@ -1162,9 +1161,6 @@ int hci_conn_del(struct hci_conn *conn)
+ }
+ }
+
+- if (conn->amp_mgr)
+- amp_mgr_put(conn->amp_mgr);
+-
+ skb_queue_purge(&conn->data_q);
+
+ /* Remove the connection from the list and cleanup its remaining
+--- a/net/bluetooth/hci_event.c
++++ b/net/bluetooth/hci_event.c
+@@ -35,8 +35,6 @@
+ #include "hci_request.h"
+ #include "hci_debugfs.h"
+ #include "hci_codec.h"
+-#include "a2mp.h"
+-#include "amp.h"
+ #include "smp.h"
+ #include "msft.h"
+ #include "eir.h"
+--- a/net/bluetooth/l2cap_core.c
++++ b/net/bluetooth/l2cap_core.c
+@@ -39,8 +39,6 @@
+ #include <net/bluetooth/l2cap.h>
+
+ #include "smp.h"
+-#include "a2mp.h"
+-#include "amp.h"
+
+ #define LE_FLOWCTL_MAX_CREDITS 65535
+
+@@ -167,24 +165,6 @@ static struct l2cap_chan *__l2cap_get_ch
+ return NULL;
+ }
+
+-static struct l2cap_chan *l2cap_get_chan_by_ident(struct l2cap_conn *conn,
+- u8 ident)
+-{
+- struct l2cap_chan *c;
+-
+- mutex_lock(&conn->chan_lock);
+- c = __l2cap_get_chan_by_ident(conn, ident);
+- if (c) {
+- /* Only lock if chan reference is not 0 */
+- c = l2cap_chan_hold_unless_zero(c);
+- if (c)
+- l2cap_chan_lock(c);
+- }
+- mutex_unlock(&conn->chan_lock);
+-
+- return c;
+-}
+-
+ static struct l2cap_chan *__l2cap_global_chan_by_addr(__le16 psm, bdaddr_t *src,
+ u8 src_type)
+ {
+@@ -682,7 +662,6 @@ void l2cap_chan_del(struct l2cap_chan *c
+ chan->ops->teardown(chan, err);
+
+ if (conn) {
+- struct amp_mgr *mgr = conn->hcon->amp_mgr;
+ /* Delete from channel list */
+ list_del(&chan->list);
+
+@@ -697,16 +676,6 @@ void l2cap_chan_del(struct l2cap_chan *c
+ if (chan->chan_type != L2CAP_CHAN_FIXED ||
+ test_bit(FLAG_HOLD_HCI_CONN, &chan->flags))
+ hci_conn_drop(conn->hcon);
+-
+- if (mgr && mgr->bredr_chan == chan)
+- mgr->bredr_chan = NULL;
+- }
+-
+- if (chan->hs_hchan) {
+- struct hci_chan *hs_hchan = chan->hs_hchan;
+-
+- BT_DBG("chan %p disconnect hs_hchan %p", chan, hs_hchan);
+- amp_disconnect_logical_link(hs_hchan);
+ }
+
+ if (test_bit(CONF_NOT_COMPLETE, &chan->conf_state))
+@@ -1008,12 +977,6 @@ static void l2cap_send_cmd(struct l2cap_
+ hci_send_acl(conn->hchan, skb, flags);
+ }
+
+-static bool __chan_is_moving(struct l2cap_chan *chan)
+-{
+- return chan->move_state != L2CAP_MOVE_STABLE &&
+- chan->move_state != L2CAP_MOVE_WAIT_PREPARE;
+-}
+-
+ static void l2cap_do_send(struct l2cap_chan *chan, struct sk_buff *skb)
+ {
+ struct hci_conn *hcon = chan->conn->hcon;
+@@ -1022,15 +985,6 @@ static void l2cap_do_send(struct l2cap_c
+ BT_DBG("chan %p, skb %p len %d priority %u", chan, skb, skb->len,
+ skb->priority);
+
+- if (chan->hs_hcon && !__chan_is_moving(chan)) {
+- if (chan->hs_hchan)
+- hci_send_acl(chan->hs_hchan, skb, ACL_COMPLETE);
+- else
+- kfree_skb(skb);
+-
+- return;
+- }
+-
+ /* Use NO_FLUSH for LE links (where this is the only option) or
+ * if the BR/EDR link supports it and flushing has not been
+ * explicitly requested (through FLAG_FLUSHABLE).
+@@ -1211,9 +1165,6 @@ static void l2cap_send_sframe(struct l2c
+ if (!control->sframe)
+ return;
+
+- if (__chan_is_moving(chan))
+- return;
+-
+ if (test_and_clear_bit(CONN_SEND_FBIT, &chan->conn_state) &&
+ !control->poll)
+ control->final = 1;
+@@ -1268,40 +1219,6 @@ static inline int __l2cap_no_conn_pendin
+ return !test_bit(CONF_CONNECT_PEND, &chan->conf_state);
+ }
+
+-static bool __amp_capable(struct l2cap_chan *chan)
+-{
+- struct l2cap_conn *conn = chan->conn;
+- struct hci_dev *hdev;
+- bool amp_available = false;
+-
+- if (!(conn->local_fixed_chan & L2CAP_FC_A2MP))
+- return false;
+-
+- if (!(conn->remote_fixed_chan & L2CAP_FC_A2MP))
+- return false;
+-
+- read_lock(&hci_dev_list_lock);
+- list_for_each_entry(hdev, &hci_dev_list, list) {
+- if (hdev->amp_type != AMP_TYPE_BREDR &&
+- test_bit(HCI_UP, &hdev->flags)) {
+- amp_available = true;
+- break;
+- }
+- }
+- read_unlock(&hci_dev_list_lock);
+-
+- if (chan->chan_policy == BT_CHANNEL_POLICY_AMP_PREFERRED)
+- return amp_available;
+-
+- return false;
+-}
+-
+-static bool l2cap_check_efs(struct l2cap_chan *chan)
+-{
+- /* Check EFS parameters */
+- return true;
+-}
+-
+ void l2cap_send_conn_req(struct l2cap_chan *chan)
+ {
+ struct l2cap_conn *conn = chan->conn;
+@@ -1317,76 +1234,6 @@ void l2cap_send_conn_req(struct l2cap_ch
+ l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_REQ, sizeof(req), &req);
+ }
+
+-static void l2cap_send_create_chan_req(struct l2cap_chan *chan, u8 amp_id)
+-{
+- struct l2cap_create_chan_req req;
+- req.scid = cpu_to_le16(chan->scid);
+- req.psm = chan->psm;
+- req.amp_id = amp_id;
+-
+- chan->ident = l2cap_get_ident(chan->conn);
+-
+- l2cap_send_cmd(chan->conn, chan->ident, L2CAP_CREATE_CHAN_REQ,
+- sizeof(req), &req);
+-}
+-
+-static void l2cap_move_setup(struct l2cap_chan *chan)
+-{
+- struct sk_buff *skb;
+-
+- BT_DBG("chan %p", chan);
+-
+- if (chan->mode != L2CAP_MODE_ERTM)
+- return;
+-
+- __clear_retrans_timer(chan);
+- __clear_monitor_timer(chan);
+- __clear_ack_timer(chan);
+-
+- chan->retry_count = 0;
+- skb_queue_walk(&chan->tx_q, skb) {
+- if (bt_cb(skb)->l2cap.retries)
+- bt_cb(skb)->l2cap.retries = 1;
+- else
+- break;
+- }
+-
+- chan->expected_tx_seq = chan->buffer_seq;
+-
+- clear_bit(CONN_REJ_ACT, &chan->conn_state);
+- clear_bit(CONN_SREJ_ACT, &chan->conn_state);
+- l2cap_seq_list_clear(&chan->retrans_list);
+- l2cap_seq_list_clear(&chan->srej_list);
+- skb_queue_purge(&chan->srej_q);
+-
+- chan->tx_state = L2CAP_TX_STATE_XMIT;
+- chan->rx_state = L2CAP_RX_STATE_MOVE;
+-
+- set_bit(CONN_REMOTE_BUSY, &chan->conn_state);
+-}
+-
+-static void l2cap_move_done(struct l2cap_chan *chan)
+-{
+- u8 move_role = chan->move_role;
+- BT_DBG("chan %p", chan);
+-
+- chan->move_state = L2CAP_MOVE_STABLE;
+- chan->move_role = L2CAP_MOVE_ROLE_NONE;
+-
+- if (chan->mode != L2CAP_MODE_ERTM)
+- return;
+-
+- switch (move_role) {
+- case L2CAP_MOVE_ROLE_INITIATOR:
+- l2cap_tx(chan, NULL, NULL, L2CAP_EV_EXPLICIT_POLL);
+- chan->rx_state = L2CAP_RX_STATE_WAIT_F;
+- break;
+- case L2CAP_MOVE_ROLE_RESPONDER:
+- chan->rx_state = L2CAP_RX_STATE_WAIT_P;
+- break;
+- }
+-}
+-
+ static void l2cap_chan_ready(struct l2cap_chan *chan)
+ {
+ /* The channel may have already been flagged as connected in
+@@ -1536,10 +1383,7 @@ static void l2cap_le_start(struct l2cap_
+
+ static void l2cap_start_connection(struct l2cap_chan *chan)
+ {
+- if (__amp_capable(chan)) {
+- BT_DBG("chan %p AMP capable: discover AMPs", chan);
+- a2mp_discover_amp(chan);
+- } else if (chan->conn->hcon->type == LE_LINK) {
++ if (chan->conn->hcon->type == LE_LINK) {
+ l2cap_le_start(chan);
+ } else {
+ l2cap_send_conn_req(chan);
+@@ -1643,11 +1487,6 @@ static void l2cap_send_disconn_req(struc
+ __clear_ack_timer(chan);
+ }
+
+- if (chan->scid == L2CAP_CID_A2MP) {
+- l2cap_state_change(chan, BT_DISCONN);
+- return;
+- }
+-
+ req.dcid = cpu_to_le16(chan->dcid);
+ req.scid = cpu_to_le16(chan->scid);
+ l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_DISCONN_REQ,
+@@ -1786,11 +1625,6 @@ static void l2cap_conn_ready(struct l2ca
+
+ l2cap_chan_lock(chan);
+
+- if (chan->scid == L2CAP_CID_A2MP) {
+- l2cap_chan_unlock(chan);
+- continue;
+- }
+-
+ if (hcon->type == LE_LINK) {
+ l2cap_le_start(chan);
+ } else if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) {
+@@ -2100,9 +1934,6 @@ static void l2cap_streaming_send(struct
+
+ BT_DBG("chan %p, skbs %p", chan, skbs);
+
+- if (__chan_is_moving(chan))
+- return;
+-
+ skb_queue_splice_tail_init(skbs, &chan->tx_q);
+
+ while (!skb_queue_empty(&chan->tx_q)) {
+@@ -2145,9 +1976,6 @@ static int l2cap_ertm_send(struct l2cap_
+ if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state))
+ return 0;
+
+- if (__chan_is_moving(chan))
+- return 0;
+-
+ while (chan->tx_send_head &&
+ chan->unacked_frames < chan->remote_tx_win &&
+ chan->tx_state == L2CAP_TX_STATE_XMIT) {
+@@ -2213,9 +2041,6 @@ static void l2cap_ertm_resend(struct l2c
+ if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state))
+ return;
+
+- if (__chan_is_moving(chan))
+- return;
+-
+ while (chan->retrans_list.head != L2CAP_SEQ_LIST_CLEAR) {
+ seq = l2cap_seq_list_pop(&chan->retrans_list);
+
+@@ -2555,8 +2380,7 @@ static int l2cap_segment_sdu(struct l2ca
+ pdu_len = chan->conn->mtu;
+
+ /* Constrain PDU size for BR/EDR connections */
+- if (!chan->hs_hcon)
+- pdu_len = min_t(size_t, pdu_len, L2CAP_BREDR_MAX_PAYLOAD);
++ pdu_len = min_t(size_t, pdu_len, L2CAP_BREDR_MAX_PAYLOAD);
+
+ /* Adjust for largest possible L2CAP overhead. */
+ if (chan->fcs)
+@@ -3334,11 +3158,6 @@ int l2cap_ertm_init(struct l2cap_chan *c
+
+ skb_queue_head_init(&chan->tx_q);
+
+- chan->local_amp_id = AMP_ID_BREDR;
+- chan->move_id = AMP_ID_BREDR;
+- chan->move_state = L2CAP_MOVE_STABLE;
+- chan->move_role = L2CAP_MOVE_ROLE_NONE;
+-
+ if (chan->mode != L2CAP_MODE_ERTM)
+ return 0;
+
+@@ -3373,52 +3192,19 @@ static inline __u8 l2cap_select_mode(__u
+
+ static inline bool __l2cap_ews_supported(struct l2cap_conn *conn)
+ {
+- return ((conn->local_fixed_chan & L2CAP_FC_A2MP) &&
+- (conn->feat_mask & L2CAP_FEAT_EXT_WINDOW));
++ return (conn->feat_mask & L2CAP_FEAT_EXT_WINDOW);
+ }
+
+ static inline bool __l2cap_efs_supported(struct l2cap_conn *conn)
+ {
+- return ((conn->local_fixed_chan & L2CAP_FC_A2MP) &&
+- (conn->feat_mask & L2CAP_FEAT_EXT_FLOW));
++ return (conn->feat_mask & L2CAP_FEAT_EXT_FLOW);
+ }
+
+ static void __l2cap_set_ertm_timeouts(struct l2cap_chan *chan,
+ struct l2cap_conf_rfc *rfc)
+ {
+- if (chan->local_amp_id != AMP_ID_BREDR && chan->hs_hcon) {
+- u64 ertm_to = chan->hs_hcon->hdev->amp_be_flush_to;
+-
+- /* Class 1 devices have must have ERTM timeouts
+- * exceeding the Link Supervision Timeout. The
+- * default Link Supervision Timeout for AMP
+- * controllers is 10 seconds.
+- *
+- * Class 1 devices use 0xffffffff for their
+- * best-effort flush timeout, so the clamping logic
+- * will result in a timeout that meets the above
+- * requirement. ERTM timeouts are 16-bit values, so
+- * the maximum timeout is 65.535 seconds.
+- */
+-
+- /* Convert timeout to milliseconds and round */
+- ertm_to = DIV_ROUND_UP_ULL(ertm_to, 1000);
+-
+- /* This is the recommended formula for class 2 devices
+- * that start ERTM timers when packets are sent to the
+- * controller.
+- */
+- ertm_to = 3 * ertm_to + 500;
+-
+- if (ertm_to > 0xffff)
+- ertm_to = 0xffff;
+-
+- rfc->retrans_timeout = cpu_to_le16((u16) ertm_to);
+- rfc->monitor_timeout = rfc->retrans_timeout;
+- } else {
+- rfc->retrans_timeout = cpu_to_le16(L2CAP_DEFAULT_RETRANS_TO);
+- rfc->monitor_timeout = cpu_to_le16(L2CAP_DEFAULT_MONITOR_TO);
+- }
++ rfc->retrans_timeout = cpu_to_le16(L2CAP_DEFAULT_RETRANS_TO);
++ rfc->monitor_timeout = cpu_to_le16(L2CAP_DEFAULT_MONITOR_TO);
+ }
+
+ static inline void l2cap_txwin_setup(struct l2cap_chan *chan)
+@@ -3673,13 +3459,7 @@ static int l2cap_parse_conf_req(struct l
+ case L2CAP_CONF_EWS:
+ if (olen != 2)
+ break;
+- if (!(chan->conn->local_fixed_chan & L2CAP_FC_A2MP))
+- return -ECONNREFUSED;
+- set_bit(FLAG_EXT_CTRL, &chan->flags);
+- set_bit(CONF_EWS_RECV, &chan->conf_state);
+- chan->tx_win_max = L2CAP_DEFAULT_EXT_WINDOW;
+- chan->remote_tx_win = val;
+- break;
++ return -ECONNREFUSED;
+
+ default:
+ if (hint)
+@@ -4107,11 +3887,7 @@ void __l2cap_connect_rsp_defer(struct l2
+ rsp.dcid = cpu_to_le16(chan->scid);
+ rsp.result = cpu_to_le16(L2CAP_CR_SUCCESS);
+ rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO);
+-
+- if (chan->hs_hcon)
+- rsp_code = L2CAP_CREATE_CHAN_RSP;
+- else
+- rsp_code = L2CAP_CONN_RSP;
++ rsp_code = L2CAP_CONN_RSP;
+
+ BT_DBG("chan %p rsp_code %u", chan, rsp_code);
+
+@@ -4274,7 +4050,6 @@ static struct l2cap_chan *l2cap_connect(
+ chan->dst_type = bdaddr_dst_type(conn->hcon);
+ chan->psm = psm;
+ chan->dcid = scid;
+- chan->local_amp_id = amp_id;
+
+ __l2cap_chan_add(conn, chan);
+
+@@ -4593,10 +4368,7 @@ static inline int l2cap_config_req(struc
+ /* check compatibility */
+
+ /* Send rsp for BR/EDR channel */
+- if (!chan->hs_hcon)
+- l2cap_send_efs_conf_rsp(chan, rsp, cmd->ident, flags);
+- else
+- chan->ident = cmd->ident;
++ l2cap_send_efs_conf_rsp(chan, rsp, cmd->ident, flags);
+ }
+
+ unlock:
+@@ -4648,15 +4420,7 @@ static inline int l2cap_config_rsp(struc
+ goto done;
+ }
+
+- if (!chan->hs_hcon) {
+- l2cap_send_efs_conf_rsp(chan, buf, cmd->ident,
+- 0);
+- } else {
+- if (l2cap_check_efs(chan)) {
+- amp_create_logical_link(chan);
+- chan->ident = cmd->ident;
+- }
+- }
++ l2cap_send_efs_conf_rsp(chan, buf, cmd->ident, 0);
+ }
+ goto done;
+
+@@ -4827,9 +4591,6 @@ static inline int l2cap_information_req(
+ if (!disable_ertm)
+ feat_mask |= L2CAP_FEAT_ERTM | L2CAP_FEAT_STREAMING
+ | L2CAP_FEAT_FCS;
+- if (conn->local_fixed_chan & L2CAP_FC_A2MP)
+- feat_mask |= L2CAP_FEAT_EXT_FLOW
+- | L2CAP_FEAT_EXT_WINDOW;
+
+ put_unaligned_le32(feat_mask, rsp->data);
+ l2cap_send_cmd(conn, cmd->ident, L2CAP_INFO_RSP, sizeof(buf),
+@@ -4920,751 +4681,6 @@ static inline int l2cap_information_rsp(
+ return 0;
+ }
+
+-static int l2cap_create_channel_req(struct l2cap_conn *conn,
+- struct l2cap_cmd_hdr *cmd,
+- u16 cmd_len, void *data)
+-{
+- struct l2cap_create_chan_req *req = data;
+- struct l2cap_create_chan_rsp rsp;
+- struct l2cap_chan *chan;
+- struct hci_dev *hdev;
+- u16 psm, scid;
+-
+- if (cmd_len != sizeof(*req))
+- return -EPROTO;
+-
+- if (!(conn->local_fixed_chan & L2CAP_FC_A2MP))
+- return -EINVAL;
+-
+- psm = le16_to_cpu(req->psm);
+- scid = le16_to_cpu(req->scid);
+-
+- BT_DBG("psm 0x%2.2x, scid 0x%4.4x, amp_id %d", psm, scid, req->amp_id);
+-
+- /* For controller id 0 make BR/EDR connection */
+- if (req->amp_id == AMP_ID_BREDR) {
+- l2cap_connect(conn, cmd, data, L2CAP_CREATE_CHAN_RSP,
+- req->amp_id);
+- return 0;
+- }
+-
+- /* Validate AMP controller id */
+- hdev = hci_dev_get(req->amp_id);
+- if (!hdev)
+- goto error;
+-
+- if (hdev->dev_type != HCI_AMP || !test_bit(HCI_UP, &hdev->flags)) {
+- hci_dev_put(hdev);
+- goto error;
+- }
+-
+- chan = l2cap_connect(conn, cmd, data, L2CAP_CREATE_CHAN_RSP,
+- req->amp_id);
+- if (chan) {
+- struct amp_mgr *mgr = conn->hcon->amp_mgr;
+- struct hci_conn *hs_hcon;
+-
+- hs_hcon = hci_conn_hash_lookup_ba(hdev, AMP_LINK,
+- &conn->hcon->dst);
+- if (!hs_hcon) {
+- hci_dev_put(hdev);
+- cmd_reject_invalid_cid(conn, cmd->ident, chan->scid,
+- chan->dcid);
+- return 0;
+- }
+-
+- BT_DBG("mgr %p bredr_chan %p hs_hcon %p", mgr, chan, hs_hcon);
+-
+- mgr->bredr_chan = chan;
+- chan->hs_hcon = hs_hcon;
+- chan->fcs = L2CAP_FCS_NONE;
+- conn->mtu = hdev->block_mtu;
+- }
+-
+- hci_dev_put(hdev);
+-
+- return 0;
+-
+-error:
+- rsp.dcid = 0;
+- rsp.scid = cpu_to_le16(scid);
+- rsp.result = cpu_to_le16(L2CAP_CR_BAD_AMP);
+- rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO);
+-
+- l2cap_send_cmd(conn, cmd->ident, L2CAP_CREATE_CHAN_RSP,
+- sizeof(rsp), &rsp);
+-
+- return 0;
+-}
+-
+-static void l2cap_send_move_chan_req(struct l2cap_chan *chan, u8 dest_amp_id)
+-{
+- struct l2cap_move_chan_req req;
+- u8 ident;
+-
+- BT_DBG("chan %p, dest_amp_id %d", chan, dest_amp_id);
+-
+- ident = l2cap_get_ident(chan->conn);
+- chan->ident = ident;
+-
+- req.icid = cpu_to_le16(chan->scid);
+- req.dest_amp_id = dest_amp_id;
+-
+- l2cap_send_cmd(chan->conn, ident, L2CAP_MOVE_CHAN_REQ, sizeof(req),
+- &req);
+-
+- __set_chan_timer(chan, L2CAP_MOVE_TIMEOUT);
+-}
+-
+-static void l2cap_send_move_chan_rsp(struct l2cap_chan *chan, u16 result)
+-{
+- struct l2cap_move_chan_rsp rsp;
+-
+- BT_DBG("chan %p, result 0x%4.4x", chan, result);
+-
+- rsp.icid = cpu_to_le16(chan->dcid);
+- rsp.result = cpu_to_le16(result);
+-
+- l2cap_send_cmd(chan->conn, chan->ident, L2CAP_MOVE_CHAN_RSP,
+- sizeof(rsp), &rsp);
+-}
+-
+-static void l2cap_send_move_chan_cfm(struct l2cap_chan *chan, u16 result)
+-{
+- struct l2cap_move_chan_cfm cfm;
+-
+- BT_DBG("chan %p, result 0x%4.4x", chan, result);
+-
+- chan->ident = l2cap_get_ident(chan->conn);
+-
+- cfm.icid = cpu_to_le16(chan->scid);
+- cfm.result = cpu_to_le16(result);
+-
+- l2cap_send_cmd(chan->conn, chan->ident, L2CAP_MOVE_CHAN_CFM,
+- sizeof(cfm), &cfm);
+-
+- __set_chan_timer(chan, L2CAP_MOVE_TIMEOUT);
+-}
+-
+-static void l2cap_send_move_chan_cfm_icid(struct l2cap_conn *conn, u16 icid)
+-{
+- struct l2cap_move_chan_cfm cfm;
+-
+- BT_DBG("conn %p, icid 0x%4.4x", conn, icid);
+-
+- cfm.icid = cpu_to_le16(icid);
+- cfm.result = cpu_to_le16(L2CAP_MC_UNCONFIRMED);
+-
+- l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_MOVE_CHAN_CFM,
+- sizeof(cfm), &cfm);
+-}
+-
+-static void l2cap_send_move_chan_cfm_rsp(struct l2cap_conn *conn, u8 ident,
+- u16 icid)
+-{
+- struct l2cap_move_chan_cfm_rsp rsp;
+-
+- BT_DBG("icid 0x%4.4x", icid);
+-
+- rsp.icid = cpu_to_le16(icid);
+- l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_CFM_RSP, sizeof(rsp), &rsp);
+-}
+-
+-static void __release_logical_link(struct l2cap_chan *chan)
+-{
+- chan->hs_hchan = NULL;
+- chan->hs_hcon = NULL;
+-
+- /* Placeholder - release the logical link */
+-}
+-
+-static void l2cap_logical_fail(struct l2cap_chan *chan)
+-{
+- /* Logical link setup failed */
+- if (chan->state != BT_CONNECTED) {
+- /* Create channel failure, disconnect */
+- l2cap_send_disconn_req(chan, ECONNRESET);
+- return;
+- }
+-
+- switch (chan->move_role) {
+- case L2CAP_MOVE_ROLE_RESPONDER:
+- l2cap_move_done(chan);
+- l2cap_send_move_chan_rsp(chan, L2CAP_MR_NOT_SUPP);
+- break;
+- case L2CAP_MOVE_ROLE_INITIATOR:
+- if (chan->move_state == L2CAP_MOVE_WAIT_LOGICAL_COMP ||
+- chan->move_state == L2CAP_MOVE_WAIT_LOGICAL_CFM) {
+- /* Remote has only sent pending or
+- * success responses, clean up
+- */
+- l2cap_move_done(chan);
+- }
+-
+- /* Other amp move states imply that the move
+- * has already aborted
+- */
+- l2cap_send_move_chan_cfm(chan, L2CAP_MC_UNCONFIRMED);
+- break;
+- }
+-}
+-
+-static void l2cap_logical_finish_create(struct l2cap_chan *chan,
+- struct hci_chan *hchan)
+-{
+- struct l2cap_conf_rsp rsp;
+-
+- chan->hs_hchan = hchan;
+- chan->hs_hcon->l2cap_data = chan->conn;
+-
+- l2cap_send_efs_conf_rsp(chan, &rsp, chan->ident, 0);
+-
+- if (test_bit(CONF_INPUT_DONE, &chan->conf_state)) {
+- int err;
+-
+- set_default_fcs(chan);
+-
+- err = l2cap_ertm_init(chan);
+- if (err < 0)
+- l2cap_send_disconn_req(chan, -err);
+- else
+- l2cap_chan_ready(chan);
+- }
+-}
+-
+-static void l2cap_logical_finish_move(struct l2cap_chan *chan,
+- struct hci_chan *hchan)
+-{
+- chan->hs_hcon = hchan->conn;
+- chan->hs_hcon->l2cap_data = chan->conn;
+-
+- BT_DBG("move_state %d", chan->move_state);
+-
+- switch (chan->move_state) {
+- case L2CAP_MOVE_WAIT_LOGICAL_COMP:
+- /* Move confirm will be sent after a success
+- * response is received
+- */
+- chan->move_state = L2CAP_MOVE_WAIT_RSP_SUCCESS;
+- break;
+- case L2CAP_MOVE_WAIT_LOGICAL_CFM:
+- if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) {
+- chan->move_state = L2CAP_MOVE_WAIT_LOCAL_BUSY;
+- } else if (chan->move_role == L2CAP_MOVE_ROLE_INITIATOR) {
+- chan->move_state = L2CAP_MOVE_WAIT_CONFIRM_RSP;
+- l2cap_send_move_chan_cfm(chan, L2CAP_MC_CONFIRMED);
+- } else if (chan->move_role == L2CAP_MOVE_ROLE_RESPONDER) {
+- chan->move_state = L2CAP_MOVE_WAIT_CONFIRM;
+- l2cap_send_move_chan_rsp(chan, L2CAP_MR_SUCCESS);
+- }
+- break;
+- default:
+- /* Move was not in expected state, free the channel */
+- __release_logical_link(chan);
+-
+- chan->move_state = L2CAP_MOVE_STABLE;
+- }
+-}
+-
+-/* Call with chan locked */
+-void l2cap_logical_cfm(struct l2cap_chan *chan, struct hci_chan *hchan,
+- u8 status)
+-{
+- BT_DBG("chan %p, hchan %p, status %d", chan, hchan, status);
+-
+- if (status) {
+- l2cap_logical_fail(chan);
+- __release_logical_link(chan);
+- return;
+- }
+-
+- if (chan->state != BT_CONNECTED) {
+- /* Ignore logical link if channel is on BR/EDR */
+- if (chan->local_amp_id != AMP_ID_BREDR)
+- l2cap_logical_finish_create(chan, hchan);
+- } else {
+- l2cap_logical_finish_move(chan, hchan);
+- }
+-}
+-
+-void l2cap_move_start(struct l2cap_chan *chan)
+-{
+- BT_DBG("chan %p", chan);
+-
+- if (chan->local_amp_id == AMP_ID_BREDR) {
+- if (chan->chan_policy != BT_CHANNEL_POLICY_AMP_PREFERRED)
+- return;
+- chan->move_role = L2CAP_MOVE_ROLE_INITIATOR;
+- chan->move_state = L2CAP_MOVE_WAIT_PREPARE;
+- /* Placeholder - start physical link setup */
+- } else {
+- chan->move_role = L2CAP_MOVE_ROLE_INITIATOR;
+- chan->move_state = L2CAP_MOVE_WAIT_RSP_SUCCESS;
+- chan->move_id = 0;
+- l2cap_move_setup(chan);
+- l2cap_send_move_chan_req(chan, 0);
+- }
+-}
+-
+-static void l2cap_do_create(struct l2cap_chan *chan, int result,
+- u8 local_amp_id, u8 remote_amp_id)
+-{
+- BT_DBG("chan %p state %s %u -> %u", chan, state_to_string(chan->state),
+- local_amp_id, remote_amp_id);
+-
+- chan->fcs = L2CAP_FCS_NONE;
+-
+- /* Outgoing channel on AMP */
+- if (chan->state == BT_CONNECT) {
+- if (result == L2CAP_CR_SUCCESS) {
+- chan->local_amp_id = local_amp_id;
+- l2cap_send_create_chan_req(chan, remote_amp_id);
+- } else {
+- /* Revert to BR/EDR connect */
+- l2cap_send_conn_req(chan);
+- }
+-
+- return;
+- }
+-
+- /* Incoming channel on AMP */
+- if (__l2cap_no_conn_pending(chan)) {
+- struct l2cap_conn_rsp rsp;
+- char buf[128];
+- rsp.scid = cpu_to_le16(chan->dcid);
+- rsp.dcid = cpu_to_le16(chan->scid);
+-
+- if (result == L2CAP_CR_SUCCESS) {
+- /* Send successful response */
+- rsp.result = cpu_to_le16(L2CAP_CR_SUCCESS);
+- rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO);
+- } else {
+- /* Send negative response */
+- rsp.result = cpu_to_le16(L2CAP_CR_NO_MEM);
+- rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO);
+- }
+-
+- l2cap_send_cmd(chan->conn, chan->ident, L2CAP_CREATE_CHAN_RSP,
+- sizeof(rsp), &rsp);
+-
+- if (result == L2CAP_CR_SUCCESS) {
+- l2cap_state_change(chan, BT_CONFIG);
+- set_bit(CONF_REQ_SENT, &chan->conf_state);
+- l2cap_send_cmd(chan->conn, l2cap_get_ident(chan->conn),
+- L2CAP_CONF_REQ,
+- l2cap_build_conf_req(chan, buf, sizeof(buf)), buf);
+- chan->num_conf_req++;
+- }
+- }
+-}
+-
+-static void l2cap_do_move_initiate(struct l2cap_chan *chan, u8 local_amp_id,
+- u8 remote_amp_id)
+-{
+- l2cap_move_setup(chan);
+- chan->move_id = local_amp_id;
+- chan->move_state = L2CAP_MOVE_WAIT_RSP;
+-
+- l2cap_send_move_chan_req(chan, remote_amp_id);
+-}
+-
+-static void l2cap_do_move_respond(struct l2cap_chan *chan, int result)
+-{
+- struct hci_chan *hchan = NULL;
+-
+- /* Placeholder - get hci_chan for logical link */
+-
+- if (hchan) {
+- if (hchan->state == BT_CONNECTED) {
+- /* Logical link is ready to go */
+- chan->hs_hcon = hchan->conn;
+- chan->hs_hcon->l2cap_data = chan->conn;
+- chan->move_state = L2CAP_MOVE_WAIT_CONFIRM;
+- l2cap_send_move_chan_rsp(chan, L2CAP_MR_SUCCESS);
+-
+- l2cap_logical_cfm(chan, hchan, L2CAP_MR_SUCCESS);
+- } else {
+- /* Wait for logical link to be ready */
+- chan->move_state = L2CAP_MOVE_WAIT_LOGICAL_CFM;
+- }
+- } else {
+- /* Logical link not available */
+- l2cap_send_move_chan_rsp(chan, L2CAP_MR_NOT_ALLOWED);
+- }
+-}
+-
+-static void l2cap_do_move_cancel(struct l2cap_chan *chan, int result)
+-{
+- if (chan->move_role == L2CAP_MOVE_ROLE_RESPONDER) {
+- u8 rsp_result;
+- if (result == -EINVAL)
+- rsp_result = L2CAP_MR_BAD_ID;
+- else
+- rsp_result = L2CAP_MR_NOT_ALLOWED;
+-
+- l2cap_send_move_chan_rsp(chan, rsp_result);
+- }
+-
+- chan->move_role = L2CAP_MOVE_ROLE_NONE;
+- chan->move_state = L2CAP_MOVE_STABLE;
+-
+- /* Restart data transmission */
+- l2cap_ertm_send(chan);
+-}
+-
+-/* Invoke with locked chan */
+-void __l2cap_physical_cfm(struct l2cap_chan *chan, int result)
+-{
+- u8 local_amp_id = chan->local_amp_id;
+- u8 remote_amp_id = chan->remote_amp_id;
+-
+- BT_DBG("chan %p, result %d, local_amp_id %d, remote_amp_id %d",
+- chan, result, local_amp_id, remote_amp_id);
+-
+- if (chan->state == BT_DISCONN || chan->state == BT_CLOSED)
+- return;
+-
+- if (chan->state != BT_CONNECTED) {
+- l2cap_do_create(chan, result, local_amp_id, remote_amp_id);
+- } else if (result != L2CAP_MR_SUCCESS) {
+- l2cap_do_move_cancel(chan, result);
+- } else {
+- switch (chan->move_role) {
+- case L2CAP_MOVE_ROLE_INITIATOR:
+- l2cap_do_move_initiate(chan, local_amp_id,
+- remote_amp_id);
+- break;
+- case L2CAP_MOVE_ROLE_RESPONDER:
+- l2cap_do_move_respond(chan, result);
+- break;
+- default:
+- l2cap_do_move_cancel(chan, result);
+- break;
+- }
+- }
+-}
+-
+-static inline int l2cap_move_channel_req(struct l2cap_conn *conn,
+- struct l2cap_cmd_hdr *cmd,
+- u16 cmd_len, void *data)
+-{
+- struct l2cap_move_chan_req *req = data;
+- struct l2cap_move_chan_rsp rsp;
+- struct l2cap_chan *chan;
+- u16 icid = 0;
+- u16 result = L2CAP_MR_NOT_ALLOWED;
+-
+- if (cmd_len != sizeof(*req))
+- return -EPROTO;
+-
+- icid = le16_to_cpu(req->icid);
+-
+- BT_DBG("icid 0x%4.4x, dest_amp_id %d", icid, req->dest_amp_id);
+-
+- if (!(conn->local_fixed_chan & L2CAP_FC_A2MP))
+- return -EINVAL;
+-
+- chan = l2cap_get_chan_by_dcid(conn, icid);
+- if (!chan) {
+- rsp.icid = cpu_to_le16(icid);
+- rsp.result = cpu_to_le16(L2CAP_MR_NOT_ALLOWED);
+- l2cap_send_cmd(conn, cmd->ident, L2CAP_MOVE_CHAN_RSP,
+- sizeof(rsp), &rsp);
+- return 0;
+- }
+-
+- chan->ident = cmd->ident;
+-
+- if (chan->scid < L2CAP_CID_DYN_START ||
+- chan->chan_policy == BT_CHANNEL_POLICY_BREDR_ONLY ||
+- (chan->mode != L2CAP_MODE_ERTM &&
+- chan->mode != L2CAP_MODE_STREAMING)) {
+- result = L2CAP_MR_NOT_ALLOWED;
+- goto send_move_response;
+- }
+-
+- if (chan->local_amp_id == req->dest_amp_id) {
+- result = L2CAP_MR_SAME_ID;
+- goto send_move_response;
+- }
+-
+- if (req->dest_amp_id != AMP_ID_BREDR) {
+- struct hci_dev *hdev;
+- hdev = hci_dev_get(req->dest_amp_id);
+- if (!hdev || hdev->dev_type != HCI_AMP ||
+- !test_bit(HCI_UP, &hdev->flags)) {
+- if (hdev)
+- hci_dev_put(hdev);
+-
+- result = L2CAP_MR_BAD_ID;
+- goto send_move_response;
+- }
+- hci_dev_put(hdev);
+- }
+-
+- /* Detect a move collision. Only send a collision response
+- * if this side has "lost", otherwise proceed with the move.
+- * The winner has the larger bd_addr.
+- */
+- if ((__chan_is_moving(chan) ||
+- chan->move_role != L2CAP_MOVE_ROLE_NONE) &&
+- bacmp(&conn->hcon->src, &conn->hcon->dst) > 0) {
+- result = L2CAP_MR_COLLISION;
+- goto send_move_response;
+- }
+-
+- chan->move_role = L2CAP_MOVE_ROLE_RESPONDER;
+- l2cap_move_setup(chan);
+- chan->move_id = req->dest_amp_id;
+-
+- if (req->dest_amp_id == AMP_ID_BREDR) {
+- /* Moving to BR/EDR */
+- if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) {
+- chan->move_state = L2CAP_MOVE_WAIT_LOCAL_BUSY;
+- result = L2CAP_MR_PEND;
+- } else {
+- chan->move_state = L2CAP_MOVE_WAIT_CONFIRM;
+- result = L2CAP_MR_SUCCESS;
+- }
+- } else {
+- chan->move_state = L2CAP_MOVE_WAIT_PREPARE;
+- /* Placeholder - uncomment when amp functions are available */
+- /*amp_accept_physical(chan, req->dest_amp_id);*/
+- result = L2CAP_MR_PEND;
+- }
+-
+-send_move_response:
+- l2cap_send_move_chan_rsp(chan, result);
+-
+- l2cap_chan_unlock(chan);
+- l2cap_chan_put(chan);
+-
+- return 0;
+-}
+-
+-static void l2cap_move_continue(struct l2cap_conn *conn, u16 icid, u16 result)
+-{
+- struct l2cap_chan *chan;
+- struct hci_chan *hchan = NULL;
+-
+- chan = l2cap_get_chan_by_scid(conn, icid);
+- if (!chan) {
+- l2cap_send_move_chan_cfm_icid(conn, icid);
+- return;
+- }
+-
+- __clear_chan_timer(chan);
+- if (result == L2CAP_MR_PEND)
+- __set_chan_timer(chan, L2CAP_MOVE_ERTX_TIMEOUT);
+-
+- switch (chan->move_state) {
+- case L2CAP_MOVE_WAIT_LOGICAL_COMP:
+- /* Move confirm will be sent when logical link
+- * is complete.
+- */
+- chan->move_state = L2CAP_MOVE_WAIT_LOGICAL_CFM;
+- break;
+- case L2CAP_MOVE_WAIT_RSP_SUCCESS:
+- if (result == L2CAP_MR_PEND) {
+- break;
+- } else if (test_bit(CONN_LOCAL_BUSY,
+- &chan->conn_state)) {
+- chan->move_state = L2CAP_MOVE_WAIT_LOCAL_BUSY;
+- } else {
+- /* Logical link is up or moving to BR/EDR,
+- * proceed with move
+- */
+- chan->move_state = L2CAP_MOVE_WAIT_CONFIRM_RSP;
+- l2cap_send_move_chan_cfm(chan, L2CAP_MC_CONFIRMED);
+- }
+- break;
+- case L2CAP_MOVE_WAIT_RSP:
+- /* Moving to AMP */
+- if (result == L2CAP_MR_SUCCESS) {
+- /* Remote is ready, send confirm immediately
+- * after logical link is ready
+- */
+- chan->move_state = L2CAP_MOVE_WAIT_LOGICAL_CFM;
+- } else {
+- /* Both logical link and move success
+- * are required to confirm
+- */
+- chan->move_state = L2CAP_MOVE_WAIT_LOGICAL_COMP;
+- }
+-
+- /* Placeholder - get hci_chan for logical link */
+- if (!hchan) {
+- /* Logical link not available */
+- l2cap_send_move_chan_cfm(chan, L2CAP_MC_UNCONFIRMED);
+- break;
+- }
+-
+- /* If the logical link is not yet connected, do not
+- * send confirmation.
+- */
+- if (hchan->state != BT_CONNECTED)
+- break;
+-
+- /* Logical link is already ready to go */
+-
+- chan->hs_hcon = hchan->conn;
+- chan->hs_hcon->l2cap_data = chan->conn;
+-
+- if (result == L2CAP_MR_SUCCESS) {
+- /* Can confirm now */
+- l2cap_send_move_chan_cfm(chan, L2CAP_MC_CONFIRMED);
+- } else {
+- /* Now only need move success
+- * to confirm
+- */
+- chan->move_state = L2CAP_MOVE_WAIT_RSP_SUCCESS;
+- }
+-
+- l2cap_logical_cfm(chan, hchan, L2CAP_MR_SUCCESS);
+- break;
+- default:
+- /* Any other amp move state means the move failed. */
+- chan->move_id = chan->local_amp_id;
+- l2cap_move_done(chan);
+- l2cap_send_move_chan_cfm(chan, L2CAP_MC_UNCONFIRMED);
+- }
+-
+- l2cap_chan_unlock(chan);
+- l2cap_chan_put(chan);
+-}
+-
+-static void l2cap_move_fail(struct l2cap_conn *conn, u8 ident, u16 icid,
+- u16 result)
+-{
+- struct l2cap_chan *chan;
+-
+- chan = l2cap_get_chan_by_ident(conn, ident);
+- if (!chan) {
+- /* Could not locate channel, icid is best guess */
+- l2cap_send_move_chan_cfm_icid(conn, icid);
+- return;
+- }
+-
+- __clear_chan_timer(chan);
+-
+- if (chan->move_role == L2CAP_MOVE_ROLE_INITIATOR) {
+- if (result == L2CAP_MR_COLLISION) {
+- chan->move_role = L2CAP_MOVE_ROLE_RESPONDER;
+- } else {
+- /* Cleanup - cancel move */
+- chan->move_id = chan->local_amp_id;
+- l2cap_move_done(chan);
+- }
+- }
+-
+- l2cap_send_move_chan_cfm(chan, L2CAP_MC_UNCONFIRMED);
+-
+- l2cap_chan_unlock(chan);
+- l2cap_chan_put(chan);
+-}
+-
+-static int l2cap_move_channel_rsp(struct l2cap_conn *conn,
+- struct l2cap_cmd_hdr *cmd,
+- u16 cmd_len, void *data)
+-{
+- struct l2cap_move_chan_rsp *rsp = data;
+- u16 icid, result;
+-
+- if (cmd_len != sizeof(*rsp))
+- return -EPROTO;
+-
+- icid = le16_to_cpu(rsp->icid);
+- result = le16_to_cpu(rsp->result);
+-
+- BT_DBG("icid 0x%4.4x, result 0x%4.4x", icid, result);
+-
+- if (result == L2CAP_MR_SUCCESS || result == L2CAP_MR_PEND)
+- l2cap_move_continue(conn, icid, result);
+- else
+- l2cap_move_fail(conn, cmd->ident, icid, result);
+-
+- return 0;
+-}
+-
+-static int l2cap_move_channel_confirm(struct l2cap_conn *conn,
+- struct l2cap_cmd_hdr *cmd,
+- u16 cmd_len, void *data)
+-{
+- struct l2cap_move_chan_cfm *cfm = data;
+- struct l2cap_chan *chan;
+- u16 icid, result;
+-
+- if (cmd_len != sizeof(*cfm))
+- return -EPROTO;
+-
+- icid = le16_to_cpu(cfm->icid);
+- result = le16_to_cpu(cfm->result);
+-
+- BT_DBG("icid 0x%4.4x, result 0x%4.4x", icid, result);
+-
+- chan = l2cap_get_chan_by_dcid(conn, icid);
+- if (!chan) {
+- /* Spec requires a response even if the icid was not found */
+- l2cap_send_move_chan_cfm_rsp(conn, cmd->ident, icid);
+- return 0;
+- }
+-
+- if (chan->move_state == L2CAP_MOVE_WAIT_CONFIRM) {
+- if (result == L2CAP_MC_CONFIRMED) {
+- chan->local_amp_id = chan->move_id;
+- if (chan->local_amp_id == AMP_ID_BREDR)
+- __release_logical_link(chan);
+- } else {
+- chan->move_id = chan->local_amp_id;
+- }
+-
+- l2cap_move_done(chan);
+- }
+-
+- l2cap_send_move_chan_cfm_rsp(conn, cmd->ident, icid);
+-
+- l2cap_chan_unlock(chan);
+- l2cap_chan_put(chan);
+-
+- return 0;
+-}
+-
+-static inline int l2cap_move_channel_confirm_rsp(struct l2cap_conn *conn,
+- struct l2cap_cmd_hdr *cmd,
+- u16 cmd_len, void *data)
+-{
+- struct l2cap_move_chan_cfm_rsp *rsp = data;
+- struct l2cap_chan *chan;
+- u16 icid;
+-
+- if (cmd_len != sizeof(*rsp))
+- return -EPROTO;
+-
+- icid = le16_to_cpu(rsp->icid);
+-
+- BT_DBG("icid 0x%4.4x", icid);
+-
+- chan = l2cap_get_chan_by_scid(conn, icid);
+- if (!chan)
+- return 0;
+-
+- __clear_chan_timer(chan);
+-
+- if (chan->move_state == L2CAP_MOVE_WAIT_CONFIRM_RSP) {
+- chan->local_amp_id = chan->move_id;
+-
+- if (chan->local_amp_id == AMP_ID_BREDR && chan->hs_hchan)
+- __release_logical_link(chan);
+-
+- l2cap_move_done(chan);
+- }
+-
+- l2cap_chan_unlock(chan);
+- l2cap_chan_put(chan);
+-
+- return 0;
+-}
+-
+ static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn,
+ struct l2cap_cmd_hdr *cmd,
+ u16 cmd_len, u8 *data)
+@@ -5818,7 +4834,6 @@ static inline int l2cap_bredr_sig_cmd(st
+ break;
+
+ case L2CAP_CONN_RSP:
+- case L2CAP_CREATE_CHAN_RSP:
+ l2cap_connect_create_rsp(conn, cmd, cmd_len, data);
+ break;
+
+@@ -5853,26 +4868,6 @@ static inline int l2cap_bredr_sig_cmd(st
+ l2cap_information_rsp(conn, cmd, cmd_len, data);
+ break;
+
+- case L2CAP_CREATE_CHAN_REQ:
+- err = l2cap_create_channel_req(conn, cmd, cmd_len, data);
+- break;
+-
+- case L2CAP_MOVE_CHAN_REQ:
+- err = l2cap_move_channel_req(conn, cmd, cmd_len, data);
+- break;
+-
+- case L2CAP_MOVE_CHAN_RSP:
+- l2cap_move_channel_rsp(conn, cmd, cmd_len, data);
+- break;
+-
+- case L2CAP_MOVE_CHAN_CFM:
+- err = l2cap_move_channel_confirm(conn, cmd, cmd_len, data);
+- break;
+-
+- case L2CAP_MOVE_CHAN_CFM_RSP:
+- l2cap_move_channel_confirm_rsp(conn, cmd, cmd_len, data);
+- break;
+-
+ default:
+ BT_ERR("Unknown BR/EDR signaling command 0x%2.2x", cmd->code);
+ err = -EINVAL;
+@@ -7251,8 +6246,8 @@ static int l2cap_rx_state_recv(struct l2
+ if (control->final) {
+ clear_bit(CONN_REMOTE_BUSY, &chan->conn_state);
+
+- if (!test_and_clear_bit(CONN_REJ_ACT, &chan->conn_state) &&
+- !__chan_is_moving(chan)) {
++ if (!test_and_clear_bit(CONN_REJ_ACT,
++ &chan->conn_state)) {
+ control->final = 0;
+ l2cap_retransmit_all(chan, control);
+ }
+@@ -7445,11 +6440,7 @@ static int l2cap_finish_move(struct l2ca
+ BT_DBG("chan %p", chan);
+
+ chan->rx_state = L2CAP_RX_STATE_RECV;
+-
+- if (chan->hs_hcon)
+- chan->conn->mtu = chan->hs_hcon->hdev->block_mtu;
+- else
+- chan->conn->mtu = chan->conn->hcon->hdev->acl_mtu;
++ chan->conn->mtu = chan->conn->hcon->hdev->acl_mtu;
+
+ return l2cap_resegment(chan);
+ }
+@@ -7516,11 +6507,7 @@ static int l2cap_rx_state_wait_f(struct
+ */
+ chan->next_tx_seq = control->reqseq;
+ chan->unacked_frames = 0;
+-
+- if (chan->hs_hcon)
+- chan->conn->mtu = chan->hs_hcon->hdev->block_mtu;
+- else
+- chan->conn->mtu = chan->conn->hcon->hdev->acl_mtu;
++ chan->conn->mtu = chan->conn->hcon->hdev->acl_mtu;
+
+ err = l2cap_resegment(chan);
+
+@@ -7904,21 +6891,10 @@ static void l2cap_data_channel(struct l2
+
+ chan = l2cap_get_chan_by_scid(conn, cid);
+ if (!chan) {
+- if (cid == L2CAP_CID_A2MP) {
+- chan = a2mp_channel_create(conn, skb);
+- if (!chan) {
+- kfree_skb(skb);
+- return;
+- }
+-
+- l2cap_chan_hold(chan);
+- l2cap_chan_lock(chan);
+- } else {
+- BT_DBG("unknown cid 0x%4.4x", cid);
+- /* Drop packet and return */
+- kfree_skb(skb);
+- return;
+- }
++ BT_DBG("unknown cid 0x%4.4x", cid);
++ /* Drop packet and return */
++ kfree_skb(skb);
++ return;
+ }
+
+ BT_DBG("chan %p, len %d", chan, skb->len);
+@@ -8123,10 +7099,6 @@ static struct l2cap_conn *l2cap_conn_add
+
+ conn->local_fixed_chan = L2CAP_FC_SIG_BREDR | L2CAP_FC_CONNLESS;
+
+- if (hcon->type == ACL_LINK &&
+- hci_dev_test_flag(hcon->hdev, HCI_HS_ENABLED))
+- conn->local_fixed_chan |= L2CAP_FC_A2MP;
+-
+ if (hci_dev_test_flag(hcon->hdev, HCI_LE_ENABLED) &&
+ (bredr_sc_enabled(hcon->hdev) ||
+ hci_dev_test_flag(hcon->hdev, HCI_FORCE_BREDR_SMP)))
+@@ -8591,11 +7563,6 @@ static void l2cap_security_cfm(struct hc
+ BT_DBG("chan %p scid 0x%4.4x state %s", chan, chan->scid,
+ state_to_string(chan->state));
+
+- if (chan->scid == L2CAP_CID_A2MP) {
+- l2cap_chan_unlock(chan);
+- continue;
+- }
+-
+ if (!status && encrypt)
+ chan->sec_level = hcon->sec_level;
+
+--- a/net/bluetooth/l2cap_sock.c
++++ b/net/bluetooth/l2cap_sock.c
+@@ -1023,23 +1023,7 @@ static int l2cap_sock_setsockopt(struct
+ if (err)
+ break;
+
+- if (opt > BT_CHANNEL_POLICY_AMP_PREFERRED) {
+- err = -EINVAL;
+- break;
+- }
+-
+- if (chan->mode != L2CAP_MODE_ERTM &&
+- chan->mode != L2CAP_MODE_STREAMING) {
+- err = -EOPNOTSUPP;
+- break;
+- }
+-
+- chan->chan_policy = (u8) opt;
+-
+- if (sk->sk_state == BT_CONNECTED &&
+- chan->move_role == L2CAP_MOVE_ROLE_NONE)
+- l2cap_move_start(chan);
+-
++ err = -EOPNOTSUPP;
+ break;
+
+ case BT_SNDMTU:
+--- a/net/bluetooth/mgmt.c
++++ b/net/bluetooth/mgmt.c
+@@ -835,8 +835,6 @@ static u32 get_supported_settings(struct
+
+ if (lmp_ssp_capable(hdev)) {
+ settings |= MGMT_SETTING_SSP;
+- if (IS_ENABLED(CONFIG_BT_HS))
+- settings |= MGMT_SETTING_HS;
+ }
+
+ if (lmp_sc_capable(hdev))
+@@ -901,9 +899,6 @@ static u32 get_current_settings(struct h
+ if (hci_dev_test_flag(hdev, HCI_SSP_ENABLED))
+ settings |= MGMT_SETTING_SSP;
+
+- if (hci_dev_test_flag(hdev, HCI_HS_ENABLED))
+- settings |= MGMT_SETTING_HS;
+-
+ if (hci_dev_test_flag(hdev, HCI_ADVERTISING))
+ settings |= MGMT_SETTING_ADVERTISING;
+
+@@ -1939,7 +1934,6 @@ static void set_ssp_complete(struct hci_
+
+ if (enable && hci_dev_test_and_clear_flag(hdev,
+ HCI_SSP_ENABLED)) {
+- hci_dev_clear_flag(hdev, HCI_HS_ENABLED);
+ new_settings(hdev, NULL);
+ }
+
+@@ -1952,12 +1946,6 @@ static void set_ssp_complete(struct hci_
+ changed = !hci_dev_test_and_set_flag(hdev, HCI_SSP_ENABLED);
+ } else {
+ changed = hci_dev_test_and_clear_flag(hdev, HCI_SSP_ENABLED);
+-
+- if (!changed)
+- changed = hci_dev_test_and_clear_flag(hdev,
+- HCI_HS_ENABLED);
+- else
+- hci_dev_clear_flag(hdev, HCI_HS_ENABLED);
+ }
+
+ mgmt_pending_foreach(MGMT_OP_SET_SSP, hdev, settings_rsp, &match);
+@@ -2021,11 +2009,6 @@ static int set_ssp(struct sock *sk, stru
+ } else {
+ changed = hci_dev_test_and_clear_flag(hdev,
+ HCI_SSP_ENABLED);
+- if (!changed)
+- changed = hci_dev_test_and_clear_flag(hdev,
+- HCI_HS_ENABLED);
+- else
+- hci_dev_clear_flag(hdev, HCI_HS_ENABLED);
+ }
+
+ err = send_settings_rsp(sk, MGMT_OP_SET_SSP, hdev);
+@@ -2071,63 +2054,10 @@ failed:
+
+ static int set_hs(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
+ {
+- struct mgmt_mode *cp = data;
+- bool changed;
+- u8 status;
+- int err;
+-
+ bt_dev_dbg(hdev, "sock %p", sk);
+
+- if (!IS_ENABLED(CONFIG_BT_HS))
+- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
++ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
+ MGMT_STATUS_NOT_SUPPORTED);
+-
+- status = mgmt_bredr_support(hdev);
+- if (status)
+- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS, status);
+-
+- if (!lmp_ssp_capable(hdev))
+- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
+- MGMT_STATUS_NOT_SUPPORTED);
+-
+- if (!hci_dev_test_flag(hdev, HCI_SSP_ENABLED))
+- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
+- MGMT_STATUS_REJECTED);
+-
+- if (cp->val != 0x00 && cp->val != 0x01)
+- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
+- MGMT_STATUS_INVALID_PARAMS);
+-
+- hci_dev_lock(hdev);
+-
+- if (pending_find(MGMT_OP_SET_SSP, hdev)) {
+- err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
+- MGMT_STATUS_BUSY);
+- goto unlock;
+- }
+-
+- if (cp->val) {
+- changed = !hci_dev_test_and_set_flag(hdev, HCI_HS_ENABLED);
+- } else {
+- if (hdev_is_powered(hdev)) {
+- err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
+- MGMT_STATUS_REJECTED);
+- goto unlock;
+- }
+-
+- changed = hci_dev_test_and_clear_flag(hdev, HCI_HS_ENABLED);
+- }
+-
+- err = send_settings_rsp(sk, MGMT_OP_SET_HS, hdev);
+- if (err < 0)
+- goto unlock;
+-
+- if (changed)
+- err = new_settings(hdev, sk);
+-
+-unlock:
+- hci_dev_unlock(hdev);
+- return err;
+ }
+
+ static void set_le_complete(struct hci_dev *hdev, void *data, int err)
+@@ -6839,7 +6769,6 @@ static int set_bredr(struct sock *sk, st
+ hci_dev_clear_flag(hdev, HCI_SSP_ENABLED);
+ hci_dev_clear_flag(hdev, HCI_LINK_SECURITY);
+ hci_dev_clear_flag(hdev, HCI_FAST_CONNECTABLE);
+- hci_dev_clear_flag(hdev, HCI_HS_ENABLED);
+ }
+
+ hci_dev_change_flag(hdev, HCI_BREDR_ENABLED);
--- /dev/null
+From stable+bounces-277357-greg=kroah.com@vger.kernel.org Sat Jul 18 18:39:35 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 18 Jul 2026 12:39:25 -0400
+Subject: bpf: Allow LPM map access from sleepable BPF programs
+To: stable@vger.kernel.org
+Cc: Vlad Poenaru <vlad.wing@gmail.com>, Emil Tsalapatis <emil@etsalapatis.com>, Alexei Starovoitov <ast@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260718163925.3343620-2-sashal@kernel.org>
+
+From: Vlad Poenaru <vlad.wing@gmail.com>
+
+[ Upstream commit 2f884d371fafea137afea504d49ee4a7c8d7985b ]
+
+trie_lookup_elem() annotates its rcu_dereference_check() walks with
+only rcu_read_lock_bh_held(). Because rcu_dereference_check(p, c)
+resolves to "c || rcu_read_lock_held()", this passes for XDP/NAPI and
+classic RCU readers but fails for sleepable BPF programs, which enter
+via __bpf_prog_enter_sleepable() and hold only rcu_read_lock_trace().
+
+trie_update_elem() and trie_delete_elem() have the same problem in a
+different form: they walk the trie with plain rcu_dereference(), which
+asserts rcu_read_lock_held() unconditionally. Both are reachable from
+sleepable BPF programs via the bpf_map_update_elem / bpf_map_delete_elem
+helpers, and from the syscall path under classic rcu_read_lock(). In
+the writer paths the trie is actually protected by trie->lock (an
+rqspinlock taken across the walk); we never relied on the RCU read-side
+lock to keep nodes alive there.
+
+A sleepable LSM hook that ends up touching an LPM trie therefore
+triggers lockdep on debug kernels:
+
+ =============================
+ WARNING: suspicious RCU usage
+ 7.1.0-... Tainted: G E
+ -----------------------------
+ kernel/bpf/lpm_trie.c:249 suspicious rcu_dereference_check() usage!
+ 1 lock held by net_tests/540:
+ #0: (rcu_tasks_trace_srcu_struct){....}-{0:0},
+ at: __bpf_prog_enter_sleepable+0x26/0x280
+ Call Trace:
+ dump_stack_lvl
+ lockdep_rcu_suspicious
+ trie_lookup_elem
+ bpf_prog_..._enforce_security_socket_connect
+ bpf_trampoline_...
+ security_socket_connect
+ __sys_connect
+ do_syscall_64
+
+This is lockdep-only -- no UAF, since Tasks Trace RCU does serialize
+against the trie's reclaim path -- but it spams the console once per
+distinct callsite on every debug kernel running a sleepable BPF LSM
+that touches an LPM trie, which is increasingly common.
+
+For the lookup path, switch the rcu_dereference_check() annotation
+from rcu_read_lock_bh_held() to bpf_rcu_lock_held(), which accepts all
+three contexts (classic, BH, Tasks Trace). Other map types already
+follow this convention.
+
+For trie_update_elem() and trie_delete_elem(), annotate the walks as
+rcu_dereference_protected(*p, 1) -- matching trie_free() in the same
+file -- since trie->lock is held across the walk. rqspinlock has no
+lockdep_map, so the predicate degenerates to '1' rather than
+lockdep_is_held(&trie->lock); the protection is real but not
+machine-verifiable. trie_get_next_key() also uses bare
+rcu_dereference() but is reachable only from the BPF syscall, which
+holds classic rcu_read_lock() before dispatching, so it is left
+untouched.
+
+Fixes: 694cea395fde ("bpf: Allow RCU-protected lookups to happen from bh context")
+Cc: stable@vger.kernel.org
+Signed-off-by: Vlad Poenaru <vlad.wing@gmail.com>
+Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
+Link: https://lore.kernel.org/r/20260609135558.193287-2-vlad.wing@gmail.com
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/bpf/lpm_trie.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/kernel/bpf/lpm_trie.c
++++ b/kernel/bpf/lpm_trie.c
+@@ -236,7 +236,7 @@ static void *trie_lookup_elem(struct bpf
+
+ /* Start walking the trie from the root node ... */
+
+- for (node = rcu_dereference_check(trie->root, rcu_read_lock_bh_held());
++ for (node = rcu_dereference_check(trie->root, bpf_rcu_lock_held());
+ node;) {
+ unsigned int next_bit;
+ size_t matchlen;
+@@ -270,7 +270,7 @@ static void *trie_lookup_elem(struct bpf
+ */
+ next_bit = extract_bit(key->data, node->prefixlen);
+ node = rcu_dereference_check(node->child[next_bit],
+- rcu_read_lock_bh_held());
++ bpf_rcu_lock_held());
+ }
+
+ if (!found)
--- /dev/null
+From stable+bounces-277356-greg=kroah.com@vger.kernel.org Sat Jul 18 18:39:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 18 Jul 2026 12:39:24 -0400
+Subject: bpf: Consistently use bpf_rcu_lock_held() everywhere
+To: stable@vger.kernel.org
+Cc: Andrii Nakryiko <andrii@kernel.org>, Daniel Borkmann <daniel@iogearbox.net>, Jiri Olsa <jolsa@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260718163925.3343620-1-sashal@kernel.org>
+
+From: Andrii Nakryiko <andrii@kernel.org>
+
+[ Upstream commit 48a97ffc6c826640907d13b199e29008f4fe2c15 ]
+
+We have many places which open-code what's now is bpf_rcu_lock_held()
+macro, so replace all those places with a clean and short macro invocation.
+For that, move bpf_rcu_lock_held() macro into include/linux/bpf.h.
+
+Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
+Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
+Acked-by: Jiri Olsa <jolsa@kernel.org>
+Link: https://lore.kernel.org/bpf/20251014201403.4104511-1-andrii@kernel.org
+Stable-dep-of: 2f884d371faf ("bpf: Allow LPM map access from sleepable BPF programs")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/bpf.h | 3 +++
+ include/linux/bpf_local_storage.h | 3 ---
+ kernel/bpf/hashtab.c | 21 +++++++--------------
+ kernel/bpf/helpers.c | 12 ++++--------
+ kernel/bpf/lpm_trie.c | 6 ++----
+ 5 files changed, 16 insertions(+), 29 deletions(-)
+
+--- a/include/linux/bpf.h
++++ b/include/linux/bpf.h
+@@ -1722,6 +1722,9 @@ out:
+ return ret;
+ }
+
++#define bpf_rcu_lock_held() \
++ (rcu_read_lock_held() || rcu_read_lock_trace_held() || rcu_read_lock_bh_held())
++
+ #ifdef CONFIG_BPF_SYSCALL
+ DECLARE_PER_CPU(int, bpf_prog_active);
+ extern struct mutex bpf_stats_enabled_mutex;
+--- a/include/linux/bpf_local_storage.h
++++ b/include/linux/bpf_local_storage.h
+@@ -17,9 +17,6 @@
+
+ #define BPF_LOCAL_STORAGE_CACHE_SIZE 16
+
+-#define bpf_rcu_lock_held() \
+- (rcu_read_lock_held() || rcu_read_lock_trace_held() || \
+- rcu_read_lock_bh_held())
+ struct bpf_local_storage_map_bucket {
+ struct hlist_head list;
+ raw_spinlock_t lock;
+--- a/kernel/bpf/hashtab.c
++++ b/kernel/bpf/hashtab.c
+@@ -670,8 +670,7 @@ static void *__htab_map_lookup_elem(stru
+ struct htab_elem *l;
+ u32 hash, key_size;
+
+- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+- !rcu_read_lock_bh_held());
++ WARN_ON_ONCE(!bpf_rcu_lock_held());
+
+ key_size = map->key_size;
+
+@@ -1088,8 +1087,7 @@ static int htab_map_update_elem(struct b
+ /* unknown flags */
+ return -EINVAL;
+
+- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+- !rcu_read_lock_bh_held());
++ WARN_ON_ONCE(!bpf_rcu_lock_held());
+
+ key_size = map->key_size;
+
+@@ -1190,8 +1188,7 @@ static int htab_lru_map_update_elem(stru
+ /* unknown flags */
+ return -EINVAL;
+
+- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+- !rcu_read_lock_bh_held());
++ WARN_ON_ONCE(!bpf_rcu_lock_held());
+
+ key_size = map->key_size;
+
+@@ -1259,8 +1256,7 @@ static int __htab_percpu_map_update_elem
+ /* unknown flags */
+ return -EINVAL;
+
+- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+- !rcu_read_lock_bh_held());
++ WARN_ON_ONCE(!bpf_rcu_lock_held());
+
+ key_size = map->key_size;
+
+@@ -1314,8 +1310,7 @@ static int __htab_lru_percpu_map_update_
+ /* unknown flags */
+ return -EINVAL;
+
+- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+- !rcu_read_lock_bh_held());
++ WARN_ON_ONCE(!bpf_rcu_lock_held());
+
+ key_size = map->key_size;
+
+@@ -1390,8 +1385,7 @@ static int htab_map_delete_elem(struct b
+ u32 hash, key_size;
+ int ret;
+
+- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+- !rcu_read_lock_bh_held());
++ WARN_ON_ONCE(!bpf_rcu_lock_held());
+
+ key_size = map->key_size;
+
+@@ -1426,8 +1420,7 @@ static int htab_lru_map_delete_elem(stru
+ u32 hash, key_size;
+ int ret;
+
+- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+- !rcu_read_lock_bh_held());
++ WARN_ON_ONCE(!bpf_rcu_lock_held());
+
+ key_size = map->key_size;
+
+--- a/kernel/bpf/helpers.c
++++ b/kernel/bpf/helpers.c
+@@ -33,8 +33,7 @@
+ */
+ BPF_CALL_2(bpf_map_lookup_elem, struct bpf_map *, map, void *, key)
+ {
+- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+- !rcu_read_lock_bh_held());
++ WARN_ON_ONCE(!bpf_rcu_lock_held());
+ return (unsigned long) map->ops->map_lookup_elem(map, key);
+ }
+
+@@ -50,8 +49,7 @@ const struct bpf_func_proto bpf_map_look
+ BPF_CALL_4(bpf_map_update_elem, struct bpf_map *, map, void *, key,
+ void *, value, u64, flags)
+ {
+- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+- !rcu_read_lock_bh_held());
++ WARN_ON_ONCE(!bpf_rcu_lock_held());
+ return map->ops->map_update_elem(map, key, value, flags);
+ }
+
+@@ -68,8 +66,7 @@ const struct bpf_func_proto bpf_map_upda
+
+ BPF_CALL_2(bpf_map_delete_elem, struct bpf_map *, map, void *, key)
+ {
+- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+- !rcu_read_lock_bh_held());
++ WARN_ON_ONCE(!bpf_rcu_lock_held());
+ return map->ops->map_delete_elem(map, key);
+ }
+
+@@ -125,8 +122,7 @@ const struct bpf_func_proto bpf_map_peek
+
+ BPF_CALL_3(bpf_map_lookup_percpu_elem, struct bpf_map *, map, void *, key, u32, cpu)
+ {
+- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() &&
+- !rcu_read_lock_bh_held());
++ WARN_ON_ONCE(!bpf_rcu_lock_held());
+ return (unsigned long) map->ops->map_lookup_percpu_elem(map, key, cpu);
+ }
+
+--- a/kernel/bpf/lpm_trie.c
++++ b/kernel/bpf/lpm_trie.c
+@@ -353,8 +353,7 @@ static int trie_update_elem(struct bpf_m
+ */
+ slot = &trie->root;
+
+- while ((node = rcu_dereference_protected(*slot,
+- lockdep_is_held(&trie->lock)))) {
++ while ((node = rcu_dereference_protected(*slot, 1))) {
+ matchlen = longest_prefix_match(trie, node, key);
+
+ if (node->prefixlen != matchlen ||
+@@ -475,8 +474,7 @@ static int trie_delete_elem(struct bpf_m
+ trim = &trie->root;
+ trim2 = trim;
+ parent = NULL;
+- while ((node = rcu_dereference_protected(
+- *trim, lockdep_is_held(&trie->lock)))) {
++ while ((node = rcu_dereference_protected(*trim, 1))) {
+ matchlen = longest_prefix_match(trie, node, key);
+
+ if (node->prefixlen != matchlen ||
--- /dev/null
+From stable+bounces-278215-greg=kroah.com@vger.kernel.org Mon Jul 20 22:22:09 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 16:18:45 -0400
+Subject: btrfs: check and set EXTENT_DELALLOC_NEW before clearing EXTENT_DELALLOC
+To: stable@vger.kernel.org
+Cc: Qu Wenruo <wqu@suse.com>, Filipe Manana <fdmanana@suse.com>, David Sterba <dsterba@suse.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720201845.3239316-1-sashal@kernel.org>
+
+From: Qu Wenruo <wqu@suse.com>
+
+[ Upstream commit 95ee2231896d5f2a31760411429075a99d6045a7 ]
+
+[WARNING]
+When running test cases with injected errors or shutdown, e.g.
+generic/388 or generic/475, there is a chance that the following kernel
+warning is triggered:
+
+ BTRFS info (device dm-2): first mount of filesystem d8a19a28-3232-4809-b0df-38df83e71bff
+ BTRFS info (device dm-2): using crc32c checksum algorithm
+ BTRFS info (device dm-2): checking UUID tree
+ BTRFS info (device dm-2): turning on async discard
+ BTRFS info (device dm-2): enabling free space tree
+ BTRFS critical (device dm-2 state E): emergency shutdown
+ ------------[ cut here ]------------
+ WARNING: extent_io.c:1742 at extent_writepage_io+0x437/0x520 [btrfs], CPU#2: kworker/u43:2/651591
+ CPU: 2 UID: 0 PID: 651591 Comm: kworker/u43:2 Tainted: G W OE 7.0.0-rc6-custom+ #365 PREEMPT(full) 5804053f02137e627472d94b5128cc9fcb110e88
+ RIP: 0010:extent_writepage_io+0x437/0x520 [btrfs]
+ Call Trace:
+ <TASK>
+ extent_write_cache_pages+0x2a5/0x820 [btrfs 70299925d0856939e93b17d480651713b3cbba58]
+ btrfs_writepages+0x74/0x130 [btrfs 70299925d0856939e93b17d480651713b3cbba58]
+ do_writepages+0xd0/0x160
+ __writeback_single_inode+0x42/0x340
+ writeback_sb_inodes+0x22d/0x580
+ wb_writeback+0xc6/0x360
+ wb_workfn+0xbd/0x470
+ process_one_work+0x198/0x3b0
+ worker_thread+0x1c8/0x330
+ kthread+0xee/0x120
+ ret_from_fork+0x2a6/0x330
+ ret_from_fork_asm+0x11/0x20
+ </TASK>
+ ---[ end trace 0000000000000000 ]---
+ BTRFS error (device dm-2 state E): root 5 ino 259 folio 1323008 is marked dirty without notifying the fs
+ BTRFS error (device dm-2 state E): failed to submit blocks, root=5 inode=259 folio=1323008 submit_bitmap=0: -117
+ BTRFS info (device dm-2 state E): last unmount of filesystem d8a19a28-3232-4809-b0df-38df83e71bff
+
+[CAUSE]
+Inside btrfs we have the following pattern in several locations, for
+example inside btrfs_dirty_folio():
+
+ btrfs_clear_extent_bit(&inode->io_tree, start_pos, end_of_last_block,
+ EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG,
+ cached);
+
+ ret = btrfs_set_extent_delalloc(inode, start_pos, end_of_last_block,
+ extra_bits, cached);
+ if (ret)
+ return ret;
+
+However btrfs_set_extent_delalloc() can return IO errors other than -ENOMEM
+through the following callchain:
+
+ btrfs_set_extent_delalloc()
+ \- btrfs_find_new_delalloc_bytes()
+ \- btrfs_get_extent()
+ \- btrfs_lookup_file_extent()
+ \- btrfs_search_slot()
+
+When such IO error happened, the previous btrfs_clear_extent_bit() has
+cleared the EXTENT_DELALLOC for the range, and we're expecting
+btrfs_set_extent_delalloc() to re-set EXTENT_DELALLOC.
+
+But since btrfs_set_extent_delalloc() failed before
+btrfs_set_extent_bit(), EXTENT_DELALLOC flag is no longer present.
+
+And if the folio range is dirty before entering
+btrfs_set_extent_delalloc(), we got a dirty folio but no EXTENT_DELALLOC
+flag now.
+
+Then we hit the folio writeback:
+
+ extent_writepage()
+ |- writepage_delalloc()
+ | No ordered extent is created, as there is no EXTENT_DELALLOC set
+ | for the folio range.
+ | This also means the folio has no ordered flag set.
+ |
+ |- extent_writepage_io()
+ \- if (unlikely(!folio_test_ordered(folio))
+ Now we hit the warning.
+
+[FIX]
+Introduce a new helper, btrfs_reset_extent_delalloc() to replace the
+currently open-coded btrfs_clear_extent_bit() +
+btrfs_set_extent_delalloc() combination.
+
+Instead of calling btrfs_clear_extent_bit() first, update
+EXTENT_DELALLOC_NEW first, as that part can fail due to metadata IO,
+meanwhile btrfs_clear_extent_bit() and btrfs_set_extent_bit() won't
+return any error but retry memory allocation until succeeded.
+
+This allows us to fail early without clearing EXTENT_DELALLOC bit, so
+even if that new btrfs_reset_extent_delalloc() failed before touching
+EXTENT_DELALLOC, the existing dirty range will still have their old
+EXTENT_DELALLOC flag present, thus avoid the warning.
+
+CC: stable@vger.kernel.org # 6.1+
+Reviewed-by: Filipe Manana <fdmanana@suse.com>
+Signed-off-by: Qu Wenruo <wqu@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/btrfs/ctree.h | 2 +
+ fs/btrfs/file.c | 12 +-------
+ fs/btrfs/inode.c | 73 ++++++++++++++++++++++++++++++++++++++---------------
+ fs/btrfs/reflink.c | 5 ---
+ 4 files changed, 58 insertions(+), 34 deletions(-)
+
+--- a/fs/btrfs/ctree.h
++++ b/fs/btrfs/ctree.h
+@@ -3327,6 +3327,8 @@ int btrfs_start_delalloc_roots(struct bt
+ int btrfs_set_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end,
+ unsigned int extra_bits,
+ struct extent_state **cached_state);
++int btrfs_reset_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end,
++ unsigned int extra_bits, struct extent_state **cached_state);
+ struct btrfs_new_inode_args {
+ /* Input */
+ struct inode *dir;
+--- a/fs/btrfs/file.c
++++ b/fs/btrfs/file.c
+@@ -144,16 +144,8 @@ int btrfs_dirty_pages(struct btrfs_inode
+
+ end_of_last_block = start_pos + num_bytes - 1;
+
+- /*
+- * The pages may have already been dirty, clear out old accounting so
+- * we can set things up properly
+- */
+- clear_extent_bit(&inode->io_tree, start_pos, end_of_last_block,
+- EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG,
+- cached);
+-
+- err = btrfs_set_extent_delalloc(inode, start_pos, end_of_last_block,
+- extra_bits, cached);
++ err = btrfs_reset_extent_delalloc(inode, start_pos, end_of_last_block,
++ extra_bits, cached);
+ if (err)
+ return err;
+
+--- a/fs/btrfs/inode.c
++++ b/fs/btrfs/inode.c
+@@ -2845,7 +2845,11 @@ int btrfs_set_extent_delalloc(struct btr
+ unsigned int extra_bits,
+ struct extent_state **cached_state)
+ {
+- WARN_ON(PAGE_ALIGNED(end));
++ const u32 blocksize = inode->root->fs_info->sectorsize;
++
++ /* Basic alignment check. */
++ ASSERT(IS_ALIGNED(start, blocksize));
++ ASSERT(IS_ALIGNED(end + 1, blocksize));
+
+ if (start >= i_size_read(&inode->vfs_inode) &&
+ !(inode->flags & BTRFS_INODE_PREALLOC)) {
+@@ -3055,6 +3059,51 @@ int btrfs_writepage_cow_fixup(struct pag
+ return -EAGAIN;
+ }
+
++/*
++ * Clear the old accounting flags and set EXTENT_DELALLOC for the range.
++ *
++ * Return <0 for error, in that case no range has EXTENT_DELALLOC bit cleared or set.
++ */
++int btrfs_reset_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end,
++ unsigned int extra_bits, struct extent_state **cached_state)
++{
++ const u32 blocksize = inode->root->fs_info->sectorsize;
++
++ /* The @extra_bits can only be EXTENT_NORESERVE for now. */
++ ASSERT(!(extra_bits & ~EXTENT_NORESERVE));
++
++ /* Basic alignment check. */
++ ASSERT(IS_ALIGNED(start, blocksize));
++ ASSERT(IS_ALIGNED(end + 1, blocksize));
++
++ /*
++ * Check and set DELALLOC_NEW flag, this needs to search tree thus can
++ * fail early. Thus we want to do this before clearing EXTENT_DELALLOC.
++ */
++ if (start >= i_size_read(&inode->vfs_inode) &&
++ !(inode->flags & BTRFS_INODE_PREALLOC)) {
++ /*
++ * There can't be any extents following EOF in this case so just
++ * set the delalloc new bit for the range directly.
++ */
++ extra_bits |= EXTENT_DELALLOC_NEW;
++ } else {
++ int ret;
++
++ ret = btrfs_find_new_delalloc_bytes(inode, start, end + 1 - start,
++ NULL);
++ if (unlikely(ret))
++ return ret;
++ }
++ /* Clear the old accounting as the range may already be dirty. */
++ clear_extent_bit(&inode->io_tree, start, end,
++ EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
++ EXTENT_DEFRAG, cached_state);
++ return set_extent_bit(&inode->io_tree, start, end,
++ EXTENT_DELALLOC | extra_bits, cached_state,
++ GFP_NOFS);
++}
++
+ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
+ struct btrfs_inode *inode, u64 file_pos,
+ struct btrfs_file_extent_item *stack_fi,
+@@ -5037,12 +5086,7 @@ again:
+ goto again;
+ }
+
+- clear_extent_bit(&inode->io_tree, block_start, block_end,
+- EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG,
+- &cached_state);
+-
+- ret = btrfs_set_extent_delalloc(inode, block_start, block_end, 0,
+- &cached_state);
++ ret = btrfs_reset_extent_delalloc(inode, block_start, block_end, 0, &cached_state);
+ if (ret) {
+ unlock_extent(io_tree, block_start, block_end, &cached_state);
+ goto out_unlock;
+@@ -8685,19 +8729,8 @@ again:
+ }
+ }
+
+- /*
+- * page_mkwrite gets called when the page is firstly dirtied after it's
+- * faulted in, but write(2) could also dirty a page and set delalloc
+- * bits, thus in this case for space account reason, we still need to
+- * clear any delalloc bits within this page range since we have to
+- * reserve data&meta space before lock_page() (see above comments).
+- */
+- clear_extent_bit(&BTRFS_I(inode)->io_tree, page_start, end,
+- EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
+- EXTENT_DEFRAG, &cached_state);
+-
+- ret2 = btrfs_set_extent_delalloc(BTRFS_I(inode), page_start, end, 0,
+- &cached_state);
++ ret2 = btrfs_reset_extent_delalloc(BTRFS_I(inode), page_start, end, 0,
++ &cached_state);
+ if (ret2) {
+ unlock_extent(io_tree, page_start, page_end, &cached_state);
+ ret = VM_FAULT_SIGBUS;
+--- a/fs/btrfs/reflink.c
++++ b/fs/btrfs/reflink.c
+@@ -90,10 +90,7 @@ static int copy_inline_to_page(struct bt
+ if (ret < 0)
+ goto out_unlock;
+
+- clear_extent_bit(&inode->io_tree, file_offset, range_end,
+- EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG,
+- NULL);
+- ret = btrfs_set_extent_delalloc(inode, file_offset, range_end, 0, NULL);
++ ret = btrfs_reset_extent_delalloc(inode, file_offset, range_end, 0, NULL);
+ if (ret)
+ goto out_unlock;
+
--- /dev/null
+From stable+bounces-278185-greg=kroah.com@vger.kernel.org Mon Jul 20 20:09:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 14:08:52 -0400
+Subject: btrfs: fix false IO failure after falling back to buffered write
+To: stable@vger.kernel.org
+Cc: Qu Wenruo <wqu@suse.com>, Boris Burkov <boris@bur.io>, David Sterba <dsterba@suse.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720180852.2923422-1-sashal@kernel.org>
+
+From: Qu Wenruo <wqu@suse.com>
+
+[ Upstream commit 66ff4d366e7eb4d31813d2acabf3af512ce03aa5 ]
+
+[BUG]
+The test case generic/362 will fail with "nodatasum" mount option (*):
+
+ MOUNT_OPTIONS -- -o nodatasum /dev/mapper/test-scratch1 /mnt/scratch
+
+# generic/362 0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad)
+# --- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930
+# +++ /home/adam/xfstests/results//generic/362.out.bad 2026-05-27 10:21:17.574771567 +0930
+# @@ -1,2 +1,3 @@
+# QA output created by 362
+# +First write failed: Input/output error
+# Silence is golden
+# ...
+
+*: If the test case has been executed before with default data checksum,
+the failure will not reproduce. Need the following fix to make it
+reliably reproducible:
+https://lore.kernel.org/linux-btrfs/20260528111659.87113-1-wqu@suse.com/
+
+[CAUSE]
+Inside __iomap_dio_rw(), the -EFAULT/-ENOTBLK error is not directly returned.
+Thus we never got an error pointer from __iomap_dio_rw().
+
+The call chain looks like this:
+
+ btrfs_direct_write()
+ |- btrfs_dio_write()
+ |- __iomap_dio_rw()
+ | |- iomap_iter()
+ | | |- btrfs_dio_iomap_begin()
+ | | Now an ordered extent is allocated for the 4K write.
+ | |
+ | |- iomi.status = iomap_dio_iter()
+ | | Where iomap_dio_iter() returned -EFAULT.
+ | |
+ | |- ret = iomap_iter()
+ | | |- btrfs_dio_iomap_end()
+ | | | |- btrfs_finish_ordered_extent(uptodate = false)
+ | | | | |- can_finish_ordered_extent()
+ | | | | |- btrfs_mark_ordered_extent_error()
+ | | | | |- mapping_set_error()
+ | | | | Now the address space is marked error.
+ | | | | return -ENOTBLK
+ | | |- return -ENOTBLK
+ | |- if (ret == -ENOTBLK) { ret = 0; }
+ | Now the return value is reset to 0.
+ | Thus no error pointer will be returned.
+ |
+ |- ret = iomap_dio_complete()
+ | Since no byte is submitted, @ret is 0.
+ |
+ |- Fallback to buffered IO
+ | And the buffered write finished without error
+ |
+ |- filemap_fdatawait_range()
+ |- filemap_check_errors()
+ The previous error is recorded, thus an error is returned
+
+However the buffered write is properly submitted and finished, the error
+is from the btrfs_finish_ordered_extent() call with @uptodate = false.
+
+[FIX]
+When a short dio write happened, any range that is submitted will have
+btrfs_extract_ordered_extent() to be called, thus the submitted range
+will always have an OE just covering the submitted range.
+
+The remaining OE range is never submitted, thus they should be treated
+as truncated, not an error. So that we can properly reclaim and not
+insert an unnecessary file extent item, without marking the mapping as
+error.
+
+Extract a helper, btrfs_mark_ordered_extent_truncated(), and utilize
+that helper to mark the direct IO ordered extent as truncated, so it
+won't cause failure for the later buffered fallback.
+
+[REASON FOR NO FIXES TAG]
+The bug itself is pretty old, at commit f85781fb505e ("btrfs: switch to
+iomap for direct IO") we're already passing @uptodate=false finishing
+the OE.
+But at that time OE with IOERR won't call mapping_set_error(), so it's
+not exposed.
+Later commit d61bec08b904 ("btrfs: mark ordered extent and inode with
+error if we fail to finish") finally exposed the bug, but that commit
+is doing a correct job, not the root cause.
+
+Anyway the bug is very old, dating back to 5.1x days, thus only CC to
+stable.
+
+CC: stable@vger.kernel.org # 5.15+
+Reviewed-by: Boris Burkov <boris@bur.io>
+Signed-off-by: Qu Wenruo <wqu@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/btrfs/inode.c | 33 +++++++++++++++++++++++++--------
+ fs/btrfs/ordered-data.c | 12 ++++++++++++
+ fs/btrfs/ordered-data.h | 2 ++
+ 3 files changed, 39 insertions(+), 8 deletions(-)
+
+--- a/fs/btrfs/inode.c
++++ b/fs/btrfs/inode.c
+@@ -8031,12 +8031,33 @@ static int btrfs_dio_iomap_end(struct in
+ if (submitted < length) {
+ pos += submitted;
+ length -= submitted;
+- if (write)
++ if (write) {
++ struct btrfs_ordered_extent *ordered;
++
++ /*
++ * We have a short write, and the ordered extent is
++ * not split at submission time on this tree, so it
++ * may cover both the submitted range and the range
++ * that was never submitted.
++ *
++ * Truncate the ordered extent to the part that was
++ * submitted properly, so the never submitted range
++ * is reclaimed without inserting a file extent item
++ * for it and without marking it as an IO error.
++ */
++ ordered = btrfs_lookup_ordered_extent(BTRFS_I(inode),
++ pos);
++ if (ordered) {
++ btrfs_mark_ordered_extent_truncated(ordered,
++ pos - ordered->file_offset);
++ btrfs_put_ordered_extent(ordered);
++ }
+ btrfs_mark_ordered_io_finished(BTRFS_I(inode), NULL,
+- pos, length, false);
+- else
++ pos, length, true);
++ } else {
+ unlock_extent(&BTRFS_I(inode)->io_tree, pos,
+ pos + length - 1, NULL);
++ }
+ ret = -ENOTBLK;
+ }
+
+@@ -8560,11 +8581,7 @@ static void btrfs_invalidate_folio(struc
+ EXTENT_LOCKED | EXTENT_DO_ACCOUNTING |
+ EXTENT_DEFRAG, &cached_state);
+
+- spin_lock_irq(&inode->ordered_tree.lock);
+- set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags);
+- ordered->truncated_len = min(ordered->truncated_len,
+- cur - ordered->file_offset);
+- spin_unlock_irq(&inode->ordered_tree.lock);
++ btrfs_mark_ordered_extent_truncated(ordered, cur - ordered->file_offset);
+
+ /*
+ * If the ordered extent has finished, we're safe to delete all
+--- a/fs/btrfs/ordered-data.c
++++ b/fs/btrfs/ordered-data.c
+@@ -279,6 +279,18 @@ void btrfs_add_ordered_sum(struct btrfs_
+ spin_unlock_irq(&tree->lock);
+ }
+
++void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered,
++ u64 truncate_len)
++{
++ struct btrfs_inode *inode = BTRFS_I(ordered->inode);
++
++ ASSERT(truncate_len <= ordered->num_bytes);
++ spin_lock_irq(&inode->ordered_tree.lock);
++ set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags);
++ ordered->truncated_len = min(ordered->truncated_len, truncate_len);
++ spin_unlock_irq(&inode->ordered_tree.lock);
++}
++
+ static void finish_ordered_fn(struct btrfs_work *work)
+ {
+ struct btrfs_ordered_extent *ordered_extent;
+--- a/fs/btrfs/ordered-data.h
++++ b/fs/btrfs/ordered-data.h
+@@ -209,6 +209,8 @@ void btrfs_lock_and_flush_ordered_range(
+ bool btrfs_try_lock_ordered_range(struct btrfs_inode *inode, u64 start, u64 end);
+ int btrfs_split_ordered_extent(struct btrfs_ordered_extent *ordered, u64 pre,
+ u64 post);
++void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered,
++ u64 truncate_len);
+ int __init ordered_data_init(void);
+ void __cold ordered_data_exit(void);
+
--- /dev/null
+From stable+bounces-278221-greg=kroah.com@vger.kernel.org Mon Jul 20 22:19:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 16:19:07 -0400
+Subject: btrfs: fix incorrect buffered IO fallback for append direct writes
+To: stable@vger.kernel.org
+Cc: Qu Wenruo <wqu@suse.com>, Boris Burkov <boris@bur.io>, David Sterba <dsterba@suse.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720201907.3240613-2-sashal@kernel.org>
+
+From: Qu Wenruo <wqu@suse.com>
+
+[ Upstream commit ff66fe6662330226b3f486014c375538d91c44aa ]
+
+[BUG]
+With the previous bug of short direct writes fixed, test case
+generic/362 (*) still fails with the following error with nodatasum
+mount option:
+
+# generic/362 0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad)
+# - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad)
+# --- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930
+# +++ /home/adam/xfstests/results//generic/362.out.bad 2026-05-27 10:13:09.072485767 +0930
+# @@ -1,2 +1,3 @@
+# QA output created by 362
+# +Wrong file size after first write, got 8192 expected 4096
+# Silence is golden
+# ...
+
+*: If the test case has been executed before with default data checksum,
+the failure will not reproduce. Need the following fix to make it
+reliably reproducible:
+https://lore.kernel.org/linux-btrfs/20260528111659.87113-1-wqu@suse.com/
+
+[CAUSE]
+Inside btrfs_dio_iomap_begin() for a direct write, we increase the isize
+if it's beyond the current isize.
+
+But if the direct io finished short, we do not revert the isize to the
+previous value nor to the short write end.
+
+Then if we need to fall back to buffered writes, and the write has
+IOCB_APPEND flag, then the buffered write will be positioned at the
+incorrect isize.
+
+The call chain looks like this:
+
+ btrfs_direct_write(pos=0, length=4K)
+ |- __iomap_dio_rw()
+ | |- iomap_iter()
+ | | |- btrfs_dio_iomap_begin()
+ | | |- btrfs_get_blocks_direct_write()
+ | | |- i_size_write()
+ | | Which updates the isize to the write end (4K).
+ | |
+ | |- iomap_dio_iter()
+ | | Failed with -EFAULT on the first page.
+ | |
+ | |- iomap_iter()
+ | | |- btrfs_dio_iomap_end()
+ | | Detects a short write, return -ENOTBLK
+ | |- if (ret == -ENOTBLK) { ret = 0;}
+ | Which resets the return value.
+ |
+ |- ret = iomap_dio_complet()
+ | Which returns 0.
+ |
+ |- btrfs_buffered_write(iocb, from);
+ |- generic_write_checks()
+ |- iocb->ki_pos = i_size_read()
+ Which is still the new size (4K), other than the original
+ isize 0.
+
+[FIX]
+Introduce the following btrfs_dio_data members:
+
+- old_isize
+
+- updated_isize
+ If the direct write has enlarged the isize.
+
+Then if we got a short write, and btrfs_dio_data::updated_isize is set,
+revert to the correct isize based on old_isize and current file
+position.
+
+And here we call i_size_write() without holding an extent lock, which is
+a very special case that we're safe to do:
+
+ - Only a single writer can be enlarging isize
+ Enlarging isize will take the exclusive inode lock.
+
+ - Buffered readers need to wait for the OE we're holding
+ Buffered readers will lock extent and wait for OE of the folio range.
+ Sometimes we can skip the OE wait, but since all page cache is
+ invalidated, the OE wait can not be skipped.
+
+But I do not think this is the most elegant solution, nor covers all
+cases. E.g. if the bio is submitted but IO failed, we are unable to do
+the revert.
+
+I believe the more elegant one would be extend the EXTENT_DIO_LOCKED
+lifespan for direct writes, so that we can update the isize when a
+write beyond EOF finished successfully.
+
+However that change is too huge for a small bug fix.
+So only implement the minimal partial fix for now.
+
+[REASON FOR NO FIXES TAG]
+The bug is again very old, before commit f85781fb505e ("btrfs: switch to
+iomap for direct IO") we are already increasing isize without a
+proper rollback for short writes.
+
+Thus only a CC to stable.
+
+CC: stable@vger.kernel.org # 5.15+
+Reviewed-by: Boris Burkov <boris@bur.io>
+Signed-off-by: Qu Wenruo <wqu@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/btrfs/inode.c | 43 ++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 42 insertions(+), 1 deletion(-)
+
+--- a/fs/btrfs/inode.c
++++ b/fs/btrfs/inode.c
+@@ -63,9 +63,11 @@ struct btrfs_iget_args {
+
+ struct btrfs_dio_data {
+ ssize_t submitted;
++ loff_t old_isize;
+ struct extent_changeset *data_reserved;
+ bool data_space_reserved;
+ bool nocow_done;
++ bool updated_isize;
+ };
+
+ struct btrfs_dio_private {
+@@ -7635,6 +7637,7 @@ static int btrfs_get_blocks_direct_write
+ bool space_reserved = false;
+ u64 len = *lenp;
+ u64 prev_len;
++ loff_t old_isize;
+ int ret = 0;
+
+ /*
+@@ -7750,8 +7753,14 @@ static int btrfs_get_blocks_direct_write
+ * Need to update the i_size under the extent lock so buffered
+ * readers will get the updated i_size when we unlock.
+ */
+- if (start + len > i_size_read(inode))
++ old_isize = i_size_read(inode);
++ if (start + len > old_isize) {
++ if (!dio_data->updated_isize) {
++ dio_data->old_isize = old_isize;
++ dio_data->updated_isize = true;
++ }
+ i_size_write(inode, start + len);
++ }
+ out:
+ if (ret && space_reserved) {
+ btrfs_delalloc_release_extents(BTRFS_I(inode), len);
+@@ -8035,6 +8044,38 @@ static int btrfs_dio_iomap_end(struct in
+ struct btrfs_ordered_extent *ordered;
+
+ /*
++ * Got a short write and have updated the isize, need to
++ * revert the isize change.
++ *
++ * Normally we need to update isize with extent lock hold,
++ * but we're safe due to the following factors:
++ *
++ * - Only a single writer can be enlarging isize
++ * Enlarging isize will take the exclusive inode lock.
++ *
++ * - Buffered readers need to wait for the OE we're holding
++ * Buffered readers will lock extent and wait for OE
++ * of the folio range, and since page cache is invalidated
++ * the OE wait can not be skipped.
++ *
++ * So here we are safe to revert the isize before
++ * finishing the OE, and no reader of the remaining range
++ * can see the enlarged size.
++ *
++ * TODO: Extend the DIO_LOCKED lifespan for direct writes,
++ * and only enlarge isize after a successful write.
++ */
++ if (dio_data->updated_isize) {
++ u64 new_isize;
++
++ if (submitted == 0)
++ new_isize = dio_data->old_isize;
++ else
++ new_isize = max(dio_data->old_isize, pos);
++ i_size_write(inode, new_isize);
++ dio_data->updated_isize = false;
++ }
++ /*
+ * We have a short write, and the ordered extent is
+ * not split at submission time on this tree, so it
+ * may cover both the submitted range and the range
--- /dev/null
+From stable+bounces-275232-greg=kroah.com@vger.kernel.org Thu Jul 16 12:19:12 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 06:18:23 -0400
+Subject: cifs: Add tracing for the cifs_tcon struct refcounting
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, "Paulo Alcantara (Red Hat)" <pc@manguebit.com>, Shyam Prasad N <nspmangalore@gmail.com>, linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716101824.1461870-4-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit afc23febd51c7e24361e3a9c09f3e892eb0a41ea ]
+
+Add tracing for the refcounting/lifecycle of the cifs_tcon struct, marking
+different events with different labels and giving each tcon its own debug
+ID so that the tracelines corresponding to individual tcons can be
+distinguished. This can be enabled with:
+
+ echo 1 >/sys/kernel/debug/tracing/events/cifs/smb3_tcon_ref/enable
+
+Signed-off-by: David Howells <dhowells@redhat.com>
+Acked-by: Paulo Alcantara (Red Hat) <pc@manguebit.com>
+cc: Shyam Prasad N <nspmangalore@gmail.com>
+cc: linux-cifs@vger.kernel.org
+cc: linux-fsdevel@vger.kernel.org
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/cifs_swn.c | 13 ++---
+ fs/smb/client/cifsfs.c | 2
+ fs/smb/client/cifsglob.h | 1
+ fs/smb/client/cifsproto.h | 8 +--
+ fs/smb/client/connect.c | 21 +++++----
+ fs/smb/client/dfs_cache.c | 2
+ fs/smb/client/fscache.c | 7 +++
+ fs/smb/client/misc.c | 12 +++--
+ fs/smb/client/smb2misc.c | 10 +++-
+ fs/smb/client/smb2ops.c | 7 ++-
+ fs/smb/client/smb2pdu.c | 8 ++-
+ fs/smb/client/smb2transport.c | 2
+ fs/smb/client/trace.h | 93 +++++++++++++++++++++++++++++++++++++++++-
+ 13 files changed, 151 insertions(+), 35 deletions(-)
+
+--- a/fs/smb/client/cifs_swn.c
++++ b/fs/smb/client/cifs_swn.c
+@@ -313,17 +313,15 @@ static struct cifs_swn_reg *cifs_get_swn
+ reg = cifs_find_swn_reg(tcon);
+ if (!IS_ERR(reg)) {
+ kref_get(®->ref_count);
+- mutex_unlock(&cifs_swnreg_idr_mutex);
+- return reg;
++ goto unlock;
+ } else if (PTR_ERR(reg) != -EEXIST) {
+- mutex_unlock(&cifs_swnreg_idr_mutex);
+- return reg;
++ goto unlock;
+ }
+
+ reg = kmalloc(sizeof(struct cifs_swn_reg), GFP_ATOMIC);
+ if (reg == NULL) {
+- mutex_unlock(&cifs_swnreg_idr_mutex);
+- return ERR_PTR(-ENOMEM);
++ ret = -ENOMEM;
++ goto fail_unlock;
+ }
+
+ kref_init(®->ref_count);
+@@ -354,7 +352,7 @@ static struct cifs_swn_reg *cifs_get_swn
+ reg->ip_notify = (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT);
+
+ reg->tcon = tcon;
+-
++unlock:
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+
+ return reg;
+@@ -365,6 +363,7 @@ fail_idr:
+ idr_remove(&cifs_swnreg_idr, reg->id);
+ fail:
+ kfree(reg);
++fail_unlock:
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+ return ERR_PTR(ret);
+ }
+--- a/fs/smb/client/cifsfs.c
++++ b/fs/smb/client/cifsfs.c
+@@ -747,6 +747,8 @@ static void cifs_umount_begin(struct sup
+
+ spin_lock(&cifs_tcp_ses_lock);
+ spin_lock(&tcon->tc_lock);
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
++ netfs_trace_tcon_ref_see_umount);
+ if ((tcon->tc_count > 1) || (tcon->status == TID_EXITING)) {
+ /* we have other mounts to same share or we have
+ already tried to umount this and woken up
+--- a/fs/smb/client/cifsglob.h
++++ b/fs/smb/client/cifsglob.h
+@@ -1121,6 +1121,7 @@ struct cifs_fattr {
+ */
+ struct cifs_tcon {
+ struct list_head tcon_list;
++ int debug_id; /* Debugging for tracing */
+ int tc_count;
+ struct list_head rlist; /* reconnect list */
+ spinlock_t tc_lock; /* protect anything here that is not protected */
+--- a/fs/smb/client/cifsproto.h
++++ b/fs/smb/client/cifsproto.h
+@@ -305,7 +305,7 @@ cifs_get_tcp_session(struct smb3_fs_cont
+ struct TCP_Server_Info *primary_server);
+ extern void cifs_put_tcp_session(struct TCP_Server_Info *server,
+ int from_reconnect);
+-extern void cifs_put_tcon(struct cifs_tcon *tcon);
++extern void cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace);
+
+ #if IS_ENABLED(CONFIG_CIFS_DFS_UPCALL)
+ extern void cifs_dfs_release_automount_timer(void);
+@@ -525,8 +525,8 @@ extern int CIFSSMBLogoff(const unsigned
+
+ extern struct cifs_ses *sesInfoAlloc(void);
+ extern void sesInfoFree(struct cifs_ses *);
+-extern struct cifs_tcon *tconInfoAlloc(void);
+-extern void tconInfoFree(struct cifs_tcon *);
++extern struct cifs_tcon *tconInfoAlloc(enum smb3_tcon_ref_trace trace);
++extern void tconInfoFree(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace);
+
+ extern int cifs_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server,
+ __u32 *pexpected_response_sequence_number);
+@@ -719,8 +719,6 @@ static inline int cifs_create_options(st
+ return options;
+ }
+
+-struct super_block *cifs_get_tcon_super(struct cifs_tcon *tcon);
+-void cifs_put_tcon_super(struct super_block *sb);
+ int cifs_wait_for_server_reconnect(struct TCP_Server_Info *server, bool retry);
+
+ static inline void release_mid(struct mid_q_entry *mid)
+--- a/fs/smb/client/connect.c
++++ b/fs/smb/client/connect.c
+@@ -1985,7 +1985,7 @@ cifs_setup_ipc(struct cifs_ses *ses, str
+ }
+ }
+
+- tcon = tconInfoAlloc();
++ tcon = tconInfoAlloc(netfs_trace_tcon_ref_new_ipc);
+ if (tcon == NULL)
+ return -ENOMEM;
+
+@@ -2002,7 +2002,7 @@ cifs_setup_ipc(struct cifs_ses *ses, str
+
+ if (rc) {
+ cifs_server_dbg(VFS, "failed to connect to IPC (rc=%d)\n", rc);
+- tconInfoFree(tcon);
++ tconInfoFree(tcon, netfs_trace_tcon_ref_free_ipc_fail);
+ goto out;
+ }
+
+@@ -2085,7 +2085,7 @@ void cifs_put_smb_ses(struct cifs_ses *s
+ * files on session close, as specified in MS-SMB2 3.3.5.6 Receiving an
+ * SMB2 LOGOFF Request.
+ */
+- tconInfoFree(tcon);
++ tconInfoFree(tcon, netfs_trace_tcon_ref_free_ipc);
+ if (do_logoff) {
+ xid = get_xid();
+ rc = server->ops->logoff(xid, ses);
+@@ -2459,6 +2459,8 @@ cifs_find_tcon(struct cifs_ses *ses, str
+ continue;
+ }
+ ++tcon->tc_count;
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
++ netfs_trace_tcon_ref_get_find);
+ spin_unlock(&tcon->tc_lock);
+ spin_unlock(&cifs_tcp_ses_lock);
+ return tcon;
+@@ -2468,7 +2470,7 @@ cifs_find_tcon(struct cifs_ses *ses, str
+ }
+
+ void
+-cifs_put_tcon(struct cifs_tcon *tcon)
++cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace)
+ {
+ unsigned int xid;
+ struct cifs_ses *ses;
+@@ -2484,6 +2486,7 @@ cifs_put_tcon(struct cifs_tcon *tcon)
+ cifs_dbg(FYI, "%s: tc_count=%d\n", __func__, tcon->tc_count);
+ spin_lock(&cifs_tcp_ses_lock);
+ spin_lock(&tcon->tc_lock);
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count - 1, trace);
+ if (--tcon->tc_count > 0) {
+ spin_unlock(&tcon->tc_lock);
+ spin_unlock(&cifs_tcp_ses_lock);
+@@ -2517,7 +2520,7 @@ cifs_put_tcon(struct cifs_tcon *tcon)
+ _free_xid(xid);
+
+ cifs_fscache_release_super_cookie(tcon);
+- tconInfoFree(tcon);
++ tconInfoFree(tcon, netfs_trace_tcon_ref_free);
+ cifs_put_smb_ses(ses);
+ }
+
+@@ -2565,7 +2568,7 @@ cifs_get_tcon(struct cifs_ses *ses, stru
+ goto out_fail;
+ }
+
+- tcon = tconInfoAlloc();
++ tcon = tconInfoAlloc(netfs_trace_tcon_ref_new);
+ if (tcon == NULL) {
+ rc = -ENOMEM;
+ goto out_fail;
+@@ -2762,7 +2765,7 @@ cifs_get_tcon(struct cifs_ses *ses, stru
+ return tcon;
+
+ out_fail:
+- tconInfoFree(tcon);
++ tconInfoFree(tcon, netfs_trace_tcon_ref_free_fail);
+ return ERR_PTR(rc);
+ }
+
+@@ -2779,7 +2782,7 @@ cifs_put_tlink(struct tcon_link *tlink)
+ }
+
+ if (!IS_ERR(tlink_tcon(tlink)))
+- cifs_put_tcon(tlink_tcon(tlink));
++ cifs_put_tcon(tlink_tcon(tlink), netfs_trace_tcon_ref_put_tlink);
+ kfree(tlink);
+ return;
+ }
+@@ -3338,7 +3341,7 @@ static inline void mount_put_conns(struc
+ int rc = 0;
+
+ if (mnt_ctx->tcon)
+- cifs_put_tcon(mnt_ctx->tcon);
++ cifs_put_tcon(mnt_ctx->tcon, netfs_trace_tcon_ref_put_mnt_ctx);
+ else if (mnt_ctx->ses)
+ cifs_put_smb_ses(mnt_ctx->ses);
+ else if (mnt_ctx->server)
+--- a/fs/smb/client/dfs_cache.c
++++ b/fs/smb/client/dfs_cache.c
+@@ -1564,7 +1564,7 @@ static void refresh_mounts(struct cifs_s
+ }
+ mutex_unlock(&server->refpath_lock);
+
+- cifs_put_tcon(tcon);
++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_dfs_refer);
+ }
+ }
+
+--- a/fs/smb/client/fscache.c
++++ b/fs/smb/client/fscache.c
+@@ -94,6 +94,11 @@ int cifs_fscache_get_super_cookie(struct
+ }
+ pr_err("Cache volume key already in use (%s)\n", key);
+ vcookie = NULL;
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
++ netfs_trace_tcon_ref_see_fscache_collision);
++ } else {
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
++ netfs_trace_tcon_ref_see_fscache_okay);
+ }
+
+ tcon->fscache = vcookie;
+@@ -115,6 +120,8 @@ void cifs_fscache_release_super_cookie(s
+ cifs_fscache_fill_volume_coherency(tcon, &cd);
+ fscache_relinquish_volume(tcon->fscache, &cd, false);
+ tcon->fscache = NULL;
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
++ netfs_trace_tcon_ref_see_fscache_relinq);
+ }
+
+ void cifs_fscache_get_inode_cookie(struct inode *inode)
+--- a/fs/smb/client/misc.c
++++ b/fs/smb/client/misc.c
+@@ -116,9 +116,10 @@ sesInfoFree(struct cifs_ses *buf_to_free
+ }
+
+ struct cifs_tcon *
+-tconInfoAlloc(void)
++tconInfoAlloc(enum smb3_tcon_ref_trace trace)
+ {
+ struct cifs_tcon *ret_buf;
++ static atomic_t tcon_debug_id;
+
+ ret_buf = kzalloc(sizeof(*ret_buf), GFP_KERNEL);
+ if (!ret_buf)
+@@ -131,7 +132,8 @@ tconInfoAlloc(void)
+
+ atomic_inc(&tconInfoAllocCount);
+ ret_buf->status = TID_NEW;
+- ++ret_buf->tc_count;
++ ret_buf->debug_id = atomic_inc_return(&tcon_debug_id);
++ ret_buf->tc_count = 1;
+ spin_lock_init(&ret_buf->tc_lock);
+ INIT_LIST_HEAD(&ret_buf->openFileList);
+ INIT_LIST_HEAD(&ret_buf->tcon_list);
+@@ -142,17 +144,19 @@ tconInfoAlloc(void)
+ #ifdef CONFIG_CIFS_FSCACHE
+ mutex_init(&ret_buf->fscache_lock);
+ #endif
++ trace_smb3_tcon_ref(ret_buf->debug_id, ret_buf->tc_count, trace);
+
+ return ret_buf;
+ }
+
+ void
+-tconInfoFree(struct cifs_tcon *tcon)
++tconInfoFree(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace)
+ {
+ if (tcon == NULL) {
+ cifs_dbg(FYI, "Null buffer passed to tconInfoFree\n");
+ return;
+ }
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, trace);
+ free_cached_dirs(tcon->cfids);
+ atomic_dec(&tconInfoAllocCount);
+ kfree(tcon->nativeFileSystem);
+@@ -848,7 +852,7 @@ void cifs_close_all_deferred_files_sb(st
+ list_for_each_entry_safe(tmp_list, q, &tcon_head, entry) {
+ cifs_close_all_deferred_files(tmp_list->tcon);
+ list_del(&tmp_list->entry);
+- cifs_put_tcon(tmp_list->tcon);
++ cifs_put_tcon(tmp_list->tcon, netfs_trace_tcon_ref_put_tlink);
+ kfree(tmp_list);
+ }
+ }
+--- a/fs/smb/client/smb2misc.c
++++ b/fs/smb/client/smb2misc.c
+@@ -804,7 +804,7 @@ smb2_cancelled_close_fid(struct work_str
+ if (rc)
+ cifs_tcon_dbg(VFS, "Close cancelled mid failed rc:%d\n", rc);
+
+- cifs_put_tcon(tcon);
++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_close_fid);
+ kfree(cancelled);
+ }
+
+@@ -848,6 +848,8 @@ smb2_handle_cancelled_close(struct cifs_
+ if (tcon->tc_count <= 0) {
+ struct TCP_Server_Info *server = NULL;
+
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
++ netfs_trace_tcon_ref_see_cancelled_close);
+ WARN_ONCE(tcon->tc_count < 0, "tcon refcount is negative");
+ spin_unlock(&cifs_tcp_ses_lock);
+
+@@ -861,12 +863,14 @@ smb2_handle_cancelled_close(struct cifs_
+ return 0;
+ }
+ tcon->tc_count++;
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
++ netfs_trace_tcon_ref_get_cancelled_close);
+ spin_unlock(&cifs_tcp_ses_lock);
+
+ rc = __smb2_handle_cancelled_cmd(tcon, SMB2_CLOSE_HE, 0,
+ persistent_fid, volatile_fid);
+ if (rc)
+- cifs_put_tcon(tcon);
++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_close);
+
+ return rc;
+ }
+@@ -894,7 +898,7 @@ smb2_handle_cancelled_mid(struct mid_q_e
+ rsp->PersistentFileId,
+ rsp->VolatileFileId);
+ if (rc)
+- cifs_put_tcon(tcon);
++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_mid);
+
+ return rc;
+ }
+--- a/fs/smb/client/smb2ops.c
++++ b/fs/smb/client/smb2ops.c
+@@ -2848,8 +2848,11 @@ smb2_get_dfs_refer(const unsigned int xi
+ tcon = list_first_entry_or_null(&ses->tcon_list,
+ struct cifs_tcon,
+ tcon_list);
+- if (tcon)
++ if (tcon) {
+ tcon->tc_count++;
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
++ netfs_trace_tcon_ref_get_dfs_refer);
++ }
+ spin_unlock(&cifs_tcp_ses_lock);
+ }
+
+@@ -2913,6 +2916,8 @@ smb2_get_dfs_refer(const unsigned int xi
+ /* ipc tcons are not refcounted */
+ spin_lock(&cifs_tcp_ses_lock);
+ tcon->tc_count--;
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
++ netfs_trace_tcon_ref_dec_dfs_refer);
+ /* tc_count can never go negative */
+ WARN_ON(tcon->tc_count < 0);
+ spin_unlock(&cifs_tcp_ses_lock);
+--- a/fs/smb/client/smb2pdu.c
++++ b/fs/smb/client/smb2pdu.c
+@@ -3995,6 +3995,8 @@ void smb2_reconnect_server(struct work_s
+ list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
+ if (tcon->need_reconnect || tcon->need_reopen_files) {
+ tcon->tc_count++;
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
++ netfs_trace_tcon_ref_get_reconnect_server);
+ list_add_tail(&tcon->rlist, &tmp_list);
+ tcon_selected = tcon_exist = true;
+ }
+@@ -4040,14 +4042,14 @@ void smb2_reconnect_server(struct work_s
+ if (tcon->ipc)
+ cifs_put_smb_ses(tcon->ses);
+ else
+- cifs_put_tcon(tcon);
++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_reconnect_server);
+ }
+
+ if (!ses_exist)
+ goto done;
+
+ /* allocate a dummy tcon struct used for reconnect */
+- tcon = tconInfoAlloc();
++ tcon = tconInfoAlloc(netfs_trace_tcon_ref_new_reconnect_server);
+ if (!tcon) {
+ resched = true;
+ list_for_each_entry_safe(ses, ses2, &tmp_ses_list, rlist) {
+@@ -4070,7 +4072,7 @@ void smb2_reconnect_server(struct work_s
+ list_del_init(&ses->rlist);
+ cifs_put_smb_ses(ses);
+ }
+- tconInfoFree(tcon);
++ tconInfoFree(tcon, netfs_trace_tcon_ref_free_reconnect_server);
+
+ done:
+ cifs_dbg(FYI, "Reconnecting tcons and channels finished\n");
+--- a/fs/smb/client/smb2transport.c
++++ b/fs/smb/client/smb2transport.c
+@@ -189,6 +189,8 @@ smb2_find_smb_sess_tcon_unlocked(struct
+ if (tcon->tid != tid)
+ continue;
+ ++tcon->tc_count;
++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
++ netfs_trace_tcon_ref_get_find_sess_tcon);
+ return tcon;
+ }
+
+--- a/fs/smb/client/trace.h
++++ b/fs/smb/client/trace.h
+@@ -3,6 +3,9 @@
+ * Copyright (C) 2018, Microsoft Corporation.
+ *
+ * Author(s): Steve French <stfrench@microsoft.com>
++ *
++ * Please use this 3-part article as a reference for writing new tracepoints:
++ * https://lwn.net/Articles/379903/
+ */
+ #undef TRACE_SYSTEM
+ #define TRACE_SYSTEM cifs
+@@ -15,9 +18,71 @@
+ #include <linux/inet.h>
+
+ /*
+- * Please use this 3-part article as a reference for writing new tracepoints:
+- * https://lwn.net/Articles/379903/
++ * Specify enums for tracing information.
++ */
++#define smb3_tcon_ref_traces \
++ EM(netfs_trace_tcon_ref_dec_dfs_refer, "DEC DfsRef") \
++ EM(netfs_trace_tcon_ref_free, "FRE ") \
++ EM(netfs_trace_tcon_ref_free_fail, "FRE Fail ") \
++ EM(netfs_trace_tcon_ref_free_ipc, "FRE Ipc ") \
++ EM(netfs_trace_tcon_ref_free_ipc_fail, "FRE Ipc-F ") \
++ EM(netfs_trace_tcon_ref_free_reconnect_server, "FRE Reconn") \
++ EM(netfs_trace_tcon_ref_get_cancelled_close, "GET Cn-Cls") \
++ EM(netfs_trace_tcon_ref_get_dfs_refer, "GET DfsRef") \
++ EM(netfs_trace_tcon_ref_get_find, "GET Find ") \
++ EM(netfs_trace_tcon_ref_get_find_sess_tcon, "GET FndSes") \
++ EM(netfs_trace_tcon_ref_get_reconnect_server, "GET Reconn") \
++ EM(netfs_trace_tcon_ref_new, "NEW ") \
++ EM(netfs_trace_tcon_ref_new_ipc, "NEW Ipc ") \
++ EM(netfs_trace_tcon_ref_new_reconnect_server, "NEW Reconn") \
++ EM(netfs_trace_tcon_ref_put_cancelled_close, "PUT Cn-Cls") \
++ EM(netfs_trace_tcon_ref_put_cancelled_close_fid, "PUT Cn-Fid") \
++ EM(netfs_trace_tcon_ref_put_cancelled_mid, "PUT Cn-Mid") \
++ EM(netfs_trace_tcon_ref_put_mnt_ctx, "PUT MntCtx") \
++ EM(netfs_trace_tcon_ref_put_dfs_refer, "PUT DfsRfr") \
++ EM(netfs_trace_tcon_ref_put_reconnect_server, "PUT Reconn") \
++ EM(netfs_trace_tcon_ref_put_tlink, "PUT Tlink ") \
++ EM(netfs_trace_tcon_ref_see_cancelled_close, "SEE Cn-Cls") \
++ EM(netfs_trace_tcon_ref_see_fscache_collision, "SEE FV-CO!") \
++ EM(netfs_trace_tcon_ref_see_fscache_okay, "SEE FV-Ok ") \
++ EM(netfs_trace_tcon_ref_see_fscache_relinq, "SEE FV-Rlq") \
++ E_(netfs_trace_tcon_ref_see_umount, "SEE Umount")
++
++#undef EM
++#undef E_
++
++/*
++ * Define those tracing enums.
+ */
++#ifndef __SMB3_DECLARE_TRACE_ENUMS_ONCE_ONLY
++#define __SMB3_DECLARE_TRACE_ENUMS_ONCE_ONLY
++
++#define EM(a, b) a,
++#define E_(a, b) a
++
++enum smb3_tcon_ref_trace { smb3_tcon_ref_traces } __mode(byte);
++
++#undef EM
++#undef E_
++#endif
++
++/*
++ * Export enum symbols via userspace.
++ */
++#define EM(a, b) TRACE_DEFINE_ENUM(a);
++#define E_(a, b) TRACE_DEFINE_ENUM(a);
++
++smb3_tcon_ref_traces;
++
++#undef EM
++#undef E_
++
++/*
++ * Now redefine the EM() and E_() macros to map the enums to the strings that
++ * will be printed in the output.
++ */
++#define EM(a, b) { a, b },
++#define E_(a, b) { a, b }
+
+ /* For logging errors in read or write */
+ DECLARE_EVENT_CLASS(smb3_rw_err_class,
+@@ -1062,6 +1127,30 @@ DEFINE_SMB3_CREDIT_EVENT(waitff_credits)
+ DEFINE_SMB3_CREDIT_EVENT(overflow_credits);
+ DEFINE_SMB3_CREDIT_EVENT(set_credits);
+
++
++TRACE_EVENT(smb3_tcon_ref,
++ TP_PROTO(unsigned int tcon_debug_id, int ref,
++ enum smb3_tcon_ref_trace trace),
++ TP_ARGS(tcon_debug_id, ref, trace),
++ TP_STRUCT__entry(
++ __field(unsigned int, tcon)
++ __field(int, ref)
++ __field(enum smb3_tcon_ref_trace, trace)
++ ),
++ TP_fast_assign(
++ __entry->tcon = tcon_debug_id;
++ __entry->ref = ref;
++ __entry->trace = trace;
++ ),
++ TP_printk("TC=%08x %s r=%u",
++ __entry->tcon,
++ __print_symbolic(__entry->trace, smb3_tcon_ref_traces),
++ __entry->ref)
++ );
++
++
++#undef EM
++#undef E_
+ #endif /* _CIFS_TRACE_H */
+
+ #undef TRACE_INCLUDE_PATH
--- /dev/null
+From stable+bounces-275230-greg=kroah.com@vger.kernel.org Thu Jul 16 12:18:56 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 06:18:21 -0400
+Subject: cifs: after disabling multichannel, mark tcon for reconnect
+To: stable@vger.kernel.org
+Cc: Shyam Prasad N <sprasad@microsoft.com>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716101824.1461870-2-sashal@kernel.org>
+
+From: Shyam Prasad N <sprasad@microsoft.com>
+
+[ Upstream commit 27e1fd343f80168ff456785c2443136b6b7ca3cc ]
+
+Once the server disables multichannel for an active multichannel
+session, on the following reconnect, the client would reduce
+the number of channels to 1. However, it could be the case that
+the tree connect was active on one of these disabled channels.
+This results in an unrecoverable state.
+
+This change fixes that by making sure that whenever a channel
+is being terminated, the session and tcon are marked for
+reconnect too. This could mean a few redundant tree connect
+calls to the server, but considering that this is not a frequent
+event, we should be okay.
+
+Fixes: ee1d21794e55 ("cifs: handle when server stops supporting multichannel")
+Signed-off-by: Shyam Prasad N <sprasad@microsoft.com>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/connect.c | 20 ++++++++++++--------
+ 1 file changed, 12 insertions(+), 8 deletions(-)
+
+--- a/fs/smb/client/connect.c
++++ b/fs/smb/client/connect.c
+@@ -256,17 +256,21 @@ cifs_mark_tcp_ses_conns_for_reconnect(st
+ /* If server is a channel, select the primary channel */
+ pserver = CIFS_SERVER_IS_CHAN(server) ? server->primary_server : server;
+
++ /*
++ * if the server has been marked for termination, there is a
++ * chance that the remaining channels all need reconnect. To be
++ * on the safer side, mark the session and trees for reconnect
++ * for this scenario. This might cause a few redundant session
++ * setup and tree connect requests, but it is better than not doing
++ * a tree connect when needed, and all following requests failing
++ */
++ if (server->terminate) {
++ mark_smb_session = true;
++ server = pserver;
++ }
+
+ spin_lock(&cifs_tcp_ses_lock);
+ list_for_each_entry_safe(ses, nses, &pserver->smb_ses_list, smb_ses_list) {
+- /*
+- * if channel has been marked for termination, nothing to do
+- * for the channel. in fact, we cannot find the channel for the
+- * server. So safe to exit here
+- */
+- if (server->terminate)
+- break;
+-
+ spin_lock(&ses->ses_lock);
+ if (ses->ses_status == SES_EXITING) {
+ spin_unlock(&ses->ses_lock);
--- /dev/null
+From stable+bounces-275231-greg=kroah.com@vger.kernel.org Thu Jul 16 12:19:35 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 06:18:22 -0400
+Subject: cifs: Fix reacquisition of volume cookie on still-live connection
+To: stable@vger.kernel.org
+Cc: David Howells <dhowells@redhat.com>, "Paulo Alcantara (Red Hat)" <pc@manguebit.com>, Shyam Prasad N <sprasad@microsoft.com>, linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716101824.1461870-3-sashal@kernel.org>
+
+From: David Howells <dhowells@redhat.com>
+
+[ Upstream commit dad80c6bff770d25f67ec25fe011730e4a463008 ]
+
+During mount, cifs_mount_get_tcon() gets a tcon resource connection record
+and then attaches an fscache volume cookie to it. However, it does this
+irrespective of whether or not the tcon returned from cifs_get_tcon() is a
+new record or one that's already in use. This leads to a warning about a
+volume cookie collision and a leaked volume cookie because tcon->fscache
+gets reset.
+
+Fix this be adding a mutex and a "we've already tried this" flag and only
+doing it once for the lifetime of the tcon.
+
+[!] Note: Looking at cifs_mount_get_tcon(), a more general solution may
+actually be required. Reacquiring the volume cookie isn't the only thing
+that function does: it also partially reinitialises the tcon record without
+any locking - which may cause live filesystem ops already using the tcon
+through a previous mount to malfunction.
+
+This can be reproduced simply by something like:
+
+ mount //example.com/test /xfstest.test -o user=shares,pass=xxx,fsc
+ mount //example.com/test /mnt -o user=shares,pass=xxx,fsc
+
+Fixes: 70431bfd825d ("cifs: Support fscache indexing rewrite")
+Signed-off-by: David Howells <dhowells@redhat.com>
+Acked-by: Paulo Alcantara (Red Hat) <pc@manguebit.com>
+cc: Shyam Prasad N <sprasad@microsoft.com>
+cc: linux-cifs@vger.kernel.org
+cc: linux-fsdevel@vger.kernel.org
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/cifsglob.h | 2 ++
+ fs/smb/client/fscache.c | 13 +++++++++++++
+ fs/smb/client/misc.c | 3 +++
+ 3 files changed, 18 insertions(+)
+
+--- a/fs/smb/client/cifsglob.h
++++ b/fs/smb/client/cifsglob.h
+@@ -1205,7 +1205,9 @@ struct cifs_tcon {
+ __u32 max_bytes_copy;
+ #ifdef CONFIG_CIFS_FSCACHE
+ u64 resource_id; /* server resource id */
++ bool fscache_acquired; /* T if we've tried acquiring a cookie */
+ struct fscache_volume *fscache; /* cookie for share */
++ struct mutex fscache_lock; /* Prevent regetting a cookie */
+ #endif
+ struct list_head pending_opens; /* list of incomplete opens */
+ struct cached_fids *cfids;
+--- a/fs/smb/client/fscache.c
++++ b/fs/smb/client/fscache.c
+@@ -43,12 +43,23 @@ int cifs_fscache_get_super_cookie(struct
+ char *key;
+ int ret = -ENOMEM;
+
++ if (tcon->fscache_acquired)
++ return 0;
++
++ mutex_lock(&tcon->fscache_lock);
++ if (tcon->fscache_acquired) {
++ mutex_unlock(&tcon->fscache_lock);
++ return 0;
++ }
++ tcon->fscache_acquired = true;
++
+ tcon->fscache = NULL;
+ switch (sa->sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ break;
+ default:
++ mutex_unlock(&tcon->fscache_lock);
+ cifs_dbg(VFS, "Unknown network family '%d'\n", sa->sa_family);
+ return -EINVAL;
+ }
+@@ -57,6 +68,7 @@ int cifs_fscache_get_super_cookie(struct
+
+ sharename = extract_sharename(tcon->tree_name);
+ if (IS_ERR(sharename)) {
++ mutex_unlock(&tcon->fscache_lock);
+ cifs_dbg(FYI, "%s: couldn't extract sharename\n", __func__);
+ return PTR_ERR(sharename);
+ }
+@@ -90,6 +102,7 @@ out_2:
+ kfree(key);
+ out:
+ kfree(sharename);
++ mutex_unlock(&tcon->fscache_lock);
+ return ret;
+ }
+
+--- a/fs/smb/client/misc.c
++++ b/fs/smb/client/misc.c
+@@ -139,6 +139,9 @@ tconInfoAlloc(void)
+ spin_lock_init(&ret_buf->stat_lock);
+ atomic_set(&ret_buf->num_local_opens, 0);
+ atomic_set(&ret_buf->num_remote_opens, 0);
++#ifdef CONFIG_CIFS_FSCACHE
++ mutex_init(&ret_buf->fscache_lock);
++#endif
+
+ return ret_buf;
+ }
--- /dev/null
+From stable+bounces-275229-greg=kroah.com@vger.kernel.org Thu Jul 16 12:24:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 06:18:20 -0400
+Subject: cifs: handle when server stops supporting multichannel
+To: stable@vger.kernel.org
+Cc: Shyam Prasad N <sprasad@microsoft.com>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716101824.1461870-1-sashal@kernel.org>
+
+From: Shyam Prasad N <sprasad@microsoft.com>
+
+[ Upstream commit ee1d21794e55ab76505745d24101331552182002 ]
+
+When a server stops supporting multichannel, we will
+keep attempting reconnects to the secondary channels today.
+Avoid this by freeing extra channels when negotiate
+returns no multichannel support.
+
+Signed-off-by: Shyam Prasad N <sprasad@microsoft.com>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/cifsglob.h | 1
+ fs/smb/client/cifsproto.h | 2 +
+ fs/smb/client/connect.c | 10 ++++++
+ fs/smb/client/sess.c | 65 +++++++++++++++++++++++++++++++++------
+ fs/smb/client/smb2pdu.c | 76 +++++++++++++++++++++++++++++++++++++++++++++-
+ fs/smb/client/transport.c | 2 -
+ 6 files changed, 145 insertions(+), 11 deletions(-)
+
+--- a/fs/smb/client/cifsglob.h
++++ b/fs/smb/client/cifsglob.h
+@@ -639,6 +639,7 @@ struct TCP_Server_Info {
+ bool noautotune; /* do not autotune send buf sizes */
+ bool nosharesock;
+ bool tcp_nodelay;
++ bool terminate;
+ unsigned int credits; /* send no more requests at once */
+ unsigned int max_credits; /* can override large 32000 default at mnt */
+ unsigned int in_flight; /* number of requests on the wire to server */
+--- a/fs/smb/client/cifsproto.h
++++ b/fs/smb/client/cifsproto.h
+@@ -662,6 +662,8 @@ cifs_chan_needs_reconnect(struct cifs_se
+ bool
+ cifs_chan_is_iface_active(struct cifs_ses *ses,
+ struct TCP_Server_Info *server);
++void
++cifs_disable_secondary_channels(struct cifs_ses *ses);
+ int
+ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server);
+ int
+--- a/fs/smb/client/connect.c
++++ b/fs/smb/client/connect.c
+@@ -259,6 +259,14 @@ cifs_mark_tcp_ses_conns_for_reconnect(st
+
+ spin_lock(&cifs_tcp_ses_lock);
+ list_for_each_entry_safe(ses, nses, &pserver->smb_ses_list, smb_ses_list) {
++ /*
++ * if channel has been marked for termination, nothing to do
++ * for the channel. in fact, we cannot find the channel for the
++ * server. So safe to exit here
++ */
++ if (server->terminate)
++ break;
++
+ spin_lock(&ses->ses_lock);
+ if (ses->ses_status == SES_EXITING) {
+ spin_unlock(&ses->ses_lock);
+@@ -302,6 +310,8 @@ cifs_mark_tcp_ses_conns_for_reconnect(st
+ spin_lock(&tcon->tc_lock);
+ tcon->status = TID_NEED_RECON;
+ spin_unlock(&tcon->tc_lock);
++
++ cancel_delayed_work(&tcon->query_interfaces);
+ }
+ if (ses->tcon_ipc) {
+ ses->tcon_ipc->need_reconnect = true;
+--- a/fs/smb/client/sess.c
++++ b/fs/smb/client/sess.c
+@@ -278,6 +278,60 @@ int cifs_try_adding_channels(struct cifs
+ }
+
+ /*
++ * called when multichannel is disabled by the server.
++ * this always gets called from smb2_reconnect
++ * and cannot get called in parallel threads.
++ */
++void
++cifs_disable_secondary_channels(struct cifs_ses *ses)
++{
++ int i, chan_count;
++ struct TCP_Server_Info *server;
++ struct cifs_server_iface *iface;
++
++ spin_lock(&ses->chan_lock);
++ chan_count = ses->chan_count;
++ if (chan_count == 1)
++ goto done;
++
++ ses->chan_count = 1;
++
++ /* for all secondary channels reset the need reconnect bit */
++ ses->chans_need_reconnect &= 1;
++
++ for (i = 1; i < chan_count; i++) {
++ iface = ses->chans[i].iface;
++ server = ses->chans[i].server;
++
++ if (iface) {
++ spin_lock(&ses->iface_lock);
++ kref_put(&iface->refcount, release_iface);
++ ses->chans[i].iface = NULL;
++ iface->num_channels--;
++ if (iface->weight_fulfilled)
++ iface->weight_fulfilled--;
++ spin_unlock(&ses->iface_lock);
++ }
++
++ spin_unlock(&ses->chan_lock);
++ if (server && !server->terminate) {
++ server->terminate = true;
++ cifs_signal_cifsd_for_reconnect(server, false);
++ }
++ spin_lock(&ses->chan_lock);
++
++ if (server) {
++ ses->chans[i].server = NULL;
++ cifs_put_tcp_session(server, false);
++ }
++
++ }
++
++done:
++ spin_unlock(&ses->chan_lock);
++}
++
++/*
+ * update the iface for the channel if necessary.
+ * will return 0 when iface is updated, 1 if removed, 2 otherwise
+ * Must be called with chan_lock held.
+@@ -570,15 +624,10 @@ cifs_ses_add_channel(struct cifs_sb_info
+
+ out:
+ if (rc && chan->server) {
+- /*
+- * we should avoid race with these delayed works before we
+- * remove this channel
+- */
+- cancel_delayed_work_sync(&chan->server->echo);
+- cancel_delayed_work_sync(&chan->server->resolve);
+- cancel_delayed_work_sync(&chan->server->reconnect);
++ cifs_put_tcp_session(chan->server, 0);
+
+ spin_lock(&ses->chan_lock);
++
+ /* we rely on all bits beyond chan_count to be clear */
+ cifs_chan_clear_need_reconnect(ses, chan->server);
+ ses->chan_count--;
+@@ -588,8 +637,6 @@ out:
+ */
+ WARN_ON(ses->chan_count < 1);
+ spin_unlock(&ses->chan_lock);
+-
+- cifs_put_tcp_session(chan->server, 0);
+ }
+
+ free_xid(xid);
+--- a/fs/smb/client/smb2pdu.c
++++ b/fs/smb/client/smb2pdu.c
+@@ -160,6 +160,8 @@ smb2_reconnect(__le16 smb2_command, stru
+ int rc = 0;
+ struct nls_table *nls_codepage = NULL;
+ struct cifs_ses *ses;
++ struct TCP_Server_Info *pserver;
++ unsigned int chan_index;
+
+ /*
+ * SMB2s NegProt, SessSetup, Logoff do not have tcon yet so
+@@ -220,6 +222,12 @@ smb2_reconnect(__le16 smb2_command, stru
+ return -EAGAIN;
+ }
+ }
++
++ /* if server is marked for termination, cifsd will cleanup */
++ if (server->terminate) {
++ spin_unlock(&server->srv_lock);
++ return -EHOSTDOWN;
++ }
+ spin_unlock(&server->srv_lock);
+
+ again:
+@@ -239,11 +247,23 @@ again:
+
+ mutex_lock(&ses->session_mutex);
+ /*
++ * if this is called by delayed work, and the channel has been disabled
++ * in parallel, the delayed work can continue to execute in parallel
++ * there's a chance that this channel may not exist anymore
++ */
++ spin_lock(&server->srv_lock);
++ if (server->tcpStatus == CifsExiting) {
++ spin_unlock(&server->srv_lock);
++ mutex_unlock(&ses->session_mutex);
++ rc = -EHOSTDOWN;
++ goto out;
++ }
++
++ /*
+ * Recheck after acquire mutex. If another thread is negotiating
+ * and the server never sends an answer the socket will be closed
+ * and tcpStatus set to reconnect.
+ */
+- spin_lock(&server->srv_lock);
+ if (server->tcpStatus == CifsNeedReconnect) {
+ spin_unlock(&server->srv_lock);
+ mutex_unlock(&ses->session_mutex);
+@@ -280,6 +300,53 @@ again:
+
+ rc = cifs_negotiate_protocol(0, ses, server);
+ if (!rc) {
++ /*
++ * if server stopped supporting multichannel
++ * and the first channel reconnected, disable all the others.
++ */
++ if (ses->chan_count > 1 &&
++ !(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
++ if (CIFS_SERVER_IS_CHAN(server)) {
++ cifs_dbg(VFS, "server %s does not support " \
++ "multichannel anymore. skipping secondary channel\n",
++ ses->server->hostname);
++
++ spin_lock(&ses->chan_lock);
++ chan_index = cifs_ses_get_chan_index(ses, server);
++ if (!chan_index) {
++ spin_unlock(&ses->chan_lock);
++ goto skip_terminate;
++ }
++
++ ses->chans[chan_index].server = NULL;
++ spin_unlock(&ses->chan_lock);
++
++ /*
++ * the above reference of server by channel
++ * needs to be dropped without holding chan_lock
++ * as cifs_put_tcp_session takes a higher lock
++ * i.e. cifs_tcp_ses_lock
++ */
++ cifs_put_tcp_session(server, 1);
++
++ server->terminate = true;
++ cifs_signal_cifsd_for_reconnect(server, false);
++
++ /* mark primary server as needing reconnect */
++ pserver = server->primary_server;
++ cifs_signal_cifsd_for_reconnect(pserver, false);
++
++skip_terminate:
++ mutex_unlock(&ses->session_mutex);
++ rc = -EHOSTDOWN;
++ goto out;
++ } else {
++ cifs_server_dbg(VFS, "does not support " \
++ "multichannel anymore. disabling all other channels\n");
++ cifs_disable_secondary_channels(ses);
++ }
++ }
++
+ rc = cifs_setup_session(0, ses, server, nls_codepage);
+ if ((rc == -EACCES) && !tcon->retry) {
+ mutex_unlock(&ses->session_mutex);
+@@ -3903,6 +3970,13 @@ void smb2_reconnect_server(struct work_s
+ /* Prevent simultaneous reconnects that can corrupt tcon->rlist list */
+ mutex_lock(&pserver->reconnect_mutex);
+
++ /* if the server is marked for termination, drop the ref count here */
++ if (server->terminate) {
++ cifs_put_tcp_session(server, true);
++ mutex_unlock(&pserver->reconnect_mutex);
++ return;
++ }
++
+ INIT_LIST_HEAD(&tmp_list);
+ INIT_LIST_HEAD(&tmp_ses_list);
+ cifs_dbg(FYI, "Reconnecting tcons and channels\n");
+--- a/fs/smb/client/transport.c
++++ b/fs/smb/client/transport.c
+@@ -1063,7 +1063,7 @@ struct TCP_Server_Info *cifs_pick_channe
+ spin_lock(&ses->chan_lock);
+ for (i = 0; i < ses->chan_count; i++) {
+ server = ses->chans[i].server;
+- if (!server)
++ if (!server || server->terminate)
+ continue;
+
+ /*
--- /dev/null
+From stable+bounces-274986-greg=kroah.com@vger.kernel.org Wed Jul 15 18:53:04 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 12:30:22 -0400
+Subject: coresight: etb10: restore atomic_t for shared reading state
+To: stable@vger.kernel.org
+Cc: Runyu Xiao <runyu.xiao@seu.edu.cn>, James Clark <james.clark@linaro.org>, Suzuki K Poulose <suzuki.poulose@arm.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715163022.950711-1-sashal@kernel.org>
+
+From: Runyu Xiao <runyu.xiao@seu.edu.cn>
+
+[ Upstream commit fa09f08ede3db3050ae16ae1ed92c902d0cada23 ]
+
+The etb10 miscdevice uses drvdata->reading as a shared exclusivity gate
+for userspace buffer access. etb_open() claims that gate with
+local_cmpxchg(), and etb_release() clears it with local_set().
+
+That gate is shared per-device state rather than CPU-local state. A
+running system can reach it whenever /dev/<etb> is opened, closed, and
+reopened by different tasks while the device remains registered, so the
+same drvdata->reading variable may be claimed on one CPU and later
+cleared on another.
+
+This code used to use atomic_t for the same gate, but commit
+27b10da8fff2 ("coresight: etb10: moving to local atomic operations")
+changed it to local_t even though the access pattern remained cross-task
+and cross-CPU. Restore atomic_t together with atomic_cmpxchg() and
+atomic_set() so the exclusivity gate again uses a primitive intended
+for shared state.
+
+The issue was found on Linux v6.18.21 by our static analysis tool while
+scanning surviving local_t-on-shared-state sites, and then manually
+reviewed against the live etb10 file-op path.
+
+It was runtime-validated with a reproducible QEMU no-device KCSAN PoC
+that kept the same report-local contract:
+
+ 1. use one shared struct etb_drvdata carrier and its
+ drvdata->reading gate;
+ 2. call etb_open() and etb_release() sequentially on that gate to
+ confirm the original claim/clear path;
+ 3. bind the open side to CPU0 and the release side to CPU1 for the
+ same gate to show cross-CPU ownership;
+ 4. run bound workers that repeatedly race etb_open() and
+ etb_release() on the same gate until KCSAN reports a target hit.
+
+The harness recorded:
+
+ L1 passed open=1 release=1
+ reading_after_open=1 reading_after_release=0
+ L2 passed open_cpu=0 release_cpu=1
+ cross_cpu_release=1 reading_after=0 open_ret=0
+
+Representative KCSAN excerpt from the no-device validation run:
+
+ BUG: KCSAN: data-race in etb_open.constprop.0.isra.0 [vuln_msv]
+
+ write to 0xffffffffc0003810 of 4 bytes by task 216 on cpu 1:
+ etb_open.constprop.0.isra.0+0x38/0x80 [vuln_msv]
+ l3_worker_thread_fn+0x4f/0xf0 [vuln_msv]
+ kthread+0x17e/0x1c0
+ ret_from_fork+0x22/0x30
+
+ read to 0xffffffffc0003810 of 4 bytes by task 215 on cpu 0:
+ etb_open.constprop.0.isra.0+0x18/0x80 [vuln_msv]
+ l3_worker_thread_fn+0x4f/0xf0 [vuln_msv]
+ kthread+0x17e/0x1c0
+ ret_from_fork+0x22/0x30
+
+ value changed: 0x00000000 -> 0x00000001
+
+ Reported by Kernel Concurrency Sanitizer on:
+ CPU: 0 PID: 215 Comm: etb10_l3_a Tainted: G O 6.1.66 #2
+
+This no-device harness is not a real ETB10 hardware end-to-end run, but
+it preserves the same shared drvdata->reading gate and the same
+etb_open()/etb_release() claim/clear contract. No real ETB10 hardware
+was available for runtime testing.
+
+Build-tested with:
+ make olddefconfig
+ make -j"$(nproc)" drivers/hwtracing/coresight/coresight-etb10.o
+
+Fixes: 27b10da8fff2 ("coresight: etb10: moving to local atomic operations")
+Cc: stable@vger.kernel.org
+Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
+Reviewed-by: James Clark <james.clark@linaro.org>
+Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
+Link: https://lore.kernel.org/r/20260528165201.319452-1-runyu.xiao@seu.edu.cn
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/hwtracing/coresight/coresight-etb10.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/hwtracing/coresight/coresight-etb10.c
++++ b/drivers/hwtracing/coresight/coresight-etb10.c
+@@ -86,7 +86,7 @@ struct etb_drvdata {
+ struct coresight_device *csdev;
+ struct miscdevice miscdev;
+ spinlock_t spinlock;
+- local_t reading;
++ atomic_t reading;
+ pid_t pid;
+ u8 *buf;
+ u32 mode;
+@@ -603,7 +603,7 @@ static int etb_open(struct inode *inode,
+ struct etb_drvdata *drvdata = container_of(file->private_data,
+ struct etb_drvdata, miscdev);
+
+- if (local_cmpxchg(&drvdata->reading, 0, 1))
++ if (atomic_cmpxchg(&drvdata->reading, 0, 1))
+ return -EBUSY;
+
+ dev_dbg(&drvdata->csdev->dev, "%s: successfully opened\n", __func__);
+@@ -641,7 +641,7 @@ static int etb_release(struct inode *ino
+ {
+ struct etb_drvdata *drvdata = container_of(file->private_data,
+ struct etb_drvdata, miscdev);
+- local_set(&drvdata->reading, 0);
++ atomic_set(&drvdata->reading, 0);
+
+ dev_dbg(&drvdata->csdev->dev, "%s: released\n", __func__);
+ return 0;
--- /dev/null
+From stable+bounces-277811-greg=kroah.com@vger.kernel.org Mon Jul 20 16:02:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 09:51:02 -0400
+Subject: crypto: atmel - Drop explicit initialization of struct i2c_device_id::driver_data to 0
+To: stable@vger.kernel.org
+Cc: "Uwe Kleine-König" <u.kleine-koenig@baylibre.com>, "Herbert Xu" <herbert@gondor.apana.org.au>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260720135103.1306043-2-sashal@kernel.org>
+
+From: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
+
+[ Upstream commit d86ad3911a5d4549297ed810ee450e5772fd665f ]
+
+These drivers don't use the driver_data member of struct i2c_device_id,
+so don't explicitly initialize this member.
+
+This prepares putting driver_data in an anonymous union which requires
+either no initialization or named designators. But it's also a nice
+cleanup on its own.
+
+Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Stable-dep-of: ea5e57cc9718 ("crypto: atmel-sha204a - drop hwrng quality reduction for ATSHA204A")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/atmel-ecc.c | 2 +-
+ drivers/crypto/atmel-sha204a.c | 4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/crypto/atmel-ecc.c
++++ b/drivers/crypto/atmel-ecc.c
+@@ -381,7 +381,7 @@ MODULE_DEVICE_TABLE(of, atmel_ecc_dt_ids
+ #endif
+
+ static const struct i2c_device_id atmel_ecc_id[] = {
+- { "atecc508a", 0 },
++ { "atecc508a" },
+ { }
+ };
+ MODULE_DEVICE_TABLE(i2c, atmel_ecc_id);
+--- a/drivers/crypto/atmel-sha204a.c
++++ b/drivers/crypto/atmel-sha204a.c
+@@ -140,8 +140,8 @@ static const struct of_device_id atmel_s
+ MODULE_DEVICE_TABLE(of, atmel_sha204a_dt_ids);
+
+ static const struct i2c_device_id atmel_sha204a_id[] = {
+- { "atsha204", 0 },
+- { "atsha204a", 0 },
++ { "atsha204" },
++ { "atsha204a" },
+ { /* sentinel */ }
+ };
+ MODULE_DEVICE_TABLE(i2c, atmel_sha204a_id);
--- /dev/null
+From stable+bounces-277812-greg=kroah.com@vger.kernel.org Mon Jul 20 17:01:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 09:51:03 -0400
+Subject: crypto: atmel-sha204a - drop hwrng quality reduction for ATSHA204A
+To: stable@vger.kernel.org
+Cc: Thorsten Blum <thorsten.blum@linux.dev>, Ard Biesheuvel <ardb@kernel.org>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720135103.1306043-3-sashal@kernel.org>
+
+From: Thorsten Blum <thorsten.blum@linux.dev>
+
+[ Upstream commit ea5e57cc97185329dcc5ebdcaae7e1500bf0ad0b ]
+
+Commit 8006aff15516 ("crypto: atmel-sha204a - Set hwrng quality to
+lowest possible") reduced the hwrng quality to 1 based on a review by
+Bill Cox [1]. However, despite its title, the review only tested the
+ATSHA204, not the ATSHA204A.
+
+In the same thread, Atmel engineer Landon Cox wrote "this behavior has
+been eliminated entirely"[2] in the ATSHA204A and "this problem does not
+affect the ATECC108 or the ATECC108A (or the ATSHA204A)"[3].
+
+According to the official ATSHA204A datasheet [4], the device contains a
+high-quality hardware RNG that combines its output with an internal seed
+value stored in EEPROM or SRAM to generate random numbers. The device
+also implements all security functions using SHA-256, and the driver
+uses the chip's Random command in seed-update mode.
+
+Keep 'quality = 1' for ATSHA204, but drop the explicit hwrng quality
+reduction for ATSHA204A and fall back to the hwrng core default.
+
+[1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html
+[2] https://www.metzdowd.com/pipermail/cryptography/2014-December/023852.html
+[3] https://www.metzdowd.com/pipermail/cryptography/2014-December/023886.html
+[4] https://ww1.microchip.com/downloads/en/DeviceDoc/ATSHA204A-Data-Sheet-40002025A.pdf
+
+Fixes: 8006aff15516 ("crypto: atmel-sha204a - Set hwrng quality to lowest possible")
+Cc: stable@vger.kernel.org
+Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
+Reviewed-by: Ard Biesheuvel <ardb@kernel.org>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/atmel-sha204a.c | 19 ++++++++++++-------
+ 1 file changed, 12 insertions(+), 7 deletions(-)
+
+--- a/drivers/crypto/atmel-sha204a.c
++++ b/drivers/crypto/atmel-sha204a.c
+@@ -18,6 +18,12 @@
+ #include <linux/workqueue.h>
+ #include "atmel-i2c.h"
+
++/*
++ * According to review by Bill Cox [1], the ATSHA204 has very low entropy.
++ * [1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html
++ */
++static const unsigned short atsha204_quality = 1;
++
+ static void atmel_sha204a_rng_done(struct atmel_i2c_work_data *work_data,
+ void *areq, int status)
+ {
+@@ -96,6 +102,7 @@ static int atmel_sha204a_probe(struct i2
+ const struct i2c_device_id *id)
+ {
+ struct atmel_i2c_client_priv *i2c_priv;
++ const unsigned short *quality;
+ int ret;
+
+ ret = atmel_i2c_probe(client, id);
+@@ -109,11 +116,9 @@ static int atmel_sha204a_probe(struct i2
+ i2c_priv->hwrng.name = dev_name(&client->dev);
+ i2c_priv->hwrng.read = atmel_sha204a_rng_read;
+
+- /*
+- * According to review by Bill Cox [1], this HWRNG has very low entropy.
+- * [1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html
+- */
+- i2c_priv->hwrng.quality = 1;
++ quality = i2c_get_match_data(client);
++ if (quality)
++ i2c_priv->hwrng.quality = *quality;
+
+ ret = devm_hwrng_register(&client->dev, &i2c_priv->hwrng);
+ if (ret)
+@@ -133,14 +138,14 @@ static void atmel_sha204a_remove(struct
+ }
+
+ static const struct of_device_id atmel_sha204a_dt_ids[] __maybe_unused = {
+- { .compatible = "atmel,atsha204", },
++ { .compatible = "atmel,atsha204", .data = &atsha204_quality },
+ { .compatible = "atmel,atsha204a", },
+ { /* sentinel */ }
+ };
+ MODULE_DEVICE_TABLE(of, atmel_sha204a_dt_ids);
+
+ static const struct i2c_device_id atmel_sha204a_id[] = {
+- { "atsha204" },
++ { "atsha204", (kernel_ulong_t)&atsha204_quality },
+ { "atsha204a" },
+ { /* sentinel */ }
+ };
--- /dev/null
+From stable+bounces-277810-greg=kroah.com@vger.kernel.org Mon Jul 20 16:19:32 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 09:51:01 -0400
+Subject: crypto: atmel-sha204a - Mark OF related data as maybe unused
+To: stable@vger.kernel.org
+Cc: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720135103.1306043-1-sashal@kernel.org>
+
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+
+[ Upstream commit 75f3d950054389e2556277e42170e37dd97cd872 ]
+
+The driver can be compile tested with !CONFIG_OF making certain data
+unused:
+
+ drivers/crypto/atmel-sha204a.c:129:34: error: ‘atmel_sha204a_dt_ids’ defined but not used [-Werror=unused-const-variable=]
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Stable-dep-of: ea5e57cc9718 ("crypto: atmel-sha204a - drop hwrng quality reduction for ATSHA204A")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/atmel-sha204a.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/crypto/atmel-sha204a.c
++++ b/drivers/crypto/atmel-sha204a.c
+@@ -132,7 +132,7 @@ static void atmel_sha204a_remove(struct
+ kfree((void *)i2c_priv->hwrng.priv);
+ }
+
+-static const struct of_device_id atmel_sha204a_dt_ids[] = {
++static const struct of_device_id atmel_sha204a_dt_ids[] __maybe_unused = {
+ { .compatible = "atmel,atsha204", },
+ { .compatible = "atmel,atsha204a", },
+ { /* sentinel */ }
--- /dev/null
+From stable+bounces-278195-greg=kroah.com@vger.kernel.org Mon Jul 20 20:54:57 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 14:54:46 -0400
+Subject: crypto: qat - fix restarting state leak on allocation failure
+To: stable@vger.kernel.org
+Cc: Ahsan Atta <ahsan.atta@intel.com>, Maksim Lukoshkov <maksim.lukoshkov@intel.com>, Giovanni Cabiddu <giovanni.cabiddu@intel.com>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720185446.3089237-1-sashal@kernel.org>
+
+From: Ahsan Atta <ahsan.atta@intel.com>
+
+[ Upstream commit 7d3ed20f7e46b3e991936fedd7a28f3ff4aec8d2 ]
+
+In adf_dev_aer_schedule_reset(), ADF_STATUS_RESTARTING is set before
+allocating reset_data. If the allocation fails, the function returns
+-ENOMEM without queuing reset work, so nothing ever clears the bit.
+This leaves the device permanently stuck in the restarting state,
+causing all subsequent reset attempts to be silently skipped.
+
+Fix this by using test_and_set_bit() to atomically claim the
+RESTARTING state, preventing duplicate reset scheduling races under
+concurrent fatal error reporting. If the subsequent allocation fails,
+clear the bit to restore clean state so future reset attempts can
+proceed.
+
+Cc: stable@vger.kernel.org
+Fixes: d8cba25d2c68 ("crypto: qat - Intel(R) QAT driver framework")
+Signed-off-by: Ahsan Atta <ahsan.atta@intel.com>
+Co-developed-by: Maksim Lukoshkov <maksim.lukoshkov@intel.com>
+Signed-off-by: Maksim Lukoshkov <maksim.lukoshkov@intel.com>
+Reviewed-by: Giovanni Cabiddu <giovanni.cabiddu@intel.com>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/qat/qat_common/adf_aer.c | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+--- a/drivers/crypto/qat/qat_common/adf_aer.c
++++ b/drivers/crypto/qat/qat_common/adf_aer.c
+@@ -116,13 +116,14 @@ static int adf_dev_aer_schedule_reset(st
+ struct adf_reset_dev_data *reset_data;
+
+ if (!adf_dev_started(accel_dev) ||
+- test_bit(ADF_STATUS_RESTARTING, &accel_dev->status))
++ test_and_set_bit(ADF_STATUS_RESTARTING, &accel_dev->status))
+ return 0;
+
+- set_bit(ADF_STATUS_RESTARTING, &accel_dev->status);
+ reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL);
+- if (!reset_data)
++ if (!reset_data) {
++ clear_bit(ADF_STATUS_RESTARTING, &accel_dev->status);
+ return -ENOMEM;
++ }
+ reset_data->accel_dev = accel_dev;
+ init_completion(&reset_data->compl);
+ reset_data->mode = mode;
--- /dev/null
+From stable+bounces-274675-greg=kroah.com@vger.kernel.org Wed Jul 15 04:46:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 22:45:30 -0400
+Subject: crypto: qat - fix VF2PF work teardown race in adf_disable_sriov()
+To: stable@vger.kernel.org
+Cc: Giovanni Cabiddu <giovanni.cabiddu@intel.com>, Ahsan Atta <ahsan.atta@intel.com>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715024530.105076-1-sashal@kernel.org>
+
+From: Giovanni Cabiddu <giovanni.cabiddu@intel.com>
+
+[ Upstream commit 277281c10c63791067d24d421f7c43a15faa9096 ]
+
+The VF2PF interrupt handler queues PF-side response work that stores a
+raw pointer to per-VF state (struct adf_accel_vf_info). Currently,
+adf_disable_sriov() destroys per-VF mutexes and frees vf_info without
+stopping new VF2PF work or waiting for in-flight workers to complete. A
+concurrently scheduled or already queued worker can then dereference
+freed memory.
+
+This manifests as a use-after-free when KASAN is enabled:
+
+ BUG: KASAN: null-ptr-deref in mutex_lock+0x76/0xe0
+ Write of size 8 at addr 0000000000000260 by task kworker/24:2/...
+ Workqueue: qat_pf2vf_resp_wq adf_iov_send_resp [intel_qat]
+ Call Trace:
+ kasan_report+0x119/0x140
+ mutex_lock+0x76/0xe0
+ adf_gen4_pfvf_send+0xd4/0x1f0 [intel_qat]
+ adf_recv_and_handle_vf2pf_msg+0x290/0x360 [intel_qat]
+ adf_iov_send_resp+0x8c/0xe0 [intel_qat]
+ process_one_work+0x6ac/0xfd0
+ worker_thread+0x4dd/0xd30
+ kthread+0x326/0x410
+ ret_from_fork+0x33b/0x670
+
+Add a PF-local flag, vf2pf_disabled, that gates work queueing, worker
+processing, and interrupt re-enabling during teardown. Set this flag
+atomically with the hardware interrupt mask inside
+adf_disable_all_vf2pf_interrupts(). After masking, synchronize the AE
+cluster MSI-X interrupt and flush the PF response workqueue before
+tearing down per-VF locks and state so all in-flight work completes
+before vf_info is destroyed.
+
+Introduce adf_enable_all_vf2pf_interrupts() to clear the flag and
+unmask all VF2PF interrupts under the same lock when SR-IOV is
+re-enabled. This ensures the software flag and hardware state transition
+atomically on both the enable and disable paths.
+
+Cc: stable@vger.kernel.org
+Fixes: ed8ccaef52fa ("crypto: qat - Add support for SRIOV")
+Signed-off-by: Giovanni Cabiddu <giovanni.cabiddu@intel.com>
+Reviewed-by: Ahsan Atta <ahsan.atta@intel.com>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/qat/qat_common/adf_accel_devices.h | 2 +
+ drivers/crypto/qat/qat_common/adf_common_drv.h | 2 +
+ drivers/crypto/qat/qat_common/adf_isr.c | 39 ++++++++++++++++++++++
+ drivers/crypto/qat/qat_common/adf_sriov.c | 20 ++++++++++-
+ 4 files changed, 61 insertions(+), 2 deletions(-)
+
+--- a/drivers/crypto/qat/qat_common/adf_accel_devices.h
++++ b/drivers/crypto/qat/qat_common/adf_accel_devices.h
+@@ -284,6 +284,8 @@ struct adf_accel_dev {
+ struct {
+ /* protects VF2PF interrupts access */
+ spinlock_t vf2pf_ints_lock;
++ /* prevents VF2PF handling from racing with VF state teardown */
++ bool vf2pf_disabled;
+ /* vf_info is non-zero when SR-IOV is init'ed */
+ struct adf_accel_vf_info *vf_info;
+ } pf;
+--- a/drivers/crypto/qat/qat_common/adf_common_drv.h
++++ b/drivers/crypto/qat/qat_common/adf_common_drv.h
+@@ -124,6 +124,7 @@ void qat_asym_algs_unregister(void);
+
+ int adf_isr_resource_alloc(struct adf_accel_dev *accel_dev);
+ void adf_isr_resource_free(struct adf_accel_dev *accel_dev);
++void adf_isr_sync_ae_cluster(struct adf_accel_dev *accel_dev);
+ int adf_vf_isr_resource_alloc(struct adf_accel_dev *accel_dev);
+ void adf_vf_isr_resource_free(struct adf_accel_dev *accel_dev);
+
+@@ -196,6 +197,7 @@ void adf_misc_wq_flush(void);
+ int adf_sriov_configure(struct pci_dev *pdev, int numvfs);
+ void adf_disable_sriov(struct adf_accel_dev *accel_dev);
+ void adf_enable_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 vf_mask);
++void adf_enable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 num_vfs);
+ void adf_disable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev);
+ bool adf_recv_and_handle_pf2vf_msg(struct adf_accel_dev *accel_dev);
+ bool adf_recv_and_handle_vf2pf_msg(struct adf_accel_dev *accel_dev, u32 vf_nr);
+--- a/drivers/crypto/qat/qat_common/adf_isr.c
++++ b/drivers/crypto/qat/qat_common/adf_isr.c
+@@ -62,6 +62,23 @@ void adf_enable_vf2pf_interrupts(struct
+ unsigned long flags;
+
+ spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags);
++ if (!READ_ONCE(accel_dev->pf.vf2pf_disabled))
++ GET_PFVF_OPS(accel_dev)->enable_vf2pf_interrupts(pmisc_addr, vf_mask);
++ spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags);
++}
++
++void adf_enable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 num_vfs)
++{
++ void __iomem *pmisc_addr = adf_get_pmisc_base(accel_dev);
++ unsigned long flags;
++ u32 vf_mask;
++
++ vf_mask = BIT_ULL(num_vfs) - 1;
++ if (!vf_mask)
++ return;
++
++ spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags);
++ WRITE_ONCE(accel_dev->pf.vf2pf_disabled, false);
+ GET_PFVF_OPS(accel_dev)->enable_vf2pf_interrupts(pmisc_addr, vf_mask);
+ spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags);
+ }
+@@ -72,6 +89,7 @@ void adf_disable_all_vf2pf_interrupts(st
+ unsigned long flags;
+
+ spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags);
++ WRITE_ONCE(accel_dev->pf.vf2pf_disabled, true);
+ GET_PFVF_OPS(accel_dev)->disable_all_vf2pf_interrupts(pmisc_addr);
+ spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags);
+ }
+@@ -151,6 +169,27 @@ static irqreturn_t adf_msix_isr_ae(int i
+ return IRQ_NONE;
+ }
+
++void adf_isr_sync_ae_cluster(struct adf_accel_dev *accel_dev)
++{
++ struct adf_accel_pci *pci_dev_info = &accel_dev->accel_pci_dev;
++ struct adf_hw_device_data *hw_data = GET_HW_DATA(accel_dev);
++ u32 num_entries = pci_dev_info->msix_entries.num_entries;
++ struct adf_irq *irqs = pci_dev_info->msix_entries.irqs;
++ u32 irq_idx;
++ int irq;
++
++ if (!test_bit(ADF_STATUS_IRQ_ALLOCATED, &accel_dev->status) || !irqs)
++ return;
++
++ irq_idx = num_entries > 1 ? hw_data->num_banks : 0;
++ if (irq_idx >= num_entries || !irqs[irq_idx].enabled)
++ return;
++
++ irq = pci_irq_vector(pci_dev_info->pci_dev, hw_data->num_banks);
++ if (irq > 0)
++ synchronize_irq(irq);
++}
++
+ static void adf_free_irqs(struct adf_accel_dev *accel_dev)
+ {
+ struct adf_accel_pci *pci_dev_info = &accel_dev->accel_pci_dev;
+--- a/drivers/crypto/qat/qat_common/adf_sriov.c
++++ b/drivers/crypto/qat/qat_common/adf_sriov.c
+@@ -26,18 +26,26 @@ static void adf_iov_send_resp(struct wor
+ u32 vf_nr = vf_info->vf_nr;
+ bool ret;
+
++ if (READ_ONCE(accel_dev->pf.vf2pf_disabled))
++ goto out;
++
+ ret = adf_recv_and_handle_vf2pf_msg(accel_dev, vf_nr);
+ if (ret)
+ /* re-enable interrupt on PF from this VF */
+ adf_enable_vf2pf_interrupts(accel_dev, 1 << vf_nr);
+
++out:
+ kfree(pf2vf_resp);
+ }
+
+ void adf_schedule_vf2pf_handler(struct adf_accel_vf_info *vf_info)
+ {
++ struct adf_accel_dev *accel_dev = vf_info->accel_dev;
+ struct adf_pf2vf_resp *pf2vf_resp;
+
++ if (READ_ONCE(accel_dev->pf.vf2pf_disabled))
++ return;
++
+ pf2vf_resp = kzalloc(sizeof(*pf2vf_resp), GFP_ATOMIC);
+ if (!pf2vf_resp)
+ return;
+@@ -47,6 +55,12 @@ void adf_schedule_vf2pf_handler(struct a
+ queue_work(pf2vf_resp_wq, &pf2vf_resp->pf2vf_resp_work);
+ }
+
++static void adf_flush_pf2vf_resp_wq(void)
++{
++ if (pf2vf_resp_wq)
++ flush_workqueue(pf2vf_resp_wq);
++}
++
+ static int adf_enable_sriov(struct adf_accel_dev *accel_dev)
+ {
+ struct pci_dev *pdev = accel_to_pci_dev(accel_dev);
+@@ -73,7 +87,7 @@ static int adf_enable_sriov(struct adf_a
+ hw_data->configure_iov_threads(accel_dev, true);
+
+ /* Enable VF to PF interrupts for all VFs */
+- adf_enable_vf2pf_interrupts(accel_dev, BIT_ULL(totalvfs) - 1);
++ adf_enable_all_vf2pf_interrupts(accel_dev, totalvfs);
+
+ /*
+ * Due to the hardware design, when SR-IOV and the ring arbiter
+@@ -105,8 +119,10 @@ void adf_disable_sriov(struct adf_accel_
+ adf_pf2vf_notify_restarting(accel_dev);
+ pci_disable_sriov(accel_to_pci_dev(accel_dev));
+
+- /* Disable VF to PF interrupts */
++ /* Block VF2PF work and disable VF to PF interrupts */
+ adf_disable_all_vf2pf_interrupts(accel_dev);
++ adf_isr_sync_ae_cluster(accel_dev);
++ adf_flush_pf2vf_resp_wq();
+
+ /* Clear Valid bits in AE Thread to PCIe Function Mapping */
+ if (hw_data->configure_iov_threads)
--- /dev/null
+From stable+bounces-278235-greg=kroah.com@vger.kernel.org Mon Jul 20 22:47:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 16:44:41 -0400
+Subject: crypto: qat - validate RSA CRT component lengths
+To: stable@vger.kernel.org
+Cc: Giovanni Cabiddu <giovanni.cabiddu@intel.com>, Ahsan Atta <ahsan.atta@intel.com>, Laurent M Coquerel <laurent.m.coquerel@intel.com>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720204441.3339249-1-sashal@kernel.org>
+
+From: Giovanni Cabiddu <giovanni.cabiddu@intel.com>
+
+[ Upstream commit b3ac78756588059729b9195fcc9f4b37d54057a5 ]
+
+The generic RSA key parser (rsa_helper.c) bounds each CRT component (p,
+q, dp, dq, qinv) by the modulus size n_sz, but qat_rsa_setkey_crt()
+allocates half-size DMA buffers (key_sz / 2) and right-aligns each
+component with:
+
+ memcpy(dst + half_key_sz - len, src, len)
+
+When a CRT component is larger than half_key_sz the subtraction
+underflows and memcpy writes past the DMA buffer, causing memory
+corruption.
+
+Add a len > half_key_sz check next to the existing !len check for each
+of the five CRT components so the driver falls back to the non-CRT path
+instead of writing out of bounds.
+
+Fixes: 879f77e9071f ("crypto: qat - Add RSA CRT mode")
+Cc: stable@vger.kernel.org
+Signed-off-by: Giovanni Cabiddu <giovanni.cabiddu@intel.com>
+Reviewed-by: Ahsan Atta <ahsan.atta@intel.com>
+Reviewed-by: Laurent M Coquerel <laurent.m.coquerel@intel.com>
+Tested-by: Laurent M Coquerel <laurent.m.coquerel@intel.com>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/qat/qat_common/qat_asym_algs.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+--- a/drivers/crypto/qat/qat_common/qat_asym_algs.c
++++ b/drivers/crypto/qat/qat_common/qat_asym_algs.c
+@@ -1032,7 +1032,7 @@ static void qat_rsa_setkey_crt(struct qa
+ ptr = rsa_key->p;
+ len = rsa_key->p_sz;
+ qat_rsa_drop_leading_zeros(&ptr, &len);
+- if (!len)
++ if (!len || len > half_key_sz)
+ goto err;
+ ctx->p = dma_alloc_coherent(dev, half_key_sz, &ctx->dma_p, GFP_KERNEL);
+ if (!ctx->p)
+@@ -1043,7 +1043,7 @@ static void qat_rsa_setkey_crt(struct qa
+ ptr = rsa_key->q;
+ len = rsa_key->q_sz;
+ qat_rsa_drop_leading_zeros(&ptr, &len);
+- if (!len)
++ if (!len || len > half_key_sz)
+ goto free_p;
+ ctx->q = dma_alloc_coherent(dev, half_key_sz, &ctx->dma_q, GFP_KERNEL);
+ if (!ctx->q)
+@@ -1054,7 +1054,7 @@ static void qat_rsa_setkey_crt(struct qa
+ ptr = rsa_key->dp;
+ len = rsa_key->dp_sz;
+ qat_rsa_drop_leading_zeros(&ptr, &len);
+- if (!len)
++ if (!len || len > half_key_sz)
+ goto free_q;
+ ctx->dp = dma_alloc_coherent(dev, half_key_sz, &ctx->dma_dp,
+ GFP_KERNEL);
+@@ -1066,7 +1066,7 @@ static void qat_rsa_setkey_crt(struct qa
+ ptr = rsa_key->dq;
+ len = rsa_key->dq_sz;
+ qat_rsa_drop_leading_zeros(&ptr, &len);
+- if (!len)
++ if (!len || len > half_key_sz)
+ goto free_dp;
+ ctx->dq = dma_alloc_coherent(dev, half_key_sz, &ctx->dma_dq,
+ GFP_KERNEL);
+@@ -1078,7 +1078,7 @@ static void qat_rsa_setkey_crt(struct qa
+ ptr = rsa_key->qinv;
+ len = rsa_key->qinv_sz;
+ qat_rsa_drop_leading_zeros(&ptr, &len);
+- if (!len)
++ if (!len || len > half_key_sz)
+ goto free_dq;
+ ctx->qinv = dma_alloc_coherent(dev, half_key_sz, &ctx->dma_qinv,
+ GFP_KERNEL);
--- /dev/null
+From stable+bounces-274997-greg=kroah.com@vger.kernel.org Wed Jul 15 18:57:27 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 12:44:15 -0400
+Subject: gpio: sch: use raw_spinlock_t in the irq startup path
+To: stable@vger.kernel.org
+Cc: Runyu Xiao <runyu.xiao@seu.edu.cn>, Sebastian Andrzej Siewior <bigeasy@linutronix.de>, Andy Shevchenko <andriy.shevchenko@intel.com>, Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715164415.958948-1-sashal@kernel.org>
+
+From: Runyu Xiao <runyu.xiao@seu.edu.cn>
+
+[ Upstream commit 286533cb14a3c8a8bd39ff64ea2fc8e1aa0f638b ]
+
+sch_irq_unmask() enables the GPIO IRQ and then updates the controller
+state through sch_irq_mask_unmask(), which takes sch->lock with
+spin_lock_irqsave(). The callback can be reached from irq_startup()
+while setting up a requested IRQ. That path is not sleepable, but on
+PREEMPT_RT a regular spinlock_t becomes a sleeping lock.
+
+This issue was found by our static analysis tool and then manually
+reviewed against the current tree.
+
+The grounded PoC kept the request_threaded_irq() -> __setup_irq() ->
+irq_startup() -> sch_irq_unmask() -> sch_irq_mask_unmask() carrier and
+used the original spin_lock_irqsave(&sch->lock) edge. Lockdep reported:
+
+ BUG: sleeping function called from invalid context
+ hardirqs last disabled at ... __setup_irq.constprop.0 ... [vuln_msv]
+ sch_rt_spin_lock_irqsave+0x1c/0x30 [vuln_msv]
+ sch_irq_mask_unmask.constprop.0+0x31/0x70 [vuln_msv]
+ __setup_irq.constprop.0+0xd/0x30 [vuln_msv]
+
+Convert the SCH controller lock to raw_spinlock_t. The same lock is
+also used by the GPIO direction and value callbacks, but those critical
+sections only update MMIO-backed GPIO registers and do not contain
+sleepable operations. Keeping this register lock non-sleeping is
+therefore appropriate for the irqchip callbacks and does not change the
+GPIO-side locking contract.
+
+Fixes: 7a81638485c1 ("gpio: sch: Add edge event support")
+Cc: stable@vger.kernel.org
+Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
+Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Link: https://patch.msgid.link/20260617154035.1199948-2-runyu.xiao@seu.edu.cn
+Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
+[ adjusted context for 6.6's pre-MMIO `iobase` port I/O and void `sch_gpio_set()` (no set_rv `return 0;`) ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpio/gpio-sch.c | 32 ++++++++++++++++----------------
+ 1 file changed, 16 insertions(+), 16 deletions(-)
+
+--- a/drivers/gpio/gpio-sch.c
++++ b/drivers/gpio/gpio-sch.c
+@@ -38,7 +38,7 @@
+
+ struct sch_gpio {
+ struct gpio_chip chip;
+- spinlock_t lock;
++ raw_spinlock_t lock;
+ unsigned short iobase;
+ unsigned short resume_base;
+
+@@ -102,9 +102,9 @@ static int sch_gpio_direction_in(struct
+ struct sch_gpio *sch = gpiochip_get_data(gc);
+ unsigned long flags;
+
+- spin_lock_irqsave(&sch->lock, flags);
++ raw_spin_lock_irqsave(&sch->lock, flags);
+ sch_gpio_reg_set(sch, gpio_num, GIO, 1);
+- spin_unlock_irqrestore(&sch->lock, flags);
++ raw_spin_unlock_irqrestore(&sch->lock, flags);
+ return 0;
+ }
+
+@@ -120,9 +120,9 @@ static void sch_gpio_set(struct gpio_chi
+ struct sch_gpio *sch = gpiochip_get_data(gc);
+ unsigned long flags;
+
+- spin_lock_irqsave(&sch->lock, flags);
++ raw_spin_lock_irqsave(&sch->lock, flags);
+ sch_gpio_reg_set(sch, gpio_num, GLV, val);
+- spin_unlock_irqrestore(&sch->lock, flags);
++ raw_spin_unlock_irqrestore(&sch->lock, flags);
+ }
+
+ static int sch_gpio_direction_out(struct gpio_chip *gc, unsigned int gpio_num,
+@@ -131,9 +131,9 @@ static int sch_gpio_direction_out(struct
+ struct sch_gpio *sch = gpiochip_get_data(gc);
+ unsigned long flags;
+
+- spin_lock_irqsave(&sch->lock, flags);
++ raw_spin_lock_irqsave(&sch->lock, flags);
+ sch_gpio_reg_set(sch, gpio_num, GIO, 0);
+- spin_unlock_irqrestore(&sch->lock, flags);
++ raw_spin_unlock_irqrestore(&sch->lock, flags);
+
+ /*
+ * according to the datasheet, writing to the level register has no
+@@ -193,14 +193,14 @@ static int sch_irq_type(struct irq_data
+ return -EINVAL;
+ }
+
+- spin_lock_irqsave(&sch->lock, flags);
++ raw_spin_lock_irqsave(&sch->lock, flags);
+
+ sch_gpio_reg_set(sch, gpio_num, GTPE, rising);
+ sch_gpio_reg_set(sch, gpio_num, GTNE, falling);
+
+ irq_set_handler_locked(d, handle_edge_irq);
+
+- spin_unlock_irqrestore(&sch->lock, flags);
++ raw_spin_unlock_irqrestore(&sch->lock, flags);
+
+ return 0;
+ }
+@@ -212,9 +212,9 @@ static void sch_irq_ack(struct irq_data
+ irq_hw_number_t gpio_num = irqd_to_hwirq(d);
+ unsigned long flags;
+
+- spin_lock_irqsave(&sch->lock, flags);
++ raw_spin_lock_irqsave(&sch->lock, flags);
+ sch_gpio_reg_set(sch, gpio_num, GTS, 1);
+- spin_unlock_irqrestore(&sch->lock, flags);
++ raw_spin_unlock_irqrestore(&sch->lock, flags);
+ }
+
+ static void sch_irq_mask_unmask(struct gpio_chip *gc, irq_hw_number_t gpio_num, int val)
+@@ -222,9 +222,9 @@ static void sch_irq_mask_unmask(struct g
+ struct sch_gpio *sch = gpiochip_get_data(gc);
+ unsigned long flags;
+
+- spin_lock_irqsave(&sch->lock, flags);
++ raw_spin_lock_irqsave(&sch->lock, flags);
+ sch_gpio_reg_set(sch, gpio_num, GGPE, val);
+- spin_unlock_irqrestore(&sch->lock, flags);
++ raw_spin_unlock_irqrestore(&sch->lock, flags);
+ }
+
+ static void sch_irq_mask(struct irq_data *d)
+@@ -265,12 +265,12 @@ static u32 sch_gpio_gpe_handler(acpi_han
+ int offset;
+ u32 ret;
+
+- spin_lock_irqsave(&sch->lock, flags);
++ raw_spin_lock_irqsave(&sch->lock, flags);
+
+ core_status = inl(sch->iobase + CORE_BANK_OFFSET + GTS);
+ resume_status = inl(sch->iobase + RESUME_BANK_OFFSET + GTS);
+
+- spin_unlock_irqrestore(&sch->lock, flags);
++ raw_spin_unlock_irqrestore(&sch->lock, flags);
+
+ pending = (resume_status << sch->resume_base) | core_status;
+ for_each_set_bit(offset, &pending, sch->chip.ngpio)
+@@ -336,7 +336,7 @@ static int sch_gpio_probe(struct platfor
+ pdev->name))
+ return -EBUSY;
+
+- spin_lock_init(&sch->lock);
++ raw_spin_lock_init(&sch->lock);
+ sch->iobase = res->start;
+ sch->chip = sch_gpio_chip;
+ sch->chip.label = dev_name(&pdev->dev);
--- /dev/null
+From stable+bounces-277290-greg=kroah.com@vger.kernel.org Sat Jul 18 13:20:50 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 18 Jul 2026 07:20:38 -0400
+Subject: hfs/hfsplus: fix u32 overflow in check_and_correct_requested_length
+To: stable@vger.kernel.org
+Cc: Tristan Madani <tristan@talencesecurity.com>, syzbot+6df204b70bf3261691c5@syzkaller.appspotmail.com, syzbot+e76bf3d19b85350571ac@syzkaller.appspotmail.com, Viacheslav Dubeyko <slava@dubeyko.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260718112038.2956632-2-sashal@kernel.org>
+
+From: Tristan Madani <tristan@talencesecurity.com>
+
+[ Upstream commit 966cb76fb2857a4242cab6ea2ea17acf818a3da7 ]
+
+check_and_correct_requested_length() compares (off + len) against
+node_size using u32 arithmetic. When the caller passes a large len
+value (e.g. from an underflowed subtraction in hfs_brec_remove()),
+off + len can wrap past 2^32 and produce a small result, causing the
+bounds check to pass when it should fail.
+
+For example, with off=14 and len=0xFFFFFFF2 (underflowed from
+data_off - keyoffset - size in hfs_brec_remove), off + len wraps to 6,
+which is less than a typical node_size of 512, so the check passes and
+the subsequent memmove reads ~4GB past the node buffer.
+
+Fix this by widening the addition to u64 before comparing against
+node_size. This prevents the u32 wrap while keeping the logic
+straightforward.
+
+Reported-by: syzbot+6df204b70bf3261691c5@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=6df204b70bf3261691c5
+Tested-by: syzbot+6df204b70bf3261691c5@syzkaller.appspotmail.com
+Reported-by: syzbot+e76bf3d19b85350571ac@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=e76bf3d19b85350571ac
+Tested-by: syzbot+e76bf3d19b85350571ac@syzkaller.appspotmail.com
+Fixes: a431930c9bac ("hfs: fix slab-out-of-bounds in hfs_bnode_read()")
+Cc: stable@vger.kernel.org
+Signed-off-by: Tristan Madani <tristan@talencesecurity.com>
+Reviewed-by: Viacheslav Dubeyko <slava@dubeyko.com>
+Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
+Link: https://lore.kernel.org/r/20260505111300.3592757-2-tristmd@gmail.com
+Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/hfs/bnode.c | 2 +-
+ fs/hfsplus/hfsplus_fs.h | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+--- a/fs/hfs/bnode.c
++++ b/fs/hfs/bnode.c
+@@ -41,7 +41,7 @@ u32 check_and_correct_requested_length(s
+
+ node_size = node->tree->node_size;
+
+- if ((off + len) > node_size) {
++ if ((u64)off + len > node_size) {
+ u32 new_len = node_size - off;
+
+ pr_err("requested length has been corrected: "
+--- a/fs/hfsplus/hfsplus_fs.h
++++ b/fs/hfsplus/hfsplus_fs.h
+@@ -613,7 +613,7 @@ u32 check_and_correct_requested_length(s
+
+ node_size = node->tree->node_size;
+
+- if ((off + len) > node_size) {
++ if ((u64)off + len > node_size) {
+ u32 new_len = node_size - off;
+
+ pr_err("requested length has been corrected: "
--- /dev/null
+From stable+bounces-277291-greg=kroah.com@vger.kernel.org Sat Jul 18 13:20:50 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 18 Jul 2026 07:20:37 -0400
+Subject: hfs/hfsplus: prevent getting negative values of offset/length
+To: stable@vger.kernel.org
+Cc: Viacheslav Dubeyko <slava@dubeyko.com>, John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>, Yangtao Li <frank.li@vivo.com>, linux-fsdevel@vger.kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260718112038.2956632-1-sashal@kernel.org>
+
+From: Viacheslav Dubeyko <slava@dubeyko.com>
+
+[ Upstream commit 00c14a09a70e10ae18eb3707d0059291425c04bd ]
+
+The syzbot reported KASAN out-of-bounds issue in
+hfs_bnode_move():
+
+[ 45.588165][ T9821] hfs: dst 14, src 65536, len -65536
+[ 45.588895][ T9821] ==================================================================
+[ 45.590114][ T9821] BUG: KASAN: out-of-bounds in hfs_bnode_move+0xfd/0x140
+[ 45.591127][ T9821] Read of size 18446744073709486080 at addr ffff888035935400 by task repro/9821
+[ 45.592207][ T9821]
+[ 45.592420][ T9821] CPU: 0 UID: 0 PID: 9821 Comm: repro Not tainted 6.16.0-rc7-dirty #42 PREEMPT(full)
+[ 45.592428][ T9821] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
+[ 45.592431][ T9821] Call Trace:
+[ 45.592434][ T9821] <TASK>
+[ 45.592437][ T9821] dump_stack_lvl+0x1c1/0x2a0
+[ 45.592446][ T9821] ? __virt_addr_valid+0x1c8/0x5c0
+[ 45.592454][ T9821] ? __pfx_dump_stack_lvl+0x10/0x10
+[ 45.592461][ T9821] ? rcu_is_watching+0x15/0xb0
+[ 45.592469][ T9821] ? lock_release+0x4b/0x3e0
+[ 45.592476][ T9821] ? __virt_addr_valid+0x1c8/0x5c0
+[ 45.592483][ T9821] ? __virt_addr_valid+0x4a5/0x5c0
+[ 45.592491][ T9821] print_report+0x17e/0x7c0
+[ 45.592497][ T9821] ? __virt_addr_valid+0x1c8/0x5c0
+[ 45.592504][ T9821] ? __virt_addr_valid+0x4a5/0x5c0
+[ 45.592511][ T9821] ? __phys_addr+0xd3/0x180
+[ 45.592519][ T9821] ? hfs_bnode_move+0xfd/0x140
+[ 45.592526][ T9821] kasan_report+0x147/0x180
+[ 45.592531][ T9821] ? _printk+0xcf/0x120
+[ 45.592537][ T9821] ? hfs_bnode_move+0xfd/0x140
+[ 45.592544][ T9821] ? hfs_bnode_move+0xfd/0x140
+[ 45.592552][ T9821] kasan_check_range+0x2b0/0x2c0
+[ 45.592557][ T9821] ? hfs_bnode_move+0xfd/0x140
+[ 45.592565][ T9821] __asan_memmove+0x29/0x70
+[ 45.592572][ T9821] hfs_bnode_move+0xfd/0x140
+[ 45.592580][ T9821] hfs_brec_remove+0x473/0x560
+[ 45.592589][ T9821] hfs_cat_move+0x6fb/0x960
+[ 45.592598][ T9821] ? __pfx_hfs_cat_move+0x10/0x10
+[ 45.592607][ T9821] ? seqcount_lockdep_reader_access+0x122/0x1c0
+[ 45.592614][ T9821] ? lockdep_hardirqs_on+0x9c/0x150
+[ 45.592631][ T9821] ? __lock_acquire+0xaec/0xd80
+[ 45.592641][ T9821] hfs_rename+0x1dc/0x2d0
+[ 45.592649][ T9821] ? __pfx_hfs_rename+0x10/0x10
+[ 45.592657][ T9821] vfs_rename+0xac6/0xed0
+[ 45.592664][ T9821] ? __pfx_vfs_rename+0x10/0x10
+[ 45.592670][ T9821] ? d_alloc+0x144/0x190
+[ 45.592677][ T9821] ? bpf_lsm_path_rename+0x9/0x20
+[ 45.592683][ T9821] ? security_path_rename+0x17d/0x490
+[ 45.592691][ T9821] do_renameat2+0x890/0xc50
+[ 45.592699][ T9821] ? __pfx_do_renameat2+0x10/0x10
+[ 45.592707][ T9821] ? getname_flags+0x1e5/0x540
+[ 45.592714][ T9821] __x64_sys_rename+0x82/0x90
+[ 45.592720][ T9821] ? entry_SYSCALL_64_after_hwframe+0x77/0x7f
+[ 45.592725][ T9821] do_syscall_64+0xf3/0x3a0
+[ 45.592741][ T9821] ? exc_page_fault+0x9f/0xf0
+[ 45.592748][ T9821] entry_SYSCALL_64_after_hwframe+0x77/0x7f
+[ 45.592754][ T9821] RIP: 0033:0x7f7f73fe3fc9
+[ 45.592760][ T9821] Code: 00 c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 48
+[ 45.592765][ T9821] RSP: 002b:00007ffc7e116cf8 EFLAGS: 00000283 ORIG_RAX: 0000000000000052
+[ 45.592772][ T9821] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f7f73fe3fc9
+[ 45.592776][ T9821] RDX: 0000200000000871 RSI: 0000200000000780 RDI: 00002000000003c0
+[ 45.592781][ T9821] RBP: 00007ffc7e116d00 R08: 0000000000000000 R09: 00007ffc7e116d30
+[ 45.592784][ T9821] R10: fffffffffffffff0 R11: 0000000000000283 R12: 00005557e81f8250
+[ 45.592788][ T9821] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000
+[ 45.592795][ T9821] </TASK>
+[ 45.592797][ T9821]
+[ 45.619721][ T9821] The buggy address belongs to the physical page:
+[ 45.620300][ T9821] page: refcount:1 mapcount:1 mapping:0000000000000000 index:0x559a88174 pfn:0x35935
+[ 45.621150][ T9821] memcg:ffff88810a1d5b00
+[ 45.621531][ T9821] anon flags: 0xfff60000020838(uptodate|dirty|lru|owner_2|swapbacked|node=0|zone=1|lastcpupid=0x7ff)
+[ 45.622496][ T9821] raw: 00fff60000020838 ffffea0000d64d88 ffff888021753e10 ffff888029da0771
+[ 45.623260][ T9821] raw: 0000000559a88174 0000000000000000 0000000100000000 ffff88810a1d5b00
+[ 45.624030][ T9821] page dumped because: kasan: bad access detected
+[ 45.624602][ T9821] page_owner tracks the page as allocated
+[ 45.625115][ T9821] page last allocated via order 0, migratetype Movable, gfp_mask 0x140dca(GFP_HIGHUSER_MOVABLE|__GFP_ZERO0
+[ 45.626685][ T9821] post_alloc_hook+0x240/0x2a0
+[ 45.627127][ T9821] get_page_from_freelist+0x2101/0x21e0
+[ 45.627628][ T9821] __alloc_frozen_pages_noprof+0x274/0x380
+[ 45.628154][ T9821] alloc_pages_mpol+0x241/0x4b0
+[ 45.628593][ T9821] vma_alloc_folio_noprof+0xe4/0x210
+[ 45.629066][ T9821] folio_prealloc+0x30/0x180
+[ 45.629487][ T9821] __handle_mm_fault+0x34bd/0x5640
+[ 45.629957][ T9821] handle_mm_fault+0x40e/0x8e0
+[ 45.630392][ T9821] do_user_addr_fault+0xa81/0x1390
+[ 45.630862][ T9821] exc_page_fault+0x76/0xf0
+[ 45.631273][ T9821] asm_exc_page_fault+0x26/0x30
+[ 45.631712][ T9821] page last free pid 5269 tgid 5269 stack trace:
+[ 45.632281][ T9821] free_unref_folios+0xc73/0x14c0
+[ 45.632740][ T9821] folios_put_refs+0x55b/0x640
+[ 45.633177][ T9821] free_pages_and_swap_cache+0x26d/0x510
+[ 45.633685][ T9821] tlb_flush_mmu+0x3a0/0x680
+[ 45.634105][ T9821] tlb_finish_mmu+0xd4/0x200
+[ 45.634525][ T9821] exit_mmap+0x44c/0xb70
+[ 45.634914][ T9821] __mmput+0x118/0x420
+[ 45.635286][ T9821] exit_mm+0x1da/0x2c0
+[ 45.635659][ T9821] do_exit+0x652/0x2330
+[ 45.636039][ T9821] do_group_exit+0x21c/0x2d0
+[ 45.636457][ T9821] __x64_sys_exit_group+0x3f/0x40
+[ 45.636915][ T9821] x64_sys_call+0x21ba/0x21c0
+[ 45.637342][ T9821] do_syscall_64+0xf3/0x3a0
+[ 45.637756][ T9821] entry_SYSCALL_64_after_hwframe+0x77/0x7f
+[ 45.638290][ T9821] page has been migrated, last migrate reason: numa_misplaced
+[ 45.638956][ T9821]
+[ 45.639173][ T9821] Memory state around the buggy address:
+[ 45.639677][ T9821] ffff888035935300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+[ 45.640397][ T9821] ffff888035935380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+[ 45.641117][ T9821] >ffff888035935400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+[ 45.641837][ T9821] ^
+[ 45.642207][ T9821] ffff888035935480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+[ 45.642929][ T9821] ffff888035935500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+[ 45.643650][ T9821] ==================================================================
+
+This commit [1] fixes the issue if an offset inside of b-tree node
+or length of the request is bigger than b-tree node. However,
+this fix is still not ready for negative values
+of the offset or length. Moreover, negative values of
+the offset or length doesn't make sense for b-tree's
+operations. Because we could try to access the memory address
+outside of the beginning of memory page's addresses range.
+Also, using of negative values make logic very complicated,
+unpredictable, and we could access the wrong item(s)
+in the b-tree node.
+
+This patch changes b-tree interface by means of converting
+signed integer arguments of offset and length on u32 type.
+Such conversion has goal to prevent of using negative values
+unintentionally or by mistake in b-tree operations.
+
+[1] 'commit a431930c9bac ("hfs: fix slab-out-of-bounds in hfs_bnode_read()")'
+
+Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
+cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
+cc: Yangtao Li <frank.li@vivo.com>
+cc: linux-fsdevel@vger.kernel.org
+Link: https://lore.kernel.org/r/20251002200020.2578311-1-slava@dubeyko.com
+Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
+Stable-dep-of: 966cb76fb285 ("hfs/hfsplus: fix u32 overflow in check_and_correct_requested_length")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/hfs/bfind.c | 2 -
+ fs/hfs/bnode.c | 52 ++++++++++++++--------------
+ fs/hfs/brec.c | 2 -
+ fs/hfs/btree.c | 2 -
+ fs/hfs/btree.h | 71 +++++++++++++++++++-------------------
+ fs/hfs/hfs_fs.h | 88 ++++++++++++++++++++++++++++--------------------
+ fs/hfs/inode.c | 3 +
+ fs/hfsplus/bfind.c | 2 -
+ fs/hfsplus/bnode.c | 60 ++++++++++++++++----------------
+ fs/hfsplus/brec.c | 2 -
+ fs/hfsplus/btree.c | 2 -
+ fs/hfsplus/hfsplus_fs.h | 38 ++++++++++----------
+ 12 files changed, 171 insertions(+), 153 deletions(-)
+
+--- a/fs/hfs/bfind.c
++++ b/fs/hfs/bfind.c
+@@ -164,7 +164,7 @@ release:
+ return res;
+ }
+
+-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len)
++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len)
+ {
+ int res;
+
+--- a/fs/hfs/bnode.c
++++ b/fs/hfs/bnode.c
+@@ -16,14 +16,14 @@
+ #include "btree.h"
+
+ static inline
+-bool is_bnode_offset_valid(struct hfs_bnode *node, int off)
++bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)
+ {
+ bool is_valid = off < node->tree->node_size;
+
+ if (!is_valid) {
+ pr_err("requested invalid offset: "
+ "NODE: id %u, type %#x, height %u, "
+- "node_size %u, offset %d\n",
++ "node_size %u, offset %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off);
+ }
+@@ -32,7 +32,7 @@ bool is_bnode_offset_valid(struct hfs_bn
+ }
+
+ static inline
+-int check_and_correct_requested_length(struct hfs_bnode *node, int off, int len)
++u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
+ {
+ unsigned int node_size;
+
+@@ -42,12 +42,12 @@ int check_and_correct_requested_length(s
+ node_size = node->tree->node_size;
+
+ if ((off + len) > node_size) {
+- int new_len = (int)node_size - off;
++ u32 new_len = node_size - off;
+
+ pr_err("requested length has been corrected: "
+ "NODE: id %u, type %#x, height %u, "
+- "node_size %u, offset %d, "
+- "requested_len %d, corrected_len %d\n",
++ "node_size %u, offset %u, "
++ "requested_len %u, corrected_len %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off, len, new_len);
+
+@@ -57,12 +57,12 @@ int check_and_correct_requested_length(s
+ return len;
+ }
+
+-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len)
++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
+ {
+ struct page *page;
+- int pagenum;
+- int bytes_read;
+- int bytes_to_read;
++ u32 pagenum;
++ u32 bytes_read;
++ u32 bytes_to_read;
+
+ memset(buf, 0, len);
+
+@@ -72,7 +72,7 @@ void hfs_bnode_read(struct hfs_bnode *no
+ if (len == 0) {
+ pr_err("requested zero length: "
+ "NODE: id %u, type %#x, height %u, "
+- "node_size %u, offset %d, len %d\n",
++ "node_size %u, offset %u, len %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off, len);
+ return;
+@@ -88,7 +88,7 @@ void hfs_bnode_read(struct hfs_bnode *no
+ if (pagenum >= node->tree->pages_per_bnode)
+ break;
+ page = node->page[pagenum];
+- bytes_to_read = min_t(int, len - bytes_read, PAGE_SIZE - off);
++ bytes_to_read = min_t(u32, len - bytes_read, PAGE_SIZE - off);
+
+ memcpy_from_page(buf + bytes_read, page, off, bytes_to_read);
+
+@@ -97,7 +97,7 @@ void hfs_bnode_read(struct hfs_bnode *no
+ }
+ }
+
+-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off)
++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
+ {
+ __be16 data;
+ // optimize later...
+@@ -105,7 +105,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode
+ return be16_to_cpu(data);
+ }
+
+-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off)
++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off)
+ {
+ u8 data;
+ // optimize later...
+@@ -113,10 +113,10 @@ u8 hfs_bnode_read_u8(struct hfs_bnode *n
+ return data;
+ }
+
+-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off)
++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off)
+ {
+ struct hfs_btree *tree;
+- int key_len;
++ u32 key_len;
+
+ tree = node->tree;
+ if (node->type == HFS_NODE_LEAF ||
+@@ -127,14 +127,14 @@ void hfs_bnode_read_key(struct hfs_bnode
+
+ if (key_len > sizeof(hfs_btree_key) || key_len < 1) {
+ memset(key, 0, sizeof(hfs_btree_key));
+- pr_err("hfs: Invalid key length: %d\n", key_len);
++ pr_err("hfs: Invalid key length: %u\n", key_len);
+ return;
+ }
+
+ hfs_bnode_read(node, key, off, key_len);
+ }
+
+-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len)
++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len)
+ {
+ struct page *page;
+
+@@ -144,7 +144,7 @@ void hfs_bnode_write(struct hfs_bnode *n
+ if (len == 0) {
+ pr_err("requested zero length: "
+ "NODE: id %u, type %#x, height %u, "
+- "node_size %u, offset %d, len %d\n",
++ "node_size %u, offset %u, len %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off, len);
+ return;
+@@ -159,20 +159,20 @@ void hfs_bnode_write(struct hfs_bnode *n
+ set_page_dirty(page);
+ }
+
+-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data)
++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data)
+ {
+ __be16 v = cpu_to_be16(data);
+ // optimize later...
+ hfs_bnode_write(node, &v, off, 2);
+ }
+
+-void hfs_bnode_write_u8(struct hfs_bnode *node, int off, u8 data)
++void hfs_bnode_write_u8(struct hfs_bnode *node, u32 off, u8 data)
+ {
+ // optimize later...
+ hfs_bnode_write(node, &data, off, 1);
+ }
+
+-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len)
++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len)
+ {
+ struct page *page;
+
+@@ -182,7 +182,7 @@ void hfs_bnode_clear(struct hfs_bnode *n
+ if (len == 0) {
+ pr_err("requested zero length: "
+ "NODE: id %u, type %#x, height %u, "
+- "node_size %u, offset %d, len %d\n",
++ "node_size %u, offset %u, len %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off, len);
+ return;
+@@ -197,8 +197,8 @@ void hfs_bnode_clear(struct hfs_bnode *n
+ set_page_dirty(page);
+ }
+
+-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst,
+- struct hfs_bnode *src_node, int src, int len)
++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst,
++ struct hfs_bnode *src_node, u32 src, u32 len)
+ {
+ struct page *src_page, *dst_page;
+
+@@ -218,7 +218,7 @@ void hfs_bnode_copy(struct hfs_bnode *ds
+ set_page_dirty(dst_page);
+ }
+
+-void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len)
++void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len)
+ {
+ struct page *page;
+ void *ptr;
+--- a/fs/hfs/brec.c
++++ b/fs/hfs/brec.c
+@@ -62,7 +62,7 @@ u16 hfs_brec_keylen(struct hfs_bnode *no
+ return retval;
+ }
+
+-int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len)
++int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len)
+ {
+ struct hfs_btree *tree;
+ struct hfs_bnode *node, *new_node;
+--- a/fs/hfs/btree.c
++++ b/fs/hfs/btree.c
+@@ -224,7 +224,7 @@ static struct hfs_bnode *hfs_bmap_new_bm
+ }
+
+ /* Make sure @tree has enough space for the @rsvd_nodes */
+-int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes)
++int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes)
+ {
+ struct inode *inode = tree->inode;
+ u32 count;
+--- a/fs/hfs/btree.h
++++ b/fs/hfs/btree.h
+@@ -86,48 +86,49 @@ struct hfs_find_data {
+
+
+ /* btree.c */
+-extern struct hfs_btree *hfs_btree_open(struct super_block *, u32, btree_keycmp);
+-extern void hfs_btree_close(struct hfs_btree *);
+-extern void hfs_btree_write(struct hfs_btree *);
+-extern int hfs_bmap_reserve(struct hfs_btree *, int);
+-extern struct hfs_bnode * hfs_bmap_alloc(struct hfs_btree *);
++extern struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id,
++ btree_keycmp keycmp);
++extern void hfs_btree_close(struct hfs_btree *tree);
++extern void hfs_btree_write(struct hfs_btree *tree);
++extern int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes);
++extern struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree);
+ extern void hfs_bmap_free(struct hfs_bnode *node);
+
+ /* bnode.c */
+-extern void hfs_bnode_read(struct hfs_bnode *, void *, int, int);
+-extern u16 hfs_bnode_read_u16(struct hfs_bnode *, int);
+-extern u8 hfs_bnode_read_u8(struct hfs_bnode *, int);
+-extern void hfs_bnode_read_key(struct hfs_bnode *, void *, int);
+-extern void hfs_bnode_write(struct hfs_bnode *, void *, int, int);
+-extern void hfs_bnode_write_u16(struct hfs_bnode *, int, u16);
+-extern void hfs_bnode_write_u8(struct hfs_bnode *, int, u8);
+-extern void hfs_bnode_clear(struct hfs_bnode *, int, int);
+-extern void hfs_bnode_copy(struct hfs_bnode *, int,
+- struct hfs_bnode *, int, int);
+-extern void hfs_bnode_move(struct hfs_bnode *, int, int, int);
+-extern void hfs_bnode_dump(struct hfs_bnode *);
+-extern void hfs_bnode_unlink(struct hfs_bnode *);
+-extern struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *, u32);
+-extern struct hfs_bnode *hfs_bnode_find(struct hfs_btree *, u32);
+-extern void hfs_bnode_unhash(struct hfs_bnode *);
+-extern void hfs_bnode_free(struct hfs_bnode *);
+-extern struct hfs_bnode *hfs_bnode_create(struct hfs_btree *, u32);
+-extern void hfs_bnode_get(struct hfs_bnode *);
+-extern void hfs_bnode_put(struct hfs_bnode *);
++extern void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len);
++extern u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off);
++extern u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off);
++extern void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off);
++extern void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len);
++extern void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data);
++extern void hfs_bnode_write_u8(struct hfs_bnode *node, u32 off, u8 data);
++extern void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len);
++extern void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst,
++ struct hfs_bnode *src_node, u32 src, u32 len);
++extern void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len);
++extern void hfs_bnode_dump(struct hfs_bnode *node);
++extern void hfs_bnode_unlink(struct hfs_bnode *node);
++extern struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid);
++extern struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num);
++extern void hfs_bnode_unhash(struct hfs_bnode *node);
++extern void hfs_bnode_free(struct hfs_bnode *node);
++extern struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num);
++extern void hfs_bnode_get(struct hfs_bnode *node);
++extern void hfs_bnode_put(struct hfs_bnode *node);
+
+ /* brec.c */
+-extern u16 hfs_brec_lenoff(struct hfs_bnode *, u16, u16 *);
+-extern u16 hfs_brec_keylen(struct hfs_bnode *, u16);
+-extern int hfs_brec_insert(struct hfs_find_data *, void *, int);
+-extern int hfs_brec_remove(struct hfs_find_data *);
++extern u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off);
++extern u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec);
++extern int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len);
++extern int hfs_brec_remove(struct hfs_find_data *fd);
+
+ /* bfind.c */
+-extern int hfs_find_init(struct hfs_btree *, struct hfs_find_data *);
+-extern void hfs_find_exit(struct hfs_find_data *);
+-extern int __hfs_brec_find(struct hfs_bnode *, struct hfs_find_data *);
+-extern int hfs_brec_find(struct hfs_find_data *);
+-extern int hfs_brec_read(struct hfs_find_data *, void *, int);
+-extern int hfs_brec_goto(struct hfs_find_data *, int);
++extern int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd);
++extern void hfs_find_exit(struct hfs_find_data *fd);
++extern int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd);
++extern int hfs_brec_find(struct hfs_find_data *fd);
++extern int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len);
++extern int hfs_brec_goto(struct hfs_find_data *fd, int cnt);
+
+
+ struct hfs_bnode_desc {
+--- a/fs/hfs/hfs_fs.h
++++ b/fs/hfs/hfs_fs.h
+@@ -171,73 +171,89 @@ struct hfs_sb_info {
+ #define HFS_FLG_ALT_MDB_DIRTY 2
+
+ /* bitmap.c */
+-extern u32 hfs_vbm_search_free(struct super_block *, u32, u32 *);
+-extern int hfs_clear_vbm_bits(struct super_block *, u16, u16);
++extern u32 hfs_vbm_search_free(struct super_block *sb, u32 goal, u32 *num_bits);
++extern int hfs_clear_vbm_bits(struct super_block *sb, u16 start, u16 count);
+
+ /* catalog.c */
+-extern int hfs_cat_keycmp(const btree_key *, const btree_key *);
++extern int hfs_cat_keycmp(const btree_key *key1, const btree_key *key2);
+ struct hfs_find_data;
+-extern int hfs_cat_find_brec(struct super_block *, u32, struct hfs_find_data *);
+-extern int hfs_cat_create(u32, struct inode *, const struct qstr *, struct inode *);
+-extern int hfs_cat_delete(u32, struct inode *, const struct qstr *);
+-extern int hfs_cat_move(u32, struct inode *, const struct qstr *,
+- struct inode *, const struct qstr *);
+-extern void hfs_cat_build_key(struct super_block *, btree_key *, u32, const struct qstr *);
++extern int hfs_cat_find_brec(struct super_block *sb, u32 cnid,
++ struct hfs_find_data *fd);
++extern int hfs_cat_create(u32 cnid, struct inode *dir,
++ const struct qstr *str, struct inode *inode);
++extern int hfs_cat_delete(u32 cnid, struct inode *dir, const struct qstr *str);
++extern int hfs_cat_move(u32 cnid, struct inode *src_dir,
++ const struct qstr *src_name,
++ struct inode *dst_dir,
++ const struct qstr *dst_name);
++extern void hfs_cat_build_key(struct super_block *sb, btree_key *key,
++ u32 parent, const struct qstr *name);
+
+ /* dir.c */
+ extern const struct file_operations hfs_dir_operations;
+ extern const struct inode_operations hfs_dir_inode_operations;
+
+ /* extent.c */
+-extern int hfs_ext_keycmp(const btree_key *, const btree_key *);
+-extern int hfs_free_fork(struct super_block *, struct hfs_cat_file *, int);
+-extern int hfs_ext_write_extent(struct inode *);
+-extern int hfs_extend_file(struct inode *);
+-extern void hfs_file_truncate(struct inode *);
++extern int hfs_ext_keycmp(const btree_key *key1, const btree_key *key2);
++extern int hfs_free_fork(struct super_block *sb,
++ struct hfs_cat_file *file, int type);
++extern int hfs_ext_write_extent(struct inode *inode);
++extern int hfs_extend_file(struct inode *inode);
++extern void hfs_file_truncate(struct inode *inode);
+
+-extern int hfs_get_block(struct inode *, sector_t, struct buffer_head *, int);
++extern int hfs_get_block(struct inode *inode, sector_t block,
++ struct buffer_head *bh_result, int create);
+
+ /* inode.c */
+ extern const struct address_space_operations hfs_aops;
+ extern const struct address_space_operations hfs_btree_aops;
+
+ int hfs_write_begin(struct file *file, struct address_space *mapping,
+- loff_t pos, unsigned len, struct page **pagep, void **fsdata);
+-extern struct inode *hfs_new_inode(struct inode *, const struct qstr *, umode_t);
+-extern void hfs_inode_write_fork(struct inode *, struct hfs_extent *, __be32 *, __be32 *);
+-extern int hfs_write_inode(struct inode *, struct writeback_control *);
+-extern int hfs_inode_setattr(struct user_namespace *, struct dentry *,
+- struct iattr *);
++ loff_t pos, unsigned int len, struct page **pagep,
++ void **fsdata);
++extern struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name,
++ umode_t mode);
++extern void hfs_inode_write_fork(struct inode *inode, struct hfs_extent *ext,
++ __be32 *log_size, __be32 *phys_size);
++extern int hfs_write_inode(struct inode *inode, struct writeback_control *wbc);
++extern int hfs_inode_setattr(struct user_namespace *mnt_userns,
++ struct dentry *dentry, struct iattr *attr);
+ extern void hfs_inode_read_fork(struct inode *inode, struct hfs_extent *ext,
+- __be32 log_size, __be32 phys_size, u32 clump_size);
+-extern struct inode *hfs_iget(struct super_block *, struct hfs_cat_key *, hfs_cat_rec *);
+-extern void hfs_evict_inode(struct inode *);
+-extern void hfs_delete_inode(struct inode *);
++ __be32 __log_size, __be32 phys_size,
++ u32 clump_size);
++extern struct inode *hfs_iget(struct super_block *sb, struct hfs_cat_key *key,
++ hfs_cat_rec *rec);
++extern void hfs_evict_inode(struct inode *inode);
++extern void hfs_delete_inode(struct inode *inode);
+
+ /* attr.c */
+ extern const struct xattr_handler *hfs_xattr_handlers[];
+
+ /* mdb.c */
+-extern int hfs_mdb_get(struct super_block *);
+-extern void hfs_mdb_commit(struct super_block *);
+-extern void hfs_mdb_close(struct super_block *);
+-extern void hfs_mdb_put(struct super_block *);
++extern int hfs_mdb_get(struct super_block *sb);
++extern void hfs_mdb_commit(struct super_block *sb);
++extern void hfs_mdb_close(struct super_block *sb);
++extern void hfs_mdb_put(struct super_block *sb);
+
+ /* part_tbl.c */
+-extern int hfs_part_find(struct super_block *, sector_t *, sector_t *);
++extern int hfs_part_find(struct super_block *sb,
++ sector_t *part_start, sector_t *part_size);
+
+ /* string.c */
+ extern const struct dentry_operations hfs_dentry_operations;
+
+-extern int hfs_hash_dentry(const struct dentry *, struct qstr *);
+-extern int hfs_strcmp(const unsigned char *, unsigned int,
+- const unsigned char *, unsigned int);
++extern int hfs_hash_dentry(const struct dentry *dentry, struct qstr *this);
++extern int hfs_strcmp(const unsigned char *s1, unsigned int len1,
++ const unsigned char *s2, unsigned int len2);
+ extern int hfs_compare_dentry(const struct dentry *dentry,
+- unsigned int len, const char *str, const struct qstr *name);
++ unsigned int len, const char *str,
++ const struct qstr *name);
+
+ /* trans.c */
+-extern void hfs_asc2mac(struct super_block *, struct hfs_name *, const struct qstr *);
+-extern int hfs_mac2asc(struct super_block *, char *, const struct hfs_name *);
++extern void hfs_asc2mac(struct super_block *sb,
++ struct hfs_name *out, const struct qstr *in);
++extern int hfs_mac2asc(struct super_block *sb,
++ char *out, const struct hfs_name *in);
+
+ /* super.c */
+ extern void hfs_mark_mdb_dirty(struct super_block *sb);
+--- a/fs/hfs/inode.c
++++ b/fs/hfs/inode.c
+@@ -50,7 +50,8 @@ static void hfs_write_failed(struct addr
+ }
+
+ int hfs_write_begin(struct file *file, struct address_space *mapping,
+- loff_t pos, unsigned len, struct page **pagep, void **fsdata)
++ loff_t pos, unsigned int len, struct page **pagep,
++ void **fsdata)
+ {
+ int ret;
+
+--- a/fs/hfsplus/bfind.c
++++ b/fs/hfsplus/bfind.c
+@@ -210,7 +210,7 @@ release:
+ return res;
+ }
+
+-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len)
++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len)
+ {
+ int res;
+
+--- a/fs/hfsplus/bnode.c
++++ b/fs/hfsplus/bnode.c
+@@ -20,10 +20,10 @@
+
+
+ /* Copy a specified range of bytes from the raw data of a node */
+-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len)
++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
+ {
+ struct page **pagep;
+- int l;
++ u32 l;
+
+ memset(buf, 0, len);
+
+@@ -33,7 +33,7 @@ void hfs_bnode_read(struct hfs_bnode *no
+ if (len == 0) {
+ pr_err("requested zero length: "
+ "NODE: id %u, type %#x, height %u, "
+- "node_size %u, offset %d, len %d\n",
++ "node_size %u, offset %u, len %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off, len);
+ return;
+@@ -45,17 +45,17 @@ void hfs_bnode_read(struct hfs_bnode *no
+ pagep = node->page + (off >> PAGE_SHIFT);
+ off &= ~PAGE_MASK;
+
+- l = min_t(int, len, PAGE_SIZE - off);
++ l = min_t(u32, len, PAGE_SIZE - off);
+ memcpy_from_page(buf, *pagep, off, l);
+
+ while ((len -= l) != 0) {
+ buf += l;
+- l = min_t(int, len, PAGE_SIZE);
++ l = min_t(u32, len, PAGE_SIZE);
+ memcpy_from_page(buf, *++pagep, 0, l);
+ }
+ }
+
+-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off)
++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
+ {
+ __be16 data;
+ /* TODO: optimize later... */
+@@ -63,7 +63,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode
+ return be16_to_cpu(data);
+ }
+
+-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off)
++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off)
+ {
+ u8 data;
+ /* TODO: optimize later... */
+@@ -71,10 +71,10 @@ u8 hfs_bnode_read_u8(struct hfs_bnode *n
+ return data;
+ }
+
+-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off)
++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off)
+ {
+ struct hfs_btree *tree;
+- int key_len;
++ u32 key_len;
+
+ tree = node->tree;
+ if (node->type == HFS_NODE_LEAF ||
+@@ -86,17 +86,17 @@ void hfs_bnode_read_key(struct hfs_bnode
+
+ if (key_len > sizeof(hfsplus_btree_key) || key_len < 1) {
+ memset(key, 0, sizeof(hfsplus_btree_key));
+- pr_err("hfsplus: Invalid key length: %d\n", key_len);
++ pr_err("hfsplus: Invalid key length: %u\n", key_len);
+ return;
+ }
+
+ hfs_bnode_read(node, key, off, key_len);
+ }
+
+-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len)
++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len)
+ {
+ struct page **pagep;
+- int l;
++ u32 l;
+
+ if (!is_bnode_offset_valid(node, off))
+ return;
+@@ -104,7 +104,7 @@ void hfs_bnode_write(struct hfs_bnode *n
+ if (len == 0) {
+ pr_err("requested zero length: "
+ "NODE: id %u, type %#x, height %u, "
+- "node_size %u, offset %d, len %d\n",
++ "node_size %u, offset %u, len %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off, len);
+ return;
+@@ -116,29 +116,29 @@ void hfs_bnode_write(struct hfs_bnode *n
+ pagep = node->page + (off >> PAGE_SHIFT);
+ off &= ~PAGE_MASK;
+
+- l = min_t(int, len, PAGE_SIZE - off);
++ l = min_t(u32, len, PAGE_SIZE - off);
+ memcpy_to_page(*pagep, off, buf, l);
+ set_page_dirty(*pagep);
+
+ while ((len -= l) != 0) {
+ buf += l;
+- l = min_t(int, len, PAGE_SIZE);
++ l = min_t(u32, len, PAGE_SIZE);
+ memcpy_to_page(*++pagep, 0, buf, l);
+ set_page_dirty(*pagep);
+ }
+ }
+
+-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data)
++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data)
+ {
+ __be16 v = cpu_to_be16(data);
+ /* TODO: optimize later... */
+ hfs_bnode_write(node, &v, off, 2);
+ }
+
+-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len)
++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len)
+ {
+ struct page **pagep;
+- int l;
++ u32 l;
+
+ if (!is_bnode_offset_valid(node, off))
+ return;
+@@ -146,7 +146,7 @@ void hfs_bnode_clear(struct hfs_bnode *n
+ if (len == 0) {
+ pr_err("requested zero length: "
+ "NODE: id %u, type %#x, height %u, "
+- "node_size %u, offset %d, len %d\n",
++ "node_size %u, offset %u, len %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off, len);
+ return;
+@@ -158,22 +158,22 @@ void hfs_bnode_clear(struct hfs_bnode *n
+ pagep = node->page + (off >> PAGE_SHIFT);
+ off &= ~PAGE_MASK;
+
+- l = min_t(int, len, PAGE_SIZE - off);
++ l = min_t(u32, len, PAGE_SIZE - off);
+ memzero_page(*pagep, off, l);
+ set_page_dirty(*pagep);
+
+ while ((len -= l) != 0) {
+- l = min_t(int, len, PAGE_SIZE);
++ l = min_t(u32, len, PAGE_SIZE);
+ memzero_page(*++pagep, 0, l);
+ set_page_dirty(*pagep);
+ }
+ }
+
+-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst,
+- struct hfs_bnode *src_node, int src, int len)
++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst,
++ struct hfs_bnode *src_node, u32 src, u32 len)
+ {
+ struct page **src_page, **dst_page;
+- int l;
++ u32 l;
+
+ hfs_dbg(BNODE_MOD, "copybytes: %u,%u,%u\n", dst, src, len);
+ if (!len)
+@@ -190,12 +190,12 @@ void hfs_bnode_copy(struct hfs_bnode *ds
+ dst &= ~PAGE_MASK;
+
+ if (src == dst) {
+- l = min_t(int, len, PAGE_SIZE - src);
++ l = min_t(u32, len, PAGE_SIZE - src);
+ memcpy_page(*dst_page, src, *src_page, src, l);
+ set_page_dirty(*dst_page);
+
+ while ((len -= l) != 0) {
+- l = min_t(int, len, PAGE_SIZE);
++ l = min_t(u32, len, PAGE_SIZE);
+ memcpy_page(*++dst_page, 0, *++src_page, 0, l);
+ set_page_dirty(*dst_page);
+ }
+@@ -227,11 +227,11 @@ void hfs_bnode_copy(struct hfs_bnode *ds
+ }
+ }
+
+-void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len)
++void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len)
+ {
+ struct page **src_page, **dst_page;
+ void *src_ptr, *dst_ptr;
+- int l;
++ u32 l;
+
+ hfs_dbg(BNODE_MOD, "movebytes: %u,%u,%u\n", dst, src, len);
+ if (!len)
+@@ -301,7 +301,7 @@ void hfs_bnode_move(struct hfs_bnode *no
+ dst &= ~PAGE_MASK;
+
+ if (src == dst) {
+- l = min_t(int, len, PAGE_SIZE - src);
++ l = min_t(u32, len, PAGE_SIZE - src);
+
+ dst_ptr = kmap_local_page(*dst_page) + src;
+ src_ptr = kmap_local_page(*src_page) + src;
+@@ -311,7 +311,7 @@ void hfs_bnode_move(struct hfs_bnode *no
+ kunmap_local(dst_ptr);
+
+ while ((len -= l) != 0) {
+- l = min_t(int, len, PAGE_SIZE);
++ l = min_t(u32, len, PAGE_SIZE);
+ dst_ptr = kmap_local_page(*++dst_page);
+ src_ptr = kmap_local_page(*++src_page);
+ memmove(dst_ptr, src_ptr, l);
+--- a/fs/hfsplus/brec.c
++++ b/fs/hfsplus/brec.c
+@@ -60,7 +60,7 @@ u16 hfs_brec_keylen(struct hfs_bnode *no
+ return retval;
+ }
+
+-int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len)
++int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len)
+ {
+ struct hfs_btree *tree;
+ struct hfs_bnode *node, *new_node;
+--- a/fs/hfsplus/btree.c
++++ b/fs/hfsplus/btree.c
+@@ -344,7 +344,7 @@ static struct hfs_bnode *hfs_bmap_new_bm
+ }
+
+ /* Make sure @tree has enough space for the @rsvd_nodes */
+-int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes)
++int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes)
+ {
+ struct inode *inode = tree->inode;
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+--- a/fs/hfsplus/hfsplus_fs.h
++++ b/fs/hfsplus/hfsplus_fs.h
+@@ -388,21 +388,21 @@ u32 hfsplus_calc_btree_clump_size(u32 bl
+ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id);
+ void hfs_btree_close(struct hfs_btree *tree);
+ int hfs_btree_write(struct hfs_btree *tree);
+-int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes);
++int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes);
+ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree);
+ void hfs_bmap_free(struct hfs_bnode *node);
+
+ /* bnode.c */
+-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len);
+-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off);
+-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off);
+-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off);
+-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len);
+-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data);
+-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len);
+-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst,
+- struct hfs_bnode *src_node, int src, int len);
+-void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len);
++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len);
++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off);
++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off);
++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off);
++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len);
++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data);
++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len);
++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst,
++ struct hfs_bnode *src_node, u32 src, u32 len);
++void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len);
+ void hfs_bnode_dump(struct hfs_bnode *node);
+ void hfs_bnode_unlink(struct hfs_bnode *node);
+ struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid);
+@@ -417,7 +417,7 @@ bool hfs_bnode_need_zeroout(struct hfs_b
+ /* brec.c */
+ u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off);
+ u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec);
+-int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len);
++int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len);
+ int hfs_brec_remove(struct hfs_find_data *fd);
+
+ /* bfind.c */
+@@ -430,7 +430,7 @@ int hfs_find_rec_by_key(struct hfs_bnode
+ int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd,
+ search_strategy_t rec_found);
+ int hfs_brec_find(struct hfs_find_data *fd, search_strategy_t do_key_compare);
+-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len);
++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len);
+ int hfs_brec_goto(struct hfs_find_data *fd, int cnt);
+
+ /* catalog.c */
+@@ -588,14 +588,14 @@ hfsplus_btree_lock_class(struct hfs_btre
+ }
+
+ static inline
+-bool is_bnode_offset_valid(struct hfs_bnode *node, int off)
++bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)
+ {
+ bool is_valid = off < node->tree->node_size;
+
+ if (!is_valid) {
+ pr_err("requested invalid offset: "
+ "NODE: id %u, type %#x, height %u, "
+- "node_size %u, offset %d\n",
++ "node_size %u, offset %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off);
+ }
+@@ -604,7 +604,7 @@ bool is_bnode_offset_valid(struct hfs_bn
+ }
+
+ static inline
+-int check_and_correct_requested_length(struct hfs_bnode *node, int off, int len)
++u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
+ {
+ unsigned int node_size;
+
+@@ -614,12 +614,12 @@ int check_and_correct_requested_length(s
+ node_size = node->tree->node_size;
+
+ if ((off + len) > node_size) {
+- int new_len = (int)node_size - off;
++ u32 new_len = node_size - off;
+
+ pr_err("requested length has been corrected: "
+ "NODE: id %u, type %#x, height %u, "
+- "node_size %u, offset %d, "
+- "requested_len %d, corrected_len %d\n",
++ "node_size %u, offset %u, "
++ "requested_len %u, corrected_len %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off, len, new_len);
+
--- /dev/null
+From stable+bounces-277068-greg=kroah.com@vger.kernel.org Fri Jul 17 16:29:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:23:09 -0400
+Subject: HID: add haptics page defines
+To: stable@vger.kernel.org
+Cc: Angela Czubak <aczubak@google.com>, Jonathan Denose <jdenose@google.com>, Benjamin Tissoires <bentiss@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717142310.1824932-1-sashal@kernel.org>
+
+From: Angela Czubak <aczubak@google.com>
+
+[ Upstream commit 5e0ae59159e3a07391a35865bb79ff335473fa79 ]
+
+Introduce haptic usages as defined in HID Usage Tables specification.
+Add HID units for newton and gram.
+
+Signed-off-by: Angela Czubak <aczubak@google.com>
+Co-developed-by: Jonathan Denose <jdenose@google.com>
+Signed-off-by: Jonathan Denose <jdenose@google.com>
+Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
+Stable-dep-of: 8813b0612275 ("HID: multitouch: fix out-of-bounds bit access on mt_io_flags")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/hid.h | 29 +++++++++++++++++++++++++++++
+ 1 file changed, 29 insertions(+)
+
+--- a/include/linux/hid.h
++++ b/include/linux/hid.h
+@@ -153,6 +153,7 @@ struct hid_item {
+ #define HID_UP_TELEPHONY 0x000b0000
+ #define HID_UP_CONSUMER 0x000c0000
+ #define HID_UP_DIGITIZER 0x000d0000
++#define HID_UP_HAPTIC 0x000e0000
+ #define HID_UP_PID 0x000f0000
+ #define HID_UP_BATTERY 0x00850000
+ #define HID_UP_HPVENDOR 0xff7f0000
+@@ -311,6 +312,28 @@ struct hid_item {
+ #define HID_DG_TOOLSERIALNUMBER 0x000d005b
+ #define HID_DG_LATENCYMODE 0x000d0060
+
++#define HID_HP_SIMPLECONTROLLER 0x000e0001
++#define HID_HP_WAVEFORMLIST 0x000e0010
++#define HID_HP_DURATIONLIST 0x000e0011
++#define HID_HP_AUTOTRIGGER 0x000e0020
++#define HID_HP_MANUALTRIGGER 0x000e0021
++#define HID_HP_AUTOTRIGGERASSOCIATEDCONTROL 0x000e0022
++#define HID_HP_INTENSITY 0x000e0023
++#define HID_HP_REPEATCOUNT 0x000e0024
++#define HID_HP_RETRIGGERPERIOD 0x000e0025
++#define HID_HP_WAVEFORMVENDORPAGE 0x000e0026
++#define HID_HP_WAVEFORMVENDORID 0x000e0027
++#define HID_HP_WAVEFORMCUTOFFTIME 0x000e0028
++#define HID_HP_WAVEFORMNONE 0x000e1001
++#define HID_HP_WAVEFORMSTOP 0x000e1002
++#define HID_HP_WAVEFORMCLICK 0x000e1003
++#define HID_HP_WAVEFORMBUZZCONTINUOUS 0x000e1004
++#define HID_HP_WAVEFORMRUMBLECONTINUOUS 0x000e1005
++#define HID_HP_WAVEFORMPRESS 0x000e1006
++#define HID_HP_WAVEFORMRELEASE 0x000e1007
++#define HID_HP_VENDORWAVEFORMMIN 0x000e2001
++#define HID_HP_VENDORWAVEFORMMAX 0x000e2fff
++
+ #define HID_BAT_ABSOLUTESTATEOFCHARGE 0x00850065
+ #define HID_BAT_CHARGING 0x00850044
+
+@@ -392,6 +415,12 @@ struct hid_item {
+ #define HID_BOOT_PROTOCOL 0
+
+ /*
++ * HID units
++ */
++#define HID_UNIT_GRAM 0x0101
++#define HID_UNIT_NEWTON 0xe111
++
++/*
+ * This is the global environment of the parser. This information is
+ * persistent for main-items. The global environment can be saved and
+ * restored with PUSH/POP statements.
--- /dev/null
+From stable+bounces-277137-greg=kroah.com@vger.kernel.org Fri Jul 17 18:38:33 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 12:38:24 -0400
+Subject: HID: appleir: fix UAF on pending key_up_timer in remove()
+To: stable@vger.kernel.org
+Cc: Manish Khadka <maskmemanish@gmail.com>, Jiri Kosina <jkosina@suse.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717163824.2035351-2-sashal@kernel.org>
+
+From: Manish Khadka <maskmemanish@gmail.com>
+
+[ Upstream commit 75fe87e19d8aff81eb2c64d15d244ab8da4de945 ]
+
+appleir_remove() runs hid_hw_stop() before timer_delete_sync().
+hid_hw_stop() synchronously unregisters the HID input device via
+hid_disconnect() -> hidinput_disconnect() -> input_unregister_device(),
+which drops the last reference and frees the underlying input_dev when
+no userspace handle holds it open.
+
+key_up_tick() reads appleir->input_dev and calls input_report_key() /
+input_sync() on it. The timer is armed from appleir_raw_event() with
+a HZ/8 (~125 ms) timeout on every keydown and key-repeat report. If a
+key was pressed shortly before the device is disconnected, the timer
+can fire after hid_hw_stop() has freed input_dev but before the
+teardown drains it.
+
+A simple reorder is not sufficient. Putting the timer drain first
+still leaves a window where a USB URB completion (raw_event) running
+during hid_hw_stop() can call mod_timer() and re-arm the timer, which
+then fires after hidinput_disconnect() has freed input_dev. The same
+URB-completion window also lets raw_event() reach key_up(), key_down()
+and battery_flat() directly, all of which dereference
+appleir->input_dev.
+
+Introduce a 'removing' flag on struct appleir, gated by the existing
+spinlock. appleir_remove() sets the flag under the lock and then
+shuts down the timer with timer_shutdown_sync(), which both drains any
+in-flight callback and permanently disables further mod_timer() calls.
+appleir_raw_event() and key_up_tick() bail out early if the flag is
+set, so no path can arm or run the timer, or dereference
+appleir->input_dev, after remove() has started tearing down.
+
+The keyrepeat and flatbattery branches of appleir_raw_event()
+previously called into the input layer without holding the spinlock;
+take it now so the flag check is well-defined. This incidentally
+closes a pre-existing read-side race on appleir->current_key in the
+keyrepeat branch.
+
+This bug is structurally a sibling of commit 4db2af929279 ("HID:
+appletb-kbd: fix UAF in inactivity-timer cleanup path") and has been
+present since the driver was introduced.
+
+Fixes: 9a4a5574ce42 ("HID: appleir: add support for Apple ir devices")
+Cc: stable@vger.kernel.org
+Signed-off-by: Manish Khadka <maskmemanish@gmail.com>
+Signed-off-by: Jiri Kosina <jkosina@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/hid/hid-appleir.c | 45 +++++++++++++++++++++++++++++++++++----------
+ 1 file changed, 35 insertions(+), 10 deletions(-)
+
+--- a/drivers/hid/hid-appleir.c
++++ b/drivers/hid/hid-appleir.c
+@@ -109,9 +109,10 @@ struct appleir {
+ struct hid_device *hid;
+ unsigned short keymap[ARRAY_SIZE(appleir_key_table)];
+ struct timer_list key_up_timer; /* timer for key up */
+- spinlock_t lock; /* protects .current_key */
++ spinlock_t lock; /* protects .current_key, .removing */
+ int current_key; /* the currently pressed key */
+ int prev_key_idx; /* key index in a 2 packets message */
++ bool removing; /* set during teardown; gates input_dev access */
+ };
+
+ static int get_key(int data)
+@@ -172,7 +173,7 @@ static void key_up_tick(struct timer_lis
+ unsigned long flags;
+
+ spin_lock_irqsave(&appleir->lock, flags);
+- if (appleir->current_key) {
++ if (!appleir->removing && appleir->current_key) {
+ key_up(hid, appleir, appleir->current_key);
+ appleir->current_key = 0;
+ }
+@@ -195,6 +196,10 @@ static int appleir_raw_event(struct hid_
+ int index;
+
+ spin_lock_irqsave(&appleir->lock, flags);
++ if (appleir->removing) {
++ spin_unlock_irqrestore(&appleir->lock, flags);
++ goto out;
++ }
+ /*
+ * If we already have a key down, take it up before marking
+ * this one down
+@@ -229,17 +234,25 @@ static int appleir_raw_event(struct hid_
+ appleir->prev_key_idx = 0;
+
+ if (!memcmp(data, keyrepeat, sizeof(keyrepeat))) {
+- key_down(hid, appleir, appleir->current_key);
+- /*
+- * Remote doesn't do key up, either pull them up, in the test
+- * above, or here set a timer which pulls them up after 1/8 s
+- */
+- mod_timer(&appleir->key_up_timer, jiffies + HZ / 8);
++ spin_lock_irqsave(&appleir->lock, flags);
++ if (!appleir->removing) {
++ key_down(hid, appleir, appleir->current_key);
++ /*
++ * Remote doesn't do key up, either pull them up, in
++ * the test above, or here set a timer which pulls them
++ * up after 1/8 s
++ */
++ mod_timer(&appleir->key_up_timer, jiffies + HZ / 8);
++ }
++ spin_unlock_irqrestore(&appleir->lock, flags);
+ goto out;
+ }
+
+ if (!memcmp(data, flatbattery, sizeof(flatbattery))) {
+- battery_flat(appleir);
++ spin_lock_irqsave(&appleir->lock, flags);
++ if (!appleir->removing)
++ battery_flat(appleir);
++ spin_unlock_irqrestore(&appleir->lock, flags);
+ /* Fall through */
+ }
+
+@@ -318,8 +331,20 @@ fail:
+ static void appleir_remove(struct hid_device *hid)
+ {
+ struct appleir *appleir = hid_get_drvdata(hid);
++ unsigned long flags;
++
++ /*
++ * Mark the driver as tearing down so that any concurrent raw_event
++ * (e.g. from a USB URB completion that hid_hw_stop() has not yet
++ * killed) and the key_up_timer softirq stop touching input_dev
++ * before hid_hw_stop() frees it via hidinput_disconnect().
++ */
++ spin_lock_irqsave(&appleir->lock, flags);
++ appleir->removing = true;
++ spin_unlock_irqrestore(&appleir->lock, flags);
++
++ timer_shutdown_sync(&appleir->key_up_timer);
+ hid_hw_stop(hid);
+- timer_delete_sync(&appleir->key_up_timer);
+ }
+
+ static const struct hid_device_id appleir_devices[] = {
--- /dev/null
+From stable+bounces-277069-greg=kroah.com@vger.kernel.org Fri Jul 17 16:29:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:23:10 -0400
+Subject: HID: multitouch: fix out-of-bounds bit access on mt_io_flags
+To: stable@vger.kernel.org
+Cc: Trung Nguyen <trungnh@cystack.net>, Benjamin Tissoires <bentiss@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717142310.1824932-2-sashal@kernel.org>
+
+From: Trung Nguyen <trungnh@cystack.net>
+
+[ Upstream commit 8813b0612275cc61fe9e6603d0ee019247ade6be ]
+
+mt_io_flags is a single unsigned long, but mt_process_slot(),
+mt_release_pending_palms() and mt_release_contacts() use it as a
+per-slot bitmap indexed by the slot number. That slot number is only
+bounded by td->maxcontacts, which is taken from the device's
+ContactCountMaximum feature report and can be up to 255, not by
+BITS_PER_LONG.
+
+As a result, a multitouch device that advertises a large contact count
+makes set_bit()/clear_bit() operate past the mt_io_flags word and
+corrupt the adjacent members of struct mt_device. The sticky-fingers
+release timer is the easiest way to reach this. mt_release_contacts()
+runs
+
+ for (i = 0; i < mt->num_slots; i++)
+ clear_bit(i, &td->mt_io_flags);
+
+with num_slots == maxcontacts. For maxcontacts around 250 the loop
+clears the bits that overlap td->applications.next, zeroing that list
+head, and the list_for_each_entry() that immediately follows then
+dereferences NULL. The kernel panics from timer (softirq) context. On a
+KASAN build this shows up as a general protection fault in
+mt_release_contacts() with a null-ptr-deref at offset 0x58, which is
+offsetof(struct mt_application, num_received).
+
+The state is reachable from an untrusted USB or Bluetooth HID
+multitouch device; no local privileges are required.
+
+Store the per-slot active state in a separately allocated bitmap sized
+for maxcontacts, the same pattern already used for pending_palm_slots,
+and keep only MT_IO_FLAGS_RUNNING in mt_io_flags. The two
+"mt_io_flags & MT_IO_SLOTS_MASK" arming checks become
+bitmap_empty(td->active_slots, td->maxcontacts).
+
+Move MT_IO_FLAGS_RUNNING back to bit 0. It was bumped to bit 32 by the
+same commit to leave the low byte for the slot bits; with the slot bits
+gone it fits in bit 0 again, which also keeps it within the unsigned
+long on 32-bit.
+
+Fixes: 46f781e0d151 ("HID: multitouch: fix sticky fingers")
+Cc: stable@vger.kernel.org
+Signed-off-by: Trung Nguyen <trungnh@cystack.net>
+Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/hid/hid-multitouch.c | 32 ++++++++++++++++++++------------
+ 1 file changed, 20 insertions(+), 12 deletions(-)
+
+--- a/drivers/hid/hid-multitouch.c
++++ b/drivers/hid/hid-multitouch.c
+@@ -31,6 +31,7 @@
+ * [1] https://gitlab.freedesktop.org/libevdev/hid-tools
+ */
+
++#include <linux/bitmap.h>
+ #include <linux/device.h>
+ #include <linux/hid.h>
+ #include <linux/module.h>
+@@ -83,8 +84,7 @@ enum latency_mode {
+ HID_LATENCY_HIGH = 1,
+ };
+
+-#define MT_IO_SLOTS_MASK GENMASK(7, 0) /* reserve first 8 bits for slot tracking */
+-#define MT_IO_FLAGS_RUNNING 32
++#define MT_IO_FLAGS_RUNNING 0
+
+ static const bool mtrue = true; /* default for true */
+ static const bool mfalse; /* default for false */
+@@ -160,10 +160,9 @@ struct mt_device {
+ struct mt_class mtclass; /* our mt device class */
+ struct timer_list release_timer; /* to release sticky fingers */
+ struct hid_device *hdev; /* hid_device we're attached to */
+- unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_RUNNING)
+- * first 8 bits are reserved for keeping the slot
+- * states, this is fine because we only support up
+- * to 250 slots (MT_MAX_MAXCONTACT)
++ unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_RUNNING) */
++ unsigned long *active_slots; /* bitmap of slots with an active
++ * contact, sized for maxcontacts
+ */
+ __u8 inputmode_value; /* InputMode HID feature value */
+ __u8 maxcontacts;
+@@ -947,7 +946,7 @@ static void mt_release_pending_palms(str
+
+ for_each_set_bit(slotnum, app->pending_palm_slots, td->maxcontacts) {
+ clear_bit(slotnum, app->pending_palm_slots);
+- clear_bit(slotnum, &td->mt_io_flags);
++ clear_bit(slotnum, td->active_slots);
+
+ input_mt_slot(input, slotnum);
+ input_mt_report_slot_inactive(input);
+@@ -1153,9 +1152,9 @@ static int mt_process_slot(struct mt_dev
+ input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
+ input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
+
+- set_bit(slotnum, &td->mt_io_flags);
++ set_bit(slotnum, td->active_slots);
+ } else {
+- clear_bit(slotnum, &td->mt_io_flags);
++ clear_bit(slotnum, td->active_slots);
+ }
+
+ return 0;
+@@ -1290,7 +1289,7 @@ static void mt_touch_report(struct hid_d
+ * defect.
+ */
+ if (app->quirks & MT_QUIRK_STICKY_FINGERS) {
+- if (td->mt_io_flags & MT_IO_SLOTS_MASK)
++ if (!bitmap_empty(td->active_slots, td->maxcontacts))
+ mod_timer(&td->release_timer,
+ jiffies + msecs_to_jiffies(100));
+ else
+@@ -1330,6 +1329,15 @@ static int mt_touch_input_configured(str
+ if (td->is_buttonpad)
+ __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+
++ if (!td->active_slots) {
++ td->active_slots = devm_kcalloc(&td->hdev->dev,
++ BITS_TO_LONGS(td->maxcontacts),
++ sizeof(long),
++ GFP_KERNEL);
++ if (!td->active_slots)
++ return -ENOMEM;
++ }
++
+ app->pending_palm_slots = devm_kcalloc(&hi->input->dev,
+ BITS_TO_LONGS(td->maxcontacts),
+ sizeof(long),
+@@ -1738,7 +1746,7 @@ static void mt_release_contacts(struct h
+ for (i = 0; i < mt->num_slots; i++) {
+ input_mt_slot(input_dev, i);
+ input_mt_report_slot_inactive(input_dev);
+- clear_bit(i, &td->mt_io_flags);
++ clear_bit(i, td->active_slots);
+ }
+ input_mt_sync_frame(input_dev);
+ input_sync(input_dev);
+@@ -1761,7 +1769,7 @@ static void mt_expired_timeout(struct ti
+ */
+ if (test_and_set_bit_lock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags))
+ return;
+- if (td->mt_io_flags & MT_IO_SLOTS_MASK)
++ if (!bitmap_empty(td->active_slots, td->maxcontacts))
+ mt_release_contacts(hdev);
+ clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags);
+ }
--- /dev/null
+From stable+bounces-274063-greg=kroah.com@vger.kernel.org Tue Jul 14 03:16:47 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 21:16:31 -0400
+Subject: iio: common: st_sensors: honour channel endianness in read_axis_data
+To: stable@vger.kernel.org
+Cc: Herman van Hazendonk <github.com@herrie.org>, Andy Shevchenko <andriy.shevchenko@intel.com>, Jonathan Cameron <jic23@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714011631.2188967-2-sashal@kernel.org>
+
+From: Herman van Hazendonk <github.com@herrie.org>
+
+[ Upstream commit 55052184ac9011db2ea983e54d6c21f0b1079a12 ]
+
+st_sensors_read_axis_data() unconditionally decoded multi-byte
+results with get_unaligned_le16() / get_unaligned_le24() regardless
+of the channel's declared scan_type.endianness.
+
+For every ST sensor that has used this helper since it was introduced
+this happened to be fine because the ST IMU/accel/gyro/pressure
+families publish their data registers as little-endian and the
+channel specs in those drivers declare IIO_LE accordingly.
+
+The LSM303DLH magnetometer however publishes its X/Y/Z output as a
+pair of big-endian bytes (the H register sits at the lower address,
+0x03/0x05/0x07, and the L register immediately after), and its
+channel specs in st_magn_core.c correctly declare IIO_BE -- but
+read_axis_data() ignored that and decoded as little-endian, swapping
+the high and low bytes of every magnetometer sample. The LSM303DLHC
+and LSM303DLM share the same st_magn_16bit_channels (IIO_BE) and
+were therefore byte-swapped by the same bug; users of those parts
+will see different in_magn_*_raw values after this fix lands.
+
+The bug is most visible on a stationary chip: in earth's field the
+true X reading is small and the high byte sits at 0x00, so swapping
+the bytes pins sysfs X at exactly the low byte's pattern (e.g. 0x00F0
+= 240). Y and Z still appear "to vary" because their magnitudes are
+larger and the noise in the low byte produces big swings in the
+swapped high byte:
+
+ before (LSM303DLH flat, sysfs in_magn_*_raw):
+ X=240 (stuck), Y= 12032..23296, Z=-16128..-9728
+
+ after (direct i2c-dev big-endian decode, same chip same orientation):
+ X≈-4096, Y≈210, Z≈80 (sensible values reflecting earth's
+ ambient field at low gauss range)
+
+Fix read_axis_data() to dispatch on ch->scan_type.endianness and
+call get_unaligned_be16() / get_unaligned_be24() when the channel
+declares IIO_BE. Existing IIO_LE consumers (st_accel, st_gyro,
+st_pressure, st_lsm6dsx and others) are unaffected because their
+channel specs already declare IIO_LE and the LE path is unchanged.
+
+While restructuring the branches, replace the previously implicit
+silent-success-with-uninitialised-*data fall-through for
+byte_for_channel outside 1..3 with an explicit return -EINVAL. No
+in-tree ST sensor publishes such a channel, but the new behaviour
+is strictly safer than handing userspace garbage.
+
+Fixes: 23491b513bcd ("iio:common: Add STMicroelectronics common library")
+Cc: stable@vger.kernel.org
+Assisted-by: Claude:claude-opus-4-7 sparse smatch clang-analyzer coccinelle checkpatch
+Assisted-by: Sashiko:claude-opus-4-7
+Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Signed-off-by: Jonathan Cameron <jic23@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/common/st_sensors/st_sensors_core.c | 23 +++++++++++++++++------
+ 1 file changed, 17 insertions(+), 6 deletions(-)
+
+--- a/drivers/iio/common/st_sensors/st_sensors_core.c
++++ b/drivers/iio/common/st_sensors/st_sensors_core.c
+@@ -524,6 +524,7 @@ static int st_sensors_read_axis_data(str
+ u8 *outdata;
+ struct st_sensor_data *sdata = iio_priv(indio_dev);
+ unsigned int byte_for_channel;
++ u32 tmp;
+
+ byte_for_channel = DIV_ROUND_UP(ch->scan_type.realbits +
+ ch->scan_type.shift, 8);
+@@ -536,12 +537,22 @@ static int st_sensors_read_axis_data(str
+ if (err < 0)
+ goto st_sensors_free_memory;
+
+- if (byte_for_channel == 1)
+- *data = (s8)*outdata;
+- else if (byte_for_channel == 2)
+- *data = (s16)get_unaligned_le16(outdata);
+- else if (byte_for_channel == 3)
+- *data = (s32)sign_extend32(get_unaligned_le24(outdata), 23);
++ if (byte_for_channel == 1) {
++ tmp = *outdata;
++ } else if (byte_for_channel == 2) {
++ if (ch->scan_type.endianness == IIO_BE)
++ tmp = get_unaligned_be16(outdata);
++ else
++ tmp = get_unaligned_le16(outdata);
++ } else if (byte_for_channel == 3) {
++ if (ch->scan_type.endianness == IIO_BE)
++ tmp = get_unaligned_be24(outdata);
++ else
++ tmp = get_unaligned_le24(outdata);
++ } else {
++ return -EINVAL;
++ }
++ *data = sign_extend32(tmp, BYTES_TO_BITS(byte_for_channel) - 1);
+
+ st_sensors_free_memory:
+ kfree(outdata);
--- /dev/null
+From stable+bounces-273957-greg=kroah.com@vger.kernel.org Mon Jul 13 20:33:53 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 14:33:44 -0400
+Subject: iio: hid-sensor-rotation: Fix stale or zero output when reading raw values
+To: stable@vger.kernel.org
+Cc: Zhang Lixu <lixu.zhang@intel.com>, Andy Shevchenko <andriy.shevchenko@intel.com>, Stable@vger.kernel.org, Jonathan Cameron <jic23@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713183344.1950826-2-sashal@kernel.org>
+
+From: Zhang Lixu <lixu.zhang@intel.com>
+
+[ Upstream commit 3ce8d099e0afc5a7da75a2007a67f67c4f5a4af1 ]
+
+When reading the raw quaternion attribute (in_rot_quaternion_raw), the
+driver currently returns either all zeros (if the sensor was never enabled)
+or stale data (if the sensor was previously enabled) because it reads from
+the internal buffer without explicitly requesting a new sample from the
+sensor.
+
+To fix this, power up the sensor, call sensor_hub_input_attr_read_values()
+to issue a synchronous GET_REPORT and receive the full quaternion data
+directly into a local buffer, then decode the four components.
+
+Fixes: fc18dddc0625 ("iio: hid-sensors: Added device rotation support")
+Signed-off-by: Zhang Lixu <lixu.zhang@intel.com>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Cc: <Stable@vger.kernel.org>
+Signed-off-by: Jonathan Cameron <jic23@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/orientation/hid-sensor-rotation.c | 40 ++++++++++++++++++++++++--
+ 1 file changed, 38 insertions(+), 2 deletions(-)
+
+--- a/drivers/iio/orientation/hid-sensor-rotation.c
++++ b/drivers/iio/orientation/hid-sensor-rotation.c
+@@ -69,6 +69,13 @@ static int dev_rot_read_raw(struct iio_d
+ long mask)
+ {
+ struct dev_rot_state *rot_state = iio_priv(indio_dev);
++ struct hid_sensor_hub_device *hsdev = rot_state->common_attributes.hsdev;
++ struct hid_sensor_hub_attribute_info *info = &rot_state->quaternion;
++ u32 usage_id = HID_USAGE_SENSOR_ORIENT_QUATERNION;
++ union {
++ s16 val16[4];
++ s32 val32[4];
++ } raw_buf;
+ int ret_type;
+ int i;
+
+@@ -78,8 +85,37 @@ static int dev_rot_read_raw(struct iio_d
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (size >= 4) {
+- for (i = 0; i < 4; ++i)
+- vals[i] = rot_state->scan.sampled_vals[i];
++ if (info->size <= 0 || info->size > sizeof(raw_buf))
++ return -EINVAL;
++
++ hid_sensor_power_state(&rot_state->common_attributes, true);
++
++ ret_type = sensor_hub_input_attr_read_values(hsdev,
++ hsdev->usage,
++ usage_id,
++ info->report_id,
++ SENSOR_HUB_SYNC,
++ info->size,
++ (u8 *)&raw_buf);
++
++ hid_sensor_power_state(&rot_state->common_attributes, false);
++
++ if (ret_type < 0)
++ return ret_type;
++
++ switch (info->size) {
++ case sizeof(raw_buf.val16):
++ for (i = 0; i < ARRAY_SIZE(raw_buf.val16); i++)
++ vals[i] = raw_buf.val16[i];
++ break;
++ case sizeof(raw_buf.val32):
++ for (i = 0; i < ARRAY_SIZE(raw_buf.val32); i++)
++ vals[i] = raw_buf.val32[i];
++ break;
++ default:
++ return -EINVAL;
++ }
++
+ ret_type = IIO_VAL_INT_MULTIPLE;
+ *val_len = 4;
+ } else
--- /dev/null
+From stable+bounces-273938-greg=kroah.com@vger.kernel.org Mon Jul 13 20:06:51 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 14:03:29 -0400
+Subject: iio: imu: adis: add IRQF_NO_THREAD to non-FIFO trigger IRQ
+To: stable@vger.kernel.org
+Cc: Runyu Xiao <runyu.xiao@seu.edu.cn>, Jonathan Cameron <jic23@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713180329.1923595-1-sashal@kernel.org>
+
+From: Runyu Xiao <runyu.xiao@seu.edu.cn>
+
+[ Upstream commit 6e1b9bff1202da55c464e36bd34a2b6863d7fe30 ]
+
+devm_adis_probe_trigger() registers iio_trigger_generic_data_rdy_poll()
+through devm_request_irq() on the non-FIFO path, but it does not add
+IRQF_NO_THREAD to the IRQ flags.
+
+When the kernel is booted with forced IRQ threading, the parent IRQ can
+otherwise be threaded by the IRQ core and the subsequent IIO trigger
+child IRQ is then dispatched from irq/... thread context instead of
+hardirq context. Because iio_trigger_generic_data_rdy_poll()
+immediately drives iio_trigger_poll(), this violates the hardirq-only
+IIO trigger helper contract and can push downstream trigger consumers
+through the wrong execution context.
+
+Add IRQF_NO_THREAD on top of the existing adis->irq_flag value for the
+non-FIFO request_irq() path, while preserving the current trigger
+polarity and IRQF_NO_AUTOEN behavior.
+
+Fixes: fec86c6b8369 ("iio: imu: adis: Add Managed device functions")
+Cc: stable@vger.kernel.org
+Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
+Signed-off-by: Jonathan Cameron <jic23@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/imu/adis_trigger.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/iio/imu/adis_trigger.c
++++ b/drivers/iio/imu/adis_trigger.c
+@@ -79,7 +79,7 @@ int devm_adis_probe_trigger(struct adis
+
+ ret = devm_request_irq(&adis->spi->dev, adis->spi->irq,
+ &iio_trigger_generic_data_rdy_poll,
+- adis->irq_flag,
++ adis->irq_flag | IRQF_NO_THREAD,
+ indio_dev->name,
+ adis->trig);
+ if (ret)
--- /dev/null
+From stable+bounces-274010-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:56 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:39:42 -0400
+Subject: iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading
+To: stable@vger.kernel.org
+Cc: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>, Jonathan Cameron <jic23@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713203942.2144352-8-sashal@kernel.org>
+
+From: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+
+[ Upstream commit affe3f077d7a4eeb25937f5323ff059a54b4712c ]
+
+Timestamps are made by measuring the chip clock using the watermark
+interrupts. If we read more than watermark samples as done today, we
+are reducing the period between interrupts and distort the time
+measurement. Fix that by reading only watermark samples in the
+interrupt case.
+
+Fixes: 7f85e42a6c54 ("iio: imu: inv_icm42600: add buffer support in iio devices")
+Cc: stable@vger.kernel.org
+Signed-off-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+Signed-off-by: Jonathan Cameron <jic23@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 9 +++++----
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h | 1 +
+ 2 files changed, 6 insertions(+), 4 deletions(-)
+
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
+@@ -257,6 +257,7 @@ int inv_icm42600_buffer_update_watermark
+
+ /* compute watermark value in bytes */
+ wm_size = watermark * packet_size;
++ st->fifo.watermark.value = watermark;
+
+ /* changing FIFO watermark requires to turn off watermark interrupt */
+ ret = regmap_update_bits_check(st->map, INV_ICM42600_REG_INT_SOURCE0,
+@@ -476,11 +477,10 @@ int inv_icm42600_buffer_fifo_read(struct
+ st->fifo.nb.accel = 0;
+ st->fifo.nb.total = 0;
+
+- /* compute maximum FIFO read size */
++ /* compute maximum FIFO read size (watermark for max = 0 interrupt case) */
+ if (max == 0)
+- max_count = sizeof(st->fifo.data);
+- else
+- max_count = max * inv_icm42600_get_packet_size(st->fifo.en);
++ max = st->fifo.watermark.value;
++ max_count = max * inv_icm42600_get_packet_size(st->fifo.en);
+
+ /* read FIFO count value */
+ raw_fifo_count = (__be16 *)st->buffer;
+@@ -592,6 +592,7 @@ int inv_icm42600_buffer_init(struct inv_
+
+ st->fifo.watermark.eff_gyro = 1;
+ st->fifo.watermark.eff_accel = 1;
++ st->fifo.watermark.value = 1;
+
+ /*
+ * Default FIFO configuration (bits 7 to 5)
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h
+@@ -34,6 +34,7 @@ struct inv_icm42600_fifo {
+ unsigned int accel;
+ unsigned int eff_gyro;
+ unsigned int eff_accel;
++ unsigned int value;
+ } watermark;
+ size_t count;
+ struct {
--- /dev/null
+From stable+bounces-274003-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:52 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:39:35 -0400
+Subject: iio: imu: inv_icm42600: make timestamp module chip independent
+To: stable@vger.kernel.org
+Cc: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>, Andy Shevchenko <andy.shevchenko@gmail.com>, Jonathan Cameron <Jonathan.Cameron@huawei.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713203942.2144352-1-sashal@kernel.org>
+
+From: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+
+[ Upstream commit 6e9f2d8375cb24ba75e02e0272e9164d06a1522e ]
+
+Move icm42600 dependent function inside the core module.
+Do some headers cleanup at the same time.
+
+Signed-off-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Link: https://lore.kernel.org/r/20230606162147.79667-2-inv.git-commit@tdk.com
+Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 11 +++++++++++
+ drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c | 14 +-------------
+ drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h | 6 ------
+ 3 files changed, 12 insertions(+), 19 deletions(-)
+
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
+@@ -522,6 +522,17 @@ static int inv_icm42600_irq_init(struct
+ "inv_icm42600", st);
+ }
+
++static int inv_icm42600_timestamp_setup(struct inv_icm42600_state *st)
++{
++ unsigned int val;
++
++ /* enable timestamp register */
++ val = INV_ICM42600_TMST_CONFIG_TMST_TO_REGS_EN |
++ INV_ICM42600_TMST_CONFIG_TMST_EN;
++ return regmap_update_bits(st->map, INV_ICM42600_REG_TMST_CONFIG,
++ INV_ICM42600_TMST_CONFIG_MASK, val);
++}
++
+ static int inv_icm42600_enable_regulator_vddio(struct inv_icm42600_state *st)
+ {
+ int ret;
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c
+@@ -3,11 +3,10 @@
+ * Copyright (C) 2020 Invensense, Inc.
+ */
+
++#include <linux/errno.h>
+ #include <linux/kernel.h>
+-#include <linux/regmap.h>
+ #include <linux/math64.h>
+
+-#include "inv_icm42600.h"
+ #include "inv_icm42600_timestamp.h"
+
+ /* internal chip period is 32kHz, 31250ns */
+@@ -56,17 +55,6 @@ void inv_icm42600_timestamp_init(struct
+ inv_update_acc(&ts->chip_period, INV_ICM42600_TIMESTAMP_PERIOD);
+ }
+
+-int inv_icm42600_timestamp_setup(struct inv_icm42600_state *st)
+-{
+- unsigned int val;
+-
+- /* enable timestamp register */
+- val = INV_ICM42600_TMST_CONFIG_TMST_TO_REGS_EN |
+- INV_ICM42600_TMST_CONFIG_TMST_EN;
+- return regmap_update_bits(st->map, INV_ICM42600_REG_TMST_CONFIG,
+- INV_ICM42600_TMST_CONFIG_MASK, val);
+-}
+-
+ int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts,
+ uint32_t period, bool fifo)
+ {
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h
+@@ -6,10 +6,6 @@
+ #ifndef INV_ICM42600_TIMESTAMP_H_
+ #define INV_ICM42600_TIMESTAMP_H_
+
+-#include <linux/kernel.h>
+-
+-struct inv_icm42600_state;
+-
+ /**
+ * struct inv_icm42600_timestamp_interval - timestamps interval
+ * @lo: interval lower bound
+@@ -53,8 +49,6 @@ struct inv_icm42600_timestamp {
+ void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts,
+ uint32_t period);
+
+-int inv_icm42600_timestamp_setup(struct inv_icm42600_state *st);
+-
+ int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts,
+ uint32_t period, bool fifo);
+
--- /dev/null
+From stable+bounces-274009-greg=kroah.com@vger.kernel.org Mon Jul 13 22:40:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:39:41 -0400
+Subject: iio: imu: inv_icm42600: stabilized timestamp in interrupt
+To: stable@vger.kernel.org
+Cc: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>, Jonathan Cameron <Jonathan.Cameron@huawei.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713203942.2144352-7-sashal@kernel.org>
+
+From: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+
+[ Upstream commit d7bd473632d07f8a54655c270c0940cc3671c548 ]
+
+Use IRQF_ONESHOT flag to ensure the timestamp is not updated in the
+hard handler during the thread handler. And compute and use the
+effective watermark value that correspond to this first timestamp.
+
+This way we can ensure the timestamp is always corresponding to the
+value used by the timestamping mechanism. Otherwise, it is possible
+that between FIFO count read and FIFO processing the timestamp is
+overwritten in the hard handler.
+
+Fixes: ec74ae9fd37c ("iio: imu: inv_icm42600: add accurate timestamping")
+Cc: stable@vger.kernel.org
+Signed-off-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+Link: https://lore.kernel.org/r/20240529154717.651863-1-inv.git-commit@tdk.com
+Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 19 +++++++++++++++++--
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h | 2 ++
+ drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 1 +
+ 3 files changed, 20 insertions(+), 2 deletions(-)
+
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
+@@ -222,10 +222,15 @@ int inv_icm42600_buffer_update_watermark
+ latency_accel = period_accel * wm_accel;
+
+ /* 0 value for watermark means that the sensor is turned off */
++ if (wm_gyro == 0 && wm_accel == 0)
++ return 0;
++
+ if (latency_gyro == 0) {
+ watermark = wm_accel;
++ st->fifo.watermark.eff_accel = wm_accel;
+ } else if (latency_accel == 0) {
+ watermark = wm_gyro;
++ st->fifo.watermark.eff_gyro = wm_gyro;
+ } else {
+ /* compute the smallest latency that is a multiple of both */
+ if (latency_gyro <= latency_accel)
+@@ -241,6 +246,13 @@ int inv_icm42600_buffer_update_watermark
+ watermark = latency / period;
+ if (watermark < 1)
+ watermark = 1;
++ /* update effective watermark */
++ st->fifo.watermark.eff_gyro = latency / period_gyro;
++ if (st->fifo.watermark.eff_gyro < 1)
++ st->fifo.watermark.eff_gyro = 1;
++ st->fifo.watermark.eff_accel = latency / period_accel;
++ if (st->fifo.watermark.eff_accel < 1)
++ st->fifo.watermark.eff_accel = 1;
+ }
+
+ /* compute watermark value in bytes */
+@@ -517,7 +529,7 @@ int inv_icm42600_buffer_fifo_parse(struc
+ /* handle gyroscope timestamp and FIFO data parsing */
+ if (st->fifo.nb.gyro > 0) {
+ ts = iio_priv(st->indio_gyro);
+- inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro,
++ inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_gyro,
+ st->timestamp.gyro);
+ ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro);
+ if (ret)
+@@ -527,7 +539,7 @@ int inv_icm42600_buffer_fifo_parse(struc
+ /* handle accelerometer timestamp and FIFO data parsing */
+ if (st->fifo.nb.accel > 0) {
+ ts = iio_priv(st->indio_accel);
+- inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel,
++ inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_accel,
+ st->timestamp.accel);
+ ret = inv_icm42600_accel_parse_fifo(st->indio_accel);
+ if (ret)
+@@ -578,6 +590,9 @@ int inv_icm42600_buffer_init(struct inv_
+ unsigned int val;
+ int ret;
+
++ st->fifo.watermark.eff_gyro = 1;
++ st->fifo.watermark.eff_accel = 1;
++
+ /*
+ * Default FIFO configuration (bits 7 to 5)
+ * - use invalid value
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h
+@@ -32,6 +32,8 @@ struct inv_icm42600_fifo {
+ struct {
+ unsigned int gyro;
+ unsigned int accel;
++ unsigned int eff_gyro;
++ unsigned int eff_accel;
+ } watermark;
+ size_t count;
+ struct {
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
+@@ -518,6 +518,7 @@ static int inv_icm42600_irq_init(struct
+ if (ret)
+ return ret;
+
++ irq_type |= IRQF_ONESHOT;
+ return devm_request_threaded_irq(dev, irq, inv_icm42600_irq_timestamp,
+ inv_icm42600_irq_handler, irq_type,
+ "inv_icm42600", st);
--- /dev/null
+From stable+bounces-274005-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:53 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:39:38 -0400
+Subject: iio: imu: inv_mpu6050: use the common inv_sensors timestamp module
+To: stable@vger.kernel.org
+Cc: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>, Andy Shevchenko <andy.shevchenko@gmail.com>, Jonathan Cameron <Jonathan.Cameron@huawei.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713203942.2144352-4-sashal@kernel.org>
+
+From: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+
+[ Upstream commit 111e1abd00455971f6a568900f033cbddec9d4e5 ]
+
+Replace timestamping by the new common inv_sensors timestamp
+module. The principle behind is the same but the implementation in
+the new module is far better providing less jitter and a better
+estimation.
+
+Signed-off-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Link: https://lore.kernel.org/r/20230606162147.79667-5-inv.git-commit@tdk.com
+Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/imu/inv_mpu6050/Kconfig | 1
+ drivers/iio/imu/inv_mpu6050/inv_mpu_core.c | 30 +++++++--
+ drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h | 18 ++---
+ drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c | 83 ++------------------------
+ drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c | 6 +
+ 5 files changed, 45 insertions(+), 93 deletions(-)
+
+--- a/drivers/iio/imu/inv_mpu6050/Kconfig
++++ b/drivers/iio/imu/inv_mpu6050/Kconfig
+@@ -7,6 +7,7 @@ config INV_MPU6050_IIO
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
++ select IIO_INV_SENSORS_TIMESTAMP
+
+ config INV_MPU6050_I2C
+ tristate "Invensense MPU6050 devices (I2C)"
+--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
++++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
+@@ -12,12 +12,15 @@
+ #include <linux/jiffies.h>
+ #include <linux/irq.h>
+ #include <linux/interrupt.h>
+-#include <linux/iio/iio.h>
+ #include <linux/acpi.h>
+ #include <linux/platform_device.h>
+ #include <linux/regulator/consumer.h>
+ #include <linux/pm.h>
+ #include <linux/pm_runtime.h>
++
++#include <linux/iio/common/inv_sensors_timestamp.h>
++#include <linux/iio/iio.h>
++
+ #include "inv_mpu_iio.h"
+ #include "inv_mpu_magn.h"
+
+@@ -512,6 +515,7 @@ static int inv_mpu6050_init_config(struc
+ int result;
+ u8 d;
+ struct inv_mpu6050_state *st = iio_priv(indio_dev);
++ struct inv_sensors_timestamp_chip timestamp;
+
+ result = inv_mpu6050_set_gyro_fsr(st, st->chip_config.fsr);
+ if (result)
+@@ -535,12 +539,12 @@ static int inv_mpu6050_init_config(struc
+ if (result)
+ return result;
+
+- /*
+- * Internal chip period is 1ms (1kHz).
+- * Let's use at the beginning the theorical value before measuring
+- * with interrupt timestamps.
+- */
+- st->chip_period = NSEC_PER_MSEC;
++ /* clock jitter is +/- 2% */
++ timestamp.clock_period = NSEC_PER_SEC / INV_MPU6050_INTERNAL_FREQ_HZ;
++ timestamp.jitter = 20;
++ timestamp.init_period =
++ NSEC_PER_SEC / INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider);
++ inv_sensors_timestamp_init(&st->timestamp, ×tamp);
+
+ /* magn chip init, noop if not present in the chip */
+ result = inv_mpu_magn_probe(st);
+@@ -927,6 +931,8 @@ inv_mpu6050_fifo_rate_store(struct devic
+ const char *buf, size_t count)
+ {
+ int fifo_rate;
++ u32 fifo_period;
++ bool fifo_on;
+ u8 d;
+ int result;
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+@@ -943,12 +949,21 @@ inv_mpu6050_fifo_rate_store(struct devic
+ d = INV_MPU6050_FIFO_RATE_TO_DIVIDER(fifo_rate);
+ /* compute back the fifo rate to handle truncation cases */
+ fifo_rate = INV_MPU6050_DIVIDER_TO_FIFO_RATE(d);
++ fifo_period = NSEC_PER_SEC / fifo_rate;
+
+ mutex_lock(&st->lock);
+ if (d == st->chip_config.divider) {
+ result = 0;
+ goto fifo_rate_fail_unlock;
+ }
++
++ fifo_on = st->chip_config.accl_fifo_enable ||
++ st->chip_config.gyro_fifo_enable ||
++ st->chip_config.magn_fifo_enable;
++ result = inv_sensors_timestamp_update_odr(&st->timestamp, fifo_period, fifo_on);
++ if (result)
++ goto fifo_rate_fail_unlock;
++
+ result = pm_runtime_resume_and_get(pdev);
+ if (result)
+ goto fifo_rate_fail_unlock;
+@@ -1776,3 +1791,4 @@ EXPORT_SYMBOL_GPL(inv_mpu_pmops);
+ MODULE_AUTHOR("Invensense Corporation");
+ MODULE_DESCRIPTION("Invensense device MPU6050 driver");
+ MODULE_LICENSE("GPL");
++MODULE_IMPORT_NS(IIO_INV_SENSORS_TIMESTAMP);
+--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
++++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
+@@ -9,15 +9,17 @@
+ #include <linux/i2c.h>
+ #include <linux/i2c-mux.h>
+ #include <linux/mutex.h>
+-#include <linux/iio/iio.h>
+-#include <linux/iio/buffer.h>
++#include <linux/platform_data/invensense_mpu6050.h>
+ #include <linux/regmap.h>
+-#include <linux/iio/sysfs.h>
++
++#include <linux/iio/buffer.h>
++#include <linux/iio/common/inv_sensors_timestamp.h>
++#include <linux/iio/iio.h>
+ #include <linux/iio/kfifo_buf.h>
+ #include <linux/iio/trigger.h>
+ #include <linux/iio/triggered_buffer.h>
+ #include <linux/iio/trigger_consumer.h>
+-#include <linux/platform_data/invensense_mpu6050.h>
++#include <linux/iio/sysfs.h>
+
+ /**
+ * struct inv_mpu6050_reg_map - Notable registers.
+@@ -169,9 +171,7 @@ struct inv_mpu6050_hw {
+ * @map regmap pointer.
+ * @irq interrupt number.
+ * @irq_mask the int_pin_cfg mask to configure interrupt type.
+- * @chip_period: chip internal period estimation (~1kHz).
+- * @it_timestamp: timestamp from previous interrupt.
+- * @data_timestamp: timestamp for next data sample.
++ * @timestamp: timestamping module
+ * @vdd_supply: VDD voltage regulator for the chip.
+ * @vddio_supply I/O voltage regulator for the chip.
+ * @magn_disabled: magnetometer disabled for backward compatibility reason.
+@@ -195,9 +195,7 @@ struct inv_mpu6050_state {
+ int irq;
+ u8 irq_mask;
+ unsigned skip_samples;
+- s64 chip_period;
+- s64 it_timestamp;
+- s64 data_timestamp;
++ struct inv_sensors_timestamp timestamp;
+ struct regulator *vdd_supply;
+ struct regulator *vddio_supply;
+ bool magn_disabled;
+--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c
++++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c
+@@ -13,81 +13,10 @@
+ #include <linux/interrupt.h>
+ #include <linux/poll.h>
+ #include <linux/math64.h>
+-#include "inv_mpu_iio.h"
+-
+-/**
+- * inv_mpu6050_update_period() - Update chip internal period estimation
+- *
+- * @st: driver state
+- * @timestamp: the interrupt timestamp
+- * @nb: number of data set in the fifo
+- *
+- * This function uses interrupt timestamps to estimate the chip period and
+- * to choose the data timestamp to come.
+- */
+-static void inv_mpu6050_update_period(struct inv_mpu6050_state *st,
+- s64 timestamp, size_t nb)
+-{
+- /* Period boundaries for accepting timestamp */
+- const s64 period_min =
+- (NSEC_PER_MSEC * (100 - INV_MPU6050_TS_PERIOD_JITTER)) / 100;
+- const s64 period_max =
+- (NSEC_PER_MSEC * (100 + INV_MPU6050_TS_PERIOD_JITTER)) / 100;
+- const s32 divider = INV_MPU6050_FREQ_DIVIDER(st);
+- s64 delta, interval;
+- bool use_it_timestamp = false;
+-
+- if (st->it_timestamp == 0) {
+- /* not initialized, forced to use it_timestamp */
+- use_it_timestamp = true;
+- } else if (nb == 1) {
+- /*
+- * Validate the use of it timestamp by checking if interrupt
+- * has been delayed.
+- * nb > 1 means interrupt was delayed for more than 1 sample,
+- * so it's obviously not good.
+- * Compute the chip period between 2 interrupts for validating.
+- */
+- delta = div_s64(timestamp - st->it_timestamp, divider);
+- if (delta > period_min && delta < period_max) {
+- /* update chip period and use it timestamp */
+- st->chip_period = (st->chip_period + delta) / 2;
+- use_it_timestamp = true;
+- }
+- }
+
+- if (use_it_timestamp) {
+- /*
+- * Manage case of multiple samples in the fifo (nb > 1):
+- * compute timestamp corresponding to the first sample using
+- * estimated chip period.
+- */
+- interval = (nb - 1) * st->chip_period * divider;
+- st->data_timestamp = timestamp - interval;
+- }
++#include <linux/iio/common/inv_sensors_timestamp.h>
+
+- /* save it timestamp */
+- st->it_timestamp = timestamp;
+-}
+-
+-/**
+- * inv_mpu6050_get_timestamp() - Return the current data timestamp
+- *
+- * @st: driver state
+- * @return: current data timestamp
+- *
+- * This function returns the current data timestamp and prepares for next one.
+- */
+-static s64 inv_mpu6050_get_timestamp(struct inv_mpu6050_state *st)
+-{
+- s64 ts;
+-
+- /* return current data timestamp and increment */
+- ts = st->data_timestamp;
+- st->data_timestamp += st->chip_period * INV_MPU6050_FREQ_DIVIDER(st);
+-
+- return ts;
+-}
++#include "inv_mpu_iio.h"
+
+ static int inv_reset_fifo(struct iio_dev *indio_dev)
+ {
+@@ -121,6 +50,7 @@ irqreturn_t inv_mpu6050_read_fifo(int ir
+ size_t bytes_per_datum;
+ int result;
+ u16 fifo_count;
++ u32 fifo_period;
+ s64 timestamp;
+ int int_status;
+ size_t i, nb;
+@@ -177,7 +107,10 @@ irqreturn_t inv_mpu6050_read_fifo(int ir
+
+ /* compute and process all complete datum */
+ nb = fifo_count / bytes_per_datum;
+- inv_mpu6050_update_period(st, pf->timestamp, nb);
++ /* Each FIFO data contains all sensors, so same number for FIFO and sensor data */
++ fifo_period = NSEC_PER_SEC / INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider);
++ inv_sensors_timestamp_interrupt(&st->timestamp, fifo_period, nb, nb, pf->timestamp);
++ inv_sensors_timestamp_apply_odr(&st->timestamp, fifo_period, nb, 0);
+ for (i = 0; i < nb; ++i) {
+ result = regmap_noinc_read(st->map, st->reg->fifo_r_w,
+ st->data, bytes_per_datum);
+@@ -188,7 +121,7 @@ irqreturn_t inv_mpu6050_read_fifo(int ir
+ st->skip_samples--;
+ continue;
+ }
+- timestamp = inv_mpu6050_get_timestamp(st);
++ timestamp = inv_sensors_timestamp_pop(&st->timestamp);
+ iio_push_to_buffers_with_timestamp(indio_dev, st->data, timestamp);
+ }
+
+--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c
++++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c
+@@ -4,6 +4,9 @@
+ */
+
+ #include <linux/pm_runtime.h>
++
++#include <linux/iio/common/inv_sensors_timestamp.h>
++
+ #include "inv_mpu_iio.h"
+
+ static unsigned int inv_scan_query_mpu6050(struct iio_dev *indio_dev)
+@@ -106,7 +109,8 @@ int inv_mpu6050_prepare_fifo(struct inv_
+ int ret;
+
+ if (enable) {
+- st->it_timestamp = 0;
++ /* reset timestamping */
++ inv_sensors_timestamp_reset(&st->timestamp);
+ /* reset FIFO */
+ d = st->chip_config.user_ctrl | INV_MPU6050_BIT_FIFO_RST;
+ ret = regmap_write(st->map, st->reg->user_ctrl, d);
--- /dev/null
+From stable+bounces-274008-greg=kroah.com@vger.kernel.org Mon Jul 13 22:40:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:39:40 -0400
+Subject: iio: invensense: fix timestamp glitches when switching frequency
+To: stable@vger.kernel.org
+Cc: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>, Stable@vger.kernel.org, Jonathan Cameron <Jonathan.Cameron@huawei.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713203942.2144352-6-sashal@kernel.org>
+
+From: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+
+[ Upstream commit bf8367b00c33c64a9391c262bb2e11d274c9f2a4 ]
+
+When a sensor is running and there is a FIFO frequency change due to
+another sensor turned on/off, there are glitches on timestamp. Fix that
+by using only interrupt timestamp when there is the corresponding sensor
+data in the FIFO.
+
+Delete FIFO period handling and simplify internal functions.
+
+Update integration inside inv_mpu6050 and inv_icm42600 drivers.
+
+Fixes: 0ecc363ccea7 ("iio: make invensense timestamp module generic")
+Cc: Stable@vger.kernel.org
+Signed-off-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+Link: https://lore.kernel.org/r/20240426094835.138389-1-inv.git-commit@tdk.com
+Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/common/inv_sensors/inv_sensors_timestamp.c | 30 +++++++----------
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 20 ++++-------
+ drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c | 2 -
+ include/linux/iio/common/inv_sensors_timestamp.h | 3 -
+ 4 files changed, 23 insertions(+), 32 deletions(-)
+
+--- a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c
++++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c
+@@ -70,13 +70,13 @@ int inv_sensors_timestamp_update_odr(str
+ }
+ EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_update_odr, IIO_INV_SENSORS_TIMESTAMP);
+
+-static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t period, uint32_t mult)
++static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t period)
+ {
+ uint32_t period_min, period_max;
+
+ /* check that period is acceptable */
+- period_min = ts->min_period * mult;
+- period_max = ts->max_period * mult;
++ period_min = ts->min_period * ts->mult;
++ period_max = ts->max_period * ts->mult;
+ if (period > period_min && period < period_max)
+ return true;
+ else
+@@ -84,32 +84,30 @@ static bool inv_validate_period(struct i
+ }
+
+ static bool inv_compute_chip_period(struct inv_sensors_timestamp *ts,
+- uint32_t mult, uint32_t period)
++ uint32_t period)
+ {
+ uint32_t new_chip_period;
+
+- if (!inv_validate_period(ts, period, mult))
++ if (!inv_validate_period(ts, period))
+ return false;
+
+ /* update chip internal period estimation */
+- new_chip_period = period / mult;
++ new_chip_period = period / ts->mult;
+ inv_update_acc(&ts->chip_period, new_chip_period);
+
+ return true;
+ }
+
+ void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts,
+- uint32_t fifo_period, size_t fifo_nb,
+- size_t sensor_nb, int64_t timestamp)
++ size_t sample_nb, int64_t timestamp)
+ {
+ struct inv_sensors_timestamp_interval *it;
+ int64_t delta, interval;
+- const uint32_t fifo_mult = fifo_period / ts->chip.clock_period;
+ uint32_t period;
+ int32_t m;
+ bool valid = false;
+
+- if (fifo_nb == 0)
++ if (sample_nb == 0)
+ return;
+
+ /* update interrupt timestamp and compute chip and sensor periods */
+@@ -119,8 +117,8 @@ void inv_sensors_timestamp_interrupt(str
+ delta = it->up - it->lo;
+ if (it->lo != 0) {
+ /* compute period: delta time divided by number of samples */
+- period = div_s64(delta, fifo_nb);
+- valid = inv_compute_chip_period(ts, fifo_mult, period);
++ period = div_s64(delta, sample_nb);
++ valid = inv_compute_chip_period(ts, period);
+ /* update sensor period if chip internal period is updated */
+ if (valid)
+ ts->period = ts->mult * ts->chip_period.val;
+@@ -129,20 +127,18 @@ void inv_sensors_timestamp_interrupt(str
+ /* no previous data, compute theoritical value from interrupt */
+ if (ts->timestamp == 0) {
+ /* elapsed time: sensor period * sensor samples number */
+- interval = (int64_t)ts->period * (int64_t)sensor_nb;
++ interval = (int64_t)ts->period * (int64_t)sample_nb;
+ ts->timestamp = it->up - interval;
+ return;
+ }
+
+ /* if interrupt interval is valid, sync with interrupt timestamp */
+ if (valid) {
+- /* compute measured fifo_period */
+- fifo_period = fifo_mult * ts->chip_period.val;
+ /* delta time between last sample and last interrupt */
+ delta = it->lo - ts->timestamp;
+ /* if there are multiple samples, go back to first one */
+- while (delta >= (fifo_period * 3 / 2))
+- delta -= fifo_period;
++ while (delta >= (ts->period * 3 / 2))
++ delta -= ts->period;
+ /* compute maximal adjustment value */
+ m = INV_SENSORS_TIMESTAMP_JITTER((int64_t)ts->period, ts->chip.jitter);
+ if (delta > m)
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
+@@ -515,20 +515,20 @@ int inv_icm42600_buffer_fifo_parse(struc
+ return 0;
+
+ /* handle gyroscope timestamp and FIFO data parsing */
+- ts = iio_priv(st->indio_gyro);
+- inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total,
+- st->fifo.nb.gyro, st->timestamp.gyro);
+ if (st->fifo.nb.gyro > 0) {
++ ts = iio_priv(st->indio_gyro);
++ inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro,
++ st->timestamp.gyro);
+ ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro);
+ if (ret)
+ return ret;
+ }
+
+ /* handle accelerometer timestamp and FIFO data parsing */
+- ts = iio_priv(st->indio_accel);
+- inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total,
+- st->fifo.nb.accel, st->timestamp.accel);
+ if (st->fifo.nb.accel > 0) {
++ ts = iio_priv(st->indio_accel);
++ inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel,
++ st->timestamp.accel);
+ ret = inv_icm42600_accel_parse_fifo(st->indio_accel);
+ if (ret)
+ return ret;
+@@ -556,9 +556,7 @@ int inv_icm42600_buffer_hwfifo_flush(str
+
+ if (st->fifo.nb.gyro > 0) {
+ ts = iio_priv(st->indio_gyro);
+- inv_sensors_timestamp_interrupt(ts, st->fifo.period,
+- st->fifo.nb.total, st->fifo.nb.gyro,
+- gyro_ts);
++ inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, gyro_ts);
+ ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro);
+ if (ret)
+ return ret;
+@@ -566,9 +564,7 @@ int inv_icm42600_buffer_hwfifo_flush(str
+
+ if (st->fifo.nb.accel > 0) {
+ ts = iio_priv(st->indio_accel);
+- inv_sensors_timestamp_interrupt(ts, st->fifo.period,
+- st->fifo.nb.total, st->fifo.nb.accel,
+- accel_ts);
++ inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, accel_ts);
+ ret = inv_icm42600_accel_parse_fifo(st->indio_accel);
+ if (ret)
+ return ret;
+--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c
++++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c
+@@ -109,7 +109,7 @@ irqreturn_t inv_mpu6050_read_fifo(int ir
+ nb = fifo_count / bytes_per_datum;
+ /* Each FIFO data contains all sensors, so same number for FIFO and sensor data */
+ fifo_period = NSEC_PER_SEC / INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider);
+- inv_sensors_timestamp_interrupt(&st->timestamp, fifo_period, nb, nb, pf->timestamp);
++ inv_sensors_timestamp_interrupt(&st->timestamp, nb, pf->timestamp);
+ inv_sensors_timestamp_apply_odr(&st->timestamp, fifo_period, nb, 0);
+ for (i = 0; i < nb; ++i) {
+ result = regmap_noinc_read(st->map, st->reg->fifo_r_w,
+--- a/include/linux/iio/common/inv_sensors_timestamp.h
++++ b/include/linux/iio/common/inv_sensors_timestamp.h
+@@ -71,8 +71,7 @@ int inv_sensors_timestamp_update_odr(str
+ uint32_t period, bool fifo);
+
+ void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts,
+- uint32_t fifo_period, size_t fifo_nb,
+- size_t sensor_nb, int64_t timestamp);
++ size_t sample_nb, int64_t timestamp);
+
+ static inline int64_t inv_sensors_timestamp_pop(struct inv_sensors_timestamp *ts)
+ {
--- /dev/null
+From stable+bounces-274007-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:39:39 -0400
+Subject: iio: invensense: remove redundant initialization of variable period
+To: stable@vger.kernel.org
+Cc: Colin Ian King <colin.i.king@gmail.com>, Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>, Jonathan Cameron <Jonathan.Cameron@huawei.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713203942.2144352-5-sashal@kernel.org>
+
+From: Colin Ian King <colin.i.king@gmail.com>
+
+[ Upstream commit b58b13f156c00c2457035b7071eaaac105fe6836 ]
+
+The variable period is being initialized with a value that is never
+read, it is being re-assigned a new value later on before it is read.
+The initialization is redundant and can be removed.
+
+Cleans up clang scan build warning:
+Value stored to 'period' during its initialization is never
+read [deadcode.DeadStores]
+
+Signed-off-by: Colin Ian King <colin.i.king@gmail.com>
+Acked-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+Link: https://lore.kernel.org/r/20240106153202.54861-1-colin.i.king@gmail.com
+Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/common/inv_sensors/inv_sensors_timestamp.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c
++++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c
+@@ -105,7 +105,7 @@ void inv_sensors_timestamp_interrupt(str
+ struct inv_sensors_timestamp_interval *it;
+ int64_t delta, interval;
+ const uint32_t fifo_mult = fifo_period / ts->chip.clock_period;
+- uint32_t period = ts->period;
++ uint32_t period;
+ int32_t m;
+ bool valid = false;
+
--- /dev/null
+From stable+bounces-274006-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:39:37 -0400
+Subject: iio: make invensense timestamp module generic
+To: stable@vger.kernel.org
+Cc: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>, Andy Shevchenko <andy.shevchenko@gmail.com>, Jonathan Cameron <Jonathan.Cameron@huawei.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713203942.2144352-3-sashal@kernel.org>
+
+From: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+
+[ Upstream commit 0ecc363ccea71eda6a2cceade120489259bcdb33 ]
+
+Rename common module to inv_sensors_timestamp, add configuration
+at init (chip internal clock, acceptable jitter, ...) and update
+inv_icm42600 driver integration.
+
+Signed-off-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Link: https://lore.kernel.org/r/20230606162147.79667-4-inv.git-commit@tdk.com
+Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/common/inv_sensors/Makefile | 2
+ drivers/iio/common/inv_sensors/inv_icm42600_timestamp.c | 192 ----------------
+ drivers/iio/common/inv_sensors/inv_sensors_timestamp.c | 191 +++++++++++++++
+ drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c | 32 +-
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 34 +-
+ drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 10
+ drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c | 32 +-
+ include/linux/iio/common/inv_icm42600_timestamp.h | 79 ------
+ include/linux/iio/common/inv_sensors_timestamp.h | 95 +++++++
+ 9 files changed, 349 insertions(+), 318 deletions(-)
+ rename drivers/iio/common/inv_sensors/{inv_icm42600_timestamp.c => inv_sensors_timestamp.c} (61%)
+ delete mode 100644 include/linux/iio/common/inv_icm42600_timestamp.h
+ create mode 100644 include/linux/iio/common/inv_sensors_timestamp.h
+
+--- a/drivers/iio/common/inv_sensors/Makefile
++++ b/drivers/iio/common/inv_sensors/Makefile
+@@ -3,4 +3,4 @@
+ # Makefile for TDK-InvenSense sensors module.
+ #
+
+-obj-$(CONFIG_IIO_INV_SENSORS_TIMESTAMP) += inv_icm42600_timestamp.o
++obj-$(CONFIG_IIO_INV_SENSORS_TIMESTAMP) += inv_sensors_timestamp.o
+--- a/drivers/iio/common/inv_sensors/inv_icm42600_timestamp.c
++++ /dev/null
+@@ -1,192 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0-or-later
+-/*
+- * Copyright (C) 2020 Invensense, Inc.
+- */
+-
+-#include <linux/errno.h>
+-#include <linux/kernel.h>
+-#include <linux/math64.h>
+-#include <linux/module.h>
+-
+-#include <linux/iio/common/inv_icm42600_timestamp.h>
+-
+-/* internal chip period is 32kHz, 31250ns */
+-#define INV_ICM42600_TIMESTAMP_PERIOD 31250
+-/* allow a jitter of +/- 2% */
+-#define INV_ICM42600_TIMESTAMP_JITTER 2
+-/* compute min and max periods accepted */
+-#define INV_ICM42600_TIMESTAMP_MIN_PERIOD(_p) \
+- (((_p) * (100 - INV_ICM42600_TIMESTAMP_JITTER)) / 100)
+-#define INV_ICM42600_TIMESTAMP_MAX_PERIOD(_p) \
+- (((_p) * (100 + INV_ICM42600_TIMESTAMP_JITTER)) / 100)
+-
+-/* Add a new value inside an accumulator and update the estimate value */
+-static void inv_update_acc(struct inv_icm42600_timestamp_acc *acc, uint32_t val)
+-{
+- uint64_t sum = 0;
+- size_t i;
+-
+- acc->values[acc->idx++] = val;
+- if (acc->idx >= ARRAY_SIZE(acc->values))
+- acc->idx = 0;
+-
+- /* compute the mean of all stored values, use 0 as empty slot */
+- for (i = 0; i < ARRAY_SIZE(acc->values); ++i) {
+- if (acc->values[i] == 0)
+- break;
+- sum += acc->values[i];
+- }
+-
+- acc->val = div_u64(sum, i);
+-}
+-
+-void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts,
+- uint32_t period)
+-{
+- /* initial odr for sensor after reset is 1kHz */
+- const uint32_t default_period = 1000000;
+-
+- /* current multiplier and period values after reset */
+- ts->mult = default_period / INV_ICM42600_TIMESTAMP_PERIOD;
+- ts->period = default_period;
+- /* new set multiplier is the one from chip initialization */
+- ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD;
+-
+- /* use theoretical value for chip period */
+- inv_update_acc(&ts->chip_period, INV_ICM42600_TIMESTAMP_PERIOD);
+-}
+-EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_init, IIO_INV_SENSORS_TIMESTAMP);
+-
+-int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts,
+- uint32_t period, bool fifo)
+-{
+- /* when FIFO is on, prevent odr change if one is already pending */
+- if (fifo && ts->new_mult != 0)
+- return -EAGAIN;
+-
+- ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD;
+-
+- return 0;
+-}
+-EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_update_odr, IIO_INV_SENSORS_TIMESTAMP);
+-
+-static bool inv_validate_period(uint32_t period, uint32_t mult)
+-{
+- const uint32_t chip_period = INV_ICM42600_TIMESTAMP_PERIOD;
+- uint32_t period_min, period_max;
+-
+- /* check that period is acceptable */
+- period_min = INV_ICM42600_TIMESTAMP_MIN_PERIOD(chip_period) * mult;
+- period_max = INV_ICM42600_TIMESTAMP_MAX_PERIOD(chip_period) * mult;
+- if (period > period_min && period < period_max)
+- return true;
+- else
+- return false;
+-}
+-
+-static bool inv_compute_chip_period(struct inv_icm42600_timestamp *ts,
+- uint32_t mult, uint32_t period)
+-{
+- uint32_t new_chip_period;
+-
+- if (!inv_validate_period(period, mult))
+- return false;
+-
+- /* update chip internal period estimation */
+- new_chip_period = period / mult;
+- inv_update_acc(&ts->chip_period, new_chip_period);
+-
+- return true;
+-}
+-
+-void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts,
+- uint32_t fifo_period, size_t fifo_nb,
+- size_t sensor_nb, int64_t timestamp)
+-{
+- struct inv_icm42600_timestamp_interval *it;
+- int64_t delta, interval;
+- const uint32_t fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD;
+- uint32_t period = ts->period;
+- int32_t m;
+- bool valid = false;
+-
+- if (fifo_nb == 0)
+- return;
+-
+- /* update interrupt timestamp and compute chip and sensor periods */
+- it = &ts->it;
+- it->lo = it->up;
+- it->up = timestamp;
+- delta = it->up - it->lo;
+- if (it->lo != 0) {
+- /* compute period: delta time divided by number of samples */
+- period = div_s64(delta, fifo_nb);
+- valid = inv_compute_chip_period(ts, fifo_mult, period);
+- /* update sensor period if chip internal period is updated */
+- if (valid)
+- ts->period = ts->mult * ts->chip_period.val;
+- }
+-
+- /* no previous data, compute theoritical value from interrupt */
+- if (ts->timestamp == 0) {
+- /* elapsed time: sensor period * sensor samples number */
+- interval = (int64_t)ts->period * (int64_t)sensor_nb;
+- ts->timestamp = it->up - interval;
+- return;
+- }
+-
+- /* if interrupt interval is valid, sync with interrupt timestamp */
+- if (valid) {
+- /* compute measured fifo_period */
+- fifo_period = fifo_mult * ts->chip_period.val;
+- /* delta time between last sample and last interrupt */
+- delta = it->lo - ts->timestamp;
+- /* if there are multiple samples, go back to first one */
+- while (delta >= (fifo_period * 3 / 2))
+- delta -= fifo_period;
+- /* compute maximal adjustment value */
+- m = INV_ICM42600_TIMESTAMP_MAX_PERIOD(ts->period) - ts->period;
+- if (delta > m)
+- delta = m;
+- else if (delta < -m)
+- delta = -m;
+- ts->timestamp += delta;
+- }
+-}
+-EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_interrupt, IIO_INV_SENSORS_TIMESTAMP);
+-
+-void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts,
+- uint32_t fifo_period, size_t fifo_nb,
+- unsigned int fifo_no)
+-{
+- int64_t interval;
+- uint32_t fifo_mult;
+-
+- if (ts->new_mult == 0)
+- return;
+-
+- /* update to new multiplier and update period */
+- ts->mult = ts->new_mult;
+- ts->new_mult = 0;
+- ts->period = ts->mult * ts->chip_period.val;
+-
+- /*
+- * After ODR change the time interval with the previous sample is
+- * undertermined (depends when the change occures). So we compute the
+- * timestamp from the current interrupt using the new FIFO period, the
+- * total number of samples and the current sample numero.
+- */
+- if (ts->timestamp != 0) {
+- /* compute measured fifo period */
+- fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD;
+- fifo_period = fifo_mult * ts->chip_period.val;
+- /* computes time interval between interrupt and this sample */
+- interval = (int64_t)(fifo_nb - fifo_no) * (int64_t)fifo_period;
+- ts->timestamp = ts->it.up - interval;
+- }
+-}
+-EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_apply_odr, IIO_INV_SENSORS_TIMESTAMP);
+-
+-MODULE_AUTHOR("InvenSense, Inc.");
+-MODULE_DESCRIPTION("InvenSense sensors timestamp module");
+-MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c
+@@ -0,0 +1,191 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (C) 2020 Invensense, Inc.
++ */
++
++#include <linux/errno.h>
++#include <linux/kernel.h>
++#include <linux/math64.h>
++#include <linux/module.h>
++
++#include <linux/iio/common/inv_sensors_timestamp.h>
++
++/* compute jitter, min and max following jitter in per mille */
++#define INV_SENSORS_TIMESTAMP_JITTER(_val, _jitter) \
++ (div_s64((_val) * (_jitter), 1000))
++#define INV_SENSORS_TIMESTAMP_MIN(_val, _jitter) \
++ (((_val) * (1000 - (_jitter))) / 1000)
++#define INV_SENSORS_TIMESTAMP_MAX(_val, _jitter) \
++ (((_val) * (1000 + (_jitter))) / 1000)
++
++/* Add a new value inside an accumulator and update the estimate value */
++static void inv_update_acc(struct inv_sensors_timestamp_acc *acc, uint32_t val)
++{
++ uint64_t sum = 0;
++ size_t i;
++
++ acc->values[acc->idx++] = val;
++ if (acc->idx >= ARRAY_SIZE(acc->values))
++ acc->idx = 0;
++
++ /* compute the mean of all stored values, use 0 as empty slot */
++ for (i = 0; i < ARRAY_SIZE(acc->values); ++i) {
++ if (acc->values[i] == 0)
++ break;
++ sum += acc->values[i];
++ }
++
++ acc->val = div_u64(sum, i);
++}
++
++void inv_sensors_timestamp_init(struct inv_sensors_timestamp *ts,
++ const struct inv_sensors_timestamp_chip *chip)
++{
++ memset(ts, 0, sizeof(*ts));
++
++ /* save chip parameters and compute min and max clock period */
++ ts->chip = *chip;
++ ts->min_period = INV_SENSORS_TIMESTAMP_MIN(chip->clock_period, chip->jitter);
++ ts->max_period = INV_SENSORS_TIMESTAMP_MAX(chip->clock_period, chip->jitter);
++
++ /* current multiplier and period values after reset */
++ ts->mult = chip->init_period / chip->clock_period;
++ ts->period = chip->init_period;
++
++ /* use theoretical value for chip period */
++ inv_update_acc(&ts->chip_period, chip->clock_period);
++}
++EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_init, IIO_INV_SENSORS_TIMESTAMP);
++
++int inv_sensors_timestamp_update_odr(struct inv_sensors_timestamp *ts,
++ uint32_t period, bool fifo)
++{
++ /* when FIFO is on, prevent odr change if one is already pending */
++ if (fifo && ts->new_mult != 0)
++ return -EAGAIN;
++
++ ts->new_mult = period / ts->chip.clock_period;
++
++ return 0;
++}
++EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_update_odr, IIO_INV_SENSORS_TIMESTAMP);
++
++static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t period, uint32_t mult)
++{
++ uint32_t period_min, period_max;
++
++ /* check that period is acceptable */
++ period_min = ts->min_period * mult;
++ period_max = ts->max_period * mult;
++ if (period > period_min && period < period_max)
++ return true;
++ else
++ return false;
++}
++
++static bool inv_compute_chip_period(struct inv_sensors_timestamp *ts,
++ uint32_t mult, uint32_t period)
++{
++ uint32_t new_chip_period;
++
++ if (!inv_validate_period(ts, period, mult))
++ return false;
++
++ /* update chip internal period estimation */
++ new_chip_period = period / mult;
++ inv_update_acc(&ts->chip_period, new_chip_period);
++
++ return true;
++}
++
++void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts,
++ uint32_t fifo_period, size_t fifo_nb,
++ size_t sensor_nb, int64_t timestamp)
++{
++ struct inv_sensors_timestamp_interval *it;
++ int64_t delta, interval;
++ const uint32_t fifo_mult = fifo_period / ts->chip.clock_period;
++ uint32_t period = ts->period;
++ int32_t m;
++ bool valid = false;
++
++ if (fifo_nb == 0)
++ return;
++
++ /* update interrupt timestamp and compute chip and sensor periods */
++ it = &ts->it;
++ it->lo = it->up;
++ it->up = timestamp;
++ delta = it->up - it->lo;
++ if (it->lo != 0) {
++ /* compute period: delta time divided by number of samples */
++ period = div_s64(delta, fifo_nb);
++ valid = inv_compute_chip_period(ts, fifo_mult, period);
++ /* update sensor period if chip internal period is updated */
++ if (valid)
++ ts->period = ts->mult * ts->chip_period.val;
++ }
++
++ /* no previous data, compute theoritical value from interrupt */
++ if (ts->timestamp == 0) {
++ /* elapsed time: sensor period * sensor samples number */
++ interval = (int64_t)ts->period * (int64_t)sensor_nb;
++ ts->timestamp = it->up - interval;
++ return;
++ }
++
++ /* if interrupt interval is valid, sync with interrupt timestamp */
++ if (valid) {
++ /* compute measured fifo_period */
++ fifo_period = fifo_mult * ts->chip_period.val;
++ /* delta time between last sample and last interrupt */
++ delta = it->lo - ts->timestamp;
++ /* if there are multiple samples, go back to first one */
++ while (delta >= (fifo_period * 3 / 2))
++ delta -= fifo_period;
++ /* compute maximal adjustment value */
++ m = INV_SENSORS_TIMESTAMP_JITTER((int64_t)ts->period, ts->chip.jitter);
++ if (delta > m)
++ delta = m;
++ else if (delta < -m)
++ delta = -m;
++ ts->timestamp += delta;
++ }
++}
++EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_interrupt, IIO_INV_SENSORS_TIMESTAMP);
++
++void inv_sensors_timestamp_apply_odr(struct inv_sensors_timestamp *ts,
++ uint32_t fifo_period, size_t fifo_nb,
++ unsigned int fifo_no)
++{
++ int64_t interval;
++ uint32_t fifo_mult;
++
++ if (ts->new_mult == 0)
++ return;
++
++ /* update to new multiplier and update period */
++ ts->mult = ts->new_mult;
++ ts->new_mult = 0;
++ ts->period = ts->mult * ts->chip_period.val;
++
++ /*
++ * After ODR change the time interval with the previous sample is
++ * undertermined (depends when the change occures). So we compute the
++ * timestamp from the current interrupt using the new FIFO period, the
++ * total number of samples and the current sample numero.
++ */
++ if (ts->timestamp != 0) {
++ /* compute measured fifo period */
++ fifo_mult = fifo_period / ts->chip.clock_period;
++ fifo_period = fifo_mult * ts->chip_period.val;
++ /* computes time interval between interrupt and this sample */
++ interval = (int64_t)(fifo_nb - fifo_no) * (int64_t)fifo_period;
++ ts->timestamp = ts->it.up - interval;
++ }
++}
++EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_apply_odr, IIO_INV_SENSORS_TIMESTAMP);
++
++MODULE_AUTHOR("InvenSense, Inc.");
++MODULE_DESCRIPTION("InvenSense sensors timestamp module");
++MODULE_LICENSE("GPL");
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
+@@ -12,7 +12,7 @@
+ #include <linux/math64.h>
+
+ #include <linux/iio/buffer.h>
+-#include <linux/iio/common/inv_icm42600_timestamp.h>
++#include <linux/iio/common/inv_sensors_timestamp.h>
+ #include <linux/iio/iio.h>
+ #include <linux/iio/kfifo_buf.h>
+
+@@ -99,7 +99,7 @@ static int inv_icm42600_accel_update_sca
+ const unsigned long *scan_mask)
+ {
+ struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
+- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev);
+ struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
+ unsigned int fifo_en = 0;
+ unsigned int sleep_temp = 0;
+@@ -127,7 +127,7 @@ static int inv_icm42600_accel_update_sca
+ }
+
+ /* update data FIFO write */
+- inv_icm42600_timestamp_apply_odr(ts, 0, 0, 0);
++ inv_sensors_timestamp_apply_odr(ts, 0, 0, 0);
+ ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en);
+
+ out_unlock:
+@@ -308,7 +308,7 @@ static int inv_icm42600_accel_write_odr(
+ int val, int val2)
+ {
+ struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
+- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(st->map);
+ unsigned int idx;
+ struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
+@@ -329,8 +329,8 @@ static int inv_icm42600_accel_write_odr(
+ pm_runtime_get_sync(dev);
+ mutex_lock(&st->lock);
+
+- ret = inv_icm42600_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr),
+- iio_buffer_enabled(indio_dev));
++ ret = inv_sensors_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr),
++ iio_buffer_enabled(indio_dev));
+ if (ret)
+ goto out_unlock;
+
+@@ -706,7 +706,8 @@ struct iio_dev *inv_icm42600_accel_init(
+ {
+ struct device *dev = regmap_get_device(st->map);
+ const char *name;
+- struct inv_icm42600_timestamp *ts;
++ struct inv_sensors_timestamp_chip ts_chip;
++ struct inv_sensors_timestamp *ts;
+ struct iio_dev *indio_dev;
+ int ret;
+
+@@ -718,8 +719,15 @@ struct iio_dev *inv_icm42600_accel_init(
+ if (!indio_dev)
+ return ERR_PTR(-ENOMEM);
+
++ /*
++ * clock period is 32kHz (31250ns)
++ * jitter is +/- 2% (20 per mille)
++ */
++ ts_chip.clock_period = 31250;
++ ts_chip.jitter = 20;
++ ts_chip.init_period = inv_icm42600_odr_to_period(st->conf.accel.odr);
+ ts = iio_priv(indio_dev);
+- inv_icm42600_timestamp_init(ts, inv_icm42600_odr_to_period(st->conf.accel.odr));
++ inv_sensors_timestamp_init(ts, &ts_chip);
+
+ iio_device_set_drvdata(indio_dev, st);
+ indio_dev->name = name;
+@@ -744,7 +752,7 @@ struct iio_dev *inv_icm42600_accel_init(
+ int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev)
+ {
+ struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
+- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev);
+ ssize_t i, size;
+ unsigned int no;
+ const void *accel, *gyro, *timestamp;
+@@ -768,13 +776,13 @@ int inv_icm42600_accel_parse_fifo(struct
+
+ /* update odr */
+ if (odr & INV_ICM42600_SENSOR_ACCEL)
+- inv_icm42600_timestamp_apply_odr(ts, st->fifo.period,
+- st->fifo.nb.total, no);
++ inv_sensors_timestamp_apply_odr(ts, st->fifo.period,
++ st->fifo.nb.total, no);
+
+ memcpy(&buffer.accel, accel, sizeof(buffer.accel));
+ /* convert 8 bits FIFO temperature in high resolution format */
+ buffer.temp = temp ? (*temp * 64) : 0;
+- ts_val = inv_icm42600_timestamp_pop(ts);
++ ts_val = inv_sensors_timestamp_pop(ts);
+ iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val);
+ }
+
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
+@@ -11,7 +11,7 @@
+ #include <linux/delay.h>
+
+ #include <linux/iio/buffer.h>
+-#include <linux/iio/common/inv_icm42600_timestamp.h>
++#include <linux/iio/common/inv_sensors_timestamp.h>
+ #include <linux/iio/iio.h>
+
+ #include "inv_icm42600.h"
+@@ -276,12 +276,12 @@ static int inv_icm42600_buffer_preenable
+ {
+ struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
+ struct device *dev = regmap_get_device(st->map);
+- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev);
+
+ pm_runtime_get_sync(dev);
+
+ mutex_lock(&st->lock);
+- inv_icm42600_timestamp_reset(ts);
++ inv_sensors_timestamp_reset(ts);
+ mutex_unlock(&st->lock);
+
+ return 0;
+@@ -378,7 +378,7 @@ out_unlock:
+ static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev)
+ {
+ struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
+- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(st->map);
+ unsigned int sensor;
+ unsigned int *watermark;
+@@ -400,7 +400,7 @@ static int inv_icm42600_buffer_postdisab
+
+ mutex_lock(&st->lock);
+
+- inv_icm42600_timestamp_apply_odr(ts, 0, 0, 0);
++ inv_sensors_timestamp_apply_odr(ts, 0, 0, 0);
+
+ ret = inv_icm42600_buffer_set_fifo_en(st, st->fifo.en & ~sensor);
+ if (ret)
+@@ -508,7 +508,7 @@ int inv_icm42600_buffer_fifo_read(struct
+
+ int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st)
+ {
+- struct inv_icm42600_timestamp *ts;
++ struct inv_sensors_timestamp *ts;
+ int ret;
+
+ if (st->fifo.nb.total == 0)
+@@ -516,8 +516,8 @@ int inv_icm42600_buffer_fifo_parse(struc
+
+ /* handle gyroscope timestamp and FIFO data parsing */
+ ts = iio_priv(st->indio_gyro);
+- inv_icm42600_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total,
+- st->fifo.nb.gyro, st->timestamp.gyro);
++ inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total,
++ st->fifo.nb.gyro, st->timestamp.gyro);
+ if (st->fifo.nb.gyro > 0) {
+ ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro);
+ if (ret)
+@@ -526,8 +526,8 @@ int inv_icm42600_buffer_fifo_parse(struc
+
+ /* handle accelerometer timestamp and FIFO data parsing */
+ ts = iio_priv(st->indio_accel);
+- inv_icm42600_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total,
+- st->fifo.nb.accel, st->timestamp.accel);
++ inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total,
++ st->fifo.nb.accel, st->timestamp.accel);
+ if (st->fifo.nb.accel > 0) {
+ ret = inv_icm42600_accel_parse_fifo(st->indio_accel);
+ if (ret)
+@@ -540,7 +540,7 @@ int inv_icm42600_buffer_fifo_parse(struc
+ int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st,
+ unsigned int count)
+ {
+- struct inv_icm42600_timestamp *ts;
++ struct inv_sensors_timestamp *ts;
+ int64_t gyro_ts, accel_ts;
+ int ret;
+
+@@ -556,9 +556,9 @@ int inv_icm42600_buffer_hwfifo_flush(str
+
+ if (st->fifo.nb.gyro > 0) {
+ ts = iio_priv(st->indio_gyro);
+- inv_icm42600_timestamp_interrupt(ts, st->fifo.period,
+- st->fifo.nb.total, st->fifo.nb.gyro,
+- gyro_ts);
++ inv_sensors_timestamp_interrupt(ts, st->fifo.period,
++ st->fifo.nb.total, st->fifo.nb.gyro,
++ gyro_ts);
+ ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro);
+ if (ret)
+ return ret;
+@@ -566,9 +566,9 @@ int inv_icm42600_buffer_hwfifo_flush(str
+
+ if (st->fifo.nb.accel > 0) {
+ ts = iio_priv(st->indio_accel);
+- inv_icm42600_timestamp_interrupt(ts, st->fifo.period,
+- st->fifo.nb.total, st->fifo.nb.accel,
+- accel_ts);
++ inv_sensors_timestamp_interrupt(ts, st->fifo.period,
++ st->fifo.nb.total, st->fifo.nb.accel,
++ accel_ts);
+ ret = inv_icm42600_accel_parse_fifo(st->indio_accel);
+ if (ret)
+ return ret;
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
+@@ -16,7 +16,7 @@
+ #include <linux/property.h>
+ #include <linux/regmap.h>
+
+-#include <linux/iio/common/inv_icm42600_timestamp.h>
++#include <linux/iio/common/inv_sensors_timestamp.h>
+ #include <linux/iio/iio.h>
+
+ #include "inv_icm42600.h"
+@@ -720,8 +720,8 @@ out_unlock:
+ static int __maybe_unused inv_icm42600_resume(struct device *dev)
+ {
+ struct inv_icm42600_state *st = dev_get_drvdata(dev);
+- struct inv_icm42600_timestamp *gyro_ts = iio_priv(st->indio_gyro);
+- struct inv_icm42600_timestamp *accel_ts = iio_priv(st->indio_accel);
++ struct inv_sensors_timestamp *gyro_ts = iio_priv(st->indio_gyro);
++ struct inv_sensors_timestamp *accel_ts = iio_priv(st->indio_accel);
+ int ret = 0;
+
+ mutex_lock(&st->lock);
+@@ -742,8 +742,8 @@ static int __maybe_unused inv_icm42600_r
+
+ /* restore FIFO data streaming */
+ if (st->fifo.on) {
+- inv_icm42600_timestamp_reset(gyro_ts);
+- inv_icm42600_timestamp_reset(accel_ts);
++ inv_sensors_timestamp_reset(gyro_ts);
++ inv_sensors_timestamp_reset(accel_ts);
+ ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG,
+ INV_ICM42600_FIFO_CONFIG_STREAM);
+ }
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
+@@ -12,7 +12,7 @@
+ #include <linux/math64.h>
+
+ #include <linux/iio/buffer.h>
+-#include <linux/iio/common/inv_icm42600_timestamp.h>
++#include <linux/iio/common/inv_sensors_timestamp.h>
+ #include <linux/iio/iio.h>
+ #include <linux/iio/kfifo_buf.h>
+
+@@ -99,7 +99,7 @@ static int inv_icm42600_gyro_update_scan
+ const unsigned long *scan_mask)
+ {
+ struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
+- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev);
+ struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
+ unsigned int fifo_en = 0;
+ unsigned int sleep_gyro = 0;
+@@ -127,7 +127,7 @@ static int inv_icm42600_gyro_update_scan
+ }
+
+ /* update data FIFO write */
+- inv_icm42600_timestamp_apply_odr(ts, 0, 0, 0);
++ inv_sensors_timestamp_apply_odr(ts, 0, 0, 0);
+ ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en);
+
+ out_unlock:
+@@ -320,7 +320,7 @@ static int inv_icm42600_gyro_write_odr(s
+ int val, int val2)
+ {
+ struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
+- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(st->map);
+ unsigned int idx;
+ struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
+@@ -341,8 +341,8 @@ static int inv_icm42600_gyro_write_odr(s
+ pm_runtime_get_sync(dev);
+ mutex_lock(&st->lock);
+
+- ret = inv_icm42600_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr),
+- iio_buffer_enabled(indio_dev));
++ ret = inv_sensors_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr),
++ iio_buffer_enabled(indio_dev));
+ if (ret)
+ goto out_unlock;
+
+@@ -717,7 +717,8 @@ struct iio_dev *inv_icm42600_gyro_init(s
+ {
+ struct device *dev = regmap_get_device(st->map);
+ const char *name;
+- struct inv_icm42600_timestamp *ts;
++ struct inv_sensors_timestamp_chip ts_chip;
++ struct inv_sensors_timestamp *ts;
+ struct iio_dev *indio_dev;
+ int ret;
+
+@@ -729,8 +730,15 @@ struct iio_dev *inv_icm42600_gyro_init(s
+ if (!indio_dev)
+ return ERR_PTR(-ENOMEM);
+
++ /*
++ * clock period is 32kHz (31250ns)
++ * jitter is +/- 2% (20 per mille)
++ */
++ ts_chip.clock_period = 31250;
++ ts_chip.jitter = 20;
++ ts_chip.init_period = inv_icm42600_odr_to_period(st->conf.accel.odr);
+ ts = iio_priv(indio_dev);
+- inv_icm42600_timestamp_init(ts, inv_icm42600_odr_to_period(st->conf.gyro.odr));
++ inv_sensors_timestamp_init(ts, &ts_chip);
+
+ iio_device_set_drvdata(indio_dev, st);
+ indio_dev->name = name;
+@@ -756,7 +764,7 @@ struct iio_dev *inv_icm42600_gyro_init(s
+ int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev)
+ {
+ struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
+- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev);
++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev);
+ ssize_t i, size;
+ unsigned int no;
+ const void *accel, *gyro, *timestamp;
+@@ -780,13 +788,13 @@ int inv_icm42600_gyro_parse_fifo(struct
+
+ /* update odr */
+ if (odr & INV_ICM42600_SENSOR_GYRO)
+- inv_icm42600_timestamp_apply_odr(ts, st->fifo.period,
+- st->fifo.nb.total, no);
++ inv_sensors_timestamp_apply_odr(ts, st->fifo.period,
++ st->fifo.nb.total, no);
+
+ memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro));
+ /* convert 8 bits FIFO temperature in high resolution format */
+ buffer.temp = temp ? (*temp * 64) : 0;
+- ts_val = inv_icm42600_timestamp_pop(ts);
++ ts_val = inv_sensors_timestamp_pop(ts);
+ iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val);
+ }
+
+--- a/include/linux/iio/common/inv_icm42600_timestamp.h
++++ /dev/null
+@@ -1,79 +0,0 @@
+-/* SPDX-License-Identifier: GPL-2.0-or-later */
+-/*
+- * Copyright (C) 2020 Invensense, Inc.
+- */
+-
+-#ifndef INV_ICM42600_TIMESTAMP_H_
+-#define INV_ICM42600_TIMESTAMP_H_
+-
+-/**
+- * struct inv_icm42600_timestamp_interval - timestamps interval
+- * @lo: interval lower bound
+- * @up: interval upper bound
+- */
+-struct inv_icm42600_timestamp_interval {
+- int64_t lo;
+- int64_t up;
+-};
+-
+-/**
+- * struct inv_icm42600_timestamp_acc - accumulator for computing an estimation
+- * @val: current estimation of the value, the mean of all values
+- * @idx: current index of the next free place in values table
+- * @values: table of all measured values, use for computing the mean
+- */
+-struct inv_icm42600_timestamp_acc {
+- uint32_t val;
+- size_t idx;
+- uint32_t values[32];
+-};
+-
+-/**
+- * struct inv_icm42600_timestamp - timestamp management states
+- * @it: interrupts interval timestamps
+- * @timestamp: store last timestamp for computing next data timestamp
+- * @mult: current internal period multiplier
+- * @new_mult: new set internal period multiplier (not yet effective)
+- * @period: measured current period of the sensor
+- * @chip_period: accumulator for computing internal chip period
+- */
+-struct inv_icm42600_timestamp {
+- struct inv_icm42600_timestamp_interval it;
+- int64_t timestamp;
+- uint32_t mult;
+- uint32_t new_mult;
+- uint32_t period;
+- struct inv_icm42600_timestamp_acc chip_period;
+-};
+-
+-void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts,
+- uint32_t period);
+-
+-int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts,
+- uint32_t period, bool fifo);
+-
+-void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts,
+- uint32_t fifo_period, size_t fifo_nb,
+- size_t sensor_nb, int64_t timestamp);
+-
+-static inline int64_t
+-inv_icm42600_timestamp_pop(struct inv_icm42600_timestamp *ts)
+-{
+- ts->timestamp += ts->period;
+- return ts->timestamp;
+-}
+-
+-void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts,
+- uint32_t fifo_period, size_t fifo_nb,
+- unsigned int fifo_no);
+-
+-static inline void
+-inv_icm42600_timestamp_reset(struct inv_icm42600_timestamp *ts)
+-{
+- const struct inv_icm42600_timestamp_interval interval_init = {0LL, 0LL};
+-
+- ts->it = interval_init;
+- ts->timestamp = 0;
+-}
+-
+-#endif
+--- /dev/null
++++ b/include/linux/iio/common/inv_sensors_timestamp.h
+@@ -0,0 +1,95 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Copyright (C) 2020 Invensense, Inc.
++ */
++
++#ifndef INV_SENSORS_TIMESTAMP_H_
++#define INV_SENSORS_TIMESTAMP_H_
++
++/**
++ * struct inv_sensors_timestamp_chip - chip internal properties
++ * @clock_period: internal clock period in ns
++ * @jitter: acceptable jitter in per-mille
++ * @init_period: chip initial period at reset in ns
++ */
++struct inv_sensors_timestamp_chip {
++ uint32_t clock_period;
++ uint32_t jitter;
++ uint32_t init_period;
++};
++
++/**
++ * struct inv_sensors_timestamp_interval - timestamps interval
++ * @lo: interval lower bound
++ * @up: interval upper bound
++ */
++struct inv_sensors_timestamp_interval {
++ int64_t lo;
++ int64_t up;
++};
++
++/**
++ * struct inv_sensors_timestamp_acc - accumulator for computing an estimation
++ * @val: current estimation of the value, the mean of all values
++ * @idx: current index of the next free place in values table
++ * @values: table of all measured values, use for computing the mean
++ */
++struct inv_sensors_timestamp_acc {
++ uint32_t val;
++ size_t idx;
++ uint32_t values[32];
++};
++
++/**
++ * struct inv_sensors_timestamp - timestamp management states
++ * @chip: chip internal characteristics
++ * @min_period: minimal acceptable clock period
++ * @max_period: maximal acceptable clock period
++ * @it: interrupts interval timestamps
++ * @timestamp: store last timestamp for computing next data timestamp
++ * @mult: current internal period multiplier
++ * @new_mult: new set internal period multiplier (not yet effective)
++ * @period: measured current period of the sensor
++ * @chip_period: accumulator for computing internal chip period
++ */
++struct inv_sensors_timestamp {
++ struct inv_sensors_timestamp_chip chip;
++ uint32_t min_period;
++ uint32_t max_period;
++ struct inv_sensors_timestamp_interval it;
++ int64_t timestamp;
++ uint32_t mult;
++ uint32_t new_mult;
++ uint32_t period;
++ struct inv_sensors_timestamp_acc chip_period;
++};
++
++void inv_sensors_timestamp_init(struct inv_sensors_timestamp *ts,
++ const struct inv_sensors_timestamp_chip *chip);
++
++int inv_sensors_timestamp_update_odr(struct inv_sensors_timestamp *ts,
++ uint32_t period, bool fifo);
++
++void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts,
++ uint32_t fifo_period, size_t fifo_nb,
++ size_t sensor_nb, int64_t timestamp);
++
++static inline int64_t inv_sensors_timestamp_pop(struct inv_sensors_timestamp *ts)
++{
++ ts->timestamp += ts->period;
++ return ts->timestamp;
++}
++
++void inv_sensors_timestamp_apply_odr(struct inv_sensors_timestamp *ts,
++ uint32_t fifo_period, size_t fifo_nb,
++ unsigned int fifo_no);
++
++static inline void inv_sensors_timestamp_reset(struct inv_sensors_timestamp *ts)
++{
++ const struct inv_sensors_timestamp_interval interval_init = {0LL, 0LL};
++
++ ts->it = interval_init;
++ ts->timestamp = 0;
++}
++
++#endif
--- /dev/null
+From stable+bounces-274004-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:51 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:39:36 -0400
+Subject: iio: move inv_icm42600 timestamp module in common
+To: stable@vger.kernel.org
+Cc: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>, Andy Shevchenko <andy.shevchenko@gmail.com>, Jonathan Cameron <Jonathan.Cameron@huawei.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713203942.2144352-2-sashal@kernel.org>
+
+From: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+
+[ Upstream commit d99ff463ecf651437e9e4abe68f331dfb6b5bd9d ]
+
+Create new inv_sensors common modules and move inv_icm42600
+timestamp module inside. This module will be used by IMUs and
+also in the future by other chips.
+
+Modify inv_icm42600 driver to use timestamp module and do some
+headers cleanup.
+
+Signed-off-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
+Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Link: https://lore.kernel.org/r/20230606162147.79667-3-inv.git-commit@tdk.com
+Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/common/Kconfig | 1 +
+ drivers/iio/common/Makefile | 1 +
+ drivers/iio/common/inv_sensors/Kconfig | 7 +++++++
+ drivers/iio/common/inv_sensors/Makefile | 6 ++++++
+ .../inv_sensors}/inv_icm42600_timestamp.c | 11 ++++++++++-
+ drivers/iio/imu/inv_icm42600/Kconfig | 1 +
+ drivers/iio/imu/inv_icm42600/Makefile | 1 -
+ drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c | 5 +++--
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 5 +++--
+ drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 4 +++-
+ drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c | 5 +++--
+ .../linux/iio/common}/inv_icm42600_timestamp.h | 0
+ drivers/iio/common/Kconfig | 1
+ drivers/iio/common/Makefile | 1
+ drivers/iio/common/inv_sensors/Kconfig | 7
+ drivers/iio/common/inv_sensors/Makefile | 6
+ drivers/iio/common/inv_sensors/inv_icm42600_timestamp.c | 192 ++++++++++++++++
+ drivers/iio/imu/inv_icm42600/Kconfig | 1
+ drivers/iio/imu/inv_icm42600/Makefile | 1
+ drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c | 5
+ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 5
+ drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 4
+ drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c | 5
+ drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c | 183 ---------------
+ drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h | 79 ------
+ include/linux/iio/common/inv_icm42600_timestamp.h | 79 ++++++
+ 14 files changed, 299 insertions(+), 270 deletions(-)
+ create mode 100644 drivers/iio/common/inv_sensors/Kconfig
+ create mode 100644 drivers/iio/common/inv_sensors/Makefile
+ rename drivers/iio/{imu/inv_icm42600 => common/inv_sensors}/inv_icm42600_timestamp.c (91%)
+ rename {drivers/iio/imu/inv_icm42600 => include/linux/iio/common}/inv_icm42600_timestamp.h (100%)
+
+--- a/drivers/iio/common/Kconfig
++++ b/drivers/iio/common/Kconfig
+@@ -5,6 +5,7 @@
+
+ source "drivers/iio/common/cros_ec_sensors/Kconfig"
+ source "drivers/iio/common/hid-sensors/Kconfig"
++source "drivers/iio/common/inv_sensors/Kconfig"
+ source "drivers/iio/common/ms_sensors/Kconfig"
+ source "drivers/iio/common/scmi_sensors/Kconfig"
+ source "drivers/iio/common/ssp_sensors/Kconfig"
+--- a/drivers/iio/common/Makefile
++++ b/drivers/iio/common/Makefile
+@@ -10,6 +10,7 @@
+ # When adding new entries keep the list in alphabetical order
+ obj-y += cros_ec_sensors/
+ obj-y += hid-sensors/
++obj-y += inv_sensors/
+ obj-y += ms_sensors/
+ obj-y += scmi_sensors/
+ obj-y += ssp_sensors/
+--- /dev/null
++++ b/drivers/iio/common/inv_sensors/Kconfig
+@@ -0,0 +1,7 @@
++# SPDX-License-Identifier: GPL-2.0-only
++#
++# TDK-InvenSense sensors common library
++#
++
++config IIO_INV_SENSORS_TIMESTAMP
++ tristate
+--- /dev/null
++++ b/drivers/iio/common/inv_sensors/Makefile
+@@ -0,0 +1,6 @@
++# SPDX-License-Identifier: GPL-2.0
++#
++# Makefile for TDK-InvenSense sensors module.
++#
++
++obj-$(CONFIG_IIO_INV_SENSORS_TIMESTAMP) += inv_icm42600_timestamp.o
+--- /dev/null
++++ b/drivers/iio/common/inv_sensors/inv_icm42600_timestamp.c
+@@ -0,0 +1,192 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (C) 2020 Invensense, Inc.
++ */
++
++#include <linux/errno.h>
++#include <linux/kernel.h>
++#include <linux/math64.h>
++#include <linux/module.h>
++
++#include <linux/iio/common/inv_icm42600_timestamp.h>
++
++/* internal chip period is 32kHz, 31250ns */
++#define INV_ICM42600_TIMESTAMP_PERIOD 31250
++/* allow a jitter of +/- 2% */
++#define INV_ICM42600_TIMESTAMP_JITTER 2
++/* compute min and max periods accepted */
++#define INV_ICM42600_TIMESTAMP_MIN_PERIOD(_p) \
++ (((_p) * (100 - INV_ICM42600_TIMESTAMP_JITTER)) / 100)
++#define INV_ICM42600_TIMESTAMP_MAX_PERIOD(_p) \
++ (((_p) * (100 + INV_ICM42600_TIMESTAMP_JITTER)) / 100)
++
++/* Add a new value inside an accumulator and update the estimate value */
++static void inv_update_acc(struct inv_icm42600_timestamp_acc *acc, uint32_t val)
++{
++ uint64_t sum = 0;
++ size_t i;
++
++ acc->values[acc->idx++] = val;
++ if (acc->idx >= ARRAY_SIZE(acc->values))
++ acc->idx = 0;
++
++ /* compute the mean of all stored values, use 0 as empty slot */
++ for (i = 0; i < ARRAY_SIZE(acc->values); ++i) {
++ if (acc->values[i] == 0)
++ break;
++ sum += acc->values[i];
++ }
++
++ acc->val = div_u64(sum, i);
++}
++
++void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts,
++ uint32_t period)
++{
++ /* initial odr for sensor after reset is 1kHz */
++ const uint32_t default_period = 1000000;
++
++ /* current multiplier and period values after reset */
++ ts->mult = default_period / INV_ICM42600_TIMESTAMP_PERIOD;
++ ts->period = default_period;
++ /* new set multiplier is the one from chip initialization */
++ ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD;
++
++ /* use theoretical value for chip period */
++ inv_update_acc(&ts->chip_period, INV_ICM42600_TIMESTAMP_PERIOD);
++}
++EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_init, IIO_INV_SENSORS_TIMESTAMP);
++
++int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts,
++ uint32_t period, bool fifo)
++{
++ /* when FIFO is on, prevent odr change if one is already pending */
++ if (fifo && ts->new_mult != 0)
++ return -EAGAIN;
++
++ ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD;
++
++ return 0;
++}
++EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_update_odr, IIO_INV_SENSORS_TIMESTAMP);
++
++static bool inv_validate_period(uint32_t period, uint32_t mult)
++{
++ const uint32_t chip_period = INV_ICM42600_TIMESTAMP_PERIOD;
++ uint32_t period_min, period_max;
++
++ /* check that period is acceptable */
++ period_min = INV_ICM42600_TIMESTAMP_MIN_PERIOD(chip_period) * mult;
++ period_max = INV_ICM42600_TIMESTAMP_MAX_PERIOD(chip_period) * mult;
++ if (period > period_min && period < period_max)
++ return true;
++ else
++ return false;
++}
++
++static bool inv_compute_chip_period(struct inv_icm42600_timestamp *ts,
++ uint32_t mult, uint32_t period)
++{
++ uint32_t new_chip_period;
++
++ if (!inv_validate_period(period, mult))
++ return false;
++
++ /* update chip internal period estimation */
++ new_chip_period = period / mult;
++ inv_update_acc(&ts->chip_period, new_chip_period);
++
++ return true;
++}
++
++void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts,
++ uint32_t fifo_period, size_t fifo_nb,
++ size_t sensor_nb, int64_t timestamp)
++{
++ struct inv_icm42600_timestamp_interval *it;
++ int64_t delta, interval;
++ const uint32_t fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD;
++ uint32_t period = ts->period;
++ int32_t m;
++ bool valid = false;
++
++ if (fifo_nb == 0)
++ return;
++
++ /* update interrupt timestamp and compute chip and sensor periods */
++ it = &ts->it;
++ it->lo = it->up;
++ it->up = timestamp;
++ delta = it->up - it->lo;
++ if (it->lo != 0) {
++ /* compute period: delta time divided by number of samples */
++ period = div_s64(delta, fifo_nb);
++ valid = inv_compute_chip_period(ts, fifo_mult, period);
++ /* update sensor period if chip internal period is updated */
++ if (valid)
++ ts->period = ts->mult * ts->chip_period.val;
++ }
++
++ /* no previous data, compute theoritical value from interrupt */
++ if (ts->timestamp == 0) {
++ /* elapsed time: sensor period * sensor samples number */
++ interval = (int64_t)ts->period * (int64_t)sensor_nb;
++ ts->timestamp = it->up - interval;
++ return;
++ }
++
++ /* if interrupt interval is valid, sync with interrupt timestamp */
++ if (valid) {
++ /* compute measured fifo_period */
++ fifo_period = fifo_mult * ts->chip_period.val;
++ /* delta time between last sample and last interrupt */
++ delta = it->lo - ts->timestamp;
++ /* if there are multiple samples, go back to first one */
++ while (delta >= (fifo_period * 3 / 2))
++ delta -= fifo_period;
++ /* compute maximal adjustment value */
++ m = INV_ICM42600_TIMESTAMP_MAX_PERIOD(ts->period) - ts->period;
++ if (delta > m)
++ delta = m;
++ else if (delta < -m)
++ delta = -m;
++ ts->timestamp += delta;
++ }
++}
++EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_interrupt, IIO_INV_SENSORS_TIMESTAMP);
++
++void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts,
++ uint32_t fifo_period, size_t fifo_nb,
++ unsigned int fifo_no)
++{
++ int64_t interval;
++ uint32_t fifo_mult;
++
++ if (ts->new_mult == 0)
++ return;
++
++ /* update to new multiplier and update period */
++ ts->mult = ts->new_mult;
++ ts->new_mult = 0;
++ ts->period = ts->mult * ts->chip_period.val;
++
++ /*
++ * After ODR change the time interval with the previous sample is
++ * undertermined (depends when the change occures). So we compute the
++ * timestamp from the current interrupt using the new FIFO period, the
++ * total number of samples and the current sample numero.
++ */
++ if (ts->timestamp != 0) {
++ /* compute measured fifo period */
++ fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD;
++ fifo_period = fifo_mult * ts->chip_period.val;
++ /* computes time interval between interrupt and this sample */
++ interval = (int64_t)(fifo_nb - fifo_no) * (int64_t)fifo_period;
++ ts->timestamp = ts->it.up - interval;
++ }
++}
++EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_apply_odr, IIO_INV_SENSORS_TIMESTAMP);
++
++MODULE_AUTHOR("InvenSense, Inc.");
++MODULE_DESCRIPTION("InvenSense sensors timestamp module");
++MODULE_LICENSE("GPL");
+--- a/drivers/iio/imu/inv_icm42600/Kconfig
++++ b/drivers/iio/imu/inv_icm42600/Kconfig
+@@ -3,6 +3,7 @@
+ config INV_ICM42600
+ tristate
+ select IIO_BUFFER
++ select IIO_INV_SENSORS_TIMESTAMP
+
+ config INV_ICM42600_I2C
+ tristate "InvenSense ICM-426xx I2C driver"
+--- a/drivers/iio/imu/inv_icm42600/Makefile
++++ b/drivers/iio/imu/inv_icm42600/Makefile
+@@ -6,7 +6,6 @@ inv-icm42600-y += inv_icm42600_gyro.o
+ inv-icm42600-y += inv_icm42600_accel.o
+ inv-icm42600-y += inv_icm42600_temp.o
+ inv-icm42600-y += inv_icm42600_buffer.o
+-inv-icm42600-y += inv_icm42600_timestamp.o
+
+ obj-$(CONFIG_INV_ICM42600_I2C) += inv-icm42600-i2c.o
+ inv-icm42600-i2c-y += inv_icm42600_i2c.o
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
+@@ -10,14 +10,15 @@
+ #include <linux/regmap.h>
+ #include <linux/delay.h>
+ #include <linux/math64.h>
+-#include <linux/iio/iio.h>
++
+ #include <linux/iio/buffer.h>
++#include <linux/iio/common/inv_icm42600_timestamp.h>
++#include <linux/iio/iio.h>
+ #include <linux/iio/kfifo_buf.h>
+
+ #include "inv_icm42600.h"
+ #include "inv_icm42600_temp.h"
+ #include "inv_icm42600_buffer.h"
+-#include "inv_icm42600_timestamp.h"
+
+ #define INV_ICM42600_ACCEL_CHAN(_modifier, _index, _ext_info) \
+ { \
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
+@@ -9,11 +9,12 @@
+ #include <linux/pm_runtime.h>
+ #include <linux/regmap.h>
+ #include <linux/delay.h>
+-#include <linux/iio/iio.h>
++
+ #include <linux/iio/buffer.h>
++#include <linux/iio/common/inv_icm42600_timestamp.h>
++#include <linux/iio/iio.h>
+
+ #include "inv_icm42600.h"
+-#include "inv_icm42600_timestamp.h"
+ #include "inv_icm42600_buffer.h"
+
+ /* FIFO header: 1 byte */
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
+@@ -15,11 +15,12 @@
+ #include <linux/pm_runtime.h>
+ #include <linux/property.h>
+ #include <linux/regmap.h>
++
++#include <linux/iio/common/inv_icm42600_timestamp.h>
+ #include <linux/iio/iio.h>
+
+ #include "inv_icm42600.h"
+ #include "inv_icm42600_buffer.h"
+-#include "inv_icm42600_timestamp.h"
+
+ static const struct regmap_range_cfg inv_icm42600_regmap_ranges[] = {
+ {
+@@ -798,3 +799,4 @@ EXPORT_SYMBOL_GPL(inv_icm42600_pm_ops);
+ MODULE_AUTHOR("InvenSense, Inc.");
+ MODULE_DESCRIPTION("InvenSense ICM-426xx device driver");
+ MODULE_LICENSE("GPL");
++MODULE_IMPORT_NS(IIO_INV_SENSORS_TIMESTAMP);
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
+@@ -10,14 +10,15 @@
+ #include <linux/regmap.h>
+ #include <linux/delay.h>
+ #include <linux/math64.h>
+-#include <linux/iio/iio.h>
++
+ #include <linux/iio/buffer.h>
++#include <linux/iio/common/inv_icm42600_timestamp.h>
++#include <linux/iio/iio.h>
+ #include <linux/iio/kfifo_buf.h>
+
+ #include "inv_icm42600.h"
+ #include "inv_icm42600_temp.h"
+ #include "inv_icm42600_buffer.h"
+-#include "inv_icm42600_timestamp.h"
+
+ #define INV_ICM42600_GYRO_CHAN(_modifier, _index, _ext_info) \
+ { \
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c
++++ /dev/null
+@@ -1,183 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0-or-later
+-/*
+- * Copyright (C) 2020 Invensense, Inc.
+- */
+-
+-#include <linux/errno.h>
+-#include <linux/kernel.h>
+-#include <linux/math64.h>
+-
+-#include "inv_icm42600_timestamp.h"
+-
+-/* internal chip period is 32kHz, 31250ns */
+-#define INV_ICM42600_TIMESTAMP_PERIOD 31250
+-/* allow a jitter of +/- 2% */
+-#define INV_ICM42600_TIMESTAMP_JITTER 2
+-/* compute min and max periods accepted */
+-#define INV_ICM42600_TIMESTAMP_MIN_PERIOD(_p) \
+- (((_p) * (100 - INV_ICM42600_TIMESTAMP_JITTER)) / 100)
+-#define INV_ICM42600_TIMESTAMP_MAX_PERIOD(_p) \
+- (((_p) * (100 + INV_ICM42600_TIMESTAMP_JITTER)) / 100)
+-
+-/* Add a new value inside an accumulator and update the estimate value */
+-static void inv_update_acc(struct inv_icm42600_timestamp_acc *acc, uint32_t val)
+-{
+- uint64_t sum = 0;
+- size_t i;
+-
+- acc->values[acc->idx++] = val;
+- if (acc->idx >= ARRAY_SIZE(acc->values))
+- acc->idx = 0;
+-
+- /* compute the mean of all stored values, use 0 as empty slot */
+- for (i = 0; i < ARRAY_SIZE(acc->values); ++i) {
+- if (acc->values[i] == 0)
+- break;
+- sum += acc->values[i];
+- }
+-
+- acc->val = div_u64(sum, i);
+-}
+-
+-void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts,
+- uint32_t period)
+-{
+- /* initial odr for sensor after reset is 1kHz */
+- const uint32_t default_period = 1000000;
+-
+- /* current multiplier and period values after reset */
+- ts->mult = default_period / INV_ICM42600_TIMESTAMP_PERIOD;
+- ts->period = default_period;
+- /* new set multiplier is the one from chip initialization */
+- ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD;
+-
+- /* use theoretical value for chip period */
+- inv_update_acc(&ts->chip_period, INV_ICM42600_TIMESTAMP_PERIOD);
+-}
+-
+-int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts,
+- uint32_t period, bool fifo)
+-{
+- /* when FIFO is on, prevent odr change if one is already pending */
+- if (fifo && ts->new_mult != 0)
+- return -EAGAIN;
+-
+- ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD;
+-
+- return 0;
+-}
+-
+-static bool inv_validate_period(uint32_t period, uint32_t mult)
+-{
+- const uint32_t chip_period = INV_ICM42600_TIMESTAMP_PERIOD;
+- uint32_t period_min, period_max;
+-
+- /* check that period is acceptable */
+- period_min = INV_ICM42600_TIMESTAMP_MIN_PERIOD(chip_period) * mult;
+- period_max = INV_ICM42600_TIMESTAMP_MAX_PERIOD(chip_period) * mult;
+- if (period > period_min && period < period_max)
+- return true;
+- else
+- return false;
+-}
+-
+-static bool inv_compute_chip_period(struct inv_icm42600_timestamp *ts,
+- uint32_t mult, uint32_t period)
+-{
+- uint32_t new_chip_period;
+-
+- if (!inv_validate_period(period, mult))
+- return false;
+-
+- /* update chip internal period estimation */
+- new_chip_period = period / mult;
+- inv_update_acc(&ts->chip_period, new_chip_period);
+-
+- return true;
+-}
+-
+-void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts,
+- uint32_t fifo_period, size_t fifo_nb,
+- size_t sensor_nb, int64_t timestamp)
+-{
+- struct inv_icm42600_timestamp_interval *it;
+- int64_t delta, interval;
+- const uint32_t fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD;
+- uint32_t period = ts->period;
+- int32_t m;
+- bool valid = false;
+-
+- if (fifo_nb == 0)
+- return;
+-
+- /* update interrupt timestamp and compute chip and sensor periods */
+- it = &ts->it;
+- it->lo = it->up;
+- it->up = timestamp;
+- delta = it->up - it->lo;
+- if (it->lo != 0) {
+- /* compute period: delta time divided by number of samples */
+- period = div_s64(delta, fifo_nb);
+- valid = inv_compute_chip_period(ts, fifo_mult, period);
+- /* update sensor period if chip internal period is updated */
+- if (valid)
+- ts->period = ts->mult * ts->chip_period.val;
+- }
+-
+- /* no previous data, compute theoritical value from interrupt */
+- if (ts->timestamp == 0) {
+- /* elapsed time: sensor period * sensor samples number */
+- interval = (int64_t)ts->period * (int64_t)sensor_nb;
+- ts->timestamp = it->up - interval;
+- return;
+- }
+-
+- /* if interrupt interval is valid, sync with interrupt timestamp */
+- if (valid) {
+- /* compute measured fifo_period */
+- fifo_period = fifo_mult * ts->chip_period.val;
+- /* delta time between last sample and last interrupt */
+- delta = it->lo - ts->timestamp;
+- /* if there are multiple samples, go back to first one */
+- while (delta >= (fifo_period * 3 / 2))
+- delta -= fifo_period;
+- /* compute maximal adjustment value */
+- m = INV_ICM42600_TIMESTAMP_MAX_PERIOD(ts->period) - ts->period;
+- if (delta > m)
+- delta = m;
+- else if (delta < -m)
+- delta = -m;
+- ts->timestamp += delta;
+- }
+-}
+-
+-void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts,
+- uint32_t fifo_period, size_t fifo_nb,
+- unsigned int fifo_no)
+-{
+- int64_t interval;
+- uint32_t fifo_mult;
+-
+- if (ts->new_mult == 0)
+- return;
+-
+- /* update to new multiplier and update period */
+- ts->mult = ts->new_mult;
+- ts->new_mult = 0;
+- ts->period = ts->mult * ts->chip_period.val;
+-
+- /*
+- * After ODR change the time interval with the previous sample is
+- * undertermined (depends when the change occures). So we compute the
+- * timestamp from the current interrupt using the new FIFO period, the
+- * total number of samples and the current sample numero.
+- */
+- if (ts->timestamp != 0) {
+- /* compute measured fifo period */
+- fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD;
+- fifo_period = fifo_mult * ts->chip_period.val;
+- /* computes time interval between interrupt and this sample */
+- interval = (int64_t)(fifo_nb - fifo_no) * (int64_t)fifo_period;
+- ts->timestamp = ts->it.up - interval;
+- }
+-}
+--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h
++++ /dev/null
+@@ -1,79 +0,0 @@
+-/* SPDX-License-Identifier: GPL-2.0-or-later */
+-/*
+- * Copyright (C) 2020 Invensense, Inc.
+- */
+-
+-#ifndef INV_ICM42600_TIMESTAMP_H_
+-#define INV_ICM42600_TIMESTAMP_H_
+-
+-/**
+- * struct inv_icm42600_timestamp_interval - timestamps interval
+- * @lo: interval lower bound
+- * @up: interval upper bound
+- */
+-struct inv_icm42600_timestamp_interval {
+- int64_t lo;
+- int64_t up;
+-};
+-
+-/**
+- * struct inv_icm42600_timestamp_acc - accumulator for computing an estimation
+- * @val: current estimation of the value, the mean of all values
+- * @idx: current index of the next free place in values table
+- * @values: table of all measured values, use for computing the mean
+- */
+-struct inv_icm42600_timestamp_acc {
+- uint32_t val;
+- size_t idx;
+- uint32_t values[32];
+-};
+-
+-/**
+- * struct inv_icm42600_timestamp - timestamp management states
+- * @it: interrupts interval timestamps
+- * @timestamp: store last timestamp for computing next data timestamp
+- * @mult: current internal period multiplier
+- * @new_mult: new set internal period multiplier (not yet effective)
+- * @period: measured current period of the sensor
+- * @chip_period: accumulator for computing internal chip period
+- */
+-struct inv_icm42600_timestamp {
+- struct inv_icm42600_timestamp_interval it;
+- int64_t timestamp;
+- uint32_t mult;
+- uint32_t new_mult;
+- uint32_t period;
+- struct inv_icm42600_timestamp_acc chip_period;
+-};
+-
+-void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts,
+- uint32_t period);
+-
+-int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts,
+- uint32_t period, bool fifo);
+-
+-void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts,
+- uint32_t fifo_period, size_t fifo_nb,
+- size_t sensor_nb, int64_t timestamp);
+-
+-static inline int64_t
+-inv_icm42600_timestamp_pop(struct inv_icm42600_timestamp *ts)
+-{
+- ts->timestamp += ts->period;
+- return ts->timestamp;
+-}
+-
+-void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts,
+- uint32_t fifo_period, size_t fifo_nb,
+- unsigned int fifo_no);
+-
+-static inline void
+-inv_icm42600_timestamp_reset(struct inv_icm42600_timestamp *ts)
+-{
+- const struct inv_icm42600_timestamp_interval interval_init = {0LL, 0LL};
+-
+- ts->it = interval_init;
+- ts->timestamp = 0;
+-}
+-
+-#endif
+--- /dev/null
++++ b/include/linux/iio/common/inv_icm42600_timestamp.h
+@@ -0,0 +1,79 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Copyright (C) 2020 Invensense, Inc.
++ */
++
++#ifndef INV_ICM42600_TIMESTAMP_H_
++#define INV_ICM42600_TIMESTAMP_H_
++
++/**
++ * struct inv_icm42600_timestamp_interval - timestamps interval
++ * @lo: interval lower bound
++ * @up: interval upper bound
++ */
++struct inv_icm42600_timestamp_interval {
++ int64_t lo;
++ int64_t up;
++};
++
++/**
++ * struct inv_icm42600_timestamp_acc - accumulator for computing an estimation
++ * @val: current estimation of the value, the mean of all values
++ * @idx: current index of the next free place in values table
++ * @values: table of all measured values, use for computing the mean
++ */
++struct inv_icm42600_timestamp_acc {
++ uint32_t val;
++ size_t idx;
++ uint32_t values[32];
++};
++
++/**
++ * struct inv_icm42600_timestamp - timestamp management states
++ * @it: interrupts interval timestamps
++ * @timestamp: store last timestamp for computing next data timestamp
++ * @mult: current internal period multiplier
++ * @new_mult: new set internal period multiplier (not yet effective)
++ * @period: measured current period of the sensor
++ * @chip_period: accumulator for computing internal chip period
++ */
++struct inv_icm42600_timestamp {
++ struct inv_icm42600_timestamp_interval it;
++ int64_t timestamp;
++ uint32_t mult;
++ uint32_t new_mult;
++ uint32_t period;
++ struct inv_icm42600_timestamp_acc chip_period;
++};
++
++void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts,
++ uint32_t period);
++
++int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts,
++ uint32_t period, bool fifo);
++
++void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts,
++ uint32_t fifo_period, size_t fifo_nb,
++ size_t sensor_nb, int64_t timestamp);
++
++static inline int64_t
++inv_icm42600_timestamp_pop(struct inv_icm42600_timestamp *ts)
++{
++ ts->timestamp += ts->period;
++ return ts->timestamp;
++}
++
++void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts,
++ uint32_t fifo_period, size_t fifo_nb,
++ unsigned int fifo_no);
++
++static inline void
++inv_icm42600_timestamp_reset(struct inv_icm42600_timestamp *ts)
++{
++ const struct inv_icm42600_timestamp_interval interval_init = {0LL, 0LL};
++
++ ts->it = interval_init;
++ ts->timestamp = 0;
++}
++
++#endif
--- /dev/null
+From stable+bounces-276760-greg=kroah.com@vger.kernel.org Thu Jul 16 18:51:03 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 11:37:03 -0400
+Subject: ksmbd: centralize ksmbd_conn final release to plug transport leak
+To: stable@vger.kernel.org
+Cc: DaeMyung Kang <charsyam@gmail.com>, Namjae Jeon <linkinjeon@kernel.org>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716153704.583869-6-sashal@kernel.org>
+
+From: DaeMyung Kang <charsyam@gmail.com>
+
+[ Upstream commit b1f1e80620deb49daf63c2e677046599b693dc1f ]
+
+ksmbd_conn_free() is one of four sites that can observe the last
+refcount drop of a struct ksmbd_conn. The other three
+
+ fs/smb/server/connection.c ksmbd_conn_r_count_dec()
+ fs/smb/server/oplock.c __free_opinfo()
+ fs/smb/server/vfs_cache.c session_fd_check()
+
+end the conn with a bare kfree(), skipping
+ida_destroy(&conn->async_ida) and
+conn->transport->ops->free_transport(conn->transport). Whenever one
+of them is the last putter, the embedded async_ida and the entire
+transport struct leak -- for TCP, that is also the struct socket and
+the kvec iov.
+
+__free_opinfo() being a final putter is not theoretical. opinfo_put()
+queues the callback via call_rcu(&opinfo->rcu, free_opinfo_rcu), so
+ksmbd_server_terminate_conn() can deposit N opinfo releases in RCU and
+have ksmbd_conn_free() run in the handler thread before any of them
+fire. ksmbd_conn_free() then observes refcnt > 0 and short-circuits;
+the last RCU-delivered __free_opinfo() falls onto its bare kfree(conn)
+branch and the transport is lost.
+
+A/B validation in a QEMU/virtme guest, mounting //127.0.0.1/testshare:
+each iteration holds 8 files open via sleep processes, force-closes
+TCP with "ss -K sport = :445", kills the holders, lazy-umounts;
+repeated 10 times, then ksmbd shutdown and kmemleak scan.
+
+ state conn_alloc conn_free tcp_free opi_rcu kmemleak
+ ---------- ---------- --------- -------- ------- --------
+ pre-patch 20 20 10 160 7
+ with patch 20 20 20 160 0
+
+Pre-patch conn_free=20 with tcp_free=10 directly demonstrates the
+bare-kfree paths skipping transport cleanup; kmemleak backtraces point
+into struct tcp_transport / iov. With this patch tcp_free matches
+conn_free at 20/20 and kmemleak is clean.
+
+Move the per-struct final release into __ksmbd_conn_release_work() and
+route the three bare-kfree final-put sites through a new
+ksmbd_conn_put(). Those sites now pair ida_destroy() and
+free_transport() with kfree(conn) regardless of which holder happens
+to release the last reference. stop_sessions() only triggers the
+transport shutdown and does not itself drop the last conn reference,
+so it is unaffected.
+
+The centralized release reaches sock_release() -> tcp_close() ->
+lock_sock_nested() (might_sleep) from every final putter, including
+__free_opinfo() invoked from an RCU softirq callback, which trips
+CONFIG_DEBUG_ATOMIC_SLEEP. Defer the release to a dedicated
+ksmbd_conn_wq workqueue so ksmbd_conn_put() is safe from any
+non-sleeping context.
+
+Make ksmbd_file own a strong connection reference while fp->conn is
+non-NULL so durable-preserve and final-close paths cannot dereference
+a stale connection. ksmbd_open_fd() and ksmbd_reopen_durable_fd()
+take the reference via ksmbd_conn_get() (the latter also reorders the
+fp->conn / fp->tcon assignments before __open_id() so the published fp
+is never observed with fp->conn == NULL); session_fd_check() and
+__ksmbd_close_fd() drop it via ksmbd_conn_put(). With that invariant,
+session_fd_check() can take a local conn pointer once and use it
+across the m_op_list and lock_list iterations even though op->conn
+puts may otherwise drop the last reference.
+
+At module exit the workqueue is flushed and destroyed after
+rcu_barrier(), so any release queued by a trailing RCU callback is
+drained before the inode hash and module text go away.
+
+Fixes: ee426bfb9d09 ("ksmbd: add refcnt to ksmbd_conn struct")
+Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/connection.c | 49 +++++++++++++++++++++++++++++++++++++++++++--
+ fs/smb/server/connection.h | 3 ++
+ fs/smb/server/oplock.c | 4 +--
+ fs/smb/server/vfs_cache.c | 18 +++++++++++++++-
+ 4 files changed, 69 insertions(+), 5 deletions(-)
+
+--- a/fs/smb/server/connection.c
++++ b/fs/smb/server/connection.c
+@@ -23,6 +23,43 @@ LIST_HEAD(conn_list);
+ DECLARE_RWSEM(conn_list_lock);
+
+ /**
++ * ksmbd_conn_get() - take a reference on @conn and return it.
++ *
++ * Returns @conn unchanged so callers can write
++ * "fp->conn = ksmbd_conn_get(work->conn);" in one expression. Returns NULL
++ * if @conn is NULL.
++ */
++struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn)
++{
++ if (!conn)
++ return NULL;
++
++ atomic_inc(&conn->refcnt);
++ return conn;
++}
++
++/**
++ * ksmbd_conn_put() - drop a reference and, if it was the last, perform the
++ * final, once-per-struct release of a ksmbd_conn.
++ *
++ * The transport is not released here: free_transport() releases it after
++ * calling ksmbd_conn_free(). The final release therefore only pairs
++ * ida_destroy() with kfree(), reaches no sleeping teardown and is safe
++ * from any context, including the RCU callback that frees an opinfo
++ * holding a connection reference.
++ */
++void ksmbd_conn_put(struct ksmbd_conn *conn)
++{
++ if (!conn)
++ return;
++
++ if (atomic_dec_and_test(&conn->refcnt)) {
++ ida_destroy(&conn->async_ida);
++ kfree(conn);
++ }
++}
++
++/**
+ * ksmbd_conn_free() - free resources of the connection instance
+ *
+ * @conn: connection instance to be cleand up
+@@ -36,12 +73,19 @@ void ksmbd_conn_free(struct ksmbd_conn *
+ list_del(&conn->conns_list);
+ up_write(&conn_list_lock);
+
++ /*
++ * request_buf / preauth_info / mechToken are only ever accessed by the
++ * connection handler thread that owns @conn. ksmbd_conn_free() is
++ * called from the transport free_transport() path when that thread is
++ * exiting, so it is safe to release them unconditionally even when
++ * ksmbd_conn_put() below is not the final putter (oplock / ksmbd_file
++ * holders only retain the conn pointer, not these per-thread buffers).
++ */
+ xa_destroy(&conn->sessions);
+ kvfree(conn->request_buf);
+ kfree(conn->preauth_info);
+ kfree(conn->mechToken);
+- ida_destroy(&conn->async_ida);
+- kfree(conn);
++ ksmbd_conn_put(conn);
+ }
+
+ /**
+@@ -70,6 +114,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void
+ conn->um = NULL;
+ atomic_set(&conn->req_running, 0);
+ atomic_set(&conn->r_count, 0);
++ atomic_set(&conn->refcnt, 1);
+ conn->total_credits = 1;
+ conn->outstanding_credits = 0;
+
+--- a/fs/smb/server/connection.h
++++ b/fs/smb/server/connection.h
+@@ -112,6 +112,7 @@ struct ksmbd_conn {
+ bool signing_negotiated;
+ __le16 signing_algorithm;
+ bool binding;
++ atomic_t refcnt;
+ };
+
+ struct ksmbd_conn_ops {
+@@ -154,6 +155,8 @@ bool ksmbd_conn_alive(struct ksmbd_conn
+ void ksmbd_conn_wait_idle(struct ksmbd_conn *conn, u64 sess_id);
+ struct ksmbd_conn *ksmbd_conn_alloc(void);
+ void ksmbd_conn_free(struct ksmbd_conn *conn);
++struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn);
++void ksmbd_conn_put(struct ksmbd_conn *conn);
+ bool ksmbd_conn_lookup_dialect(struct ksmbd_conn *c);
+ int ksmbd_conn_write(struct ksmbd_work *work);
+ int ksmbd_conn_rdma_read(struct ksmbd_conn *conn,
+--- a/fs/smb/server/oplock.c
++++ b/fs/smb/server/oplock.c
+@@ -30,7 +30,6 @@ static DEFINE_RWLOCK(lease_list_lock);
+ static struct oplock_info *alloc_opinfo(struct ksmbd_work *work,
+ u64 id, __u16 Tid)
+ {
+- struct ksmbd_conn *conn = work->conn;
+ struct ksmbd_session *sess = work->sess;
+ struct oplock_info *opinfo;
+
+@@ -39,7 +38,7 @@ static struct oplock_info *alloc_opinfo(
+ return NULL;
+
+ opinfo->sess = sess;
+- opinfo->conn = conn;
++ opinfo->conn = ksmbd_conn_get(work->conn);
+ opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
+ opinfo->op_state = OPLOCK_STATE_NONE;
+ opinfo->pending_break = 0;
+@@ -124,6 +123,7 @@ static void free_opinfo(struct oplock_in
+ {
+ if (opinfo->is_lease)
+ free_lease(opinfo);
++ ksmbd_conn_put(opinfo->conn);
+ kfree(opinfo);
+ }
+
+--- a/fs/smb/server/vfs_cache.c
++++ b/fs/smb/server/vfs_cache.c
+@@ -328,6 +328,14 @@ static void __ksmbd_close_fd(struct ksmb
+ kfree(smb_lock);
+ }
+
++ /*
++ * Drop fp's strong reference on conn (taken in ksmbd_open_fd()).
++ */
++ if (fp->conn) {
++ ksmbd_conn_put(fp->conn);
++ fp->conn = NULL;
++ }
++
+ if (ksmbd_stream_fd(fp))
+ kfree(fp->stream.name);
+ kmem_cache_free(filp_cache, fp);
+@@ -580,7 +588,13 @@ struct ksmbd_file *ksmbd_open_fd(struct
+ atomic_set(&fp->refcount, 1);
+
+ fp->filp = filp;
+- fp->conn = work->conn;
++ /*
++ * fp owns a strong reference on fp->conn for as long as fp->conn is
++ * non-NULL, so __ksmbd_close_fd() never dereferences a dangling
++ * pointer. Paired with ksmbd_conn_put() in __ksmbd_close_fd()
++ * (final close) and on the error paths below.
++ */
++ fp->conn = ksmbd_conn_get(work->conn);
+ fp->tcon = work->tcon;
+ fp->volatile_id = KSMBD_NO_FID;
+ fp->persistent_id = KSMBD_NO_FID;
+@@ -602,6 +616,8 @@ struct ksmbd_file *ksmbd_open_fd(struct
+ return fp;
+
+ err_out:
++ /* fp->conn was set and refcounted before every branch here. */
++ ksmbd_conn_put(fp->conn);
+ kmem_cache_free(filp_cache, fp);
+ return ERR_PTR(ret);
+ }
--- /dev/null
+From stable+bounces-276759-greg=kroah.com@vger.kernel.org Thu Jul 16 18:52:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 11:37:02 -0400
+Subject: ksmbd: destroy async_ida in ksmbd_conn_free()
+To: stable@vger.kernel.org
+Cc: DaeMyung Kang <charsyam@gmail.com>, Namjae Jeon <linkinjeon@kernel.org>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716153704.583869-5-sashal@kernel.org>
+
+From: DaeMyung Kang <charsyam@gmail.com>
+
+[ Upstream commit b32c8db48212a34998c36d0bbc05b29d5c407ef5 ]
+
+When per-connection async_ida was converted from a dynamically
+allocated ksmbd_ida to an embedded struct ida, ksmbd_ida_free() was
+removed from the connection teardown path but no matching
+ida_destroy() was added. The connection is therefore freed with the
+IDA's backing xarray still intact.
+
+The kernel IDA API expects ida_init() and ida_destroy() to be paired
+over an object's lifetime, so add the missing cleanup before the
+connection is freed.
+
+No leak has been observed in testing; this is a pairing fix to match
+the IDA lifetime rules, not a response to a reproduced regression.
+
+Fixes: d40012a83f87 ("cifsd: declare ida statically")
+Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/connection.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/fs/smb/server/connection.c
++++ b/fs/smb/server/connection.c
+@@ -40,6 +40,7 @@ void ksmbd_conn_free(struct ksmbd_conn *
+ kvfree(conn->request_buf);
+ kfree(conn->preauth_info);
+ kfree(conn->mechToken);
++ ida_destroy(&conn->async_ida);
+ kfree(conn);
+ }
+
--- /dev/null
+From stable+bounces-276758-greg=kroah.com@vger.kernel.org Thu Jul 16 17:37:21 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 11:37:01 -0400
+Subject: ksmbd: fix mechToken leak when SPNEGO decode fails after token alloc
+To: stable@vger.kernel.org
+Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Namjae Jeon <linkinjeon@kernel.org>, Steve French <smfrench@gmail.com>, Sergey Senozhatsky <senozhatsky@chromium.org>, Tom Talpey <tom@talpey.com>, linux-cifs@vger.kernel.org, stable@kernel.org, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716153704.583869-4-sashal@kernel.org>
+
+From: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+[ Upstream commit ad0057fb91218914d6c98268718ceb9d59b388e1 ]
+
+The kernel ASN.1 BER decoder calls action callbacks incrementally as it
+walks the input. When ksmbd_decode_negTokenInit() reaches the mechToken
+[2] OCTET STRING element, ksmbd_neg_token_alloc() allocates
+conn->mechToken immediately via kmemdup_nul(). If a later element in
+the same blob is malformed, then the decoder will return nonzero after
+the allocation is already live. This could happen if mechListMIC [3]
+overrunse the enclosing SEQUENCE.
+
+decode_negotiation_token() then sets conn->use_spnego = false because
+both the negTokenInit and negTokenTarg grammars failed. The cleanup at
+the bottom of smb2_sess_setup() is gated on use_spnego:
+
+ if (conn->use_spnego && conn->mechToken) {
+ kfree(conn->mechToken);
+ conn->mechToken = NULL;
+ }
+
+so the kfree is skipped, causing the mechToken to never be freed.
+
+This codepath is reachable pre-authentication, so untrusted clients can
+cause slow memory leaks on a server without even being properly
+authenticated.
+
+Fix this up by not checking check for use_spnego, as it's not required,
+so the memory will always be properly freed. At the same time, always
+free the memory in ksmbd_conn_free() incase some other failure path
+forgot to free it.
+
+Cc: Namjae Jeon <linkinjeon@kernel.org>
+Cc: Steve French <smfrench@gmail.com>
+Cc: Sergey Senozhatsky <senozhatsky@chromium.org>
+Cc: Tom Talpey <tom@talpey.com>
+Cc: linux-cifs@vger.kernel.org
+Cc: <stable@kernel.org>
+Assisted-by: gregkh_clanker_t1000
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/connection.c | 1 +
+ fs/smb/server/smb2pdu.c | 2 +-
+ 2 files changed, 2 insertions(+), 1 deletion(-)
+
+--- a/fs/smb/server/connection.c
++++ b/fs/smb/server/connection.c
+@@ -39,6 +39,7 @@ void ksmbd_conn_free(struct ksmbd_conn *
+ xa_destroy(&conn->sessions);
+ kvfree(conn->request_buf);
+ kfree(conn->preauth_info);
++ kfree(conn->mechToken);
+ kfree(conn);
+ }
+
+--- a/fs/smb/server/smb2pdu.c
++++ b/fs/smb/server/smb2pdu.c
+@@ -1909,7 +1909,7 @@ out_err:
+ else if (rc)
+ rsp->hdr.Status = STATUS_LOGON_FAILURE;
+
+- if (conn->use_spnego && conn->mechToken) {
++ if (conn->mechToken) {
+ kfree(conn->mechToken);
+ conn->mechToken = NULL;
+ }
--- /dev/null
+From stable+bounces-276757-greg=kroah.com@vger.kernel.org Thu Jul 16 17:37:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 11:37:00 -0400
+Subject: ksmbd: fix use-after-free in __ksmbd_close_fd() via durable scavenger
+To: stable@vger.kernel.org
+Cc: Namjae Jeon <linkinjeon@kernel.org>, munan Huang <munanevil@gmail.com>, ChenXiaoSong <chenxiaosong@kylinos.cn>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716153704.583869-3-sashal@kernel.org>
+
+From: Namjae Jeon <linkinjeon@kernel.org>
+
+[ Upstream commit 235e32320a470fcd3998fb3774f2290a0eb302a1 ]
+
+When a durable file handle survives session disconnect (TCP close without
+SMB2_LOGOFF), session_fd_check() sets fp->conn = NULL to preserve the
+handle for later reconnection. However, it did not clean up the byte-range
+locks on fp->lock_list.
+
+Later, when the durable scavenger thread times out and calls
+__ksmbd_close_fd(NULL, fp), the lock cleanup loop did:
+
+ spin_lock(&fp->conn->llist_lock);
+
+This caused a slab use-after-free because fp->conn was NULL and the
+original connection object had already been freed by
+ksmbd_tcp_disconnect().
+
+The root cause is asymmetric cleanup: lock entries (smb_lock->clist) were
+left dangling on the freed conn->lock_list while fp->conn was nulled out.
+
+To fix this issue properly, we need to handle the lifetime of
+smb_lock->clist across three paths:
+ - Safely skip clist deletion when list is empty and fp->conn is NULL.
+ - Remove the lock from the old connection's lock_list in
+ session_fd_check()
+ - Re-add the lock to the new connection's lock_list in
+ ksmbd_reopen_durable_fd().
+
+Fixes: c8efcc786146 ("ksmbd: add support for durable handles v1/v2")
+Co-developed-by: munan Huang <munanevil@gmail.com>
+Signed-off-by: munan Huang <munanevil@gmail.com>
+Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/vfs_cache.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+--- a/fs/smb/server/vfs_cache.c
++++ b/fs/smb/server/vfs_cache.c
+@@ -317,9 +317,11 @@ static void __ksmbd_close_fd(struct ksmb
+ * there are not accesses to fp->lock_list.
+ */
+ list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
+- spin_lock(&fp->conn->llist_lock);
+- list_del(&smb_lock->clist);
+- spin_unlock(&fp->conn->llist_lock);
++ if (!list_empty(&smb_lock->clist) && fp->conn) {
++ spin_lock(&fp->conn->llist_lock);
++ list_del(&smb_lock->clist);
++ spin_unlock(&fp->conn->llist_lock);
++ }
+
+ list_del(&smb_lock->flist);
+ locks_free_lock(smb_lock->fl);
--- /dev/null
+From stable+bounces-276761-greg=kroah.com@vger.kernel.org Thu Jul 16 17:37:27 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 11:37:04 -0400
+Subject: ksmbd: track the connection owning a byte-range lock
+To: stable@vger.kernel.org
+Cc: Namjae Jeon <linkinjeon@kernel.org>, Musaab Khan <musaab.khan@protonmail.com>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716153704.583869-7-sashal@kernel.org>
+
+From: Namjae Jeon <linkinjeon@kernel.org>
+
+[ Upstream commit c1016dd1d8b2bcd1158bbaabe94a31bb7e7431fb ]
+
+SMB2_LOCK adds each granted byte-range lock to both the file lock list
+and the lock list of the connection which handled the request. The
+final close and durable handle paths, however, remove the connection
+list entry while holding fp->conn->llist_lock.
+
+With SMB3 multichannel, the connection handling the LOCK request can be
+different from the connection which opened the file. The entry can
+therefore be removed under a different spinlock from the one protecting
+the list it belongs to. A concurrent traversal can then access freed
+struct ksmbd_lock and struct file_lock objects.
+
+Record the connection owning each lock's clist entry and hold a
+reference to it while the entry is linked. Use that connection and its
+llist_lock for unlock, rollback, close, and durable preserve. Durable
+reconnect assigns the new connection as the owner when publishing the
+locks again.
+
+Fixes: f5a544e3bab7 ("ksmbd: add support for SMB3 multichannel")
+Cc: stable@vger.kernel.org
+Reported-by: Musaab Khan <musaab.khan@protonmail.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/smb2pdu.c | 10 ++++++++--
+ fs/smb/server/vfs_cache.c | 12 ++++++++----
+ fs/smb/server/vfs_cache.h | 1 +
+ 3 files changed, 17 insertions(+), 6 deletions(-)
+
+--- a/fs/smb/server/smb2pdu.c
++++ b/fs/smb/server/smb2pdu.c
+@@ -7136,9 +7136,11 @@ int smb2_lock(struct ksmbd_work *work)
+ nolock = 0;
+ list_del(&cmp_lock->flist);
+ list_del(&cmp_lock->clist);
++ cmp_lock->conn = NULL;
+ spin_unlock(&conn->llist_lock);
+ up_read(&conn_list_lock);
+
++ ksmbd_conn_put(conn);
+ locks_free_lock(cmp_lock->fl);
+ kfree(cmp_lock);
+ goto out_check_cl;
+@@ -7273,6 +7275,7 @@ skip:
+ goto out2;
+ } else if (!rc) {
+ list_add(&smb_lock->llist, &rollback_list);
++ smb_lock->conn = ksmbd_conn_get(work->conn);
+ spin_lock(&work->conn->llist_lock);
+ list_add_tail(&smb_lock->clist,
+ &work->conn->lock_list);
+@@ -7327,11 +7330,14 @@ out:
+ }
+
+ list_del(&smb_lock->llist);
+- spin_lock(&work->conn->llist_lock);
++ conn = smb_lock->conn;
++ spin_lock(&conn->llist_lock);
+ if (!list_empty(&smb_lock->flist))
+ list_del(&smb_lock->flist);
+ list_del(&smb_lock->clist);
+- spin_unlock(&work->conn->llist_lock);
++ smb_lock->conn = NULL;
++ spin_unlock(&conn->llist_lock);
++ ksmbd_conn_put(conn);
+
+ locks_free_lock(smb_lock->fl);
+ if (rlock)
+--- a/fs/smb/server/vfs_cache.c
++++ b/fs/smb/server/vfs_cache.c
+@@ -317,10 +317,14 @@ static void __ksmbd_close_fd(struct ksmb
+ * there are not accesses to fp->lock_list.
+ */
+ list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
+- if (!list_empty(&smb_lock->clist) && fp->conn) {
+- spin_lock(&fp->conn->llist_lock);
+- list_del(&smb_lock->clist);
+- spin_unlock(&fp->conn->llist_lock);
++ struct ksmbd_conn *conn = smb_lock->conn;
++
++ if (conn) {
++ spin_lock(&conn->llist_lock);
++ list_del_init(&smb_lock->clist);
++ smb_lock->conn = NULL;
++ spin_unlock(&conn->llist_lock);
++ ksmbd_conn_put(conn);
+ }
+
+ list_del(&smb_lock->flist);
+--- a/fs/smb/server/vfs_cache.h
++++ b/fs/smb/server/vfs_cache.h
+@@ -31,6 +31,7 @@ struct ksmbd_session;
+
+ struct ksmbd_lock {
+ struct file_lock *fl;
++ struct ksmbd_conn *conn;
+ struct list_head clist;
+ struct list_head flist;
+ struct list_head llist;
--- /dev/null
+From stable+bounces-276102-greg=kroah.com@vger.kernel.org Thu Jul 16 16:15:46 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 10:07:47 -0400
+Subject: ksmbd: use opener credentials for FSCTL mutations
+To: stable@vger.kernel.org
+Cc: Namjae Jeon <linkinjeon@kernel.org>, Musaab Khan <musaab.khan@protonmail.com>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716140747.405713-1-sashal@kernel.org>
+
+From: Namjae Jeon <linkinjeon@kernel.org>
+
+[ Upstream commit c6394bcaf254c5baf9aff43376020be5db6d3316 ]
+
+SET_SPARSE, SET_ZERO_DATA and SET_COMPRESSION operate on an open SMB
+handle but call VFS xattr, fallocate or fileattr helpers with the current
+ksmbd worker credentials. Those helpers can revalidate inode permissions,
+ownership and LSM policy independently of the SMB handle access mask.
+
+Run each operation with the credentials captured in the target file when
+the handle was opened. Keep credential handling local to these single-file
+FSCTLs rather than applying session credentials to the complete IOCTL
+handler, which also contains handle-less and multi-handle operations.
+
+Cc: stable@vger.kernel.org
+Reported-by: Musaab Khan <musaab.khan@protonmail.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/smb2pdu.c | 3 +++
+ fs/smb/server/vfs.c | 20 +++++++++++++-------
+ 2 files changed, 16 insertions(+), 7 deletions(-)
+
+--- a/fs/smb/server/smb2pdu.c
++++ b/fs/smb/server/smb2pdu.c
+@@ -7739,6 +7739,7 @@ static inline int fsctl_set_sparse(struc
+ if (fp->f_ci->m_fattr != old_fattr &&
+ test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_STORE_DOS_ATTRS)) {
++ const struct cred *saved_cred;
+ struct xattr_dos_attrib da;
+
+ ret = ksmbd_vfs_get_dos_attrib_xattr(user_ns,
+@@ -7747,9 +7748,11 @@ static inline int fsctl_set_sparse(struc
+ goto out;
+
+ da.attr = le32_to_cpu(fp->f_ci->m_fattr);
++ saved_cred = override_creds(fp->filp->f_cred);
+ ret = ksmbd_vfs_set_dos_attrib_xattr(user_ns,
+ &fp->filp->f_path,
+ &da, true);
++ revert_creds(saved_cred);
+ if (ret)
+ fp->f_ci->m_fattr = old_fattr;
+ }
+--- a/fs/smb/server/vfs.c
++++ b/fs/smb/server/vfs.c
+@@ -991,15 +991,21 @@ void ksmbd_vfs_set_fadvise(struct file *
+ int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp,
+ loff_t off, loff_t len)
+ {
++ const struct cred *saved_cred;
++ int err;
++
+ smb_break_all_levII_oplock(work, fp, 1);
++ saved_cred = override_creds(fp->filp->f_cred);
+ if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE)
+- return vfs_fallocate(fp->filp,
+- FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+- off, len);
+-
+- return vfs_fallocate(fp->filp,
+- FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE,
+- off, len);
++ err = vfs_fallocate(fp->filp,
++ FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
++ off, len);
++ else
++ err = vfs_fallocate(fp->filp,
++ FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE,
++ off, len);
++ revert_creds(saved_cred);
++ return err;
+ }
+
+ int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length,
--- /dev/null
+From stable+bounces-274985-greg=kroah.com@vger.kernel.org Wed Jul 15 18:34:54 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 12:30:06 -0400
+Subject: mm/shrinker: do not hold RCU lock in shrinker_debugfs_count_show()
+To: stable@vger.kernel.org
+Cc: Shakeel Butt <shakeel.butt@linux.dev>, Zenghui Yu <zenghui.yu@linux.dev>, Nhat Pham <nphamcs@gmail.com>, SeongJae Park <sj@kernel.org>, Qi Zheng <qi.zheng@linux.dev>, Muchun Song <muchun.song@linux.dev>, Roman Gushchin <roman.gushchin@linux.dev>, Dave Chinner <david@fromorbit.com>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715163006.950613-2-sashal@kernel.org>
+
+From: Shakeel Butt <shakeel.butt@linux.dev>
+
+[ Upstream commit b902890c62d200b3509cb5e09cf1e0a66553c128 ]
+
+Reading the debugfs "count" file of a memcg-aware shrinker can sleep
+inside an RCU read-side critical section:
+
+ BUG: sleeping function called from invalid context at kernel/cgroup/rstat.c:421
+ RCU nest depth: 1, expected: 0
+ css_rstat_flush
+ mem_cgroup_flush_stats
+ zswap_shrinker_count
+ shrinker_debugfs_count_show
+
+shrinker_debugfs_count_show() invokes the ->count_objects() callback under
+rcu_read_lock(). The zswap callback flushes memcg stats via
+css_rstat_flush(), which may sleep, so it must not run under RCU.
+
+The RCU lock is not needed here. mem_cgroup_iter() takes RCU internally
+and returns a memcg holding a css reference (dropped on the next iteration
+or by mem_cgroup_iter_break()), so the memcg stays alive without it. The
+shrinker is kept alive by the open debugfs file: shrinker_free() removes
+the debugfs entries via debugfs_remove_recursive(), which waits for
+in-flight readers to drain, before call_rcu(..., shrinker_free_rcu_cb).
+The sibling "scan" handler already invokes the sleeping ->scan_objects()
+callback with no RCU section.
+
+Drop the rcu_read_lock()/rcu_read_unlock().
+
+Link: https://lore.kernel.org/20260610232048.62930-1-shakeel.butt@linux.dev
+Fixes: 5035ebc644ae ("mm: shrinkers: introduce debugfs interface for memory shrinkers")
+Signed-off-by: Shakeel Butt <shakeel.butt@linux.dev>
+Reported-by: Zenghui Yu <zenghui.yu@linux.dev>
+Closes: https://lore.kernel.org/all/c052a064-cddb-494f-a0d8-f8a10b4b1c4d@linux.dev/
+Suggested-by: Nhat Pham <nphamcs@gmail.com>
+Reviewed-by: SeongJae Park <sj@kernel.org>
+Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
+Tested-by: Zenghui Yu (Huawei) <zenghui.yu@linux.dev>
+Reviewed-by: Nhat Pham <nphamcs@gmail.com>
+Acked-by: Muchun Song <muchun.song@linux.dev>
+Reviewed-by: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Dave Chinner <david@fromorbit.com>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ mm/shrinker_debug.c | 4 ----
+ 1 file changed, 4 deletions(-)
+
+--- a/mm/shrinker_debug.c
++++ b/mm/shrinker_debug.c
+@@ -55,8 +55,6 @@ static int shrinker_debugfs_count_show(s
+ if (!count_per_node)
+ return -ENOMEM;
+
+- rcu_read_lock();
+-
+ memcg_aware = shrinker->flags & SHRINKER_MEMCG_AWARE;
+
+ memcg = mem_cgroup_iter(NULL, NULL, NULL);
+@@ -86,8 +84,6 @@ static int shrinker_debugfs_count_show(s
+ }
+ } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL);
+
+- rcu_read_unlock();
+-
+ kfree(count_per_node);
+ return ret;
+ }
--- /dev/null
+From stable+bounces-275028-greg=kroah.com@vger.kernel.org Wed Jul 15 22:16:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 16:16:03 -0400
+Subject: mm: shrinker: fix NULL pointer dereference in debugfs
+To: stable@vger.kernel.org
+Cc: Qi Zheng <zhengqi.arch@bytedance.com>, Muchun Song <muchun.song@linux.dev>, Dave Chinner <david@fromorbit.com>, Roman Gushchin <roman.gushchin@linux.dev>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715201603.63084-2-sashal@kernel.org>
+
+From: Qi Zheng <zhengqi.arch@bytedance.com>
+
+[ Upstream commit e30453c61e185e914fde83c650e268067b140218 ]
+
+shrinker_debugfs_add() creates both "count" and "scan" debugfs files
+unconditionally.
+
+That assumes every shrinker implements both count_objects() and
+scan_objects(), which is not guaranteed. For example, the xen-backend
+shrinker sets count_objects() but leaves scan_objects() NULL, so writing
+to its scan file calls through a NULL function pointer and panics the
+kernel:
+
+BUG: kernel NULL pointer dereference, address: 0000000000000000
+RIP: 0010:0x0
+Code: Unable to access opcode bytes at 0xffffffffffffffd6.
+Call Trace:
+ <TASK>
+ shrinker_debugfs_scan_write+0x12e/0x270
+ full_proxy_write+0x5f/0x90
+ vfs_write+0xde/0x420
+ ? filp_flush+0x75/0x90
+ ? filp_close+0x1d/0x30
+ ? do_dup2+0xb8/0x120
+ ksys_write+0x68/0xf0
+ ? filp_flush+0x75/0x90
+ do_syscall_64+0xb3/0x5b0
+ entry_SYSCALL_64_after_hwframe+0x76/0x7e
+
+The count path has the same issue in principle if a shrinker omits
+count_objects().
+
+To fix it, only create "count" and "scan" debugfs files when the
+corresponding callbacks are present.
+
+Link: https://lore.kernel.org/20260617090052.27325-1-qi.zheng@linux.dev
+Fixes: bbf535fd6f06 ("mm: shrinkers: add scan interface for shrinker debugfs")
+Signed-off-by: Qi Zheng <zhengqi.arch@bytedance.com>
+Reviewed-by: Muchun Song <muchun.song@linux.dev>
+Cc: Dave Chinner <david@fromorbit.com>
+Cc: Qi Zheng <zhengqi.arch@bytedance.com>
+Cc: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ mm/shrinker_debug.c | 10 ++++++----
+ 1 file changed, 6 insertions(+), 4 deletions(-)
+
+--- a/mm/shrinker_debug.c
++++ b/mm/shrinker_debug.c
+@@ -180,10 +180,12 @@ int shrinker_debugfs_add(struct shrinker
+ }
+ shrinker->debugfs_entry = entry;
+
+- debugfs_create_file("count", 0440, entry, shrinker,
+- &shrinker_debugfs_count_fops);
+- debugfs_create_file("scan", 0220, entry, shrinker,
+- &shrinker_debugfs_scan_fops);
++ if (shrinker->count_objects)
++ debugfs_create_file("count", 0440, entry, shrinker,
++ &shrinker_debugfs_count_fops);
++ if (shrinker->scan_objects)
++ debugfs_create_file("scan", 0220, entry, shrinker,
++ &shrinker_debugfs_scan_fops);
+ return 0;
+ }
+
--- /dev/null
+From sashal@kernel.org Wed Jul 15 18:30:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 12:30:05 -0400
+Subject: mm: shrinker: remove redundant shrinker_rwsem in debugfs operations
+To: stable@vger.kernel.org
+Cc: "Qi Zheng" <zhengqi.arch@bytedance.com>, "Muchun Song" <songmuchun@bytedance.com>, "Christian Brauner" <brauner@kernel.org>, "Christian König" <christian.koenig@amd.com>, "Chuck Lever" <cel@kernel.org>, "Daniel Vetter" <daniel@ffwll.ch>, "Daniel Vetter" <daniel.vetter@ffwll.ch>, "Darrick J. Wong" <djwong@kernel.org>, "Dave Chinner" <david@fromorbit.com>, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>, "Joel Fernandes" <joel@joelfernandes.org>, "Kirill Tkhai" <tkhai@ya.ru>, "Paul E. McKenney" <paulmck@kernel.org>, "Roman Gushchin" <roman.gushchin@linux.dev>, "Sergey Senozhatsky" <senozhatsky@chromium.org>, "Steven Price" <steven.price@arm.com>, "Theodore Ts'o" <tytso@mit.edu>, "Vlastimil Babka" <vbabka@suse.cz>, "Abhinav Kumar" <quic_abhinavk@quicinc.com>, "Alasdair Kergon" <agk@redhat.com>, "Alexander Viro" <viro@zeniv.linux.org.uk>, "Alyssa Rosenzweig" <alyssa.rosenzweig@collabora.com>, "Andreas Dilger" <adilger.kernel@dilger.ca>, "Andreas Gruenbacher" <agruenba@redhat.com>, "Anna Schumaker" <anna@kernel.org>, "Arnd Bergmann" <arnd@arndb.de>, "Bob Peterson" <rpeterso@redhat.com>, "Borislav Petkov" <bp@alien8.de>, "Carlos Llamas" <cmllamas@google.com>, "Chandan Babu R" <chandan.babu@oracle.com>, "Chao Yu" <chao@kernel.org>, "Chris Mason" <clm@fb.com>, "Coly Li" <colyli@suse.de>, "Dai Ngo" <Dai.Ngo@oracle.com>, "Dave Hansen" <dave.hansen@linux.intel.com>, "David Airlie" <airlied@gmail.com>, "David Hildenbrand" <david@redhat.com>, "David Sterba" <dsterba@suse.com>, "Dmitry Baryshkov" <dmitry.baryshkov@linaro.org>, "Gao Xiang" <hsiangkao@linux.alibaba.com>, "Huang Rui" <ray.huang@amd.com>, "Ingo Molnar" <mingo@redhat.com>, "Jaegeuk Kim" <jaegeuk@kernel.org>, "Jani Nikula" <jani.nikula@linux.intel.com>, "Jan Kara" <jack@suse.cz>, "Jason Wang" <jasowang@redhat.com>, "Jeff Layton" <jlayton@kernel.org>, "Jeffle Xu" <jefflexu@linux.alibaba.com>, "Joonas Lahtinen" <joonas.lahtinen@linux.intel.com>, "Josef Bacik" <josef@toxicpanda.com>, "Juergen Gross" <jgross@suse.com>, "Kent Overstreet" <kent.overstreet@gmail.com>, "Marijn Suijten" <marijn.suijten@somainline.org>, "Michael S. Tsirkin" <mst@redhat.com>, "Mike Snitzer" <snitzer@kernel.org>, "Minchan Kim" <minchan@kernel.org>, "Muchun Song" <muchun.song@linux.dev>, "Nadav Amit" <namit@vmware.com>, "Neil Brown" <neilb@suse.de>, "Oleksandr Tyshchenko" <oleksandr_tyshchenko@epam.com>, "Olga Kornievskaia" <kolga@netapp.com>, "Richard Weinberger" <richard@nod.at>, "Rob Clark" <robdclark@gmail.com>, "Rob Herring" <robh@kernel.org>, "Rodrigo Vivi" <rodrigo.vivi@intel.com>, "Sean Paul" <sean@poorly.run>, "Song Liu" <song@kernel.org>, "Stefano Stabellini" <sstabellini@kernel.org>, "Thomas Gleixner" <tglx@linutronix.de>, "Tomeu Vizoso" <tomeu.vizoso@collabora.com>, "Tom Talpey" <tom@talpey.com>, "Trond Myklebust" <trond.myklebust@hammerspace.com>, "Tvrtko Ursulin" <tvrtko.ursulin@linux.intel.com>, "Xuan Zhuo" <xuanzhuo@linux.alibaba.com>, "Yue Hu" <huyue2@coolpad.com>, "Andrew Morton" <akpm@linux-foundation.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715163006.950613-1-sashal@kernel.org>
+
+From: Qi Zheng <zhengqi.arch@bytedance.com>
+
+[ Upstream commit 1dd49e58f966b1eecd935dc28458a8369ae94ad1 ]
+
+debugfs_remove_recursive() will wait for debugfs_file_put() to return, so
+the shrinker will not be freed when doing debugfs operations (such as
+shrinker_debugfs_count_show() and shrinker_debugfs_scan_write()), so there
+is no need to hold shrinker_rwsem during debugfs operations.
+
+Link: https://lkml.kernel.org/r/20230911092517.64141-4-zhengqi.arch@bytedance.com
+Signed-off-by: Qi Zheng <zhengqi.arch@bytedance.com>
+Reviewed-by: Muchun Song <songmuchun@bytedance.com>
+Cc: Christian Brauner <brauner@kernel.org>
+Cc: Christian König <christian.koenig@amd.com>
+Cc: Chuck Lever <cel@kernel.org>
+Cc: Daniel Vetter <daniel@ffwll.ch>
+Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
+Cc: Darrick J. Wong <djwong@kernel.org>
+Cc: Dave Chinner <david@fromorbit.com>
+Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Cc: Joel Fernandes <joel@joelfernandes.org>
+Cc: Kirill Tkhai <tkhai@ya.ru>
+Cc: Paul E. McKenney <paulmck@kernel.org>
+Cc: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Sergey Senozhatsky <senozhatsky@chromium.org>
+Cc: Steven Price <steven.price@arm.com>
+Cc: Theodore Ts'o <tytso@mit.edu>
+Cc: Vlastimil Babka <vbabka@suse.cz>
+Cc: Abhinav Kumar <quic_abhinavk@quicinc.com>
+Cc: Alasdair Kergon <agk@redhat.com>
+Cc: Alexander Viro <viro@zeniv.linux.org.uk>
+Cc: Alyssa Rosenzweig <alyssa.rosenzweig@collabora.com>
+Cc: Andreas Dilger <adilger.kernel@dilger.ca>
+Cc: Andreas Gruenbacher <agruenba@redhat.com>
+Cc: Anna Schumaker <anna@kernel.org>
+Cc: Arnd Bergmann <arnd@arndb.de>
+Cc: Bob Peterson <rpeterso@redhat.com>
+Cc: Borislav Petkov <bp@alien8.de>
+Cc: Carlos Llamas <cmllamas@google.com>
+Cc: Chandan Babu R <chandan.babu@oracle.com>
+Cc: Chao Yu <chao@kernel.org>
+Cc: Chris Mason <clm@fb.com>
+Cc: Coly Li <colyli@suse.de>
+Cc: Dai Ngo <Dai.Ngo@oracle.com>
+Cc: Dave Hansen <dave.hansen@linux.intel.com>
+Cc: David Airlie <airlied@gmail.com>
+Cc: David Hildenbrand <david@redhat.com>
+Cc: David Sterba <dsterba@suse.com>
+Cc: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+Cc: Gao Xiang <hsiangkao@linux.alibaba.com>
+Cc: Huang Rui <ray.huang@amd.com>
+Cc: Ingo Molnar <mingo@redhat.com>
+Cc: Jaegeuk Kim <jaegeuk@kernel.org>
+Cc: Jani Nikula <jani.nikula@linux.intel.com>
+Cc: Jan Kara <jack@suse.cz>
+Cc: Jason Wang <jasowang@redhat.com>
+Cc: Jeff Layton <jlayton@kernel.org>
+Cc: Jeffle Xu <jefflexu@linux.alibaba.com>
+Cc: Joonas Lahtinen <joonas.lahtinen@linux.intel.com>
+Cc: Josef Bacik <josef@toxicpanda.com>
+Cc: Juergen Gross <jgross@suse.com>
+Cc: Kent Overstreet <kent.overstreet@gmail.com>
+Cc: Marijn Suijten <marijn.suijten@somainline.org>
+Cc: "Michael S. Tsirkin" <mst@redhat.com>
+Cc: Mike Snitzer <snitzer@kernel.org>
+Cc: Minchan Kim <minchan@kernel.org>
+Cc: Muchun Song <muchun.song@linux.dev>
+Cc: Nadav Amit <namit@vmware.com>
+Cc: Neil Brown <neilb@suse.de>
+Cc: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com>
+Cc: Olga Kornievskaia <kolga@netapp.com>
+Cc: Richard Weinberger <richard@nod.at>
+Cc: Rob Clark <robdclark@gmail.com>
+Cc: Rob Herring <robh@kernel.org>
+Cc: Rodrigo Vivi <rodrigo.vivi@intel.com>
+Cc: Sean Paul <sean@poorly.run>
+Cc: Song Liu <song@kernel.org>
+Cc: Stefano Stabellini <sstabellini@kernel.org>
+Cc: Thomas Gleixner <tglx@linutronix.de>
+Cc: Tomeu Vizoso <tomeu.vizoso@collabora.com>
+Cc: Tom Talpey <tom@talpey.com>
+Cc: Trond Myklebust <trond.myklebust@hammerspace.com>
+Cc: Tvrtko Ursulin <tvrtko.ursulin@linux.intel.com>
+Cc: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
+Cc: Yue Hu <huyue2@coolpad.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Stable-dep-of: b902890c62d2 ("mm/shrinker: do not hold RCU lock in shrinker_debugfs_count_show()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ mm/shrinker_debug.c | 16 +---------------
+ 1 file changed, 1 insertion(+), 15 deletions(-)
+
+--- a/mm/shrinker_debug.c
++++ b/mm/shrinker_debug.c
+@@ -49,17 +49,12 @@ static int shrinker_debugfs_count_show(s
+ struct mem_cgroup *memcg;
+ unsigned long total;
+ bool memcg_aware;
+- int ret, nid;
++ int ret = 0, nid;
+
+ count_per_node = kcalloc(nr_node_ids, sizeof(unsigned long), GFP_KERNEL);
+ if (!count_per_node)
+ return -ENOMEM;
+
+- ret = down_read_killable(&shrinker_rwsem);
+- if (ret) {
+- kfree(count_per_node);
+- return ret;
+- }
+ rcu_read_lock();
+
+ memcg_aware = shrinker->flags & SHRINKER_MEMCG_AWARE;
+@@ -92,7 +87,6 @@ static int shrinker_debugfs_count_show(s
+ } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL);
+
+ rcu_read_unlock();
+- up_read(&shrinker_rwsem);
+
+ kfree(count_per_node);
+ return ret;
+@@ -117,7 +111,6 @@ static ssize_t shrinker_debugfs_scan_wri
+ struct mem_cgroup *memcg = NULL;
+ int nid;
+ char kbuf[72];
+- ssize_t ret;
+
+ read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1);
+ if (copy_from_user(kbuf, buf, read_len))
+@@ -146,12 +139,6 @@ static ssize_t shrinker_debugfs_scan_wri
+ return -EINVAL;
+ }
+
+- ret = down_read_killable(&shrinker_rwsem);
+- if (ret) {
+- mem_cgroup_put(memcg);
+- return ret;
+- }
+-
+ sc.nid = nid;
+ sc.memcg = memcg;
+ sc.nr_to_scan = nr_to_scan;
+@@ -159,7 +146,6 @@ static ssize_t shrinker_debugfs_scan_wri
+
+ shrinker->scan_objects(shrinker, &sc);
+
+- up_read(&shrinker_rwsem);
+ mem_cgroup_put(memcg);
+
+ return size;
--- /dev/null
+From stable+bounces-275027-greg=kroah.com@vger.kernel.org Wed Jul 15 22:16:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 16:16:02 -0400
+Subject: mm: shrinkers: fix debugfs file permissions
+To: stable@vger.kernel.org
+Cc: John Keeping <john@metanate.com>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715201603.63084-1-sashal@kernel.org>
+
+From: John Keeping <john@metanate.com>
+
+[ Upstream commit 2124f79de6a909630d1a62b01ecc32db9f967181 ]
+
+The permissions for the files here are swapped as "count" is read-only and
+"scan" is write-only. While this doesn't really matter as these
+permissions don't stop the files being opened for reading/writing as
+appropriate, they are shown by "ls -l" and are confusing.
+
+Link: https://lkml.kernel.org/r/20230418101906.3131303-1-john@metanate.com
+Signed-off-by: John Keeping <john@metanate.com>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Stable-dep-of: e30453c61e18 ("mm: shrinker: fix NULL pointer dereference in debugfs")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ mm/shrinker_debug.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/mm/shrinker_debug.c
++++ b/mm/shrinker_debug.c
+@@ -180,9 +180,9 @@ int shrinker_debugfs_add(struct shrinker
+ }
+ shrinker->debugfs_entry = entry;
+
+- debugfs_create_file("count", 0220, entry, shrinker,
++ debugfs_create_file("count", 0440, entry, shrinker,
+ &shrinker_debugfs_count_fops);
+- debugfs_create_file("scan", 0440, entry, shrinker,
++ debugfs_create_file("scan", 0220, entry, shrinker,
+ &shrinker_debugfs_scan_fops);
+ return 0;
+ }
--- /dev/null
+From stable+bounces-275043-greg=kroah.com@vger.kernel.org Wed Jul 15 23:13:56 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 17:13:48 -0400
+Subject: netfilter: ebtables: Use vmalloc_array() to improve code
+To: stable@vger.kernel.org
+Cc: Qianfeng Rong <rongqianfeng@vivo.com>, Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715211349.253327-1-sashal@kernel.org>
+
+From: Qianfeng Rong <rongqianfeng@vivo.com>
+
+[ Upstream commit 46015e6b3ea75297b28d4806564f3f692cf11861 ]
+
+Remove array_size() calls and replace vmalloc() with vmalloc_array() to
+simplify the code. vmalloc_array() is also optimized better, uses fewer
+instructions, and handles overflow more concisely[1].
+
+[1]: https://lore.kernel.org/lkml/abc66ec5-85a4-47e1-9759-2f60ab111971@vivo.com/
+Signed-off-by: Qianfeng Rong <rongqianfeng@vivo.com>
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Stable-dep-of: cbfe53599eeb ("netfilter: ebtables: zero chainstack array")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/bridge/netfilter/ebtables.c | 14 +++++++-------
+ 1 file changed, 7 insertions(+), 7 deletions(-)
+
+--- a/net/bridge/netfilter/ebtables.c
++++ b/net/bridge/netfilter/ebtables.c
+@@ -923,8 +923,8 @@ static int translate_table(struct net *n
+ * if an error occurs
+ */
+ newinfo->chainstack =
+- vmalloc(array_size(nr_cpu_ids,
+- sizeof(*(newinfo->chainstack))));
++ vmalloc_array(nr_cpu_ids,
++ sizeof(*(newinfo->chainstack)));
+ if (!newinfo->chainstack)
+ return -ENOMEM;
+ for_each_possible_cpu(i) {
+@@ -941,7 +941,7 @@ static int translate_table(struct net *n
+ }
+ }
+
+- cl_s = vmalloc(array_size(udc_cnt, sizeof(*cl_s)));
++ cl_s = vmalloc_array(udc_cnt, sizeof(*cl_s));
+ if (!cl_s)
+ return -ENOMEM;
+ i = 0; /* the i'th udc */
+@@ -1021,8 +1021,8 @@ static int do_replace_finish(struct net
+ * the check on the size is done later, when we have the lock
+ */
+ if (repl->num_counters) {
+- unsigned long size = repl->num_counters * sizeof(*counterstmp);
+- counterstmp = vmalloc(size);
++ counterstmp = vmalloc_array(repl->num_counters,
++ sizeof(*counterstmp));
+ if (!counterstmp)
+ return -ENOMEM;
+ }
+@@ -1389,7 +1389,7 @@ static int do_update_counters(struct net
+ if (num_counters == 0)
+ return -EINVAL;
+
+- tmp = vmalloc(array_size(num_counters, sizeof(*tmp)));
++ tmp = vmalloc_array(num_counters, sizeof(*tmp));
+ if (!tmp)
+ return -ENOMEM;
+
+@@ -1531,7 +1531,7 @@ static int copy_counters_to_user(struct
+ if (num_counters != nentries)
+ return -EINVAL;
+
+- counterstmp = vmalloc(array_size(nentries, sizeof(*counterstmp)));
++ counterstmp = vmalloc_array(nentries, sizeof(*counterstmp));
+ if (!counterstmp)
+ return -ENOMEM;
+
--- /dev/null
+From stable+bounces-275044-greg=kroah.com@vger.kernel.org Wed Jul 15 23:13:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 17:13:49 -0400
+Subject: netfilter: ebtables: zero chainstack array
+To: stable@vger.kernel.org
+Cc: Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715211349.253327-2-sashal@kernel.org>
+
+From: Florian Westphal <fw@strlen.de>
+
+[ Upstream commit cbfe53599eebffd188938ab6774cc41794f6f9d5 ]
+
+sashiko reports:
+ looking at ebtables table
+ translation, could a sparse cpu_possible_mask lead to an uninitialized pointer
+ free?
+
+ If cpu_possible_mask is sparse (for example, CPU 0 and CPU 2 are possible,
+ but CPU 1 is not), the allocation loop skips CPU 1. If vmalloc_node() fails at
+ CPU 2, the cleanup loop will blindly decrement and call vfree() on
+ newinfo->chainstack[1].
+
+Not a real-world bug, such allocation isn't expected to fail
+in the first place.
+
+Cc: stable@vger.kernel.org
+Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/bridge/netfilter/ebtables.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/net/bridge/netfilter/ebtables.c
++++ b/net/bridge/netfilter/ebtables.c
+@@ -923,8 +923,7 @@ static int translate_table(struct net *n
+ * if an error occurs
+ */
+ newinfo->chainstack =
+- vmalloc_array(nr_cpu_ids,
+- sizeof(*(newinfo->chainstack)));
++ vcalloc(nr_cpu_ids, sizeof(*(newinfo->chainstack)));
+ if (!newinfo->chainstack)
+ return -ENOMEM;
+ for_each_possible_cpu(i) {
--- /dev/null
+From stable+bounces-277835-greg=kroah.com@vger.kernel.org Mon Jul 20 16:30:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 10:18:04 -0400
+Subject: nvmet: remove superfluous initialization
+To: stable@vger.kernel.org
+Cc: Chaitanya Kulkarni <kch@nvidia.com>, Keith Busch <kbusch@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720141806.1621638-1-sashal@kernel.org>
+
+From: Chaitanya Kulkarni <kch@nvidia.com>
+
+[ Upstream commit 8d30528a170905ede9ab6ab81f229e441808590b ]
+
+Remove superfluous initialization of status variable in
+nvmet_execute_admin_connect() and nvmet_execute_io_connect(), since it
+will get overwritten by nvmet_copy_from_sgl().
+
+Signed-off-by: Chaitanya Kulkarni <kch@nvidia.com>
+Signed-off-by: Keith Busch <kbusch@kernel.org>
+Stable-dep-of: 3a413ece2504 ("nvmet-auth: validate reply message payload bounds against transfer length")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/nvme/target/fabrics-cmd.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/nvme/target/fabrics-cmd.c
++++ b/drivers/nvme/target/fabrics-cmd.c
+@@ -209,7 +209,7 @@ static void nvmet_execute_admin_connect(
+ struct nvmf_connect_command *c = &req->cmd->connect;
+ struct nvmf_connect_data *d;
+ struct nvmet_ctrl *ctrl = NULL;
+- u16 status = 0;
++ u16 status;
+ int ret;
+
+ if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data)))
+@@ -287,7 +287,7 @@ static void nvmet_execute_io_connect(str
+ struct nvmf_connect_data *d;
+ struct nvmet_ctrl *ctrl;
+ u16 qid = le16_to_cpu(c->qid);
+- u16 status = 0;
++ u16 status;
+
+ if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data)))
+ return;
--- /dev/null
+From stable+bounces-274690-greg=kroah.com@vger.kernel.org Wed Jul 15 04:47:08 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 22:45:57 -0400
+Subject: PCI: Add kerneldoc for pci_resize_resource()
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Bjorn Helgaas" <bhelgaas@google.com>, "Alex Bennée" <alex.bennee@linaro.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715024559.106836-4-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit d787018e2dfdc4c1331538e7a8717690d1b7c9b3 ]
+
+As pci_resize_resource() is meant to be used also outside of PCI core,
+document the interface with kerneldoc.
+
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Tested-by: Alex Bennée <alex.bennee@linaro.org> # AVA, AMD GPU
+Link: https://patch.msgid.link/20251113162628.5946-8-ilpo.jarvinen@linux.intel.com
+Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/setup-res.c | 20 ++++++++++++++++++++
+ 1 file changed, 20 insertions(+)
+
+--- a/drivers/pci/setup-res.c
++++ b/drivers/pci/setup-res.c
+@@ -427,6 +427,26 @@ void pci_release_resource(struct pci_dev
+ }
+ EXPORT_SYMBOL(pci_release_resource);
+
++/**
++ * pci_resize_resource - reconfigure a Resizable BAR and resources
++ * @dev: the PCI device
++ * @resno: index of the BAR to be resized
++ * @size: new size as defined in the spec (0=1MB, 31=128TB)
++ * @exclude_bars: a mask of BARs that should not be released
++ *
++ * Reconfigure @resno to @size and re-run resource assignment algorithm
++ * with the new size.
++ *
++ * Prior to resize, release @dev resources that share a bridge window with
++ * @resno. This unpins the bridge window resource to allow changing it.
++ *
++ * The caller may prevent releasing a particular BAR by providing
++ * @exclude_bars mask, but this may result in the resize operation failing
++ * due to insufficient space.
++ *
++ * Return: 0 on success, or negative on error. In case of an error, the
++ * resources are restored to their original places.
++ */
+ int pci_resize_resource(struct pci_dev *dev, int resno, int size,
+ int exclude_bars)
+ {
--- /dev/null
+From stable+bounces-274436-greg=kroah.com@vger.kernel.org Tue Jul 14 18:38:20 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 12:38:13 -0400
+Subject: PCI: altera: Fix resource leaks on probe failure
+To: stable@vger.kernel.org
+Cc: Mahesh Vaidya <mahesh.vaidya@altera.com>, Manivannan Sadhasivam <mani@kernel.org>, "Subhransu S. Prusty" <subhransu.sekhar.prusty@altera.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714163813.2850299-1-sashal@kernel.org>
+
+From: Mahesh Vaidya <mahesh.vaidya@altera.com>
+
+[ Upstream commit 7a94138caeb27f3c49c1dbd93bf422098925bb28 ]
+
+The chained IRQ handler is set during probe, but is only removed during the
+driver remove(). If pci_host_probe() fails, the handler and INTx IRQ
+domain remain set even though the devm-managed host bridge storage
+containing struct altera_pcie will be released, leaving the handler with
+a stale data pointer.
+
+Interrupts are also enabled before pci_host_probe() is called. If probe
+fails after that point, the controller interrupt source should be disabled
+before the chained handler and INTx domain are removed.
+
+So set the chained handler only after the INTx domain has been created.
+Disable controller interrupts during IRQ teardown, and tear the IRQ setup
+down if pci_host_probe() fails.
+
+Fixes: c63aed7334c2 ("PCI: altera: Use pci_host_probe() to register host")
+Signed-off-by: Mahesh Vaidya <mahesh.vaidya@altera.com>
+[mani: commit log]
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+Reviewed-by: Subhransu S. Prusty <subhransu.sekhar.prusty@altera.com>
+Cc: stable@vger.kernel.org
+Link: https://patch.msgid.link/20260430204330.3121003-3-mahesh.vaidya@altera.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/controller/pcie-altera.c | 28 ++++++++++++++++++++++++++--
+ 1 file changed, 26 insertions(+), 2 deletions(-)
+
+--- a/drivers/pci/controller/pcie-altera.c
++++ b/drivers/pci/controller/pcie-altera.c
+@@ -681,8 +681,18 @@ static int altera_pcie_init_irq_domain(s
+ return 0;
+ }
+
++static void altera_pcie_disable_irq(struct altera_pcie *pcie)
++{
++ if (pcie->pcie_data->version == ALTERA_PCIE_V1 ||
++ pcie->pcie_data->version == ALTERA_PCIE_V2) {
++ /* Disable all P2A interrupts */
++ cra_writel(pcie, 0, P2A_INT_ENABLE);
++ }
++}
++
+ static void altera_pcie_irq_teardown(struct altera_pcie *pcie)
+ {
++ altera_pcie_disable_irq(pcie);
+ irq_set_chained_handler_and_data(pcie->irq, NULL, NULL);
+ irq_domain_remove(pcie->irq_domain);
+ }
+@@ -707,7 +717,6 @@ static int altera_pcie_parse_dt(struct a
+ if (pcie->irq < 0)
+ return pcie->irq;
+
+- irq_set_chained_handler_and_data(pcie->irq, altera_pcie_isr, pcie);
+ return 0;
+ }
+
+@@ -792,6 +801,12 @@ static int altera_pcie_probe(struct plat
+ return ret;
+ }
+
++ /*
++ * The chained handler uses pcie->irq_domain, so set it only after the
++ * INTx domain has been created.
++ */
++ irq_set_chained_handler_and_data(pcie->irq, altera_pcie_isr, pcie);
++
+ /* clear all interrupts */
+ cra_writel(pcie, P2A_INT_STS_ALL, P2A_INT_STATUS);
+ /* enable all interrupts */
+@@ -802,7 +817,16 @@ static int altera_pcie_probe(struct plat
+ bridge->busnr = pcie->root_bus_nr;
+ bridge->ops = &altera_pcie_ops;
+
+- return pci_host_probe(bridge);
++ ret = pci_host_probe(bridge);
++ if (ret)
++ goto err_teardown_irq;
++
++ return 0;
++
++err_teardown_irq:
++ altera_pcie_irq_teardown(pcie);
++
++ return ret;
+ }
+
+ static int altera_pcie_remove(struct platform_device *pdev)
--- /dev/null
+From stable+bounces-274608-greg=kroah.com@vger.kernel.org Wed Jul 15 01:34:53 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 19:34:14 -0400
+Subject: PCI: controller: Use dev_fwnode() instead of of_fwnode_handle()
+To: stable@vger.kernel.org
+Cc: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>, Arnd Bergmann <arnd@arndb.de>, Bjorn Helgaas <bhelgaas@google.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714233418.3592225-1-sashal@kernel.org>
+
+From: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>
+
+[ Upstream commit a103d2dede5683dabbac2c3374bc24b6a9434478 ]
+
+All irq_domain functions now accept fwnode instead of of_node. But many
+PCI controllers still extract dev to of_node and then of_node to fwnode.
+
+Instead, clean this up and simply use the dev_fwnode() helper to extract
+fwnode directly from dev. Internally, it still does dev => of_node =>
+fwnode steps, but it's now hidden from the users.
+
+In the case of altera, this also removes an unused 'node' variable that is
+only used when CONFIG_OF is enabled:
+
+ drivers/pci/controller/pcie-altera.c: In function 'altera_pcie_init_irq_domain':
+ drivers/pci/controller/pcie-altera.c:855:29: error: unused variable 'node' [-Werror=unused-variable]
+ 855 | struct device_node *node = dev->of_node;
+
+Signed-off-by: Jiri Slaby (SUSE) <jirislaby@kernel.org>
+Signed-off-by: Arnd Bergmann <arnd@arndb.de> # altera
+[bhelgaas: squash together, rebase to precede msi-parent]
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Link: https://patch.msgid.link/20250521163329.2137973-1-arnd@kernel.org
+Link: https://patch.msgid.link/20250611104348.192092-16-jirislaby@kernel.org
+Link: https://patch.msgid.link/20250723065907.1841758-1-jirislaby@kernel.org
+Stable-dep-of: f865a57896bd ("PCI: mediatek: Fix IRQ domain leak when port fails to enable")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/controller/dwc/pcie-designware-host.c | 2 +-
+ drivers/pci/controller/mobiveil/pcie-mobiveil-host.c | 8 +++-----
+ drivers/pci/controller/pcie-altera-msi.c | 2 +-
+ drivers/pci/controller/pcie-altera.c | 3 +--
+ drivers/pci/controller/pcie-mediatek-gen3.c | 4 ++--
+ drivers/pci/controller/pcie-mediatek.c | 2 +-
+ drivers/pci/controller/pcie-xilinx-nwl.c | 2 +-
+ 7 files changed, 10 insertions(+), 13 deletions(-)
+
+--- a/drivers/pci/controller/dwc/pcie-designware-host.c
++++ b/drivers/pci/controller/dwc/pcie-designware-host.c
+@@ -232,7 +232,7 @@ static const struct irq_domain_ops dw_pc
+ int dw_pcie_allocate_domains(struct dw_pcie_rp *pp)
+ {
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+- struct fwnode_handle *fwnode = of_node_to_fwnode(pci->dev->of_node);
++ struct fwnode_handle *fwnode = dev_fwnode(pci->dev);
+
+ pp->irq_domain = irq_domain_create_linear(fwnode, pp->num_vectors,
+ &dw_pcie_msi_domain_ops, pp);
+--- a/drivers/pci/controller/mobiveil/pcie-mobiveil-host.c
++++ b/drivers/pci/controller/mobiveil/pcie-mobiveil-host.c
+@@ -445,7 +445,7 @@ static const struct irq_domain_ops msi_d
+ static int mobiveil_allocate_msi_domains(struct mobiveil_pcie *pcie)
+ {
+ struct device *dev = &pcie->pdev->dev;
+- struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node);
++ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ struct mobiveil_msi *msi = &pcie->rp.msi;
+
+ mutex_init(&msi->lock);
+@@ -471,13 +471,11 @@ static int mobiveil_allocate_msi_domains
+ static int mobiveil_pcie_init_irq_domain(struct mobiveil_pcie *pcie)
+ {
+ struct device *dev = &pcie->pdev->dev;
+- struct device_node *node = dev->of_node;
+ struct mobiveil_root_port *rp = &pcie->rp;
+
+ /* setup INTx */
+- rp->intx_domain = irq_domain_add_linear(node, PCI_NUM_INTX,
+- &intx_domain_ops, pcie);
+-
++ rp->intx_domain = irq_domain_create_linear(dev_fwnode(dev), PCI_NUM_INTX, &intx_domain_ops,
++ pcie);
+ if (!rp->intx_domain) {
+ dev_err(dev, "Failed to get a INTx IRQ domain\n");
+ return -ENOMEM;
+--- a/drivers/pci/controller/pcie-altera-msi.c
++++ b/drivers/pci/controller/pcie-altera-msi.c
+@@ -171,7 +171,7 @@ static const struct irq_domain_ops msi_d
+
+ static int altera_allocate_domains(struct altera_msi *msi)
+ {
+- struct fwnode_handle *fwnode = of_node_to_fwnode(msi->pdev->dev.of_node);
++ struct fwnode_handle *fwnode = dev_fwnode(&msi->pdev->dev);
+
+ msi->inner_domain = irq_domain_add_linear(NULL, msi->num_of_vectors,
+ &msi_domain_ops, msi);
+--- a/drivers/pci/controller/pcie-altera.c
++++ b/drivers/pci/controller/pcie-altera.c
+@@ -668,10 +668,9 @@ static void altera_pcie_isr(struct irq_d
+ static int altera_pcie_init_irq_domain(struct altera_pcie *pcie)
+ {
+ struct device *dev = &pcie->pdev->dev;
+- struct device_node *node = dev->of_node;
+
+ /* Setup INTx */
+- pcie->irq_domain = irq_domain_add_linear(node, PCI_NUM_INTX,
++ pcie->irq_domain = irq_domain_create_linear(dev_fwnode(dev), PCI_NUM_INTX,
+ &intx_domain_ops, pcie);
+ if (!pcie->irq_domain) {
+ dev_err(dev, "Failed to get a INTx IRQ domain\n");
+--- a/drivers/pci/controller/pcie-mediatek-gen3.c
++++ b/drivers/pci/controller/pcie-mediatek-gen3.c
+@@ -661,8 +661,8 @@ static int mtk_pcie_init_irq_domains(str
+ /* Setup MSI */
+ mutex_init(&pcie->lock);
+
+- pcie->msi_bottom_domain = irq_domain_add_linear(node, PCIE_MSI_IRQS_NUM,
+- &mtk_msi_bottom_domain_ops, pcie);
++ pcie->msi_bottom_domain = irq_domain_create_linear(dev_fwnode(dev), PCIE_MSI_IRQS_NUM,
++ &mtk_msi_bottom_domain_ops, pcie);
+ if (!pcie->msi_bottom_domain) {
+ dev_err(dev, "failed to create MSI bottom domain\n");
+ ret = -ENODEV;
+--- a/drivers/pci/controller/pcie-mediatek.c
++++ b/drivers/pci/controller/pcie-mediatek.c
+@@ -496,7 +496,7 @@ static struct msi_domain_info mtk_msi_do
+
+ static int mtk_pcie_allocate_msi_domains(struct mtk_pcie_port *port)
+ {
+- struct fwnode_handle *fwnode = of_node_to_fwnode(port->pcie->dev->of_node);
++ struct fwnode_handle *fwnode = dev_fwnode(port->pcie->dev);
+
+ mutex_init(&port->lock);
+
+--- a/drivers/pci/controller/pcie-xilinx-nwl.c
++++ b/drivers/pci/controller/pcie-xilinx-nwl.c
+@@ -503,7 +503,7 @@ static int nwl_pcie_init_msi_irq_domain(
+ {
+ #ifdef CONFIG_PCI_MSI
+ struct device *dev = pcie->dev;
+- struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node);
++ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ struct nwl_msi *msi = &pcie->msi;
+
+ msi->dev_domain = irq_domain_add_linear(NULL, INT_PCI_MSI_NR,
--- /dev/null
+From stable+bounces-274689-greg=kroah.com@vger.kernel.org Wed Jul 15 04:46:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 22:45:56 -0400
+Subject: PCI: Fix restoring BARs on BAR resize rollback path
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Simon Richter" <Simon.Richter@hogyros.de>, "Alex Bennée" <alex.bennee@linaro.org>, "Bjorn Helgaas" <bhelgaas@google.com>, "Christian König" <christian.koenig@amd.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715024559.106836-3-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit 337b1b566db087347194e4543ddfdfa5645275cc ]
+
+BAR resize operation is implemented in the pci_resize_resource() and
+pbus_reassign_bridge_resources() functions. pci_resize_resource() can be
+called either from __resource_resize_store() from sysfs or directly by the
+driver for the Endpoint Device.
+
+The pci_resize_resource() requires that caller has released the device
+resources that share the bridge window with the BAR to be resized as
+otherwise the bridge window is pinned in place and cannot be changed.
+
+pbus_reassign_bridge_resources() rolls back resources if the resize
+operation fails, but rollback is performed only for the bridge windows.
+Because releasing the device resources are done by the caller of the BAR
+resize interface, these functions performing the BAR resize do not have
+access to the device resources as they were before the resize.
+
+pbus_reassign_bridge_resources() could try __pci_bridge_assign_resources()
+after rolling back the bridge windows as they were, however, it will not
+guarantee the resource are assigned due to differences in how FW and the
+kernel assign the resources (alignment of the start address and tail).
+
+To perform rollback robustly, the BAR resize interface has to be altered to
+also release the device resources that share the bridge window with the BAR
+to be resized.
+
+Also, remove restoring from the entries failed list as saved list should
+now contain both the bridge windows and device resources so the extra
+restore is duplicated work.
+
+Some drivers (currently only amdgpu) want to prevent releasing some
+resources. Add exclude_bars param to pci_resize_resource() and make amdgpu
+pass its register BAR (BAR 2 or 5), which should never be released during
+resize operation. Normally 64-bit prefetchable resources do not share a
+bridge window with the 32-bit only register BAR, but there are various
+fallbacks in the resource assignment logic which may make the resources
+share the bridge window in rare cases.
+
+This change (together with the driver side changes) is to counter the
+resource releases that had to be done to prevent resource tree corruption
+in the ("PCI: Release assigned resource before restoring them") change. As
+such, it likely restores functionality in cases where device resources were
+released to avoid resource tree conflicts which appeared to be "working"
+when such conflicts were not correctly detected by the kernel.
+
+Reported-by: Simon Richter <Simon.Richter@hogyros.de>
+Link: https://lore.kernel.org/linux-pci/f9a8c975-f5d3-4dd2-988e-4371a1433a60@hogyros.de/
+Reported-by: Alex Bennée <alex.bennee@linaro.org>
+Link: https://lore.kernel.org/linux-pci/874irqop6b.fsf@draig.linaro.org/
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+[bhelgaas: squash amdgpu BAR selection from
+https: //lore.kernel.org/r/20251114103053.13778-1-ilpo.jarvinen@linux.intel.com]
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Tested-by: Alex Bennée <alex.bennee@linaro.org> # AVA, AMD GPU
+Reviewed-by: Christian König <christian.koenig@amd.com>
+Link: https://patch.msgid.link/20251113162628.5946-7-ilpo.jarvinen@linux.intel.com
+Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 4 -
+ drivers/gpu/drm/i915/gt/intel_region_lmem.c | 2
+ drivers/pci/pci-sysfs.c | 14 ---
+ drivers/pci/pci.h | 3
+ drivers/pci/setup-bus.c | 101 +++++++++++++++++++---------
+ drivers/pci/setup-res.c | 19 +----
+ include/linux/pci.h | 4 -
+ 7 files changed, 87 insertions(+), 60 deletions(-)
+
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
+@@ -1272,7 +1272,9 @@ int amdgpu_device_resize_fb_bar(struct a
+
+ pci_release_resource(adev->pdev, 0);
+
+- r = pci_resize_resource(adev->pdev, 0, rbar_size);
++ r = pci_resize_resource(adev->pdev, 0, rbar_size,
++ (adev->asic_type >= CHIP_BONAIRE) ? 1 << 5
++ : 1 << 2);
+ if (r == -ENOSPC)
+ DRM_INFO("Not enough PCI address space for a large BAR.");
+ else if (r && r != -ENOTSUPP)
+--- a/drivers/gpu/drm/i915/gt/intel_region_lmem.c
++++ b/drivers/gpu/drm/i915/gt/intel_region_lmem.c
+@@ -37,7 +37,7 @@ _resize_bar(struct drm_i915_private *i91
+
+ _release_bars(pdev);
+
+- ret = pci_resize_resource(pdev, resno, bar_size);
++ ret = pci_resize_resource(pdev, resno, bar_size, 0);
+ if (ret) {
+ drm_info(&i915->drm, "Failed to resize BAR%d to %dM (%pe)\n",
+ resno, 1 << bar_size, ERR_PTR(ret));
+--- a/drivers/pci/pci-sysfs.c
++++ b/drivers/pci/pci-sysfs.c
+@@ -1455,8 +1455,8 @@ static ssize_t resource##n##_resize_stor
+ const char *buf, size_t count)\
+ { \
+ struct pci_dev *pdev = to_pci_dev(dev); \
+- unsigned long size, flags; \
+- int ret, i; \
++ unsigned long size; \
++ int ret; \
+ u16 cmd; \
+ \
+ if (kstrtoul(buf, 0, &size) < 0) \
+@@ -1481,17 +1481,9 @@ static ssize_t resource##n##_resize_stor
+ pci_write_config_word(pdev, PCI_COMMAND, \
+ cmd & ~PCI_COMMAND_MEMORY); \
+ \
+- flags = pci_resource_flags(pdev, n); \
+- \
+ pci_remove_resource_files(pdev); \
+ \
+- for (i = 0; i < PCI_STD_NUM_BARS; i++) { \
+- if (pci_resource_len(pdev, i) && \
+- pci_resource_flags(pdev, i) == flags) \
+- pci_release_resource(pdev, i); \
+- } \
+- \
+- ret = pci_resize_resource(pdev, n, size); \
++ ret = pci_resize_resource(pdev, n, size, 0); \
+ \
+ pci_assign_unassigned_bus_resources(pdev->bus); \
+ \
+--- a/drivers/pci/pci.h
++++ b/drivers/pci/pci.h
+@@ -245,6 +245,9 @@ enum pci_bar_type {
+ struct device *pci_get_host_bridge_device(struct pci_dev *dev);
+ void pci_put_host_bridge_device(struct device *dev);
+
++int pci_do_resource_release_and_resize(struct pci_dev *dev, int resno, int size,
++ int exclude_bars);
++
+ int pci_configure_extended_tags(struct pci_dev *dev, void *ign);
+ bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *pl,
+ int crs_timeout);
+--- a/drivers/pci/setup-bus.c
++++ b/drivers/pci/setup-bus.c
+@@ -2223,18 +2223,16 @@ enable_all:
+ }
+ EXPORT_SYMBOL_GPL(pci_assign_unassigned_bridge_resources);
+
+-int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type)
++static int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type,
++ struct list_head *saved)
+ {
+ struct pci_dev_resource *dev_res;
+ struct pci_dev *next;
+- LIST_HEAD(saved);
+ LIST_HEAD(added);
+ LIST_HEAD(failed);
+ unsigned int i;
+ int ret;
+
+- down_read(&pci_bus_sem);
+-
+ /* Walk to the root hub, releasing bridge BARs when possible */
+ next = bridge;
+ do {
+@@ -2251,9 +2249,9 @@ int pci_reassign_bridge_resources(struct
+ if (res->child)
+ continue;
+
+- ret = add_to_list(&saved, bridge, res, 0, 0);
++ ret = add_to_list(saved, bridge, res, 0, 0);
+ if (ret)
+- goto cleanup;
++ return ret;
+
+ pci_info(bridge, "%s %pR: releasing\n", res_name, res);
+
+@@ -2269,67 +2267,108 @@ int pci_reassign_bridge_resources(struct
+ next = bridge->bus ? bridge->bus->self : NULL;
+ } while (next);
+
+- if (list_empty(&saved)) {
+- up_read(&pci_bus_sem);
++ if (list_empty(saved))
+ return -ENOENT;
+- }
+
+ __pci_bus_size_bridges(bridge->subordinate, &added);
+ __pci_bridge_assign_resources(bridge, &added, &failed);
+ BUG_ON(!list_empty(&added));
+
+ if (!list_empty(&failed)) {
+- ret = -ENOSPC;
+- goto cleanup;
++ free_list(&failed);
++ return -ENOSPC;
+ }
+
+- list_for_each_entry(dev_res, &saved, list) {
++ list_for_each_entry(dev_res, saved, list) {
+ /* Skip the bridge we just assigned resources for */
+ if (bridge == dev_res->dev)
+ continue;
+
++ if (!dev_res->dev->subordinate)
++ continue;
++
+ bridge = dev_res->dev;
+ pci_setup_bridge(bridge->subordinate);
+ }
+
+- free_list(&saved);
+- up_read(&pci_bus_sem);
+ return 0;
++}
+
+-cleanup:
+- /* Restore size and flags */
+- list_for_each_entry(dev_res, &failed, list) {
+- struct resource *res = dev_res->res;
++int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size,
++ int exclude_bars)
++{
++ struct resource *res = pdev->resource + resno;
++ unsigned long flags = res->flags;
++ struct pci_dev_resource *dev_res;
++ struct pci_bus *bus = pdev->bus;
++ struct resource *r;
++ LIST_HEAD(saved);
++ unsigned int i;
++ int ret = 0;
+
+- res->start = dev_res->start;
+- res->end = dev_res->end;
+- res->flags = dev_res->flags;
++ down_read(&pci_bus_sem);
++
++ pci_dev_for_each_resource(pdev, r, i) {
++ if (i >= PCI_BRIDGE_RESOURCES)
++ break;
++
++ if (exclude_bars & BIT(i))
++ continue;
++
++ if (!pci_resource_len(pdev, i) || r->flags != flags)
++ continue;
++
++ ret = add_to_list(&saved, pdev, r, 0, 0);
++ if (ret)
++ goto restore;
++ pci_release_resource(pdev, i);
+ }
+- free_list(&failed);
+
++ res->end = res->start + pci_rebar_size_to_bytes(size) - 1;
++
++ if (!bus->self)
++ goto out;
++
++ ret = pci_reassign_bridge_resources(bus->self, res->flags, &saved);
++ if (ret)
++ goto restore;
++
++out:
++ up_read(&pci_bus_sem);
++ free_list(&saved);
++ return ret;
++
++restore:
+ /* Revert to the old configuration */
+ list_for_each_entry(dev_res, &saved, list) {
+ struct resource *res = dev_res->res;
++ struct pci_dev *dev = dev_res->dev;
+
+- bridge = dev_res->dev;
+- i = res - bridge->resource;
++ i = res - dev->resource;
+
+ if (res->parent) {
+ release_child_resources(res);
+- pci_release_resource(bridge, i);
++ pci_release_resource(dev, i);
+ }
+
+ res->start = dev_res->start;
+ res->end = dev_res->end;
+ res->flags = dev_res->flags;
+
+- pci_claim_resource(bridge, i);
+- pci_setup_bridge(bridge->subordinate);
+- }
+- up_read(&pci_bus_sem);
+- free_list(&saved);
++ if (pci_claim_resource(dev, i))
++ continue;
+
+- return ret;
++ if (i < PCI_BRIDGE_RESOURCES) {
++ const char *res_name = pci_resource_name(dev, i);
++
++ pci_update_resource(dev, i);
++ pci_info(dev, "%s %pR: old value restored\n",
++ res_name, res);
++ }
++ if (dev->subordinate)
++ pci_setup_bridge(dev->subordinate);
++ }
++ goto out;
+ }
+
+ void pci_assign_unassigned_bus_resources(struct pci_bus *bus)
+--- a/drivers/pci/setup-res.c
++++ b/drivers/pci/setup-res.c
+@@ -427,9 +427,9 @@ void pci_release_resource(struct pci_dev
+ }
+ EXPORT_SYMBOL(pci_release_resource);
+
+-int pci_resize_resource(struct pci_dev *dev, int resno, int size)
++int pci_resize_resource(struct pci_dev *dev, int resno, int size,
++ int exclude_bars)
+ {
+- struct resource *res = dev->resource + resno;
+ struct pci_host_bridge *host;
+ int old, ret;
+ u32 sizes;
+@@ -440,10 +440,6 @@ int pci_resize_resource(struct pci_dev *
+ if (host->preserve_config)
+ return -ENOTSUPP;
+
+- /* Make sure the resource isn't assigned before resizing it. */
+- if (!(res->flags & IORESOURCE_UNSET))
+- return -EBUSY;
+-
+ pci_read_config_word(dev, PCI_COMMAND, &cmd);
+ if (cmd & PCI_COMMAND_MEMORY)
+ return -EBUSY;
+@@ -463,19 +459,14 @@ int pci_resize_resource(struct pci_dev *
+ if (ret)
+ return ret;
+
+- res->end = res->start + pci_rebar_size_to_bytes(size) - 1;
++ ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars);
++ if (ret)
++ goto error_resize;
+
+- /* Check if the new config works by trying to assign everything. */
+- if (dev->bus->self) {
+- ret = pci_reassign_bridge_resources(dev->bus->self, res->flags);
+- if (ret)
+- goto error_resize;
+- }
+ return 0;
+
+ error_resize:
+ pci_rebar_set_size(dev, resno, old);
+- res->end = res->start + pci_rebar_size_to_bytes(old) - 1;
+ return ret;
+ }
+ EXPORT_SYMBOL(pci_resize_resource);
+--- a/include/linux/pci.h
++++ b/include/linux/pci.h
+@@ -1380,7 +1380,8 @@ static inline int pci_rebar_bytes_to_siz
+ }
+
+ u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar);
+-int __must_check pci_resize_resource(struct pci_dev *dev, int i, int size);
++int __must_check pci_resize_resource(struct pci_dev *dev, int i, int size,
++ int exclude_bars);
+ int pci_select_bars(struct pci_dev *dev, unsigned long flags);
+ bool pci_device_is_present(struct pci_dev *pdev);
+ void pci_ignore_hotplug(struct pci_dev *dev);
+@@ -1450,7 +1451,6 @@ void pci_assign_unassigned_resources(voi
+ void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge);
+ void pci_assign_unassigned_bus_resources(struct pci_bus *bus);
+ void pci_assign_unassigned_root_bus_resources(struct pci_bus *bus);
+-int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type);
+ void pdev_enable_device(struct pci_dev *);
+ int pci_enable_resources(struct pci_dev *, int mask);
+ void pci_assign_irq(struct pci_dev *dev);
--- /dev/null
+From stable+bounces-274688-greg=kroah.com@vger.kernel.org Wed Jul 15 04:46:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 22:45:55 -0400
+Subject: PCI: Free saved list without holding pci_bus_sem
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Bjorn Helgaas" <bhelgaas@google.com>, "Alex Bennée" <alex.bennee@linaro.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715024559.106836-2-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit 1d8a0506f69895b7cfd9d5c4546761c508231a8a ]
+
+Freeing the saved list does not require holding pci_bus_sem, so the
+critical section can be made shorter.
+
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Tested-by: Alex Bennée <alex.bennee@linaro.org> # AVA, AMD GPU
+Link: https://patch.msgid.link/20251113162628.5946-6-ilpo.jarvinen@linux.intel.com
+Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/setup-bus.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/pci/setup-bus.c
++++ b/drivers/pci/setup-bus.c
+@@ -2326,8 +2326,8 @@ cleanup:
+ pci_claim_resource(bridge, i);
+ pci_setup_bridge(bridge->subordinate);
+ }
+- free_list(&saved);
+ up_read(&pci_bus_sem);
++ free_list(&saved);
+
+ return ret;
+ }
--- /dev/null
+From stable+bounces-274502-greg=kroah.com@vger.kernel.org Tue Jul 14 20:22:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 14:21:13 -0400
+Subject: PCI: imx6: Fix IMX6SX_GPR12_PCIE_TEST_POWERDOWN handling
+To: stable@vger.kernel.org
+Cc: Richard Zhu <hongxing.zhu@nxp.com>, Manivannan Sadhasivam <mani@kernel.org>, Bjorn Helgaas <bhelgaas@google.com>, Frank Li <Frank.Li@nxp.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714182113.3081792-1-sashal@kernel.org>
+
+From: Richard Zhu <hongxing.zhu@nxp.com>
+
+[ Upstream commit aad953fb4eed0df5486cd54ccad80ac197678e01 ]
+
+The IMX6SX_GPR12_PCIE_TEST_POWERDOWN bit does not control the PCIe
+reference clock on i.MX6SX. Instead, it is part of i.MX6SX PCIe core
+reset sequence.
+
+Move the IMX6SX_GPR12_PCIE_TEST_POWERDOWN assertion/deassertion into
+the core reset functions to properly reflect its purpose. Remove the
+.enable_ref_clk() callback for i.MX6SX since it was incorrectly
+manipulating this bit.
+
+Fixes: e3c06cd063d6 ("PCI: imx6: Add initial imx6sx support")
+Signed-off-by: Richard Zhu <hongxing.zhu@nxp.com>
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Cc: stable@vger.kernel.org
+Link: https://patch.msgid.link/20260319090844.444987-1-hongxing.zhu@nxp.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/controller/dwc/pci-imx6.c | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+--- a/drivers/pci/controller/dwc/pci-imx6.c
++++ b/drivers/pci/controller/dwc/pci-imx6.c
+@@ -539,9 +539,6 @@ static int imx6_pcie_enable_ref_clk(stru
+ dev_err(dev, "unable to enable pcie_axi clock\n");
+ break;
+ }
+-
+- regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
+- IMX6SX_GPR12_PCIE_TEST_POWERDOWN, 0);
+ break;
+ case IMX6QP:
+ case IMX6Q:
+@@ -740,6 +737,8 @@ static int imx6_pcie_deassert_core_reset
+ imx7d_pcie_wait_for_phy_pll_lock(imx6_pcie);
+ break;
+ case IMX6SX:
++ regmap_clear_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
++ IMX6SX_GPR12_PCIE_TEST_POWERDOWN);
+ regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR5,
+ IMX6SX_GPR5_PCIE_BTNRST_RESET, 0);
+ break;
--- /dev/null
+From stable+bounces-274609-greg=kroah.com@vger.kernel.org Wed Jul 15 01:34:30 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 19:34:15 -0400
+Subject: PCI: mediatek: Convert bool to single quirks entry and bitmap
+To: stable@vger.kernel.org
+Cc: Christian Marangi <ansuelsmth@gmail.com>, Manivannan Sadhasivam <mani@kernel.org>, AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714233418.3592225-2-sashal@kernel.org>
+
+From: Christian Marangi <ansuelsmth@gmail.com>
+
+[ Upstream commit 04305367fab7ec9c98eeba315ad09c8b20abce93 ]
+
+To clean Mediatek SoC PCIe struct, convert all the bool to a bitmap and
+use a single quirks to reference all the values. This permits cleaner
+addition of new quirk without having to define a new bool in the struct.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Link: https://patch.msgid.link/20251020111121.31779-4-ansuelsmth@gmail.com
+Stable-dep-of: f865a57896bd ("PCI: mediatek: Fix IRQ domain leak when port fails to enable")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/controller/pcie-mediatek.c | 33 ++++++++++++++++++++-------------
+ 1 file changed, 20 insertions(+), 13 deletions(-)
+
+--- a/drivers/pci/controller/pcie-mediatek.c
++++ b/drivers/pci/controller/pcie-mediatek.c
+@@ -143,23 +143,31 @@
+ struct mtk_pcie_port;
+
+ /**
++ * enum mtk_pcie_quirks - MTK PCIe quirks
++ * @MTK_PCIE_FIX_CLASS_ID: host's class ID needed to be fixed
++ * @MTK_PCIE_FIX_DEVICE_ID: host's device ID needed to be fixed
++ * @MTK_PCIE_NO_MSI: Bridge has no MSI support, and relies on an external block
++ */
++enum mtk_pcie_quirks {
++ MTK_PCIE_FIX_CLASS_ID = BIT(0),
++ MTK_PCIE_FIX_DEVICE_ID = BIT(1),
++ MTK_PCIE_NO_MSI = BIT(2),
++};
++
++/**
+ * struct mtk_pcie_soc - differentiate between host generations
+- * @need_fix_class_id: whether this host's class ID needed to be fixed or not
+- * @need_fix_device_id: whether this host's device ID needed to be fixed or not
+- * @no_msi: Bridge has no MSI support, and relies on an external block
+ * @device_id: device ID which this host need to be fixed
+ * @ops: pointer to configuration access functions
+ * @startup: pointer to controller setting functions
+ * @setup_irq: pointer to initialize IRQ functions
++ * @quirks: PCIe device quirks.
+ */
+ struct mtk_pcie_soc {
+- bool need_fix_class_id;
+- bool need_fix_device_id;
+- bool no_msi;
+ unsigned int device_id;
+ struct pci_ops *ops;
+ int (*startup)(struct mtk_pcie_port *port);
+ int (*setup_irq)(struct mtk_pcie_port *port, struct device_node *node);
++ enum mtk_pcie_quirks quirks;
+ };
+
+ /**
+@@ -720,7 +728,7 @@ static int mtk_pcie_startup_port_v2(stru
+ writel(val, port->base + PCIE_RST_CTRL);
+
+ /* Set up vendor ID and class code */
+- if (soc->need_fix_class_id) {
++ if (soc->quirks & MTK_PCIE_FIX_CLASS_ID) {
+ val = PCI_VENDOR_ID_MEDIATEK;
+ writew(val, port->base + PCIE_CONF_VEND_ID);
+
+@@ -728,7 +736,7 @@ static int mtk_pcie_startup_port_v2(stru
+ writew(val, port->base + PCIE_CONF_CLASS_ID);
+ }
+
+- if (soc->need_fix_device_id)
++ if (soc->quirks & MTK_PCIE_FIX_DEVICE_ID)
+ writew(soc->device_id, port->base + PCIE_CONF_DEVICE_ID);
+
+ /* 100ms timeout value should be enough for Gen1/2 training */
+@@ -1129,7 +1137,7 @@ static int mtk_pcie_probe(struct platfor
+
+ host->ops = pcie->soc->ops;
+ host->sysdata = pcie;
+- host->msi_domain = pcie->soc->no_msi;
++ host->msi_domain = !!(pcie->soc->quirks & MTK_PCIE_NO_MSI);
+
+ err = pci_host_probe(host);
+ if (err)
+@@ -1219,9 +1227,9 @@ static const struct dev_pm_ops mtk_pcie_
+ };
+
+ static const struct mtk_pcie_soc mtk_pcie_soc_v1 = {
+- .no_msi = true,
+ .ops = &mtk_pcie_ops,
+ .startup = mtk_pcie_startup_port,
++ .quirks = MTK_PCIE_NO_MSI,
+ };
+
+ static const struct mtk_pcie_soc mtk_pcie_soc_mt2712 = {
+@@ -1231,19 +1239,18 @@ static const struct mtk_pcie_soc mtk_pci
+ };
+
+ static const struct mtk_pcie_soc mtk_pcie_soc_mt7622 = {
+- .need_fix_class_id = true,
+ .ops = &mtk_pcie_ops_v2,
+ .startup = mtk_pcie_startup_port_v2,
+ .setup_irq = mtk_pcie_setup_irq,
++ .quirks = MTK_PCIE_FIX_CLASS_ID,
+ };
+
+ static const struct mtk_pcie_soc mtk_pcie_soc_mt7629 = {
+- .need_fix_class_id = true,
+- .need_fix_device_id = true,
+ .device_id = PCI_DEVICE_ID_MEDIATEK_7629,
+ .ops = &mtk_pcie_ops_v2,
+ .startup = mtk_pcie_startup_port_v2,
+ .setup_irq = mtk_pcie_setup_irq,
++ .quirks = MTK_PCIE_FIX_CLASS_ID | MTK_PCIE_FIX_DEVICE_ID,
+ };
+
+ static const struct of_device_id mtk_pcie_ids[] = {
--- /dev/null
+From stable+bounces-274611-greg=kroah.com@vger.kernel.org Wed Jul 15 01:35:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 19:34:17 -0400
+Subject: PCI: mediatek: Fix IRQ domain leak when port fails to enable
+To: stable@vger.kernel.org
+Cc: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>, Manivannan Sadhasivam <mani@kernel.org>, Caleb James DeLisle <cjd@cjdns.fr>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714233418.3592225-4-sashal@kernel.org>
+
+From: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
+
+[ Upstream commit f865a57896bd92d7662eb2818d8f48872e2cbbc7 ]
+
+When mtk_pcie_enable_port() fails, mtk_pcie_port_free() removes the port
+from pcie->ports and frees the port structure. However, the IRQ domains set
+up earlier by mtk_pcie_init_irq_domain() are never freed.
+
+Fix this by refactoring mtk_pcie_irq_teardown() into a per-port helper,
+mtk_pcie_irq_teardown_port(), and calling it from mtk_pcie_setup() when
+mtk_pcie_enable_port() fails. Since the IRQ teardown must only happen in
+the probe error path (during resume, child devices may have active MSI
+mappings and the NOIRQ context prohibits sleeping locks),
+mtk_pcie_enable_port() is changed to return an error code so callers can
+distinguish the two paths and act accordingly.
+
+This issue was reported by Sashiko while reviewing the EcoNet EN7528 SoC
+support series.
+
+Fixes: b099631df160 ("PCI: mediatek: Add controller support for MT2712 and MT7622")
+Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+Cc: stable@vger.kernel.org # 5.10
+Cc: Caleb James DeLisle <cjd@cjdns.fr>
+Link: https://patch.msgid.link/20260521174617.17692-1-mani@kernel.org
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/controller/pcie-mediatek.c | 67 ++++++++++++++++++++-------------
+ drivers/pci/pci.h | 3 +
+ 2 files changed, 45 insertions(+), 25 deletions(-)
+
+--- a/drivers/pci/controller/pcie-mediatek.c
++++ b/drivers/pci/controller/pcie-mediatek.c
+@@ -540,25 +540,29 @@ static void mtk_pcie_enable_msi(struct m
+ writel(val, port->base + PCIE_INT_MASK);
+ }
+
+-static void mtk_pcie_irq_teardown(struct mtk_pcie *pcie)
++static void mtk_pcie_irq_teardown_port(struct mtk_pcie_port *port)
+ {
+- struct mtk_pcie_port *port, *tmp;
++ irq_set_chained_handler_and_data(port->irq, NULL, NULL);
+
+- list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
+- irq_set_chained_handler_and_data(port->irq, NULL, NULL);
++ if (port->irq_domain)
++ irq_domain_remove(port->irq_domain);
+
+- if (port->irq_domain)
+- irq_domain_remove(port->irq_domain);
++ if (IS_ENABLED(CONFIG_PCI_MSI)) {
++ if (port->msi_domain)
++ irq_domain_remove(port->msi_domain);
++ if (port->inner_domain)
++ irq_domain_remove(port->inner_domain);
++ }
+
+- if (IS_ENABLED(CONFIG_PCI_MSI)) {
+- if (port->msi_domain)
+- irq_domain_remove(port->msi_domain);
+- if (port->inner_domain)
+- irq_domain_remove(port->inner_domain);
+- }
++ irq_dispose_mapping(port->irq);
++}
+
+- irq_dispose_mapping(port->irq);
+- }
++static void mtk_pcie_irq_teardown(struct mtk_pcie *pcie)
++{
++ struct mtk_pcie_port *port, *tmp;
++
++ list_for_each_entry_safe(port, tmp, &pcie->ports, list)
++ mtk_pcie_irq_teardown_port(port);
+ }
+
+ static int mtk_pcie_intx_map(struct irq_domain *domain, unsigned int irq,
+@@ -841,7 +845,7 @@ static int mtk_pcie_startup_port(struct
+ return 0;
+ }
+
+-static void mtk_pcie_enable_port(struct mtk_pcie_port *port)
++static int mtk_pcie_enable_port(struct mtk_pcie_port *port)
+ {
+ struct mtk_pcie *pcie = port->pcie;
+ struct device *dev = pcie->dev;
+@@ -850,7 +854,7 @@ static void mtk_pcie_enable_port(struct
+ err = clk_prepare_enable(port->sys_ck);
+ if (err) {
+ dev_err(dev, "failed to enable sys_ck%d clock\n", port->slot);
+- goto err_sys_clk;
++ return err;
+ }
+
+ err = clk_prepare_enable(port->ahb_ck);
+@@ -898,11 +902,15 @@ static void mtk_pcie_enable_port(struct
+ goto err_phy_on;
+ }
+
+- if (!pcie->soc->startup(port))
+- return;
++ err = pcie->soc->startup(port);
++ if (err) {
++ dev_info(dev, "Port%d link down\n", port->slot);
++ goto err_soc_startup;
++ }
+
+- dev_info(dev, "Port%d link down\n", port->slot);
++ return 0;
+
++err_soc_startup:
+ phy_power_off(port->phy);
+ err_phy_on:
+ phy_exit(port->phy);
+@@ -918,8 +926,8 @@ err_aux_clk:
+ clk_disable_unprepare(port->ahb_ck);
+ err_ahb_clk:
+ clk_disable_unprepare(port->sys_ck);
+-err_sys_clk:
+- mtk_pcie_port_free(port);
++
++ return err;
+ }
+
+ static int mtk_pcie_parse_port(struct mtk_pcie *pcie,
+@@ -1095,8 +1103,13 @@ static int mtk_pcie_setup(struct mtk_pci
+ return err;
+
+ /* enable each port, and then check link status */
+- list_for_each_entry_safe(port, tmp, &pcie->ports, list)
+- mtk_pcie_enable_port(port);
++ list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
++ err = mtk_pcie_enable_port(port);
++ if (err) {
++ mtk_pcie_irq_teardown_port(port);
++ mtk_pcie_port_free(port);
++ }
++ }
+
+ /* power down PCIe subsys if slots are all empty (link down) */
+ if (list_empty(&pcie->ports))
+@@ -1200,14 +1213,18 @@ static int mtk_pcie_resume_noirq(struct
+ {
+ struct mtk_pcie *pcie = dev_get_drvdata(dev);
+ struct mtk_pcie_port *port, *tmp;
++ int err;
+
+ if (list_empty(&pcie->ports))
+ return 0;
+
+ clk_prepare_enable(pcie->free_ck);
+
+- list_for_each_entry_safe(port, tmp, &pcie->ports, list)
+- mtk_pcie_enable_port(port);
++ list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
++ err = mtk_pcie_enable_port(port);
++ if (err)
++ mtk_pcie_port_free(port);
++ }
+
+ /* In case of EP was removed while system suspend. */
+ if (list_empty(&pcie->ports))
+--- a/drivers/pci/pci.h
++++ b/drivers/pci/pci.h
+@@ -14,6 +14,9 @@
+ #define PCI_EXP_AER_FLAGS (PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | \
+ PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE)
+
++/* Power stable to PERST# inactive from PCIe card Electromechanical Spec */
++#define PCIE_T_PVPERL_MS 100
++
+ /*
+ * PCIe r6.0, sec 5.3.3.2.1 <PME Synchronization>
+ * Recommends 1ms to 10ms timeout to check L2 ready.
--- /dev/null
+From stable+bounces-274610-greg=kroah.com@vger.kernel.org Wed Jul 15 01:35:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 19:34:16 -0400
+Subject: PCI: mediatek: Use generic MACRO for TPVPERL delay
+To: stable@vger.kernel.org
+Cc: Christian Marangi <ansuelsmth@gmail.com>, Manivannan Sadhasivam <mani@kernel.org>, AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714233418.3592225-3-sashal@kernel.org>
+
+From: Christian Marangi <ansuelsmth@gmail.com>
+
+[ Upstream commit 2d58bc777728bfc37aa35dce7b90e72296cceb9f ]
+
+Use the generic PCI MACRO for TPVPERL delay to wait for clock and power
+stabilization after PERST# Signal instead of the raw value of 100 ms.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Link: https://patch.msgid.link/20251020111121.31779-5-ansuelsmth@gmail.com
+Stable-dep-of: f865a57896bd ("PCI: mediatek: Fix IRQ domain leak when port fails to enable")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/controller/pcie-mediatek.c | 7 +------
+ 1 file changed, 1 insertion(+), 6 deletions(-)
+
+--- a/drivers/pci/controller/pcie-mediatek.c
++++ b/drivers/pci/controller/pcie-mediatek.c
+@@ -714,12 +714,7 @@ static int mtk_pcie_startup_port_v2(stru
+ */
+ writel(PCIE_LINKDOWN_RST_EN, port->base + PCIE_RST_CTRL);
+
+- /*
+- * Described in PCIe CEM specification sections 2.2 (PERST# Signal) and
+- * 2.2.1 (Initial Power-Up (G3 to S0)). The deassertion of PERST# should
+- * be delayed 100ms (TPVPERL) for the power and clock to become stable.
+- */
+- msleep(100);
++ msleep(PCIE_T_PVPERL_MS);
+
+ /* De-assert PHY, PE, PIPE, MAC and configuration reset */
+ val = readl(port->base + PCIE_RST_CTRL);
--- /dev/null
+From stable+bounces-274692-greg=kroah.com@vger.kernel.org Wed Jul 15 04:47:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 22:45:58 -0400
+Subject: PCI: Move Resizable BAR code to rebar.c
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Bjorn Helgaas" <bhelgaas@google.com>, "Christian König" <christian.koenig@amd.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715024559.106836-5-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit 9f71938cd77f32a448f40a288e409eca60e55486 ]
+
+For lack of a better place to put it, Resizable BAR code has been placed
+inside pci.c and setup-res.c that do not use it for anything. Upcoming
+changes are going to add more Resizable BAR related functions, increasing
+the code size.
+
+As pci.c is huge as is, move the Resizable BAR related code and the BAR
+resize code from setup-res.c to rebar.c.
+
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Reviewed-by: Christian König <christian.koenig@amd.com>
+Link: https://patch.msgid.link/20251113180053.27944-2-ilpo.jarvinen@linux.intel.com
+Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ Documentation/driver-api/pci/pci.rst | 3
+ drivers/pci/Makefile | 2
+ drivers/pci/pci.c | 136 ----------------------
+ drivers/pci/pci.h | 1
+ drivers/pci/rebar.c | 212 +++++++++++++++++++++++++++++++++++
+ drivers/pci/setup-res.c | 64 ----------
+ 6 files changed, 217 insertions(+), 201 deletions(-)
+ create mode 100644 drivers/pci/rebar.c
+
+--- a/Documentation/driver-api/pci/pci.rst
++++ b/Documentation/driver-api/pci/pci.rst
+@@ -31,6 +31,9 @@ PCI Support Library
+ .. kernel-doc:: drivers/pci/slot.c
+ :export:
+
++.. kernel-doc:: drivers/pci/rebar.c
++ :export:
++
+ .. kernel-doc:: drivers/pci/rom.c
+ :export:
+
+--- a/drivers/pci/Makefile
++++ b/drivers/pci/Makefile
+@@ -4,7 +4,7 @@
+
+ obj-$(CONFIG_PCI) += access.o bus.o probe.o host-bridge.o \
+ remove.o pci.o pci-driver.o search.o \
+- pci-sysfs.o rom.o setup-res.o irq.o vpd.o \
++ pci-sysfs.o rebar.o rom.o setup-res.o irq.o vpd.o \
+ setup-bus.o vc.o mmap.o setup-irq.o
+
+ obj-$(CONFIG_PCI) += msi/
+--- a/drivers/pci/pci.c
++++ b/drivers/pci/pci.c
+@@ -1840,33 +1840,6 @@ static void pci_restore_config_space(str
+ }
+ }
+
+-static void pci_restore_rebar_state(struct pci_dev *pdev)
+-{
+- unsigned int pos, nbars, i;
+- u32 ctrl;
+-
+- pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR);
+- if (!pos)
+- return;
+-
+- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+- nbars = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >>
+- PCI_REBAR_CTRL_NBAR_SHIFT;
+-
+- for (i = 0; i < nbars; i++, pos += 8) {
+- struct resource *res;
+- int bar_idx, size;
+-
+- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+- bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
+- res = pdev->resource + bar_idx;
+- size = pci_rebar_bytes_to_size(resource_size(res));
+- ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
+- ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT;
+- pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
+- }
+-}
+-
+ /**
+ * pci_restore_state - Restore the saved state of a PCI device
+ * @dev: PCI device that we're dealing with
+@@ -3784,115 +3757,6 @@ void pci_acs_init(struct pci_dev *dev)
+ }
+
+ /**
+- * pci_rebar_find_pos - find position of resize ctrl reg for BAR
+- * @pdev: PCI device
+- * @bar: BAR to find
+- *
+- * Helper to find the position of the ctrl register for a BAR.
+- * Returns -ENOTSUPP if resizable BARs are not supported at all.
+- * Returns -ENOENT if no ctrl register for the BAR could be found.
+- */
+-static int pci_rebar_find_pos(struct pci_dev *pdev, int bar)
+-{
+- unsigned int pos, nbars, i;
+- u32 ctrl;
+-
+- pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR);
+- if (!pos)
+- return -ENOTSUPP;
+-
+- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+- nbars = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >>
+- PCI_REBAR_CTRL_NBAR_SHIFT;
+-
+- for (i = 0; i < nbars; i++, pos += 8) {
+- int bar_idx;
+-
+- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+- bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
+- if (bar_idx == bar)
+- return pos;
+- }
+-
+- return -ENOENT;
+-}
+-
+-/**
+- * pci_rebar_get_possible_sizes - get possible sizes for BAR
+- * @pdev: PCI device
+- * @bar: BAR to query
+- *
+- * Get the possible sizes of a resizable BAR as bitmask defined in the spec
+- * (bit 0=1MB, bit 19=512GB). Returns 0 if BAR isn't resizable.
+- */
+-u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar)
+-{
+- int pos;
+- u32 cap;
+-
+- pos = pci_rebar_find_pos(pdev, bar);
+- if (pos < 0)
+- return 0;
+-
+- pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap);
+- cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap);
+-
+- /* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */
+- if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f &&
+- bar == 0 && cap == 0x700)
+- return 0x3f00;
+-
+- return cap;
+-}
+-EXPORT_SYMBOL(pci_rebar_get_possible_sizes);
+-
+-/**
+- * pci_rebar_get_current_size - get the current size of a BAR
+- * @pdev: PCI device
+- * @bar: BAR to set size to
+- *
+- * Read the size of a BAR from the resizable BAR config.
+- * Returns size if found or negative error code.
+- */
+-int pci_rebar_get_current_size(struct pci_dev *pdev, int bar)
+-{
+- int pos;
+- u32 ctrl;
+-
+- pos = pci_rebar_find_pos(pdev, bar);
+- if (pos < 0)
+- return pos;
+-
+- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+- return (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> PCI_REBAR_CTRL_BAR_SHIFT;
+-}
+-
+-/**
+- * pci_rebar_set_size - set a new size for a BAR
+- * @pdev: PCI device
+- * @bar: BAR to set size to
+- * @size: new size as defined in the spec (0=1MB, 19=512GB)
+- *
+- * Set the new size of a BAR as defined in the spec.
+- * Returns zero if resizing was successful, error code otherwise.
+- */
+-int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size)
+-{
+- int pos;
+- u32 ctrl;
+-
+- pos = pci_rebar_find_pos(pdev, bar);
+- if (pos < 0)
+- return pos;
+-
+- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+- ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
+- ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT;
+- pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
+- return 0;
+-}
+-
+-/**
+ * pci_enable_atomic_ops_to_root - enable AtomicOp requests to root port
+ * @dev: the PCI device
+ * @cap_mask: mask of desired AtomicOp sizes, including one or more of:
+--- a/drivers/pci/pci.h
++++ b/drivers/pci/pci.h
+@@ -615,6 +615,7 @@ static inline int acpi_get_rc_resources(
+ }
+ #endif
+
++void pci_restore_rebar_state(struct pci_dev *pdev);
+ int pci_rebar_get_current_size(struct pci_dev *pdev, int bar);
+ int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size);
+ static inline u64 pci_rebar_size_to_bytes(int size)
+--- /dev/null
++++ b/drivers/pci/rebar.c
+@@ -0,0 +1,212 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * PCI Resizable BAR Extended Capability handling.
++ */
++
++#include <linux/bitfield.h>
++#include <linux/errno.h>
++#include <linux/export.h>
++#include <linux/ioport.h>
++#include <linux/pci.h>
++#include <linux/types.h>
++
++#include "pci.h"
++
++/**
++ * pci_rebar_find_pos - find position of resize ctrl reg for BAR
++ * @pdev: PCI device
++ * @bar: BAR to find
++ *
++ * Helper to find the position of the ctrl register for a BAR.
++ * Returns -ENOTSUPP if resizable BARs are not supported at all.
++ * Returns -ENOENT if no ctrl register for the BAR could be found.
++ */
++static int pci_rebar_find_pos(struct pci_dev *pdev, int bar)
++{
++ unsigned int pos, nbars, i;
++ u32 ctrl;
++
++ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR);
++ if (!pos)
++ return -ENOTSUPP;
++
++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++ nbars = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >>
++ PCI_REBAR_CTRL_NBAR_SHIFT;
++
++ for (i = 0; i < nbars; i++, pos += 8) {
++ int bar_idx;
++
++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++ bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
++ if (bar_idx == bar)
++ return pos;
++ }
++
++ return -ENOENT;
++}
++
++/**
++ * pci_rebar_get_possible_sizes - get possible sizes for BAR
++ * @pdev: PCI device
++ * @bar: BAR to query
++ *
++ * Get the possible sizes of a resizable BAR as bitmask defined in the spec
++ * (bit 0=1MB, bit 19=512GB). Returns 0 if BAR isn't resizable.
++ */
++u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar)
++{
++ int pos;
++ u32 cap;
++
++ pos = pci_rebar_find_pos(pdev, bar);
++ if (pos < 0)
++ return 0;
++
++ pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap);
++ cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap);
++
++ /* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */
++ if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f &&
++ bar == 0 && cap == 0x700)
++ return 0x3f00;
++
++ return cap;
++}
++EXPORT_SYMBOL(pci_rebar_get_possible_sizes);
++
++/**
++ * pci_rebar_get_current_size - get the current size of a BAR
++ * @pdev: PCI device
++ * @bar: BAR to set size to
++ *
++ * Read the size of a BAR from the resizable BAR config.
++ * Returns size if found or negative error code.
++ */
++int pci_rebar_get_current_size(struct pci_dev *pdev, int bar)
++{
++ int pos;
++ u32 ctrl;
++
++ pos = pci_rebar_find_pos(pdev, bar);
++ if (pos < 0)
++ return pos;
++
++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++ return (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> PCI_REBAR_CTRL_BAR_SHIFT;
++}
++
++/**
++ * pci_rebar_set_size - set a new size for a BAR
++ * @pdev: PCI device
++ * @bar: BAR to set size to
++ * @size: new size as defined in the spec (0=1MB, 19=512GB)
++ *
++ * Set the new size of a BAR as defined in the spec.
++ * Returns zero if resizing was successful, error code otherwise.
++ */
++int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size)
++{
++ int pos;
++ u32 ctrl;
++
++ pos = pci_rebar_find_pos(pdev, bar);
++ if (pos < 0)
++ return pos;
++
++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++ ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
++ ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT;
++ pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
++ return 0;
++}
++
++void pci_restore_rebar_state(struct pci_dev *pdev)
++{
++ unsigned int pos, nbars, i;
++ u32 ctrl;
++
++ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR);
++ if (!pos)
++ return;
++
++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++ nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl);
++
++ for (i = 0; i < nbars; i++, pos += 8) {
++ struct resource *res;
++ int bar_idx, size;
++
++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++ bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
++ res = pci_resource_n(pdev, bar_idx);
++ size = pci_rebar_bytes_to_size(resource_size(res));
++ ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
++ ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT;
++ pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
++ }
++}
++
++/**
++ * pci_resize_resource - reconfigure a Resizable BAR and resources
++ * @dev: the PCI device
++ * @resno: index of the BAR to be resized
++ * @size: new size as defined in the spec (0=1MB, 31=128TB)
++ * @exclude_bars: a mask of BARs that should not be released
++ *
++ * Reconfigure @resno to @size and re-run resource assignment algorithm
++ * with the new size.
++ *
++ * Prior to resize, release @dev resources that share a bridge window with
++ * @resno. This unpins the bridge window resource to allow changing it.
++ *
++ * The caller may prevent releasing a particular BAR by providing
++ * @exclude_bars mask, but this may result in the resize operation failing
++ * due to insufficient space.
++ *
++ * Return: 0 on success, or negative on error. In case of an error, the
++ * resources are restored to their original places.
++ */
++int pci_resize_resource(struct pci_dev *dev, int resno, int size,
++ int exclude_bars)
++{
++ struct pci_host_bridge *host;
++ int old, ret;
++ u32 sizes;
++ u16 cmd;
++
++ /* Check if we must preserve the firmware's resource assignment */
++ host = pci_find_host_bridge(dev->bus);
++ if (host->preserve_config)
++ return -ENOTSUPP;
++
++ pci_read_config_word(dev, PCI_COMMAND, &cmd);
++ if (cmd & PCI_COMMAND_MEMORY)
++ return -EBUSY;
++
++ sizes = pci_rebar_get_possible_sizes(dev, resno);
++ if (!sizes)
++ return -ENOTSUPP;
++
++ if (!(sizes & BIT(size)))
++ return -EINVAL;
++
++ old = pci_rebar_get_current_size(dev, resno);
++ if (old < 0)
++ return old;
++
++ ret = pci_rebar_set_size(dev, resno, size);
++ if (ret)
++ return ret;
++
++ ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars);
++ if (ret)
++ goto error_resize;
++
++ return 0;
++
++error_resize:
++ pci_rebar_set_size(dev, resno, old);
++ return ret;
++}
++EXPORT_SYMBOL(pci_resize_resource);
+--- a/drivers/pci/setup-res.c
++++ b/drivers/pci/setup-res.c
+@@ -427,70 +427,6 @@ void pci_release_resource(struct pci_dev
+ }
+ EXPORT_SYMBOL(pci_release_resource);
+
+-/**
+- * pci_resize_resource - reconfigure a Resizable BAR and resources
+- * @dev: the PCI device
+- * @resno: index of the BAR to be resized
+- * @size: new size as defined in the spec (0=1MB, 31=128TB)
+- * @exclude_bars: a mask of BARs that should not be released
+- *
+- * Reconfigure @resno to @size and re-run resource assignment algorithm
+- * with the new size.
+- *
+- * Prior to resize, release @dev resources that share a bridge window with
+- * @resno. This unpins the bridge window resource to allow changing it.
+- *
+- * The caller may prevent releasing a particular BAR by providing
+- * @exclude_bars mask, but this may result in the resize operation failing
+- * due to insufficient space.
+- *
+- * Return: 0 on success, or negative on error. In case of an error, the
+- * resources are restored to their original places.
+- */
+-int pci_resize_resource(struct pci_dev *dev, int resno, int size,
+- int exclude_bars)
+-{
+- struct pci_host_bridge *host;
+- int old, ret;
+- u32 sizes;
+- u16 cmd;
+-
+- /* Check if we must preserve the firmware's resource assignment */
+- host = pci_find_host_bridge(dev->bus);
+- if (host->preserve_config)
+- return -ENOTSUPP;
+-
+- pci_read_config_word(dev, PCI_COMMAND, &cmd);
+- if (cmd & PCI_COMMAND_MEMORY)
+- return -EBUSY;
+-
+- sizes = pci_rebar_get_possible_sizes(dev, resno);
+- if (!sizes)
+- return -ENOTSUPP;
+-
+- if (!(sizes & BIT(size)))
+- return -EINVAL;
+-
+- old = pci_rebar_get_current_size(dev, resno);
+- if (old < 0)
+- return old;
+-
+- ret = pci_rebar_set_size(dev, resno, size);
+- if (ret)
+- return ret;
+-
+- ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars);
+- if (ret)
+- goto error_resize;
+-
+- return 0;
+-
+-error_resize:
+- pci_rebar_set_size(dev, resno, old);
+- return ret;
+-}
+-EXPORT_SYMBOL(pci_resize_resource);
+-
+ int pci_enable_resources(struct pci_dev *dev, int mask)
+ {
+ u16 cmd, old_cmd;
--- /dev/null
+From stable+bounces-274687-greg=kroah.com@vger.kernel.org Wed Jul 15 04:46:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 22:45:54 -0400
+Subject: PCI: Prevent resource tree corruption when BAR resize fails
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Simon Richter" <Simon.Richter@hogyros.de>, "Alex Bennée" <alex.bennee@linaro.org>, "Bjorn Helgaas" <bhelgaas@google.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260715024559.106836-1-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit 91c4c89db41499eea1b29c56655f79c3bae66e93 ]
+
+pbus_reassign_bridge_resources() saves bridge windows into the saved
+list before attempting to adjust resource assignments to perform a BAR
+resize operation. If resource adjustments cannot be completed fully,
+rollback is attempted by restoring the resource from the saved list.
+
+The rollback, however, does not check whether the resources it restores were
+assigned by the partial resize attempt. If restore changes addresses of the
+resource, it can result in corrupting the resource tree.
+
+An example of a corrupted resource tree with overlapping addresses:
+
+ 6200000000000-6203fbfffffff : pciex@620c3c0000000
+ 6200000000000-6203fbff0ffff : PCI Bus 0030:01
+ 6200020000000-62000207fffff : 0030:01:00.0
+ 6200000000000-6203fbff0ffff : PCI Bus 0030:02
+
+A resource that are assigned into the resource tree must remain
+unchanged. Thus, release such a resource before attempting to restore
+and claim it back.
+
+For simplicity, always do the release and claim back for the resource
+even in the cases where it is restored to the same address range.
+
+Note: this fix may "break" some cases where devices "worked" because
+the resource tree corruption allowed address space double counting to
+fit more resource than what can now be assigned without double
+counting. The upcoming changes to BAR resizing should address those
+scenarios (to the extent possible).
+
+Fixes: 8bb705e3e79d ("PCI: Add pci_resize_resource() for resizing BARs")
+Reported-by: Simon Richter <Simon.Richter@hogyros.de>
+Link: https://lore.kernel.org/linux-pci/67840a16-99b4-4d8c-9b5c-4721ab0970a2@hogyros.de/
+Reported-by: Alex Bennée <alex.bennee@linaro.org>
+Link: https://lore.kernel.org/linux-pci/874irqop6b.fsf@draig.linaro.org/
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Tested-by: Alex Bennée <alex.bennee@linaro.org> # AVA, AMD GPU
+Link: https://patch.msgid.link/20251113162628.5946-2-ilpo.jarvinen@linux.intel.com
+Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/setup-bus.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/drivers/pci/setup-bus.c
++++ b/drivers/pci/setup-bus.c
+@@ -2314,6 +2314,11 @@ cleanup:
+ bridge = dev_res->dev;
+ i = res - bridge->resource;
+
++ if (res->parent) {
++ release_child_resources(res);
++ pci_release_resource(bridge, i);
++ }
++
+ res->start = dev_res->start;
+ res->end = dev_res->end;
+ res->flags = dev_res->flags;
--- /dev/null
+From stable+bounces-274691-greg=kroah.com@vger.kernel.org Wed Jul 15 04:47:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 22:45:59 -0400
+Subject: PCI: Skip Resizable BAR restore on read error
+To: stable@vger.kernel.org
+Cc: Marco Nenciarini <mnencia@kcore.it>, Bjorn Helgaas <bhelgaas@google.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715024559.106836-6-sashal@kernel.org>
+
+From: Marco Nenciarini <mnencia@kcore.it>
+
+[ Upstream commit ee7471fe968d210939be9046089a924cd23c8c3b ]
+
+pci_restore_rebar_state() uses the Resizable BAR Control register to decide
+how many BARs to restore (nbars) and which BAR each iteration addresses
+(bar_idx).
+
+When a device does not respond, config reads typically return
+PCI_ERROR_RESPONSE (~0). Both fields are 3 bits wide, so nbars and bar_idx
+both evaluate to 7, past the spec's valid ranges for both fields.
+pci_resource_n() then returns an unrelated resource slot, whose size is
+used to derive a nonsensical value written back to the Resizable BAR
+Control register.
+
+Bail out if any Resizable BAR Control read returns PCI_ERROR_RESPONSE. No
+further BARs are touched, which is safe because a config read that returns
+PCI_ERROR_RESPONSE indicates the device is unreachable and restoration is
+pointless.
+
+Fixes: d3252ace0bc6 ("PCI: Restore resized BAR state on resume")
+Signed-off-by: Marco Nenciarini <mnencia@kcore.it>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Cc: stable@vger.kernel.org
+Link: https://patch.msgid.link/666cac19b5daa0ab0e0ab64454e76b4d24465dbd.1776429882.git.mnencia@kcore.it
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pci/rebar.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/drivers/pci/rebar.c
++++ b/drivers/pci/rebar.c
+@@ -131,6 +131,9 @@ void pci_restore_rebar_state(struct pci_
+ return;
+
+ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++ if (PCI_POSSIBLE_ERROR(ctrl))
++ return;
++
+ nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl);
+
+ for (i = 0; i < nbars; i++, pos += 8) {
+@@ -138,6 +141,9 @@ void pci_restore_rebar_state(struct pci_
+ int bar_idx, size;
+
+ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++ if (PCI_POSSIBLE_ERROR(ctrl))
++ return;
++
+ bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
+ res = pci_resource_n(pdev, bar_idx);
+ size = pci_rebar_bytes_to_size(resource_size(res));
--- /dev/null
+From stable+bounces-276818-greg=kroah.com@vger.kernel.org Thu Jul 16 21:03:47 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 15:03:40 -0400
+Subject: perf/x86/intel/uncore: Defer ADL global PMON enable to enable_box()
+To: stable@vger.kernel.org
+Cc: Zide Chen <zide.chen@intel.com>, "Peter Zijlstra (Intel)" <peterz@infradead.org>, Dapeng Mi <dapeng1.mi@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716190340.1045396-1-sashal@kernel.org>
+
+From: Zide Chen <zide.chen@intel.com>
+
+[ Upstream commit 9a0bb848a37150aeccc10088e141339917d995dc ]
+
+On some Raptor Cove CPUs, enabling uncore PMON globally at driver init
+may increase power consumption even when no perf events are in use.
+
+Drop adl_uncore_msr_init_box() and defer programming the global control
+register to enable_box(), so it is only set when a box is actually used.
+
+IMC and IMC freerunning counters use a separate control path and are
+unaffected.
+
+Fixes: 772ed05f3c5c ("perf/x86/intel/uncore: Add Alder Lake support")
+Signed-off-by: Zide Chen <zide.chen@intel.com>
+Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
+Reviewed-by: Dapeng Mi <dapeng1.mi@linux.intel.com>
+Cc: stable@vger.kernel.org
+Link: https://patch.msgid.link/20260602144908.263680-5-zide.chen@intel.com
+[ deleted adl_uncore_msr_init_box() in its 6.12 wrmsrl() spelling since the tree predates the wrmsrq() rename ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/x86/events/intel/uncore_snb.c | 7 -------
+ 1 file changed, 7 deletions(-)
+
+--- a/arch/x86/events/intel/uncore_snb.c
++++ b/arch/x86/events/intel/uncore_snb.c
+@@ -537,12 +537,6 @@ void tgl_uncore_cpu_init(void)
+ skl_uncore_msr_ops.init_box = rkl_uncore_msr_init_box;
+ }
+
+-static void adl_uncore_msr_init_box(struct intel_uncore_box *box)
+-{
+- if (box->pmu->pmu_idx == 0)
+- wrmsrl(ADL_UNC_PERF_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN);
+-}
+-
+ static void adl_uncore_msr_enable_box(struct intel_uncore_box *box)
+ {
+ wrmsrl(ADL_UNC_PERF_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN);
+@@ -561,7 +555,6 @@ static void adl_uncore_msr_exit_box(stru
+ }
+
+ static struct intel_uncore_ops adl_uncore_msr_ops = {
+- .init_box = adl_uncore_msr_init_box,
+ .enable_box = adl_uncore_msr_enable_box,
+ .disable_box = adl_uncore_msr_disable_box,
+ .exit_box = adl_uncore_msr_exit_box,
--- /dev/null
+From stable+bounces-277024-greg=kroah.com@vger.kernel.org Fri Jul 17 13:43:32 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 07:18:20 -0400
+Subject: proc: Move fdinfo PTRACE_MODE_READ check into the inode .permission operation
+To: stable@vger.kernel.org
+Cc: "Tyler Hicks (Microsoft)" <code@tyhicks.com>, "Kevin Parsons (Microsoft)" <parsonskev@gmail.com>, "Christian Brauner" <brauner@kernel.org>, "Christian König" <christian.koenig@amd.com>, "Jann Horn" <jannh@google.com>, "Kalesh Singh" <kaleshsingh@google.com>, "Hardik Garg" <hargar@linux.microsoft.com>, "Allen Pais" <apais@linux.microsoft.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260717111823.1573608-2-sashal@kernel.org>
+
+From: "Tyler Hicks (Microsoft)" <code@tyhicks.com>
+
+[ Upstream commit 0a960ba49869ebe8ff859d000351504dd6b93b68 ]
+
+The following commits loosened the permissions of /proc/<PID>/fdinfo/
+directory, as well as the files within it, from 0500 to 0555 while also
+introducing a PTRACE_MODE_READ check between the current task and
+<PID>'s task:
+
+ - commit 7bc3fa0172a4 ("procfs: allow reading fdinfo with PTRACE_MODE_READ")
+ - commit 1927e498aee1 ("procfs: prevent unprivileged processes accessing fdinfo dir")
+
+Before those changes, inode based system calls like inotify_add_watch(2)
+would fail when the current task didn't have sufficient read permissions:
+
+ [...]
+ lstat("/proc/1/task/1/fdinfo", {st_mode=S_IFDIR|0500, st_size=0, ...}) = 0
+ inotify_add_watch(64, "/proc/1/task/1/fdinfo",
+ IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_CREATE|IN_DELETE|
+ IN_ONLYDIR|IN_DONT_FOLLOW|IN_EXCL_UNLINK) = -1 EACCES (Permission denied)
+ [...]
+
+This matches the documented behavior in the inotify_add_watch(2) man
+page:
+
+ ERRORS
+ EACCES Read access to the given file is not permitted.
+
+After those changes, inotify_add_watch(2) started succeeding despite the
+current task not having PTRACE_MODE_READ privileges on the target task:
+
+ [...]
+ lstat("/proc/1/task/1/fdinfo", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
+ inotify_add_watch(64, "/proc/1/task/1/fdinfo",
+ IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_CREATE|IN_DELETE|
+ IN_ONLYDIR|IN_DONT_FOLLOW|IN_EXCL_UNLINK) = 1757
+ openat(AT_FDCWD, "/proc/1/task/1/fdinfo",
+ O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = -1 EACCES (Permission denied)
+ [...]
+
+This change in behavior broke .NET prior to v7. See the github link
+below for the v7 commit that inadvertently/quietly (?) fixed .NET after
+the kernel changes mentioned above.
+
+Return to the old behavior by moving the PTRACE_MODE_READ check out of
+the file .open operation and into the inode .permission operation:
+
+ [...]
+ lstat("/proc/1/task/1/fdinfo", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
+ inotify_add_watch(64, "/proc/1/task/1/fdinfo",
+ IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_CREATE|IN_DELETE|
+ IN_ONLYDIR|IN_DONT_FOLLOW|IN_EXCL_UNLINK) = -1 EACCES (Permission denied)
+ [...]
+
+Reported-by: Kevin Parsons (Microsoft) <parsonskev@gmail.com>
+Link: https://github.com/dotnet/runtime/commit/89e5469ac591b82d38510fe7de98346cce74ad4f
+Link: https://stackoverflow.com/questions/75379065/start-self-contained-net6-build-exe-as-service-on-raspbian-system-unauthorizeda
+Fixes: 7bc3fa0172a4 ("procfs: allow reading fdinfo with PTRACE_MODE_READ")
+Cc: stable@vger.kernel.org
+Cc: Christian Brauner <brauner@kernel.org>
+Cc: Christian König <christian.koenig@amd.com>
+Cc: Jann Horn <jannh@google.com>
+Cc: Kalesh Singh <kaleshsingh@google.com>
+Cc: Hardik Garg <hargar@linux.microsoft.com>
+Cc: Allen Pais <apais@linux.microsoft.com>
+Signed-off-by: Tyler Hicks (Microsoft) <code@tyhicks.com>
+Link: https://lore.kernel.org/r/20240501005646.745089-1-code@tyhicks.com
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 6255da28d4bb ("proc: protect ptrace_may_access() with exec_update_lock (FD links)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/proc/fd.c | 42 ++++++++++++++++++++----------------------
+ 1 file changed, 20 insertions(+), 22 deletions(-)
+
+--- a/fs/proc/fd.c
++++ b/fs/proc/fd.c
+@@ -72,7 +72,18 @@ out:
+ return 0;
+ }
+
+-static int proc_fdinfo_access_allowed(struct inode *inode)
++static int seq_fdinfo_open(struct inode *inode, struct file *file)
++{
++ return single_open(file, seq_show, inode);
++}
++
++/**
++ * Shared /proc/pid/fdinfo and /proc/pid/fdinfo/fd permission helper to ensure
++ * that the current task has PTRACE_MODE_READ in addition to the normal
++ * POSIX-like checks.
++ */
++static int proc_fdinfo_permission(struct mnt_idmap *idmap, struct inode *inode,
++ int mask)
+ {
+ bool allowed = false;
+ struct task_struct *task = get_proc_task(inode);
+@@ -86,18 +97,13 @@ static int proc_fdinfo_access_allowed(st
+ if (!allowed)
+ return -EACCES;
+
+- return 0;
++ return generic_permission(idmap, inode, mask);
+ }
+
+-static int seq_fdinfo_open(struct inode *inode, struct file *file)
+-{
+- int ret = proc_fdinfo_access_allowed(inode);
+-
+- if (ret)
+- return ret;
+-
+- return single_open(file, seq_show, inode);
+-}
++static const struct inode_operations proc_fdinfo_file_inode_operations = {
++ .permission = proc_fdinfo_permission,
++ .setattr = proc_setattr,
++};
+
+ static const struct file_operations proc_fdinfo_file_operations = {
+ .open = seq_fdinfo_open,
+@@ -339,6 +345,8 @@ static struct dentry *proc_fdinfo_instan
+ ei = PROC_I(inode);
+ ei->fd = data->fd;
+
++ inode->i_op = &proc_fdinfo_file_inode_operations;
++
+ inode->i_fop = &proc_fdinfo_file_operations;
+ tid_fd_update_inode(task, inode, 0);
+
+@@ -358,23 +366,13 @@ static int proc_readfdinfo(struct file *
+ proc_fdinfo_instantiate);
+ }
+
+-static int proc_open_fdinfo(struct inode *inode, struct file *file)
+-{
+- int ret = proc_fdinfo_access_allowed(inode);
+-
+- if (ret)
+- return ret;
+-
+- return 0;
+-}
+-
+ const struct inode_operations proc_fdinfo_inode_operations = {
+ .lookup = proc_lookupfdinfo,
++ .permission = proc_fdinfo_permission,
+ .setattr = proc_setattr,
+ };
+
+ const struct file_operations proc_fdinfo_operations = {
+- .open = proc_open_fdinfo,
+ .read = generic_read_dir,
+ .iterate_shared = proc_readfdinfo,
+ .llseek = generic_file_llseek,
--- /dev/null
+From stable+bounces-277026-greg=kroah.com@vger.kernel.org Fri Jul 17 13:42:40 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 07:18:22 -0400
+Subject: proc: protect ptrace_may_access() with exec_update_lock (FD links)
+To: stable@vger.kernel.org
+Cc: Jann Horn <jannh@google.com>, "Christian Brauner (Amutable)" <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717111823.1573608-4-sashal@kernel.org>
+
+From: Jann Horn <jannh@google.com>
+
+[ Upstream commit 6255da28d4bb5349fe18e84cb043ccd394eba75d ]
+
+proc_pid_get_link() and proc_pid_readlink() currently look up the task from
+the pid once, then do the ptrace access check on that task, then look up
+the task from the pid a second time to do the actual access.
+That's racy in several ways.
+
+To fix it, pass the task to the ->proc_get_link() handler, and instead of
+proc_fd_access_allowed(), introduce a new helper call_proc_get_link() that
+looks up and locks the task, does the access check, and calls
+->proc_get_link().
+
+Fixes: 778c1144771f ("[PATCH] proc: Use sane permission checks on the /proc/<pid>/fd/ symlinks")
+Cc: stable@vger.kernel.org
+Signed-off-by: Jann Horn <jannh@google.com>
+Link: https://patch.msgid.link/20260518-procfs-lockfix-part1-v1-2-5c3d20e0ac33@google.com
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/proc/base.c | 119 ++++++++++++++++++++---------------------------------
+ fs/proc/fd.c | 25 ++++-------
+ fs/proc/internal.h | 2
+ 3 files changed, 58 insertions(+), 88 deletions(-)
+
+--- a/fs/proc/base.c
++++ b/fs/proc/base.c
+@@ -217,33 +217,24 @@ static int get_task_root(struct task_str
+ return result;
+ }
+
+-static int proc_cwd_link(struct dentry *dentry, struct path *path)
++static int proc_cwd_link(struct dentry *dentry, struct path *path,
++ struct task_struct *task)
+ {
+- struct task_struct *task = get_proc_task(d_inode(dentry));
+ int result = -ENOENT;
+
+- if (task) {
+- task_lock(task);
+- if (task->fs) {
+- get_fs_pwd(task->fs, path);
+- result = 0;
+- }
+- task_unlock(task);
+- put_task_struct(task);
++ task_lock(task);
++ if (task->fs) {
++ get_fs_pwd(task->fs, path);
++ result = 0;
+ }
++ task_unlock(task);
+ return result;
+ }
+
+-static int proc_root_link(struct dentry *dentry, struct path *path)
++static int proc_root_link(struct dentry *dentry, struct path *path,
++ struct task_struct *task)
+ {
+- struct task_struct *task = get_proc_task(d_inode(dentry));
+- int result = -ENOENT;
+-
+- if (task) {
+- result = get_task_root(task, path);
+- put_task_struct(task);
+- }
+- return result;
++ return get_task_root(task, path);
+ }
+
+ /*
+@@ -703,23 +694,6 @@ static int proc_pid_syscall(struct seq_f
+ /* Here the fs part begins */
+ /************************************************************************/
+
+-/* permission checks */
+-static bool proc_fd_access_allowed(struct inode *inode)
+-{
+- struct task_struct *task;
+- bool allowed = false;
+- /* Allow access to a task's file descriptors if it is us or we
+- * may use ptrace attach to the process and find out that
+- * information.
+- */
+- task = get_proc_task(inode);
+- if (task) {
+- allowed = ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS);
+- put_task_struct(task);
+- }
+- return allowed;
+-}
+-
+ int proc_nochmod_setattr(struct user_namespace *mnt_userns, struct dentry *dentry,
+ struct iattr *attr)
+ {
+@@ -1778,16 +1752,12 @@ static const struct file_operations proc
+ .release = single_release,
+ };
+
+-static int proc_exe_link(struct dentry *dentry, struct path *exe_path)
++static int proc_exe_link(struct dentry *dentry, struct path *exe_path,
++ struct task_struct *task)
+ {
+- struct task_struct *task;
+ struct file *exe_file;
+
+- task = get_proc_task(d_inode(dentry));
+- if (!task)
+- return -ENOENT;
+ exe_file = get_task_exe_file(task);
+- put_task_struct(task);
+ if (exe_file) {
+ *exe_path = exe_file->f_path;
+ path_get(&exe_file->f_path);
+@@ -1797,26 +1767,42 @@ static int proc_exe_link(struct dentry *
+ return -ENOENT;
+ }
+
++static int call_proc_get_link(struct dentry *dentry, struct inode *inode, struct path *path_out)
++{
++ struct task_struct *task;
++ int ret;
++
++ task = get_proc_task(inode);
++ if (!task)
++ return -ENOENT;
++ ret = down_read_killable(&task->signal->exec_update_lock);
++ if (ret)
++ goto out_put_task;
++ if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) {
++ ret = -EACCES;
++ goto out;
++ }
++ ret = PROC_I(inode)->op.proc_get_link(dentry, path_out, task);
++
++out:
++ up_read(&task->signal->exec_update_lock);
++out_put_task:
++ put_task_struct(task);
++ return ret;
++}
++
+ static const char *proc_pid_get_link(struct dentry *dentry,
+ struct inode *inode,
+ struct delayed_call *done)
+ {
+ struct path path;
+- int error = -EACCES;
++ int error;
+
+ if (!dentry)
+ return ERR_PTR(-ECHILD);
+-
+- /* Are we allowed to snoop on the tasks file descriptors? */
+- if (!proc_fd_access_allowed(inode))
+- goto out;
+-
+- error = PROC_I(inode)->op.proc_get_link(dentry, &path);
+- if (error)
+- goto out;
+-
+- error = nd_jump_link(&path);
+-out:
++ error = call_proc_get_link(dentry, inode, &path);
++ if (!error)
++ error = nd_jump_link(&path);
+ return ERR_PTR(error);
+ }
+
+@@ -1850,17 +1836,11 @@ static int proc_pid_readlink(struct dent
+ struct inode *inode = d_inode(dentry);
+ struct path path;
+
+- /* Are we allowed to snoop on the tasks file descriptors? */
+- if (!proc_fd_access_allowed(inode))
+- goto out;
+-
+- error = PROC_I(inode)->op.proc_get_link(dentry, &path);
+- if (error)
+- goto out;
+-
+- error = do_proc_readlink(&path, buffer, buflen);
+- path_put(&path);
+-out:
++ error = call_proc_get_link(dentry, inode, &path);
++ if (!error) {
++ error = do_proc_readlink(&path, buffer, buflen);
++ path_put(&path);
++ }
+ return error;
+ }
+
+@@ -2248,21 +2228,16 @@ static const struct dentry_operations ti
+ .d_delete = pid_delete_dentry,
+ };
+
+-static int map_files_get_link(struct dentry *dentry, struct path *path)
++static int map_files_get_link(struct dentry *dentry, struct path *path,
++ struct task_struct *task)
+ {
+ unsigned long vm_start, vm_end;
+ struct vm_area_struct *vma;
+- struct task_struct *task;
+ struct mm_struct *mm;
+ int rc;
+
+ rc = -ENOENT;
+- task = get_proc_task(d_inode(dentry));
+- if (!task)
+- goto out;
+-
+ mm = get_task_mm(task);
+- put_task_struct(task);
+ if (!mm)
+ goto out;
+
+--- a/fs/proc/fd.c
++++ b/fs/proc/fd.c
+@@ -170,24 +170,19 @@ static const struct dentry_operations ti
+ .d_delete = pid_delete_dentry,
+ };
+
+-static int proc_fd_link(struct dentry *dentry, struct path *path)
++static int proc_fd_link(struct dentry *dentry, struct path *path,
++ struct task_struct *task)
+ {
+- struct task_struct *task;
+ int ret = -ENOENT;
++ unsigned int fd = proc_fd(d_inode(dentry));
++ struct file *fd_file;
+
+- task = get_proc_task(d_inode(dentry));
+- if (task) {
+- unsigned int fd = proc_fd(d_inode(dentry));
+- struct file *fd_file;
+-
+- fd_file = fget_task(task, fd);
+- if (fd_file) {
+- *path = fd_file->f_path;
+- path_get(&fd_file->f_path);
+- ret = 0;
+- fput(fd_file);
+- }
+- put_task_struct(task);
++ fd_file = fget_task(task, fd);
++ if (fd_file) {
++ *path = fd_file->f_path;
++ path_get(&fd_file->f_path);
++ ret = 0;
++ fput(fd_file);
+ }
+
+ return ret;
+--- a/fs/proc/internal.h
++++ b/fs/proc/internal.h
+@@ -107,7 +107,7 @@ extern struct kmem_cache *proc_dir_entry
+ void pde_free(struct proc_dir_entry *pde);
+
+ union proc_op {
+- int (*proc_get_link)(struct dentry *, struct path *);
++ int (*proc_get_link)(struct dentry *, struct path *, struct task_struct *);
+ int (*proc_show)(struct seq_file *m,
+ struct pid_namespace *ns, struct pid *pid,
+ struct task_struct *task);
--- /dev/null
+From stable+bounces-277097-greg=kroah.com@vger.kernel.org Fri Jul 17 17:00:45 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 11:00:07 -0400
+Subject: proc: protect ptrace_may_access() with exec_update_lock (part 1)
+To: stable@vger.kernel.org
+Cc: Jann Horn <jannh@google.com>, "Christian Brauner (Amutable)" <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717150007.1930653-3-sashal@kernel.org>
+
+From: Jann Horn <jannh@google.com>
+
+[ Upstream commit 6650527444dadc63d84aa939d14ecba4fadb2f69 ]
+
+Fix the easy cases where procfs currently calls ptrace_may_access() without
+exec_update_lock protection, where the fix is to simply add the extra lock
+or use mm_access():
+
+ - do_task_stat(): grab exec_update_lock
+ - proc_pid_wchan(): grab exec_update_lock
+ - proc_map_files_lookup(): use mm_access() instead of get_task_mm()
+ - proc_map_files_readdir(): use mm_access() instead of get_task_mm()
+ - proc_ns_get_link(): grab exec_update_lock
+ - proc_ns_readlink(): grab exec_update_lock
+
+Fixes: f83ce3e6b02d ("proc: avoid information leaks to non-privileged processes")
+Cc: stable@vger.kernel.org
+Signed-off-by: Jann Horn <jannh@google.com>
+Link: https://patch.msgid.link/20260518-procfs-lockfix-part1-v1-1-5c3d20e0ac33@google.com
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/proc/array.c | 6 ++++++
+ fs/proc/base.c | 41 ++++++++++++++++++++++-------------------
+ fs/proc/namespaces.c | 12 ++++++++++++
+ 3 files changed, 40 insertions(+), 19 deletions(-)
+
+--- a/fs/proc/array.c
++++ b/fs/proc/array.c
+@@ -473,6 +473,11 @@ static int do_task_stat(struct seq_file
+ unsigned long flags;
+ int exit_code = task->exit_code;
+ struct signal_struct *sig = task->signal;
++ int ret;
++
++ ret = down_read_killable(&task->signal->exec_update_lock);
++ if (ret)
++ return ret;
+
+ state = *get_task_state(task);
+ vsize = eip = esp = 0;
+@@ -648,6 +653,7 @@ static int do_task_stat(struct seq_file
+ seq_puts(m, " 0");
+
+ seq_putc(m, '\n');
++ up_read(&task->signal->exec_update_lock);
+ if (mm)
+ mmput(mm);
+ return 0;
+--- a/fs/proc/base.c
++++ b/fs/proc/base.c
+@@ -413,18 +413,24 @@ static int proc_pid_wchan(struct seq_fil
+ {
+ unsigned long wchan;
+ char symname[KSYM_NAME_LEN];
++ int err;
+
++ err = down_read_killable(&task->signal->exec_update_lock);
++ if (err)
++ return err;
+ if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
+ goto print0;
+
+ wchan = get_wchan(task);
+ if (wchan && !lookup_symbol_name(wchan, symname)) {
+ seq_puts(m, symname);
++ up_read(&task->signal->exec_update_lock);
+ return 0;
+ }
+
+ print0:
+ seq_putc(m, '0');
++ up_read(&task->signal->exec_update_lock);
+ return 0;
+ }
+ #endif /* CONFIG_KALLSYMS */
+@@ -2333,17 +2339,15 @@ static struct dentry *proc_map_files_loo
+ if (!task)
+ goto out;
+
+- result = ERR_PTR(-EACCES);
+- if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
+- goto out_put_task;
+-
+ result = ERR_PTR(-ENOENT);
+ if (dname_to_vma_addr(dentry, &vm_start, &vm_end))
+ goto out_put_task;
+
+- mm = get_task_mm(task);
+- if (!mm)
++ mm = mm_access(task, PTRACE_MODE_READ_FSCREDS);
++ if (IS_ERR(mm)) {
++ result = ERR_CAST(mm);
+ goto out_put_task;
++ }
+
+ result = ERR_PTR(-EINTR);
+ if (mmap_read_lock_killable(mm))
+@@ -2393,23 +2397,22 @@ proc_map_files_readdir(struct file *file
+ if (!task)
+ goto out;
+
+- ret = -EACCES;
+- if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
+- goto out_put_task;
+-
+ ret = 0;
+ if (!dir_emit_dots(file, ctx))
+ goto out_put_task;
+
+- mm = get_task_mm(task);
+- if (!mm)
++ mm = mm_access(task, PTRACE_MODE_READ_FSCREDS);
++ if (IS_ERR(mm)) {
++ ret = PTR_ERR(mm);
++ /* if the task has no mm, the directory should just be empty */
++ if (ret == -ESRCH)
++ ret = 0;
+ goto out_put_task;
++ }
+
+ ret = mmap_read_lock_killable(mm);
+- if (ret) {
+- mmput(mm);
+- goto out_put_task;
+- }
++ if (ret)
++ goto out_put_mm;
+
+ nr_files = 0;
+
+@@ -2435,8 +2438,7 @@ proc_map_files_readdir(struct file *file
+ if (!p) {
+ ret = -ENOMEM;
+ mmap_read_unlock(mm);
+- mmput(mm);
+- goto out_put_task;
++ goto out_put_mm;
+ }
+
+ p->start = vma->vm_start;
+@@ -2444,7 +2446,6 @@ proc_map_files_readdir(struct file *file
+ p->mode = vma->vm_file->f_mode;
+ }
+ mmap_read_unlock(mm);
+- mmput(mm);
+
+ for (i = 0; i < nr_files; i++) {
+ char buf[4 * sizeof(long) + 2]; /* max: %lx-%lx\0 */
+@@ -2461,6 +2462,8 @@ proc_map_files_readdir(struct file *file
+ ctx->pos++;
+ }
+
++out_put_mm:
++ mmput(mm);
+ out_put_task:
+ put_task_struct(task);
+ out:
+--- a/fs/proc/namespaces.c
++++ b/fs/proc/namespaces.c
+@@ -55,6 +55,10 @@ static const char *proc_ns_get_link(stru
+ if (!task)
+ return ERR_PTR(-EACCES);
+
++ error = down_read_killable(&task->signal->exec_update_lock);
++ if (error)
++ goto out_put_task;
++
+ if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
+ goto out;
+
+@@ -64,6 +68,8 @@ static const char *proc_ns_get_link(stru
+
+ error = nd_jump_link(&ns_path);
+ out:
++ up_read(&task->signal->exec_update_lock);
++out_put_task:
+ put_task_struct(task);
+ return ERR_PTR(error);
+ }
+@@ -80,11 +86,17 @@ static int proc_ns_readlink(struct dentr
+ if (!task)
+ return res;
+
++ res = down_read_killable(&task->signal->exec_update_lock);
++ if (res)
++ goto out_put_task;
++
+ if (ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) {
+ res = ns_get_name(name, sizeof(name), task, ns_ops);
+ if (res >= 0)
+ res = readlink_copy(buffer, buflen, name);
+ }
++ up_read(&task->signal->exec_update_lock);
++out_put_task:
+ put_task_struct(task);
+ return res;
+ }
--- /dev/null
+From stable+bounces-277023-greg=kroah.com@vger.kernel.org Fri Jul 17 13:43:33 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 07:18:21 -0400
+Subject: proc: rename proc_setattr to proc_nochmod_setattr
+To: stable@vger.kernel.org
+Cc: Christoph Hellwig <hch@lst.de>, Jan Kara <jack@suse.cz>, Christian Brauner <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717111823.1573608-3-sashal@kernel.org>
+
+From: Christoph Hellwig <hch@lst.de>
+
+[ Upstream commit 690005b0b1e6b567c88b7790e6d90d4d6c9e09cc ]
+
+What is currently proc_setattr is a special version added after the more
+general procfs ->seattr in commit 6d76fa58b050 ("Don't allow chmod() on
+the /proc/<pid>/ files"). Give it a name that reflects that to free the
+proc_setattr name and better describe what is doing.
+
+Signed-off-by: Christoph Hellwig <hch@lst.de>
+Link: https://patch.msgid.link/20260325063711.3298685-5-hch@lst.de
+Reviewed-by: Jan Kara <jack@suse.cz>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 6255da28d4bb ("proc: protect ptrace_may_access() with exec_update_lock (FD links)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/proc/base.c | 22 +++++++++++-----------
+ fs/proc/fd.c | 10 +++++-----
+ fs/proc/internal.h | 4 ++--
+ fs/proc/namespaces.c | 4 ++--
+ fs/proc/proc_net.c | 2 +-
+ 5 files changed, 21 insertions(+), 21 deletions(-)
+
+--- a/fs/proc/base.c
++++ b/fs/proc/base.c
+@@ -720,7 +720,7 @@ static bool proc_fd_access_allowed(struc
+ return allowed;
+ }
+
+-int proc_setattr(struct user_namespace *mnt_userns, struct dentry *dentry,
++int proc_nochmod_setattr(struct user_namespace *mnt_userns, struct dentry *dentry,
+ struct iattr *attr)
+ {
+ int error;
+@@ -794,7 +794,7 @@ static int proc_pid_permission(struct us
+
+
+ static const struct inode_operations proc_def_inode_operations = {
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+
+ static int proc_single_show(struct seq_file *m, void *v)
+@@ -1867,7 +1867,7 @@ out:
+ const struct inode_operations proc_pid_link_inode_operations = {
+ .readlink = proc_pid_readlink,
+ .get_link = proc_pid_get_link,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+
+
+@@ -2317,7 +2317,7 @@ proc_map_files_get_link(struct dentry *d
+ static const struct inode_operations proc_map_files_link_inode_operations = {
+ .readlink = proc_pid_readlink,
+ .get_link = proc_map_files_get_link,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+
+ static struct dentry *
+@@ -2396,7 +2396,7 @@ out:
+ static const struct inode_operations proc_map_files_inode_operations = {
+ .lookup = proc_map_files_lookup,
+ .permission = proc_fd_permission,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+
+ static int
+@@ -2892,7 +2892,7 @@ static struct dentry *proc_##LSM##_attr_
+ static const struct inode_operations proc_##LSM##_attr_dir_inode_ops = { \
+ .lookup = proc_##LSM##_attr_dir_lookup, \
+ .getattr = pid_getattr, \
+- .setattr = proc_setattr, \
++ .setattr = proc_nochmod_setattr, \
+ }
+
+ #ifdef CONFIG_SECURITY_SMACK
+@@ -2951,7 +2951,7 @@ static struct dentry *proc_attr_dir_look
+ static const struct inode_operations proc_attr_dir_inode_operations = {
+ .lookup = proc_attr_dir_lookup,
+ .getattr = pid_getattr,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+
+ #endif
+@@ -3441,7 +3441,7 @@ static struct dentry *proc_tgid_base_loo
+ static const struct inode_operations proc_tgid_base_inode_operations = {
+ .lookup = proc_tgid_base_lookup,
+ .getattr = pid_getattr,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ .permission = proc_pid_permission,
+ };
+
+@@ -3641,7 +3641,7 @@ static int proc_tid_comm_permission(stru
+ }
+
+ static const struct inode_operations proc_tid_comm_inode_operations = {
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ .permission = proc_tid_comm_permission,
+ };
+
+@@ -3772,7 +3772,7 @@ static const struct file_operations proc
+ static const struct inode_operations proc_tid_base_inode_operations = {
+ .lookup = proc_tid_base_lookup,
+ .getattr = pid_getattr,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+
+ static struct dentry *proc_task_instantiate(struct dentry *dentry,
+@@ -3971,7 +3971,7 @@ static int proc_task_getattr(struct user
+ static const struct inode_operations proc_task_inode_operations = {
+ .lookup = proc_task_lookup,
+ .getattr = proc_task_getattr,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ .permission = proc_pid_permission,
+ };
+
+--- a/fs/proc/fd.c
++++ b/fs/proc/fd.c
+@@ -82,7 +82,7 @@ static int seq_fdinfo_open(struct inode
+ * that the current task has PTRACE_MODE_READ in addition to the normal
+ * POSIX-like checks.
+ */
+-static int proc_fdinfo_permission(struct mnt_idmap *idmap, struct inode *inode,
++static int proc_fdinfo_permission(struct user_namespace *mnt_userns, struct inode *inode,
+ int mask)
+ {
+ bool allowed = false;
+@@ -97,12 +97,12 @@ static int proc_fdinfo_permission(struct
+ if (!allowed)
+ return -EACCES;
+
+- return generic_permission(idmap, inode, mask);
++ return generic_permission(mnt_userns, inode, mask);
+ }
+
+ static const struct inode_operations proc_fdinfo_file_inode_operations = {
+ .permission = proc_fdinfo_permission,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+
+ static const struct file_operations proc_fdinfo_file_operations = {
+@@ -328,7 +328,7 @@ int proc_fd_permission(struct user_names
+ const struct inode_operations proc_fd_inode_operations = {
+ .lookup = proc_lookupfd,
+ .permission = proc_fd_permission,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+
+ static struct dentry *proc_fdinfo_instantiate(struct dentry *dentry,
+@@ -369,7 +369,7 @@ static int proc_readfdinfo(struct file *
+ const struct inode_operations proc_fdinfo_inode_operations = {
+ .lookup = proc_lookupfdinfo,
+ .permission = proc_fdinfo_permission,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+
+ const struct file_operations proc_fdinfo_operations = {
+--- a/fs/proc/internal.h
++++ b/fs/proc/internal.h
+@@ -183,8 +183,8 @@ extern int proc_pid_statm(struct seq_fil
+ extern const struct dentry_operations pid_dentry_operations;
+ extern int pid_getattr(struct user_namespace *, const struct path *,
+ struct kstat *, u32, unsigned int);
+-extern int proc_setattr(struct user_namespace *, struct dentry *,
+- struct iattr *);
++int proc_nochmod_setattr(struct user_namespace *mnt_userns, struct dentry *dentry,
++ struct iattr *attr);
+ extern void proc_pid_evict_inode(struct proc_inode *);
+ extern struct inode *proc_pid_make_inode(struct super_block *, struct task_struct *, umode_t);
+ extern void pid_update_inode(struct task_struct *, struct inode *);
+--- a/fs/proc/namespaces.c
++++ b/fs/proc/namespaces.c
+@@ -92,7 +92,7 @@ static int proc_ns_readlink(struct dentr
+ static const struct inode_operations proc_ns_link_inode_operations = {
+ .readlink = proc_ns_readlink,
+ .get_link = proc_ns_get_link,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+
+ static struct dentry *proc_ns_instantiate(struct dentry *dentry,
+@@ -179,5 +179,5 @@ out_no_task:
+ const struct inode_operations proc_ns_dir_inode_operations = {
+ .lookup = proc_ns_dir_lookup,
+ .getattr = pid_getattr,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+--- a/fs/proc/proc_net.c
++++ b/fs/proc/proc_net.c
+@@ -321,7 +321,7 @@ static int proc_tgid_net_getattr(struct
+ const struct inode_operations proc_net_inode_operations = {
+ .lookup = proc_tgid_net_lookup,
+ .getattr = proc_tgid_net_getattr,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+
+ static int proc_tgid_net_readdir(struct file *file, struct dir_context *ctx)
--- /dev/null
+From stable+bounces-277025-greg=kroah.com@vger.kernel.org Fri Jul 17 13:43:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 07:18:19 -0400
+Subject: proc: use generic setattr() for /proc/$PID/net
+To: stable@vger.kernel.org
+Cc: "Thomas Weißschuh" <linux@weissschuh.net>, "Zhangjin Wu" <falcon@tinylab.org>, "Willy Tarreau" <w@1wt.eu>, "Christian Brauner" <brauner@kernel.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260717111823.1573608-1-sashal@kernel.org>
+
+From: Thomas Weißschuh <linux@weissschuh.net>
+
+[ Upstream commit 18e66ae67673f2c8df6f02428798b1355691f2a9 ]
+
+All other files in /proc/$PID/ use proc_setattr().
+
+Not using it allows the usage of chmod() on /proc/$PID/net, even on
+other processes owned by the same user.
+The same would probably also be true for other attributes to be changed.
+
+As this technically represents an ABI change it is not marked for
+stable so any unlikely regressions are caught during a full release cycle.
+
+Fixes: e9720acd728a ("[NET]: Make /proc/net a symlink on /proc/self/net (v3)")
+Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
+Link: https://lore.kernel.org/lkml/d0d111ef-edae-4760-83fb-36db84278da1@t-8ch.de/
+Fixes: b4844fa0bdb4 ("selftests/nolibc: implement a few tests for various syscalls")
+Tested-by: Zhangjin Wu <falcon@tinylab.org>
+Signed-off-by: Willy Tarreau <w@1wt.eu>
+Message-Id: <20230624-proc-net-setattr-v1-2-73176812adee@weissschuh.net>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 6255da28d4bb ("proc: protect ptrace_may_access() with exec_update_lock (FD links)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/proc/proc_net.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/fs/proc/proc_net.c
++++ b/fs/proc/proc_net.c
+@@ -321,6 +321,7 @@ static int proc_tgid_net_getattr(struct
+ const struct inode_operations proc_net_inode_operations = {
+ .lookup = proc_tgid_net_lookup,
+ .getattr = proc_tgid_net_getattr,
++ .setattr = proc_setattr,
+ };
+
+ static int proc_tgid_net_readdir(struct file *file, struct dir_context *ctx)
--- /dev/null
+From stable+bounces-278319-greg=kroah.com@vger.kernel.org Tue Jul 21 03:28:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 21:28:14 -0400
+Subject: regulator: scmi: fix of_node refcount leak in scmi_regulator_probe()
+To: stable@vger.kernel.org
+Cc: Wentao Liang <vulab@iscas.ac.cn>, Mark Brown <broonie@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721012814.3459151-2-sashal@kernel.org>
+
+From: Wentao Liang <vulab@iscas.ac.cn>
+
+[ Upstream commit fa11039d6cdff84584a3ef8cc1f5e1b56e045da2 ]
+
+scmi_regulator_probe() calls of_find_node_by_name() which takes a
+reference on the returned device node. On the error path where
+process_scmi_regulator_of_node() fails, the function returns without
+calling of_node_put() on the child node, leaking the reference.
+
+Add of_node_put(np) on the error path to properly release the
+reference.
+
+Cc: stable@vger.kernel.org
+Fixes: 0fbeae70ee7c ("regulator: add SCMI driver")
+Signed-off-by: Wentao Liang <vulab@iscas.ac.cn>
+Link: https://patch.msgid.link/20260527104850.872415-1-vulab@iscas.ac.cn
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/regulator/scmi-regulator.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/regulator/scmi-regulator.c
++++ b/drivers/regulator/scmi-regulator.c
+@@ -349,8 +349,10 @@ static int scmi_regulator_probe(struct s
+ for_each_child_of_node_scoped(np, child) {
+ ret = process_scmi_regulator_of_node(sdev, ph, child, rinfo);
+ /* abort on any mem issue */
+- if (ret == -ENOMEM)
++ if (ret == -ENOMEM) {
++ of_node_put(np);
+ return ret;
++ }
+ }
+ of_node_put(np);
+ /*
--- /dev/null
+From stable+bounces-278318-greg=kroah.com@vger.kernel.org Tue Jul 21 03:28:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 21:28:13 -0400
+Subject: regulator: scmi: Simplify with scoped for each OF child loop
+To: stable@vger.kernel.org
+Cc: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>, Mark Brown <broonie@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721012814.3459151-1-sashal@kernel.org>
+
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+
+[ Upstream commit 99cf5db9cdd39136fd5dbd10bda833aa0f870452 ]
+
+Use scoped for_each_available_child_of_node_scoped() when iterating over
+device nodes to make code a bit simpler.
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Link: https://patch.msgid.link/20240814-cleanup-h-of-node-put-regulator-v1-7-87151088b883@linaro.org
+Signed-off-by: Mark Brown <broonie@kernel.org>
+Stable-dep-of: fa11039d6cdf ("regulator: scmi: fix of_node refcount leak in scmi_regulator_probe()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/regulator/scmi-regulator.c | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/regulator/scmi-regulator.c
++++ b/drivers/regulator/scmi-regulator.c
+@@ -298,7 +298,7 @@ static int process_scmi_regulator_of_nod
+ static int scmi_regulator_probe(struct scmi_device *sdev)
+ {
+ int d, ret, num_doms;
+- struct device_node *np, *child;
++ struct device_node *np;
+ const struct scmi_handle *handle = sdev->handle;
+ struct scmi_regulator_info *rinfo;
+ struct scmi_protocol_handle *ph;
+@@ -346,13 +346,11 @@ static int scmi_regulator_probe(struct s
+ */
+ of_node_get(handle->dev->of_node);
+ np = of_find_node_by_name(handle->dev->of_node, "regulators");
+- for_each_child_of_node(np, child) {
++ for_each_child_of_node_scoped(np, child) {
+ ret = process_scmi_regulator_of_node(sdev, ph, child, rinfo);
+ /* abort on any mem issue */
+- if (ret == -ENOMEM) {
+- of_node_put(child);
++ if (ret == -ENOMEM)
+ return ret;
+- }
+ }
+ of_node_put(np);
+ /*
--- /dev/null
+From stable+bounces-277096-greg=kroah.com@vger.kernel.org Fri Jul 17 17:00:41 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 11:00:06 -0400
+Subject: seqlock: Change do_task_stat() to use scoped_seqlock_read()
+To: stable@vger.kernel.org
+Cc: Oleg Nesterov <oleg@redhat.com>, "Peter Zijlstra (Intel)" <peterz@infradead.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717150007.1930653-2-sashal@kernel.org>
+
+From: Oleg Nesterov <oleg@redhat.com>
+
+[ Upstream commit b76f72bea2c601afec81829ea427fc0d20f83216 ]
+
+To simplify the code and make it more readable.
+
+[peterz: change to new interface]
+Signed-off-by: Oleg Nesterov <oleg@redhat.com>
+Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
+Stable-dep-of: 6650527444da ("proc: protect ptrace_may_access() with exec_update_lock (part 1)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/proc/array.c | 9 ++-------
+ 1 file changed, 2 insertions(+), 7 deletions(-)
+
+--- a/fs/proc/array.c
++++ b/fs/proc/array.c
+@@ -473,7 +473,6 @@ static int do_task_stat(struct seq_file
+ unsigned long flags;
+ int exit_code = task->exit_code;
+ struct signal_struct *sig = task->signal;
+- unsigned int seq = 1;
+
+ state = *get_task_state(task);
+ vsize = eip = esp = 0;
+@@ -530,10 +529,7 @@ static int do_task_stat(struct seq_file
+ if (permitted && (!whole || num_threads < 2))
+ wchan = !task_is_running(task);
+
+- do {
+- seq++; /* 2 on the 1st/lockless path, otherwise odd */
+- flags = read_seqbegin_or_lock_irqsave(&sig->stats_lock, &seq);
+-
++ scoped_seqlock_read (&sig->stats_lock, ss_lock_irqsave) {
+ cmin_flt = sig->cmin_flt;
+ cmaj_flt = sig->cmaj_flt;
+ cutime = sig->cutime;
+@@ -555,8 +551,7 @@ static int do_task_stat(struct seq_file
+ }
+ rcu_read_unlock();
+ }
+- } while (need_seqretry(&sig->stats_lock, seq));
+- done_seqretry_irqrestore(&sig->stats_lock, seq, flags);
++ }
+
+ if (whole) {
+ thread_group_cputime_adjusted(task, &utime, &stime);
--- /dev/null
+From stable+bounces-277095-greg=kroah.com@vger.kernel.org Fri Jul 17 17:02:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 11:00:05 -0400
+Subject: seqlock: Introduce scoped_seqlock_read()
+To: stable@vger.kernel.org
+Cc: Peter Zijlstra <peterz@infradead.org>, Oleg Nesterov <oleg@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717150007.1930653-1-sashal@kernel.org>
+
+From: Peter Zijlstra <peterz@infradead.org>
+
+[ Upstream commit cc39f3872c0865bef992b713338df369554fa9e0 ]
+
+The read_seqbegin/need_seqretry/done_seqretry API is cumbersome and
+error prone. With the new helper the "typical" code like
+
+ int seq, nextseq;
+ unsigned long flags;
+
+ nextseq = 0;
+ do {
+ seq = nextseq;
+ flags = read_seqbegin_or_lock_irqsave(&seqlock, &seq);
+
+ // read-side critical section
+
+ nextseq = 1;
+ } while (need_seqretry(&seqlock, seq));
+ done_seqretry_irqrestore(&seqlock, seq, flags);
+
+can be rewritten as
+
+ scoped_seqlock_read (&seqlock, ss_lock_irqsave) {
+ // read-side critical section
+ }
+
+Original idea by Oleg Nesterov; with contributions from Linus.
+
+Originally-by: Oleg Nesterov <oleg@redhat.com>
+Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
+Stable-dep-of: 6650527444da ("proc: protect ptrace_may_access() with exec_update_lock (part 1)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/seqlock.h | 111 ++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 111 insertions(+)
+
+--- a/include/linux/seqlock.h
++++ b/include/linux/seqlock.h
+@@ -1266,4 +1266,115 @@ done_seqretry_irqrestore(seqlock_t *lock
+ if (seq & 1)
+ read_sequnlock_excl_irqrestore(lock, flags);
+ }
++
++enum ss_state {
++ ss_done = 0,
++ ss_lock,
++ ss_lock_irqsave,
++ ss_lockless,
++};
++
++struct ss_tmp {
++ enum ss_state state;
++ unsigned long data;
++ spinlock_t *lock;
++ spinlock_t *lock_irqsave;
++};
++
++static inline void __scoped_seqlock_cleanup(struct ss_tmp *sst)
++{
++ if (sst->lock)
++ spin_unlock(sst->lock);
++ if (sst->lock_irqsave)
++ spin_unlock_irqrestore(sst->lock_irqsave, sst->data);
++}
++
++extern void __scoped_seqlock_invalid_target(void);
++
++#if defined(CONFIG_CC_IS_GCC) && CONFIG_GCC_VERSION < 90000
++/*
++ * For some reason some GCC-8 architectures (nios2, alpha) have trouble
++ * determining that the ss_done state is impossible in __scoped_seqlock_next()
++ * below.
++ */
++static inline void __scoped_seqlock_bug(void) { }
++#else
++/*
++ * Canary for compiler optimization -- if the compiler doesn't realize this is
++ * an impossible state, it very likely generates sub-optimal code here.
++ */
++extern void __scoped_seqlock_bug(void);
++#endif
++
++static inline void
++__scoped_seqlock_next(struct ss_tmp *sst, seqlock_t *lock, enum ss_state target)
++{
++ switch (sst->state) {
++ case ss_done:
++ __scoped_seqlock_bug();
++ return;
++
++ case ss_lock:
++ case ss_lock_irqsave:
++ sst->state = ss_done;
++ return;
++
++ case ss_lockless:
++ if (!read_seqretry(lock, sst->data)) {
++ sst->state = ss_done;
++ return;
++ }
++ break;
++ }
++
++ switch (target) {
++ case ss_done:
++ __scoped_seqlock_invalid_target();
++ return;
++
++ case ss_lock:
++ sst->lock = &lock->lock;
++ spin_lock(sst->lock);
++ sst->state = ss_lock;
++ return;
++
++ case ss_lock_irqsave:
++ sst->lock_irqsave = &lock->lock;
++ spin_lock_irqsave(sst->lock_irqsave, sst->data);
++ sst->state = ss_lock_irqsave;
++ return;
++
++ case ss_lockless:
++ sst->data = read_seqbegin(lock);
++ return;
++ }
++}
++
++#define __scoped_seqlock_read(_seqlock, _target, _s) \
++ for (struct ss_tmp _s __cleanup(__scoped_seqlock_cleanup) = \
++ { .state = ss_lockless, .data = read_seqbegin(_seqlock) }; \
++ _s.state != ss_done; \
++ __scoped_seqlock_next(&_s, _seqlock, _target))
++
++/**
++ * scoped_seqlock_read (lock, ss_state) - execute the read side critical
++ * section without manual sequence
++ * counter handling or calls to other
++ * helpers
++ * @lock: pointer to seqlock_t protecting the data
++ * @ss_state: one of {ss_lock, ss_lock_irqsave, ss_lockless} indicating
++ * the type of critical read section
++ *
++ * Example:
++ *
++ * scoped_seqlock_read (&lock, ss_lock) {
++ * // read-side critical section
++ * }
++ *
++ * Starts with a lockess pass first. If it fails, restarts the critical
++ * section with the lock held.
++ */
++#define scoped_seqlock_read(_seqlock, _target) \
++ __scoped_seqlock_read(_seqlock, _target, __UNIQUE_ID(seqlock))
++
+ #endif /* __LINUX_SEQLOCK_H */
--- /dev/null
+From stable+bounces-277228-greg=kroah.com@vger.kernel.org Sat Jul 18 02:51:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 20:51:18 -0400
+Subject: serial: 8250_mid: Disable DMA for selected platforms
+To: stable@vger.kernel.org
+Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>, micas-opensource <zjianan156@gmail.com>, stable <stable@kernel.org>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260718005118.2484504-2-sashal@kernel.org>
+
+From: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+
+[ Upstream commit b1b4efea05a56c0995e4702a86d6624b4fdff32f ]
+
+In accordance with Errata (specification updates)
+HSUART May Stop Functioning when DMA is Active.
+
+- Denverton document #572409, rev 3.4, DNV60
+- Ice Lake Xeon D document #714070, ICXD65
+- Snowridge document #731931, SNR44
+
+For a quick fix just disable the respective callbacks during the device probe.
+Depending on the future development we might remove them completely.
+
+Reported-by: micas-opensource <zjianan156@gmail.com>
+Closes: https://lore.kernel.org/linux-serial/20250625031409.2404219-1-opensource@ruijie.com.cn/
+Fixes: 6ede6dcd87aa ("serial: 8250_mid: add support for DMA engine handling from UART MMIO")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Link: https://patch.msgid.link/20260626094937.561776-1-andriy.shevchenko@linux.intel.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/8250/8250_mid.c | 13 +++++++++++--
+ 1 file changed, 11 insertions(+), 2 deletions(-)
+
+--- a/drivers/tty/serial/8250/8250_mid.c
++++ b/drivers/tty/serial/8250/8250_mid.c
+@@ -10,6 +10,7 @@
+ #include <linux/module.h>
+ #include <linux/pci.h>
+ #include <linux/rational.h>
++#include <linux/util_macros.h>
+
+ #include <linux/dma/hsu.h>
+
+@@ -368,8 +369,16 @@ static const struct mid8250_board dnv_bo
+ .freq = 133333333,
+ .base_baud = 115200,
+ .bar = 1,
+- .setup = dnv_setup,
+- .exit = dnv_exit,
++ /*
++ * Errata:
++ * HSUART May Stop Functioning when DMA is Active.
++ *
++ * - Denverton document #572409, rev 3.4, DNV60
++ * - Ice Lake Xeon D document #714070, ICXD65
++ * - Snowridge document #731931, SNR44
++ */
++ .setup = PTR_IF(false, dnv_setup),
++ .exit = PTR_IF(false, dnv_exit),
+ };
+
+ static const struct pci_device_id pci_ids[] = {
--- /dev/null
+From stable+bounces-277227-greg=kroah.com@vger.kernel.org Sat Jul 18 02:51:40 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 20:51:17 -0400
+Subject: serial: 8250_mid: Remove 8250_pci usage
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260718005118.2484504-1-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit a136abd7e7abe0f1247f8ffde6cc7c8ab09f985b ]
+
+8250_mid uses FL_*BASE* from linux/8250_pci.h and nothing else. The
+code can be simplified by directly defining BARs within the driver
+instead.
+
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+Link: https://lore.kernel.org/r/20230915094336.13278-1-ilpo.jarvinen@linux.intel.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Stable-dep-of: b1b4efea05a5 ("serial: 8250_mid: Disable DMA for selected platforms")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/8250/8250_mid.c | 18 +++++++-----------
+ 1 file changed, 7 insertions(+), 11 deletions(-)
+
+--- a/drivers/tty/serial/8250/8250_mid.c
++++ b/drivers/tty/serial/8250/8250_mid.c
+@@ -12,7 +12,6 @@
+ #include <linux/rational.h>
+
+ #include <linux/dma/hsu.h>
+-#include <linux/8250_pci.h>
+
+ #include "8250.h"
+
+@@ -32,9 +31,9 @@
+ struct mid8250;
+
+ struct mid8250_board {
+- unsigned int flags;
+ unsigned long freq;
+ unsigned int base_baud;
++ unsigned int bar;
+ int (*setup)(struct mid8250 *, struct uart_port *p);
+ void (*exit)(struct mid8250 *);
+ };
+@@ -169,7 +168,6 @@ static int dnv_setup(struct mid8250 *mid
+ {
+ struct hsu_dma_chip *chip = &mid->dma_chip;
+ struct pci_dev *pdev = to_pci_dev(p->dev);
+- unsigned int bar = FL_GET_BASE(mid->board->flags);
+ int ret;
+
+ pci_set_master(pdev);
+@@ -183,7 +181,7 @@ static int dnv_setup(struct mid8250 *mid
+ chip->dev = &pdev->dev;
+ chip->irq = pci_irq_vector(pdev, 0);
+ chip->regs = p->membase;
+- chip->length = pci_resource_len(pdev, bar);
++ chip->length = pci_resource_len(pdev, mid->board->bar);
+ chip->offset = DNV_DMA_CHAN_OFFSET;
+
+ /* Falling back to PIO mode if DMA probing fails */
+@@ -291,7 +289,6 @@ static int mid8250_probe(struct pci_dev
+ {
+ struct uart_8250_port uart;
+ struct mid8250 *mid;
+- unsigned int bar;
+ int ret;
+
+ ret = pcim_enable_device(pdev);
+@@ -303,7 +300,6 @@ static int mid8250_probe(struct pci_dev
+ return -ENOMEM;
+
+ mid->board = (struct mid8250_board *)id->driver_data;
+- bar = FL_GET_BASE(mid->board->flags);
+
+ memset(&uart, 0, sizeof(struct uart_8250_port));
+
+@@ -316,8 +312,8 @@ static int mid8250_probe(struct pci_dev
+ uart.port.flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE;
+ uart.port.set_termios = mid8250_set_termios;
+
+- uart.port.mapbase = pci_resource_start(pdev, bar);
+- uart.port.membase = pcim_iomap(pdev, bar, 0);
++ uart.port.mapbase = pci_resource_start(pdev, mid->board->bar);
++ uart.port.membase = pcim_iomap(pdev, mid->board->bar, 0);
+ if (!uart.port.membase)
+ return -ENOMEM;
+
+@@ -353,25 +349,25 @@ static void mid8250_remove(struct pci_de
+ }
+
+ static const struct mid8250_board pnw_board = {
+- .flags = FL_BASE0,
+ .freq = 50000000,
+ .base_baud = 115200,
++ .bar = 0,
+ .setup = pnw_setup,
+ .exit = pnw_exit,
+ };
+
+ static const struct mid8250_board tng_board = {
+- .flags = FL_BASE0,
+ .freq = 38400000,
+ .base_baud = 1843200,
++ .bar = 0,
+ .setup = tng_setup,
+ .exit = tng_exit,
+ };
+
+ static const struct mid8250_board dnv_board = {
+- .flags = FL_BASE1,
+ .freq = 133333333,
+ .base_baud = 115200,
++ .bar = 1,
+ .setup = dnv_setup,
+ .exit = dnv_exit,
+ };
mtd-rawnand-lpc32xx_mlc-fail-dma-transfers-on-timeout.patch
mtd-rawnand-lpc32xx_slc-fail-dma-transfer-on-completion-timeout.patch
perf-x86-amd-brs-fix-kernel-address-leakage.patch
+acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch
+iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch
+iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch
+iio-imu-inv_icm42600-make-timestamp-module-chip-independent.patch
+iio-move-inv_icm42600-timestamp-module-in-common.patch
+iio-make-invensense-timestamp-module-generic.patch
+iio-imu-inv_mpu6050-use-the-common-inv_sensors-timestamp-module.patch
+iio-invensense-remove-redundant-initialization-of-variable-period.patch
+iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch
+iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch
+iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch
+alsa-aoa-check-snd_ctl_new1-return-value.patch
+bitops-make-bytes_to_bits-treewide-available.patch
+iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch
+vfio-pci-use-bitfield-for-struct-vfio_pci_core_device-flags.patch
+vfio-pci-use-a-private-flag-to-prevent-power-state-change-with-vfs.patch
+vfio-pci-latch-disable_idle_d3-per-device.patch
+pci-altera-fix-resource-leaks-on-probe-failure.patch
+vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch
+pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch
+staging-rtl8723bs-fix-indentation-issues.patch
+staging-rtl8723bs-fix-space-issues.patch
+staging-rtl8723bs-fix-heap-buffer-overflow-in-rtw_cfg80211_set_wpa_ie.patch
+pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch
+pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch
+pci-mediatek-use-generic-macro-for-tpvperl-delay.patch
+pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch
+staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch
+staging-rtl8723bs-fix-spaces-around-binary-operators.patch
+staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch
+crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch
+pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch
+pci-free-saved-list-without-holding-pci_bus_sem.patch
+pci-fix-restoring-bars-on-bar-resize-rollback-path.patch
+pci-add-kerneldoc-for-pci_resize_resource.patch
+pci-move-resizable-bar-code-to-rebar.c.patch
+pci-skip-resizable-bar-restore-on-read-error.patch
+mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch
+mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch
+coresight-etb10-restore-atomic_t-for-shared-reading-state.patch
+gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch
+mm-shrinkers-fix-debugfs-file-permissions.patch
+mm-shrinker-fix-null-pointer-dereference-in-debugfs.patch
+netfilter-ebtables-use-vmalloc_array-to-improve-code.patch
+netfilter-ebtables-zero-chainstack-array.patch
+bluetooth-make-handle-of-hci_conn-be-unique.patch
+bluetooth-remove-bt_hs.patch
+bluetooth-l2cap-fix-slab-use-after-free-in-l2cap_connect.patch
+bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch
+bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch
+bluetooth-l2cap-fix-not-tracking-outstanding-tx-ident.patch
+bluetooth-l2cap-fix-deadlock-in-l2cap_conn_del.patch
+bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch
+bluetooth-hci_core-enable-buffer-flow-control-for-sco-esco.patch
+cifs-handle-when-server-stops-supporting-multichannel.patch
+cifs-after-disabling-multichannel-mark-tcon-for-reconnect.patch
+cifs-fix-reacquisition-of-volume-cookie-on-still-live-connection.patch
+cifs-add-tracing-for-the-cifs_tcon-struct-refcounting.patch
+smb-client-resolve-swn-tcon-from-live-registrations.patch
+ksmbd-use-opener-credentials-for-fsctl-mutations.patch
+smb-move-some-duplicate-definitions-to-common-cifsglob.h.patch
+smb-move-smb_version_values-to-common-smbglob.h.patch
+ksmbd-fix-use-after-free-in-__ksmbd_close_fd-via-durable-scavenger.patch
+ksmbd-fix-mechtoken-leak-when-spnego-decode-fails-after-token-alloc.patch
+ksmbd-destroy-async_ida-in-ksmbd_conn_free.patch
+ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch
+ksmbd-track-the-connection-owning-a-byte-range-lock.patch
+writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch
+writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch
+perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch
+proc-use-generic-setattr-for-proc-pid-net.patch
+proc-move-fdinfo-ptrace_mode_read-check-into-the-inode-.permission-operation.patch
+proc-rename-proc_setattr-to-proc_nochmod_setattr.patch
+proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch
+hid-add-haptics-page-defines.patch
+hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch
+seqlock-introduce-scoped_seqlock_read.patch
+seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch
+proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch
+treewide-switch-rename-to-timer_delete.patch
+hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch
+serial-8250_mid-remove-8250_pci-usage.patch
+serial-8250_mid-disable-dma-for-selected-platforms.patch
+hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch
+hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch
+bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch
+bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch
+usb-iowarrior-remove-inherent-race-with-minor-number.patch
+usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch
+usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch
+usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch
+crypto-atmel-sha204a-mark-of-related-data-as-maybe-unused.patch
+crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch
+crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch
+nvmet-remove-superfluous-initialization.patch
+usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch
+crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch
+btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch
+crypto-qat-validate-rsa-crt-component-lengths.patch
+audit-add-audit_log_nf_skb-helper-function.patch
+audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch
+regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch
+regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch
+btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch
+btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch
--- /dev/null
+From stable+bounces-275233-greg=kroah.com@vger.kernel.org Thu Jul 16 12:24:32 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 06:18:24 -0400
+Subject: smb: client: resolve SWN tcon from live registrations
+To: stable@vger.kernel.org
+Cc: Michael Bommarito <michael.bommarito@gmail.com>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716101824.1461870-5-sashal@kernel.org>
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit ec457f9afe5ae9538bdcd58fd4cb442b9787e183 ]
+
+cifs_swn_notify() looks up a witness registration by id under
+cifs_swnreg_idr_mutex, drops the mutex, and then uses the registration's
+cached tcon pointer. That pointer is not a lifetime reference, and it is
+not a stable representative once cifs_get_swn_reg() lets multiple tcons
+for the same net/share name share one registration id.
+
+A same-share second mount can keep the cifs_swn_reg alive after the first
+tcon unregisters and is freed. The registration then still points at the
+freed first tcon, so taking tc_lock or incrementing tc_count through
+swnreg->tcon only moves the use-after-free earlier. Taking tc_lock while
+holding cifs_swnreg_idr_mutex also violates the documented CIFS lock
+order.
+
+Fix this by making the registration store only the stable witness
+identity: id, net name, share name, and notify flags. When a notify
+arrives, copy that identity under cifs_swnreg_idr_mutex, drop the mutex,
+then find and pin a live witness tcon that currently matches the net/share
+pair under the normal cifs_tcp_ses_lock -> tc_lock order. The notification
+path uses that pinned tcon directly and drops the reference when done.
+
+Registration and unregister messages now use the live tcon passed by the
+caller instead of a cached tcon in the registration. The final unregister
+send is folded into cifs_swn_unregister() while the registration is still
+protected by cifs_swnreg_idr_mutex. This removes the previous
+find/drop/reacquire raw-pointer window. The release path only removes the
+idr entry and frees the stable identity strings.
+
+This preserves the intended one-registration/many-tcon behavior: a
+registration id represents a net/share pair, and notify handling acts on a
+live representative selected at use time. It also preserves CLIENT_MOVE
+ordering for the representative tcon because the old-IP unregister is sent
+before cifs_swn_register() sends the new-IP register.
+
+Fixes: fed979a7e082 ("cifs: Set witness notification handler for messages from userspace daemon")
+Cc: stable@vger.kernel.org
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
+Assisted-by: Claude:claude-opus-4-7
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/cifs_swn.c | 314 ++++++++++++++++++++++++++++++++++++++---------
+ fs/smb/client/trace.h | 2
+ 2 files changed, 262 insertions(+), 54 deletions(-)
+
+--- a/fs/smb/client/cifs_swn.c
++++ b/fs/smb/client/cifs_swn.c
+@@ -28,10 +28,54 @@ struct cifs_swn_reg {
+ bool net_name_notify;
+ bool share_name_notify;
+ bool ip_notify;
++};
+
+- struct cifs_tcon *tcon;
++struct cifs_swn_reg_info {
++ int id;
++ unsigned int ref_count;
++ const char *net_name;
++ const char *share_name;
++ bool net_name_notify;
++ bool share_name_notify;
++ bool ip_notify;
+ };
+
++static void cifs_swn_snapshot_reg(struct cifs_swn_reg *swnreg,
++ struct cifs_swn_reg_info *info)
++{
++ info->id = swnreg->id;
++ info->ref_count = kref_read(&swnreg->ref_count);
++ info->net_name = swnreg->net_name;
++ info->share_name = swnreg->share_name;
++ info->net_name_notify = swnreg->net_name_notify;
++ info->share_name_notify = swnreg->share_name_notify;
++ info->ip_notify = swnreg->ip_notify;
++}
++
++static int cifs_swn_dup_reg(struct cifs_swn_reg *swnreg,
++ struct cifs_swn_reg_info *info)
++{
++ cifs_swn_snapshot_reg(swnreg, info);
++
++ info->net_name = kstrdup(swnreg->net_name, GFP_KERNEL);
++ if (!info->net_name)
++ return -ENOMEM;
++
++ info->share_name = kstrdup(swnreg->share_name, GFP_KERNEL);
++ if (!info->share_name) {
++ kfree(info->net_name);
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
++static void cifs_swn_free_reg_info(struct cifs_swn_reg_info *info)
++{
++ kfree(info->net_name);
++ kfree(info->share_name);
++}
++
+ static int cifs_swn_auth_info_krb(struct cifs_tcon *tcon, struct sk_buff *skb)
+ {
+ int ret;
+@@ -73,7 +117,8 @@ static int cifs_swn_auth_info_ntlm(struc
+ * The authentication information to connect to the witness service is bundled
+ * into the message.
+ */
+-static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)
++static int cifs_swn_send_register_message(struct cifs_swn_reg_info *swnreg,
++ struct cifs_tcon *tcon)
+ {
+ struct sk_buff *skb;
+ struct genlmsghdr *hdr;
+@@ -111,10 +156,10 @@ static int cifs_swn_send_register_messag
+ * told to switch to it (client move message). In these cases we unregister from the
+ * server address and register to the new address when we receive the notification.
+ */
+- if (swnreg->tcon->ses->server->use_swn_dstaddr)
+- addr = &swnreg->tcon->ses->server->swn_dstaddr;
++ if (tcon->ses->server->use_swn_dstaddr)
++ addr = &tcon->ses->server->swn_dstaddr;
+ else
+- addr = &swnreg->tcon->ses->server->dstaddr;
++ addr = &tcon->ses->server->dstaddr;
+
+ ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage), addr);
+ if (ret < 0)
+@@ -138,10 +183,10 @@ static int cifs_swn_send_register_messag
+ goto nlmsg_fail;
+ }
+
+- authtype = cifs_select_sectype(swnreg->tcon->ses->server, swnreg->tcon->ses->sectype);
++ authtype = cifs_select_sectype(tcon->ses->server, tcon->ses->sectype);
+ switch (authtype) {
+ case Kerberos:
+- ret = cifs_swn_auth_info_krb(swnreg->tcon, skb);
++ ret = cifs_swn_auth_info_krb(tcon, skb);
+ if (ret < 0) {
+ cifs_dbg(VFS, "%s: Failed to get kerberos auth info: %d\n", __func__, ret);
+ goto nlmsg_fail;
+@@ -149,7 +194,7 @@ static int cifs_swn_send_register_messag
+ break;
+ case NTLMv2:
+ case RawNTLMSSP:
+- ret = cifs_swn_auth_info_ntlm(swnreg->tcon, skb);
++ ret = cifs_swn_auth_info_ntlm(tcon, skb);
+ if (ret < 0) {
+ cifs_dbg(VFS, "%s: Failed to get NTLM auth info: %d\n", __func__, ret);
+ goto nlmsg_fail;
+@@ -179,7 +224,8 @@ fail:
+ /*
+ * Sends an uregister message to the userspace daemon based on the registration
+ */
+-static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg)
++static int cifs_swn_send_unregister_message(struct cifs_swn_reg_info *swnreg,
++ struct cifs_tcon *tcon)
+ {
+ struct sk_buff *skb;
+ struct genlmsghdr *hdr;
+@@ -208,7 +254,7 @@ static int cifs_swn_send_unregister_mess
+ goto nlmsg_fail;
+
+ ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage),
+- &swnreg->tcon->ses->server->dstaddr);
++ &tcon->ses->server->dstaddr);
+ if (ret < 0)
+ goto nlmsg_fail;
+
+@@ -245,6 +291,88 @@ nlmsg_fail:
+ }
+
+ /*
++ * Allocation-free mirror of extract_hostname() + extract_sharename() from
++ * fs/smb/client/unc.c. Those helpers kmalloc(GFP_KERNEL); this runs under
++ * cifs_tcp_ses_lock and tcon->tc_lock, both spinlocks, so we mirror their
++ * parsing in place against the caller's stable net_name/share_name strings.
++ * Keep in sync with unc.c.
++ */
++static bool cifs_swn_tcon_matches(struct cifs_tcon *tcon,
++ const char *net_name,
++ const char *share_name)
++{
++ const char *unc = tcon->tree_name;
++ const char *host, *share, *delim;
++ size_t host_len, share_len;
++
++ if (!tcon->use_witness)
++ return false;
++
++ /* extract_hostname: require strlen(unc) >= 3 */
++ if (strnlen(unc, 3) < 3)
++ return false;
++ /* extract_hostname: skip all leading '\' characters */
++ for (host = unc; *host == '\\'; host++)
++ ;
++ if (!*host)
++ return false;
++ delim = strchr(host, '\\');
++ if (!delim)
++ return false;
++ host_len = delim - host;
++ if (strlen(net_name) != host_len ||
++ strncasecmp(host, net_name, host_len))
++ return false;
++
++ /* extract_sharename: start at unc + 2, then first '\' onward */
++ share = unc + 2;
++ delim = strchr(share, '\\');
++ if (!delim)
++ return false;
++ share = delim + 1;
++ share_len = strlen(share);
++
++ return strlen(share_name) == share_len &&
++ !strncasecmp(share, share_name, share_len);
++}
++
++/*
++ * One SWN registration id represents one net/share name pair. Multiple
++ * mounted tcons can therefore share the id. Pick a live representative at
++ * use time instead of caching the first tcon pointer in the registration.
++ */
++static struct cifs_tcon *cifs_swn_get_tcon(struct cifs_swn_reg_info *swnreg)
++{
++ struct TCP_Server_Info *server;
++ struct cifs_ses *ses;
++ struct cifs_tcon *tcon;
++
++ spin_lock(&cifs_tcp_ses_lock);
++ list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
++ list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
++ list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
++ spin_lock(&tcon->tc_lock);
++ if (tcon->status == TID_EXITING ||
++ !cifs_swn_tcon_matches(tcon, swnreg->net_name,
++ swnreg->share_name)) {
++ spin_unlock(&tcon->tc_lock);
++ continue;
++ }
++ ++tcon->tc_count;
++ trace_smb3_tcon_ref(tcon->debug_id,
++ tcon->tc_count,
++ netfs_trace_tcon_ref_get_swn_notify);
++ spin_unlock(&tcon->tc_lock);
++ spin_unlock(&cifs_tcp_ses_lock);
++ return tcon;
++ }
++ }
++ }
++ spin_unlock(&cifs_tcp_ses_lock);
++ return NULL;
++}
++
++/*
+ * Try to find a matching registration for the tcon's server name and share name.
+ * Calls to this function must be protected by cifs_swnreg_idr_mutex.
+ * TODO Try to avoid memory allocations
+@@ -350,8 +478,6 @@ static struct cifs_swn_reg *cifs_get_swn
+ reg->net_name_notify = true;
+ reg->share_name_notify = true;
+ reg->ip_notify = (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT);
+-
+- reg->tcon = tcon;
+ unlock:
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+
+@@ -371,11 +497,6 @@ fail_unlock:
+ static void cifs_swn_reg_release(struct kref *ref)
+ {
+ struct cifs_swn_reg *swnreg = container_of(ref, struct cifs_swn_reg, ref_count);
+- int ret;
+-
+- ret = cifs_swn_send_unregister_message(swnreg);
+- if (ret < 0)
+- cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n", __func__, ret);
+
+ idr_remove(&cifs_swnreg_idr, swnreg->id);
+ kfree(swnreg->net_name);
+@@ -383,23 +504,33 @@ static void cifs_swn_reg_release(struct
+ kfree(swnreg);
+ }
+
+-static void cifs_put_swn_reg(struct cifs_swn_reg *swnreg)
++static void cifs_put_swn_reg_locked(struct cifs_swn_reg *swnreg,
++ struct cifs_tcon *tcon)
+ {
+- mutex_lock(&cifs_swnreg_idr_mutex);
++ if (kref_read(&swnreg->ref_count) == 1) {
++ struct cifs_swn_reg_info swnreg_info;
++ int ret;
++
++ cifs_swn_snapshot_reg(swnreg, &swnreg_info);
++ ret = cifs_swn_send_unregister_message(&swnreg_info, tcon);
++ if (ret < 0)
++ cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n",
++ __func__, ret);
++ }
++
+ kref_put(&swnreg->ref_count, cifs_swn_reg_release);
+- mutex_unlock(&cifs_swnreg_idr_mutex);
+ }
+
+-static int cifs_swn_resource_state_changed(struct cifs_swn_reg *swnreg, const char *name, int state)
++static int cifs_swn_resource_state_changed(struct cifs_tcon *tcon, const char *name, int state)
+ {
+ switch (state) {
+ case CIFS_SWN_RESOURCE_STATE_UNAVAILABLE:
+ cifs_dbg(FYI, "%s: resource name '%s' become unavailable\n", __func__, name);
+- cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true);
++ cifs_signal_cifsd_for_reconnect(tcon->ses->server, true);
+ break;
+ case CIFS_SWN_RESOURCE_STATE_AVAILABLE:
+ cifs_dbg(FYI, "%s: resource name '%s' become available\n", __func__, name);
+- cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true);
++ cifs_signal_cifsd_for_reconnect(tcon->ses->server, true);
+ break;
+ case CIFS_SWN_RESOURCE_STATE_UNKNOWN:
+ cifs_dbg(FYI, "%s: resource name '%s' changed to unknown state\n", __func__, name);
+@@ -505,7 +636,7 @@ unlock:
+ return ret;
+ }
+
+-static int cifs_swn_client_move(struct cifs_swn_reg *swnreg, struct sockaddr_storage *addr)
++static int cifs_swn_client_move(struct cifs_tcon *tcon, struct sockaddr_storage *addr)
+ {
+ struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr;
+ struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr;
+@@ -515,14 +646,17 @@ static int cifs_swn_client_move(struct c
+ else if (addr->ss_family == AF_INET6)
+ cifs_dbg(FYI, "%s: move to %pI6\n", __func__, &ipv6->sin6_addr);
+
+- return cifs_swn_reconnect(swnreg->tcon, addr);
++ return cifs_swn_reconnect(tcon, addr);
+ }
+
+ int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info)
+ {
+ struct cifs_swn_reg *swnreg;
++ struct cifs_swn_reg_info swnreg_info;
++ struct cifs_tcon *tcon;
+ char name[256];
+ int type;
++ int ret = 0;
+
+ if (info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]) {
+ int swnreg_id;
+@@ -530,21 +664,34 @@ int cifs_swn_notify(struct sk_buff *skb,
+ swnreg_id = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]);
+ mutex_lock(&cifs_swnreg_idr_mutex);
+ swnreg = idr_find(&cifs_swnreg_idr, swnreg_id);
+- mutex_unlock(&cifs_swnreg_idr_mutex);
+ if (swnreg == NULL) {
++ mutex_unlock(&cifs_swnreg_idr_mutex);
+ cifs_dbg(FYI, "%s: registration id %d not found\n", __func__, swnreg_id);
+ return -EINVAL;
+ }
++ ret = cifs_swn_dup_reg(swnreg, &swnreg_info);
++ mutex_unlock(&cifs_swnreg_idr_mutex);
++ if (ret)
++ return ret;
+ } else {
+ cifs_dbg(FYI, "%s: missing registration id attribute\n", __func__);
+ return -EINVAL;
+ }
+
++ tcon = cifs_swn_get_tcon(&swnreg_info);
++ if (!tcon) {
++ cifs_dbg(FYI, "%s: registration id %d has no live tcon\n",
++ __func__, swnreg_info.id);
++ ret = -ENODEV;
++ goto free_info;
++ }
++
+ if (info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]) {
+ type = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]);
+ } else {
+ cifs_dbg(FYI, "%s: missing notification type attribute\n", __func__);
+- return -EINVAL;
++ ret = -EINVAL;
++ goto out;
+ }
+
+ switch (type) {
+@@ -556,15 +703,18 @@ int cifs_swn_notify(struct sk_buff *skb,
+ sizeof(name));
+ } else {
+ cifs_dbg(FYI, "%s: missing resource name attribute\n", __func__);
+- return -EINVAL;
++ ret = -EINVAL;
++ goto out;
+ }
+ if (info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]) {
+ state = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]);
+ } else {
+ cifs_dbg(FYI, "%s: missing resource state attribute\n", __func__);
+- return -EINVAL;
++ ret = -EINVAL;
++ goto out;
+ }
+- return cifs_swn_resource_state_changed(swnreg, name, state);
++ ret = cifs_swn_resource_state_changed(tcon, name, state);
++ break;
+ }
+ case CIFS_SWN_NOTIFICATION_CLIENT_MOVE: {
+ struct sockaddr_storage addr;
+@@ -573,28 +723,36 @@ int cifs_swn_notify(struct sk_buff *skb,
+ nla_memcpy(&addr, info->attrs[CIFS_GENL_ATTR_SWN_IP], sizeof(addr));
+ } else {
+ cifs_dbg(FYI, "%s: missing IP address attribute\n", __func__);
+- return -EINVAL;
++ ret = -EINVAL;
++ goto out;
+ }
+- return cifs_swn_client_move(swnreg, &addr);
++ ret = cifs_swn_client_move(tcon, &addr);
++ break;
+ }
+ default:
+ cifs_dbg(FYI, "%s: unknown notification type %d\n", __func__, type);
+ break;
+ }
+
+- return 0;
++out:
++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify);
++free_info:
++ cifs_swn_free_reg_info(&swnreg_info);
++ return ret;
+ }
+
+ int cifs_swn_register(struct cifs_tcon *tcon)
+ {
+ struct cifs_swn_reg *swnreg;
++ struct cifs_swn_reg_info swnreg_info;
+ int ret;
+
+ swnreg = cifs_get_swn_reg(tcon);
+ if (IS_ERR(swnreg))
+ return PTR_ERR(swnreg);
+
+- ret = cifs_swn_send_register_message(swnreg);
++ cifs_swn_snapshot_reg(swnreg, &swnreg_info);
++ ret = cifs_swn_send_register_message(&swnreg_info, tcon);
+ if (ret < 0) {
+ cifs_dbg(VFS, "%s: Failed to send swn register message: %d\n", __func__, ret);
+ /* Do not put the swnreg or return error, the echo task will retry */
+@@ -615,35 +773,68 @@ int cifs_swn_unregister(struct cifs_tcon
+ return PTR_ERR(swnreg);
+ }
+
++ cifs_put_swn_reg_locked(swnreg, tcon);
+ mutex_unlock(&cifs_swnreg_idr_mutex);
+
+- cifs_put_swn_reg(swnreg);
+-
+ return 0;
+ }
+
+-void cifs_swn_dump(struct seq_file *m)
++/*
++ * Snapshot one registration under cifs_swnreg_idr_mutex and return. Callers
++ * intentionally do the per-registration network/genlmsg work without the
++ * mutex held, both to keep the critical section short and to avoid nesting
++ * cifs_swnreg_idr_mutex inside the higher tc_lock when a live tcon is then
++ * pinned for the send.
++ */
++static int cifs_swn_get_next_reg_info(int *id, struct cifs_swn_reg_info *info)
+ {
+ struct cifs_swn_reg *swnreg;
++ int ret = 0;
++
++ mutex_lock(&cifs_swnreg_idr_mutex);
++ swnreg = idr_get_next(&cifs_swnreg_idr, id);
++ if (swnreg) {
++ ret = cifs_swn_dup_reg(swnreg, info);
++ if (!ret) {
++ *id = swnreg->id + 1;
++ ret = 1;
++ }
++ }
++ mutex_unlock(&cifs_swnreg_idr_mutex);
++
++ return ret;
++}
++
++void cifs_swn_dump(struct seq_file *m)
++{
++ struct cifs_swn_reg_info swnreg_info;
++ struct cifs_tcon *tcon;
+ struct sockaddr_in *sa;
+ struct sockaddr_in6 *sa6;
+- int id;
++ int id = 0;
++ int ret;
+
+ seq_puts(m, "Witness registrations:");
+
+- mutex_lock(&cifs_swnreg_idr_mutex);
+- idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) {
++ while ((ret = cifs_swn_get_next_reg_info(&id, &swnreg_info)) > 0) {
+ seq_printf(m, "\nId: %u Refs: %u Network name: '%s'%s Share name: '%s'%s Ip address: ",
+- id, kref_read(&swnreg->ref_count),
+- swnreg->net_name, swnreg->net_name_notify ? "(y)" : "(n)",
+- swnreg->share_name, swnreg->share_name_notify ? "(y)" : "(n)");
+- switch (swnreg->tcon->ses->server->dstaddr.ss_family) {
++ swnreg_info.id, swnreg_info.ref_count,
++ swnreg_info.net_name, swnreg_info.net_name_notify ? "(y)" : "(n)",
++ swnreg_info.share_name, swnreg_info.share_name_notify ? "(y)" : "(n)");
++
++ tcon = cifs_swn_get_tcon(&swnreg_info);
++ if (!tcon) {
++ seq_puts(m, "(no live tcon)");
++ goto next;
++ }
++
++ switch (tcon->ses->server->dstaddr.ss_family) {
+ case AF_INET:
+- sa = (struct sockaddr_in *) &swnreg->tcon->ses->server->dstaddr;
++ sa = (struct sockaddr_in *)&tcon->ses->server->dstaddr;
+ seq_printf(m, "%pI4", &sa->sin_addr.s_addr);
+ break;
+ case AF_INET6:
+- sa6 = (struct sockaddr_in6 *) &swnreg->tcon->ses->server->dstaddr;
++ sa6 = (struct sockaddr_in6 *)&tcon->ses->server->dstaddr;
+ seq_printf(m, "%pI6", &sa6->sin6_addr.s6_addr);
+ if (sa6->sin6_scope_id)
+ seq_printf(m, "%%%u", sa6->sin6_scope_id);
+@@ -651,23 +842,38 @@ void cifs_swn_dump(struct seq_file *m)
+ default:
+ seq_puts(m, "(unknown)");
+ }
+- seq_printf(m, "%s", swnreg->ip_notify ? "(y)" : "(n)");
++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify);
++next:
++ seq_printf(m, "%s", swnreg_info.ip_notify ? "(y)" : "(n)");
++ cifs_swn_free_reg_info(&swnreg_info);
+ }
+- mutex_unlock(&cifs_swnreg_idr_mutex);
++ if (ret < 0)
++ seq_printf(m, "\nFailed to snapshot witness registration: %d", ret);
+ seq_puts(m, "\n");
+ }
+
+ void cifs_swn_check(void)
+ {
+- struct cifs_swn_reg *swnreg;
+- int id;
++ struct cifs_swn_reg_info swnreg_info;
++ struct cifs_tcon *tcon;
++ int id = 0;
+ int ret;
+
+- mutex_lock(&cifs_swnreg_idr_mutex);
+- idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) {
+- ret = cifs_swn_send_register_message(swnreg);
++ while ((ret = cifs_swn_get_next_reg_info(&id, &swnreg_info)) > 0) {
++ tcon = cifs_swn_get_tcon(&swnreg_info);
++ if (!tcon) {
++ cifs_dbg(FYI, "%s: registration id %d has no live tcon\n",
++ __func__, swnreg_info.id);
++ goto free_info;
++ }
++
++ ret = cifs_swn_send_register_message(&swnreg_info, tcon);
+ if (ret < 0)
+ cifs_dbg(FYI, "%s: Failed to send register message: %d\n", __func__, ret);
++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify);
++free_info:
++ cifs_swn_free_reg_info(&swnreg_info);
+ }
+- mutex_unlock(&cifs_swnreg_idr_mutex);
++ if (ret < 0)
++ cifs_dbg(FYI, "%s: Failed to snapshot registration: %d\n", __func__, ret);
+ }
+--- a/fs/smb/client/trace.h
++++ b/fs/smb/client/trace.h
+@@ -32,6 +32,7 @@
+ EM(netfs_trace_tcon_ref_get_find, "GET Find ") \
+ EM(netfs_trace_tcon_ref_get_find_sess_tcon, "GET FndSes") \
+ EM(netfs_trace_tcon_ref_get_reconnect_server, "GET Reconn") \
++ EM(netfs_trace_tcon_ref_get_swn_notify, "GET SwnNot") \
+ EM(netfs_trace_tcon_ref_new, "NEW ") \
+ EM(netfs_trace_tcon_ref_new_ipc, "NEW Ipc ") \
+ EM(netfs_trace_tcon_ref_new_reconnect_server, "NEW Reconn") \
+@@ -41,6 +42,7 @@
+ EM(netfs_trace_tcon_ref_put_mnt_ctx, "PUT MntCtx") \
+ EM(netfs_trace_tcon_ref_put_dfs_refer, "PUT DfsRfr") \
+ EM(netfs_trace_tcon_ref_put_reconnect_server, "PUT Reconn") \
++ EM(netfs_trace_tcon_ref_put_swn_notify, "PUT SwnNot") \
+ EM(netfs_trace_tcon_ref_put_tlink, "PUT Tlink ") \
+ EM(netfs_trace_tcon_ref_see_cancelled_close, "SEE Cn-Cls") \
+ EM(netfs_trace_tcon_ref_see_fscache_collision, "SEE FV-CO!") \
--- /dev/null
+From stable+bounces-276756-greg=kroah.com@vger.kernel.org Thu Jul 16 18:31:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 11:36:59 -0400
+Subject: smb: move smb_version_values to common/smbglob.h
+To: stable@vger.kernel.org
+Cc: ZhangGuoDong <zhangguodong@kylinos.cn>, ChenXiaoSong <chenxiaosong@kylinos.cn>, Namjae Jeon <linkinjeon@kernel.org>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716153704.583869-2-sashal@kernel.org>
+
+From: ZhangGuoDong <zhangguodong@kylinos.cn>
+
+[ Upstream commit 34cf191bb6a349dc88ec2c4f6355fe006ac669e0 ]
+
+Merge the struct members of the server and the client:
+
+ - req_capabilities: from client
+ - header_preamble_size: from client
+ - cap_unicode: from client
+ - capabilities: from server, rename to req_capabilities
+ - max_read_size: from server
+ - max_write_size: from server
+ - max_trans_size: from server
+ - max_credits: from server
+ - create_durable_size: from server
+ - create_durable_v2_size: from server
+ - create_mxac_size: from server
+ - create_disk_id_size: from server
+ - create_posix_size: from server
+
+Then move duplicate definitions to common header file.
+
+Co-developed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
+Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
+Signed-off-by: ZhangGuoDong <zhangguodong@kylinos.cn>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/cifsglob.h | 21 ---------------------
+ fs/smb/common/cifsglob.h | 31 +++++++++++++++++++++++++++++++
+ fs/smb/server/smb2misc.c | 2 +-
+ fs/smb/server/smb2ops.c | 28 ++++++++++++++--------------
+ fs/smb/server/smb2pdu.c | 10 +++++-----
+ fs/smb/server/smb_common.h | 29 -----------------------------
+ 6 files changed, 51 insertions(+), 70 deletions(-)
+
+--- a/fs/smb/client/cifsglob.h
++++ b/fs/smb/client/cifsglob.h
+@@ -543,27 +543,6 @@ struct smb_version_operations {
+ void (*is_network_name_deleted)(char *buf, struct TCP_Server_Info *srv);
+ };
+
+-struct smb_version_values {
+- char *version_string;
+- __u16 protocol_id;
+- __u32 req_capabilities;
+- __u32 large_lock_type;
+- __u32 exclusive_lock_type;
+- __u32 shared_lock_type;
+- __u32 unlock_lock_type;
+- size_t header_preamble_size;
+- size_t header_size;
+- size_t max_header_size;
+- size_t read_rsp_size;
+- __le16 lock_cmd;
+- unsigned int cap_unix;
+- unsigned int cap_nt_find;
+- unsigned int cap_large_files;
+- __u16 signing_enabled;
+- __u16 signing_required;
+- size_t create_lease_size;
+-};
+-
+ #define HEADER_SIZE(server) (server->vals->header_size)
+ #define MAX_HEADER_SIZE(server) (server->vals->max_header_size)
+ #define HEADER_PREAMBLE_SIZE(server) (server->vals->header_preamble_size)
+--- a/fs/smb/common/cifsglob.h
++++ b/fs/smb/common/cifsglob.h
+@@ -9,6 +9,37 @@
+ #ifndef _COMMON_CIFS_GLOB_H
+ #define _COMMON_CIFS_GLOB_H
+
++struct smb_version_values {
++ char *version_string;
++ __u16 protocol_id;
++ __le16 lock_cmd;
++ __u32 req_capabilities;
++ __u32 max_read_size;
++ __u32 max_write_size;
++ __u32 max_trans_size;
++ __u32 max_credits;
++ __u32 large_lock_type;
++ __u32 exclusive_lock_type;
++ __u32 shared_lock_type;
++ __u32 unlock_lock_type;
++ size_t header_preamble_size;
++ size_t header_size;
++ size_t max_header_size;
++ size_t read_rsp_size;
++ unsigned int cap_unix;
++ unsigned int cap_nt_find;
++ unsigned int cap_large_files;
++ unsigned int cap_unicode;
++ __u16 signing_enabled;
++ __u16 signing_required;
++ size_t create_lease_size;
++ size_t create_durable_size;
++ size_t create_durable_v2_size;
++ size_t create_mxac_size;
++ size_t create_disk_id_size;
++ size_t create_posix_size;
++};
++
+ static inline void inc_rfc1001_len(void *buf, int count)
+ {
+ be32_add_cpu((__be32 *)buf, count);
+--- a/fs/smb/server/smb2misc.c
++++ b/fs/smb/server/smb2misc.c
+@@ -460,7 +460,7 @@ int ksmbd_smb2_check_message(struct ksmb
+ }
+
+ validate_credit:
+- if ((work->conn->vals->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) &&
++ if ((work->conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) &&
+ smb2_validate_credit_charge(work->conn, hdr))
+ return 1;
+
+--- a/fs/smb/server/smb2ops.c
++++ b/fs/smb/server/smb2ops.c
+@@ -15,7 +15,7 @@
+ static struct smb_version_values smb21_server_values = {
+ .version_string = SMB21_VERSION_STRING,
+ .protocol_id = SMB21_PROT_ID,
+- .capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
++ .req_capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
+ .max_read_size = SMB21_DEFAULT_IOSIZE,
+ .max_write_size = SMB21_DEFAULT_IOSIZE,
+ .max_trans_size = SMB21_DEFAULT_IOSIZE,
+@@ -41,7 +41,7 @@ static struct smb_version_values smb21_s
+ static struct smb_version_values smb30_server_values = {
+ .version_string = SMB30_VERSION_STRING,
+ .protocol_id = SMB30_PROT_ID,
+- .capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
++ .req_capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
+ .max_read_size = SMB3_DEFAULT_IOSIZE,
+ .max_write_size = SMB3_DEFAULT_IOSIZE,
+ .max_trans_size = SMB3_DEFAULT_TRANS_SIZE,
+@@ -68,7 +68,7 @@ static struct smb_version_values smb30_s
+ static struct smb_version_values smb302_server_values = {
+ .version_string = SMB302_VERSION_STRING,
+ .protocol_id = SMB302_PROT_ID,
+- .capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
++ .req_capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
+ .max_read_size = SMB3_DEFAULT_IOSIZE,
+ .max_write_size = SMB3_DEFAULT_IOSIZE,
+ .max_trans_size = SMB3_DEFAULT_TRANS_SIZE,
+@@ -95,7 +95,7 @@ static struct smb_version_values smb302_
+ static struct smb_version_values smb311_server_values = {
+ .version_string = SMB311_VERSION_STRING,
+ .protocol_id = SMB311_PROT_ID,
+- .capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
++ .req_capabilities = SMB2_GLOBAL_CAP_LARGE_MTU,
+ .max_read_size = SMB3_DEFAULT_IOSIZE,
+ .max_write_size = SMB3_DEFAULT_IOSIZE,
+ .max_trans_size = SMB3_DEFAULT_TRANS_SIZE,
+@@ -204,7 +204,7 @@ void init_smb2_1_server(struct ksmbd_con
+ conn->signing_algorithm = SIGNING_ALG_HMAC_SHA256_LE;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
+- conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING;
++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_LEASING;
+ }
+
+ /**
+@@ -221,20 +221,20 @@ void init_smb3_0_server(struct ksmbd_con
+ conn->signing_algorithm = SIGNING_ALG_AES_CMAC_LE;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
+- conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING |
++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_LEASING |
+ SMB2_GLOBAL_CAP_DIRECTORY_LEASING;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION &&
+ conn->cli_cap & SMB2_GLOBAL_CAP_ENCRYPTION)
+- conn->vals->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION ||
+ (!(server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION_OFF) &&
+ conn->cli_cap & SMB2_GLOBAL_CAP_ENCRYPTION))
+- conn->vals->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
+- conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
+ }
+
+ /**
+@@ -251,16 +251,16 @@ void init_smb3_02_server(struct ksmbd_co
+ conn->signing_algorithm = SIGNING_ALG_AES_CMAC_LE;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
+- conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING |
++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_LEASING |
+ SMB2_GLOBAL_CAP_DIRECTORY_LEASING;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION ||
+ (!(server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION_OFF) &&
+ conn->cli_cap & SMB2_GLOBAL_CAP_ENCRYPTION))
+- conn->vals->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
+- conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
+ }
+
+ /**
+@@ -277,11 +277,11 @@ int init_smb3_11_server(struct ksmbd_con
+ conn->signing_algorithm = SIGNING_ALG_AES_CMAC_LE;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES)
+- conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING |
++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_LEASING |
+ SMB2_GLOBAL_CAP_DIRECTORY_LEASING;
+
+ if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
+- conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
+
+ INIT_LIST_HEAD(&conn->preauth_sess_table);
+ return 0;
+--- a/fs/smb/server/smb2pdu.c
++++ b/fs/smb/server/smb2pdu.c
+@@ -283,7 +283,7 @@ int init_smb2_neg_rsp(struct ksmbd_work
+ /* Not setting conn guid rsp->ServerGUID, as it
+ * not used by client for identifying connection
+ */
+- rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
++ rsp->Capabilities = cpu_to_le32(conn->vals->req_capabilities);
+ /* Default Max Message Size till SMB2.0, 64K*/
+ rsp->MaxTransactSize = cpu_to_le32(conn->vals->max_trans_size);
+ rsp->MaxReadSize = cpu_to_le32(conn->vals->max_read_size);
+@@ -975,7 +975,7 @@ bool smb3_encryption_negotiated(struct k
+ * SMB 3.0 and 3.0.2 dialects use the SMB2_GLOBAL_CAP_ENCRYPTION flag.
+ * SMB 3.1.1 uses the cipher_type field.
+ */
+- return (conn->vals->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) ||
++ return (conn->vals->req_capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) ||
+ conn->cipher_type;
+ }
+
+@@ -1228,7 +1228,7 @@ int smb2_handle_negotiate(struct ksmbd_w
+ rc = -EINVAL;
+ goto err_out;
+ }
+- rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
++ rsp->Capabilities = cpu_to_le32(conn->vals->req_capabilities);
+
+ /* For stats */
+ conn->connection_type = conn->dialect;
+@@ -3254,7 +3254,7 @@ int smb2_open(struct ksmbd_work *work)
+ share_ret = ksmbd_smb_check_shared_mode(fp->filp, fp);
+ if (!test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_OPLOCKS) ||
+ (req_op_level == SMB2_OPLOCK_LEVEL_LEASE &&
+- !(conn->vals->capabilities & SMB2_GLOBAL_CAP_LEASING))) {
++ !(conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LEASING))) {
+ if (share_ret < 0 && !S_ISDIR(file_inode(fp->filp)->i_mode)) {
+ rc = share_ret;
+ goto err_out1;
+@@ -7632,7 +7632,7 @@ static int fsctl_validate_negotiate_info
+ goto err_out;
+ }
+
+- neg_rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
++ neg_rsp->Capabilities = cpu_to_le32(conn->vals->req_capabilities);
+ memset(neg_rsp->Guid, 0, SMB2_CLIENT_GUID_SIZE);
+ neg_rsp->SecurityMode = cpu_to_le16(conn->srv_sec_mode);
+ neg_rsp->Dialect = cpu_to_le16(conn->dialect);
+--- a/fs/smb/server/smb_common.h
++++ b/fs/smb/server/smb_common.h
+@@ -336,35 +336,6 @@ struct file_id_full_dir_info {
+ char FileName[];
+ } __packed; /* level 0x105 FF rsp data */
+
+-struct smb_version_values {
+- char *version_string;
+- __u16 protocol_id;
+- __le16 lock_cmd;
+- __u32 capabilities;
+- __u32 max_read_size;
+- __u32 max_write_size;
+- __u32 max_trans_size;
+- __u32 max_credits;
+- __u32 large_lock_type;
+- __u32 exclusive_lock_type;
+- __u32 shared_lock_type;
+- __u32 unlock_lock_type;
+- size_t header_size;
+- size_t max_header_size;
+- size_t read_rsp_size;
+- unsigned int cap_unix;
+- unsigned int cap_nt_find;
+- unsigned int cap_large_files;
+- __u16 signing_enabled;
+- __u16 signing_required;
+- size_t create_lease_size;
+- size_t create_durable_size;
+- size_t create_durable_v2_size;
+- size_t create_mxac_size;
+- size_t create_disk_id_size;
+- size_t create_posix_size;
+-};
+-
+ struct filesystem_posix_info {
+ /* For undefined recommended transfer size return -1 in that field */
+ __le32 OptimalTransferSize; /* bsize on some os, iosize on other os */
--- /dev/null
+From stable+bounces-276755-greg=kroah.com@vger.kernel.org Thu Jul 16 18:30:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 11:36:58 -0400
+Subject: smb: move some duplicate definitions to common/cifsglob.h
+To: stable@vger.kernel.org
+Cc: ZhangGuoDong <zhangguodong@kylinos.cn>, ChenXiaoSong <chenxiaosong@kylinos.cn>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716153704.583869-1-sashal@kernel.org>
+
+From: ZhangGuoDong <zhangguodong@kylinos.cn>
+
+[ Upstream commit d877470b59910b5c50383d634dda3782386bba51 ]
+
+In order to maintain the code more easily, move duplicate definitions to
+new common header file.
+
+Co-developed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
+Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
+Signed-off-by: ZhangGuoDong <zhangguodong@kylinos.cn>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/cifsglob.h | 19 +------------------
+ fs/smb/common/cifsglob.h | 30 ++++++++++++++++++++++++++++++
+ fs/smb/server/smb_common.h | 14 +-------------
+ 3 files changed, 32 insertions(+), 31 deletions(-)
+ create mode 100644 fs/smb/common/cifsglob.h
+
+--- a/fs/smb/client/cifsglob.h
++++ b/fs/smb/client/cifsglob.h
+@@ -26,6 +26,7 @@
+ #include <crypto/internal/hash.h>
+ #include <linux/scatterlist.h>
+ #include <uapi/linux/cifs/cifs_mount.h>
++#include "../common/cifsglob.h"
+ #include "../common/smb2pdu.h"
+ #include "smb2pdu.h"
+
+@@ -606,12 +607,6 @@ get_rfc1002_length(void *buf)
+ return be32_to_cpu(*((__be32 *)buf)) & 0xffffff;
+ }
+
+-static inline void
+-inc_rfc1001_len(void *buf, int count)
+-{
+- be32_add_cpu((__be32 *)buf, count);
+-}
+-
+ struct TCP_Server_Info {
+ struct list_head tcp_ses_list;
+ struct list_head smb_ses_list;
+@@ -913,8 +908,6 @@ compare_mid(__u16 mid, const struct smb_
+ #define CIFS_MAX_RFC1002_WSIZE ((1<<17) - 1 - sizeof(WRITE_REQ) + 4)
+ #define CIFS_MAX_RFC1002_RSIZE ((1<<17) - 1 - sizeof(READ_RSP) + 4)
+
+-#define CIFS_DEFAULT_IOSIZE (1024 * 1024)
+-
+ /*
+ * Windows only supports a max of 60kb reads and 65535 byte writes. Default to
+ * those values when posix extensions aren't in force. In actuality here, we
+@@ -2022,30 +2015,20 @@ extern __u32 cifs_lock_secret;
+ extern mempool_t *cifs_mid_poolp;
+
+ /* Operations for different SMB versions */
+-#define SMB1_VERSION_STRING "1.0"
+-#define SMB20_VERSION_STRING "2.0"
+ #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
+ extern struct smb_version_operations smb1_operations;
+ extern struct smb_version_values smb1_values;
+ extern struct smb_version_operations smb20_operations;
+ extern struct smb_version_values smb20_values;
+ #endif /* CIFS_ALLOW_INSECURE_LEGACY */
+-#define SMB21_VERSION_STRING "2.1"
+ extern struct smb_version_operations smb21_operations;
+ extern struct smb_version_values smb21_values;
+-#define SMBDEFAULT_VERSION_STRING "default"
+ extern struct smb_version_values smbdefault_values;
+-#define SMB3ANY_VERSION_STRING "3"
+ extern struct smb_version_values smb3any_values;
+-#define SMB30_VERSION_STRING "3.0"
+ extern struct smb_version_operations smb30_operations;
+ extern struct smb_version_values smb30_values;
+-#define SMB302_VERSION_STRING "3.02"
+-#define ALT_SMB302_VERSION_STRING "3.0.2"
+ /*extern struct smb_version_operations smb302_operations;*/ /* not needed yet */
+ extern struct smb_version_values smb302_values;
+-#define SMB311_VERSION_STRING "3.1.1"
+-#define ALT_SMB311_VERSION_STRING "3.11"
+ extern struct smb_version_operations smb311_operations;
+ extern struct smb_version_values smb311_values;
+
+--- /dev/null
++++ b/fs/smb/common/cifsglob.h
+@@ -0,0 +1,30 @@
++/* SPDX-License-Identifier: LGPL-2.1 */
++/*
++ *
++ * Copyright (C) International Business Machines Corp., 2002,2008
++ * Author(s): Steve French (sfrench@us.ibm.com)
++ * Jeremy Allison (jra@samba.org)
++ *
++ */
++#ifndef _COMMON_CIFS_GLOB_H
++#define _COMMON_CIFS_GLOB_H
++
++static inline void inc_rfc1001_len(void *buf, int count)
++{
++ be32_add_cpu((__be32 *)buf, count);
++}
++
++#define SMB1_VERSION_STRING "1.0"
++#define SMB20_VERSION_STRING "2.0"
++#define SMB21_VERSION_STRING "2.1"
++#define SMBDEFAULT_VERSION_STRING "default"
++#define SMB3ANY_VERSION_STRING "3"
++#define SMB30_VERSION_STRING "3.0"
++#define SMB302_VERSION_STRING "3.02"
++#define ALT_SMB302_VERSION_STRING "3.0.2"
++#define SMB311_VERSION_STRING "3.1.1"
++#define ALT_SMB311_VERSION_STRING "3.11"
++
++#define CIFS_DEFAULT_IOSIZE (1024 * 1024)
++
++#endif /* _COMMON_CIFS_GLOB_H */
+--- a/fs/smb/server/smb_common.h
++++ b/fs/smb/server/smb_common.h
+@@ -10,6 +10,7 @@
+
+ #include "glob.h"
+ #include "nterr.h"
++#include "../common/cifsglob.h"
+ #include "../common/smb2pdu.h"
+ #include "smb2pdu.h"
+
+@@ -26,16 +27,8 @@
+ #define SMB311_PROT 6
+ #define BAD_PROT 0xFFFF
+
+-#define SMB1_VERSION_STRING "1.0"
+-#define SMB20_VERSION_STRING "2.0"
+-#define SMB21_VERSION_STRING "2.1"
+-#define SMB30_VERSION_STRING "3.0"
+-#define SMB302_VERSION_STRING "3.02"
+-#define SMB311_VERSION_STRING "3.1.1"
+-
+ #define SMB_ECHO_INTERVAL (60 * HZ)
+
+-#define CIFS_DEFAULT_IOSIZE (64 * 1024)
+ #define MAX_CIFS_SMALL_BUFFER_SIZE 448 /* big enough for most */
+
+ #define MAX_STREAM_PROT_LEN 0x00FFFFFF
+@@ -462,9 +455,4 @@ static inline unsigned int get_rfc1002_l
+ {
+ return be32_to_cpu(*((__be32 *)buf)) & 0xffffff;
+ }
+-
+-static inline void inc_rfc1001_len(void *buf, int count)
+-{
+- be32_add_cpu((__be32 *)buf, count);
+-}
+ #endif /* __SMB_COMMON_H__ */
--- /dev/null
+From stable+bounces-274649-greg=kroah.com@vger.kernel.org Wed Jul 15 02:47:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:47:02 -0400
+Subject: staging: rtl8723bs: core: move constants to right side in comparison
+To: stable@vger.kernel.org
+Cc: William Hansen-Baird <william.hansen.baird@gmail.com>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715004704.3934423-1-sashal@kernel.org>
+
+From: William Hansen-Baird <william.hansen.baird@gmail.com>
+
+[ Upstream commit cf0f2680c30d4b438a5e84b73dc70be405936b54 ]
+
+Move constants to right side in if-statement conditions.
+
+Signed-off-by: William Hansen-Baird <william.hansen.baird@gmail.com>
+Link: https://patch.msgid.link/20251224100329.762141-3-william.hansen.baird@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Stable-dep-of: 1463ca3ec660 ("staging: rtl8723bs: fix OOB reads in rtw_get_sec_ie(), rtw_get_wapi_ie(), and rtw_get_wps_attr()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 4 ++--
+ drivers/staging/rtl8723bs/core/rtw_security.c | 2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
+@@ -1012,7 +1012,7 @@ static int rtw_get_cipher_info(struct wl
+ pbuf = rtw_get_wpa_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12);
+
+ if (pbuf && (wpa_ielen > 0)) {
+- if (_SUCCESS == rtw_parse_wpa_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x)) {
++ if (rtw_parse_wpa_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) {
+ pnetwork->bcn_info.pairwise_cipher = pairwise_cipher;
+ pnetwork->bcn_info.group_cipher = group_cipher;
+ pnetwork->bcn_info.is_8021x = is8021x;
+@@ -1022,7 +1022,7 @@ static int rtw_get_cipher_info(struct wl
+ pbuf = rtw_get_wpa2_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12);
+
+ if (pbuf && (wpa_ielen > 0)) {
+- if (_SUCCESS == rtw_parse_wpa2_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x)) {
++ if (rtw_parse_wpa2_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) {
+ pnetwork->bcn_info.pairwise_cipher = pairwise_cipher;
+ pnetwork->bcn_info.group_cipher = group_cipher;
+ pnetwork->bcn_info.is_8021x = is8021x;
+--- a/drivers/staging/rtl8723bs/core/rtw_security.c
++++ b/drivers/staging/rtl8723bs/core/rtw_security.c
+@@ -1538,7 +1538,7 @@ void rtw_sec_restore_wep_key(struct adap
+ struct security_priv *securitypriv = &(adapter->securitypriv);
+ signed int keyid;
+
+- if ((_WEP40_ == securitypriv->dot11PrivacyAlgrthm) || (_WEP104_ == securitypriv->dot11PrivacyAlgrthm)) {
++ if ((securitypriv->dot11PrivacyAlgrthm == _WEP40_) || (securitypriv->dot11PrivacyAlgrthm == _WEP104_)) {
+ for (keyid = 0; keyid < 4; keyid++) {
+ if (securitypriv->key_mask & BIT(keyid)) {
+ if (keyid == securitypriv->dot11PrivacyKeyIndex)
--- /dev/null
+From stable+bounces-274554-greg=kroah.com@vger.kernel.org Tue Jul 14 22:04:41 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 16:02:41 -0400
+Subject: staging: rtl8723bs: fix heap buffer overflow in rtw_cfg80211_set_wpa_ie()
+To: stable@vger.kernel.org
+Cc: Alexandru Hossu <hossu.alexandru@gmail.com>, stable <stable@kernel.org>, Luka Gejak <luka.gejak@linux.dev>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714200241.3154071-3-sashal@kernel.org>
+
+From: Alexandru Hossu <hossu.alexandru@gmail.com>
+
+[ Upstream commit 5a752a616e756844388a1a45404db9fc29fec655 ]
+
+supplicant_ie is a 256-byte array in struct security_priv. The WPA and
+WPA2 IE copy paths use:
+
+ memcpy(padapter->securitypriv.supplicant_ie, &pwpa[0], wpa_ielen + 2);
+
+where wpa_ielen is the raw IE length field (u8, 0-255). When a local user
+supplies a connect request via nl80211 with a crafted WPA IE of length 255,
+wpa_ielen + 2 equals 257, overflowing the 256-byte buffer by one byte into
+the adjacent last_mic_err_time field.
+
+rtw_parse_wpa_ie() does not prevent this: its length consistency check
+compares *(wpa_ie+1) against (u8)(wpa_ie_len-2), which is (u8)(255) == 255
+when wpa_ie_len = 257, so the check passes silently.
+
+Add explicit bounds checks for both the WPA and WPA2 paths before the
+memcpy, rejecting any IE whose total size (wpa_ielen + 2) exceeds the
+supplicant_ie buffer.
+
+Fixes: 554c0a3abf21 ("staging: Add rtl8723bs sdio wifi driver")
+Cc: stable <stable@kernel.org>
+Reviewed-by: Luka Gejak <luka.gejak@linux.dev>
+Signed-off-by: Alexandru Hossu <hossu.alexandru@gmail.com>
+Link: https://patch.msgid.link/20260522004531.1038924-4-hossu.alexandru@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c
++++ b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c
+@@ -1482,6 +1482,10 @@ static int rtw_cfg80211_set_wpa_ie(struc
+
+ pwpa = rtw_get_wpa_ie(buf, &wpa_ielen, ielen);
+ if (pwpa && wpa_ielen > 0) {
++ if (wpa_ielen + 2 > sizeof(padapter->securitypriv.supplicant_ie)) {
++ ret = -EINVAL;
++ goto exit;
++ }
+ if (rtw_parse_wpa_ie(pwpa, wpa_ielen + 2, &group_cipher, &pairwise_cipher, NULL) == _SUCCESS) {
+ padapter->securitypriv.dot11AuthAlgrthm = dot11AuthAlgrthm_8021X;
+ padapter->securitypriv.ndisauthtype = Ndis802_11AuthModeWPAPSK;
+@@ -1491,6 +1495,10 @@ static int rtw_cfg80211_set_wpa_ie(struc
+
+ pwpa2 = rtw_get_wpa2_ie(buf, &wpa2_ielen, ielen);
+ if (pwpa2 && wpa2_ielen > 0) {
++ if (wpa2_ielen + 2 > sizeof(padapter->securitypriv.supplicant_ie)) {
++ ret = -EINVAL;
++ goto exit;
++ }
+ if (rtw_parse_wpa2_ie(pwpa2, wpa2_ielen + 2, &group_cipher, &pairwise_cipher, NULL) == _SUCCESS) {
+ padapter->securitypriv.dot11AuthAlgrthm = dot11AuthAlgrthm_8021X;
+ padapter->securitypriv.ndisauthtype = Ndis802_11AuthModeWPA2PSK;
--- /dev/null
+From stable+bounces-274547-greg=kroah.com@vger.kernel.org Tue Jul 14 22:04:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 16:02:39 -0400
+Subject: staging: rtl8723bs: Fix indentation issues
+To: stable@vger.kernel.org
+Cc: Franziska Naepelt <franziska.naepelt@googlemail.com>, Franziska Naepelt <franziska.naepelt@gmail.com>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714200241.3154071-1-sashal@kernel.org>
+
+From: Franziska Naepelt <franziska.naepelt@googlemail.com>
+
+[ Upstream commit b59cba2309b16fa364df03e3693b8d45c3fadbd6 ]
+
+Fix the following checkpatch indentation issues:
+- WARNING: suspect code indent for conditional statements (32, 48)
+- WARNING: suspect code indent for conditional statements (24, 24)
+- ERROR: code indent should use tabs where possible
+
+Signed-off-by: Franziska Naepelt <franziska.naepelt@gmail.com>
+Link: https://lore.kernel.org/r/20230619180351.18925-1-franziska.naepelt@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Stable-dep-of: 5a752a616e75 ("staging: rtl8723bs: fix heap buffer overflow in rtw_cfg80211_set_wpa_ie()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c
++++ b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c
+@@ -582,7 +582,7 @@ static int rtw_cfg80211_ap_set_encryptio
+
+ psecuritypriv->dot118021XGrpPrivacy = _WEP40_;
+ if (param->u.crypt.key_len == 13)
+- psecuritypriv->dot118021XGrpPrivacy = _WEP104_;
++ psecuritypriv->dot118021XGrpPrivacy = _WEP104_;
+
+ } else if (strcmp(param->u.crypt.alg, "TKIP") == 0) {
+ psecuritypriv->dot118021XGrpPrivacy = _TKIP_;
+@@ -1306,7 +1306,7 @@ static int cfg80211_rtw_scan(struct wiph
+ } else if (request->n_channels <= 4) {
+ for (j = request->n_channels - 1; j >= 0; j--)
+ for (i = 0; i < survey_times; i++)
+- memcpy(&ch[j*survey_times+i], &ch[j], sizeof(struct rtw_ieee80211_channel));
++ memcpy(&ch[j*survey_times+i], &ch[j], sizeof(struct rtw_ieee80211_channel));
+ _status = rtw_sitesurvey_cmd(padapter, ssid, RTW_SSID_SCAN_AMOUNT, ch, survey_times * request->n_channels);
+ } else {
+ _status = rtw_sitesurvey_cmd(padapter, ssid, RTW_SSID_SCAN_AMOUNT, NULL, 0);
+@@ -2811,7 +2811,7 @@ int rtw_wdev_alloc(struct adapter *padap
+ wdev->netdev = pnetdev;
+
+ wdev->iftype = NL80211_IFTYPE_STATION; /* will be init in rtw_hal_init() */
+- /* Must sync with _rtw_init_mlme_priv() */
++ /* Must sync with _rtw_init_mlme_priv() */
+ /* pmlmepriv->fw_state = WIFI_STATION_STATE */
+ padapter->rtw_wdev = wdev;
+ pnetdev->ieee80211_ptr = wdev;
--- /dev/null
+From stable+bounces-274651-greg=kroah.com@vger.kernel.org Wed Jul 15 02:47:21 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:47:04 -0400
+Subject: staging: rtl8723bs: fix OOB reads in rtw_get_sec_ie(), rtw_get_wapi_ie(), and rtw_get_wps_attr()
+To: stable@vger.kernel.org
+Cc: Alexandru Hossu <hossu.alexandru@gmail.com>, stable <stable@kernel.org>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715004704.3934423-3-sashal@kernel.org>
+
+From: Alexandru Hossu <hossu.alexandru@gmail.com>
+
+[ Upstream commit 1463ca3ec6601cbb097d8d87dbf5dcf1cb86a344 ]
+
+Three IE/attribute parsing functions have missing bounds checks.
+
+rtw_get_sec_ie() and rtw_get_wapi_ie() iterate over a raw IE buffer
+without verifying that the header bytes (tag + length) are within the
+remaining buffer before reading them. Additionally, rtw_get_sec_ie()
+compares the 4-byte WPA OUI at cnt+2 without checking that at least
+6 bytes remain, and rtw_get_wapi_ie() compares a 4-byte WAPI OUI at
+cnt+6 without checking that at least 10 bytes remain.
+
+rtw_get_wps_attr() reads wps_ie[0] and wps_ie+2 unconditionally at
+entry, before verifying that wps_ielen is large enough to contain
+the 6-byte WPS IE header (element_id + length + 4-byte OUI). Inside
+the attribute loop, get_unaligned_be16() is called on attr_ptr and
+attr_ptr+2 without checking that 4 bytes remain in the buffer.
+
+Add a cnt+2 bounds check before each loop body in rtw_get_sec_ie()
+and rtw_get_wapi_ie(), guard each multi-byte comparison with a minimum
+IE length requirement, add a wps_ielen < 6 early return in
+rtw_get_wps_attr(), and add a 4-byte bounds check in its inner loop.
+
+Fixes: 554c0a3abf21 ("staging: Add rtl8723bs sdio wifi driver")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Alexandru Hossu <hossu.alexandru@gmail.com>
+Link: https://patch.msgid.link/20260522004531.1038924-8-hossu.alexandru@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
+@@ -584,9 +584,14 @@ int rtw_get_wapi_ie(u8 *in_ie, uint in_l
+ cnt = (_TIMESTAMP_ + _BEACON_ITERVAL_ + _CAPABILITY_);
+
+ while (cnt < in_len) {
++ if (cnt + 2 > in_len)
++ break;
++ if (cnt + 2 + in_ie[cnt + 1] > in_len)
++ break;
+ authmode = in_ie[cnt];
+
+ if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY &&
++ in_ie[cnt + 1] >= 8 &&
+ (!memcmp(&in_ie[cnt + 6], wapi_oui1, 4) ||
+ !memcmp(&in_ie[cnt + 6], wapi_oui2, 4))) {
+ if (wapi_ie)
+@@ -619,9 +624,14 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l
+ cnt = (_TIMESTAMP_ + _BEACON_ITERVAL_ + _CAPABILITY_);
+
+ while (cnt < in_len) {
++ if (cnt + 2 > in_len)
++ break;
++ if (cnt + 2 + in_ie[cnt + 1] > in_len)
++ break;
+ authmode = in_ie[cnt];
+
+ if ((authmode == WLAN_EID_VENDOR_SPECIFIC) &&
++ in_ie[cnt + 1] >= 4 &&
+ (!memcmp(&in_ie[cnt + 2], &wpa_oui[0], 4))) {
+ if (wpa_ie)
+ memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt + 1] + 2);
+@@ -706,6 +716,9 @@ u8 *rtw_get_wps_attr(u8 *wps_ie, uint wp
+ if (len_attr)
+ *len_attr = 0;
+
++ if (wps_ielen < 6)
++ return attr_ptr;
++
+ if ((wps_ie[0] != WLAN_EID_VENDOR_SPECIFIC) ||
+ (memcmp(wps_ie + 2, wps_oui, 4))) {
+ return attr_ptr;
+@@ -716,6 +729,8 @@ u8 *rtw_get_wps_attr(u8 *wps_ie, uint wp
+
+ while (attr_ptr - wps_ie < wps_ielen) {
+ /* 4 = 2(Attribute ID) + 2(Length) */
++ if (attr_ptr + 4 > wps_ie + wps_ielen)
++ break;
+ u16 attr_id = get_unaligned_be16(attr_ptr);
+ u16 attr_data_len = get_unaligned_be16(attr_ptr + 2);
+ u16 attr_len = attr_data_len + 4;
--- /dev/null
+From stable+bounces-274552-greg=kroah.com@vger.kernel.org Tue Jul 14 22:04:36 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 16:02:40 -0400
+Subject: staging: rtl8723bs: Fix space issues
+To: stable@vger.kernel.org
+Cc: Franziska Naepelt <franziska.naepelt@googlemail.com>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714200241.3154071-2-sashal@kernel.org>
+
+From: Franziska Naepelt <franziska.naepelt@googlemail.com>
+
+[ Upstream commit c4b811b9361b0d7ea167a79587f5ec0a13fd8863 ]
+
+Fix the following checkpatch space issues:
+- CHECK: spaces preferred around that '*' (ctx:VxV)
+- CHECK: spaces preferred around that '+' (ctx:VxV)
+- CHECK: spaces preferred around that '-' (ctx:VxV)
+- CHECK: spaces preferred around that '|' (ctx:VxV)
+- CHECK: No space is necessary after a cast
+- WARNING: please, no spaces at the start of a line
+
+Signed-off-by: Franziska Naepelt <franziska.naepelt@googlemail.com>
+Link: https://lore.kernel.org/r/20230701102538.5359-1-franziska.naepelt@googlemail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Stable-dep-of: 5a752a616e75 ("staging: rtl8723bs: fix heap buffer overflow in rtw_cfg80211_set_wpa_ie()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c | 76 +++++++++++-----------
+ 1 file changed, 38 insertions(+), 38 deletions(-)
+
+--- a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c
++++ b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c
+@@ -95,14 +95,14 @@ static struct ieee80211_channel rtw_2ghz
+ static void rtw_2g_channels_init(struct ieee80211_channel *channels)
+ {
+ memcpy((void *)channels, (void *)rtw_2ghz_channels,
+- sizeof(struct ieee80211_channel)*RTW_2G_CHANNELS_NUM
++ sizeof(struct ieee80211_channel) * RTW_2G_CHANNELS_NUM
+ );
+ }
+
+ static void rtw_2g_rates_init(struct ieee80211_rate *rates)
+ {
+ memcpy(rates, rtw_g_rates,
+- sizeof(struct ieee80211_rate)*RTW_G_RATES_NUM
++ sizeof(struct ieee80211_rate) * RTW_G_RATES_NUM
+ );
+ }
+
+@@ -126,8 +126,8 @@ static struct ieee80211_supported_band *
+ if (!spt_band)
+ goto exit;
+
+- spt_band->channels = (struct ieee80211_channel *)(((u8 *)spt_band)+sizeof(struct ieee80211_supported_band));
+- spt_band->bitrates = (struct ieee80211_rate *)(((u8 *)spt_band->channels)+sizeof(struct ieee80211_channel)*n_channels);
++ spt_band->channels = (struct ieee80211_channel *)(((u8 *)spt_band) + sizeof(struct ieee80211_supported_band));
++ spt_band->bitrates = (struct ieee80211_rate *)(((u8 *)spt_band->channels) + sizeof(struct ieee80211_channel) * n_channels);
+ spt_band->band = band;
+ spt_band->n_channels = n_channels;
+ spt_band->n_bitrates = n_bitrates;
+@@ -247,10 +247,10 @@ struct cfg80211_bss *rtw_cfg80211_inform
+ u32 wpsielen = 0;
+ u8 *wpsie = NULL;
+
+- wpsie = rtw_get_wps_ie(pnetwork->network.ies+_FIXED_IE_LENGTH_, pnetwork->network.ie_length-_FIXED_IE_LENGTH_, NULL, &wpsielen);
++ wpsie = rtw_get_wps_ie(pnetwork->network.ies + _FIXED_IE_LENGTH_, pnetwork->network.ie_length - _FIXED_IE_LENGTH_, NULL, &wpsielen);
+
+ if (wpsie && wpsielen > 0)
+- psr = rtw_get_wps_attr_content(wpsie, wpsielen, WPS_ATTR_SELECTED_REGISTRAR, (u8 *)(&sr), NULL);
++ psr = rtw_get_wps_attr_content(wpsie, wpsielen, WPS_ATTR_SELECTED_REGISTRAR, (u8 *)(&sr), NULL);
+
+ if (sr != 0) {
+ /* it means under processing WPS */
+@@ -277,9 +277,9 @@ struct cfg80211_bss *rtw_cfg80211_inform
+ /* We've set wiphy's signal_type as CFG80211_SIGNAL_TYPE_MBM: signal strength in mBm (100*dBm) */
+ if (check_fwstate(pmlmepriv, _FW_LINKED) == true &&
+ is_same_network(&pmlmepriv->cur_network.network, &pnetwork->network, 0)) {
+- notify_signal = 100*translate_percentage_to_dbm(padapter->recvpriv.signal_strength);/* dbm */
++ notify_signal = 100 * translate_percentage_to_dbm(padapter->recvpriv.signal_strength);/* dbm */
+ } else {
+- notify_signal = 100*translate_percentage_to_dbm(pnetwork->network.phy_info.signal_strength);/* dbm */
++ notify_signal = 100 * translate_percentage_to_dbm(pnetwork->network.phy_info.signal_strength);/* dbm */
+ }
+
+ buf = kzalloc(MAX_BSSINFO_LEN, GFP_ATOMIC);
+@@ -453,20 +453,20 @@ check_bss:
+ roam_info.links[0].channel = notify_channel;
+ roam_info.links[0].bssid = cur_network->network.mac_address;
+ roam_info.req_ie =
+- pmlmepriv->assoc_req+sizeof(struct ieee80211_hdr_3addr)+2;
++ pmlmepriv->assoc_req + sizeof(struct ieee80211_hdr_3addr) + 2;
+ roam_info.req_ie_len =
+- pmlmepriv->assoc_req_len-sizeof(struct ieee80211_hdr_3addr)-2;
++ pmlmepriv->assoc_req_len - sizeof(struct ieee80211_hdr_3addr) - 2;
+ roam_info.resp_ie =
+- pmlmepriv->assoc_rsp+sizeof(struct ieee80211_hdr_3addr)+6;
++ pmlmepriv->assoc_rsp + sizeof(struct ieee80211_hdr_3addr) + 6;
+ roam_info.resp_ie_len =
+- pmlmepriv->assoc_rsp_len-sizeof(struct ieee80211_hdr_3addr)-6;
++ pmlmepriv->assoc_rsp_len - sizeof(struct ieee80211_hdr_3addr) - 6;
+ cfg80211_roamed(padapter->pnetdev, &roam_info, GFP_ATOMIC);
+ } else {
+ cfg80211_connect_result(padapter->pnetdev, cur_network->network.mac_address
+- , pmlmepriv->assoc_req+sizeof(struct ieee80211_hdr_3addr)+2
+- , pmlmepriv->assoc_req_len-sizeof(struct ieee80211_hdr_3addr)-2
+- , pmlmepriv->assoc_rsp+sizeof(struct ieee80211_hdr_3addr)+6
+- , pmlmepriv->assoc_rsp_len-sizeof(struct ieee80211_hdr_3addr)-6
++ , pmlmepriv->assoc_req + sizeof(struct ieee80211_hdr_3addr) + 2
++ , pmlmepriv->assoc_req_len - sizeof(struct ieee80211_hdr_3addr) - 2
++ , pmlmepriv->assoc_rsp + sizeof(struct ieee80211_hdr_3addr) + 6
++ , pmlmepriv->assoc_rsp_len - sizeof(struct ieee80211_hdr_3addr) - 6
+ , WLAN_STATUS_SUCCESS, GFP_ATOMIC);
+ }
+ }
+@@ -721,7 +721,7 @@ static int rtw_cfg80211_set_encryption(s
+ param->u.crypt.err = 0;
+ param->u.crypt.alg[IEEE_CRYPT_ALG_NAME_LEN - 1] = '\0';
+
+- if (param_len < (u32) ((u8 *) param->u.crypt.key - (u8 *) param) + param->u.crypt.key_len) {
++ if (param_len < (u32)((u8 *)param->u.crypt.key - (u8 *)param) + param->u.crypt.key_len) {
+ ret = -EINVAL;
+ goto exit;
+ }
+@@ -1036,10 +1036,10 @@ static int cfg80211_rtw_get_station(stru
+ }
+
+ /* for Ad-Hoc/AP mode */
+- if ((check_fwstate(pmlmepriv, WIFI_ADHOC_STATE)
+- || check_fwstate(pmlmepriv, WIFI_ADHOC_MASTER_STATE)
+- || check_fwstate(pmlmepriv, WIFI_AP_STATE))
+- && check_fwstate(pmlmepriv, _FW_LINKED)) {
++ if ((check_fwstate(pmlmepriv, WIFI_ADHOC_STATE) ||
++ check_fwstate(pmlmepriv, WIFI_ADHOC_MASTER_STATE) ||
++ check_fwstate(pmlmepriv, WIFI_AP_STATE)) &&
++ check_fwstate(pmlmepriv, _FW_LINKED)) {
+ /* TODO: should acquire station info... */
+ }
+
+@@ -1238,7 +1238,7 @@ static int cfg80211_rtw_scan(struct wiph
+ spin_unlock_bh(&pwdev_priv->scan_req_lock);
+
+ if (check_fwstate(pmlmepriv, WIFI_AP_STATE) == true) {
+- if (check_fwstate(pmlmepriv, WIFI_UNDER_WPS|_FW_UNDER_SURVEY|_FW_UNDER_LINKING) == true) {
++ if (check_fwstate(pmlmepriv, WIFI_UNDER_WPS | _FW_UNDER_SURVEY | _FW_UNDER_LINKING) == true) {
+ need_indicate_scan_done = true;
+ goto check_need_indicate_scan_done;
+ }
+@@ -1292,7 +1292,7 @@ static int cfg80211_rtw_scan(struct wiph
+ }
+
+ /* parsing channels, n_channels */
+- memset(ch, 0, sizeof(struct rtw_ieee80211_channel)*RTW_CHANNEL_SCAN_AMOUNT);
++ memset(ch, 0, sizeof(struct rtw_ieee80211_channel) * RTW_CHANNEL_SCAN_AMOUNT);
+ for (i = 0; i < request->n_channels && i < RTW_CHANNEL_SCAN_AMOUNT; i++) {
+ ch[i].hw_value = request->channels[i]->hw_value;
+ ch[i].flags = request->channels[i]->flags;
+@@ -1306,7 +1306,7 @@ static int cfg80211_rtw_scan(struct wiph
+ } else if (request->n_channels <= 4) {
+ for (j = request->n_channels - 1; j >= 0; j--)
+ for (i = 0; i < survey_times; i++)
+- memcpy(&ch[j*survey_times+i], &ch[j], sizeof(struct rtw_ieee80211_channel));
++ memcpy(&ch[j * survey_times + i], &ch[j], sizeof(struct rtw_ieee80211_channel));
+ _status = rtw_sitesurvey_cmd(padapter, ssid, RTW_SSID_SCAN_AMOUNT, ch, survey_times * request->n_channels);
+ } else {
+ _status = rtw_sitesurvey_cmd(padapter, ssid, RTW_SSID_SCAN_AMOUNT, NULL, 0);
+@@ -1462,7 +1462,7 @@ static int rtw_cfg80211_set_wpa_ie(struc
+ goto exit;
+ }
+
+- if (ielen > MAX_WPA_IE_LEN+MAX_WPS_IE_LEN+MAX_P2P_IE_LEN) {
++ if (ielen > MAX_WPA_IE_LEN + MAX_WPS_IE_LEN + MAX_P2P_IE_LEN) {
+ ret = -EINVAL;
+ goto exit;
+ }
+@@ -1482,19 +1482,19 @@ static int rtw_cfg80211_set_wpa_ie(struc
+
+ pwpa = rtw_get_wpa_ie(buf, &wpa_ielen, ielen);
+ if (pwpa && wpa_ielen > 0) {
+- if (rtw_parse_wpa_ie(pwpa, wpa_ielen+2, &group_cipher, &pairwise_cipher, NULL) == _SUCCESS) {
++ if (rtw_parse_wpa_ie(pwpa, wpa_ielen + 2, &group_cipher, &pairwise_cipher, NULL) == _SUCCESS) {
+ padapter->securitypriv.dot11AuthAlgrthm = dot11AuthAlgrthm_8021X;
+ padapter->securitypriv.ndisauthtype = Ndis802_11AuthModeWPAPSK;
+- memcpy(padapter->securitypriv.supplicant_ie, &pwpa[0], wpa_ielen+2);
++ memcpy(padapter->securitypriv.supplicant_ie, &pwpa[0], wpa_ielen + 2);
+ }
+ }
+
+ pwpa2 = rtw_get_wpa2_ie(buf, &wpa2_ielen, ielen);
+ if (pwpa2 && wpa2_ielen > 0) {
+- if (rtw_parse_wpa2_ie(pwpa2, wpa2_ielen+2, &group_cipher, &pairwise_cipher, NULL) == _SUCCESS) {
++ if (rtw_parse_wpa2_ie(pwpa2, wpa2_ielen + 2, &group_cipher, &pairwise_cipher, NULL) == _SUCCESS) {
+ padapter->securitypriv.dot11AuthAlgrthm = dot11AuthAlgrthm_8021X;
+ padapter->securitypriv.ndisauthtype = Ndis802_11AuthModeWPA2PSK;
+- memcpy(padapter->securitypriv.supplicant_ie, &pwpa2[0], wpa2_ielen+2);
++ memcpy(padapter->securitypriv.supplicant_ie, &pwpa2[0], wpa2_ielen + 2);
+ }
+ }
+
+@@ -1893,7 +1893,7 @@ static int cfg80211_rtw_set_pmksa(struct
+
+ memcpy(psecuritypriv->PMKIDList[index].PMKID, (u8 *)pmksa->pmkid, WLAN_PMKID_LEN);
+ psecuritypriv->PMKIDList[index].bUsed = true;
+- psecuritypriv->PMKIDIndex = index+1;
++ psecuritypriv->PMKIDIndex = index + 1;
+ blInserted = true;
+ break;
+ }
+@@ -2095,8 +2095,8 @@ static netdev_tx_t rtw_cfg80211_monitor_
+ _rtw_xmit_entry(skb, padapter->pnetdev);
+ return NETDEV_TX_OK;
+
+- } else if ((frame_control & (IEEE80211_FCTL_FTYPE|IEEE80211_FCTL_STYPE)) ==
+- (IEEE80211_FTYPE_MGMT|IEEE80211_STYPE_ACTION)) {
++ } else if ((frame_control & (IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) ==
++ (IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ACTION)) {
+ /* only for action frames */
+ struct xmit_frame *pmgntframe;
+ struct pkt_attrib *pattrib;
+@@ -2210,7 +2210,7 @@ static int rtw_cfg80211_add_monitor_if(s
+ goto out;
+
+ *ndev = pwdev_priv->pmon_ndev = mon_ndev;
+- memcpy(pwdev_priv->ifname_mon, name, IFNAMSIZ+1);
++ memcpy(pwdev_priv->ifname_mon, name, IFNAMSIZ + 1);
+
+ out:
+ if (ret && mon_wdev) {
+@@ -2304,14 +2304,14 @@ static int rtw_add_beacon(struct adapter
+ if (head_len < 24)
+ return -EINVAL;
+
+- pbuf = rtw_zmalloc(head_len+tail_len);
++ pbuf = rtw_zmalloc(head_len + tail_len);
+ if (!pbuf)
+ return -ENOMEM;
+
+- memcpy(pbuf, (void *)head+24, head_len-24);/* 24 =beacon header len. */
+- memcpy(pbuf+head_len-24, (void *)tail, tail_len);
++ memcpy(pbuf, (void *)head + 24, head_len - 24);/* 24 =beacon header len. */
++ memcpy(pbuf + head_len - 24, (void *)tail, tail_len);
+
+- len = head_len+tail_len-24;
++ len = head_len + tail_len - 24;
+
+ /* check wps ie if inclued */
+ rtw_get_wps_ie(pbuf + _FIXED_IE_LENGTH_, len - _FIXED_IE_LENGTH_, NULL, &wps_ielen);
+@@ -2601,7 +2601,7 @@ static int cfg80211_rtw_mgmt_tx(struct w
+ pwdev_priv = adapter_wdev_data(padapter);
+
+ /* cookie generation */
+- *cookie = (unsigned long) buf;
++ *cookie = (unsigned long)buf;
+
+ /* indicate ack before issue frame to avoid racing with rsp frame */
+ rtw_cfg80211_mgmt_tx_status(padapter, *cookie, buf, len, ack, GFP_KERNEL);
--- /dev/null
+From stable+bounces-274650-greg=kroah.com@vger.kernel.org Wed Jul 15 02:47:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:47:03 -0400
+Subject: staging: rtl8723bs: fix spaces around binary operators
+To: stable@vger.kernel.org
+Cc: Nikolay Kulikov <nikolayof23@gmail.com>, Ethan Tidmore <ethantidmore06@gmail.com>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715004704.3934423-2-sashal@kernel.org>
+
+From: Nikolay Kulikov <nikolayof23@gmail.com>
+
+[ Upstream commit 0c9d1b56f9af0762a5be5118e1bb962f23784dc4 ]
+
+Add missing spaces and fix line length to comply with kernel coding
+style.
+
+Signed-off-by: Nikolay Kulikov <nikolayof23@gmail.com>
+Reviewed-by: Ethan Tidmore <ethantidmore06@gmail.com>
+Link: https://patch.msgid.link/20260221172751.52329-1-nikolayof23@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Stable-dep-of: 1463ca3ec660 ("staging: rtl8723bs: fix OOB reads in rtw_get_sec_ie(), rtw_get_wapi_ie(), and rtw_get_wps_attr()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 78 +++++++++++++------------
+ 1 file changed, 42 insertions(+), 36 deletions(-)
+
+--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c
+@@ -455,10 +455,10 @@ int rtw_parse_wpa_ie(u8 *wpa_ie, int wpa
+ return _FAIL;
+ }
+
+- if ((*wpa_ie != WLAN_EID_VENDOR_SPECIFIC) || (*(wpa_ie+1) != (u8)(wpa_ie_len - 2)) ||
+- (memcmp(wpa_ie+2, RTW_WPA_OUI_TYPE, WPA_SELECTOR_LEN))) {
++ if ((*wpa_ie != WLAN_EID_VENDOR_SPECIFIC) ||
++ (*(wpa_ie + 1) != (u8)(wpa_ie_len - 2)) ||
++ (memcmp(wpa_ie + 2, RTW_WPA_OUI_TYPE, WPA_SELECTOR_LEN)))
+ return _FAIL;
+- }
+
+ pos = wpa_ie;
+
+@@ -518,7 +518,7 @@ int rtw_parse_wpa2_ie(u8 *rsn_ie, int rs
+ return _FAIL;
+ }
+
+- if ((*rsn_ie != WLAN_EID_RSN) || (*(rsn_ie+1) != (u8)(rsn_ie_len - 2)))
++ if ((*rsn_ie != WLAN_EID_RSN) || (*(rsn_ie + 1) != (u8)(rsn_ie_len - 2)))
+ return _FAIL;
+
+ pos = rsn_ie;
+@@ -586,18 +586,18 @@ int rtw_get_wapi_ie(u8 *in_ie, uint in_l
+ while (cnt < in_len) {
+ authmode = in_ie[cnt];
+
+- /* if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY) */
+- if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY && (!memcmp(&in_ie[cnt+6], wapi_oui1, 4) ||
+- !memcmp(&in_ie[cnt+6], wapi_oui2, 4))) {
++ if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY &&
++ (!memcmp(&in_ie[cnt + 6], wapi_oui1, 4) ||
++ !memcmp(&in_ie[cnt + 6], wapi_oui2, 4))) {
+ if (wapi_ie)
+- memcpy(wapi_ie, &in_ie[cnt], in_ie[cnt+1]+2);
++ memcpy(wapi_ie, &in_ie[cnt], in_ie[cnt + 1] + 2);
+
+ if (wapi_len)
+- *wapi_len = in_ie[cnt+1]+2;
++ *wapi_len = in_ie[cnt + 1] + 2;
+
+- cnt += in_ie[cnt+1]+2; /* get next */
++ cnt += in_ie[cnt + 1] + 2; /* get next */
+ } else {
+- cnt += in_ie[cnt+1]+2; /* get next */
++ cnt += in_ie[cnt + 1] + 2; /* get next */
+ }
+ }
+
+@@ -621,9 +621,10 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l
+ while (cnt < in_len) {
+ authmode = in_ie[cnt];
+
+- if ((authmode == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt+2], &wpa_oui[0], 4))) {
++ if ((authmode == WLAN_EID_VENDOR_SPECIFIC) &&
++ (!memcmp(&in_ie[cnt + 2], &wpa_oui[0], 4))) {
+ if (wpa_ie)
+- memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt+1]+2);
++ memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt + 1] + 2);
+
+ *wpa_len = in_ie[cnt + 1] + 2;
+ cnt += in_ie[cnt + 1] + 2; /* get next */
+@@ -632,10 +633,10 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l
+ if (rsn_ie)
+ memcpy(rsn_ie, &in_ie[cnt], in_ie[cnt + 1] + 2);
+
+- *rsn_len = in_ie[cnt+1]+2;
+- cnt += in_ie[cnt+1]+2; /* get next */
++ *rsn_len = in_ie[cnt + 1] + 2;
++ cnt += in_ie[cnt + 1] + 2; /* get next */
+ } else {
+- cnt += in_ie[cnt+1]+2; /* get next */
++ cnt += in_ie[cnt + 1] + 2; /* get next */
+ }
+ }
+ }
+@@ -667,20 +668,20 @@ u8 *rtw_get_wps_ie(u8 *in_ie, uint in_le
+ while (cnt < in_len) {
+ eid = in_ie[cnt];
+
+- if ((eid == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt+2], wps_oui, 4))) {
++ if ((eid == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt + 2], wps_oui, 4))) {
+ wpsie_ptr = &in_ie[cnt];
+
+ if (wps_ie)
+- memcpy(wps_ie, &in_ie[cnt], in_ie[cnt+1]+2);
++ memcpy(wps_ie, &in_ie[cnt], in_ie[cnt + 1] + 2);
+
+ if (wps_ielen)
+- *wps_ielen = in_ie[cnt+1]+2;
++ *wps_ielen = in_ie[cnt + 1] + 2;
+
+- cnt += in_ie[cnt+1]+2;
++ cnt += in_ie[cnt + 1] + 2;
+
+ break;
+ }
+- cnt += in_ie[cnt+1]+2; /* goto next */
++ cnt += in_ie[cnt + 1] + 2; /* goto next */
+ }
+
+ return wpsie_ptr;
+@@ -758,12 +759,12 @@ u8 *rtw_get_wps_attr_content(u8 *wps_ie,
+
+ if (attr_ptr && attr_len) {
+ if (buf_content)
+- memcpy(buf_content, attr_ptr+4, attr_len-4);
++ memcpy(buf_content, attr_ptr + 4, attr_len - 4);
+
+ if (len_content)
+- *len_content = attr_len-4;
++ *len_content = attr_len - 4;
+
+- return attr_ptr+4;
++ return attr_ptr + 4;
+ }
+
+ return NULL;
+@@ -1009,20 +1010,25 @@ static int rtw_get_cipher_info(struct wl
+ int group_cipher = 0, pairwise_cipher = 0, is8021x = 0;
+ int ret = _FAIL;
+
+- pbuf = rtw_get_wpa_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12);
++ pbuf = rtw_get_wpa_ie(&pnetwork->network.ies[12],
++ &wpa_ielen,
++ pnetwork->network.ie_length - 12);
+
+ if (pbuf && (wpa_ielen > 0)) {
+- if (rtw_parse_wpa_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) {
++ if (rtw_parse_wpa_ie(pbuf, wpa_ielen + 2, &group_cipher,
++ &pairwise_cipher, &is8021x) == _SUCCESS) {
+ pnetwork->bcn_info.pairwise_cipher = pairwise_cipher;
+ pnetwork->bcn_info.group_cipher = group_cipher;
+ pnetwork->bcn_info.is_8021x = is8021x;
+ ret = _SUCCESS;
+ }
+ } else {
+- pbuf = rtw_get_wpa2_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12);
++ pbuf = rtw_get_wpa2_ie(&pnetwork->network.ies[12], &wpa_ielen,
++ pnetwork->network.ie_length - 12);
+
+ if (pbuf && (wpa_ielen > 0)) {
+- if (rtw_parse_wpa2_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) {
++ if (rtw_parse_wpa2_ie(pbuf, wpa_ielen + 2, &group_cipher,
++ &pairwise_cipher, &is8021x) == _SUCCESS) {
+ pnetwork->bcn_info.pairwise_cipher = pairwise_cipher;
+ pnetwork->bcn_info.group_cipher = group_cipher;
+ pnetwork->bcn_info.is_8021x = is8021x;
+@@ -1091,21 +1097,21 @@ u16 rtw_mcs_rate(u8 bw_40MHz, u8 short_G
+ u16 max_rate = 0;
+
+ if (MCS_rate[0] & BIT(7))
+- max_rate = (bw_40MHz) ? ((short_GI)?1500:1350):((short_GI)?722:650);
++ max_rate = (bw_40MHz) ? ((short_GI) ? 1500 : 1350) : ((short_GI) ? 722 : 650);
+ else if (MCS_rate[0] & BIT(6))
+- max_rate = (bw_40MHz) ? ((short_GI)?1350:1215):((short_GI)?650:585);
++ max_rate = (bw_40MHz) ? ((short_GI) ? 1350 : 1215) : ((short_GI) ? 650 : 585);
+ else if (MCS_rate[0] & BIT(5))
+- max_rate = (bw_40MHz) ? ((short_GI)?1200:1080):((short_GI)?578:520);
++ max_rate = (bw_40MHz) ? ((short_GI) ? 1200 : 1080) : ((short_GI) ? 578 : 520);
+ else if (MCS_rate[0] & BIT(4))
+- max_rate = (bw_40MHz) ? ((short_GI)?900:810):((short_GI)?433:390);
++ max_rate = (bw_40MHz) ? ((short_GI) ? 900 : 810) : ((short_GI) ? 433 : 390);
+ else if (MCS_rate[0] & BIT(3))
+- max_rate = (bw_40MHz) ? ((short_GI)?600:540):((short_GI)?289:260);
++ max_rate = (bw_40MHz) ? ((short_GI) ? 600 : 540) : ((short_GI) ? 289 : 260);
+ else if (MCS_rate[0] & BIT(2))
+- max_rate = (bw_40MHz) ? ((short_GI)?450:405):((short_GI)?217:195);
++ max_rate = (bw_40MHz) ? ((short_GI) ? 450 : 405) : ((short_GI) ? 217 : 195);
+ else if (MCS_rate[0] & BIT(1))
+- max_rate = (bw_40MHz) ? ((short_GI)?300:270):((short_GI)?144:130);
++ max_rate = (bw_40MHz) ? ((short_GI) ? 300 : 270) : ((short_GI) ? 144 : 130);
+ else if (MCS_rate[0] & BIT(0))
+- max_rate = (bw_40MHz) ? ((short_GI)?150:135):((short_GI)?72:65);
++ max_rate = (bw_40MHz) ? ((short_GI) ? 150 : 135) : ((short_GI) ? 72 : 65);
+
+ return max_rate;
+ }
--- /dev/null
+From stable+bounces-277136-greg=kroah.com@vger.kernel.org Fri Jul 17 18:38:32 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 12:38:23 -0400
+Subject: treewide: Switch/rename to timer_delete[_sync]()
+To: stable@vger.kernel.org
+Cc: Thomas Gleixner <tglx@linutronix.de>, Ingo Molnar <mingo@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260717163824.2035351-1-sashal@kernel.org>
+
+From: Thomas Gleixner <tglx@linutronix.de>
+
+[ Upstream commit 8fa7292fee5c5240402371ea89ab285ec856c916 ]
+
+timer_delete[_sync]() replaces del_timer[_sync](). Convert the whole tree
+over and remove the historical wrapper inlines.
+
+Conversion was done with coccinelle plus manual fixups where necessary.
+
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Ingo Molnar <mingo@kernel.org>
+Stable-dep-of: 75fe87e19d8a ("HID: appleir: fix UAF on pending key_up_timer in remove()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/hid/hid-appleir.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/hid/hid-appleir.c
++++ b/drivers/hid/hid-appleir.c
+@@ -319,7 +319,7 @@ static void appleir_remove(struct hid_de
+ {
+ struct appleir *appleir = hid_get_drvdata(hid);
+ hid_hw_stop(hid);
+- del_timer_sync(&appleir->key_up_timer);
++ timer_delete_sync(&appleir->key_up_timer);
+ }
+
+ static const struct hid_device_id appleir_devices[] = {
--- /dev/null
+From sashal@kernel.org Sun Jul 19 20:01:35 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 19 Jul 2026 14:01:31 -0400
+Subject: usb: atm: ueagle-atm: wait for pre-firmware load in .disconnect()
+To: stable@vger.kernel.org
+Cc: Mauricio Faria de Oliveira <mfo@igalia.com>, stable <stable@kernel.org>, syzbot+ce1e5a1b4e086b43e56d@syzkaller.appspotmail.com, syzbot+306212936b13e520679d@syzkaller.appspotmail.com, syzbot+457452d30bcdda75ead2@syzkaller.appspotmail.com, Andrey Tsygunka <aitsygunka@yandex.ru>, Stanislaw Gruszka <stf_xl@wp.pl>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260719180131.1055331-1-sashal@kernel.org>
+
+From: Mauricio Faria de Oliveira <mfo@igalia.com>
+
+[ Upstream commit e2674dfbed8a30d57e2bc872c4bfa6c3eec918bf ]
+
+ueagle-atm uses the asynchronous request_firmware_nowait() in .probe(),
+but does not wait for its completion, not even in .disconnect(); so, if the
+device is unplugged meanwhile, its teardown runs concurrently with that.
+
+Even though this inconsistency is worth addressing on its own, it has also
+triggered several bug reports in syzbot over the years (some auto-closed)
+where the firmware sysfs fallback mechanism (CONFIG_FW_LOADER_USER_HELPER)
+creates a firmware subdirectory in the device directory during its removal,
+which might hit unexpected conditions in kernfs, apparently, depending at
+which point the add and remove operations raced. (See links.)
+
+The pattern is:
+
+usb ?-?: Direct firmware load for ueagle-atm/eagle?.fw failed with error -2
+usb ?-?: Falling back to sysfs fallback for: ueagle-atm/eagle?.fw
+<ERROR>
+Call trace:
+ ...
+ kernfs_create_dir_ns
+ sysfs_create_dir_ns
+ create_dir
+ kobject_add_internal
+ kobject_add_varg
+ kobject_add
+ class_dir_create_and_add
+ get_device_parent
+ device_add
+ fw_load_sysfs_fallback
+ fw_load_from_user_helper
+ firmware_fallback_sysfs
+ _request_firmware
+ request_firmware_work_func
+ ...
+
+(Some variations are observed, after fw_load_sysfs_fallback(), e.g., [1].)
+
+While the kernfs side is being looked at, the ueagle-atm side can be fixed
+by waiting for the pre-firmware load in the .disconnect() handler.
+
+This change has a similar approach to previous work by Andrey Tsygunka [2]
+(wait_for_completion() in .disconnect()), but it is relatively different in
+design/implementation; using the Originally-by tag for credit assignment.
+
+This has been tested with:
+- synthetic reproducer to check the error path;
+- USB gadget (virtual device) to check the firmware upload path;
+- QEMU device emulator to check the device ID re-enumeration path;
+(The latter two were written by Claude; no other code/text in this commit.)
+
+Links (year first reported):
+ 2025 https://syzbot.org/bug?extid=ce1e5a1b4e086b43e56d
+ 2025 https://syzbot.org/bug?extid=9af8471255ac36e34fd4
+ 2024 https://syzbot.org/bug?extid=306212936b13e520679d
+ 2023 https://syzkaller.appspot.com/bug?extid=457452d30bcdda75ead2
+ 2022 https://syzbot.org/bug?extid=782984d6f1701b526edb
+ 2021 https://syzbot.org/bug?id=f3f221579f4ef7e9691281f3c6f56c05f83e8490
+ 2021 https://syzbot.org/bug?id=84d86f0d71394829df6fc53daf6642c045983881
+ 2021 https://syzbot.org/bug?id=3302dc1c0e2b9c94f2e8edb404eabc9267bc6f90
+
+[1] https://syzkaller.appspot.com/bug?extid=457452d30bcdda75ead2
+[2] https://lore.kernel.org/lkml/20250410093146.3776801-2-aitsygunka@yandex.ru/
+
+Cc: stable <stable@kernel.org>
+Reported-by: syzbot+ce1e5a1b4e086b43e56d@syzkaller.appspotmail.com
+Closes: https://syzbot.org/bug?extid=ce1e5a1b4e086b43e56d
+Reported-by: syzbot+306212936b13e520679d@syzkaller.appspotmail.com
+Closes: https://syzbot.org/bug?extid=306212936b13e520679d
+Reported-by: syzbot+457452d30bcdda75ead2@syzkaller.appspotmail.com
+Closes: https://syzkaller.appspot.com/bug?extid=457452d30bcdda75ead2
+Originally-by: Andrey Tsygunka <aitsygunka@yandex.ru>
+Fixes: b72458a80c75 ("[PATCH] USB: Eagle and ADI 930 usb adsl modem driver")
+Assisted-by: Claude:claude-opus-4.7 # usb gadget & qemu device for testing
+Signed-off-by: Mauricio Faria de Oliveira <mfo@igalia.com>
+Acked-by: Stanislaw Gruszka <stf_xl@wp.pl>
+Link: https://patch.msgid.link/20260526-ueagle-atm_req-fw-sync-v3-1-93c01961daaf@igalia.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/atm/ueagle-atm.c | 36 +++++++++++++++++++++++++++++++-----
+ 1 file changed, 31 insertions(+), 5 deletions(-)
+
+--- a/drivers/usb/atm/ueagle-atm.c
++++ b/drivers/usb/atm/ueagle-atm.c
+@@ -599,7 +599,9 @@ static int uea_send_modem_cmd(struct usb
+ static void uea_upload_pre_firmware(const struct firmware *fw_entry,
+ void *context)
+ {
+- struct usb_device *usb = context;
++ struct usb_interface *intf = context;
++ struct usb_device *usb = interface_to_usbdev(intf);
++ struct completion *fw_done = usb_get_intfdata(intf);
+ const u8 *pfw;
+ u8 value;
+ u32 crc = 0;
+@@ -670,15 +672,17 @@ err_fw_corrupted:
+ err:
+ release_firmware(fw_entry);
+ uea_leaves(usb);
++ complete(fw_done);
+ }
+
+ /*
+ * uea_load_firmware - Load usb firmware for pre-firmware devices.
+ */
+-static int uea_load_firmware(struct usb_device *usb, unsigned int ver)
++static int uea_load_firmware(struct usb_interface *intf, unsigned int ver)
+ {
+ int ret;
+ char *fw_name = EAGLE_FIRMWARE;
++ struct usb_device *usb = interface_to_usbdev(intf);
+
+ uea_enters(usb);
+ uea_info(usb, "pre-firmware device, uploading firmware\n");
+@@ -702,7 +706,7 @@ static int uea_load_firmware(struct usb_
+ }
+
+ ret = request_firmware_nowait(THIS_MODULE, 1, fw_name, &usb->dev,
+- GFP_KERNEL, usb,
++ GFP_KERNEL, intf,
+ uea_upload_pre_firmware);
+ if (ret)
+ uea_err(usb, "firmware %s is not available\n", fw_name);
+@@ -2598,8 +2602,23 @@ static int uea_probe(struct usb_interfac
+
+ usb_reset_device(usb);
+
+- if (UEA_IS_PREFIRM(id))
+- return uea_load_firmware(usb, UEA_CHIP_VERSION(id));
++ if (UEA_IS_PREFIRM(id)) {
++ struct completion *fw_done;
++
++ /* Wait for the firmware load to be done, in .disconnect() */
++ fw_done = kzalloc(sizeof(*fw_done), GFP_KERNEL);
++ if (!fw_done)
++ return -ENOMEM;
++
++ init_completion(fw_done);
++ usb_set_intfdata(intf, fw_done);
++
++ ret = uea_load_firmware(intf, UEA_CHIP_VERSION(id));
++ if (ret)
++ kfree(fw_done);
++
++ return ret;
++ }
+
+ ret = usbatm_usb_probe(intf, id, &uea_usbatm_driver);
+ if (ret == 0) {
+@@ -2630,6 +2649,13 @@ static void uea_disconnect(struct usb_in
+ usbatm_usb_disconnect(intf);
+ mutex_unlock(&uea_mutex);
+ uea_info(usb, "ADSL device removed\n");
++ } else if (usb->config->desc.bNumInterfaces == 1) {
++ struct completion *fw_done = usb_get_intfdata(intf);
++
++ uea_dbg(usb, "pre-firmware device, waiting firmware upload\n");
++ wait_for_completion(fw_done);
++ uea_dbg(usb, "pre-firmware device, finished waiting\n");
++ kfree(fw_done);
+ }
+
+ uea_leaves(usb);
--- /dev/null
+From sashal@kernel.org Mon Jul 20 15:51:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 09:50:57 -0400
+Subject: usb: gadget: f_fs: initialize reset_work at allocation time
+To: stable@vger.kernel.org
+Cc: "Tyler Baker" <tyler.baker@oss.qualcomm.com>, stable <stable@kernel.org>, "Loic Poulain" <loic.poulain@oss.qualcomm.com>, "Dmitry Baryshkov" <dmitry.baryshkov@oss.qualcomm.com>, "Srinivas Kandagatla" <srinivas.kandagatla@oss.qualcomm.com>, "Peter Chen" <peter.chen@kernel.org>, "Michał Nazarewicz" <mina86@mina86.com>, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260720135057.1305612-1-sashal@kernel.org>
+
+From: Tyler Baker <tyler.baker@oss.qualcomm.com>
+
+[ Upstream commit 3137b243c93982fe3460335e12f9247739766e10 ]
+
+ffs_fs_kill_sb() unconditionally calls cancel_work_sync() on
+ffs->reset_work when a functionfs instance is unmounted:
+
+ ffs_data_reset(ffs);
+ cancel_work_sync(&ffs->reset_work);
+
+However ffs->reset_work is only ever initialized via INIT_WORK() in
+ffs_func_set_alt() and ffs_func_disable(), and only on the
+FFS_DEACTIVATED path. That state is reached solely by ffs_data_closed()
+when the instance is mounted with the "no_disconnect" option, so for the
+common case (no "no_disconnect", or mounted and unmounted without ever
+being deactivated) reset_work is never initialized.
+
+ffs_data_new() allocates the ffs_data with kzalloc_obj() and does not
+initialize reset_work, and ffs_data_reset()/ffs_data_clear() do not touch
+it either, so reset_work.func is left NULL. cancel_work_sync() on such a
+work then trips the WARN_ON(!work->func) guard in __flush_work():
+
+ WARNING: kernel/workqueue.c:4301 at __flush_work+0x330/0x360, CPU#3: umount
+ Call trace:
+ __flush_work
+ cancel_work_sync
+ ffs_fs_kill_sb [usb_f_fs]
+ deactivate_locked_super
+ deactivate_super
+ cleanup_mnt
+ __cleanup_mnt
+ task_work_run
+ exit_to_user_mode_loop
+ el0_svc
+
+On older kernels cancel_work_sync() on a zero-initialized work struct was
+a silent no-op, which hid the missing initialization.
+
+Initialize reset_work once in ffs_data_new() so it is always valid for
+the lifetime of the ffs_data, and drop the now-redundant INIT_WORK()
+calls from the two deactivation paths.
+
+Fixes: 18d6b32fca38 ("usb: gadget: f_fs: add "no_disconnect" mode")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Tyler Baker <tyler.baker@oss.qualcomm.com>
+Cc: Loic Poulain <loic.poulain@oss.qualcomm.com>
+Cc: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Cc: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
+Tested-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
+Reviewed-by: Peter Chen <peter.chen@kernel.org>
+Acked-by: Michał Nazarewicz <mina86@mina86.com>
+Link: https://patch.msgid.link/20260609193635.2284430-1-tyler.baker@oss.qualcomm.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+[ dropped the ffs_func_disable hunk since 6.6 predates the disable-body split and routes disable through ffs_func_set_alt, so removing the single lazy INIT_WORK there covers both paths ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_fs.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/usb/gadget/function/f_fs.c
++++ b/drivers/usb/gadget/function/f_fs.c
+@@ -255,6 +255,7 @@ static int ffs_acquire_dev(const char *d
+ static void ffs_release_dev(struct ffs_dev *ffs_dev);
+ static int ffs_ready(struct ffs_data *ffs);
+ static void ffs_closed(struct ffs_data *ffs);
++static void ffs_reset_work(struct work_struct *work);
+
+ /* Misc helper functions ****************************************************/
+
+@@ -1786,6 +1787,7 @@ static struct ffs_data *ffs_data_new(con
+ init_waitqueue_head(&ffs->ev.waitq);
+ init_waitqueue_head(&ffs->wait);
+ init_completion(&ffs->ep0req_completion);
++ INIT_WORK(&ffs->reset_work, ffs_reset_work);
+
+ /* XXX REVISIT need to update it in some places, or do we? */
+ ffs->ev.can_stall = 1;
+@@ -3342,7 +3344,6 @@ static int ffs_func_set_alt(struct usb_f
+
+ if (ffs->state == FFS_DEACTIVATED) {
+ ffs->state = FFS_CLOSING;
+- INIT_WORK(&ffs->reset_work, ffs_reset_work);
+ schedule_work(&ffs->reset_work);
+ return -ENODEV;
+ }
--- /dev/null
+From stable+bounces-277908-greg=kroah.com@vger.kernel.org Mon Jul 20 17:49:11 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 11:00:54 -0400
+Subject: usb: gadget: f_fs: Tie read_buffer lifetime to ffs_epfile
+To: stable@vger.kernel.org
+Cc: Neill Kapron <nkapron@google.com>, stable <stable@kernel.org>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720150054.1919483-1-sashal@kernel.org>
+
+From: Neill Kapron <nkapron@google.com>
+
+[ Upstream commit 8bdcf96eb135aebacac319667f87db034fb38406 ]
+
+Currently, ffs_epfile_release unconditionally frees the endpoint's
+read_buffer when a file descriptor is closed. If userspace explicitly
+opens the endpoint multiple times and closes one, the read_buffer is
+destroyed. This can lead to silent data loss if other file descriptors
+are still actively reading from the endpoint.
+
+By tying the lifetime of the read_buffer to the ffs_epfile structure itself
+(which is destroyed when the functionfs instance is torn down in
+ffs_epfiles_destroy), we eliminate the brittle dependency on open/release
+calls while correctly matching the conceptual lifetime of unread data on
+the hardware endpoint.
+
+Fixes: 9353afbbfa7b ("usb: gadget: f_fs: buffer data from ‘oversized’ OUT requests")
+Cc: stable <stable@kernel.org>
+Assisted-by: Antigravity:gemini-3.1-pro
+Signed-off-by: Neill Kapron <nkapron@google.com>
+Link: https://patch.msgid.link/20260619040609.4010746-3-nkapron@google.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+[ adjusted context in ffs_epfiles_destroy() since 6.12 lacks the simple_remove_by_name() rework and still uses dentry-based cleanup ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_fs.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/usb/gadget/function/f_fs.c
++++ b/drivers/usb/gadget/function/f_fs.c
+@@ -1292,7 +1292,6 @@ ffs_epfile_release(struct inode *inode,
+
+ ENTER();
+
+- __ffs_epfile_read_buffer_free(epfile);
+ ffs_data_closed(epfile->ffs);
+
+ return 0;
+@@ -1957,6 +1956,7 @@ static void ffs_epfiles_destroy(struct f
+
+ for (; count; --count, ++epfile) {
+ BUG_ON(mutex_is_locked(&epfile->mutex));
++ __ffs_epfile_read_buffer_free(epfile);
+ if (epfile->dentry) {
+ d_delete(epfile->dentry);
+ dput(epfile->dentry);
--- /dev/null
+From stable+bounces-277487-greg=kroah.com@vger.kernel.org Sun Jul 19 17:29:31 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 19 Jul 2026 11:28:58 -0400
+Subject: usb: iowarrior: remove inherent race with minor number
+To: stable@vger.kernel.org
+Cc: Oliver Neukum <oneukum@suse.com>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260719152859.190429-1-sashal@kernel.org>
+
+From: Oliver Neukum <oneukum@suse.com>
+
+[ Upstream commit 56dd29088c9d9510c48a8ebad2465248fde36551 ]
+
+The driver saves the minor number it gets upon registration
+in its descriptor for debugging purposes. However, there is
+inevitably a window between registration and saving the correct
+minor in a descriptor. During this window the debugging output
+will be wrong.
+As wrong debug output is worse than no debug output, just
+remove it.
+
+Signed-off-by: Oliver Neukum <oneukum@suse.com>
+Link: https://patch.msgid.link/20260312094619.1590556-1-oneukum@suse.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Stable-dep-of: c602254ba4c1 ("USB: iowarrior: fix use-after-free on disconnect race")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/misc/iowarrior.c | 17 +++--------------
+ 1 file changed, 3 insertions(+), 14 deletions(-)
+
+--- a/drivers/usb/misc/iowarrior.c
++++ b/drivers/usb/misc/iowarrior.c
+@@ -74,7 +74,6 @@ struct iowarrior {
+ struct mutex mutex; /* locks this structure */
+ struct usb_device *udev; /* save off the usb device pointer */
+ struct usb_interface *interface; /* the interface for this device */
+- unsigned char minor; /* the starting minor number for this device */
+ struct usb_endpoint_descriptor *int_out_endpoint; /* endpoint for reading (needed for IOW56 only) */
+ struct usb_endpoint_descriptor *int_in_endpoint; /* endpoint for reading */
+ struct urb *int_in_urb; /* the urb for reading data */
+@@ -246,7 +245,6 @@ static void iowarrior_write_callback(str
+ */
+ static inline void iowarrior_delete(struct iowarrior *dev)
+ {
+- dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor);
+ kfree(dev->int_in_buffer);
+ usb_free_urb(dev->int_in_urb);
+ kfree(dev->read_queue);
+@@ -297,9 +295,6 @@ static ssize_t iowarrior_read(struct fil
+ goto exit;
+ }
+
+- dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n",
+- dev->minor, count);
+-
+ /* read count must be packet size (+ time stamp) */
+ if ((count != dev->report_size)
+ && (count != (dev->report_size + 1))) {
+@@ -379,8 +374,6 @@ static ssize_t iowarrior_write(struct fi
+ retval = -ENODEV;
+ goto exit;
+ }
+- dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n",
+- dev->minor, count);
+ /* if count is 0 we're already done */
+ if (count == 0) {
+ retval = 0;
+@@ -523,9 +516,6 @@ static long iowarrior_ioctl(struct file
+ goto error_out;
+ }
+
+- dev_dbg(&dev->interface->dev, "minor %d, cmd 0x%.4x, arg %ld\n",
+- dev->minor, cmd, arg);
+-
+ retval = 0;
+ io_res = 0;
+ switch (cmd) {
+@@ -672,8 +662,6 @@ static int iowarrior_release(struct inod
+ if (!dev)
+ return -ENODEV;
+
+- dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor);
+-
+ /* lock our device */
+ mutex_lock(&dev->mutex);
+
+@@ -776,6 +764,7 @@ static int iowarrior_probe(struct usb_in
+ struct usb_host_interface *iface_desc;
+ int retval = -ENOMEM;
+ int res;
++ int minor;
+
+ /* allocate memory for our device state and initialize it */
+ dev = kzalloc(sizeof(struct iowarrior), GFP_KERNEL);
+@@ -891,12 +880,12 @@ static int iowarrior_probe(struct usb_in
+ goto error;
+ }
+
+- dev->minor = interface->minor;
++ minor = interface->minor;
+
+ /* let the user know what node this device is now attached to */
+ dev_info(&interface->dev, "IOWarrior product=0x%x, serial=%s interface=%d "
+ "now attached to iowarrior%d\n", dev->product_id, dev->chip_serial,
+- iface_desc->desc.bInterfaceNumber, dev->minor - IOWARRIOR_MINOR_BASE);
++ iface_desc->desc.bInterfaceNumber, minor - IOWARRIOR_MINOR_BASE);
+ return retval;
+
+ error:
--- /dev/null
+From sashal@kernel.org Sun Jul 19 20:01:37 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 19 Jul 2026 14:01:34 -0400
+Subject: usb: typec: tcpm: Fix VDM type for Enter Mode commands
+To: stable@vger.kernel.org
+Cc: Andy Yan <andyshrk@163.com>, stable <stable@kernel.org>, Heikki Krogerus <heikki.krogerus@linux.intel.com>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260719180134.1055370-1-sashal@kernel.org>
+
+From: Andy Yan <andyshrk@163.com>
+
+[ Upstream commit 9cff680e47632b7723cb19f9c5e63669063c3417 ]
+
+VDO() second parameter is VDM type (bit 15): 1 for SVDM, 0 for UVDM.
+Using 'vdo ? 2 : 1' corrupts SVID low bit when vdo is non-NULL
+(2 << 15 = BIT(16)). Enter Mode is always SVDM, hardcode to 1.
+
+Fixes: 8face9aa57c8 ("usb: typec: Add parameter for the VDO to typec_altmode_enter()")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Andy Yan <andyshrk@163.com>
+Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Link: https://patch.msgid.link/20260604105059.18750-1-andyshrk@163.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/typec/tcpm/tcpm.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/usb/typec/tcpm/tcpm.c
++++ b/drivers/usb/typec/tcpm/tcpm.c
+@@ -2231,7 +2231,7 @@ static int tcpm_altmode_enter(struct typ
+ if (svdm_version < 0)
+ return svdm_version;
+
+- header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE);
++ header = VDO(altmode->svid, 1, svdm_version, CMD_ENTER_MODE);
+ header |= VDO_OPOS(altmode->mode);
+
+ tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0);
--- /dev/null
+From stable+bounces-274437-greg=kroah.com@vger.kernel.org Tue Jul 14 18:39:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 12:38:16 -0400
+Subject: vfio/mlx5: Fix racy bitfields and tighten struct layout
+To: stable@vger.kernel.org
+Cc: Alex Williamson <alex.williamson@nvidia.com>, Yishai Hadas <yishaih@nvidia.com>, Kevin Tian <kevin.tian@intel.com>, Alex Williamson <alex@shazbot.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714163816.2850342-1-sashal@kernel.org>
+
+From: Alex Williamson <alex.williamson@nvidia.com>
+
+[ Upstream commit f2365a63b02ddea32e7db78b742c2503ec7b81f1 ]
+
+Bitfield operations are not atomic, they use a read-modify-write
+pattern, therefore we should be careful not to pack bitfields that
+can be concurrently updated into the same storage unit.
+
+This split takes a binary approach: flags that are only modified
+pre/post open/close remain bitfields, flags modified from user
+action, including actions that reach across to another device (ex.
+reset) use dedicated storage units.
+
+Note mlx5_vhca_page_tracker.status is relocated to fill the alignment
+hole this split exposes.
+
+Bitfield justifications:
+
+ migrate_cap: written only in mlx5vf_cmd_set_migratable() at probe
+ chunk_mode: written only in mlx5vf_cmd_set_migratable() at probe
+ mig_state_cap: written only in mlx5vf_cmd_set_migratable() at probe
+
+Dedicated storage units:
+
+ mdev_detach: written in the VF attach/detach event notifier
+ mlx5fv_vf_event() at runtime
+ log_active: written in mlx5vf_start_page_tracker()/
+ mlx5vf_stop_page_tracker() during runtime dirty tracking
+ deferred_reset: written in mlx5vf_state_mutex_unlock()/
+ mlx5vf_pci_aer_reset_done() during runtime reset handling
+ is_err: set by tracker error handling and dirty-log polling at runtime
+ object_changed: set by tracker event handling and cleared by dirty-log
+ polling at runtime
+
+Fixes: 61a2f1460fd0 ("vfio/mlx5: Manage the VF attach/detach callback from the PF")
+Fixes: 79c3cf279926 ("vfio/mlx5: Init QP based resources for dirty tracking")
+Fixes: f886473071d6 ("vfio/mlx5: Add support for tracker object change event")
+Cc: Yishai Hadas <yishaih@nvidia.com>
+Cc: stable@vger.kernel.org
+Assisted-by: Claude:claude-opus-4-8
+Signed-off-by: Alex Williamson <alex.williamson@nvidia.com>
+Reviewed-by: Kevin Tian <kevin.tian@intel.com>
+Link: https://lore.kernel.org/r/20260615191241.688297-5-alex.williamson@nvidia.com
+Signed-off-by: Alex Williamson <alex@shazbot.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vfio/pci/mlx5/cmd.h | 13 ++++++++-----
+ 1 file changed, 8 insertions(+), 5 deletions(-)
+
+--- a/drivers/vfio/pci/mlx5/cmd.h
++++ b/drivers/vfio/pci/mlx5/cmd.h
+@@ -82,23 +82,26 @@ struct mlx5_vhca_qp {
+ struct mlx5_vhca_page_tracker {
+ u32 id;
+ u32 pdn;
+- u8 is_err:1;
++ /* Flags modified at runtime - dedicated storage unit */
++ u8 is_err;
++ int status;
+ struct mlx5_uars_page *uar;
+ struct mlx5_vhca_cq cq;
+ struct mlx5_vhca_qp *host_qp;
+ struct mlx5_vhca_qp *fw_qp;
+ struct mlx5_nb nb;
+- int status;
+ };
+
+ struct mlx5vf_pci_core_device {
+ struct vfio_pci_core_device core_device;
+ int vf_id;
+ u16 vhca_id;
++ /* Flags only modified on setup/release - bitfield ok */
+ u8 migrate_cap:1;
+- u8 deferred_reset:1;
+- u8 mdev_detach:1;
+- u8 log_active:1;
++ /* Flags modified at runtime - dedicated storage unit */
++ u8 mdev_detach;
++ u8 log_active;
++ u8 deferred_reset;
+ struct completion tracker_comp;
+ /* protect migration state */
+ struct mutex state_mutex;
--- /dev/null
+From stable+bounces-274113-greg=kroah.com@vger.kernel.org Tue Jul 14 05:33:39 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 23:33:07 -0400
+Subject: vfio/pci: Latch disable_idle_d3 per device
+To: stable@vger.kernel.org
+Cc: Alex Williamson <alex.williamson@nvidia.com>, Kevin Tian <kevin.tian@intel.com>, Alex Williamson <alex@shazbot.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714033307.2397254-3-sashal@kernel.org>
+
+From: Alex Williamson <alex.williamson@nvidia.com>
+
+[ Upstream commit 4575e9aac5336d1365138c0284773bf8da4b1fa3 ]
+
+When disable_idle_d3 was introduced in vfio-pci, it directly manipulated
+the device power state with pci_set_power_state(). There were no
+refcounts to maintain or balanced operations, we could unconditionally
+bring the device to D0 and conditionally move it to D3hot. Therefore
+the module parameter was made writable.
+
+Later, in commit c61302aa48f7 ("vfio/pci: Move module parameters to
+vfio_pci.c"), as part of the vfio-pci-core split, the writable aspect
+of the module parameter was nullified. The parameter value could still
+be changed through sysfs, but the vfio-pci driver latched the values
+into vfio-pci-core globals at module init. Loading the vfio-pci module,
+or unloading and reloading, with non-default or different values could
+change the globals relative to existing devices bound to vfio-pci
+variant drivers.
+
+Runtime PM was introduced in commit 7ab5e10eda02 ("vfio/pci: Move the
+unused device into low power state with runtime PM"), which marks the
+point where power states became refcounted. PM get and put operations
+need to be balanced, but the same module operations noted above can
+change the global variables relative to those devices already bound to
+vfio-pci variant drivers. This introduces a window where PM operations
+can now become unbalanced.
+
+To resolve this with a narrow footprint for stable backports, the
+disable_idle_d3 flag is latched into the vfio_pci_core_device at the
+time of initialization, such that the device always operates with a
+consistent value.
+
+NB. vfio_pci_dev_set_try_reset() now unconditionally raises the
+runtime PM usage count around bus reset to account for disable_idle_d3
+becoming a per-device rather than global flag. When this flag is set,
+the additional get/put pair is harmless and allows continued use of the
+shared vfio_pci_dev_set_pm_runtime_get() helper.
+
+Fixes: 7ab5e10eda02 ("vfio/pci: Move the unused device into low power state with runtime PM")
+Cc: stable@vger.kernel.org
+Assisted-by: Claude:claude-opus-4-8
+Signed-off-by: Alex Williamson <alex.williamson@nvidia.com>
+Reviewed-by: Kevin Tian <kevin.tian@intel.com>
+Link: https://lore.kernel.org/r/20260615191241.688297-2-alex.williamson@nvidia.com
+Signed-off-by: Alex Williamson <alex@shazbot.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vfio/pci/vfio_pci_core.c | 19 ++++++++++---------
+ include/linux/vfio_pci_core.h | 1 +
+ 2 files changed, 11 insertions(+), 9 deletions(-)
+
+--- a/drivers/vfio/pci/vfio_pci_core.c
++++ b/drivers/vfio/pci/vfio_pci_core.c
+@@ -472,7 +472,7 @@ int vfio_pci_core_enable(struct vfio_pci
+ u16 cmd;
+ u8 msix_pos;
+
+- if (!disable_idle_d3) {
++ if (!vdev->disable_idle_d3) {
+ ret = pm_runtime_resume_and_get(&pdev->dev);
+ if (ret < 0)
+ return ret;
+@@ -547,7 +547,7 @@ out_free_state:
+ out_disable_device:
+ pci_disable_device(pdev);
+ out_power:
+- if (!disable_idle_d3)
++ if (!vdev->disable_idle_d3)
+ pm_runtime_put(&pdev->dev);
+ return ret;
+ }
+@@ -674,7 +674,7 @@ out:
+ vfio_pci_dev_set_try_reset(vdev->vdev.dev_set);
+
+ /* Put the pm-runtime usage counter acquired during enable */
+- if (!disable_idle_d3)
++ if (!vdev->disable_idle_d3)
+ pm_runtime_put(&pdev->dev);
+ }
+ EXPORT_SYMBOL_GPL(vfio_pci_core_disable);
+@@ -2091,6 +2091,8 @@ int vfio_pci_core_init_dev(struct vfio_d
+ INIT_LIST_HEAD(&vdev->sriov_pfs_item);
+ init_rwsem(&vdev->memory_lock);
+
++ vdev->disable_idle_d3 = disable_idle_d3;
++
+ return 0;
+ }
+ EXPORT_SYMBOL_GPL(vfio_pci_core_init_dev);
+@@ -2183,7 +2185,7 @@ int vfio_pci_core_register_device(struct
+
+ dev->driver->pm = &vfio_pci_core_pm_ops;
+ pm_runtime_allow(dev);
+- if (!disable_idle_d3)
++ if (!vdev->disable_idle_d3)
+ pm_runtime_put(dev);
+
+ ret = vfio_register_group_dev(&vdev->vdev);
+@@ -2192,7 +2194,7 @@ int vfio_pci_core_register_device(struct
+ return 0;
+
+ out_power:
+- if (!disable_idle_d3)
++ if (!vdev->disable_idle_d3)
+ pm_runtime_get_noresume(dev);
+
+ pm_runtime_forbid(dev);
+@@ -2212,7 +2214,7 @@ void vfio_pci_core_unregister_device(str
+ vfio_pci_vf_uninit(vdev);
+ vfio_pci_vga_uninit(vdev);
+
+- if (!disable_idle_d3)
++ if (!vdev->disable_idle_d3)
+ pm_runtime_get_noresume(&vdev->pdev->dev);
+
+ pm_runtime_forbid(&vdev->pdev->dev);
+@@ -2527,7 +2529,7 @@ static void vfio_pci_dev_set_try_reset(s
+ * state. Increment the usage count for all the devices in the dev_set
+ * before reset and decrement the same after reset.
+ */
+- if (!disable_idle_d3 && vfio_pci_dev_set_pm_runtime_get(dev_set))
++ if (vfio_pci_dev_set_pm_runtime_get(dev_set))
+ return;
+
+ if (!pci_reset_bus(pdev))
+@@ -2537,8 +2539,7 @@ static void vfio_pci_dev_set_try_reset(s
+ if (reset_done)
+ cur->needs_reset = false;
+
+- if (!disable_idle_d3)
+- pm_runtime_put(&cur->pdev->dev);
++ pm_runtime_put(&cur->pdev->dev);
+ }
+ }
+
+--- a/include/linux/vfio_pci_core.h
++++ b/include/linux/vfio_pci_core.h
+@@ -80,6 +80,7 @@ struct vfio_pci_core_device {
+ bool needs_pm_restore:1;
+ bool pm_intx_masked:1;
+ bool pm_runtime_engaged:1;
++ bool disable_idle_d3:1;
+ bool sriov_active;
+ struct pci_saved_state *pci_saved_state;
+ struct pci_saved_state *pm_save;
--- /dev/null
+From stable+bounces-274109-greg=kroah.com@vger.kernel.org Tue Jul 14 05:33:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 23:33:01 -0400
+Subject: vfio/pci: Use a private flag to prevent power state change with VFs
+To: stable@vger.kernel.org
+Cc: Raghavendra Rao Ananta <rananta@google.com>, Jason Gunthorpe <jgg@ziepe.ca>, Alex Williamson <alex@shazbot.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714033301.2397090-2-sashal@kernel.org>
+
+From: Raghavendra Rao Ananta <rananta@google.com>
+
+[ Upstream commit 40ef3edf151e184d021917a5c4c771cc0870844a ]
+
+The current implementation uses pci_num_vf() while holding the
+memory_lock to prevent changing the power state of a PF when
+VFs are enabled. This creates a lockdep circular dependency
+warning because memory_lock is held during device probing.
+
+[ 286.997167] ======================================================
+[ 287.003363] WARNING: possible circular locking dependency detected
+[ 287.009562] 7.0.0-dbg-DEV #3 Tainted: G S
+[ 287.015074] ------------------------------------------------------
+[ 287.021270] vfio_pci_sriov_/18636 is trying to acquire lock:
+[ 287.026942] ff45bea2294d4968 (&vdev->memory_lock){+.+.}-{4:4}, at:
+vfio_pci_core_runtime_resume+0x1f/0xa0
+[ 287.036530]
+[ 287.036530] but task is already holding lock:
+[ 287.042383] ff45bea3a96b8230 (&new_dev_set->lock){+.+.}-{4:4}, at:
+vfio_group_fops_unl_ioctl+0x44d/0x7b0
+[ 287.051879]
+[ 287.051879] which lock already depends on the new lock.
+[ 287.051879]
+[ 287.060070]
+[ 287.060070] the existing dependency chain (in reverse order) is:
+[ 287.067568]
+[ 287.067568] -> #2 (&new_dev_set->lock){+.+.}-{4:4}:
+[ 287.073941] __mutex_lock+0x92/0xb80
+[ 287.078058] vfio_assign_device_set+0x66/0x1b0
+[ 287.083042] vfio_pci_core_register_device+0xd1/0x2a0
+[ 287.088638] vfio_pci_probe+0xd2/0x100
+[ 287.092933] local_pci_probe_callback+0x4d/0xa0
+[ 287.098001] process_scheduled_works+0x2ca/0x680
+[ 287.103158] worker_thread+0x1e8/0x2f0
+[ 287.107452] kthread+0x10c/0x140
+[ 287.111230] ret_from_fork+0x18e/0x360
+[ 287.115519] ret_from_fork_asm+0x1a/0x30
+[ 287.119983]
+[ 287.119983] -> #1 ((work_completion)(&arg.work)){+.+.}-{0:0}:
+[ 287.127219] __flush_work+0x345/0x490
+[ 287.131429] pci_device_probe+0x2e3/0x490
+[ 287.135979] really_probe+0x1f9/0x4e0
+[ 287.140180] __driver_probe_device+0x77/0x100
+[ 287.145079] driver_probe_device+0x1e/0x110
+[ 287.149803] __device_attach_driver+0xe3/0x170
+[ 287.154789] bus_for_each_drv+0x125/0x150
+[ 287.159346] __device_attach+0xca/0x1a0
+[ 287.163720] device_initial_probe+0x34/0x50
+[ 287.168445] pci_bus_add_device+0x6e/0x90
+[ 287.172995] pci_iov_add_virtfn+0x3c9/0x3e0
+[ 287.177719] sriov_add_vfs+0x2c/0x60
+[ 287.181838] sriov_enable+0x306/0x4a0
+[ 287.186038] vfio_pci_core_sriov_configure+0x184/0x220
+[ 287.191715] sriov_numvfs_store+0xd9/0x1c0
+[ 287.196351] kernfs_fop_write_iter+0x13f/0x1d0
+[ 287.201338] vfs_write+0x2be/0x3b0
+[ 287.205286] ksys_write+0x73/0x100
+[ 287.209233] do_syscall_64+0x14d/0x750
+[ 287.213529] entry_SYSCALL_64_after_hwframe+0x77/0x7f
+[ 287.219120]
+[ 287.219120] -> #0 (&vdev->memory_lock){+.+.}-{4:4}:
+[ 287.225491] __lock_acquire+0x14c6/0x2800
+[ 287.230048] lock_acquire+0xd3/0x2f0
+[ 287.234168] down_write+0x3a/0xc0
+[ 287.238019] vfio_pci_core_runtime_resume+0x1f/0xa0
+[ 287.243436] __rpm_callback+0x8c/0x310
+[ 287.247730] rpm_resume+0x529/0x6f0
+[ 287.251765] __pm_runtime_resume+0x68/0x90
+[ 287.256402] vfio_pci_core_enable+0x44/0x310
+[ 287.261216] vfio_pci_open_device+0x1c/0x80
+[ 287.265947] vfio_df_open+0x10f/0x150
+[ 287.270148] vfio_group_fops_unl_ioctl+0x4a4/0x7b0
+[ 287.275476] __se_sys_ioctl+0x71/0xc0
+[ 287.279679] do_syscall_64+0x14d/0x750
+[ 287.283975] entry_SYSCALL_64_after_hwframe+0x77/0x7f
+[ 287.289559]
+[ 287.289559] other info that might help us debug this:
+[ 287.289559]
+[ 287.297582] Chain exists of:
+[ 287.297582] &vdev->memory_lock --> (work_completion)(&arg.work)
+--> &new_dev_set->lock
+[ 287.297582]
+[ 287.310023] Possible unsafe locking scenario:
+[ 287.310023]
+[ 287.315961] CPU0 CPU1
+[ 287.320510] ---- ----
+[ 287.325059] lock(&new_dev_set->lock);
+[ 287.328917]
+lock((work_completion)(&arg.work));
+[ 287.336153] lock(&new_dev_set->lock);
+[ 287.342523] lock(&vdev->memory_lock);
+[ 287.346382]
+[ 287.346382] *** DEADLOCK ***
+[ 287.346382]
+[ 287.352315] 2 locks held by vfio_pci_sriov_/18636:
+[ 287.357125] #0: ff45bea208ed3e18 (&group->group_lock){+.+.}-{4:4},
+at: vfio_group_fops_unl_ioctl+0x3e3/0x7b0
+[ 287.367048] #1: ff45bea3a96b8230 (&new_dev_set->lock){+.+.}-{4:4},
+at: vfio_group_fops_unl_ioctl+0x44d/0x7b0
+[ 287.376976]
+[ 287.376976] stack backtrace:
+[ 287.381353] CPU: 191 UID: 0 PID: 18636 Comm: vfio_pci_sriov_
+Tainted: G S 7.0.0-dbg-DEV #3 PREEMPTLAZY
+[ 287.381355] Tainted: [S]=CPU_OUT_OF_SPEC
+[ 287.381356] Call Trace:
+[ 287.381357] <TASK>
+[ 287.381358] dump_stack_lvl+0x54/0x70
+[ 287.381361] print_circular_bug+0x2e1/0x300
+[ 287.381363] check_noncircular+0xf9/0x120
+[ 287.381364] ? __lock_acquire+0x5b4/0x2800
+[ 287.381366] __lock_acquire+0x14c6/0x2800
+[ 287.381368] ? pci_mmcfg_read+0x4f/0x220
+[ 287.381370] ? pci_mmcfg_write+0x57/0x220
+[ 287.381371] ? lock_acquire+0xd3/0x2f0
+[ 287.381373] ? pci_mmcfg_write+0x57/0x220
+[ 287.381374] ? lock_release+0xef/0x360
+[ 287.381376] ? vfio_pci_core_runtime_resume+0x1f/0xa0
+[ 287.381377] lock_acquire+0xd3/0x2f0
+[ 287.381378] ? vfio_pci_core_runtime_resume+0x1f/0xa0
+[ 287.381379] ? lock_is_held_type+0x76/0x100
+[ 287.381382] down_write+0x3a/0xc0
+[ 287.381382] ? vfio_pci_core_runtime_resume+0x1f/0xa0
+[ 287.381383] vfio_pci_core_runtime_resume+0x1f/0xa0
+[ 287.381384] ? __pfx_pci_pm_runtime_resume+0x10/0x10
+[ 287.381385] __rpm_callback+0x8c/0x310
+[ 287.381386] ? ktime_get_mono_fast_ns+0x3d/0xb0
+[ 287.381389] ? __pfx_pci_pm_runtime_resume+0x10/0x10
+[ 287.381390] rpm_resume+0x529/0x6f0
+[ 287.381392] ? lock_is_held_type+0x76/0x100
+[ 287.381394] __pm_runtime_resume+0x68/0x90
+[ 287.381396] vfio_pci_core_enable+0x44/0x310
+[ 287.381398] vfio_pci_open_device+0x1c/0x80
+[ 287.381399] vfio_df_open+0x10f/0x150
+[ 287.381401] vfio_group_fops_unl_ioctl+0x4a4/0x7b0
+[ 287.381402] __se_sys_ioctl+0x71/0xc0
+[ 287.381404] do_syscall_64+0x14d/0x750
+[ 287.381405] ? entry_SYSCALL_64_after_hwframe+0x77/0x7f
+[ 287.381406] ? trace_irq_disable+0x25/0xd0
+[ 287.381409] entry_SYSCALL_64_after_hwframe+0x77/0x7f
+
+Introduce a private flag 'sriov_active' in the vfio_pci_core_device
+struct. This allows the driver to track the SR-IOV power state requirement
+without relying on pci_num_vf() while holding the memory_lock. The lock is
+now only held to set the flag and ensure the device is in D0, after which
+pci_enable_sriov() can be called without the lock.
+
+Fixes: f4162eb1e2fc ("vfio/pci: Change the PF power state to D0 before enabling VFs")
+Cc: stable@vger.kernel.org
+Suggested-by: Jason Gunthorpe <jgg@ziepe.ca>
+Suggested-by: Alex Williamson <alex@shazbot.org>
+Signed-off-by: Raghavendra Rao Ananta <rananta@google.com>
+Link: https://lore.kernel.org/r/20260514173449.3282188-1-rananta@google.com
+[promote bitfield to plain bool to avoid storage-unit races]
+Signed-off-by: Alex Williamson <alex@shazbot.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vfio/pci/vfio_pci_core.c | 17 ++++++++++++++---
+ include/linux/vfio_pci_core.h | 1 +
+ 2 files changed, 15 insertions(+), 3 deletions(-)
+
+--- a/drivers/vfio/pci/vfio_pci_core.c
++++ b/drivers/vfio/pci/vfio_pci_core.c
+@@ -237,8 +237,11 @@ int vfio_pci_set_power_state(struct vfio
+ int ret;
+
+ /* Prevent changing power state for PFs with VFs enabled */
+- if (pci_num_vf(pdev) && state > PCI_D0)
+- return -EBUSY;
++ if (state > PCI_D0) {
++ lockdep_assert_held_write(&vdev->memory_lock);
++ if (vdev->sriov_active)
++ return -EBUSY;
++ }
+
+ if (vdev->needs_pm_restore) {
+ if (pdev->current_state < PCI_D3hot && state >= PCI_D3hot) {
+@@ -2271,8 +2274,9 @@ int vfio_pci_core_sriov_configure(struct
+
+ down_write(&vdev->memory_lock);
+ vfio_pci_set_power_state(vdev, PCI_D0);
+- ret = pci_enable_sriov(pdev, nr_virtfn);
++ vdev->sriov_active = true;
+ up_write(&vdev->memory_lock);
++ ret = pci_enable_sriov(pdev, nr_virtfn);
+ if (ret) {
+ pm_runtime_put(&pdev->dev);
+ goto out_del;
+@@ -2286,6 +2290,13 @@ int vfio_pci_core_sriov_configure(struct
+ }
+
+ out_del:
++ /*
++ * Avoid taking the memory_lock intentionally. A race with a power
++ * state transition would at most result in an -EBUSY, leaving the
++ * device in PCI_D0.
++ */
++ vdev->sriov_active = false;
++
+ mutex_lock(&vfio_pci_sriov_pfs_mutex);
+ list_del_init(&vdev->sriov_pfs_item);
+ out_unlock:
+--- a/include/linux/vfio_pci_core.h
++++ b/include/linux/vfio_pci_core.h
+@@ -80,6 +80,7 @@ struct vfio_pci_core_device {
+ bool needs_pm_restore:1;
+ bool pm_intx_masked:1;
+ bool pm_runtime_engaged:1;
++ bool sriov_active;
+ struct pci_saved_state *pci_saved_state;
+ struct pci_saved_state *pm_save;
+ int ioeventfds_nr;
--- /dev/null
+From stable+bounces-274108-greg=kroah.com@vger.kernel.org Tue Jul 14 05:33:22 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 23:33:00 -0400
+Subject: vfio/pci: Use bitfield for struct vfio_pci_core_device flags
+To: stable@vger.kernel.org
+Cc: Reinette Chatre <reinette.chatre@intel.com>, Jason Gunthorpe <jgg@nvidia.com>, Kevin Tian <kevin.tian@intel.com>, Thomas Gleixner <tglx@linutronix.de>, Alex Williamson <alex.williamson@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714033301.2397090-1-sashal@kernel.org>
+
+From: Reinette Chatre <reinette.chatre@intel.com>
+
+[ Upstream commit 9cd0f6d5cbb6fda09aa83beb8146c287a552017e ]
+
+struct vfio_pci_core_device contains eleven boolean flags.
+Boolean flags clearly indicate their usage but space usage
+starts to be a concern when there are many.
+
+An upcoming change adds another boolean flag to
+struct vfio_pci_core_device, thereby increasing the concern
+that the boolean flags are consuming unnecessary space.
+
+Transition the boolean flags to use bitfields. On a system that
+uses one byte per boolean this reduces the space consumed
+by existing flags from 11 bytes to 2 bytes with room for
+a few more flags without increasing the structure's size.
+
+Suggested-by: Jason Gunthorpe <jgg@nvidia.com>
+Signed-off-by: Reinette Chatre <reinette.chatre@intel.com>
+Reviewed-by: Kevin Tian <kevin.tian@intel.com>
+Acked-by: Thomas Gleixner <tglx@linutronix.de>
+Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
+Link: https://lore.kernel.org/r/cf34bf0499c889554a8105eeb18cc0ab673005be.1683740667.git.reinette.chatre@intel.com
+Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
+Stable-dep-of: 40ef3edf151e ("vfio/pci: Use a private flag to prevent power state change with VFs")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/vfio_pci_core.h | 22 +++++++++++-----------
+ 1 file changed, 11 insertions(+), 11 deletions(-)
+
+--- a/include/linux/vfio_pci_core.h
++++ b/include/linux/vfio_pci_core.h
+@@ -69,17 +69,17 @@ struct vfio_pci_core_device {
+ u16 msix_size;
+ u32 msix_offset;
+ u32 rbar[7];
+- bool pci_2_3;
+- bool virq_disabled;
+- bool reset_works;
+- bool extended_caps;
+- bool bardirty;
+- bool has_vga;
+- bool needs_reset;
+- bool nointx;
+- bool needs_pm_restore;
+- bool pm_intx_masked;
+- bool pm_runtime_engaged;
++ bool pci_2_3:1;
++ bool virq_disabled:1;
++ bool reset_works:1;
++ bool extended_caps:1;
++ bool bardirty:1;
++ bool has_vga:1;
++ bool needs_reset:1;
++ bool nointx:1;
++ bool needs_pm_restore:1;
++ bool pm_intx_masked:1;
++ bool pm_runtime_engaged:1;
+ struct pci_saved_state *pci_saved_state;
+ struct pci_saved_state *pm_save;
+ int ioeventfds_nr;
--- /dev/null
+From stable+bounces-276803-greg=kroah.com@vger.kernel.org Thu Jul 16 20:39:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 14:21:57 -0400
+Subject: writeback: Avoid contention on wb->list_lock when switching inodes
+To: stable@vger.kernel.org
+Cc: Jan Kara <jack@suse.cz>, Tejun Heo <tj@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716182158.920140-1-sashal@kernel.org>
+
+From: Jan Kara <jack@suse.cz>
+
+[ Upstream commit e1b849cfa6b61f1c866a908c9e8dd9b5aaab820b ]
+
+There can be multiple inode switch works that are trying to switch
+inodes to / from the same wb. This can happen in particular if some
+cgroup exits which owns many (thousands) inodes and we need to switch
+them all. In this case several inode_switch_wbs_work_fn() instances will
+be just spinning on the same wb->list_lock while only one of them makes
+forward progress. This wastes CPU cycles and quickly leads to softlockup
+reports and unusable system.
+
+Instead of running several inode_switch_wbs_work_fn() instances in
+parallel switching to the same wb and contending on wb->list_lock, run
+just one work item per wb and manage a queue of isw items switching to
+this wb.
+
+Acked-by: Tejun Heo <tj@kernel.org>
+Signed-off-by: Jan Kara <jack@suse.cz>
+Stable-dep-of: cba38ec4cbd3 ("writeback: fix race between cgroup_writeback_umount() and inode_switch_wbs()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fs-writeback.c | 99 ++++++++++++++++++++++++---------------
+ include/linux/backing-dev-defs.h | 4 +
+ include/linux/writeback.h | 2
+ mm/backing-dev.c | 5 +
+ 4 files changed, 74 insertions(+), 36 deletions(-)
+
+--- a/fs/fs-writeback.c
++++ b/fs/fs-writeback.c
+@@ -343,7 +343,8 @@ static struct bdi_writeback *inode_to_wb
+ }
+
+ struct inode_switch_wbs_context {
+- struct rcu_work work;
++ /* List of queued switching contexts for the wb */
++ struct llist_node list;
+
+ /*
+ * Multiple inodes can be switched at once. The switching procedure
+@@ -353,7 +354,6 @@ struct inode_switch_wbs_context {
+ * array embedded into struct inode_switch_wbs_context. Otherwise
+ * an inode could be left in a non-consistent state.
+ */
+- struct bdi_writeback *new_wb;
+ struct inode *inodes[];
+ };
+
+@@ -462,13 +462,11 @@ skip_switch:
+ return switched;
+ }
+
+-static void inode_switch_wbs_work_fn(struct work_struct *work)
++static void process_inode_switch_wbs(struct bdi_writeback *new_wb,
++ struct inode_switch_wbs_context *isw)
+ {
+- struct inode_switch_wbs_context *isw =
+- container_of(to_rcu_work(work), struct inode_switch_wbs_context, work);
+ struct backing_dev_info *bdi = inode_to_bdi(isw->inodes[0]);
+ struct bdi_writeback *old_wb = isw->inodes[0]->i_wb;
+- struct bdi_writeback *new_wb = isw->new_wb;
+ unsigned long nr_switched = 0;
+ struct inode **inodep;
+
+@@ -528,6 +526,38 @@ relock:
+ atomic_dec(&isw_nr_in_flight);
+ }
+
++void inode_switch_wbs_work_fn(struct work_struct *work)
++{
++ struct bdi_writeback *new_wb = container_of(work, struct bdi_writeback,
++ switch_work);
++ struct inode_switch_wbs_context *isw, *next_isw;
++ struct llist_node *list;
++
++ /*
++ * Grab out reference to wb so that it cannot get freed under us
++ * after we process all the isw items.
++ */
++ wb_get(new_wb);
++ while (1) {
++ list = llist_del_all(&new_wb->switch_wbs_ctxs);
++ /* Nothing to do? */
++ if (!list)
++ break;
++ /*
++ * In addition to synchronizing among switchers, I_WB_SWITCH
++ * tells the RCU protected stat update paths to grab the i_page
++ * lock so that stat transfer can synchronize against them.
++ * Let's continue after I_WB_SWITCH is guaranteed to be
++ * visible.
++ */
++ synchronize_rcu();
++
++ llist_for_each_entry_safe(isw, next_isw, list, list)
++ process_inode_switch_wbs(new_wb, isw);
++ }
++ wb_put(new_wb);
++}
++
+ static bool inode_prepare_wbs_switch(struct inode *inode,
+ struct bdi_writeback *new_wb)
+ {
+@@ -557,6 +587,13 @@ static bool inode_prepare_wbs_switch(str
+ return true;
+ }
+
++static void wb_queue_isw(struct bdi_writeback *wb,
++ struct inode_switch_wbs_context *isw)
++{
++ if (llist_add(&isw->list, &wb->switch_wbs_ctxs))
++ queue_work(isw_wq, &wb->switch_work);
++}
++
+ /**
+ * inode_switch_wbs - change the wb association of an inode
+ * @inode: target inode
+@@ -570,6 +607,7 @@ static void inode_switch_wbs(struct inod
+ struct backing_dev_info *bdi = inode_to_bdi(inode);
+ struct cgroup_subsys_state *memcg_css;
+ struct inode_switch_wbs_context *isw;
++ struct bdi_writeback *new_wb = NULL;
+
+ /* noop if seems to be already in progress */
+ if (inode->i_state & I_WB_SWITCH)
+@@ -594,40 +632,34 @@ static void inode_switch_wbs(struct inod
+ if (!memcg_css)
+ goto out_free;
+
+- isw->new_wb = wb_get_create(bdi, memcg_css, GFP_ATOMIC);
++ new_wb = wb_get_create(bdi, memcg_css, GFP_ATOMIC);
+ css_put(memcg_css);
+- if (!isw->new_wb)
++ if (!new_wb)
+ goto out_free;
+
+- if (!inode_prepare_wbs_switch(inode, isw->new_wb))
++ if (!inode_prepare_wbs_switch(inode, new_wb))
+ goto out_free;
+
+ isw->inodes[0] = inode;
+
+- /*
+- * In addition to synchronizing among switchers, I_WB_SWITCH tells
+- * the RCU protected stat update paths to grab the i_page
+- * lock so that stat transfer can synchronize against them.
+- * Let's continue after I_WB_SWITCH is guaranteed to be visible.
+- */
+- INIT_RCU_WORK(&isw->work, inode_switch_wbs_work_fn);
+- queue_rcu_work(isw_wq, &isw->work);
++ wb_queue_isw(new_wb, isw);
+ return;
+
+ out_free:
+ atomic_dec(&isw_nr_in_flight);
+- if (isw->new_wb)
+- wb_put(isw->new_wb);
++ if (new_wb)
++ wb_put(new_wb);
+ kfree(isw);
+ }
+
+-static bool isw_prepare_wbs_switch(struct inode_switch_wbs_context *isw,
++static bool isw_prepare_wbs_switch(struct bdi_writeback *new_wb,
++ struct inode_switch_wbs_context *isw,
+ struct list_head *list, int *nr)
+ {
+ struct inode *inode;
+
+ list_for_each_entry(inode, list, i_io_list) {
+- if (!inode_prepare_wbs_switch(inode, isw->new_wb))
++ if (!inode_prepare_wbs_switch(inode, new_wb))
+ continue;
+
+ isw->inodes[*nr] = inode;
+@@ -651,6 +683,7 @@ bool cleanup_offline_cgwb(struct bdi_wri
+ {
+ struct cgroup_subsys_state *memcg_css;
+ struct inode_switch_wbs_context *isw;
++ struct bdi_writeback *new_wb;
+ int nr;
+ bool restart = false;
+
+@@ -663,12 +696,12 @@ bool cleanup_offline_cgwb(struct bdi_wri
+
+ for (memcg_css = wb->memcg_css->parent; memcg_css;
+ memcg_css = memcg_css->parent) {
+- isw->new_wb = wb_get_create(wb->bdi, memcg_css, GFP_KERNEL);
+- if (isw->new_wb)
++ new_wb = wb_get_create(wb->bdi, memcg_css, GFP_KERNEL);
++ if (new_wb)
+ break;
+ }
+- if (unlikely(!isw->new_wb))
+- isw->new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */
++ if (unlikely(!new_wb))
++ new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */
+
+ nr = 0;
+ spin_lock(&wb->list_lock);
+@@ -680,27 +713,21 @@ bool cleanup_offline_cgwb(struct bdi_wri
+ * bandwidth restrictions, as writeback of inode metadata is not
+ * accounted for.
+ */
+- restart = isw_prepare_wbs_switch(isw, &wb->b_attached, &nr);
++ restart = isw_prepare_wbs_switch(new_wb, isw, &wb->b_attached, &nr);
+ if (!restart)
+- restart = isw_prepare_wbs_switch(isw, &wb->b_dirty_time, &nr);
++ restart = isw_prepare_wbs_switch(new_wb, isw, &wb->b_dirty_time,
++ &nr);
+ spin_unlock(&wb->list_lock);
+
+ /* no attached inodes? bail out */
+ if (nr == 0) {
+ atomic_dec(&isw_nr_in_flight);
+- wb_put(isw->new_wb);
++ wb_put(new_wb);
+ kfree(isw);
+ return restart;
+ }
+
+- /*
+- * In addition to synchronizing among switchers, I_WB_SWITCH tells
+- * the RCU protected stat update paths to grab the i_page
+- * lock so that stat transfer can synchronize against them.
+- * Let's continue after I_WB_SWITCH is guaranteed to be visible.
+- */
+- INIT_RCU_WORK(&isw->work, inode_switch_wbs_work_fn);
+- queue_rcu_work(isw_wq, &isw->work);
++ wb_queue_isw(new_wb, isw);
+
+ return restart;
+ }
+--- a/include/linux/backing-dev-defs.h
++++ b/include/linux/backing-dev-defs.h
+@@ -154,6 +154,10 @@ struct bdi_writeback {
+ struct list_head blkcg_node; /* anchored at blkcg->cgwb_list */
+ struct list_head b_attached; /* attached inodes, protected by list_lock */
+ struct list_head offline_node; /* anchored at offline_cgwbs */
++ struct work_struct switch_work; /* work used to perform inode switching
++ * to this wb */
++ struct llist_head switch_wbs_ctxs; /* queued contexts for
++ * writeback switching */
+
+ union {
+ struct work_struct release_work;
+--- a/include/linux/writeback.h
++++ b/include/linux/writeback.h
+@@ -288,6 +288,8 @@ static inline void wbc_init_bio(struct w
+ bio_associate_blkg_from_css(bio, wbc->wb->blkcg_css);
+ }
+
++void inode_switch_wbs_work_fn(struct work_struct *work);
++
+ #else /* CONFIG_CGROUP_WRITEBACK */
+
+ static inline void inode_attach_wb(struct inode *inode, struct page *page)
+--- a/mm/backing-dev.c
++++ b/mm/backing-dev.c
+@@ -415,6 +415,7 @@ static void cgwb_release_workfn(struct w
+ wb_exit(wb);
+ bdi_put(bdi);
+ WARN_ON_ONCE(!list_empty(&wb->b_attached));
++ WARN_ON_ONCE(work_pending(&wb->switch_work));
+ call_rcu(&wb->rcu, cgwb_free_rcu);
+ }
+
+@@ -491,6 +492,8 @@ static int cgwb_create(struct backing_de
+ wb->memcg_css = memcg_css;
+ wb->blkcg_css = blkcg_css;
+ INIT_LIST_HEAD(&wb->b_attached);
++ INIT_WORK(&wb->switch_work, inode_switch_wbs_work_fn);
++ init_llist_head(&wb->switch_wbs_ctxs);
+ INIT_WORK(&wb->release_work, cgwb_release_workfn);
+ set_bit(WB_registered, &wb->state);
+ bdi_get(bdi);
+@@ -624,6 +627,8 @@ static int cgwb_bdi_init(struct backing_
+ if (!ret) {
+ bdi->wb.memcg_css = &root_mem_cgroup->css;
+ bdi->wb.blkcg_css = blkcg_root_css;
++ INIT_WORK(&bdi->wb.switch_work, inode_switch_wbs_work_fn);
++ init_llist_head(&bdi->wb.switch_wbs_ctxs);
+ }
+ return ret;
+ }
--- /dev/null
+From stable+bounces-276804-greg=kroah.com@vger.kernel.org Thu Jul 16 20:28:53 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 14:21:58 -0400
+Subject: writeback: fix race between cgroup_writeback_umount() and inode_switch_wbs()
+To: stable@vger.kernel.org
+Cc: Baokun Li <libaokun@linux.alibaba.com>, Jan Kara <jack@suse.cz>, Tejun Heo <tj@kernel.org>, "Christian Brauner (Amutable)" <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716182158.920140-2-sashal@kernel.org>
+
+From: Baokun Li <libaokun@linux.alibaba.com>
+
+[ Upstream commit cba38ec4cbd3a7b8b942a8d52531a05be8a9ff0d ]
+
+When a container exits, the following BUG_ON() is occasionally triggered:
+
+==================================================================
+ VFS: Busy inodes after unmount of sdb (ext4)
+ ------------[ cut here ]------------
+ kernel BUG at fs/super.c:695!
+ CPU: 3 PID: 6 Comm: containerd-shim Tainted: G OE K 6.6 #1
+ pstate: 63400009 (nZCv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--)
+ pc : generic_shutdown_super+0xf0/0x100
+ lr : generic_shutdown_super+0xf0/0x100
+ Call trace:
+ generic_shutdown_super+0xf0/0x100
+ kill_block_super+0x20/0x48
+ ext4_kill_sb+0x28/0x60
+ deactivate_locked_super+0x54/0x130
+ deactivate_super+0x84/0xa0
+ cleanup_mnt+0xa4/0x140
+ __cleanup_mnt+0x18/0x28
+ task_work_run+0x78/0xe0
+ do_notify_resume+0x204/0x240
+==================================================================
+
+The root cause is a race between cgroup_writeback_umount() and
+inode_switch_wbs()/cleanup_offline_cgwb(). There is a window between
+inode_prepare_wbs_switch() returning true and the subsequent
+wb_queue_isw() call. Following is the process that triggers the issue:
+
+ CPU A (umount) | CPU B (writeback)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ inode_switch_wbs/cleanup_offline_cgwb
+ atomic_inc(&isw_nr_in_flight)
+ inode_prepare_wbs_switch
+ -> passes SB_ACTIVE check
+ __iget(inode)
+ generic_shutdown_super
+ sb->s_flags &= ~SB_ACTIVE
+ cgroup_writeback_umount(sb)
+ smp_mb()
+ atomic_read(&isw_nr_in_flight)
+ rcu_barrier()
+ -> no pending RCU callbacks
+ flush_workqueue(isw_wq)
+ -> nothing queued, returns
+ evict_inodes(sb)
+ -> Inode skipped as isw still holds a ref.
+ sop->put_super(sb)
+ /* destroys percpu counters */
+ -> VFS: Busy inodes after unmount!
+ wb_queue_isw()
+ queue_work(isw_wq, ...)
+ /* later in work function */
+ inode_switch_wbs_work_fn
+ process_inode_switch_wbs
+ iput() -> evict
+ percpu_counter_dec() // UAF!
+
+Fix this by extending the RCU read-side critical section in
+inode_switch_wbs() and cleanup_offline_cgwb() to cover from
+inode_prepare_wbs_switch() through wb_queue_isw(). Since there is
+no sleep in this window, rcu_read_lock() can be used. Then add a
+synchronize_rcu() in cgroup_writeback_umount() before the existing
+rcu_barrier(), so that all in-flight switchers that have passed the
+SB_ACTIVE check have completed queue_work() before flush_workqueue()
+is called.
+
+The existing rcu_barrier() is intentionally retained so this fix can
+be backported unchanged to stable kernels (5.10.y, 6.6.y, ...) that
+still queue switches via queue_rcu_work(). It is a no-op on current
+mainline (since commit e1b849cfa6b6 ("writeback: Avoid contention on
+wb->list_lock when switching inodes")) and is removed in a follow-up
+patch.
+
+Fixes: a1a0e23e4903 ("writeback: flush inode cgroup wb switches instead of pinning super_block")
+Cc: stable@vger.kernel.org
+Suggested-by: Jan Kara <jack@suse.cz>
+Link: https://lore.kernel.org/all/mxnjq2l6guusfchvauxr3v7c4bwjasybxlleqbbh4efloeqspz@iqylk76ohufz
+Reviewed-by: Jan Kara <jack@suse.cz>
+Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
+Link: https://patch.msgid.link/20260521095016.2791354-2-libaokun@linux.alibaba.com
+Acked-by: Tejun Heo <tj@kernel.org>
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fs-writeback.c | 31 +++++++++++++++++++++++++++++--
+ 1 file changed, 29 insertions(+), 2 deletions(-)
+
+--- a/fs/fs-writeback.c
++++ b/fs/fs-writeback.c
+@@ -623,12 +623,19 @@ static void inode_switch_wbs(struct inod
+
+ atomic_inc(&isw_nr_in_flight);
+
+- /* find and pin the new wb */
++ /*
++ * Paired with synchronize_rcu() in cgroup_writeback_umount():
++ * holding rcu_read_lock across inode_prepare_wbs_switch()
++ * (covering the SB_ACTIVE check and the inode grab) and
++ * wb_queue_isw() ensures synchronize_rcu() cannot return until
++ * the work is queued, so the subsequent flush_workqueue() will
++ * wait for the switch.
++ */
+ rcu_read_lock();
++ /* find and pin the new wb */
+ memcg_css = css_from_id(new_wb_id, &memory_cgrp_subsys);
+ if (memcg_css && !css_tryget(memcg_css))
+ memcg_css = NULL;
+- rcu_read_unlock();
+ if (!memcg_css)
+ goto out_free;
+
+@@ -643,9 +650,11 @@ static void inode_switch_wbs(struct inod
+ isw->inodes[0] = inode;
+
+ wb_queue_isw(new_wb, isw);
++ rcu_read_unlock();
+ return;
+
+ out_free:
++ rcu_read_unlock();
+ atomic_dec(&isw_nr_in_flight);
+ if (new_wb)
+ wb_put(new_wb);
+@@ -704,6 +713,14 @@ bool cleanup_offline_cgwb(struct bdi_wri
+ new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */
+
+ nr = 0;
++ /*
++ * Paired with synchronize_rcu() in cgroup_writeback_umount().
++ * Holding rcu_read_lock across the SB_ACTIVE check, the inode grab
++ * and wb_queue_isw() ensures synchronize_rcu() cannot return until
++ * the work is queued, so the subsequent flush_workqueue() will wait
++ * for the switch.
++ */
++ rcu_read_lock();
+ spin_lock(&wb->list_lock);
+ /*
+ * In addition to the inodes that have completed writeback, also switch
+@@ -721,6 +738,7 @@ bool cleanup_offline_cgwb(struct bdi_wri
+
+ /* no attached inodes? bail out */
+ if (nr == 0) {
++ rcu_read_unlock();
+ atomic_dec(&isw_nr_in_flight);
+ wb_put(new_wb);
+ kfree(isw);
+@@ -728,6 +746,7 @@ bool cleanup_offline_cgwb(struct bdi_wri
+ }
+
+ wb_queue_isw(new_wb, isw);
++ rcu_read_unlock();
+
+ return restart;
+ }
+@@ -1159,6 +1178,14 @@ void cgroup_writeback_umount(void)
+
+ if (atomic_read(&isw_nr_in_flight)) {
+ /*
++ * Paired with rcu_read_lock() in inode_switch_wbs() and
++ * cleanup_offline_cgwb(). synchronize_rcu() waits for any
++ * in-flight switcher that already passed the SB_ACTIVE check
++ * to finish queueing its work, so flush_workqueue() below
++ * will then drain it.
++ */
++ synchronize_rcu();
++ /*
+ * Use rcu_barrier() to wait for all pending callbacks to
+ * ensure that all in-flight wb switches are in the workqueue.
+ */