--- /dev/null
+From stable+bounces-273278-greg=kroah.com@vger.kernel.org Fri Jul 10 18:14:51 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Jul 2026 12:14:39 -0400
+Subject: ACPI: bus: Introduce devm_acpi_install_notify_handler()
+To: stable@vger.kernel.org
+Cc: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260710161442.293558-2-sashal@kernel.org>
+
+From: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
+
+[ Upstream commit ca70ce555a1eb8edf5fb1d5575f1fe19c9ae17f4 ]
+
+Introduce devm_acpi_install_notify_handler() for installing an ACPI
+notify handler managed by devres that will be removed automatically on
+driver detach.
+
+It installs the notify handler on the device object in the ACPI
+namespace that corresponds to the owner device's ACPI companion, if
+present (an error is returned if the owner device doesn't have an ACPI
+companion).
+
+Currently, there is no way to manually remove the notify handler
+installed by it because none of its users brought on subsequently
+will need to do that.
+
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+[ rjw: Kerneldoc comment refinement ]
+Link: https://patch.msgid.link/2268031.irdbgypaU6@rafael.j.wysocki
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/acpi/bus.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++
+ include/acpi/acpi_bus.h | 2 +
+ 2 files changed, 68 insertions(+)
+
+--- a/drivers/acpi/bus.c
++++ b/drivers/acpi/bus.c
+@@ -622,6 +622,72 @@ void acpi_dev_remove_notify_handler(stru
+ }
+ EXPORT_SYMBOL_GPL(acpi_dev_remove_notify_handler);
+
++struct acpi_notify_handler_devres {
++ acpi_notify_handler handler;
++ u32 handler_type;
++};
++
++static void devm_acpi_notify_handler_release(struct device *dev, void *res)
++{
++ struct acpi_notify_handler_devres *dr = res;
++
++ acpi_dev_remove_notify_handler(ACPI_COMPANION(dev), dr->handler_type,
++ dr->handler);
++}
++
++/**
++ * devm_acpi_install_notify_handler - Install an ACPI notify handler for a
++ * managed device
++ * @dev: Device to install a notify handler for
++ * @handler_type: Type of the notify handler
++ * @handler: Handler function to install
++ * @context: Data passed back to the handler function
++ *
++ * This function performs the same function as acpi_dev_install_notify_handler()
++ * called for the ACPI companion of @dev with the same @handler_type, @handler,
++ * and @context arguments, but the ACPI notify handler installed by it will be
++ * automatically removed on driver detach.
++ *
++ * Callers should ensure that all resources used by @handler have been allocated
++ * prior to invoking this function, in which case those resources should be
++ * devres-managed so that they won't be released before the notify handler
++ * removal. Otherwise, special synchronization between @handler and the
++ * management of those resources is required.
++ *
++ * When the request fails, an error message is printed. Don't add extra error
++ * messages at the call sites.
++ *
++ * Return: 0 on success or a negative error number.
++ */
++int devm_acpi_install_notify_handler(struct device *dev, u32 handler_type,
++ acpi_notify_handler handler, void *context)
++{
++ struct acpi_notify_handler_devres *dr;
++ struct acpi_device *adev;
++ int ret;
++
++ adev = ACPI_COMPANION(dev);
++ if (!adev)
++ return dev_err_probe(dev, -ENODEV, "No ACPI companion in %s()\n", __func__);
++
++ dr = devres_alloc(devm_acpi_notify_handler_release, sizeof(*dr), GFP_KERNEL);
++ if (!dr)
++ return -ENOMEM;
++
++ ret = acpi_dev_install_notify_handler(adev, handler_type, handler, context);
++ if (ret) {
++ devres_free(dr);
++ return dev_err_probe(dev, ret, "Failed to install an ACPI notify handler\n");
++ }
++
++ dr->handler = handler;
++ dr->handler_type = handler_type;
++ devres_add(dev, dr);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(devm_acpi_install_notify_handler);
++
+ /* Handle events targeting \_SB device (at present only graceful shutdown) */
+
+ #define ACPI_SB_NOTIFY_SHUTDOWN_REQUEST 0x81
+--- a/include/acpi/acpi_bus.h
++++ b/include/acpi/acpi_bus.h
+@@ -625,6 +625,8 @@ int acpi_dev_install_notify_handler(stru
+ void acpi_dev_remove_notify_handler(struct acpi_device *adev,
+ u32 handler_type,
+ acpi_notify_handler handler);
++int devm_acpi_install_notify_handler(struct device *dev, u32 handler_type,
++ acpi_notify_handler handler, void *context);
+ extern int acpi_notifier_call_chain(struct acpi_device *, u32, u32);
+ extern int register_acpi_notifier(struct notifier_block *);
+ extern int unregister_acpi_notifier(struct notifier_block *);
--- /dev/null
+From stable+bounces-273277-greg=kroah.com@vger.kernel.org Fri Jul 10 18:18:09 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Jul 2026 12:14:38 -0400
+Subject: ACPI: driver: Check ACPI_COMPANION() against NULL during probe
+To: stable@vger.kernel.org
+Cc: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>, Hans de Goede <johannes.goede@oss.qualcomm.com>, Andy Shevchenko <andriy.shevchenko@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260710161442.293558-1-sashal@kernel.org>
+
+From: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
+
+[ Upstream commit e4865a56d013e86e46ea6acea15bb6eae01898ff ]
+
+Since every platform driver can be forced to match a device that doesn't
+match its list of device IDs because of device_match_driver_override(),
+platform drivers that rely on the existence of a device's ACPI companion
+object should verify its presence.
+
+Accordingly, add requisite ACPI_COMPANION() or ACPI_HANDLE() checks
+against NULL to 13 platform drivers handling core ACPI devices.
+
+Also change the value returned by the ACPI thermal zone driver when
+the device's ACPI companion is not present to -ENODEV for consistency
+with the other drivers.
+
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+Reviewed-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Link: https://patch.msgid.link/4516068.ejJDZkT8p0@rafael.j.wysocki
+Cc: 7.0+ <stable@vger.kernel.org> # 7.0+
+Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/acpi/ac.c | 6 +++++-
+ drivers/acpi/acpi_pad.c | 6 +++++-
+ drivers/acpi/acpi_tad.c | 6 +++++-
+ drivers/acpi/pfr_telemetry.c | 6 +++++-
+ drivers/acpi/pfr_update.c | 6 +++++-
+ drivers/acpi/thermal.c | 2 +-
+ 6 files changed, 26 insertions(+), 6 deletions(-)
+
+--- a/drivers/acpi/ac.c
++++ b/drivers/acpi/ac.c
+@@ -203,11 +203,15 @@ static const struct dmi_system_id ac_dmi
+
+ static int acpi_ac_probe(struct platform_device *pdev)
+ {
+- struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
+ struct power_supply_config psy_cfg = {};
++ struct acpi_device *adev;
+ struct acpi_ac *ac;
+ int result;
+
++ adev = ACPI_COMPANION(&pdev->dev);
++ if (!adev)
++ return -ENODEV;
++
+ ac = kzalloc(sizeof(struct acpi_ac), GFP_KERNEL);
+ if (!ac)
+ return -ENOMEM;
+--- a/drivers/acpi/acpi_pad.c
++++ b/drivers/acpi/acpi_pad.c
+@@ -427,9 +427,13 @@ static void acpi_pad_notify(acpi_handle
+
+ static int acpi_pad_probe(struct platform_device *pdev)
+ {
+- struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
++ struct acpi_device *adev;
+ acpi_status status;
+
++ adev = ACPI_COMPANION(&pdev->dev);
++ if (!adev)
++ return -ENODEV;
++
+ strscpy(acpi_device_name(adev), ACPI_PROCESSOR_AGGREGATOR_DEVICE_NAME);
+ strscpy(acpi_device_class(adev), ACPI_PROCESSOR_AGGREGATOR_CLASS);
+
+--- a/drivers/acpi/acpi_tad.c
++++ b/drivers/acpi/acpi_tad.c
+@@ -588,12 +588,16 @@ static void acpi_tad_remove(struct platf
+ static int acpi_tad_probe(struct platform_device *pdev)
+ {
+ struct device *dev = &pdev->dev;
+- acpi_handle handle = ACPI_HANDLE(dev);
+ struct acpi_tad_driver_data *dd;
++ acpi_handle handle;
+ acpi_status status;
+ unsigned long long caps;
+ int ret;
+
++ handle = ACPI_HANDLE(dev);
++ if (!handle)
++ return -ENODEV;
++
+ ret = acpi_install_cmos_rtc_space_handler(handle);
+ if (ret < 0) {
+ dev_info(dev, "Unable to install space handler\n");
+--- a/drivers/acpi/pfr_telemetry.c
++++ b/drivers/acpi/pfr_telemetry.c
+@@ -363,10 +363,14 @@ static void pfrt_log_put_idx(void *data)
+
+ static int acpi_pfrt_log_probe(struct platform_device *pdev)
+ {
+- acpi_handle handle = ACPI_HANDLE(&pdev->dev);
+ struct pfrt_log_device *pfrt_log_dev;
++ acpi_handle handle;
+ int ret;
+
++ handle = ACPI_HANDLE(&pdev->dev);
++ if (!handle)
++ return -ENODEV;
++
+ if (!acpi_has_method(handle, "_DSM")) {
+ dev_dbg(&pdev->dev, "Missing _DSM\n");
+ return -ENODEV;
+--- a/drivers/acpi/pfr_update.c
++++ b/drivers/acpi/pfr_update.c
+@@ -505,10 +505,14 @@ static void pfru_put_idx(void *data)
+
+ static int acpi_pfru_probe(struct platform_device *pdev)
+ {
+- acpi_handle handle = ACPI_HANDLE(&pdev->dev);
+ struct pfru_device *pfru_dev;
++ acpi_handle handle;
+ int ret;
+
++ handle = ACPI_HANDLE(&pdev->dev);
++ if (!handle)
++ return -ENODEV;
++
+ if (!acpi_has_method(handle, "_DSM")) {
+ dev_dbg(&pdev->dev, "Missing _DSM\n");
+ return -ENODEV;
+--- a/drivers/acpi/thermal.c
++++ b/drivers/acpi/thermal.c
+@@ -789,7 +789,7 @@ static int acpi_thermal_add(struct acpi_
+ int i;
+
+ if (!device)
+- return -EINVAL;
++ return -ENODEV;
+
+ tz = kzalloc(sizeof(struct acpi_thermal), GFP_KERNEL);
+ if (!tz)
--- /dev/null
+From stable+bounces-273239-greg=kroah.com@vger.kernel.org Fri Jul 10 15:35:09 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Jul 2026 09:34:39 -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: <20260710133439.82432-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 | 18 +++++++++++++++---
+ 1 file changed, 15 insertions(+), 3 deletions(-)
+
+--- a/drivers/acpi/nfit/core.c
++++ b/drivers/acpi/nfit/core.c
+@@ -3061,6 +3061,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)
+@@ -3301,7 +3303,10 @@ static void acpi_nfit_remove_notify_hand
+ 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
+@@ -3316,6 +3321,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
+@@ -3393,9 +3399,14 @@ static int acpi_nfit_add(struct acpi_dev
+ sz - sizeof(struct acpi_table_nfit));
+
+ if (rc)
+- return rc;
++ acpi_nfit_shutdown(acpi_desc);
+
+- return devm_add_action_or_reset(dev, acpi_nfit_shutdown, acpi_desc);
++ return rc;
++}
++
++static void acpi_nfit_remove(struct acpi_device *adev)
++{
++ acpi_nfit_shutdown(dev_get_drvdata(&adev->dev));
+ }
+
+ static void acpi_nfit_update_notify(struct device *dev, acpi_handle handle)
+@@ -3482,6 +3493,7 @@ static struct acpi_driver acpi_nfit_driv
+ .ids = acpi_nfit_ids,
+ .ops = {
+ .add = acpi_nfit_add,
++ .remove = acpi_nfit_remove,
+ },
+ };
+
--- /dev/null
+From stable+bounces-273281-greg=kroah.com@vger.kernel.org Fri Jul 10 18:18:36 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Jul 2026 12:14:42 -0400
+Subject: ACPI: NFIT: core: Fix possible deadlock and missing notifications
+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: <20260710161442.293558-5-sashal@kernel.org>
+
+From: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
+
+[ Upstream commit 18a00ed0e718473f5c3fcfa49df46c944575e60c ]
+
+After commit 9b311b7313d6 ("ACPI: NFIT: Install Notify() handler before
+getting NFIT table"), ACPI NFIT driver removal may deadlock if an ACPI
+notify on the NFIT device is triggered concurrently. A similar deadlock
+may occur if an ACPI notify on the NFIT device is triggered during a
+failing driver probe.
+
+The deadlock is possible because acpi_dev_remove_notify_handler() calls
+acpi_os_wait_events_complete() after removing the notify handler and the
+driver core invokes it under the NFIT platform device lock which is also
+acquired by acpi_nfit_notify(). Thus acpi_os_wait_events_complete() may
+be waiting for acpi_nfit_notify() to complete, but the latter may not be
+able to acquire the device lock which is being held by the driver core
+while the former is being executed.
+
+Moreover, after commit 03667e146f81 ("ACPI: NFIT: core: Convert the
+driver to a platform one"), there are no sysfs notifications regarding
+NVDIMM devices because __acpi_nvdimm_notify() always bails out after
+checking the driver data pointer of the device's parent. That parent
+is the ACPI companion of the platform device used for driver binding,
+so its driver data pointer is always NULL after the commit in question
+which was overlooked by it.
+
+A remedy for the deadlock is to use a special separate lock for ACPI
+notify synchronization with driver probe and removal instead of the
+device lock of the NFIT device, while a remedy for the second issue
+is to populate the driver data pointer of the NFIT device's ACPI
+companion when the driver is ready to operate, so do both these things.
+However, since the new lock is not held across the entire teardown and
+acpi_nfit_notify() should do nothing when teardown is in progress, make
+it check the driver data pointer of the NFIT device's ACPI companion, in
+analogy with the existing check in __acpi_nvdimm_notify(), and bail out
+if that pointer is NULL.
+
+Fixes: 9b311b7313d6 ("ACPI: NFIT: Install Notify() handler before getting NFIT table")
+Fixes: 03667e146f81 ("ACPI: NFIT: core: Convert the driver to a platform one")
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+Cc: All applicable <stable@vger.kernel.org> # 9995e4404ea4: ACPI: NFIT: core: Eliminate redundant local variable
+Reviewed-by: Dave Jiang <dave.jiang@intel.com>
+Link: https://patch.msgid.link/3420096.aeNJFYEL58@rafael.j.wysocki
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/acpi/bus.c | 6 +++-
+ drivers/acpi/nfit/core.c | 62 +++++++++++++++++++++++++++++++++++++----------
+ 2 files changed, 54 insertions(+), 14 deletions(-)
+
+--- a/drivers/acpi/bus.c
++++ b/drivers/acpi/bus.c
+@@ -623,6 +623,7 @@ void acpi_dev_remove_notify_handler(stru
+ EXPORT_SYMBOL_GPL(acpi_dev_remove_notify_handler);
+
+ struct acpi_notify_handler_devres {
++ struct acpi_device *adev;
+ acpi_notify_handler handler;
+ u32 handler_type;
+ };
+@@ -631,7 +632,7 @@ static void devm_acpi_notify_handler_rel
+ {
+ struct acpi_notify_handler_devres *dr = res;
+
+- acpi_dev_remove_notify_handler(ACPI_COMPANION(dev), dr->handler_type,
++ acpi_dev_remove_notify_handler(dr->adev, dr->handler_type,
+ dr->handler);
+ }
+
+@@ -667,6 +668,8 @@ int devm_acpi_install_notify_handler(str
+ int ret;
+
+ adev = ACPI_COMPANION(dev);
++ if (!adev && dev->bus == &acpi_bus_type)
++ adev = to_acpi_device(dev);
+ if (!adev)
+ return dev_err_probe(dev, -ENODEV, "No ACPI companion in %s()\n", __func__);
+
+@@ -680,6 +683,7 @@ int devm_acpi_install_notify_handler(str
+ return dev_err_probe(dev, ret, "Failed to install an ACPI notify handler\n");
+ }
+
++ dr->adev = adev;
+ dr->handler = handler;
+ dr->handler_type = handler_type;
+ devres_add(dev, dr);
+--- a/drivers/acpi/nfit/core.c
++++ b/drivers/acpi/nfit/core.c
+@@ -55,6 +55,8 @@ MODULE_PARM_DESC(force_labels, "Opt-in t
+ LIST_HEAD(acpi_descs);
+ DEFINE_MUTEX(acpi_desc_lock);
+
++DEFINE_MUTEX(acpi_notify_lock);
++
+ static struct workqueue_struct *nfit_wq;
+
+ struct nfit_table_prev {
+@@ -1702,9 +1704,15 @@ static void acpi_nvdimm_notify(acpi_hand
+ struct acpi_device *adev = data;
+ struct device *dev = &adev->dev;
+
+- device_lock(dev->parent);
+- __acpi_nvdimm_notify(dev, event);
+- device_unlock(dev->parent);
++ /*
++ * Locking is needed here for synchronization with driver probe and
++ * removal and the parent NFIT device's ACPI driver data pointer is
++ * NULL when teardown is in progress.
++ */
++ guard(mutex)(&acpi_notify_lock);
++
++ if (acpi_driver_data(to_acpi_device(dev->parent)))
++ __acpi_nvdimm_notify(dev, event);
+ }
+
+ static bool acpi_nvdimm_has_method(struct acpi_device *adev, char *method)
+@@ -3150,11 +3158,10 @@ EXPORT_SYMBOL_GPL(acpi_nfit_init);
+ static int acpi_nfit_flush_probe(struct nvdimm_bus_descriptor *nd_desc)
+ {
+ struct acpi_nfit_desc *acpi_desc = to_acpi_desc(nd_desc);
+- struct device *dev = acpi_desc->dev;
+
+- /* Bounce the device lock to flush acpi_nfit_add / acpi_nfit_notify */
+- device_lock(dev);
+- device_unlock(dev);
++ /* Bounce the notify lock to flush acpi_nfit_add / acpi_nfit_notify */
++ mutex_lock(&acpi_notify_lock);
++ mutex_unlock(&acpi_notify_lock);
+
+ /* Bounce the init_mutex to complete initial registration */
+ mutex_lock(&acpi_desc->init_mutex);
+@@ -3286,10 +3293,17 @@ static void acpi_nfit_put_table(void *ta
+ static void acpi_nfit_notify(acpi_handle handle, u32 event, void *data)
+ {
+ struct device *dev = data;
++ struct acpi_device *adev = to_acpi_device(dev);
+
+- device_lock(dev);
+- __acpi_nfit_notify(dev, handle, event);
+- device_unlock(dev);
++ /*
++ * Locking is needed here for synchronization with driver probe and
++ * removal and the ACPI driver data pointer is NULL when teardown
++ * is in progress.
++ */
++ guard(mutex)(&acpi_notify_lock);
++
++ if (acpi_driver_data(adev))
++ __acpi_nfit_notify(dev, handle, event);
+ }
+
+ void acpi_nfit_shutdown(void *data)
+@@ -3336,6 +3350,12 @@ static int acpi_nfit_add(struct acpi_dev
+ acpi_size sz;
+ int rc = 0;
+
++ /*
++ * Prevent acpi_nfit_notify() from progressing until the probe is
++ * complete in case there is a concurrent event to process.
++ */
++ guard(mutex)(&acpi_notify_lock);
++
+ rc = devm_acpi_install_notify_handler(dev, ACPI_DEVICE_NOTIFY,
+ acpi_nfit_notify, dev);
+ if (rc)
+@@ -3351,6 +3371,11 @@ static int acpi_nfit_add(struct acpi_dev
+ * data in the format of a series of NFIT Structures.
+ */
+ dev_dbg(dev, "failed to find NFIT at startup\n");
++ /*
++ * Let acpi_nfit_update_notify() run in case it will need to
++ * allocate the acpi_desc object.
++ */
++ adev->driver_data = dev;
+ return 0;
+ }
+
+@@ -3368,7 +3393,7 @@ static int acpi_nfit_add(struct acpi_dev
+ acpi_desc->acpi_header = *tbl;
+
+ /* Evaluate _FIT and override with that if present */
+- status = acpi_evaluate_object(ACPI_HANDLE(dev), "_FIT", NULL, &buf);
++ status = acpi_evaluate_object(adev->handle, "_FIT", NULL, &buf);
+ if (ACPI_SUCCESS(status) && buf.length > 0) {
+ union acpi_object *obj = buf.pointer;
+
+@@ -3385,14 +3410,25 @@ static int acpi_nfit_add(struct acpi_dev
+ + sizeof(struct acpi_table_nfit),
+ sz - sizeof(struct acpi_table_nfit));
+
+- if (rc)
++ if (rc) {
+ acpi_nfit_shutdown(acpi_desc);
++ return rc;
++ }
+
+- return rc;
++ /*
++ * Let notify handlers operate (the actual value of the ACPI driver
++ * data pointer does not matter here so long as it is not NULL).
++ */
++ adev->driver_data = dev;
++ return 0;
+ }
+
+ static void acpi_nfit_remove(struct acpi_device *adev)
+ {
++ guard(mutex)(&acpi_notify_lock);
++
++ /* Make notify handlers bail out early going forward. */
++ adev->driver_data = NULL;
+ acpi_nfit_shutdown(dev_get_drvdata(&adev->dev));
+ }
+
--- /dev/null
+From stable+bounces-273279-greg=kroah.com@vger.kernel.org Fri Jul 10 18:18:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Jul 2026 12:14:40 -0400
+Subject: ACPI: NFIT: core: Use devm_acpi_install_notify_handler()
+To: stable@vger.kernel.org
+Cc: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260710161442.293558-3-sashal@kernel.org>
+
+From: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
+
+[ Upstream commit 198541ad53c0d0d891fedea4098f9953a0f566c0 ]
+
+Now that devm_acpi_install_notify_handler() is available, use it in
+acpi_nfit_probe() instead of a custom devm action removing an ACPI
+notify handler installed via acpi_dev_install_notify_handler().
+
+Also drop the explicit ACPI_COMPANION() check against NULL that is
+not necessary any more becuase devm_acpi_install_notify_handler()
+carries out an equivalent check internally and use ACPI_HANDLE() to
+retrieve the platform device's ACPI handle.
+
+No intentional functional impact.
+
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+Link: https://patch.msgid.link/3048737.e9J7NaK4W3@rafael.j.wysocki
+Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/acpi/nfit/core.c | 27 +++++++--------------------
+ 1 file changed, 7 insertions(+), 20 deletions(-)
+
+--- a/drivers/acpi/nfit/core.c
++++ b/drivers/acpi/nfit/core.c
+@@ -3285,19 +3285,11 @@ static void acpi_nfit_put_table(void *ta
+
+ static void acpi_nfit_notify(acpi_handle handle, u32 event, void *data)
+ {
+- struct acpi_device *adev = data;
++ struct device *dev = data;
+
+- device_lock(&adev->dev);
+- __acpi_nfit_notify(&adev->dev, handle, event);
+- device_unlock(&adev->dev);
+-}
+-
+-static void acpi_nfit_remove_notify_handler(void *data)
+-{
+- struct acpi_device *adev = data;
+-
+- acpi_dev_remove_notify_handler(adev, ACPI_DEVICE_NOTIFY,
+- acpi_nfit_notify);
++ device_lock(dev);
++ __acpi_nfit_notify(dev, handle, event);
++ device_unlock(dev);
+ }
+
+ void acpi_nfit_shutdown(void *data)
+@@ -3344,13 +3336,8 @@ static int acpi_nfit_add(struct acpi_dev
+ acpi_size sz;
+ int rc = 0;
+
+- rc = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY,
+- acpi_nfit_notify, adev);
+- if (rc)
+- return rc;
+-
+- rc = devm_add_action_or_reset(dev, acpi_nfit_remove_notify_handler,
+- adev);
++ rc = devm_acpi_install_notify_handler(dev, ACPI_DEVICE_NOTIFY,
++ acpi_nfit_notify, dev);
+ if (rc)
+ return rc;
+
+@@ -3381,7 +3368,7 @@ static int acpi_nfit_add(struct acpi_dev
+ acpi_desc->acpi_header = *tbl;
+
+ /* Evaluate _FIT and override with that if present */
+- status = acpi_evaluate_object(adev->handle, "_FIT", NULL, &buf);
++ status = acpi_evaluate_object(ACPI_HANDLE(dev), "_FIT", NULL, &buf);
+ if (ACPI_SUCCESS(status) && buf.length > 0) {
+ union acpi_object *obj = buf.pointer;
+
--- /dev/null
+From stable+bounces-273999-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:39: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: <20260713203917.2143472-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
+@@ -948,6 +948,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));
+@@ -962,6 +964,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",
+@@ -969,6 +973,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-274002-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:35 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 16:39:27 -0400
+Subject: ALSA: hda/cs35l41: Fix firmware load work teardown
+To: stable@vger.kernel.org
+Cc: "Cássio Gabriel" <cassiogabrielcontato@gmail.com>, "Stefan Binding" <sbinding@opensource.cirrus.com>, "Takashi Iwai" <tiwai@suse.de>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260713203927.2143760-1-sashal@kernel.org>
+
+From: Cássio Gabriel <cassiogabrielcontato@gmail.com>
+
+[ Upstream commit b65020d5398f499c09498c9786dba6d67ae57664 ]
+
+cs35l41_hda creates ALSA controls whose private data points at the
+cs35l41_hda object. The firmware load control can also queue
+fw_load_work.
+
+Those controls are not removed on component unbind, and device remove
+only cancels fw_load_work through cs35l41_remove_dsp(). That helper is
+skipped when halo_initialized is false. With firmware_autostart
+disabled, a firmware load can be requested before the DSP has been
+initialized. If the component or device is removed before the queued
+work runs, the worker can run after teardown and dereference driver
+state that is no longer valid.
+
+Track the created controls and remove them on unbind so no new control
+callback can reach the driver data or queue more work. Then cancel
+fw_load_work to drain any request that was already queued. Also cancel
+the work unconditionally during device remove before runtime PM teardown.
+
+Fixes: 47ceabd99a28 ("ALSA: hda: cs35l41: Support Firmware switching and reloading")
+Fixes: 4c870513fbb0 ("ALSA: hda: cs35l41: Add read-only ALSA control for forced mute")
+Cc: stable@vger.kernel.org
+Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
+Reviewed-by: Stefan Binding <sbinding@opensource.cirrus.com>
+Signed-off-by: Takashi Iwai <tiwai@suse.de>
+Link: https://patch.msgid.link/20260511-alsa-hda-cs35l41-fw-work-teardown-v1-1-1184e9bc4f25@gmail.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/pci/hda/cs35l41_hda.c | 77 +++++++++++++++++++++++++++++++-------------
+ sound/pci/hda/cs35l41_hda.h | 5 ++
+ 2 files changed, 60 insertions(+), 22 deletions(-)
+
+--- a/sound/pci/hda/cs35l41_hda.c
++++ b/sound/pci/hda/cs35l41_hda.c
+@@ -1293,6 +1293,43 @@ static int cs35l41_fw_type_ctl_info(stru
+ return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(hda_cs_dsp_fw_ids), hda_cs_dsp_fw_ids);
+ }
+
++static void cs35l41_remove_controls(struct cs35l41_hda *cs35l41)
++{
++ if (!cs35l41->codec)
++ return;
++
++ snd_ctl_remove(cs35l41->codec->card, cs35l41->mute_override_ctl);
++ cs35l41->mute_override_ctl = NULL;
++
++ snd_ctl_remove(cs35l41->codec->card, cs35l41->fw_load_ctl);
++ cs35l41->fw_load_ctl = NULL;
++
++ snd_ctl_remove(cs35l41->codec->card, cs35l41->fw_type_ctl);
++ cs35l41->fw_type_ctl = NULL;
++}
++
++static int cs35l41_add_control(struct cs35l41_hda *cs35l41,
++ struct snd_kcontrol_new *ctl,
++ struct snd_kcontrol **kctl)
++{
++ int ret;
++
++ *kctl = snd_ctl_new1(ctl, cs35l41);
++ if (!*kctl)
++ return -ENOMEM;
++
++ ret = snd_ctl_add(cs35l41->codec->card, *kctl);
++ if (ret) {
++ dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", ctl->name, ret);
++ *kctl = NULL;
++ return ret;
++ }
++
++ dev_dbg(cs35l41->dev, "Added Control %s\n", ctl->name);
++
++ return 0;
++}
++
+ static int cs35l41_create_controls(struct cs35l41_hda *cs35l41)
+ {
+ char fw_type_ctl_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+@@ -1328,32 +1365,23 @@ static int cs35l41_create_controls(struc
+ scnprintf(mute_override_ctl_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s Forced Mute Status",
+ cs35l41->amp_name);
+
+- ret = snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&fw_type_ctl, cs35l41));
+- if (ret) {
+- dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", fw_type_ctl.name, ret);
+- return ret;
+- }
+-
+- dev_dbg(cs35l41->dev, "Added Control %s\n", fw_type_ctl.name);
+-
+- ret = snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&fw_load_ctl, cs35l41));
+- if (ret) {
+- dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", fw_load_ctl.name, ret);
+- return ret;
+- }
+-
+- dev_dbg(cs35l41->dev, "Added Control %s\n", fw_load_ctl.name);
++ ret = cs35l41_add_control(cs35l41, &fw_type_ctl, &cs35l41->fw_type_ctl);
++ if (ret)
++ goto err;
+
+- ret = snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&mute_override_ctl, cs35l41));
+- if (ret) {
+- dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", mute_override_ctl.name,
+- ret);
+- return ret;
+- }
++ ret = cs35l41_add_control(cs35l41, &fw_load_ctl, &cs35l41->fw_load_ctl);
++ if (ret)
++ goto err;
+
+- dev_dbg(cs35l41->dev, "Added Control %s\n", mute_override_ctl.name);
++ ret = cs35l41_add_control(cs35l41, &mute_override_ctl, &cs35l41->mute_override_ctl);
++ if (ret)
++ goto err;
+
+ return 0;
++
++err:
++ cs35l41_remove_controls(cs35l41);
++ return ret;
+ }
+
+ static bool cs35l41_dsm_supported(acpi_handle handle, unsigned int commands)
+@@ -1491,6 +1519,10 @@ static void cs35l41_hda_unbind(struct de
+ device_link_remove(&cs35l41->codec->core.dev, cs35l41->dev);
+ unlock_system_sleep(sleep_flags);
+ memset(comp, 0, sizeof(*comp));
++
++ cs35l41_remove_controls(cs35l41);
++ cancel_work_sync(&cs35l41->fw_load_work);
++ cs35l41->codec = NULL;
+ }
+ }
+
+@@ -2030,6 +2062,7 @@ void cs35l41_hda_remove(struct device *d
+ struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev);
+
+ component_del(cs35l41->dev, &cs35l41_hda_comp_ops);
++ cancel_work_sync(&cs35l41->fw_load_work);
+
+ pm_runtime_get_sync(cs35l41->dev);
+ pm_runtime_dont_use_autosuspend(cs35l41->dev);
+--- a/sound/pci/hda/cs35l41_hda.h
++++ b/sound/pci/hda/cs35l41_hda.h
+@@ -56,6 +56,8 @@ enum control_bus {
+ SPI
+ };
+
++struct snd_kcontrol;
++
+ struct cs35l41_hda {
+ struct device *dev;
+ struct regmap *regmap;
+@@ -74,6 +76,9 @@ struct cs35l41_hda {
+ int speaker_id;
+ struct mutex fw_mutex;
+ struct work_struct fw_load_work;
++ struct snd_kcontrol *fw_type_ctl;
++ struct snd_kcontrol *fw_load_ctl;
++ struct snd_kcontrol *mute_override_ctl;
+
+ struct regmap_irq_chip_data *irq_data;
+ bool firmware_running;
--- /dev/null
+From stable+bounces-274088-greg=kroah.com@vger.kernel.org Tue Jul 14 05:04:48 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 23:04:37 -0400
+Subject: ALSA: scarlett2: Allow selecting config_set by firmware version
+To: stable@vger.kernel.org
+Cc: "Geoffrey D. Bennett" <g@b4.vu>, Takashi Iwai <tiwai@suse.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714030438.2382402-1-sashal@kernel.org>
+
+From: "Geoffrey D. Bennett" <g@b4.vu>
+
+[ Upstream commit 732a6397a526c025cd29c3c9309b0db6a2c08837 ]
+
+The Scarlett 2i2 Gen 4 firmware 2417 moved the direct monitor gain
+parameters, so we now need to allow each device to list multiple
+scarlett2_config_set entries, one per applicable firmware version
+range, and pick the matching one at probe time.
+
+No functional change yet: each device gets a single config_sets
+entry whose from_firmware_version matches the existing
+min_firmware_version (0 where none was set). This both prepares for
+selection and lets a follow-up commit remove the now-redundant
+min_firmware_version field.
+
+scarlett2_count_io() depends on the resolved config_set so it moves
+out of scarlett2_init_private() into snd_scarlett2_controls_create()
+after the firmware version has been read.
+
+Signed-off-by: Geoffrey D. Bennett <g@b4.vu>
+Signed-off-by: Takashi Iwai <tiwai@suse.de>
+Link: https://patch.msgid.link/ae1695b4c4825f365b4c86b22174035f742807e3.1777151532.git.g@b4.vu
+Stable-dep-of: 3ca15754b561 ("ALSA: scarlett2: Update offsets for 2i2 Gen 4 firmware 2417")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/usb/mixer_scarlett2.c | 143 +++++++++++++++++++++++++++++++++++++-------
+ 1 file changed, 121 insertions(+), 22 deletions(-)
+
+--- a/sound/usb/mixer_scarlett2.c
++++ b/sound/usb/mixer_scarlett2.c
+@@ -588,6 +588,20 @@ struct scarlett2_config_set {
+ const struct scarlett2_config items[SCARLETT2_CONFIG_COUNT];
+ };
+
++/* Map firmware versions to config sets per-device.
++ *
++ * Each device lists one or more entries, sorted in ascending order of
++ * from_firmware_version. At probe time the running firmware version
++ * is looked up against this list and the last entry whose
++ * from_firmware_version is <= the running version is selected.
++ *
++ * The list is terminated by a sentinel entry with config_set == NULL.
++ */
++struct scarlett2_config_set_entry {
++ u16 from_firmware_version;
++ const struct scarlett2_config_set *config_set;
++};
++
+ /* Input gain TLV dB ranges */
+
+ static const DECLARE_TLV_DB_MINMAX(
+@@ -1073,8 +1087,8 @@ struct scarlett2_meter_entry {
+ };
+
+ struct scarlett2_device_info {
+- /* which set of configuration parameters the device uses */
+- const struct scarlett2_config_set *config_set;
++ /* which sets of configuration parameters the device uses */
++ const struct scarlett2_config_set_entry *config_sets;
+
+ /* minimum firmware version required */
+ u16 min_firmware_version;
+@@ -1309,7 +1323,10 @@ struct scarlett2_data {
+ /*** Model-specific data ***/
+
+ static const struct scarlett2_device_info s6i6_gen2_info = {
+- .config_set = &scarlett2_config_set_gen2a,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 0, &scarlett2_config_set_gen2a },
++ { }
++ },
+ .level_input_count = 2,
+ .pad_input_count = 2,
+
+@@ -1359,7 +1376,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info s18i8_gen2_info = {
+- .config_set = &scarlett2_config_set_gen2a,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 0, &scarlett2_config_set_gen2a },
++ { }
++ },
+ .level_input_count = 2,
+ .pad_input_count = 4,
+
+@@ -1412,7 +1432,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info s18i20_gen2_info = {
+- .config_set = &scarlett2_config_set_gen2b,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 0, &scarlett2_config_set_gen2b },
++ { }
++ },
+
+ .line_out_descrs = {
+ "Monitor L",
+@@ -1469,7 +1492,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info solo_gen3_info = {
+- .config_set = &scarlett2_config_set_gen3a,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 0, &scarlett2_config_set_gen3a },
++ { }
++ },
+ .level_input_count = 1,
+ .level_input_first = 1,
+ .air_input_count = 1,
+@@ -1479,7 +1505,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info s2i2_gen3_info = {
+- .config_set = &scarlett2_config_set_gen3a,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 0, &scarlett2_config_set_gen3a },
++ { }
++ },
+ .level_input_count = 2,
+ .air_input_count = 2,
+ .phantom_count = 1,
+@@ -1488,7 +1517,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info s4i4_gen3_info = {
+- .config_set = &scarlett2_config_set_gen3b,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 0, &scarlett2_config_set_gen3b },
++ { }
++ },
+ .level_input_count = 2,
+ .pad_input_count = 2,
+ .air_input_count = 2,
+@@ -1537,7 +1569,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info s8i6_gen3_info = {
+- .config_set = &scarlett2_config_set_gen3b,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 0, &scarlett2_config_set_gen3b },
++ { }
++ },
+ .level_input_count = 2,
+ .pad_input_count = 2,
+ .air_input_count = 2,
+@@ -1603,7 +1638,10 @@ static const char * const scarlett2_spdi
+ };
+
+ static const struct scarlett2_device_info s18i8_gen3_info = {
+- .config_set = &scarlett2_config_set_gen3c,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 0, &scarlett2_config_set_gen3c },
++ { }
++ },
+ .has_speaker_switching = 1,
+ .level_input_count = 2,
+ .pad_input_count = 4,
+@@ -1695,7 +1733,10 @@ static const char * const scarlett2_spdi
+ };
+
+ static const struct scarlett2_device_info s18i20_gen3_info = {
+- .config_set = &scarlett2_config_set_gen3c,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 0, &scarlett2_config_set_gen3c },
++ { }
++ },
+ .has_speaker_switching = 1,
+ .has_talkback = 1,
+ .level_input_count = 2,
+@@ -1769,7 +1810,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info vocaster_one_info = {
+- .config_set = &scarlett2_config_set_vocaster,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 1769, &scarlett2_config_set_vocaster },
++ { }
++ },
+ .min_firmware_version = 1769,
+
+ .phantom_count = 1,
+@@ -1811,7 +1855,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info vocaster_two_info = {
+- .config_set = &scarlett2_config_set_vocaster,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 1769, &scarlett2_config_set_vocaster },
++ { }
++ },
+ .min_firmware_version = 1769,
+
+ .phantom_count = 2,
+@@ -1854,7 +1901,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info solo_gen4_info = {
+- .config_set = &scarlett2_config_set_gen4_solo,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 2115, &scarlett2_config_set_gen4_solo },
++ { }
++ },
+ .min_firmware_version = 2115,
+
+ .level_input_count = 1,
+@@ -1908,7 +1958,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info s2i2_gen4_info = {
+- .config_set = &scarlett2_config_set_gen4_2i2,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 2115, &scarlett2_config_set_gen4_2i2 },
++ { }
++ },
+ .min_firmware_version = 2115,
+
+ .level_input_count = 2,
+@@ -1962,7 +2015,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info s4i4_gen4_info = {
+- .config_set = &scarlett2_config_set_gen4_4i4,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 2089, &scarlett2_config_set_gen4_4i4 },
++ { }
++ },
+ .min_firmware_version = 2089,
+
+ .level_input_count = 2,
+@@ -2010,7 +2066,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info clarett_2pre_info = {
+- .config_set = &scarlett2_config_set_clarett,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 0, &scarlett2_config_set_clarett },
++ { }
++ },
+ .level_input_count = 2,
+ .air_input_count = 2,
+
+@@ -2066,7 +2125,10 @@ static const char * const scarlett2_spdi
+ };
+
+ static const struct scarlett2_device_info clarett_4pre_info = {
+- .config_set = &scarlett2_config_set_clarett,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 0, &scarlett2_config_set_clarett },
++ { }
++ },
+ .level_input_count = 2,
+ .air_input_count = 4,
+
+@@ -2122,7 +2184,10 @@ static const struct scarlett2_device_inf
+ };
+
+ static const struct scarlett2_device_info clarett_8pre_info = {
+- .config_set = &scarlett2_config_set_clarett,
++ .config_sets = (const struct scarlett2_config_set_entry[]) {
++ { 0, &scarlett2_config_set_clarett },
++ { }
++ },
+ .level_input_count = 2,
+ .air_input_count = 8,
+
+@@ -8539,10 +8604,32 @@ static void scarlett2_private_suspend(st
+
+ /*** Initialisation ***/
+
++/* Select the config_set matching the running firmware version.
++ *
++ * The device info's config_sets array is ordered by ascending
++ * from_firmware_version; pick the last entry whose version is <= the
++ * running firmware version. If the running firmware is older than the
++ * first entry's from_firmware_version (i.e. older than the driver's
++ * minimum supported version for this device), the first entry's
++ * config_set is selected anyway so firmware updates can still be done
++ * (requires only the ACK handler), but the usual mixer controls
++ * aren't created.
++ */
++static void scarlett2_resolve_config_set(struct scarlett2_data *private)
++{
++ const struct scarlett2_config_set_entry *entry =
++ private->info->config_sets;
++
++ private->config_set = entry->config_set;
++ for (entry++; entry->config_set; entry++)
++ if (entry->from_firmware_version <= private->firmware_version)
++ private->config_set = entry->config_set;
++}
++
+ static void scarlett2_count_io(struct scarlett2_data *private)
+ {
+ const struct scarlett2_device_info *info = private->info;
+- const struct scarlett2_config_set *config_set = info->config_set;
++ const struct scarlett2_config_set *config_set = private->config_set;
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
+ int port_type, srcs = 0, dsts = 0, i;
+
+@@ -8640,9 +8727,14 @@ static int scarlett2_init_private(struct
+ mixer->private_suspend = scarlett2_private_suspend;
+
+ private->info = entry->info;
+- private->config_set = entry->info->config_set;
++
++ /* Set config_set to the first entry's config_set so the
++ * notify handler has a valid pointer while USB init runs; it
++ * is re-resolved once the firmware version has been read.
++ */
++ private->config_set = entry->info->config_sets[0].config_set;
++
+ private->series_name = entry->series_name;
+- scarlett2_count_io(private);
+ private->scarlett2_seq = 0;
+ private->mixer = mixer;
+
+@@ -9046,6 +9138,13 @@ static int snd_scarlett2_controls_create
+ if (err < 0)
+ return err;
+
++ /* Now that the firmware version is known, pick the matching
++ * config_set
++ */
++ scarlett2_resolve_config_set(private);
++
++ scarlett2_count_io(private);
++
+ /* Get the upgrade & settings flash segment numbers */
+ err = scarlett2_get_flash_segment_nums(mixer);
+ if (err < 0)
--- /dev/null
+From stable+bounces-274089-greg=kroah.com@vger.kernel.org Tue Jul 14 05:06:12 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 23:04:38 -0400
+Subject: ALSA: scarlett2: Update offsets for 2i2 Gen 4 firmware 2417
+To: stable@vger.kernel.org
+Cc: "Geoffrey D. Bennett" <g@b4.vu>, Takashi Iwai <tiwai@suse.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714030438.2382402-2-sashal@kernel.org>
+
+From: "Geoffrey D. Bennett" <g@b4.vu>
+
+[ Upstream commit 3ca15754b561483aa7a1bce51677d6389f8ff5bb ]
+
+Firmware 2417 for the Scarlett 4th Gen 2i2 moved the direct monitor gain
+parameters, so add a second config_set with the shifted offset and
+select it for firmware versions >= 2417.
+
+Fixes: 4e809a299677 ("ALSA: scarlett2: Add support for Solo, 2i2, and 4i4 Gen 4")
+Cc: stable@vger.kernel.org # ALSA: scarlett2: Allow selecting config_set by firmware version
+Cc: stable@vger.kernel.org # ALSA: scarlett2: Fold min_firmware_version into config_sets
+Cc: stable@vger.kernel.org
+Signed-off-by: Geoffrey D. Bennett <g@b4.vu>
+Signed-off-by: Takashi Iwai <tiwai@suse.de>
+Link: https://patch.msgid.link/ad0fc5a131e76eb656a24e0e198382f7134068fe.1777151532.git.g@b4.vu
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ sound/usb/mixer_scarlett2.c | 58 ++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 58 insertions(+)
+
+--- a/sound/usb/mixer_scarlett2.c
++++ b/sound/usb/mixer_scarlett2.c
+@@ -910,6 +910,63 @@ static const struct scarlett2_config_set
+ }
+ };
+
++/* 2i2 Gen 4, firmware version 2417 and above
++ *
++ * Firmware 2417 shifted DIRECT_MONITOR_GAIN by 4 bytes; all other
++ * offsets are unchanged from scarlett2_config_set_gen4_2i2.
++ */
++static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2_2417 = {
++ .notifications = scarlett4_2i2_notifications,
++ .param_buf_addr = 0xfc,
++ .input_gain_tlv = db_scale_gen4_gain,
++ .autogain_status_texts = scarlett2_autogain_status_gen4,
++ .items = {
++ [SCARLETT2_CONFIG_MSD_SWITCH] = {
++ .offset = 0x49, .size = 8, .activate = 4 },
++
++ [SCARLETT2_CONFIG_DIRECT_MONITOR] = {
++ .offset = 0x14a, .size = 8, .activate = 16, .pbuf = 1 },
++
++ [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = {
++ .offset = 0x135, .size = 8, .activate = 10, .pbuf = 1 },
++
++ [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = {
++ .offset = 0x137, .size = 8 },
++
++ [SCARLETT2_CONFIG_AG_MEAN_TARGET] = {
++ .offset = 0x131, .size = 8, .activate = 29, .pbuf = 1 },
++
++ [SCARLETT2_CONFIG_AG_PEAK_TARGET] = {
++ .offset = 0x132, .size = 8, .activate = 30, .pbuf = 1 },
++
++ [SCARLETT2_CONFIG_PHANTOM_SWITCH] = {
++ .offset = 0x48, .size = 8, .activate = 11, .pbuf = 1,
++ .mute = 1 },
++
++ [SCARLETT2_CONFIG_INPUT_GAIN] = {
++ .offset = 0x4b, .size = 8, .activate = 12, .pbuf = 1 },
++
++ [SCARLETT2_CONFIG_LEVEL_SWITCH] = {
++ .offset = 0x3c, .size = 8, .activate = 13, .pbuf = 1,
++ .mute = 1 },
++
++ [SCARLETT2_CONFIG_SAFE_SWITCH] = {
++ .offset = 0x147, .size = 8, .activate = 14, .pbuf = 1 },
++
++ [SCARLETT2_CONFIG_AIR_SWITCH] = {
++ .offset = 0x3e, .size = 8, .activate = 15, .pbuf = 1 },
++
++ [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = {
++ .offset = 0x14b, .size = 8, .activate = 17, .pbuf = 1 },
++
++ [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = {
++ .offset = 0x14e, .size = 8, .activate = 18, .pbuf = 1 },
++
++ [SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN] = {
++ .offset = 0x2a4, .size = 16, .activate = 36 }
++ }
++};
++
+ /* 4i4 Gen 4 */
+ static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4 = {
+ .notifications = scarlett4_4i4_notifications,
+@@ -1960,6 +2017,7 @@ static const struct scarlett2_device_inf
+ static const struct scarlett2_device_info s2i2_gen4_info = {
+ .config_sets = (const struct scarlett2_config_set_entry[]) {
+ { 2115, &scarlett2_config_set_gen4_2i2 },
++ { 2417, &scarlett2_config_set_gen4_2i2_2417 },
+ { }
+ },
+ .min_firmware_version = 2115,
--- /dev/null
+From stable+bounces-275049-greg=kroah.com@vger.kernel.org Wed Jul 15 23:22:21 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 17:21:01 -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: <20260715212102.274128-1-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. */
+ }
+@@ -1254,6 +1274,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);
+@@ -1265,7 +1286,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-275047-greg=kroah.com@vger.kernel.org Wed Jul 15 23:14:09 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 17:13:55 -0400
+Subject: Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
+To: stable@vger.kernel.org
+Cc: Siwei Zhang <oss@fourdim.xyz>, XIAO WU <xiaowu.417@qq.com>, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715211355.253617-3-sashal@kernel.org>
+
+From: Siwei Zhang <oss@fourdim.xyz>
+
+[ Upstream commit 12917f591cea1af36087dba5b9ec888652f0b42a ]
+
+hci_abort_conn() read hci_skb_event(hdev->sent_cmd) when a connection
+was pending, but hdev->sent_cmd can be NULL while req_status is still
+HCI_REQ_PEND, leading to a NULL pointer dereference and a general
+protection fault from the hci_rx_work() receive path.
+
+Instead of inspecting hdev->sent_cmd, track the in-flight create
+connection command with a new per-connection HCI_CONN_CREATE flag and
+route all cancellation through hci_cancel_connect_sync(), which
+dispatches to a dedicated per-type cancel function. The create command
+is in exactly one of two states: still queued, or in flight. The cancel
+function holds cmd_sync_work_lock across the whole decision: the worker
+takes this lock to dequeue every entry, so while it is held a queued
+command cannot start running and an in-flight command cannot complete
+and let the next command become pending. This keeps the flag test and
+hci_cmd_sync_cancel() atomic with respect to the worker, so a queued
+command is simply dequeued, and an in-flight command owned by this
+connection is cancelled without the risk of cancelling an unrelated
+command that became pending in the meantime. CIS uses the same flag
+mechanism via HCI_CONN_CREATE_CIS but cannot be dequeued per-connection.
+
+hci_acl_create_conn_sync() and hci_le_create_conn_sync() clear
+HCI_CONN_CREATE after the create command completes, but the command
+status handler can free conn via hci_conn_del() (for example when the
+controller rejects the connection) while the worker is still blocked on
+the connection complete event. Hold a reference on conn across the
+create command so the flag can be cleared without a use-after-free.
+
+Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections")
+Cc: stable@vger.kernel.org
+Suggested-by: XIAO WU <xiaowu.417@qq.com>
+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/hci_core.h | 1
+ net/bluetooth/hci_conn.c | 21 ------
+ net/bluetooth/hci_sync.c | 133 ++++++++++++++++++++++++++++++++++-----
+ 3 files changed, 123 insertions(+), 32 deletions(-)
+
+--- a/include/net/bluetooth/hci_core.h
++++ b/include/net/bluetooth/hci_core.h
+@@ -958,6 +958,7 @@ enum {
+ HCI_CONN_AUTH_FAILURE,
+ HCI_CONN_PER_ADV,
+ HCI_CONN_BIG_CREATED,
++ HCI_CONN_CREATE,
+ HCI_CONN_CREATE_CIS,
+ HCI_CONN_CREATE_BIG_SYNC,
+ HCI_CONN_BIG_SYNC,
+--- a/net/bluetooth/hci_conn.c
++++ b/net/bluetooth/hci_conn.c
+@@ -2921,26 +2921,11 @@ int hci_abort_conn(struct hci_conn *conn
+
+ conn->abort_reason = reason;
+
+- /* If the connection is pending check the command opcode since that
+- * might be blocking on hci_cmd_sync_work while waiting its respective
+- * event so we need to hci_cmd_sync_cancel to cancel it.
+- *
+- * hci_connect_le serializes the connection attempts so only one
+- * connection can be in BT_CONNECT at time.
++ /* Cancel the connect attempt. A return of 0 means the create command
++ * was still queued and got dequeued, so there is nothing to disconnect.
+ */
+- if (conn->state == BT_CONNECT && READ_ONCE(hdev->req_status) == HCI_REQ_PEND) {
+- switch (hci_skb_event(hdev->sent_cmd)) {
+- case HCI_EV_CONN_COMPLETE:
+- case HCI_EV_LE_CONN_COMPLETE:
+- case HCI_EV_LE_ENHANCED_CONN_COMPLETE:
+- case HCI_EVT_LE_CIS_ESTABLISHED:
+- hci_cmd_sync_cancel(hdev, ECANCELED);
+- break;
+- }
+- /* Cancel connect attempt if still queued/pending */
+- } else if (!hci_cancel_connect_sync(hdev, conn)) {
++ if (!hci_cancel_connect_sync(hdev, conn))
+ return 0;
+- }
+
+ /* Run immediately if on cmd_sync_work since this may be called
+ * as a result to MGMT_OP_DISCONNECT/MGMT_OP_UNPAIR which does
+--- a/net/bluetooth/hci_sync.c
++++ b/net/bluetooth/hci_sync.c
+@@ -6568,6 +6568,11 @@ static int hci_le_create_conn_sync(struc
+
+ bt_dev_dbg(hdev, "conn %p", conn);
+
++ /* Hold a reference so conn stays valid for the HCI_CONN_CREATE
++ * clear_bit() at done.
++ */
++ hci_conn_get(conn);
++
+ clear_bit(HCI_CONN_SCANNING, &conn->flags);
+ conn->state = BT_CONNECT;
+
+@@ -6580,6 +6585,7 @@ static int hci_le_create_conn_sync(struc
+ hdev->le_scan_type == LE_SCAN_ACTIVE &&
+ !hci_dev_test_flag(hdev, HCI_LE_SIMULTANEOUS_ROLES)) {
+ hci_conn_del(conn);
++ hci_conn_put(conn);
+ return -EBUSY;
+ }
+
+@@ -6625,6 +6631,12 @@ static int hci_le_create_conn_sync(struc
+ &own_addr_type);
+ if (err)
+ goto done;
++
++ /* Mark create connection in flight so hci_cancel_connect_sync() can
++ * cancel it while blocking on the connection complete event.
++ */
++ set_bit(HCI_CONN_CREATE, &conn->flags);
++
+ /* Send command LE Extended Create Connection if supported */
+ if (use_ext_conn(hdev)) {
+ err = hci_le_ext_create_conn_sync(hdev, conn, own_addr_type);
+@@ -6660,11 +6672,14 @@ static int hci_le_create_conn_sync(struc
+ conn->conn_timeout, NULL);
+
+ done:
++ clear_bit(HCI_CONN_CREATE, &conn->flags);
++
+ if (err == -ETIMEDOUT)
+ hci_le_connect_cancel_sync(hdev, conn, 0x00);
+
+ /* Re-enable advertising after the connection attempt is finished. */
+ hci_resume_advertising_sync(hdev);
++ hci_conn_put(conn);
+ return err;
+ }
+
+@@ -6939,10 +6954,25 @@ static int hci_acl_create_conn_sync(stru
+ else
+ cp.role_switch = 0x00;
+
+- return __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN,
+- sizeof(cp), &cp,
+- HCI_EV_CONN_COMPLETE,
+- conn->conn_timeout, NULL);
++ /* Hold a reference so conn stays valid for the HCI_CONN_CREATE
++ * clear_bit() below.
++ */
++ hci_conn_get(conn);
++
++ /* Mark create connection in flight so hci_cancel_connect_sync() can
++ * cancel it while blocking on the connection complete event.
++ */
++ set_bit(HCI_CONN_CREATE, &conn->flags);
++
++ err = __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN,
++ sizeof(cp), &cp,
++ HCI_EV_CONN_COMPLETE,
++ conn->conn_timeout, NULL);
++
++ clear_bit(HCI_CONN_CREATE, &conn->flags);
++ hci_conn_put(conn);
++
++ return err;
+ }
+
+ int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
+@@ -6988,22 +7018,97 @@ int hci_connect_le_sync(struct hci_dev *
+ create_le_conn_complete);
+ }
+
+-int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
++static int hci_acl_cancel_create_conn_sync(struct hci_dev *hdev,
++ struct hci_conn *conn)
++{
++ struct hci_cmd_sync_work_entry *entry;
++ int err = -EBUSY;
++
++ /* cmd_sync_work_lock makes the HCI_CONN_CREATE test and the cancel
++ * atomic against the worker, which takes this lock to dequeue every
++ * entry: while it is held no other command can become pending, so
++ * hci_cmd_sync_cancel() cannot cancel an unrelated command.
++ */
++ mutex_lock(&hdev->cmd_sync_work_lock);
++
++ /* In flight: this connection owns the pending request, cancel it. */
++ if (test_bit(HCI_CONN_CREATE, &conn->flags)) {
++ hci_cmd_sync_cancel(hdev, ECANCELED);
++ goto unlock;
++ }
++
++ /* Still queued: a successful dequeue means it never started, so there
++ * is nothing to disconnect.
++ */
++ entry = _hci_cmd_sync_lookup_entry(hdev, hci_acl_create_conn_sync, conn,
++ NULL);
++ if (entry) {
++ _hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED);
++ err = 0;
++ }
++
++unlock:
++ mutex_unlock(&hdev->cmd_sync_work_lock);
++ return err;
++}
++
++static int hci_le_cancel_create_conn_sync(struct hci_dev *hdev,
++ struct hci_conn *conn)
+ {
+- if (conn->state != BT_OPEN)
+- return -EINVAL;
++ struct hci_cmd_sync_work_entry *entry;
++ int err = -EBUSY;
++
++ /* cmd_sync_work_lock keeps the HCI_CONN_CREATE test and the cancel
++ * atomic against the cmd_sync worker.
++ */
++ mutex_lock(&hdev->cmd_sync_work_lock);
++
++ if (test_bit(HCI_CONN_CREATE, &conn->flags)) {
++ hci_cmd_sync_cancel(hdev, ECANCELED);
++ goto unlock;
++ }
+
++ entry = _hci_cmd_sync_lookup_entry(hdev, hci_le_create_conn_sync, conn,
++ create_le_conn_complete);
++ if (entry) {
++ _hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED);
++ err = 0;
++ }
++
++unlock:
++ mutex_unlock(&hdev->cmd_sync_work_lock);
++ return err;
++}
++
++static int hci_cis_cancel_create_conn_sync(struct hci_dev *hdev,
++ struct hci_conn *conn)
++{
++ /* LE Create CIS is shared by the whole CIG and cannot be dequeued
++ * per-connection, so only an in-flight command can be cancelled.
++ * cmd_sync_work_lock keeps the test and the cancel atomic against the
++ * cmd_sync worker.
++ */
++ mutex_lock(&hdev->cmd_sync_work_lock);
++
++ if (test_bit(HCI_CONN_CREATE_CIS, &conn->flags))
++ hci_cmd_sync_cancel(hdev, ECANCELED);
++
++ mutex_unlock(&hdev->cmd_sync_work_lock);
++ return -EBUSY;
++}
++
++int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
++{
+ switch (conn->type) {
+ case ACL_LINK:
+- return !hci_cmd_sync_dequeue_once(hdev,
+- hci_acl_create_conn_sync,
+- conn, NULL);
++ return hci_acl_cancel_create_conn_sync(hdev, conn);
+ case LE_LINK:
+- return !hci_cmd_sync_dequeue_once(hdev, hci_le_create_conn_sync,
+- conn, create_le_conn_complete);
++ return hci_le_cancel_create_conn_sync(hdev, conn);
++ case CIS_LINK:
++ return hci_cis_cancel_create_conn_sync(hdev, conn);
++ default:
++ return -ENOENT;
+ }
+-
+- return -ENOENT;
+ }
+
+ int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn,
--- /dev/null
+From stable+bounces-275045-greg=kroah.com@vger.kernel.org Wed Jul 15 23:14:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 17:13:53 -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: <20260715211355.253617-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
+@@ -207,6 +207,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 LE states reported through the
+ * HCI_LE_READ_SUPPORTED_STATES are invalid/broken.
+ *
+@@ -448,6 +455,7 @@ enum {
+ HCI_WIDEBAND_SPEECH_ENABLED,
+ HCI_EVENT_FILTER_CONFIGURED,
+ HCI_PA_SYNC,
++ HCI_SCO_FLOWCTL,
+
+ HCI_DUT_MODE,
+ HCI_VENDOR_DIAG,
+@@ -1545,6 +1553,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
+@@ -1891,6 +1891,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
+@@ -3608,42 +3608,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);
+@@ -3651,8 +3636,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)
+@@ -3688,8 +3682,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);
+ }
+ }
+
+@@ -3744,8 +3738,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);
+ }
+ }
+
+@@ -3790,8 +3784,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
+@@ -4446,9 +4446,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
+@@ -3888,6 +3888,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 */
+@@ -3906,6 +3928,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-275037-greg=kroah.com@vger.kernel.org Wed Jul 15 22:49:51 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 16:49:44 -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: <20260715204944.205221-2-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
+@@ -1755,19 +1755,13 @@ static void l2cap_conn_del(struct hci_co
+ disable_delayed_work_sync(&conn->info_timer);
+ disable_delayed_work_sync(&conn->id_addr_timer);
+
++ cancel_work_sync(&conn->pending_rx_work);
++
+ mutex_lock(&conn->lock);
+
+ 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-275036-greg=kroah.com@vger.kernel.org Wed Jul 15 22:50:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 16:49:43 -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: <20260715204944.205221-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
+@@ -658,8 +658,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
+@@ -928,26 +928,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_acl(struct l2cap_conn *conn, struct sk_buff *skb,
+@@ -1776,6 +1768,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);
++
+ l2cap_unregister_all_users(conn);
+
+ /* Force the connection to be immediately dropped */
+@@ -4776,12 +4770,41 @@ static int l2cap_le_connect_rsp(struct l
+ 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);
+@@ -5495,6 +5518,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);
+@@ -7043,13 +7068,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->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-274676-greg=kroah.com@vger.kernel.org Wed Jul 15 04:46:17 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 22:45:35 -0400
+Subject: Bluetooth: L2CAP: Fix UAF in channel timeout by holding conn ref
+To: stable@vger.kernel.org
+Cc: Marco Elver <elver@google.com>, Siwei Zhang <oss@fourdim.xyz>, Luiz Augusto von Dentz <luiz.von.dentz@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715024535.105267-1-sashal@kernel.org>
+
+From: Marco Elver <elver@google.com>
+
+[ Upstream commit b66774b48dd98f07254951f74ea6f513efe7ff8b ]
+
+l2cap_chan_timeout() runs asynchronously and accesses chan->conn. If
+the connection is torn down while the timer is running or pending,
+chan->conn can be freed, leading to a use-after-free when the timer
+worker attempts to lock conn->lock:
+
+| BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
+| BUG: KASAN: slab-use-after-free in atomic_long_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:4456 [inline]
+| BUG: KASAN: slab-use-after-free in __mutex_trylock_fast kernel/locking/mutex.c:161 [inline]
+| BUG: KASAN: slab-use-after-free in mutex_lock+0x4f/0xa0 kernel/locking/mutex.c:318
+| Write of size 8 at addr ffff8881298d9550 by task kworker/2:1/83
+|
+| CPU: 2 UID: 0 PID: 83 Comm: kworker/2:1 Not tainted 7.1.0-rc6-next-20260601-dirty #6 PREEMPT(full)
+| Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
+| Workqueue: events l2cap_chan_timeout
+| Call Trace:
+| <TASK>
+| instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
+| atomic_long_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:4456 [inline]
+| __mutex_trylock_fast kernel/locking/mutex.c:161 [inline]
+| mutex_lock+0x4f/0xa0 kernel/locking/mutex.c:318
+| l2cap_chan_timeout+0x5d/0x1b0 net/bluetooth/l2cap_core.c:422
+| process_one_work kernel/workqueue.c:3326 [inline]
+| process_scheduled_works+0x7c8/0xfb0 kernel/workqueue.c:3409
+| worker_thread+0x8a9/0xcf0 kernel/workqueue.c:3490
+| kthread+0x346/0x430 kernel/kthread.c:436
+| ret_from_fork+0x1a3/0x470 arch/x86/kernel/process.c:158
+| ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
+| </TASK>
+|
+| Allocated by task 320:
+| l2cap_conn_add+0xa7/0x820 net/bluetooth/l2cap_core.c:7075
+| l2cap_connect_cfm+0xdb/0xd70 net/bluetooth/l2cap_core.c:7452
+| hci_connect_cfm include/net/bluetooth/hci_core.h:2139 [inline]
+| hci_remote_features_evt+0x52f/0x9f0 net/bluetooth/hci_event.c:3760
+| hci_event_func net/bluetooth/hci_event.c:7796 [inline]
+| hci_event_packet+0x561/0xa70 net/bluetooth/hci_event.c:7847
+| hci_rx_work+0x370/0x890 net/bluetooth/hci_core.c:4040
+| process_one_work kernel/workqueue.c:3326 [inline]
+| process_scheduled_works+0x7c8/0xfb0 kernel/workqueue.c:3409
+| worker_thread+0x8a9/0xcf0 kernel/workqueue.c:3490
+| kthread+0x346/0x430 kernel/kthread.c:436
+| ret_from_fork+0x1a3/0x470 arch/x86/kernel/process.c:158
+| ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
+|
+| Freed by task 322:
+| hci_disconn_cfm include/net/bluetooth/hci_core.h:2154 [inline]
+| hci_conn_hash_flush+0x101/0x1f0 net/bluetooth/hci_conn.c:2736
+| hci_dev_close_sync+0x889/0xde0 net/bluetooth/hci_sync.c:5405
+| hci_dev_do_close net/bluetooth/hci_core.c:502 [inline]
+| hci_unregister_dev+0x1f7/0x370 net/bluetooth/hci_core.c:2679
+| vhci_release+0x12a/0x180 drivers/bluetooth/hci_vhci.c:690
+| __fput+0x369/0x890 fs/file_table.c:510
+| task_work_run+0x160/0x1d0 kernel/task_work.c:233
+| get_signal+0xf5b/0x1120 kernel/signal.c:2810
+| arch_do_signal_or_restart+0x4d/0x600 arch/x86/kernel/signal.c:337
+| __exit_to_user_mode_loop kernel/entry/common.c:64 [inline]
+| exit_to_user_mode_loop+0x85/0x510 kernel/entry/common.c:98
+| do_syscall_64+0x263/0x3d0 arch/x86/entry/syscall_64.c:100
+| entry_SYSCALL_64_after_hwframe+0x77/0x7f
+|
+| The buggy address belongs to the object at ffff8881298d9400
+| which belongs to the cache kmalloc-512 of size 512
+| The buggy address is located 336 bytes inside of
+| freed 512-byte region [ffff8881298d9400, ffff8881298d9600)
+
+Fix it by having chan->conn hold a reference to l2cap_conn (via
+l2cap_conn_get) when the channel is added to the connection, and
+releasing it in the channel destructor. This ensures the l2cap_conn
+remains alive as long as the channel exists.
+
+A new FLAG_DEL channel flag is introduced to indicate that the channel
+has been deleted from its connection. l2cap_chan_del() atomically sets
+this flag using test_and_set_bit() instead of setting chan->conn to
+NULL. All asynchronous workers (l2cap_chan_timeout, l2cap_ack_timeout,
+l2cap_monitor_timeout, l2cap_retrans_timeout) and l2cap_chan_send()
+check FLAG_DEL to determine whether the channel has been torn down,
+rather than testing chan->conn for NULL.
+
+Fixes: 8c8e620467a7 ("Bluetooth: L2CAP: use chan timer to close channels in cleanup_listen()")
+Cc: <stable@vger.kernel.org>
+Cc: Siwei Zhang <oss@fourdim.xyz>
+Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Assisted-by: Gemini:gemini-3.1-pro-preview
+Reported-by: https://sashiko.dev/#/patchset/20260521021249.3258069-1-oss%40fourdim.xyz
+Signed-off-by: Marco Elver <elver@google.com>
+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 | 1 +
+ net/bluetooth/l2cap_core.c | 34 ++++++++++++++++++++--------------
+ 2 files changed, 21 insertions(+), 14 deletions(-)
+
+--- a/include/net/bluetooth/l2cap.h
++++ b/include/net/bluetooth/l2cap.h
+@@ -748,6 +748,7 @@ enum {
+ FLAG_ECRED_CONN_REQ_SENT,
+ FLAG_PENDING_SECURITY,
+ FLAG_HOLD_HCI_CONN,
++ FLAG_DEL,
+ };
+
+ /* Lock nesting levels for L2CAP channels. We need these because lockdep
+--- a/net/bluetooth/l2cap_core.c
++++ b/net/bluetooth/l2cap_core.c
+@@ -411,7 +411,7 @@ static void l2cap_chan_timeout(struct wo
+
+ BT_DBG("chan %p state %s", chan, state_to_string(chan->state));
+
+- if (!conn) {
++ if (test_bit(FLAG_DEL, &chan->flags)) {
+ l2cap_chan_put(chan);
+ return;
+ }
+@@ -422,6 +422,9 @@ static void l2cap_chan_timeout(struct wo
+ */
+ l2cap_chan_lock(chan);
+
++ if (test_bit(FLAG_DEL, &chan->flags))
++ goto unlock;
++
+ if (chan->state == BT_CONNECTED || chan->state == BT_CONFIG)
+ reason = ECONNREFUSED;
+ else if (chan->state == BT_CONNECT &&
+@@ -434,10 +437,10 @@ static void l2cap_chan_timeout(struct wo
+
+ chan->ops->close(chan);
+
++unlock:
+ l2cap_chan_unlock(chan);
+- l2cap_chan_put(chan);
+-
+ mutex_unlock(&conn->lock);
++ l2cap_chan_put(chan);
+ }
+
+ struct l2cap_chan *l2cap_chan_create(void)
+@@ -490,6 +493,9 @@ static void l2cap_chan_destroy(struct kr
+ list_del(&chan->global_l);
+ write_unlock(&chan_list_lock);
+
++ if (chan->conn)
++ l2cap_conn_put(chan->conn);
++
+ kfree(chan);
+ }
+
+@@ -593,7 +599,7 @@ void __l2cap_chan_add(struct l2cap_conn
+
+ conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM;
+
+- chan->conn = conn;
++ chan->conn = l2cap_conn_get(conn);
+
+ switch (chan->chan_type) {
+ case L2CAP_CHAN_CONN_ORIENTED:
+@@ -648,30 +654,26 @@ void l2cap_chan_add(struct l2cap_conn *c
+
+ void l2cap_chan_del(struct l2cap_chan *chan, int err)
+ {
+- struct l2cap_conn *conn = chan->conn;
+-
+ __clear_chan_timer(chan);
+
+- BT_DBG("chan %p, conn %p, err %d, state %s", chan, conn, err,
++ BT_DBG("chan %p, err %d, state %s", chan, err,
+ state_to_string(chan->state));
+
+ chan->ops->teardown(chan, err);
+
+- if (conn) {
++ if (!test_and_set_bit(FLAG_DEL, &chan->flags)) {
+ /* Delete from channel list */
+ list_del(&chan->list);
+
+ l2cap_chan_put(chan);
+
+- chan->conn = NULL;
+-
+ /* Reference was only held for non-fixed channels or
+ * fixed channels that explicitly requested it using the
+ * FLAG_HOLD_HCI_CONN flag.
+ */
+ if (chan->chan_type != L2CAP_CHAN_FIXED ||
+ test_bit(FLAG_HOLD_HCI_CONN, &chan->flags))
+- hci_conn_drop(conn->hcon);
++ hci_conn_drop(chan->conn->hcon);
+ }
+
+ if (test_bit(CONF_NOT_COMPLETE, &chan->conf_state))
+@@ -1886,7 +1888,7 @@ static void l2cap_monitor_timeout(struct
+
+ l2cap_chan_lock(chan);
+
+- if (!chan->conn) {
++ if (test_bit(FLAG_DEL, &chan->flags)) {
+ l2cap_chan_unlock(chan);
+ l2cap_chan_put(chan);
+ return;
+@@ -1907,7 +1909,7 @@ static void l2cap_retrans_timeout(struct
+
+ l2cap_chan_lock(chan);
+
+- if (!chan->conn) {
++ if (test_bit(FLAG_DEL, &chan->flags)) {
+ l2cap_chan_unlock(chan);
+ l2cap_chan_put(chan);
+ return;
+@@ -2522,7 +2524,7 @@ int l2cap_chan_send(struct l2cap_chan *c
+ int err;
+ struct sk_buff_head seg_queue;
+
+- if (!chan->conn)
++ if (test_bit(FLAG_DEL, &chan->flags))
+ return -ENOTCONN;
+
+ /* Connectionless channel */
+@@ -3119,12 +3121,16 @@ static void l2cap_ack_timeout(struct wor
+
+ l2cap_chan_lock(chan);
+
++ if (test_bit(FLAG_DEL, &chan->flags))
++ goto unlock;
++
+ frames_to_ack = __seq_offset(chan, chan->buffer_seq,
+ chan->last_acked_seq);
+
+ if (frames_to_ack)
+ l2cap_send_rr_or_rnr(chan, 0);
+
++unlock:
+ l2cap_chan_unlock(chan);
+ l2cap_chan_put(chan);
+ }
--- /dev/null
+From stable+bounces-275050-greg=kroah.com@vger.kernel.org Wed Jul 15 23:25:05 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 17:21:02 -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: <20260715212102.274128-2-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
+@@ -620,7 +620,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);
+@@ -884,9 +885,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)
+@@ -962,7 +964,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
+@@ -525,7 +525,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;
+@@ -539,6 +542,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);
+
+@@ -3969,6 +3997,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)
+ {
+@@ -4015,7 +4075,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;
+
+@@ -4033,8 +4093,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));
+@@ -4932,7 +4990,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;
+@@ -4947,8 +5005,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;
+@@ -5156,7 +5212,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;
+@@ -5171,8 +5227,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 */
+@@ -7440,14 +7494,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)
+@@ -1243,6 +1244,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.
+ */
+@@ -1253,13 +1271,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);
+ }
+@@ -1502,12 +1516,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);
+
+@@ -1515,25 +1530,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)
+@@ -1831,10 +1849,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);
+@@ -1866,30 +1881,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) {
+@@ -1915,7 +1912,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 */
+@@ -1934,10 +1931,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)
+@@ -1948,16 +1945,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;
+@@ -1967,6 +1955,7 @@ static int l2cap_sock_create(struct net
+ int kern)
+ {
+ struct sock *sk;
++ struct l2cap_chan *chan;
+
+ BT_DBG("sock %p", sock);
+
+@@ -1981,10 +1970,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
+@@ -3234,34 +3234,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 = {
+@@ -3332,7 +3317,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-275046-greg=kroah.com@vger.kernel.org Wed Jul 15 23:14:05 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 17:13:54 -0400
+Subject: Bluetooth: separate CIS_LINK and BIS_LINK link types
+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: <20260715211355.253617-2-sashal@kernel.org>
+
+From: Pauli Virtanen <pav@iki.fi>
+
+[ Upstream commit 23205562ffc8de20f57afdd984858cab29e77968 ]
+
+Use separate link type id for unicast and broadcast ISO connections.
+These connection types are handled with separate HCI commands, socket
+API is different, and hci_conn has union fields that are different in
+the two cases, so they shall not be mixed up.
+
+Currently in most places it is attempted to distinguish ucast by
+bacmp(&c->dst, BDADDR_ANY) but it is wrong as dst is set for bcast sink
+hci_conn in iso_conn_ready(). Additionally checking sync_handle might be
+OK, but depends on details of bcast conn configuration flow.
+
+To avoid complicating it, use separate link types.
+
+Fixes: f764a6c2c1e4 ("Bluetooth: ISO: Add broadcast support")
+Signed-off-by: Pauli Virtanen <pav@iki.fi>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+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 | 3 +-
+ include/net/bluetooth/hci_core.h | 48 +++++++++++++++++----------------------
+ net/bluetooth/hci_conn.c | 41 +++++++++++++++++++--------------
+ net/bluetooth/hci_core.c | 21 ++++++++++-------
+ net/bluetooth/hci_event.c | 24 ++++++++++---------
+ net/bluetooth/hci_sync.c | 16 ++++++++-----
+ net/bluetooth/iso.c | 4 +--
+ net/bluetooth/mgmt.c | 3 +-
+ 8 files changed, 87 insertions(+), 73 deletions(-)
+
+--- a/include/net/bluetooth/hci.h
++++ b/include/net/bluetooth/hci.h
+@@ -558,7 +558,8 @@ enum {
+ #define ESCO_LINK 0x02
+ /* Low Energy links do not have defined link type. Use invented one */
+ #define LE_LINK 0x80
+-#define ISO_LINK 0x82
++#define CIS_LINK 0x82
++#define BIS_LINK 0x83
+ #define INVALID_LINK 0xff
+
+ /* LMP features */
+--- a/include/net/bluetooth/hci_core.h
++++ b/include/net/bluetooth/hci_core.h
+@@ -998,7 +998,8 @@ static inline void hci_conn_hash_add(str
+ case ESCO_LINK:
+ h->sco_num++;
+ break;
+- case ISO_LINK:
++ case CIS_LINK:
++ case BIS_LINK:
+ h->iso_num++;
+ break;
+ }
+@@ -1024,7 +1025,8 @@ static inline void hci_conn_hash_del(str
+ case ESCO_LINK:
+ h->sco_num--;
+ break;
+- case ISO_LINK:
++ case CIS_LINK:
++ case BIS_LINK:
+ h->iso_num--;
+ break;
+ }
+@@ -1041,7 +1043,8 @@ static inline unsigned int hci_conn_num(
+ case SCO_LINK:
+ case ESCO_LINK:
+ return h->sco_num;
+- case ISO_LINK:
++ case CIS_LINK:
++ case BIS_LINK:
+ return h->iso_num;
+ default:
+ return 0;
+@@ -1102,7 +1105,7 @@ static inline struct hci_conn *hci_conn_
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (bacmp(&c->dst, ba) || c->type != ISO_LINK)
++ if (bacmp(&c->dst, ba) || c->type != BIS_LINK)
+ continue;
+
+ if (c->iso_qos.bcast.bis == bis) {
+@@ -1124,7 +1127,7 @@ hci_conn_hash_lookup_create_pa_sync(stru
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (c->type != ISO_LINK)
++ if (c->type != BIS_LINK)
+ continue;
+
+ if (!test_bit(HCI_CONN_CREATE_PA_SYNC, &c->flags))
+@@ -1150,8 +1153,8 @@ hci_conn_hash_lookup_per_adv_bis(struct
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (bacmp(&c->dst, ba) || c->type != ISO_LINK ||
+- !test_bit(HCI_CONN_PER_ADV, &c->flags))
++ if (bacmp(&c->dst, ba) || c->type != BIS_LINK ||
++ !test_bit(HCI_CONN_PER_ADV, &c->flags))
+ continue;
+
+ if (c->iso_qos.bcast.big == big &&
+@@ -1261,7 +1264,7 @@ static inline struct hci_conn *hci_conn_
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (c->type != ISO_LINK || !bacmp(&c->dst, BDADDR_ANY))
++ if (c->type != CIS_LINK)
+ continue;
+
+ /* Match CIG ID if set */
+@@ -1293,7 +1296,7 @@ static inline struct hci_conn *hci_conn_
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (c->type != ISO_LINK || !bacmp(&c->dst, BDADDR_ANY))
++ if (c->type != CIS_LINK)
+ continue;
+
+ if (handle == c->iso_qos.ucast.cig) {
+@@ -1316,17 +1319,7 @@ static inline struct hci_conn *hci_conn_
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (c->type != ISO_LINK)
+- continue;
+-
+- /* An ISO_LINK hcon with BDADDR_ANY as destination
+- * address is a Broadcast connection. A Broadcast
+- * slave connection is associated with a PA train,
+- * so the sync_handle can be used to differentiate
+- * from unicast.
+- */
+- if (bacmp(&c->dst, BDADDR_ANY) &&
+- c->sync_handle == HCI_SYNC_HANDLE_INVALID)
++ if (c->type != BIS_LINK)
+ continue;
+
+ if (handle == c->iso_qos.bcast.big) {
+@@ -1350,7 +1343,7 @@ hci_conn_hash_lookup_big_sync_pend(struc
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (c->type != ISO_LINK)
++ if (c->type != BIS_LINK)
+ continue;
+
+ if (handle == c->iso_qos.bcast.big && num_bis == c->num_bis) {
+@@ -1373,8 +1366,8 @@ hci_conn_hash_lookup_big_state(struct hc
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (bacmp(&c->dst, BDADDR_ANY) || c->type != ISO_LINK ||
+- c->state != state)
++ if (c->type != BIS_LINK || bacmp(&c->dst, BDADDR_ANY) ||
++ c->state != state)
+ continue;
+
+ if (handle == c->iso_qos.bcast.big) {
+@@ -1397,8 +1390,8 @@ hci_conn_hash_lookup_pa_sync_big_handle(
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (c->type != ISO_LINK ||
+- !test_bit(HCI_CONN_PA_SYNC, &c->flags))
++ if (c->type != BIS_LINK ||
++ !test_bit(HCI_CONN_PA_SYNC, &c->flags))
+ continue;
+
+ if (c->iso_qos.bcast.big == big) {
+@@ -1420,7 +1413,7 @@ hci_conn_hash_lookup_pa_sync_handle(stru
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (c->type != ISO_LINK)
++ if (c->type != BIS_LINK)
+ continue;
+
+ /* Ignore the listen hcon, we are looking
+@@ -2030,7 +2023,8 @@ static inline int hci_proto_connect_ind(
+ case ESCO_LINK:
+ return sco_connect_ind(hdev, bdaddr, flags);
+
+- case ISO_LINK:
++ case CIS_LINK:
++ case BIS_LINK:
+ return iso_connect_ind(hdev, bdaddr, flags);
+
+ default:
+--- a/net/bluetooth/hci_conn.c
++++ b/net/bluetooth/hci_conn.c
+@@ -785,7 +785,7 @@ static int hci_le_big_terminate(struct h
+ d->sync_handle = conn->sync_handle;
+
+ if (test_and_clear_bit(HCI_CONN_PA_SYNC, &conn->flags)) {
+- hci_conn_hash_list_flag(hdev, find_bis, ISO_LINK,
++ hci_conn_hash_list_flag(hdev, find_bis, BIS_LINK,
+ HCI_CONN_PA_SYNC, d);
+
+ if (!d->count)
+@@ -795,7 +795,7 @@ static int hci_le_big_terminate(struct h
+ }
+
+ if (test_and_clear_bit(HCI_CONN_BIG_SYNC, &conn->flags)) {
+- hci_conn_hash_list_flag(hdev, find_bis, ISO_LINK,
++ hci_conn_hash_list_flag(hdev, find_bis, BIS_LINK,
+ HCI_CONN_BIG_SYNC, d);
+
+ if (!d->count)
+@@ -885,9 +885,11 @@ static void cis_cleanup(struct hci_conn
+ /* Check if ISO connection is a CIS and remove CIG if there are
+ * no other connections using it.
+ */
+- hci_conn_hash_list_state(hdev, find_cis, ISO_LINK, BT_BOUND, &d);
+- hci_conn_hash_list_state(hdev, find_cis, ISO_LINK, BT_CONNECT, &d);
+- hci_conn_hash_list_state(hdev, find_cis, ISO_LINK, BT_CONNECTED, &d);
++ hci_conn_hash_list_state(hdev, find_cis, CIS_LINK, BT_BOUND, &d);
++ hci_conn_hash_list_state(hdev, find_cis, CIS_LINK, BT_CONNECT,
++ &d);
++ hci_conn_hash_list_state(hdev, find_cis, CIS_LINK, BT_CONNECTED,
++ &d);
+ if (d.count)
+ return;
+
+@@ -910,7 +912,8 @@ static struct hci_conn *__hci_conn_add(s
+ if (!hdev->acl_mtu)
+ return ERR_PTR(-ECONNREFUSED);
+ break;
+- case ISO_LINK:
++ case CIS_LINK:
++ case BIS_LINK:
+ if (hdev->iso_mtu)
+ /* Dedicated ISO Buffer exists */
+ break;
+@@ -975,7 +978,8 @@ static struct hci_conn *__hci_conn_add(s
+ hci_copy_identity_address(hdev, &conn->src, &conn->src_type);
+ conn->mtu = hdev->le_mtu ? hdev->le_mtu : hdev->acl_mtu;
+ break;
+- case ISO_LINK:
++ case CIS_LINK:
++ case BIS_LINK:
+ /* conn->src should reflect the local identity address */
+ hci_copy_identity_address(hdev, &conn->src, &conn->src_type);
+
+@@ -1071,7 +1075,8 @@ static void hci_conn_cleanup_child(struc
+ if (HCI_CONN_HANDLE_UNSET(conn->handle))
+ hci_conn_failed(conn, reason);
+ break;
+- case ISO_LINK:
++ case CIS_LINK:
++ case BIS_LINK:
+ if ((conn->state != BT_CONNECTED &&
+ !test_bit(HCI_CONN_CREATE_CIS, &conn->flags)) ||
+ test_bit(HCI_CONN_BIG_CREATED, &conn->flags))
+@@ -1146,7 +1151,8 @@ void hci_conn_del(struct hci_conn *conn)
+ hdev->acl_cnt += conn->sent;
+ } else {
+ /* Unacked ISO frames */
+- if (conn->type == ISO_LINK) {
++ if (conn->type == CIS_LINK ||
++ conn->type == BIS_LINK) {
+ if (hdev->iso_pkts)
+ hdev->iso_cnt += conn->sent;
+ else if (hdev->le_pkts)
+@@ -1531,7 +1537,7 @@ static struct hci_conn *hci_add_bis(stru
+ memcmp(conn->le_per_adv_data, base, base_len)))
+ return ERR_PTR(-EADDRINUSE);
+
+- conn = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_MASTER);
++ conn = hci_conn_add_unset(hdev, BIS_LINK, dst, HCI_ROLE_MASTER);
+ if (IS_ERR(conn))
+ return conn;
+
+@@ -1740,7 +1746,7 @@ static int hci_le_create_big(struct hci_
+ data.count = 0;
+
+ /* Create a BIS for each bound connection */
+- hci_conn_hash_list_state(hdev, bis_list, ISO_LINK,
++ hci_conn_hash_list_state(hdev, bis_list, BIS_LINK,
+ BT_BOUND, &data);
+
+ cp.handle = qos->bcast.big;
+@@ -1835,12 +1841,12 @@ static bool hci_le_set_cig_params(struct
+ for (data.cig = 0x00; data.cig < 0xf0; data.cig++) {
+ data.count = 0;
+
+- hci_conn_hash_list_state(hdev, find_cis, ISO_LINK,
++ hci_conn_hash_list_state(hdev, find_cis, CIS_LINK,
+ BT_CONNECT, &data);
+ if (data.count)
+ continue;
+
+- hci_conn_hash_list_state(hdev, find_cis, ISO_LINK,
++ hci_conn_hash_list_state(hdev, find_cis, CIS_LINK,
+ BT_CONNECTED, &data);
+ if (!data.count)
+ break;
+@@ -1892,7 +1898,8 @@ struct hci_conn *hci_bind_cis(struct hci
+ cis = hci_conn_hash_lookup_cis(hdev, dst, dst_type, qos->ucast.cig,
+ qos->ucast.cis);
+ if (!cis) {
+- cis = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_MASTER);
++ cis = hci_conn_add_unset(hdev, CIS_LINK, dst,
++ HCI_ROLE_MASTER);
+ if (IS_ERR(cis))
+ return cis;
+ cis->cleanup = cis_cleanup;
+@@ -1982,7 +1989,7 @@ bool hci_iso_setup_path(struct hci_conn
+
+ int hci_conn_check_create_cis(struct hci_conn *conn)
+ {
+- if (conn->type != ISO_LINK || !bacmp(&conn->dst, BDADDR_ANY))
++ if (conn->type != CIS_LINK)
+ return -EINVAL;
+
+ if (!conn->parent || conn->parent->state != BT_CONNECTED ||
+@@ -2082,7 +2089,7 @@ struct hci_conn *hci_pa_create_sync(stru
+
+ bt_dev_dbg(hdev, "dst %pMR type %d sid %d", dst, dst_type, sid);
+
+- conn = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_SLAVE);
++ conn = hci_conn_add_unset(hdev, BIS_LINK, dst, HCI_ROLE_SLAVE);
+ if (IS_ERR(conn))
+ return conn;
+
+@@ -2259,7 +2266,7 @@ struct hci_conn *hci_connect_bis(struct
+ * the start periodic advertising and create BIG commands have
+ * been queued
+ */
+- hci_conn_hash_list_state(hdev, bis_mark_per_adv, ISO_LINK,
++ hci_conn_hash_list_state(hdev, bis_mark_per_adv, BIS_LINK,
+ BT_BOUND, &data);
+
+ /* Queue start periodic advertising and create BIG */
+--- a/net/bluetooth/hci_core.c
++++ b/net/bluetooth/hci_core.c
+@@ -2959,12 +2959,13 @@ int hci_recv_frame(struct hci_dev *hdev,
+ break;
+ case HCI_ACLDATA_PKT:
+ /* Detect if ISO packet has been sent as ACL */
+- if (hci_conn_num(hdev, ISO_LINK)) {
++ if (hci_conn_num(hdev, CIS_LINK) ||
++ hci_conn_num(hdev, BIS_LINK)) {
+ __u16 handle = __le16_to_cpu(hci_acl_hdr(skb)->handle);
+ __u8 type;
+
+ type = hci_conn_lookup_type(hdev, hci_handle(handle));
+- if (type == ISO_LINK)
++ if (type == CIS_LINK || type == BIS_LINK)
+ hci_skb_pkt_type(skb) = HCI_ISODATA_PKT;
+ }
+ break;
+@@ -3399,7 +3400,8 @@ static inline void hci_quote_sent(struct
+ case LE_LINK:
+ cnt = hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt;
+ break;
+- case ISO_LINK:
++ case CIS_LINK:
++ case BIS_LINK:
+ cnt = hdev->iso_mtu ? hdev->iso_cnt :
+ hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt;
+ break;
+@@ -3413,7 +3415,7 @@ static inline void hci_quote_sent(struct
+ }
+
+ static struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type,
+- int *quote)
++ __u8 type2, int *quote)
+ {
+ struct hci_conn_hash *h = &hdev->conn_hash;
+ struct hci_conn *conn = NULL, *c;
+@@ -3425,7 +3427,8 @@ static struct hci_conn *hci_low_sent(str
+ rcu_read_lock();
+
+ list_for_each_entry_rcu(c, &h->list, list) {
+- if (c->type != type || skb_queue_empty(&c->data_q))
++ if ((c->type != type && c->type != type2) ||
++ skb_queue_empty(&c->data_q))
+ continue;
+
+ if (c->state != BT_CONNECTED && c->state != BT_CONFIG)
+@@ -3628,7 +3631,7 @@ static void hci_sched_sco(struct hci_dev
+ else
+ cnt = &hdev->sco_cnt;
+
+- while (*cnt && (conn = hci_low_sent(hdev, type, "e))) {
++ while (*cnt && (conn = hci_low_sent(hdev, type, type, "e))) {
+ while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
+ BT_DBG("skb %p len %d", skb, skb->len);
+ hci_send_frame(hdev, skb);
+@@ -3756,12 +3759,14 @@ static void hci_sched_iso(struct hci_dev
+
+ BT_DBG("%s", hdev->name);
+
+- if (!hci_conn_num(hdev, ISO_LINK))
++ if (!hci_conn_num(hdev, CIS_LINK) &&
++ !hci_conn_num(hdev, BIS_LINK))
+ return;
+
+ cnt = hdev->iso_pkts ? &hdev->iso_cnt :
+ hdev->le_pkts ? &hdev->le_cnt : &hdev->acl_cnt;
+- while (*cnt && (conn = hci_low_sent(hdev, ISO_LINK, "e))) {
++ while (*cnt && (conn = hci_low_sent(hdev, CIS_LINK, BIS_LINK,
++ "e))) {
+ while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
+ BT_DBG("skb %p len %d", skb, skb->len);
+ hci_send_frame(hdev, skb);
+--- a/net/bluetooth/hci_event.c
++++ b/net/bluetooth/hci_event.c
+@@ -3776,7 +3776,7 @@ static void hci_unbound_cis_failed(struc
+ lockdep_assert_held(&hdev->lock);
+
+ list_for_each_entry_safe(conn, tmp, &hdev->conn_hash.list, list) {
+- if (conn->type != ISO_LINK || !bacmp(&conn->dst, BDADDR_ANY) ||
++ if (conn->type != CIS_LINK ||
+ conn->state == BT_OPEN || conn->iso_qos.ucast.cig != cig)
+ continue;
+
+@@ -4453,7 +4453,8 @@ static void hci_num_comp_pkts_evt(struct
+
+ break;
+
+- case ISO_LINK:
++ case CIS_LINK:
++ case BIS_LINK:
+ if (hdev->iso_pkts) {
+ hdev->iso_cnt += count;
+ if (hdev->iso_cnt > hdev->iso_pkts)
+@@ -6420,7 +6421,8 @@ static void hci_le_pa_sync_estabilished_
+ conn->sync_handle = le16_to_cpu(ev->handle);
+ conn->sid = HCI_SID_INVALID;
+
+- mask |= hci_proto_connect_ind(hdev, &ev->bdaddr, ISO_LINK, &flags);
++ mask |= hci_proto_connect_ind(hdev, &ev->bdaddr, BIS_LINK,
++ &flags);
+ if (!(mask & HCI_LM_ACCEPT)) {
+ hci_le_pa_term_sync(hdev, ev->handle);
+ goto unlock;
+@@ -6430,7 +6432,7 @@ static void hci_le_pa_sync_estabilished_
+ goto unlock;
+
+ /* Add connection to indicate PA sync event */
+- pa_sync = hci_conn_add_unset(hdev, ISO_LINK, BDADDR_ANY,
++ pa_sync = hci_conn_add_unset(hdev, BIS_LINK, BDADDR_ANY,
+ HCI_ROLE_SLAVE);
+
+ if (IS_ERR(pa_sync))
+@@ -6461,7 +6463,7 @@ static void hci_le_per_adv_report_evt(st
+
+ hci_dev_lock(hdev);
+
+- mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, ISO_LINK, &flags);
++ mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, BIS_LINK, &flags);
+ if (!(mask & HCI_LM_ACCEPT))
+ goto unlock;
+
+@@ -6752,7 +6754,7 @@ static void hci_le_cis_estabilished_evt(
+ goto unlock;
+ }
+
+- if (conn->type != ISO_LINK) {
++ if (conn->type != CIS_LINK) {
+ bt_dev_err(hdev,
+ "Invalid connection link type handle 0x%4.4x",
+ handle);
+@@ -6870,7 +6872,7 @@ static void hci_le_cis_req_evt(struct hc
+ if (!acl)
+ goto unlock;
+
+- mask = hci_proto_connect_ind(hdev, &acl->dst, ISO_LINK, &flags);
++ mask = hci_proto_connect_ind(hdev, &acl->dst, CIS_LINK, &flags);
+ if (!(mask & HCI_LM_ACCEPT)) {
+ hci_le_reject_cis(hdev, ev->cis_handle);
+ goto unlock;
+@@ -6878,8 +6880,8 @@ 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_handle);
++ cis = hci_conn_add(hdev, CIS_LINK, &acl->dst,
++ HCI_ROLE_SLAVE, cis_handle);
+ if (IS_ERR(cis)) {
+ hci_le_reject_cis(hdev, ev->cis_handle);
+ goto unlock;
+@@ -7017,7 +7019,7 @@ static void hci_le_big_sync_established_
+ bt_dev_dbg(hdev, "ignore too large handle %u", handle);
+ continue;
+ }
+- bis = hci_conn_add(hdev, ISO_LINK, BDADDR_ANY,
++ bis = hci_conn_add(hdev, BIS_LINK, BDADDR_ANY,
+ HCI_ROLE_SLAVE, handle);
+ if (IS_ERR(bis))
+ continue;
+@@ -7076,7 +7078,7 @@ static void hci_le_big_info_adv_report_e
+
+ hci_dev_lock(hdev);
+
+- mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, ISO_LINK, &flags);
++ mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, BIS_LINK, &flags);
+ if (!(mask & HCI_LM_ACCEPT))
+ goto unlock;
+
+--- a/net/bluetooth/hci_sync.c
++++ b/net/bluetooth/hci_sync.c
+@@ -2979,7 +2979,7 @@ static int hci_le_set_ext_scan_param_syn
+ if (sent) {
+ struct hci_conn *conn;
+
+- conn = hci_conn_hash_lookup_ba(hdev, ISO_LINK,
++ conn = hci_conn_hash_lookup_ba(hdev, BIS_LINK,
+ &sent->bdaddr);
+ if (conn) {
+ struct bt_iso_qos *qos = &conn->iso_qos;
+@@ -5596,7 +5596,7 @@ static int hci_connect_cancel_sync(struc
+ if (conn->type == LE_LINK)
+ return hci_le_connect_cancel_sync(hdev, conn, reason);
+
+- if (conn->type == ISO_LINK) {
++ if (conn->type == CIS_LINK) {
+ /* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 4, Part E
+ * page 1857:
+ *
+@@ -5609,9 +5609,10 @@ static int hci_connect_cancel_sync(struc
+ return hci_disconnect_sync(hdev, conn, reason);
+
+ /* CIS with no Create CIS sent have nothing to cancel */
+- if (bacmp(&conn->dst, BDADDR_ANY))
+- return HCI_ERROR_LOCAL_HOST_TERM;
++ return HCI_ERROR_LOCAL_HOST_TERM;
++ }
+
++ if (conn->type == BIS_LINK) {
+ /* There is no way to cancel a BIS without terminating the BIG
+ * which is done later on connection cleanup.
+ */
+@@ -5673,9 +5674,12 @@ static int hci_reject_conn_sync(struct h
+ {
+ struct hci_cp_reject_conn_req cp;
+
+- if (conn->type == ISO_LINK)
++ if (conn->type == CIS_LINK)
+ return hci_le_reject_cis_sync(hdev, conn, reason);
+
++ if (conn->type == BIS_LINK)
++ return -EINVAL;
++
+ if (conn->type == SCO_LINK || conn->type == ESCO_LINK)
+ return hci_reject_sco_sync(hdev, conn, reason);
+
+@@ -7039,7 +7043,7 @@ static void create_pa_complete(struct hc
+ goto unlock;
+
+ /* Add connection to indicate PA sync error */
+- pa_sync = hci_conn_add_unset(hdev, ISO_LINK, BDADDR_ANY,
++ pa_sync = hci_conn_add_unset(hdev, BIS_LINK, BDADDR_ANY,
+ HCI_ROLE_SLAVE);
+
+ if (IS_ERR(pa_sync))
+--- a/net/bluetooth/iso.c
++++ b/net/bluetooth/iso.c
+@@ -2211,7 +2211,7 @@ done:
+
+ static void iso_connect_cfm(struct hci_conn *hcon, __u8 status)
+ {
+- if (hcon->type != ISO_LINK) {
++ if (hcon->type != CIS_LINK && hcon->type != BIS_LINK) {
+ if (hcon->type != LE_LINK)
+ return;
+
+@@ -2252,7 +2252,7 @@ static void iso_connect_cfm(struct hci_c
+
+ static void iso_disconn_cfm(struct hci_conn *hcon, __u8 reason)
+ {
+- if (hcon->type != ISO_LINK)
++ if (hcon->type != CIS_LINK && hcon->type != BIS_LINK)
+ return;
+
+ BT_DBG("hcon %p reason %d", hcon, reason);
+--- a/net/bluetooth/mgmt.c
++++ b/net/bluetooth/mgmt.c
+@@ -3240,7 +3240,8 @@ failed:
+ static u8 link_to_bdaddr(u8 link_type, u8 addr_type)
+ {
+ switch (link_type) {
+- case ISO_LINK:
++ case CIS_LINK:
++ case BIS_LINK:
+ case LE_LINK:
+ switch (addr_type) {
+ case ADDR_LE_DEV_PUBLIC:
--- /dev/null
+From stable+bounces-277343-greg=kroah.com@vger.kernel.org Sat Jul 18 17:13:47 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 18 Jul 2026 11:13:29 -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: <20260718151329.3271163-4-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 | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/kernel/bpf/lpm_trie.c
++++ b/kernel/bpf/lpm_trie.c
+@@ -245,7 +245,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;
+@@ -279,7 +279,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)
+@@ -362,7 +362,7 @@ static long trie_update_elem(struct bpf_
+ */
+ slot = &trie->root;
+
+- while ((node = rcu_dereference(*slot))) {
++ while ((node = rcu_dereference_protected(*slot, 1))) {
+ matchlen = longest_prefix_match(trie, node, key);
+
+ if (node->prefixlen != matchlen ||
+@@ -483,7 +483,7 @@ static long trie_delete_elem(struct bpf_
+ trim = &trie->root;
+ trim2 = trim;
+ parent = NULL;
+- while ((node = rcu_dereference(*trim))) {
++ while ((node = rcu_dereference_protected(*trim, 1))) {
+ matchlen = longest_prefix_match(trie, node, key);
+
+ if (node->prefixlen != matchlen ||
--- /dev/null
+From stable+bounces-277341-greg=kroah.com@vger.kernel.org Sat Jul 18 17:13:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 18 Jul 2026 11:13:27 -0400
+Subject: bpf, arm64, powerpc: Add bpf_jit_bypass_spec_v1/v4()
+To: stable@vger.kernel.org
+Cc: Luis Gerhorst <luis.gerhorst@fau.de>, Hari Bathini <hbathini@linux.ibm.com>, Henriette Herzog <henriette.herzog@rub.de>, Maximilian Ott <ott@cs.fau.de>, Milan Stephan <milan.stephan@fau.de>, Alexei Starovoitov <ast@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260718151329.3271163-2-sashal@kernel.org>
+
+From: Luis Gerhorst <luis.gerhorst@fau.de>
+
+[ Upstream commit 03c68a0f8c68936a0bb915b030693923784724cb ]
+
+JITs can set bpf_jit_bypass_spec_v1/v4() if they want the verifier to
+skip analysis/patching for the respective vulnerability. For v4, this
+will reduce the number of barriers the verifier inserts. For v1, it
+allows more programs to be accepted.
+
+The primary motivation for this is to not regress unpriv BPF's
+performance on ARM64 in a future commit where BPF_NOSPEC is also used
+against Spectre v1.
+
+This has the user-visible change that v1-induced rejections on
+non-vulnerable PowerPC CPUs are avoided.
+
+For now, this does not change the semantics of BPF_NOSPEC. It is still a
+v4-only barrier and must not be implemented if bypass_spec_v4 is always
+true for the arch. Changing it to a v1 AND v4-barrier is done in a
+future commit.
+
+As an alternative to bypass_spec_v1/v4, one could introduce NOSPEC_V1
+AND NOSPEC_V4 instructions and allow backends to skip their lowering as
+suggested by commit f5e81d111750 ("bpf: Introduce BPF nospec instruction
+for mitigating Spectre v4"). Adding bpf_jit_bypass_spec_v1/v4() was
+found to be preferable for the following reason:
+
+* bypass_spec_v1/v4 benefits non-vulnerable CPUs: Always performing the
+ same analysis (not taking into account whether the current CPU is
+ vulnerable), needlessly restricts users of CPUs that are not
+ vulnerable. The only use case for this would be portability-testing,
+ but this can later be added easily when needed by allowing users to
+ force bypass_spec_v1/v4 to false.
+
+* Portability is still acceptable: Directly disabling the analysis
+ instead of skipping the lowering of BPF_NOSPEC(_V1/V4) might allow
+ programs on non-vulnerable CPUs to be accepted while the program will
+ be rejected on vulnerable CPUs. With the fallback to speculation
+ barriers for Spectre v1 implemented in a future commit, this will only
+ affect programs that do variable stack-accesses or are very complex.
+
+For PowerPC, the SEC_FTR checking in bpf_jit_bypass_spec_v4() is based
+on the check that was previously located in the BPF_NOSPEC case.
+
+For LoongArch, it would likely be safe to set both
+bpf_jit_bypass_spec_v1() and _v4() according to
+commit a6f6a95f2580 ("LoongArch, bpf: Fix jit to skip speculation
+barrier opcode"). This is omitted here as I am unable to do any testing
+for LoongArch.
+
+Hari's ack concerns the PowerPC part only.
+
+Signed-off-by: Luis Gerhorst <luis.gerhorst@fau.de>
+Acked-by: Hari Bathini <hbathini@linux.ibm.com>
+Cc: Henriette Herzog <henriette.herzog@rub.de>
+Cc: Maximilian Ott <ott@cs.fau.de>
+Cc: Milan Stephan <milan.stephan@fau.de>
+Link: https://lore.kernel.org/r/20250603211318.337474-1-luis.gerhorst@fau.de
+Signed-off-by: Alexei Starovoitov <ast@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>
+---
+ arch/arm64/net/bpf_jit_comp.c | 21 ++++++++++++---------
+ arch/powerpc/net/bpf_jit_comp64.c | 21 +++++++++++++++++----
+ include/linux/bpf.h | 11 +++++++++--
+ kernel/bpf/core.c | 15 +++++++++++++++
+ 4 files changed, 53 insertions(+), 15 deletions(-)
+
+--- a/arch/arm64/net/bpf_jit_comp.c
++++ b/arch/arm64/net/bpf_jit_comp.c
+@@ -1558,15 +1558,7 @@ emit_cond_jmp:
+
+ /* speculation barrier */
+ case BPF_ST | BPF_NOSPEC:
+- /*
+- * Nothing required here.
+- *
+- * In case of arm64, we rely on the firmware mitigation of
+- * Speculative Store Bypass as controlled via the ssbd kernel
+- * parameter. Whenever the mitigation is enabled, it works
+- * for all of the kernel code with no need to provide any
+- * additional instructions.
+- */
++ /* See bpf_jit_bypass_spec_v4() */
+ break;
+
+ /* ST: *(size *)(dst + off) = imm */
+@@ -2730,6 +2722,17 @@ bool bpf_jit_supports_percpu_insn(void)
+ return true;
+ }
+
++bool bpf_jit_bypass_spec_v4(void)
++{
++ /* In case of arm64, we rely on the firmware mitigation of Speculative
++ * Store Bypass as controlled via the ssbd kernel parameter. Whenever
++ * the mitigation is enabled, it works for all of the kernel code with
++ * no need to provide any additional instructions. Therefore, skip
++ * inserting nospec insns against Spectre v4.
++ */
++ return true;
++}
++
+ bool bpf_jit_inlines_helper_call(s32 imm)
+ {
+ switch (imm) {
+--- a/arch/powerpc/net/bpf_jit_comp64.c
++++ b/arch/powerpc/net/bpf_jit_comp64.c
+@@ -440,6 +440,23 @@ static int bpf_jit_emit_tail_call(u32 *i
+ return 0;
+ }
+
++bool bpf_jit_bypass_spec_v1(void)
++{
++#if defined(CONFIG_PPC_E500) || defined(CONFIG_PPC_BOOK3S_64)
++ return !(security_ftr_enabled(SEC_FTR_FAVOUR_SECURITY) &&
++ security_ftr_enabled(SEC_FTR_BNDS_CHK_SPEC_BAR));
++#else
++ return true;
++#endif
++}
++
++bool bpf_jit_bypass_spec_v4(void)
++{
++ return !(security_ftr_enabled(SEC_FTR_FAVOUR_SECURITY) &&
++ security_ftr_enabled(SEC_FTR_STF_BARRIER) &&
++ stf_barrier_type_get() != STF_BARRIER_NONE);
++}
++
+ /*
+ * We spill into the redzone always, even if the bpf program has its own stackframe.
+ * Offsets hardcoded based on BPF_PPC_STACK_SAVE -- see bpf_jit_stack_local()
+@@ -864,10 +881,6 @@ emit_clear:
+ * BPF_ST NOSPEC (speculation barrier)
+ */
+ case BPF_ST | BPF_NOSPEC:
+- if (!security_ftr_enabled(SEC_FTR_FAVOUR_SECURITY) ||
+- !security_ftr_enabled(SEC_FTR_STF_BARRIER))
+- break;
+-
+ switch (stf_barrier) {
+ case STF_BARRIER_EIEIO:
+ EMIT(PPC_RAW_EIEIO() | 0x02000000);
+--- a/include/linux/bpf.h
++++ b/include/linux/bpf.h
+@@ -2216,6 +2216,9 @@ bpf_prog_run_array_uprobe(const struct b
+ return ret;
+ }
+
++bool bpf_jit_bypass_spec_v1(void);
++bool bpf_jit_bypass_spec_v4(void);
++
+ #ifdef CONFIG_BPF_SYSCALL
+ DECLARE_PER_CPU(int, bpf_prog_active);
+ extern struct mutex bpf_stats_enabled_mutex;
+@@ -2386,12 +2389,16 @@ static inline bool bpf_allow_uninit_stac
+
+ static inline bool bpf_bypass_spec_v1(const struct bpf_token *token)
+ {
+- return cpu_mitigations_off() || bpf_token_capable(token, CAP_PERFMON);
++ return bpf_jit_bypass_spec_v1() ||
++ cpu_mitigations_off() ||
++ bpf_token_capable(token, CAP_PERFMON);
+ }
+
+ static inline bool bpf_bypass_spec_v4(const struct bpf_token *token)
+ {
+- return cpu_mitigations_off() || bpf_token_capable(token, CAP_PERFMON);
++ return bpf_jit_bypass_spec_v4() ||
++ cpu_mitigations_off() ||
++ bpf_token_capable(token, CAP_PERFMON);
+ }
+
+ int bpf_map_new_fd(struct bpf_map *map, int flags);
+--- a/kernel/bpf/core.c
++++ b/kernel/bpf/core.c
+@@ -3088,6 +3088,21 @@ bool __weak bpf_jit_needs_zext(void)
+ return false;
+ }
+
++/* By default, enable the verifier's mitigations against Spectre v1 and v4 for
++ * all archs. The value returned must not change at runtime as there is
++ * currently no support for reloading programs that were loaded without
++ * mitigations.
++ */
++bool __weak bpf_jit_bypass_spec_v1(void)
++{
++ return false;
++}
++
++bool __weak bpf_jit_bypass_spec_v4(void)
++{
++ return false;
++}
++
+ /* Return true if the JIT inlines the call to the helper corresponding to
+ * the imm.
+ *
--- /dev/null
+From stable+bounces-277342-greg=kroah.com@vger.kernel.org Sat Jul 18 17:13:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 18 Jul 2026 11:13:28 -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: <20260718151329.3271163-3-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 ++++--------
+ 4 files changed, 14 insertions(+), 25 deletions(-)
+
+--- a/include/linux/bpf.h
++++ b/include/linux/bpf.h
+@@ -2219,6 +2219,9 @@ bpf_prog_run_array_uprobe(const struct b
+ bool bpf_jit_bypass_spec_v1(void);
+ bool bpf_jit_bypass_spec_v4(void);
+
++#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
+@@ -18,9 +18,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
+@@ -685,8 +685,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;
+
+@@ -1122,8 +1121,7 @@ static long htab_map_update_elem(struct
+ /* 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;
+
+@@ -1240,8 +1238,7 @@ static long htab_lru_map_update_elem(str
+ /* 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;
+
+@@ -1309,8 +1306,7 @@ static long __htab_percpu_map_update_ele
+ /* 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;
+
+@@ -1364,8 +1360,7 @@ static long __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;
+
+@@ -1442,8 +1437,7 @@ static long htab_map_delete_elem(struct
+ 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;
+
+@@ -1478,8 +1472,7 @@ static long htab_lru_map_delete_elem(str
+ 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
+@@ -37,8 +37,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);
+ }
+
+@@ -54,8 +53,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);
+ }
+
+@@ -72,8 +70,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);
+ }
+
+@@ -129,8 +126,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);
+ }
+
--- /dev/null
+From stable+bounces-277340-greg=kroah.com@vger.kernel.org Sat Jul 18 17:13:36 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 18 Jul 2026 11:13:26 -0400
+Subject: bpf: Convert lpm_trie.c to rqspinlock
+To: stable@vger.kernel.org
+Cc: Kumar Kartikeya Dwivedi <memxor@gmail.com>, Alexei Starovoitov <ast@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260718151329.3271163-1-sashal@kernel.org>
+
+From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
+
+[ Upstream commit 47979314c0fe245ed54306e2f91b3f819c7c0f9f ]
+
+Convert all LPM trie usage of raw_spinlock to rqspinlock.
+
+Note that rcu_dereference_protected in trie_delete_elem is switched over
+to plain rcu_dereference, the RCU read lock should be held from BPF
+program side or eBPF syscall path, and the trie->lock is just acquired
+before the dereference. It is not clear the reason the protected variant
+was used from the commit history, but the above reasoning makes sense so
+switch over.
+
+Closes: https://lore.kernel.org/lkml/000000000000adb08b061413919e@google.com
+Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
+Link: https://lore.kernel.org/r/20250316040541.108729-22-memxor@gmail.com
+Signed-off-by: Alexei Starovoitov <ast@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>
+---
+ kernel/bpf/lpm_trie.c | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+--- a/kernel/bpf/lpm_trie.c
++++ b/kernel/bpf/lpm_trie.c
+@@ -15,6 +15,7 @@
+ #include <net/ipv6.h>
+ #include <uapi/linux/btf.h>
+ #include <linux/btf_ids.h>
++#include <linux/bpf_local_storage.h>
+
+ /* Intermediate node */
+ #define LPM_TREE_NODE_FLAG_IM BIT(0)
+@@ -361,8 +362,7 @@ static long trie_update_elem(struct bpf_
+ */
+ slot = &trie->root;
+
+- while ((node = rcu_dereference_protected(*slot,
+- lockdep_is_held(&trie->lock)))) {
++ while ((node = rcu_dereference(*slot))) {
+ matchlen = longest_prefix_match(trie, node, key);
+
+ if (node->prefixlen != matchlen ||
+@@ -483,8 +483,7 @@ static long trie_delete_elem(struct bpf_
+ trim = &trie->root;
+ trim2 = trim;
+ parent = NULL;
+- while ((node = rcu_dereference_protected(
+- *trim, lockdep_is_held(&trie->lock)))) {
++ while ((node = rcu_dereference(*trim))) {
+ matchlen = longest_prefix_match(trie, node, key);
+
+ if (node->prefixlen != matchlen ||
--- /dev/null
+From stable+bounces-278144-greg=kroah.com@vger.kernel.org Mon Jul 20 19:39:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 12:51:08 -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: <20260720165108.2526667-3-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/btrfs_inode.h | 2 +
+ fs/btrfs/file.c | 27 +++--------------------
+ fs/btrfs/inode.c | 57 ++++++++++++++++++++++++++++++++++++++++++-------
+ fs/btrfs/reflink.c | 5 ----
+ 4 files changed, 57 insertions(+), 34 deletions(-)
+
+--- a/fs/btrfs/btrfs_inode.h
++++ b/fs/btrfs/btrfs_inode.h
+@@ -547,6 +547,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 */
+--- a/fs/btrfs/file.c
++++ b/fs/btrfs/file.c
+@@ -150,16 +150,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);
+-
+- ret = btrfs_set_extent_delalloc(inode, start_pos, end_of_last_block,
+- extra_bits, cached);
++ ret = btrfs_reset_extent_delalloc(inode, start_pos, end_of_last_block,
++ extra_bits, cached);
+ if (ret)
+ return ret;
+
+@@ -1997,19 +1989,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/inode.c
++++ b/fs/btrfs/inode.c
+@@ -2799,7 +2799,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)) {
+@@ -2822,6 +2826,50 @@ int btrfs_set_extent_delalloc(struct btr
+ EXTENT_DELALLOC | extra_bits, cached_state);
+ }
+
++/*
++ * 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);
++}
++
+ 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,
+@@ -4745,12 +4793,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;
+--- a/fs/btrfs/reflink.c
++++ b/fs/btrfs/reflink.c
+@@ -95,10 +95,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-278120-greg=kroah.com@vger.kernel.org Mon Jul 20 18:07:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 11:56:32 -0400
+Subject: btrfs: concentrate the error handling of submit_one_sector()
+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: <20260720155635.2425597-1-sashal@kernel.org>
+
+From: Qu Wenruo <wqu@suse.com>
+
+[ Upstream commit 44820d80026e0b509007d41c83d42f1213ee8589 ]
+
+Currently submit_one_sector() has only one failure path from
+btrfs_get_extent().
+
+However the error handling is split into two parts, one inside
+submit_one_sector(), which clears the dirty flag and finishes the
+writeback for the fs block.
+
+The other part is to submit any remaining bio inside bio_ctrl and mark
+the ordered extent finished for the fs block.
+
+There is no special reason that we must split the error handling, let's
+just concentrate all the error handling into submit_one_sector().
+
+Reviewed-by: Boris Burkov <boris@bur.io>
+Signed-off-by: Qu Wenruo <wqu@suse.com>
+Reviewed-by: David Sterba <dsterba@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Stable-dep-of: 66ff4d366e7e ("btrfs: fix false IO failure after falling back to buffered write")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/btrfs/extent_io.c | 46 ++++++++++++++++++++++++++++------------------
+ 1 file changed, 28 insertions(+), 18 deletions(-)
+
+--- a/fs/btrfs/extent_io.c
++++ b/fs/btrfs/extent_io.c
+@@ -1417,7 +1417,7 @@ out:
+
+ /*
+ * Return 0 if we have submitted or queued the sector for submission.
+- * Return <0 for critical errors.
++ * Return <0 for critical errors, and the involved sector will be cleaned up.
+ *
+ * Caller should make sure filepos < i_size and handle filepos >= i_size case.
+ */
+@@ -1440,8 +1440,31 @@ static int submit_one_sector(struct btrf
+ ASSERT(filepos < i_size);
+
+ em = btrfs_get_extent(inode, NULL, filepos, sectorsize);
+- if (IS_ERR(em))
+- return PTR_ERR_OR_ZERO(em);
++ if (IS_ERR(em)) {
++ /*
++ * bio_ctrl may contain a bio crossing several folios.
++ * Submit it immediately so that the bio has a chance
++ * to finish normally, other than marked as error.
++ */
++ submit_one_bio(bio_ctrl);
++
++ /*
++ * When submission failed, we should still clear the folio dirty.
++ * Or the folio will be written back again but without any
++ * ordered extent.
++ */
++ btrfs_folio_clear_dirty(fs_info, folio, filepos, sectorsize);
++ btrfs_folio_set_writeback(fs_info, folio, filepos, sectorsize);
++ btrfs_folio_clear_writeback(fs_info, folio, filepos, sectorsize);
++
++ /*
++ * Since there is no bio submitted to finish the ordered
++ * extent, we have to manually finish this sector.
++ */
++ btrfs_mark_ordered_io_finished(inode, folio, filepos,
++ fs_info->sectorsize, false);
++ return PTR_ERR(em);
++ }
+
+ extent_offset = filepos - em->start;
+ em_end = extent_map_end(em);
+@@ -1559,19 +1582,6 @@ static noinline_for_stack int extent_wri
+ }
+ ret = submit_one_sector(inode, folio, cur, bio_ctrl, i_size);
+ if (unlikely(ret < 0)) {
+- /*
+- * bio_ctrl may contain a bio crossing several folios.
+- * Submit it immediately so that the bio has a chance
+- * to finish normally, other than marked as error.
+- */
+- submit_one_bio(bio_ctrl);
+- /*
+- * Failed to grab the extent map which should be very rare.
+- * Since there is no bio submitted to finish the ordered
+- * extent, we have to manually finish this sector.
+- */
+- btrfs_mark_ordered_io_finished(inode, folio, cur,
+- fs_info->sectorsize, false);
+ if (!found_error)
+ found_error = ret;
+ continue;
+@@ -1587,8 +1597,8 @@ static noinline_for_stack int extent_wri
+ * Here we set writeback and clear for the range. If the full folio
+ * is no longer dirty then we clear the PAGECACHE_TAG_DIRTY tag.
+ *
+- * If we hit any error, the corresponding sector will still be dirty
+- * thus no need to clear PAGECACHE_TAG_DIRTY.
++ * If we hit any error, the corresponding sector will have its dirty
++ * flag cleared and writeback finished, thus no need to handle the error case.
+ */
+ if (!submitted_io && !found_error) {
+ btrfs_folio_set_writeback(fs_info, folio, start, len);
--- /dev/null
+From stable+bounces-278124-greg=kroah.com@vger.kernel.org Mon Jul 20 18:07:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 11:56:35 -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: <20260720155635.2425597-4-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/direct-io.c | 17 ++++++++++++++---
+ fs/btrfs/inode.c | 6 +-----
+ fs/btrfs/ordered-data.c | 12 ++++++++++++
+ fs/btrfs/ordered-data.h | 2 ++
+ 4 files changed, 29 insertions(+), 8 deletions(-)
+
+--- a/fs/btrfs/direct-io.c
++++ b/fs/btrfs/direct-io.c
+@@ -623,12 +623,23 @@ static int btrfs_dio_iomap_end(struct in
+ if (submitted < length) {
+ pos += submitted;
+ length -= submitted;
+- if (write)
++ if (write) {
++ /*
++ * We have a short write, if there is any range
++ * that is submitted properly, that part will have
++ * its own OE split from the original one.
++ *
++ * So for the OE at dio_data->ordered, it's the part
++ * that is not submitted, and should be marked
++ * as fully truncated.
++ */
++ btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0);
+ btrfs_finish_ordered_extent(dio_data->ordered,
+- pos, length, false);
+- else
++ pos, length, true);
++ } else {
+ unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos,
+ pos + length - 1, NULL);
++ }
+ ret = -ENOTBLK;
+ }
+ if (write) {
+--- a/fs/btrfs/inode.c
++++ b/fs/btrfs/inode.c
+@@ -7388,11 +7388,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
+@@ -329,6 +329,18 @@ void btrfs_mark_ordered_extent_error(str
+ mapping_set_error(ordered->inode->vfs_inode.i_mapping, -EIO);
+ }
+
++void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered,
++ u64 truncate_len)
++{
++ struct btrfs_inode *inode = 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
+@@ -213,6 +213,8 @@ bool btrfs_try_lock_ordered_range(struct
+ struct btrfs_ordered_extent *btrfs_split_ordered_extent(
+ struct btrfs_ordered_extent *ordered, u64 len);
+ void btrfs_mark_ordered_extent_error(struct btrfs_ordered_extent *ordered);
++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-278170-greg=kroah.com@vger.kernel.org Mon Jul 20 19:33:41 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 13:27:30 -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: <20260720172730.2757976-5-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/direct-io.c | 43 ++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 42 insertions(+), 1 deletion(-)
+
+--- a/fs/btrfs/direct-io.c
++++ b/fs/btrfs/direct-io.c
+@@ -13,10 +13,12 @@
+
+ struct btrfs_dio_data {
+ ssize_t submitted;
++ loff_t old_isize;
+ struct extent_changeset *data_reserved;
+ struct btrfs_ordered_extent *ordered;
+ bool data_space_reserved;
+ bool nocow_done;
++ bool updated_isize;
+ };
+
+ struct btrfs_dio_private {
+@@ -227,6 +229,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;
+
+ /*
+@@ -340,8 +343,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);
+@@ -625,6 +634,38 @@ static int btrfs_dio_iomap_end(struct in
+ length -= submitted;
+ if (write) {
+ /*
++ * 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, if there is any range
+ * that is submitted properly, that part will have
+ * its own OE split from the original one.
--- /dev/null
+From stable+bounces-278122-greg=kroah.com@vger.kernel.org Mon Jul 20 18:37:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 11:56:34 -0400
+Subject: btrfs: remove folio parameter from ordered io related functions
+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: <20260720155635.2425597-3-sashal@kernel.org>
+
+From: Qu Wenruo <wqu@suse.com>
+
+[ Upstream commit a11d6912fdd9e57aff889ec97256b1d6b4e5bf06 ]
+
+Both functions btrfs_finish_ordered_extent() and
+btrfs_mark_ordered_io_finished() are accepting an optional folio
+parameter.
+
+That @folio is passed into can_finish_ordered_extent(), which later will
+test and clear the ordered flag for the involved range.
+
+However I do not think there is any other call site that can clear
+ordered flags of an page cache folio and can affect
+can_finish_ordered_extent().
+
+There are limited *_clear_ordered() callers out of
+can_finish_ordered_extent() function:
+
+- btrfs_migrate_folio()
+ This is completely unrelated, it's just migrating the ordered flag to
+ the new folio.
+
+- btrfs_cleanup_ordered_extents()
+ We manually clean the ordered flags of all involved folios, then call
+ btrfs_mark_ordered_io_finished() without a @folio parameter.
+ So it doesn't need and didn't pass a @folio parameter in the first
+ place.
+
+- btrfs_writepage_fixup_worker()
+ This function is going to be removed soon, and we should not hit that
+ function anymore.
+
+- btrfs_invalidate_folio()
+ This is the real call site we need to bother with.
+
+ If we already have a bio running, btrfs_finish_ordered_extent() in
+ end_bbio_data_write() will be executed first, as
+ btrfs_invalidate_folio() will wait for the writeback to finish.
+
+ Thus if there is a running bio, it will not see the range has
+ ordered flags, and just skip to the next range.
+
+ If there is no bio running, meaning the ordered extent is created but
+ the folio is not yet submitted.
+
+ In that case btrfs_invalidate_folio() will manually clear the folio
+ ordered range, but then manually finish the ordered extent with
+ btrfs_dec_test_ordered_pending() without bothering the folio ordered
+ flags.
+
+ Meaning if the OE range with folio ordered flags will be finished
+ manually without the need to call can_finish_ordered_extent().
+
+This means all can_finish_ordered_extent() call sites should get a range
+that has folio ordered flag set, thus the old "return false" branch
+should never be triggered.
+
+Now we can:
+
+- Remove the @folio parameter from involved functions
+ * btrfs_mark_ordered_io_finished()
+ * btrfs_finish_ordered_extent()
+
+ For call sites passing a @folio into those functions, let them
+ manually clear the ordered flag of involved folios.
+
+- Move btrfs_finish_ordered_extent() out of the loop in
+ end_bbio_data_write()
+
+ We only need to call btrfs_finish_ordered_extent() once per bbio,
+ not per folio.
+
+- Add an ASSERT() to make sure all folio ranges have ordered flags
+ It's only for end_bbio_data_write().
+
+ And we already have enough safe nets to catch over-accounting of ordered
+ extents.
+
+Reviewed-by: Filipe Manana <fdmanana@suse.com>
+Signed-off-by: Qu Wenruo <wqu@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Stable-dep-of: 66ff4d366e7e ("btrfs: fix false IO failure after falling back to buffered write")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/btrfs/compression.c | 2 +-
+ fs/btrfs/direct-io.c | 9 ++++-----
+ fs/btrfs/extent_io.c | 23 ++++++++++++++---------
+ fs/btrfs/inode.c | 6 ++++--
+ fs/btrfs/ordered-data.c | 29 +++++------------------------
+ fs/btrfs/ordered-data.h | 6 ++----
+ 6 files changed, 30 insertions(+), 45 deletions(-)
+
+--- a/fs/btrfs/compression.c
++++ b/fs/btrfs/compression.c
+@@ -316,7 +316,7 @@ static void btrfs_finish_compressed_writ
+ struct compressed_bio *cb =
+ container_of(work, struct compressed_bio, write_end_work);
+
+- btrfs_finish_ordered_extent(cb->bbio.ordered, NULL, cb->start, cb->len,
++ btrfs_finish_ordered_extent(cb->bbio.ordered, cb->start, cb->len,
+ cb->bbio.bio.bi_status == BLK_STS_OK);
+
+ if (cb->writeback)
+--- a/fs/btrfs/direct-io.c
++++ b/fs/btrfs/direct-io.c
+@@ -624,7 +624,7 @@ static int btrfs_dio_iomap_end(struct in
+ pos += submitted;
+ length -= submitted;
+ if (write)
+- btrfs_finish_ordered_extent(dio_data->ordered, NULL,
++ btrfs_finish_ordered_extent(dio_data->ordered,
+ pos, length, false);
+ else
+ unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos,
+@@ -656,9 +656,8 @@ static void btrfs_dio_end_io(struct btrf
+ }
+
+ if (btrfs_op(bio) == BTRFS_MAP_WRITE) {
+- btrfs_finish_ordered_extent(bbio->ordered, NULL,
+- dip->file_offset, dip->bytes,
+- !bio->bi_status);
++ btrfs_finish_ordered_extent(bbio->ordered, dip->file_offset,
++ dip->bytes, !bio->bi_status);
+ } else {
+ unlock_dio_extent(&inode->io_tree, dip->file_offset,
+ dip->file_offset + dip->bytes - 1, NULL);
+@@ -736,7 +735,7 @@ static void btrfs_dio_submit_io(const st
+
+ ret = btrfs_extract_ordered_extent(bbio, dio_data->ordered);
+ if (ret) {
+- btrfs_finish_ordered_extent(dio_data->ordered, NULL,
++ btrfs_finish_ordered_extent(dio_data->ordered,
+ file_offset, dip->bytes,
+ !ret);
+ bio->bi_status = errno_to_blk_status(ret);
+--- a/fs/btrfs/extent_io.c
++++ b/fs/btrfs/extent_io.c
+@@ -485,6 +485,7 @@ static void end_bbio_data_write(struct b
+ int error = blk_status_to_errno(bio->bi_status);
+ struct folio_iter fi;
+ const u32 sectorsize = fs_info->sectorsize;
++ u32 bio_size = 0;
+
+ ASSERT(!bio_flagged(bio, BIO_CLONED));
+ bio_for_each_folio_all(fi, bio) {
+@@ -495,6 +496,7 @@ static void end_bbio_data_write(struct b
+ /* Only order 0 (single page) folios are allowed for data. */
+ ASSERT(folio_order(folio) == 0);
+
++ bio_size += len;
+ /* Our read/write should always be sector aligned. */
+ if (!IS_ALIGNED(fi.offset, sectorsize))
+ btrfs_err(fs_info,
+@@ -505,13 +507,15 @@ static void end_bbio_data_write(struct b
+ "incomplete page write with offset %zu and length %zu",
+ fi.offset, fi.length);
+
+- btrfs_finish_ordered_extent(bbio->ordered, folio, start, len,
+- !error);
+ if (error)
+ mapping_set_error(folio->mapping, error);
++
++ ASSERT(btrfs_folio_test_ordered(fs_info, folio, start, len));
++ btrfs_folio_clear_ordered(fs_info, folio, start, len);
+ btrfs_folio_clear_writeback(fs_info, folio, start, len);
+ }
+
++ btrfs_finish_ordered_extent(bbio->ordered, bbio->file_offset, bio_size, !error);
+ bio_put(bio);
+ }
+
+@@ -1384,7 +1388,8 @@ static noinline_for_stack int writepage_
+ u64 start = page_start + (start_bit << fs_info->sectorsize_bits);
+ u32 len = (end_bit - start_bit) << fs_info->sectorsize_bits;
+
+- btrfs_mark_ordered_io_finished(inode, folio, start, len, false);
++ btrfs_folio_clear_ordered(fs_info, folio, start, len);
++ btrfs_mark_ordered_io_finished(inode, start, len, false);
+ }
+ return ret;
+ }
+@@ -1460,6 +1465,7 @@ static int submit_one_sector(struct btrf
+ * ordered extent.
+ */
+ btrfs_folio_clear_dirty(fs_info, folio, filepos, sectorsize);
++ btrfs_folio_clear_ordered(fs_info, folio, filepos, sectorsize);
+ btrfs_folio_set_writeback(fs_info, folio, filepos, sectorsize);
+ btrfs_folio_clear_writeback(fs_info, folio, filepos, sectorsize);
+
+@@ -1467,8 +1473,8 @@ static int submit_one_sector(struct btrf
+ * Since there is no bio submitted to finish the ordered
+ * extent, we have to manually finish this sector.
+ */
+- btrfs_mark_ordered_io_finished(inode, folio, filepos,
+- fs_info->sectorsize, false);
++ btrfs_mark_ordered_io_finished(inode, filepos, fs_info->sectorsize,
++ false);
+ return PTR_ERR(em);
+ }
+
+@@ -1573,8 +1579,8 @@ static noinline_for_stack int extent_wri
+ spin_unlock_irqrestore(&inode->ordered_tree_lock, flags);
+ btrfs_put_ordered_extent(ordered);
+
+- btrfs_mark_ordered_io_finished(inode, folio, cur,
+- fs_info->sectorsize, true);
++ btrfs_folio_clear_ordered(fs_info, folio, cur, fs_info->sectorsize);
++ btrfs_mark_ordered_io_finished(inode, cur, fs_info->sectorsize, true);
+ /*
+ * This range is beyond i_size, thus we don't need to
+ * bother writing back.
+@@ -2415,8 +2421,7 @@ void extent_write_locked_range(struct in
+ * code is just in case, but shouldn't actually be run.
+ */
+ if (IS_ERR(folio)) {
+- btrfs_mark_ordered_io_finished(BTRFS_I(inode), NULL,
+- cur, cur_len, false);
++ btrfs_mark_ordered_io_finished(BTRFS_I(inode), cur, cur_len, false);
+ mapping_set_error(mapping, PTR_ERR(folio));
+ cur = cur_end + 1;
+ continue;
+--- a/fs/btrfs/inode.c
++++ b/fs/btrfs/inode.c
+@@ -456,7 +456,7 @@ static inline void btrfs_cleanup_ordered
+ }
+ }
+
+- return btrfs_mark_ordered_io_finished(inode, NULL, offset, bytes, false);
++ return btrfs_mark_ordered_io_finished(inode, offset, bytes, false);
+ }
+
+ static int btrfs_dirty_inode(struct btrfs_inode *inode);
+@@ -2937,7 +2937,9 @@ out_page:
+ * to reflect the errors and clean the page.
+ */
+ mapping_set_error(folio->mapping, ret);
+- btrfs_mark_ordered_io_finished(inode, folio, page_start,
++ btrfs_folio_clear_ordered(fs_info, folio, page_start,
++ folio_size(folio));
++ btrfs_mark_ordered_io_finished(inode, page_start,
+ folio_size(folio), !ret);
+ folio_clear_dirty_for_io(folio);
+ }
+--- a/fs/btrfs/ordered-data.c
++++ b/fs/btrfs/ordered-data.c
+@@ -338,30 +338,13 @@ static void finish_ordered_fn(struct btr
+ }
+
+ static bool can_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
+- struct folio *folio, u64 file_offset,
+- u64 len, bool uptodate)
++ u64 file_offset, u64 len, bool uptodate)
+ {
+ struct btrfs_inode *inode = ordered->inode;
+ struct btrfs_fs_info *fs_info = inode->root->fs_info;
+
+ lockdep_assert_held(&inode->ordered_tree_lock);
+
+- if (folio) {
+- ASSERT(folio->mapping);
+- ASSERT(folio_pos(folio) <= file_offset);
+- ASSERT(file_offset + len <= folio_pos(folio) + folio_size(folio));
+-
+- /*
+- * Ordered (Private2) bit indicates whether we still have
+- * pending io unfinished for the ordered extent.
+- *
+- * If there's no such bit, we need to skip to next range.
+- */
+- if (!btrfs_folio_test_ordered(fs_info, folio, file_offset, len))
+- return false;
+- btrfs_folio_clear_ordered(fs_info, folio, file_offset, len);
+- }
+-
+ /* Now we're fine to update the accounting. */
+ if (WARN_ON_ONCE(len > ordered->bytes_left)) {
+ btrfs_crit(fs_info,
+@@ -403,8 +386,7 @@ static void btrfs_queue_ordered_fn(struc
+ }
+
+ void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
+- struct folio *folio, u64 file_offset, u64 len,
+- bool uptodate)
++ u64 file_offset, u64 len, bool uptodate)
+ {
+ struct btrfs_inode *inode = ordered->inode;
+ unsigned long flags;
+@@ -413,7 +395,7 @@ void btrfs_finish_ordered_extent(struct
+ trace_btrfs_finish_ordered_extent(inode, file_offset, len, uptodate);
+
+ spin_lock_irqsave(&inode->ordered_tree_lock, flags);
+- ret = can_finish_ordered_extent(ordered, folio, file_offset, len,
++ ret = can_finish_ordered_extent(ordered, file_offset, len,
+ uptodate);
+ spin_unlock_irqrestore(&inode->ordered_tree_lock, flags);
+
+@@ -466,8 +448,7 @@ void btrfs_finish_ordered_extent(struct
+ * extent(s) covering it.
+ */
+ void btrfs_mark_ordered_io_finished(struct btrfs_inode *inode,
+- struct folio *folio, u64 file_offset,
+- u64 num_bytes, bool uptodate)
++ u64 file_offset, u64 num_bytes, bool uptodate)
+ {
+ struct rb_node *node;
+ struct btrfs_ordered_extent *entry = NULL;
+@@ -530,7 +511,7 @@ void btrfs_mark_ordered_io_finished(stru
+ ASSERT(end + 1 - cur < U32_MAX);
+ len = end + 1 - cur;
+
+- if (can_finish_ordered_extent(entry, folio, cur, len, uptodate)) {
++ if (can_finish_ordered_extent(entry, cur, len, uptodate)) {
+ spin_unlock_irqrestore(&inode->ordered_tree_lock, flags);
+ btrfs_queue_ordered_fn(entry);
+ spin_lock_irqsave(&inode->ordered_tree_lock, flags);
+--- a/fs/btrfs/ordered-data.h
++++ b/fs/btrfs/ordered-data.h
+@@ -163,11 +163,9 @@ void btrfs_put_ordered_extent(struct btr
+ void btrfs_remove_ordered_extent(struct btrfs_inode *btrfs_inode,
+ struct btrfs_ordered_extent *entry);
+ void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
+- struct folio *folio, u64 file_offset, u64 len,
+- bool uptodate);
++ u64 file_offset, u64 len, bool uptodate);
+ void btrfs_mark_ordered_io_finished(struct btrfs_inode *inode,
+- struct folio *folio, u64 file_offset,
+- u64 num_bytes, bool uptodate);
++ u64 file_offset, u64 num_bytes, bool uptodate);
+ bool btrfs_dec_test_ordered_pending(struct btrfs_inode *inode,
+ struct btrfs_ordered_extent **cached,
+ u64 file_offset, u64 io_size);
--- /dev/null
+From stable+bounces-278143-greg=kroah.com@vger.kernel.org Mon Jul 20 19:39:12 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 12:51:07 -0400
+Subject: btrfs: remove the COW fixup mechanism
+To: stable@vger.kernel.org
+Cc: Qu Wenruo <wqu@suse.com>, David Sterba <dsterba@suse.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720165108.2526667-2-sashal@kernel.org>
+
+From: Qu Wenruo <wqu@suse.com>
+
+[ Upstream commit b2a9f217ad3fa8012940744059956b20a3971135 ]
+
+[BACKGROUND]
+Btrfs has a special mechanism called COW fixup, which detects dirty
+pages without an ordered extent (folio ordered flag).
+
+Normally a dirty folio must go through delayed allocation (delalloc)
+before it can be submitted, and delalloc will create an ordered extent
+for it and mark the range with ordered flag.
+
+However in older kernels, there are bugs related to get_user_pages()
+which can lead to some page marked dirty but without notifying the fs to
+properly prepare them for writeback.
+
+In that case without an ordered extent btrfs is unable to properly
+submit such dirty folios, thus the COW fixup mechanism is introduced,
+which do the extra space reservation so that they can be written back
+properly.
+
+[MODERN SOLUTIONS]
+The MM layer has solved it properly now with the introduction of
+pin_user_pages*(), so we're handling cases that are no longer valid.
+
+So commit 7ca3e84980ef ("btrfs: reject out-of-band dirty folios during
+writeback") is introduced to change the behavior from going through
+COW fixup to rejecting them directly for experimental builds.
+
+So far it works fine, but when errors are injected into the IO path, we
+have random failures triggering the new warnings.
+
+It looks like we have error path that cleared the ordered flag but
+leaves the folio dirty flag, which later triggers the warning.
+
+[REMOVAL OF COW FIXUP]
+Although I hope to fix all those known warnings cases, I just can not
+figure out the root cause yet.
+
+But on the other hand, if we remove the ordered and checked flags in the
+future, and purely rely on the dirty flags and ordered extent search, we
+can get a much cleaner handling.
+
+Considering it's no longer hitting the COW fixup for normal IO paths, I
+think it's finally the time to remove the COW fixup completely.
+
+Furthermore, the function name "btrfs_writepage_cow_fixup()" is no
+longer meaningful, and since it's pretty small, only a folio flag check
+with error message, there is no need to put it as a dedicated helper,
+just open code it inside extent_writepage_io().
+
+Signed-off-by: Qu Wenruo <wqu@suse.com>
+Reviewed-by: David Sterba <dsterba@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Stable-dep-of: 95ee2231896d ("btrfs: check and set EXTENT_DELALLOC_NEW before clearing EXTENT_DELALLOC")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/btrfs/btrfs_inode.h | 1
+ fs/btrfs/disk-io.c | 16 ----
+ fs/btrfs/extent_io.c | 17 ++--
+ fs/btrfs/fs.h | 7 -
+ fs/btrfs/inode.c | 187 -------------------------------------------------
+ 5 files changed, 12 insertions(+), 216 deletions(-)
+
+--- a/fs/btrfs/btrfs_inode.h
++++ b/fs/btrfs/btrfs_inode.h
+@@ -609,7 +609,6 @@ int btrfs_prealloc_file_range_trans(stru
+ loff_t actual_len, u64 *alloc_hint);
+ int btrfs_run_delalloc_range(struct btrfs_inode *inode, struct folio *locked_folio,
+ u64 start, u64 end, struct writeback_control *wbc);
+-int btrfs_writepage_cow_fixup(struct folio *folio);
+ int btrfs_encoded_io_compression_from_extent(struct btrfs_fs_info *fs_info,
+ int compress_type);
+ int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode,
+--- a/fs/btrfs/disk-io.c
++++ b/fs/btrfs/disk-io.c
+@@ -1747,7 +1747,6 @@ static int read_backup_root(struct btrfs
+ /* helper to cleanup workers */
+ static void btrfs_stop_all_workers(struct btrfs_fs_info *fs_info)
+ {
+- btrfs_destroy_workqueue(fs_info->fixup_workers);
+ btrfs_destroy_workqueue(fs_info->delalloc_workers);
+ btrfs_destroy_workqueue(fs_info->workers);
+ if (fs_info->endio_workers)
+@@ -1954,9 +1953,6 @@ static int btrfs_init_workqueues(struct
+ fs_info->caching_workers =
+ btrfs_alloc_workqueue(fs_info, "cache", flags, max_active, 0);
+
+- fs_info->fixup_workers =
+- btrfs_alloc_ordered_workqueue(fs_info, "fixup", ordered_flags);
+-
+ fs_info->endio_workers =
+ alloc_workqueue("btrfs-endio", flags, max_active);
+ fs_info->endio_meta_workers =
+@@ -1985,7 +1981,7 @@ static int btrfs_init_workqueues(struct
+ fs_info->compressed_write_workers &&
+ fs_info->endio_write_workers &&
+ fs_info->endio_freespace_worker && fs_info->rmw_workers &&
+- fs_info->caching_workers && fs_info->fixup_workers &&
++ fs_info->caching_workers &&
+ fs_info->delayed_workers && fs_info->qgroup_rescan_workers &&
+ fs_info->discard_ctl.discard_workers)) {
+ return -ENOMEM;
+@@ -4246,16 +4242,6 @@ void __cold close_ctree(struct btrfs_fs_
+ btrfs_error_commit_super(fs_info);
+
+ /*
+- * Wait for any fixup workers to complete.
+- * If we don't wait for them here and they are still running by the time
+- * we call kthread_stop() against the cleaner kthread further below, we
+- * get an use-after-free on the cleaner because the fixup worker adds an
+- * inode to the list of delayed iputs and then attempts to wakeup the
+- * cleaner kthread, which was already stopped and destroyed. We parked
+- * already the cleaner, but below we run all pending delayed iputs.
+- */
+- btrfs_flush_workqueue(fs_info->fixup_workers);
+- /*
+ * Similar case here, we have to wait for delalloc workers before we
+ * proceed below and stop the cleaner kthread, otherwise we trigger a
+ * use-after-tree on the cleaner kthread task_struct when a delalloc
+--- a/fs/btrfs/extent_io.c
++++ b/fs/btrfs/extent_io.c
+@@ -1543,12 +1543,17 @@ static noinline_for_stack int extent_wri
+
+ ASSERT(start >= folio_start && end <= folio_end);
+
+- ret = btrfs_writepage_cow_fixup(folio);
+- if (ret) {
+- /* Fixup worker will requeue */
+- folio_redirty_for_writepage(bio_ctrl->wbc, folio);
+- folio_unlock(folio);
+- return 1;
++ if (unlikely(!folio_test_ordered(folio))) {
++ WARN_ON(IS_ENABLED(CONFIG_BTRFS_DEBUG));
++ btrfs_err_rl(fs_info,
++ "root %lld ino %llu folio %llu is marked dirty without notifying the fs",
++ btrfs_root_id(inode->root),
++ btrfs_ino(inode),
++ folio_pos(folio));
++ btrfs_folio_clear_dirty(fs_info, folio, start, len);
++ btrfs_folio_set_writeback(fs_info, folio, start, len);
++ btrfs_folio_clear_writeback(fs_info, folio, start, len);
++ return -EUCLEAN;
+ }
+
+ bitmap_set(&range_bitmap, (start - folio_pos(folio)) >> fs_info->sectorsize_bits,
+--- a/fs/btrfs/fs.h
++++ b/fs/btrfs/fs.h
+@@ -620,13 +620,6 @@ struct btrfs_fs_info {
+ struct btrfs_workqueue *endio_write_workers;
+ struct btrfs_workqueue *endio_freespace_worker;
+ struct btrfs_workqueue *caching_workers;
+-
+- /*
+- * Fixup workers take dirty pages that didn't properly go through the
+- * cow mechanism and make them safe to write. It happens for the
+- * sys_munmap function call path.
+- */
+- struct btrfs_workqueue *fixup_workers;
+ struct btrfs_workqueue *delayed_workers;
+
+ struct task_struct *transaction_kthread;
+--- a/fs/btrfs/inode.c
++++ b/fs/btrfs/inode.c
+@@ -2822,193 +2822,6 @@ int btrfs_set_extent_delalloc(struct btr
+ EXTENT_DELALLOC | extra_bits, cached_state);
+ }
+
+-/* see btrfs_writepage_start_hook for details on why this is required */
+-struct btrfs_writepage_fixup {
+- struct folio *folio;
+- struct btrfs_inode *inode;
+- struct btrfs_work work;
+-};
+-
+-static void btrfs_writepage_fixup_worker(struct btrfs_work *work)
+-{
+- struct btrfs_writepage_fixup *fixup =
+- container_of(work, struct btrfs_writepage_fixup, work);
+- struct btrfs_ordered_extent *ordered;
+- struct extent_state *cached_state = NULL;
+- struct extent_changeset *data_reserved = NULL;
+- struct folio *folio = fixup->folio;
+- struct btrfs_inode *inode = fixup->inode;
+- struct btrfs_fs_info *fs_info = inode->root->fs_info;
+- u64 page_start = folio_pos(folio);
+- u64 page_end = folio_pos(folio) + folio_size(folio) - 1;
+- int ret = 0;
+- bool free_delalloc_space = true;
+-
+- /*
+- * This is similar to page_mkwrite, we need to reserve the space before
+- * we take the folio lock.
+- */
+- ret = btrfs_delalloc_reserve_space(inode, &data_reserved, page_start,
+- folio_size(folio));
+-again:
+- folio_lock(folio);
+-
+- /*
+- * Before we queued this fixup, we took a reference on the folio.
+- * folio->mapping may go NULL, but it shouldn't be moved to a different
+- * address space.
+- */
+- if (!folio->mapping || !folio_test_dirty(folio) ||
+- !folio_test_checked(folio)) {
+- /*
+- * Unfortunately this is a little tricky, either
+- *
+- * 1) We got here and our folio had already been dealt with and
+- * we reserved our space, thus ret == 0, so we need to just
+- * drop our space reservation and bail. This can happen the
+- * first time we come into the fixup worker, or could happen
+- * while waiting for the ordered extent.
+- * 2) Our folio was already dealt with, but we happened to get an
+- * ENOSPC above from the btrfs_delalloc_reserve_space. In
+- * this case we obviously don't have anything to release, but
+- * because the folio was already dealt with we don't want to
+- * mark the folio with an error, so make sure we're resetting
+- * ret to 0. This is why we have this check _before_ the ret
+- * check, because we do not want to have a surprise ENOSPC
+- * when the folio was already properly dealt with.
+- */
+- if (!ret) {
+- btrfs_delalloc_release_extents(inode, folio_size(folio));
+- btrfs_delalloc_release_space(inode, data_reserved,
+- page_start, folio_size(folio),
+- true);
+- }
+- ret = 0;
+- goto out_page;
+- }
+-
+- /*
+- * We can't mess with the folio state unless it is locked, so now that
+- * it is locked bail if we failed to make our space reservation.
+- */
+- if (ret)
+- goto out_page;
+-
+- lock_extent(&inode->io_tree, page_start, page_end, &cached_state);
+-
+- /* already ordered? We're done */
+- if (folio_test_ordered(folio))
+- goto out_reserved;
+-
+- ordered = btrfs_lookup_ordered_range(inode, page_start, PAGE_SIZE);
+- if (ordered) {
+- unlock_extent(&inode->io_tree, page_start, page_end,
+- &cached_state);
+- folio_unlock(folio);
+- btrfs_start_ordered_extent(ordered);
+- btrfs_put_ordered_extent(ordered);
+- goto again;
+- }
+-
+- ret = btrfs_set_extent_delalloc(inode, page_start, page_end, 0,
+- &cached_state);
+- if (ret)
+- goto out_reserved;
+-
+- /*
+- * Everything went as planned, we're now the owner of a dirty page with
+- * delayed allocation bits set and space reserved for our COW
+- * destination.
+- *
+- * The page was dirty when we started, nothing should have cleaned it.
+- */
+- BUG_ON(!folio_test_dirty(folio));
+- free_delalloc_space = false;
+-out_reserved:
+- btrfs_delalloc_release_extents(inode, PAGE_SIZE);
+- if (free_delalloc_space)
+- btrfs_delalloc_release_space(inode, data_reserved, page_start,
+- PAGE_SIZE, true);
+- unlock_extent(&inode->io_tree, page_start, page_end, &cached_state);
+-out_page:
+- if (ret) {
+- /*
+- * We hit ENOSPC or other errors. Update the mapping and page
+- * to reflect the errors and clean the page.
+- */
+- mapping_set_error(folio->mapping, ret);
+- btrfs_folio_clear_ordered(fs_info, folio, page_start,
+- folio_size(folio));
+- btrfs_mark_ordered_io_finished(inode, page_start,
+- folio_size(folio), !ret);
+- folio_clear_dirty_for_io(folio);
+- }
+- btrfs_folio_clear_checked(fs_info, folio, page_start, PAGE_SIZE);
+- folio_unlock(folio);
+- folio_put(folio);
+- kfree(fixup);
+- extent_changeset_free(data_reserved);
+- /*
+- * As a precaution, do a delayed iput in case it would be the last iput
+- * that could need flushing space. Recursing back to fixup worker would
+- * deadlock.
+- */
+- btrfs_add_delayed_iput(inode);
+-}
+-
+-/*
+- * There are a few paths in the higher layers of the kernel that directly
+- * set the folio dirty bit without asking the filesystem if it is a
+- * good idea. This causes problems because we want to make sure COW
+- * properly happens and the data=ordered rules are followed.
+- *
+- * In our case any range that doesn't have the ORDERED bit set
+- * hasn't been properly setup for IO. We kick off an async process
+- * to fix it up. The async helper will wait for ordered extents, set
+- * the delalloc bit and make it safe to write the folio.
+- */
+-int btrfs_writepage_cow_fixup(struct folio *folio)
+-{
+- struct inode *inode = folio->mapping->host;
+- struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
+- struct btrfs_writepage_fixup *fixup;
+-
+- /* This folio has ordered extent covering it already */
+- if (folio_test_ordered(folio))
+- return 0;
+-
+- /*
+- * folio_checked is set below when we create a fixup worker for this
+- * folio, don't try to create another one if we're already
+- * folio_test_checked.
+- *
+- * The extent_io writepage code will redirty the foio if we send back
+- * EAGAIN.
+- */
+- if (folio_test_checked(folio))
+- return -EAGAIN;
+-
+- fixup = kzalloc(sizeof(*fixup), GFP_NOFS);
+- if (!fixup)
+- return -EAGAIN;
+-
+- /*
+- * We are already holding a reference to this inode from
+- * write_cache_pages. We need to hold it because the space reservation
+- * takes place outside of the folio lock, and we can't trust
+- * page->mapping outside of the folio lock.
+- */
+- ihold(inode);
+- btrfs_folio_set_checked(fs_info, folio, folio_pos(folio), folio_size(folio));
+- folio_get(folio);
+- btrfs_init_work(&fixup->work, btrfs_writepage_fixup_worker, NULL);
+- fixup->folio = folio;
+- fixup->inode = BTRFS_I(inode);
+- btrfs_queue_work(fs_info->fixup_workers, &fixup->work);
+-
+- return -EAGAIN;
+-}
+-
+ 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,
--- /dev/null
+From stable+bounces-278121-greg=kroah.com@vger.kernel.org Mon Jul 20 18:07:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 11:56:33 -0400
+Subject: btrfs: replace for_each_set_bit() with for_each_set_bitmap()
+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: <20260720155635.2425597-2-sashal@kernel.org>
+
+From: Qu Wenruo <wqu@suse.com>
+
+[ Upstream commit e6698b34fab33867ef3faeeea6feb165f31aae24 ]
+
+Inside extent_io.c, there are several simple call sites doing things
+like:
+
+ for_each_set_bit(bit, bitmap, bitmap_size) {
+ /* handle one fs block */
+ }
+
+The workload includes:
+
+- set_bit()
+ Inside extent_writepage_io().
+
+ This can be replaced with a bitmap_set().
+
+- btrfs_folio_set_lock()
+- btrfs_mark_ordered_io_finished()
+ Inside writepage_delalloc().
+
+ Instead of calling it multiple times, we can pass a range into the
+ function with one call.
+
+Reviewed-by: Boris Burkov <boris@bur.io>
+Signed-off-by: Qu Wenruo <wqu@suse.com>
+Reviewed-by: David Sterba <dsterba@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Stable-dep-of: 66ff4d366e7e ("btrfs: fix false IO failure after falling back to buffered write")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/btrfs/extent_io.c | 26 ++++++++++++++++----------
+ 1 file changed, 16 insertions(+), 10 deletions(-)
+
+--- a/fs/btrfs/extent_io.c
++++ b/fs/btrfs/extent_io.c
+@@ -1245,8 +1245,9 @@ static noinline_for_stack int writepage_
+ u64 delalloc_start = page_start;
+ u64 delalloc_end = page_end;
+ u64 delalloc_to_write = 0;
++ unsigned int start_bit;
++ unsigned int end_bit;
+ int ret = 0;
+- int bit;
+
+ /* Save the dirty bitmap as our submission bitmap will be a subset of it. */
+ if (btrfs_is_subpage(fs_info, inode->vfs_inode.i_mapping)) {
+@@ -1256,10 +1257,12 @@ static noinline_for_stack int writepage_
+ bio_ctrl->submit_bitmap = 1;
+ }
+
+- for_each_set_bit(bit, &bio_ctrl->submit_bitmap, blocks_per_folio) {
+- u64 start = page_start + (bit << fs_info->sectorsize_bits);
++ for_each_set_bitrange(start_bit, end_bit, &bio_ctrl->submit_bitmap,
++ blocks_per_folio) {
++ u64 start = page_start + (start_bit << fs_info->sectorsize_bits);
++ u32 len = (end_bit - start_bit) << fs_info->sectorsize_bits;
+
+- btrfs_folio_set_lock(fs_info, folio, start, fs_info->sectorsize);
++ btrfs_folio_set_lock(fs_info, folio, start, len);
+ }
+
+ /* Lock all (subpage) delalloc ranges inside the folio first. */
+@@ -1376,10 +1379,13 @@ static noinline_for_stack int writepage_
+ fs_info->sectorsize_bits,
+ blocks_per_folio);
+
+- for_each_set_bit(bit, &bio_ctrl->submit_bitmap, bitmap_size)
+- btrfs_mark_ordered_io_finished(inode, folio,
+- page_start + (bit << fs_info->sectorsize_bits),
+- fs_info->sectorsize, false);
++ for_each_set_bitrange(start_bit, end_bit, &bio_ctrl->submit_bitmap,
++ bitmap_size) {
++ u64 start = page_start + (start_bit << fs_info->sectorsize_bits);
++ u32 len = (end_bit - start_bit) << fs_info->sectorsize_bits;
++
++ btrfs_mark_ordered_io_finished(inode, folio, start, len, false);
++ }
+ return ret;
+ }
+ out:
+@@ -1539,8 +1545,8 @@ static noinline_for_stack int extent_wri
+ return 1;
+ }
+
+- for (cur = start; cur < end; cur += fs_info->sectorsize)
+- set_bit((cur - folio_start) >> fs_info->sectorsize_bits, &range_bitmap);
++ bitmap_set(&range_bitmap, (start - folio_pos(folio)) >> fs_info->sectorsize_bits,
++ len >> fs_info->sectorsize_bits);
+ bitmap_and(&bio_ctrl->submit_bitmap, &bio_ctrl->submit_bitmap, &range_bitmap,
+ blocks_per_folio);
+
--- /dev/null
+From stable+bounces-277639-greg=kroah.com@vger.kernel.org Mon Jul 20 13:15:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 06:54:41 -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: <20260720105442.215749-1-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
+@@ -380,7 +380,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
+@@ -214,8 +214,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-277640-greg=kroah.com@vger.kernel.org Mon Jul 20 13:15:03 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 06:54:42 -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: <20260720105442.215749-2-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
+@@ -19,6 +19,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)
+ {
+@@ -158,6 +164,7 @@ static const struct attribute_group atme
+ static int atmel_sha204a_probe(struct i2c_client *client)
+ {
+ struct atmel_i2c_client_priv *i2c_priv;
++ const unsigned short *quality;
+ int ret;
+
+ ret = atmel_i2c_probe(client);
+@@ -171,11 +178,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)
+@@ -207,14 +212,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-277813-greg=kroah.com@vger.kernel.org Mon Jul 20 16:02:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 09:51:06 -0400
+Subject: crypto: atmel-sha204a - fail on hwrng registration error in probe path
+To: stable@vger.kernel.org
+Cc: Thorsten Blum <thorsten.blum@linux.dev>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720135106.1306281-1-sashal@kernel.org>
+
+From: Thorsten Blum <thorsten.blum@linux.dev>
+
+[ Upstream commit 49e05bb00f2e8168695f7af4d694c39e1423e8a2 ]
+
+Commit 13909a0c8897 ("crypto: atmel-sha204a - provide the otp content")
+overwrote the hwrng registration return value when creating the sysfs
+group, which allowed atmel_sha204a_probe() to succeed even if
+devm_hwrng_register() failed.
+
+Return immediately when devm_hwrng_register() fails, and report both
+hwrng and sysfs registration errors with dev_err(). Adjust the sysfs
+error log message for consistency.
+
+Fixes: 13909a0c8897 ("crypto: atmel-sha204a - provide the otp content")
+Cc: stable@vger.kernel.org
+Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
+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 | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+--- a/drivers/crypto/atmel-sha204a.c
++++ b/drivers/crypto/atmel-sha204a.c
+@@ -183,8 +183,10 @@ static int atmel_sha204a_probe(struct i2
+ i2c_priv->hwrng.quality = *quality;
+
+ ret = devm_hwrng_register(&client->dev, &i2c_priv->hwrng);
+- if (ret)
+- dev_warn(&client->dev, "failed to register RNG (%d)\n", ret);
++ if (ret) {
++ dev_err(&client->dev, "failed to register RNG (%d)\n", ret);
++ return ret;
++ }
+
+ /* otp read out */
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+@@ -192,7 +194,7 @@ static int atmel_sha204a_probe(struct i2
+
+ ret = sysfs_create_group(&client->dev.kobj, &atmel_sha204a_groups);
+ if (ret) {
+- dev_err(&client->dev, "failed to register sysfs entry\n");
++ dev_err(&client->dev, "failed to create sysfs group (%d)\n", ret);
+ return ret;
+ }
+
--- /dev/null
+From stable+bounces-278164-greg=kroah.com@vger.kernel.org Mon Jul 20 19:45:04 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 13:15:35 -0400
+Subject: crypto: ccp - Do not initialize SNP for ioctl(SNP_CONFIG)
+To: stable@vger.kernel.org
+Cc: "Tycho Andersen (AMD)" <tycho@kernel.org>, Tom Lendacky <thomas.lendacky@amd.com>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720171535.2695659-6-sashal@kernel.org>
+
+From: "Tycho Andersen (AMD)" <tycho@kernel.org>
+
+[ Upstream commit 08f0e65e784c4b20e6e620dd4f68d8636073a3d2 ]
+
+Sashiko notes:
+
+> if SEV initialization fails and KVM is actively running normal VMs, could a
+> userspace process trigger this code path via /dev/sev ioctls (e.g.,
+> SEV_PDH_GEN) and zero out MSR_VM_HSAVE_PA globally? Would the next VMRUN
+> execution for an active VM trigger a general protection fault and crash the
+> host?
+
+Refuse to re-try initialization if SNP is not already initialized for
+SNP_CONFIG.
+
+This is technically an ABI break: before if SNP initialization failed it
+could be transparently retriggered by this ioctl, and if no VMs were
+running, everything worked fine. Hopefully this is enough of a corner case
+that nobody will notice, but someone does, there are a few options:
+
+* do something like symbol_get() for kvm and refuse to initialize if KVM is
+ loaded
+* check each cpu's HSAVE_PA for non-zero data before re-initializing
+* once initialization has failed, continue to refuse to initialize until
+ the ccp module is unloaded
+
+Fixes: ceac7fb89e8d ("crypto: ccp - Ensure implicit SEV/SNP init and shutdown in ioctls")
+Reported-by: Sashiko
+Assisted-by: Gemini:gemini-3.1-pro-preview
+Link: https://sashiko.dev/#/patchset/20260324161301.1353976-1-tycho%40kernel.org
+CC: <stable@vger.kernel.org>
+Signed-off-by: Tycho Andersen (AMD) <tycho@kernel.org>
+Reviewed-by: Tom Lendacky <thomas.lendacky@amd.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/ccp/sev-dev.c | 33 ++++-----------------------------
+ 1 file changed, 4 insertions(+), 29 deletions(-)
+
+--- a/drivers/crypto/ccp/sev-dev.c
++++ b/drivers/crypto/ccp/sev-dev.c
+@@ -1434,21 +1434,6 @@ static int sev_move_to_init_state(struct
+ return 0;
+ }
+
+-static int snp_move_to_init_state(struct sev_issue_cmd *argp, bool *shutdown_required)
+-{
+- int error, rc;
+-
+- rc = __sev_snp_init_locked(&error);
+- if (rc) {
+- argp->error = SEV_RET_INVALID_PLATFORM_STATE;
+- return rc;
+- }
+-
+- *shutdown_required = true;
+-
+- return 0;
+-}
+-
+ static int sev_ioctl_do_reset(struct sev_issue_cmd *argp, bool writable)
+ {
+ int state, rc;
+@@ -2143,8 +2128,6 @@ static int sev_ioctl_do_snp_set_config(s
+ {
+ struct sev_device *sev = psp_master->sev_data;
+ struct sev_user_data_snp_config config;
+- bool shutdown_required = false;
+- int ret, error;
+
+ if (!argp->data)
+ return -EINVAL;
+@@ -2152,21 +2135,13 @@ static int sev_ioctl_do_snp_set_config(s
+ if (!writable)
+ return -EPERM;
+
++ if (!sev->snp_initialized)
++ return -ENODEV;
++
+ if (copy_from_user(&config, (void __user *)argp->data, sizeof(config)))
+ return -EFAULT;
+
+- if (!sev->snp_initialized) {
+- ret = snp_move_to_init_state(argp, &shutdown_required);
+- if (ret)
+- return ret;
+- }
+-
+- ret = __sev_do_cmd_locked(SEV_CMD_SNP_CONFIG, &config, &argp->error);
+-
+- if (shutdown_required)
+- __sev_snp_shutdown_locked(&error, false);
+-
+- return ret;
++ return __sev_do_cmd_locked(SEV_CMD_SNP_CONFIG, &config, &argp->error);
+ }
+
+ static int sev_ioctl_do_snp_vlek_load(struct sev_issue_cmd *argp, bool writable)
--- /dev/null
+From stable+bounces-278163-greg=kroah.com@vger.kernel.org Mon Jul 20 19:16:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 13:15:34 -0400
+Subject: crypto: ccp - Fix a case where SNP_SHUTDOWN is missed
+To: stable@vger.kernel.org
+Cc: Tom Lendacky <thomas.lendacky@amd.com>, "Tycho Andersen (AMD)" <tycho@kernel.org>, Alexey Kardashevskiy <aik@amd.com>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720171535.2695659-5-sashal@kernel.org>
+
+From: Tom Lendacky <thomas.lendacky@amd.com>
+
+[ Upstream commit 551120148b67e04527b405c5ec33a31593846ba4 ]
+
+If page reclaim fails in sev_ioctl_do_snp_platform_status() and SNP was
+moved from UNINIT to INIT for the function, SNP is not moved back to
+UNINIT state. Additionally, SNP is not required to be initialized in order
+to execute the SNP_PLATFORM_STATUS command, so don't attempt to move to
+INIT state and let SNP_PLATFORM_STATUS report the status as is.
+
+Fixes: ceac7fb89e8d ("crypto: ccp - Ensure implicit SEV/SNP init and shutdown in ioctls")
+Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
+Reviewed-by: Tycho Andersen (AMD) <tycho@kernel.org>
+Reviewed-by: Alexey Kardashevskiy <aik@amd.com>
+Signed-off-by: Tycho Andersen (AMD) <tycho@kernel.org>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Stable-dep-of: 08f0e65e784c ("crypto: ccp - Do not initialize SNP for ioctl(SNP_CONFIG)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/ccp/sev-dev.c | 46 +++++++++++++++++++++----------------------
+ 1 file changed, 23 insertions(+), 23 deletions(-)
+
+--- a/drivers/crypto/ccp/sev-dev.c
++++ b/drivers/crypto/ccp/sev-dev.c
+@@ -2071,11 +2071,10 @@ e_free_pdh:
+ static int sev_ioctl_do_snp_platform_status(struct sev_issue_cmd *argp)
+ {
+ struct sev_device *sev = psp_master->sev_data;
+- bool shutdown_required = false;
+ struct sev_data_snp_addr buf;
+ struct page *status_page;
+- int ret, error;
+ void *data;
++ int ret;
+
+ if (!argp->data)
+ return -EINVAL;
+@@ -2086,31 +2085,35 @@ static int sev_ioctl_do_snp_platform_sta
+
+ data = page_address(status_page);
+
+- if (!sev->snp_initialized) {
+- ret = snp_move_to_init_state(argp, &shutdown_required);
+- if (ret)
+- goto cleanup;
+- }
+-
+ /*
+- * Firmware expects status page to be in firmware-owned state, otherwise
+- * it will report firmware error code INVALID_PAGE_STATE (0x1A).
++ * SNP_PLATFORM_STATUS can be executed in any SNP state. But if executed
++ * when SNP has been initialized, the status page must be firmware-owned.
+ */
+- if (rmp_mark_pages_firmware(__pa(data), 1, true)) {
+- ret = -EFAULT;
+- goto cleanup;
++ if (sev->snp_initialized) {
++ /*
++ * Firmware expects the status page to be in Firmware state,
++ * otherwise it will report an error INVALID_PAGE_STATE.
++ */
++ if (rmp_mark_pages_firmware(__pa(data), 1, true)) {
++ ret = -EFAULT;
++ goto cleanup;
++ }
+ }
+
+ buf.address = __psp_pa(data);
+ ret = __sev_do_cmd_locked(SEV_CMD_SNP_PLATFORM_STATUS, &buf, &argp->error);
+
+- /*
+- * Status page will be transitioned to Reclaim state upon success, or
+- * left in Firmware state in failure. Use snp_reclaim_pages() to
+- * transition either case back to Hypervisor-owned state.
+- */
+- if (snp_reclaim_pages(__pa(data), 1, true))
+- return -EFAULT;
++ if (sev->snp_initialized) {
++ /*
++ * The status page will be in Reclaim state on success, or left
++ * in Firmware state on failure. Use snp_reclaim_pages() to
++ * transition either case back to Hypervisor-owned state.
++ */
++ if (snp_reclaim_pages(__pa(data), 1, true)) {
++ snp_leak_pages(__page_to_pfn(status_page), 1);
++ return -EFAULT;
++ }
++ }
+
+ if (ret)
+ goto cleanup;
+@@ -2120,9 +2123,6 @@ static int sev_ioctl_do_snp_platform_sta
+ ret = -EFAULT;
+
+ cleanup:
+- if (shutdown_required)
+- __sev_snp_shutdown_locked(&error, false);
+-
+ __free_pages(status_page, 0);
+ return ret;
+ }
--- /dev/null
+From stable+bounces-278160-greg=kroah.com@vger.kernel.org Mon Jul 20 19:15:46 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 13:15:30 -0400
+Subject: crypto: ccp - Move dev_info/err messages for SEV/SNP init and shutdown
+To: stable@vger.kernel.org
+Cc: Ashish Kalra <ashish.kalra@amd.com>, Tom Lendacky <thomas.lendacky@amd.com>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720171535.2695659-1-sashal@kernel.org>
+
+From: Ashish Kalra <ashish.kalra@amd.com>
+
+[ Upstream commit 9770b428b1a28360663f1f5e524ee458b4cf454b ]
+
+Move dev_info and dev_err messages related to SEV/SNP initialization
+and shutdown into __sev_platform_init_locked(), __sev_snp_init_locked()
+and __sev_platform_shutdown_locked(), __sev_snp_shutdown_locked() so
+that they don't need to be issued from callers.
+
+This allows both _sev_platform_init_locked() and various SEV/SNP ioctls
+to call __sev_platform_init_locked(), __sev_snp_init_locked() and
+__sev_platform_shutdown_locked(), __sev_snp_shutdown_locked() for
+implicit SEV/SNP initialization and shutdown without additionally
+printing any errors/success messages.
+
+Reviewed-by: Tom Lendacky <thomas.lendacky@amd.com>
+Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Stable-dep-of: 08f0e65e784c ("crypto: ccp - Do not initialize SNP for ioctl(SNP_CONFIG)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/ccp/sev-dev.c | 44 +++++++++++++++++++++++++++++++------------
+ 1 file changed, 32 insertions(+), 12 deletions(-)
+
+--- a/drivers/crypto/ccp/sev-dev.c
++++ b/drivers/crypto/ccp/sev-dev.c
+@@ -1174,21 +1174,31 @@ static int __sev_snp_init_locked(int *er
+ wbinvd_on_all_cpus();
+
+ rc = __sev_do_cmd_locked(cmd, arg, error);
+- if (rc)
++ if (rc) {
++ dev_err(sev->dev, "SEV-SNP: %s failed rc %d, error %#x\n",
++ cmd == SEV_CMD_SNP_INIT_EX ? "SNP_INIT_EX" : "SNP_INIT",
++ rc, *error);
+ return rc;
++ }
+
+ /* Prepare for first SNP guest launch after INIT. */
+ wbinvd_on_all_cpus();
+ rc = __sev_do_cmd_locked(SEV_CMD_SNP_DF_FLUSH, NULL, error);
+- if (rc)
++ if (rc) {
++ dev_err(sev->dev, "SEV-SNP: SNP_DF_FLUSH failed rc %d, error %#x\n",
++ rc, *error);
+ return rc;
++ }
+
+ sev->snp_initialized = true;
+ dev_dbg(sev->dev, "SEV-SNP firmware initialized\n");
+
++ dev_info(sev->dev, "SEV-SNP API:%d.%d build:%d\n", sev->api_major,
++ sev->api_minor, sev->build);
++
+ sev_es_tmr_size = SNP_TMR_SIZE;
+
+- return rc;
++ return 0;
+ }
+
+ static void __sev_platform_init_handle_tmr(struct sev_device *sev)
+@@ -1285,16 +1295,22 @@ static int __sev_platform_init_locked(in
+ if (error)
+ *error = psp_ret;
+
+- if (rc)
++ if (rc) {
++ dev_err(sev->dev, "SEV: %s failed %#x, rc %d\n",
++ sev_init_ex_buffer ? "INIT_EX" : "INIT", psp_ret, rc);
+ return rc;
++ }
+
+ sev->state = SEV_STATE_INIT;
+
+ /* Prepare for first SEV guest launch after INIT */
+ wbinvd_on_all_cpus();
+ rc = __sev_do_cmd_locked(SEV_CMD_DF_FLUSH, NULL, error);
+- if (rc)
++ if (rc) {
++ dev_err(sev->dev, "SEV: DF_FLUSH failed %#x, rc %d\n",
++ *error, rc);
+ return rc;
++ }
+
+ dev_dbg(sev->dev, "SEV firmware initialized\n");
+
+@@ -1374,8 +1390,11 @@ static int __sev_platform_shutdown_locke
+ return 0;
+
+ ret = __sev_do_cmd_locked(SEV_CMD_SHUTDOWN, NULL, error);
+- if (ret)
++ if (ret) {
++ dev_err(sev->dev, "SEV: failed to SHUTDOWN error %#x, rc %d\n",
++ *error, ret);
+ return ret;
++ }
+
+ sev->state = SEV_STATE_UNINIT;
+ dev_dbg(sev->dev, "SEV firmware shutdown\n");
+@@ -1733,9 +1752,12 @@ static int __sev_snp_shutdown_locked(int
+ ret = __sev_do_cmd_locked(SEV_CMD_SNP_SHUTDOWN_EX, &data, error);
+ /* SHUTDOWN may require DF_FLUSH */
+ if (*error == SEV_RET_DFFLUSH_REQUIRED) {
+- ret = __sev_do_cmd_locked(SEV_CMD_SNP_DF_FLUSH, NULL, NULL);
++ int dfflush_error;
++
++ ret = __sev_do_cmd_locked(SEV_CMD_SNP_DF_FLUSH, NULL, &dfflush_error);
+ if (ret) {
+- dev_err(sev->dev, "SEV-SNP DF_FLUSH failed\n");
++ dev_err(sev->dev, "SEV-SNP DF_FLUSH failed, ret = %d, error = %#x\n",
++ ret, dfflush_error);
+ return ret;
+ }
+ /* reissue the shutdown command */
+@@ -1743,7 +1765,8 @@ static int __sev_snp_shutdown_locked(int
+ error);
+ }
+ if (ret) {
+- dev_err(sev->dev, "SEV-SNP firmware shutdown failed\n");
++ dev_err(sev->dev, "SEV-SNP firmware shutdown failed, rc %d, error %#x\n",
++ ret, *error);
+ return ret;
+ }
+
+@@ -2511,9 +2534,6 @@ void sev_pci_init(void)
+ dev_err(sev->dev, "SEV: failed to INIT error %#x, rc %d\n",
+ args.error, rc);
+
+- dev_info(sev->dev, "SEV%s API:%d.%d build:%d\n", sev->snp_initialized ?
+- "-SNP" : "", sev->api_major, sev->api_minor, sev->build);
+-
+ atomic_notifier_chain_register(&panic_notifier_list,
+ &snp_panic_notifier);
+ return;
--- /dev/null
+From stable+bounces-278165-greg=kroah.com@vger.kernel.org Mon Jul 20 19:15:54 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 13:15:33 -0400
+Subject: crypto: ccp - Move SEV/SNP Platform initialization to KVM
+To: stable@vger.kernel.org
+Cc: Ashish Kalra <ashish.kalra@amd.com>, Sean Christopherson <seanjc@google.com>, Alexey Kardashevskiy <aik@amd.com>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720171535.2695659-4-sashal@kernel.org>
+
+From: Ashish Kalra <ashish.kalra@amd.com>
+
+[ Upstream commit 3f8f0133a5fc9b32d0c308530320c3f2430ba5ab ]
+
+SNP initialization is forced during PSP driver probe purely because SNP
+can't be initialized if VMs are running. But the only in-tree user of
+SEV/SNP functionality is KVM, and KVM depends on PSP driver for the same.
+Forcing SEV/SNP initialization because a hypervisor could be running
+legacy non-confidential VMs make no sense.
+
+This patch removes SEV/SNP initialization from the PSP driver probe
+time and moves the requirement to initialize SEV/SNP functionality
+to KVM if it wants to use SEV/SNP.
+
+Suggested-by: Sean Christopherson <seanjc@google.com>
+Reviewed-by: Alexey Kardashevskiy <aik@amd.com>
+Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Stable-dep-of: 08f0e65e784c ("crypto: ccp - Do not initialize SNP for ioctl(SNP_CONFIG)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/ccp/sev-dev.c | 13 -------------
+ 1 file changed, 13 deletions(-)
+
+--- a/drivers/crypto/ccp/sev-dev.c
++++ b/drivers/crypto/ccp/sev-dev.c
+@@ -1352,10 +1352,6 @@ static int _sev_platform_init_locked(str
+ if (sev->state == SEV_STATE_INIT)
+ return 0;
+
+- /*
+- * Legacy guests cannot be running while SNP_INIT(_EX) is executing,
+- * so perform SEV-SNP initialization at probe time.
+- */
+ rc = __sev_snp_init_locked(&args->error);
+ if (rc && rc != -ENODEV) {
+ /*
+@@ -2514,9 +2510,7 @@ EXPORT_SYMBOL_GPL(sev_issue_cmd_external
+ void sev_pci_init(void)
+ {
+ struct sev_device *sev = psp_master->sev_data;
+- struct sev_platform_init_args args = {0};
+ u8 api_major, api_minor, build;
+- int rc;
+
+ if (!sev)
+ return;
+@@ -2539,13 +2533,6 @@ void sev_pci_init(void)
+ api_major, api_minor, build,
+ sev->api_major, sev->api_minor, sev->build);
+
+- /* Initialize the platform */
+- args.probe = true;
+- rc = sev_platform_init(&args);
+- if (rc)
+- dev_err(sev->dev, "SEV: failed to INIT error %#x, rc %d\n",
+- args.error, rc);
+-
+ return;
+
+ err:
--- /dev/null
+From stable+bounces-278162-greg=kroah.com@vger.kernel.org Mon Jul 20 19:15:47 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 13:15:32 -0400
+Subject: crypto: ccp - Register SNP panic notifier only if SNP is enabled
+To: stable@vger.kernel.org
+Cc: Ashish Kalra <ashish.kalra@amd.com>, Dionna Glaze <dionnaglaze@google.com>, Alexey Kardashevskiy <aik@amd.com>, Tom Lendacky <thomas.lendacky@amd.com>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720171535.2695659-3-sashal@kernel.org>
+
+From: Ashish Kalra <ashish.kalra@amd.com>
+
+[ Upstream commit 19860c3274fb209e36ebcba562dc5da301a53cb3 ]
+
+Currently, the SNP panic notifier is registered on module initialization
+regardless of whether SNP is being enabled or initialized.
+
+Instead, register the SNP panic notifier only when SNP is actually
+initialized and unregister the notifier when SNP is shutdown.
+
+Reviewed-by: Dionna Glaze <dionnaglaze@google.com>
+Reviewed-by: Alexey Kardashevskiy <aik@amd.com>
+Reviewed-by: Tom Lendacky <thomas.lendacky@amd.com>
+Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Stable-dep-of: 08f0e65e784c ("crypto: ccp - Do not initialize SNP for ioctl(SNP_CONFIG)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/ccp/sev-dev.c | 22 +++++++++++++---------
+ 1 file changed, 13 insertions(+), 9 deletions(-)
+
+--- a/drivers/crypto/ccp/sev-dev.c
++++ b/drivers/crypto/ccp/sev-dev.c
+@@ -105,6 +105,13 @@ static void *sev_init_ex_buffer;
+
+ static void __sev_firmware_shutdown(struct sev_device *sev, bool panic);
+
++static int snp_shutdown_on_panic(struct notifier_block *nb,
++ unsigned long reason, void *arg);
++
++static struct notifier_block snp_panic_notifier = {
++ .notifier_call = snp_shutdown_on_panic,
++};
++
+ static inline bool sev_version_greater_or_equal(u8 maj, u8 min)
+ {
+ struct sev_device *sev = psp_master->sev_data;
+@@ -1196,6 +1203,9 @@ static int __sev_snp_init_locked(int *er
+ dev_info(sev->dev, "SEV-SNP API:%d.%d build:%d\n", sev->api_major,
+ sev->api_minor, sev->build);
+
++ atomic_notifier_chain_register(&panic_notifier_list,
++ &snp_panic_notifier);
++
+ sev_es_tmr_size = SNP_TMR_SIZE;
+
+ return 0;
+@@ -1792,6 +1802,9 @@ static int __sev_snp_shutdown_locked(int
+ sev->snp_initialized = false;
+ dev_dbg(sev->dev, "SEV-SNP firmware shutdown\n");
+
++ atomic_notifier_chain_unregister(&panic_notifier_list,
++ &snp_panic_notifier);
++
+ /* Reset TMR size back to default */
+ sev_es_tmr_size = SEV_TMR_SIZE;
+
+@@ -2488,10 +2501,6 @@ static int snp_shutdown_on_panic(struct
+ return NOTIFY_DONE;
+ }
+
+-static struct notifier_block snp_panic_notifier = {
+- .notifier_call = snp_shutdown_on_panic,
+-};
+-
+ int sev_issue_cmd_external_user(struct file *filep, unsigned int cmd,
+ void *data, int *error)
+ {
+@@ -2537,8 +2546,6 @@ void sev_pci_init(void)
+ dev_err(sev->dev, "SEV: failed to INIT error %#x, rc %d\n",
+ args.error, rc);
+
+- atomic_notifier_chain_register(&panic_notifier_list,
+- &snp_panic_notifier);
+ return;
+
+ err:
+@@ -2555,7 +2562,4 @@ void sev_pci_exit(void)
+ return;
+
+ sev_firmware_shutdown(sev);
+-
+- atomic_notifier_chain_unregister(&panic_notifier_list,
+- &snp_panic_notifier);
+ }
--- /dev/null
+From stable+bounces-278161-greg=kroah.com@vger.kernel.org Mon Jul 20 19:15:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 13:15:31 -0400
+Subject: crypto: ccp - Reset TMR size at SNP Shutdown
+To: stable@vger.kernel.org
+Cc: Ashish Kalra <ashish.kalra@amd.com>, Dionna Glaze <dionnaglaze@google.com>, Tom Lendacky <thomas.lendacky@amd.com>, Herbert Xu <herbert@gondor.apana.org.au>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260720171535.2695659-2-sashal@kernel.org>
+
+From: Ashish Kalra <ashish.kalra@amd.com>
+
+[ Upstream commit 65a895a44e641fe47f3419b064288d1d713024a5 ]
+
+Implicit SNP initialization as part of some SNP ioctls modify TMR size
+to be SNP compliant which followed by SNP shutdown will leave the
+TMR size modified and then subsequently cause SEV only initialization
+to fail, hence, reset TMR size to default at SNP Shutdown.
+
+Acked-by: Dionna Glaze <dionnaglaze@google.com>
+Reviewed-by: Tom Lendacky <thomas.lendacky@amd.com>
+Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
+Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
+Stable-dep-of: 08f0e65e784c ("crypto: ccp - Do not initialize SNP for ioctl(SNP_CONFIG)")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/crypto/ccp/sev-dev.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/crypto/ccp/sev-dev.c
++++ b/drivers/crypto/ccp/sev-dev.c
+@@ -1792,6 +1792,9 @@ static int __sev_snp_shutdown_locked(int
+ sev->snp_initialized = false;
+ dev_dbg(sev->dev, "SEV-SNP firmware shutdown\n");
+
++ /* Reset TMR size back to default */
++ sev_es_tmr_size = SEV_TMR_SIZE;
++
+ return ret;
+ }
+
--- /dev/null
+From stable+bounces-278187-greg=kroah.com@vger.kernel.org Mon Jul 20 20:24:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 14:23: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: <20260720182346.3021518-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/intel/qat/qat_common/adf_aer.c | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+--- a/drivers/crypto/intel/qat/qat_common/adf_aer.c
++++ b/drivers/crypto/intel/qat/qat_common/adf_aer.c
+@@ -157,13 +157,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-274655-greg=kroah.com@vger.kernel.org Wed Jul 15 03:01:04 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 21:00:48 -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: <20260715010048.4009225-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/intel/qat/qat_common/adf_accel_devices.h | 2
+ drivers/crypto/intel/qat/qat_common/adf_common_drv.h | 2
+ drivers/crypto/intel/qat/qat_common/adf_isr.c | 39 ++++++++++++++++
+ drivers/crypto/intel/qat/qat_common/adf_sriov.c | 20 +++++++-
+ 4 files changed, 61 insertions(+), 2 deletions(-)
+
+--- a/drivers/crypto/intel/qat/qat_common/adf_accel_devices.h
++++ b/drivers/crypto/intel/qat/qat_common/adf_accel_devices.h
+@@ -485,6 +485,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/intel/qat/qat_common/adf_common_drv.h
++++ b/drivers/crypto/intel/qat/qat_common/adf_common_drv.h
+@@ -119,6 +119,7 @@ void qat_comp_alg_callback(void *resp);
+
+ 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);
+
+@@ -192,6 +193,7 @@ int adf_sriov_configure(struct pci_dev *
+ void adf_disable_sriov(struct adf_accel_dev *accel_dev);
+ void adf_reenable_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/intel/qat/qat_common/adf_isr.c
++++ b/drivers/crypto/intel/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);
+ }
+@@ -174,6 +192,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/intel/qat/qat_common/adf_sriov.c
++++ b/drivers/crypto/intel/qat/qat_common/adf_sriov.c
+@@ -26,6 +26,9 @@ 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;
++
+ mutex_lock(&vf_info->pfvf_mig_lock);
+ ret = adf_recv_and_handle_vf2pf_msg(accel_dev, vf_nr);
+ if (ret)
+@@ -33,13 +36,18 @@ static void adf_iov_send_resp(struct wor
+ adf_enable_vf2pf_interrupts(accel_dev, 1 << vf_nr);
+ mutex_unlock(&vf_info->pfvf_mig_lock);
+
++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;
+@@ -49,6 +57,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);
+@@ -75,7 +89,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);
+
+ /* Do not enable SR-IOV if already enabled */
+ if (pci_num_vf(pdev))
+@@ -259,8 +273,10 @@ void adf_disable_sriov(struct adf_accel_
+ if (!test_bit(ADF_STATUS_RESTARTING, &accel_dev->status))
+ 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-278322-greg=kroah.com@vger.kernel.org Tue Jul 21 03:29:21 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 21:28:18 -0400
+Subject: exfat: add exfat_get_dentry_set_by_ei() helper
+To: stable@vger.kernel.org
+Cc: Yuezhang Mo <Yuezhang.Mo@sony.com>, Aoyama Wataru <wataru.aoyama@sony.com>, Daniel Palmer <daniel.palmer@sony.com>, Sungjong Seo <sj1557.seo@samsung.com>, Namjae Jeon <linkinjeon@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721012821.3459448-3-sashal@kernel.org>
+
+From: Yuezhang Mo <Yuezhang.Mo@sony.com>
+
+[ Upstream commit ac844e91364a03c35838fd488437605fbe56f8c3 ]
+
+This helper gets the directory entry set of the file for the exfat
+inode which has been created.
+
+It's used to remove all the instances of the pattern it replaces
+making the code cleaner, it's also a preparation for changing ->dir
+to record the cluster where the directory entry set is located and
+changing ->entry to record the index of the directory entry within
+the cluster.
+
+Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
+Reviewed-by: Aoyama Wataru <wataru.aoyama@sony.com>
+Reviewed-by: Daniel Palmer <daniel.palmer@sony.com>
+Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/exfat/exfat_fs.h | 2 +
+ fs/exfat/inode.c | 2 -
+ fs/exfat/namei.c | 53 +++++++++++++++++-----------------------------------
+ 3 files changed, 21 insertions(+), 36 deletions(-)
+
+--- a/fs/exfat/exfat_fs.h
++++ b/fs/exfat/exfat_fs.h
+@@ -508,6 +508,8 @@ struct exfat_dentry *exfat_get_dentry_ca
+ int exfat_get_dentry_set(struct exfat_entry_set_cache *es,
+ struct super_block *sb, struct exfat_chain *p_dir, int entry,
+ unsigned int num_entries);
++#define exfat_get_dentry_set_by_ei(es, sb, ei) \
++ exfat_get_dentry_set(es, sb, &(ei)->dir, (ei)->entry, ES_ALL_ENTRIES)
+ int exfat_get_empty_dentry_set(struct exfat_entry_set_cache *es,
+ struct super_block *sb, struct exfat_chain *p_dir, int entry,
+ unsigned int num_entries);
+--- a/fs/exfat/inode.c
++++ b/fs/exfat/inode.c
+@@ -43,7 +43,7 @@ int __exfat_write_inode(struct inode *in
+ exfat_set_volume_dirty(sb);
+
+ /* get the directory entry of given file or directory */
+- if (exfat_get_dentry_set(&es, sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES))
++ if (exfat_get_dentry_set_by_ei(&es, sb, ei))
+ return -EIO;
+ ep = exfat_get_dentry_cached(&es, ES_IDX_FILE);
+ ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM);
+--- a/fs/exfat/namei.c
++++ b/fs/exfat/namei.c
+@@ -788,26 +788,23 @@ unlock:
+ /* remove an entry, BUT don't truncate */
+ static int exfat_unlink(struct inode *dir, struct dentry *dentry)
+ {
+- struct exfat_chain cdir;
+ struct super_block *sb = dir->i_sb;
+ struct inode *inode = dentry->d_inode;
+ struct exfat_inode_info *ei = EXFAT_I(inode);
+ struct exfat_entry_set_cache es;
+- int entry, err = 0;
++ int err = 0;
+
+ if (unlikely(exfat_forced_shutdown(sb)))
+ return -EIO;
+
+ mutex_lock(&EXFAT_SB(sb)->s_lock);
+- exfat_chain_dup(&cdir, &ei->dir);
+- entry = ei->entry;
+ if (ei->dir.dir == DIR_DELETED) {
+ exfat_err(sb, "abnormal access to deleted dentry");
+ err = -ENOENT;
+ goto unlock;
+ }
+
+- err = exfat_get_dentry_set(&es, sb, &cdir, entry, ES_ALL_ENTRIES);
++ err = exfat_get_dentry_set_by_ei(&es, sb, ei);
+ if (err) {
+ err = -EIO;
+ goto unlock;
+@@ -942,21 +939,18 @@ static int exfat_check_dir_empty(struct
+ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
+ {
+ struct inode *inode = dentry->d_inode;
+- struct exfat_chain cdir, clu_to_free;
++ struct exfat_chain clu_to_free;
+ struct super_block *sb = inode->i_sb;
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+ struct exfat_inode_info *ei = EXFAT_I(inode);
+ struct exfat_entry_set_cache es;
+- int entry, err;
++ int err;
+
+ if (unlikely(exfat_forced_shutdown(sb)))
+ return -EIO;
+
+ mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
+
+- exfat_chain_dup(&cdir, &ei->dir);
+- entry = ei->entry;
+-
+ if (ei->dir.dir == DIR_DELETED) {
+ exfat_err(sb, "abnormal access to deleted dentry");
+ err = -ENOENT;
+@@ -974,7 +968,7 @@ static int exfat_rmdir(struct inode *dir
+ goto unlock;
+ }
+
+- err = exfat_get_dentry_set(&es, sb, &cdir, entry, ES_ALL_ENTRIES);
++ err = exfat_get_dentry_set_by_ei(&es, sb, ei);
+ if (err) {
+ err = -EIO;
+ goto unlock;
+@@ -1009,8 +1003,8 @@ unlock:
+ return err;
+ }
+
+-static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_dir,
+- int oldentry, struct exfat_uni_name *p_uniname,
++static int exfat_rename_file(struct inode *parent_inode,
++ struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname,
+ struct exfat_inode_info *ei)
+ {
+ int ret, num_new_entries;
+@@ -1026,7 +1020,7 @@ static int exfat_rename_file(struct inod
+ if (num_new_entries < 0)
+ return num_new_entries;
+
+- ret = exfat_get_dentry_set(&old_es, sb, p_dir, oldentry, ES_ALL_ENTRIES);
++ ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei);
+ if (ret) {
+ ret = -EIO;
+ return ret;
+@@ -1080,21 +1074,19 @@ put_old_es:
+ return ret;
+ }
+
+-static int exfat_move_file(struct inode *parent_inode, struct exfat_chain *p_olddir,
+- int oldentry, struct exfat_chain *p_newdir,
+- struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
++static int exfat_move_file(struct inode *parent_inode,
++ struct exfat_chain *p_newdir, struct exfat_uni_name *p_uniname,
++ struct exfat_inode_info *ei)
+ {
+ int ret, newentry, num_new_entries;
+ struct exfat_dentry *epmov, *epnew;
+- struct super_block *sb = parent_inode->i_sb;
+ struct exfat_entry_set_cache mov_es, new_es;
+
+ num_new_entries = exfat_calc_num_entries(p_uniname);
+ if (num_new_entries < 0)
+ return num_new_entries;
+
+- ret = exfat_get_dentry_set(&mov_es, sb, p_olddir, oldentry,
+- ES_ALL_ENTRIES);
++ ret = exfat_get_dentry_set_by_ei(&mov_es, parent_inode->i_sb, ei);
+ if (ret)
+ return -EIO;
+
+@@ -1143,8 +1135,7 @@ static int __exfat_rename(struct inode *
+ struct dentry *new_dentry)
+ {
+ int ret;
+- int dentry;
+- struct exfat_chain olddir, newdir;
++ struct exfat_chain newdir;
+ struct exfat_uni_name uni_name;
+ struct super_block *sb = old_parent_inode->i_sb;
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+@@ -1161,11 +1152,6 @@ static int __exfat_rename(struct inode *
+ return -ENOENT;
+ }
+
+- exfat_chain_set(&olddir, EXFAT_I(old_parent_inode)->start_clu,
+- EXFAT_B_TO_CLU_ROUND_UP(i_size_read(old_parent_inode), sbi),
+- EXFAT_I(old_parent_inode)->flags);
+- dentry = ei->entry;
+-
+ /* check whether new dir is existing directory and empty */
+ if (new_inode) {
+ ret = -EIO;
+@@ -1200,21 +1186,18 @@ static int __exfat_rename(struct inode *
+
+ exfat_set_volume_dirty(sb);
+
+- if (olddir.dir == newdir.dir)
+- ret = exfat_rename_file(new_parent_inode, &olddir, dentry,
++ if (new_parent_inode == old_parent_inode)
++ ret = exfat_rename_file(new_parent_inode, &newdir,
+ &uni_name, ei);
+ else
+- ret = exfat_move_file(new_parent_inode, &olddir, dentry,
+- &newdir, &uni_name, ei);
++ ret = exfat_move_file(new_parent_inode, &newdir,
++ &uni_name, ei);
+
+ if (!ret && new_inode) {
+ struct exfat_entry_set_cache es;
+- struct exfat_chain *p_dir = &(new_ei->dir);
+- int new_entry = new_ei->entry;
+
+ /* delete entries of new_dir */
+- ret = exfat_get_dentry_set(&es, sb, p_dir, new_entry,
+- ES_ALL_ENTRIES);
++ ret = exfat_get_dentry_set_by_ei(&es, sb, new_ei);
+ if (ret) {
+ ret = -EIO;
+ goto del_out;
--- /dev/null
+From stable+bounces-278324-greg=kroah.com@vger.kernel.org Tue Jul 21 03:29:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 21:28:20 -0400
+Subject: exfat: fix incorrect directory checksum after rename to shorter name
+To: stable@vger.kernel.org
+Cc: Chi Zhiling <chizhiling@kylinos.cn>, Sungjong Seo <sj1557.seo@samsung.com>, Yuezhang Mo <Yuezhang.Mo@sony.com>, Namjae Jeon <linkinjeon@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721012821.3459448-5-sashal@kernel.org>
+
+From: Chi Zhiling <chizhiling@kylinos.cn>
+
+[ Upstream commit ff37797badd831797b8a27830fe5046d7e23fdc3 ]
+
+When renaming a file in-place to a shorter name, exfat_remove_entries
+marks excess entries as DELETED, but es->num_entries is not updated
+accordingly. As a result, exfat_update_dir_chksum iterates over the
+deleted entries and computes an incorrect checksum.
+
+This does not lead to persistent corruption because mark_inode_dirty()
+is called afterward, and __exfat_write_inode later recomputes the
+checksum using the correct num_entries value.
+
+Fix by setting es->num_entries = num_entries in exfat_init_ext_entry.
+
+Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
+Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
+Reviewed-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/exfat/dir.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/fs/exfat/dir.c
++++ b/fs/exfat/dir.c
+@@ -507,6 +507,7 @@ void exfat_init_ext_entry(struct exfat_e
+ unsigned short *uniname = p_uniname->name;
+ struct exfat_dentry *ep;
+
++ es->num_entries = num_entries;
+ ep = exfat_get_dentry_cached(es, ES_IDX_FILE);
+ ep->dentry.file.num_ext = (unsigned char)(num_entries - 1);
+
--- /dev/null
+From stable+bounces-278323-greg=kroah.com@vger.kernel.org Tue Jul 21 03:29:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 21:28:19 -0400
+Subject: exfat: move exfat_chain_set() out of __exfat_resolve_path()
+To: stable@vger.kernel.org
+Cc: Yuezhang Mo <Yuezhang.Mo@sony.com>, Aoyama Wataru <wataru.aoyama@sony.com>, Daniel Palmer <daniel.palmer@sony.com>, Sungjong Seo <sj1557.seo@samsung.com>, Namjae Jeon <linkinjeon@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721012821.3459448-4-sashal@kernel.org>
+
+From: Yuezhang Mo <Yuezhang.Mo@sony.com>
+
+[ Upstream commit 0891c7313d87a1b6baf7162bc2f0d755ce70383f ]
+
+__exfat_resolve_path() mixes two functions. The first one is to
+resolve and check if the path is valid. The second one is to output
+the cluster assigned to the directory.
+
+The second one is only needed when need to traverse the directory
+entries, and calling exfat_chain_set() so early causes p_dir to be
+passed as an argument multiple times, increasing the complexity of
+the code.
+
+This commit moves the call to exfat_chain_set() before traversing
+directory entries.
+
+Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
+Reviewed-by: Aoyama Wataru <wataru.aoyama@sony.com>
+Reviewed-by: Daniel Palmer <daniel.palmer@sony.com>
+Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/exfat/namei.c | 60 +++++++++++++++++++++++--------------------------------
+ 1 file changed, 26 insertions(+), 34 deletions(-)
+
+--- a/fs/exfat/namei.c
++++ b/fs/exfat/namei.c
+@@ -311,6 +311,9 @@ static int exfat_find_empty_entry(struct
+ ei->hint_femp.eidx = EXFAT_HINT_NONE;
+ }
+
++ exfat_chain_set(p_dir, ei->start_clu,
++ EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags);
++
+ while ((dentry = exfat_search_empty_slot(sb, &hint_femp, p_dir,
+ num_entries, es)) < 0) {
+ if (dentry == -EIO)
+@@ -386,14 +389,11 @@ static int exfat_find_empty_entry(struct
+ * Zero if it was successful; otherwise nonzero.
+ */
+ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path,
+- struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname,
+- int lookup)
++ struct exfat_uni_name *p_uniname, int lookup)
+ {
+ int namelen;
+ int lossy = NLS_NAME_NO_LOSSY;
+ struct super_block *sb = inode->i_sb;
+- struct exfat_sb_info *sbi = EXFAT_SB(sb);
+- struct exfat_inode_info *ei = EXFAT_I(inode);
+ int pathlen = strlen(path);
+
+ /*
+@@ -432,24 +432,19 @@ static int __exfat_resolve_path(struct i
+ if ((lossy && !lookup) || !namelen)
+ return (lossy & NLS_NAME_OVERLEN) ? -ENAMETOOLONG : -EINVAL;
+
+- exfat_chain_set(p_dir, ei->start_clu,
+- EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags);
+-
+ return 0;
+ }
+
+ static inline int exfat_resolve_path(struct inode *inode,
+- const unsigned char *path, struct exfat_chain *dir,
+- struct exfat_uni_name *uni)
++ const unsigned char *path, struct exfat_uni_name *uni)
+ {
+- return __exfat_resolve_path(inode, path, dir, uni, 0);
++ return __exfat_resolve_path(inode, path, uni, 0);
+ }
+
+ static inline int exfat_resolve_path_for_lookup(struct inode *inode,
+- const unsigned char *path, struct exfat_chain *dir,
+- struct exfat_uni_name *uni)
++ const unsigned char *path, struct exfat_uni_name *uni)
+ {
+- return __exfat_resolve_path(inode, path, dir, uni, 1);
++ return __exfat_resolve_path(inode, path, uni, 1);
+ }
+
+ static inline loff_t exfat_make_i_pos(struct exfat_dir_entry *info)
+@@ -471,7 +466,7 @@ static int exfat_add_entry(struct inode
+ int clu_size = 0;
+ unsigned int start_clu = EXFAT_FREE_CLUSTER;
+
+- ret = exfat_resolve_path(inode, path, p_dir, &uniname);
++ ret = exfat_resolve_path(inode, path, &uniname);
+ if (ret)
+ goto out;
+
+@@ -602,10 +597,13 @@ static int exfat_find(struct inode *dir,
+ return -ENOENT;
+
+ /* check the validity of directory name in the given pathname */
+- ret = exfat_resolve_path_for_lookup(dir, qname->name, &cdir, &uni_name);
++ ret = exfat_resolve_path_for_lookup(dir, qname->name, &uni_name);
+ if (ret)
+ return ret;
+
++ exfat_chain_set(&cdir, ei->start_clu,
++ EXFAT_B_TO_CLU(i_size_read(dir), sbi), ei->flags);
++
+ /* check the validation of hint_stat and initialize it if required */
+ if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) {
+ ei->hint_stat.clu = cdir.dir;
+@@ -1004,8 +1002,7 @@ unlock:
+ }
+
+ static int exfat_rename_file(struct inode *parent_inode,
+- struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname,
+- struct exfat_inode_info *ei)
++ struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
+ {
+ int ret, num_new_entries;
+ struct exfat_dentry *epold, *epnew;
+@@ -1030,9 +1027,10 @@ static int exfat_rename_file(struct inod
+
+ if (old_es.num_entries < num_new_entries) {
+ int newentry;
++ struct exfat_chain dir;
+
+- newentry = exfat_find_empty_entry(parent_inode, p_dir, num_new_entries,
+- &new_es);
++ newentry = exfat_find_empty_entry(parent_inode, &dir,
++ num_new_entries, &new_es);
+ if (newentry < 0) {
+ ret = newentry; /* -EIO or -ENOSPC */
+ goto put_old_es;
+@@ -1056,7 +1054,7 @@ static int exfat_rename_file(struct inod
+ goto put_old_es;
+
+ exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE);
+- ei->dir = *p_dir;
++ ei->dir = dir;
+ ei->entry = newentry;
+ } else {
+ if (exfat_get_entry_type(epold) == TYPE_FILE) {
+@@ -1075,12 +1073,12 @@ put_old_es:
+ }
+
+ static int exfat_move_file(struct inode *parent_inode,
+- struct exfat_chain *p_newdir, struct exfat_uni_name *p_uniname,
+- struct exfat_inode_info *ei)
++ struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
+ {
+ int ret, newentry, num_new_entries;
+ struct exfat_dentry *epmov, *epnew;
+ struct exfat_entry_set_cache mov_es, new_es;
++ struct exfat_chain newdir;
+
+ num_new_entries = exfat_calc_num_entries(p_uniname);
+ if (num_new_entries < 0)
+@@ -1090,8 +1088,8 @@ static int exfat_move_file(struct inode
+ if (ret)
+ return -EIO;
+
+- newentry = exfat_find_empty_entry(parent_inode, p_newdir, num_new_entries,
+- &new_es);
++ newentry = exfat_find_empty_entry(parent_inode, &newdir,
++ num_new_entries, &new_es);
+ if (newentry < 0) {
+ ret = newentry; /* -EIO or -ENOSPC */
+ goto put_mov_es;
+@@ -1112,9 +1110,7 @@ static int exfat_move_file(struct inode
+ exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
+ exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE);
+
+- exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size,
+- p_newdir->flags);
+-
++ ei->dir = newdir;
+ ei->entry = newentry;
+
+ ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode));
+@@ -1135,7 +1131,6 @@ static int __exfat_rename(struct inode *
+ struct dentry *new_dentry)
+ {
+ int ret;
+- struct exfat_chain newdir;
+ struct exfat_uni_name uni_name;
+ struct super_block *sb = old_parent_inode->i_sb;
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+@@ -1179,19 +1174,16 @@ static int __exfat_rename(struct inode *
+ }
+
+ /* check the validity of directory name in the given new pathname */
+- ret = exfat_resolve_path(new_parent_inode, new_path, &newdir,
+- &uni_name);
++ ret = exfat_resolve_path(new_parent_inode, new_path, &uni_name);
+ if (ret)
+ goto out;
+
+ exfat_set_volume_dirty(sb);
+
+ if (new_parent_inode == old_parent_inode)
+- ret = exfat_rename_file(new_parent_inode, &newdir,
+- &uni_name, ei);
++ ret = exfat_rename_file(new_parent_inode, &uni_name, ei);
+ else
+- ret = exfat_move_file(new_parent_inode, &newdir,
+- &uni_name, ei);
++ ret = exfat_move_file(new_parent_inode, &uni_name, ei);
+
+ if (!ret && new_inode) {
+ struct exfat_entry_set_cache es;
--- /dev/null
+From stable+bounces-278325-greg=kroah.com@vger.kernel.org Tue Jul 21 03:29:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 21:28:21 -0400
+Subject: exfat: preserve benign secondary entries during rename and move
+To: stable@vger.kernel.org
+Cc: Rochan Avlur <rochan.avlur@gmail.com>, Yuezhang Mo <Yuezhang.Mo@sony.com>, Namjae Jeon <linkinjeon@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721012821.3459448-6-sashal@kernel.org>
+
+From: Rochan Avlur <rochan.avlur@gmail.com>
+
+[ Upstream commit 942296784b2a9439651750c42f540bf2579b330f ]
+
+Commit 8258ef28001a ("exfat: handle unreconized benign secondary
+entries") added cluster freeing for benign secondary entries inside
+exfat_remove_entries(). However, exfat_remove_entries() is also called
+from the rename and move paths (exfat_rename_file and exfat_move_file),
+where the old entry set is being relocated rather than deleted. This
+causes benign secondary entries such as vendor extension entries to be
+silently destroyed on rename or cross-directory move, violating the
+exFAT spec requirement (section 8.2) that implementations preserve
+unrecognized benign secondary entries.
+
+Fix this by adding a free_benign parameter to exfat_remove_entries()
+so callers can suppress cluster freeing during relocation, and
+extending exfat_init_ext_entry() to copy trailing benign secondary
+entries from the old entry set into the new one internally. Also
+clean up the error paths to delete newly allocated entries on failure.
+
+Fixes: 8258ef28001a ("exfat: handle unreconized benign secondary entries")
+Cc: stable@vger.kernel.org
+Link: https://lore.kernel.org/linux-fsdevel/CAG7tbBV--waov7XVu2FHQEc6paR92dufS=em9DW5Kzsrpu3iQg@mail.gmail.com/
+Signed-off-by: Rochan Avlur <rochan.avlur@gmail.com>
+Reviewed-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/exfat/dir.c | 50 +++++++++++++++++++++++++----
+ fs/exfat/exfat_fs.h | 5 +-
+ fs/exfat/namei.c | 89 ++++++++++++++++++++++++++++++++++++++++------------
+ 3 files changed, 116 insertions(+), 28 deletions(-)
+
+--- a/fs/exfat/dir.c
++++ b/fs/exfat/dir.c
+@@ -500,32 +500,70 @@ static void exfat_free_benign_secondary_
+ exfat_free_cluster(inode, &dir);
+ }
+
++/*
++ * exfat_init_ext_entry - initialize extension entries in a directory entry set
++ * @es: target entry set
++ * @num_entries: number of entries excluding benign secondary entries
++ * @p_uniname: filename to store
++ * @old_es: optional source entry set with benign secondary entries, or NULL
++ * @num_extra: number of benign secondary entries to copy from @old_es
++ *
++ * Set up the file, stream extension, and filename entries in @es, optionally
++ * preserving @num_extra benign secondary entries from @old_es. @es and @old_es
++ * may refer to the same entry set; excess entries are marked as deleted.
++ */
+ void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries,
+- struct exfat_uni_name *p_uniname)
++ struct exfat_uni_name *p_uniname,
++ struct exfat_entry_set_cache *old_es, int num_extra)
+ {
+- int i;
++ int i, src_start = 0, old_num;
+ unsigned short *uniname = p_uniname->name;
+ struct exfat_dentry *ep;
+
+- es->num_entries = num_entries;
++ if (WARN_ON(num_extra < 0 || (num_extra && (!old_es ||
++ old_es->num_entries < ES_IDX_FIRST_FILENAME + num_extra))))
++ num_extra = 0;
++
++ /*
++ * Save old entry count and source position before modifying
++ * es->num_entries, since old_es and es may point to the same
++ * entry set.
++ */
++ old_num = es->num_entries;
++ if (old_es && num_extra > 0)
++ src_start = old_es->num_entries - num_extra;
++
++ es->num_entries = num_entries + num_extra;
+ ep = exfat_get_dentry_cached(es, ES_IDX_FILE);
+- ep->dentry.file.num_ext = (unsigned char)(num_entries - 1);
++ ep->dentry.file.num_ext = (unsigned char)(num_entries - 1 + num_extra);
+
+ ep = exfat_get_dentry_cached(es, ES_IDX_STREAM);
+ ep->dentry.stream.name_len = p_uniname->name_len;
+ ep->dentry.stream.name_hash = cpu_to_le16(p_uniname->name_hash);
+
++ if (old_es && num_extra > 0) {
++ for (i = 0; i < num_extra; i++)
++ *exfat_get_dentry_cached(es, num_entries + i) =
++ *exfat_get_dentry_cached(old_es, src_start + i);
++ }
++
+ for (i = ES_IDX_FIRST_FILENAME; i < num_entries; i++) {
+ ep = exfat_get_dentry_cached(es, i);
+ exfat_init_name_entry(ep, uniname);
+ uniname += EXFAT_FILE_NAME_LEN;
+ }
+
++ /* Mark excess old entries as deleted (in-place shrink) */
++ for (i = num_entries + num_extra; i < old_num; i++) {
++ ep = exfat_get_dentry_cached(es, i);
++ exfat_set_entry_type(ep, TYPE_DELETED);
++ }
++
+ exfat_update_dir_chksum(es);
+ }
+
+ void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
+- int order)
++ int order, bool free_benign)
+ {
+ int i;
+ struct exfat_dentry *ep;
+@@ -533,7 +571,7 @@ void exfat_remove_entries(struct inode *
+ for (i = order; i < es->num_entries; i++) {
+ ep = exfat_get_dentry_cached(es, i);
+
+- if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC)
++ if (free_benign && (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC))
+ exfat_free_benign_secondary_clusters(inode, ep);
+
+ exfat_set_entry_type(ep, TYPE_DELETED);
+--- a/fs/exfat/exfat_fs.h
++++ b/fs/exfat/exfat_fs.h
+@@ -492,9 +492,10 @@ void exfat_init_dir_entry(struct exfat_e
+ unsigned int type, unsigned int start_clu,
+ unsigned long long size, struct timespec64 *ts);
+ void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries,
+- struct exfat_uni_name *p_uniname);
++ struct exfat_uni_name *p_uniname,
++ struct exfat_entry_set_cache *old_es, int num_extra);
+ void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
+- int order);
++ int order, bool free_benign);
+ void exfat_update_dir_chksum(struct exfat_entry_set_cache *es);
+ int exfat_calc_num_entries(struct exfat_uni_name *p_uniname);
+ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei,
+--- a/fs/exfat/namei.c
++++ b/fs/exfat/namei.c
+@@ -498,7 +498,7 @@ static int exfat_add_entry(struct inode
+ * the first cluster is not determined yet. (0)
+ */
+ exfat_init_dir_entry(&es, type, start_clu, clu_size, &ts);
+- exfat_init_ext_entry(&es, num_entries, &uniname);
++ exfat_init_ext_entry(&es, num_entries, &uniname, NULL, 0);
+
+ ret = exfat_put_dentry_set(&es, IS_DIRSYNC(inode));
+ if (ret)
+@@ -811,7 +811,7 @@ static int exfat_unlink(struct inode *di
+ exfat_set_volume_dirty(sb);
+
+ /* update the directory entry */
+- exfat_remove_entries(inode, &es, ES_IDX_FILE);
++ exfat_remove_entries(inode, &es, ES_IDX_FILE, true);
+
+ err = exfat_put_dentry_set(&es, IS_DIRSYNC(inode));
+ if (err)
+@@ -974,7 +974,7 @@ static int exfat_rmdir(struct inode *dir
+
+ exfat_set_volume_dirty(sb);
+
+- exfat_remove_entries(inode, &es, ES_IDX_FILE);
++ exfat_remove_entries(inode, &es, ES_IDX_FILE, true);
+
+ err = exfat_put_dentry_set(&es, IS_DIRSYNC(dir));
+ if (err)
+@@ -1001,6 +1001,23 @@ unlock:
+ return err;
+ }
+
++/*
++ * Count benign secondary entries beyond the filename entries.
++ * Returns the count, or -EIO if the entry set is inconsistent.
++ */
++static int exfat_count_extra_entries(struct exfat_entry_set_cache *es)
++{
++ struct exfat_dentry *stream;
++ unsigned int name_entries;
++ int extra;
++
++ stream = exfat_get_dentry_cached(es, ES_IDX_STREAM);
++ name_entries = EXFAT_FILENAME_ENTRY_NUM(stream->dentry.stream.name_len);
++ extra = es->num_entries - (ES_IDX_FIRST_FILENAME + name_entries);
++
++ return extra >= 0 ? extra : -EIO;
++}
++
+ static int exfat_rename_file(struct inode *parent_inode,
+ struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
+ {
+@@ -1009,6 +1026,7 @@ static int exfat_rename_file(struct inod
+ struct super_block *sb = parent_inode->i_sb;
+ struct exfat_entry_set_cache old_es, new_es;
+ int sync = IS_DIRSYNC(parent_inode);
++ unsigned int num_extra_entries, num_total_entries;
+
+ if (unlikely(exfat_forced_shutdown(sb)))
+ return -EIO;
+@@ -1018,19 +1036,23 @@ static int exfat_rename_file(struct inod
+ return num_new_entries;
+
+ ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei);
+- if (ret) {
+- ret = -EIO;
+- return ret;
+- }
++ if (ret)
++ return -EIO;
+
+ epold = exfat_get_dentry_cached(&old_es, ES_IDX_FILE);
+
+- if (old_es.num_entries < num_new_entries) {
++ ret = exfat_count_extra_entries(&old_es);
++ if (ret < 0)
++ goto put_old_es;
++ num_extra_entries = ret;
++ num_total_entries = num_new_entries + num_extra_entries;
++
++ if (old_es.num_entries < num_total_entries) {
+ int newentry;
+ struct exfat_chain dir;
+
+ newentry = exfat_find_empty_entry(parent_inode, &dir,
+- num_new_entries, &new_es);
++ num_total_entries, &new_es);
+ if (newentry < 0) {
+ ret = newentry; /* -EIO or -ENOSPC */
+ goto put_old_es;
+@@ -1047,13 +1069,23 @@ static int exfat_rename_file(struct inod
+ epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM);
+ *epnew = *epold;
+
+- exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
++ exfat_init_ext_entry(&new_es, num_new_entries, p_uniname,
++ &old_es, num_extra_entries);
+
+ ret = exfat_put_dentry_set(&new_es, sync);
+- if (ret)
++ if (ret) {
++ /* Best-effort delete to avoid duplicate entries */
++ if (!exfat_get_dentry_set(&new_es, sb,
++ &dir, newentry,
++ ES_ALL_ENTRIES)) {
++ exfat_remove_entries(parent_inode, &new_es,
++ ES_IDX_FILE, false);
++ exfat_put_dentry_set(&new_es, false);
++ }
+ goto put_old_es;
++ }
+
+- exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE);
++ exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE, false);
+ ei->dir = dir;
+ ei->entry = newentry;
+ } else {
+@@ -1062,8 +1094,8 @@ static int exfat_rename_file(struct inod
+ ei->attr |= EXFAT_ATTR_ARCHIVE;
+ }
+
+- exfat_remove_entries(parent_inode, &old_es, ES_IDX_FIRST_FILENAME + 1);
+- exfat_init_ext_entry(&old_es, num_new_entries, p_uniname);
++ exfat_init_ext_entry(&old_es, num_new_entries, p_uniname,
++ &old_es, num_extra_entries);
+ }
+ return exfat_put_dentry_set(&old_es, sync);
+
+@@ -1079,6 +1111,7 @@ static int exfat_move_file(struct inode
+ struct exfat_dentry *epmov, *epnew;
+ struct exfat_entry_set_cache mov_es, new_es;
+ struct exfat_chain newdir;
++ unsigned int num_extra_entries, num_total_entries;
+
+ num_new_entries = exfat_calc_num_entries(p_uniname);
+ if (num_new_entries < 0)
+@@ -1088,8 +1121,14 @@ static int exfat_move_file(struct inode
+ if (ret)
+ return -EIO;
+
++ ret = exfat_count_extra_entries(&mov_es);
++ if (ret < 0)
++ goto put_mov_es;
++ num_extra_entries = ret;
++ num_total_entries = num_new_entries + num_extra_entries;
++
+ newentry = exfat_find_empty_entry(parent_inode, &newdir,
+- num_new_entries, &new_es);
++ num_total_entries, &new_es);
+ if (newentry < 0) {
+ ret = newentry; /* -EIO or -ENOSPC */
+ goto put_mov_es;
+@@ -1107,21 +1146,31 @@ static int exfat_move_file(struct inode
+ epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM);
+ *epnew = *epmov;
+
+- exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
+- exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE);
++ exfat_init_ext_entry(&new_es, num_new_entries, p_uniname,
++ &mov_es, num_extra_entries);
++
++ exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE, false);
+
+ ei->dir = newdir;
+ ei->entry = newentry;
+
+ ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode));
+- if (ret)
++ if (ret) {
++ /* Best-effort delete to avoid duplicate entries */
++ if (!exfat_get_dentry_set(&new_es, parent_inode->i_sb,
++ &newdir, newentry,
++ ES_ALL_ENTRIES)) {
++ exfat_remove_entries(parent_inode, &new_es,
++ ES_IDX_FILE, false);
++ exfat_put_dentry_set(&new_es, false);
++ }
+ goto put_mov_es;
++ }
+
+ return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode));
+
+ put_mov_es:
+ exfat_put_dentry_set(&mov_es, false);
+-
+ return ret;
+ }
+
+@@ -1195,7 +1244,7 @@ static int __exfat_rename(struct inode *
+ goto del_out;
+ }
+
+- exfat_remove_entries(new_inode, &es, ES_IDX_FILE);
++ exfat_remove_entries(new_inode, &es, ES_IDX_FILE, true);
+
+ ret = exfat_put_dentry_set(&es, IS_DIRSYNC(new_inode));
+ if (ret)
--- /dev/null
+From stable+bounces-278320-greg=kroah.com@vger.kernel.org Tue Jul 21 03:29:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 21:28:16 -0400
+Subject: exfat: remove unnecessary read entry in __exfat_rename()
+To: stable@vger.kernel.org
+Cc: Yuezhang Mo <Yuezhang.Mo@sony.com>, Aoyama Wataru <wataru.aoyama@sony.com>, Daniel Palmer <daniel.palmer@sony.com>, Sungjong Seo <sj1557.seo@samsung.com>, Namjae Jeon <linkinjeon@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721012821.3459448-1-sashal@kernel.org>
+
+From: Yuezhang Mo <Yuezhang.Mo@sony.com>
+
+[ Upstream commit 30ef0e0d7ff5b6dceda19d18a85d9d72a4909784 ]
+
+To determine whether it is a directory, there is no need to read its
+directory entry, just use S_ISDIR(inode->i_mode).
+
+Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
+Reviewed-by: Aoyama Wataru <wataru.aoyama@sony.com>
+Reviewed-by: Daniel Palmer <daniel.palmer@sony.com>
+Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/exfat/namei.c | 20 ++++----------------
+ 1 file changed, 4 insertions(+), 16 deletions(-)
+
+--- a/fs/exfat/namei.c
++++ b/fs/exfat/namei.c
+@@ -1145,17 +1145,12 @@ static int __exfat_rename(struct inode *
+ int ret;
+ int dentry;
+ struct exfat_chain olddir, newdir;
+- struct exfat_chain *p_dir = NULL;
+ struct exfat_uni_name uni_name;
+- struct exfat_dentry *ep;
+ struct super_block *sb = old_parent_inode->i_sb;
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+ const unsigned char *new_path = new_dentry->d_name.name;
+ struct inode *new_inode = new_dentry->d_inode;
+ struct exfat_inode_info *new_ei = NULL;
+- unsigned int new_entry_type = TYPE_UNUSED;
+- int new_entry = 0;
+- struct buffer_head *new_bh = NULL;
+
+ /* check the validity of pointer parameters */
+ if (new_path == NULL || strlen(new_path) == 0)
+@@ -1181,17 +1176,8 @@ static int __exfat_rename(struct inode *
+ goto out;
+ }
+
+- p_dir = &(new_ei->dir);
+- new_entry = new_ei->entry;
+- ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh);
+- if (!ep)
+- goto out;
+-
+- new_entry_type = exfat_get_entry_type(ep);
+- brelse(new_bh);
+-
+ /* if new_inode exists, update ei */
+- if (new_entry_type == TYPE_DIR) {
++ if (S_ISDIR(new_inode->i_mode)) {
+ struct exfat_chain new_clu;
+
+ new_clu.dir = new_ei->start_clu;
+@@ -1223,6 +1209,8 @@ static int __exfat_rename(struct inode *
+
+ if (!ret && new_inode) {
+ struct exfat_entry_set_cache es;
++ struct exfat_chain *p_dir = &(new_ei->dir);
++ int new_entry = new_ei->entry;
+
+ /* delete entries of new_dir */
+ ret = exfat_get_dentry_set(&es, sb, p_dir, new_entry,
+@@ -1239,7 +1227,7 @@ static int __exfat_rename(struct inode *
+ goto del_out;
+
+ /* Free the clusters if new_inode is a dir(as if exfat_rmdir) */
+- if (new_entry_type == TYPE_DIR &&
++ if (S_ISDIR(new_inode->i_mode) &&
+ new_ei->start_clu != EXFAT_EOF_CLUSTER) {
+ /* new_ei, new_clu_to_free */
+ struct exfat_chain new_clu_to_free;
--- /dev/null
+From stable+bounces-278321-greg=kroah.com@vger.kernel.org Tue Jul 21 03:29:17 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 21:28:17 -0400
+Subject: exfat: rename argument name for exfat_move_file and exfat_rename_file
+To: stable@vger.kernel.org
+Cc: Yuezhang Mo <Yuezhang.Mo@sony.com>, Sungjong Seo <sj1557.seo@samsung.com>, Namjae Jeon <linkinjeon@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721012821.3459448-2-sashal@kernel.org>
+
+From: Yuezhang Mo <Yuezhang.Mo@sony.com>
+
+[ Upstream commit 06a2b0b3b490a6103376652c01c3ac6e8e22e654 ]
+
+In this exfat implementation, the relationship between inode and ei
+is ei=EXFAT_I(inode). However, in the arguments of exfat_move_file()
+and exfat_rename_file(), argument 'inode' indicates the parent
+directory, but argument 'ei' indicates the target file to be renamed.
+They do not have the above relationship, which is not friendly to code
+readers.
+
+So this commit renames 'inode' to 'parent_inode', making the argument
+name match its role.
+
+Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
+Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/exfat/namei.c | 24 ++++++++++++------------
+ 1 file changed, 12 insertions(+), 12 deletions(-)
+
+--- a/fs/exfat/namei.c
++++ b/fs/exfat/namei.c
+@@ -1009,15 +1009,15 @@ unlock:
+ return err;
+ }
+
+-static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,
++static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_dir,
+ int oldentry, struct exfat_uni_name *p_uniname,
+ struct exfat_inode_info *ei)
+ {
+ int ret, num_new_entries;
+ struct exfat_dentry *epold, *epnew;
+- struct super_block *sb = inode->i_sb;
++ struct super_block *sb = parent_inode->i_sb;
+ struct exfat_entry_set_cache old_es, new_es;
+- int sync = IS_DIRSYNC(inode);
++ int sync = IS_DIRSYNC(parent_inode);
+
+ if (unlikely(exfat_forced_shutdown(sb)))
+ return -EIO;
+@@ -1037,7 +1037,7 @@ static int exfat_rename_file(struct inod
+ if (old_es.num_entries < num_new_entries) {
+ int newentry;
+
+- newentry = exfat_find_empty_entry(inode, p_dir, num_new_entries,
++ newentry = exfat_find_empty_entry(parent_inode, p_dir, num_new_entries,
+ &new_es);
+ if (newentry < 0) {
+ ret = newentry; /* -EIO or -ENOSPC */
+@@ -1061,7 +1061,7 @@ static int exfat_rename_file(struct inod
+ if (ret)
+ goto put_old_es;
+
+- exfat_remove_entries(inode, &old_es, ES_IDX_FILE);
++ exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE);
+ ei->dir = *p_dir;
+ ei->entry = newentry;
+ } else {
+@@ -1070,7 +1070,7 @@ static int exfat_rename_file(struct inod
+ ei->attr |= EXFAT_ATTR_ARCHIVE;
+ }
+
+- exfat_remove_entries(inode, &old_es, ES_IDX_FIRST_FILENAME + 1);
++ exfat_remove_entries(parent_inode, &old_es, ES_IDX_FIRST_FILENAME + 1);
+ exfat_init_ext_entry(&old_es, num_new_entries, p_uniname);
+ }
+ return exfat_put_dentry_set(&old_es, sync);
+@@ -1080,13 +1080,13 @@ put_old_es:
+ return ret;
+ }
+
+-static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir,
++static int exfat_move_file(struct inode *parent_inode, struct exfat_chain *p_olddir,
+ int oldentry, struct exfat_chain *p_newdir,
+ struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
+ {
+ int ret, newentry, num_new_entries;
+ struct exfat_dentry *epmov, *epnew;
+- struct super_block *sb = inode->i_sb;
++ struct super_block *sb = parent_inode->i_sb;
+ struct exfat_entry_set_cache mov_es, new_es;
+
+ num_new_entries = exfat_calc_num_entries(p_uniname);
+@@ -1098,7 +1098,7 @@ static int exfat_move_file(struct inode
+ if (ret)
+ return -EIO;
+
+- newentry = exfat_find_empty_entry(inode, p_newdir, num_new_entries,
++ newentry = exfat_find_empty_entry(parent_inode, p_newdir, num_new_entries,
+ &new_es);
+ if (newentry < 0) {
+ ret = newentry; /* -EIO or -ENOSPC */
+@@ -1118,18 +1118,18 @@ static int exfat_move_file(struct inode
+ *epnew = *epmov;
+
+ exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
+- exfat_remove_entries(inode, &mov_es, ES_IDX_FILE);
++ exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE);
+
+ exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size,
+ p_newdir->flags);
+
+ ei->entry = newentry;
+
+- ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(inode));
++ ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode));
+ if (ret)
+ goto put_mov_es;
+
+- return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(inode));
++ return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode));
+
+ put_mov_es:
+ exfat_put_dentry_set(&mov_es, false);
--- /dev/null
+From stable+bounces-274889-greg=kroah.com@vger.kernel.org Wed Jul 15 13:15:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 07:15:02 -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: <20260715111502.642327-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>
+[ dropped the `return 0;` and kept sch_gpio_set()'s void signature since 6.12 lacks the GPIO setter-return-value conversion ]
+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
+@@ -39,7 +39,7 @@
+ struct sch_gpio {
+ struct gpio_chip chip;
+ void __iomem *regs;
+- spinlock_t lock;
++ raw_spinlock_t lock;
+ unsigned short resume_base;
+
+ /* GPE handling */
+@@ -104,9 +104,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;
+ }
+
+@@ -122,9 +122,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,
+@@ -133,9 +133,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
+@@ -195,14 +195,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;
+ }
+@@ -214,9 +214,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)
+@@ -224,9 +224,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)
+@@ -267,12 +267,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 = ioread32(sch->regs + CORE_BANK_OFFSET + GTS);
+ resume_status = ioread32(sch->regs + 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)
+@@ -342,7 +342,7 @@ static int sch_gpio_probe(struct platfor
+
+ sch->regs = regs;
+
+- spin_lock_init(&sch->lock);
++ raw_spin_lock_init(&sch->lock);
+ sch->chip = sch_gpio_chip;
+ sch->chip.label = dev_name(dev);
+ sch->chip.parent = dev;
--- /dev/null
+From stable+bounces-277225-greg=kroah.com@vger.kernel.org Sat Jul 18 02:51:24 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 20:51:15 -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: <20260718005115.2484456-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
+@@ -614,7 +614,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-277226-greg=kroah.com@vger.kernel.org Sat Jul 18 02:51:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 20:51:14 -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: <20260718005115.2484456-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
+@@ -167,7 +167,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
+@@ -259,7 +259,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,74 +171,90 @@ 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_ext_keycmp(const btree_key *key1, const btree_key *key2);
+ extern u16 hfs_ext_find_block(struct hfs_extent *ext, u16 off);
+-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_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 folio **foliop, 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 mnt_idmap *, struct dentry *,
+- struct iattr *);
++ loff_t pos, unsigned int len, struct folio **foliop,
++ 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 mnt_idmap *idmap, 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 * const 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
+@@ -45,7 +45,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 folio **foliop, void **fsdata)
++ loff_t pos, unsigned int len, struct folio **foliop,
++ 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
+@@ -389,21 +389,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);
+@@ -418,7 +418,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 */
+@@ -431,7 +431,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 */
+@@ -589,14 +589,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);
+ }
+@@ -605,7 +605,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;
+
+@@ -615,12 +615,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-277058-greg=kroah.com@vger.kernel.org Fri Jul 17 16:04:05 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:00:20 -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: <20260717140021.1717288-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
+@@ -154,6 +154,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_CAMERA 0x00900000
+@@ -314,6 +315,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
+
+@@ -420,6 +443,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-277092-greg=kroah.com@vger.kernel.org Fri Jul 17 17:00:46 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:55:02 -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: <20260717145502.1925738-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-277059-greg=kroah.com@vger.kernel.org Fri Jul 17 16:00:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:00:21 -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: <20260717140021.1717288-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>
+@@ -84,8 +85,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;
+@@ -968,7 +967,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);
+@@ -1174,9 +1173,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;
+@@ -1311,7 +1310,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
+@@ -1351,6 +1350,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),
+@@ -1815,7 +1823,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);
+@@ -1838,7 +1846,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-277191-greg=kroah.com@vger.kernel.org Fri Jul 17 21:24:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 15:24:01 -0400
+Subject: HID: pidff: Add missing spaces
+To: stable@vger.kernel.org
+Cc: "Tomasz Pakuła" <tomasz.pakula.oficjalny@gmail.com>, "Jiri Kosina" <jkosina@suse.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260717192403.2131944-2-sashal@kernel.org>
+
+From: Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>
+
+[ Upstream commit 61ea33ded9327a07d9ef85a6933cb6316e2f185f ]
+
+Fixes checkpatch.pl errors
+
+Signed-off-by: Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>
+Signed-off-by: Jiri Kosina <jkosina@suse.com>
+Stable-dep-of: b251598b8bf3 ("HID: pidff: Use correct effect type in effect update")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/hid/usbhid/hid-pidff.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+--- a/drivers/hid/usbhid/hid-pidff.c
++++ b/drivers/hid/usbhid/hid-pidff.c
+@@ -251,9 +251,9 @@ static u32 pidff_rescale_time(u16 time,
+ int exponent = field->unit_exponent;
+
+ pr_debug("time field exponent: %d\n", exponent);
+- for (;exponent < FF_TIME_EXPONENT; exponent++)
++ for (; exponent < FF_TIME_EXPONENT; exponent++)
+ scaled_time *= 10;
+- for (;exponent > FF_TIME_EXPONENT; exponent--)
++ for (; exponent > FF_TIME_EXPONENT; exponent--)
+ scaled_time /= 10;
+
+ pr_debug("time calculated from %d to %d\n", time, scaled_time);
+@@ -587,7 +587,7 @@ static void pidff_set_device_control(str
+ hid_dbg(pidff->hid, "DEVICE_CONTROL is a bitmask\n");
+
+ /* Clear current bitmask */
+- for(i = 0; i < sizeof(pidff_device_control); i++) {
++ for (i = 0; i < sizeof(pidff_device_control); i++) {
+ index = pidff->control_id[i];
+ if (index < 1)
+ continue;
+@@ -638,7 +638,7 @@ static void pidff_fetch_pool(struct pidf
+ struct hid_device *hid = pidff->hid;
+
+ /* Repeat if PID_SIMULTANEOUS_MAX < 2 to make sure it's correct */
+- for(i = 0; i < 20; i++) {
++ for (i = 0; i < 20; i++) {
+ hid_hw_request(hid, pidff->reports[PID_POOL], HID_REQ_GET_REPORT);
+ hid_hw_wait(hid);
+
+@@ -869,7 +869,7 @@ static int pidff_upload_effect(struct in
+ case FF_INERTIA:
+ case FF_FRICTION:
+ if (!old) {
+- switch(effect->type) {
++ switch (effect->type) {
+ case FF_SPRING:
+ type_id = PID_SPRING;
+ break;
--- /dev/null
+From stable+bounces-277190-greg=kroah.com@vger.kernel.org Fri Jul 17 21:24:09 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 15:24:00 -0400
+Subject: HID: pidff: Fix missing blank lines after declarations
+To: stable@vger.kernel.org
+Cc: "Tomasz Pakuła" <tomasz.pakula.oficjalny@gmail.com>, "Jiri Kosina" <jkosina@suse.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260717192403.2131944-1-sashal@kernel.org>
+
+From: Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>
+
+[ Upstream commit 42a2bd61650f13605f9283b5a9e6b1a26b7d4ec2 ]
+
+Fixes chackpatch.pl warnings
+
+Signed-off-by: Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>
+Signed-off-by: Jiri Kosina <jkosina@suse.com>
+Stable-dep-of: b251598b8bf3 ("HID: pidff: Use correct effect type in effect update")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/hid/usbhid/hid-pidff.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+--- a/drivers/hid/usbhid/hid-pidff.c
++++ b/drivers/hid/usbhid/hid-pidff.c
+@@ -249,8 +249,8 @@ static u32 pidff_rescale_time(u16 time,
+ {
+ u32 scaled_time = time;
+ int exponent = field->unit_exponent;
+- pr_debug("time field exponent: %d\n", exponent);
+
++ pr_debug("time field exponent: %d\n", exponent);
+ for (;exponent < FF_TIME_EXPONENT; exponent++)
+ scaled_time *= 10;
+ for (;exponent > FF_TIME_EXPONENT; exponent--)
+@@ -354,6 +354,7 @@ static int pidff_needs_set_envelope(stru
+ struct ff_envelope *old)
+ {
+ bool needs_new_envelope;
++
+ needs_new_envelope = envelope->attack_level != 0 ||
+ envelope->fade_level != 0 ||
+ envelope->attack_length != 0 ||
+@@ -733,6 +734,7 @@ static void pidff_playback_pid(struct pi
+ static int pidff_playback(struct input_dev *dev, int effect_id, int value)
+ {
+ struct pidff_device *pidff = dev->ff->private;
++
+ pidff_playback_pid(pidff, pidff->pid_id[effect_id], value);
+ return 0;
+ }
+@@ -1233,6 +1235,7 @@ static int pidff_find_effects(struct pid
+
+ for (i = 0; i < sizeof(pidff_effect_types); i++) {
+ int pidff_type = pidff->type_id[i];
++
+ if (pidff->set_effect_type->usage[pidff_type].hid !=
+ pidff->create_new_effect_type->usage[pidff_type].hid) {
+ hid_err(pidff->hid,
--- /dev/null
+From stable+bounces-277192-greg=kroah.com@vger.kernel.org Fri Jul 17 21:24:15 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 15:24:02 -0400
+Subject: HID: pidff: Rework pidff_upload_effect
+To: stable@vger.kernel.org
+Cc: "Tomasz Pakuła" <tomasz.pakula.oficjalny@gmail.com>, "Jiri Kosina" <jkosina@suse.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260717192403.2131944-3-sashal@kernel.org>
+
+From: Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>
+
+[ Upstream commit 7fbaa031b94182a9c9e58310935a2f74265ef78d ]
+
+One of the more complicated functions. Expunge some of the logic to
+separate functions (FF -> PID id conversion)
+
+Add a macro for envelope check to make it more readable in the upload
+function.
+
+All this made it possible to to expunge common code from the big switch
+statement and reduce the overall function size considerably. Now it can
+fit on one screen.
+
+Move the effect_cout logic from report functions to upload/erase
+functions.
+
+Signed-off-by: Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>
+Signed-off-by: Jiri Kosina <jkosina@suse.com>
+Stable-dep-of: b251598b8bf3 ("HID: pidff: Use correct effect type in effect update")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/hid/usbhid/hid-pidff.c | 243 +++++++++++++++++++----------------------
+ 1 file changed, 115 insertions(+), 128 deletions(-)
+
+--- a/drivers/hid/usbhid/hid-pidff.c
++++ b/drivers/hid/usbhid/hid-pidff.c
+@@ -141,7 +141,7 @@ static const u8 pidff_effect_types[] = {
+ #define PID_BLOCK_LOAD_SUCCESS 0
+ #define PID_BLOCK_LOAD_FULL 1
+ #define PID_BLOCK_LOAD_ERROR 2
+-static const u8 pidff_block_load_status[] = { 0x8c, 0x8d, 0x8e};
++static const u8 pidff_block_load_status[] = { 0x8c, 0x8d, 0x8e };
+
+ #define PID_EFFECT_START 0
+ #define PID_EFFECT_STOP 1
+@@ -214,6 +214,62 @@ static int pidff_is_effect_conditional(s
+ }
+
+ /*
++ * Get PID effect index from FF effect type.
++ * Return 0 if invalid.
++ */
++static int pidff_effect_ff_to_pid(struct ff_effect *effect)
++{
++ switch (effect->type) {
++ case FF_CONSTANT:
++ return PID_CONSTANT;
++ case FF_RAMP:
++ return PID_RAMP;
++ case FF_SPRING:
++ return PID_SPRING;
++ case FF_DAMPER:
++ return PID_DAMPER;
++ case FF_INERTIA:
++ return PID_INERTIA;
++ case FF_FRICTION:
++ return PID_FRICTION;
++ case FF_PERIODIC:
++ switch (effect->u.periodic.waveform) {
++ case FF_SQUARE:
++ return PID_SQUARE;
++ case FF_TRIANGLE:
++ return PID_TRIANGLE;
++ case FF_SINE:
++ return PID_SINE;
++ case FF_SAW_UP:
++ return PID_SAW_UP;
++ case FF_SAW_DOWN:
++ return PID_SAW_DOWN;
++ }
++ }
++ pr_err("invalid effect type\n");
++ return -EINVAL;
++}
++
++/*
++ * Get effect id in the device descriptor.
++ * Return 0 if invalid.
++ */
++static int pidff_get_effect_type_id(struct pidff_device *pidff,
++ struct ff_effect *effect)
++{
++ int id = pidff_effect_ff_to_pid(effect);
++
++ if (id < 0)
++ return 0;
++
++ if (effect->type == FF_PERIODIC &&
++ pidff->quirks & HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY)
++ id = PID_SINE;
++
++ return pidff->type_id[id];
++}
++
++/*
+ * Clamp value for a given field
+ */
+ static s32 pidff_clamp(s32 i, struct hid_field *field)
+@@ -335,16 +391,16 @@ static void pidff_set_envelope_report(st
+ pidff->set_envelope[PID_FADE_LEVEL].field);
+
+ pidff_set_time(&pidff->set_envelope[PID_ATTACK_TIME],
+- envelope->attack_length);
++ envelope->attack_length);
+ pidff_set_time(&pidff->set_envelope[PID_FADE_TIME],
+- envelope->fade_length);
++ envelope->fade_length);
+
+ hid_dbg(pidff->hid, "attack %u => %d\n",
+ envelope->attack_level,
+ pidff->set_envelope[PID_ATTACK_LEVEL].value[0]);
+
+ hid_hw_request(pidff->hid, pidff->reports[PID_SET_ENVELOPE],
+- HID_REQ_SET_REPORT);
++ HID_REQ_SET_REPORT);
+ }
+
+ /*
+@@ -353,7 +409,7 @@ static void pidff_set_envelope_report(st
+ static int pidff_needs_set_envelope(struct ff_envelope *envelope,
+ struct ff_envelope *old)
+ {
+- bool needs_new_envelope;
++ int needs_new_envelope;
+
+ needs_new_envelope = envelope->attack_level != 0 ||
+ envelope->fade_level != 0 ||
+@@ -361,8 +417,7 @@ static int pidff_needs_set_envelope(stru
+ envelope->fade_length != 0;
+
+ if (!needs_new_envelope)
+- return false;
+-
++ return 0;
+ if (!old)
+ return needs_new_envelope;
+
+@@ -375,8 +430,8 @@ static int pidff_needs_set_envelope(stru
+ /*
+ * Send constant force report to the device
+ */
+-static void pidff_set_constant_force_report(struct pidff_device *pidff,
+- struct ff_effect *effect)
++static void pidff_set_constant_report(struct pidff_device *pidff,
++ struct ff_effect *effect)
+ {
+ pidff->set_constant[PID_EFFECT_BLOCK_INDEX].value[0] =
+ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+@@ -384,7 +439,7 @@ static void pidff_set_constant_force_rep
+ effect->u.constant.level);
+
+ hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONSTANT],
+- HID_REQ_SET_REPORT);
++ HID_REQ_SET_REPORT);
+ }
+
+ /*
+@@ -536,8 +591,8 @@ static int pidff_needs_set_condition(str
+ /*
+ * Send ramp force report to the device
+ */
+-static void pidff_set_ramp_force_report(struct pidff_device *pidff,
+- struct ff_effect *effect)
++static void pidff_set_ramp_report(struct pidff_device *pidff,
++ struct ff_effect *effect)
+ {
+ pidff->set_ramp[PID_EFFECT_BLOCK_INDEX].value[0] =
+ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+@@ -546,7 +601,7 @@ static void pidff_set_ramp_force_report(
+ pidff_set_signed(&pidff->set_ramp[PID_RAMP_END],
+ effect->u.ramp.end_level);
+ hid_hw_request(pidff->hid, pidff->reports[PID_SET_RAMP],
+- HID_REQ_SET_REPORT);
++ HID_REQ_SET_REPORT);
+ }
+
+ /*
+@@ -664,9 +719,6 @@ static int pidff_request_effect_upload(s
+ {
+ int j;
+
+- if (!pidff->effect_count)
+- pidff_reset(pidff);
+-
+ pidff->create_new_effect_type->value[0] = efnum;
+ hid_hw_request(pidff->hid, pidff->reports[PID_CREATE_NEW_EFFECT],
+ HID_REQ_SET_REPORT);
+@@ -686,8 +738,6 @@ static int pidff_request_effect_upload(s
+ hid_dbg(pidff->hid, "device reported free memory: %d bytes\n",
+ pidff->block_load[PID_RAM_POOL_AVAILABLE].value ?
+ pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1);
+-
+- pidff->effect_count++;
+ return 0;
+ }
+ if (pidff->block_load_status->value[0] ==
+@@ -725,7 +775,7 @@ static void pidff_playback_pid(struct pi
+ }
+
+ hid_hw_request(pidff->hid, pidff->reports[PID_EFFECT_OPERATION],
+- HID_REQ_SET_REPORT);
++ HID_REQ_SET_REPORT);
+ }
+
+ /*
+@@ -747,10 +797,7 @@ static void pidff_erase_pid(struct pidff
+ {
+ pidff->block_free[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id;
+ hid_hw_request(pidff->hid, pidff->reports[PID_BLOCK_FREE],
+- HID_REQ_SET_REPORT);
+-
+- if (pidff->effect_count > 0)
+- pidff->effect_count--;
++ HID_REQ_SET_REPORT);
+ }
+
+ /*
+@@ -772,139 +819,79 @@ static int pidff_erase_effect(struct inp
+ pidff_playback_pid(pidff, pid_id, 0);
+ pidff_erase_pid(pidff, pid_id);
+
++ if (pidff->effect_count > 0)
++ pidff->effect_count--;
++
++ hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count);
+ return 0;
+ }
+
++#define PIDFF_SET_REPORT_IF_NEEDED(type, effect, old) \
++ ({ if (!old || pidff_needs_set_## type(effect, old)) \
++ pidff_set_ ##type## _report(pidff, effect); })
++
++#define PIDFF_SET_ENVELOPE_IF_NEEDED(type, effect, old) \
++ ({ if (pidff_needs_set_envelope(&effect->u.type.envelope, \
++ old ? &old->u.type.envelope : NULL)) \
++ pidff_set_envelope_report(pidff, &effect->u.type.envelope); })
++
+ /*
+ * Effect upload handler
+ */
+-static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
++static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *new,
+ struct ff_effect *old)
+ {
+ struct pidff_device *pidff = dev->ff->private;
+- int type_id;
+- int error;
++ const int type_id = pidff_get_effect_type_id(pidff, new);
+
+- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0;
+- if (old) {
+- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] =
+- pidff->pid_id[effect->id];
++ if (!type_id) {
++ hid_err(pidff->hid, "effect type not supported\n");
++ return -EINVAL;
+ }
+
+- switch (effect->type) {
++ if (!pidff->effect_count)
++ pidff_reset(pidff);
++
++ if (!old) {
++ int error = pidff_request_effect_upload(pidff, type_id);
++
++ if (error)
++ return error;
++
++ pidff->effect_count++;
++ hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count);
++ pidff->pid_id[new->id] =
++ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
++ }
++
++ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] =
++ pidff->pid_id[new->id];
++
++ PIDFF_SET_REPORT_IF_NEEDED(effect, new, old);
++ switch (new->type) {
+ case FF_CONSTANT:
+- if (!old) {
+- error = pidff_request_effect_upload(pidff,
+- pidff->type_id[PID_CONSTANT]);
+- if (error)
+- return error;
+- }
+- if (!old || pidff_needs_set_effect(effect, old))
+- pidff_set_effect_report(pidff, effect);
+- if (!old || pidff_needs_set_constant(effect, old))
+- pidff_set_constant_force_report(pidff, effect);
+- if (pidff_needs_set_envelope(&effect->u.constant.envelope,
+- old ? &old->u.constant.envelope : NULL))
+- pidff_set_envelope_report(pidff, &effect->u.constant.envelope);
++ PIDFF_SET_REPORT_IF_NEEDED(constant, new, old);
++ PIDFF_SET_ENVELOPE_IF_NEEDED(constant, new, old);
+ break;
+
+ case FF_PERIODIC:
+- if (!old) {
+- switch (effect->u.periodic.waveform) {
+- case FF_SQUARE:
+- type_id = PID_SQUARE;
+- break;
+- case FF_TRIANGLE:
+- type_id = PID_TRIANGLE;
+- break;
+- case FF_SINE:
+- type_id = PID_SINE;
+- break;
+- case FF_SAW_UP:
+- type_id = PID_SAW_UP;
+- break;
+- case FF_SAW_DOWN:
+- type_id = PID_SAW_DOWN;
+- break;
+- default:
+- hid_err(pidff->hid, "invalid waveform\n");
+- return -EINVAL;
+- }
+-
+- if (pidff->quirks & HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY)
+- type_id = PID_SINE;
+-
+- error = pidff_request_effect_upload(pidff,
+- pidff->type_id[type_id]);
+- if (error)
+- return error;
+- }
+- if (!old || pidff_needs_set_effect(effect, old))
+- pidff_set_effect_report(pidff, effect);
+- if (!old || pidff_needs_set_periodic(effect, old))
+- pidff_set_periodic_report(pidff, effect);
+- if (pidff_needs_set_envelope(&effect->u.periodic.envelope,
+- old ? &old->u.periodic.envelope : NULL))
+- pidff_set_envelope_report(pidff, &effect->u.periodic.envelope);
++ PIDFF_SET_REPORT_IF_NEEDED(periodic, new, old);
++ PIDFF_SET_ENVELOPE_IF_NEEDED(periodic, new, old);
+ break;
+
+ case FF_RAMP:
+- if (!old) {
+- error = pidff_request_effect_upload(pidff,
+- pidff->type_id[PID_RAMP]);
+- if (error)
+- return error;
+- }
+- if (!old || pidff_needs_set_effect(effect, old))
+- pidff_set_effect_report(pidff, effect);
+- if (!old || pidff_needs_set_ramp(effect, old))
+- pidff_set_ramp_force_report(pidff, effect);
+- if (pidff_needs_set_envelope(&effect->u.ramp.envelope,
+- old ? &old->u.ramp.envelope : NULL))
+- pidff_set_envelope_report(pidff, &effect->u.ramp.envelope);
++ PIDFF_SET_REPORT_IF_NEEDED(ramp, new, old);
++ PIDFF_SET_ENVELOPE_IF_NEEDED(ramp, new, old);
+ break;
+
+ case FF_SPRING:
+ case FF_DAMPER:
+ case FF_INERTIA:
+ case FF_FRICTION:
+- if (!old) {
+- switch (effect->type) {
+- case FF_SPRING:
+- type_id = PID_SPRING;
+- break;
+- case FF_DAMPER:
+- type_id = PID_DAMPER;
+- break;
+- case FF_INERTIA:
+- type_id = PID_INERTIA;
+- break;
+- case FF_FRICTION:
+- type_id = PID_FRICTION;
+- break;
+- }
+- error = pidff_request_effect_upload(pidff,
+- pidff->type_id[type_id]);
+- if (error)
+- return error;
+- }
+- if (!old || pidff_needs_set_effect(effect, old))
+- pidff_set_effect_report(pidff, effect);
+- if (!old || pidff_needs_set_condition(effect, old))
+- pidff_set_condition_report(pidff, effect);
++ PIDFF_SET_REPORT_IF_NEEDED(condition, new, old);
+ break;
+-
+- default:
+- hid_err(pidff->hid, "invalid type\n");
+- return -EINVAL;
+ }
+-
+- if (!old)
+- pidff->pid_id[effect->id] =
+- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+-
+ hid_dbg(pidff->hid, "uploaded\n");
+-
+ return 0;
+ }
+
--- /dev/null
+From stable+bounces-277193-greg=kroah.com@vger.kernel.org Fri Jul 17 21:24:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 15:24:03 -0400
+Subject: HID: pidff: Use correct effect type in effect update
+To: stable@vger.kernel.org
+Cc: "Oleg Makarenko" <oleg@makarenk.ooo>, "Oliver Roundtree" <oroundtree1@gmail.com>, "Ryno Kotzé" <lemon.xah@gmail.com>, "Jiri Kosina" <jkosina@suse.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260717192403.2131944-4-sashal@kernel.org>
+
+From: Oleg Makarenko <oleg@makarenk.ooo>
+
+[ Upstream commit b251598b8bf37300510868f739a79e07800d41ce ]
+
+When updating an existing effect, the effect type from the last created
+effect was sent to the device instead of the updated one.
+This caused incorrect reports when a game creates multiple different
+effects and updates only one that is not the last created.
+
+Fixes FFB in multiple games that create multiple simultaneous effects
+(Forza Horizon 5/6).
+
+Fixes: 224ee88fe395 ("Input: add force feedback driver for PID devices")
+Cc: stable@vger.kernel.org
+Tested-by: Oliver Roundtree <oroundtree1@gmail.com>
+Co-developed-by: Ryno Kotzé <lemon.xah@gmail.com>
+Signed-off-by: Ryno Kotzé <lemon.xah@gmail.com>
+Signed-off-by: Oleg Makarenko <oleg@makarenk.ooo>
+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/usbhid/hid-pidff.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/hid/usbhid/hid-pidff.c
++++ b/drivers/hid/usbhid/hid-pidff.c
+@@ -460,7 +460,7 @@ static void pidff_set_effect_report(stru
+ pidff->set_effect[PID_EFFECT_BLOCK_INDEX].value[0] =
+ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+ pidff->set_effect_type->value[0] =
+- pidff->create_new_effect_type->value[0];
++ pidff_get_effect_type_id(pidff, effect);
+
+ pidff_set_duration(&pidff->set_effect[PID_DURATION],
+ effect->replay.length);
--- /dev/null
+From stable+bounces-273950-greg=kroah.com@vger.kernel.org Mon Jul 13 20:16:53 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 14:13:28 -0400
+Subject: iio: adc: ad7380: select REGMAP
+To: stable@vger.kernel.org
+Cc: "Samuel Moelius" <samuel.moelius@trailofbits.com>, "Andy Shevchenko" <andriy.shevchenko@intel.com>, "Nuno Sá" <nuno.sa@analog.com>, Stable@vger.kernel.org, "Jonathan Cameron" <jic23@kernel.org>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260713181328.1932706-1-sashal@kernel.org>
+
+From: Samuel Moelius <samuel.moelius@trailofbits.com>
+
+[ Upstream commit 6697091b386a4e2830bdd38512c87a4befff2b32 ]
+
+The AD7380 driver uses generic regmap types and APIs. However, its
+Kconfig entry does not select REGMAP.
+
+As a result, AD7380 can be enabled from an allnoconfig-derived config
+with SPI_MASTER=y while REGMAP remains unset, causing ad7380.o to fail
+to build.
+
+Fixes: b095217c104b ("iio: adc: ad7380: new driver for AD7380 ADCs")
+Signed-off-by: Samuel Moelius <samuel.moelius@trailofbits.com>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Reviewed-by: Nuno Sá <nuno.sa@analog.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/adc/Kconfig | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/iio/adc/Kconfig
++++ b/drivers/iio/adc/Kconfig
+@@ -188,6 +188,7 @@ config AD7298
+ config AD7380
+ tristate "Analog Devices AD7380 ADC driver"
+ depends on SPI_MASTER
++ select REGMAP
+ select IIO_BUFFER
+ select IIO_TRIGGER
+ select IIO_TRIGGERED_BUFFER
--- /dev/null
+From stable+bounces-273939-greg=kroah.com@vger.kernel.org Mon Jul 13 20:06:54 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 14:03:26 -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: <20260713180326.1923554-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-273975-greg=kroah.com@vger.kernel.org Mon Jul 13 21:37:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 15:37:08 -0400
+Subject: iio: pressure: mpl115: fix runtime PM leak on read error
+To: stable@vger.kernel.org
+Cc: Biren Pandya <birenpandya@gmail.com>, Stable@vger.kernel.org, Jonathan Cameron <jic23@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713193708.2054715-2-sashal@kernel.org>
+
+From: Biren Pandya <birenpandya@gmail.com>
+
+[ Upstream commit fbe67ff37a6fd855a6c097f84f3738bd13d0a898 ]
+
+mpl115_read_raw() takes a runtime PM reference with pm_runtime_get_sync()
+before reading the processed pressure or raw temperature, but on the read
+error path it returns without calling pm_runtime_put_autosuspend(). Each
+failed read therefore leaks a runtime PM reference and prevents the device
+from autosuspending.
+
+Drop the reference before checking the return value so both the success
+and error paths are balanced.
+
+Fixes: 0c3a333524a3 ("iio: pressure: mpl115: Implementing low power mode by shutdown gpio")
+Signed-off-by: Biren Pandya <birenpandya@gmail.com>
+Assisted-by: Claude:claude-opus-4-8 coccinelle
+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/pressure/mpl115.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/iio/pressure/mpl115.c
++++ b/drivers/iio/pressure/mpl115.c
+@@ -106,18 +106,18 @@ static int mpl115_read_raw(struct iio_de
+ case IIO_CHAN_INFO_PROCESSED:
+ pm_runtime_get_sync(data->dev);
+ ret = mpl115_comp_pressure(data, val, val2);
++ pm_runtime_put_autosuspend(data->dev);
+ if (ret < 0)
+ return ret;
+- pm_runtime_put_autosuspend(data->dev);
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_RAW:
+ pm_runtime_get_sync(data->dev);
+ /* temperature -5.35 C / LSB, 472 LSB is 25 C */
+ ret = mpl115_read_temp(data);
++ pm_runtime_put_autosuspend(data->dev);
+ if (ret < 0)
+ return ret;
+- pm_runtime_put_autosuspend(data->dev);
+ *val = ret >> 6;
+
+ return IIO_VAL_INT;
--- /dev/null
+From stable+bounces-273974-greg=kroah.com@vger.kernel.org Mon Jul 13 21:37:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 13 Jul 2026 15:37:07 -0400
+Subject: iio: pressure: Remove redundant pm_runtime_mark_last_busy() calls
+To: stable@vger.kernel.org
+Cc: Sakari Ailus <sakari.ailus@linux.intel.com>, Jonathan Cameron <Jonathan.Cameron@huawei.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260713193708.2054715-1-sashal@kernel.org>
+
+From: Sakari Ailus <sakari.ailus@linux.intel.com>
+
+[ Upstream commit dfb68a8ebb2e8d9af6e356ae0806bfd8e854ab44 ]
+
+pm_runtime_put_autosuspend(), pm_runtime_put_sync_autosuspend(),
+pm_runtime_autosuspend() and pm_request_autosuspend() now include a call
+to pm_runtime_mark_last_busy(). Remove the now-reduntant explicit call to
+pm_runtime_mark_last_busy().
+
+Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
+Link: https://patch.msgid.link/20250825135401.1765847-11-sakari.ailus@linux.intel.com
+Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
+Stable-dep-of: fbe67ff37a6f ("iio: pressure: mpl115: fix runtime PM leak on read error")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iio/pressure/bmp280-core.c | 5 -----
+ drivers/iio/pressure/icp10100.c | 1 -
+ drivers/iio/pressure/mpl115.c | 2 --
+ drivers/iio/pressure/zpa2326.c | 2 --
+ 4 files changed, 10 deletions(-)
+
+--- a/drivers/iio/pressure/bmp280-core.c
++++ b/drivers/iio/pressure/bmp280-core.c
+@@ -747,7 +747,6 @@ static int bmp280_read_raw(struct iio_de
+
+ pm_runtime_get_sync(data->dev);
+ ret = bmp280_read_raw_impl(indio_dev, chan, val, val2, mask);
+- pm_runtime_mark_last_busy(data->dev);
+ pm_runtime_put_autosuspend(data->dev);
+
+ return ret;
+@@ -922,7 +921,6 @@ static int bmp280_write_raw(struct iio_d
+
+ pm_runtime_get_sync(data->dev);
+ ret = bmp280_write_raw_impl(indio_dev, chan, val, val2, mask);
+- pm_runtime_mark_last_busy(data->dev);
+ pm_runtime_put_autosuspend(data->dev);
+
+ return ret;
+@@ -1951,7 +1949,6 @@ static int bmp580_nvmem_read(void *priv,
+
+ pm_runtime_get_sync(data->dev);
+ ret = bmp580_nvmem_read_impl(priv, offset, val, bytes);
+- pm_runtime_mark_last_busy(data->dev);
+ pm_runtime_put_autosuspend(data->dev);
+
+ return ret;
+@@ -2026,7 +2023,6 @@ static int bmp580_nvmem_write(void *priv
+
+ pm_runtime_get_sync(data->dev);
+ ret = bmp580_nvmem_write_impl(priv, offset, val, bytes);
+- pm_runtime_mark_last_busy(data->dev);
+ pm_runtime_put_autosuspend(data->dev);
+
+ return ret;
+@@ -2634,7 +2630,6 @@ static int bmp280_buffer_postdisable(str
+ {
+ struct bmp280_data *data = iio_priv(indio_dev);
+
+- pm_runtime_mark_last_busy(data->dev);
+ pm_runtime_put_autosuspend(data->dev);
+
+ return 0;
+--- a/drivers/iio/pressure/icp10100.c
++++ b/drivers/iio/pressure/icp10100.c
+@@ -265,7 +265,6 @@ static int icp10100_get_measures(struct
+ (be16_to_cpu(measures[1]) >> 8);
+ *temperature = be16_to_cpu(measures[2]);
+
+- pm_runtime_mark_last_busy(&st->client->dev);
+ error_measure:
+ pm_runtime_put_autosuspend(&st->client->dev);
+ return ret;
+--- a/drivers/iio/pressure/mpl115.c
++++ b/drivers/iio/pressure/mpl115.c
+@@ -108,7 +108,6 @@ static int mpl115_read_raw(struct iio_de
+ ret = mpl115_comp_pressure(data, val, val2);
+ if (ret < 0)
+ return ret;
+- pm_runtime_mark_last_busy(data->dev);
+ pm_runtime_put_autosuspend(data->dev);
+
+ return IIO_VAL_INT_PLUS_MICRO;
+@@ -118,7 +117,6 @@ static int mpl115_read_raw(struct iio_de
+ ret = mpl115_read_temp(data);
+ if (ret < 0)
+ return ret;
+- pm_runtime_mark_last_busy(data->dev);
+ pm_runtime_put_autosuspend(data->dev);
+ *val = ret >> 6;
+
+--- a/drivers/iio/pressure/zpa2326.c
++++ b/drivers/iio/pressure/zpa2326.c
+@@ -699,7 +699,6 @@ static void zpa2326_suspend(struct iio_d
+
+ zpa2326_sleep(indio_dev);
+
+- pm_runtime_mark_last_busy(parent);
+ pm_runtime_put_autosuspend(parent);
+ }
+
+@@ -710,7 +709,6 @@ static void zpa2326_init_runtime(struct
+ pm_runtime_enable(parent);
+ pm_runtime_set_autosuspend_delay(parent, 1000);
+ pm_runtime_use_autosuspend(parent);
+- pm_runtime_mark_last_busy(parent);
+ pm_runtime_put_autosuspend(parent);
+ }
+
--- /dev/null
+From stable+bounces-274911-greg=kroah.com@vger.kernel.org Wed Jul 15 13:43:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 07:40:17 -0400
+Subject: io_uring/rw: ensure reissue path is correctly handled for IOPOLL
+To: stable@vger.kernel.org
+Cc: Jens Axboe <axboe@kernel.dk>, John Garry <john.g.garry@oracle.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715114018.726395-1-sashal@kernel.org>
+
+From: Jens Axboe <axboe@kernel.dk>
+
+[ Upstream commit bcb0fda3c2da9fe4721d3e73d80e778c038e7d27 ]
+
+The IOPOLL path posts CQEs when the io_kiocb is marked as completed,
+so it cannot rely on the usual retry that non-IOPOLL requests do for
+read/write requests.
+
+If -EAGAIN is received and the request should be retried, go through
+the normal completion path and let the normal flush logic catch it and
+reissue it, like what is done for !IOPOLL reads or writes.
+
+Fixes: d803d123948f ("io_uring/rw: handle -EAGAIN retry at IO completion time")
+Reported-by: John Garry <john.g.garry@oracle.com>
+Link: https://lore.kernel.org/io-uring/2b43ccfa-644d-4a09-8f8f-39ad71810f41@oracle.com/
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+Stable-dep-of: c554246ff4c6 ("io_uring/rw: preserve partial result for iopoll")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ io_uring/rw.c | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+--- a/io_uring/rw.c
++++ b/io_uring/rw.c
+@@ -550,11 +550,10 @@ static void io_complete_rw_iopoll(struct
+ if (kiocb->ki_flags & IOCB_WRITE)
+ io_req_end_write(req);
+ if (unlikely(res != req->cqe.res)) {
+- if (res == -EAGAIN && io_rw_should_reissue(req)) {
++ if (res == -EAGAIN && io_rw_should_reissue(req))
+ req->flags |= REQ_F_REISSUE | REQ_F_BL_NO_RECYCLE;
+- return;
+- }
+- req->cqe.res = res;
++ else
++ req->cqe.res = res;
+ }
+
+ /* order with io_iopoll_complete() checking ->iopoll_completed */
--- /dev/null
+From stable+bounces-274912-greg=kroah.com@vger.kernel.org Wed Jul 15 13:43:53 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 07:40:18 -0400
+Subject: io_uring/rw: preserve partial result for iopoll
+To: stable@vger.kernel.org
+Cc: Michael Wigham <michael@wigham.net>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715114018.726395-2-sashal@kernel.org>
+
+From: Michael Wigham <michael@wigham.net>
+
+[ Upstream commit c554246ff4c68abf71b61a89c6e39d3cf94f523e ]
+
+A partial read will store the completed byte count in io->bytes_done.
+The regular completion path applies io_fixup_rw_res() so that, when the
+following operation reaches EOF, the number of bytes already read is
+returned.
+
+The iopoll completion path does not apply this fixup to the return value
+and can return zero instead.
+
+Use the fixup result when updating the CQE, and the raw result for the
+reissue check.
+
+Cc: stable@vger.kernel.org
+Fixes: 4d9cb92ca41d ("io_uring/rw: fix short rw error handling")
+Signed-off-by: Michael Wigham <michael@wigham.net>
+Link: https://patch.msgid.link/20260613225240.34032-1-michael@wigham.net
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ io_uring/rw.c | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+--- a/io_uring/rw.c
++++ b/io_uring/rw.c
+@@ -546,15 +546,15 @@ static void io_complete_rw_iopoll(struct
+ {
+ struct io_rw *rw = container_of(kiocb, struct io_rw, kiocb);
+ struct io_kiocb *req = cmd_to_io_kiocb(rw);
++ int final_res = io_fixup_rw_res(req, res);
+
+ if (kiocb->ki_flags & IOCB_WRITE)
+ io_req_end_write(req);
+- if (unlikely(res != req->cqe.res)) {
+- if (res == -EAGAIN && io_rw_should_reissue(req))
+- req->flags |= REQ_F_REISSUE | REQ_F_BL_NO_RECYCLE;
+- else
+- req->cqe.res = res;
+- }
++
++ if (res == -EAGAIN && io_rw_should_reissue(req))
++ req->flags |= REQ_F_REISSUE | REQ_F_BL_NO_RECYCLE;
++ else if (unlikely(final_res != req->cqe.res))
++ req->cqe.res = final_res;
+
+ /* order with io_iopoll_complete() checking ->iopoll_completed */
+ smp_store_release(&req->iopoll_completed, 1);
--- /dev/null
+From stable+bounces-275317-greg=kroah.com@vger.kernel.org Thu Jul 16 13:52:39 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 07:52:30 -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: <20260716115231.1785404-1-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 | 101 ++++++++++++++++++++++++++++++++++++++-------
+ fs/smb/server/connection.h | 6 ++
+ fs/smb/server/oplock.c | 7 ---
+ fs/smb/server/server.c | 12 +++++
+ fs/smb/server/vfs_cache.c | 60 ++++++++++++++++++++++----
+ 5 files changed, 156 insertions(+), 30 deletions(-)
+
+--- a/fs/smb/server/connection.c
++++ b/fs/smb/server/connection.c
+@@ -22,6 +22,81 @@ static struct ksmbd_conn_ops default_con
+ DEFINE_HASHTABLE(conn_list, CONN_HASH_BITS);
+ DECLARE_RWSEM(conn_list_lock);
+
++static struct workqueue_struct *ksmbd_conn_wq;
++
++int ksmbd_conn_wq_init(void)
++{
++ ksmbd_conn_wq = alloc_workqueue("ksmbd-conn-release",
++ WQ_UNBOUND | WQ_MEM_RECLAIM, 0);
++ if (!ksmbd_conn_wq)
++ return -ENOMEM;
++ return 0;
++}
++
++void ksmbd_conn_wq_destroy(void)
++{
++ if (ksmbd_conn_wq) {
++ destroy_workqueue(ksmbd_conn_wq);
++ ksmbd_conn_wq = NULL;
++ }
++}
++
++/*
++ * __ksmbd_conn_release_work() - perform the final, once-per-struct cleanup
++ * of a ksmbd_conn whose refcount has just dropped to zero.
++ *
++ * This is the common release path used by ksmbd_conn_put() for the embedded
++ * state that outlives the connection thread: async_ida and the attached
++ * transport (which owns the socket and iov for TCP). Called from a workqueue
++ * so that sleep-allowed teardown (sock_release -> tcp_close ->
++ * lock_sock_nested) never runs from an RCU softirq callback (free_opinfo_rcu)
++ * or any other non-sleeping putter context.
++ */
++static void __ksmbd_conn_release_work(struct work_struct *work)
++{
++ struct ksmbd_conn *conn =
++ container_of(work, struct ksmbd_conn, release_work);
++
++ ida_destroy(&conn->async_ida);
++ conn->transport->ops->free_transport(conn->transport);
++ kfree(conn);
++}
++
++/**
++ * 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, queue the
++ * release onto ksmbd_conn_wq so it runs from process context.
++ *
++ * Callable from any context including RCU softirq callbacks and non-sleeping
++ * locks; the actual release is deferred to the workqueue. ksmbd_conn_wq is
++ * created in ksmbd_server_init() before any conn can be allocated and is
++ * destroyed in ksmbd_server_exit() after rcu_barrier(), so it is always
++ * non-NULL while a conn reference is held.
++ */
++void ksmbd_conn_put(struct ksmbd_conn *conn)
++{
++ if (!conn)
++ return;
++
++ if (atomic_dec_and_test(&conn->refcnt))
++ queue_work(ksmbd_conn_wq, &conn->release_work);
++}
++
+ /**
+ * ksmbd_conn_free() - free resources of the connection instance
+ *
+@@ -36,23 +111,19 @@ void ksmbd_conn_free(struct ksmbd_conn *
+ hash_del(&conn->hlist);
+ 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);
+- if (atomic_dec_and_test(&conn->refcnt)) {
+- /*
+- * async_ida is embedded in struct ksmbd_conn, so pair
+- * ida_destroy() with the final kfree() rather than with
+- * the unconditional field teardown above. This keeps
+- * the IDA valid for the entire lifetime of the struct,
+- * even while other refcount holders (oplock / vfs
+- * durable handles) still reference the connection.
+- */
+- ida_destroy(&conn->async_ida);
+- conn->transport->ops->free_transport(conn->transport);
+- kfree(conn);
+- }
++ ksmbd_conn_put(conn);
+ }
+
+ /**
+@@ -79,6 +150,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void
+ conn->um = ERR_PTR(-EOPNOTSUPP);
+ if (IS_ERR(conn->um))
+ conn->um = NULL;
++ INIT_WORK(&conn->release_work, __ksmbd_conn_release_work);
+ atomic_set(&conn->req_running, 0);
+ atomic_set(&conn->r_count, 0);
+ atomic_set(&conn->refcnt, 1);
+@@ -457,8 +529,7 @@ void ksmbd_conn_r_count_dec(struct ksmbd
+ if (!atomic_dec_return(&conn->r_count) && waitqueue_active(&conn->r_count_q))
+ wake_up(&conn->r_count_q);
+
+- if (atomic_dec_and_test(&conn->refcnt))
+- kfree(conn);
++ ksmbd_conn_put(conn);
+ }
+
+ int ksmbd_conn_transport_init(void)
+--- a/fs/smb/server/connection.h
++++ b/fs/smb/server/connection.h
+@@ -15,6 +15,7 @@
+ #include <linux/kthread.h>
+ #include <linux/nls.h>
+ #include <linux/unicode.h>
++#include <linux/workqueue.h>
+
+ #include "smb_common.h"
+ #include "ksmbd_work.h"
+@@ -117,6 +118,7 @@ struct ksmbd_conn {
+ bool binding;
+ atomic_t refcnt;
+ bool is_aapl;
++ struct work_struct release_work;
+ };
+
+ struct ksmbd_conn_ops {
+@@ -162,6 +164,10 @@ void ksmbd_conn_wait_idle(struct ksmbd_c
+ int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_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);
++int ksmbd_conn_wq_init(void);
++void ksmbd_conn_wq_destroy(void);
+ 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;
+@@ -50,7 +49,6 @@ static struct oplock_info *alloc_opinfo(
+ init_waitqueue_head(&opinfo->oplock_brk);
+ atomic_set(&opinfo->refcount, 1);
+ atomic_set(&opinfo->breaking_cnt, 0);
+- atomic_inc(&opinfo->conn->refcnt);
+
+ return opinfo;
+ }
+@@ -132,8 +130,7 @@ static void __free_opinfo(struct oplock_
+ {
+ if (opinfo->is_lease)
+ free_lease(opinfo);
+- if (opinfo->conn && atomic_dec_and_test(&opinfo->conn->refcnt))
+- kfree(opinfo->conn);
++ ksmbd_conn_put(opinfo->conn);
+ kfree(opinfo);
+ }
+
+--- a/fs/smb/server/server.c
++++ b/fs/smb/server/server.c
+@@ -587,8 +587,14 @@ static int __init ksmbd_server_init(void
+ if (ret)
+ goto err_crypto_destroy;
+
++ ret = ksmbd_conn_wq_init();
++ if (ret)
++ goto err_workqueue_destroy;
++
+ return 0;
+
++err_workqueue_destroy:
++ ksmbd_workqueue_destroy();
+ err_crypto_destroy:
+ ksmbd_crypto_destroy();
+ err_release_inode_hash:
+@@ -614,6 +620,12 @@ static void __exit ksmbd_server_exit(voi
+ {
+ ksmbd_server_shutdown();
+ rcu_barrier();
++ /*
++ * ksmbd_conn_put() defers the final release onto ksmbd_conn_wq,
++ * so drain it after rcu_barrier() has fired any pending RCU
++ * callbacks that may have queued a release.
++ */
++ ksmbd_conn_wq_destroy();
+ ksmbd_release_inode_hash();
+ }
+
+--- a/fs/smb/server/vfs_cache.c
++++ b/fs/smb/server/vfs_cache.c
+@@ -402,6 +402,17 @@ static void __ksmbd_close_fd(struct ksmb
+ kfree(smb_lock);
+ }
+
++ /*
++ * Drop fp's strong reference on conn (taken in ksmbd_open_fd() /
++ * ksmbd_reopen_durable_fd()). Durable fps that reached the
++ * scavenger have already had fp->conn cleared by session_fd_check(),
++ * in which case there is nothing to drop here.
++ */
++ if (fp->conn) {
++ ksmbd_conn_put(fp->conn);
++ fp->conn = NULL;
++ }
++
+ if (ksmbd_stream_fd(fp))
+ kfree(fp->stream.name);
+ kfree(fp->owner.name);
+@@ -694,7 +705,14 @@ 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 session_fd_check() and __ksmbd_close_fd() never
++ * dereference a dangling pointer. Paired with ksmbd_conn_put() in
++ * session_fd_check() (durable preserve), 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;
+@@ -716,6 +734,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);
+ }
+@@ -1043,25 +1063,32 @@ static bool session_fd_check(struct ksmb
+ if (!is_reconnectable(fp))
+ return false;
+
++ if (WARN_ON_ONCE(!fp->conn))
++ return false;
++
+ if (ksmbd_vfs_copy_durable_owner(fp, user))
+ return false;
+
++ /*
++ * fp owns a strong reference on fp->conn (taken in ksmbd_open_fd()
++ * / ksmbd_reopen_durable_fd()), so conn stays valid for the whole
++ * body of this function regardless of any op->conn puts below.
++ */
+ conn = fp->conn;
+ ci = fp->f_ci;
+ down_write(&ci->m_lock);
+ list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) {
+ if (op->conn != conn)
+ continue;
+- if (op->conn && atomic_dec_and_test(&op->conn->refcnt))
+- kfree(op->conn);
++ ksmbd_conn_put(op->conn);
+ op->conn = NULL;
+ }
+ up_write(&ci->m_lock);
+
+ list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
+- spin_lock(&fp->conn->llist_lock);
++ spin_lock(&conn->llist_lock);
+ list_del_init(&smb_lock->clist);
+- spin_unlock(&fp->conn->llist_lock);
++ spin_unlock(&conn->llist_lock);
+ }
+
+ fp->conn = NULL;
+@@ -1072,6 +1099,8 @@ static bool session_fd_check(struct ksmb
+ fp->durable_scavenger_timeout =
+ jiffies_to_msecs(jiffies) + fp->durable_timeout;
+
++ /* Drop fp's own reference on conn. */
++ ksmbd_conn_put(conn);
+ return true;
+ }
+
+@@ -1158,15 +1187,27 @@ int ksmbd_reopen_durable_fd(struct ksmbd
+
+ old_f_state = fp->f_state;
+ fp->f_state = FP_NEW;
++
++ /*
++ * Initialize fp's connection binding before publishing fp into the
++ * session's file table. If __open_id() is ordered first, a
++ * concurrent teardown that iterates the table can observe a valid
++ * volatile_id with fp->conn == NULL and preserve a
++ * partially-initialized fp. fp owns a strong reference on the new
++ * conn (see ksmbd_open_fd()); undo it on __open_id() failure.
++ */
++ fp->conn = ksmbd_conn_get(conn);
++ fp->tcon = work->tcon;
++
+ __open_id(&work->sess->file_table, fp, OPEN_ID_TYPE_VOLATILE_ID);
+ if (!has_file_id(fp->volatile_id)) {
++ fp->conn = NULL;
++ fp->tcon = NULL;
++ ksmbd_conn_put(conn);
+ fp->f_state = old_f_state;
+ return -EBADF;
+ }
+
+- fp->conn = conn;
+- fp->tcon = work->tcon;
+-
+ list_for_each_entry(smb_lock, &fp->lock_list, flist) {
+ spin_lock(&conn->llist_lock);
+ list_add_tail(&smb_lock->clist, &conn->lock_list);
+@@ -1178,8 +1219,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd
+ list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) {
+ if (op->conn)
+ continue;
+- op->conn = fp->conn;
+- atomic_inc(&op->conn->refcnt);
++ op->conn = ksmbd_conn_get(fp->conn);
+ }
+ up_write(&ci->m_lock);
+
--- /dev/null
+From stable+bounces-275244-greg=kroah.com@vger.kernel.org Thu Jul 16 13:21:00 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 07:20:43 -0400
+Subject: ksmbd: fix path resolution in ksmbd_vfs_kern_path_create
+To: stable@vger.kernel.org
+Cc: Davide Ornaghi <d.ornaghi97@gmail.com>, Namjae Jeon <linkinjeon@kernel.org>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716112043.1732826-3-sashal@kernel.org>
+
+From: Davide Ornaghi <d.ornaghi97@gmail.com>
+
+[ Upstream commit 1c8951963d8ed357f70f59e0ad4ddce2199d2016 ]
+
+The SMB2 open lookup is rooted at the share with LOOKUP_BENEATH, but the
+create/mkdir/hardlink sink is not: ksmbd_vfs_kern_path_create() builds an
+absolute path with convert_to_unix_name() and resolves it from AT_FDCWD
+via start_creating_path(), so a ".." component is walked from the real
+filesystem root and escapes the export.
+
+An authenticated client races a missing path component so the rooted open
+lookup returns -ENOENT (taking the create branch) while the same component
+is present (a directory) when the create walk runs; the create then
+resolves ".." out of the share.
+
+Root the create walk at the share like the lookup and rename paths already
+are: resolve the parent with vfs_path_parent_lookup(..., LOOKUP_BENEATH,
+&share_conf->vfs_path) and create the final component with
+start_creating_noperm(). convert_to_unix_name() then has no callers and is
+removed.
+
+Fixes: 265fd1991c1d ("ksmbd: use LOOKUP_BENEATH to prevent the out of share access")
+Cc: stable@vger.kernel.org
+Signed-off-by: Davide Ornaghi <d.ornaghi97@gmail.com>
+Acked-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/misc.c | 33 ---------------------------------
+ fs/smb/server/misc.h | 1 -
+ fs/smb/server/vfs.c | 36 ++++++++++++++++++++++++++++++------
+ 3 files changed, 30 insertions(+), 40 deletions(-)
+
+--- a/fs/smb/server/misc.c
++++ b/fs/smb/server/misc.c
+@@ -278,39 +278,6 @@ char *ksmbd_extract_sharename(struct uni
+ return ksmbd_casefold_sharename(um, name);
+ }
+
+-/**
+- * convert_to_unix_name() - convert windows name to unix format
+- * @share: ksmbd_share_config pointer
+- * @name: file name that is relative to share
+- *
+- * Return: converted name on success, otherwise NULL
+- */
+-char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name)
+-{
+- int no_slash = 0, name_len, path_len;
+- char *new_name;
+-
+- if (name[0] == '/')
+- name++;
+-
+- path_len = share->path_sz;
+- name_len = strlen(name);
+- new_name = kmalloc(path_len + name_len + 2, KSMBD_DEFAULT_GFP);
+- if (!new_name)
+- return new_name;
+-
+- memcpy(new_name, share->path, path_len);
+- if (new_name[path_len - 1] != '/') {
+- new_name[path_len] = '/';
+- no_slash = 1;
+- }
+-
+- memcpy(new_name + path_len + no_slash, name, name_len);
+- path_len += name_len + no_slash;
+- new_name[path_len] = 0x00;
+- return new_name;
+-}
+-
+ char *ksmbd_convert_dir_info_name(struct ksmbd_dir_info *d_info,
+ const struct nls_table *local_nls,
+ int *conv_len)
+--- a/fs/smb/server/misc.h
++++ b/fs/smb/server/misc.h
+@@ -22,7 +22,6 @@ void ksmbd_strip_last_slash(char *path);
+ void ksmbd_conv_path_to_windows(char *path);
+ char *ksmbd_casefold_sharename(struct unicode_map *um, const char *name);
+ char *ksmbd_extract_sharename(struct unicode_map *um, const char *treename);
+-char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name);
+
+ #define KSMBD_DIR_INFO_ALIGNMENT 8
+ struct ksmbd_dir_info;
+--- a/fs/smb/server/vfs.c
++++ b/fs/smb/server/vfs.c
+@@ -1319,15 +1319,39 @@ struct dentry *ksmbd_vfs_kern_path_creat
+ unsigned int flags,
+ struct path *path)
+ {
+- char *abs_name;
++ struct ksmbd_share_config *share_conf = work->tcon->share_conf;
++ struct qstr last;
++ struct filename *filename;
+ struct dentry *dent;
++ int err;
+
+- abs_name = convert_to_unix_name(work->tcon->share_conf, name);
+- if (!abs_name)
+- return ERR_PTR(-ENOMEM);
++ /* resolve the name beneath the share root so ".." cannot escape */
++ filename = getname_kernel(name);
++ if (IS_ERR(filename))
++ return ERR_CAST(filename);
+
+- dent = kern_path_create(AT_FDCWD, abs_name, path, flags);
+- kfree(abs_name);
++ err = vfs_path_parent_lookup(filename, flags | LOOKUP_BENEATH,
++ path, &last, &share_conf->vfs_path);
++ if (err) {
++ putname(filename);
++ return ERR_PTR(err);
++ }
++
++ err = mnt_want_write(path->mnt);
++ if (err) {
++ path_put(path);
++ putname(filename);
++ return ERR_PTR(err);
++ }
++
++ inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT);
++ dent = lookup_one_qstr_excl(&last, path->dentry, LOOKUP_CREATE);
++ if (IS_ERR(dent)) {
++ inode_unlock(path->dentry->d_inode);
++ mnt_drop_write(path->mnt);
++ path_put(path);
++ }
++ putname(filename);
+ return dent;
+ }
+
--- /dev/null
+From stable+bounces-275318-greg=kroah.com@vger.kernel.org Thu Jul 16 13:52:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 07:52:31 -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: <20260716115231.1785404-2-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 | 23 +++++++++++++++++------
+ fs/smb/server/vfs_cache.h | 1 +
+ 3 files changed, 26 insertions(+), 8 deletions(-)
+
+--- a/fs/smb/server/smb2pdu.c
++++ b/fs/smb/server/smb2pdu.c
+@@ -7602,9 +7602,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;
+@@ -7739,6 +7741,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);
+@@ -7793,11 +7796,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
+@@ -391,10 +391,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);
+@@ -1086,9 +1090,15 @@ static bool session_fd_check(struct ksmb
+ up_write(&ci->m_lock);
+
+ list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
+- spin_lock(&conn->llist_lock);
++ struct ksmbd_conn *lock_conn = smb_lock->conn;
++
++ if (!lock_conn)
++ continue;
++ spin_lock(&lock_conn->llist_lock);
+ list_del_init(&smb_lock->clist);
+- spin_unlock(&conn->llist_lock);
++ smb_lock->conn = NULL;
++ spin_unlock(&lock_conn->llist_lock);
++ ksmbd_conn_put(lock_conn);
+ }
+
+ fp->conn = NULL;
+@@ -1209,6 +1219,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd
+ }
+
+ list_for_each_entry(smb_lock, &fp->lock_list, flist) {
++ smb_lock->conn = ksmbd_conn_get(conn);
+ spin_lock(&conn->llist_lock);
+ list_add_tail(&smb_lock->clist, &conn->lock_list);
+ spin_unlock(&conn->llist_lock);
+--- a/fs/smb/server/vfs_cache.h
++++ b/fs/smb/server/vfs_cache.h
+@@ -32,6 +32,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-275247-greg=kroah.com@vger.kernel.org Thu Jul 16 13:21:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 07:20:53 -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: <20260716112053.1733439-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
+@@ -8204,6 +8204,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(idmap,
+@@ -8212,9 +8213,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(idmap,
+ &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
+@@ -992,15 +992,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-275242-greg=kroah.com@vger.kernel.org Thu Jul 16 13:20:54 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 07:20:41 -0400
+Subject: ksmbd_vfs_rename(): vfs_path_parent_lookup() accepts ERR_PTR() as name
+To: stable@vger.kernel.org
+Cc: Al Viro <viro@zeniv.linux.org.uk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716112043.1732826-1-sashal@kernel.org>
+
+From: Al Viro <viro@zeniv.linux.org.uk>
+
+[ Upstream commit ba33ac100d3feb1efb43b32e63cc0c6430936aa3 ]
+
+no need to check in the caller
+
+Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
+Stable-dep-of: 1c8951963d8e ("ksmbd: fix path resolution in ksmbd_vfs_kern_path_create")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/vfs.c | 5 -----
+ 1 file changed, 5 deletions(-)
+
+--- a/fs/smb/server/vfs.c
++++ b/fs/smb/server/vfs.c
+@@ -715,10 +715,6 @@ int ksmbd_vfs_rename(struct ksmbd_work *
+ return -ENOMEM;
+
+ to = getname_kernel(newname);
+- if (IS_ERR(to)) {
+- err = PTR_ERR(to);
+- goto revert_fsids;
+- }
+
+ retry:
+ err = vfs_path_parent_lookup(to, lookup_flags | LOOKUP_BENEATH,
+@@ -819,7 +815,6 @@ out2:
+ }
+ out1:
+ putname(to);
+-revert_fsids:
+ ksmbd_revert_fsids(work);
+ return err;
+ }
--- /dev/null
+From stable+bounces-274994-greg=kroah.com@vger.kernel.org Wed Jul 15 18:37:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 12:32:30 -0400
+Subject: media: nxp: imx8-isi: Fix use-after-free on remove
+To: stable@vger.kernel.org
+Cc: Xiaolei Wang <xiaolei.wang@windriver.com>, Frank Li <Frank.Li@nxp.com>, Laurent Pinchart <laurent.pinchart@ideasonboard.com>, Hans Verkuil <hverkuil+cisco@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715163230.952514-2-sashal@kernel.org>
+
+From: Xiaolei Wang <xiaolei.wang@windriver.com>
+
+[ Upstream commit b670bf89824ede5d07d20bb9bfbafb754846081d ]
+
+KASAN reports a slab-use-after-free in __media_entity_remove_link()
+during rmmod of imx8_isi:
+
+ BUG: KASAN: slab-use-after-free in __media_entity_remove_link+0x608/0x650
+ Read of size 2 at addr ffff0000d47cb02a by task rmmod/724
+
+ Call trace:
+ __media_entity_remove_link+0x608/0x650
+ __media_entity_remove_links+0x78/0x144
+ __media_device_unregister_entity+0x150/0x280
+ media_device_unregister_entity+0x48/0x68
+ v4l2_device_unregister_subdev+0x158/0x300
+ v4l2_async_unbind_subdev_one+0x22c/0x358
+ v4l2_async_nf_unbind_all_subdevs+0xfc/0x1c0
+ v4l2_async_nf_unregister+0x5c/0x14c
+ mxc_isi_remove+0x124/0x2a0 [imx8_isi]
+
+ Allocated by task 249:
+ __kmalloc_noprof+0x27c/0x690
+ mxc_isi_crossbar_init+0x22c/0x560 [imx8_isi]
+
+ Freed by task 724:
+ kfree+0x1e4/0x5b0
+ mxc_isi_crossbar_cleanup+0x34/0x80 [imx8_isi]
+ mxc_isi_remove+0x11c/0x2a0 [imx8_isi]
+
+The problem is that mxc_isi_remove() calls mxc_isi_crossbar_cleanup()
+before mxc_isi_v4l2_cleanup(). The crossbar cleanup frees the media
+entity pads, but the subsequent v4l2 cleanup still tries to remove
+media links that reference those pads.
+
+Fix this by calling mxc_isi_v4l2_cleanup() before
+mxc_isi_crossbar_cleanup() to ensure all media entities are properly
+unregistered while the pads are still valid.
+
+Fixes: cf21f328fcaf ("media: nxp: Add i.MX8 ISI driver")
+Cc: stable@vger.kernel.org
+Signed-off-by: Xiaolei Wang <xiaolei.wang@windriver.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+Link: https://patch.msgid.link/20260507041318.491594-2-xiaolei.wang@windriver.com
+Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
++++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
+@@ -519,8 +519,8 @@ static void mxc_isi_remove(struct platfo
+ mxc_isi_pipe_cleanup(pipe);
+ }
+
+- mxc_isi_crossbar_cleanup(&isi->crossbar);
+ mxc_isi_v4l2_cleanup(isi);
++ mxc_isi_crossbar_cleanup(&isi->crossbar);
+ }
+
+ static const struct of_device_id mxc_isi_of_match[] = {
--- /dev/null
+From stable+bounces-274993-greg=kroah.com@vger.kernel.org Wed Jul 15 18:37:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 12:32:29 -0400
+Subject: media: nxp: imx8-isi: use devm_pm_runtime_enable() to simplify code
+To: stable@vger.kernel.org
+Cc: Frank Li <Frank.Li@nxp.com>, Laurent Pinchart <laurent.pinchart@ideasonboard.com>, Hans Verkuil <hverkuil+cisco@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715163230.952514-1-sashal@kernel.org>
+
+From: Frank Li <Frank.Li@nxp.com>
+
+[ Upstream commit 078161dd44d6f848a62a473206d69025607736ec ]
+
+Use devm_pm_runtime_enable() to simplify code. Change to use
+dev_err_probe() because previous goto change to return.
+
+No functional change.
+
+Signed-off-by: Frank Li <Frank.Li@nxp.com>
+Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+Link: https://patch.msgid.link/20260116-cam_cleanup-v4-2-29ce01640443@nxp.com
+Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
+Stable-dep-of: b670bf89824e ("media: nxp: imx8-isi: Fix use-after-free on remove")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c | 16 +++++++---------
+ 1 file changed, 7 insertions(+), 9 deletions(-)
+
+--- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
++++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
+@@ -472,13 +472,14 @@ static int mxc_isi_probe(struct platform
+ return ret;
+ }
+
+- pm_runtime_enable(dev);
++ ret = devm_pm_runtime_enable(dev);
++ if (ret)
++ return ret;
+
+ ret = mxc_isi_crossbar_init(isi);
+- if (ret) {
+- dev_err(dev, "Failed to initialize crossbar: %d\n", ret);
+- goto err_pm;
+- }
++ if (ret)
++ return dev_err_probe(dev, ret,
++ "Failed to initialize crossbar\n");
+
+ for (i = 0; i < isi->pdata->num_channels; ++i) {
+ ret = mxc_isi_pipe_init(isi, i);
+@@ -501,8 +502,7 @@ static int mxc_isi_probe(struct platform
+
+ err_xbar:
+ mxc_isi_crossbar_cleanup(&isi->crossbar);
+-err_pm:
+- pm_runtime_disable(isi->dev);
++
+ return ret;
+ }
+
+@@ -521,8 +521,6 @@ static void mxc_isi_remove(struct platfo
+
+ mxc_isi_crossbar_cleanup(&isi->crossbar);
+ mxc_isi_v4l2_cleanup(isi);
+-
+- pm_runtime_disable(isi->dev);
+ }
+
+ static const struct of_device_id mxc_isi_of_match[] = {
--- /dev/null
+From stable+bounces-275057-greg=kroah.com@vger.kernel.org Wed Jul 15 23:28:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 17:28:07 -0400
+Subject: mm: swap_cgroup: fix NULL deref in lookup_swap_cgroup_id on swapless host
+To: stable@vger.kernel.org
+Cc: "Jose Fernandez (Anthropic)" <jose.fernandez@linux.dev>, syzbot+e12bd9ca48157add237a@syzkaller.appspotmail.com, Barry Song <baohua@kernel.org>, David Hildenbrand <david@kernel.org>, Hugh Dickins <hughd@google.com>, Johannes Weiner <hannes@cmpxchg.org>, Kairui Song <ryncsn@gmail.com>, Michal Hocko <mhocko@kernel.org>, Muchun Song <muchun.song@linux.dev>, Roman Gushchin <roman.gushchin@linux.dev>, Shakeel Butt <shakeel.butt@linux.dev>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260715212807.292409-1-sashal@kernel.org>
+
+From: "Jose Fernandez (Anthropic)" <jose.fernandez@linux.dev>
+
+[ Upstream commit 63b02a9409cb5180398491b093e48bcb5315f5fb ]
+
+lookup_swap_cgroup_id() passes swap_cgroup_ctrl[type].map to
+__swap_cgroup_id_lookup() without checking that the type was ever
+registered via swap_cgroup_swapon(). On a swapless host every ctrl->map
+is NULL, so __swap_cgroup_id_lookup() dereferences NULL + a scaled
+swp_offset().
+
+Since commit bea67dcc5eea ("mm: attempt to batch free swap entries for
+zap_pte_range()"), zap_pte_range() -> swap_pte_batch() calls
+lookup_swap_cgroup_id() on any non-present, non-none PTE that decodes as a
+real swap entry, without first validating it against swap_info[]. A
+single PTE corrupted into a type-0 swap entry takes the host down at
+process exit.
+
+We hit this in production on a swapless 6.12.58 host: ~1s of
+"get_swap_device: Bad swap file entry 3f800204222bb" (do_swap_page() being
+correctly defensive about the same entry) followed by
+
+ BUG: unable to handle page fault for address: 000003f800204220
+ RIP: 0010:lookup_swap_cgroup_id+0x2b/0x60
+ Call Trace:
+ swap_pte_batch+0xbf/0x230
+ zap_pte_range+0x4c8/0x780
+ unmap_page_range+0x190/0x3e0
+ exit_mmap+0xd9/0x3c0
+ do_exit+0x20c/0x4b0
+
+syzbot has reported the identical stack.
+
+The source of the PTE corruption is a separate bug; this change makes the
+teardown path as robust as the fault path already is. Every other caller
+of lookup_swap_cgroup_id() is downstream of a get_swap_device() that has
+already validated the entry, so the new branch is cold.
+
+Link: https://lore.kernel.org/20260504-swap-cgroup-fix-7-0-v1-1-f53ff41ee553@linux.dev
+Fixes: bea67dcc5eea ("mm: attempt to batch free swap entries for zap_pte_range()")
+Signed-off-by: Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
+Reported-by: syzbot+e12bd9ca48157add237a@syzkaller.appspotmail.com
+Link: https://lore.kernel.org/r/69859728.050a0220.3b3015.0033.GAE@google.com
+Assisted-by: Claude:unspecified
+Cc: Barry Song <baohua@kernel.org>
+Cc: David Hildenbrand <david@kernel.org>
+Cc: Hugh Dickins <hughd@google.com>
+Cc: Johannes Weiner <hannes@cmpxchg.org>
+Cc: Kairui Song <ryncsn@gmail.com>
+Cc: Michal Hocko <mhocko@kernel.org>
+Cc: Muchun Song <muchun.song@linux.dev>
+Cc: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Shakeel Butt <shakeel.butt@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/swap_cgroup.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/mm/swap_cgroup.c
++++ b/mm/swap_cgroup.c
+@@ -161,8 +161,14 @@ unsigned short swap_cgroup_record(swp_en
+ */
+ unsigned short lookup_swap_cgroup_id(swp_entry_t ent)
+ {
++ struct swap_cgroup_ctrl *ctrl;
++
+ if (mem_cgroup_disabled())
+ return 0;
++
++ ctrl = &swap_cgroup_ctrl[swp_type(ent)];
++ if (unlikely(!ctrl->map))
++ return 0;
+ return lookup_swap_cgroup(ent, NULL)->id;
+ }
+
--- /dev/null
+From stable+bounces-275029-greg=kroah.com@vger.kernel.org Wed Jul 15 22:16:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 16:16:09 -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: <20260715201610.64283-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-275030-greg=kroah.com@vger.kernel.org Wed Jul 15 22:16:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 16:16:10 -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: <20260715201610.64283-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-274263-greg=kroah.com@vger.kernel.org Tue Jul 14 16:22:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 10:06:05 -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: <20260714140605.2729466-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
+@@ -679,8 +679,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);
+ }
+@@ -705,7 +715,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;
+ }
+
+@@ -790,6 +799,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 */
+@@ -800,7 +815,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 void altera_pcie_remove(struct platform_device *pdev)
--- /dev/null
+From stable+bounces-274543-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 16:02:22 -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: <20260714200226.3152882-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-dma-pl.c | 2 +-
+ drivers/pci/controller/pcie-xilinx-nwl.c | 2 +-
+ drivers/pci/controller/plda/pcie-plda-host.c | 2 +-
+ 9 files changed, 12 insertions(+), 15 deletions(-)
+
+--- a/drivers/pci/controller/dwc/pcie-designware-host.c
++++ b/drivers/pci/controller/dwc/pcie-designware-host.c
+@@ -227,7 +227,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
+@@ -435,7 +435,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);
+@@ -461,13 +461,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
+@@ -164,7 +164,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
+@@ -666,10 +666,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
+@@ -695,8 +695,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
+@@ -488,7 +488,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-dma-pl.c
++++ b/drivers/pci/controller/pcie-xilinx-dma-pl.c
+@@ -470,7 +470,7 @@ static int xilinx_pl_dma_pcie_init_msi_i
+ struct device *dev = port->dev;
+ struct xilinx_msi *msi = &port->msi;
+ int size = BITS_TO_LONGS(XILINX_NUM_MSI_IRQS) * sizeof(long);
+- struct fwnode_handle *fwnode = of_node_to_fwnode(port->dev->of_node);
++ struct fwnode_handle *fwnode = dev_fwnode(port->dev);
+
+ msi->dev_domain = irq_domain_add_linear(NULL, XILINX_NUM_MSI_IRQS,
+ &dev_msi_domain_ops, port);
+--- a/drivers/pci/controller/pcie-xilinx-nwl.c
++++ b/drivers/pci/controller/pcie-xilinx-nwl.c
+@@ -495,7 +495,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,
+--- a/drivers/pci/controller/plda/pcie-plda-host.c
++++ b/drivers/pci/controller/plda/pcie-plda-host.c
+@@ -150,7 +150,7 @@ static struct msi_domain_info plda_msi_d
+ static int plda_allocate_msi_domains(struct plda_pcie_rp *port)
+ {
+ struct device *dev = port->dev;
+- struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node);
++ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ struct plda_msi *msi = &port->msi;
+
+ mutex_init(&port->msi.lock);
--- /dev/null
+From stable+bounces-274581-greg=kroah.com@vger.kernel.org Wed Jul 15 00:04:47 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 18:04:19 -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: <20260714220421.3334071-4-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/gpu/drm/xe/xe_vram.c | 2
+ drivers/pci/pci-sysfs.c | 17 ----
+ drivers/pci/pci.h | 3
+ drivers/pci/setup-bus.c | 101 +++++++++++++++++++---------
+ drivers/pci/setup-res.c | 19 +----
+ include/linux/pci.h | 4 -
+ 8 files changed, 87 insertions(+), 65 deletions(-)
+
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
+@@ -1611,7 +1611,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/gpu/drm/xe/xe_vram.c
++++ b/drivers/gpu/drm/xe/xe_vram.c
+@@ -33,7 +33,7 @@ _resize_bar(struct xe_device *xe, int re
+ if (pci_resource_len(pdev, resno))
+ pci_release_resource(pdev, resno);
+
+- ret = pci_resize_resource(pdev, resno, bar_size);
++ ret = pci_resize_resource(pdev, resno, bar_size, 0);
+ if (ret) {
+ drm_info(&xe->drm, "Failed to resize BAR%d to %dM (%pe). Consider enabling 'Resizable BAR' support in your BIOS\n",
+ resno, 1 << bar_size, ERR_PTR(ret));
+--- a/drivers/pci/pci-sysfs.c
++++ b/drivers/pci/pci-sysfs.c
+@@ -1427,18 +1427,13 @@ static ssize_t __resource_resize_store(s
+ {
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct pci_bus *bus = pdev->bus;
+- struct resource *b_win, *res;
+ unsigned long size;
+- int ret, i;
++ int ret;
+ u16 cmd;
+
+ if (kstrtoul(buf, 0, &size) < 0)
+ return -EINVAL;
+
+- b_win = pbus_select_window(bus, pci_resource_n(pdev, n));
+- if (!b_win)
+- return -EINVAL;
+-
+ device_lock(dev);
+ if (dev->driver || pci_num_vf(pdev)) {
+ ret = -EBUSY;
+@@ -1460,15 +1455,7 @@ static ssize_t __resource_resize_store(s
+
+ pci_remove_resource_files(pdev);
+
+- pci_dev_for_each_resource(pdev, res, i) {
+- if (i >= PCI_BRIDGE_RESOURCES)
+- break;
+-
+- if (b_win == pbus_select_window(bus, res))
+- 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(bus);
+
+--- a/drivers/pci/pci.h
++++ b/drivers/pci/pci.h
+@@ -330,6 +330,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 rrs_timeout);
+--- a/drivers/pci/setup-bus.c
++++ b/drivers/pci/setup-bus.c
+@@ -2297,18 +2297,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 {
+@@ -2325,9 +2323,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);
+
+@@ -2343,67 +2341,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
+@@ -429,9 +429,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;
+@@ -442,10 +442,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;
+@@ -465,19 +461,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
+@@ -1418,7 +1418,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);
+@@ -1488,7 +1489,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);
+ int pci_enable_resources(struct pci_dev *, int mask);
+ void pci_assign_irq(struct pci_dev *dev);
+ struct resource *pci_find_resource(struct pci_dev *dev, struct resource *res);
--- /dev/null
+From stable+bounces-274580-greg=kroah.com@vger.kernel.org Wed Jul 15 00:05:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 18:04:18 -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: <20260714220421.3334071-3-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
+@@ -2400,8 +2400,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-274539-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:27 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 16:02:24 -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: <20260714200226.3152882-3-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
+@@ -144,23 +144,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;
+ };
+
+ /**
+@@ -708,7 +716,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);
+
+@@ -716,7 +724,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 */
+@@ -1117,7 +1125,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)
+@@ -1205,9 +1213,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 = {
+@@ -1217,19 +1225,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-274541-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:40 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 16:02:26 -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: <20260714200226.3152882-5-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 | 75 +++++++++++++++++++++------------
+ 1 file changed, 48 insertions(+), 27 deletions(-)
+
+--- a/drivers/pci/controller/pcie-mediatek.c
++++ b/drivers/pci/controller/pcie-mediatek.c
+@@ -13,7 +13,7 @@
+ #include <linux/iopoll.h>
+ #include <linux/irq.h>
+ #include <linux/irqchip/chained_irq.h>
+-#include <linux/irqchip/irq-msi-lib.h>
++#include "../../irqchip/irq-msi-lib.h"
+ #include <linux/irqdomain.h>
+ #include <linux/kernel.h>
+ #include <linux/mfd/syscon.h>
+@@ -491,7 +491,6 @@ static const struct msi_parent_ops mtk_m
+ .required_flags = MTK_MSI_FLAGS_REQUIRED,
+ .supported_flags = MTK_MSI_FLAGS_SUPPORTED,
+ .bus_select_token = DOMAIN_BUS_PCI_MSI,
+- .chip_flags = MSI_CHIP_FLAG_SET_ACK,
+ .prefix = "MTK-",
+ .init_dev_msi_info = msi_lib_init_dev_msi_info,
+ };
+@@ -505,13 +504,18 @@ static int mtk_pcie_allocate_msi_domains
+ .ops = &msi_domain_ops,
+ .host_data = port,
+ .size = MTK_MSI_IRQS_NUM,
++ .hwirq_max = MTK_MSI_IRQS_NUM,
++ .bus_token = DOMAIN_BUS_PCI_MSI,
++ .domain_flags = IRQ_DOMAIN_FLAG_MSI_PARENT,
+ };
+
+- port->inner_domain = msi_create_parent_irq_domain(&info, &mtk_msi_parent_ops);
+- if (!port->inner_domain) {
++ port->inner_domain = irq_domain_instantiate(&info);
++ if (IS_ERR(port->inner_domain)) {
+ dev_err(port->pcie->dev, "failed to create IRQ domain\n");
++ port->inner_domain = NULL;
+ return -ENOMEM;
+ }
++ port->inner_domain->msi_parent_ops = &mtk_msi_parent_ops;
+
+ return 0;
+ }
+@@ -530,23 +534,27 @@ 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->inner_domain)
++ irq_domain_remove(port->inner_domain);
++ }
+
+- if (IS_ENABLED(CONFIG_PCI_MSI)) {
+- 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,
+@@ -829,7 +837,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;
+@@ -838,7 +846,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);
+@@ -886,11 +894,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);
+@@ -906,8 +918,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,
+@@ -1083,8 +1095,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))
+@@ -1186,14 +1203,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))
--- /dev/null
+From stable+bounces-274540-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 16:02:23 -0400
+Subject: PCI: mediatek: Switch to msi_create_parent_irq_domain()
+To: stable@vger.kernel.org
+Cc: Nam Cao <namcao@linutronix.de>, Manivannan Sadhasivam <mani@kernel.org>, Bjorn Helgaas <bhelgaas@google.com>, Thomas Gleixner <tglx@linutronix.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260714200226.3152882-2-sashal@kernel.org>
+
+From: Nam Cao <namcao@linutronix.de>
+
+[ Upstream commit e449cb9afc963cf9cf47139bb873c412605c83e7 ]
+
+Switch to msi_create_parent_irq_domain() from pci_msi_create_irq_domain()
+which was using legacy MSI domain setup.
+
+Signed-off-by: Nam Cao <namcao@linutronix.de>
+[mani: reworded commit message]
+Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
+[bhelgaas: rebase on dev_fwnode() conversion, drop fwnode local var]
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
+Link: https://patch.msgid.link/76f6e6ce6021607cd0fdfd79fef7d2eb69d9f361.1750858083.git.namcao@linutronix.de
+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/Kconfig | 1
+ drivers/pci/controller/pcie-mediatek.c | 50 +++++++++++++++------------------
+ 2 files changed, 24 insertions(+), 27 deletions(-)
+
+--- a/drivers/pci/controller/Kconfig
++++ b/drivers/pci/controller/Kconfig
+@@ -190,6 +190,7 @@ config PCIE_MEDIATEK
+ depends on ARCH_AIROHA || ARCH_MEDIATEK || COMPILE_TEST
+ depends on OF
+ depends on PCI_MSI
++ select IRQ_MSI_LIB
+ help
+ Say Y here if you want to enable PCIe controller support on
+ MediaTek SoCs.
+--- a/drivers/pci/controller/pcie-mediatek.c
++++ b/drivers/pci/controller/pcie-mediatek.c
+@@ -13,6 +13,7 @@
+ #include <linux/iopoll.h>
+ #include <linux/irq.h>
+ #include <linux/irqchip/chained_irq.h>
++#include <linux/irqchip/irq-msi-lib.h>
+ #include <linux/irqdomain.h>
+ #include <linux/kernel.h>
+ #include <linux/mfd/syscon.h>
+@@ -182,7 +183,6 @@ struct mtk_pcie_soc {
+ * @irq: GIC irq
+ * @irq_domain: legacy INTx IRQ domain
+ * @inner_domain: inner IRQ domain
+- * @msi_domain: MSI IRQ domain
+ * @lock: protect the msi_irq_in_use bitmap
+ * @msi_irq_in_use: bit map for assigned MSI IRQ
+ */
+@@ -203,7 +203,6 @@ struct mtk_pcie_port {
+ int irq;
+ struct irq_domain *irq_domain;
+ struct irq_domain *inner_domain;
+- struct irq_domain *msi_domain;
+ struct mutex lock;
+ DECLARE_BITMAP(msi_irq_in_use, MTK_MSI_IRQS_NUM);
+ };
+@@ -473,40 +472,39 @@ static const struct irq_domain_ops msi_d
+ .free = mtk_pcie_irq_domain_free,
+ };
+
+-static struct irq_chip mtk_msi_irq_chip = {
+- .name = "MTK PCIe MSI",
+- .irq_ack = irq_chip_ack_parent,
+- .irq_mask = pci_msi_mask_irq,
+- .irq_unmask = pci_msi_unmask_irq,
+-};
+-
+-static struct msi_domain_info mtk_msi_domain_info = {
+- .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
+- MSI_FLAG_NO_AFFINITY | MSI_FLAG_PCI_MSIX,
+- .chip = &mtk_msi_irq_chip,
++#define MTK_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
++ MSI_FLAG_USE_DEF_CHIP_OPS | \
++ MSI_FLAG_NO_AFFINITY)
++
++#define MTK_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \
++ MSI_FLAG_PCI_MSIX)
++
++static const struct msi_parent_ops mtk_msi_parent_ops = {
++ .required_flags = MTK_MSI_FLAGS_REQUIRED,
++ .supported_flags = MTK_MSI_FLAGS_SUPPORTED,
++ .bus_select_token = DOMAIN_BUS_PCI_MSI,
++ .chip_flags = MSI_CHIP_FLAG_SET_ACK,
++ .prefix = "MTK-",
++ .init_dev_msi_info = msi_lib_init_dev_msi_info,
+ };
+
+ static int mtk_pcie_allocate_msi_domains(struct mtk_pcie_port *port)
+ {
+- struct fwnode_handle *fwnode = dev_fwnode(port->pcie->dev);
+-
+ mutex_init(&port->lock);
+
+- port->inner_domain = irq_domain_create_linear(fwnode, MTK_MSI_IRQS_NUM,
+- &msi_domain_ops, port);
++ struct irq_domain_info info = {
++ .fwnode = dev_fwnode(port->pcie->dev),
++ .ops = &msi_domain_ops,
++ .host_data = port,
++ .size = MTK_MSI_IRQS_NUM,
++ };
++
++ port->inner_domain = msi_create_parent_irq_domain(&info, &mtk_msi_parent_ops);
+ if (!port->inner_domain) {
+ dev_err(port->pcie->dev, "failed to create IRQ domain\n");
+ return -ENOMEM;
+ }
+
+- port->msi_domain = pci_msi_create_irq_domain(fwnode, &mtk_msi_domain_info,
+- port->inner_domain);
+- if (!port->msi_domain) {
+- dev_err(port->pcie->dev, "failed to create MSI domain\n");
+- irq_domain_remove(port->inner_domain);
+- return -ENOMEM;
+- }
+-
+ return 0;
+ }
+
+@@ -535,8 +533,6 @@ static void mtk_pcie_irq_teardown(struct
+ 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);
+ }
--- /dev/null
+From stable+bounces-274542-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:38 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 16:02:25 -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: <20260714200226.3152882-4-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
+@@ -702,12 +702,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-274583-greg=kroah.com@vger.kernel.org Wed Jul 15 00:05:15 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 18:04:20 -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: <20260714220421.3334071-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 | 134 ------------------------
+ drivers/pci/pci.h | 1
+ drivers/pci/rebar.c | 191 +++++++++++++++++++++++++++++++++++
+ drivers/pci/setup-res.c | 44 --------
+ 6 files changed, 196 insertions(+), 179 deletions(-)
+ create mode 100644 drivers/pci/rebar.c
+
+--- a/Documentation/driver-api/pci/pci.rst
++++ b/Documentation/driver-api/pci/pci.rst
+@@ -37,6 +37,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 \
+- rom.o setup-res.o irq.o vpd.o \
++ rebar.o rom.o setup-res.o irq.o vpd.o \
+ setup-bus.o vc.o mmap.o devres.o
+
+ obj-$(CONFIG_PCI) += msi/
+--- a/drivers/pci/pci.c
++++ b/drivers/pci/pci.c
+@@ -1907,32 +1907,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 = 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 = pdev->resource + bar_idx;
+- size = pci_rebar_bytes_to_size(resource_size(res));
+- ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
+- ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size);
+- 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
+@@ -3725,114 +3699,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 = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl);
+-
+- for (i = 0; i < nbars; i++, pos += 8) {
+- int bar_idx;
+-
+- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
+- bar_idx = FIELD_GET(PCI_REBAR_CTRL_BAR_IDX, ctrl);
+- 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 FIELD_GET(PCI_REBAR_CTRL_BAR_SIZE, ctrl);
+-}
+-
+-/**
+- * 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 |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size);
+- 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
+@@ -751,6 +751,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,191 @@
++// 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 = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl);
++
++ for (i = 0; i < nbars; i++, pos += 8) {
++ int bar_idx;
++
++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
++ bar_idx = FIELD_GET(PCI_REBAR_CTRL_BAR_IDX, ctrl);
++ 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 FIELD_GET(PCI_REBAR_CTRL_BAR_SIZE, ctrl);
++}
++
++/**
++ * 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 |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size);
++ 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 |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size);
++ pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
++ }
++}
++
++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
+@@ -429,50 +429,6 @@ 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 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-274579-greg=kroah.com@vger.kernel.org Wed Jul 15 00:05:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 18:04:17 -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: <20260714220421.3334071-2-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
+@@ -2388,6 +2388,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-274582-greg=kroah.com@vger.kernel.org Wed Jul 15 00:04:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 18:04:21 -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: <20260714220421.3334071-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
+@@ -130,6 +130,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) {
+@@ -137,6 +140,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-274578-greg=kroah.com@vger.kernel.org Wed Jul 15 00:05:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 18:04:16 -0400
+Subject: PCI: Use pbus_select_window() during BAR resize
+To: stable@vger.kernel.org
+Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>, "Bjorn Helgaas" <bhelgaas@google.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260714220421.3334071-1-sashal@kernel.org>
+
+From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+
+[ Upstream commit 7dc58aa7f1b32a215fb0b7c6ca30ddf4663dedf4 ]
+
+Prior to a BAR resize, __resource_resize_store() loops through the normal
+resources of the PCI device and releases those that match to the flags of
+the BAR to be resized. This is necessary to allow resizing also the
+upstream bridge window as only childless bridge windows can be resized.
+
+While the flags check (mostly) works (if corner cases are ignored), the
+more straightforward way is to check if the resources share the bridge
+window. Change __resource_resize_store() to do the check using
+pbus_select_window().
+
+Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+Link: https://patch.msgid.link/20250829131113.36754-16-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/pci-sysfs.c | 20 +++++++++++++-------
+ 1 file changed, 13 insertions(+), 7 deletions(-)
+
+--- a/drivers/pci/pci-sysfs.c
++++ b/drivers/pci/pci-sysfs.c
+@@ -1426,13 +1426,19 @@ static ssize_t __resource_resize_store(s
+ const char *buf, size_t count)
+ {
+ struct pci_dev *pdev = to_pci_dev(dev);
+- unsigned long size, flags;
++ struct pci_bus *bus = pdev->bus;
++ struct resource *b_win, *res;
++ unsigned long size;
+ int ret, i;
+ u16 cmd;
+
+ if (kstrtoul(buf, 0, &size) < 0)
+ return -EINVAL;
+
++ b_win = pbus_select_window(bus, pci_resource_n(pdev, n));
++ if (!b_win)
++ return -EINVAL;
++
+ device_lock(dev);
+ if (dev->driver || pci_num_vf(pdev)) {
+ ret = -EBUSY;
+@@ -1452,19 +1458,19 @@ static ssize_t __resource_resize_store(s
+ 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_BRIDGE_RESOURCES; i++) {
+- if (pci_resource_len(pdev, i) &&
+- pci_resource_flags(pdev, i) == flags)
++ pci_dev_for_each_resource(pdev, res, i) {
++ if (i >= PCI_BRIDGE_RESOURCES)
++ break;
++
++ if (b_win == pbus_select_window(bus, res))
+ pci_release_resource(pdev, i);
+ }
+
+ ret = pci_resize_resource(pdev, n, size);
+
+- pci_assign_unassigned_bus_resources(pdev->bus);
++ pci_assign_unassigned_bus_resources(bus);
+
+ if (pci_create_resource_files(pdev))
+ pci_warn(pdev, "Failed to recreate resource files after BAR resizing\n");
--- /dev/null
+From stable+bounces-276815-greg=kroah.com@vger.kernel.org Thu Jul 16 20:49:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 14:45:34 -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: <20260716184534.1012849-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
+@@ -538,12 +538,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);
+@@ -562,7 +556,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-276779-greg=kroah.com@vger.kernel.org Thu Jul 16 18:51:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 12:49:26 -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: <20260716164926.673440-2-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
+@@ -218,33 +218,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);
+ }
+
+ /*
+@@ -704,23 +695,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 mnt_idmap *idmap, 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;
+ }
+
+@@ -2246,21 +2226,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
+@@ -172,24 +172,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
+@@ -108,7 +108,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-277062-greg=kroah.com@vger.kernel.org Fri Jul 17 16:04:27 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:00:25 -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: <20260717140025.1717514-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
+@@ -483,6 +483,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;
+@@ -658,6 +663,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
+@@ -414,18 +414,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 */
+@@ -2331,17 +2337,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))
+@@ -2391,23 +2395,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;
+
+@@ -2433,8 +2436,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;
+@@ -2442,7 +2444,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 */
+@@ -2459,6 +2460,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-276778-greg=kroah.com@vger.kernel.org Thu Jul 16 18:51:48 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 12:49:25 -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: <20260716164926.673440-1-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 | 6 +++---
+ fs/proc/internal.h | 4 ++--
+ fs/proc/namespaces.c | 4 ++--
+ fs/proc/proc_net.c | 2 +-
+ 5 files changed, 19 insertions(+), 19 deletions(-)
+
+--- a/fs/proc/base.c
++++ b/fs/proc/base.c
+@@ -721,7 +721,7 @@ static bool proc_fd_access_allowed(struc
+ return allowed;
+ }
+
+-int proc_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
++int proc_nochmod_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct iattr *attr)
+ {
+ int error;
+@@ -794,7 +794,7 @@ static int proc_pid_permission(struct mn
+
+
+ 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,
+ };
+
+
+@@ -2315,7 +2315,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 *
+@@ -2394,7 +2394,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
+@@ -2890,7 +2890,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
+@@ -2949,7 +2949,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
+@@ -3454,7 +3454,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,
+ };
+
+@@ -3654,7 +3654,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,
+ };
+
+@@ -3785,7 +3785,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,
+@@ -3999,7 +3999,7 @@ static loff_t proc_dir_llseek(struct fil
+ 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
+@@ -102,7 +102,7 @@ static int proc_fdinfo_permission(struct
+
+ 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 = {
+@@ -375,7 +375,7 @@ const struct inode_operations proc_fd_in
+ .lookup = proc_lookupfd,
+ .permission = proc_fd_permission,
+ .getattr = proc_fd_getattr,
+- .setattr = proc_setattr,
++ .setattr = proc_nochmod_setattr,
+ };
+
+ static struct dentry *proc_fdinfo_instantiate(struct dentry *dentry,
+@@ -416,7 +416,7 @@ static int proc_fdinfo_iterate(struct fi
+ 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
+@@ -215,8 +215,8 @@ extern int proc_pid_statm(struct seq_fil
+ extern const struct dentry_operations pid_dentry_operations;
+ extern int pid_getattr(struct mnt_idmap *, const struct path *,
+ struct kstat *, u32, unsigned int);
+-extern int proc_setattr(struct mnt_idmap *, struct dentry *,
+- struct iattr *);
++int proc_nochmod_setattr(struct mnt_idmap *idmap, 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
+@@ -322,7 +322,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-273238-greg=kroah.com@vger.kernel.org Fri Jul 10 15:35:43 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 10 Jul 2026 09:34:36 -0400
+Subject: rust: block: fix GenDisk cleanup paths
+To: stable@vger.kernel.org
+Cc: Haoze Xie <royenheart@gmail.com>, stable@kernel.org, Yuan Tan <yuantan098@gmail.com>, Xin Liu <bird@lzu.edu.cn>, Andreas Hindborg <a.hindborg@kernel.org>, Ren Wei <n05ec@lzu.edu.cn>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260710133436.82387-1-sashal@kernel.org>
+
+From: Haoze Xie <royenheart@gmail.com>
+
+[ Upstream commit 2957771379fa335103a4b539db57bb2271e12142 ]
+
+GenDiskBuilder::build() still has fallible work after
+__blk_mq_alloc_disk(), but its error path only recovers the
+foreign queue data. That leaks the temporary gendisk and
+request_queue until later teardown. If the caller moved the last
+Arc<TagSet<T>> into build(), the leaked queue can retain blk-mq
+state after the tag set is dropped.
+
+Fix the pre-registration failure path by dropping the temporary
+gendisk reference with put_disk() before recovering queue_data,
+so disk_release() can tear down the owned queue.
+
+Also pair GenDisk::drop() with put_disk() after del_gendisk().
+Once a Rust GenDisk has been added with device_add_disk(),
+del_gendisk() only unregisters it; the final gendisk reference
+still has to be dropped to complete the release path.
+
+Fixes: 3253aba3408a ("rust: block: introduce `kernel::block::mq` module")
+Cc: stable@kernel.org
+Reported-by: Yuan Tan <yuantan098@gmail.com>
+Reported-by: Xin Liu <bird@lzu.edu.cn>
+Reviewed-by: Andreas Hindborg <a.hindborg@kernel.org>
+Signed-off-by: Haoze Xie <royenheart@gmail.com>
+Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
+Link: https://patch.msgid.link/b70aff9a920cc42110fe5cf454c3099561863519.1780063368.git.royenheart@gmail.com
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+[ no queue-data recovery or recover_data.dismiss() in 6.12 ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ rust/kernel/block/mq/gen_disk.rs | 15 ++++++++++++++-
+ 1 file changed, 14 insertions(+), 1 deletion(-)
+
+--- a/rust/kernel/block/mq/gen_disk.rs
++++ b/rust/kernel/block/mq/gen_disk.rs
+@@ -6,7 +6,7 @@
+ //! C header: [`include/linux/blk-mq.h`](srctree/include/linux/blk-mq.h)
+
+ use crate::block::mq::{raw_writer::RawWriter, Operations, TagSet};
+-use crate::{bindings, error::from_err_ptr, error::Result, sync::Arc};
++use crate::{bindings, error::from_err_ptr, error::Result, sync::Arc, types::ScopeGuard};
+ use crate::{error, static_lock_class};
+ use core::fmt::{self, Write};
+
+@@ -139,6 +139,12 @@ impl GenDiskBuilder {
+ // SAFETY: `gendisk` is a valid pointer as we initialized it above
+ unsafe { (*gendisk).fops = &TABLE };
+
++ let cleanup_failure = ScopeGuard::new_with_data(gendisk, |gendisk| {
++ // SAFETY: `gendisk` came from `__blk_mq_alloc_disk()` above and
++ // has not been added to the VFS on this cleanup path.
++ unsafe { bindings::put_disk(gendisk) };
++ });
++
+ let mut raw_writer = RawWriter::from_array(
+ // SAFETY: `gendisk` points to a valid and initialized instance. We
+ // have exclusive access, since the disk is not added to the VFS
+@@ -161,6 +167,8 @@ impl GenDiskBuilder {
+ },
+ )?;
+
++ cleanup_failure.dismiss();
++
+ // INVARIANT: `gendisk` was initialized above.
+ // INVARIANT: `gendisk` was added to the VFS via `device_add_disk` above.
+ Ok(GenDisk {
+@@ -192,5 +200,10 @@ impl<T: Operations> Drop for GenDisk<T>
+ // initialized instance of `struct gendisk`, and it was previously added
+ // to the VFS.
+ unsafe { bindings::del_gendisk(self.gendisk) };
++
++ // SAFETY: By type invariant, `self.gendisk` was added to the VFS, so
++ // `put_disk()` must follow `del_gendisk()` to drop the final gendisk
++ // reference and trigger the remaining release path.
++ unsafe { bindings::put_disk(self.gendisk) };
+ }
+ }
--- /dev/null
+From stable+bounces-277061-greg=kroah.com@vger.kernel.org Fri Jul 17 16:01:41 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:00:24 -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: <20260717140025.1717514-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
+@@ -483,7 +483,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;
+@@ -540,10 +539,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;
+@@ -565,8 +561,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-277060-greg=kroah.com@vger.kernel.org Fri Jul 17 16:01:40 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:00:23 -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: <20260717140025.1717514-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
+@@ -1186,4 +1186,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 */
mtd-rawnand-lpc32xx_slc-fail-dma-transfer-on-completion-timeout.patch
mmc-block-fix-rpmb-device-unregister-ordering.patch
mmc-sdhci-of-dwcmshc-check-bus-clock-enable-result-in-the-probe-method.patch
+rust-block-fix-gendisk-cleanup-paths.patch
+acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch
+acpi-driver-check-acpi_companion-against-null-during-probe.patch
+acpi-bus-introduce-devm_acpi_install_notify_handler.patch
+acpi-nfit-core-use-devm_acpi_install_notify_handler.patch
+acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch
+iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch
+iio-adc-ad7380-select-regmap.patch
+iio-pressure-remove-redundant-pm_runtime_mark_last_busy-calls.patch
+iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch
+alsa-aoa-check-snd_ctl_new1-return-value.patch
+alsa-hda-cs35l41-fix-firmware-load-work-teardown.patch
+alsa-scarlett2-allow-selecting-config_set-by-firmware-version.patch
+alsa-scarlett2-update-offsets-for-2i2-gen-4-firmware-2417.patch
+vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch
+pci-altera-fix-resource-leaks-on-probe-failure.patch
+pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch
+pci-mediatek-switch-to-msi_create_parent_irq_domain.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
+pci-use-pbus_select_window-during-bar-resize.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-move-resizable-bar-code-to-rebar.c.patch
+pci-skip-resizable-bar-restore-on-read-error.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
+bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch
+gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch
+io_uring-rw-ensure-reissue-path-is-correctly-handled-for-iopoll.patch
+io_uring-rw-preserve-partial-result-for-iopoll.patch
+media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch
+media-nxp-imx8-isi-fix-use-after-free-on-remove.patch
+netfilter-ebtables-use-vmalloc_array-to-improve-code.patch
+netfilter-ebtables-zero-chainstack-array.patch
+bluetooth-l2cap-fix-not-tracking-outstanding-tx-ident.patch
+bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch
+bluetooth-hci_core-enable-buffer-flow-control-for-sco-esco.patch
+bluetooth-separate-cis_link-and-bis_link-link-types.patch
+bluetooth-hci_conn-fix-null-ptr-deref-in-hci_abort_conn.patch
+bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch
+bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch
+mm-swap_cgroup-fix-null-deref-in-lookup_swap_cgroup_id-on-swapless-host.patch
+smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch
+smb-client-resolve-swn-tcon-from-live-registrations.patch
+ksmbd_vfs_rename-vfs_path_parent_lookup-accepts-err_ptr-as-name.patch
+vfs-make-last_xxx-private-to-fs-namei.c.patch
+ksmbd-fix-path-resolution-in-ksmbd_vfs_kern_path_create.patch
+ksmbd-use-opener-credentials-for-fsctl-mutations.patch
+ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch
+ksmbd-track-the-connection-owning-a-byte-range-lock.patch
+proc-rename-proc_setattr-to-proc_nochmod_setattr.patch
+proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch
+perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.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
+hid-pidff-fix-missing-blank-lines-after-declarations.patch
+hid-pidff-add-missing-spaces.patch
+hid-pidff-rework-pidff_upload_effect.patch
+hid-pidff-use-correct-effect-type-in-effect-update.patch
+hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch
+hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch
+bpf-convert-lpm_trie.c-to-rqspinlock.patch
+bpf-arm64-powerpc-add-bpf_jit_bypass_spec_v1-v4.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-iowarrior-fix-use-after-free-on-disconnect-race.patch
+usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.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
+usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch
+crypto-atmel-sha204a-fail-on-hwrng-registration-error-in-probe-path.patch
+usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch
+btrfs-concentrate-the-error-handling-of-submit_one_sector.patch
+btrfs-replace-for_each_set_bit-with-for_each_set_bitmap.patch
+btrfs-remove-folio-parameter-from-ordered-io-related-functions.patch
+btrfs-remove-the-cow-fixup-mechanism.patch
+btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch
+crypto-ccp-move-dev_info-err-messages-for-sev-snp-init-and-shutdown.patch
+crypto-ccp-reset-tmr-size-at-snp-shutdown.patch
+crypto-ccp-register-snp-panic-notifier-only-if-snp-is-enabled.patch
+crypto-ccp-move-sev-snp-platform-initialization-to-kvm.patch
+crypto-ccp-fix-a-case-where-snp_shutdown-is-missed.patch
+crypto-ccp-do-not-initialize-snp-for-ioctl-snp_config.patch
+crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch
+exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch
+exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch
+exfat-add-exfat_get_dentry_set_by_ei-helper.patch
+exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch
+exfat-fix-incorrect-directory-checksum-after-rename-to-shorter-name.patch
+exfat-preserve-benign-secondary-entries-during-rename-and-move.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-275109-greg=kroah.com@vger.kernel.org Thu Jul 16 02:34:52 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 20:30:31 -0400
+Subject: smb: client: Improve unlocking of a mutex in cifs_get_swn_reg()
+To: stable@vger.kernel.org
+Cc: Markus Elfring <elfring@users.sourceforge.net>, Steve French <stfrench@microsoft.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716003032.615867-1-sashal@kernel.org>
+
+From: Markus Elfring <elfring@users.sourceforge.net>
+
+[ Upstream commit e2080b70c5851a132547bec3bd7dde847e649678 ]
+
+Use two additional labels so that another bit of common code can be better
+reused at the end of this function implementation.
+
+Signed-off-by: Markus Elfring <elfring@users.sourceforge.net>
+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 ++++++-------
+ 1 file changed, 6 insertions(+), 7 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);
+ }
--- /dev/null
+From stable+bounces-275110-greg=kroah.com@vger.kernel.org Thu Jul 16 02:30:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Jul 2026 20:30:32 -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: <20260716003032.615867-2-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
+@@ -52,6 +52,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") \
+@@ -63,6 +64,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-274643-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:47 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:17:58 -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: <20260715001800.3784291-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
+@@ -1013,7 +1013,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;
+@@ -1023,7 +1023,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
+@@ -1537,7 +1537,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-274645-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:57 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:18:00 -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: <20260715001800.3784291-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
+@@ -585,9 +585,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)
+@@ -620,9 +625,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);
+@@ -707,6 +717,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;
+@@ -717,6 +730,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-274644-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 20:17:59 -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: <20260715001800.3784291-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
+@@ -456,10 +456,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;
+
+@@ -519,7 +519,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;
+@@ -587,18 +587,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 */
+ }
+ }
+
+@@ -622,9 +622,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 */
+@@ -633,10 +634,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 */
+ }
+ }
+ }
+@@ -668,20 +669,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;
+@@ -759,12 +760,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;
+@@ -1010,20 +1011,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;
+@@ -1092,21 +1098,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-277091-greg=kroah.com@vger.kernel.org Fri Jul 17 17:03:21 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 17 Jul 2026 10:55:01 -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: <20260717145502.1925738-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 stable+bounces-277505-greg=kroah.com@vger.kernel.org Sun Jul 19 18:41:12 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 19 Jul 2026 12:41:05 -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: <20260719164105.663963-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);
+@@ -2597,8 +2601,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) {
+@@ -2629,6 +2648,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 12:55:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 06:55:26 -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: <20260720105526.221348-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>
+[ adjusted context around the removed INIT_WORK() lines since 6.12 lacks the spin_unlock_irqrestore(&ffs->eps_lock) calls from 2005aabe94eaab ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_fs.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/usb/gadget/function/f_fs.c
++++ b/drivers/usb/gadget/function/f_fs.c
+@@ -291,6 +291,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 ****************************************************/
+
+@@ -2224,6 +2225,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;
+@@ -3770,7 +3772,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;
+ }
+@@ -3797,7 +3798,6 @@ static void ffs_func_disable(struct usb_
+
+ if (ffs->state == FFS_DEACTIVATED) {
+ ffs->state = FFS_CLOSING;
+- INIT_WORK(&ffs->reset_work, ffs_reset_work);
+ schedule_work(&ffs->reset_work);
+ return;
+ }
--- /dev/null
+From sashal@kernel.org Mon Jul 20 16:10:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 20 Jul 2026 10:10:24 -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: <20260720141024.1497393-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
+@@ -1365,7 +1365,6 @@ ffs_epfile_release(struct inode *inode,
+
+ mutex_unlock(&epfile->dmabufs_mutex);
+
+- __ffs_epfile_read_buffer_free(epfile);
+ ffs_data_closed(epfile->ffs);
+
+ return 0;
+@@ -2386,6 +2385,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 sashal@kernel.org Sun Jul 19 17:01:51 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 19 Jul 2026 11:01:47 -0400
+Subject: USB: iowarrior: fix use-after-free on disconnect race
+To: stable@vger.kernel.org
+Cc: Johan Hovold <johan@kernel.org>, stable <stable@kernel.org>, Yue Sun <samsun1006219@gmail.com>, Greg Kroah-Hartman <gregkh@linuxfoundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260719150147.60929-2-sashal@kernel.org>
+
+From: Johan Hovold <johan@kernel.org>
+
+[ Upstream commit c602254ba4c10f60a73cd99d147874f86a3f485c ]
+
+mutex_unlock() may access the mutex structure after releasing the lock
+and therefore cannot be used to manage lifetime of objects directly
+(unlike spinlocks and refcounts). [1][2]
+
+Use a kref to release the driver data to avoid use-after-free in
+mutex_unlock() when release() races with disconnect().
+
+[1] a51749ab34d9 ("locking/mutex: Document that mutex_unlock() is non-atomic")
+[2] 2b9d9e0a9ba0 ("locking/mutex: Clarify that mutex_unlock(), and most
+ other sleeping locks, can still use the lock object
+ after it's unlocked")
+
+Fixes: 946b960d13c1 ("USB: add driver for iowarrior devices.")
+Cc: stable <stable@kernel.org>
+Reported-by: Yue Sun <samsun1006219@gmail.com>
+Link: https://lore.kernel.org/r/20260618080204.38322-1-samsun1006219@gmail.com
+Signed-off-by: Johan Hovold <johan@kernel.org>
+Link: https://patch.msgid.link/20260622152612.116422-2-johan@kernel.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/misc/iowarrior.c | 57 ++++++++++++++++++-------------------------
+ 1 file changed, 24 insertions(+), 33 deletions(-)
+
+--- a/drivers/usb/misc/iowarrior.c
++++ b/drivers/usb/misc/iowarrior.c
+@@ -71,6 +71,7 @@ static struct usb_driver iowarrior_drive
+
+ /* Structure to hold all of our device specific stuff */
+ struct iowarrior {
++ struct kref kref;
+ 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 */
+@@ -243,8 +244,10 @@ static void iowarrior_write_callback(str
+ /*
+ * iowarrior_delete
+ */
+-static inline void iowarrior_delete(struct iowarrior *dev)
++static inline void iowarrior_delete(struct kref *kref)
+ {
++ struct iowarrior *dev = container_of(kref, struct iowarrior, kref);
++
+ kfree(dev->int_in_buffer);
+ usb_free_urb(dev->int_in_urb);
+ kfree(dev->read_queue);
+@@ -640,6 +643,9 @@ static int iowarrior_open(struct inode *
+ }
+ /* increment our usage count for the driver */
+ ++dev->opened;
++
++ kref_get(&dev->kref);
++
+ /* save our object in the file's private structure */
+ file->private_data = dev;
+ retval = 0;
+@@ -655,7 +661,6 @@ out:
+ static int iowarrior_release(struct inode *inode, struct file *file)
+ {
+ struct iowarrior *dev;
+- int retval = 0;
+
+ dev = file->private_data;
+ if (!dev)
+@@ -663,29 +668,18 @@ static int iowarrior_release(struct inod
+
+ /* lock our device */
+ mutex_lock(&dev->mutex);
++ dev->opened = 0; /* we're closing now */
+
+- if (dev->opened <= 0) {
+- retval = -ENODEV; /* close called more than once */
+- mutex_unlock(&dev->mutex);
+- } else {
+- dev->opened = 0; /* we're closing now */
+- retval = 0;
+- if (dev->present) {
+- /*
+- The device is still connected so we only shutdown
+- pending read-/write-ops.
+- */
+- usb_kill_urb(dev->int_in_urb);
+- wake_up_interruptible(&dev->read_wait);
+- wake_up_interruptible(&dev->write_wait);
+- mutex_unlock(&dev->mutex);
+- } else {
+- /* The device was unplugged, cleanup resources */
+- mutex_unlock(&dev->mutex);
+- iowarrior_delete(dev);
+- }
++ if (dev->present) {
++ usb_kill_urb(dev->int_in_urb);
++ wake_up_interruptible(&dev->read_wait);
++ wake_up_interruptible(&dev->write_wait);
+ }
+- return retval;
++ mutex_unlock(&dev->mutex);
++
++ kref_put(&dev->kref, iowarrior_delete);
++
++ return 0;
+ }
+
+ static __poll_t iowarrior_poll(struct file *file, poll_table * wait)
+@@ -770,6 +764,7 @@ static int iowarrior_probe(struct usb_in
+ if (!dev)
+ return retval;
+
++ kref_init(&dev->kref);
+ mutex_init(&dev->mutex);
+
+ atomic_set(&dev->intr_idx, 0);
+@@ -888,7 +883,8 @@ static int iowarrior_probe(struct usb_in
+ return retval;
+
+ error:
+- iowarrior_delete(dev);
++ kref_put(&dev->kref, iowarrior_delete);
++
+ return retval;
+ }
+
+@@ -912,19 +908,14 @@ static void iowarrior_disconnect(struct
+ usb_kill_anchored_urbs(&dev->submitted);
+
+ if (dev->opened) {
+- /* There is a process that holds a filedescriptor to the device ,
+- so we only shutdown read-/write-ops going on.
+- Deleting the device is postponed until close() was called.
+- */
+ usb_kill_urb(dev->int_in_urb);
+ wake_up_interruptible(&dev->read_wait);
+ wake_up_interruptible(&dev->write_wait);
+- mutex_unlock(&dev->mutex);
+- } else {
+- /* no process is using the device, cleanup now */
+- mutex_unlock(&dev->mutex);
+- iowarrior_delete(dev);
+ }
++
++ mutex_unlock(&dev->mutex);
++
++ kref_put(&dev->kref, iowarrior_delete);
+ }
+
+ /* usb specific object needed to register this driver with the usb subsystem */
--- /dev/null
+From sashal@kernel.org Sun Jul 19 17:01:50 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 19 Jul 2026 11:01:46 -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: <20260719150147.60929-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;
+ switch (cmd) {
+ case IOW_WRITE:
+@@ -671,8 +661,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);
+
+@@ -775,6 +763,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);
+@@ -890,12 +879,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 stable+bounces-274247-greg=kroah.com@vger.kernel.org Tue Jul 14 15:36:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 14 Jul 2026 09:34:43 -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: <20260714133443.2672245-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 | 15 +++++++++------
+ 1 file changed, 9 insertions(+), 6 deletions(-)
+
+--- a/drivers/vfio/pci/mlx5/cmd.h
++++ b/drivers/vfio/pci/mlx5/cmd.h
+@@ -157,25 +157,28 @@ struct mlx5_vhca_qp {
+ struct mlx5_vhca_page_tracker {
+ u32 id;
+ u32 pdn;
+- u8 is_err:1;
+- u8 object_changed:1;
++ /* Flags modified at runtime - dedicated storage unit */
++ u8 is_err;
++ u8 object_changed;
++ 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;
+ u8 chunk_mode: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-275243-greg=kroah.com@vger.kernel.org Thu Jul 16 13:20:57 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 16 Jul 2026 07:20:42 -0400
+Subject: vfs: make LAST_XXX private to fs/namei.c
+To: stable@vger.kernel.org
+Cc: Jori Koolstra <jkoolstra@xs4all.nl>, Amir Goldstein <amir73il@gmail.com>, NeilBrown <neil@brown.name>, Namjae Jeon <linkinjeon@kernel.org>, "Christian Brauner (Amutable)" <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260716112043.1732826-2-sashal@kernel.org>
+
+From: Jori Koolstra <jkoolstra@xs4all.nl>
+
+[ Upstream commit f2f1dddccae50f7a1d088285c53c376e26cedf67 ]
+
+The only user of LAST_XXX outside of fs/namei.c is fs/smb/server/vfs.c;
+ksmbd_vfs_path_lookup() calls vfs_path_parent_lookup() and expects a
+LAST_NORM last type (or it will be ENOENT). ksmbd_vfs_rename() also calls
+vfs_path_parent_lookup() but forgets the LAST_NORM check.
+
+It does not really make sense to have vfs_path_parent_lookup() expose
+the last_type because it is only needed to ensure it is LAST_NORM. So
+let's do this check in vfs_path_parent_lookup() instead and keep the
+LAST_XXX internal to fs/namei.c. This changes the ENOENT errno in
+ksmbd_vfs_path_lookup() to EINVAL, which matches better with how this is
+handled by callers of filename_parentat().
+
+Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
+Link: https://patch.msgid.link/20260528175854.57626-1-jkoolstra@xs4all.nl
+Reviewed-by: Amir Goldstein <amir73il@gmail.com>
+Reviewed-by: NeilBrown <neil@brown.name>
+Reviewed-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+Stable-dep-of: 1c8951963d8e ("ksmbd: fix path resolution in ksmbd_vfs_kern_path_create")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/namei.c | 20 ++++++++++++++++----
+ fs/smb/server/vfs.c | 15 +++------------
+ include/linux/namei.h | 7 +------
+ 3 files changed, 20 insertions(+), 22 deletions(-)
+
+--- a/fs/namei.c
++++ b/fs/namei.c
+@@ -125,6 +125,11 @@
+
+ #define EMBEDDED_NAME_MAX (PATH_MAX - offsetof(struct filename, iname))
+
++/*
++ * Type of the last component on LOOKUP_PARENT
++ */
++enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT};
++
+ struct filename *
+ getname_flags(const char __user *filename, int flags)
+ {
+@@ -2736,15 +2741,22 @@ EXPORT_SYMBOL(kern_path);
+ * @flags: lookup flags
+ * @parent: pointer to struct path to fill
+ * @last: last component
+- * @type: type of the last component
+ * @root: pointer to struct path of the base directory
+ */
+ int vfs_path_parent_lookup(struct filename *filename, unsigned int flags,
+- struct path *parent, struct qstr *last, int *type,
++ struct path *parent, struct qstr *last,
+ const struct path *root)
+ {
+- return __filename_parentat(AT_FDCWD, filename, flags, parent, last,
+- type, root);
++ int type;
++ int err = __filename_parentat(AT_FDCWD, filename, flags, parent, last,
++ &type, root);
++ if (err)
++ return err;
++ if (unlikely(type != LAST_NORM)) {
++ path_put(parent);
++ return -EINVAL;
++ }
++ return 0;
+ }
+ EXPORT_SYMBOL(vfs_path_parent_lookup);
+
+--- a/fs/smb/server/vfs.c
++++ b/fs/smb/server/vfs.c
+@@ -18,7 +18,6 @@
+ #include <linux/vmalloc.h>
+ #include <linux/sched/xacct.h>
+ #include <linux/crc32c.h>
+-#include <linux/namei.h>
+
+ #include "glob.h"
+ #include "oplock.h"
+@@ -73,7 +72,7 @@ static int ksmbd_vfs_path_lookup_locked(
+ struct qstr last;
+ struct filename *filename;
+ struct path *root_share_path = &share_conf->vfs_path;
+- int err, type;
++ int err;
+ struct dentry *d;
+
+ if (pathname[0] == '\0') {
+@@ -88,19 +87,13 @@ static int ksmbd_vfs_path_lookup_locked(
+ return PTR_ERR(filename);
+
+ err = vfs_path_parent_lookup(filename, flags,
+- parent_path, &last, &type,
++ parent_path, &last,
+ root_share_path);
+ if (err) {
+ putname(filename);
+ return err;
+ }
+
+- if (unlikely(type != LAST_NORM)) {
+- path_put(parent_path);
+- putname(filename);
+- return -ENOENT;
+- }
+-
+ err = mnt_want_write(parent_path->mnt);
+ if (err) {
+ path_put(parent_path);
+@@ -708,7 +701,6 @@ int ksmbd_vfs_rename(struct ksmbd_work *
+ struct filename *to;
+ struct ksmbd_share_config *share_conf = work->tcon->share_conf;
+ struct ksmbd_file *parent_fp;
+- int new_type;
+ int err, lookup_flags = LOOKUP_NO_SYMLINKS;
+
+ if (ksmbd_override_fsids(work))
+@@ -718,8 +710,7 @@ int ksmbd_vfs_rename(struct ksmbd_work *
+
+ retry:
+ err = vfs_path_parent_lookup(to, lookup_flags | LOOKUP_BENEATH,
+- &new_path, &new_last, &new_type,
+- &share_conf->vfs_path);
++ &new_path, &new_last, &share_conf->vfs_path);
+ if (err)
+ goto out1;
+
+--- a/include/linux/namei.h
++++ b/include/linux/namei.h
+@@ -12,11 +12,6 @@ enum { MAX_NESTED_LINKS = 8 };
+
+ #define MAXSYMLINKS 40
+
+-/*
+- * Type of the last component on LOOKUP_PARENT
+- */
+-enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT};
+-
+ /* pathwalk mode */
+ #define LOOKUP_FOLLOW 0x0001 /* follow links at the end */
+ #define LOOKUP_DIRECTORY 0x0002 /* require a directory */
+@@ -63,7 +58,7 @@ extern void done_path_create(struct path
+ extern struct dentry *kern_path_locked(const char *, struct path *);
+ extern struct dentry *user_path_locked_at(int , const char __user *, struct path *);
+ int vfs_path_parent_lookup(struct filename *filename, unsigned int flags,
+- struct path *parent, struct qstr *last, int *type,
++ struct path *parent, struct qstr *last,
+ const struct path *root);
+ int vfs_path_lookup(struct dentry *, struct vfsmount *, const char *,
+ unsigned int, struct path *);