From: Greg Kroah-Hartman Date: Tue, 21 Jul 2026 13:40:20 +0000 (+0200) Subject: 6.1-stable patches X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=b5853f8c0710ccaf7e63c4b7ac990189eccf44cd;p=thirdparty%2Fkernel%2Fstable-queue.git 6.1-stable patches added patches: acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch alsa-aoa-check-snd_ctl_new1-return-value.patch audit-add-audit_log_nf_skb-helper-function.patch audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch bitops-make-bytes_to_bits-treewide-available.patch bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch bluetooth-hci_core-enable-buffer-flow-control-for-sco-esco.patch bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch bluetooth-l2cap-fix-deadlock-in-l2cap_conn_del.patch bluetooth-l2cap-fix-not-tracking-outstanding-tx-ident.patch bluetooth-l2cap-fix-slab-use-after-free-in-l2cap_connect.patch bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch bluetooth-make-handle-of-hci_conn-be-unique.patch bluetooth-remove-bt_hs.patch bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch cifs-add-tracing-for-the-cifs_tcon-struct-refcounting.patch cifs-after-disabling-multichannel-mark-tcon-for-reconnect.patch cifs-fix-reacquisition-of-volume-cookie-on-still-live-connection.patch cifs-handle-when-server-stops-supporting-multichannel.patch coresight-etb10-restore-atomic_t-for-shared-reading-state.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 crypto-atmel-sha204a-mark-of-related-data-as-maybe-unused.patch crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch crypto-qat-validate-rsa-crt-component-lengths.patch gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch hid-add-haptics-page-defines.patch hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch iio-imu-inv_icm42600-make-timestamp-module-chip-independent.patch iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch iio-imu-inv_mpu6050-use-the-common-inv_sensors-timestamp-module.patch iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch iio-invensense-remove-redundant-initialization-of-variable-period.patch iio-make-invensense-timestamp-module-generic.patch iio-move-inv_icm42600-timestamp-module-in-common.patch ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch ksmbd-destroy-async_ida-in-ksmbd_conn_free.patch ksmbd-fix-mechtoken-leak-when-spnego-decode-fails-after-token-alloc.patch ksmbd-fix-use-after-free-in-__ksmbd_close_fd-via-durable-scavenger.patch ksmbd-track-the-connection-owning-a-byte-range-lock.patch ksmbd-use-opener-credentials-for-fsctl-mutations.patch mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch mm-shrinker-fix-null-pointer-dereference-in-debugfs.patch mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch mm-shrinkers-fix-debugfs-file-permissions.patch netfilter-ebtables-use-vmalloc_array-to-improve-code.patch netfilter-ebtables-zero-chainstack-array.patch nvmet-remove-superfluous-initialization.patch pci-add-kerneldoc-for-pci_resize_resource.patch pci-altera-fix-resource-leaks-on-probe-failure.patch pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch pci-fix-restoring-bars-on-bar-resize-rollback-path.patch pci-free-saved-list-without-holding-pci_bus_sem.patch pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch pci-mediatek-use-generic-macro-for-tpvperl-delay.patch pci-move-resizable-bar-code-to-rebar.c.patch pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch pci-skip-resizable-bar-restore-on-read-error.patch perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch proc-move-fdinfo-ptrace_mode_read-check-into-the-inode-.permission-operation.patch proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch proc-rename-proc_setattr-to-proc_nochmod_setattr.patch proc-use-generic-setattr-for-proc-pid-net.patch regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch seqlock-introduce-scoped_seqlock_read.patch serial-8250_mid-disable-dma-for-selected-platforms.patch serial-8250_mid-remove-8250_pci-usage.patch smb-client-resolve-swn-tcon-from-live-registrations.patch smb-move-smb_version_values-to-common-smbglob.h.patch smb-move-some-duplicate-definitions-to-common-cifsglob.h.patch staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch staging-rtl8723bs-fix-heap-buffer-overflow-in-rtw_cfg80211_set_wpa_ie.patch staging-rtl8723bs-fix-indentation-issues.patch staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch staging-rtl8723bs-fix-space-issues.patch staging-rtl8723bs-fix-spaces-around-binary-operators.patch treewide-switch-rename-to-timer_delete.patch usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch usb-iowarrior-remove-inherent-race-with-minor-number.patch usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch vfio-pci-latch-disable_idle_d3-per-device.patch vfio-pci-use-a-private-flag-to-prevent-power-state-change-with-vfs.patch vfio-pci-use-bitfield-for-struct-vfio_pci_core_device-flags.patch writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch --- diff --git a/queue-6.1/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch b/queue-6.1/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch new file mode 100644 index 0000000000..a513e9349d --- /dev/null +++ b/queue-6.1/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch @@ -0,0 +1,103 @@ +From stable+bounces-273248-greg=kroah.com@vger.kernel.org Fri Jul 10 16:22:38 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 10:17:40 -0400 +Subject: ACPI: NFIT: core: Fix acpi_nfit_init() error cleanup +To: stable@vger.kernel.org +Cc: "Rafael J. Wysocki" , Dave Jiang , Sasha Levin +Message-ID: <20260710141740.132102-1-sashal@kernel.org> + +From: "Rafael J. Wysocki" + +[ 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 +Cc: All applicable +Reviewed-by: Dave Jiang +Link: https://patch.msgid.link/1963615.tdWV9SEqCh@rafael.j.wysocki +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/acpi/nfit/core.c | 15 +++++++++++---- + 1 file changed, 11 insertions(+), 4 deletions(-) + +--- a/drivers/acpi/nfit/core.c ++++ b/drivers/acpi/nfit/core.c +@@ -3067,6 +3067,8 @@ static void acpi_nfit_unregister(void *d + struct acpi_nfit_desc *acpi_desc = data; + + nvdimm_bus_unregister(acpi_desc->nvdimm_bus); ++ /* The nvdimm_bus object may have been freed, so clear the pointer. */ ++ acpi_desc->nvdimm_bus = NULL; + } + + int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, void *data, acpi_size sz) +@@ -3290,7 +3292,10 @@ static void acpi_nfit_put_table(void *ta + void acpi_nfit_shutdown(void *data) + { + struct acpi_nfit_desc *acpi_desc = data; +- struct device *bus_dev = to_nvdimm_bus_dev(acpi_desc->nvdimm_bus); ++ struct device *bus_dev; ++ ++ if (!acpi_desc || !acpi_desc->nvdimm_bus) ++ return; + + /* + * Destruct under acpi_desc_lock so that nfit_handle_mce does not +@@ -3305,6 +3310,7 @@ void acpi_nfit_shutdown(void *data) + mutex_unlock(&acpi_desc->init_mutex); + cancel_delayed_work_sync(&acpi_desc->dwork); + ++ bus_dev = to_nvdimm_bus_dev(acpi_desc->nvdimm_bus); + /* + * Bounce the nvdimm bus lock to make sure any in-flight + * acpi_nfit_ars_rescan() submissions have had a chance to +@@ -3372,13 +3378,14 @@ static int acpi_nfit_add(struct acpi_dev + sz - sizeof(struct acpi_table_nfit)); + + if (rc) +- return rc; +- return devm_add_action_or_reset(dev, acpi_nfit_shutdown, acpi_desc); ++ acpi_nfit_shutdown(acpi_desc); ++ ++ return rc; + } + + static int acpi_nfit_remove(struct acpi_device *adev) + { +- /* see acpi_nfit_unregister */ ++ acpi_nfit_shutdown(dev_get_drvdata(&adev->dev)); + return 0; + } + diff --git a/queue-6.1/alsa-aoa-check-snd_ctl_new1-return-value.patch b/queue-6.1/alsa-aoa-check-snd_ctl_new1-return-value.patch new file mode 100644 index 0000000000..b3541b5f4d --- /dev/null +++ b/queue-6.1/alsa-aoa-check-snd_ctl_new1-return-value.patch @@ -0,0 +1,61 @@ +From stable+bounces-274054-greg=kroah.com@vger.kernel.org Tue Jul 14 03:16:24 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 21:16:17 -0400 +Subject: ALSA: aoa: check snd_ctl_new1() return value +To: stable@vger.kernel.org +Cc: Zhao Dongdong , Takashi Iwai , Sasha Levin +Message-ID: <20260714011617.2188420-1-sashal@kernel.org> + +From: Zhao Dongdong + +[ 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 +Link: https://patch.msgid.link/tencent_35F3A25FEEBF190A2E15ED787754C57E3708@qq.com +Signed-off-by: Takashi Iwai +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/aoa/fabrics/layout.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +--- a/sound/aoa/fabrics/layout.c ++++ b/sound/aoa/fabrics/layout.c +@@ -947,6 +947,8 @@ static void layout_attached_codec(struct + if (lineout == 1) + ldev->gpio.methods->set_lineout(codec->gpio, 1); + ctl = snd_ctl_new1(&lineout_ctl, codec->gpio); ++ if (!ctl) ++ return; + if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) + strscpy(ctl->id.name, + "Headphone Switch", sizeof(ctl->id.name)); +@@ -961,6 +963,8 @@ static void layout_attached_codec(struct + if (ldev->have_lineout_detect) { + ctl = snd_ctl_new1(&lineout_detect_choice, + ldev); ++ if (!ctl) ++ return; + if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) + strscpy(ctl->id.name, + "Headphone Detect Autoswitch", +@@ -968,6 +972,8 @@ static void layout_attached_codec(struct + aoa_snd_ctl_add(ctl); + ctl = snd_ctl_new1(&lineout_detected, + ldev); ++ if (!ctl) ++ return; + if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) + strscpy(ctl->id.name, + "Headphone Detected", diff --git a/queue-6.1/audit-add-audit_log_nf_skb-helper-function.patch b/queue-6.1/audit-add-audit_log_nf_skb-helper-function.patch new file mode 100644 index 0000000000..6f7a22276c --- /dev/null +++ b/queue-6.1/audit-add-audit_log_nf_skb-helper-function.patch @@ -0,0 +1,296 @@ +From stable+bounces-278315-greg=kroah.com@vger.kernel.org Tue Jul 21 03:28:00 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:27:50 -0400 +Subject: audit: add audit_log_nf_skb helper function +To: stable@vger.kernel.org +Cc: Ricardo Robaina , Florian Westphal , Paul Moore , Sasha Levin +Message-ID: <20260721012751.3458464-1-sashal@kernel.org> + +From: Ricardo Robaina + +[ Upstream commit f19590b07cb620be1fcd5474c49515e21a05d406 ] + +Netfilter code (net/netfilter/nft_log.c and net/netfilter/xt_AUDIT.c) +have to be kept in sync. Both source files had duplicated versions of +audit_ip4() and audit_ip6() functions, which can result in lack of +consistency and/or duplicated work. + +This patch adds a helper function in audit.c that can be called by +netfilter code commonly, aiming to improve maintainability and +consistency. + +Suggested-by: Florian Westphal +Suggested-by: Paul Moore +Signed-off-by: Ricardo Robaina +Acked-by: Florian Westphal +Signed-off-by: Paul Moore +Stable-dep-of: 65dfde57d1e2 ("audit: fix potential integer overflow in audit_log_n_hex()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/audit.h | 8 +++++ + kernel/audit.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++ + net/netfilter/nft_log.c | 58 ------------------------------------------ + net/netfilter/xt_AUDIT.c | 58 ------------------------------------------ + 4 files changed, 74 insertions(+), 114 deletions(-) + +--- a/include/linux/audit.h ++++ b/include/linux/audit.h +@@ -177,6 +177,8 @@ extern void audit_log_lost(const ch + + extern int audit_log_task_context(struct audit_buffer *ab); + extern void audit_log_task_info(struct audit_buffer *ab); ++extern int audit_log_nf_skb(struct audit_buffer *ab, ++ const struct sk_buff *skb, u8 nfproto); + + extern int audit_update_lsm_rules(void); + +@@ -242,6 +244,12 @@ static inline int audit_log_task_context + static inline void audit_log_task_info(struct audit_buffer *ab) + { } + ++static inline int audit_log_nf_skb(struct audit_buffer *ab, ++ const struct sk_buff *skb, u8 nfproto) ++{ ++ return 0; ++} ++ + static inline kuid_t audit_get_loginuid(struct task_struct *tsk) + { + return INVALID_UID; +--- a/kernel/audit.c ++++ b/kernel/audit.c +@@ -59,6 +59,8 @@ + #include + #include + #include ++#include ++#include + + #include "audit.h" + +@@ -2305,6 +2307,68 @@ void audit_log_path_denied(int type, con + audit_log_end(ab); + } + ++int audit_log_nf_skb(struct audit_buffer *ab, ++ const struct sk_buff *skb, u8 nfproto) ++{ ++ /* find the IP protocol in the case of NFPROTO_BRIDGE */ ++ if (nfproto == NFPROTO_BRIDGE) { ++ switch (eth_hdr(skb)->h_proto) { ++ case htons(ETH_P_IP): ++ nfproto = NFPROTO_IPV4; ++ break; ++ case htons(ETH_P_IPV6): ++ nfproto = NFPROTO_IPV6; ++ break; ++ default: ++ goto unknown_proto; ++ } ++ } ++ ++ switch (nfproto) { ++ case NFPROTO_IPV4: { ++ struct iphdr iph; ++ const struct iphdr *ih; ++ ++ ih = skb_header_pointer(skb, skb_network_offset(skb), ++ sizeof(iph), &iph); ++ if (!ih) ++ return -ENOMEM; ++ ++ audit_log_format(ab, " saddr=%pI4 daddr=%pI4 proto=%hhu", ++ &ih->saddr, &ih->daddr, ih->protocol); ++ break; ++ } ++ case NFPROTO_IPV6: { ++ struct ipv6hdr iph; ++ const struct ipv6hdr *ih; ++ u8 nexthdr; ++ __be16 frag_off; ++ ++ ih = skb_header_pointer(skb, skb_network_offset(skb), ++ sizeof(iph), &iph); ++ if (!ih) ++ return -ENOMEM; ++ ++ nexthdr = ih->nexthdr; ++ ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(iph), ++ &nexthdr, &frag_off); ++ ++ audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu", ++ &ih->saddr, &ih->daddr, nexthdr); ++ break; ++ } ++ default: ++ goto unknown_proto; ++ } ++ ++ return 0; ++ ++unknown_proto: ++ audit_log_format(ab, " saddr=? daddr=? proto=?"); ++ return -EPFNOSUPPORT; ++} ++EXPORT_SYMBOL(audit_log_nf_skb); ++ + /* global counter which is incremented every time something logs in */ + static atomic_t session_id = ATOMIC_INIT(0); + +--- a/net/netfilter/nft_log.c ++++ b/net/netfilter/nft_log.c +@@ -26,46 +26,10 @@ struct nft_log { + char *prefix; + }; + +-static bool audit_ip4(struct audit_buffer *ab, struct sk_buff *skb) +-{ +- struct iphdr _iph; +- const struct iphdr *ih; +- +- ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_iph), &_iph); +- if (!ih) +- return false; +- +- audit_log_format(ab, " saddr=%pI4 daddr=%pI4 proto=%hhu", +- &ih->saddr, &ih->daddr, ih->protocol); +- +- return true; +-} +- +-static bool audit_ip6(struct audit_buffer *ab, struct sk_buff *skb) +-{ +- struct ipv6hdr _ip6h; +- const struct ipv6hdr *ih; +- u8 nexthdr; +- __be16 frag_off; +- +- ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_ip6h), &_ip6h); +- if (!ih) +- return false; +- +- nexthdr = ih->nexthdr; +- ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(_ip6h), &nexthdr, &frag_off); +- +- audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu", +- &ih->saddr, &ih->daddr, nexthdr); +- +- return true; +-} +- + static void nft_log_eval_audit(const struct nft_pktinfo *pkt) + { + struct sk_buff *skb = pkt->skb; + struct audit_buffer *ab; +- int fam = -1; + + if (!audit_enabled) + return; +@@ -76,27 +40,7 @@ static void nft_log_eval_audit(const str + + audit_log_format(ab, "mark=%#x", skb->mark); + +- switch (nft_pf(pkt)) { +- case NFPROTO_BRIDGE: +- switch (eth_hdr(skb)->h_proto) { +- case htons(ETH_P_IP): +- fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1; +- break; +- case htons(ETH_P_IPV6): +- fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1; +- break; +- } +- break; +- case NFPROTO_IPV4: +- fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1; +- break; +- case NFPROTO_IPV6: +- fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1; +- break; +- } +- +- if (fam == -1) +- audit_log_format(ab, " saddr=? daddr=? proto=-1"); ++ audit_log_nf_skb(ab, skb, nft_pf(pkt)); + + audit_log_end(ab); + } +--- a/net/netfilter/xt_AUDIT.c ++++ b/net/netfilter/xt_AUDIT.c +@@ -28,46 +28,10 @@ MODULE_ALIAS("ip6t_AUDIT"); + MODULE_ALIAS("ebt_AUDIT"); + MODULE_ALIAS("arpt_AUDIT"); + +-static bool audit_ip4(struct audit_buffer *ab, struct sk_buff *skb) +-{ +- struct iphdr _iph; +- const struct iphdr *ih; +- +- ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_iph), &_iph); +- if (!ih) +- return false; +- +- audit_log_format(ab, " saddr=%pI4 daddr=%pI4 proto=%hhu", +- &ih->saddr, &ih->daddr, ih->protocol); +- +- return true; +-} +- +-static bool audit_ip6(struct audit_buffer *ab, struct sk_buff *skb) +-{ +- struct ipv6hdr _ip6h; +- const struct ipv6hdr *ih; +- u8 nexthdr; +- __be16 frag_off; +- +- ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_ip6h), &_ip6h); +- if (!ih) +- return false; +- +- nexthdr = ih->nexthdr; +- ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(_ip6h), &nexthdr, &frag_off); +- +- audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu", +- &ih->saddr, &ih->daddr, nexthdr); +- +- return true; +-} +- + static unsigned int + audit_tg(struct sk_buff *skb, const struct xt_action_param *par) + { + struct audit_buffer *ab; +- int fam = -1; + + if (audit_enabled == AUDIT_OFF) + goto errout; +@@ -77,27 +41,7 @@ audit_tg(struct sk_buff *skb, const stru + + audit_log_format(ab, "mark=%#x", skb->mark); + +- switch (xt_family(par)) { +- case NFPROTO_BRIDGE: +- switch (eth_hdr(skb)->h_proto) { +- case htons(ETH_P_IP): +- fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1; +- break; +- case htons(ETH_P_IPV6): +- fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1; +- break; +- } +- break; +- case NFPROTO_IPV4: +- fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1; +- break; +- case NFPROTO_IPV6: +- fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1; +- break; +- } +- +- if (fam == -1) +- audit_log_format(ab, " saddr=? daddr=? proto=-1"); ++ audit_log_nf_skb(ab, skb, xt_family(par)); + + audit_log_end(ab); + diff --git a/queue-6.1/audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch b/queue-6.1/audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch new file mode 100644 index 0000000000..65390ec775 --- /dev/null +++ b/queue-6.1/audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch @@ -0,0 +1,66 @@ +From stable+bounces-278316-greg=kroah.com@vger.kernel.org Tue Jul 21 03:28:03 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:27:51 -0400 +Subject: audit: fix potential integer overflow in audit_log_n_hex() +To: stable@vger.kernel.org +Cc: Ricardo Robaina , Richard Guy Briggs , Paul Moore , Sasha Levin +Message-ID: <20260721012751.3458464-2-sashal@kernel.org> + +From: Ricardo Robaina + +[ Upstream commit 65dfde57d1e29ce2b76fc23dd565eccd5c0bc0f0 ] + +The function calculates new_len as len << 1 for hex encoding. This +has two overflow risks: the shift itself can overflow when len is +large, and the result can be truncated when assigned to new_len +(declared as int) from the size_t calculation. + +Fix by using check_shl_overflow() to catch shift overflow and +changing new_len and loop counter i to size_t to prevent truncation. + +Cc: stable@vger.kernel.org +Fixes: 168b7173959f ("AUDIT: Clean up logging of untrusted strings") +Reviewed-by: Richard Guy Briggs +Signed-off-by: Ricardo Robaina +[PM: remove vertical whitspace noise] +Signed-off-by: Paul Moore +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/audit.c | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +--- a/kernel/audit.c ++++ b/kernel/audit.c +@@ -61,6 +61,7 @@ + #include + #include + #include ++#include + + #include "audit.h" + +@@ -2035,7 +2036,8 @@ void audit_log_format(struct audit_buffe + void audit_log_n_hex(struct audit_buffer *ab, const unsigned char *buf, + size_t len) + { +- int i, avail, new_len; ++ int avail; ++ size_t i, new_len; + unsigned char *ptr; + struct sk_buff *skb; + +@@ -2045,7 +2047,12 @@ void audit_log_n_hex(struct audit_buffer + BUG_ON(!ab->skb); + skb = ab->skb; + avail = skb_tailroom(skb); +- new_len = len<<1; ++ ++ if (check_shl_overflow(len, 1, &new_len)) { ++ audit_log_format(ab, "?"); ++ return; ++ } ++ + if (new_len >= avail) { + /* Round the buffer request up to the next multiple */ + new_len = AUDIT_BUFSIZ*(((new_len-avail)/AUDIT_BUFSIZ) + 1); diff --git a/queue-6.1/bitops-make-bytes_to_bits-treewide-available.patch b/queue-6.1/bitops-make-bytes_to_bits-treewide-available.patch new file mode 100644 index 0000000000..5f96ddad7b --- /dev/null +++ b/queue-6.1/bitops-make-bytes_to_bits-treewide-available.patch @@ -0,0 +1,90 @@ +From stable+bounces-274061-greg=kroah.com@vger.kernel.org Tue Jul 14 03:16:44 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 21:16:30 -0400 +Subject: bitops: make BYTES_TO_BITS() treewide-available +To: stable@vger.kernel.org +Cc: Alexander Lobakin , Andy Shevchenko , Przemek Kitszel , Yury Norov , "David S. Miller" , Sasha Levin +Message-ID: <20260714011631.2188967-1-sashal@kernel.org> + +From: Alexander Lobakin + +[ Upstream commit 7d8296b250f2eed73f1758607926d4d258dea5d4 ] + +Avoid open-coding that simple expression each time by moving +BYTES_TO_BITS() from the probes code to to export +it to the rest of the kernel. +Simplify the macro while at it. `BITS_PER_LONG / sizeof(long)` always +equals to %BITS_PER_BYTE, regardless of the target architecture. +Do the same for the tools ecosystem as well (incl. its version of +bitops.h). The previous implementation had its implicit type of long, +while the new one is int, so adjust the format literal accordingly in +the perf code. + +Suggested-by: Andy Shevchenko +Reviewed-by: Przemek Kitszel +Acked-by: Yury Norov +Signed-off-by: Alexander Lobakin +Signed-off-by: David S. Miller +Stable-dep-of: 55052184ac90 ("iio: common: st_sensors: honour channel endianness in read_axis_data") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/bitops.h | 2 ++ + kernel/trace/trace_probe.c | 2 -- + tools/include/linux/bitops.h | 2 ++ + tools/perf/util/probe-finder.c | 4 +--- + 4 files changed, 5 insertions(+), 5 deletions(-) + +--- a/include/linux/bitops.h ++++ b/include/linux/bitops.h +@@ -21,6 +21,8 @@ + #define BITS_TO_U32(nr) __KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(u32)) + #define BITS_TO_BYTES(nr) __KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(char)) + ++#define BYTES_TO_BITS(nb) ((nb) * BITS_PER_BYTE) ++ + extern unsigned int __sw_hweight8(unsigned int w); + extern unsigned int __sw_hweight16(unsigned int w); + extern unsigned int __sw_hweight32(unsigned int w); +--- a/kernel/trace/trace_probe.c ++++ b/kernel/trace/trace_probe.c +@@ -535,8 +535,6 @@ parse_probe_arg(char *arg, const struct + return ret; + } + +-#define BYTES_TO_BITS(nb) ((BITS_PER_LONG * (nb)) / sizeof(long)) +- + /* Bitfield type needs to be parsed into a fetch function */ + static int __parse_bitfield_probe_arg(const char *bf, + const struct fetch_type *t, +--- a/tools/include/linux/bitops.h ++++ b/tools/include/linux/bitops.h +@@ -20,6 +20,8 @@ + #define BITS_TO_U32(nr) DIV_ROUND_UP(nr, BITS_PER_TYPE(u32)) + #define BITS_TO_BYTES(nr) DIV_ROUND_UP(nr, BITS_PER_TYPE(char)) + ++#define BYTES_TO_BITS(nb) ((nb) * BITS_PER_BYTE) ++ + extern unsigned int __sw_hweight8(unsigned int w); + extern unsigned int __sw_hweight16(unsigned int w); + extern unsigned int __sw_hweight32(unsigned int w); +--- a/tools/perf/util/probe-finder.c ++++ b/tools/perf/util/probe-finder.c +@@ -304,8 +304,6 @@ static_var: + return ret2; + } + +-#define BYTES_TO_BITS(nb) ((nb) * BITS_PER_LONG / sizeof(long)) +- + static int convert_variable_type(Dwarf_Die *vr_die, + struct probe_trace_arg *tvar, + const char *cast, bool user_access) +@@ -335,7 +333,7 @@ static int convert_variable_type(Dwarf_D + total = dwarf_bytesize(vr_die); + if (boffs < 0 || total < 0) + return -ENOENT; +- ret = snprintf(buf, 16, "b%d@%d/%zd", bsize, boffs, ++ ret = snprintf(buf, 16, "b%d@%d/%d", bsize, boffs, + BYTES_TO_BITS(total)); + goto formatted; + } diff --git a/queue-6.1/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch b/queue-6.1/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch new file mode 100644 index 0000000000..fa5cea9c13 --- /dev/null +++ b/queue-6.1/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch @@ -0,0 +1,101 @@ +From stable+bounces-275091-greg=kroah.com@vger.kernel.org Thu Jul 16 01:56:04 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 19:55:18 -0400 +Subject: Bluetooth: 6lowpan: fix cyclic locking warning on netdev unregister +To: stable@vger.kernel.org +Cc: Pauli Virtanen , Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715235519.525147-4-sashal@kernel.org> + +From: Pauli Virtanen + +[ 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 +Signed-off-by: Luiz Augusto von Dentz +Stable-dep-of: 6fef032af009 ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/bluetooth/6lowpan.c | 25 +++++++++++++++++++++++-- + 1 file changed, 23 insertions(+), 2 deletions(-) + +--- a/net/bluetooth/6lowpan.c ++++ b/net/bluetooth/6lowpan.c +@@ -759,13 +759,33 @@ static inline struct l2cap_chan *chan_ne + return chan; + } + ++static void unregister_dev(struct lowpan_btle_dev *dev) ++{ ++ struct hci_dev *hdev = READ_ONCE(dev->hdev); ++ ++ /* If netdev holds last reference to hci_dev (its parent device), this ++ * leads to theoretical cyclic locking on lowpan_unregister_netdev: ++ * ++ * rtnl_lock -> put_device(parent) -> hci_release_dev -> ++ * destroy_workqueue -> hci_rx_work -> l2cap_recv_acldata -> ++ * chan_ready_cb -> ifup -> rtnl_lock ++ * ++ * However, hci_rx_work is disabled in hci_unregister_dev, so this ++ * should not occur. Make lockdep happy by postponing hdev release after ++ * netdev put. ++ */ ++ hci_dev_hold(hdev); ++ lowpan_unregister_netdev(dev->netdev); ++ hci_dev_put(hdev); ++} ++ + static void delete_netdev(struct work_struct *work) + { + struct lowpan_btle_dev *entry = container_of(work, + struct lowpan_btle_dev, + delete_netdev); + +- lowpan_unregister_netdev(entry->netdev); ++ unregister_dev(entry); + + /* The entry pointer is deleted by the netdev destructor. */ + } +@@ -1245,6 +1265,7 @@ static void disconnect_devices(void) + break; + + new_dev->netdev = entry->netdev; ++ new_dev->hdev = entry->hdev; + INIT_LIST_HEAD(&new_dev->list); + + list_add_rcu(&new_dev->list, &devices); +@@ -1256,7 +1277,7 @@ static void disconnect_devices(void) + ifdown(entry->netdev); + BT_DBG("Unregistering netdev %s %p", + entry->netdev->name, entry->netdev); +- lowpan_unregister_netdev(entry->netdev); ++ unregister_dev(entry); + kfree(entry); + } + } diff --git a/queue-6.1/bluetooth-hci_core-enable-buffer-flow-control-for-sco-esco.patch b/queue-6.1/bluetooth-hci_core-enable-buffer-flow-control-for-sco-esco.patch new file mode 100644 index 0000000000..61cb4cfcb0 --- /dev/null +++ b/queue-6.1/bluetooth-hci_core-enable-buffer-flow-control-for-sco-esco.patch @@ -0,0 +1,271 @@ +From stable+bounces-275112-greg=kroah.com@vger.kernel.org Thu Jul 16 03:16:04 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 21:15:55 -0400 +Subject: Bluetooth: hci_core: Enable buffer flow control for SCO/eSCO +To: stable@vger.kernel.org +Cc: Luiz Augusto von Dentz , Pauli Virtanen , Sasha Levin +Message-ID: <20260716011555.683225-1-sashal@kernel.org> + +From: Luiz Augusto von Dentz + +[ 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 +Tested-by: Pauli Virtanen +Stable-dep-of: 12917f591cea ("Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/bluetooth/hci.h | 13 ++++++++ + include/net/bluetooth/hci_core.h | 1 + net/bluetooth/hci_core.c | 62 +++++++++++++++++---------------------- + net/bluetooth/hci_event.c | 2 + + net/bluetooth/hci_sync.c | 24 +++++++++++++++ + 5 files changed, 68 insertions(+), 34 deletions(-) + +--- a/include/net/bluetooth/hci.h ++++ b/include/net/bluetooth/hci.h +@@ -228,6 +228,13 @@ enum { + */ + HCI_QUIRK_WIDEBAND_SPEECH_SUPPORTED, + ++ /* When this quirk is set consider Sync Flow Control as supported by ++ * the driver. ++ * ++ * This quirk must be set before hci_register_dev is called. ++ */ ++ HCI_QUIRK_SYNC_FLOWCTL_SUPPORTED, ++ + /* When this quirk is set, the controller has validated that + * LE states reported through the HCI_LE_READ_SUPPORTED_STATES are + * valid. This mechanism is necessary as many controllers have +@@ -396,6 +403,7 @@ enum { + HCI_WIDEBAND_SPEECH_ENABLED, + HCI_EVENT_FILTER_CONFIGURED, + HCI_PA_SYNC, ++ HCI_SCO_FLOWCTL, + + HCI_DUT_MODE, + HCI_VENDOR_DIAG, +@@ -1538,6 +1546,11 @@ struct hci_rp_read_tx_power { + __s8 tx_power; + } __packed; + ++#define HCI_OP_WRITE_SYNC_FLOWCTL 0x0c2f ++struct hci_cp_write_sync_flowctl { ++ __u8 enable; ++} __packed; ++ + #define HCI_OP_READ_PAGE_SCAN_TYPE 0x0c46 + struct hci_rp_read_page_scan_type { + __u8 status; +--- a/include/net/bluetooth/hci_core.h ++++ b/include/net/bluetooth/hci_core.h +@@ -1659,6 +1659,7 @@ void hci_conn_del_sysfs(struct hci_conn + #define lmp_hold_capable(dev) ((dev)->features[0][0] & LMP_HOLD) + #define lmp_sniff_capable(dev) ((dev)->features[0][0] & LMP_SNIFF) + #define lmp_park_capable(dev) ((dev)->features[0][1] & LMP_PARK) ++#define lmp_sco_capable(dev) ((dev)->features[0][1] & LMP_SCO) + #define lmp_inq_rssi_capable(dev) ((dev)->features[0][3] & LMP_RSSI_INQ) + #define lmp_esco_capable(dev) ((dev)->features[0][3] & LMP_ESCO) + #define lmp_bredr_capable(dev) (!((dev)->features[0][4] & LMP_NO_BREDR)) +--- a/net/bluetooth/hci_core.c ++++ b/net/bluetooth/hci_core.c +@@ -3586,42 +3586,27 @@ static void __check_timeout(struct hci_d + } + + /* Schedule SCO */ +-static void hci_sched_sco(struct hci_dev *hdev) ++static void hci_sched_sco(struct hci_dev *hdev, __u8 type) + { + struct hci_conn *conn; + struct sk_buff *skb; +- int quote; ++ int quote, *cnt; ++ unsigned int pkts = hdev->sco_pkts; + +- BT_DBG("%s", hdev->name); ++ bt_dev_dbg(hdev, "type %u", type); + +- if (!hci_conn_num(hdev, SCO_LINK)) ++ if (!hci_conn_num(hdev, type) || !pkts) + return; + +- while (hdev->sco_cnt && (conn = hci_low_sent(hdev, SCO_LINK, "e))) { +- while (quote-- && (skb = skb_dequeue(&conn->data_q))) { +- BT_DBG("skb %p len %d", skb, skb->len); +- hci_send_frame(hdev, skb); +- +- conn->sent++; +- if (conn->sent == ~0) +- conn->sent = 0; +- } +- } +-} +- +-static void hci_sched_esco(struct hci_dev *hdev) +-{ +- struct hci_conn *conn; +- struct sk_buff *skb; +- int quote; +- +- BT_DBG("%s", hdev->name); +- +- if (!hci_conn_num(hdev, ESCO_LINK)) +- return; ++ /* Use sco_pkts if flow control has not been enabled which will limit ++ * the amount of buffer sent in a row. ++ */ ++ if (!hci_dev_test_flag(hdev, HCI_SCO_FLOWCTL)) ++ cnt = &pkts; ++ else ++ cnt = &hdev->sco_cnt; + +- while (hdev->sco_cnt && (conn = hci_low_sent(hdev, ESCO_LINK, +- "e))) { ++ while (*cnt && (conn = hci_low_sent(hdev, type, "e))) { + while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + BT_DBG("skb %p len %d", skb, skb->len); + hci_send_frame(hdev, skb); +@@ -3629,8 +3614,17 @@ static void hci_sched_esco(struct hci_de + conn->sent++; + if (conn->sent == ~0) + conn->sent = 0; ++ (*cnt)--; + } + } ++ ++ /* Rescheduled if all packets were sent and flow control is not enabled ++ * as there could be more packets queued that could not be sent and ++ * since no HCI_EV_NUM_COMP_PKTS event will be generated the reschedule ++ * needs to be forced. ++ */ ++ if (!pkts && !hci_dev_test_flag(hdev, HCI_SCO_FLOWCTL)) ++ queue_work(hdev->workqueue, &hdev->tx_work); + } + + static void hci_sched_acl_pkt(struct hci_dev *hdev) +@@ -3666,8 +3660,8 @@ static void hci_sched_acl_pkt(struct hci + chan->conn->sent++; + + /* Send pending SCO packets right away */ +- hci_sched_sco(hdev); +- hci_sched_esco(hdev); ++ hci_sched_sco(hdev, SCO_LINK); ++ hci_sched_sco(hdev, ESCO_LINK); + } + } + +@@ -3788,8 +3782,8 @@ static void hci_sched_le(struct hci_dev + chan->conn->sent++; + + /* Send pending SCO packets right away */ +- hci_sched_sco(hdev); +- hci_sched_esco(hdev); ++ hci_sched_sco(hdev, SCO_LINK); ++ hci_sched_sco(hdev, ESCO_LINK); + } + } + +@@ -3834,8 +3828,8 @@ static void hci_tx_work(struct work_stru + + if (!hci_dev_test_flag(hdev, HCI_USER_CHANNEL)) { + /* Schedule queues and send stuff to HCI driver */ +- hci_sched_sco(hdev); +- hci_sched_esco(hdev); ++ hci_sched_sco(hdev, SCO_LINK); ++ hci_sched_sco(hdev, ESCO_LINK); + hci_sched_iso(hdev); + hci_sched_acl(hdev); + hci_sched_le(hdev); +--- a/net/bluetooth/hci_event.c ++++ b/net/bluetooth/hci_event.c +@@ -4463,9 +4463,11 @@ static void hci_num_comp_pkts_evt(struct + break; + + case SCO_LINK: ++ case ESCO_LINK: + hdev->sco_cnt += count; + if (hdev->sco_cnt > hdev->sco_pkts) + hdev->sco_cnt = hdev->sco_pkts; ++ + break; + + case ISO_LINK: +--- a/net/bluetooth/hci_sync.c ++++ b/net/bluetooth/hci_sync.c +@@ -3854,6 +3854,28 @@ static int hci_write_ca_timeout_sync(str + sizeof(param), ¶m, HCI_CMD_TIMEOUT); + } + ++/* Enable SCO flow control if supported */ ++static int hci_write_sync_flowctl_sync(struct hci_dev *hdev) ++{ ++ struct hci_cp_write_sync_flowctl cp; ++ int err; ++ ++ /* Check if the controller supports SCO and HCI_OP_WRITE_SYNC_FLOWCTL */ ++ if (!lmp_sco_capable(hdev) || !(hdev->commands[10] & BIT(4)) || ++ !test_bit(HCI_QUIRK_SYNC_FLOWCTL_SUPPORTED, &hdev->quirks)) ++ return 0; ++ ++ memset(&cp, 0, sizeof(cp)); ++ cp.enable = 0x01; ++ ++ err = __hci_cmd_sync_status(hdev, HCI_OP_WRITE_SYNC_FLOWCTL, ++ sizeof(cp), &cp, HCI_CMD_TIMEOUT); ++ if (!err) ++ hci_dev_set_flag(hdev, HCI_SCO_FLOWCTL); ++ ++ return err; ++} ++ + /* BR Controller init stage 2 command sequence */ + static const struct hci_init_stage br_init2[] = { + /* HCI_OP_READ_BUFFER_SIZE */ +@@ -3872,6 +3894,8 @@ static const struct hci_init_stage br_in + HCI_INIT(hci_clear_event_filter_sync), + /* HCI_OP_WRITE_CA_TIMEOUT */ + HCI_INIT(hci_write_ca_timeout_sync), ++ /* HCI_OP_WRITE_SYNC_FLOWCTL */ ++ HCI_INIT(hci_write_sync_flowctl_sync), + {} + }; + diff --git a/queue-6.1/bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch b/queue-6.1/bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch new file mode 100644 index 0000000000..a4e39f887c --- /dev/null +++ b/queue-6.1/bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch @@ -0,0 +1,67 @@ +From stable+bounces-275096-greg=kroah.com@vger.kernel.org Thu Jul 16 01:56:15 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 19:55:25 -0400 +Subject: Bluetooth: L2CAP: cancel pending_rx_work before taking conn->lock +To: stable@vger.kernel.org +Cc: Runyu Xiao , Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715235525.525483-3-sashal@kernel.org> + +From: Runyu Xiao + +[ 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 +Signed-off-by: Luiz Augusto von Dentz +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/bluetooth/l2cap_core.c | 10 ++-------- + 1 file changed, 2 insertions(+), 8 deletions(-) + +--- a/net/bluetooth/l2cap_core.c ++++ b/net/bluetooth/l2cap_core.c +@@ -1785,17 +1785,11 @@ static void l2cap_conn_del(struct hci_co + cancel_delayed_work_sync(&conn->info_timer); + cancel_work_sync(&conn->id_addr_update_work); + ++ cancel_work_sync(&conn->pending_rx_work); ++ + kfree_skb(conn->rx_skb); + + skb_queue_purge(&conn->pending_rx); +- +- /* We can not call flush_work(&conn->pending_rx_work) here since we +- * might block if we are running on a worker from the same workqueue +- * pending_rx_work is waiting on. +- */ +- if (work_pending(&conn->pending_rx_work)) +- cancel_work_sync(&conn->pending_rx_work); +- + ida_destroy(&conn->tx_ida); + + l2cap_unregister_all_users(conn); diff --git a/queue-6.1/bluetooth-l2cap-fix-deadlock-in-l2cap_conn_del.patch b/queue-6.1/bluetooth-l2cap-fix-deadlock-in-l2cap_conn_del.patch new file mode 100644 index 0000000000..8047493fbb --- /dev/null +++ b/queue-6.1/bluetooth-l2cap-fix-deadlock-in-l2cap_conn_del.patch @@ -0,0 +1,65 @@ +From stable+bounces-275095-greg=kroah.com@vger.kernel.org Thu Jul 16 01:56:12 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 19:55:24 -0400 +Subject: Bluetooth: L2CAP: Fix deadlock in l2cap_conn_del() +To: stable@vger.kernel.org +Cc: Hyunwoo Kim , Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715235525.525483-2-sashal@kernel.org> + +From: Hyunwoo Kim + +[ Upstream commit 00fdebbbc557a2fc21321ff2eaa22fd70c078608 ] + +l2cap_conn_del() calls cancel_delayed_work_sync() for both info_timer +and id_addr_timer while holding conn->lock. However, the work functions +l2cap_info_timeout() and l2cap_conn_update_id_addr() both acquire +conn->lock, creating a potential AB-BA deadlock if the work is already +executing when l2cap_conn_del() takes the lock. + +Move the work cancellations before acquiring conn->lock and use +disable_delayed_work_sync() to additionally prevent the works from +being rearmed after cancellation, consistent with the pattern used in +hci_conn_del(). + +Fixes: ab4eedb790ca ("Bluetooth: L2CAP: Fix corrupted list in hci_chan_del") +Signed-off-by: Hyunwoo Kim +Signed-off-by: Luiz Augusto von Dentz +Stable-dep-of: 2641a9e0a1dd ("Bluetooth: L2CAP: cancel pending_rx_work before taking conn->lock") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/bluetooth/l2cap_core.c | 9 +++------ + 1 file changed, 3 insertions(+), 6 deletions(-) + +--- a/net/bluetooth/l2cap_core.c ++++ b/net/bluetooth/l2cap_core.c +@@ -1782,6 +1782,9 @@ static void l2cap_conn_del(struct hci_co + + BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); + ++ cancel_delayed_work_sync(&conn->info_timer); ++ cancel_work_sync(&conn->id_addr_update_work); ++ + kfree_skb(conn->rx_skb); + + skb_queue_purge(&conn->pending_rx); +@@ -1795,9 +1798,6 @@ static void l2cap_conn_del(struct hci_co + + ida_destroy(&conn->tx_ida); + +- if (work_pending(&conn->id_addr_update_work)) +- cancel_work_sync(&conn->id_addr_update_work); +- + l2cap_unregister_all_users(conn); + + /* Force the connection to be immediately dropped */ +@@ -1822,9 +1822,6 @@ static void l2cap_conn_del(struct hci_co + + hci_chan_del(conn->hchan); + +- if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT) +- cancel_delayed_work_sync(&conn->info_timer); +- + hcon->l2cap_data = NULL; + conn->hchan = NULL; + l2cap_conn_put(conn); diff --git a/queue-6.1/bluetooth-l2cap-fix-not-tracking-outstanding-tx-ident.patch b/queue-6.1/bluetooth-l2cap-fix-not-tracking-outstanding-tx-ident.patch new file mode 100644 index 0000000000..543d5da3e9 --- /dev/null +++ b/queue-6.1/bluetooth-l2cap-fix-not-tracking-outstanding-tx-ident.patch @@ -0,0 +1,154 @@ +From stable+bounces-275094-greg=kroah.com@vger.kernel.org Thu Jul 16 01:56:11 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 19:55:23 -0400 +Subject: Bluetooth: L2CAP: Fix not tracking outstanding TX ident +To: stable@vger.kernel.org +Cc: Luiz Augusto von Dentz , Paul Menzel , Sasha Levin +Message-ID: <20260715235525.525483-1-sashal@kernel.org> + +From: Luiz Augusto von Dentz + +[ 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 +Reviewed-by: Paul Menzel +Stable-dep-of: 2641a9e0a1dd ("Bluetooth: L2CAP: cancel pending_rx_work before taking conn->lock") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/bluetooth/l2cap.h | 3 -- + net/bluetooth/l2cap_core.c | 53 ++++++++++++++++++++++++++++++------------ + 2 files changed, 40 insertions(+), 16 deletions(-) + +--- a/include/net/bluetooth/l2cap.h ++++ b/include/net/bluetooth/l2cap.h +@@ -653,8 +653,7 @@ struct l2cap_conn { + + struct sk_buff *rx_skb; + __u32 rx_len; +- __u8 tx_ident; +- struct mutex ident_lock; ++ struct ida tx_ida; + + struct sk_buff_head pending_rx; + struct work_struct pending_rx_work; +--- a/net/bluetooth/l2cap_core.c ++++ b/net/bluetooth/l2cap_core.c +@@ -958,26 +958,18 @@ int l2cap_chan_check_security(struct l2c + initiator); + } + +-static u8 l2cap_get_ident(struct l2cap_conn *conn) ++static int l2cap_get_ident(struct l2cap_conn *conn) + { +- u8 id; ++ /* LE link does not support tools like l2ping so use the full range */ ++ if (conn->hcon->type == LE_LINK) ++ return ida_alloc_range(&conn->tx_ida, 1, 255, GFP_ATOMIC); + + /* Get next available identificator. + * 1 - 128 are used by kernel. + * 129 - 199 are reserved. + * 200 - 254 are used by utilities like l2ping, etc. + */ +- +- mutex_lock(&conn->ident_lock); +- +- if (++conn->tx_ident > 128) +- conn->tx_ident = 1; +- +- id = conn->tx_ident; +- +- mutex_unlock(&conn->ident_lock); +- +- return id; ++ return ida_alloc_range(&conn->tx_ida, 1, 128, GFP_ATOMIC); + } + + static void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len, +@@ -1801,6 +1793,8 @@ static void l2cap_conn_del(struct hci_co + if (work_pending(&conn->pending_rx_work)) + cancel_work_sync(&conn->pending_rx_work); + ++ ida_destroy(&conn->tx_ida); ++ + if (work_pending(&conn->id_addr_update_work)) + cancel_work_sync(&conn->id_addr_update_work); + +@@ -4875,12 +4869,41 @@ unlock: + return err; + } + ++static void l2cap_put_ident(struct l2cap_conn *conn, u8 code, u8 id) ++{ ++ int ret; ++ ++ switch (code) { ++ case L2CAP_COMMAND_REJ: ++ case L2CAP_CONN_RSP: ++ case L2CAP_CONF_RSP: ++ case L2CAP_DISCONN_RSP: ++ case L2CAP_ECHO_RSP: ++ case L2CAP_INFO_RSP: ++ case L2CAP_CONN_PARAM_UPDATE_RSP: ++ case L2CAP_ECRED_CONN_RSP: ++ case L2CAP_ECRED_RECONF_RSP: ++ /* The remote may send bogus ids that would make ida_free ++ * generate warnings, so only free ids that are actually ++ * allocated: probing the exact id returns -ENOSPC when it ++ * is in use, otherwise the probe allocated it and freeing ++ * is safe either way. Only on -ENOMEM is the id known to ++ * be unallocated and the free must be skipped. ++ */ ++ ret = ida_alloc_range(&conn->tx_ida, id, id, GFP_ATOMIC); ++ if (ret >= 0 || ret == -ENOSPC) ++ ida_free(&conn->tx_ida, id); ++ } ++} ++ + static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, u16 cmd_len, + u8 *data) + { + int err = 0; + ++ l2cap_put_ident(conn, cmd->code, cmd->ident); ++ + switch (cmd->code) { + case L2CAP_COMMAND_REJ: + l2cap_command_rej(conn, cmd, cmd_len, data); +@@ -5602,6 +5625,8 @@ static inline int l2cap_le_sig_cmd(struc + { + int err = 0; + ++ l2cap_put_ident(conn, cmd->code, cmd->ident); ++ + switch (cmd->code) { + case L2CAP_COMMAND_REJ: + l2cap_le_command_rej(conn, cmd, cmd_len, data); +@@ -7157,13 +7182,13 @@ static struct l2cap_conn *l2cap_conn_add + hci_dev_test_flag(hcon->hdev, HCI_FORCE_BREDR_SMP))) + conn->local_fixed_chan |= L2CAP_FC_SMP_BREDR; + +- mutex_init(&conn->ident_lock); + mutex_init(&conn->chan_lock); + + INIT_LIST_HEAD(&conn->chan_l); + INIT_LIST_HEAD(&conn->users); + + INIT_DELAYED_WORK(&conn->info_timer, l2cap_info_timeout); ++ ida_init(&conn->tx_ida); + + skb_queue_head_init(&conn->pending_rx); + INIT_WORK(&conn->pending_rx_work, process_pending_rx); diff --git a/queue-6.1/bluetooth-l2cap-fix-slab-use-after-free-in-l2cap_connect.patch b/queue-6.1/bluetooth-l2cap-fix-slab-use-after-free-in-l2cap_connect.patch new file mode 100644 index 0000000000..4329d70976 --- /dev/null +++ b/queue-6.1/bluetooth-l2cap-fix-slab-use-after-free-in-l2cap_connect.patch @@ -0,0 +1,120 @@ +From stable+bounces-275090-greg=kroah.com@vger.kernel.org Thu Jul 16 01:55:35 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 19:55:17 -0400 +Subject: Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_connect() +To: stable@vger.kernel.org +Cc: Sungwoo Kim , Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715235519.525147-3-sashal@kernel.org> + +From: Sungwoo Kim + +[ Upstream commit 4d7b41c0e43995b0e992b9f8903109275744b658 ] + +Extend a critical section to prevent chan from early freeing. +Also make the l2cap_connect() return type void. Nothing is using the +returned value but it is ugly to return a potentially freed pointer. +Making it void will help with backports because earlier kernels did use +the return value. Now the compile will break for kernels where this +patch is not a complete fix. + +Call stack summary: + +[use] +l2cap_bredr_sig_cmd + l2cap_connect + ┌ mutex_lock(&conn->chan_lock); + │ chan = pchan->ops->new_connection(pchan); <- alloc chan + │ __l2cap_chan_add(conn, chan); + │ l2cap_chan_hold(chan); + │ list_add(&chan->list, &conn->chan_l); ... (1) + └ mutex_unlock(&conn->chan_lock); + chan->conf_state ... (4) <- use after free + +[free] +l2cap_conn_del +┌ mutex_lock(&conn->chan_lock); +│ foreach chan in conn->chan_l: ... (2) +│ l2cap_chan_put(chan); +│ l2cap_chan_destroy +│ kfree(chan) ... (3) <- chan freed +└ mutex_unlock(&conn->chan_lock); + +================================================================== +BUG: KASAN: slab-use-after-free in instrument_atomic_read +include/linux/instrumented.h:68 [inline] +BUG: KASAN: slab-use-after-free in _test_bit +include/asm-generic/bitops/instrumented-non-atomic.h:141 [inline] +BUG: KASAN: slab-use-after-free in l2cap_connect+0xa67/0x11a0 +net/bluetooth/l2cap_core.c:4260 +Read of size 8 at addr ffff88810bf040a0 by task kworker/u3:1/311 + +Fixes: 73ffa904b782 ("Bluetooth: Move conf_{req,rsp} stuff to struct l2cap_chan") +Signed-off-by: Sungwoo Kim +Signed-off-by: Luiz Augusto von Dentz +Stable-dep-of: 6fef032af009 ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/bluetooth/l2cap_core.c | 21 ++++++++++----------- + 1 file changed, 10 insertions(+), 11 deletions(-) + +--- a/net/bluetooth/l2cap_core.c ++++ b/net/bluetooth/l2cap_core.c +@@ -3985,13 +3985,12 @@ static inline int l2cap_command_rej(stru + return 0; + } + +-static struct l2cap_chan *l2cap_connect(struct l2cap_conn *conn, +- struct l2cap_cmd_hdr *cmd, +- u8 *data, u8 rsp_code, u8 amp_id) ++static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, ++ u8 *data, u8 rsp_code, u8 amp_id) + { + struct l2cap_conn_req *req = (struct l2cap_conn_req *) data; + struct l2cap_conn_rsp rsp; +- struct l2cap_chan *chan = NULL, *pchan; ++ struct l2cap_chan *chan = NULL, *pchan = NULL; + int result, status = L2CAP_CS_NO_INFO; + + u16 dcid = 0, scid = __le16_to_cpu(req->scid); +@@ -4004,7 +4003,7 @@ static struct l2cap_chan *l2cap_connect( + &conn->hcon->dst, ACL_LINK); + if (!pchan) { + result = L2CAP_CR_BAD_PSM; +- goto sendresp; ++ goto response; + } + + mutex_lock(&conn->chan_lock); +@@ -4092,17 +4091,15 @@ static struct l2cap_chan *l2cap_connect( + } + + response: +- l2cap_chan_unlock(pchan); +- mutex_unlock(&conn->chan_lock); +- l2cap_chan_put(pchan); +- +-sendresp: + rsp.scid = cpu_to_le16(scid); + rsp.dcid = cpu_to_le16(dcid); + rsp.result = cpu_to_le16(result); + rsp.status = cpu_to_le16(status); + l2cap_send_cmd(conn, cmd->ident, rsp_code, sizeof(rsp), &rsp); + ++ if (!pchan) ++ return; ++ + if (result == L2CAP_CR_PEND && status == L2CAP_CS_NO_INFO) { + struct l2cap_info_req info; + info.type = cpu_to_le16(L2CAP_IT_FEAT_MASK); +@@ -4125,7 +4122,9 @@ sendresp: + chan->num_conf_req++; + } + +- return chan; ++ l2cap_chan_unlock(pchan); ++ mutex_unlock(&conn->chan_lock); ++ l2cap_chan_put(pchan); + } + + static int l2cap_connect_req(struct l2cap_conn *conn, diff --git a/queue-6.1/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch b/queue-6.1/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch new file mode 100644 index 0000000000..7c8044586a --- /dev/null +++ b/queue-6.1/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch @@ -0,0 +1,542 @@ +From stable+bounces-275092-greg=kroah.com@vger.kernel.org Thu Jul 16 01:56:07 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 19:55:19 -0400 +Subject: Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb() +To: stable@vger.kernel.org +Cc: Siwei Zhang , stable@kernel.org, Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715235519.525147-5-sashal@kernel.org> + +From: Siwei Zhang + +[ 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 +Signed-off-by: Luiz Augusto von Dentz +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/bluetooth/l2cap.h | 10 ++-- + net/bluetooth/6lowpan.c | 18 ------- + net/bluetooth/l2cap_core.c | 78 ++++++++++++++++++++++++++----- + net/bluetooth/l2cap_sock.c | 103 +++++++++++++++++++----------------------- + net/bluetooth/smp.c | 27 ++--------- + 5 files changed, 127 insertions(+), 109 deletions(-) + +--- a/include/net/bluetooth/l2cap.h ++++ b/include/net/bluetooth/l2cap.h +@@ -614,7 +614,8 @@ struct l2cap_chan { + struct l2cap_ops { + char *name; + +- struct l2cap_chan *(*new_connection) (struct l2cap_chan *chan); ++ int (*new_connection)(struct l2cap_chan *chan, ++ struct l2cap_chan *new_chan); + int (*recv) (struct l2cap_chan * chan, + struct sk_buff *skb); + void (*teardown) (struct l2cap_chan *chan, int err); +@@ -878,9 +879,10 @@ static inline __u16 __next_seq(struct l2 + return (seq + 1) % (chan->tx_win_max + 1); + } + +-static inline struct l2cap_chan *l2cap_chan_no_new_connection(struct l2cap_chan *chan) ++static inline int l2cap_chan_no_new_connection(struct l2cap_chan *chan, ++ struct l2cap_chan *new_chan) + { +- return NULL; ++ return -EOPNOTSUPP; + } + + static inline int l2cap_chan_no_recv(struct l2cap_chan *chan, struct sk_buff *skb) +@@ -956,7 +958,7 @@ int l2cap_chan_send(struct l2cap_chan *c + void l2cap_chan_busy(struct l2cap_chan *chan, int busy); + void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail); + int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator); +-void l2cap_chan_set_defaults(struct l2cap_chan *chan); ++void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan); + int l2cap_ertm_init(struct l2cap_chan *chan); + void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan); + void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan); +--- a/net/bluetooth/6lowpan.c ++++ b/net/bluetooth/6lowpan.c +@@ -631,7 +631,7 @@ static struct l2cap_chan *chan_create(vo + if (!chan) + return NULL; + +- l2cap_chan_set_defaults(chan); ++ l2cap_chan_set_defaults(chan, NULL); + + chan->chan_type = L2CAP_CHAN_CONN_ORIENTED; + chan->mode = L2CAP_MODE_LE_FLOWCTL; +@@ -744,21 +744,6 @@ static inline void chan_ready_cb(struct + ifup(dev->netdev); + } + +-static inline struct l2cap_chan *chan_new_conn_cb(struct l2cap_chan *pchan) +-{ +- struct l2cap_chan *chan; +- +- chan = chan_create(); +- if (!chan) +- return NULL; +- +- chan->ops = pchan->ops; +- +- BT_DBG("chan %p pchan %p", chan, pchan); +- +- return chan; +-} +- + static void unregister_dev(struct lowpan_btle_dev *dev) + { + struct hci_dev *hdev = READ_ONCE(dev->hdev); +@@ -900,7 +885,6 @@ static long chan_get_sndtimeo_cb(struct + + static const struct l2cap_ops bt_6lowpan_chan_ops = { + .name = "L2CAP 6LoWPAN channel", +- .new_connection = chan_new_conn_cb, + .recv = chan_recv_cb, + .close = chan_close_cb, + .state_change = chan_state_change_cb, +--- a/net/bluetooth/l2cap_core.c ++++ b/net/bluetooth/l2cap_core.c +@@ -523,7 +523,10 @@ void l2cap_chan_put(struct l2cap_chan *c + } + EXPORT_SYMBOL_GPL(l2cap_chan_put); + +-void l2cap_chan_set_defaults(struct l2cap_chan *chan) ++/* Initialise @chan with default values, inheriting from the parent channel ++ * @pchan when it is given. ++ */ ++void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan) + { + chan->fcs = L2CAP_FCS_CRC16; + chan->max_tx = L2CAP_DEFAULT_MAX_TX; +@@ -537,6 +540,31 @@ void l2cap_chan_set_defaults(struct l2ca + chan->retrans_timeout = L2CAP_DEFAULT_RETRANS_TO; + chan->monitor_timeout = L2CAP_DEFAULT_MONITOR_TO; + ++ if (pchan) { ++ BT_DBG("chan %p pchan %p", chan, pchan); ++ ++ chan->chan_type = pchan->chan_type; ++ chan->imtu = pchan->imtu; ++ chan->omtu = pchan->omtu; ++ chan->mode = pchan->mode; ++ chan->fcs = pchan->fcs; ++ chan->max_tx = pchan->max_tx; ++ chan->tx_win = pchan->tx_win; ++ chan->tx_win_max = pchan->tx_win_max; ++ chan->sec_level = pchan->sec_level; ++ chan->conf_state = pchan->conf_state; ++ chan->flags = pchan->flags; ++ chan->tx_credits = pchan->tx_credits; ++ chan->rx_credits = pchan->rx_credits; ++ ++ if (chan->chan_type == L2CAP_CHAN_FIXED) { ++ chan->scid = pchan->scid; ++ chan->dcid = pchan->scid; ++ } ++ ++ return; ++ } ++ + chan->conf_state = 0; + set_bit(CONF_NOT_COMPLETE, &chan->conf_state); + +@@ -3985,6 +4013,38 @@ static inline int l2cap_command_rej(stru + return 0; + } + ++/* Allocate and initialise a channel for an incoming connection. ++ * ++ * The channel inherits its configuration from @pchan and is linked into @conn ++ * before ->new_connection() runs, so the conn list reference keeps it alive if ++ * the callback exposes it (e.g. via the socket accept queue) before this ++ * returns. The l2cap_chan_create() reference is taken over by the subsystem on ++ * success and dropped here on failure. ++ */ ++static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn, ++ struct l2cap_chan *pchan) ++{ ++ struct l2cap_chan *chan; ++ ++ chan = l2cap_chan_create(); ++ if (!chan) ++ return NULL; ++ ++ l2cap_chan_set_defaults(chan, pchan); ++ chan->ops = pchan->ops; ++ ++ __l2cap_chan_add(conn, chan); ++ ++ if (pchan->ops->new_connection && ++ pchan->ops->new_connection(pchan, chan) < 0) { ++ l2cap_chan_del(chan, 0); ++ l2cap_chan_put(chan); ++ return NULL; ++ } ++ ++ return chan; ++} ++ + static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, + u8 *data, u8 rsp_code, u8 amp_id) + { +@@ -4032,7 +4092,7 @@ static void l2cap_connect(struct l2cap_c + goto response; + } + +- chan = pchan->ops->new_connection(pchan); ++ chan = l2cap_new_connection(conn, pchan); + if (!chan) + goto response; + +@@ -4050,8 +4110,6 @@ static void l2cap_connect(struct l2cap_c + chan->psm = psm; + chan->dcid = scid; + +- __l2cap_chan_add(conn, chan); +- + dcid = chan->scid; + + __set_chan_timer(chan, chan->ops->get_sndtimeo(chan)); +@@ -4957,7 +5015,7 @@ static int l2cap_le_connect_req(struct l + goto response_unlock; + } + +- chan = pchan->ops->new_connection(pchan); ++ chan = l2cap_new_connection(conn, pchan); + if (!chan) { + result = L2CAP_CR_LE_NO_MEM; + goto response_unlock; +@@ -4972,8 +5030,6 @@ static int l2cap_le_connect_req(struct l + chan->omtu = mtu; + chan->remote_mps = mps; + +- __l2cap_chan_add(conn, chan); +- + l2cap_le_flowctl_init(chan, __le16_to_cpu(req->credits)); + + dcid = chan->scid; +@@ -5184,7 +5240,7 @@ static inline int l2cap_ecred_conn_req(s + continue; + } + +- chan = pchan->ops->new_connection(pchan); ++ chan = l2cap_new_connection(conn, pchan); + if (!chan) { + result = L2CAP_CR_LE_NO_MEM; + continue; +@@ -5199,8 +5255,6 @@ static inline int l2cap_ecred_conn_req(s + chan->omtu = mtu; + chan->remote_mps = mps; + +- __l2cap_chan_add(conn, chan); +- + l2cap_ecred_init(chan, __le16_to_cpu(req->credits)); + + /* Init response */ +@@ -7486,14 +7540,12 @@ static void l2cap_connect_cfm(struct hci + goto next; + + l2cap_chan_lock(pchan); +- chan = pchan->ops->new_connection(pchan); ++ chan = l2cap_new_connection(conn, pchan); + if (chan) { + bacpy(&chan->src, &hcon->src); + bacpy(&chan->dst, &hcon->dst); + chan->src_type = bdaddr_src_type(hcon); + chan->dst_type = dst_type; +- +- __l2cap_chan_add(conn, chan); + } + + l2cap_chan_unlock(pchan); +--- a/net/bluetooth/l2cap_sock.c ++++ b/net/bluetooth/l2cap_sock.c +@@ -45,7 +45,8 @@ static struct bt_sock_list l2cap_sk_list + static const struct proto_ops l2cap_sock_ops; + static void l2cap_sock_init(struct sock *sk, struct sock *parent); + static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, +- int proto, gfp_t prio, int kern); ++ int proto, gfp_t prio, int kern, ++ struct l2cap_chan *chan); + static void l2cap_sock_cleanup_listen(struct sock *parent); + + bool l2cap_is_socket(struct socket *sock) +@@ -1240,6 +1241,23 @@ done: + return err; + } + ++/* Release the sock's ref on chan and clear the pointer so that the ref is ++ * dropped exactly once even if both l2cap_sock_kill() and ++ * l2cap_sock_destruct() run. Setting chan->data to NULL first stops any other ++ * task from dereferencing the now-dead sock pointer. ++ */ ++static void l2cap_sock_put_chan(struct sock *sk) ++{ ++ struct l2cap_chan *chan = l2cap_pi(sk)->chan; ++ ++ if (!chan) ++ return; ++ ++ chan->data = NULL; ++ l2cap_pi(sk)->chan = NULL; ++ l2cap_chan_put(chan); ++} ++ + /* Kill socket (only if zapped and orphan) + * Must be called on unlocked socket, with l2cap channel lock. + */ +@@ -1250,13 +1268,9 @@ static void l2cap_sock_kill(struct sock + + BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state)); + +- /* Sock is dead, so set chan data to NULL, avoid other task use invalid +- * sock pointer. +- */ +- l2cap_pi(sk)->chan->data = NULL; +- /* Kill poor orphan */ ++ l2cap_sock_put_chan(sk); + +- l2cap_chan_put(l2cap_pi(sk)->chan); ++ /* Kill poor orphan */ + sock_set_flag(sk, SOCK_DEAD); + sock_put(sk); + } +@@ -1500,12 +1514,13 @@ static void l2cap_sock_cleanup_listen(st + } + } + +-static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan) ++static int l2cap_sock_new_connection_cb(struct l2cap_chan *chan, ++ struct l2cap_chan *new_chan) + { + struct sock *sk, *parent = chan->data; + + if (!parent) +- return NULL; ++ return -EINVAL; + + lock_sock(parent); + +@@ -1513,25 +1528,28 @@ static struct l2cap_chan *l2cap_sock_new + if (sk_acceptq_is_full(parent)) { + BT_DBG("backlog full %d", parent->sk_ack_backlog); + release_sock(parent); +- return NULL; ++ return -ENOBUFS; + } + + sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP, +- GFP_ATOMIC, 0); ++ GFP_ATOMIC, 0, new_chan); + if (!sk) { + release_sock(parent); +- return NULL; +- } ++ return -ENOMEM; ++ } + + bt_sock_reclassify_lock(sk, BTPROTO_L2CAP); + + l2cap_sock_init(sk, parent); + ++ /* The conn list reference taken by l2cap_new_connection() keeps new_chan ++ * alive once release_sock() lets another task free this socket. ++ */ + bt_accept_enqueue(parent, sk, false); + + release_sock(parent); + +- return l2cap_pi(sk)->chan; ++ return 0; + } + + static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb) +@@ -1829,10 +1847,7 @@ static void l2cap_sock_destruct(struct s + + BT_DBG("sk %p", sk); + +- if (l2cap_pi(sk)->chan) { +- l2cap_pi(sk)->chan->data = NULL; +- l2cap_chan_put(l2cap_pi(sk)->chan); +- } ++ l2cap_sock_put_chan(sk); + + list_for_each_entry_safe(rx_busy, next, &l2cap_pi(sk)->rx_busy, list) { + kfree_skb(rx_busy->skb); +@@ -1864,30 +1879,12 @@ static void l2cap_sock_init(struct sock + BT_DBG("sk %p", sk); + + if (parent) { +- struct l2cap_chan *pchan = l2cap_pi(parent)->chan; +- + sk->sk_type = parent->sk_type; + bt_sk(sk)->flags = bt_sk(parent)->flags; + +- chan->chan_type = pchan->chan_type; +- chan->imtu = pchan->imtu; +- chan->omtu = pchan->omtu; +- chan->conf_state = pchan->conf_state; +- chan->mode = pchan->mode; +- chan->fcs = pchan->fcs; +- chan->max_tx = pchan->max_tx; +- chan->tx_win = pchan->tx_win; +- chan->tx_win_max = pchan->tx_win_max; +- chan->sec_level = pchan->sec_level; +- chan->flags = pchan->flags; +- chan->tx_credits = pchan->tx_credits; +- chan->rx_credits = pchan->rx_credits; +- +- if (chan->chan_type == L2CAP_CHAN_FIXED) { +- chan->scid = pchan->scid; +- chan->dcid = pchan->scid; +- } +- ++ /* Channel configuration is inherited from the parent by ++ * l2cap_new_connection(). ++ */ + security_sk_clone(parent, sk); + } else { + switch (sk->sk_type) { +@@ -1913,7 +1910,7 @@ static void l2cap_sock_init(struct sock + chan->mode = L2CAP_MODE_BASIC; + } + +- l2cap_chan_set_defaults(chan); ++ l2cap_chan_set_defaults(chan, NULL); + } + + /* Default config options */ +@@ -1932,10 +1929,10 @@ static struct proto l2cap_proto = { + }; + + static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, +- int proto, gfp_t prio, int kern) ++ int proto, gfp_t prio, int kern, ++ struct l2cap_chan *chan) + { + struct sock *sk; +- struct l2cap_chan *chan; + + sk = bt_sock_alloc(net, sock, &l2cap_proto, proto, prio, kern); + if (!sk) +@@ -1946,16 +1943,7 @@ static struct sock *l2cap_sock_alloc(str + + INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy); + +- chan = l2cap_chan_create(); +- if (!chan) { +- sk_free(sk); +- if (sock) +- sock->sk = NULL; +- return NULL; +- } +- +- l2cap_chan_hold(chan); +- ++ /* The sock takes ownership of the caller's reference on chan. */ + l2cap_pi(sk)->chan = chan; + + return sk; +@@ -1965,6 +1953,7 @@ static int l2cap_sock_create(struct net + int kern) + { + struct sock *sk; ++ struct l2cap_chan *chan; + + BT_DBG("sock %p", sock); + +@@ -1979,10 +1968,16 @@ static int l2cap_sock_create(struct net + + sock->ops = &l2cap_sock_ops; + +- sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern); +- if (!sk) ++ chan = l2cap_chan_create(); ++ if (!chan) + return -ENOMEM; + ++ sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern, chan); ++ if (!sk) { ++ l2cap_chan_put(chan); ++ return -ENOMEM; ++ } ++ + l2cap_sock_init(sk, NULL); + bt_sock_link(&l2cap_sk_list, sk); + return 0; +--- a/net/bluetooth/smp.c ++++ b/net/bluetooth/smp.c +@@ -3228,34 +3228,19 @@ static const struct l2cap_ops smp_chan_o + .get_sndtimeo = l2cap_chan_no_get_sndtimeo, + }; + +-static inline struct l2cap_chan *smp_new_conn_cb(struct l2cap_chan *pchan) ++static inline int smp_new_conn_cb(struct l2cap_chan *chan, ++ struct l2cap_chan *new_chan) + { +- struct l2cap_chan *chan; +- +- BT_DBG("pchan %p", pchan); +- +- chan = l2cap_chan_create(); +- if (!chan) +- return NULL; +- +- chan->chan_type = pchan->chan_type; +- chan->ops = &smp_chan_ops; +- chan->scid = pchan->scid; +- chan->dcid = chan->scid; +- chan->imtu = pchan->imtu; +- chan->omtu = pchan->omtu; +- chan->mode = pchan->mode; ++ new_chan->ops = &smp_chan_ops; + + /* Other L2CAP channels may request SMP routines in order to + * change the security level. This means that the SMP channel + * lock must be considered in its own category to avoid lockdep + * warnings. + */ +- atomic_set(&chan->nesting, L2CAP_NESTING_SMP); +- +- BT_DBG("created chan %p", chan); ++ atomic_set(&new_chan->nesting, L2CAP_NESTING_SMP); + +- return chan; ++ return 0; + } + + static const struct l2cap_ops smp_root_chan_ops = { +@@ -3326,7 +3311,7 @@ create_chan: + + l2cap_add_scid(chan, cid); + +- l2cap_chan_set_defaults(chan); ++ l2cap_chan_set_defaults(chan, NULL); + + if (cid == L2CAP_CID_SMP) { + u8 bdaddr_type; diff --git a/queue-6.1/bluetooth-make-handle-of-hci_conn-be-unique.patch b/queue-6.1/bluetooth-make-handle-of-hci_conn-be-unique.patch new file mode 100644 index 0000000000..f522bf3865 --- /dev/null +++ b/queue-6.1/bluetooth-make-handle-of-hci_conn-be-unique.patch @@ -0,0 +1,407 @@ +From stable+bounces-275089-greg=kroah.com@vger.kernel.org Thu Jul 16 01:56:00 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 19:55:15 -0400 +Subject: Bluetooth: Make handle of hci_conn be unique +To: stable@vger.kernel.org +Cc: Ziyang Xuan , Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715235519.525147-1-sashal@kernel.org> + +From: Ziyang Xuan + +[ Upstream commit 181a42edddf51d5d9697ecdf365d72ebeab5afb0 ] + +The handle of new hci_conn is always HCI_CONN_HANDLE_MAX + 1 if +the handle of the first hci_conn entry in hci_dev->conn_hash->list +is not HCI_CONN_HANDLE_MAX + 1. Use ida to manage the allocation of +hci_conn->handle to make it be unique. + +Fixes: 9f78191cc9f1 ("Bluetooth: hci_conn: Always allocate unique handles") +Signed-off-by: Ziyang Xuan +Signed-off-by: Luiz Augusto von Dentz +Stable-dep-of: 6fef032af009 ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/bluetooth/hci_core.h | 8 ++++- + net/bluetooth/amp.c | 3 -- + net/bluetooth/hci_conn.c | 45 +++++++++++++++++++++++-------- + net/bluetooth/hci_core.c | 3 ++ + net/bluetooth/hci_event.c | 56 ++++++++++++++++++++++++--------------- + 5 files changed, 79 insertions(+), 36 deletions(-) + +--- a/include/net/bluetooth/hci_core.h ++++ b/include/net/bluetooth/hci_core.h +@@ -322,8 +322,8 @@ struct adv_monitor { + + #define HCI_MAX_SHORT_NAME_LENGTH 10 + +-#define HCI_CONN_HANDLE_UNSET 0xffff + #define HCI_CONN_HANDLE_MAX 0x0eff ++#define HCI_CONN_HANDLE_UNSET(_handle) (_handle > HCI_CONN_HANDLE_MAX) + + /* Min encryption key size to match with SMP */ + #define HCI_MIN_ENC_KEY_SIZE 7 +@@ -352,6 +352,8 @@ struct hci_dev { + struct srcu_struct srcu; + struct mutex lock; + ++ struct ida unset_handle_ida; ++ + const char *name; + unsigned long flags; + __u16 id; +@@ -1332,7 +1334,9 @@ bool hci_iso_setup_path(struct hci_conn + int hci_le_create_cis(struct hci_conn *conn); + + struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst, +- u8 role); ++ u8 role, u16 handle); ++struct hci_conn *hci_conn_add_unset(struct hci_dev *hdev, int type, ++ bdaddr_t *dst, u8 role); + int hci_conn_del(struct hci_conn *conn); + void hci_conn_hash_flush(struct hci_dev *hdev); + void hci_conn_check_pending(struct hci_dev *hdev); +--- a/net/bluetooth/amp.c ++++ b/net/bluetooth/amp.c +@@ -109,7 +109,7 @@ struct hci_conn *phylink_add(struct hci_ + struct hci_conn *hcon; + u8 role = out ? HCI_ROLE_MASTER : HCI_ROLE_SLAVE; + +- hcon = hci_conn_add(hdev, AMP_LINK, dst, role); ++ hcon = hci_conn_add(hdev, AMP_LINK, dst, role, __next_handle(mgr)); + if (!hcon) + return NULL; + +@@ -117,7 +117,6 @@ struct hci_conn *phylink_add(struct hci_ + + hcon->state = BT_CONNECT; + hcon->attempt++; +- hcon->handle = __next_handle(mgr); + hcon->remote_id = remote_id; + hcon->amp_mgr = amp_mgr_get(mgr); + +--- a/net/bluetooth/hci_conn.c ++++ b/net/bluetooth/hci_conn.c +@@ -151,6 +151,9 @@ static void hci_conn_cleanup(struct hci_ + + hci_conn_hash_del(hdev, conn); + ++ if (HCI_CONN_HANDLE_UNSET(conn->handle)) ++ ida_free(&hdev->unset_handle_ida, conn->handle); ++ + if (conn->cleanup) + conn->cleanup(conn); + +@@ -980,12 +983,18 @@ static void cis_cleanup(struct hci_conn + hci_le_remove_cig(hdev, conn->iso_qos.cig); + } + ++static int hci_conn_hash_alloc_unset(struct hci_dev *hdev) ++{ ++ return ida_alloc_range(&hdev->unset_handle_ida, HCI_CONN_HANDLE_MAX + 1, ++ U16_MAX, GFP_ATOMIC); ++} ++ + struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst, +- u8 role) ++ u8 role, u16 handle) + { + struct hci_conn *conn; + +- BT_DBG("%s dst %pMR", hdev->name, dst); ++ bt_dev_dbg(hdev, "dst %pMR handle 0x%4.4x", dst, handle); + + conn = kzalloc(sizeof(*conn), GFP_KERNEL); + if (!conn) +@@ -993,7 +1002,7 @@ struct hci_conn *hci_conn_add(struct hci + + bacpy(&conn->dst, dst); + bacpy(&conn->src, &hdev->bdaddr); +- conn->handle = HCI_CONN_HANDLE_UNSET; ++ conn->handle = handle; + conn->hdev = hdev; + conn->type = type; + conn->role = role; +@@ -1077,6 +1086,20 @@ struct hci_conn *hci_conn_add(struct hci + return conn; + } + ++struct hci_conn *hci_conn_add_unset(struct hci_dev *hdev, int type, ++ bdaddr_t *dst, u8 role) ++{ ++ int handle; ++ ++ bt_dev_dbg(hdev, "dst %pMR", dst); ++ ++ handle = hci_conn_hash_alloc_unset(hdev); ++ if (unlikely(handle < 0)) ++ return NULL; ++ ++ return hci_conn_add(hdev, type, dst, role, handle); ++} ++ + static bool hci_conn_unlink(struct hci_conn *conn) + { + if (!conn->link) +@@ -1107,7 +1130,7 @@ int hci_conn_del(struct hci_conn *conn) + * yet at this point. Delete it now, otherwise it is + * possible for it to be stuck and can't be deleted. + */ +- if (link->handle == HCI_CONN_HANDLE_UNSET) ++ if (HCI_CONN_HANDLE_UNSET(link->handle)) + hci_conn_del(link); + } + +@@ -1336,7 +1359,7 @@ struct hci_conn *hci_connect_le(struct h + if (conn) { + bacpy(&conn->dst, dst); + } else { +- conn = hci_conn_add(hdev, LE_LINK, dst, role); ++ conn = hci_conn_add_unset(hdev, LE_LINK, dst, role); + if (!conn) + return ERR_PTR(-ENOMEM); + hci_conn_hold(conn); +@@ -1503,7 +1526,7 @@ static struct hci_conn *hci_add_bis(stru + if (conn) + return ERR_PTR(-EADDRINUSE); + +- conn = hci_conn_add(hdev, ISO_LINK, dst, HCI_ROLE_MASTER); ++ conn = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_MASTER); + if (!conn) + return ERR_PTR(-ENOMEM); + +@@ -1548,7 +1571,7 @@ struct hci_conn *hci_connect_le_scan(str + + BT_DBG("requesting refresh of dst_addr"); + +- conn = hci_conn_add(hdev, LE_LINK, dst, HCI_ROLE_MASTER); ++ conn = hci_conn_add_unset(hdev, LE_LINK, dst, HCI_ROLE_MASTER); + if (!conn) + return ERR_PTR(-ENOMEM); + +@@ -1596,7 +1619,7 @@ struct hci_conn *hci_connect_acl(struct + + acl = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst); + if (!acl) { +- acl = hci_conn_add(hdev, ACL_LINK, dst, HCI_ROLE_MASTER); ++ acl = hci_conn_add_unset(hdev, ACL_LINK, dst, HCI_ROLE_MASTER); + if (!acl) + return ERR_PTR(-ENOMEM); + } +@@ -1627,7 +1650,7 @@ struct hci_conn *hci_connect_sco(struct + + sco = hci_conn_hash_lookup_ba(hdev, type, dst); + if (!sco) { +- sco = hci_conn_add(hdev, type, dst, HCI_ROLE_MASTER); ++ sco = hci_conn_add_unset(hdev, type, dst, HCI_ROLE_MASTER); + if (!sco) { + hci_conn_drop(acl); + return ERR_PTR(-ENOMEM); +@@ -1837,7 +1860,7 @@ struct hci_conn *hci_bind_cis(struct hci + + cis = hci_conn_hash_lookup_cis(hdev, dst, dst_type); + if (!cis) { +- cis = hci_conn_add(hdev, ISO_LINK, dst, HCI_ROLE_MASTER); ++ cis = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_MASTER); + if (!cis) + return ERR_PTR(-ENOMEM); + cis->cleanup = cis_cleanup; +@@ -2247,7 +2270,7 @@ struct hci_conn *hci_connect_cis(struct + /* If LE is already connected and CIS handle is already set proceed to + * Create CIS immediately. + */ +- if (le->state == BT_CONNECTED && cis->handle != HCI_CONN_HANDLE_UNSET) ++ if (le->state == BT_CONNECTED && !HCI_CONN_HANDLE_UNSET(cis->handle)) + hci_le_create_cis(le); + + return cis; +--- a/net/bluetooth/hci_core.c ++++ b/net/bluetooth/hci_core.c +@@ -2525,6 +2525,8 @@ struct hci_dev *hci_alloc_dev_priv(int s + mutex_init(&hdev->lock); + mutex_init(&hdev->req_lock); + ++ ida_init(&hdev->unset_handle_ida); ++ + INIT_LIST_HEAD(&hdev->mesh_pending); + INIT_LIST_HEAD(&hdev->mgmt_pending); + INIT_LIST_HEAD(&hdev->reject_list); +@@ -2784,6 +2786,7 @@ void hci_release_dev(struct hci_dev *hde + msft_release(hdev); + hci_dev_unlock(hdev); + ++ ida_destroy(&hdev->unset_handle_ida); + ida_simple_remove(&hci_index_ida, hdev->id); + kfree_skb(hdev->sent_cmd); + kfree_skb(hdev->recv_event); +--- a/net/bluetooth/hci_event.c ++++ b/net/bluetooth/hci_event.c +@@ -2317,8 +2317,8 @@ static void hci_cs_create_conn(struct hc + } + } else { + if (!conn) { +- conn = hci_conn_add(hdev, ACL_LINK, &cp->bdaddr, +- HCI_ROLE_MASTER); ++ conn = hci_conn_add_unset(hdev, ACL_LINK, &cp->bdaddr, ++ HCI_ROLE_MASTER); + if (!conn) + bt_dev_err(hdev, "no memory for new connection"); + } +@@ -3160,8 +3160,8 @@ static void hci_conn_complete_evt(struct + hci_bdaddr_list_lookup_with_flags(&hdev->accept_list, + &ev->bdaddr, + BDADDR_BREDR)) { +- conn = hci_conn_add(hdev, ev->link_type, &ev->bdaddr, +- HCI_ROLE_SLAVE); ++ conn = hci_conn_add_unset(hdev, ev->link_type, ++ &ev->bdaddr, HCI_ROLE_SLAVE); + if (!conn) { + bt_dev_err(hdev, "no memory for new conn"); + goto unlock; +@@ -3185,20 +3185,24 @@ static void hci_conn_complete_evt(struct + * As the connection handle is set here for the first time, it indicates + * whether the connection is already set up. + */ +- if (conn->handle != HCI_CONN_HANDLE_UNSET) { ++ if (!HCI_CONN_HANDLE_UNSET(conn->handle)) { + bt_dev_err(hdev, "Ignoring HCI_Connection_Complete for existing connection"); + goto unlock; + } + + if (!status) { +- conn->handle = __le16_to_cpu(ev->handle); +- if (conn->handle > HCI_CONN_HANDLE_MAX) { ++ if (__le16_to_cpu(ev->handle) > HCI_CONN_HANDLE_MAX) { + bt_dev_err(hdev, "Invalid handle: 0x%4.4x > 0x%4.4x", +- conn->handle, HCI_CONN_HANDLE_MAX); ++ __le16_to_cpu(ev->handle), ++ HCI_CONN_HANDLE_MAX); + status = HCI_ERROR_INVALID_PARAMETERS; + goto done; + } + ++ if (HCI_CONN_HANDLE_UNSET(conn->handle)) ++ ida_free(&hdev->unset_handle_ida, conn->handle); ++ conn->handle = __le16_to_cpu(ev->handle); ++ + if (conn->type == ACL_LINK) { + conn->state = BT_CONFIG; + hci_conn_hold(conn); +@@ -3355,8 +3359,8 @@ static void hci_conn_request_evt(struct + conn = hci_conn_hash_lookup_ba(hdev, ev->link_type, + &ev->bdaddr); + if (!conn) { +- conn = hci_conn_add(hdev, ev->link_type, &ev->bdaddr, +- HCI_ROLE_SLAVE); ++ conn = hci_conn_add_unset(hdev, ev->link_type, &ev->bdaddr, ++ HCI_ROLE_SLAVE); + if (!conn) { + bt_dev_err(hdev, "no memory for new connection"); + goto unlock; +@@ -3867,6 +3871,8 @@ static u8 hci_cc_le_set_cig_params(struc + conn->state == BT_CONNECTED) + continue; + ++ if (HCI_CONN_HANDLE_UNSET(conn->handle)) ++ ida_free(&hdev->unset_handle_ida, conn->handle); + conn->handle = __le16_to_cpu(rp->handle[i++]); + + bt_dev_dbg(hdev, "%p handle 0x%4.4x link %p", conn, +@@ -5050,22 +5056,26 @@ static void hci_sync_conn_complete_evt(s + * As the connection handle is set here for the first time, it indicates + * whether the connection is already set up. + */ +- if (conn->handle != HCI_CONN_HANDLE_UNSET) { ++ if (!HCI_CONN_HANDLE_UNSET(conn->handle)) { + bt_dev_err(hdev, "Ignoring HCI_Sync_Conn_Complete event for existing connection"); + goto unlock; + } + + switch (status) { + case 0x00: +- conn->handle = __le16_to_cpu(ev->handle); +- if (conn->handle > HCI_CONN_HANDLE_MAX) { ++ if (__le16_to_cpu(ev->handle) > HCI_CONN_HANDLE_MAX) { + bt_dev_err(hdev, "Invalid handle: 0x%4.4x > 0x%4.4x", +- conn->handle, HCI_CONN_HANDLE_MAX); ++ __le16_to_cpu(ev->handle), ++ HCI_CONN_HANDLE_MAX); + status = HCI_ERROR_INVALID_PARAMETERS; + conn->state = BT_CLOSED; + break; + } + ++ if (HCI_CONN_HANDLE_UNSET(conn->handle)) ++ ida_free(&hdev->unset_handle_ida, conn->handle); ++ conn->handle = __le16_to_cpu(ev->handle); ++ + conn->state = BT_CONNECTED; + conn->type = ev->link_type; + +@@ -5752,7 +5762,7 @@ static void le_conn_complete_evt(struct + if (status) + goto unlock; + +- conn = hci_conn_add(hdev, LE_LINK, bdaddr, role); ++ conn = hci_conn_add_unset(hdev, LE_LINK, bdaddr, role); + if (!conn) { + bt_dev_err(hdev, "no memory for new connection"); + goto unlock; +@@ -5790,7 +5800,7 @@ static void le_conn_complete_evt(struct + * As the connection handle is set here for the first time, it indicates + * whether the connection is already set up. + */ +- if (conn->handle != HCI_CONN_HANDLE_UNSET) { ++ if (!HCI_CONN_HANDLE_UNSET(conn->handle)) { + bt_dev_err(hdev, "Ignoring HCI_Connection_Complete for existing connection"); + goto unlock; + } +@@ -5848,6 +5858,8 @@ static void le_conn_complete_evt(struct + mgmt_device_connected(hdev, conn, NULL, 0); + + conn->sec_level = BT_SECURITY_LOW; ++ if (HCI_CONN_HANDLE_UNSET(conn->handle)) ++ ida_free(&hdev->unset_handle_ida, conn->handle); + conn->handle = handle; + conn->state = BT_CONFIG; + +@@ -6849,12 +6861,12 @@ static void hci_le_cis_req_evt(struct hc + + cis = hci_conn_hash_lookup_handle(hdev, cis_handle); + if (!cis) { +- cis = hci_conn_add(hdev, ISO_LINK, &acl->dst, HCI_ROLE_SLAVE); ++ cis = hci_conn_add(hdev, ISO_LINK, &acl->dst, HCI_ROLE_SLAVE, ++ cis_handle); + if (!cis) { + hci_le_reject_cis(hdev, ev->cis_handle); + goto unlock; + } +- cis->handle = cis_handle; + } + + cis->iso_qos.cig = ev->cig_id; +@@ -6896,8 +6908,11 @@ static void hci_le_create_big_complete_e + goto unlock; + } + +- if (ev->num_bis) ++ if (ev->num_bis) { ++ if (HCI_CONN_HANDLE_UNSET(conn->handle)) ++ ida_free(&hdev->unset_handle_ida, conn->handle); + conn->handle = __le16_to_cpu(ev->bis_handle[0]); ++ } + + if (!ev->status) { + conn->state = BT_CONNECTED; +@@ -6939,10 +6954,9 @@ static void hci_le_big_sync_established_ + bis = hci_conn_hash_lookup_handle(hdev, handle); + if (!bis) { + bis = hci_conn_add(hdev, ISO_LINK, BDADDR_ANY, +- HCI_ROLE_SLAVE); ++ HCI_ROLE_SLAVE, handle); + if (!bis) + continue; +- bis->handle = handle; + } + + bis->iso_qos.big = ev->handle; diff --git a/queue-6.1/bluetooth-remove-bt_hs.patch b/queue-6.1/bluetooth-remove-bt_hs.patch new file mode 100644 index 0000000000..d6907f2d23 --- /dev/null +++ b/queue-6.1/bluetooth-remove-bt_hs.patch @@ -0,0 +1,3546 @@ +From stable+bounces-275093-greg=kroah.com@vger.kernel.org Thu Jul 16 01:55:39 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 19:55:16 -0400 +Subject: Bluetooth: Remove BT_HS +To: stable@vger.kernel.org +Cc: Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715235519.525147-2-sashal@kernel.org> + +From: Luiz Augusto von Dentz + +[ Upstream commit e7b02296fb400ee64822fbdd81a0718449066333 ] + +High Speed, Alternate MAC and PHY (AMP) extension, has been removed from +Bluetooth Core specification on 5.3: + +https://www.bluetooth.com/blog/new-core-specification-v5-3-feature-enhancements/ + +Fixes: 244bc377591c ("Bluetooth: Add BT_HS config option") +Signed-off-by: Luiz Augusto von Dentz +Stable-dep-of: 6fef032af009 ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/bluetooth/hci.h | 1 + include/net/bluetooth/l2cap.h | 42 - + net/bluetooth/Kconfig | 8 + net/bluetooth/Makefile | 1 + net/bluetooth/a2mp.c | 1054 ----------------------------------------- + net/bluetooth/a2mp.h | 154 ------ + net/bluetooth/amp.c | 590 ----------------------- + net/bluetooth/amp.h | 61 -- + net/bluetooth/hci_conn.c | 4 + net/bluetooth/hci_event.c | 2 + net/bluetooth/l2cap_core.c | 1069 ------------------------------------------ + net/bluetooth/l2cap_sock.c | 18 + net/bluetooth/mgmt.c | 73 -- + 13 files changed, 20 insertions(+), 3057 deletions(-) + delete mode 100644 net/bluetooth/a2mp.c + delete mode 100644 net/bluetooth/a2mp.h + delete mode 100644 net/bluetooth/amp.c + delete mode 100644 net/bluetooth/amp.h + +--- a/include/net/bluetooth/hci.h ++++ b/include/net/bluetooth/hci.h +@@ -382,7 +382,6 @@ enum { + HCI_LIMITED_PRIVACY, + HCI_RPA_EXPIRED, + HCI_RPA_RESOLVING, +- HCI_HS_ENABLED, + HCI_LE_ENABLED, + HCI_ADVERTISING, + HCI_ADVERTISING_CONNECTABLE, +--- a/include/net/bluetooth/l2cap.h ++++ b/include/net/bluetooth/l2cap.h +@@ -60,8 +60,6 @@ + #define L2CAP_WAIT_ACK_POLL_PERIOD msecs_to_jiffies(200) + #define L2CAP_WAIT_ACK_TIMEOUT msecs_to_jiffies(10000) + +-#define L2CAP_A2MP_DEFAULT_MTU 670 +- + /* L2CAP socket address */ + struct sockaddr_l2 { + sa_family_t l2_family; +@@ -110,12 +108,6 @@ struct l2cap_conninfo { + #define L2CAP_ECHO_RSP 0x09 + #define L2CAP_INFO_REQ 0x0a + #define L2CAP_INFO_RSP 0x0b +-#define L2CAP_CREATE_CHAN_REQ 0x0c +-#define L2CAP_CREATE_CHAN_RSP 0x0d +-#define L2CAP_MOVE_CHAN_REQ 0x0e +-#define L2CAP_MOVE_CHAN_RSP 0x0f +-#define L2CAP_MOVE_CHAN_CFM 0x10 +-#define L2CAP_MOVE_CHAN_CFM_RSP 0x11 + #define L2CAP_CONN_PARAM_UPDATE_REQ 0x12 + #define L2CAP_CONN_PARAM_UPDATE_RSP 0x13 + #define L2CAP_LE_CONN_REQ 0x14 +@@ -145,7 +137,6 @@ struct l2cap_conninfo { + /* L2CAP fixed channels */ + #define L2CAP_FC_SIG_BREDR 0x02 + #define L2CAP_FC_CONNLESS 0x04 +-#define L2CAP_FC_A2MP 0x08 + #define L2CAP_FC_ATT 0x10 + #define L2CAP_FC_SIG_LE 0x20 + #define L2CAP_FC_SMP_LE 0x40 +@@ -268,7 +259,6 @@ struct l2cap_conn_rsp { + /* channel identifier */ + #define L2CAP_CID_SIGNALING 0x0001 + #define L2CAP_CID_CONN_LESS 0x0002 +-#define L2CAP_CID_A2MP 0x0003 + #define L2CAP_CID_ATT 0x0004 + #define L2CAP_CID_LE_SIGNALING 0x0005 + #define L2CAP_CID_SMP 0x0006 +@@ -283,7 +273,6 @@ struct l2cap_conn_rsp { + #define L2CAP_CR_BAD_PSM 0x0002 + #define L2CAP_CR_SEC_BLOCK 0x0003 + #define L2CAP_CR_NO_MEM 0x0004 +-#define L2CAP_CR_BAD_AMP 0x0005 + #define L2CAP_CR_INVALID_SCID 0x0006 + #define L2CAP_CR_SCID_IN_USE 0x0007 + +@@ -405,29 +394,6 @@ struct l2cap_info_rsp { + __u8 data[]; + } __packed; + +-struct l2cap_create_chan_req { +- __le16 psm; +- __le16 scid; +- __u8 amp_id; +-} __packed; +- +-struct l2cap_create_chan_rsp { +- __le16 dcid; +- __le16 scid; +- __le16 result; +- __le16 status; +-} __packed; +- +-struct l2cap_move_chan_req { +- __le16 icid; +- __u8 dest_amp_id; +-} __packed; +- +-struct l2cap_move_chan_rsp { +- __le16 icid; +- __le16 result; +-} __packed; +- + #define L2CAP_MR_SUCCESS 0x0000 + #define L2CAP_MR_PEND 0x0001 + #define L2CAP_MR_BAD_ID 0x0002 +@@ -542,8 +508,6 @@ struct l2cap_seq_list { + + struct l2cap_chan { + struct l2cap_conn *conn; +- struct hci_conn *hs_hcon; +- struct hci_chan *hs_hchan; + struct kref kref; + atomic_t nesting; + +@@ -597,12 +561,6 @@ struct l2cap_chan { + unsigned long conn_state; + unsigned long flags; + +- __u8 remote_amp_id; +- __u8 local_amp_id; +- __u8 move_id; +- __u8 move_state; +- __u8 move_role; +- + __u16 next_tx_seq; + __u16 expected_ack_seq; + __u16 expected_tx_seq; +--- a/net/bluetooth/Kconfig ++++ b/net/bluetooth/Kconfig +@@ -62,14 +62,6 @@ source "net/bluetooth/cmtp/Kconfig" + + source "net/bluetooth/hidp/Kconfig" + +-config BT_HS +- bool "Bluetooth High Speed (HS) features" +- depends on BT_BREDR +- help +- Bluetooth High Speed includes support for off-loading +- Bluetooth connections via 802.11 (wifi) physical layer +- available with Bluetooth version 3.0 or later. +- + config BT_LE + bool "Bluetooth Low Energy (LE) features" + depends on BT +--- a/net/bluetooth/Makefile ++++ b/net/bluetooth/Makefile +@@ -19,7 +19,6 @@ bluetooth-y := af_bluetooth.o hci_core.o + + bluetooth-$(CONFIG_BT_BREDR) += sco.o + bluetooth-$(CONFIG_BT_LE) += iso.o +-bluetooth-$(CONFIG_BT_HS) += a2mp.o amp.o + bluetooth-$(CONFIG_BT_LEDS) += leds.o + bluetooth-$(CONFIG_BT_MSFTEXT) += msft.o + bluetooth-$(CONFIG_BT_AOSPEXT) += aosp.o +--- a/net/bluetooth/a2mp.c ++++ /dev/null +@@ -1,1054 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +- Copyright (c) 2010,2011 Code Aurora Forum. All rights reserved. +- Copyright (c) 2011,2012 Intel Corp. +- +-*/ +- +-#include +-#include +-#include +- +-#include "hci_request.h" +-#include "a2mp.h" +-#include "amp.h" +- +-#define A2MP_FEAT_EXT 0x8000 +- +-/* Global AMP Manager list */ +-static LIST_HEAD(amp_mgr_list); +-static DEFINE_MUTEX(amp_mgr_list_lock); +- +-/* A2MP build & send command helper functions */ +-static struct a2mp_cmd *__a2mp_build(u8 code, u8 ident, u16 len, void *data) +-{ +- struct a2mp_cmd *cmd; +- int plen; +- +- plen = sizeof(*cmd) + len; +- cmd = kzalloc(plen, GFP_KERNEL); +- if (!cmd) +- return NULL; +- +- cmd->code = code; +- cmd->ident = ident; +- cmd->len = cpu_to_le16(len); +- +- memcpy(cmd->data, data, len); +- +- return cmd; +-} +- +-static void a2mp_send(struct amp_mgr *mgr, u8 code, u8 ident, u16 len, void *data) +-{ +- struct l2cap_chan *chan = mgr->a2mp_chan; +- struct a2mp_cmd *cmd; +- u16 total_len = len + sizeof(*cmd); +- struct kvec iv; +- struct msghdr msg; +- +- cmd = __a2mp_build(code, ident, len, data); +- if (!cmd) +- return; +- +- iv.iov_base = cmd; +- iv.iov_len = total_len; +- +- memset(&msg, 0, sizeof(msg)); +- +- iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &iv, 1, total_len); +- +- l2cap_chan_send(chan, &msg, total_len); +- +- kfree(cmd); +-} +- +-static u8 __next_ident(struct amp_mgr *mgr) +-{ +- if (++mgr->ident == 0) +- mgr->ident = 1; +- +- return mgr->ident; +-} +- +-static struct amp_mgr *amp_mgr_lookup_by_state(u8 state) +-{ +- struct amp_mgr *mgr; +- +- mutex_lock(&_mgr_list_lock); +- list_for_each_entry(mgr, &_mgr_list, list) { +- if (test_and_clear_bit(state, &mgr->state)) { +- amp_mgr_get(mgr); +- mutex_unlock(&_mgr_list_lock); +- return mgr; +- } +- } +- mutex_unlock(&_mgr_list_lock); +- +- return NULL; +-} +- +-/* hci_dev_list shall be locked */ +-static void __a2mp_add_cl(struct amp_mgr *mgr, struct a2mp_cl *cl) +-{ +- struct hci_dev *hdev; +- int i = 1; +- +- cl[0].id = AMP_ID_BREDR; +- cl[0].type = AMP_TYPE_BREDR; +- cl[0].status = AMP_STATUS_BLUETOOTH_ONLY; +- +- list_for_each_entry(hdev, &hci_dev_list, list) { +- if (hdev->dev_type == HCI_AMP) { +- cl[i].id = hdev->id; +- cl[i].type = hdev->amp_type; +- if (test_bit(HCI_UP, &hdev->flags)) +- cl[i].status = hdev->amp_status; +- else +- cl[i].status = AMP_STATUS_POWERED_DOWN; +- i++; +- } +- } +-} +- +-/* Processing A2MP messages */ +-static int a2mp_command_rej(struct amp_mgr *mgr, struct sk_buff *skb, +- struct a2mp_cmd *hdr) +-{ +- struct a2mp_cmd_rej *rej = (void *) skb->data; +- +- if (le16_to_cpu(hdr->len) < sizeof(*rej)) +- return -EINVAL; +- +- BT_DBG("ident %u reason %d", hdr->ident, le16_to_cpu(rej->reason)); +- +- skb_pull(skb, sizeof(*rej)); +- +- return 0; +-} +- +-static int a2mp_discover_req(struct amp_mgr *mgr, struct sk_buff *skb, +- struct a2mp_cmd *hdr) +-{ +- struct a2mp_discov_req *req = (void *) skb->data; +- u16 len = le16_to_cpu(hdr->len); +- struct a2mp_discov_rsp *rsp; +- u16 ext_feat; +- u8 num_ctrl; +- struct hci_dev *hdev; +- +- if (len < sizeof(*req)) +- return -EINVAL; +- +- skb_pull(skb, sizeof(*req)); +- +- ext_feat = le16_to_cpu(req->ext_feat); +- +- BT_DBG("mtu %d efm 0x%4.4x", le16_to_cpu(req->mtu), ext_feat); +- +- /* check that packet is not broken for now */ +- while (ext_feat & A2MP_FEAT_EXT) { +- if (len < sizeof(ext_feat)) +- return -EINVAL; +- +- ext_feat = get_unaligned_le16(skb->data); +- BT_DBG("efm 0x%4.4x", ext_feat); +- len -= sizeof(ext_feat); +- skb_pull(skb, sizeof(ext_feat)); +- } +- +- read_lock(&hci_dev_list_lock); +- +- /* at minimum the BR/EDR needs to be listed */ +- num_ctrl = 1; +- +- list_for_each_entry(hdev, &hci_dev_list, list) { +- if (hdev->dev_type == HCI_AMP) +- num_ctrl++; +- } +- +- len = struct_size(rsp, cl, num_ctrl); +- rsp = kmalloc(len, GFP_ATOMIC); +- if (!rsp) { +- read_unlock(&hci_dev_list_lock); +- return -ENOMEM; +- } +- +- rsp->mtu = cpu_to_le16(L2CAP_A2MP_DEFAULT_MTU); +- rsp->ext_feat = 0; +- +- __a2mp_add_cl(mgr, rsp->cl); +- +- read_unlock(&hci_dev_list_lock); +- +- a2mp_send(mgr, A2MP_DISCOVER_RSP, hdr->ident, len, rsp); +- +- kfree(rsp); +- return 0; +-} +- +-static int a2mp_discover_rsp(struct amp_mgr *mgr, struct sk_buff *skb, +- struct a2mp_cmd *hdr) +-{ +- struct a2mp_discov_rsp *rsp = (void *) skb->data; +- u16 len = le16_to_cpu(hdr->len); +- struct a2mp_cl *cl; +- u16 ext_feat; +- bool found = false; +- +- if (len < sizeof(*rsp)) +- return -EINVAL; +- +- len -= sizeof(*rsp); +- skb_pull(skb, sizeof(*rsp)); +- +- ext_feat = le16_to_cpu(rsp->ext_feat); +- +- BT_DBG("mtu %d efm 0x%4.4x", le16_to_cpu(rsp->mtu), ext_feat); +- +- /* check that packet is not broken for now */ +- while (ext_feat & A2MP_FEAT_EXT) { +- if (len < sizeof(ext_feat)) +- return -EINVAL; +- +- ext_feat = get_unaligned_le16(skb->data); +- BT_DBG("efm 0x%4.4x", ext_feat); +- len -= sizeof(ext_feat); +- skb_pull(skb, sizeof(ext_feat)); +- } +- +- cl = (void *) skb->data; +- while (len >= sizeof(*cl)) { +- BT_DBG("Remote AMP id %u type %u status %u", cl->id, cl->type, +- cl->status); +- +- if (cl->id != AMP_ID_BREDR && cl->type != AMP_TYPE_BREDR) { +- struct a2mp_info_req req; +- +- found = true; +- +- memset(&req, 0, sizeof(req)); +- +- req.id = cl->id; +- a2mp_send(mgr, A2MP_GETINFO_REQ, __next_ident(mgr), +- sizeof(req), &req); +- } +- +- len -= sizeof(*cl); +- cl = skb_pull(skb, sizeof(*cl)); +- } +- +- /* Fall back to L2CAP init sequence */ +- if (!found) { +- struct l2cap_conn *conn = mgr->l2cap_conn; +- struct l2cap_chan *chan; +- +- mutex_lock(&conn->chan_lock); +- +- list_for_each_entry(chan, &conn->chan_l, list) { +- +- BT_DBG("chan %p state %s", chan, +- state_to_string(chan->state)); +- +- if (chan->scid == L2CAP_CID_A2MP) +- continue; +- +- l2cap_chan_lock(chan); +- +- if (chan->state == BT_CONNECT) +- l2cap_send_conn_req(chan); +- +- l2cap_chan_unlock(chan); +- } +- +- mutex_unlock(&conn->chan_lock); +- } +- +- return 0; +-} +- +-static int a2mp_change_notify(struct amp_mgr *mgr, struct sk_buff *skb, +- struct a2mp_cmd *hdr) +-{ +- struct a2mp_cl *cl = (void *) skb->data; +- +- while (skb->len >= sizeof(*cl)) { +- BT_DBG("Controller id %u type %u status %u", cl->id, cl->type, +- cl->status); +- cl = skb_pull(skb, sizeof(*cl)); +- } +- +- /* TODO send A2MP_CHANGE_RSP */ +- +- return 0; +-} +- +-static void read_local_amp_info_complete(struct hci_dev *hdev, u8 status, +- u16 opcode) +-{ +- BT_DBG("%s status 0x%2.2x", hdev->name, status); +- +- a2mp_send_getinfo_rsp(hdev); +-} +- +-static int a2mp_getinfo_req(struct amp_mgr *mgr, struct sk_buff *skb, +- struct a2mp_cmd *hdr) +-{ +- struct a2mp_info_req *req = (void *) skb->data; +- struct hci_dev *hdev; +- struct hci_request hreq; +- int err = 0; +- +- if (le16_to_cpu(hdr->len) < sizeof(*req)) +- return -EINVAL; +- +- BT_DBG("id %u", req->id); +- +- hdev = hci_dev_get(req->id); +- if (!hdev || hdev->dev_type != HCI_AMP) { +- struct a2mp_info_rsp rsp; +- +- memset(&rsp, 0, sizeof(rsp)); +- +- rsp.id = req->id; +- rsp.status = A2MP_STATUS_INVALID_CTRL_ID; +- +- a2mp_send(mgr, A2MP_GETINFO_RSP, hdr->ident, sizeof(rsp), +- &rsp); +- +- goto done; +- } +- +- set_bit(READ_LOC_AMP_INFO, &mgr->state); +- hci_req_init(&hreq, hdev); +- hci_req_add(&hreq, HCI_OP_READ_LOCAL_AMP_INFO, 0, NULL); +- err = hci_req_run(&hreq, read_local_amp_info_complete); +- if (err < 0) +- a2mp_send_getinfo_rsp(hdev); +- +-done: +- if (hdev) +- hci_dev_put(hdev); +- +- skb_pull(skb, sizeof(*req)); +- return 0; +-} +- +-static int a2mp_getinfo_rsp(struct amp_mgr *mgr, struct sk_buff *skb, +- struct a2mp_cmd *hdr) +-{ +- struct a2mp_info_rsp *rsp = (struct a2mp_info_rsp *) skb->data; +- struct a2mp_amp_assoc_req req; +- struct amp_ctrl *ctrl; +- +- if (le16_to_cpu(hdr->len) < sizeof(*rsp)) +- return -EINVAL; +- +- BT_DBG("id %u status 0x%2.2x", rsp->id, rsp->status); +- +- if (rsp->status) +- return -EINVAL; +- +- ctrl = amp_ctrl_add(mgr, rsp->id); +- if (!ctrl) +- return -ENOMEM; +- +- memset(&req, 0, sizeof(req)); +- +- req.id = rsp->id; +- a2mp_send(mgr, A2MP_GETAMPASSOC_REQ, __next_ident(mgr), sizeof(req), +- &req); +- +- skb_pull(skb, sizeof(*rsp)); +- return 0; +-} +- +-static int a2mp_getampassoc_req(struct amp_mgr *mgr, struct sk_buff *skb, +- struct a2mp_cmd *hdr) +-{ +- struct a2mp_amp_assoc_req *req = (void *) skb->data; +- struct hci_dev *hdev; +- struct amp_mgr *tmp; +- +- if (le16_to_cpu(hdr->len) < sizeof(*req)) +- return -EINVAL; +- +- BT_DBG("id %u", req->id); +- +- /* Make sure that other request is not processed */ +- tmp = amp_mgr_lookup_by_state(READ_LOC_AMP_ASSOC); +- +- hdev = hci_dev_get(req->id); +- if (!hdev || hdev->amp_type == AMP_TYPE_BREDR || tmp) { +- struct a2mp_amp_assoc_rsp rsp; +- +- memset(&rsp, 0, sizeof(rsp)); +- rsp.id = req->id; +- +- if (tmp) { +- rsp.status = A2MP_STATUS_COLLISION_OCCURED; +- amp_mgr_put(tmp); +- } else { +- rsp.status = A2MP_STATUS_INVALID_CTRL_ID; +- } +- +- a2mp_send(mgr, A2MP_GETAMPASSOC_RSP, hdr->ident, sizeof(rsp), +- &rsp); +- +- goto done; +- } +- +- amp_read_loc_assoc(hdev, mgr); +- +-done: +- if (hdev) +- hci_dev_put(hdev); +- +- skb_pull(skb, sizeof(*req)); +- return 0; +-} +- +-static int a2mp_getampassoc_rsp(struct amp_mgr *mgr, struct sk_buff *skb, +- struct a2mp_cmd *hdr) +-{ +- struct a2mp_amp_assoc_rsp *rsp = (void *) skb->data; +- u16 len = le16_to_cpu(hdr->len); +- struct hci_dev *hdev; +- struct amp_ctrl *ctrl; +- struct hci_conn *hcon; +- size_t assoc_len; +- +- if (len < sizeof(*rsp)) +- return -EINVAL; +- +- assoc_len = len - sizeof(*rsp); +- +- BT_DBG("id %u status 0x%2.2x assoc len %zu", rsp->id, rsp->status, +- assoc_len); +- +- if (rsp->status) +- return -EINVAL; +- +- /* Save remote ASSOC data */ +- ctrl = amp_ctrl_lookup(mgr, rsp->id); +- if (ctrl) { +- u8 *assoc; +- +- assoc = kmemdup(rsp->amp_assoc, assoc_len, GFP_KERNEL); +- if (!assoc) { +- amp_ctrl_put(ctrl); +- return -ENOMEM; +- } +- +- ctrl->assoc = assoc; +- ctrl->assoc_len = assoc_len; +- ctrl->assoc_rem_len = assoc_len; +- ctrl->assoc_len_so_far = 0; +- +- amp_ctrl_put(ctrl); +- } +- +- /* Create Phys Link */ +- hdev = hci_dev_get(rsp->id); +- if (!hdev) +- return -EINVAL; +- +- hcon = phylink_add(hdev, mgr, rsp->id, true); +- if (!hcon) +- goto done; +- +- BT_DBG("Created hcon %p: loc:%u -> rem:%u", hcon, hdev->id, rsp->id); +- +- mgr->bredr_chan->remote_amp_id = rsp->id; +- +- amp_create_phylink(hdev, mgr, hcon); +- +-done: +- hci_dev_put(hdev); +- skb_pull(skb, len); +- return 0; +-} +- +-static int a2mp_createphyslink_req(struct amp_mgr *mgr, struct sk_buff *skb, +- struct a2mp_cmd *hdr) +-{ +- struct a2mp_physlink_req *req = (void *) skb->data; +- struct a2mp_physlink_rsp rsp; +- struct hci_dev *hdev; +- struct hci_conn *hcon; +- struct amp_ctrl *ctrl; +- +- if (le16_to_cpu(hdr->len) < sizeof(*req)) +- return -EINVAL; +- +- BT_DBG("local_id %u, remote_id %u", req->local_id, req->remote_id); +- +- memset(&rsp, 0, sizeof(rsp)); +- +- rsp.local_id = req->remote_id; +- rsp.remote_id = req->local_id; +- +- hdev = hci_dev_get(req->remote_id); +- if (!hdev || hdev->amp_type == AMP_TYPE_BREDR) { +- rsp.status = A2MP_STATUS_INVALID_CTRL_ID; +- goto send_rsp; +- } +- +- ctrl = amp_ctrl_lookup(mgr, rsp.remote_id); +- if (!ctrl) { +- ctrl = amp_ctrl_add(mgr, rsp.remote_id); +- if (ctrl) { +- amp_ctrl_get(ctrl); +- } else { +- rsp.status = A2MP_STATUS_UNABLE_START_LINK_CREATION; +- goto send_rsp; +- } +- } +- +- if (ctrl) { +- size_t assoc_len = le16_to_cpu(hdr->len) - sizeof(*req); +- u8 *assoc; +- +- assoc = kmemdup(req->amp_assoc, assoc_len, GFP_KERNEL); +- if (!assoc) { +- amp_ctrl_put(ctrl); +- hci_dev_put(hdev); +- return -ENOMEM; +- } +- +- ctrl->assoc = assoc; +- ctrl->assoc_len = assoc_len; +- ctrl->assoc_rem_len = assoc_len; +- ctrl->assoc_len_so_far = 0; +- +- amp_ctrl_put(ctrl); +- } +- +- hcon = phylink_add(hdev, mgr, req->local_id, false); +- if (hcon) { +- amp_accept_phylink(hdev, mgr, hcon); +- rsp.status = A2MP_STATUS_SUCCESS; +- } else { +- rsp.status = A2MP_STATUS_UNABLE_START_LINK_CREATION; +- } +- +-send_rsp: +- if (hdev) +- hci_dev_put(hdev); +- +- /* Reply error now and success after HCI Write Remote AMP Assoc +- command complete with success status +- */ +- if (rsp.status != A2MP_STATUS_SUCCESS) { +- a2mp_send(mgr, A2MP_CREATEPHYSLINK_RSP, hdr->ident, +- sizeof(rsp), &rsp); +- } else { +- set_bit(WRITE_REMOTE_AMP_ASSOC, &mgr->state); +- mgr->ident = hdr->ident; +- } +- +- skb_pull(skb, le16_to_cpu(hdr->len)); +- return 0; +-} +- +-static int a2mp_discphyslink_req(struct amp_mgr *mgr, struct sk_buff *skb, +- struct a2mp_cmd *hdr) +-{ +- struct a2mp_physlink_req *req = (void *) skb->data; +- struct a2mp_physlink_rsp rsp; +- struct hci_dev *hdev; +- struct hci_conn *hcon; +- +- if (le16_to_cpu(hdr->len) < sizeof(*req)) +- return -EINVAL; +- +- BT_DBG("local_id %u remote_id %u", req->local_id, req->remote_id); +- +- memset(&rsp, 0, sizeof(rsp)); +- +- rsp.local_id = req->remote_id; +- rsp.remote_id = req->local_id; +- rsp.status = A2MP_STATUS_SUCCESS; +- +- hdev = hci_dev_get(req->remote_id); +- if (!hdev) { +- rsp.status = A2MP_STATUS_INVALID_CTRL_ID; +- goto send_rsp; +- } +- +- hcon = hci_conn_hash_lookup_ba(hdev, AMP_LINK, +- &mgr->l2cap_conn->hcon->dst); +- if (!hcon) { +- bt_dev_err(hdev, "no phys link exist"); +- rsp.status = A2MP_STATUS_NO_PHYSICAL_LINK_EXISTS; +- goto clean; +- } +- +- /* TODO Disconnect Phys Link here */ +- +-clean: +- hci_dev_put(hdev); +- +-send_rsp: +- a2mp_send(mgr, A2MP_DISCONNPHYSLINK_RSP, hdr->ident, sizeof(rsp), &rsp); +- +- skb_pull(skb, sizeof(*req)); +- return 0; +-} +- +-static inline int a2mp_cmd_rsp(struct amp_mgr *mgr, struct sk_buff *skb, +- struct a2mp_cmd *hdr) +-{ +- BT_DBG("ident %u code 0x%2.2x", hdr->ident, hdr->code); +- +- skb_pull(skb, le16_to_cpu(hdr->len)); +- return 0; +-} +- +-/* Handle A2MP signalling */ +-static int a2mp_chan_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb) +-{ +- struct a2mp_cmd *hdr; +- struct amp_mgr *mgr = chan->data; +- int err = 0; +- +- amp_mgr_get(mgr); +- +- while (skb->len >= sizeof(*hdr)) { +- u16 len; +- +- hdr = (void *) skb->data; +- len = le16_to_cpu(hdr->len); +- +- BT_DBG("code 0x%2.2x id %u len %u", hdr->code, hdr->ident, len); +- +- skb_pull(skb, sizeof(*hdr)); +- +- if (len > skb->len || !hdr->ident) { +- err = -EINVAL; +- break; +- } +- +- mgr->ident = hdr->ident; +- +- switch (hdr->code) { +- case A2MP_COMMAND_REJ: +- a2mp_command_rej(mgr, skb, hdr); +- break; +- +- case A2MP_DISCOVER_REQ: +- err = a2mp_discover_req(mgr, skb, hdr); +- break; +- +- case A2MP_CHANGE_NOTIFY: +- err = a2mp_change_notify(mgr, skb, hdr); +- break; +- +- case A2MP_GETINFO_REQ: +- err = a2mp_getinfo_req(mgr, skb, hdr); +- break; +- +- case A2MP_GETAMPASSOC_REQ: +- err = a2mp_getampassoc_req(mgr, skb, hdr); +- break; +- +- case A2MP_CREATEPHYSLINK_REQ: +- err = a2mp_createphyslink_req(mgr, skb, hdr); +- break; +- +- case A2MP_DISCONNPHYSLINK_REQ: +- err = a2mp_discphyslink_req(mgr, skb, hdr); +- break; +- +- case A2MP_DISCOVER_RSP: +- err = a2mp_discover_rsp(mgr, skb, hdr); +- break; +- +- case A2MP_GETINFO_RSP: +- err = a2mp_getinfo_rsp(mgr, skb, hdr); +- break; +- +- case A2MP_GETAMPASSOC_RSP: +- err = a2mp_getampassoc_rsp(mgr, skb, hdr); +- break; +- +- case A2MP_CHANGE_RSP: +- case A2MP_CREATEPHYSLINK_RSP: +- case A2MP_DISCONNPHYSLINK_RSP: +- err = a2mp_cmd_rsp(mgr, skb, hdr); +- break; +- +- default: +- BT_ERR("Unknown A2MP sig cmd 0x%2.2x", hdr->code); +- err = -EINVAL; +- break; +- } +- } +- +- if (err) { +- struct a2mp_cmd_rej rej; +- +- memset(&rej, 0, sizeof(rej)); +- +- rej.reason = cpu_to_le16(0); +- hdr = (void *) skb->data; +- +- BT_DBG("Send A2MP Rej: cmd 0x%2.2x err %d", hdr->code, err); +- +- a2mp_send(mgr, A2MP_COMMAND_REJ, hdr->ident, sizeof(rej), +- &rej); +- } +- +- /* Always free skb and return success error code to prevent +- from sending L2CAP Disconnect over A2MP channel */ +- kfree_skb(skb); +- +- amp_mgr_put(mgr); +- +- return 0; +-} +- +-static void a2mp_chan_close_cb(struct l2cap_chan *chan) +-{ +- l2cap_chan_put(chan); +-} +- +-static void a2mp_chan_state_change_cb(struct l2cap_chan *chan, int state, +- int err) +-{ +- struct amp_mgr *mgr = chan->data; +- +- if (!mgr) +- return; +- +- BT_DBG("chan %p state %s", chan, state_to_string(state)); +- +- chan->state = state; +- +- switch (state) { +- case BT_CLOSED: +- if (mgr) +- amp_mgr_put(mgr); +- break; +- } +-} +- +-static struct sk_buff *a2mp_chan_alloc_skb_cb(struct l2cap_chan *chan, +- unsigned long hdr_len, +- unsigned long len, int nb) +-{ +- struct sk_buff *skb; +- +- skb = bt_skb_alloc(hdr_len + len, GFP_KERNEL); +- if (!skb) +- return ERR_PTR(-ENOMEM); +- +- return skb; +-} +- +-static const struct l2cap_ops a2mp_chan_ops = { +- .name = "L2CAP A2MP channel", +- .recv = a2mp_chan_recv_cb, +- .close = a2mp_chan_close_cb, +- .state_change = a2mp_chan_state_change_cb, +- .alloc_skb = a2mp_chan_alloc_skb_cb, +- +- /* Not implemented for A2MP */ +- .new_connection = l2cap_chan_no_new_connection, +- .teardown = l2cap_chan_no_teardown, +- .ready = l2cap_chan_no_ready, +- .defer = l2cap_chan_no_defer, +- .resume = l2cap_chan_no_resume, +- .set_shutdown = l2cap_chan_no_set_shutdown, +- .get_sndtimeo = l2cap_chan_no_get_sndtimeo, +-}; +- +-static struct l2cap_chan *a2mp_chan_open(struct l2cap_conn *conn, bool locked) +-{ +- struct l2cap_chan *chan; +- int err; +- +- chan = l2cap_chan_create(); +- if (!chan) +- return NULL; +- +- BT_DBG("chan %p", chan); +- +- chan->chan_type = L2CAP_CHAN_FIXED; +- chan->scid = L2CAP_CID_A2MP; +- chan->dcid = L2CAP_CID_A2MP; +- chan->omtu = L2CAP_A2MP_DEFAULT_MTU; +- chan->imtu = L2CAP_A2MP_DEFAULT_MTU; +- chan->flush_to = L2CAP_DEFAULT_FLUSH_TO; +- +- chan->ops = &a2mp_chan_ops; +- +- l2cap_chan_set_defaults(chan); +- chan->remote_max_tx = chan->max_tx; +- chan->remote_tx_win = chan->tx_win; +- +- chan->retrans_timeout = L2CAP_DEFAULT_RETRANS_TO; +- chan->monitor_timeout = L2CAP_DEFAULT_MONITOR_TO; +- +- skb_queue_head_init(&chan->tx_q); +- +- chan->mode = L2CAP_MODE_ERTM; +- +- err = l2cap_ertm_init(chan); +- if (err < 0) { +- l2cap_chan_del(chan, 0); +- return NULL; +- } +- +- chan->conf_state = 0; +- +- if (locked) +- __l2cap_chan_add(conn, chan); +- else +- l2cap_chan_add(conn, chan); +- +- chan->remote_mps = chan->omtu; +- chan->mps = chan->omtu; +- +- chan->state = BT_CONNECTED; +- +- return chan; +-} +- +-/* AMP Manager functions */ +-struct amp_mgr *amp_mgr_get(struct amp_mgr *mgr) +-{ +- BT_DBG("mgr %p orig refcnt %d", mgr, kref_read(&mgr->kref)); +- +- kref_get(&mgr->kref); +- +- return mgr; +-} +- +-static void amp_mgr_destroy(struct kref *kref) +-{ +- struct amp_mgr *mgr = container_of(kref, struct amp_mgr, kref); +- +- BT_DBG("mgr %p", mgr); +- +- mutex_lock(&_mgr_list_lock); +- list_del(&mgr->list); +- mutex_unlock(&_mgr_list_lock); +- +- amp_ctrl_list_flush(mgr); +- kfree(mgr); +-} +- +-int amp_mgr_put(struct amp_mgr *mgr) +-{ +- BT_DBG("mgr %p orig refcnt %d", mgr, kref_read(&mgr->kref)); +- +- return kref_put(&mgr->kref, &_mgr_destroy); +-} +- +-static struct amp_mgr *amp_mgr_create(struct l2cap_conn *conn, bool locked) +-{ +- struct amp_mgr *mgr; +- struct l2cap_chan *chan; +- +- mgr = kzalloc(sizeof(*mgr), GFP_KERNEL); +- if (!mgr) +- return NULL; +- +- BT_DBG("conn %p mgr %p", conn, mgr); +- +- mgr->l2cap_conn = conn; +- +- chan = a2mp_chan_open(conn, locked); +- if (!chan) { +- kfree(mgr); +- return NULL; +- } +- +- mgr->a2mp_chan = chan; +- chan->data = mgr; +- +- conn->hcon->amp_mgr = mgr; +- +- kref_init(&mgr->kref); +- +- /* Remote AMP ctrl list initialization */ +- INIT_LIST_HEAD(&mgr->amp_ctrls); +- mutex_init(&mgr->amp_ctrls_lock); +- +- mutex_lock(&_mgr_list_lock); +- list_add(&mgr->list, &_mgr_list); +- mutex_unlock(&_mgr_list_lock); +- +- return mgr; +-} +- +-struct l2cap_chan *a2mp_channel_create(struct l2cap_conn *conn, +- struct sk_buff *skb) +-{ +- struct amp_mgr *mgr; +- +- if (conn->hcon->type != ACL_LINK) +- return NULL; +- +- mgr = amp_mgr_create(conn, false); +- if (!mgr) { +- BT_ERR("Could not create AMP manager"); +- return NULL; +- } +- +- BT_DBG("mgr: %p chan %p", mgr, mgr->a2mp_chan); +- +- return mgr->a2mp_chan; +-} +- +-void a2mp_send_getinfo_rsp(struct hci_dev *hdev) +-{ +- struct amp_mgr *mgr; +- struct a2mp_info_rsp rsp; +- +- mgr = amp_mgr_lookup_by_state(READ_LOC_AMP_INFO); +- if (!mgr) +- return; +- +- BT_DBG("%s mgr %p", hdev->name, mgr); +- +- memset(&rsp, 0, sizeof(rsp)); +- +- rsp.id = hdev->id; +- rsp.status = A2MP_STATUS_INVALID_CTRL_ID; +- +- if (hdev->amp_type != AMP_TYPE_BREDR) { +- rsp.status = 0; +- rsp.total_bw = cpu_to_le32(hdev->amp_total_bw); +- rsp.max_bw = cpu_to_le32(hdev->amp_max_bw); +- rsp.min_latency = cpu_to_le32(hdev->amp_min_latency); +- rsp.pal_cap = cpu_to_le16(hdev->amp_pal_cap); +- rsp.assoc_size = cpu_to_le16(hdev->amp_assoc_size); +- } +- +- a2mp_send(mgr, A2MP_GETINFO_RSP, mgr->ident, sizeof(rsp), &rsp); +- amp_mgr_put(mgr); +-} +- +-void a2mp_send_getampassoc_rsp(struct hci_dev *hdev, u8 status) +-{ +- struct amp_mgr *mgr; +- struct amp_assoc *loc_assoc = &hdev->loc_assoc; +- struct a2mp_amp_assoc_rsp *rsp; +- size_t len; +- +- mgr = amp_mgr_lookup_by_state(READ_LOC_AMP_ASSOC); +- if (!mgr) +- return; +- +- BT_DBG("%s mgr %p", hdev->name, mgr); +- +- len = sizeof(struct a2mp_amp_assoc_rsp) + loc_assoc->len; +- rsp = kzalloc(len, GFP_KERNEL); +- if (!rsp) { +- amp_mgr_put(mgr); +- return; +- } +- +- rsp->id = hdev->id; +- +- if (status) { +- rsp->status = A2MP_STATUS_INVALID_CTRL_ID; +- } else { +- rsp->status = A2MP_STATUS_SUCCESS; +- memcpy(rsp->amp_assoc, loc_assoc->data, loc_assoc->len); +- } +- +- a2mp_send(mgr, A2MP_GETAMPASSOC_RSP, mgr->ident, len, rsp); +- amp_mgr_put(mgr); +- kfree(rsp); +-} +- +-void a2mp_send_create_phy_link_req(struct hci_dev *hdev, u8 status) +-{ +- struct amp_mgr *mgr; +- struct amp_assoc *loc_assoc = &hdev->loc_assoc; +- struct a2mp_physlink_req *req; +- struct l2cap_chan *bredr_chan; +- size_t len; +- +- mgr = amp_mgr_lookup_by_state(READ_LOC_AMP_ASSOC_FINAL); +- if (!mgr) +- return; +- +- len = sizeof(*req) + loc_assoc->len; +- +- BT_DBG("%s mgr %p assoc_len %zu", hdev->name, mgr, len); +- +- req = kzalloc(len, GFP_KERNEL); +- if (!req) { +- amp_mgr_put(mgr); +- return; +- } +- +- bredr_chan = mgr->bredr_chan; +- if (!bredr_chan) +- goto clean; +- +- req->local_id = hdev->id; +- req->remote_id = bredr_chan->remote_amp_id; +- memcpy(req->amp_assoc, loc_assoc->data, loc_assoc->len); +- +- a2mp_send(mgr, A2MP_CREATEPHYSLINK_REQ, __next_ident(mgr), len, req); +- +-clean: +- amp_mgr_put(mgr); +- kfree(req); +-} +- +-void a2mp_send_create_phy_link_rsp(struct hci_dev *hdev, u8 status) +-{ +- struct amp_mgr *mgr; +- struct a2mp_physlink_rsp rsp; +- struct hci_conn *hs_hcon; +- +- mgr = amp_mgr_lookup_by_state(WRITE_REMOTE_AMP_ASSOC); +- if (!mgr) +- return; +- +- memset(&rsp, 0, sizeof(rsp)); +- +- hs_hcon = hci_conn_hash_lookup_state(hdev, AMP_LINK, BT_CONNECT); +- if (!hs_hcon) { +- rsp.status = A2MP_STATUS_UNABLE_START_LINK_CREATION; +- } else { +- rsp.remote_id = hs_hcon->remote_id; +- rsp.status = A2MP_STATUS_SUCCESS; +- } +- +- BT_DBG("%s mgr %p hs_hcon %p status %u", hdev->name, mgr, hs_hcon, +- status); +- +- rsp.local_id = hdev->id; +- a2mp_send(mgr, A2MP_CREATEPHYSLINK_RSP, mgr->ident, sizeof(rsp), &rsp); +- amp_mgr_put(mgr); +-} +- +-void a2mp_discover_amp(struct l2cap_chan *chan) +-{ +- struct l2cap_conn *conn = chan->conn; +- struct amp_mgr *mgr = conn->hcon->amp_mgr; +- struct a2mp_discov_req req; +- +- BT_DBG("chan %p conn %p mgr %p", chan, conn, mgr); +- +- if (!mgr) { +- mgr = amp_mgr_create(conn, true); +- if (!mgr) +- return; +- } +- +- mgr->bredr_chan = chan; +- +- memset(&req, 0, sizeof(req)); +- +- req.mtu = cpu_to_le16(L2CAP_A2MP_DEFAULT_MTU); +- req.ext_feat = 0; +- a2mp_send(mgr, A2MP_DISCOVER_REQ, 1, sizeof(req), &req); +-} +--- a/net/bluetooth/a2mp.h ++++ /dev/null +@@ -1,154 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/* +- Copyright (c) 2010,2011 Code Aurora Forum. All rights reserved. +- Copyright (c) 2011,2012 Intel Corp. +- +-*/ +- +-#ifndef __A2MP_H +-#define __A2MP_H +- +-#include +- +-enum amp_mgr_state { +- READ_LOC_AMP_INFO, +- READ_LOC_AMP_ASSOC, +- READ_LOC_AMP_ASSOC_FINAL, +- WRITE_REMOTE_AMP_ASSOC, +-}; +- +-struct amp_mgr { +- struct list_head list; +- struct l2cap_conn *l2cap_conn; +- struct l2cap_chan *a2mp_chan; +- struct l2cap_chan *bredr_chan; +- struct kref kref; +- __u8 ident; +- __u8 handle; +- unsigned long state; +- unsigned long flags; +- +- struct list_head amp_ctrls; +- struct mutex amp_ctrls_lock; +-}; +- +-struct a2mp_cmd { +- __u8 code; +- __u8 ident; +- __le16 len; +- __u8 data[]; +-} __packed; +- +-/* A2MP command codes */ +-#define A2MP_COMMAND_REJ 0x01 +-struct a2mp_cmd_rej { +- __le16 reason; +- __u8 data[]; +-} __packed; +- +-#define A2MP_DISCOVER_REQ 0x02 +-struct a2mp_discov_req { +- __le16 mtu; +- __le16 ext_feat; +-} __packed; +- +-struct a2mp_cl { +- __u8 id; +- __u8 type; +- __u8 status; +-} __packed; +- +-#define A2MP_DISCOVER_RSP 0x03 +-struct a2mp_discov_rsp { +- __le16 mtu; +- __le16 ext_feat; +- struct a2mp_cl cl[]; +-} __packed; +- +-#define A2MP_CHANGE_NOTIFY 0x04 +-#define A2MP_CHANGE_RSP 0x05 +- +-#define A2MP_GETINFO_REQ 0x06 +-struct a2mp_info_req { +- __u8 id; +-} __packed; +- +-#define A2MP_GETINFO_RSP 0x07 +-struct a2mp_info_rsp { +- __u8 id; +- __u8 status; +- __le32 total_bw; +- __le32 max_bw; +- __le32 min_latency; +- __le16 pal_cap; +- __le16 assoc_size; +-} __packed; +- +-#define A2MP_GETAMPASSOC_REQ 0x08 +-struct a2mp_amp_assoc_req { +- __u8 id; +-} __packed; +- +-#define A2MP_GETAMPASSOC_RSP 0x09 +-struct a2mp_amp_assoc_rsp { +- __u8 id; +- __u8 status; +- __u8 amp_assoc[]; +-} __packed; +- +-#define A2MP_CREATEPHYSLINK_REQ 0x0A +-#define A2MP_DISCONNPHYSLINK_REQ 0x0C +-struct a2mp_physlink_req { +- __u8 local_id; +- __u8 remote_id; +- __u8 amp_assoc[]; +-} __packed; +- +-#define A2MP_CREATEPHYSLINK_RSP 0x0B +-#define A2MP_DISCONNPHYSLINK_RSP 0x0D +-struct a2mp_physlink_rsp { +- __u8 local_id; +- __u8 remote_id; +- __u8 status; +-} __packed; +- +-/* A2MP response status */ +-#define A2MP_STATUS_SUCCESS 0x00 +-#define A2MP_STATUS_INVALID_CTRL_ID 0x01 +-#define A2MP_STATUS_UNABLE_START_LINK_CREATION 0x02 +-#define A2MP_STATUS_NO_PHYSICAL_LINK_EXISTS 0x02 +-#define A2MP_STATUS_COLLISION_OCCURED 0x03 +-#define A2MP_STATUS_DISCONN_REQ_RECVD 0x04 +-#define A2MP_STATUS_PHYS_LINK_EXISTS 0x05 +-#define A2MP_STATUS_SECURITY_VIOLATION 0x06 +- +-struct amp_mgr *amp_mgr_get(struct amp_mgr *mgr); +- +-#if IS_ENABLED(CONFIG_BT_HS) +-int amp_mgr_put(struct amp_mgr *mgr); +-struct l2cap_chan *a2mp_channel_create(struct l2cap_conn *conn, +- struct sk_buff *skb); +-void a2mp_discover_amp(struct l2cap_chan *chan); +-#else +-static inline int amp_mgr_put(struct amp_mgr *mgr) +-{ +- return 0; +-} +- +-static inline struct l2cap_chan *a2mp_channel_create(struct l2cap_conn *conn, +- struct sk_buff *skb) +-{ +- return NULL; +-} +- +-static inline void a2mp_discover_amp(struct l2cap_chan *chan) +-{ +-} +-#endif +- +-void a2mp_send_getinfo_rsp(struct hci_dev *hdev); +-void a2mp_send_getampassoc_rsp(struct hci_dev *hdev, u8 status); +-void a2mp_send_create_phy_link_req(struct hci_dev *hdev, u8 status); +-void a2mp_send_create_phy_link_rsp(struct hci_dev *hdev, u8 status); +- +-#endif /* __A2MP_H */ +--- a/net/bluetooth/amp.c ++++ /dev/null +@@ -1,590 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +- Copyright (c) 2011,2012 Intel Corp. +- +-*/ +- +-#include +-#include +-#include +-#include +- +-#include "hci_request.h" +-#include "a2mp.h" +-#include "amp.h" +- +-/* Remote AMP Controllers interface */ +-void amp_ctrl_get(struct amp_ctrl *ctrl) +-{ +- BT_DBG("ctrl %p orig refcnt %d", ctrl, +- kref_read(&ctrl->kref)); +- +- kref_get(&ctrl->kref); +-} +- +-static void amp_ctrl_destroy(struct kref *kref) +-{ +- struct amp_ctrl *ctrl = container_of(kref, struct amp_ctrl, kref); +- +- BT_DBG("ctrl %p", ctrl); +- +- kfree(ctrl->assoc); +- kfree(ctrl); +-} +- +-int amp_ctrl_put(struct amp_ctrl *ctrl) +-{ +- BT_DBG("ctrl %p orig refcnt %d", ctrl, +- kref_read(&ctrl->kref)); +- +- return kref_put(&ctrl->kref, &_ctrl_destroy); +-} +- +-struct amp_ctrl *amp_ctrl_add(struct amp_mgr *mgr, u8 id) +-{ +- struct amp_ctrl *ctrl; +- +- ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); +- if (!ctrl) +- return NULL; +- +- kref_init(&ctrl->kref); +- ctrl->id = id; +- +- mutex_lock(&mgr->amp_ctrls_lock); +- list_add(&ctrl->list, &mgr->amp_ctrls); +- mutex_unlock(&mgr->amp_ctrls_lock); +- +- BT_DBG("mgr %p ctrl %p", mgr, ctrl); +- +- return ctrl; +-} +- +-void amp_ctrl_list_flush(struct amp_mgr *mgr) +-{ +- struct amp_ctrl *ctrl, *n; +- +- BT_DBG("mgr %p", mgr); +- +- mutex_lock(&mgr->amp_ctrls_lock); +- list_for_each_entry_safe(ctrl, n, &mgr->amp_ctrls, list) { +- list_del(&ctrl->list); +- amp_ctrl_put(ctrl); +- } +- mutex_unlock(&mgr->amp_ctrls_lock); +-} +- +-struct amp_ctrl *amp_ctrl_lookup(struct amp_mgr *mgr, u8 id) +-{ +- struct amp_ctrl *ctrl; +- +- BT_DBG("mgr %p id %u", mgr, id); +- +- mutex_lock(&mgr->amp_ctrls_lock); +- list_for_each_entry(ctrl, &mgr->amp_ctrls, list) { +- if (ctrl->id == id) { +- amp_ctrl_get(ctrl); +- mutex_unlock(&mgr->amp_ctrls_lock); +- return ctrl; +- } +- } +- mutex_unlock(&mgr->amp_ctrls_lock); +- +- return NULL; +-} +- +-/* Physical Link interface */ +-static u8 __next_handle(struct amp_mgr *mgr) +-{ +- if (++mgr->handle == 0) +- mgr->handle = 1; +- +- return mgr->handle; +-} +- +-struct hci_conn *phylink_add(struct hci_dev *hdev, struct amp_mgr *mgr, +- u8 remote_id, bool out) +-{ +- bdaddr_t *dst = &mgr->l2cap_conn->hcon->dst; +- struct hci_conn *hcon; +- u8 role = out ? HCI_ROLE_MASTER : HCI_ROLE_SLAVE; +- +- hcon = hci_conn_add(hdev, AMP_LINK, dst, role, __next_handle(mgr)); +- if (!hcon) +- return NULL; +- +- BT_DBG("hcon %p dst %pMR", hcon, dst); +- +- hcon->state = BT_CONNECT; +- hcon->attempt++; +- hcon->remote_id = remote_id; +- hcon->amp_mgr = amp_mgr_get(mgr); +- +- return hcon; +-} +- +-/* AMP crypto key generation interface */ +-static int hmac_sha256(u8 *key, u8 ksize, char *plaintext, u8 psize, u8 *output) +-{ +- struct crypto_shash *tfm; +- struct shash_desc *shash; +- int ret; +- +- if (!ksize) +- return -EINVAL; +- +- tfm = crypto_alloc_shash("hmac(sha256)", 0, 0); +- if (IS_ERR(tfm)) { +- BT_DBG("crypto_alloc_ahash failed: err %ld", PTR_ERR(tfm)); +- return PTR_ERR(tfm); +- } +- +- ret = crypto_shash_setkey(tfm, key, ksize); +- if (ret) { +- BT_DBG("crypto_ahash_setkey failed: err %d", ret); +- goto failed; +- } +- +- shash = kzalloc(sizeof(*shash) + crypto_shash_descsize(tfm), +- GFP_KERNEL); +- if (!shash) { +- ret = -ENOMEM; +- goto failed; +- } +- +- shash->tfm = tfm; +- +- ret = crypto_shash_digest(shash, plaintext, psize, output); +- +- kfree(shash); +- +-failed: +- crypto_free_shash(tfm); +- return ret; +-} +- +-int phylink_gen_key(struct hci_conn *conn, u8 *data, u8 *len, u8 *type) +-{ +- struct hci_dev *hdev = conn->hdev; +- struct link_key *key; +- u8 keybuf[HCI_AMP_LINK_KEY_SIZE]; +- u8 gamp_key[HCI_AMP_LINK_KEY_SIZE]; +- int err; +- +- if (!hci_conn_check_link_mode(conn)) +- return -EACCES; +- +- BT_DBG("conn %p key_type %d", conn, conn->key_type); +- +- /* Legacy key */ +- if (conn->key_type < 3) { +- bt_dev_err(hdev, "legacy key type %u", conn->key_type); +- return -EACCES; +- } +- +- *type = conn->key_type; +- *len = HCI_AMP_LINK_KEY_SIZE; +- +- key = hci_find_link_key(hdev, &conn->dst); +- if (!key) { +- BT_DBG("No Link key for conn %p dst %pMR", conn, &conn->dst); +- return -EACCES; +- } +- +- /* BR/EDR Link Key concatenated together with itself */ +- memcpy(&keybuf[0], key->val, HCI_LINK_KEY_SIZE); +- memcpy(&keybuf[HCI_LINK_KEY_SIZE], key->val, HCI_LINK_KEY_SIZE); +- +- /* Derive Generic AMP Link Key (gamp) */ +- err = hmac_sha256(keybuf, HCI_AMP_LINK_KEY_SIZE, "gamp", 4, gamp_key); +- if (err) { +- bt_dev_err(hdev, "could not derive Generic AMP Key: err %d", err); +- return err; +- } +- +- if (conn->key_type == HCI_LK_DEBUG_COMBINATION) { +- BT_DBG("Use Generic AMP Key (gamp)"); +- memcpy(data, gamp_key, HCI_AMP_LINK_KEY_SIZE); +- return err; +- } +- +- /* Derive Dedicated AMP Link Key: "802b" is 802.11 PAL keyID */ +- return hmac_sha256(gamp_key, HCI_AMP_LINK_KEY_SIZE, "802b", 4, data); +-} +- +-static void read_local_amp_assoc_complete(struct hci_dev *hdev, u8 status, +- u16 opcode, struct sk_buff *skb) +-{ +- struct hci_rp_read_local_amp_assoc *rp = (void *)skb->data; +- struct amp_assoc *assoc = &hdev->loc_assoc; +- size_t rem_len, frag_len; +- +- BT_DBG("%s status 0x%2.2x", hdev->name, rp->status); +- +- if (rp->status) +- goto send_rsp; +- +- frag_len = skb->len - sizeof(*rp); +- rem_len = __le16_to_cpu(rp->rem_len); +- +- if (rem_len > frag_len) { +- BT_DBG("frag_len %zu rem_len %zu", frag_len, rem_len); +- +- memcpy(assoc->data + assoc->offset, rp->frag, frag_len); +- assoc->offset += frag_len; +- +- /* Read other fragments */ +- amp_read_loc_assoc_frag(hdev, rp->phy_handle); +- +- return; +- } +- +- memcpy(assoc->data + assoc->offset, rp->frag, rem_len); +- assoc->len = assoc->offset + rem_len; +- assoc->offset = 0; +- +-send_rsp: +- /* Send A2MP Rsp when all fragments are received */ +- a2mp_send_getampassoc_rsp(hdev, rp->status); +- a2mp_send_create_phy_link_req(hdev, rp->status); +-} +- +-void amp_read_loc_assoc_frag(struct hci_dev *hdev, u8 phy_handle) +-{ +- struct hci_cp_read_local_amp_assoc cp; +- struct amp_assoc *loc_assoc = &hdev->loc_assoc; +- struct hci_request req; +- int err; +- +- BT_DBG("%s handle %u", hdev->name, phy_handle); +- +- cp.phy_handle = phy_handle; +- cp.max_len = cpu_to_le16(hdev->amp_assoc_size); +- cp.len_so_far = cpu_to_le16(loc_assoc->offset); +- +- hci_req_init(&req, hdev); +- hci_req_add(&req, HCI_OP_READ_LOCAL_AMP_ASSOC, sizeof(cp), &cp); +- err = hci_req_run_skb(&req, read_local_amp_assoc_complete); +- if (err < 0) +- a2mp_send_getampassoc_rsp(hdev, A2MP_STATUS_INVALID_CTRL_ID); +-} +- +-void amp_read_loc_assoc(struct hci_dev *hdev, struct amp_mgr *mgr) +-{ +- struct hci_cp_read_local_amp_assoc cp; +- struct hci_request req; +- int err; +- +- memset(&hdev->loc_assoc, 0, sizeof(struct amp_assoc)); +- memset(&cp, 0, sizeof(cp)); +- +- cp.max_len = cpu_to_le16(hdev->amp_assoc_size); +- +- set_bit(READ_LOC_AMP_ASSOC, &mgr->state); +- hci_req_init(&req, hdev); +- hci_req_add(&req, HCI_OP_READ_LOCAL_AMP_ASSOC, sizeof(cp), &cp); +- err = hci_req_run_skb(&req, read_local_amp_assoc_complete); +- if (err < 0) +- a2mp_send_getampassoc_rsp(hdev, A2MP_STATUS_INVALID_CTRL_ID); +-} +- +-void amp_read_loc_assoc_final_data(struct hci_dev *hdev, +- struct hci_conn *hcon) +-{ +- struct hci_cp_read_local_amp_assoc cp; +- struct amp_mgr *mgr = hcon->amp_mgr; +- struct hci_request req; +- int err; +- +- if (!mgr) +- return; +- +- cp.phy_handle = hcon->handle; +- cp.len_so_far = cpu_to_le16(0); +- cp.max_len = cpu_to_le16(hdev->amp_assoc_size); +- +- set_bit(READ_LOC_AMP_ASSOC_FINAL, &mgr->state); +- +- /* Read Local AMP Assoc final link information data */ +- hci_req_init(&req, hdev); +- hci_req_add(&req, HCI_OP_READ_LOCAL_AMP_ASSOC, sizeof(cp), &cp); +- err = hci_req_run_skb(&req, read_local_amp_assoc_complete); +- if (err < 0) +- a2mp_send_getampassoc_rsp(hdev, A2MP_STATUS_INVALID_CTRL_ID); +-} +- +-static void write_remote_amp_assoc_complete(struct hci_dev *hdev, u8 status, +- u16 opcode, struct sk_buff *skb) +-{ +- struct hci_rp_write_remote_amp_assoc *rp = (void *)skb->data; +- +- BT_DBG("%s status 0x%2.2x phy_handle 0x%2.2x", +- hdev->name, rp->status, rp->phy_handle); +- +- if (rp->status) +- return; +- +- amp_write_rem_assoc_continue(hdev, rp->phy_handle); +-} +- +-/* Write AMP Assoc data fragments, returns true with last fragment written*/ +-static bool amp_write_rem_assoc_frag(struct hci_dev *hdev, +- struct hci_conn *hcon) +-{ +- struct hci_cp_write_remote_amp_assoc *cp; +- struct amp_mgr *mgr = hcon->amp_mgr; +- struct amp_ctrl *ctrl; +- struct hci_request req; +- u16 frag_len, len; +- +- ctrl = amp_ctrl_lookup(mgr, hcon->remote_id); +- if (!ctrl) +- return false; +- +- if (!ctrl->assoc_rem_len) { +- BT_DBG("all fragments are written"); +- ctrl->assoc_rem_len = ctrl->assoc_len; +- ctrl->assoc_len_so_far = 0; +- +- amp_ctrl_put(ctrl); +- return true; +- } +- +- frag_len = min_t(u16, 248, ctrl->assoc_rem_len); +- len = frag_len + sizeof(*cp); +- +- cp = kzalloc(len, GFP_KERNEL); +- if (!cp) { +- amp_ctrl_put(ctrl); +- return false; +- } +- +- BT_DBG("hcon %p ctrl %p frag_len %u assoc_len %u rem_len %u", +- hcon, ctrl, frag_len, ctrl->assoc_len, ctrl->assoc_rem_len); +- +- cp->phy_handle = hcon->handle; +- cp->len_so_far = cpu_to_le16(ctrl->assoc_len_so_far); +- cp->rem_len = cpu_to_le16(ctrl->assoc_rem_len); +- memcpy(cp->frag, ctrl->assoc, frag_len); +- +- ctrl->assoc_len_so_far += frag_len; +- ctrl->assoc_rem_len -= frag_len; +- +- amp_ctrl_put(ctrl); +- +- hci_req_init(&req, hdev); +- hci_req_add(&req, HCI_OP_WRITE_REMOTE_AMP_ASSOC, len, cp); +- hci_req_run_skb(&req, write_remote_amp_assoc_complete); +- +- kfree(cp); +- +- return false; +-} +- +-void amp_write_rem_assoc_continue(struct hci_dev *hdev, u8 handle) +-{ +- struct hci_conn *hcon; +- +- BT_DBG("%s phy handle 0x%2.2x", hdev->name, handle); +- +- hcon = hci_conn_hash_lookup_handle(hdev, handle); +- if (!hcon) +- return; +- +- /* Send A2MP create phylink rsp when all fragments are written */ +- if (amp_write_rem_assoc_frag(hdev, hcon)) +- a2mp_send_create_phy_link_rsp(hdev, 0); +-} +- +-void amp_write_remote_assoc(struct hci_dev *hdev, u8 handle) +-{ +- struct hci_conn *hcon; +- +- BT_DBG("%s phy handle 0x%2.2x", hdev->name, handle); +- +- hcon = hci_conn_hash_lookup_handle(hdev, handle); +- if (!hcon) +- return; +- +- BT_DBG("%s phy handle 0x%2.2x hcon %p", hdev->name, handle, hcon); +- +- amp_write_rem_assoc_frag(hdev, hcon); +-} +- +-static void create_phylink_complete(struct hci_dev *hdev, u8 status, +- u16 opcode) +-{ +- struct hci_cp_create_phy_link *cp; +- +- BT_DBG("%s status 0x%2.2x", hdev->name, status); +- +- cp = hci_sent_cmd_data(hdev, HCI_OP_CREATE_PHY_LINK); +- if (!cp) +- return; +- +- hci_dev_lock(hdev); +- +- if (status) { +- struct hci_conn *hcon; +- +- hcon = hci_conn_hash_lookup_handle(hdev, cp->phy_handle); +- if (hcon) +- hci_conn_del(hcon); +- } else { +- amp_write_remote_assoc(hdev, cp->phy_handle); +- } +- +- hci_dev_unlock(hdev); +-} +- +-void amp_create_phylink(struct hci_dev *hdev, struct amp_mgr *mgr, +- struct hci_conn *hcon) +-{ +- struct hci_cp_create_phy_link cp; +- struct hci_request req; +- +- cp.phy_handle = hcon->handle; +- +- BT_DBG("%s hcon %p phy handle 0x%2.2x", hdev->name, hcon, +- hcon->handle); +- +- if (phylink_gen_key(mgr->l2cap_conn->hcon, cp.key, &cp.key_len, +- &cp.key_type)) { +- BT_DBG("Cannot create link key"); +- return; +- } +- +- hci_req_init(&req, hdev); +- hci_req_add(&req, HCI_OP_CREATE_PHY_LINK, sizeof(cp), &cp); +- hci_req_run(&req, create_phylink_complete); +-} +- +-static void accept_phylink_complete(struct hci_dev *hdev, u8 status, +- u16 opcode) +-{ +- struct hci_cp_accept_phy_link *cp; +- +- BT_DBG("%s status 0x%2.2x", hdev->name, status); +- +- if (status) +- return; +- +- cp = hci_sent_cmd_data(hdev, HCI_OP_ACCEPT_PHY_LINK); +- if (!cp) +- return; +- +- amp_write_remote_assoc(hdev, cp->phy_handle); +-} +- +-void amp_accept_phylink(struct hci_dev *hdev, struct amp_mgr *mgr, +- struct hci_conn *hcon) +-{ +- struct hci_cp_accept_phy_link cp; +- struct hci_request req; +- +- cp.phy_handle = hcon->handle; +- +- BT_DBG("%s hcon %p phy handle 0x%2.2x", hdev->name, hcon, +- hcon->handle); +- +- if (phylink_gen_key(mgr->l2cap_conn->hcon, cp.key, &cp.key_len, +- &cp.key_type)) { +- BT_DBG("Cannot create link key"); +- return; +- } +- +- hci_req_init(&req, hdev); +- hci_req_add(&req, HCI_OP_ACCEPT_PHY_LINK, sizeof(cp), &cp); +- hci_req_run(&req, accept_phylink_complete); +-} +- +-void amp_physical_cfm(struct hci_conn *bredr_hcon, struct hci_conn *hs_hcon) +-{ +- struct hci_dev *bredr_hdev = hci_dev_hold(bredr_hcon->hdev); +- struct amp_mgr *mgr = hs_hcon->amp_mgr; +- struct l2cap_chan *bredr_chan; +- +- BT_DBG("bredr_hcon %p hs_hcon %p mgr %p", bredr_hcon, hs_hcon, mgr); +- +- if (!bredr_hdev || !mgr || !mgr->bredr_chan) +- return; +- +- bredr_chan = mgr->bredr_chan; +- +- l2cap_chan_lock(bredr_chan); +- +- set_bit(FLAG_EFS_ENABLE, &bredr_chan->flags); +- bredr_chan->remote_amp_id = hs_hcon->remote_id; +- bredr_chan->local_amp_id = hs_hcon->hdev->id; +- bredr_chan->hs_hcon = hs_hcon; +- bredr_chan->conn->mtu = hs_hcon->hdev->block_mtu; +- +- __l2cap_physical_cfm(bredr_chan, 0); +- +- l2cap_chan_unlock(bredr_chan); +- +- hci_dev_put(bredr_hdev); +-} +- +-void amp_create_logical_link(struct l2cap_chan *chan) +-{ +- struct hci_conn *hs_hcon = chan->hs_hcon; +- struct hci_cp_create_accept_logical_link cp; +- struct hci_dev *hdev; +- +- BT_DBG("chan %p hs_hcon %p dst %pMR", chan, hs_hcon, +- &chan->conn->hcon->dst); +- +- if (!hs_hcon) +- return; +- +- hdev = hci_dev_hold(chan->hs_hcon->hdev); +- if (!hdev) +- return; +- +- cp.phy_handle = hs_hcon->handle; +- +- cp.tx_flow_spec.id = chan->local_id; +- cp.tx_flow_spec.stype = chan->local_stype; +- cp.tx_flow_spec.msdu = cpu_to_le16(chan->local_msdu); +- cp.tx_flow_spec.sdu_itime = cpu_to_le32(chan->local_sdu_itime); +- cp.tx_flow_spec.acc_lat = cpu_to_le32(chan->local_acc_lat); +- cp.tx_flow_spec.flush_to = cpu_to_le32(chan->local_flush_to); +- +- cp.rx_flow_spec.id = chan->remote_id; +- cp.rx_flow_spec.stype = chan->remote_stype; +- cp.rx_flow_spec.msdu = cpu_to_le16(chan->remote_msdu); +- cp.rx_flow_spec.sdu_itime = cpu_to_le32(chan->remote_sdu_itime); +- cp.rx_flow_spec.acc_lat = cpu_to_le32(chan->remote_acc_lat); +- cp.rx_flow_spec.flush_to = cpu_to_le32(chan->remote_flush_to); +- +- if (hs_hcon->out) +- hci_send_cmd(hdev, HCI_OP_CREATE_LOGICAL_LINK, sizeof(cp), +- &cp); +- else +- hci_send_cmd(hdev, HCI_OP_ACCEPT_LOGICAL_LINK, sizeof(cp), +- &cp); +- +- hci_dev_put(hdev); +-} +- +-void amp_disconnect_logical_link(struct hci_chan *hchan) +-{ +- struct hci_conn *hcon = hchan->conn; +- struct hci_cp_disconn_logical_link cp; +- +- if (hcon->state != BT_CONNECTED) { +- BT_DBG("hchan %p not connected", hchan); +- return; +- } +- +- cp.log_handle = cpu_to_le16(hchan->handle); +- hci_send_cmd(hcon->hdev, HCI_OP_DISCONN_LOGICAL_LINK, sizeof(cp), &cp); +-} +- +-void amp_destroy_logical_link(struct hci_chan *hchan, u8 reason) +-{ +- BT_DBG("hchan %p", hchan); +- +- hci_chan_del(hchan); +-} +--- a/net/bluetooth/amp.h ++++ /dev/null +@@ -1,61 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/* +- Copyright (c) 2011,2012 Intel Corp. +- +-*/ +- +-#ifndef __AMP_H +-#define __AMP_H +- +-struct amp_ctrl { +- struct list_head list; +- struct kref kref; +- __u8 id; +- __u16 assoc_len_so_far; +- __u16 assoc_rem_len; +- __u16 assoc_len; +- __u8 *assoc; +-}; +- +-int amp_ctrl_put(struct amp_ctrl *ctrl); +-void amp_ctrl_get(struct amp_ctrl *ctrl); +-struct amp_ctrl *amp_ctrl_add(struct amp_mgr *mgr, u8 id); +-struct amp_ctrl *amp_ctrl_lookup(struct amp_mgr *mgr, u8 id); +-void amp_ctrl_list_flush(struct amp_mgr *mgr); +- +-struct hci_conn *phylink_add(struct hci_dev *hdev, struct amp_mgr *mgr, +- u8 remote_id, bool out); +- +-int phylink_gen_key(struct hci_conn *hcon, u8 *data, u8 *len, u8 *type); +- +-void amp_read_loc_info(struct hci_dev *hdev, struct amp_mgr *mgr); +-void amp_read_loc_assoc_frag(struct hci_dev *hdev, u8 phy_handle); +-void amp_read_loc_assoc(struct hci_dev *hdev, struct amp_mgr *mgr); +-void amp_read_loc_assoc_final_data(struct hci_dev *hdev, +- struct hci_conn *hcon); +-void amp_create_phylink(struct hci_dev *hdev, struct amp_mgr *mgr, +- struct hci_conn *hcon); +-void amp_accept_phylink(struct hci_dev *hdev, struct amp_mgr *mgr, +- struct hci_conn *hcon); +- +-#if IS_ENABLED(CONFIG_BT_HS) +-void amp_create_logical_link(struct l2cap_chan *chan); +-void amp_disconnect_logical_link(struct hci_chan *hchan); +-#else +-static inline void amp_create_logical_link(struct l2cap_chan *chan) +-{ +-} +- +-static inline void amp_disconnect_logical_link(struct hci_chan *hchan) +-{ +-} +-#endif +- +-void amp_write_remote_assoc(struct hci_dev *hdev, u8 handle); +-void amp_write_rem_assoc_continue(struct hci_dev *hdev, u8 handle); +-void amp_physical_cfm(struct hci_conn *bredr_hcon, struct hci_conn *hs_hcon); +-void amp_create_logical_link(struct l2cap_chan *chan); +-void amp_disconnect_logical_link(struct hci_chan *hchan); +-void amp_destroy_logical_link(struct hci_chan *hchan, u8 reason); +- +-#endif /* __AMP_H */ +--- a/net/bluetooth/hci_conn.c ++++ b/net/bluetooth/hci_conn.c +@@ -35,7 +35,6 @@ + + #include "hci_request.h" + #include "smp.h" +-#include "a2mp.h" + #include "eir.h" + + struct sco_param { +@@ -1162,9 +1161,6 @@ int hci_conn_del(struct hci_conn *conn) + } + } + +- if (conn->amp_mgr) +- amp_mgr_put(conn->amp_mgr); +- + skb_queue_purge(&conn->data_q); + + /* Remove the connection from the list and cleanup its remaining +--- a/net/bluetooth/hci_event.c ++++ b/net/bluetooth/hci_event.c +@@ -35,8 +35,6 @@ + #include "hci_request.h" + #include "hci_debugfs.h" + #include "hci_codec.h" +-#include "a2mp.h" +-#include "amp.h" + #include "smp.h" + #include "msft.h" + #include "eir.h" +--- a/net/bluetooth/l2cap_core.c ++++ b/net/bluetooth/l2cap_core.c +@@ -39,8 +39,6 @@ + #include + + #include "smp.h" +-#include "a2mp.h" +-#include "amp.h" + + #define LE_FLOWCTL_MAX_CREDITS 65535 + +@@ -167,24 +165,6 @@ static struct l2cap_chan *__l2cap_get_ch + return NULL; + } + +-static struct l2cap_chan *l2cap_get_chan_by_ident(struct l2cap_conn *conn, +- u8 ident) +-{ +- struct l2cap_chan *c; +- +- mutex_lock(&conn->chan_lock); +- c = __l2cap_get_chan_by_ident(conn, ident); +- if (c) { +- /* Only lock if chan reference is not 0 */ +- c = l2cap_chan_hold_unless_zero(c); +- if (c) +- l2cap_chan_lock(c); +- } +- mutex_unlock(&conn->chan_lock); +- +- return c; +-} +- + static struct l2cap_chan *__l2cap_global_chan_by_addr(__le16 psm, bdaddr_t *src, + u8 src_type) + { +@@ -682,7 +662,6 @@ void l2cap_chan_del(struct l2cap_chan *c + chan->ops->teardown(chan, err); + + if (conn) { +- struct amp_mgr *mgr = conn->hcon->amp_mgr; + /* Delete from channel list */ + list_del(&chan->list); + +@@ -697,16 +676,6 @@ void l2cap_chan_del(struct l2cap_chan *c + if (chan->chan_type != L2CAP_CHAN_FIXED || + test_bit(FLAG_HOLD_HCI_CONN, &chan->flags)) + hci_conn_drop(conn->hcon); +- +- if (mgr && mgr->bredr_chan == chan) +- mgr->bredr_chan = NULL; +- } +- +- if (chan->hs_hchan) { +- struct hci_chan *hs_hchan = chan->hs_hchan; +- +- BT_DBG("chan %p disconnect hs_hchan %p", chan, hs_hchan); +- amp_disconnect_logical_link(hs_hchan); + } + + if (test_bit(CONF_NOT_COMPLETE, &chan->conf_state)) +@@ -1008,12 +977,6 @@ static void l2cap_send_cmd(struct l2cap_ + hci_send_acl(conn->hchan, skb, flags); + } + +-static bool __chan_is_moving(struct l2cap_chan *chan) +-{ +- return chan->move_state != L2CAP_MOVE_STABLE && +- chan->move_state != L2CAP_MOVE_WAIT_PREPARE; +-} +- + static void l2cap_do_send(struct l2cap_chan *chan, struct sk_buff *skb) + { + struct hci_conn *hcon = chan->conn->hcon; +@@ -1022,15 +985,6 @@ static void l2cap_do_send(struct l2cap_c + BT_DBG("chan %p, skb %p len %d priority %u", chan, skb, skb->len, + skb->priority); + +- if (chan->hs_hcon && !__chan_is_moving(chan)) { +- if (chan->hs_hchan) +- hci_send_acl(chan->hs_hchan, skb, ACL_COMPLETE); +- else +- kfree_skb(skb); +- +- return; +- } +- + /* Use NO_FLUSH for LE links (where this is the only option) or + * if the BR/EDR link supports it and flushing has not been + * explicitly requested (through FLAG_FLUSHABLE). +@@ -1211,9 +1165,6 @@ static void l2cap_send_sframe(struct l2c + if (!control->sframe) + return; + +- if (__chan_is_moving(chan)) +- return; +- + if (test_and_clear_bit(CONN_SEND_FBIT, &chan->conn_state) && + !control->poll) + control->final = 1; +@@ -1268,40 +1219,6 @@ static inline int __l2cap_no_conn_pendin + return !test_bit(CONF_CONNECT_PEND, &chan->conf_state); + } + +-static bool __amp_capable(struct l2cap_chan *chan) +-{ +- struct l2cap_conn *conn = chan->conn; +- struct hci_dev *hdev; +- bool amp_available = false; +- +- if (!(conn->local_fixed_chan & L2CAP_FC_A2MP)) +- return false; +- +- if (!(conn->remote_fixed_chan & L2CAP_FC_A2MP)) +- return false; +- +- read_lock(&hci_dev_list_lock); +- list_for_each_entry(hdev, &hci_dev_list, list) { +- if (hdev->amp_type != AMP_TYPE_BREDR && +- test_bit(HCI_UP, &hdev->flags)) { +- amp_available = true; +- break; +- } +- } +- read_unlock(&hci_dev_list_lock); +- +- if (chan->chan_policy == BT_CHANNEL_POLICY_AMP_PREFERRED) +- return amp_available; +- +- return false; +-} +- +-static bool l2cap_check_efs(struct l2cap_chan *chan) +-{ +- /* Check EFS parameters */ +- return true; +-} +- + void l2cap_send_conn_req(struct l2cap_chan *chan) + { + struct l2cap_conn *conn = chan->conn; +@@ -1317,76 +1234,6 @@ void l2cap_send_conn_req(struct l2cap_ch + l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_REQ, sizeof(req), &req); + } + +-static void l2cap_send_create_chan_req(struct l2cap_chan *chan, u8 amp_id) +-{ +- struct l2cap_create_chan_req req; +- req.scid = cpu_to_le16(chan->scid); +- req.psm = chan->psm; +- req.amp_id = amp_id; +- +- chan->ident = l2cap_get_ident(chan->conn); +- +- l2cap_send_cmd(chan->conn, chan->ident, L2CAP_CREATE_CHAN_REQ, +- sizeof(req), &req); +-} +- +-static void l2cap_move_setup(struct l2cap_chan *chan) +-{ +- struct sk_buff *skb; +- +- BT_DBG("chan %p", chan); +- +- if (chan->mode != L2CAP_MODE_ERTM) +- return; +- +- __clear_retrans_timer(chan); +- __clear_monitor_timer(chan); +- __clear_ack_timer(chan); +- +- chan->retry_count = 0; +- skb_queue_walk(&chan->tx_q, skb) { +- if (bt_cb(skb)->l2cap.retries) +- bt_cb(skb)->l2cap.retries = 1; +- else +- break; +- } +- +- chan->expected_tx_seq = chan->buffer_seq; +- +- clear_bit(CONN_REJ_ACT, &chan->conn_state); +- clear_bit(CONN_SREJ_ACT, &chan->conn_state); +- l2cap_seq_list_clear(&chan->retrans_list); +- l2cap_seq_list_clear(&chan->srej_list); +- skb_queue_purge(&chan->srej_q); +- +- chan->tx_state = L2CAP_TX_STATE_XMIT; +- chan->rx_state = L2CAP_RX_STATE_MOVE; +- +- set_bit(CONN_REMOTE_BUSY, &chan->conn_state); +-} +- +-static void l2cap_move_done(struct l2cap_chan *chan) +-{ +- u8 move_role = chan->move_role; +- BT_DBG("chan %p", chan); +- +- chan->move_state = L2CAP_MOVE_STABLE; +- chan->move_role = L2CAP_MOVE_ROLE_NONE; +- +- if (chan->mode != L2CAP_MODE_ERTM) +- return; +- +- switch (move_role) { +- case L2CAP_MOVE_ROLE_INITIATOR: +- l2cap_tx(chan, NULL, NULL, L2CAP_EV_EXPLICIT_POLL); +- chan->rx_state = L2CAP_RX_STATE_WAIT_F; +- break; +- case L2CAP_MOVE_ROLE_RESPONDER: +- chan->rx_state = L2CAP_RX_STATE_WAIT_P; +- break; +- } +-} +- + static void l2cap_chan_ready(struct l2cap_chan *chan) + { + /* The channel may have already been flagged as connected in +@@ -1536,10 +1383,7 @@ static void l2cap_le_start(struct l2cap_ + + static void l2cap_start_connection(struct l2cap_chan *chan) + { +- if (__amp_capable(chan)) { +- BT_DBG("chan %p AMP capable: discover AMPs", chan); +- a2mp_discover_amp(chan); +- } else if (chan->conn->hcon->type == LE_LINK) { ++ if (chan->conn->hcon->type == LE_LINK) { + l2cap_le_start(chan); + } else { + l2cap_send_conn_req(chan); +@@ -1643,11 +1487,6 @@ static void l2cap_send_disconn_req(struc + __clear_ack_timer(chan); + } + +- if (chan->scid == L2CAP_CID_A2MP) { +- l2cap_state_change(chan, BT_DISCONN); +- return; +- } +- + req.dcid = cpu_to_le16(chan->dcid); + req.scid = cpu_to_le16(chan->scid); + l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_DISCONN_REQ, +@@ -1786,11 +1625,6 @@ static void l2cap_conn_ready(struct l2ca + + l2cap_chan_lock(chan); + +- if (chan->scid == L2CAP_CID_A2MP) { +- l2cap_chan_unlock(chan); +- continue; +- } +- + if (hcon->type == LE_LINK) { + l2cap_le_start(chan); + } else if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) { +@@ -2100,9 +1934,6 @@ static void l2cap_streaming_send(struct + + BT_DBG("chan %p, skbs %p", chan, skbs); + +- if (__chan_is_moving(chan)) +- return; +- + skb_queue_splice_tail_init(skbs, &chan->tx_q); + + while (!skb_queue_empty(&chan->tx_q)) { +@@ -2145,9 +1976,6 @@ static int l2cap_ertm_send(struct l2cap_ + if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state)) + return 0; + +- if (__chan_is_moving(chan)) +- return 0; +- + while (chan->tx_send_head && + chan->unacked_frames < chan->remote_tx_win && + chan->tx_state == L2CAP_TX_STATE_XMIT) { +@@ -2213,9 +2041,6 @@ static void l2cap_ertm_resend(struct l2c + if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state)) + return; + +- if (__chan_is_moving(chan)) +- return; +- + while (chan->retrans_list.head != L2CAP_SEQ_LIST_CLEAR) { + seq = l2cap_seq_list_pop(&chan->retrans_list); + +@@ -2555,8 +2380,7 @@ static int l2cap_segment_sdu(struct l2ca + pdu_len = chan->conn->mtu; + + /* Constrain PDU size for BR/EDR connections */ +- if (!chan->hs_hcon) +- pdu_len = min_t(size_t, pdu_len, L2CAP_BREDR_MAX_PAYLOAD); ++ pdu_len = min_t(size_t, pdu_len, L2CAP_BREDR_MAX_PAYLOAD); + + /* Adjust for largest possible L2CAP overhead. */ + if (chan->fcs) +@@ -3334,11 +3158,6 @@ int l2cap_ertm_init(struct l2cap_chan *c + + skb_queue_head_init(&chan->tx_q); + +- chan->local_amp_id = AMP_ID_BREDR; +- chan->move_id = AMP_ID_BREDR; +- chan->move_state = L2CAP_MOVE_STABLE; +- chan->move_role = L2CAP_MOVE_ROLE_NONE; +- + if (chan->mode != L2CAP_MODE_ERTM) + return 0; + +@@ -3373,52 +3192,19 @@ static inline __u8 l2cap_select_mode(__u + + static inline bool __l2cap_ews_supported(struct l2cap_conn *conn) + { +- return ((conn->local_fixed_chan & L2CAP_FC_A2MP) && +- (conn->feat_mask & L2CAP_FEAT_EXT_WINDOW)); ++ return (conn->feat_mask & L2CAP_FEAT_EXT_WINDOW); + } + + static inline bool __l2cap_efs_supported(struct l2cap_conn *conn) + { +- return ((conn->local_fixed_chan & L2CAP_FC_A2MP) && +- (conn->feat_mask & L2CAP_FEAT_EXT_FLOW)); ++ return (conn->feat_mask & L2CAP_FEAT_EXT_FLOW); + } + + static void __l2cap_set_ertm_timeouts(struct l2cap_chan *chan, + struct l2cap_conf_rfc *rfc) + { +- if (chan->local_amp_id != AMP_ID_BREDR && chan->hs_hcon) { +- u64 ertm_to = chan->hs_hcon->hdev->amp_be_flush_to; +- +- /* Class 1 devices have must have ERTM timeouts +- * exceeding the Link Supervision Timeout. The +- * default Link Supervision Timeout for AMP +- * controllers is 10 seconds. +- * +- * Class 1 devices use 0xffffffff for their +- * best-effort flush timeout, so the clamping logic +- * will result in a timeout that meets the above +- * requirement. ERTM timeouts are 16-bit values, so +- * the maximum timeout is 65.535 seconds. +- */ +- +- /* Convert timeout to milliseconds and round */ +- ertm_to = DIV_ROUND_UP_ULL(ertm_to, 1000); +- +- /* This is the recommended formula for class 2 devices +- * that start ERTM timers when packets are sent to the +- * controller. +- */ +- ertm_to = 3 * ertm_to + 500; +- +- if (ertm_to > 0xffff) +- ertm_to = 0xffff; +- +- rfc->retrans_timeout = cpu_to_le16((u16) ertm_to); +- rfc->monitor_timeout = rfc->retrans_timeout; +- } else { +- rfc->retrans_timeout = cpu_to_le16(L2CAP_DEFAULT_RETRANS_TO); +- rfc->monitor_timeout = cpu_to_le16(L2CAP_DEFAULT_MONITOR_TO); +- } ++ rfc->retrans_timeout = cpu_to_le16(L2CAP_DEFAULT_RETRANS_TO); ++ rfc->monitor_timeout = cpu_to_le16(L2CAP_DEFAULT_MONITOR_TO); + } + + static inline void l2cap_txwin_setup(struct l2cap_chan *chan) +@@ -3673,13 +3459,7 @@ static int l2cap_parse_conf_req(struct l + case L2CAP_CONF_EWS: + if (olen != 2) + break; +- if (!(chan->conn->local_fixed_chan & L2CAP_FC_A2MP)) +- return -ECONNREFUSED; +- set_bit(FLAG_EXT_CTRL, &chan->flags); +- set_bit(CONF_EWS_RECV, &chan->conf_state); +- chan->tx_win_max = L2CAP_DEFAULT_EXT_WINDOW; +- chan->remote_tx_win = val; +- break; ++ return -ECONNREFUSED; + + default: + if (hint) +@@ -4107,11 +3887,7 @@ void __l2cap_connect_rsp_defer(struct l2 + rsp.dcid = cpu_to_le16(chan->scid); + rsp.result = cpu_to_le16(L2CAP_CR_SUCCESS); + rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO); +- +- if (chan->hs_hcon) +- rsp_code = L2CAP_CREATE_CHAN_RSP; +- else +- rsp_code = L2CAP_CONN_RSP; ++ rsp_code = L2CAP_CONN_RSP; + + BT_DBG("chan %p rsp_code %u", chan, rsp_code); + +@@ -4274,7 +4050,6 @@ static struct l2cap_chan *l2cap_connect( + chan->dst_type = bdaddr_dst_type(conn->hcon); + chan->psm = psm; + chan->dcid = scid; +- chan->local_amp_id = amp_id; + + __l2cap_chan_add(conn, chan); + +@@ -4593,10 +4368,7 @@ static inline int l2cap_config_req(struc + /* check compatibility */ + + /* Send rsp for BR/EDR channel */ +- if (!chan->hs_hcon) +- l2cap_send_efs_conf_rsp(chan, rsp, cmd->ident, flags); +- else +- chan->ident = cmd->ident; ++ l2cap_send_efs_conf_rsp(chan, rsp, cmd->ident, flags); + } + + unlock: +@@ -4648,15 +4420,7 @@ static inline int l2cap_config_rsp(struc + goto done; + } + +- if (!chan->hs_hcon) { +- l2cap_send_efs_conf_rsp(chan, buf, cmd->ident, +- 0); +- } else { +- if (l2cap_check_efs(chan)) { +- amp_create_logical_link(chan); +- chan->ident = cmd->ident; +- } +- } ++ l2cap_send_efs_conf_rsp(chan, buf, cmd->ident, 0); + } + goto done; + +@@ -4827,9 +4591,6 @@ static inline int l2cap_information_req( + if (!disable_ertm) + feat_mask |= L2CAP_FEAT_ERTM | L2CAP_FEAT_STREAMING + | L2CAP_FEAT_FCS; +- if (conn->local_fixed_chan & L2CAP_FC_A2MP) +- feat_mask |= L2CAP_FEAT_EXT_FLOW +- | L2CAP_FEAT_EXT_WINDOW; + + put_unaligned_le32(feat_mask, rsp->data); + l2cap_send_cmd(conn, cmd->ident, L2CAP_INFO_RSP, sizeof(buf), +@@ -4920,751 +4681,6 @@ static inline int l2cap_information_rsp( + return 0; + } + +-static int l2cap_create_channel_req(struct l2cap_conn *conn, +- struct l2cap_cmd_hdr *cmd, +- u16 cmd_len, void *data) +-{ +- struct l2cap_create_chan_req *req = data; +- struct l2cap_create_chan_rsp rsp; +- struct l2cap_chan *chan; +- struct hci_dev *hdev; +- u16 psm, scid; +- +- if (cmd_len != sizeof(*req)) +- return -EPROTO; +- +- if (!(conn->local_fixed_chan & L2CAP_FC_A2MP)) +- return -EINVAL; +- +- psm = le16_to_cpu(req->psm); +- scid = le16_to_cpu(req->scid); +- +- BT_DBG("psm 0x%2.2x, scid 0x%4.4x, amp_id %d", psm, scid, req->amp_id); +- +- /* For controller id 0 make BR/EDR connection */ +- if (req->amp_id == AMP_ID_BREDR) { +- l2cap_connect(conn, cmd, data, L2CAP_CREATE_CHAN_RSP, +- req->amp_id); +- return 0; +- } +- +- /* Validate AMP controller id */ +- hdev = hci_dev_get(req->amp_id); +- if (!hdev) +- goto error; +- +- if (hdev->dev_type != HCI_AMP || !test_bit(HCI_UP, &hdev->flags)) { +- hci_dev_put(hdev); +- goto error; +- } +- +- chan = l2cap_connect(conn, cmd, data, L2CAP_CREATE_CHAN_RSP, +- req->amp_id); +- if (chan) { +- struct amp_mgr *mgr = conn->hcon->amp_mgr; +- struct hci_conn *hs_hcon; +- +- hs_hcon = hci_conn_hash_lookup_ba(hdev, AMP_LINK, +- &conn->hcon->dst); +- if (!hs_hcon) { +- hci_dev_put(hdev); +- cmd_reject_invalid_cid(conn, cmd->ident, chan->scid, +- chan->dcid); +- return 0; +- } +- +- BT_DBG("mgr %p bredr_chan %p hs_hcon %p", mgr, chan, hs_hcon); +- +- mgr->bredr_chan = chan; +- chan->hs_hcon = hs_hcon; +- chan->fcs = L2CAP_FCS_NONE; +- conn->mtu = hdev->block_mtu; +- } +- +- hci_dev_put(hdev); +- +- return 0; +- +-error: +- rsp.dcid = 0; +- rsp.scid = cpu_to_le16(scid); +- rsp.result = cpu_to_le16(L2CAP_CR_BAD_AMP); +- rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO); +- +- l2cap_send_cmd(conn, cmd->ident, L2CAP_CREATE_CHAN_RSP, +- sizeof(rsp), &rsp); +- +- return 0; +-} +- +-static void l2cap_send_move_chan_req(struct l2cap_chan *chan, u8 dest_amp_id) +-{ +- struct l2cap_move_chan_req req; +- u8 ident; +- +- BT_DBG("chan %p, dest_amp_id %d", chan, dest_amp_id); +- +- ident = l2cap_get_ident(chan->conn); +- chan->ident = ident; +- +- req.icid = cpu_to_le16(chan->scid); +- req.dest_amp_id = dest_amp_id; +- +- l2cap_send_cmd(chan->conn, ident, L2CAP_MOVE_CHAN_REQ, sizeof(req), +- &req); +- +- __set_chan_timer(chan, L2CAP_MOVE_TIMEOUT); +-} +- +-static void l2cap_send_move_chan_rsp(struct l2cap_chan *chan, u16 result) +-{ +- struct l2cap_move_chan_rsp rsp; +- +- BT_DBG("chan %p, result 0x%4.4x", chan, result); +- +- rsp.icid = cpu_to_le16(chan->dcid); +- rsp.result = cpu_to_le16(result); +- +- l2cap_send_cmd(chan->conn, chan->ident, L2CAP_MOVE_CHAN_RSP, +- sizeof(rsp), &rsp); +-} +- +-static void l2cap_send_move_chan_cfm(struct l2cap_chan *chan, u16 result) +-{ +- struct l2cap_move_chan_cfm cfm; +- +- BT_DBG("chan %p, result 0x%4.4x", chan, result); +- +- chan->ident = l2cap_get_ident(chan->conn); +- +- cfm.icid = cpu_to_le16(chan->scid); +- cfm.result = cpu_to_le16(result); +- +- l2cap_send_cmd(chan->conn, chan->ident, L2CAP_MOVE_CHAN_CFM, +- sizeof(cfm), &cfm); +- +- __set_chan_timer(chan, L2CAP_MOVE_TIMEOUT); +-} +- +-static void l2cap_send_move_chan_cfm_icid(struct l2cap_conn *conn, u16 icid) +-{ +- struct l2cap_move_chan_cfm cfm; +- +- BT_DBG("conn %p, icid 0x%4.4x", conn, icid); +- +- cfm.icid = cpu_to_le16(icid); +- cfm.result = cpu_to_le16(L2CAP_MC_UNCONFIRMED); +- +- l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_MOVE_CHAN_CFM, +- sizeof(cfm), &cfm); +-} +- +-static void l2cap_send_move_chan_cfm_rsp(struct l2cap_conn *conn, u8 ident, +- u16 icid) +-{ +- struct l2cap_move_chan_cfm_rsp rsp; +- +- BT_DBG("icid 0x%4.4x", icid); +- +- rsp.icid = cpu_to_le16(icid); +- l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_CFM_RSP, sizeof(rsp), &rsp); +-} +- +-static void __release_logical_link(struct l2cap_chan *chan) +-{ +- chan->hs_hchan = NULL; +- chan->hs_hcon = NULL; +- +- /* Placeholder - release the logical link */ +-} +- +-static void l2cap_logical_fail(struct l2cap_chan *chan) +-{ +- /* Logical link setup failed */ +- if (chan->state != BT_CONNECTED) { +- /* Create channel failure, disconnect */ +- l2cap_send_disconn_req(chan, ECONNRESET); +- return; +- } +- +- switch (chan->move_role) { +- case L2CAP_MOVE_ROLE_RESPONDER: +- l2cap_move_done(chan); +- l2cap_send_move_chan_rsp(chan, L2CAP_MR_NOT_SUPP); +- break; +- case L2CAP_MOVE_ROLE_INITIATOR: +- if (chan->move_state == L2CAP_MOVE_WAIT_LOGICAL_COMP || +- chan->move_state == L2CAP_MOVE_WAIT_LOGICAL_CFM) { +- /* Remote has only sent pending or +- * success responses, clean up +- */ +- l2cap_move_done(chan); +- } +- +- /* Other amp move states imply that the move +- * has already aborted +- */ +- l2cap_send_move_chan_cfm(chan, L2CAP_MC_UNCONFIRMED); +- break; +- } +-} +- +-static void l2cap_logical_finish_create(struct l2cap_chan *chan, +- struct hci_chan *hchan) +-{ +- struct l2cap_conf_rsp rsp; +- +- chan->hs_hchan = hchan; +- chan->hs_hcon->l2cap_data = chan->conn; +- +- l2cap_send_efs_conf_rsp(chan, &rsp, chan->ident, 0); +- +- if (test_bit(CONF_INPUT_DONE, &chan->conf_state)) { +- int err; +- +- set_default_fcs(chan); +- +- err = l2cap_ertm_init(chan); +- if (err < 0) +- l2cap_send_disconn_req(chan, -err); +- else +- l2cap_chan_ready(chan); +- } +-} +- +-static void l2cap_logical_finish_move(struct l2cap_chan *chan, +- struct hci_chan *hchan) +-{ +- chan->hs_hcon = hchan->conn; +- chan->hs_hcon->l2cap_data = chan->conn; +- +- BT_DBG("move_state %d", chan->move_state); +- +- switch (chan->move_state) { +- case L2CAP_MOVE_WAIT_LOGICAL_COMP: +- /* Move confirm will be sent after a success +- * response is received +- */ +- chan->move_state = L2CAP_MOVE_WAIT_RSP_SUCCESS; +- break; +- case L2CAP_MOVE_WAIT_LOGICAL_CFM: +- if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) { +- chan->move_state = L2CAP_MOVE_WAIT_LOCAL_BUSY; +- } else if (chan->move_role == L2CAP_MOVE_ROLE_INITIATOR) { +- chan->move_state = L2CAP_MOVE_WAIT_CONFIRM_RSP; +- l2cap_send_move_chan_cfm(chan, L2CAP_MC_CONFIRMED); +- } else if (chan->move_role == L2CAP_MOVE_ROLE_RESPONDER) { +- chan->move_state = L2CAP_MOVE_WAIT_CONFIRM; +- l2cap_send_move_chan_rsp(chan, L2CAP_MR_SUCCESS); +- } +- break; +- default: +- /* Move was not in expected state, free the channel */ +- __release_logical_link(chan); +- +- chan->move_state = L2CAP_MOVE_STABLE; +- } +-} +- +-/* Call with chan locked */ +-void l2cap_logical_cfm(struct l2cap_chan *chan, struct hci_chan *hchan, +- u8 status) +-{ +- BT_DBG("chan %p, hchan %p, status %d", chan, hchan, status); +- +- if (status) { +- l2cap_logical_fail(chan); +- __release_logical_link(chan); +- return; +- } +- +- if (chan->state != BT_CONNECTED) { +- /* Ignore logical link if channel is on BR/EDR */ +- if (chan->local_amp_id != AMP_ID_BREDR) +- l2cap_logical_finish_create(chan, hchan); +- } else { +- l2cap_logical_finish_move(chan, hchan); +- } +-} +- +-void l2cap_move_start(struct l2cap_chan *chan) +-{ +- BT_DBG("chan %p", chan); +- +- if (chan->local_amp_id == AMP_ID_BREDR) { +- if (chan->chan_policy != BT_CHANNEL_POLICY_AMP_PREFERRED) +- return; +- chan->move_role = L2CAP_MOVE_ROLE_INITIATOR; +- chan->move_state = L2CAP_MOVE_WAIT_PREPARE; +- /* Placeholder - start physical link setup */ +- } else { +- chan->move_role = L2CAP_MOVE_ROLE_INITIATOR; +- chan->move_state = L2CAP_MOVE_WAIT_RSP_SUCCESS; +- chan->move_id = 0; +- l2cap_move_setup(chan); +- l2cap_send_move_chan_req(chan, 0); +- } +-} +- +-static void l2cap_do_create(struct l2cap_chan *chan, int result, +- u8 local_amp_id, u8 remote_amp_id) +-{ +- BT_DBG("chan %p state %s %u -> %u", chan, state_to_string(chan->state), +- local_amp_id, remote_amp_id); +- +- chan->fcs = L2CAP_FCS_NONE; +- +- /* Outgoing channel on AMP */ +- if (chan->state == BT_CONNECT) { +- if (result == L2CAP_CR_SUCCESS) { +- chan->local_amp_id = local_amp_id; +- l2cap_send_create_chan_req(chan, remote_amp_id); +- } else { +- /* Revert to BR/EDR connect */ +- l2cap_send_conn_req(chan); +- } +- +- return; +- } +- +- /* Incoming channel on AMP */ +- if (__l2cap_no_conn_pending(chan)) { +- struct l2cap_conn_rsp rsp; +- char buf[128]; +- rsp.scid = cpu_to_le16(chan->dcid); +- rsp.dcid = cpu_to_le16(chan->scid); +- +- if (result == L2CAP_CR_SUCCESS) { +- /* Send successful response */ +- rsp.result = cpu_to_le16(L2CAP_CR_SUCCESS); +- rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO); +- } else { +- /* Send negative response */ +- rsp.result = cpu_to_le16(L2CAP_CR_NO_MEM); +- rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO); +- } +- +- l2cap_send_cmd(chan->conn, chan->ident, L2CAP_CREATE_CHAN_RSP, +- sizeof(rsp), &rsp); +- +- if (result == L2CAP_CR_SUCCESS) { +- l2cap_state_change(chan, BT_CONFIG); +- set_bit(CONF_REQ_SENT, &chan->conf_state); +- l2cap_send_cmd(chan->conn, l2cap_get_ident(chan->conn), +- L2CAP_CONF_REQ, +- l2cap_build_conf_req(chan, buf, sizeof(buf)), buf); +- chan->num_conf_req++; +- } +- } +-} +- +-static void l2cap_do_move_initiate(struct l2cap_chan *chan, u8 local_amp_id, +- u8 remote_amp_id) +-{ +- l2cap_move_setup(chan); +- chan->move_id = local_amp_id; +- chan->move_state = L2CAP_MOVE_WAIT_RSP; +- +- l2cap_send_move_chan_req(chan, remote_amp_id); +-} +- +-static void l2cap_do_move_respond(struct l2cap_chan *chan, int result) +-{ +- struct hci_chan *hchan = NULL; +- +- /* Placeholder - get hci_chan for logical link */ +- +- if (hchan) { +- if (hchan->state == BT_CONNECTED) { +- /* Logical link is ready to go */ +- chan->hs_hcon = hchan->conn; +- chan->hs_hcon->l2cap_data = chan->conn; +- chan->move_state = L2CAP_MOVE_WAIT_CONFIRM; +- l2cap_send_move_chan_rsp(chan, L2CAP_MR_SUCCESS); +- +- l2cap_logical_cfm(chan, hchan, L2CAP_MR_SUCCESS); +- } else { +- /* Wait for logical link to be ready */ +- chan->move_state = L2CAP_MOVE_WAIT_LOGICAL_CFM; +- } +- } else { +- /* Logical link not available */ +- l2cap_send_move_chan_rsp(chan, L2CAP_MR_NOT_ALLOWED); +- } +-} +- +-static void l2cap_do_move_cancel(struct l2cap_chan *chan, int result) +-{ +- if (chan->move_role == L2CAP_MOVE_ROLE_RESPONDER) { +- u8 rsp_result; +- if (result == -EINVAL) +- rsp_result = L2CAP_MR_BAD_ID; +- else +- rsp_result = L2CAP_MR_NOT_ALLOWED; +- +- l2cap_send_move_chan_rsp(chan, rsp_result); +- } +- +- chan->move_role = L2CAP_MOVE_ROLE_NONE; +- chan->move_state = L2CAP_MOVE_STABLE; +- +- /* Restart data transmission */ +- l2cap_ertm_send(chan); +-} +- +-/* Invoke with locked chan */ +-void __l2cap_physical_cfm(struct l2cap_chan *chan, int result) +-{ +- u8 local_amp_id = chan->local_amp_id; +- u8 remote_amp_id = chan->remote_amp_id; +- +- BT_DBG("chan %p, result %d, local_amp_id %d, remote_amp_id %d", +- chan, result, local_amp_id, remote_amp_id); +- +- if (chan->state == BT_DISCONN || chan->state == BT_CLOSED) +- return; +- +- if (chan->state != BT_CONNECTED) { +- l2cap_do_create(chan, result, local_amp_id, remote_amp_id); +- } else if (result != L2CAP_MR_SUCCESS) { +- l2cap_do_move_cancel(chan, result); +- } else { +- switch (chan->move_role) { +- case L2CAP_MOVE_ROLE_INITIATOR: +- l2cap_do_move_initiate(chan, local_amp_id, +- remote_amp_id); +- break; +- case L2CAP_MOVE_ROLE_RESPONDER: +- l2cap_do_move_respond(chan, result); +- break; +- default: +- l2cap_do_move_cancel(chan, result); +- break; +- } +- } +-} +- +-static inline int l2cap_move_channel_req(struct l2cap_conn *conn, +- struct l2cap_cmd_hdr *cmd, +- u16 cmd_len, void *data) +-{ +- struct l2cap_move_chan_req *req = data; +- struct l2cap_move_chan_rsp rsp; +- struct l2cap_chan *chan; +- u16 icid = 0; +- u16 result = L2CAP_MR_NOT_ALLOWED; +- +- if (cmd_len != sizeof(*req)) +- return -EPROTO; +- +- icid = le16_to_cpu(req->icid); +- +- BT_DBG("icid 0x%4.4x, dest_amp_id %d", icid, req->dest_amp_id); +- +- if (!(conn->local_fixed_chan & L2CAP_FC_A2MP)) +- return -EINVAL; +- +- chan = l2cap_get_chan_by_dcid(conn, icid); +- if (!chan) { +- rsp.icid = cpu_to_le16(icid); +- rsp.result = cpu_to_le16(L2CAP_MR_NOT_ALLOWED); +- l2cap_send_cmd(conn, cmd->ident, L2CAP_MOVE_CHAN_RSP, +- sizeof(rsp), &rsp); +- return 0; +- } +- +- chan->ident = cmd->ident; +- +- if (chan->scid < L2CAP_CID_DYN_START || +- chan->chan_policy == BT_CHANNEL_POLICY_BREDR_ONLY || +- (chan->mode != L2CAP_MODE_ERTM && +- chan->mode != L2CAP_MODE_STREAMING)) { +- result = L2CAP_MR_NOT_ALLOWED; +- goto send_move_response; +- } +- +- if (chan->local_amp_id == req->dest_amp_id) { +- result = L2CAP_MR_SAME_ID; +- goto send_move_response; +- } +- +- if (req->dest_amp_id != AMP_ID_BREDR) { +- struct hci_dev *hdev; +- hdev = hci_dev_get(req->dest_amp_id); +- if (!hdev || hdev->dev_type != HCI_AMP || +- !test_bit(HCI_UP, &hdev->flags)) { +- if (hdev) +- hci_dev_put(hdev); +- +- result = L2CAP_MR_BAD_ID; +- goto send_move_response; +- } +- hci_dev_put(hdev); +- } +- +- /* Detect a move collision. Only send a collision response +- * if this side has "lost", otherwise proceed with the move. +- * The winner has the larger bd_addr. +- */ +- if ((__chan_is_moving(chan) || +- chan->move_role != L2CAP_MOVE_ROLE_NONE) && +- bacmp(&conn->hcon->src, &conn->hcon->dst) > 0) { +- result = L2CAP_MR_COLLISION; +- goto send_move_response; +- } +- +- chan->move_role = L2CAP_MOVE_ROLE_RESPONDER; +- l2cap_move_setup(chan); +- chan->move_id = req->dest_amp_id; +- +- if (req->dest_amp_id == AMP_ID_BREDR) { +- /* Moving to BR/EDR */ +- if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) { +- chan->move_state = L2CAP_MOVE_WAIT_LOCAL_BUSY; +- result = L2CAP_MR_PEND; +- } else { +- chan->move_state = L2CAP_MOVE_WAIT_CONFIRM; +- result = L2CAP_MR_SUCCESS; +- } +- } else { +- chan->move_state = L2CAP_MOVE_WAIT_PREPARE; +- /* Placeholder - uncomment when amp functions are available */ +- /*amp_accept_physical(chan, req->dest_amp_id);*/ +- result = L2CAP_MR_PEND; +- } +- +-send_move_response: +- l2cap_send_move_chan_rsp(chan, result); +- +- l2cap_chan_unlock(chan); +- l2cap_chan_put(chan); +- +- return 0; +-} +- +-static void l2cap_move_continue(struct l2cap_conn *conn, u16 icid, u16 result) +-{ +- struct l2cap_chan *chan; +- struct hci_chan *hchan = NULL; +- +- chan = l2cap_get_chan_by_scid(conn, icid); +- if (!chan) { +- l2cap_send_move_chan_cfm_icid(conn, icid); +- return; +- } +- +- __clear_chan_timer(chan); +- if (result == L2CAP_MR_PEND) +- __set_chan_timer(chan, L2CAP_MOVE_ERTX_TIMEOUT); +- +- switch (chan->move_state) { +- case L2CAP_MOVE_WAIT_LOGICAL_COMP: +- /* Move confirm will be sent when logical link +- * is complete. +- */ +- chan->move_state = L2CAP_MOVE_WAIT_LOGICAL_CFM; +- break; +- case L2CAP_MOVE_WAIT_RSP_SUCCESS: +- if (result == L2CAP_MR_PEND) { +- break; +- } else if (test_bit(CONN_LOCAL_BUSY, +- &chan->conn_state)) { +- chan->move_state = L2CAP_MOVE_WAIT_LOCAL_BUSY; +- } else { +- /* Logical link is up or moving to BR/EDR, +- * proceed with move +- */ +- chan->move_state = L2CAP_MOVE_WAIT_CONFIRM_RSP; +- l2cap_send_move_chan_cfm(chan, L2CAP_MC_CONFIRMED); +- } +- break; +- case L2CAP_MOVE_WAIT_RSP: +- /* Moving to AMP */ +- if (result == L2CAP_MR_SUCCESS) { +- /* Remote is ready, send confirm immediately +- * after logical link is ready +- */ +- chan->move_state = L2CAP_MOVE_WAIT_LOGICAL_CFM; +- } else { +- /* Both logical link and move success +- * are required to confirm +- */ +- chan->move_state = L2CAP_MOVE_WAIT_LOGICAL_COMP; +- } +- +- /* Placeholder - get hci_chan for logical link */ +- if (!hchan) { +- /* Logical link not available */ +- l2cap_send_move_chan_cfm(chan, L2CAP_MC_UNCONFIRMED); +- break; +- } +- +- /* If the logical link is not yet connected, do not +- * send confirmation. +- */ +- if (hchan->state != BT_CONNECTED) +- break; +- +- /* Logical link is already ready to go */ +- +- chan->hs_hcon = hchan->conn; +- chan->hs_hcon->l2cap_data = chan->conn; +- +- if (result == L2CAP_MR_SUCCESS) { +- /* Can confirm now */ +- l2cap_send_move_chan_cfm(chan, L2CAP_MC_CONFIRMED); +- } else { +- /* Now only need move success +- * to confirm +- */ +- chan->move_state = L2CAP_MOVE_WAIT_RSP_SUCCESS; +- } +- +- l2cap_logical_cfm(chan, hchan, L2CAP_MR_SUCCESS); +- break; +- default: +- /* Any other amp move state means the move failed. */ +- chan->move_id = chan->local_amp_id; +- l2cap_move_done(chan); +- l2cap_send_move_chan_cfm(chan, L2CAP_MC_UNCONFIRMED); +- } +- +- l2cap_chan_unlock(chan); +- l2cap_chan_put(chan); +-} +- +-static void l2cap_move_fail(struct l2cap_conn *conn, u8 ident, u16 icid, +- u16 result) +-{ +- struct l2cap_chan *chan; +- +- chan = l2cap_get_chan_by_ident(conn, ident); +- if (!chan) { +- /* Could not locate channel, icid is best guess */ +- l2cap_send_move_chan_cfm_icid(conn, icid); +- return; +- } +- +- __clear_chan_timer(chan); +- +- if (chan->move_role == L2CAP_MOVE_ROLE_INITIATOR) { +- if (result == L2CAP_MR_COLLISION) { +- chan->move_role = L2CAP_MOVE_ROLE_RESPONDER; +- } else { +- /* Cleanup - cancel move */ +- chan->move_id = chan->local_amp_id; +- l2cap_move_done(chan); +- } +- } +- +- l2cap_send_move_chan_cfm(chan, L2CAP_MC_UNCONFIRMED); +- +- l2cap_chan_unlock(chan); +- l2cap_chan_put(chan); +-} +- +-static int l2cap_move_channel_rsp(struct l2cap_conn *conn, +- struct l2cap_cmd_hdr *cmd, +- u16 cmd_len, void *data) +-{ +- struct l2cap_move_chan_rsp *rsp = data; +- u16 icid, result; +- +- if (cmd_len != sizeof(*rsp)) +- return -EPROTO; +- +- icid = le16_to_cpu(rsp->icid); +- result = le16_to_cpu(rsp->result); +- +- BT_DBG("icid 0x%4.4x, result 0x%4.4x", icid, result); +- +- if (result == L2CAP_MR_SUCCESS || result == L2CAP_MR_PEND) +- l2cap_move_continue(conn, icid, result); +- else +- l2cap_move_fail(conn, cmd->ident, icid, result); +- +- return 0; +-} +- +-static int l2cap_move_channel_confirm(struct l2cap_conn *conn, +- struct l2cap_cmd_hdr *cmd, +- u16 cmd_len, void *data) +-{ +- struct l2cap_move_chan_cfm *cfm = data; +- struct l2cap_chan *chan; +- u16 icid, result; +- +- if (cmd_len != sizeof(*cfm)) +- return -EPROTO; +- +- icid = le16_to_cpu(cfm->icid); +- result = le16_to_cpu(cfm->result); +- +- BT_DBG("icid 0x%4.4x, result 0x%4.4x", icid, result); +- +- chan = l2cap_get_chan_by_dcid(conn, icid); +- if (!chan) { +- /* Spec requires a response even if the icid was not found */ +- l2cap_send_move_chan_cfm_rsp(conn, cmd->ident, icid); +- return 0; +- } +- +- if (chan->move_state == L2CAP_MOVE_WAIT_CONFIRM) { +- if (result == L2CAP_MC_CONFIRMED) { +- chan->local_amp_id = chan->move_id; +- if (chan->local_amp_id == AMP_ID_BREDR) +- __release_logical_link(chan); +- } else { +- chan->move_id = chan->local_amp_id; +- } +- +- l2cap_move_done(chan); +- } +- +- l2cap_send_move_chan_cfm_rsp(conn, cmd->ident, icid); +- +- l2cap_chan_unlock(chan); +- l2cap_chan_put(chan); +- +- return 0; +-} +- +-static inline int l2cap_move_channel_confirm_rsp(struct l2cap_conn *conn, +- struct l2cap_cmd_hdr *cmd, +- u16 cmd_len, void *data) +-{ +- struct l2cap_move_chan_cfm_rsp *rsp = data; +- struct l2cap_chan *chan; +- u16 icid; +- +- if (cmd_len != sizeof(*rsp)) +- return -EPROTO; +- +- icid = le16_to_cpu(rsp->icid); +- +- BT_DBG("icid 0x%4.4x", icid); +- +- chan = l2cap_get_chan_by_scid(conn, icid); +- if (!chan) +- return 0; +- +- __clear_chan_timer(chan); +- +- if (chan->move_state == L2CAP_MOVE_WAIT_CONFIRM_RSP) { +- chan->local_amp_id = chan->move_id; +- +- if (chan->local_amp_id == AMP_ID_BREDR && chan->hs_hchan) +- __release_logical_link(chan); +- +- l2cap_move_done(chan); +- } +- +- l2cap_chan_unlock(chan); +- l2cap_chan_put(chan); +- +- return 0; +-} +- + static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, + u16 cmd_len, u8 *data) +@@ -5818,7 +4834,6 @@ static inline int l2cap_bredr_sig_cmd(st + break; + + case L2CAP_CONN_RSP: +- case L2CAP_CREATE_CHAN_RSP: + l2cap_connect_create_rsp(conn, cmd, cmd_len, data); + break; + +@@ -5853,26 +4868,6 @@ static inline int l2cap_bredr_sig_cmd(st + l2cap_information_rsp(conn, cmd, cmd_len, data); + break; + +- case L2CAP_CREATE_CHAN_REQ: +- err = l2cap_create_channel_req(conn, cmd, cmd_len, data); +- break; +- +- case L2CAP_MOVE_CHAN_REQ: +- err = l2cap_move_channel_req(conn, cmd, cmd_len, data); +- break; +- +- case L2CAP_MOVE_CHAN_RSP: +- l2cap_move_channel_rsp(conn, cmd, cmd_len, data); +- break; +- +- case L2CAP_MOVE_CHAN_CFM: +- err = l2cap_move_channel_confirm(conn, cmd, cmd_len, data); +- break; +- +- case L2CAP_MOVE_CHAN_CFM_RSP: +- l2cap_move_channel_confirm_rsp(conn, cmd, cmd_len, data); +- break; +- + default: + BT_ERR("Unknown BR/EDR signaling command 0x%2.2x", cmd->code); + err = -EINVAL; +@@ -7251,8 +6246,8 @@ static int l2cap_rx_state_recv(struct l2 + if (control->final) { + clear_bit(CONN_REMOTE_BUSY, &chan->conn_state); + +- if (!test_and_clear_bit(CONN_REJ_ACT, &chan->conn_state) && +- !__chan_is_moving(chan)) { ++ if (!test_and_clear_bit(CONN_REJ_ACT, ++ &chan->conn_state)) { + control->final = 0; + l2cap_retransmit_all(chan, control); + } +@@ -7445,11 +6440,7 @@ static int l2cap_finish_move(struct l2ca + BT_DBG("chan %p", chan); + + chan->rx_state = L2CAP_RX_STATE_RECV; +- +- if (chan->hs_hcon) +- chan->conn->mtu = chan->hs_hcon->hdev->block_mtu; +- else +- chan->conn->mtu = chan->conn->hcon->hdev->acl_mtu; ++ chan->conn->mtu = chan->conn->hcon->hdev->acl_mtu; + + return l2cap_resegment(chan); + } +@@ -7516,11 +6507,7 @@ static int l2cap_rx_state_wait_f(struct + */ + chan->next_tx_seq = control->reqseq; + chan->unacked_frames = 0; +- +- if (chan->hs_hcon) +- chan->conn->mtu = chan->hs_hcon->hdev->block_mtu; +- else +- chan->conn->mtu = chan->conn->hcon->hdev->acl_mtu; ++ chan->conn->mtu = chan->conn->hcon->hdev->acl_mtu; + + err = l2cap_resegment(chan); + +@@ -7904,21 +6891,10 @@ static void l2cap_data_channel(struct l2 + + chan = l2cap_get_chan_by_scid(conn, cid); + if (!chan) { +- if (cid == L2CAP_CID_A2MP) { +- chan = a2mp_channel_create(conn, skb); +- if (!chan) { +- kfree_skb(skb); +- return; +- } +- +- l2cap_chan_hold(chan); +- l2cap_chan_lock(chan); +- } else { +- BT_DBG("unknown cid 0x%4.4x", cid); +- /* Drop packet and return */ +- kfree_skb(skb); +- return; +- } ++ BT_DBG("unknown cid 0x%4.4x", cid); ++ /* Drop packet and return */ ++ kfree_skb(skb); ++ return; + } + + BT_DBG("chan %p, len %d", chan, skb->len); +@@ -8123,10 +7099,6 @@ static struct l2cap_conn *l2cap_conn_add + + conn->local_fixed_chan = L2CAP_FC_SIG_BREDR | L2CAP_FC_CONNLESS; + +- if (hcon->type == ACL_LINK && +- hci_dev_test_flag(hcon->hdev, HCI_HS_ENABLED)) +- conn->local_fixed_chan |= L2CAP_FC_A2MP; +- + if (hci_dev_test_flag(hcon->hdev, HCI_LE_ENABLED) && + (bredr_sc_enabled(hcon->hdev) || + hci_dev_test_flag(hcon->hdev, HCI_FORCE_BREDR_SMP))) +@@ -8591,11 +7563,6 @@ static void l2cap_security_cfm(struct hc + BT_DBG("chan %p scid 0x%4.4x state %s", chan, chan->scid, + state_to_string(chan->state)); + +- if (chan->scid == L2CAP_CID_A2MP) { +- l2cap_chan_unlock(chan); +- continue; +- } +- + if (!status && encrypt) + chan->sec_level = hcon->sec_level; + +--- a/net/bluetooth/l2cap_sock.c ++++ b/net/bluetooth/l2cap_sock.c +@@ -1023,23 +1023,7 @@ static int l2cap_sock_setsockopt(struct + if (err) + break; + +- if (opt > BT_CHANNEL_POLICY_AMP_PREFERRED) { +- err = -EINVAL; +- break; +- } +- +- if (chan->mode != L2CAP_MODE_ERTM && +- chan->mode != L2CAP_MODE_STREAMING) { +- err = -EOPNOTSUPP; +- break; +- } +- +- chan->chan_policy = (u8) opt; +- +- if (sk->sk_state == BT_CONNECTED && +- chan->move_role == L2CAP_MOVE_ROLE_NONE) +- l2cap_move_start(chan); +- ++ err = -EOPNOTSUPP; + break; + + case BT_SNDMTU: +--- a/net/bluetooth/mgmt.c ++++ b/net/bluetooth/mgmt.c +@@ -835,8 +835,6 @@ static u32 get_supported_settings(struct + + if (lmp_ssp_capable(hdev)) { + settings |= MGMT_SETTING_SSP; +- if (IS_ENABLED(CONFIG_BT_HS)) +- settings |= MGMT_SETTING_HS; + } + + if (lmp_sc_capable(hdev)) +@@ -901,9 +899,6 @@ static u32 get_current_settings(struct h + if (hci_dev_test_flag(hdev, HCI_SSP_ENABLED)) + settings |= MGMT_SETTING_SSP; + +- if (hci_dev_test_flag(hdev, HCI_HS_ENABLED)) +- settings |= MGMT_SETTING_HS; +- + if (hci_dev_test_flag(hdev, HCI_ADVERTISING)) + settings |= MGMT_SETTING_ADVERTISING; + +@@ -1939,7 +1934,6 @@ static void set_ssp_complete(struct hci_ + + if (enable && hci_dev_test_and_clear_flag(hdev, + HCI_SSP_ENABLED)) { +- hci_dev_clear_flag(hdev, HCI_HS_ENABLED); + new_settings(hdev, NULL); + } + +@@ -1952,12 +1946,6 @@ static void set_ssp_complete(struct hci_ + changed = !hci_dev_test_and_set_flag(hdev, HCI_SSP_ENABLED); + } else { + changed = hci_dev_test_and_clear_flag(hdev, HCI_SSP_ENABLED); +- +- if (!changed) +- changed = hci_dev_test_and_clear_flag(hdev, +- HCI_HS_ENABLED); +- else +- hci_dev_clear_flag(hdev, HCI_HS_ENABLED); + } + + mgmt_pending_foreach(MGMT_OP_SET_SSP, hdev, settings_rsp, &match); +@@ -2021,11 +2009,6 @@ static int set_ssp(struct sock *sk, stru + } else { + changed = hci_dev_test_and_clear_flag(hdev, + HCI_SSP_ENABLED); +- if (!changed) +- changed = hci_dev_test_and_clear_flag(hdev, +- HCI_HS_ENABLED); +- else +- hci_dev_clear_flag(hdev, HCI_HS_ENABLED); + } + + err = send_settings_rsp(sk, MGMT_OP_SET_SSP, hdev); +@@ -2071,63 +2054,10 @@ failed: + + static int set_hs(struct sock *sk, struct hci_dev *hdev, void *data, u16 len) + { +- struct mgmt_mode *cp = data; +- bool changed; +- u8 status; +- int err; +- + bt_dev_dbg(hdev, "sock %p", sk); + +- if (!IS_ENABLED(CONFIG_BT_HS)) +- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS, ++ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS, + MGMT_STATUS_NOT_SUPPORTED); +- +- status = mgmt_bredr_support(hdev); +- if (status) +- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS, status); +- +- if (!lmp_ssp_capable(hdev)) +- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS, +- MGMT_STATUS_NOT_SUPPORTED); +- +- if (!hci_dev_test_flag(hdev, HCI_SSP_ENABLED)) +- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS, +- MGMT_STATUS_REJECTED); +- +- if (cp->val != 0x00 && cp->val != 0x01) +- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS, +- MGMT_STATUS_INVALID_PARAMS); +- +- hci_dev_lock(hdev); +- +- if (pending_find(MGMT_OP_SET_SSP, hdev)) { +- err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS, +- MGMT_STATUS_BUSY); +- goto unlock; +- } +- +- if (cp->val) { +- changed = !hci_dev_test_and_set_flag(hdev, HCI_HS_ENABLED); +- } else { +- if (hdev_is_powered(hdev)) { +- err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_HS, +- MGMT_STATUS_REJECTED); +- goto unlock; +- } +- +- changed = hci_dev_test_and_clear_flag(hdev, HCI_HS_ENABLED); +- } +- +- err = send_settings_rsp(sk, MGMT_OP_SET_HS, hdev); +- if (err < 0) +- goto unlock; +- +- if (changed) +- err = new_settings(hdev, sk); +- +-unlock: +- hci_dev_unlock(hdev); +- return err; + } + + static void set_le_complete(struct hci_dev *hdev, void *data, int err) +@@ -6839,7 +6769,6 @@ static int set_bredr(struct sock *sk, st + hci_dev_clear_flag(hdev, HCI_SSP_ENABLED); + hci_dev_clear_flag(hdev, HCI_LINK_SECURITY); + hci_dev_clear_flag(hdev, HCI_FAST_CONNECTABLE); +- hci_dev_clear_flag(hdev, HCI_HS_ENABLED); + } + + hci_dev_change_flag(hdev, HCI_BREDR_ENABLED); diff --git a/queue-6.1/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch b/queue-6.1/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch new file mode 100644 index 0000000000..296bc6fa6d --- /dev/null +++ b/queue-6.1/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch @@ -0,0 +1,100 @@ +From stable+bounces-277357-greg=kroah.com@vger.kernel.org Sat Jul 18 18:39:35 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 12:39:25 -0400 +Subject: bpf: Allow LPM map access from sleepable BPF programs +To: stable@vger.kernel.org +Cc: Vlad Poenaru , Emil Tsalapatis , Alexei Starovoitov , Sasha Levin +Message-ID: <20260718163925.3343620-2-sashal@kernel.org> + +From: Vlad Poenaru + +[ 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 +Reviewed-by: Emil Tsalapatis +Link: https://lore.kernel.org/r/20260609135558.193287-2-vlad.wing@gmail.com +Signed-off-by: Alexei Starovoitov +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/bpf/lpm_trie.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/kernel/bpf/lpm_trie.c ++++ b/kernel/bpf/lpm_trie.c +@@ -236,7 +236,7 @@ static void *trie_lookup_elem(struct bpf + + /* Start walking the trie from the root node ... */ + +- for (node = rcu_dereference_check(trie->root, rcu_read_lock_bh_held()); ++ for (node = rcu_dereference_check(trie->root, bpf_rcu_lock_held()); + node;) { + unsigned int next_bit; + size_t matchlen; +@@ -270,7 +270,7 @@ static void *trie_lookup_elem(struct bpf + */ + next_bit = extract_bit(key->data, node->prefixlen); + node = rcu_dereference_check(node->child[next_bit], +- rcu_read_lock_bh_held()); ++ bpf_rcu_lock_held()); + } + + if (!found) diff --git a/queue-6.1/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch b/queue-6.1/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch new file mode 100644 index 0000000000..9045f85eb9 --- /dev/null +++ b/queue-6.1/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch @@ -0,0 +1,191 @@ +From stable+bounces-277356-greg=kroah.com@vger.kernel.org Sat Jul 18 18:39:34 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 12:39:24 -0400 +Subject: bpf: Consistently use bpf_rcu_lock_held() everywhere +To: stable@vger.kernel.org +Cc: Andrii Nakryiko , Daniel Borkmann , Jiri Olsa , Sasha Levin +Message-ID: <20260718163925.3343620-1-sashal@kernel.org> + +From: Andrii Nakryiko + +[ 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 +Signed-off-by: Daniel Borkmann +Acked-by: Jiri Olsa +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 +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/bpf.h | 3 +++ + include/linux/bpf_local_storage.h | 3 --- + kernel/bpf/hashtab.c | 21 +++++++-------------- + kernel/bpf/helpers.c | 12 ++++-------- + kernel/bpf/lpm_trie.c | 6 ++---- + 5 files changed, 16 insertions(+), 29 deletions(-) + +--- a/include/linux/bpf.h ++++ b/include/linux/bpf.h +@@ -1722,6 +1722,9 @@ out: + return ret; + } + ++#define bpf_rcu_lock_held() \ ++ (rcu_read_lock_held() || rcu_read_lock_trace_held() || rcu_read_lock_bh_held()) ++ + #ifdef CONFIG_BPF_SYSCALL + DECLARE_PER_CPU(int, bpf_prog_active); + extern struct mutex bpf_stats_enabled_mutex; +--- a/include/linux/bpf_local_storage.h ++++ b/include/linux/bpf_local_storage.h +@@ -17,9 +17,6 @@ + + #define BPF_LOCAL_STORAGE_CACHE_SIZE 16 + +-#define bpf_rcu_lock_held() \ +- (rcu_read_lock_held() || rcu_read_lock_trace_held() || \ +- rcu_read_lock_bh_held()) + struct bpf_local_storage_map_bucket { + struct hlist_head list; + raw_spinlock_t lock; +--- a/kernel/bpf/hashtab.c ++++ b/kernel/bpf/hashtab.c +@@ -670,8 +670,7 @@ static void *__htab_map_lookup_elem(stru + struct htab_elem *l; + u32 hash, key_size; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +@@ -1088,8 +1087,7 @@ static int htab_map_update_elem(struct b + /* unknown flags */ + return -EINVAL; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +@@ -1190,8 +1188,7 @@ static int htab_lru_map_update_elem(stru + /* unknown flags */ + return -EINVAL; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +@@ -1259,8 +1256,7 @@ static int __htab_percpu_map_update_elem + /* unknown flags */ + return -EINVAL; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +@@ -1314,8 +1310,7 @@ static int __htab_lru_percpu_map_update_ + /* unknown flags */ + return -EINVAL; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +@@ -1390,8 +1385,7 @@ static int htab_map_delete_elem(struct b + u32 hash, key_size; + int ret; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +@@ -1426,8 +1420,7 @@ static int htab_lru_map_delete_elem(stru + u32 hash, key_size; + int ret; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +--- a/kernel/bpf/helpers.c ++++ b/kernel/bpf/helpers.c +@@ -33,8 +33,7 @@ + */ + BPF_CALL_2(bpf_map_lookup_elem, struct bpf_map *, map, void *, key) + { +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + return (unsigned long) map->ops->map_lookup_elem(map, key); + } + +@@ -50,8 +49,7 @@ const struct bpf_func_proto bpf_map_look + BPF_CALL_4(bpf_map_update_elem, struct bpf_map *, map, void *, key, + void *, value, u64, flags) + { +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + return map->ops->map_update_elem(map, key, value, flags); + } + +@@ -68,8 +66,7 @@ const struct bpf_func_proto bpf_map_upda + + BPF_CALL_2(bpf_map_delete_elem, struct bpf_map *, map, void *, key) + { +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + return map->ops->map_delete_elem(map, key); + } + +@@ -125,8 +122,7 @@ const struct bpf_func_proto bpf_map_peek + + BPF_CALL_3(bpf_map_lookup_percpu_elem, struct bpf_map *, map, void *, key, u32, cpu) + { +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + return (unsigned long) map->ops->map_lookup_percpu_elem(map, key, cpu); + } + +--- a/kernel/bpf/lpm_trie.c ++++ b/kernel/bpf/lpm_trie.c +@@ -353,8 +353,7 @@ static int trie_update_elem(struct bpf_m + */ + slot = &trie->root; + +- while ((node = rcu_dereference_protected(*slot, +- lockdep_is_held(&trie->lock)))) { ++ while ((node = rcu_dereference_protected(*slot, 1))) { + matchlen = longest_prefix_match(trie, node, key); + + if (node->prefixlen != matchlen || +@@ -475,8 +474,7 @@ static int trie_delete_elem(struct bpf_m + trim = &trie->root; + trim2 = trim; + parent = NULL; +- while ((node = rcu_dereference_protected( +- *trim, lockdep_is_held(&trie->lock)))) { ++ while ((node = rcu_dereference_protected(*trim, 1))) { + matchlen = longest_prefix_match(trie, node, key); + + if (node->prefixlen != matchlen || diff --git a/queue-6.1/btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch b/queue-6.1/btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch new file mode 100644 index 0000000000..3601ea0edb --- /dev/null +++ b/queue-6.1/btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch @@ -0,0 +1,269 @@ +From stable+bounces-278215-greg=kroah.com@vger.kernel.org Mon Jul 20 22:22:09 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 16:18:45 -0400 +Subject: btrfs: check and set EXTENT_DELALLOC_NEW before clearing EXTENT_DELALLOC +To: stable@vger.kernel.org +Cc: Qu Wenruo , Filipe Manana , David Sterba , Sasha Levin +Message-ID: <20260720201845.3239316-1-sashal@kernel.org> + +From: Qu Wenruo + +[ 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: + + 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 + + ---[ 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 +Signed-off-by: Qu Wenruo +Signed-off-by: David Sterba +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/ctree.h | 2 + + fs/btrfs/file.c | 12 +------- + fs/btrfs/inode.c | 73 ++++++++++++++++++++++++++++++++++++++--------------- + fs/btrfs/reflink.c | 5 --- + 4 files changed, 58 insertions(+), 34 deletions(-) + +--- a/fs/btrfs/ctree.h ++++ b/fs/btrfs/ctree.h +@@ -3327,6 +3327,8 @@ int btrfs_start_delalloc_roots(struct bt + int btrfs_set_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end, + unsigned int extra_bits, + struct extent_state **cached_state); ++int btrfs_reset_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end, ++ unsigned int extra_bits, struct extent_state **cached_state); + struct btrfs_new_inode_args { + /* Input */ + struct inode *dir; +--- a/fs/btrfs/file.c ++++ b/fs/btrfs/file.c +@@ -144,16 +144,8 @@ int btrfs_dirty_pages(struct btrfs_inode + + end_of_last_block = start_pos + num_bytes - 1; + +- /* +- * The pages may have already been dirty, clear out old accounting so +- * we can set things up properly +- */ +- clear_extent_bit(&inode->io_tree, start_pos, end_of_last_block, +- EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG, +- cached); +- +- err = btrfs_set_extent_delalloc(inode, start_pos, end_of_last_block, +- extra_bits, cached); ++ err = btrfs_reset_extent_delalloc(inode, start_pos, end_of_last_block, ++ extra_bits, cached); + if (err) + return err; + +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -2845,7 +2845,11 @@ int btrfs_set_extent_delalloc(struct btr + unsigned int extra_bits, + struct extent_state **cached_state) + { +- WARN_ON(PAGE_ALIGNED(end)); ++ const u32 blocksize = inode->root->fs_info->sectorsize; ++ ++ /* Basic alignment check. */ ++ ASSERT(IS_ALIGNED(start, blocksize)); ++ ASSERT(IS_ALIGNED(end + 1, blocksize)); + + if (start >= i_size_read(&inode->vfs_inode) && + !(inode->flags & BTRFS_INODE_PREALLOC)) { +@@ -3055,6 +3059,51 @@ int btrfs_writepage_cow_fixup(struct pag + return -EAGAIN; + } + ++/* ++ * Clear the old accounting flags and set EXTENT_DELALLOC for the range. ++ * ++ * Return <0 for error, in that case no range has EXTENT_DELALLOC bit cleared or set. ++ */ ++int btrfs_reset_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end, ++ unsigned int extra_bits, struct extent_state **cached_state) ++{ ++ const u32 blocksize = inode->root->fs_info->sectorsize; ++ ++ /* The @extra_bits can only be EXTENT_NORESERVE for now. */ ++ ASSERT(!(extra_bits & ~EXTENT_NORESERVE)); ++ ++ /* Basic alignment check. */ ++ ASSERT(IS_ALIGNED(start, blocksize)); ++ ASSERT(IS_ALIGNED(end + 1, blocksize)); ++ ++ /* ++ * Check and set DELALLOC_NEW flag, this needs to search tree thus can ++ * fail early. Thus we want to do this before clearing EXTENT_DELALLOC. ++ */ ++ if (start >= i_size_read(&inode->vfs_inode) && ++ !(inode->flags & BTRFS_INODE_PREALLOC)) { ++ /* ++ * There can't be any extents following EOF in this case so just ++ * set the delalloc new bit for the range directly. ++ */ ++ extra_bits |= EXTENT_DELALLOC_NEW; ++ } else { ++ int ret; ++ ++ ret = btrfs_find_new_delalloc_bytes(inode, start, end + 1 - start, ++ NULL); ++ if (unlikely(ret)) ++ return ret; ++ } ++ /* Clear the old accounting as the range may already be dirty. */ ++ clear_extent_bit(&inode->io_tree, start, end, ++ EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | ++ EXTENT_DEFRAG, cached_state); ++ return set_extent_bit(&inode->io_tree, start, end, ++ EXTENT_DELALLOC | extra_bits, cached_state, ++ GFP_NOFS); ++} ++ + static int insert_reserved_file_extent(struct btrfs_trans_handle *trans, + struct btrfs_inode *inode, u64 file_pos, + struct btrfs_file_extent_item *stack_fi, +@@ -5037,12 +5086,7 @@ again: + goto again; + } + +- clear_extent_bit(&inode->io_tree, block_start, block_end, +- EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG, +- &cached_state); +- +- ret = btrfs_set_extent_delalloc(inode, block_start, block_end, 0, +- &cached_state); ++ ret = btrfs_reset_extent_delalloc(inode, block_start, block_end, 0, &cached_state); + if (ret) { + unlock_extent(io_tree, block_start, block_end, &cached_state); + goto out_unlock; +@@ -8685,19 +8729,8 @@ again: + } + } + +- /* +- * page_mkwrite gets called when the page is firstly dirtied after it's +- * faulted in, but write(2) could also dirty a page and set delalloc +- * bits, thus in this case for space account reason, we still need to +- * clear any delalloc bits within this page range since we have to +- * reserve data&meta space before lock_page() (see above comments). +- */ +- clear_extent_bit(&BTRFS_I(inode)->io_tree, page_start, end, +- EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | +- EXTENT_DEFRAG, &cached_state); +- +- ret2 = btrfs_set_extent_delalloc(BTRFS_I(inode), page_start, end, 0, +- &cached_state); ++ ret2 = btrfs_reset_extent_delalloc(BTRFS_I(inode), page_start, end, 0, ++ &cached_state); + if (ret2) { + unlock_extent(io_tree, page_start, page_end, &cached_state); + ret = VM_FAULT_SIGBUS; +--- a/fs/btrfs/reflink.c ++++ b/fs/btrfs/reflink.c +@@ -90,10 +90,7 @@ static int copy_inline_to_page(struct bt + if (ret < 0) + goto out_unlock; + +- clear_extent_bit(&inode->io_tree, file_offset, range_end, +- EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG, +- NULL); +- ret = btrfs_set_extent_delalloc(inode, file_offset, range_end, 0, NULL); ++ ret = btrfs_reset_extent_delalloc(inode, file_offset, range_end, 0, NULL); + if (ret) + goto out_unlock; + diff --git a/queue-6.1/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch b/queue-6.1/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch new file mode 100644 index 0000000000..43c1b26a3c --- /dev/null +++ b/queue-6.1/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch @@ -0,0 +1,196 @@ +From stable+bounces-278185-greg=kroah.com@vger.kernel.org Mon Jul 20 20:09:25 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 14:08:52 -0400 +Subject: btrfs: fix false IO failure after falling back to buffered write +To: stable@vger.kernel.org +Cc: Qu Wenruo , Boris Burkov , David Sterba , Sasha Levin +Message-ID: <20260720180852.2923422-1-sashal@kernel.org> + +From: Qu Wenruo + +[ 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 +Signed-off-by: Qu Wenruo +Signed-off-by: David Sterba +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/inode.c | 33 +++++++++++++++++++++++++-------- + fs/btrfs/ordered-data.c | 12 ++++++++++++ + fs/btrfs/ordered-data.h | 2 ++ + 3 files changed, 39 insertions(+), 8 deletions(-) + +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -8031,12 +8031,33 @@ static int btrfs_dio_iomap_end(struct in + if (submitted < length) { + pos += submitted; + length -= submitted; +- if (write) ++ if (write) { ++ struct btrfs_ordered_extent *ordered; ++ ++ /* ++ * We have a short write, and the ordered extent is ++ * not split at submission time on this tree, so it ++ * may cover both the submitted range and the range ++ * that was never submitted. ++ * ++ * Truncate the ordered extent to the part that was ++ * submitted properly, so the never submitted range ++ * is reclaimed without inserting a file extent item ++ * for it and without marking it as an IO error. ++ */ ++ ordered = btrfs_lookup_ordered_extent(BTRFS_I(inode), ++ pos); ++ if (ordered) { ++ btrfs_mark_ordered_extent_truncated(ordered, ++ pos - ordered->file_offset); ++ btrfs_put_ordered_extent(ordered); ++ } + btrfs_mark_ordered_io_finished(BTRFS_I(inode), NULL, +- pos, length, false); +- else ++ pos, length, true); ++ } else { + unlock_extent(&BTRFS_I(inode)->io_tree, pos, + pos + length - 1, NULL); ++ } + ret = -ENOTBLK; + } + +@@ -8560,11 +8581,7 @@ static void btrfs_invalidate_folio(struc + EXTENT_LOCKED | EXTENT_DO_ACCOUNTING | + EXTENT_DEFRAG, &cached_state); + +- spin_lock_irq(&inode->ordered_tree.lock); +- set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags); +- ordered->truncated_len = min(ordered->truncated_len, +- cur - ordered->file_offset); +- spin_unlock_irq(&inode->ordered_tree.lock); ++ btrfs_mark_ordered_extent_truncated(ordered, cur - ordered->file_offset); + + /* + * If the ordered extent has finished, we're safe to delete all +--- a/fs/btrfs/ordered-data.c ++++ b/fs/btrfs/ordered-data.c +@@ -279,6 +279,18 @@ void btrfs_add_ordered_sum(struct btrfs_ + spin_unlock_irq(&tree->lock); + } + ++void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered, ++ u64 truncate_len) ++{ ++ struct btrfs_inode *inode = BTRFS_I(ordered->inode); ++ ++ ASSERT(truncate_len <= ordered->num_bytes); ++ spin_lock_irq(&inode->ordered_tree.lock); ++ set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags); ++ ordered->truncated_len = min(ordered->truncated_len, truncate_len); ++ spin_unlock_irq(&inode->ordered_tree.lock); ++} ++ + static void finish_ordered_fn(struct btrfs_work *work) + { + struct btrfs_ordered_extent *ordered_extent; +--- a/fs/btrfs/ordered-data.h ++++ b/fs/btrfs/ordered-data.h +@@ -209,6 +209,8 @@ void btrfs_lock_and_flush_ordered_range( + bool btrfs_try_lock_ordered_range(struct btrfs_inode *inode, u64 start, u64 end); + int btrfs_split_ordered_extent(struct btrfs_ordered_extent *ordered, u64 pre, + u64 post); ++void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered, ++ u64 truncate_len); + int __init ordered_data_init(void); + void __cold ordered_data_exit(void); + diff --git a/queue-6.1/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch b/queue-6.1/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch new file mode 100644 index 0000000000..f593a5371e --- /dev/null +++ b/queue-6.1/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch @@ -0,0 +1,199 @@ +From stable+bounces-278221-greg=kroah.com@vger.kernel.org Mon Jul 20 22:19:49 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 16:19:07 -0400 +Subject: btrfs: fix incorrect buffered IO fallback for append direct writes +To: stable@vger.kernel.org +Cc: Qu Wenruo , Boris Burkov , David Sterba , Sasha Levin +Message-ID: <20260720201907.3240613-2-sashal@kernel.org> + +From: Qu Wenruo + +[ 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 +Signed-off-by: Qu Wenruo +Signed-off-by: David Sterba +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/inode.c | 43 ++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 42 insertions(+), 1 deletion(-) + +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -63,9 +63,11 @@ struct btrfs_iget_args { + + struct btrfs_dio_data { + ssize_t submitted; ++ loff_t old_isize; + struct extent_changeset *data_reserved; + bool data_space_reserved; + bool nocow_done; ++ bool updated_isize; + }; + + struct btrfs_dio_private { +@@ -7635,6 +7637,7 @@ static int btrfs_get_blocks_direct_write + bool space_reserved = false; + u64 len = *lenp; + u64 prev_len; ++ loff_t old_isize; + int ret = 0; + + /* +@@ -7750,8 +7753,14 @@ static int btrfs_get_blocks_direct_write + * Need to update the i_size under the extent lock so buffered + * readers will get the updated i_size when we unlock. + */ +- if (start + len > i_size_read(inode)) ++ old_isize = i_size_read(inode); ++ if (start + len > old_isize) { ++ if (!dio_data->updated_isize) { ++ dio_data->old_isize = old_isize; ++ dio_data->updated_isize = true; ++ } + i_size_write(inode, start + len); ++ } + out: + if (ret && space_reserved) { + btrfs_delalloc_release_extents(BTRFS_I(inode), len); +@@ -8035,6 +8044,38 @@ static int btrfs_dio_iomap_end(struct in + struct btrfs_ordered_extent *ordered; + + /* ++ * Got a short write and have updated the isize, need to ++ * revert the isize change. ++ * ++ * Normally we need to update isize with extent lock hold, ++ * but we're safe due to the following factors: ++ * ++ * - Only a single writer can be enlarging isize ++ * Enlarging isize will take the exclusive inode lock. ++ * ++ * - Buffered readers need to wait for the OE we're holding ++ * Buffered readers will lock extent and wait for OE ++ * of the folio range, and since page cache is invalidated ++ * the OE wait can not be skipped. ++ * ++ * So here we are safe to revert the isize before ++ * finishing the OE, and no reader of the remaining range ++ * can see the enlarged size. ++ * ++ * TODO: Extend the DIO_LOCKED lifespan for direct writes, ++ * and only enlarge isize after a successful write. ++ */ ++ if (dio_data->updated_isize) { ++ u64 new_isize; ++ ++ if (submitted == 0) ++ new_isize = dio_data->old_isize; ++ else ++ new_isize = max(dio_data->old_isize, pos); ++ i_size_write(inode, new_isize); ++ dio_data->updated_isize = false; ++ } ++ /* + * We have a short write, and the ordered extent is + * not split at submission time on this tree, so it + * may cover both the submitted range and the range diff --git a/queue-6.1/cifs-add-tracing-for-the-cifs_tcon-struct-refcounting.patch b/queue-6.1/cifs-add-tracing-for-the-cifs_tcon-struct-refcounting.patch new file mode 100644 index 0000000000..f05bc0bd32 --- /dev/null +++ b/queue-6.1/cifs-add-tracing-for-the-cifs_tcon-struct-refcounting.patch @@ -0,0 +1,559 @@ +From stable+bounces-275232-greg=kroah.com@vger.kernel.org Thu Jul 16 12:19:12 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 06:18:23 -0400 +Subject: cifs: Add tracing for the cifs_tcon struct refcounting +To: stable@vger.kernel.org +Cc: David Howells , "Paulo Alcantara (Red Hat)" , Shyam Prasad N , linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org, Steve French , Sasha Levin +Message-ID: <20260716101824.1461870-4-sashal@kernel.org> + +From: David Howells + +[ Upstream commit afc23febd51c7e24361e3a9c09f3e892eb0a41ea ] + +Add tracing for the refcounting/lifecycle of the cifs_tcon struct, marking +different events with different labels and giving each tcon its own debug +ID so that the tracelines corresponding to individual tcons can be +distinguished. This can be enabled with: + + echo 1 >/sys/kernel/debug/tracing/events/cifs/smb3_tcon_ref/enable + +Signed-off-by: David Howells +Acked-by: Paulo Alcantara (Red Hat) +cc: Shyam Prasad N +cc: linux-cifs@vger.kernel.org +cc: linux-fsdevel@vger.kernel.org +Signed-off-by: Steve French +Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/cifs_swn.c | 13 ++--- + fs/smb/client/cifsfs.c | 2 + fs/smb/client/cifsglob.h | 1 + fs/smb/client/cifsproto.h | 8 +-- + fs/smb/client/connect.c | 21 +++++---- + fs/smb/client/dfs_cache.c | 2 + fs/smb/client/fscache.c | 7 +++ + fs/smb/client/misc.c | 12 +++-- + fs/smb/client/smb2misc.c | 10 +++- + fs/smb/client/smb2ops.c | 7 ++- + fs/smb/client/smb2pdu.c | 8 ++- + fs/smb/client/smb2transport.c | 2 + fs/smb/client/trace.h | 93 +++++++++++++++++++++++++++++++++++++++++- + 13 files changed, 151 insertions(+), 35 deletions(-) + +--- a/fs/smb/client/cifs_swn.c ++++ b/fs/smb/client/cifs_swn.c +@@ -313,17 +313,15 @@ static struct cifs_swn_reg *cifs_get_swn + reg = cifs_find_swn_reg(tcon); + if (!IS_ERR(reg)) { + kref_get(®->ref_count); +- mutex_unlock(&cifs_swnreg_idr_mutex); +- return reg; ++ goto unlock; + } else if (PTR_ERR(reg) != -EEXIST) { +- mutex_unlock(&cifs_swnreg_idr_mutex); +- return reg; ++ goto unlock; + } + + reg = kmalloc(sizeof(struct cifs_swn_reg), GFP_ATOMIC); + if (reg == NULL) { +- mutex_unlock(&cifs_swnreg_idr_mutex); +- return ERR_PTR(-ENOMEM); ++ ret = -ENOMEM; ++ goto fail_unlock; + } + + kref_init(®->ref_count); +@@ -354,7 +352,7 @@ static struct cifs_swn_reg *cifs_get_swn + reg->ip_notify = (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT); + + reg->tcon = tcon; +- ++unlock: + mutex_unlock(&cifs_swnreg_idr_mutex); + + return reg; +@@ -365,6 +363,7 @@ fail_idr: + idr_remove(&cifs_swnreg_idr, reg->id); + fail: + kfree(reg); ++fail_unlock: + mutex_unlock(&cifs_swnreg_idr_mutex); + return ERR_PTR(ret); + } +--- a/fs/smb/client/cifsfs.c ++++ b/fs/smb/client/cifsfs.c +@@ -747,6 +747,8 @@ static void cifs_umount_begin(struct sup + + spin_lock(&cifs_tcp_ses_lock); + spin_lock(&tcon->tc_lock); ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, ++ netfs_trace_tcon_ref_see_umount); + if ((tcon->tc_count > 1) || (tcon->status == TID_EXITING)) { + /* we have other mounts to same share or we have + already tried to umount this and woken up +--- a/fs/smb/client/cifsglob.h ++++ b/fs/smb/client/cifsglob.h +@@ -1121,6 +1121,7 @@ struct cifs_fattr { + */ + struct cifs_tcon { + struct list_head tcon_list; ++ int debug_id; /* Debugging for tracing */ + int tc_count; + struct list_head rlist; /* reconnect list */ + spinlock_t tc_lock; /* protect anything here that is not protected */ +--- a/fs/smb/client/cifsproto.h ++++ b/fs/smb/client/cifsproto.h +@@ -305,7 +305,7 @@ cifs_get_tcp_session(struct smb3_fs_cont + struct TCP_Server_Info *primary_server); + extern void cifs_put_tcp_session(struct TCP_Server_Info *server, + int from_reconnect); +-extern void cifs_put_tcon(struct cifs_tcon *tcon); ++extern void cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace); + + #if IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) + extern void cifs_dfs_release_automount_timer(void); +@@ -525,8 +525,8 @@ extern int CIFSSMBLogoff(const unsigned + + extern struct cifs_ses *sesInfoAlloc(void); + extern void sesInfoFree(struct cifs_ses *); +-extern struct cifs_tcon *tconInfoAlloc(void); +-extern void tconInfoFree(struct cifs_tcon *); ++extern struct cifs_tcon *tconInfoAlloc(enum smb3_tcon_ref_trace trace); ++extern void tconInfoFree(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace); + + extern int cifs_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server, + __u32 *pexpected_response_sequence_number); +@@ -719,8 +719,6 @@ static inline int cifs_create_options(st + return options; + } + +-struct super_block *cifs_get_tcon_super(struct cifs_tcon *tcon); +-void cifs_put_tcon_super(struct super_block *sb); + int cifs_wait_for_server_reconnect(struct TCP_Server_Info *server, bool retry); + + static inline void release_mid(struct mid_q_entry *mid) +--- a/fs/smb/client/connect.c ++++ b/fs/smb/client/connect.c +@@ -1985,7 +1985,7 @@ cifs_setup_ipc(struct cifs_ses *ses, str + } + } + +- tcon = tconInfoAlloc(); ++ tcon = tconInfoAlloc(netfs_trace_tcon_ref_new_ipc); + if (tcon == NULL) + return -ENOMEM; + +@@ -2002,7 +2002,7 @@ cifs_setup_ipc(struct cifs_ses *ses, str + + if (rc) { + cifs_server_dbg(VFS, "failed to connect to IPC (rc=%d)\n", rc); +- tconInfoFree(tcon); ++ tconInfoFree(tcon, netfs_trace_tcon_ref_free_ipc_fail); + goto out; + } + +@@ -2085,7 +2085,7 @@ void cifs_put_smb_ses(struct cifs_ses *s + * files on session close, as specified in MS-SMB2 3.3.5.6 Receiving an + * SMB2 LOGOFF Request. + */ +- tconInfoFree(tcon); ++ tconInfoFree(tcon, netfs_trace_tcon_ref_free_ipc); + if (do_logoff) { + xid = get_xid(); + rc = server->ops->logoff(xid, ses); +@@ -2459,6 +2459,8 @@ cifs_find_tcon(struct cifs_ses *ses, str + continue; + } + ++tcon->tc_count; ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, ++ netfs_trace_tcon_ref_get_find); + spin_unlock(&tcon->tc_lock); + spin_unlock(&cifs_tcp_ses_lock); + return tcon; +@@ -2468,7 +2470,7 @@ cifs_find_tcon(struct cifs_ses *ses, str + } + + void +-cifs_put_tcon(struct cifs_tcon *tcon) ++cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace) + { + unsigned int xid; + struct cifs_ses *ses; +@@ -2484,6 +2486,7 @@ cifs_put_tcon(struct cifs_tcon *tcon) + cifs_dbg(FYI, "%s: tc_count=%d\n", __func__, tcon->tc_count); + spin_lock(&cifs_tcp_ses_lock); + spin_lock(&tcon->tc_lock); ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count - 1, trace); + if (--tcon->tc_count > 0) { + spin_unlock(&tcon->tc_lock); + spin_unlock(&cifs_tcp_ses_lock); +@@ -2517,7 +2520,7 @@ cifs_put_tcon(struct cifs_tcon *tcon) + _free_xid(xid); + + cifs_fscache_release_super_cookie(tcon); +- tconInfoFree(tcon); ++ tconInfoFree(tcon, netfs_trace_tcon_ref_free); + cifs_put_smb_ses(ses); + } + +@@ -2565,7 +2568,7 @@ cifs_get_tcon(struct cifs_ses *ses, stru + goto out_fail; + } + +- tcon = tconInfoAlloc(); ++ tcon = tconInfoAlloc(netfs_trace_tcon_ref_new); + if (tcon == NULL) { + rc = -ENOMEM; + goto out_fail; +@@ -2762,7 +2765,7 @@ cifs_get_tcon(struct cifs_ses *ses, stru + return tcon; + + out_fail: +- tconInfoFree(tcon); ++ tconInfoFree(tcon, netfs_trace_tcon_ref_free_fail); + return ERR_PTR(rc); + } + +@@ -2779,7 +2782,7 @@ cifs_put_tlink(struct tcon_link *tlink) + } + + if (!IS_ERR(tlink_tcon(tlink))) +- cifs_put_tcon(tlink_tcon(tlink)); ++ cifs_put_tcon(tlink_tcon(tlink), netfs_trace_tcon_ref_put_tlink); + kfree(tlink); + return; + } +@@ -3338,7 +3341,7 @@ static inline void mount_put_conns(struc + int rc = 0; + + if (mnt_ctx->tcon) +- cifs_put_tcon(mnt_ctx->tcon); ++ cifs_put_tcon(mnt_ctx->tcon, netfs_trace_tcon_ref_put_mnt_ctx); + else if (mnt_ctx->ses) + cifs_put_smb_ses(mnt_ctx->ses); + else if (mnt_ctx->server) +--- a/fs/smb/client/dfs_cache.c ++++ b/fs/smb/client/dfs_cache.c +@@ -1564,7 +1564,7 @@ static void refresh_mounts(struct cifs_s + } + mutex_unlock(&server->refpath_lock); + +- cifs_put_tcon(tcon); ++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_dfs_refer); + } + } + +--- a/fs/smb/client/fscache.c ++++ b/fs/smb/client/fscache.c +@@ -94,6 +94,11 @@ int cifs_fscache_get_super_cookie(struct + } + pr_err("Cache volume key already in use (%s)\n", key); + vcookie = NULL; ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, ++ netfs_trace_tcon_ref_see_fscache_collision); ++ } else { ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, ++ netfs_trace_tcon_ref_see_fscache_okay); + } + + tcon->fscache = vcookie; +@@ -115,6 +120,8 @@ void cifs_fscache_release_super_cookie(s + cifs_fscache_fill_volume_coherency(tcon, &cd); + fscache_relinquish_volume(tcon->fscache, &cd, false); + tcon->fscache = NULL; ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, ++ netfs_trace_tcon_ref_see_fscache_relinq); + } + + void cifs_fscache_get_inode_cookie(struct inode *inode) +--- a/fs/smb/client/misc.c ++++ b/fs/smb/client/misc.c +@@ -116,9 +116,10 @@ sesInfoFree(struct cifs_ses *buf_to_free + } + + struct cifs_tcon * +-tconInfoAlloc(void) ++tconInfoAlloc(enum smb3_tcon_ref_trace trace) + { + struct cifs_tcon *ret_buf; ++ static atomic_t tcon_debug_id; + + ret_buf = kzalloc(sizeof(*ret_buf), GFP_KERNEL); + if (!ret_buf) +@@ -131,7 +132,8 @@ tconInfoAlloc(void) + + atomic_inc(&tconInfoAllocCount); + ret_buf->status = TID_NEW; +- ++ret_buf->tc_count; ++ ret_buf->debug_id = atomic_inc_return(&tcon_debug_id); ++ ret_buf->tc_count = 1; + spin_lock_init(&ret_buf->tc_lock); + INIT_LIST_HEAD(&ret_buf->openFileList); + INIT_LIST_HEAD(&ret_buf->tcon_list); +@@ -142,17 +144,19 @@ tconInfoAlloc(void) + #ifdef CONFIG_CIFS_FSCACHE + mutex_init(&ret_buf->fscache_lock); + #endif ++ trace_smb3_tcon_ref(ret_buf->debug_id, ret_buf->tc_count, trace); + + return ret_buf; + } + + void +-tconInfoFree(struct cifs_tcon *tcon) ++tconInfoFree(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace) + { + if (tcon == NULL) { + cifs_dbg(FYI, "Null buffer passed to tconInfoFree\n"); + return; + } ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, trace); + free_cached_dirs(tcon->cfids); + atomic_dec(&tconInfoAllocCount); + kfree(tcon->nativeFileSystem); +@@ -848,7 +852,7 @@ void cifs_close_all_deferred_files_sb(st + list_for_each_entry_safe(tmp_list, q, &tcon_head, entry) { + cifs_close_all_deferred_files(tmp_list->tcon); + list_del(&tmp_list->entry); +- cifs_put_tcon(tmp_list->tcon); ++ cifs_put_tcon(tmp_list->tcon, netfs_trace_tcon_ref_put_tlink); + kfree(tmp_list); + } + } +--- a/fs/smb/client/smb2misc.c ++++ b/fs/smb/client/smb2misc.c +@@ -804,7 +804,7 @@ smb2_cancelled_close_fid(struct work_str + if (rc) + cifs_tcon_dbg(VFS, "Close cancelled mid failed rc:%d\n", rc); + +- cifs_put_tcon(tcon); ++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_close_fid); + kfree(cancelled); + } + +@@ -848,6 +848,8 @@ smb2_handle_cancelled_close(struct cifs_ + if (tcon->tc_count <= 0) { + struct TCP_Server_Info *server = NULL; + ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, ++ netfs_trace_tcon_ref_see_cancelled_close); + WARN_ONCE(tcon->tc_count < 0, "tcon refcount is negative"); + spin_unlock(&cifs_tcp_ses_lock); + +@@ -861,12 +863,14 @@ smb2_handle_cancelled_close(struct cifs_ + return 0; + } + tcon->tc_count++; ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, ++ netfs_trace_tcon_ref_get_cancelled_close); + spin_unlock(&cifs_tcp_ses_lock); + + rc = __smb2_handle_cancelled_cmd(tcon, SMB2_CLOSE_HE, 0, + persistent_fid, volatile_fid); + if (rc) +- cifs_put_tcon(tcon); ++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_close); + + return rc; + } +@@ -894,7 +898,7 @@ smb2_handle_cancelled_mid(struct mid_q_e + rsp->PersistentFileId, + rsp->VolatileFileId); + if (rc) +- cifs_put_tcon(tcon); ++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_mid); + + return rc; + } +--- a/fs/smb/client/smb2ops.c ++++ b/fs/smb/client/smb2ops.c +@@ -2848,8 +2848,11 @@ smb2_get_dfs_refer(const unsigned int xi + tcon = list_first_entry_or_null(&ses->tcon_list, + struct cifs_tcon, + tcon_list); +- if (tcon) ++ if (tcon) { + tcon->tc_count++; ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, ++ netfs_trace_tcon_ref_get_dfs_refer); ++ } + spin_unlock(&cifs_tcp_ses_lock); + } + +@@ -2913,6 +2916,8 @@ smb2_get_dfs_refer(const unsigned int xi + /* ipc tcons are not refcounted */ + spin_lock(&cifs_tcp_ses_lock); + tcon->tc_count--; ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, ++ netfs_trace_tcon_ref_dec_dfs_refer); + /* tc_count can never go negative */ + WARN_ON(tcon->tc_count < 0); + spin_unlock(&cifs_tcp_ses_lock); +--- a/fs/smb/client/smb2pdu.c ++++ b/fs/smb/client/smb2pdu.c +@@ -3995,6 +3995,8 @@ void smb2_reconnect_server(struct work_s + list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { + if (tcon->need_reconnect || tcon->need_reopen_files) { + tcon->tc_count++; ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, ++ netfs_trace_tcon_ref_get_reconnect_server); + list_add_tail(&tcon->rlist, &tmp_list); + tcon_selected = tcon_exist = true; + } +@@ -4040,14 +4042,14 @@ void smb2_reconnect_server(struct work_s + if (tcon->ipc) + cifs_put_smb_ses(tcon->ses); + else +- cifs_put_tcon(tcon); ++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_reconnect_server); + } + + if (!ses_exist) + goto done; + + /* allocate a dummy tcon struct used for reconnect */ +- tcon = tconInfoAlloc(); ++ tcon = tconInfoAlloc(netfs_trace_tcon_ref_new_reconnect_server); + if (!tcon) { + resched = true; + list_for_each_entry_safe(ses, ses2, &tmp_ses_list, rlist) { +@@ -4070,7 +4072,7 @@ void smb2_reconnect_server(struct work_s + list_del_init(&ses->rlist); + cifs_put_smb_ses(ses); + } +- tconInfoFree(tcon); ++ tconInfoFree(tcon, netfs_trace_tcon_ref_free_reconnect_server); + + done: + cifs_dbg(FYI, "Reconnecting tcons and channels finished\n"); +--- a/fs/smb/client/smb2transport.c ++++ b/fs/smb/client/smb2transport.c +@@ -189,6 +189,8 @@ smb2_find_smb_sess_tcon_unlocked(struct + if (tcon->tid != tid) + continue; + ++tcon->tc_count; ++ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, ++ netfs_trace_tcon_ref_get_find_sess_tcon); + return tcon; + } + +--- a/fs/smb/client/trace.h ++++ b/fs/smb/client/trace.h +@@ -3,6 +3,9 @@ + * Copyright (C) 2018, Microsoft Corporation. + * + * Author(s): Steve French ++ * ++ * Please use this 3-part article as a reference for writing new tracepoints: ++ * https://lwn.net/Articles/379903/ + */ + #undef TRACE_SYSTEM + #define TRACE_SYSTEM cifs +@@ -15,9 +18,71 @@ + #include + + /* +- * Please use this 3-part article as a reference for writing new tracepoints: +- * https://lwn.net/Articles/379903/ ++ * Specify enums for tracing information. ++ */ ++#define smb3_tcon_ref_traces \ ++ EM(netfs_trace_tcon_ref_dec_dfs_refer, "DEC DfsRef") \ ++ EM(netfs_trace_tcon_ref_free, "FRE ") \ ++ EM(netfs_trace_tcon_ref_free_fail, "FRE Fail ") \ ++ EM(netfs_trace_tcon_ref_free_ipc, "FRE Ipc ") \ ++ EM(netfs_trace_tcon_ref_free_ipc_fail, "FRE Ipc-F ") \ ++ EM(netfs_trace_tcon_ref_free_reconnect_server, "FRE Reconn") \ ++ EM(netfs_trace_tcon_ref_get_cancelled_close, "GET Cn-Cls") \ ++ EM(netfs_trace_tcon_ref_get_dfs_refer, "GET DfsRef") \ ++ EM(netfs_trace_tcon_ref_get_find, "GET Find ") \ ++ EM(netfs_trace_tcon_ref_get_find_sess_tcon, "GET FndSes") \ ++ EM(netfs_trace_tcon_ref_get_reconnect_server, "GET Reconn") \ ++ EM(netfs_trace_tcon_ref_new, "NEW ") \ ++ EM(netfs_trace_tcon_ref_new_ipc, "NEW Ipc ") \ ++ EM(netfs_trace_tcon_ref_new_reconnect_server, "NEW Reconn") \ ++ EM(netfs_trace_tcon_ref_put_cancelled_close, "PUT Cn-Cls") \ ++ EM(netfs_trace_tcon_ref_put_cancelled_close_fid, "PUT Cn-Fid") \ ++ EM(netfs_trace_tcon_ref_put_cancelled_mid, "PUT Cn-Mid") \ ++ EM(netfs_trace_tcon_ref_put_mnt_ctx, "PUT MntCtx") \ ++ EM(netfs_trace_tcon_ref_put_dfs_refer, "PUT DfsRfr") \ ++ EM(netfs_trace_tcon_ref_put_reconnect_server, "PUT Reconn") \ ++ EM(netfs_trace_tcon_ref_put_tlink, "PUT Tlink ") \ ++ EM(netfs_trace_tcon_ref_see_cancelled_close, "SEE Cn-Cls") \ ++ EM(netfs_trace_tcon_ref_see_fscache_collision, "SEE FV-CO!") \ ++ EM(netfs_trace_tcon_ref_see_fscache_okay, "SEE FV-Ok ") \ ++ EM(netfs_trace_tcon_ref_see_fscache_relinq, "SEE FV-Rlq") \ ++ E_(netfs_trace_tcon_ref_see_umount, "SEE Umount") ++ ++#undef EM ++#undef E_ ++ ++/* ++ * Define those tracing enums. + */ ++#ifndef __SMB3_DECLARE_TRACE_ENUMS_ONCE_ONLY ++#define __SMB3_DECLARE_TRACE_ENUMS_ONCE_ONLY ++ ++#define EM(a, b) a, ++#define E_(a, b) a ++ ++enum smb3_tcon_ref_trace { smb3_tcon_ref_traces } __mode(byte); ++ ++#undef EM ++#undef E_ ++#endif ++ ++/* ++ * Export enum symbols via userspace. ++ */ ++#define EM(a, b) TRACE_DEFINE_ENUM(a); ++#define E_(a, b) TRACE_DEFINE_ENUM(a); ++ ++smb3_tcon_ref_traces; ++ ++#undef EM ++#undef E_ ++ ++/* ++ * Now redefine the EM() and E_() macros to map the enums to the strings that ++ * will be printed in the output. ++ */ ++#define EM(a, b) { a, b }, ++#define E_(a, b) { a, b } + + /* For logging errors in read or write */ + DECLARE_EVENT_CLASS(smb3_rw_err_class, +@@ -1062,6 +1127,30 @@ DEFINE_SMB3_CREDIT_EVENT(waitff_credits) + DEFINE_SMB3_CREDIT_EVENT(overflow_credits); + DEFINE_SMB3_CREDIT_EVENT(set_credits); + ++ ++TRACE_EVENT(smb3_tcon_ref, ++ TP_PROTO(unsigned int tcon_debug_id, int ref, ++ enum smb3_tcon_ref_trace trace), ++ TP_ARGS(tcon_debug_id, ref, trace), ++ TP_STRUCT__entry( ++ __field(unsigned int, tcon) ++ __field(int, ref) ++ __field(enum smb3_tcon_ref_trace, trace) ++ ), ++ TP_fast_assign( ++ __entry->tcon = tcon_debug_id; ++ __entry->ref = ref; ++ __entry->trace = trace; ++ ), ++ TP_printk("TC=%08x %s r=%u", ++ __entry->tcon, ++ __print_symbolic(__entry->trace, smb3_tcon_ref_traces), ++ __entry->ref) ++ ); ++ ++ ++#undef EM ++#undef E_ + #endif /* _CIFS_TRACE_H */ + + #undef TRACE_INCLUDE_PATH diff --git a/queue-6.1/cifs-after-disabling-multichannel-mark-tcon-for-reconnect.patch b/queue-6.1/cifs-after-disabling-multichannel-mark-tcon-for-reconnect.patch new file mode 100644 index 0000000000..bdc07f91f6 --- /dev/null +++ b/queue-6.1/cifs-after-disabling-multichannel-mark-tcon-for-reconnect.patch @@ -0,0 +1,66 @@ +From stable+bounces-275230-greg=kroah.com@vger.kernel.org Thu Jul 16 12:18:56 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 06:18:21 -0400 +Subject: cifs: after disabling multichannel, mark tcon for reconnect +To: stable@vger.kernel.org +Cc: Shyam Prasad N , Steve French , Sasha Levin +Message-ID: <20260716101824.1461870-2-sashal@kernel.org> + +From: Shyam Prasad N + +[ Upstream commit 27e1fd343f80168ff456785c2443136b6b7ca3cc ] + +Once the server disables multichannel for an active multichannel +session, on the following reconnect, the client would reduce +the number of channels to 1. However, it could be the case that +the tree connect was active on one of these disabled channels. +This results in an unrecoverable state. + +This change fixes that by making sure that whenever a channel +is being terminated, the session and tcon are marked for +reconnect too. This could mean a few redundant tree connect +calls to the server, but considering that this is not a frequent +event, we should be okay. + +Fixes: ee1d21794e55 ("cifs: handle when server stops supporting multichannel") +Signed-off-by: Shyam Prasad N +Signed-off-by: Steve French +Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/connect.c | 20 ++++++++++++-------- + 1 file changed, 12 insertions(+), 8 deletions(-) + +--- a/fs/smb/client/connect.c ++++ b/fs/smb/client/connect.c +@@ -256,17 +256,21 @@ cifs_mark_tcp_ses_conns_for_reconnect(st + /* If server is a channel, select the primary channel */ + pserver = CIFS_SERVER_IS_CHAN(server) ? server->primary_server : server; + ++ /* ++ * if the server has been marked for termination, there is a ++ * chance that the remaining channels all need reconnect. To be ++ * on the safer side, mark the session and trees for reconnect ++ * for this scenario. This might cause a few redundant session ++ * setup and tree connect requests, but it is better than not doing ++ * a tree connect when needed, and all following requests failing ++ */ ++ if (server->terminate) { ++ mark_smb_session = true; ++ server = pserver; ++ } + + spin_lock(&cifs_tcp_ses_lock); + list_for_each_entry_safe(ses, nses, &pserver->smb_ses_list, smb_ses_list) { +- /* +- * if channel has been marked for termination, nothing to do +- * for the channel. in fact, we cannot find the channel for the +- * server. So safe to exit here +- */ +- if (server->terminate) +- break; +- + spin_lock(&ses->ses_lock); + if (ses->ses_status == SES_EXITING) { + spin_unlock(&ses->ses_lock); diff --git a/queue-6.1/cifs-fix-reacquisition-of-volume-cookie-on-still-live-connection.patch b/queue-6.1/cifs-fix-reacquisition-of-volume-cookie-on-still-live-connection.patch new file mode 100644 index 0000000000..66d5015360 --- /dev/null +++ b/queue-6.1/cifs-fix-reacquisition-of-volume-cookie-on-still-live-connection.patch @@ -0,0 +1,115 @@ +From stable+bounces-275231-greg=kroah.com@vger.kernel.org Thu Jul 16 12:19:35 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 06:18:22 -0400 +Subject: cifs: Fix reacquisition of volume cookie on still-live connection +To: stable@vger.kernel.org +Cc: David Howells , "Paulo Alcantara (Red Hat)" , Shyam Prasad N , linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org, Steve French , Sasha Levin +Message-ID: <20260716101824.1461870-3-sashal@kernel.org> + +From: David Howells + +[ Upstream commit dad80c6bff770d25f67ec25fe011730e4a463008 ] + +During mount, cifs_mount_get_tcon() gets a tcon resource connection record +and then attaches an fscache volume cookie to it. However, it does this +irrespective of whether or not the tcon returned from cifs_get_tcon() is a +new record or one that's already in use. This leads to a warning about a +volume cookie collision and a leaked volume cookie because tcon->fscache +gets reset. + +Fix this be adding a mutex and a "we've already tried this" flag and only +doing it once for the lifetime of the tcon. + +[!] Note: Looking at cifs_mount_get_tcon(), a more general solution may +actually be required. Reacquiring the volume cookie isn't the only thing +that function does: it also partially reinitialises the tcon record without +any locking - which may cause live filesystem ops already using the tcon +through a previous mount to malfunction. + +This can be reproduced simply by something like: + + mount //example.com/test /xfstest.test -o user=shares,pass=xxx,fsc + mount //example.com/test /mnt -o user=shares,pass=xxx,fsc + +Fixes: 70431bfd825d ("cifs: Support fscache indexing rewrite") +Signed-off-by: David Howells +Acked-by: Paulo Alcantara (Red Hat) +cc: Shyam Prasad N +cc: linux-cifs@vger.kernel.org +cc: linux-fsdevel@vger.kernel.org +Signed-off-by: Steve French +Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/cifsglob.h | 2 ++ + fs/smb/client/fscache.c | 13 +++++++++++++ + fs/smb/client/misc.c | 3 +++ + 3 files changed, 18 insertions(+) + +--- a/fs/smb/client/cifsglob.h ++++ b/fs/smb/client/cifsglob.h +@@ -1205,7 +1205,9 @@ struct cifs_tcon { + __u32 max_bytes_copy; + #ifdef CONFIG_CIFS_FSCACHE + u64 resource_id; /* server resource id */ ++ bool fscache_acquired; /* T if we've tried acquiring a cookie */ + struct fscache_volume *fscache; /* cookie for share */ ++ struct mutex fscache_lock; /* Prevent regetting a cookie */ + #endif + struct list_head pending_opens; /* list of incomplete opens */ + struct cached_fids *cfids; +--- a/fs/smb/client/fscache.c ++++ b/fs/smb/client/fscache.c +@@ -43,12 +43,23 @@ int cifs_fscache_get_super_cookie(struct + char *key; + int ret = -ENOMEM; + ++ if (tcon->fscache_acquired) ++ return 0; ++ ++ mutex_lock(&tcon->fscache_lock); ++ if (tcon->fscache_acquired) { ++ mutex_unlock(&tcon->fscache_lock); ++ return 0; ++ } ++ tcon->fscache_acquired = true; ++ + tcon->fscache = NULL; + switch (sa->sa_family) { + case AF_INET: + case AF_INET6: + break; + default: ++ mutex_unlock(&tcon->fscache_lock); + cifs_dbg(VFS, "Unknown network family '%d'\n", sa->sa_family); + return -EINVAL; + } +@@ -57,6 +68,7 @@ int cifs_fscache_get_super_cookie(struct + + sharename = extract_sharename(tcon->tree_name); + if (IS_ERR(sharename)) { ++ mutex_unlock(&tcon->fscache_lock); + cifs_dbg(FYI, "%s: couldn't extract sharename\n", __func__); + return PTR_ERR(sharename); + } +@@ -90,6 +102,7 @@ out_2: + kfree(key); + out: + kfree(sharename); ++ mutex_unlock(&tcon->fscache_lock); + return ret; + } + +--- a/fs/smb/client/misc.c ++++ b/fs/smb/client/misc.c +@@ -139,6 +139,9 @@ tconInfoAlloc(void) + spin_lock_init(&ret_buf->stat_lock); + atomic_set(&ret_buf->num_local_opens, 0); + atomic_set(&ret_buf->num_remote_opens, 0); ++#ifdef CONFIG_CIFS_FSCACHE ++ mutex_init(&ret_buf->fscache_lock); ++#endif + + return ret_buf; + } diff --git a/queue-6.1/cifs-handle-when-server-stops-supporting-multichannel.patch b/queue-6.1/cifs-handle-when-server-stops-supporting-multichannel.patch new file mode 100644 index 0000000000..432aa4360d --- /dev/null +++ b/queue-6.1/cifs-handle-when-server-stops-supporting-multichannel.patch @@ -0,0 +1,296 @@ +From stable+bounces-275229-greg=kroah.com@vger.kernel.org Thu Jul 16 12:24:13 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 06:18:20 -0400 +Subject: cifs: handle when server stops supporting multichannel +To: stable@vger.kernel.org +Cc: Shyam Prasad N , Steve French , Sasha Levin +Message-ID: <20260716101824.1461870-1-sashal@kernel.org> + +From: Shyam Prasad N + +[ Upstream commit ee1d21794e55ab76505745d24101331552182002 ] + +When a server stops supporting multichannel, we will +keep attempting reconnects to the secondary channels today. +Avoid this by freeing extra channels when negotiate +returns no multichannel support. + +Signed-off-by: Shyam Prasad N +Signed-off-by: Steve French +Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/cifsglob.h | 1 + fs/smb/client/cifsproto.h | 2 + + fs/smb/client/connect.c | 10 ++++++ + fs/smb/client/sess.c | 65 +++++++++++++++++++++++++++++++++------ + fs/smb/client/smb2pdu.c | 76 +++++++++++++++++++++++++++++++++++++++++++++- + fs/smb/client/transport.c | 2 - + 6 files changed, 145 insertions(+), 11 deletions(-) + +--- a/fs/smb/client/cifsglob.h ++++ b/fs/smb/client/cifsglob.h +@@ -639,6 +639,7 @@ struct TCP_Server_Info { + bool noautotune; /* do not autotune send buf sizes */ + bool nosharesock; + bool tcp_nodelay; ++ bool terminate; + unsigned int credits; /* send no more requests at once */ + unsigned int max_credits; /* can override large 32000 default at mnt */ + unsigned int in_flight; /* number of requests on the wire to server */ +--- a/fs/smb/client/cifsproto.h ++++ b/fs/smb/client/cifsproto.h +@@ -662,6 +662,8 @@ cifs_chan_needs_reconnect(struct cifs_se + bool + cifs_chan_is_iface_active(struct cifs_ses *ses, + struct TCP_Server_Info *server); ++void ++cifs_disable_secondary_channels(struct cifs_ses *ses); + int + cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server); + int +--- a/fs/smb/client/connect.c ++++ b/fs/smb/client/connect.c +@@ -259,6 +259,14 @@ cifs_mark_tcp_ses_conns_for_reconnect(st + + spin_lock(&cifs_tcp_ses_lock); + list_for_each_entry_safe(ses, nses, &pserver->smb_ses_list, smb_ses_list) { ++ /* ++ * if channel has been marked for termination, nothing to do ++ * for the channel. in fact, we cannot find the channel for the ++ * server. So safe to exit here ++ */ ++ if (server->terminate) ++ break; ++ + spin_lock(&ses->ses_lock); + if (ses->ses_status == SES_EXITING) { + spin_unlock(&ses->ses_lock); +@@ -302,6 +310,8 @@ cifs_mark_tcp_ses_conns_for_reconnect(st + spin_lock(&tcon->tc_lock); + tcon->status = TID_NEED_RECON; + spin_unlock(&tcon->tc_lock); ++ ++ cancel_delayed_work(&tcon->query_interfaces); + } + if (ses->tcon_ipc) { + ses->tcon_ipc->need_reconnect = true; +--- a/fs/smb/client/sess.c ++++ b/fs/smb/client/sess.c +@@ -278,6 +278,60 @@ int cifs_try_adding_channels(struct cifs + } + + /* ++ * called when multichannel is disabled by the server. ++ * this always gets called from smb2_reconnect ++ * and cannot get called in parallel threads. ++ */ ++void ++cifs_disable_secondary_channels(struct cifs_ses *ses) ++{ ++ int i, chan_count; ++ struct TCP_Server_Info *server; ++ struct cifs_server_iface *iface; ++ ++ spin_lock(&ses->chan_lock); ++ chan_count = ses->chan_count; ++ if (chan_count == 1) ++ goto done; ++ ++ ses->chan_count = 1; ++ ++ /* for all secondary channels reset the need reconnect bit */ ++ ses->chans_need_reconnect &= 1; ++ ++ for (i = 1; i < chan_count; i++) { ++ iface = ses->chans[i].iface; ++ server = ses->chans[i].server; ++ ++ if (iface) { ++ spin_lock(&ses->iface_lock); ++ kref_put(&iface->refcount, release_iface); ++ ses->chans[i].iface = NULL; ++ iface->num_channels--; ++ if (iface->weight_fulfilled) ++ iface->weight_fulfilled--; ++ spin_unlock(&ses->iface_lock); ++ } ++ ++ spin_unlock(&ses->chan_lock); ++ if (server && !server->terminate) { ++ server->terminate = true; ++ cifs_signal_cifsd_for_reconnect(server, false); ++ } ++ spin_lock(&ses->chan_lock); ++ ++ if (server) { ++ ses->chans[i].server = NULL; ++ cifs_put_tcp_session(server, false); ++ } ++ ++ } ++ ++done: ++ spin_unlock(&ses->chan_lock); ++} ++ ++/* + * update the iface for the channel if necessary. + * will return 0 when iface is updated, 1 if removed, 2 otherwise + * Must be called with chan_lock held. +@@ -570,15 +624,10 @@ cifs_ses_add_channel(struct cifs_sb_info + + out: + if (rc && chan->server) { +- /* +- * we should avoid race with these delayed works before we +- * remove this channel +- */ +- cancel_delayed_work_sync(&chan->server->echo); +- cancel_delayed_work_sync(&chan->server->resolve); +- cancel_delayed_work_sync(&chan->server->reconnect); ++ cifs_put_tcp_session(chan->server, 0); + + spin_lock(&ses->chan_lock); ++ + /* we rely on all bits beyond chan_count to be clear */ + cifs_chan_clear_need_reconnect(ses, chan->server); + ses->chan_count--; +@@ -588,8 +637,6 @@ out: + */ + WARN_ON(ses->chan_count < 1); + spin_unlock(&ses->chan_lock); +- +- cifs_put_tcp_session(chan->server, 0); + } + + free_xid(xid); +--- a/fs/smb/client/smb2pdu.c ++++ b/fs/smb/client/smb2pdu.c +@@ -160,6 +160,8 @@ smb2_reconnect(__le16 smb2_command, stru + int rc = 0; + struct nls_table *nls_codepage = NULL; + struct cifs_ses *ses; ++ struct TCP_Server_Info *pserver; ++ unsigned int chan_index; + + /* + * SMB2s NegProt, SessSetup, Logoff do not have tcon yet so +@@ -220,6 +222,12 @@ smb2_reconnect(__le16 smb2_command, stru + return -EAGAIN; + } + } ++ ++ /* if server is marked for termination, cifsd will cleanup */ ++ if (server->terminate) { ++ spin_unlock(&server->srv_lock); ++ return -EHOSTDOWN; ++ } + spin_unlock(&server->srv_lock); + + again: +@@ -239,11 +247,23 @@ again: + + mutex_lock(&ses->session_mutex); + /* ++ * if this is called by delayed work, and the channel has been disabled ++ * in parallel, the delayed work can continue to execute in parallel ++ * there's a chance that this channel may not exist anymore ++ */ ++ spin_lock(&server->srv_lock); ++ if (server->tcpStatus == CifsExiting) { ++ spin_unlock(&server->srv_lock); ++ mutex_unlock(&ses->session_mutex); ++ rc = -EHOSTDOWN; ++ goto out; ++ } ++ ++ /* + * Recheck after acquire mutex. If another thread is negotiating + * and the server never sends an answer the socket will be closed + * and tcpStatus set to reconnect. + */ +- spin_lock(&server->srv_lock); + if (server->tcpStatus == CifsNeedReconnect) { + spin_unlock(&server->srv_lock); + mutex_unlock(&ses->session_mutex); +@@ -280,6 +300,53 @@ again: + + rc = cifs_negotiate_protocol(0, ses, server); + if (!rc) { ++ /* ++ * if server stopped supporting multichannel ++ * and the first channel reconnected, disable all the others. ++ */ ++ if (ses->chan_count > 1 && ++ !(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) { ++ if (CIFS_SERVER_IS_CHAN(server)) { ++ cifs_dbg(VFS, "server %s does not support " \ ++ "multichannel anymore. skipping secondary channel\n", ++ ses->server->hostname); ++ ++ spin_lock(&ses->chan_lock); ++ chan_index = cifs_ses_get_chan_index(ses, server); ++ if (!chan_index) { ++ spin_unlock(&ses->chan_lock); ++ goto skip_terminate; ++ } ++ ++ ses->chans[chan_index].server = NULL; ++ spin_unlock(&ses->chan_lock); ++ ++ /* ++ * the above reference of server by channel ++ * needs to be dropped without holding chan_lock ++ * as cifs_put_tcp_session takes a higher lock ++ * i.e. cifs_tcp_ses_lock ++ */ ++ cifs_put_tcp_session(server, 1); ++ ++ server->terminate = true; ++ cifs_signal_cifsd_for_reconnect(server, false); ++ ++ /* mark primary server as needing reconnect */ ++ pserver = server->primary_server; ++ cifs_signal_cifsd_for_reconnect(pserver, false); ++ ++skip_terminate: ++ mutex_unlock(&ses->session_mutex); ++ rc = -EHOSTDOWN; ++ goto out; ++ } else { ++ cifs_server_dbg(VFS, "does not support " \ ++ "multichannel anymore. disabling all other channels\n"); ++ cifs_disable_secondary_channels(ses); ++ } ++ } ++ + rc = cifs_setup_session(0, ses, server, nls_codepage); + if ((rc == -EACCES) && !tcon->retry) { + mutex_unlock(&ses->session_mutex); +@@ -3903,6 +3970,13 @@ void smb2_reconnect_server(struct work_s + /* Prevent simultaneous reconnects that can corrupt tcon->rlist list */ + mutex_lock(&pserver->reconnect_mutex); + ++ /* if the server is marked for termination, drop the ref count here */ ++ if (server->terminate) { ++ cifs_put_tcp_session(server, true); ++ mutex_unlock(&pserver->reconnect_mutex); ++ return; ++ } ++ + INIT_LIST_HEAD(&tmp_list); + INIT_LIST_HEAD(&tmp_ses_list); + cifs_dbg(FYI, "Reconnecting tcons and channels\n"); +--- a/fs/smb/client/transport.c ++++ b/fs/smb/client/transport.c +@@ -1063,7 +1063,7 @@ struct TCP_Server_Info *cifs_pick_channe + spin_lock(&ses->chan_lock); + for (i = 0; i < ses->chan_count; i++) { + server = ses->chans[i].server; +- if (!server) ++ if (!server || server->terminate) + continue; + + /* diff --git a/queue-6.1/coresight-etb10-restore-atomic_t-for-shared-reading-state.patch b/queue-6.1/coresight-etb10-restore-atomic_t-for-shared-reading-state.patch new file mode 100644 index 0000000000..ef9d3085eb --- /dev/null +++ b/queue-6.1/coresight-etb10-restore-atomic_t-for-shared-reading-state.patch @@ -0,0 +1,123 @@ +From stable+bounces-274986-greg=kroah.com@vger.kernel.org Wed Jul 15 18:53:04 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 12:30:22 -0400 +Subject: coresight: etb10: restore atomic_t for shared reading state +To: stable@vger.kernel.org +Cc: Runyu Xiao , James Clark , Suzuki K Poulose , Sasha Levin +Message-ID: <20260715163022.950711-1-sashal@kernel.org> + +From: Runyu Xiao + +[ Upstream commit fa09f08ede3db3050ae16ae1ed92c902d0cada23 ] + +The etb10 miscdevice uses drvdata->reading as a shared exclusivity gate +for userspace buffer access. etb_open() claims that gate with +local_cmpxchg(), and etb_release() clears it with local_set(). + +That gate is shared per-device state rather than CPU-local state. A +running system can reach it whenever /dev/ is opened, closed, and +reopened by different tasks while the device remains registered, so the +same drvdata->reading variable may be claimed on one CPU and later +cleared on another. + +This code used to use atomic_t for the same gate, but commit +27b10da8fff2 ("coresight: etb10: moving to local atomic operations") +changed it to local_t even though the access pattern remained cross-task +and cross-CPU. Restore atomic_t together with atomic_cmpxchg() and +atomic_set() so the exclusivity gate again uses a primitive intended +for shared state. + +The issue was found on Linux v6.18.21 by our static analysis tool while +scanning surviving local_t-on-shared-state sites, and then manually +reviewed against the live etb10 file-op path. + +It was runtime-validated with a reproducible QEMU no-device KCSAN PoC +that kept the same report-local contract: + + 1. use one shared struct etb_drvdata carrier and its + drvdata->reading gate; + 2. call etb_open() and etb_release() sequentially on that gate to + confirm the original claim/clear path; + 3. bind the open side to CPU0 and the release side to CPU1 for the + same gate to show cross-CPU ownership; + 4. run bound workers that repeatedly race etb_open() and + etb_release() on the same gate until KCSAN reports a target hit. + +The harness recorded: + + L1 passed open=1 release=1 + reading_after_open=1 reading_after_release=0 + L2 passed open_cpu=0 release_cpu=1 + cross_cpu_release=1 reading_after=0 open_ret=0 + +Representative KCSAN excerpt from the no-device validation run: + + BUG: KCSAN: data-race in etb_open.constprop.0.isra.0 [vuln_msv] + + write to 0xffffffffc0003810 of 4 bytes by task 216 on cpu 1: + etb_open.constprop.0.isra.0+0x38/0x80 [vuln_msv] + l3_worker_thread_fn+0x4f/0xf0 [vuln_msv] + kthread+0x17e/0x1c0 + ret_from_fork+0x22/0x30 + + read to 0xffffffffc0003810 of 4 bytes by task 215 on cpu 0: + etb_open.constprop.0.isra.0+0x18/0x80 [vuln_msv] + l3_worker_thread_fn+0x4f/0xf0 [vuln_msv] + kthread+0x17e/0x1c0 + ret_from_fork+0x22/0x30 + + value changed: 0x00000000 -> 0x00000001 + + Reported by Kernel Concurrency Sanitizer on: + CPU: 0 PID: 215 Comm: etb10_l3_a Tainted: G O 6.1.66 #2 + +This no-device harness is not a real ETB10 hardware end-to-end run, but +it preserves the same shared drvdata->reading gate and the same +etb_open()/etb_release() claim/clear contract. No real ETB10 hardware +was available for runtime testing. + +Build-tested with: + make olddefconfig + make -j"$(nproc)" drivers/hwtracing/coresight/coresight-etb10.o + +Fixes: 27b10da8fff2 ("coresight: etb10: moving to local atomic operations") +Cc: stable@vger.kernel.org +Signed-off-by: Runyu Xiao +Reviewed-by: James Clark +Signed-off-by: Suzuki K Poulose +Link: https://lore.kernel.org/r/20260528165201.319452-1-runyu.xiao@seu.edu.cn +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/hwtracing/coresight/coresight-etb10.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +--- a/drivers/hwtracing/coresight/coresight-etb10.c ++++ b/drivers/hwtracing/coresight/coresight-etb10.c +@@ -86,7 +86,7 @@ struct etb_drvdata { + struct coresight_device *csdev; + struct miscdevice miscdev; + spinlock_t spinlock; +- local_t reading; ++ atomic_t reading; + pid_t pid; + u8 *buf; + u32 mode; +@@ -603,7 +603,7 @@ static int etb_open(struct inode *inode, + struct etb_drvdata *drvdata = container_of(file->private_data, + struct etb_drvdata, miscdev); + +- if (local_cmpxchg(&drvdata->reading, 0, 1)) ++ if (atomic_cmpxchg(&drvdata->reading, 0, 1)) + return -EBUSY; + + dev_dbg(&drvdata->csdev->dev, "%s: successfully opened\n", __func__); +@@ -641,7 +641,7 @@ static int etb_release(struct inode *ino + { + struct etb_drvdata *drvdata = container_of(file->private_data, + struct etb_drvdata, miscdev); +- local_set(&drvdata->reading, 0); ++ atomic_set(&drvdata->reading, 0); + + dev_dbg(&drvdata->csdev->dev, "%s: released\n", __func__); + return 0; diff --git a/queue-6.1/crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch b/queue-6.1/crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch new file mode 100644 index 0000000000..fe28ceb5ab --- /dev/null +++ b/queue-6.1/crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch @@ -0,0 +1,53 @@ +From stable+bounces-277811-greg=kroah.com@vger.kernel.org Mon Jul 20 16:02:06 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 09:51:02 -0400 +Subject: crypto: atmel - Drop explicit initialization of struct i2c_device_id::driver_data to 0 +To: stable@vger.kernel.org +Cc: "Uwe Kleine-König" , "Herbert Xu" , "Sasha Levin" +Message-ID: <20260720135103.1306043-2-sashal@kernel.org> + +From: Uwe Kleine-König + +[ 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 +Signed-off-by: Herbert Xu +Stable-dep-of: ea5e57cc9718 ("crypto: atmel-sha204a - drop hwrng quality reduction for ATSHA204A") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/atmel-ecc.c | 2 +- + drivers/crypto/atmel-sha204a.c | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +--- a/drivers/crypto/atmel-ecc.c ++++ b/drivers/crypto/atmel-ecc.c +@@ -381,7 +381,7 @@ MODULE_DEVICE_TABLE(of, atmel_ecc_dt_ids + #endif + + static const struct i2c_device_id atmel_ecc_id[] = { +- { "atecc508a", 0 }, ++ { "atecc508a" }, + { } + }; + MODULE_DEVICE_TABLE(i2c, atmel_ecc_id); +--- a/drivers/crypto/atmel-sha204a.c ++++ b/drivers/crypto/atmel-sha204a.c +@@ -140,8 +140,8 @@ static const struct of_device_id atmel_s + MODULE_DEVICE_TABLE(of, atmel_sha204a_dt_ids); + + static const struct i2c_device_id atmel_sha204a_id[] = { +- { "atsha204", 0 }, +- { "atsha204a", 0 }, ++ { "atsha204" }, ++ { "atsha204a" }, + { /* sentinel */ } + }; + MODULE_DEVICE_TABLE(i2c, atmel_sha204a_id); diff --git a/queue-6.1/crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch b/queue-6.1/crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch new file mode 100644 index 0000000000..c585faad56 --- /dev/null +++ b/queue-6.1/crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch @@ -0,0 +1,101 @@ +From stable+bounces-277812-greg=kroah.com@vger.kernel.org Mon Jul 20 17:01:02 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 09:51:03 -0400 +Subject: crypto: atmel-sha204a - drop hwrng quality reduction for ATSHA204A +To: stable@vger.kernel.org +Cc: Thorsten Blum , Ard Biesheuvel , Herbert Xu , Sasha Levin +Message-ID: <20260720135103.1306043-3-sashal@kernel.org> + +From: Thorsten Blum + +[ 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 +Reviewed-by: Ard Biesheuvel +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/atmel-sha204a.c | 19 ++++++++++++------- + 1 file changed, 12 insertions(+), 7 deletions(-) + +--- a/drivers/crypto/atmel-sha204a.c ++++ b/drivers/crypto/atmel-sha204a.c +@@ -18,6 +18,12 @@ + #include + #include "atmel-i2c.h" + ++/* ++ * According to review by Bill Cox [1], the ATSHA204 has very low entropy. ++ * [1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html ++ */ ++static const unsigned short atsha204_quality = 1; ++ + static void atmel_sha204a_rng_done(struct atmel_i2c_work_data *work_data, + void *areq, int status) + { +@@ -96,6 +102,7 @@ static int atmel_sha204a_probe(struct i2 + const struct i2c_device_id *id) + { + struct atmel_i2c_client_priv *i2c_priv; ++ const unsigned short *quality; + int ret; + + ret = atmel_i2c_probe(client, id); +@@ -109,11 +116,9 @@ static int atmel_sha204a_probe(struct i2 + i2c_priv->hwrng.name = dev_name(&client->dev); + i2c_priv->hwrng.read = atmel_sha204a_rng_read; + +- /* +- * According to review by Bill Cox [1], this HWRNG has very low entropy. +- * [1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html +- */ +- i2c_priv->hwrng.quality = 1; ++ quality = i2c_get_match_data(client); ++ if (quality) ++ i2c_priv->hwrng.quality = *quality; + + ret = devm_hwrng_register(&client->dev, &i2c_priv->hwrng); + if (ret) +@@ -133,14 +138,14 @@ static void atmel_sha204a_remove(struct + } + + static const struct of_device_id atmel_sha204a_dt_ids[] __maybe_unused = { +- { .compatible = "atmel,atsha204", }, ++ { .compatible = "atmel,atsha204", .data = &atsha204_quality }, + { .compatible = "atmel,atsha204a", }, + { /* sentinel */ } + }; + MODULE_DEVICE_TABLE(of, atmel_sha204a_dt_ids); + + static const struct i2c_device_id atmel_sha204a_id[] = { +- { "atsha204" }, ++ { "atsha204", (kernel_ulong_t)&atsha204_quality }, + { "atsha204a" }, + { /* sentinel */ } + }; diff --git a/queue-6.1/crypto-atmel-sha204a-mark-of-related-data-as-maybe-unused.patch b/queue-6.1/crypto-atmel-sha204a-mark-of-related-data-as-maybe-unused.patch new file mode 100644 index 0000000000..83570ea6ea --- /dev/null +++ b/queue-6.1/crypto-atmel-sha204a-mark-of-related-data-as-maybe-unused.patch @@ -0,0 +1,37 @@ +From stable+bounces-277810-greg=kroah.com@vger.kernel.org Mon Jul 20 16:19:32 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 09:51:01 -0400 +Subject: crypto: atmel-sha204a - Mark OF related data as maybe unused +To: stable@vger.kernel.org +Cc: Krzysztof Kozlowski , Herbert Xu , Sasha Levin +Message-ID: <20260720135103.1306043-1-sashal@kernel.org> + +From: Krzysztof Kozlowski + +[ Upstream commit 75f3d950054389e2556277e42170e37dd97cd872 ] + +The driver can be compile tested with !CONFIG_OF making certain data +unused: + + drivers/crypto/atmel-sha204a.c:129:34: error: ‘atmel_sha204a_dt_ids’ defined but not used [-Werror=unused-const-variable=] + +Signed-off-by: Krzysztof Kozlowski +Signed-off-by: Herbert Xu +Stable-dep-of: ea5e57cc9718 ("crypto: atmel-sha204a - drop hwrng quality reduction for ATSHA204A") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/atmel-sha204a.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/crypto/atmel-sha204a.c ++++ b/drivers/crypto/atmel-sha204a.c +@@ -132,7 +132,7 @@ static void atmel_sha204a_remove(struct + kfree((void *)i2c_priv->hwrng.priv); + } + +-static const struct of_device_id atmel_sha204a_dt_ids[] = { ++static const struct of_device_id atmel_sha204a_dt_ids[] __maybe_unused = { + { .compatible = "atmel,atsha204", }, + { .compatible = "atmel,atsha204a", }, + { /* sentinel */ } diff --git a/queue-6.1/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch b/queue-6.1/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch new file mode 100644 index 0000000000..0961b79ea9 --- /dev/null +++ b/queue-6.1/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch @@ -0,0 +1,57 @@ +From stable+bounces-278195-greg=kroah.com@vger.kernel.org Mon Jul 20 20:54:57 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 14:54:46 -0400 +Subject: crypto: qat - fix restarting state leak on allocation failure +To: stable@vger.kernel.org +Cc: Ahsan Atta , Maksim Lukoshkov , Giovanni Cabiddu , Herbert Xu , Sasha Levin +Message-ID: <20260720185446.3089237-1-sashal@kernel.org> + +From: Ahsan Atta + +[ 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 +Co-developed-by: Maksim Lukoshkov +Signed-off-by: Maksim Lukoshkov +Reviewed-by: Giovanni Cabiddu +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/qat/qat_common/adf_aer.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +--- a/drivers/crypto/qat/qat_common/adf_aer.c ++++ b/drivers/crypto/qat/qat_common/adf_aer.c +@@ -116,13 +116,14 @@ static int adf_dev_aer_schedule_reset(st + struct adf_reset_dev_data *reset_data; + + if (!adf_dev_started(accel_dev) || +- test_bit(ADF_STATUS_RESTARTING, &accel_dev->status)) ++ test_and_set_bit(ADF_STATUS_RESTARTING, &accel_dev->status)) + return 0; + +- set_bit(ADF_STATUS_RESTARTING, &accel_dev->status); + reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL); +- if (!reset_data) ++ if (!reset_data) { ++ clear_bit(ADF_STATUS_RESTARTING, &accel_dev->status); + return -ENOMEM; ++ } + reset_data->accel_dev = accel_dev; + init_completion(&reset_data->compl); + reset_data->mode = mode; diff --git a/queue-6.1/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch b/queue-6.1/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch new file mode 100644 index 0000000000..9854505abd --- /dev/null +++ b/queue-6.1/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch @@ -0,0 +1,216 @@ +From stable+bounces-274675-greg=kroah.com@vger.kernel.org Wed Jul 15 04:46:14 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 22:45:30 -0400 +Subject: crypto: qat - fix VF2PF work teardown race in adf_disable_sriov() +To: stable@vger.kernel.org +Cc: Giovanni Cabiddu , Ahsan Atta , Herbert Xu , Sasha Levin +Message-ID: <20260715024530.105076-1-sashal@kernel.org> + +From: Giovanni Cabiddu + +[ 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 +Reviewed-by: Ahsan Atta +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/qat/qat_common/adf_accel_devices.h | 2 + + drivers/crypto/qat/qat_common/adf_common_drv.h | 2 + + drivers/crypto/qat/qat_common/adf_isr.c | 39 ++++++++++++++++++++++ + drivers/crypto/qat/qat_common/adf_sriov.c | 20 ++++++++++- + 4 files changed, 61 insertions(+), 2 deletions(-) + +--- a/drivers/crypto/qat/qat_common/adf_accel_devices.h ++++ b/drivers/crypto/qat/qat_common/adf_accel_devices.h +@@ -284,6 +284,8 @@ struct adf_accel_dev { + struct { + /* protects VF2PF interrupts access */ + spinlock_t vf2pf_ints_lock; ++ /* prevents VF2PF handling from racing with VF state teardown */ ++ bool vf2pf_disabled; + /* vf_info is non-zero when SR-IOV is init'ed */ + struct adf_accel_vf_info *vf_info; + } pf; +--- a/drivers/crypto/qat/qat_common/adf_common_drv.h ++++ b/drivers/crypto/qat/qat_common/adf_common_drv.h +@@ -124,6 +124,7 @@ void qat_asym_algs_unregister(void); + + int adf_isr_resource_alloc(struct adf_accel_dev *accel_dev); + void adf_isr_resource_free(struct adf_accel_dev *accel_dev); ++void adf_isr_sync_ae_cluster(struct adf_accel_dev *accel_dev); + int adf_vf_isr_resource_alloc(struct adf_accel_dev *accel_dev); + void adf_vf_isr_resource_free(struct adf_accel_dev *accel_dev); + +@@ -196,6 +197,7 @@ void adf_misc_wq_flush(void); + int adf_sriov_configure(struct pci_dev *pdev, int numvfs); + void adf_disable_sriov(struct adf_accel_dev *accel_dev); + void adf_enable_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 vf_mask); ++void adf_enable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 num_vfs); + void adf_disable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev); + bool adf_recv_and_handle_pf2vf_msg(struct adf_accel_dev *accel_dev); + bool adf_recv_and_handle_vf2pf_msg(struct adf_accel_dev *accel_dev, u32 vf_nr); +--- a/drivers/crypto/qat/qat_common/adf_isr.c ++++ b/drivers/crypto/qat/qat_common/adf_isr.c +@@ -62,6 +62,23 @@ void adf_enable_vf2pf_interrupts(struct + unsigned long flags; + + spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags); ++ if (!READ_ONCE(accel_dev->pf.vf2pf_disabled)) ++ GET_PFVF_OPS(accel_dev)->enable_vf2pf_interrupts(pmisc_addr, vf_mask); ++ spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags); ++} ++ ++void adf_enable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 num_vfs) ++{ ++ void __iomem *pmisc_addr = adf_get_pmisc_base(accel_dev); ++ unsigned long flags; ++ u32 vf_mask; ++ ++ vf_mask = BIT_ULL(num_vfs) - 1; ++ if (!vf_mask) ++ return; ++ ++ spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags); ++ WRITE_ONCE(accel_dev->pf.vf2pf_disabled, false); + GET_PFVF_OPS(accel_dev)->enable_vf2pf_interrupts(pmisc_addr, vf_mask); + spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags); + } +@@ -72,6 +89,7 @@ void adf_disable_all_vf2pf_interrupts(st + unsigned long flags; + + spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags); ++ WRITE_ONCE(accel_dev->pf.vf2pf_disabled, true); + GET_PFVF_OPS(accel_dev)->disable_all_vf2pf_interrupts(pmisc_addr); + spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags); + } +@@ -151,6 +169,27 @@ static irqreturn_t adf_msix_isr_ae(int i + return IRQ_NONE; + } + ++void adf_isr_sync_ae_cluster(struct adf_accel_dev *accel_dev) ++{ ++ struct adf_accel_pci *pci_dev_info = &accel_dev->accel_pci_dev; ++ struct adf_hw_device_data *hw_data = GET_HW_DATA(accel_dev); ++ u32 num_entries = pci_dev_info->msix_entries.num_entries; ++ struct adf_irq *irqs = pci_dev_info->msix_entries.irqs; ++ u32 irq_idx; ++ int irq; ++ ++ if (!test_bit(ADF_STATUS_IRQ_ALLOCATED, &accel_dev->status) || !irqs) ++ return; ++ ++ irq_idx = num_entries > 1 ? hw_data->num_banks : 0; ++ if (irq_idx >= num_entries || !irqs[irq_idx].enabled) ++ return; ++ ++ irq = pci_irq_vector(pci_dev_info->pci_dev, hw_data->num_banks); ++ if (irq > 0) ++ synchronize_irq(irq); ++} ++ + static void adf_free_irqs(struct adf_accel_dev *accel_dev) + { + struct adf_accel_pci *pci_dev_info = &accel_dev->accel_pci_dev; +--- a/drivers/crypto/qat/qat_common/adf_sriov.c ++++ b/drivers/crypto/qat/qat_common/adf_sriov.c +@@ -26,18 +26,26 @@ static void adf_iov_send_resp(struct wor + u32 vf_nr = vf_info->vf_nr; + bool ret; + ++ if (READ_ONCE(accel_dev->pf.vf2pf_disabled)) ++ goto out; ++ + ret = adf_recv_and_handle_vf2pf_msg(accel_dev, vf_nr); + if (ret) + /* re-enable interrupt on PF from this VF */ + adf_enable_vf2pf_interrupts(accel_dev, 1 << vf_nr); + ++out: + kfree(pf2vf_resp); + } + + void adf_schedule_vf2pf_handler(struct adf_accel_vf_info *vf_info) + { ++ struct adf_accel_dev *accel_dev = vf_info->accel_dev; + struct adf_pf2vf_resp *pf2vf_resp; + ++ if (READ_ONCE(accel_dev->pf.vf2pf_disabled)) ++ return; ++ + pf2vf_resp = kzalloc(sizeof(*pf2vf_resp), GFP_ATOMIC); + if (!pf2vf_resp) + return; +@@ -47,6 +55,12 @@ void adf_schedule_vf2pf_handler(struct a + queue_work(pf2vf_resp_wq, &pf2vf_resp->pf2vf_resp_work); + } + ++static void adf_flush_pf2vf_resp_wq(void) ++{ ++ if (pf2vf_resp_wq) ++ flush_workqueue(pf2vf_resp_wq); ++} ++ + static int adf_enable_sriov(struct adf_accel_dev *accel_dev) + { + struct pci_dev *pdev = accel_to_pci_dev(accel_dev); +@@ -73,7 +87,7 @@ static int adf_enable_sriov(struct adf_a + hw_data->configure_iov_threads(accel_dev, true); + + /* Enable VF to PF interrupts for all VFs */ +- adf_enable_vf2pf_interrupts(accel_dev, BIT_ULL(totalvfs) - 1); ++ adf_enable_all_vf2pf_interrupts(accel_dev, totalvfs); + + /* + * Due to the hardware design, when SR-IOV and the ring arbiter +@@ -105,8 +119,10 @@ void adf_disable_sriov(struct adf_accel_ + adf_pf2vf_notify_restarting(accel_dev); + pci_disable_sriov(accel_to_pci_dev(accel_dev)); + +- /* Disable VF to PF interrupts */ ++ /* Block VF2PF work and disable VF to PF interrupts */ + adf_disable_all_vf2pf_interrupts(accel_dev); ++ adf_isr_sync_ae_cluster(accel_dev); ++ adf_flush_pf2vf_resp_wq(); + + /* Clear Valid bits in AE Thread to PCIe Function Mapping */ + if (hw_data->configure_iov_threads) diff --git a/queue-6.1/crypto-qat-validate-rsa-crt-component-lengths.patch b/queue-6.1/crypto-qat-validate-rsa-crt-component-lengths.patch new file mode 100644 index 0000000000..014ff8e4c1 --- /dev/null +++ b/queue-6.1/crypto-qat-validate-rsa-crt-component-lengths.patch @@ -0,0 +1,87 @@ +From stable+bounces-278235-greg=kroah.com@vger.kernel.org Mon Jul 20 22:47:19 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 16:44:41 -0400 +Subject: crypto: qat - validate RSA CRT component lengths +To: stable@vger.kernel.org +Cc: Giovanni Cabiddu , Ahsan Atta , Laurent M Coquerel , Herbert Xu , Sasha Levin +Message-ID: <20260720204441.3339249-1-sashal@kernel.org> + +From: Giovanni Cabiddu + +[ Upstream commit b3ac78756588059729b9195fcc9f4b37d54057a5 ] + +The generic RSA key parser (rsa_helper.c) bounds each CRT component (p, +q, dp, dq, qinv) by the modulus size n_sz, but qat_rsa_setkey_crt() +allocates half-size DMA buffers (key_sz / 2) and right-aligns each +component with: + + memcpy(dst + half_key_sz - len, src, len) + +When a CRT component is larger than half_key_sz the subtraction +underflows and memcpy writes past the DMA buffer, causing memory +corruption. + +Add a len > half_key_sz check next to the existing !len check for each +of the five CRT components so the driver falls back to the non-CRT path +instead of writing out of bounds. + +Fixes: 879f77e9071f ("crypto: qat - Add RSA CRT mode") +Cc: stable@vger.kernel.org +Signed-off-by: Giovanni Cabiddu +Reviewed-by: Ahsan Atta +Reviewed-by: Laurent M Coquerel +Tested-by: Laurent M Coquerel +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/qat/qat_common/qat_asym_algs.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +--- a/drivers/crypto/qat/qat_common/qat_asym_algs.c ++++ b/drivers/crypto/qat/qat_common/qat_asym_algs.c +@@ -1032,7 +1032,7 @@ static void qat_rsa_setkey_crt(struct qa + ptr = rsa_key->p; + len = rsa_key->p_sz; + qat_rsa_drop_leading_zeros(&ptr, &len); +- if (!len) ++ if (!len || len > half_key_sz) + goto err; + ctx->p = dma_alloc_coherent(dev, half_key_sz, &ctx->dma_p, GFP_KERNEL); + if (!ctx->p) +@@ -1043,7 +1043,7 @@ static void qat_rsa_setkey_crt(struct qa + ptr = rsa_key->q; + len = rsa_key->q_sz; + qat_rsa_drop_leading_zeros(&ptr, &len); +- if (!len) ++ if (!len || len > half_key_sz) + goto free_p; + ctx->q = dma_alloc_coherent(dev, half_key_sz, &ctx->dma_q, GFP_KERNEL); + if (!ctx->q) +@@ -1054,7 +1054,7 @@ static void qat_rsa_setkey_crt(struct qa + ptr = rsa_key->dp; + len = rsa_key->dp_sz; + qat_rsa_drop_leading_zeros(&ptr, &len); +- if (!len) ++ if (!len || len > half_key_sz) + goto free_q; + ctx->dp = dma_alloc_coherent(dev, half_key_sz, &ctx->dma_dp, + GFP_KERNEL); +@@ -1066,7 +1066,7 @@ static void qat_rsa_setkey_crt(struct qa + ptr = rsa_key->dq; + len = rsa_key->dq_sz; + qat_rsa_drop_leading_zeros(&ptr, &len); +- if (!len) ++ if (!len || len > half_key_sz) + goto free_dp; + ctx->dq = dma_alloc_coherent(dev, half_key_sz, &ctx->dma_dq, + GFP_KERNEL); +@@ -1078,7 +1078,7 @@ static void qat_rsa_setkey_crt(struct qa + ptr = rsa_key->qinv; + len = rsa_key->qinv_sz; + qat_rsa_drop_leading_zeros(&ptr, &len); +- if (!len) ++ if (!len || len > half_key_sz) + goto free_dq; + ctx->qinv = dma_alloc_coherent(dev, half_key_sz, &ctx->dma_qinv, + GFP_KERNEL); diff --git a/queue-6.1/gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch b/queue-6.1/gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch new file mode 100644 index 0000000000..e7e46cd20f --- /dev/null +++ b/queue-6.1/gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch @@ -0,0 +1,164 @@ +From stable+bounces-274997-greg=kroah.com@vger.kernel.org Wed Jul 15 18:57:27 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 12:44:15 -0400 +Subject: gpio: sch: use raw_spinlock_t in the irq startup path +To: stable@vger.kernel.org +Cc: Runyu Xiao , Sebastian Andrzej Siewior , Andy Shevchenko , Bartosz Golaszewski , Sasha Levin +Message-ID: <20260715164415.958948-1-sashal@kernel.org> + +From: Runyu Xiao + +[ 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 +Reviewed-by: Sebastian Andrzej Siewior +Reviewed-by: Andy Shevchenko +Link: https://patch.msgid.link/20260617154035.1199948-2-runyu.xiao@seu.edu.cn +Signed-off-by: Bartosz Golaszewski +[ adjusted context for 6.6's pre-MMIO `iobase` port I/O and void `sch_gpio_set()` (no set_rv `return 0;`) ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/gpio/gpio-sch.c | 32 ++++++++++++++++---------------- + 1 file changed, 16 insertions(+), 16 deletions(-) + +--- a/drivers/gpio/gpio-sch.c ++++ b/drivers/gpio/gpio-sch.c +@@ -38,7 +38,7 @@ + + struct sch_gpio { + struct gpio_chip chip; +- spinlock_t lock; ++ raw_spinlock_t lock; + unsigned short iobase; + unsigned short resume_base; + +@@ -102,9 +102,9 @@ static int sch_gpio_direction_in(struct + struct sch_gpio *sch = gpiochip_get_data(gc); + unsigned long flags; + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + sch_gpio_reg_set(sch, gpio_num, GIO, 1); +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + return 0; + } + +@@ -120,9 +120,9 @@ static void sch_gpio_set(struct gpio_chi + struct sch_gpio *sch = gpiochip_get_data(gc); + unsigned long flags; + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + sch_gpio_reg_set(sch, gpio_num, GLV, val); +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + } + + static int sch_gpio_direction_out(struct gpio_chip *gc, unsigned int gpio_num, +@@ -131,9 +131,9 @@ static int sch_gpio_direction_out(struct + struct sch_gpio *sch = gpiochip_get_data(gc); + unsigned long flags; + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + sch_gpio_reg_set(sch, gpio_num, GIO, 0); +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + + /* + * according to the datasheet, writing to the level register has no +@@ -193,14 +193,14 @@ static int sch_irq_type(struct irq_data + return -EINVAL; + } + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + + sch_gpio_reg_set(sch, gpio_num, GTPE, rising); + sch_gpio_reg_set(sch, gpio_num, GTNE, falling); + + irq_set_handler_locked(d, handle_edge_irq); + +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + + return 0; + } +@@ -212,9 +212,9 @@ static void sch_irq_ack(struct irq_data + irq_hw_number_t gpio_num = irqd_to_hwirq(d); + unsigned long flags; + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + sch_gpio_reg_set(sch, gpio_num, GTS, 1); +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + } + + static void sch_irq_mask_unmask(struct gpio_chip *gc, irq_hw_number_t gpio_num, int val) +@@ -222,9 +222,9 @@ static void sch_irq_mask_unmask(struct g + struct sch_gpio *sch = gpiochip_get_data(gc); + unsigned long flags; + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + sch_gpio_reg_set(sch, gpio_num, GGPE, val); +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + } + + static void sch_irq_mask(struct irq_data *d) +@@ -265,12 +265,12 @@ static u32 sch_gpio_gpe_handler(acpi_han + int offset; + u32 ret; + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + + core_status = inl(sch->iobase + CORE_BANK_OFFSET + GTS); + resume_status = inl(sch->iobase + RESUME_BANK_OFFSET + GTS); + +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + + pending = (resume_status << sch->resume_base) | core_status; + for_each_set_bit(offset, &pending, sch->chip.ngpio) +@@ -336,7 +336,7 @@ static int sch_gpio_probe(struct platfor + pdev->name)) + return -EBUSY; + +- spin_lock_init(&sch->lock); ++ raw_spin_lock_init(&sch->lock); + sch->iobase = res->start; + sch->chip = sch_gpio_chip; + sch->chip.label = dev_name(&pdev->dev); diff --git a/queue-6.1/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch b/queue-6.1/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch new file mode 100644 index 0000000000..2569b2148d --- /dev/null +++ b/queue-6.1/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch @@ -0,0 +1,69 @@ +From stable+bounces-277290-greg=kroah.com@vger.kernel.org Sat Jul 18 13:20:50 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 07:20:38 -0400 +Subject: hfs/hfsplus: fix u32 overflow in check_and_correct_requested_length +To: stable@vger.kernel.org +Cc: Tristan Madani , syzbot+6df204b70bf3261691c5@syzkaller.appspotmail.com, syzbot+e76bf3d19b85350571ac@syzkaller.appspotmail.com, Viacheslav Dubeyko , Sasha Levin +Message-ID: <20260718112038.2956632-2-sashal@kernel.org> + +From: Tristan Madani + +[ 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 +Reviewed-by: Viacheslav Dubeyko +Signed-off-by: Viacheslav Dubeyko +Link: https://lore.kernel.org/r/20260505111300.3592757-2-tristmd@gmail.com +Signed-off-by: Viacheslav Dubeyko +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/hfs/bnode.c | 2 +- + fs/hfsplus/hfsplus_fs.h | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +--- a/fs/hfs/bnode.c ++++ b/fs/hfs/bnode.c +@@ -41,7 +41,7 @@ u32 check_and_correct_requested_length(s + + node_size = node->tree->node_size; + +- if ((off + len) > node_size) { ++ if ((u64)off + len > node_size) { + u32 new_len = node_size - off; + + pr_err("requested length has been corrected: " +--- a/fs/hfsplus/hfsplus_fs.h ++++ b/fs/hfsplus/hfsplus_fs.h +@@ -613,7 +613,7 @@ u32 check_and_correct_requested_length(s + + node_size = node->tree->node_size; + +- if ((off + len) > node_size) { ++ if ((u64)off + len > node_size) { + u32 new_len = node_size - off; + + pr_err("requested length has been corrected: " diff --git a/queue-6.1/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch b/queue-6.1/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch new file mode 100644 index 0000000000..552eb40a42 --- /dev/null +++ b/queue-6.1/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch @@ -0,0 +1,956 @@ +From stable+bounces-277291-greg=kroah.com@vger.kernel.org Sat Jul 18 13:20:50 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 07:20:37 -0400 +Subject: hfs/hfsplus: prevent getting negative values of offset/length +To: stable@vger.kernel.org +Cc: Viacheslav Dubeyko , John Paul Adrian Glaubitz , Yangtao Li , linux-fsdevel@vger.kernel.org, Sasha Levin +Message-ID: <20260718112038.2956632-1-sashal@kernel.org> + +From: Viacheslav Dubeyko + +[ 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] +[ 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] +[ 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 +cc: John Paul Adrian Glaubitz +cc: Yangtao Li +cc: linux-fsdevel@vger.kernel.org +Link: https://lore.kernel.org/r/20251002200020.2578311-1-slava@dubeyko.com +Signed-off-by: Viacheslav Dubeyko +Stable-dep-of: 966cb76fb285 ("hfs/hfsplus: fix u32 overflow in check_and_correct_requested_length") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/hfs/bfind.c | 2 - + fs/hfs/bnode.c | 52 ++++++++++++++-------------- + fs/hfs/brec.c | 2 - + fs/hfs/btree.c | 2 - + fs/hfs/btree.h | 71 +++++++++++++++++++------------------- + fs/hfs/hfs_fs.h | 88 ++++++++++++++++++++++++++++-------------------- + fs/hfs/inode.c | 3 + + fs/hfsplus/bfind.c | 2 - + fs/hfsplus/bnode.c | 60 ++++++++++++++++---------------- + fs/hfsplus/brec.c | 2 - + fs/hfsplus/btree.c | 2 - + fs/hfsplus/hfsplus_fs.h | 38 ++++++++++---------- + 12 files changed, 171 insertions(+), 153 deletions(-) + +--- a/fs/hfs/bfind.c ++++ b/fs/hfs/bfind.c +@@ -164,7 +164,7 @@ release: + return res; + } + +-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len) ++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len) + { + int res; + +--- a/fs/hfs/bnode.c ++++ b/fs/hfs/bnode.c +@@ -16,14 +16,14 @@ + #include "btree.h" + + static inline +-bool is_bnode_offset_valid(struct hfs_bnode *node, int off) ++bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off) + { + bool is_valid = off < node->tree->node_size; + + if (!is_valid) { + pr_err("requested invalid offset: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d\n", ++ "node_size %u, offset %u\n", + node->this, node->type, node->height, + node->tree->node_size, off); + } +@@ -32,7 +32,7 @@ bool is_bnode_offset_valid(struct hfs_bn + } + + static inline +-int check_and_correct_requested_length(struct hfs_bnode *node, int off, int len) ++u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len) + { + unsigned int node_size; + +@@ -42,12 +42,12 @@ int check_and_correct_requested_length(s + node_size = node->tree->node_size; + + if ((off + len) > node_size) { +- int new_len = (int)node_size - off; ++ u32 new_len = node_size - off; + + pr_err("requested length has been corrected: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, " +- "requested_len %d, corrected_len %d\n", ++ "node_size %u, offset %u, " ++ "requested_len %u, corrected_len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len, new_len); + +@@ -57,12 +57,12 @@ int check_and_correct_requested_length(s + return len; + } + +-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len) ++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len) + { + struct page *page; +- int pagenum; +- int bytes_read; +- int bytes_to_read; ++ u32 pagenum; ++ u32 bytes_read; ++ u32 bytes_to_read; + + memset(buf, 0, len); + +@@ -72,7 +72,7 @@ void hfs_bnode_read(struct hfs_bnode *no + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -88,7 +88,7 @@ void hfs_bnode_read(struct hfs_bnode *no + if (pagenum >= node->tree->pages_per_bnode) + break; + page = node->page[pagenum]; +- bytes_to_read = min_t(int, len - bytes_read, PAGE_SIZE - off); ++ bytes_to_read = min_t(u32, len - bytes_read, PAGE_SIZE - off); + + memcpy_from_page(buf + bytes_read, page, off, bytes_to_read); + +@@ -97,7 +97,7 @@ void hfs_bnode_read(struct hfs_bnode *no + } + } + +-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off) ++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off) + { + __be16 data; + // optimize later... +@@ -105,7 +105,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode + return be16_to_cpu(data); + } + +-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off) ++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off) + { + u8 data; + // optimize later... +@@ -113,10 +113,10 @@ u8 hfs_bnode_read_u8(struct hfs_bnode *n + return data; + } + +-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off) ++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off) + { + struct hfs_btree *tree; +- int key_len; ++ u32 key_len; + + tree = node->tree; + if (node->type == HFS_NODE_LEAF || +@@ -127,14 +127,14 @@ void hfs_bnode_read_key(struct hfs_bnode + + if (key_len > sizeof(hfs_btree_key) || key_len < 1) { + memset(key, 0, sizeof(hfs_btree_key)); +- pr_err("hfs: Invalid key length: %d\n", key_len); ++ pr_err("hfs: Invalid key length: %u\n", key_len); + return; + } + + hfs_bnode_read(node, key, off, key_len); + } + +-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len) ++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len) + { + struct page *page; + +@@ -144,7 +144,7 @@ void hfs_bnode_write(struct hfs_bnode *n + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -159,20 +159,20 @@ void hfs_bnode_write(struct hfs_bnode *n + set_page_dirty(page); + } + +-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data) ++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data) + { + __be16 v = cpu_to_be16(data); + // optimize later... + hfs_bnode_write(node, &v, off, 2); + } + +-void hfs_bnode_write_u8(struct hfs_bnode *node, int off, u8 data) ++void hfs_bnode_write_u8(struct hfs_bnode *node, u32 off, u8 data) + { + // optimize later... + hfs_bnode_write(node, &data, off, 1); + } + +-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len) ++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len) + { + struct page *page; + +@@ -182,7 +182,7 @@ void hfs_bnode_clear(struct hfs_bnode *n + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -197,8 +197,8 @@ void hfs_bnode_clear(struct hfs_bnode *n + set_page_dirty(page); + } + +-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst, +- struct hfs_bnode *src_node, int src, int len) ++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst, ++ struct hfs_bnode *src_node, u32 src, u32 len) + { + struct page *src_page, *dst_page; + +@@ -218,7 +218,7 @@ void hfs_bnode_copy(struct hfs_bnode *ds + set_page_dirty(dst_page); + } + +-void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len) ++void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len) + { + struct page *page; + void *ptr; +--- a/fs/hfs/brec.c ++++ b/fs/hfs/brec.c +@@ -62,7 +62,7 @@ u16 hfs_brec_keylen(struct hfs_bnode *no + return retval; + } + +-int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len) ++int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len) + { + struct hfs_btree *tree; + struct hfs_bnode *node, *new_node; +--- a/fs/hfs/btree.c ++++ b/fs/hfs/btree.c +@@ -224,7 +224,7 @@ static struct hfs_bnode *hfs_bmap_new_bm + } + + /* Make sure @tree has enough space for the @rsvd_nodes */ +-int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes) ++int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes) + { + struct inode *inode = tree->inode; + u32 count; +--- a/fs/hfs/btree.h ++++ b/fs/hfs/btree.h +@@ -86,48 +86,49 @@ struct hfs_find_data { + + + /* btree.c */ +-extern struct hfs_btree *hfs_btree_open(struct super_block *, u32, btree_keycmp); +-extern void hfs_btree_close(struct hfs_btree *); +-extern void hfs_btree_write(struct hfs_btree *); +-extern int hfs_bmap_reserve(struct hfs_btree *, int); +-extern struct hfs_bnode * hfs_bmap_alloc(struct hfs_btree *); ++extern struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, ++ btree_keycmp keycmp); ++extern void hfs_btree_close(struct hfs_btree *tree); ++extern void hfs_btree_write(struct hfs_btree *tree); ++extern int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes); ++extern struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree); + extern void hfs_bmap_free(struct hfs_bnode *node); + + /* bnode.c */ +-extern void hfs_bnode_read(struct hfs_bnode *, void *, int, int); +-extern u16 hfs_bnode_read_u16(struct hfs_bnode *, int); +-extern u8 hfs_bnode_read_u8(struct hfs_bnode *, int); +-extern void hfs_bnode_read_key(struct hfs_bnode *, void *, int); +-extern void hfs_bnode_write(struct hfs_bnode *, void *, int, int); +-extern void hfs_bnode_write_u16(struct hfs_bnode *, int, u16); +-extern void hfs_bnode_write_u8(struct hfs_bnode *, int, u8); +-extern void hfs_bnode_clear(struct hfs_bnode *, int, int); +-extern void hfs_bnode_copy(struct hfs_bnode *, int, +- struct hfs_bnode *, int, int); +-extern void hfs_bnode_move(struct hfs_bnode *, int, int, int); +-extern void hfs_bnode_dump(struct hfs_bnode *); +-extern void hfs_bnode_unlink(struct hfs_bnode *); +-extern struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *, u32); +-extern struct hfs_bnode *hfs_bnode_find(struct hfs_btree *, u32); +-extern void hfs_bnode_unhash(struct hfs_bnode *); +-extern void hfs_bnode_free(struct hfs_bnode *); +-extern struct hfs_bnode *hfs_bnode_create(struct hfs_btree *, u32); +-extern void hfs_bnode_get(struct hfs_bnode *); +-extern void hfs_bnode_put(struct hfs_bnode *); ++extern void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len); ++extern u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off); ++extern u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off); ++extern void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off); ++extern void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len); ++extern void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data); ++extern void hfs_bnode_write_u8(struct hfs_bnode *node, u32 off, u8 data); ++extern void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len); ++extern void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst, ++ struct hfs_bnode *src_node, u32 src, u32 len); ++extern void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len); ++extern void hfs_bnode_dump(struct hfs_bnode *node); ++extern void hfs_bnode_unlink(struct hfs_bnode *node); ++extern struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid); ++extern struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num); ++extern void hfs_bnode_unhash(struct hfs_bnode *node); ++extern void hfs_bnode_free(struct hfs_bnode *node); ++extern struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num); ++extern void hfs_bnode_get(struct hfs_bnode *node); ++extern void hfs_bnode_put(struct hfs_bnode *node); + + /* brec.c */ +-extern u16 hfs_brec_lenoff(struct hfs_bnode *, u16, u16 *); +-extern u16 hfs_brec_keylen(struct hfs_bnode *, u16); +-extern int hfs_brec_insert(struct hfs_find_data *, void *, int); +-extern int hfs_brec_remove(struct hfs_find_data *); ++extern u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off); ++extern u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec); ++extern int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len); ++extern int hfs_brec_remove(struct hfs_find_data *fd); + + /* bfind.c */ +-extern int hfs_find_init(struct hfs_btree *, struct hfs_find_data *); +-extern void hfs_find_exit(struct hfs_find_data *); +-extern int __hfs_brec_find(struct hfs_bnode *, struct hfs_find_data *); +-extern int hfs_brec_find(struct hfs_find_data *); +-extern int hfs_brec_read(struct hfs_find_data *, void *, int); +-extern int hfs_brec_goto(struct hfs_find_data *, int); ++extern int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd); ++extern void hfs_find_exit(struct hfs_find_data *fd); ++extern int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd); ++extern int hfs_brec_find(struct hfs_find_data *fd); ++extern int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len); ++extern int hfs_brec_goto(struct hfs_find_data *fd, int cnt); + + + struct hfs_bnode_desc { +--- a/fs/hfs/hfs_fs.h ++++ b/fs/hfs/hfs_fs.h +@@ -171,73 +171,89 @@ struct hfs_sb_info { + #define HFS_FLG_ALT_MDB_DIRTY 2 + + /* bitmap.c */ +-extern u32 hfs_vbm_search_free(struct super_block *, u32, u32 *); +-extern int hfs_clear_vbm_bits(struct super_block *, u16, u16); ++extern u32 hfs_vbm_search_free(struct super_block *sb, u32 goal, u32 *num_bits); ++extern int hfs_clear_vbm_bits(struct super_block *sb, u16 start, u16 count); + + /* catalog.c */ +-extern int hfs_cat_keycmp(const btree_key *, const btree_key *); ++extern int hfs_cat_keycmp(const btree_key *key1, const btree_key *key2); + struct hfs_find_data; +-extern int hfs_cat_find_brec(struct super_block *, u32, struct hfs_find_data *); +-extern int hfs_cat_create(u32, struct inode *, const struct qstr *, struct inode *); +-extern int hfs_cat_delete(u32, struct inode *, const struct qstr *); +-extern int hfs_cat_move(u32, struct inode *, const struct qstr *, +- struct inode *, const struct qstr *); +-extern void hfs_cat_build_key(struct super_block *, btree_key *, u32, const struct qstr *); ++extern int hfs_cat_find_brec(struct super_block *sb, u32 cnid, ++ struct hfs_find_data *fd); ++extern int hfs_cat_create(u32 cnid, struct inode *dir, ++ const struct qstr *str, struct inode *inode); ++extern int hfs_cat_delete(u32 cnid, struct inode *dir, const struct qstr *str); ++extern int hfs_cat_move(u32 cnid, struct inode *src_dir, ++ const struct qstr *src_name, ++ struct inode *dst_dir, ++ const struct qstr *dst_name); ++extern void hfs_cat_build_key(struct super_block *sb, btree_key *key, ++ u32 parent, const struct qstr *name); + + /* dir.c */ + extern const struct file_operations hfs_dir_operations; + extern const struct inode_operations hfs_dir_inode_operations; + + /* extent.c */ +-extern int hfs_ext_keycmp(const btree_key *, const btree_key *); +-extern int hfs_free_fork(struct super_block *, struct hfs_cat_file *, int); +-extern int hfs_ext_write_extent(struct inode *); +-extern int hfs_extend_file(struct inode *); +-extern void hfs_file_truncate(struct inode *); ++extern int hfs_ext_keycmp(const btree_key *key1, const btree_key *key2); ++extern int hfs_free_fork(struct super_block *sb, ++ struct hfs_cat_file *file, int type); ++extern int hfs_ext_write_extent(struct inode *inode); ++extern int hfs_extend_file(struct inode *inode); ++extern void hfs_file_truncate(struct inode *inode); + +-extern int hfs_get_block(struct inode *, sector_t, struct buffer_head *, int); ++extern int hfs_get_block(struct inode *inode, sector_t block, ++ struct buffer_head *bh_result, int create); + + /* inode.c */ + extern const struct address_space_operations hfs_aops; + extern const struct address_space_operations hfs_btree_aops; + + int hfs_write_begin(struct file *file, struct address_space *mapping, +- loff_t pos, unsigned len, struct page **pagep, void **fsdata); +-extern struct inode *hfs_new_inode(struct inode *, const struct qstr *, umode_t); +-extern void hfs_inode_write_fork(struct inode *, struct hfs_extent *, __be32 *, __be32 *); +-extern int hfs_write_inode(struct inode *, struct writeback_control *); +-extern int hfs_inode_setattr(struct user_namespace *, struct dentry *, +- struct iattr *); ++ loff_t pos, unsigned int len, struct page **pagep, ++ void **fsdata); ++extern struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, ++ umode_t mode); ++extern void hfs_inode_write_fork(struct inode *inode, struct hfs_extent *ext, ++ __be32 *log_size, __be32 *phys_size); ++extern int hfs_write_inode(struct inode *inode, struct writeback_control *wbc); ++extern int hfs_inode_setattr(struct user_namespace *mnt_userns, ++ struct dentry *dentry, struct iattr *attr); + extern void hfs_inode_read_fork(struct inode *inode, struct hfs_extent *ext, +- __be32 log_size, __be32 phys_size, u32 clump_size); +-extern struct inode *hfs_iget(struct super_block *, struct hfs_cat_key *, hfs_cat_rec *); +-extern void hfs_evict_inode(struct inode *); +-extern void hfs_delete_inode(struct inode *); ++ __be32 __log_size, __be32 phys_size, ++ u32 clump_size); ++extern struct inode *hfs_iget(struct super_block *sb, struct hfs_cat_key *key, ++ hfs_cat_rec *rec); ++extern void hfs_evict_inode(struct inode *inode); ++extern void hfs_delete_inode(struct inode *inode); + + /* attr.c */ + extern const struct xattr_handler *hfs_xattr_handlers[]; + + /* mdb.c */ +-extern int hfs_mdb_get(struct super_block *); +-extern void hfs_mdb_commit(struct super_block *); +-extern void hfs_mdb_close(struct super_block *); +-extern void hfs_mdb_put(struct super_block *); ++extern int hfs_mdb_get(struct super_block *sb); ++extern void hfs_mdb_commit(struct super_block *sb); ++extern void hfs_mdb_close(struct super_block *sb); ++extern void hfs_mdb_put(struct super_block *sb); + + /* part_tbl.c */ +-extern int hfs_part_find(struct super_block *, sector_t *, sector_t *); ++extern int hfs_part_find(struct super_block *sb, ++ sector_t *part_start, sector_t *part_size); + + /* string.c */ + extern const struct dentry_operations hfs_dentry_operations; + +-extern int hfs_hash_dentry(const struct dentry *, struct qstr *); +-extern int hfs_strcmp(const unsigned char *, unsigned int, +- const unsigned char *, unsigned int); ++extern int hfs_hash_dentry(const struct dentry *dentry, struct qstr *this); ++extern int hfs_strcmp(const unsigned char *s1, unsigned int len1, ++ const unsigned char *s2, unsigned int len2); + extern int hfs_compare_dentry(const struct dentry *dentry, +- unsigned int len, const char *str, const struct qstr *name); ++ unsigned int len, const char *str, ++ const struct qstr *name); + + /* trans.c */ +-extern void hfs_asc2mac(struct super_block *, struct hfs_name *, const struct qstr *); +-extern int hfs_mac2asc(struct super_block *, char *, const struct hfs_name *); ++extern void hfs_asc2mac(struct super_block *sb, ++ struct hfs_name *out, const struct qstr *in); ++extern int hfs_mac2asc(struct super_block *sb, ++ char *out, const struct hfs_name *in); + + /* super.c */ + extern void hfs_mark_mdb_dirty(struct super_block *sb); +--- a/fs/hfs/inode.c ++++ b/fs/hfs/inode.c +@@ -50,7 +50,8 @@ static void hfs_write_failed(struct addr + } + + int hfs_write_begin(struct file *file, struct address_space *mapping, +- loff_t pos, unsigned len, struct page **pagep, void **fsdata) ++ loff_t pos, unsigned int len, struct page **pagep, ++ void **fsdata) + { + int ret; + +--- a/fs/hfsplus/bfind.c ++++ b/fs/hfsplus/bfind.c +@@ -210,7 +210,7 @@ release: + return res; + } + +-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len) ++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len) + { + int res; + +--- a/fs/hfsplus/bnode.c ++++ b/fs/hfsplus/bnode.c +@@ -20,10 +20,10 @@ + + + /* Copy a specified range of bytes from the raw data of a node */ +-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len) ++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len) + { + struct page **pagep; +- int l; ++ u32 l; + + memset(buf, 0, len); + +@@ -33,7 +33,7 @@ void hfs_bnode_read(struct hfs_bnode *no + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -45,17 +45,17 @@ void hfs_bnode_read(struct hfs_bnode *no + pagep = node->page + (off >> PAGE_SHIFT); + off &= ~PAGE_MASK; + +- l = min_t(int, len, PAGE_SIZE - off); ++ l = min_t(u32, len, PAGE_SIZE - off); + memcpy_from_page(buf, *pagep, off, l); + + while ((len -= l) != 0) { + buf += l; +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + memcpy_from_page(buf, *++pagep, 0, l); + } + } + +-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off) ++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off) + { + __be16 data; + /* TODO: optimize later... */ +@@ -63,7 +63,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode + return be16_to_cpu(data); + } + +-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off) ++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off) + { + u8 data; + /* TODO: optimize later... */ +@@ -71,10 +71,10 @@ u8 hfs_bnode_read_u8(struct hfs_bnode *n + return data; + } + +-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off) ++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off) + { + struct hfs_btree *tree; +- int key_len; ++ u32 key_len; + + tree = node->tree; + if (node->type == HFS_NODE_LEAF || +@@ -86,17 +86,17 @@ void hfs_bnode_read_key(struct hfs_bnode + + if (key_len > sizeof(hfsplus_btree_key) || key_len < 1) { + memset(key, 0, sizeof(hfsplus_btree_key)); +- pr_err("hfsplus: Invalid key length: %d\n", key_len); ++ pr_err("hfsplus: Invalid key length: %u\n", key_len); + return; + } + + hfs_bnode_read(node, key, off, key_len); + } + +-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len) ++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len) + { + struct page **pagep; +- int l; ++ u32 l; + + if (!is_bnode_offset_valid(node, off)) + return; +@@ -104,7 +104,7 @@ void hfs_bnode_write(struct hfs_bnode *n + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -116,29 +116,29 @@ void hfs_bnode_write(struct hfs_bnode *n + pagep = node->page + (off >> PAGE_SHIFT); + off &= ~PAGE_MASK; + +- l = min_t(int, len, PAGE_SIZE - off); ++ l = min_t(u32, len, PAGE_SIZE - off); + memcpy_to_page(*pagep, off, buf, l); + set_page_dirty(*pagep); + + while ((len -= l) != 0) { + buf += l; +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + memcpy_to_page(*++pagep, 0, buf, l); + set_page_dirty(*pagep); + } + } + +-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data) ++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data) + { + __be16 v = cpu_to_be16(data); + /* TODO: optimize later... */ + hfs_bnode_write(node, &v, off, 2); + } + +-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len) ++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len) + { + struct page **pagep; +- int l; ++ u32 l; + + if (!is_bnode_offset_valid(node, off)) + return; +@@ -146,7 +146,7 @@ void hfs_bnode_clear(struct hfs_bnode *n + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -158,22 +158,22 @@ void hfs_bnode_clear(struct hfs_bnode *n + pagep = node->page + (off >> PAGE_SHIFT); + off &= ~PAGE_MASK; + +- l = min_t(int, len, PAGE_SIZE - off); ++ l = min_t(u32, len, PAGE_SIZE - off); + memzero_page(*pagep, off, l); + set_page_dirty(*pagep); + + while ((len -= l) != 0) { +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + memzero_page(*++pagep, 0, l); + set_page_dirty(*pagep); + } + } + +-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst, +- struct hfs_bnode *src_node, int src, int len) ++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst, ++ struct hfs_bnode *src_node, u32 src, u32 len) + { + struct page **src_page, **dst_page; +- int l; ++ u32 l; + + hfs_dbg(BNODE_MOD, "copybytes: %u,%u,%u\n", dst, src, len); + if (!len) +@@ -190,12 +190,12 @@ void hfs_bnode_copy(struct hfs_bnode *ds + dst &= ~PAGE_MASK; + + if (src == dst) { +- l = min_t(int, len, PAGE_SIZE - src); ++ l = min_t(u32, len, PAGE_SIZE - src); + memcpy_page(*dst_page, src, *src_page, src, l); + set_page_dirty(*dst_page); + + while ((len -= l) != 0) { +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + memcpy_page(*++dst_page, 0, *++src_page, 0, l); + set_page_dirty(*dst_page); + } +@@ -227,11 +227,11 @@ void hfs_bnode_copy(struct hfs_bnode *ds + } + } + +-void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len) ++void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len) + { + struct page **src_page, **dst_page; + void *src_ptr, *dst_ptr; +- int l; ++ u32 l; + + hfs_dbg(BNODE_MOD, "movebytes: %u,%u,%u\n", dst, src, len); + if (!len) +@@ -301,7 +301,7 @@ void hfs_bnode_move(struct hfs_bnode *no + dst &= ~PAGE_MASK; + + if (src == dst) { +- l = min_t(int, len, PAGE_SIZE - src); ++ l = min_t(u32, len, PAGE_SIZE - src); + + dst_ptr = kmap_local_page(*dst_page) + src; + src_ptr = kmap_local_page(*src_page) + src; +@@ -311,7 +311,7 @@ void hfs_bnode_move(struct hfs_bnode *no + kunmap_local(dst_ptr); + + while ((len -= l) != 0) { +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + dst_ptr = kmap_local_page(*++dst_page); + src_ptr = kmap_local_page(*++src_page); + memmove(dst_ptr, src_ptr, l); +--- a/fs/hfsplus/brec.c ++++ b/fs/hfsplus/brec.c +@@ -60,7 +60,7 @@ u16 hfs_brec_keylen(struct hfs_bnode *no + return retval; + } + +-int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len) ++int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len) + { + struct hfs_btree *tree; + struct hfs_bnode *node, *new_node; +--- a/fs/hfsplus/btree.c ++++ b/fs/hfsplus/btree.c +@@ -344,7 +344,7 @@ static struct hfs_bnode *hfs_bmap_new_bm + } + + /* Make sure @tree has enough space for the @rsvd_nodes */ +-int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes) ++int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes) + { + struct inode *inode = tree->inode; + struct hfsplus_inode_info *hip = HFSPLUS_I(inode); +--- a/fs/hfsplus/hfsplus_fs.h ++++ b/fs/hfsplus/hfsplus_fs.h +@@ -388,21 +388,21 @@ u32 hfsplus_calc_btree_clump_size(u32 bl + struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id); + void hfs_btree_close(struct hfs_btree *tree); + int hfs_btree_write(struct hfs_btree *tree); +-int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes); ++int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes); + struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree); + void hfs_bmap_free(struct hfs_bnode *node); + + /* bnode.c */ +-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len); +-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off); +-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off); +-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off); +-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len); +-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data); +-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len); +-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst, +- struct hfs_bnode *src_node, int src, int len); +-void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len); ++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len); ++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off); ++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off); ++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off); ++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len); ++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data); ++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len); ++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst, ++ struct hfs_bnode *src_node, u32 src, u32 len); ++void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len); + void hfs_bnode_dump(struct hfs_bnode *node); + void hfs_bnode_unlink(struct hfs_bnode *node); + struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid); +@@ -417,7 +417,7 @@ bool hfs_bnode_need_zeroout(struct hfs_b + /* brec.c */ + u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off); + u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec); +-int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len); ++int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len); + int hfs_brec_remove(struct hfs_find_data *fd); + + /* bfind.c */ +@@ -430,7 +430,7 @@ int hfs_find_rec_by_key(struct hfs_bnode + int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd, + search_strategy_t rec_found); + int hfs_brec_find(struct hfs_find_data *fd, search_strategy_t do_key_compare); +-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len); ++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len); + int hfs_brec_goto(struct hfs_find_data *fd, int cnt); + + /* catalog.c */ +@@ -588,14 +588,14 @@ hfsplus_btree_lock_class(struct hfs_btre + } + + static inline +-bool is_bnode_offset_valid(struct hfs_bnode *node, int off) ++bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off) + { + bool is_valid = off < node->tree->node_size; + + if (!is_valid) { + pr_err("requested invalid offset: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d\n", ++ "node_size %u, offset %u\n", + node->this, node->type, node->height, + node->tree->node_size, off); + } +@@ -604,7 +604,7 @@ bool is_bnode_offset_valid(struct hfs_bn + } + + static inline +-int check_and_correct_requested_length(struct hfs_bnode *node, int off, int len) ++u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len) + { + unsigned int node_size; + +@@ -614,12 +614,12 @@ int check_and_correct_requested_length(s + node_size = node->tree->node_size; + + if ((off + len) > node_size) { +- int new_len = (int)node_size - off; ++ u32 new_len = node_size - off; + + pr_err("requested length has been corrected: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, " +- "requested_len %d, corrected_len %d\n", ++ "node_size %u, offset %u, " ++ "requested_len %u, corrected_len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len, new_len); + diff --git a/queue-6.1/hid-add-haptics-page-defines.patch b/queue-6.1/hid-add-haptics-page-defines.patch new file mode 100644 index 0000000000..4198aee901 --- /dev/null +++ b/queue-6.1/hid-add-haptics-page-defines.patch @@ -0,0 +1,78 @@ +From stable+bounces-277068-greg=kroah.com@vger.kernel.org Fri Jul 17 16:29:58 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:23:09 -0400 +Subject: HID: add haptics page defines +To: stable@vger.kernel.org +Cc: Angela Czubak , Jonathan Denose , Benjamin Tissoires , Sasha Levin +Message-ID: <20260717142310.1824932-1-sashal@kernel.org> + +From: Angela Czubak + +[ 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 +Co-developed-by: Jonathan Denose +Signed-off-by: Jonathan Denose +Signed-off-by: Benjamin Tissoires +Stable-dep-of: 8813b0612275 ("HID: multitouch: fix out-of-bounds bit access on mt_io_flags") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/hid.h | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +--- a/include/linux/hid.h ++++ b/include/linux/hid.h +@@ -153,6 +153,7 @@ struct hid_item { + #define HID_UP_TELEPHONY 0x000b0000 + #define HID_UP_CONSUMER 0x000c0000 + #define HID_UP_DIGITIZER 0x000d0000 ++#define HID_UP_HAPTIC 0x000e0000 + #define HID_UP_PID 0x000f0000 + #define HID_UP_BATTERY 0x00850000 + #define HID_UP_HPVENDOR 0xff7f0000 +@@ -311,6 +312,28 @@ struct hid_item { + #define HID_DG_TOOLSERIALNUMBER 0x000d005b + #define HID_DG_LATENCYMODE 0x000d0060 + ++#define HID_HP_SIMPLECONTROLLER 0x000e0001 ++#define HID_HP_WAVEFORMLIST 0x000e0010 ++#define HID_HP_DURATIONLIST 0x000e0011 ++#define HID_HP_AUTOTRIGGER 0x000e0020 ++#define HID_HP_MANUALTRIGGER 0x000e0021 ++#define HID_HP_AUTOTRIGGERASSOCIATEDCONTROL 0x000e0022 ++#define HID_HP_INTENSITY 0x000e0023 ++#define HID_HP_REPEATCOUNT 0x000e0024 ++#define HID_HP_RETRIGGERPERIOD 0x000e0025 ++#define HID_HP_WAVEFORMVENDORPAGE 0x000e0026 ++#define HID_HP_WAVEFORMVENDORID 0x000e0027 ++#define HID_HP_WAVEFORMCUTOFFTIME 0x000e0028 ++#define HID_HP_WAVEFORMNONE 0x000e1001 ++#define HID_HP_WAVEFORMSTOP 0x000e1002 ++#define HID_HP_WAVEFORMCLICK 0x000e1003 ++#define HID_HP_WAVEFORMBUZZCONTINUOUS 0x000e1004 ++#define HID_HP_WAVEFORMRUMBLECONTINUOUS 0x000e1005 ++#define HID_HP_WAVEFORMPRESS 0x000e1006 ++#define HID_HP_WAVEFORMRELEASE 0x000e1007 ++#define HID_HP_VENDORWAVEFORMMIN 0x000e2001 ++#define HID_HP_VENDORWAVEFORMMAX 0x000e2fff ++ + #define HID_BAT_ABSOLUTESTATEOFCHARGE 0x00850065 + #define HID_BAT_CHARGING 0x00850044 + +@@ -392,6 +415,12 @@ struct hid_item { + #define HID_BOOT_PROTOCOL 0 + + /* ++ * HID units ++ */ ++#define HID_UNIT_GRAM 0x0101 ++#define HID_UNIT_NEWTON 0xe111 ++ ++/* + * This is the global environment of the parser. This information is + * persistent for main-items. The global environment can be saved and + * restored with PUSH/POP statements. diff --git a/queue-6.1/hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch b/queue-6.1/hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch new file mode 100644 index 0000000000..b2be6eeb0b --- /dev/null +++ b/queue-6.1/hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch @@ -0,0 +1,150 @@ +From stable+bounces-277137-greg=kroah.com@vger.kernel.org Fri Jul 17 18:38:33 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 12:38:24 -0400 +Subject: HID: appleir: fix UAF on pending key_up_timer in remove() +To: stable@vger.kernel.org +Cc: Manish Khadka , Jiri Kosina , Sasha Levin +Message-ID: <20260717163824.2035351-2-sashal@kernel.org> + +From: Manish Khadka + +[ 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 +Signed-off-by: Jiri Kosina +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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[] = { diff --git a/queue-6.1/hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch b/queue-6.1/hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch new file mode 100644 index 0000000000..5afe87941d --- /dev/null +++ b/queue-6.1/hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch @@ -0,0 +1,158 @@ +From stable+bounces-277069-greg=kroah.com@vger.kernel.org Fri Jul 17 16:29:59 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:23:10 -0400 +Subject: HID: multitouch: fix out-of-bounds bit access on mt_io_flags +To: stable@vger.kernel.org +Cc: Trung Nguyen , Benjamin Tissoires , Sasha Levin +Message-ID: <20260717142310.1824932-2-sashal@kernel.org> + +From: Trung Nguyen + +[ 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 +Signed-off-by: Benjamin Tissoires +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + #include + #include +@@ -83,8 +84,7 @@ enum latency_mode { + HID_LATENCY_HIGH = 1, + }; + +-#define MT_IO_SLOTS_MASK GENMASK(7, 0) /* reserve first 8 bits for slot tracking */ +-#define MT_IO_FLAGS_RUNNING 32 ++#define MT_IO_FLAGS_RUNNING 0 + + static const bool mtrue = true; /* default for true */ + static const bool mfalse; /* default for false */ +@@ -160,10 +160,9 @@ struct mt_device { + struct mt_class mtclass; /* our mt device class */ + struct timer_list release_timer; /* to release sticky fingers */ + struct hid_device *hdev; /* hid_device we're attached to */ +- unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_RUNNING) +- * first 8 bits are reserved for keeping the slot +- * states, this is fine because we only support up +- * to 250 slots (MT_MAX_MAXCONTACT) ++ unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_RUNNING) */ ++ unsigned long *active_slots; /* bitmap of slots with an active ++ * contact, sized for maxcontacts + */ + __u8 inputmode_value; /* InputMode HID feature value */ + __u8 maxcontacts; +@@ -947,7 +946,7 @@ static void mt_release_pending_palms(str + + for_each_set_bit(slotnum, app->pending_palm_slots, td->maxcontacts) { + clear_bit(slotnum, app->pending_palm_slots); +- clear_bit(slotnum, &td->mt_io_flags); ++ clear_bit(slotnum, td->active_slots); + + input_mt_slot(input, slotnum); + input_mt_report_slot_inactive(input); +@@ -1153,9 +1152,9 @@ static int mt_process_slot(struct mt_dev + input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major); + input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor); + +- set_bit(slotnum, &td->mt_io_flags); ++ set_bit(slotnum, td->active_slots); + } else { +- clear_bit(slotnum, &td->mt_io_flags); ++ clear_bit(slotnum, td->active_slots); + } + + return 0; +@@ -1290,7 +1289,7 @@ static void mt_touch_report(struct hid_d + * defect. + */ + if (app->quirks & MT_QUIRK_STICKY_FINGERS) { +- if (td->mt_io_flags & MT_IO_SLOTS_MASK) ++ if (!bitmap_empty(td->active_slots, td->maxcontacts)) + mod_timer(&td->release_timer, + jiffies + msecs_to_jiffies(100)); + else +@@ -1330,6 +1329,15 @@ static int mt_touch_input_configured(str + if (td->is_buttonpad) + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + ++ if (!td->active_slots) { ++ td->active_slots = devm_kcalloc(&td->hdev->dev, ++ BITS_TO_LONGS(td->maxcontacts), ++ sizeof(long), ++ GFP_KERNEL); ++ if (!td->active_slots) ++ return -ENOMEM; ++ } ++ + app->pending_palm_slots = devm_kcalloc(&hi->input->dev, + BITS_TO_LONGS(td->maxcontacts), + sizeof(long), +@@ -1738,7 +1746,7 @@ static void mt_release_contacts(struct h + for (i = 0; i < mt->num_slots; i++) { + input_mt_slot(input_dev, i); + input_mt_report_slot_inactive(input_dev); +- clear_bit(i, &td->mt_io_flags); ++ clear_bit(i, td->active_slots); + } + input_mt_sync_frame(input_dev); + input_sync(input_dev); +@@ -1761,7 +1769,7 @@ static void mt_expired_timeout(struct ti + */ + if (test_and_set_bit_lock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags)) + return; +- if (td->mt_io_flags & MT_IO_SLOTS_MASK) ++ if (!bitmap_empty(td->active_slots, td->maxcontacts)) + mt_release_contacts(hdev); + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } diff --git a/queue-6.1/iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch b/queue-6.1/iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch new file mode 100644 index 0000000000..99f078fe22 --- /dev/null +++ b/queue-6.1/iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch @@ -0,0 +1,109 @@ +From stable+bounces-274063-greg=kroah.com@vger.kernel.org Tue Jul 14 03:16:47 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 21:16:31 -0400 +Subject: iio: common: st_sensors: honour channel endianness in read_axis_data +To: stable@vger.kernel.org +Cc: Herman van Hazendonk , Andy Shevchenko , Jonathan Cameron , Sasha Levin +Message-ID: <20260714011631.2188967-2-sashal@kernel.org> + +From: Herman van Hazendonk + +[ Upstream commit 55052184ac9011db2ea983e54d6c21f0b1079a12 ] + +st_sensors_read_axis_data() unconditionally decoded multi-byte +results with get_unaligned_le16() / get_unaligned_le24() regardless +of the channel's declared scan_type.endianness. + +For every ST sensor that has used this helper since it was introduced +this happened to be fine because the ST IMU/accel/gyro/pressure +families publish their data registers as little-endian and the +channel specs in those drivers declare IIO_LE accordingly. + +The LSM303DLH magnetometer however publishes its X/Y/Z output as a +pair of big-endian bytes (the H register sits at the lower address, +0x03/0x05/0x07, and the L register immediately after), and its +channel specs in st_magn_core.c correctly declare IIO_BE -- but +read_axis_data() ignored that and decoded as little-endian, swapping +the high and low bytes of every magnetometer sample. The LSM303DLHC +and LSM303DLM share the same st_magn_16bit_channels (IIO_BE) and +were therefore byte-swapped by the same bug; users of those parts +will see different in_magn_*_raw values after this fix lands. + +The bug is most visible on a stationary chip: in earth's field the +true X reading is small and the high byte sits at 0x00, so swapping +the bytes pins sysfs X at exactly the low byte's pattern (e.g. 0x00F0 += 240). Y and Z still appear "to vary" because their magnitudes are +larger and the noise in the low byte produces big swings in the +swapped high byte: + + before (LSM303DLH flat, sysfs in_magn_*_raw): + X=240 (stuck), Y= 12032..23296, Z=-16128..-9728 + + after (direct i2c-dev big-endian decode, same chip same orientation): + X≈-4096, Y≈210, Z≈80 (sensible values reflecting earth's + ambient field at low gauss range) + +Fix read_axis_data() to dispatch on ch->scan_type.endianness and +call get_unaligned_be16() / get_unaligned_be24() when the channel +declares IIO_BE. Existing IIO_LE consumers (st_accel, st_gyro, +st_pressure, st_lsm6dsx and others) are unaffected because their +channel specs already declare IIO_LE and the LE path is unchanged. + +While restructuring the branches, replace the previously implicit +silent-success-with-uninitialised-*data fall-through for +byte_for_channel outside 1..3 with an explicit return -EINVAL. No +in-tree ST sensor publishes such a channel, but the new behaviour +is strictly safer than handing userspace garbage. + +Fixes: 23491b513bcd ("iio:common: Add STMicroelectronics common library") +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-7 sparse smatch clang-analyzer coccinelle checkpatch +Assisted-by: Sashiko:claude-opus-4-7 +Signed-off-by: Herman van Hazendonk +Reviewed-by: Andy Shevchenko +Signed-off-by: Jonathan Cameron +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/common/st_sensors/st_sensors_core.c | 23 +++++++++++++++++------ + 1 file changed, 17 insertions(+), 6 deletions(-) + +--- a/drivers/iio/common/st_sensors/st_sensors_core.c ++++ b/drivers/iio/common/st_sensors/st_sensors_core.c +@@ -524,6 +524,7 @@ static int st_sensors_read_axis_data(str + u8 *outdata; + struct st_sensor_data *sdata = iio_priv(indio_dev); + unsigned int byte_for_channel; ++ u32 tmp; + + byte_for_channel = DIV_ROUND_UP(ch->scan_type.realbits + + ch->scan_type.shift, 8); +@@ -536,12 +537,22 @@ static int st_sensors_read_axis_data(str + if (err < 0) + goto st_sensors_free_memory; + +- if (byte_for_channel == 1) +- *data = (s8)*outdata; +- else if (byte_for_channel == 2) +- *data = (s16)get_unaligned_le16(outdata); +- else if (byte_for_channel == 3) +- *data = (s32)sign_extend32(get_unaligned_le24(outdata), 23); ++ if (byte_for_channel == 1) { ++ tmp = *outdata; ++ } else if (byte_for_channel == 2) { ++ if (ch->scan_type.endianness == IIO_BE) ++ tmp = get_unaligned_be16(outdata); ++ else ++ tmp = get_unaligned_le16(outdata); ++ } else if (byte_for_channel == 3) { ++ if (ch->scan_type.endianness == IIO_BE) ++ tmp = get_unaligned_be24(outdata); ++ else ++ tmp = get_unaligned_le24(outdata); ++ } else { ++ return -EINVAL; ++ } ++ *data = sign_extend32(tmp, BYTES_TO_BITS(byte_for_channel) - 1); + + st_sensors_free_memory: + kfree(outdata); diff --git a/queue-6.1/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch b/queue-6.1/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch new file mode 100644 index 0000000000..a3b198c421 --- /dev/null +++ b/queue-6.1/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch @@ -0,0 +1,89 @@ +From stable+bounces-273957-greg=kroah.com@vger.kernel.org Mon Jul 13 20:33:53 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 14:33:44 -0400 +Subject: iio: hid-sensor-rotation: Fix stale or zero output when reading raw values +To: stable@vger.kernel.org +Cc: Zhang Lixu , Andy Shevchenko , Stable@vger.kernel.org, Jonathan Cameron , Sasha Levin +Message-ID: <20260713183344.1950826-2-sashal@kernel.org> + +From: Zhang Lixu + +[ 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 +Reviewed-by: Andy Shevchenko +Cc: +Signed-off-by: Jonathan Cameron +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 diff --git a/queue-6.1/iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch b/queue-6.1/iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch new file mode 100644 index 0000000000..0868382390 --- /dev/null +++ b/queue-6.1/iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch @@ -0,0 +1,49 @@ +From stable+bounces-273938-greg=kroah.com@vger.kernel.org Mon Jul 13 20:06:51 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 14:03:29 -0400 +Subject: iio: imu: adis: add IRQF_NO_THREAD to non-FIFO trigger IRQ +To: stable@vger.kernel.org +Cc: Runyu Xiao , Jonathan Cameron , Sasha Levin +Message-ID: <20260713180329.1923595-1-sashal@kernel.org> + +From: Runyu Xiao + +[ Upstream commit 6e1b9bff1202da55c464e36bd34a2b6863d7fe30 ] + +devm_adis_probe_trigger() registers iio_trigger_generic_data_rdy_poll() +through devm_request_irq() on the non-FIFO path, but it does not add +IRQF_NO_THREAD to the IRQ flags. + +When the kernel is booted with forced IRQ threading, the parent IRQ can +otherwise be threaded by the IRQ core and the subsequent IIO trigger +child IRQ is then dispatched from irq/... thread context instead of +hardirq context. Because iio_trigger_generic_data_rdy_poll() +immediately drives iio_trigger_poll(), this violates the hardirq-only +IIO trigger helper contract and can push downstream trigger consumers +through the wrong execution context. + +Add IRQF_NO_THREAD on top of the existing adis->irq_flag value for the +non-FIFO request_irq() path, while preserving the current trigger +polarity and IRQF_NO_AUTOEN behavior. + +Fixes: fec86c6b8369 ("iio: imu: adis: Add Managed device functions") +Cc: stable@vger.kernel.org +Signed-off-by: Runyu Xiao +Signed-off-by: Jonathan Cameron +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/imu/adis_trigger.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/iio/imu/adis_trigger.c ++++ b/drivers/iio/imu/adis_trigger.c +@@ -79,7 +79,7 @@ int devm_adis_probe_trigger(struct adis + + ret = devm_request_irq(&adis->spi->dev, adis->spi->irq, + &iio_trigger_generic_data_rdy_poll, +- adis->irq_flag, ++ adis->irq_flag | IRQF_NO_THREAD, + indio_dev->name, + adis->trig); + if (ret) diff --git a/queue-6.1/iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch b/queue-6.1/iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch new file mode 100644 index 0000000000..f3aaa7648f --- /dev/null +++ b/queue-6.1/iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch @@ -0,0 +1,72 @@ +From stable+bounces-274010-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:56 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:39:42 -0400 +Subject: iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading +To: stable@vger.kernel.org +Cc: Jean-Baptiste Maneyrol , Jonathan Cameron , Sasha Levin +Message-ID: <20260713203942.2144352-8-sashal@kernel.org> + +From: Jean-Baptiste Maneyrol + +[ Upstream commit affe3f077d7a4eeb25937f5323ff059a54b4712c ] + +Timestamps are made by measuring the chip clock using the watermark +interrupts. If we read more than watermark samples as done today, we +are reducing the period between interrupts and distort the time +measurement. Fix that by reading only watermark samples in the +interrupt case. + +Fixes: 7f85e42a6c54 ("iio: imu: inv_icm42600: add buffer support in iio devices") +Cc: stable@vger.kernel.org +Signed-off-by: Jean-Baptiste Maneyrol +Signed-off-by: Jonathan Cameron +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 9 +++++---- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h | 1 + + 2 files changed, 6 insertions(+), 4 deletions(-) + +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +@@ -257,6 +257,7 @@ int inv_icm42600_buffer_update_watermark + + /* compute watermark value in bytes */ + wm_size = watermark * packet_size; ++ st->fifo.watermark.value = watermark; + + /* changing FIFO watermark requires to turn off watermark interrupt */ + ret = regmap_update_bits_check(st->map, INV_ICM42600_REG_INT_SOURCE0, +@@ -476,11 +477,10 @@ int inv_icm42600_buffer_fifo_read(struct + st->fifo.nb.accel = 0; + st->fifo.nb.total = 0; + +- /* compute maximum FIFO read size */ ++ /* compute maximum FIFO read size (watermark for max = 0 interrupt case) */ + if (max == 0) +- max_count = sizeof(st->fifo.data); +- else +- max_count = max * inv_icm42600_get_packet_size(st->fifo.en); ++ max = st->fifo.watermark.value; ++ max_count = max * inv_icm42600_get_packet_size(st->fifo.en); + + /* read FIFO count value */ + raw_fifo_count = (__be16 *)st->buffer; +@@ -592,6 +592,7 @@ int inv_icm42600_buffer_init(struct inv_ + + st->fifo.watermark.eff_gyro = 1; + st->fifo.watermark.eff_accel = 1; ++ st->fifo.watermark.value = 1; + + /* + * Default FIFO configuration (bits 7 to 5) +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h +@@ -34,6 +34,7 @@ struct inv_icm42600_fifo { + unsigned int accel; + unsigned int eff_gyro; + unsigned int eff_accel; ++ unsigned int value; + } watermark; + size_t count; + struct { diff --git a/queue-6.1/iio-imu-inv_icm42600-make-timestamp-module-chip-independent.patch b/queue-6.1/iio-imu-inv_icm42600-make-timestamp-module-chip-independent.patch new file mode 100644 index 0000000000..0f17f882ee --- /dev/null +++ b/queue-6.1/iio-imu-inv_icm42600-make-timestamp-module-chip-independent.patch @@ -0,0 +1,103 @@ +From stable+bounces-274003-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:52 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:39:35 -0400 +Subject: iio: imu: inv_icm42600: make timestamp module chip independent +To: stable@vger.kernel.org +Cc: Jean-Baptiste Maneyrol , Andy Shevchenko , Jonathan Cameron , Sasha Levin +Message-ID: <20260713203942.2144352-1-sashal@kernel.org> + +From: Jean-Baptiste Maneyrol + +[ Upstream commit 6e9f2d8375cb24ba75e02e0272e9164d06a1522e ] + +Move icm42600 dependent function inside the core module. +Do some headers cleanup at the same time. + +Signed-off-by: Jean-Baptiste Maneyrol +Reviewed-by: Andy Shevchenko +Link: https://lore.kernel.org/r/20230606162147.79667-2-inv.git-commit@tdk.com +Signed-off-by: Jonathan Cameron +Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 11 +++++++++++ + drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c | 14 +------------- + drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h | 6 ------ + 3 files changed, 12 insertions(+), 19 deletions(-) + +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c +@@ -522,6 +522,17 @@ static int inv_icm42600_irq_init(struct + "inv_icm42600", st); + } + ++static int inv_icm42600_timestamp_setup(struct inv_icm42600_state *st) ++{ ++ unsigned int val; ++ ++ /* enable timestamp register */ ++ val = INV_ICM42600_TMST_CONFIG_TMST_TO_REGS_EN | ++ INV_ICM42600_TMST_CONFIG_TMST_EN; ++ return regmap_update_bits(st->map, INV_ICM42600_REG_TMST_CONFIG, ++ INV_ICM42600_TMST_CONFIG_MASK, val); ++} ++ + static int inv_icm42600_enable_regulator_vddio(struct inv_icm42600_state *st) + { + int ret; +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c +@@ -3,11 +3,10 @@ + * Copyright (C) 2020 Invensense, Inc. + */ + ++#include + #include +-#include + #include + +-#include "inv_icm42600.h" + #include "inv_icm42600_timestamp.h" + + /* internal chip period is 32kHz, 31250ns */ +@@ -56,17 +55,6 @@ void inv_icm42600_timestamp_init(struct + inv_update_acc(&ts->chip_period, INV_ICM42600_TIMESTAMP_PERIOD); + } + +-int inv_icm42600_timestamp_setup(struct inv_icm42600_state *st) +-{ +- unsigned int val; +- +- /* enable timestamp register */ +- val = INV_ICM42600_TMST_CONFIG_TMST_TO_REGS_EN | +- INV_ICM42600_TMST_CONFIG_TMST_EN; +- return regmap_update_bits(st->map, INV_ICM42600_REG_TMST_CONFIG, +- INV_ICM42600_TMST_CONFIG_MASK, val); +-} +- + int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts, + uint32_t period, bool fifo) + { +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h +@@ -6,10 +6,6 @@ + #ifndef INV_ICM42600_TIMESTAMP_H_ + #define INV_ICM42600_TIMESTAMP_H_ + +-#include +- +-struct inv_icm42600_state; +- + /** + * struct inv_icm42600_timestamp_interval - timestamps interval + * @lo: interval lower bound +@@ -53,8 +49,6 @@ struct inv_icm42600_timestamp { + void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts, + uint32_t period); + +-int inv_icm42600_timestamp_setup(struct inv_icm42600_state *st); +- + int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts, + uint32_t period, bool fifo); + diff --git a/queue-6.1/iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch b/queue-6.1/iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch new file mode 100644 index 0000000000..94bdd23593 --- /dev/null +++ b/queue-6.1/iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch @@ -0,0 +1,116 @@ +From stable+bounces-274009-greg=kroah.com@vger.kernel.org Mon Jul 13 22:40:06 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:39:41 -0400 +Subject: iio: imu: inv_icm42600: stabilized timestamp in interrupt +To: stable@vger.kernel.org +Cc: Jean-Baptiste Maneyrol , Jonathan Cameron , Sasha Levin +Message-ID: <20260713203942.2144352-7-sashal@kernel.org> + +From: Jean-Baptiste Maneyrol + +[ Upstream commit d7bd473632d07f8a54655c270c0940cc3671c548 ] + +Use IRQF_ONESHOT flag to ensure the timestamp is not updated in the +hard handler during the thread handler. And compute and use the +effective watermark value that correspond to this first timestamp. + +This way we can ensure the timestamp is always corresponding to the +value used by the timestamping mechanism. Otherwise, it is possible +that between FIFO count read and FIFO processing the timestamp is +overwritten in the hard handler. + +Fixes: ec74ae9fd37c ("iio: imu: inv_icm42600: add accurate timestamping") +Cc: stable@vger.kernel.org +Signed-off-by: Jean-Baptiste Maneyrol +Link: https://lore.kernel.org/r/20240529154717.651863-1-inv.git-commit@tdk.com +Signed-off-by: Jonathan Cameron +Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 19 +++++++++++++++++-- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h | 2 ++ + drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 1 + + 3 files changed, 20 insertions(+), 2 deletions(-) + +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +@@ -222,10 +222,15 @@ int inv_icm42600_buffer_update_watermark + latency_accel = period_accel * wm_accel; + + /* 0 value for watermark means that the sensor is turned off */ ++ if (wm_gyro == 0 && wm_accel == 0) ++ return 0; ++ + if (latency_gyro == 0) { + watermark = wm_accel; ++ st->fifo.watermark.eff_accel = wm_accel; + } else if (latency_accel == 0) { + watermark = wm_gyro; ++ st->fifo.watermark.eff_gyro = wm_gyro; + } else { + /* compute the smallest latency that is a multiple of both */ + if (latency_gyro <= latency_accel) +@@ -241,6 +246,13 @@ int inv_icm42600_buffer_update_watermark + watermark = latency / period; + if (watermark < 1) + watermark = 1; ++ /* update effective watermark */ ++ st->fifo.watermark.eff_gyro = latency / period_gyro; ++ if (st->fifo.watermark.eff_gyro < 1) ++ st->fifo.watermark.eff_gyro = 1; ++ st->fifo.watermark.eff_accel = latency / period_accel; ++ if (st->fifo.watermark.eff_accel < 1) ++ st->fifo.watermark.eff_accel = 1; + } + + /* compute watermark value in bytes */ +@@ -517,7 +529,7 @@ int inv_icm42600_buffer_fifo_parse(struc + /* handle gyroscope timestamp and FIFO data parsing */ + if (st->fifo.nb.gyro > 0) { + ts = iio_priv(st->indio_gyro); +- inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, ++ inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_gyro, + st->timestamp.gyro); + ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); + if (ret) +@@ -527,7 +539,7 @@ int inv_icm42600_buffer_fifo_parse(struc + /* handle accelerometer timestamp and FIFO data parsing */ + if (st->fifo.nb.accel > 0) { + ts = iio_priv(st->indio_accel); +- inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, ++ inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_accel, + st->timestamp.accel); + ret = inv_icm42600_accel_parse_fifo(st->indio_accel); + if (ret) +@@ -578,6 +590,9 @@ int inv_icm42600_buffer_init(struct inv_ + unsigned int val; + int ret; + ++ st->fifo.watermark.eff_gyro = 1; ++ st->fifo.watermark.eff_accel = 1; ++ + /* + * Default FIFO configuration (bits 7 to 5) + * - use invalid value +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h +@@ -32,6 +32,8 @@ struct inv_icm42600_fifo { + struct { + unsigned int gyro; + unsigned int accel; ++ unsigned int eff_gyro; ++ unsigned int eff_accel; + } watermark; + size_t count; + struct { +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c +@@ -518,6 +518,7 @@ static int inv_icm42600_irq_init(struct + if (ret) + return ret; + ++ irq_type |= IRQF_ONESHOT; + return devm_request_threaded_irq(dev, irq, inv_icm42600_irq_timestamp, + inv_icm42600_irq_handler, irq_type, + "inv_icm42600", st); diff --git a/queue-6.1/iio-imu-inv_mpu6050-use-the-common-inv_sensors-timestamp-module.patch b/queue-6.1/iio-imu-inv_mpu6050-use-the-common-inv_sensors-timestamp-module.patch new file mode 100644 index 0000000000..0ef3551cd1 --- /dev/null +++ b/queue-6.1/iio-imu-inv_mpu6050-use-the-common-inv_sensors-timestamp-module.patch @@ -0,0 +1,307 @@ +From stable+bounces-274005-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:53 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:39:38 -0400 +Subject: iio: imu: inv_mpu6050: use the common inv_sensors timestamp module +To: stable@vger.kernel.org +Cc: Jean-Baptiste Maneyrol , Andy Shevchenko , Jonathan Cameron , Sasha Levin +Message-ID: <20260713203942.2144352-4-sashal@kernel.org> + +From: Jean-Baptiste Maneyrol + +[ Upstream commit 111e1abd00455971f6a568900f033cbddec9d4e5 ] + +Replace timestamping by the new common inv_sensors timestamp +module. The principle behind is the same but the implementation in +the new module is far better providing less jitter and a better +estimation. + +Signed-off-by: Jean-Baptiste Maneyrol +Reviewed-by: Andy Shevchenko +Link: https://lore.kernel.org/r/20230606162147.79667-5-inv.git-commit@tdk.com +Signed-off-by: Jonathan Cameron +Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/imu/inv_mpu6050/Kconfig | 1 + drivers/iio/imu/inv_mpu6050/inv_mpu_core.c | 30 +++++++-- + drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h | 18 ++--- + drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c | 83 ++------------------------ + drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c | 6 + + 5 files changed, 45 insertions(+), 93 deletions(-) + +--- a/drivers/iio/imu/inv_mpu6050/Kconfig ++++ b/drivers/iio/imu/inv_mpu6050/Kconfig +@@ -7,6 +7,7 @@ config INV_MPU6050_IIO + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER ++ select IIO_INV_SENSORS_TIMESTAMP + + config INV_MPU6050_I2C + tristate "Invensense MPU6050 devices (I2C)" +--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c ++++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +@@ -12,12 +12,15 @@ + #include + #include + #include +-#include + #include + #include + #include + #include + #include ++ ++#include ++#include ++ + #include "inv_mpu_iio.h" + #include "inv_mpu_magn.h" + +@@ -512,6 +515,7 @@ static int inv_mpu6050_init_config(struc + int result; + u8 d; + struct inv_mpu6050_state *st = iio_priv(indio_dev); ++ struct inv_sensors_timestamp_chip timestamp; + + result = inv_mpu6050_set_gyro_fsr(st, st->chip_config.fsr); + if (result) +@@ -535,12 +539,12 @@ static int inv_mpu6050_init_config(struc + if (result) + return result; + +- /* +- * Internal chip period is 1ms (1kHz). +- * Let's use at the beginning the theorical value before measuring +- * with interrupt timestamps. +- */ +- st->chip_period = NSEC_PER_MSEC; ++ /* clock jitter is +/- 2% */ ++ timestamp.clock_period = NSEC_PER_SEC / INV_MPU6050_INTERNAL_FREQ_HZ; ++ timestamp.jitter = 20; ++ timestamp.init_period = ++ NSEC_PER_SEC / INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider); ++ inv_sensors_timestamp_init(&st->timestamp, ×tamp); + + /* magn chip init, noop if not present in the chip */ + result = inv_mpu_magn_probe(st); +@@ -927,6 +931,8 @@ inv_mpu6050_fifo_rate_store(struct devic + const char *buf, size_t count) + { + int fifo_rate; ++ u32 fifo_period; ++ bool fifo_on; + u8 d; + int result; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); +@@ -943,12 +949,21 @@ inv_mpu6050_fifo_rate_store(struct devic + d = INV_MPU6050_FIFO_RATE_TO_DIVIDER(fifo_rate); + /* compute back the fifo rate to handle truncation cases */ + fifo_rate = INV_MPU6050_DIVIDER_TO_FIFO_RATE(d); ++ fifo_period = NSEC_PER_SEC / fifo_rate; + + mutex_lock(&st->lock); + if (d == st->chip_config.divider) { + result = 0; + goto fifo_rate_fail_unlock; + } ++ ++ fifo_on = st->chip_config.accl_fifo_enable || ++ st->chip_config.gyro_fifo_enable || ++ st->chip_config.magn_fifo_enable; ++ result = inv_sensors_timestamp_update_odr(&st->timestamp, fifo_period, fifo_on); ++ if (result) ++ goto fifo_rate_fail_unlock; ++ + result = pm_runtime_resume_and_get(pdev); + if (result) + goto fifo_rate_fail_unlock; +@@ -1776,3 +1791,4 @@ EXPORT_SYMBOL_GPL(inv_mpu_pmops); + MODULE_AUTHOR("Invensense Corporation"); + MODULE_DESCRIPTION("Invensense device MPU6050 driver"); + MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS(IIO_INV_SENSORS_TIMESTAMP); +--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h ++++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h +@@ -9,15 +9,17 @@ + #include + #include + #include +-#include +-#include ++#include + #include +-#include ++ ++#include ++#include ++#include + #include + #include + #include + #include +-#include ++#include + + /** + * struct inv_mpu6050_reg_map - Notable registers. +@@ -169,9 +171,7 @@ struct inv_mpu6050_hw { + * @map regmap pointer. + * @irq interrupt number. + * @irq_mask the int_pin_cfg mask to configure interrupt type. +- * @chip_period: chip internal period estimation (~1kHz). +- * @it_timestamp: timestamp from previous interrupt. +- * @data_timestamp: timestamp for next data sample. ++ * @timestamp: timestamping module + * @vdd_supply: VDD voltage regulator for the chip. + * @vddio_supply I/O voltage regulator for the chip. + * @magn_disabled: magnetometer disabled for backward compatibility reason. +@@ -195,9 +195,7 @@ struct inv_mpu6050_state { + int irq; + u8 irq_mask; + unsigned skip_samples; +- s64 chip_period; +- s64 it_timestamp; +- s64 data_timestamp; ++ struct inv_sensors_timestamp timestamp; + struct regulator *vdd_supply; + struct regulator *vddio_supply; + bool magn_disabled; +--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c ++++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c +@@ -13,81 +13,10 @@ + #include + #include + #include +-#include "inv_mpu_iio.h" +- +-/** +- * inv_mpu6050_update_period() - Update chip internal period estimation +- * +- * @st: driver state +- * @timestamp: the interrupt timestamp +- * @nb: number of data set in the fifo +- * +- * This function uses interrupt timestamps to estimate the chip period and +- * to choose the data timestamp to come. +- */ +-static void inv_mpu6050_update_period(struct inv_mpu6050_state *st, +- s64 timestamp, size_t nb) +-{ +- /* Period boundaries for accepting timestamp */ +- const s64 period_min = +- (NSEC_PER_MSEC * (100 - INV_MPU6050_TS_PERIOD_JITTER)) / 100; +- const s64 period_max = +- (NSEC_PER_MSEC * (100 + INV_MPU6050_TS_PERIOD_JITTER)) / 100; +- const s32 divider = INV_MPU6050_FREQ_DIVIDER(st); +- s64 delta, interval; +- bool use_it_timestamp = false; +- +- if (st->it_timestamp == 0) { +- /* not initialized, forced to use it_timestamp */ +- use_it_timestamp = true; +- } else if (nb == 1) { +- /* +- * Validate the use of it timestamp by checking if interrupt +- * has been delayed. +- * nb > 1 means interrupt was delayed for more than 1 sample, +- * so it's obviously not good. +- * Compute the chip period between 2 interrupts for validating. +- */ +- delta = div_s64(timestamp - st->it_timestamp, divider); +- if (delta > period_min && delta < period_max) { +- /* update chip period and use it timestamp */ +- st->chip_period = (st->chip_period + delta) / 2; +- use_it_timestamp = true; +- } +- } + +- if (use_it_timestamp) { +- /* +- * Manage case of multiple samples in the fifo (nb > 1): +- * compute timestamp corresponding to the first sample using +- * estimated chip period. +- */ +- interval = (nb - 1) * st->chip_period * divider; +- st->data_timestamp = timestamp - interval; +- } ++#include + +- /* save it timestamp */ +- st->it_timestamp = timestamp; +-} +- +-/** +- * inv_mpu6050_get_timestamp() - Return the current data timestamp +- * +- * @st: driver state +- * @return: current data timestamp +- * +- * This function returns the current data timestamp and prepares for next one. +- */ +-static s64 inv_mpu6050_get_timestamp(struct inv_mpu6050_state *st) +-{ +- s64 ts; +- +- /* return current data timestamp and increment */ +- ts = st->data_timestamp; +- st->data_timestamp += st->chip_period * INV_MPU6050_FREQ_DIVIDER(st); +- +- return ts; +-} ++#include "inv_mpu_iio.h" + + static int inv_reset_fifo(struct iio_dev *indio_dev) + { +@@ -121,6 +50,7 @@ irqreturn_t inv_mpu6050_read_fifo(int ir + size_t bytes_per_datum; + int result; + u16 fifo_count; ++ u32 fifo_period; + s64 timestamp; + int int_status; + size_t i, nb; +@@ -177,7 +107,10 @@ irqreturn_t inv_mpu6050_read_fifo(int ir + + /* compute and process all complete datum */ + nb = fifo_count / bytes_per_datum; +- inv_mpu6050_update_period(st, pf->timestamp, nb); ++ /* Each FIFO data contains all sensors, so same number for FIFO and sensor data */ ++ fifo_period = NSEC_PER_SEC / INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider); ++ inv_sensors_timestamp_interrupt(&st->timestamp, fifo_period, nb, nb, pf->timestamp); ++ inv_sensors_timestamp_apply_odr(&st->timestamp, fifo_period, nb, 0); + for (i = 0; i < nb; ++i) { + result = regmap_noinc_read(st->map, st->reg->fifo_r_w, + st->data, bytes_per_datum); +@@ -188,7 +121,7 @@ irqreturn_t inv_mpu6050_read_fifo(int ir + st->skip_samples--; + continue; + } +- timestamp = inv_mpu6050_get_timestamp(st); ++ timestamp = inv_sensors_timestamp_pop(&st->timestamp); + iio_push_to_buffers_with_timestamp(indio_dev, st->data, timestamp); + } + +--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c ++++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c +@@ -4,6 +4,9 @@ + */ + + #include ++ ++#include ++ + #include "inv_mpu_iio.h" + + static unsigned int inv_scan_query_mpu6050(struct iio_dev *indio_dev) +@@ -106,7 +109,8 @@ int inv_mpu6050_prepare_fifo(struct inv_ + int ret; + + if (enable) { +- st->it_timestamp = 0; ++ /* reset timestamping */ ++ inv_sensors_timestamp_reset(&st->timestamp); + /* reset FIFO */ + d = st->chip_config.user_ctrl | INV_MPU6050_BIT_FIFO_RST; + ret = regmap_write(st->map, st->reg->user_ctrl, d); diff --git a/queue-6.1/iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch b/queue-6.1/iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch new file mode 100644 index 0000000000..07f92b24f3 --- /dev/null +++ b/queue-6.1/iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch @@ -0,0 +1,202 @@ +From stable+bounces-274008-greg=kroah.com@vger.kernel.org Mon Jul 13 22:40:01 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:39:40 -0400 +Subject: iio: invensense: fix timestamp glitches when switching frequency +To: stable@vger.kernel.org +Cc: Jean-Baptiste Maneyrol , Stable@vger.kernel.org, Jonathan Cameron , Sasha Levin +Message-ID: <20260713203942.2144352-6-sashal@kernel.org> + +From: Jean-Baptiste Maneyrol + +[ Upstream commit bf8367b00c33c64a9391c262bb2e11d274c9f2a4 ] + +When a sensor is running and there is a FIFO frequency change due to +another sensor turned on/off, there are glitches on timestamp. Fix that +by using only interrupt timestamp when there is the corresponding sensor +data in the FIFO. + +Delete FIFO period handling and simplify internal functions. + +Update integration inside inv_mpu6050 and inv_icm42600 drivers. + +Fixes: 0ecc363ccea7 ("iio: make invensense timestamp module generic") +Cc: Stable@vger.kernel.org +Signed-off-by: Jean-Baptiste Maneyrol +Link: https://lore.kernel.org/r/20240426094835.138389-1-inv.git-commit@tdk.com +Signed-off-by: Jonathan Cameron +Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/common/inv_sensors/inv_sensors_timestamp.c | 30 +++++++---------- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 20 ++++------- + drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c | 2 - + include/linux/iio/common/inv_sensors_timestamp.h | 3 - + 4 files changed, 23 insertions(+), 32 deletions(-) + +--- a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c ++++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c +@@ -70,13 +70,13 @@ int inv_sensors_timestamp_update_odr(str + } + EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_update_odr, IIO_INV_SENSORS_TIMESTAMP); + +-static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t period, uint32_t mult) ++static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t period) + { + uint32_t period_min, period_max; + + /* check that period is acceptable */ +- period_min = ts->min_period * mult; +- period_max = ts->max_period * mult; ++ period_min = ts->min_period * ts->mult; ++ period_max = ts->max_period * ts->mult; + if (period > period_min && period < period_max) + return true; + else +@@ -84,32 +84,30 @@ static bool inv_validate_period(struct i + } + + static bool inv_compute_chip_period(struct inv_sensors_timestamp *ts, +- uint32_t mult, uint32_t period) ++ uint32_t period) + { + uint32_t new_chip_period; + +- if (!inv_validate_period(ts, period, mult)) ++ if (!inv_validate_period(ts, period)) + return false; + + /* update chip internal period estimation */ +- new_chip_period = period / mult; ++ new_chip_period = period / ts->mult; + inv_update_acc(&ts->chip_period, new_chip_period); + + return true; + } + + void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, +- uint32_t fifo_period, size_t fifo_nb, +- size_t sensor_nb, int64_t timestamp) ++ size_t sample_nb, int64_t timestamp) + { + struct inv_sensors_timestamp_interval *it; + int64_t delta, interval; +- const uint32_t fifo_mult = fifo_period / ts->chip.clock_period; + uint32_t period; + int32_t m; + bool valid = false; + +- if (fifo_nb == 0) ++ if (sample_nb == 0) + return; + + /* update interrupt timestamp and compute chip and sensor periods */ +@@ -119,8 +117,8 @@ void inv_sensors_timestamp_interrupt(str + delta = it->up - it->lo; + if (it->lo != 0) { + /* compute period: delta time divided by number of samples */ +- period = div_s64(delta, fifo_nb); +- valid = inv_compute_chip_period(ts, fifo_mult, period); ++ period = div_s64(delta, sample_nb); ++ valid = inv_compute_chip_period(ts, period); + /* update sensor period if chip internal period is updated */ + if (valid) + ts->period = ts->mult * ts->chip_period.val; +@@ -129,20 +127,18 @@ void inv_sensors_timestamp_interrupt(str + /* no previous data, compute theoritical value from interrupt */ + if (ts->timestamp == 0) { + /* elapsed time: sensor period * sensor samples number */ +- interval = (int64_t)ts->period * (int64_t)sensor_nb; ++ interval = (int64_t)ts->period * (int64_t)sample_nb; + ts->timestamp = it->up - interval; + return; + } + + /* if interrupt interval is valid, sync with interrupt timestamp */ + if (valid) { +- /* compute measured fifo_period */ +- fifo_period = fifo_mult * ts->chip_period.val; + /* delta time between last sample and last interrupt */ + delta = it->lo - ts->timestamp; + /* if there are multiple samples, go back to first one */ +- while (delta >= (fifo_period * 3 / 2)) +- delta -= fifo_period; ++ while (delta >= (ts->period * 3 / 2)) ++ delta -= ts->period; + /* compute maximal adjustment value */ + m = INV_SENSORS_TIMESTAMP_JITTER((int64_t)ts->period, ts->chip.jitter); + if (delta > m) +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +@@ -515,20 +515,20 @@ int inv_icm42600_buffer_fifo_parse(struc + return 0; + + /* handle gyroscope timestamp and FIFO data parsing */ +- ts = iio_priv(st->indio_gyro); +- inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, +- st->fifo.nb.gyro, st->timestamp.gyro); + if (st->fifo.nb.gyro > 0) { ++ ts = iio_priv(st->indio_gyro); ++ inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, ++ st->timestamp.gyro); + ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; + } + + /* handle accelerometer timestamp and FIFO data parsing */ +- ts = iio_priv(st->indio_accel); +- inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, +- st->fifo.nb.accel, st->timestamp.accel); + if (st->fifo.nb.accel > 0) { ++ ts = iio_priv(st->indio_accel); ++ inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, ++ st->timestamp.accel); + ret = inv_icm42600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; +@@ -556,9 +556,7 @@ int inv_icm42600_buffer_hwfifo_flush(str + + if (st->fifo.nb.gyro > 0) { + ts = iio_priv(st->indio_gyro); +- inv_sensors_timestamp_interrupt(ts, st->fifo.period, +- st->fifo.nb.total, st->fifo.nb.gyro, +- gyro_ts); ++ inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, gyro_ts); + ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; +@@ -566,9 +564,7 @@ int inv_icm42600_buffer_hwfifo_flush(str + + if (st->fifo.nb.accel > 0) { + ts = iio_priv(st->indio_accel); +- inv_sensors_timestamp_interrupt(ts, st->fifo.period, +- st->fifo.nb.total, st->fifo.nb.accel, +- accel_ts); ++ inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, accel_ts); + ret = inv_icm42600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; +--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c ++++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c +@@ -109,7 +109,7 @@ irqreturn_t inv_mpu6050_read_fifo(int ir + nb = fifo_count / bytes_per_datum; + /* Each FIFO data contains all sensors, so same number for FIFO and sensor data */ + fifo_period = NSEC_PER_SEC / INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider); +- inv_sensors_timestamp_interrupt(&st->timestamp, fifo_period, nb, nb, pf->timestamp); ++ inv_sensors_timestamp_interrupt(&st->timestamp, nb, pf->timestamp); + inv_sensors_timestamp_apply_odr(&st->timestamp, fifo_period, nb, 0); + for (i = 0; i < nb; ++i) { + result = regmap_noinc_read(st->map, st->reg->fifo_r_w, +--- a/include/linux/iio/common/inv_sensors_timestamp.h ++++ b/include/linux/iio/common/inv_sensors_timestamp.h +@@ -71,8 +71,7 @@ int inv_sensors_timestamp_update_odr(str + uint32_t period, bool fifo); + + void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, +- uint32_t fifo_period, size_t fifo_nb, +- size_t sensor_nb, int64_t timestamp); ++ size_t sample_nb, int64_t timestamp); + + static inline int64_t inv_sensors_timestamp_pop(struct inv_sensors_timestamp *ts) + { diff --git a/queue-6.1/iio-invensense-remove-redundant-initialization-of-variable-period.patch b/queue-6.1/iio-invensense-remove-redundant-initialization-of-variable-period.patch new file mode 100644 index 0000000000..faa30d1de6 --- /dev/null +++ b/queue-6.1/iio-invensense-remove-redundant-initialization-of-variable-period.patch @@ -0,0 +1,42 @@ +From stable+bounces-274007-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:58 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:39:39 -0400 +Subject: iio: invensense: remove redundant initialization of variable period +To: stable@vger.kernel.org +Cc: Colin Ian King , Jean-Baptiste Maneyrol , Jonathan Cameron , Sasha Levin +Message-ID: <20260713203942.2144352-5-sashal@kernel.org> + +From: Colin Ian King + +[ Upstream commit b58b13f156c00c2457035b7071eaaac105fe6836 ] + +The variable period is being initialized with a value that is never +read, it is being re-assigned a new value later on before it is read. +The initialization is redundant and can be removed. + +Cleans up clang scan build warning: +Value stored to 'period' during its initialization is never +read [deadcode.DeadStores] + +Signed-off-by: Colin Ian King +Acked-by: Jean-Baptiste Maneyrol +Link: https://lore.kernel.org/r/20240106153202.54861-1-colin.i.king@gmail.com +Signed-off-by: Jonathan Cameron +Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/common/inv_sensors/inv_sensors_timestamp.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c ++++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c +@@ -105,7 +105,7 @@ void inv_sensors_timestamp_interrupt(str + struct inv_sensors_timestamp_interval *it; + int64_t delta, interval; + const uint32_t fifo_mult = fifo_period / ts->chip.clock_period; +- uint32_t period = ts->period; ++ uint32_t period; + int32_t m; + bool valid = false; + diff --git a/queue-6.1/iio-make-invensense-timestamp-module-generic.patch b/queue-6.1/iio-make-invensense-timestamp-module-generic.patch new file mode 100644 index 0000000000..353a60cebd --- /dev/null +++ b/queue-6.1/iio-make-invensense-timestamp-module-generic.patch @@ -0,0 +1,962 @@ +From stable+bounces-274006-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:55 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:39:37 -0400 +Subject: iio: make invensense timestamp module generic +To: stable@vger.kernel.org +Cc: Jean-Baptiste Maneyrol , Andy Shevchenko , Jonathan Cameron , Sasha Levin +Message-ID: <20260713203942.2144352-3-sashal@kernel.org> + +From: Jean-Baptiste Maneyrol + +[ Upstream commit 0ecc363ccea71eda6a2cceade120489259bcdb33 ] + +Rename common module to inv_sensors_timestamp, add configuration +at init (chip internal clock, acceptable jitter, ...) and update +inv_icm42600 driver integration. + +Signed-off-by: Jean-Baptiste Maneyrol +Reviewed-by: Andy Shevchenko +Link: https://lore.kernel.org/r/20230606162147.79667-4-inv.git-commit@tdk.com +Signed-off-by: Jonathan Cameron +Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/common/inv_sensors/Makefile | 2 + drivers/iio/common/inv_sensors/inv_icm42600_timestamp.c | 192 ---------------- + drivers/iio/common/inv_sensors/inv_sensors_timestamp.c | 191 +++++++++++++++ + drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c | 32 +- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 34 +- + drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 10 + drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c | 32 +- + include/linux/iio/common/inv_icm42600_timestamp.h | 79 ------ + include/linux/iio/common/inv_sensors_timestamp.h | 95 +++++++ + 9 files changed, 349 insertions(+), 318 deletions(-) + rename drivers/iio/common/inv_sensors/{inv_icm42600_timestamp.c => inv_sensors_timestamp.c} (61%) + delete mode 100644 include/linux/iio/common/inv_icm42600_timestamp.h + create mode 100644 include/linux/iio/common/inv_sensors_timestamp.h + +--- a/drivers/iio/common/inv_sensors/Makefile ++++ b/drivers/iio/common/inv_sensors/Makefile +@@ -3,4 +3,4 @@ + # Makefile for TDK-InvenSense sensors module. + # + +-obj-$(CONFIG_IIO_INV_SENSORS_TIMESTAMP) += inv_icm42600_timestamp.o ++obj-$(CONFIG_IIO_INV_SENSORS_TIMESTAMP) += inv_sensors_timestamp.o +--- a/drivers/iio/common/inv_sensors/inv_icm42600_timestamp.c ++++ /dev/null +@@ -1,192 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (C) 2020 Invensense, Inc. +- */ +- +-#include +-#include +-#include +-#include +- +-#include +- +-/* internal chip period is 32kHz, 31250ns */ +-#define INV_ICM42600_TIMESTAMP_PERIOD 31250 +-/* allow a jitter of +/- 2% */ +-#define INV_ICM42600_TIMESTAMP_JITTER 2 +-/* compute min and max periods accepted */ +-#define INV_ICM42600_TIMESTAMP_MIN_PERIOD(_p) \ +- (((_p) * (100 - INV_ICM42600_TIMESTAMP_JITTER)) / 100) +-#define INV_ICM42600_TIMESTAMP_MAX_PERIOD(_p) \ +- (((_p) * (100 + INV_ICM42600_TIMESTAMP_JITTER)) / 100) +- +-/* Add a new value inside an accumulator and update the estimate value */ +-static void inv_update_acc(struct inv_icm42600_timestamp_acc *acc, uint32_t val) +-{ +- uint64_t sum = 0; +- size_t i; +- +- acc->values[acc->idx++] = val; +- if (acc->idx >= ARRAY_SIZE(acc->values)) +- acc->idx = 0; +- +- /* compute the mean of all stored values, use 0 as empty slot */ +- for (i = 0; i < ARRAY_SIZE(acc->values); ++i) { +- if (acc->values[i] == 0) +- break; +- sum += acc->values[i]; +- } +- +- acc->val = div_u64(sum, i); +-} +- +-void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts, +- uint32_t period) +-{ +- /* initial odr for sensor after reset is 1kHz */ +- const uint32_t default_period = 1000000; +- +- /* current multiplier and period values after reset */ +- ts->mult = default_period / INV_ICM42600_TIMESTAMP_PERIOD; +- ts->period = default_period; +- /* new set multiplier is the one from chip initialization */ +- ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD; +- +- /* use theoretical value for chip period */ +- inv_update_acc(&ts->chip_period, INV_ICM42600_TIMESTAMP_PERIOD); +-} +-EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_init, IIO_INV_SENSORS_TIMESTAMP); +- +-int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts, +- uint32_t period, bool fifo) +-{ +- /* when FIFO is on, prevent odr change if one is already pending */ +- if (fifo && ts->new_mult != 0) +- return -EAGAIN; +- +- ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD; +- +- return 0; +-} +-EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_update_odr, IIO_INV_SENSORS_TIMESTAMP); +- +-static bool inv_validate_period(uint32_t period, uint32_t mult) +-{ +- const uint32_t chip_period = INV_ICM42600_TIMESTAMP_PERIOD; +- uint32_t period_min, period_max; +- +- /* check that period is acceptable */ +- period_min = INV_ICM42600_TIMESTAMP_MIN_PERIOD(chip_period) * mult; +- period_max = INV_ICM42600_TIMESTAMP_MAX_PERIOD(chip_period) * mult; +- if (period > period_min && period < period_max) +- return true; +- else +- return false; +-} +- +-static bool inv_compute_chip_period(struct inv_icm42600_timestamp *ts, +- uint32_t mult, uint32_t period) +-{ +- uint32_t new_chip_period; +- +- if (!inv_validate_period(period, mult)) +- return false; +- +- /* update chip internal period estimation */ +- new_chip_period = period / mult; +- inv_update_acc(&ts->chip_period, new_chip_period); +- +- return true; +-} +- +-void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts, +- uint32_t fifo_period, size_t fifo_nb, +- size_t sensor_nb, int64_t timestamp) +-{ +- struct inv_icm42600_timestamp_interval *it; +- int64_t delta, interval; +- const uint32_t fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD; +- uint32_t period = ts->period; +- int32_t m; +- bool valid = false; +- +- if (fifo_nb == 0) +- return; +- +- /* update interrupt timestamp and compute chip and sensor periods */ +- it = &ts->it; +- it->lo = it->up; +- it->up = timestamp; +- delta = it->up - it->lo; +- if (it->lo != 0) { +- /* compute period: delta time divided by number of samples */ +- period = div_s64(delta, fifo_nb); +- valid = inv_compute_chip_period(ts, fifo_mult, period); +- /* update sensor period if chip internal period is updated */ +- if (valid) +- ts->period = ts->mult * ts->chip_period.val; +- } +- +- /* no previous data, compute theoritical value from interrupt */ +- if (ts->timestamp == 0) { +- /* elapsed time: sensor period * sensor samples number */ +- interval = (int64_t)ts->period * (int64_t)sensor_nb; +- ts->timestamp = it->up - interval; +- return; +- } +- +- /* if interrupt interval is valid, sync with interrupt timestamp */ +- if (valid) { +- /* compute measured fifo_period */ +- fifo_period = fifo_mult * ts->chip_period.val; +- /* delta time between last sample and last interrupt */ +- delta = it->lo - ts->timestamp; +- /* if there are multiple samples, go back to first one */ +- while (delta >= (fifo_period * 3 / 2)) +- delta -= fifo_period; +- /* compute maximal adjustment value */ +- m = INV_ICM42600_TIMESTAMP_MAX_PERIOD(ts->period) - ts->period; +- if (delta > m) +- delta = m; +- else if (delta < -m) +- delta = -m; +- ts->timestamp += delta; +- } +-} +-EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_interrupt, IIO_INV_SENSORS_TIMESTAMP); +- +-void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts, +- uint32_t fifo_period, size_t fifo_nb, +- unsigned int fifo_no) +-{ +- int64_t interval; +- uint32_t fifo_mult; +- +- if (ts->new_mult == 0) +- return; +- +- /* update to new multiplier and update period */ +- ts->mult = ts->new_mult; +- ts->new_mult = 0; +- ts->period = ts->mult * ts->chip_period.val; +- +- /* +- * After ODR change the time interval with the previous sample is +- * undertermined (depends when the change occures). So we compute the +- * timestamp from the current interrupt using the new FIFO period, the +- * total number of samples and the current sample numero. +- */ +- if (ts->timestamp != 0) { +- /* compute measured fifo period */ +- fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD; +- fifo_period = fifo_mult * ts->chip_period.val; +- /* computes time interval between interrupt and this sample */ +- interval = (int64_t)(fifo_nb - fifo_no) * (int64_t)fifo_period; +- ts->timestamp = ts->it.up - interval; +- } +-} +-EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_apply_odr, IIO_INV_SENSORS_TIMESTAMP); +- +-MODULE_AUTHOR("InvenSense, Inc."); +-MODULE_DESCRIPTION("InvenSense sensors timestamp module"); +-MODULE_LICENSE("GPL"); +--- /dev/null ++++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c +@@ -0,0 +1,191 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2020 Invensense, Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++/* compute jitter, min and max following jitter in per mille */ ++#define INV_SENSORS_TIMESTAMP_JITTER(_val, _jitter) \ ++ (div_s64((_val) * (_jitter), 1000)) ++#define INV_SENSORS_TIMESTAMP_MIN(_val, _jitter) \ ++ (((_val) * (1000 - (_jitter))) / 1000) ++#define INV_SENSORS_TIMESTAMP_MAX(_val, _jitter) \ ++ (((_val) * (1000 + (_jitter))) / 1000) ++ ++/* Add a new value inside an accumulator and update the estimate value */ ++static void inv_update_acc(struct inv_sensors_timestamp_acc *acc, uint32_t val) ++{ ++ uint64_t sum = 0; ++ size_t i; ++ ++ acc->values[acc->idx++] = val; ++ if (acc->idx >= ARRAY_SIZE(acc->values)) ++ acc->idx = 0; ++ ++ /* compute the mean of all stored values, use 0 as empty slot */ ++ for (i = 0; i < ARRAY_SIZE(acc->values); ++i) { ++ if (acc->values[i] == 0) ++ break; ++ sum += acc->values[i]; ++ } ++ ++ acc->val = div_u64(sum, i); ++} ++ ++void inv_sensors_timestamp_init(struct inv_sensors_timestamp *ts, ++ const struct inv_sensors_timestamp_chip *chip) ++{ ++ memset(ts, 0, sizeof(*ts)); ++ ++ /* save chip parameters and compute min and max clock period */ ++ ts->chip = *chip; ++ ts->min_period = INV_SENSORS_TIMESTAMP_MIN(chip->clock_period, chip->jitter); ++ ts->max_period = INV_SENSORS_TIMESTAMP_MAX(chip->clock_period, chip->jitter); ++ ++ /* current multiplier and period values after reset */ ++ ts->mult = chip->init_period / chip->clock_period; ++ ts->period = chip->init_period; ++ ++ /* use theoretical value for chip period */ ++ inv_update_acc(&ts->chip_period, chip->clock_period); ++} ++EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_init, IIO_INV_SENSORS_TIMESTAMP); ++ ++int inv_sensors_timestamp_update_odr(struct inv_sensors_timestamp *ts, ++ uint32_t period, bool fifo) ++{ ++ /* when FIFO is on, prevent odr change if one is already pending */ ++ if (fifo && ts->new_mult != 0) ++ return -EAGAIN; ++ ++ ts->new_mult = period / ts->chip.clock_period; ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_update_odr, IIO_INV_SENSORS_TIMESTAMP); ++ ++static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t period, uint32_t mult) ++{ ++ uint32_t period_min, period_max; ++ ++ /* check that period is acceptable */ ++ period_min = ts->min_period * mult; ++ period_max = ts->max_period * mult; ++ if (period > period_min && period < period_max) ++ return true; ++ else ++ return false; ++} ++ ++static bool inv_compute_chip_period(struct inv_sensors_timestamp *ts, ++ uint32_t mult, uint32_t period) ++{ ++ uint32_t new_chip_period; ++ ++ if (!inv_validate_period(ts, period, mult)) ++ return false; ++ ++ /* update chip internal period estimation */ ++ new_chip_period = period / mult; ++ inv_update_acc(&ts->chip_period, new_chip_period); ++ ++ return true; ++} ++ ++void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, ++ uint32_t fifo_period, size_t fifo_nb, ++ size_t sensor_nb, int64_t timestamp) ++{ ++ struct inv_sensors_timestamp_interval *it; ++ int64_t delta, interval; ++ const uint32_t fifo_mult = fifo_period / ts->chip.clock_period; ++ uint32_t period = ts->period; ++ int32_t m; ++ bool valid = false; ++ ++ if (fifo_nb == 0) ++ return; ++ ++ /* update interrupt timestamp and compute chip and sensor periods */ ++ it = &ts->it; ++ it->lo = it->up; ++ it->up = timestamp; ++ delta = it->up - it->lo; ++ if (it->lo != 0) { ++ /* compute period: delta time divided by number of samples */ ++ period = div_s64(delta, fifo_nb); ++ valid = inv_compute_chip_period(ts, fifo_mult, period); ++ /* update sensor period if chip internal period is updated */ ++ if (valid) ++ ts->period = ts->mult * ts->chip_period.val; ++ } ++ ++ /* no previous data, compute theoritical value from interrupt */ ++ if (ts->timestamp == 0) { ++ /* elapsed time: sensor period * sensor samples number */ ++ interval = (int64_t)ts->period * (int64_t)sensor_nb; ++ ts->timestamp = it->up - interval; ++ return; ++ } ++ ++ /* if interrupt interval is valid, sync with interrupt timestamp */ ++ if (valid) { ++ /* compute measured fifo_period */ ++ fifo_period = fifo_mult * ts->chip_period.val; ++ /* delta time between last sample and last interrupt */ ++ delta = it->lo - ts->timestamp; ++ /* if there are multiple samples, go back to first one */ ++ while (delta >= (fifo_period * 3 / 2)) ++ delta -= fifo_period; ++ /* compute maximal adjustment value */ ++ m = INV_SENSORS_TIMESTAMP_JITTER((int64_t)ts->period, ts->chip.jitter); ++ if (delta > m) ++ delta = m; ++ else if (delta < -m) ++ delta = -m; ++ ts->timestamp += delta; ++ } ++} ++EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_interrupt, IIO_INV_SENSORS_TIMESTAMP); ++ ++void inv_sensors_timestamp_apply_odr(struct inv_sensors_timestamp *ts, ++ uint32_t fifo_period, size_t fifo_nb, ++ unsigned int fifo_no) ++{ ++ int64_t interval; ++ uint32_t fifo_mult; ++ ++ if (ts->new_mult == 0) ++ return; ++ ++ /* update to new multiplier and update period */ ++ ts->mult = ts->new_mult; ++ ts->new_mult = 0; ++ ts->period = ts->mult * ts->chip_period.val; ++ ++ /* ++ * After ODR change the time interval with the previous sample is ++ * undertermined (depends when the change occures). So we compute the ++ * timestamp from the current interrupt using the new FIFO period, the ++ * total number of samples and the current sample numero. ++ */ ++ if (ts->timestamp != 0) { ++ /* compute measured fifo period */ ++ fifo_mult = fifo_period / ts->chip.clock_period; ++ fifo_period = fifo_mult * ts->chip_period.val; ++ /* computes time interval between interrupt and this sample */ ++ interval = (int64_t)(fifo_nb - fifo_no) * (int64_t)fifo_period; ++ ts->timestamp = ts->it.up - interval; ++ } ++} ++EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_apply_odr, IIO_INV_SENSORS_TIMESTAMP); ++ ++MODULE_AUTHOR("InvenSense, Inc."); ++MODULE_DESCRIPTION("InvenSense sensors timestamp module"); ++MODULE_LICENSE("GPL"); +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c +@@ -12,7 +12,7 @@ + #include + + #include +-#include ++#include + #include + #include + +@@ -99,7 +99,7 @@ static int inv_icm42600_accel_update_sca + const unsigned long *scan_mask) + { + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); +- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); ++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int fifo_en = 0; + unsigned int sleep_temp = 0; +@@ -127,7 +127,7 @@ static int inv_icm42600_accel_update_sca + } + + /* update data FIFO write */ +- inv_icm42600_timestamp_apply_odr(ts, 0, 0, 0); ++ inv_sensors_timestamp_apply_odr(ts, 0, 0, 0); + ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); + + out_unlock: +@@ -308,7 +308,7 @@ static int inv_icm42600_accel_write_odr( + int val, int val2) + { + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); +- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); ++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; +@@ -329,8 +329,8 @@ static int inv_icm42600_accel_write_odr( + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + +- ret = inv_icm42600_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr), +- iio_buffer_enabled(indio_dev)); ++ ret = inv_sensors_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr), ++ iio_buffer_enabled(indio_dev)); + if (ret) + goto out_unlock; + +@@ -706,7 +706,8 @@ struct iio_dev *inv_icm42600_accel_init( + { + struct device *dev = regmap_get_device(st->map); + const char *name; +- struct inv_icm42600_timestamp *ts; ++ struct inv_sensors_timestamp_chip ts_chip; ++ struct inv_sensors_timestamp *ts; + struct iio_dev *indio_dev; + int ret; + +@@ -718,8 +719,15 @@ struct iio_dev *inv_icm42600_accel_init( + if (!indio_dev) + return ERR_PTR(-ENOMEM); + ++ /* ++ * clock period is 32kHz (31250ns) ++ * jitter is +/- 2% (20 per mille) ++ */ ++ ts_chip.clock_period = 31250; ++ ts_chip.jitter = 20; ++ ts_chip.init_period = inv_icm42600_odr_to_period(st->conf.accel.odr); + ts = iio_priv(indio_dev); +- inv_icm42600_timestamp_init(ts, inv_icm42600_odr_to_period(st->conf.accel.odr)); ++ inv_sensors_timestamp_init(ts, &ts_chip); + + iio_device_set_drvdata(indio_dev, st); + indio_dev->name = name; +@@ -744,7 +752,7 @@ struct iio_dev *inv_icm42600_accel_init( + int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev) + { + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); +- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); ++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + ssize_t i, size; + unsigned int no; + const void *accel, *gyro, *timestamp; +@@ -768,13 +776,13 @@ int inv_icm42600_accel_parse_fifo(struct + + /* update odr */ + if (odr & INV_ICM42600_SENSOR_ACCEL) +- inv_icm42600_timestamp_apply_odr(ts, st->fifo.period, +- st->fifo.nb.total, no); ++ inv_sensors_timestamp_apply_odr(ts, st->fifo.period, ++ st->fifo.nb.total, no); + + memcpy(&buffer.accel, accel, sizeof(buffer.accel)); + /* convert 8 bits FIFO temperature in high resolution format */ + buffer.temp = temp ? (*temp * 64) : 0; +- ts_val = inv_icm42600_timestamp_pop(ts); ++ ts_val = inv_sensors_timestamp_pop(ts); + iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val); + } + +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +@@ -11,7 +11,7 @@ + #include + + #include +-#include ++#include + #include + + #include "inv_icm42600.h" +@@ -276,12 +276,12 @@ static int inv_icm42600_buffer_preenable + { + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); +- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); ++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + + pm_runtime_get_sync(dev); + + mutex_lock(&st->lock); +- inv_icm42600_timestamp_reset(ts); ++ inv_sensors_timestamp_reset(ts); + mutex_unlock(&st->lock); + + return 0; +@@ -378,7 +378,7 @@ out_unlock: + static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev) + { + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); +- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); ++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int sensor; + unsigned int *watermark; +@@ -400,7 +400,7 @@ static int inv_icm42600_buffer_postdisab + + mutex_lock(&st->lock); + +- inv_icm42600_timestamp_apply_odr(ts, 0, 0, 0); ++ inv_sensors_timestamp_apply_odr(ts, 0, 0, 0); + + ret = inv_icm42600_buffer_set_fifo_en(st, st->fifo.en & ~sensor); + if (ret) +@@ -508,7 +508,7 @@ int inv_icm42600_buffer_fifo_read(struct + + int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st) + { +- struct inv_icm42600_timestamp *ts; ++ struct inv_sensors_timestamp *ts; + int ret; + + if (st->fifo.nb.total == 0) +@@ -516,8 +516,8 @@ int inv_icm42600_buffer_fifo_parse(struc + + /* handle gyroscope timestamp and FIFO data parsing */ + ts = iio_priv(st->indio_gyro); +- inv_icm42600_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, +- st->fifo.nb.gyro, st->timestamp.gyro); ++ inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, ++ st->fifo.nb.gyro, st->timestamp.gyro); + if (st->fifo.nb.gyro > 0) { + ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); + if (ret) +@@ -526,8 +526,8 @@ int inv_icm42600_buffer_fifo_parse(struc + + /* handle accelerometer timestamp and FIFO data parsing */ + ts = iio_priv(st->indio_accel); +- inv_icm42600_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, +- st->fifo.nb.accel, st->timestamp.accel); ++ inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, ++ st->fifo.nb.accel, st->timestamp.accel); + if (st->fifo.nb.accel > 0) { + ret = inv_icm42600_accel_parse_fifo(st->indio_accel); + if (ret) +@@ -540,7 +540,7 @@ int inv_icm42600_buffer_fifo_parse(struc + int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st, + unsigned int count) + { +- struct inv_icm42600_timestamp *ts; ++ struct inv_sensors_timestamp *ts; + int64_t gyro_ts, accel_ts; + int ret; + +@@ -556,9 +556,9 @@ int inv_icm42600_buffer_hwfifo_flush(str + + if (st->fifo.nb.gyro > 0) { + ts = iio_priv(st->indio_gyro); +- inv_icm42600_timestamp_interrupt(ts, st->fifo.period, +- st->fifo.nb.total, st->fifo.nb.gyro, +- gyro_ts); ++ inv_sensors_timestamp_interrupt(ts, st->fifo.period, ++ st->fifo.nb.total, st->fifo.nb.gyro, ++ gyro_ts); + ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; +@@ -566,9 +566,9 @@ int inv_icm42600_buffer_hwfifo_flush(str + + if (st->fifo.nb.accel > 0) { + ts = iio_priv(st->indio_accel); +- inv_icm42600_timestamp_interrupt(ts, st->fifo.period, +- st->fifo.nb.total, st->fifo.nb.accel, +- accel_ts); ++ inv_sensors_timestamp_interrupt(ts, st->fifo.period, ++ st->fifo.nb.total, st->fifo.nb.accel, ++ accel_ts); + ret = inv_icm42600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c +@@ -16,7 +16,7 @@ + #include + #include + +-#include ++#include + #include + + #include "inv_icm42600.h" +@@ -720,8 +720,8 @@ out_unlock: + static int __maybe_unused inv_icm42600_resume(struct device *dev) + { + struct inv_icm42600_state *st = dev_get_drvdata(dev); +- struct inv_icm42600_timestamp *gyro_ts = iio_priv(st->indio_gyro); +- struct inv_icm42600_timestamp *accel_ts = iio_priv(st->indio_accel); ++ struct inv_sensors_timestamp *gyro_ts = iio_priv(st->indio_gyro); ++ struct inv_sensors_timestamp *accel_ts = iio_priv(st->indio_accel); + int ret = 0; + + mutex_lock(&st->lock); +@@ -742,8 +742,8 @@ static int __maybe_unused inv_icm42600_r + + /* restore FIFO data streaming */ + if (st->fifo.on) { +- inv_icm42600_timestamp_reset(gyro_ts); +- inv_icm42600_timestamp_reset(accel_ts); ++ inv_sensors_timestamp_reset(gyro_ts); ++ inv_sensors_timestamp_reset(accel_ts); + ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, + INV_ICM42600_FIFO_CONFIG_STREAM); + } +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c +@@ -12,7 +12,7 @@ + #include + + #include +-#include ++#include + #include + #include + +@@ -99,7 +99,7 @@ static int inv_icm42600_gyro_update_scan + const unsigned long *scan_mask) + { + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); +- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); ++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int fifo_en = 0; + unsigned int sleep_gyro = 0; +@@ -127,7 +127,7 @@ static int inv_icm42600_gyro_update_scan + } + + /* update data FIFO write */ +- inv_icm42600_timestamp_apply_odr(ts, 0, 0, 0); ++ inv_sensors_timestamp_apply_odr(ts, 0, 0, 0); + ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); + + out_unlock: +@@ -320,7 +320,7 @@ static int inv_icm42600_gyro_write_odr(s + int val, int val2) + { + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); +- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); ++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; +@@ -341,8 +341,8 @@ static int inv_icm42600_gyro_write_odr(s + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + +- ret = inv_icm42600_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr), +- iio_buffer_enabled(indio_dev)); ++ ret = inv_sensors_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr), ++ iio_buffer_enabled(indio_dev)); + if (ret) + goto out_unlock; + +@@ -717,7 +717,8 @@ struct iio_dev *inv_icm42600_gyro_init(s + { + struct device *dev = regmap_get_device(st->map); + const char *name; +- struct inv_icm42600_timestamp *ts; ++ struct inv_sensors_timestamp_chip ts_chip; ++ struct inv_sensors_timestamp *ts; + struct iio_dev *indio_dev; + int ret; + +@@ -729,8 +730,15 @@ struct iio_dev *inv_icm42600_gyro_init(s + if (!indio_dev) + return ERR_PTR(-ENOMEM); + ++ /* ++ * clock period is 32kHz (31250ns) ++ * jitter is +/- 2% (20 per mille) ++ */ ++ ts_chip.clock_period = 31250; ++ ts_chip.jitter = 20; ++ ts_chip.init_period = inv_icm42600_odr_to_period(st->conf.accel.odr); + ts = iio_priv(indio_dev); +- inv_icm42600_timestamp_init(ts, inv_icm42600_odr_to_period(st->conf.gyro.odr)); ++ inv_sensors_timestamp_init(ts, &ts_chip); + + iio_device_set_drvdata(indio_dev, st); + indio_dev->name = name; +@@ -756,7 +764,7 @@ struct iio_dev *inv_icm42600_gyro_init(s + int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev) + { + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); +- struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); ++ struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + ssize_t i, size; + unsigned int no; + const void *accel, *gyro, *timestamp; +@@ -780,13 +788,13 @@ int inv_icm42600_gyro_parse_fifo(struct + + /* update odr */ + if (odr & INV_ICM42600_SENSOR_GYRO) +- inv_icm42600_timestamp_apply_odr(ts, st->fifo.period, +- st->fifo.nb.total, no); ++ inv_sensors_timestamp_apply_odr(ts, st->fifo.period, ++ st->fifo.nb.total, no); + + memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro)); + /* convert 8 bits FIFO temperature in high resolution format */ + buffer.temp = temp ? (*temp * 64) : 0; +- ts_val = inv_icm42600_timestamp_pop(ts); ++ ts_val = inv_sensors_timestamp_pop(ts); + iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val); + } + +--- a/include/linux/iio/common/inv_icm42600_timestamp.h ++++ /dev/null +@@ -1,79 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-or-later */ +-/* +- * Copyright (C) 2020 Invensense, Inc. +- */ +- +-#ifndef INV_ICM42600_TIMESTAMP_H_ +-#define INV_ICM42600_TIMESTAMP_H_ +- +-/** +- * struct inv_icm42600_timestamp_interval - timestamps interval +- * @lo: interval lower bound +- * @up: interval upper bound +- */ +-struct inv_icm42600_timestamp_interval { +- int64_t lo; +- int64_t up; +-}; +- +-/** +- * struct inv_icm42600_timestamp_acc - accumulator for computing an estimation +- * @val: current estimation of the value, the mean of all values +- * @idx: current index of the next free place in values table +- * @values: table of all measured values, use for computing the mean +- */ +-struct inv_icm42600_timestamp_acc { +- uint32_t val; +- size_t idx; +- uint32_t values[32]; +-}; +- +-/** +- * struct inv_icm42600_timestamp - timestamp management states +- * @it: interrupts interval timestamps +- * @timestamp: store last timestamp for computing next data timestamp +- * @mult: current internal period multiplier +- * @new_mult: new set internal period multiplier (not yet effective) +- * @period: measured current period of the sensor +- * @chip_period: accumulator for computing internal chip period +- */ +-struct inv_icm42600_timestamp { +- struct inv_icm42600_timestamp_interval it; +- int64_t timestamp; +- uint32_t mult; +- uint32_t new_mult; +- uint32_t period; +- struct inv_icm42600_timestamp_acc chip_period; +-}; +- +-void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts, +- uint32_t period); +- +-int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts, +- uint32_t period, bool fifo); +- +-void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts, +- uint32_t fifo_period, size_t fifo_nb, +- size_t sensor_nb, int64_t timestamp); +- +-static inline int64_t +-inv_icm42600_timestamp_pop(struct inv_icm42600_timestamp *ts) +-{ +- ts->timestamp += ts->period; +- return ts->timestamp; +-} +- +-void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts, +- uint32_t fifo_period, size_t fifo_nb, +- unsigned int fifo_no); +- +-static inline void +-inv_icm42600_timestamp_reset(struct inv_icm42600_timestamp *ts) +-{ +- const struct inv_icm42600_timestamp_interval interval_init = {0LL, 0LL}; +- +- ts->it = interval_init; +- ts->timestamp = 0; +-} +- +-#endif +--- /dev/null ++++ b/include/linux/iio/common/inv_sensors_timestamp.h +@@ -0,0 +1,95 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (C) 2020 Invensense, Inc. ++ */ ++ ++#ifndef INV_SENSORS_TIMESTAMP_H_ ++#define INV_SENSORS_TIMESTAMP_H_ ++ ++/** ++ * struct inv_sensors_timestamp_chip - chip internal properties ++ * @clock_period: internal clock period in ns ++ * @jitter: acceptable jitter in per-mille ++ * @init_period: chip initial period at reset in ns ++ */ ++struct inv_sensors_timestamp_chip { ++ uint32_t clock_period; ++ uint32_t jitter; ++ uint32_t init_period; ++}; ++ ++/** ++ * struct inv_sensors_timestamp_interval - timestamps interval ++ * @lo: interval lower bound ++ * @up: interval upper bound ++ */ ++struct inv_sensors_timestamp_interval { ++ int64_t lo; ++ int64_t up; ++}; ++ ++/** ++ * struct inv_sensors_timestamp_acc - accumulator for computing an estimation ++ * @val: current estimation of the value, the mean of all values ++ * @idx: current index of the next free place in values table ++ * @values: table of all measured values, use for computing the mean ++ */ ++struct inv_sensors_timestamp_acc { ++ uint32_t val; ++ size_t idx; ++ uint32_t values[32]; ++}; ++ ++/** ++ * struct inv_sensors_timestamp - timestamp management states ++ * @chip: chip internal characteristics ++ * @min_period: minimal acceptable clock period ++ * @max_period: maximal acceptable clock period ++ * @it: interrupts interval timestamps ++ * @timestamp: store last timestamp for computing next data timestamp ++ * @mult: current internal period multiplier ++ * @new_mult: new set internal period multiplier (not yet effective) ++ * @period: measured current period of the sensor ++ * @chip_period: accumulator for computing internal chip period ++ */ ++struct inv_sensors_timestamp { ++ struct inv_sensors_timestamp_chip chip; ++ uint32_t min_period; ++ uint32_t max_period; ++ struct inv_sensors_timestamp_interval it; ++ int64_t timestamp; ++ uint32_t mult; ++ uint32_t new_mult; ++ uint32_t period; ++ struct inv_sensors_timestamp_acc chip_period; ++}; ++ ++void inv_sensors_timestamp_init(struct inv_sensors_timestamp *ts, ++ const struct inv_sensors_timestamp_chip *chip); ++ ++int inv_sensors_timestamp_update_odr(struct inv_sensors_timestamp *ts, ++ uint32_t period, bool fifo); ++ ++void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, ++ uint32_t fifo_period, size_t fifo_nb, ++ size_t sensor_nb, int64_t timestamp); ++ ++static inline int64_t inv_sensors_timestamp_pop(struct inv_sensors_timestamp *ts) ++{ ++ ts->timestamp += ts->period; ++ return ts->timestamp; ++} ++ ++void inv_sensors_timestamp_apply_odr(struct inv_sensors_timestamp *ts, ++ uint32_t fifo_period, size_t fifo_nb, ++ unsigned int fifo_no); ++ ++static inline void inv_sensors_timestamp_reset(struct inv_sensors_timestamp *ts) ++{ ++ const struct inv_sensors_timestamp_interval interval_init = {0LL, 0LL}; ++ ++ ts->it = interval_init; ++ ts->timestamp = 0; ++} ++ ++#endif diff --git a/queue-6.1/iio-move-inv_icm42600-timestamp-module-in-common.patch b/queue-6.1/iio-move-inv_icm42600-timestamp-module-in-common.patch new file mode 100644 index 0000000000..d81bc46950 --- /dev/null +++ b/queue-6.1/iio-move-inv_icm42600-timestamp-module-in-common.patch @@ -0,0 +1,741 @@ +From stable+bounces-274004-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:51 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:39:36 -0400 +Subject: iio: move inv_icm42600 timestamp module in common +To: stable@vger.kernel.org +Cc: Jean-Baptiste Maneyrol , Andy Shevchenko , Jonathan Cameron , Sasha Levin +Message-ID: <20260713203942.2144352-2-sashal@kernel.org> + +From: Jean-Baptiste Maneyrol + +[ Upstream commit d99ff463ecf651437e9e4abe68f331dfb6b5bd9d ] + +Create new inv_sensors common modules and move inv_icm42600 +timestamp module inside. This module will be used by IMUs and +also in the future by other chips. + +Modify inv_icm42600 driver to use timestamp module and do some +headers cleanup. + +Signed-off-by: Jean-Baptiste Maneyrol +Reviewed-by: Andy Shevchenko +Link: https://lore.kernel.org/r/20230606162147.79667-3-inv.git-commit@tdk.com +Signed-off-by: Jonathan Cameron +Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/common/Kconfig | 1 + + drivers/iio/common/Makefile | 1 + + drivers/iio/common/inv_sensors/Kconfig | 7 +++++++ + drivers/iio/common/inv_sensors/Makefile | 6 ++++++ + .../inv_sensors}/inv_icm42600_timestamp.c | 11 ++++++++++- + drivers/iio/imu/inv_icm42600/Kconfig | 1 + + drivers/iio/imu/inv_icm42600/Makefile | 1 - + drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c | 5 +++-- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 5 +++-- + drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 4 +++- + drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c | 5 +++-- + .../linux/iio/common}/inv_icm42600_timestamp.h | 0 + drivers/iio/common/Kconfig | 1 + drivers/iio/common/Makefile | 1 + drivers/iio/common/inv_sensors/Kconfig | 7 + drivers/iio/common/inv_sensors/Makefile | 6 + drivers/iio/common/inv_sensors/inv_icm42600_timestamp.c | 192 ++++++++++++++++ + drivers/iio/imu/inv_icm42600/Kconfig | 1 + drivers/iio/imu/inv_icm42600/Makefile | 1 + drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c | 5 + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 5 + drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 4 + drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c | 5 + drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c | 183 --------------- + drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h | 79 ------ + include/linux/iio/common/inv_icm42600_timestamp.h | 79 ++++++ + 14 files changed, 299 insertions(+), 270 deletions(-) + create mode 100644 drivers/iio/common/inv_sensors/Kconfig + create mode 100644 drivers/iio/common/inv_sensors/Makefile + rename drivers/iio/{imu/inv_icm42600 => common/inv_sensors}/inv_icm42600_timestamp.c (91%) + rename {drivers/iio/imu/inv_icm42600 => include/linux/iio/common}/inv_icm42600_timestamp.h (100%) + +--- a/drivers/iio/common/Kconfig ++++ b/drivers/iio/common/Kconfig +@@ -5,6 +5,7 @@ + + source "drivers/iio/common/cros_ec_sensors/Kconfig" + source "drivers/iio/common/hid-sensors/Kconfig" ++source "drivers/iio/common/inv_sensors/Kconfig" + source "drivers/iio/common/ms_sensors/Kconfig" + source "drivers/iio/common/scmi_sensors/Kconfig" + source "drivers/iio/common/ssp_sensors/Kconfig" +--- a/drivers/iio/common/Makefile ++++ b/drivers/iio/common/Makefile +@@ -10,6 +10,7 @@ + # When adding new entries keep the list in alphabetical order + obj-y += cros_ec_sensors/ + obj-y += hid-sensors/ ++obj-y += inv_sensors/ + obj-y += ms_sensors/ + obj-y += scmi_sensors/ + obj-y += ssp_sensors/ +--- /dev/null ++++ b/drivers/iio/common/inv_sensors/Kconfig +@@ -0,0 +1,7 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++# ++# TDK-InvenSense sensors common library ++# ++ ++config IIO_INV_SENSORS_TIMESTAMP ++ tristate +--- /dev/null ++++ b/drivers/iio/common/inv_sensors/Makefile +@@ -0,0 +1,6 @@ ++# SPDX-License-Identifier: GPL-2.0 ++# ++# Makefile for TDK-InvenSense sensors module. ++# ++ ++obj-$(CONFIG_IIO_INV_SENSORS_TIMESTAMP) += inv_icm42600_timestamp.o +--- /dev/null ++++ b/drivers/iio/common/inv_sensors/inv_icm42600_timestamp.c +@@ -0,0 +1,192 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2020 Invensense, Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++/* internal chip period is 32kHz, 31250ns */ ++#define INV_ICM42600_TIMESTAMP_PERIOD 31250 ++/* allow a jitter of +/- 2% */ ++#define INV_ICM42600_TIMESTAMP_JITTER 2 ++/* compute min and max periods accepted */ ++#define INV_ICM42600_TIMESTAMP_MIN_PERIOD(_p) \ ++ (((_p) * (100 - INV_ICM42600_TIMESTAMP_JITTER)) / 100) ++#define INV_ICM42600_TIMESTAMP_MAX_PERIOD(_p) \ ++ (((_p) * (100 + INV_ICM42600_TIMESTAMP_JITTER)) / 100) ++ ++/* Add a new value inside an accumulator and update the estimate value */ ++static void inv_update_acc(struct inv_icm42600_timestamp_acc *acc, uint32_t val) ++{ ++ uint64_t sum = 0; ++ size_t i; ++ ++ acc->values[acc->idx++] = val; ++ if (acc->idx >= ARRAY_SIZE(acc->values)) ++ acc->idx = 0; ++ ++ /* compute the mean of all stored values, use 0 as empty slot */ ++ for (i = 0; i < ARRAY_SIZE(acc->values); ++i) { ++ if (acc->values[i] == 0) ++ break; ++ sum += acc->values[i]; ++ } ++ ++ acc->val = div_u64(sum, i); ++} ++ ++void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts, ++ uint32_t period) ++{ ++ /* initial odr for sensor after reset is 1kHz */ ++ const uint32_t default_period = 1000000; ++ ++ /* current multiplier and period values after reset */ ++ ts->mult = default_period / INV_ICM42600_TIMESTAMP_PERIOD; ++ ts->period = default_period; ++ /* new set multiplier is the one from chip initialization */ ++ ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD; ++ ++ /* use theoretical value for chip period */ ++ inv_update_acc(&ts->chip_period, INV_ICM42600_TIMESTAMP_PERIOD); ++} ++EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_init, IIO_INV_SENSORS_TIMESTAMP); ++ ++int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts, ++ uint32_t period, bool fifo) ++{ ++ /* when FIFO is on, prevent odr change if one is already pending */ ++ if (fifo && ts->new_mult != 0) ++ return -EAGAIN; ++ ++ ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD; ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_update_odr, IIO_INV_SENSORS_TIMESTAMP); ++ ++static bool inv_validate_period(uint32_t period, uint32_t mult) ++{ ++ const uint32_t chip_period = INV_ICM42600_TIMESTAMP_PERIOD; ++ uint32_t period_min, period_max; ++ ++ /* check that period is acceptable */ ++ period_min = INV_ICM42600_TIMESTAMP_MIN_PERIOD(chip_period) * mult; ++ period_max = INV_ICM42600_TIMESTAMP_MAX_PERIOD(chip_period) * mult; ++ if (period > period_min && period < period_max) ++ return true; ++ else ++ return false; ++} ++ ++static bool inv_compute_chip_period(struct inv_icm42600_timestamp *ts, ++ uint32_t mult, uint32_t period) ++{ ++ uint32_t new_chip_period; ++ ++ if (!inv_validate_period(period, mult)) ++ return false; ++ ++ /* update chip internal period estimation */ ++ new_chip_period = period / mult; ++ inv_update_acc(&ts->chip_period, new_chip_period); ++ ++ return true; ++} ++ ++void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts, ++ uint32_t fifo_period, size_t fifo_nb, ++ size_t sensor_nb, int64_t timestamp) ++{ ++ struct inv_icm42600_timestamp_interval *it; ++ int64_t delta, interval; ++ const uint32_t fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD; ++ uint32_t period = ts->period; ++ int32_t m; ++ bool valid = false; ++ ++ if (fifo_nb == 0) ++ return; ++ ++ /* update interrupt timestamp and compute chip and sensor periods */ ++ it = &ts->it; ++ it->lo = it->up; ++ it->up = timestamp; ++ delta = it->up - it->lo; ++ if (it->lo != 0) { ++ /* compute period: delta time divided by number of samples */ ++ period = div_s64(delta, fifo_nb); ++ valid = inv_compute_chip_period(ts, fifo_mult, period); ++ /* update sensor period if chip internal period is updated */ ++ if (valid) ++ ts->period = ts->mult * ts->chip_period.val; ++ } ++ ++ /* no previous data, compute theoritical value from interrupt */ ++ if (ts->timestamp == 0) { ++ /* elapsed time: sensor period * sensor samples number */ ++ interval = (int64_t)ts->period * (int64_t)sensor_nb; ++ ts->timestamp = it->up - interval; ++ return; ++ } ++ ++ /* if interrupt interval is valid, sync with interrupt timestamp */ ++ if (valid) { ++ /* compute measured fifo_period */ ++ fifo_period = fifo_mult * ts->chip_period.val; ++ /* delta time between last sample and last interrupt */ ++ delta = it->lo - ts->timestamp; ++ /* if there are multiple samples, go back to first one */ ++ while (delta >= (fifo_period * 3 / 2)) ++ delta -= fifo_period; ++ /* compute maximal adjustment value */ ++ m = INV_ICM42600_TIMESTAMP_MAX_PERIOD(ts->period) - ts->period; ++ if (delta > m) ++ delta = m; ++ else if (delta < -m) ++ delta = -m; ++ ts->timestamp += delta; ++ } ++} ++EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_interrupt, IIO_INV_SENSORS_TIMESTAMP); ++ ++void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts, ++ uint32_t fifo_period, size_t fifo_nb, ++ unsigned int fifo_no) ++{ ++ int64_t interval; ++ uint32_t fifo_mult; ++ ++ if (ts->new_mult == 0) ++ return; ++ ++ /* update to new multiplier and update period */ ++ ts->mult = ts->new_mult; ++ ts->new_mult = 0; ++ ts->period = ts->mult * ts->chip_period.val; ++ ++ /* ++ * After ODR change the time interval with the previous sample is ++ * undertermined (depends when the change occures). So we compute the ++ * timestamp from the current interrupt using the new FIFO period, the ++ * total number of samples and the current sample numero. ++ */ ++ if (ts->timestamp != 0) { ++ /* compute measured fifo period */ ++ fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD; ++ fifo_period = fifo_mult * ts->chip_period.val; ++ /* computes time interval between interrupt and this sample */ ++ interval = (int64_t)(fifo_nb - fifo_no) * (int64_t)fifo_period; ++ ts->timestamp = ts->it.up - interval; ++ } ++} ++EXPORT_SYMBOL_NS_GPL(inv_icm42600_timestamp_apply_odr, IIO_INV_SENSORS_TIMESTAMP); ++ ++MODULE_AUTHOR("InvenSense, Inc."); ++MODULE_DESCRIPTION("InvenSense sensors timestamp module"); ++MODULE_LICENSE("GPL"); +--- a/drivers/iio/imu/inv_icm42600/Kconfig ++++ b/drivers/iio/imu/inv_icm42600/Kconfig +@@ -3,6 +3,7 @@ + config INV_ICM42600 + tristate + select IIO_BUFFER ++ select IIO_INV_SENSORS_TIMESTAMP + + config INV_ICM42600_I2C + tristate "InvenSense ICM-426xx I2C driver" +--- a/drivers/iio/imu/inv_icm42600/Makefile ++++ b/drivers/iio/imu/inv_icm42600/Makefile +@@ -6,7 +6,6 @@ inv-icm42600-y += inv_icm42600_gyro.o + inv-icm42600-y += inv_icm42600_accel.o + inv-icm42600-y += inv_icm42600_temp.o + inv-icm42600-y += inv_icm42600_buffer.o +-inv-icm42600-y += inv_icm42600_timestamp.o + + obj-$(CONFIG_INV_ICM42600_I2C) += inv-icm42600-i2c.o + inv-icm42600-i2c-y += inv_icm42600_i2c.o +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c +@@ -10,14 +10,15 @@ + #include + #include + #include +-#include ++ + #include ++#include ++#include + #include + + #include "inv_icm42600.h" + #include "inv_icm42600_temp.h" + #include "inv_icm42600_buffer.h" +-#include "inv_icm42600_timestamp.h" + + #define INV_ICM42600_ACCEL_CHAN(_modifier, _index, _ext_info) \ + { \ +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +@@ -9,11 +9,12 @@ + #include + #include + #include +-#include ++ + #include ++#include ++#include + + #include "inv_icm42600.h" +-#include "inv_icm42600_timestamp.h" + #include "inv_icm42600_buffer.h" + + /* FIFO header: 1 byte */ +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c +@@ -15,11 +15,12 @@ + #include + #include + #include ++ ++#include + #include + + #include "inv_icm42600.h" + #include "inv_icm42600_buffer.h" +-#include "inv_icm42600_timestamp.h" + + static const struct regmap_range_cfg inv_icm42600_regmap_ranges[] = { + { +@@ -798,3 +799,4 @@ EXPORT_SYMBOL_GPL(inv_icm42600_pm_ops); + MODULE_AUTHOR("InvenSense, Inc."); + MODULE_DESCRIPTION("InvenSense ICM-426xx device driver"); + MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS(IIO_INV_SENSORS_TIMESTAMP); +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c +@@ -10,14 +10,15 @@ + #include + #include + #include +-#include ++ + #include ++#include ++#include + #include + + #include "inv_icm42600.h" + #include "inv_icm42600_temp.h" + #include "inv_icm42600_buffer.h" +-#include "inv_icm42600_timestamp.h" + + #define INV_ICM42600_GYRO_CHAN(_modifier, _index, _ext_info) \ + { \ +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c ++++ /dev/null +@@ -1,183 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (C) 2020 Invensense, Inc. +- */ +- +-#include +-#include +-#include +- +-#include "inv_icm42600_timestamp.h" +- +-/* internal chip period is 32kHz, 31250ns */ +-#define INV_ICM42600_TIMESTAMP_PERIOD 31250 +-/* allow a jitter of +/- 2% */ +-#define INV_ICM42600_TIMESTAMP_JITTER 2 +-/* compute min and max periods accepted */ +-#define INV_ICM42600_TIMESTAMP_MIN_PERIOD(_p) \ +- (((_p) * (100 - INV_ICM42600_TIMESTAMP_JITTER)) / 100) +-#define INV_ICM42600_TIMESTAMP_MAX_PERIOD(_p) \ +- (((_p) * (100 + INV_ICM42600_TIMESTAMP_JITTER)) / 100) +- +-/* Add a new value inside an accumulator and update the estimate value */ +-static void inv_update_acc(struct inv_icm42600_timestamp_acc *acc, uint32_t val) +-{ +- uint64_t sum = 0; +- size_t i; +- +- acc->values[acc->idx++] = val; +- if (acc->idx >= ARRAY_SIZE(acc->values)) +- acc->idx = 0; +- +- /* compute the mean of all stored values, use 0 as empty slot */ +- for (i = 0; i < ARRAY_SIZE(acc->values); ++i) { +- if (acc->values[i] == 0) +- break; +- sum += acc->values[i]; +- } +- +- acc->val = div_u64(sum, i); +-} +- +-void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts, +- uint32_t period) +-{ +- /* initial odr for sensor after reset is 1kHz */ +- const uint32_t default_period = 1000000; +- +- /* current multiplier and period values after reset */ +- ts->mult = default_period / INV_ICM42600_TIMESTAMP_PERIOD; +- ts->period = default_period; +- /* new set multiplier is the one from chip initialization */ +- ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD; +- +- /* use theoretical value for chip period */ +- inv_update_acc(&ts->chip_period, INV_ICM42600_TIMESTAMP_PERIOD); +-} +- +-int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts, +- uint32_t period, bool fifo) +-{ +- /* when FIFO is on, prevent odr change if one is already pending */ +- if (fifo && ts->new_mult != 0) +- return -EAGAIN; +- +- ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD; +- +- return 0; +-} +- +-static bool inv_validate_period(uint32_t period, uint32_t mult) +-{ +- const uint32_t chip_period = INV_ICM42600_TIMESTAMP_PERIOD; +- uint32_t period_min, period_max; +- +- /* check that period is acceptable */ +- period_min = INV_ICM42600_TIMESTAMP_MIN_PERIOD(chip_period) * mult; +- period_max = INV_ICM42600_TIMESTAMP_MAX_PERIOD(chip_period) * mult; +- if (period > period_min && period < period_max) +- return true; +- else +- return false; +-} +- +-static bool inv_compute_chip_period(struct inv_icm42600_timestamp *ts, +- uint32_t mult, uint32_t period) +-{ +- uint32_t new_chip_period; +- +- if (!inv_validate_period(period, mult)) +- return false; +- +- /* update chip internal period estimation */ +- new_chip_period = period / mult; +- inv_update_acc(&ts->chip_period, new_chip_period); +- +- return true; +-} +- +-void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts, +- uint32_t fifo_period, size_t fifo_nb, +- size_t sensor_nb, int64_t timestamp) +-{ +- struct inv_icm42600_timestamp_interval *it; +- int64_t delta, interval; +- const uint32_t fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD; +- uint32_t period = ts->period; +- int32_t m; +- bool valid = false; +- +- if (fifo_nb == 0) +- return; +- +- /* update interrupt timestamp and compute chip and sensor periods */ +- it = &ts->it; +- it->lo = it->up; +- it->up = timestamp; +- delta = it->up - it->lo; +- if (it->lo != 0) { +- /* compute period: delta time divided by number of samples */ +- period = div_s64(delta, fifo_nb); +- valid = inv_compute_chip_period(ts, fifo_mult, period); +- /* update sensor period if chip internal period is updated */ +- if (valid) +- ts->period = ts->mult * ts->chip_period.val; +- } +- +- /* no previous data, compute theoritical value from interrupt */ +- if (ts->timestamp == 0) { +- /* elapsed time: sensor period * sensor samples number */ +- interval = (int64_t)ts->period * (int64_t)sensor_nb; +- ts->timestamp = it->up - interval; +- return; +- } +- +- /* if interrupt interval is valid, sync with interrupt timestamp */ +- if (valid) { +- /* compute measured fifo_period */ +- fifo_period = fifo_mult * ts->chip_period.val; +- /* delta time between last sample and last interrupt */ +- delta = it->lo - ts->timestamp; +- /* if there are multiple samples, go back to first one */ +- while (delta >= (fifo_period * 3 / 2)) +- delta -= fifo_period; +- /* compute maximal adjustment value */ +- m = INV_ICM42600_TIMESTAMP_MAX_PERIOD(ts->period) - ts->period; +- if (delta > m) +- delta = m; +- else if (delta < -m) +- delta = -m; +- ts->timestamp += delta; +- } +-} +- +-void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts, +- uint32_t fifo_period, size_t fifo_nb, +- unsigned int fifo_no) +-{ +- int64_t interval; +- uint32_t fifo_mult; +- +- if (ts->new_mult == 0) +- return; +- +- /* update to new multiplier and update period */ +- ts->mult = ts->new_mult; +- ts->new_mult = 0; +- ts->period = ts->mult * ts->chip_period.val; +- +- /* +- * After ODR change the time interval with the previous sample is +- * undertermined (depends when the change occures). So we compute the +- * timestamp from the current interrupt using the new FIFO period, the +- * total number of samples and the current sample numero. +- */ +- if (ts->timestamp != 0) { +- /* compute measured fifo period */ +- fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD; +- fifo_period = fifo_mult * ts->chip_period.val; +- /* computes time interval between interrupt and this sample */ +- interval = (int64_t)(fifo_nb - fifo_no) * (int64_t)fifo_period; +- ts->timestamp = ts->it.up - interval; +- } +-} +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h ++++ /dev/null +@@ -1,79 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-or-later */ +-/* +- * Copyright (C) 2020 Invensense, Inc. +- */ +- +-#ifndef INV_ICM42600_TIMESTAMP_H_ +-#define INV_ICM42600_TIMESTAMP_H_ +- +-/** +- * struct inv_icm42600_timestamp_interval - timestamps interval +- * @lo: interval lower bound +- * @up: interval upper bound +- */ +-struct inv_icm42600_timestamp_interval { +- int64_t lo; +- int64_t up; +-}; +- +-/** +- * struct inv_icm42600_timestamp_acc - accumulator for computing an estimation +- * @val: current estimation of the value, the mean of all values +- * @idx: current index of the next free place in values table +- * @values: table of all measured values, use for computing the mean +- */ +-struct inv_icm42600_timestamp_acc { +- uint32_t val; +- size_t idx; +- uint32_t values[32]; +-}; +- +-/** +- * struct inv_icm42600_timestamp - timestamp management states +- * @it: interrupts interval timestamps +- * @timestamp: store last timestamp for computing next data timestamp +- * @mult: current internal period multiplier +- * @new_mult: new set internal period multiplier (not yet effective) +- * @period: measured current period of the sensor +- * @chip_period: accumulator for computing internal chip period +- */ +-struct inv_icm42600_timestamp { +- struct inv_icm42600_timestamp_interval it; +- int64_t timestamp; +- uint32_t mult; +- uint32_t new_mult; +- uint32_t period; +- struct inv_icm42600_timestamp_acc chip_period; +-}; +- +-void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts, +- uint32_t period); +- +-int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts, +- uint32_t period, bool fifo); +- +-void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts, +- uint32_t fifo_period, size_t fifo_nb, +- size_t sensor_nb, int64_t timestamp); +- +-static inline int64_t +-inv_icm42600_timestamp_pop(struct inv_icm42600_timestamp *ts) +-{ +- ts->timestamp += ts->period; +- return ts->timestamp; +-} +- +-void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts, +- uint32_t fifo_period, size_t fifo_nb, +- unsigned int fifo_no); +- +-static inline void +-inv_icm42600_timestamp_reset(struct inv_icm42600_timestamp *ts) +-{ +- const struct inv_icm42600_timestamp_interval interval_init = {0LL, 0LL}; +- +- ts->it = interval_init; +- ts->timestamp = 0; +-} +- +-#endif +--- /dev/null ++++ b/include/linux/iio/common/inv_icm42600_timestamp.h +@@ -0,0 +1,79 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (C) 2020 Invensense, Inc. ++ */ ++ ++#ifndef INV_ICM42600_TIMESTAMP_H_ ++#define INV_ICM42600_TIMESTAMP_H_ ++ ++/** ++ * struct inv_icm42600_timestamp_interval - timestamps interval ++ * @lo: interval lower bound ++ * @up: interval upper bound ++ */ ++struct inv_icm42600_timestamp_interval { ++ int64_t lo; ++ int64_t up; ++}; ++ ++/** ++ * struct inv_icm42600_timestamp_acc - accumulator for computing an estimation ++ * @val: current estimation of the value, the mean of all values ++ * @idx: current index of the next free place in values table ++ * @values: table of all measured values, use for computing the mean ++ */ ++struct inv_icm42600_timestamp_acc { ++ uint32_t val; ++ size_t idx; ++ uint32_t values[32]; ++}; ++ ++/** ++ * struct inv_icm42600_timestamp - timestamp management states ++ * @it: interrupts interval timestamps ++ * @timestamp: store last timestamp for computing next data timestamp ++ * @mult: current internal period multiplier ++ * @new_mult: new set internal period multiplier (not yet effective) ++ * @period: measured current period of the sensor ++ * @chip_period: accumulator for computing internal chip period ++ */ ++struct inv_icm42600_timestamp { ++ struct inv_icm42600_timestamp_interval it; ++ int64_t timestamp; ++ uint32_t mult; ++ uint32_t new_mult; ++ uint32_t period; ++ struct inv_icm42600_timestamp_acc chip_period; ++}; ++ ++void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts, ++ uint32_t period); ++ ++int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts, ++ uint32_t period, bool fifo); ++ ++void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts, ++ uint32_t fifo_period, size_t fifo_nb, ++ size_t sensor_nb, int64_t timestamp); ++ ++static inline int64_t ++inv_icm42600_timestamp_pop(struct inv_icm42600_timestamp *ts) ++{ ++ ts->timestamp += ts->period; ++ return ts->timestamp; ++} ++ ++void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts, ++ uint32_t fifo_period, size_t fifo_nb, ++ unsigned int fifo_no); ++ ++static inline void ++inv_icm42600_timestamp_reset(struct inv_icm42600_timestamp *ts) ++{ ++ const struct inv_icm42600_timestamp_interval interval_init = {0LL, 0LL}; ++ ++ ts->it = interval_init; ++ ts->timestamp = 0; ++} ++ ++#endif diff --git a/queue-6.1/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch b/queue-6.1/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch new file mode 100644 index 0000000000..2bbad8ff6f --- /dev/null +++ b/queue-6.1/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch @@ -0,0 +1,256 @@ +From stable+bounces-276760-greg=kroah.com@vger.kernel.org Thu Jul 16 18:51:03 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 11:37:03 -0400 +Subject: ksmbd: centralize ksmbd_conn final release to plug transport leak +To: stable@vger.kernel.org +Cc: DaeMyung Kang , Namjae Jeon , Steve French , Sasha Levin +Message-ID: <20260716153704.583869-6-sashal@kernel.org> + +From: DaeMyung Kang + +[ 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 +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/connection.c | 49 +++++++++++++++++++++++++++++++++++++++++++-- + fs/smb/server/connection.h | 3 ++ + fs/smb/server/oplock.c | 4 +-- + fs/smb/server/vfs_cache.c | 18 +++++++++++++++- + 4 files changed, 69 insertions(+), 5 deletions(-) + +--- a/fs/smb/server/connection.c ++++ b/fs/smb/server/connection.c +@@ -23,6 +23,43 @@ LIST_HEAD(conn_list); + DECLARE_RWSEM(conn_list_lock); + + /** ++ * ksmbd_conn_get() - take a reference on @conn and return it. ++ * ++ * Returns @conn unchanged so callers can write ++ * "fp->conn = ksmbd_conn_get(work->conn);" in one expression. Returns NULL ++ * if @conn is NULL. ++ */ ++struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn) ++{ ++ if (!conn) ++ return NULL; ++ ++ atomic_inc(&conn->refcnt); ++ return conn; ++} ++ ++/** ++ * ksmbd_conn_put() - drop a reference and, if it was the last, perform the ++ * final, once-per-struct release of a ksmbd_conn. ++ * ++ * The transport is not released here: free_transport() releases it after ++ * calling ksmbd_conn_free(). The final release therefore only pairs ++ * ida_destroy() with kfree(), reaches no sleeping teardown and is safe ++ * from any context, including the RCU callback that frees an opinfo ++ * holding a connection reference. ++ */ ++void ksmbd_conn_put(struct ksmbd_conn *conn) ++{ ++ if (!conn) ++ return; ++ ++ if (atomic_dec_and_test(&conn->refcnt)) { ++ ida_destroy(&conn->async_ida); ++ kfree(conn); ++ } ++} ++ ++/** + * ksmbd_conn_free() - free resources of the connection instance + * + * @conn: connection instance to be cleand up +@@ -36,12 +73,19 @@ void ksmbd_conn_free(struct ksmbd_conn * + list_del(&conn->conns_list); + up_write(&conn_list_lock); + ++ /* ++ * request_buf / preauth_info / mechToken are only ever accessed by the ++ * connection handler thread that owns @conn. ksmbd_conn_free() is ++ * called from the transport free_transport() path when that thread is ++ * exiting, so it is safe to release them unconditionally even when ++ * ksmbd_conn_put() below is not the final putter (oplock / ksmbd_file ++ * holders only retain the conn pointer, not these per-thread buffers). ++ */ + xa_destroy(&conn->sessions); + kvfree(conn->request_buf); + kfree(conn->preauth_info); + kfree(conn->mechToken); +- ida_destroy(&conn->async_ida); +- kfree(conn); ++ ksmbd_conn_put(conn); + } + + /** +@@ -70,6 +114,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void + conn->um = NULL; + atomic_set(&conn->req_running, 0); + atomic_set(&conn->r_count, 0); ++ atomic_set(&conn->refcnt, 1); + conn->total_credits = 1; + conn->outstanding_credits = 0; + +--- a/fs/smb/server/connection.h ++++ b/fs/smb/server/connection.h +@@ -112,6 +112,7 @@ struct ksmbd_conn { + bool signing_negotiated; + __le16 signing_algorithm; + bool binding; ++ atomic_t refcnt; + }; + + struct ksmbd_conn_ops { +@@ -154,6 +155,8 @@ bool ksmbd_conn_alive(struct ksmbd_conn + void ksmbd_conn_wait_idle(struct ksmbd_conn *conn, u64 sess_id); + struct ksmbd_conn *ksmbd_conn_alloc(void); + void ksmbd_conn_free(struct ksmbd_conn *conn); ++struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn); ++void ksmbd_conn_put(struct ksmbd_conn *conn); + bool ksmbd_conn_lookup_dialect(struct ksmbd_conn *c); + int ksmbd_conn_write(struct ksmbd_work *work); + int ksmbd_conn_rdma_read(struct ksmbd_conn *conn, +--- a/fs/smb/server/oplock.c ++++ b/fs/smb/server/oplock.c +@@ -30,7 +30,6 @@ static DEFINE_RWLOCK(lease_list_lock); + static struct oplock_info *alloc_opinfo(struct ksmbd_work *work, + u64 id, __u16 Tid) + { +- struct ksmbd_conn *conn = work->conn; + struct ksmbd_session *sess = work->sess; + struct oplock_info *opinfo; + +@@ -39,7 +38,7 @@ static struct oplock_info *alloc_opinfo( + return NULL; + + opinfo->sess = sess; +- opinfo->conn = conn; ++ opinfo->conn = ksmbd_conn_get(work->conn); + opinfo->level = SMB2_OPLOCK_LEVEL_NONE; + opinfo->op_state = OPLOCK_STATE_NONE; + opinfo->pending_break = 0; +@@ -124,6 +123,7 @@ static void free_opinfo(struct oplock_in + { + if (opinfo->is_lease) + free_lease(opinfo); ++ ksmbd_conn_put(opinfo->conn); + kfree(opinfo); + } + +--- a/fs/smb/server/vfs_cache.c ++++ b/fs/smb/server/vfs_cache.c +@@ -328,6 +328,14 @@ static void __ksmbd_close_fd(struct ksmb + kfree(smb_lock); + } + ++ /* ++ * Drop fp's strong reference on conn (taken in ksmbd_open_fd()). ++ */ ++ if (fp->conn) { ++ ksmbd_conn_put(fp->conn); ++ fp->conn = NULL; ++ } ++ + if (ksmbd_stream_fd(fp)) + kfree(fp->stream.name); + kmem_cache_free(filp_cache, fp); +@@ -580,7 +588,13 @@ struct ksmbd_file *ksmbd_open_fd(struct + atomic_set(&fp->refcount, 1); + + fp->filp = filp; +- fp->conn = work->conn; ++ /* ++ * fp owns a strong reference on fp->conn for as long as fp->conn is ++ * non-NULL, so __ksmbd_close_fd() never dereferences a dangling ++ * pointer. Paired with ksmbd_conn_put() in __ksmbd_close_fd() ++ * (final close) and on the error paths below. ++ */ ++ fp->conn = ksmbd_conn_get(work->conn); + fp->tcon = work->tcon; + fp->volatile_id = KSMBD_NO_FID; + fp->persistent_id = KSMBD_NO_FID; +@@ -602,6 +616,8 @@ struct ksmbd_file *ksmbd_open_fd(struct + return fp; + + err_out: ++ /* fp->conn was set and refcounted before every branch here. */ ++ ksmbd_conn_put(fp->conn); + kmem_cache_free(filp_cache, fp); + return ERR_PTR(ret); + } diff --git a/queue-6.1/ksmbd-destroy-async_ida-in-ksmbd_conn_free.patch b/queue-6.1/ksmbd-destroy-async_ida-in-ksmbd_conn_free.patch new file mode 100644 index 0000000000..e6b87e5179 --- /dev/null +++ b/queue-6.1/ksmbd-destroy-async_ida-in-ksmbd_conn_free.patch @@ -0,0 +1,46 @@ +From stable+bounces-276759-greg=kroah.com@vger.kernel.org Thu Jul 16 18:52:13 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 11:37:02 -0400 +Subject: ksmbd: destroy async_ida in ksmbd_conn_free() +To: stable@vger.kernel.org +Cc: DaeMyung Kang , Namjae Jeon , Steve French , Sasha Levin +Message-ID: <20260716153704.583869-5-sashal@kernel.org> + +From: DaeMyung Kang + +[ Upstream commit b32c8db48212a34998c36d0bbc05b29d5c407ef5 ] + +When per-connection async_ida was converted from a dynamically +allocated ksmbd_ida to an embedded struct ida, ksmbd_ida_free() was +removed from the connection teardown path but no matching +ida_destroy() was added. The connection is therefore freed with the +IDA's backing xarray still intact. + +The kernel IDA API expects ida_init() and ida_destroy() to be paired +over an object's lifetime, so add the missing cleanup before the +connection is freed. + +No leak has been observed in testing; this is a pairing fix to match +the IDA lifetime rules, not a response to a reproduced regression. + +Fixes: d40012a83f87 ("cifsd: declare ida statically") +Signed-off-by: DaeMyung Kang +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/connection.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/fs/smb/server/connection.c ++++ b/fs/smb/server/connection.c +@@ -40,6 +40,7 @@ void ksmbd_conn_free(struct ksmbd_conn * + kvfree(conn->request_buf); + kfree(conn->preauth_info); + kfree(conn->mechToken); ++ ida_destroy(&conn->async_ida); + kfree(conn); + } + diff --git a/queue-6.1/ksmbd-fix-mechtoken-leak-when-spnego-decode-fails-after-token-alloc.patch b/queue-6.1/ksmbd-fix-mechtoken-leak-when-spnego-decode-fails-after-token-alloc.patch new file mode 100644 index 0000000000..3129ed6018 --- /dev/null +++ b/queue-6.1/ksmbd-fix-mechtoken-leak-when-spnego-decode-fails-after-token-alloc.patch @@ -0,0 +1,79 @@ +From stable+bounces-276758-greg=kroah.com@vger.kernel.org Thu Jul 16 17:37:21 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 11:37:01 -0400 +Subject: ksmbd: fix mechToken leak when SPNEGO decode fails after token alloc +To: stable@vger.kernel.org +Cc: Greg Kroah-Hartman , Namjae Jeon , Steve French , Sergey Senozhatsky , Tom Talpey , linux-cifs@vger.kernel.org, stable@kernel.org, Steve French , Sasha Levin +Message-ID: <20260716153704.583869-4-sashal@kernel.org> + +From: Greg Kroah-Hartman + +[ Upstream commit ad0057fb91218914d6c98268718ceb9d59b388e1 ] + +The kernel ASN.1 BER decoder calls action callbacks incrementally as it +walks the input. When ksmbd_decode_negTokenInit() reaches the mechToken +[2] OCTET STRING element, ksmbd_neg_token_alloc() allocates +conn->mechToken immediately via kmemdup_nul(). If a later element in +the same blob is malformed, then the decoder will return nonzero after +the allocation is already live. This could happen if mechListMIC [3] +overrunse the enclosing SEQUENCE. + +decode_negotiation_token() then sets conn->use_spnego = false because +both the negTokenInit and negTokenTarg grammars failed. The cleanup at +the bottom of smb2_sess_setup() is gated on use_spnego: + + if (conn->use_spnego && conn->mechToken) { + kfree(conn->mechToken); + conn->mechToken = NULL; + } + +so the kfree is skipped, causing the mechToken to never be freed. + +This codepath is reachable pre-authentication, so untrusted clients can +cause slow memory leaks on a server without even being properly +authenticated. + +Fix this up by not checking check for use_spnego, as it's not required, +so the memory will always be properly freed. At the same time, always +free the memory in ksmbd_conn_free() incase some other failure path +forgot to free it. + +Cc: Namjae Jeon +Cc: Steve French +Cc: Sergey Senozhatsky +Cc: Tom Talpey +Cc: linux-cifs@vger.kernel.org +Cc: +Assisted-by: gregkh_clanker_t1000 +Signed-off-by: Greg Kroah-Hartman +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/connection.c | 1 + + fs/smb/server/smb2pdu.c | 2 +- + 2 files changed, 2 insertions(+), 1 deletion(-) + +--- a/fs/smb/server/connection.c ++++ b/fs/smb/server/connection.c +@@ -39,6 +39,7 @@ void ksmbd_conn_free(struct ksmbd_conn * + xa_destroy(&conn->sessions); + kvfree(conn->request_buf); + kfree(conn->preauth_info); ++ kfree(conn->mechToken); + kfree(conn); + } + +--- a/fs/smb/server/smb2pdu.c ++++ b/fs/smb/server/smb2pdu.c +@@ -1909,7 +1909,7 @@ out_err: + else if (rc) + rsp->hdr.Status = STATUS_LOGON_FAILURE; + +- if (conn->use_spnego && conn->mechToken) { ++ if (conn->mechToken) { + kfree(conn->mechToken); + conn->mechToken = NULL; + } diff --git a/queue-6.1/ksmbd-fix-use-after-free-in-__ksmbd_close_fd-via-durable-scavenger.patch b/queue-6.1/ksmbd-fix-use-after-free-in-__ksmbd_close_fd-via-durable-scavenger.patch new file mode 100644 index 0000000000..d900af00ae --- /dev/null +++ b/queue-6.1/ksmbd-fix-use-after-free-in-__ksmbd_close_fd-via-durable-scavenger.patch @@ -0,0 +1,67 @@ +From stable+bounces-276757-greg=kroah.com@vger.kernel.org Thu Jul 16 17:37:19 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 11:37:00 -0400 +Subject: ksmbd: fix use-after-free in __ksmbd_close_fd() via durable scavenger +To: stable@vger.kernel.org +Cc: Namjae Jeon , munan Huang , ChenXiaoSong , Steve French , Sasha Levin +Message-ID: <20260716153704.583869-3-sashal@kernel.org> + +From: Namjae Jeon + +[ Upstream commit 235e32320a470fcd3998fb3774f2290a0eb302a1 ] + +When a durable file handle survives session disconnect (TCP close without +SMB2_LOGOFF), session_fd_check() sets fp->conn = NULL to preserve the +handle for later reconnection. However, it did not clean up the byte-range +locks on fp->lock_list. + +Later, when the durable scavenger thread times out and calls +__ksmbd_close_fd(NULL, fp), the lock cleanup loop did: + + spin_lock(&fp->conn->llist_lock); + +This caused a slab use-after-free because fp->conn was NULL and the +original connection object had already been freed by +ksmbd_tcp_disconnect(). + +The root cause is asymmetric cleanup: lock entries (smb_lock->clist) were +left dangling on the freed conn->lock_list while fp->conn was nulled out. + +To fix this issue properly, we need to handle the lifetime of +smb_lock->clist across three paths: + - Safely skip clist deletion when list is empty and fp->conn is NULL. + - Remove the lock from the old connection's lock_list in + session_fd_check() + - Re-add the lock to the new connection's lock_list in + ksmbd_reopen_durable_fd(). + +Fixes: c8efcc786146 ("ksmbd: add support for durable handles v1/v2") +Co-developed-by: munan Huang +Signed-off-by: munan Huang +Reviewed-by: ChenXiaoSong +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/vfs_cache.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +--- a/fs/smb/server/vfs_cache.c ++++ b/fs/smb/server/vfs_cache.c +@@ -317,9 +317,11 @@ static void __ksmbd_close_fd(struct ksmb + * there are not accesses to fp->lock_list. + */ + list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) { +- spin_lock(&fp->conn->llist_lock); +- list_del(&smb_lock->clist); +- spin_unlock(&fp->conn->llist_lock); ++ if (!list_empty(&smb_lock->clist) && fp->conn) { ++ spin_lock(&fp->conn->llist_lock); ++ list_del(&smb_lock->clist); ++ spin_unlock(&fp->conn->llist_lock); ++ } + + list_del(&smb_lock->flist); + locks_free_lock(smb_lock->fl); diff --git a/queue-6.1/ksmbd-track-the-connection-owning-a-byte-range-lock.patch b/queue-6.1/ksmbd-track-the-connection-owning-a-byte-range-lock.patch new file mode 100644 index 0000000000..76de637f9b --- /dev/null +++ b/queue-6.1/ksmbd-track-the-connection-owning-a-byte-range-lock.patch @@ -0,0 +1,112 @@ +From stable+bounces-276761-greg=kroah.com@vger.kernel.org Thu Jul 16 17:37:27 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 11:37:04 -0400 +Subject: ksmbd: track the connection owning a byte-range lock +To: stable@vger.kernel.org +Cc: Namjae Jeon , Musaab Khan , Steve French , Sasha Levin +Message-ID: <20260716153704.583869-7-sashal@kernel.org> + +From: Namjae Jeon + +[ 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 +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/smb2pdu.c | 10 ++++++++-- + fs/smb/server/vfs_cache.c | 12 ++++++++---- + fs/smb/server/vfs_cache.h | 1 + + 3 files changed, 17 insertions(+), 6 deletions(-) + +--- a/fs/smb/server/smb2pdu.c ++++ b/fs/smb/server/smb2pdu.c +@@ -7136,9 +7136,11 @@ int smb2_lock(struct ksmbd_work *work) + nolock = 0; + list_del(&cmp_lock->flist); + list_del(&cmp_lock->clist); ++ cmp_lock->conn = NULL; + spin_unlock(&conn->llist_lock); + up_read(&conn_list_lock); + ++ ksmbd_conn_put(conn); + locks_free_lock(cmp_lock->fl); + kfree(cmp_lock); + goto out_check_cl; +@@ -7273,6 +7275,7 @@ skip: + goto out2; + } else if (!rc) { + list_add(&smb_lock->llist, &rollback_list); ++ smb_lock->conn = ksmbd_conn_get(work->conn); + spin_lock(&work->conn->llist_lock); + list_add_tail(&smb_lock->clist, + &work->conn->lock_list); +@@ -7327,11 +7330,14 @@ out: + } + + list_del(&smb_lock->llist); +- spin_lock(&work->conn->llist_lock); ++ conn = smb_lock->conn; ++ spin_lock(&conn->llist_lock); + if (!list_empty(&smb_lock->flist)) + list_del(&smb_lock->flist); + list_del(&smb_lock->clist); +- spin_unlock(&work->conn->llist_lock); ++ smb_lock->conn = NULL; ++ spin_unlock(&conn->llist_lock); ++ ksmbd_conn_put(conn); + + locks_free_lock(smb_lock->fl); + if (rlock) +--- a/fs/smb/server/vfs_cache.c ++++ b/fs/smb/server/vfs_cache.c +@@ -317,10 +317,14 @@ static void __ksmbd_close_fd(struct ksmb + * there are not accesses to fp->lock_list. + */ + list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) { +- if (!list_empty(&smb_lock->clist) && fp->conn) { +- spin_lock(&fp->conn->llist_lock); +- list_del(&smb_lock->clist); +- spin_unlock(&fp->conn->llist_lock); ++ struct ksmbd_conn *conn = smb_lock->conn; ++ ++ if (conn) { ++ spin_lock(&conn->llist_lock); ++ list_del_init(&smb_lock->clist); ++ smb_lock->conn = NULL; ++ spin_unlock(&conn->llist_lock); ++ ksmbd_conn_put(conn); + } + + list_del(&smb_lock->flist); +--- a/fs/smb/server/vfs_cache.h ++++ b/fs/smb/server/vfs_cache.h +@@ -31,6 +31,7 @@ struct ksmbd_session; + + struct ksmbd_lock { + struct file_lock *fl; ++ struct ksmbd_conn *conn; + struct list_head clist; + struct list_head flist; + struct list_head llist; diff --git a/queue-6.1/ksmbd-use-opener-credentials-for-fsctl-mutations.patch b/queue-6.1/ksmbd-use-opener-credentials-for-fsctl-mutations.patch new file mode 100644 index 0000000000..224183ee12 --- /dev/null +++ b/queue-6.1/ksmbd-use-opener-credentials-for-fsctl-mutations.patch @@ -0,0 +1,86 @@ +From stable+bounces-276102-greg=kroah.com@vger.kernel.org Thu Jul 16 16:15:46 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 10:07:47 -0400 +Subject: ksmbd: use opener credentials for FSCTL mutations +To: stable@vger.kernel.org +Cc: Namjae Jeon , Musaab Khan , Steve French , Sasha Levin +Message-ID: <20260716140747.405713-1-sashal@kernel.org> + +From: Namjae Jeon + +[ 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 +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/smb2pdu.c | 3 +++ + fs/smb/server/vfs.c | 20 +++++++++++++------- + 2 files changed, 16 insertions(+), 7 deletions(-) + +--- a/fs/smb/server/smb2pdu.c ++++ b/fs/smb/server/smb2pdu.c +@@ -7739,6 +7739,7 @@ static inline int fsctl_set_sparse(struc + if (fp->f_ci->m_fattr != old_fattr && + test_share_config_flag(work->tcon->share_conf, + KSMBD_SHARE_FLAG_STORE_DOS_ATTRS)) { ++ const struct cred *saved_cred; + struct xattr_dos_attrib da; + + ret = ksmbd_vfs_get_dos_attrib_xattr(user_ns, +@@ -7747,9 +7748,11 @@ static inline int fsctl_set_sparse(struc + goto out; + + da.attr = le32_to_cpu(fp->f_ci->m_fattr); ++ saved_cred = override_creds(fp->filp->f_cred); + ret = ksmbd_vfs_set_dos_attrib_xattr(user_ns, + &fp->filp->f_path, + &da, true); ++ revert_creds(saved_cred); + if (ret) + fp->f_ci->m_fattr = old_fattr; + } +--- a/fs/smb/server/vfs.c ++++ b/fs/smb/server/vfs.c +@@ -991,15 +991,21 @@ void ksmbd_vfs_set_fadvise(struct file * + int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp, + loff_t off, loff_t len) + { ++ const struct cred *saved_cred; ++ int err; ++ + smb_break_all_levII_oplock(work, fp, 1); ++ saved_cred = override_creds(fp->filp->f_cred); + if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE) +- return vfs_fallocate(fp->filp, +- FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, +- off, len); +- +- return vfs_fallocate(fp->filp, +- FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE, +- off, len); ++ err = vfs_fallocate(fp->filp, ++ FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, ++ off, len); ++ else ++ err = vfs_fallocate(fp->filp, ++ FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE, ++ off, len); ++ revert_creds(saved_cred); ++ return err; + } + + int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length, diff --git a/queue-6.1/mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch b/queue-6.1/mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch new file mode 100644 index 0000000000..26e2e5982f --- /dev/null +++ b/queue-6.1/mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch @@ -0,0 +1,78 @@ +From stable+bounces-274985-greg=kroah.com@vger.kernel.org Wed Jul 15 18:34:54 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 12:30:06 -0400 +Subject: mm/shrinker: do not hold RCU lock in shrinker_debugfs_count_show() +To: stable@vger.kernel.org +Cc: Shakeel Butt , Zenghui Yu , Nhat Pham , SeongJae Park , Qi Zheng , Muchun Song , Roman Gushchin , Dave Chinner , Andrew Morton , Sasha Levin +Message-ID: <20260715163006.950613-2-sashal@kernel.org> + +From: Shakeel Butt + +[ Upstream commit b902890c62d200b3509cb5e09cf1e0a66553c128 ] + +Reading the debugfs "count" file of a memcg-aware shrinker can sleep +inside an RCU read-side critical section: + + BUG: sleeping function called from invalid context at kernel/cgroup/rstat.c:421 + RCU nest depth: 1, expected: 0 + css_rstat_flush + mem_cgroup_flush_stats + zswap_shrinker_count + shrinker_debugfs_count_show + +shrinker_debugfs_count_show() invokes the ->count_objects() callback under +rcu_read_lock(). The zswap callback flushes memcg stats via +css_rstat_flush(), which may sleep, so it must not run under RCU. + +The RCU lock is not needed here. mem_cgroup_iter() takes RCU internally +and returns a memcg holding a css reference (dropped on the next iteration +or by mem_cgroup_iter_break()), so the memcg stays alive without it. The +shrinker is kept alive by the open debugfs file: shrinker_free() removes +the debugfs entries via debugfs_remove_recursive(), which waits for +in-flight readers to drain, before call_rcu(..., shrinker_free_rcu_cb). +The sibling "scan" handler already invokes the sleeping ->scan_objects() +callback with no RCU section. + +Drop the rcu_read_lock()/rcu_read_unlock(). + +Link: https://lore.kernel.org/20260610232048.62930-1-shakeel.butt@linux.dev +Fixes: 5035ebc644ae ("mm: shrinkers: introduce debugfs interface for memory shrinkers") +Signed-off-by: Shakeel Butt +Reported-by: Zenghui Yu +Closes: https://lore.kernel.org/all/c052a064-cddb-494f-a0d8-f8a10b4b1c4d@linux.dev/ +Suggested-by: Nhat Pham +Reviewed-by: SeongJae Park +Reviewed-by: Qi Zheng +Tested-by: Zenghui Yu (Huawei) +Reviewed-by: Nhat Pham +Acked-by: Muchun Song +Reviewed-by: Roman Gushchin +Cc: Dave Chinner +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + mm/shrinker_debug.c | 4 ---- + 1 file changed, 4 deletions(-) + +--- a/mm/shrinker_debug.c ++++ b/mm/shrinker_debug.c +@@ -55,8 +55,6 @@ static int shrinker_debugfs_count_show(s + if (!count_per_node) + return -ENOMEM; + +- rcu_read_lock(); +- + memcg_aware = shrinker->flags & SHRINKER_MEMCG_AWARE; + + memcg = mem_cgroup_iter(NULL, NULL, NULL); +@@ -86,8 +84,6 @@ static int shrinker_debugfs_count_show(s + } + } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL); + +- rcu_read_unlock(); +- + kfree(count_per_node); + return ret; + } diff --git a/queue-6.1/mm-shrinker-fix-null-pointer-dereference-in-debugfs.patch b/queue-6.1/mm-shrinker-fix-null-pointer-dereference-in-debugfs.patch new file mode 100644 index 0000000000..1c9c291fe1 --- /dev/null +++ b/queue-6.1/mm-shrinker-fix-null-pointer-dereference-in-debugfs.patch @@ -0,0 +1,77 @@ +From stable+bounces-275028-greg=kroah.com@vger.kernel.org Wed Jul 15 22:16:19 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 16:16:03 -0400 +Subject: mm: shrinker: fix NULL pointer dereference in debugfs +To: stable@vger.kernel.org +Cc: Qi Zheng , Muchun Song , Dave Chinner , Roman Gushchin , Andrew Morton , Sasha Levin +Message-ID: <20260715201603.63084-2-sashal@kernel.org> + +From: Qi Zheng + +[ Upstream commit e30453c61e185e914fde83c650e268067b140218 ] + +shrinker_debugfs_add() creates both "count" and "scan" debugfs files +unconditionally. + +That assumes every shrinker implements both count_objects() and +scan_objects(), which is not guaranteed. For example, the xen-backend +shrinker sets count_objects() but leaves scan_objects() NULL, so writing +to its scan file calls through a NULL function pointer and panics the +kernel: + +BUG: kernel NULL pointer dereference, address: 0000000000000000 +RIP: 0010:0x0 +Code: Unable to access opcode bytes at 0xffffffffffffffd6. +Call Trace: + + shrinker_debugfs_scan_write+0x12e/0x270 + full_proxy_write+0x5f/0x90 + vfs_write+0xde/0x420 + ? filp_flush+0x75/0x90 + ? filp_close+0x1d/0x30 + ? do_dup2+0xb8/0x120 + ksys_write+0x68/0xf0 + ? filp_flush+0x75/0x90 + do_syscall_64+0xb3/0x5b0 + entry_SYSCALL_64_after_hwframe+0x76/0x7e + +The count path has the same issue in principle if a shrinker omits +count_objects(). + +To fix it, only create "count" and "scan" debugfs files when the +corresponding callbacks are present. + +Link: https://lore.kernel.org/20260617090052.27325-1-qi.zheng@linux.dev +Fixes: bbf535fd6f06 ("mm: shrinkers: add scan interface for shrinker debugfs") +Signed-off-by: Qi Zheng +Reviewed-by: Muchun Song +Cc: Dave Chinner +Cc: Qi Zheng +Cc: Roman Gushchin +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + mm/shrinker_debug.c | 10 ++++++---- + 1 file changed, 6 insertions(+), 4 deletions(-) + +--- a/mm/shrinker_debug.c ++++ b/mm/shrinker_debug.c +@@ -180,10 +180,12 @@ int shrinker_debugfs_add(struct shrinker + } + shrinker->debugfs_entry = entry; + +- debugfs_create_file("count", 0440, entry, shrinker, +- &shrinker_debugfs_count_fops); +- debugfs_create_file("scan", 0220, entry, shrinker, +- &shrinker_debugfs_scan_fops); ++ if (shrinker->count_objects) ++ debugfs_create_file("count", 0440, entry, shrinker, ++ &shrinker_debugfs_count_fops); ++ if (shrinker->scan_objects) ++ debugfs_create_file("scan", 0220, entry, shrinker, ++ &shrinker_debugfs_scan_fops); + return 0; + } + diff --git a/queue-6.1/mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch b/queue-6.1/mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch new file mode 100644 index 0000000000..852894d160 --- /dev/null +++ b/queue-6.1/mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch @@ -0,0 +1,159 @@ +From sashal@kernel.org Wed Jul 15 18:30:16 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 12:30:05 -0400 +Subject: mm: shrinker: remove redundant shrinker_rwsem in debugfs operations +To: stable@vger.kernel.org +Cc: "Qi Zheng" , "Muchun Song" , "Christian Brauner" , "Christian König" , "Chuck Lever" , "Daniel Vetter" , "Daniel Vetter" , "Darrick J. Wong" , "Dave Chinner" , "Greg Kroah-Hartman" , "Joel Fernandes" , "Kirill Tkhai" , "Paul E. McKenney" , "Roman Gushchin" , "Sergey Senozhatsky" , "Steven Price" , "Theodore Ts'o" , "Vlastimil Babka" , "Abhinav Kumar" , "Alasdair Kergon" , "Alexander Viro" , "Alyssa Rosenzweig" , "Andreas Dilger" , "Andreas Gruenbacher" , "Anna Schumaker" , "Arnd Bergmann" , "Bob Peterson" , "Borislav Petkov" , "Carlos Llamas" , "Chandan Babu R" , "Chao Yu" , "Chris Mason" , "Coly Li" , "Dai Ngo" , "Dave Hansen" , "David Airlie" , "David Hildenbrand" , "David Sterba" , "Dmitry Baryshkov" , "Gao Xiang" , "Huang Rui" , "Ingo Molnar" , "Jaegeuk Kim" , "Jani Nikula" , "Jan Kara" , "Jason Wang" , "Jeff Layton" , "Jeffle Xu" , "Joonas Lahtinen" , "Josef Bacik" , "Juergen Gross" , "Kent Overstreet" , "Marijn Suijten" , "Michael S. Tsirkin" , "Mike Snitzer" , "Minchan Kim" , "Muchun Song" , "Nadav Amit" , "Neil Brown" , "Oleksandr Tyshchenko" , "Olga Kornievskaia" , "Richard Weinberger" , "Rob Clark" , "Rob Herring" , "Rodrigo Vivi" , "Sean Paul" , "Song Liu" , "Stefano Stabellini" , "Thomas Gleixner" , "Tomeu Vizoso" , "Tom Talpey" , "Trond Myklebust" , "Tvrtko Ursulin" , "Xuan Zhuo" , "Yue Hu" , "Andrew Morton" , "Sasha Levin" +Message-ID: <20260715163006.950613-1-sashal@kernel.org> + +From: Qi Zheng + +[ Upstream commit 1dd49e58f966b1eecd935dc28458a8369ae94ad1 ] + +debugfs_remove_recursive() will wait for debugfs_file_put() to return, so +the shrinker will not be freed when doing debugfs operations (such as +shrinker_debugfs_count_show() and shrinker_debugfs_scan_write()), so there +is no need to hold shrinker_rwsem during debugfs operations. + +Link: https://lkml.kernel.org/r/20230911092517.64141-4-zhengqi.arch@bytedance.com +Signed-off-by: Qi Zheng +Reviewed-by: Muchun Song +Cc: Christian Brauner +Cc: Christian König +Cc: Chuck Lever +Cc: Daniel Vetter +Cc: Daniel Vetter +Cc: Darrick J. Wong +Cc: Dave Chinner +Cc: Greg Kroah-Hartman +Cc: Joel Fernandes +Cc: Kirill Tkhai +Cc: Paul E. McKenney +Cc: Roman Gushchin +Cc: Sergey Senozhatsky +Cc: Steven Price +Cc: Theodore Ts'o +Cc: Vlastimil Babka +Cc: Abhinav Kumar +Cc: Alasdair Kergon +Cc: Alexander Viro +Cc: Alyssa Rosenzweig +Cc: Andreas Dilger +Cc: Andreas Gruenbacher +Cc: Anna Schumaker +Cc: Arnd Bergmann +Cc: Bob Peterson +Cc: Borislav Petkov +Cc: Carlos Llamas +Cc: Chandan Babu R +Cc: Chao Yu +Cc: Chris Mason +Cc: Coly Li +Cc: Dai Ngo +Cc: Dave Hansen +Cc: David Airlie +Cc: David Hildenbrand +Cc: David Sterba +Cc: Dmitry Baryshkov +Cc: Gao Xiang +Cc: Huang Rui +Cc: Ingo Molnar +Cc: Jaegeuk Kim +Cc: Jani Nikula +Cc: Jan Kara +Cc: Jason Wang +Cc: Jeff Layton +Cc: Jeffle Xu +Cc: Joonas Lahtinen +Cc: Josef Bacik +Cc: Juergen Gross +Cc: Kent Overstreet +Cc: Marijn Suijten +Cc: "Michael S. Tsirkin" +Cc: Mike Snitzer +Cc: Minchan Kim +Cc: Muchun Song +Cc: Nadav Amit +Cc: Neil Brown +Cc: Oleksandr Tyshchenko +Cc: Olga Kornievskaia +Cc: Richard Weinberger +Cc: Rob Clark +Cc: Rob Herring +Cc: Rodrigo Vivi +Cc: Sean Paul +Cc: Song Liu +Cc: Stefano Stabellini +Cc: Thomas Gleixner +Cc: Tomeu Vizoso +Cc: Tom Talpey +Cc: Trond Myklebust +Cc: Tvrtko Ursulin +Cc: Xuan Zhuo +Cc: Yue Hu +Signed-off-by: Andrew Morton +Stable-dep-of: b902890c62d2 ("mm/shrinker: do not hold RCU lock in shrinker_debugfs_count_show()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + mm/shrinker_debug.c | 16 +--------------- + 1 file changed, 1 insertion(+), 15 deletions(-) + +--- a/mm/shrinker_debug.c ++++ b/mm/shrinker_debug.c +@@ -49,17 +49,12 @@ static int shrinker_debugfs_count_show(s + struct mem_cgroup *memcg; + unsigned long total; + bool memcg_aware; +- int ret, nid; ++ int ret = 0, nid; + + count_per_node = kcalloc(nr_node_ids, sizeof(unsigned long), GFP_KERNEL); + if (!count_per_node) + return -ENOMEM; + +- ret = down_read_killable(&shrinker_rwsem); +- if (ret) { +- kfree(count_per_node); +- return ret; +- } + rcu_read_lock(); + + memcg_aware = shrinker->flags & SHRINKER_MEMCG_AWARE; +@@ -92,7 +87,6 @@ static int shrinker_debugfs_count_show(s + } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL); + + rcu_read_unlock(); +- up_read(&shrinker_rwsem); + + kfree(count_per_node); + return ret; +@@ -117,7 +111,6 @@ static ssize_t shrinker_debugfs_scan_wri + struct mem_cgroup *memcg = NULL; + int nid; + char kbuf[72]; +- ssize_t ret; + + read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1); + if (copy_from_user(kbuf, buf, read_len)) +@@ -146,12 +139,6 @@ static ssize_t shrinker_debugfs_scan_wri + return -EINVAL; + } + +- ret = down_read_killable(&shrinker_rwsem); +- if (ret) { +- mem_cgroup_put(memcg); +- return ret; +- } +- + sc.nid = nid; + sc.memcg = memcg; + sc.nr_to_scan = nr_to_scan; +@@ -159,7 +146,6 @@ static ssize_t shrinker_debugfs_scan_wri + + shrinker->scan_objects(shrinker, &sc); + +- up_read(&shrinker_rwsem); + mem_cgroup_put(memcg); + + return size; diff --git a/queue-6.1/mm-shrinkers-fix-debugfs-file-permissions.patch b/queue-6.1/mm-shrinkers-fix-debugfs-file-permissions.patch new file mode 100644 index 0000000000..43258a3700 --- /dev/null +++ b/queue-6.1/mm-shrinkers-fix-debugfs-file-permissions.patch @@ -0,0 +1,41 @@ +From stable+bounces-275027-greg=kroah.com@vger.kernel.org Wed Jul 15 22:16:20 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 16:16:02 -0400 +Subject: mm: shrinkers: fix debugfs file permissions +To: stable@vger.kernel.org +Cc: John Keeping , Andrew Morton , Sasha Levin +Message-ID: <20260715201603.63084-1-sashal@kernel.org> + +From: John Keeping + +[ Upstream commit 2124f79de6a909630d1a62b01ecc32db9f967181 ] + +The permissions for the files here are swapped as "count" is read-only and +"scan" is write-only. While this doesn't really matter as these +permissions don't stop the files being opened for reading/writing as +appropriate, they are shown by "ls -l" and are confusing. + +Link: https://lkml.kernel.org/r/20230418101906.3131303-1-john@metanate.com +Signed-off-by: John Keeping +Signed-off-by: Andrew Morton +Stable-dep-of: e30453c61e18 ("mm: shrinker: fix NULL pointer dereference in debugfs") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + mm/shrinker_debug.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/mm/shrinker_debug.c ++++ b/mm/shrinker_debug.c +@@ -180,9 +180,9 @@ int shrinker_debugfs_add(struct shrinker + } + shrinker->debugfs_entry = entry; + +- debugfs_create_file("count", 0220, entry, shrinker, ++ debugfs_create_file("count", 0440, entry, shrinker, + &shrinker_debugfs_count_fops); +- debugfs_create_file("scan", 0440, entry, shrinker, ++ debugfs_create_file("scan", 0220, entry, shrinker, + &shrinker_debugfs_scan_fops); + return 0; + } diff --git a/queue-6.1/netfilter-ebtables-use-vmalloc_array-to-improve-code.patch b/queue-6.1/netfilter-ebtables-use-vmalloc_array-to-improve-code.patch new file mode 100644 index 0000000000..1cd2f8267f --- /dev/null +++ b/queue-6.1/netfilter-ebtables-use-vmalloc_array-to-improve-code.patch @@ -0,0 +1,77 @@ +From stable+bounces-275043-greg=kroah.com@vger.kernel.org Wed Jul 15 23:13:56 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:13:48 -0400 +Subject: netfilter: ebtables: Use vmalloc_array() to improve code +To: stable@vger.kernel.org +Cc: Qianfeng Rong , Florian Westphal , Sasha Levin +Message-ID: <20260715211349.253327-1-sashal@kernel.org> + +From: Qianfeng Rong + +[ 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 +Signed-off-by: Florian Westphal +Stable-dep-of: cbfe53599eeb ("netfilter: ebtables: zero chainstack array") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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; + diff --git a/queue-6.1/netfilter-ebtables-zero-chainstack-array.patch b/queue-6.1/netfilter-ebtables-zero-chainstack-array.patch new file mode 100644 index 0000000000..2c96002ee5 --- /dev/null +++ b/queue-6.1/netfilter-ebtables-zero-chainstack-array.patch @@ -0,0 +1,46 @@ +From stable+bounces-275044-greg=kroah.com@vger.kernel.org Wed Jul 15 23:13:59 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:13:49 -0400 +Subject: netfilter: ebtables: zero chainstack array +To: stable@vger.kernel.org +Cc: Florian Westphal , Sasha Levin +Message-ID: <20260715211349.253327-2-sashal@kernel.org> + +From: Florian Westphal + +[ 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 +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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) { diff --git a/queue-6.1/nvmet-remove-superfluous-initialization.patch b/queue-6.1/nvmet-remove-superfluous-initialization.patch new file mode 100644 index 0000000000..ebebdddbb1 --- /dev/null +++ b/queue-6.1/nvmet-remove-superfluous-initialization.patch @@ -0,0 +1,45 @@ +From stable+bounces-277835-greg=kroah.com@vger.kernel.org Mon Jul 20 16:30:59 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 10:18:04 -0400 +Subject: nvmet: remove superfluous initialization +To: stable@vger.kernel.org +Cc: Chaitanya Kulkarni , Keith Busch , Sasha Levin +Message-ID: <20260720141806.1621638-1-sashal@kernel.org> + +From: Chaitanya Kulkarni + +[ Upstream commit 8d30528a170905ede9ab6ab81f229e441808590b ] + +Remove superfluous initialization of status variable in +nvmet_execute_admin_connect() and nvmet_execute_io_connect(), since it +will get overwritten by nvmet_copy_from_sgl(). + +Signed-off-by: Chaitanya Kulkarni +Signed-off-by: Keith Busch +Stable-dep-of: 3a413ece2504 ("nvmet-auth: validate reply message payload bounds against transfer length") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/nvme/target/fabrics-cmd.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/nvme/target/fabrics-cmd.c ++++ b/drivers/nvme/target/fabrics-cmd.c +@@ -209,7 +209,7 @@ static void nvmet_execute_admin_connect( + struct nvmf_connect_command *c = &req->cmd->connect; + struct nvmf_connect_data *d; + struct nvmet_ctrl *ctrl = NULL; +- u16 status = 0; ++ u16 status; + int ret; + + if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data))) +@@ -287,7 +287,7 @@ static void nvmet_execute_io_connect(str + struct nvmf_connect_data *d; + struct nvmet_ctrl *ctrl; + u16 qid = le16_to_cpu(c->qid); +- u16 status = 0; ++ u16 status; + + if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data))) + return; diff --git a/queue-6.1/pci-add-kerneldoc-for-pci_resize_resource.patch b/queue-6.1/pci-add-kerneldoc-for-pci_resize_resource.patch new file mode 100644 index 0000000000..844b03171d --- /dev/null +++ b/queue-6.1/pci-add-kerneldoc-for-pci_resize_resource.patch @@ -0,0 +1,55 @@ +From stable+bounces-274690-greg=kroah.com@vger.kernel.org Wed Jul 15 04:47:08 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 22:45:57 -0400 +Subject: PCI: Add kerneldoc for pci_resize_resource() +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Bjorn Helgaas" , "Alex Bennée" , "Sasha Levin" +Message-ID: <20260715024559.106836-4-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit d787018e2dfdc4c1331538e7a8717690d1b7c9b3 ] + +As pci_resize_resource() is meant to be used also outside of PCI core, +document the interface with kerneldoc. + +Signed-off-by: Ilpo Järvinen +Signed-off-by: Bjorn Helgaas +Tested-by: Alex Bennée # AVA, AMD GPU +Link: https://patch.msgid.link/20251113162628.5946-8-ilpo.jarvinen@linux.intel.com +Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/setup-res.c | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +--- a/drivers/pci/setup-res.c ++++ b/drivers/pci/setup-res.c +@@ -427,6 +427,26 @@ void pci_release_resource(struct pci_dev + } + EXPORT_SYMBOL(pci_release_resource); + ++/** ++ * pci_resize_resource - reconfigure a Resizable BAR and resources ++ * @dev: the PCI device ++ * @resno: index of the BAR to be resized ++ * @size: new size as defined in the spec (0=1MB, 31=128TB) ++ * @exclude_bars: a mask of BARs that should not be released ++ * ++ * Reconfigure @resno to @size and re-run resource assignment algorithm ++ * with the new size. ++ * ++ * Prior to resize, release @dev resources that share a bridge window with ++ * @resno. This unpins the bridge window resource to allow changing it. ++ * ++ * The caller may prevent releasing a particular BAR by providing ++ * @exclude_bars mask, but this may result in the resize operation failing ++ * due to insufficient space. ++ * ++ * Return: 0 on success, or negative on error. In case of an error, the ++ * resources are restored to their original places. ++ */ + int pci_resize_resource(struct pci_dev *dev, int resno, int size, + int exclude_bars) + { diff --git a/queue-6.1/pci-altera-fix-resource-leaks-on-probe-failure.patch b/queue-6.1/pci-altera-fix-resource-leaks-on-probe-failure.patch new file mode 100644 index 0000000000..29f1661451 --- /dev/null +++ b/queue-6.1/pci-altera-fix-resource-leaks-on-probe-failure.patch @@ -0,0 +1,99 @@ +From stable+bounces-274436-greg=kroah.com@vger.kernel.org Tue Jul 14 18:38:20 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 12:38:13 -0400 +Subject: PCI: altera: Fix resource leaks on probe failure +To: stable@vger.kernel.org +Cc: Mahesh Vaidya , Manivannan Sadhasivam , "Subhransu S. Prusty" , Sasha Levin +Message-ID: <20260714163813.2850299-1-sashal@kernel.org> + +From: Mahesh Vaidya + +[ 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 +[mani: commit log] +Signed-off-by: Manivannan Sadhasivam +Reviewed-by: Subhransu S. Prusty +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/20260430204330.3121003-3-mahesh.vaidya@altera.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/pcie-altera.c | 28 ++++++++++++++++++++++++++-- + 1 file changed, 26 insertions(+), 2 deletions(-) + +--- a/drivers/pci/controller/pcie-altera.c ++++ b/drivers/pci/controller/pcie-altera.c +@@ -681,8 +681,18 @@ static int altera_pcie_init_irq_domain(s + return 0; + } + ++static void altera_pcie_disable_irq(struct altera_pcie *pcie) ++{ ++ if (pcie->pcie_data->version == ALTERA_PCIE_V1 || ++ pcie->pcie_data->version == ALTERA_PCIE_V2) { ++ /* Disable all P2A interrupts */ ++ cra_writel(pcie, 0, P2A_INT_ENABLE); ++ } ++} ++ + static void altera_pcie_irq_teardown(struct altera_pcie *pcie) + { ++ altera_pcie_disable_irq(pcie); + irq_set_chained_handler_and_data(pcie->irq, NULL, NULL); + irq_domain_remove(pcie->irq_domain); + } +@@ -707,7 +717,6 @@ static int altera_pcie_parse_dt(struct a + if (pcie->irq < 0) + return pcie->irq; + +- irq_set_chained_handler_and_data(pcie->irq, altera_pcie_isr, pcie); + return 0; + } + +@@ -792,6 +801,12 @@ static int altera_pcie_probe(struct plat + return ret; + } + ++ /* ++ * The chained handler uses pcie->irq_domain, so set it only after the ++ * INTx domain has been created. ++ */ ++ irq_set_chained_handler_and_data(pcie->irq, altera_pcie_isr, pcie); ++ + /* clear all interrupts */ + cra_writel(pcie, P2A_INT_STS_ALL, P2A_INT_STATUS); + /* enable all interrupts */ +@@ -802,7 +817,16 @@ static int altera_pcie_probe(struct plat + bridge->busnr = pcie->root_bus_nr; + bridge->ops = &altera_pcie_ops; + +- return pci_host_probe(bridge); ++ ret = pci_host_probe(bridge); ++ if (ret) ++ goto err_teardown_irq; ++ ++ return 0; ++ ++err_teardown_irq: ++ altera_pcie_irq_teardown(pcie); ++ ++ return ret; + } + + static int altera_pcie_remove(struct platform_device *pdev) diff --git a/queue-6.1/pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch b/queue-6.1/pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch new file mode 100644 index 0000000000..1311beb13e --- /dev/null +++ b/queue-6.1/pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch @@ -0,0 +1,144 @@ +From stable+bounces-274608-greg=kroah.com@vger.kernel.org Wed Jul 15 01:34:53 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 19:34:14 -0400 +Subject: PCI: controller: Use dev_fwnode() instead of of_fwnode_handle() +To: stable@vger.kernel.org +Cc: "Jiri Slaby (SUSE)" , Arnd Bergmann , Bjorn Helgaas , Sasha Levin +Message-ID: <20260714233418.3592225-1-sashal@kernel.org> + +From: "Jiri Slaby (SUSE)" + +[ 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) +Signed-off-by: Arnd Bergmann # altera +[bhelgaas: squash together, rebase to precede msi-parent] +Signed-off-by: Bjorn Helgaas +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 +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/dwc/pcie-designware-host.c | 2 +- + drivers/pci/controller/mobiveil/pcie-mobiveil-host.c | 8 +++----- + drivers/pci/controller/pcie-altera-msi.c | 2 +- + drivers/pci/controller/pcie-altera.c | 3 +-- + drivers/pci/controller/pcie-mediatek-gen3.c | 4 ++-- + drivers/pci/controller/pcie-mediatek.c | 2 +- + drivers/pci/controller/pcie-xilinx-nwl.c | 2 +- + 7 files changed, 10 insertions(+), 13 deletions(-) + +--- a/drivers/pci/controller/dwc/pcie-designware-host.c ++++ b/drivers/pci/controller/dwc/pcie-designware-host.c +@@ -232,7 +232,7 @@ static const struct irq_domain_ops dw_pc + int dw_pcie_allocate_domains(struct dw_pcie_rp *pp) + { + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); +- struct fwnode_handle *fwnode = of_node_to_fwnode(pci->dev->of_node); ++ struct fwnode_handle *fwnode = dev_fwnode(pci->dev); + + pp->irq_domain = irq_domain_create_linear(fwnode, pp->num_vectors, + &dw_pcie_msi_domain_ops, pp); +--- a/drivers/pci/controller/mobiveil/pcie-mobiveil-host.c ++++ b/drivers/pci/controller/mobiveil/pcie-mobiveil-host.c +@@ -445,7 +445,7 @@ static const struct irq_domain_ops msi_d + static int mobiveil_allocate_msi_domains(struct mobiveil_pcie *pcie) + { + struct device *dev = &pcie->pdev->dev; +- struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); ++ struct fwnode_handle *fwnode = dev_fwnode(dev); + struct mobiveil_msi *msi = &pcie->rp.msi; + + mutex_init(&msi->lock); +@@ -471,13 +471,11 @@ static int mobiveil_allocate_msi_domains + static int mobiveil_pcie_init_irq_domain(struct mobiveil_pcie *pcie) + { + struct device *dev = &pcie->pdev->dev; +- struct device_node *node = dev->of_node; + struct mobiveil_root_port *rp = &pcie->rp; + + /* setup INTx */ +- rp->intx_domain = irq_domain_add_linear(node, PCI_NUM_INTX, +- &intx_domain_ops, pcie); +- ++ rp->intx_domain = irq_domain_create_linear(dev_fwnode(dev), PCI_NUM_INTX, &intx_domain_ops, ++ pcie); + if (!rp->intx_domain) { + dev_err(dev, "Failed to get a INTx IRQ domain\n"); + return -ENOMEM; +--- a/drivers/pci/controller/pcie-altera-msi.c ++++ b/drivers/pci/controller/pcie-altera-msi.c +@@ -171,7 +171,7 @@ static const struct irq_domain_ops msi_d + + static int altera_allocate_domains(struct altera_msi *msi) + { +- struct fwnode_handle *fwnode = of_node_to_fwnode(msi->pdev->dev.of_node); ++ struct fwnode_handle *fwnode = dev_fwnode(&msi->pdev->dev); + + msi->inner_domain = irq_domain_add_linear(NULL, msi->num_of_vectors, + &msi_domain_ops, msi); +--- a/drivers/pci/controller/pcie-altera.c ++++ b/drivers/pci/controller/pcie-altera.c +@@ -668,10 +668,9 @@ static void altera_pcie_isr(struct irq_d + static int altera_pcie_init_irq_domain(struct altera_pcie *pcie) + { + struct device *dev = &pcie->pdev->dev; +- struct device_node *node = dev->of_node; + + /* Setup INTx */ +- pcie->irq_domain = irq_domain_add_linear(node, PCI_NUM_INTX, ++ pcie->irq_domain = irq_domain_create_linear(dev_fwnode(dev), PCI_NUM_INTX, + &intx_domain_ops, pcie); + if (!pcie->irq_domain) { + dev_err(dev, "Failed to get a INTx IRQ domain\n"); +--- a/drivers/pci/controller/pcie-mediatek-gen3.c ++++ b/drivers/pci/controller/pcie-mediatek-gen3.c +@@ -661,8 +661,8 @@ static int mtk_pcie_init_irq_domains(str + /* Setup MSI */ + mutex_init(&pcie->lock); + +- pcie->msi_bottom_domain = irq_domain_add_linear(node, PCIE_MSI_IRQS_NUM, +- &mtk_msi_bottom_domain_ops, pcie); ++ pcie->msi_bottom_domain = irq_domain_create_linear(dev_fwnode(dev), PCIE_MSI_IRQS_NUM, ++ &mtk_msi_bottom_domain_ops, pcie); + if (!pcie->msi_bottom_domain) { + dev_err(dev, "failed to create MSI bottom domain\n"); + ret = -ENODEV; +--- a/drivers/pci/controller/pcie-mediatek.c ++++ b/drivers/pci/controller/pcie-mediatek.c +@@ -496,7 +496,7 @@ static struct msi_domain_info mtk_msi_do + + static int mtk_pcie_allocate_msi_domains(struct mtk_pcie_port *port) + { +- struct fwnode_handle *fwnode = of_node_to_fwnode(port->pcie->dev->of_node); ++ struct fwnode_handle *fwnode = dev_fwnode(port->pcie->dev); + + mutex_init(&port->lock); + +--- a/drivers/pci/controller/pcie-xilinx-nwl.c ++++ b/drivers/pci/controller/pcie-xilinx-nwl.c +@@ -503,7 +503,7 @@ static int nwl_pcie_init_msi_irq_domain( + { + #ifdef CONFIG_PCI_MSI + struct device *dev = pcie->dev; +- struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); ++ struct fwnode_handle *fwnode = dev_fwnode(dev); + struct nwl_msi *msi = &pcie->msi; + + msi->dev_domain = irq_domain_add_linear(NULL, INT_PCI_MSI_NR, diff --git a/queue-6.1/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch b/queue-6.1/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch new file mode 100644 index 0000000000..e932b8f6c2 --- /dev/null +++ b/queue-6.1/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch @@ -0,0 +1,384 @@ +From stable+bounces-274689-greg=kroah.com@vger.kernel.org Wed Jul 15 04:46:10 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 22:45:56 -0400 +Subject: PCI: Fix restoring BARs on BAR resize rollback path +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Simon Richter" , "Alex Bennée" , "Bjorn Helgaas" , "Christian König" , "Sasha Levin" +Message-ID: <20260715024559.106836-3-sashal@kernel.org> + +From: Ilpo Järvinen + +[ 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 +Link: https://lore.kernel.org/linux-pci/f9a8c975-f5d3-4dd2-988e-4371a1433a60@hogyros.de/ +Reported-by: Alex Bennée +Link: https://lore.kernel.org/linux-pci/874irqop6b.fsf@draig.linaro.org/ +Signed-off-by: Ilpo Järvinen +[bhelgaas: squash amdgpu BAR selection from +https: //lore.kernel.org/r/20251114103053.13778-1-ilpo.jarvinen@linux.intel.com] +Signed-off-by: Bjorn Helgaas +Tested-by: Alex Bennée # AVA, AMD GPU +Reviewed-by: Christian König +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 +Signed-off-by: Greg Kroah-Hartman +--- + drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 4 - + drivers/gpu/drm/i915/gt/intel_region_lmem.c | 2 + drivers/pci/pci-sysfs.c | 14 --- + drivers/pci/pci.h | 3 + drivers/pci/setup-bus.c | 101 +++++++++++++++++++--------- + drivers/pci/setup-res.c | 19 +---- + include/linux/pci.h | 4 - + 7 files changed, 87 insertions(+), 60 deletions(-) + +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +@@ -1272,7 +1272,9 @@ int amdgpu_device_resize_fb_bar(struct a + + pci_release_resource(adev->pdev, 0); + +- r = pci_resize_resource(adev->pdev, 0, rbar_size); ++ r = pci_resize_resource(adev->pdev, 0, rbar_size, ++ (adev->asic_type >= CHIP_BONAIRE) ? 1 << 5 ++ : 1 << 2); + if (r == -ENOSPC) + DRM_INFO("Not enough PCI address space for a large BAR."); + else if (r && r != -ENOTSUPP) +--- a/drivers/gpu/drm/i915/gt/intel_region_lmem.c ++++ b/drivers/gpu/drm/i915/gt/intel_region_lmem.c +@@ -37,7 +37,7 @@ _resize_bar(struct drm_i915_private *i91 + + _release_bars(pdev); + +- ret = pci_resize_resource(pdev, resno, bar_size); ++ ret = pci_resize_resource(pdev, resno, bar_size, 0); + if (ret) { + drm_info(&i915->drm, "Failed to resize BAR%d to %dM (%pe)\n", + resno, 1 << bar_size, ERR_PTR(ret)); +--- a/drivers/pci/pci-sysfs.c ++++ b/drivers/pci/pci-sysfs.c +@@ -1455,8 +1455,8 @@ static ssize_t resource##n##_resize_stor + const char *buf, size_t count)\ + { \ + struct pci_dev *pdev = to_pci_dev(dev); \ +- unsigned long size, flags; \ +- int ret, i; \ ++ unsigned long size; \ ++ int ret; \ + u16 cmd; \ + \ + if (kstrtoul(buf, 0, &size) < 0) \ +@@ -1481,17 +1481,9 @@ static ssize_t resource##n##_resize_stor + pci_write_config_word(pdev, PCI_COMMAND, \ + cmd & ~PCI_COMMAND_MEMORY); \ + \ +- flags = pci_resource_flags(pdev, n); \ +- \ + pci_remove_resource_files(pdev); \ + \ +- for (i = 0; i < PCI_STD_NUM_BARS; i++) { \ +- if (pci_resource_len(pdev, i) && \ +- pci_resource_flags(pdev, i) == flags) \ +- pci_release_resource(pdev, i); \ +- } \ +- \ +- ret = pci_resize_resource(pdev, n, size); \ ++ ret = pci_resize_resource(pdev, n, size, 0); \ + \ + pci_assign_unassigned_bus_resources(pdev->bus); \ + \ +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -245,6 +245,9 @@ enum pci_bar_type { + struct device *pci_get_host_bridge_device(struct pci_dev *dev); + void pci_put_host_bridge_device(struct device *dev); + ++int pci_do_resource_release_and_resize(struct pci_dev *dev, int resno, int size, ++ int exclude_bars); ++ + int pci_configure_extended_tags(struct pci_dev *dev, void *ign); + bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *pl, + int crs_timeout); +--- a/drivers/pci/setup-bus.c ++++ b/drivers/pci/setup-bus.c +@@ -2223,18 +2223,16 @@ enable_all: + } + EXPORT_SYMBOL_GPL(pci_assign_unassigned_bridge_resources); + +-int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type) ++static int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type, ++ struct list_head *saved) + { + struct pci_dev_resource *dev_res; + struct pci_dev *next; +- LIST_HEAD(saved); + LIST_HEAD(added); + LIST_HEAD(failed); + unsigned int i; + int ret; + +- down_read(&pci_bus_sem); +- + /* Walk to the root hub, releasing bridge BARs when possible */ + next = bridge; + do { +@@ -2251,9 +2249,9 @@ int pci_reassign_bridge_resources(struct + if (res->child) + continue; + +- ret = add_to_list(&saved, bridge, res, 0, 0); ++ ret = add_to_list(saved, bridge, res, 0, 0); + if (ret) +- goto cleanup; ++ return ret; + + pci_info(bridge, "%s %pR: releasing\n", res_name, res); + +@@ -2269,67 +2267,108 @@ int pci_reassign_bridge_resources(struct + next = bridge->bus ? bridge->bus->self : NULL; + } while (next); + +- if (list_empty(&saved)) { +- up_read(&pci_bus_sem); ++ if (list_empty(saved)) + return -ENOENT; +- } + + __pci_bus_size_bridges(bridge->subordinate, &added); + __pci_bridge_assign_resources(bridge, &added, &failed); + BUG_ON(!list_empty(&added)); + + if (!list_empty(&failed)) { +- ret = -ENOSPC; +- goto cleanup; ++ free_list(&failed); ++ return -ENOSPC; + } + +- list_for_each_entry(dev_res, &saved, list) { ++ list_for_each_entry(dev_res, saved, list) { + /* Skip the bridge we just assigned resources for */ + if (bridge == dev_res->dev) + continue; + ++ if (!dev_res->dev->subordinate) ++ continue; ++ + bridge = dev_res->dev; + pci_setup_bridge(bridge->subordinate); + } + +- free_list(&saved); +- up_read(&pci_bus_sem); + return 0; ++} + +-cleanup: +- /* Restore size and flags */ +- list_for_each_entry(dev_res, &failed, list) { +- struct resource *res = dev_res->res; ++int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size, ++ int exclude_bars) ++{ ++ struct resource *res = pdev->resource + resno; ++ unsigned long flags = res->flags; ++ struct pci_dev_resource *dev_res; ++ struct pci_bus *bus = pdev->bus; ++ struct resource *r; ++ LIST_HEAD(saved); ++ unsigned int i; ++ int ret = 0; + +- res->start = dev_res->start; +- res->end = dev_res->end; +- res->flags = dev_res->flags; ++ down_read(&pci_bus_sem); ++ ++ pci_dev_for_each_resource(pdev, r, i) { ++ if (i >= PCI_BRIDGE_RESOURCES) ++ break; ++ ++ if (exclude_bars & BIT(i)) ++ continue; ++ ++ if (!pci_resource_len(pdev, i) || r->flags != flags) ++ continue; ++ ++ ret = add_to_list(&saved, pdev, r, 0, 0); ++ if (ret) ++ goto restore; ++ pci_release_resource(pdev, i); + } +- free_list(&failed); + ++ res->end = res->start + pci_rebar_size_to_bytes(size) - 1; ++ ++ if (!bus->self) ++ goto out; ++ ++ ret = pci_reassign_bridge_resources(bus->self, res->flags, &saved); ++ if (ret) ++ goto restore; ++ ++out: ++ up_read(&pci_bus_sem); ++ free_list(&saved); ++ return ret; ++ ++restore: + /* Revert to the old configuration */ + list_for_each_entry(dev_res, &saved, list) { + struct resource *res = dev_res->res; ++ struct pci_dev *dev = dev_res->dev; + +- bridge = dev_res->dev; +- i = res - bridge->resource; ++ i = res - dev->resource; + + if (res->parent) { + release_child_resources(res); +- pci_release_resource(bridge, i); ++ pci_release_resource(dev, i); + } + + res->start = dev_res->start; + res->end = dev_res->end; + res->flags = dev_res->flags; + +- pci_claim_resource(bridge, i); +- pci_setup_bridge(bridge->subordinate); +- } +- up_read(&pci_bus_sem); +- free_list(&saved); ++ if (pci_claim_resource(dev, i)) ++ continue; + +- return ret; ++ if (i < PCI_BRIDGE_RESOURCES) { ++ const char *res_name = pci_resource_name(dev, i); ++ ++ pci_update_resource(dev, i); ++ pci_info(dev, "%s %pR: old value restored\n", ++ res_name, res); ++ } ++ if (dev->subordinate) ++ pci_setup_bridge(dev->subordinate); ++ } ++ goto out; + } + + void pci_assign_unassigned_bus_resources(struct pci_bus *bus) +--- a/drivers/pci/setup-res.c ++++ b/drivers/pci/setup-res.c +@@ -427,9 +427,9 @@ void pci_release_resource(struct pci_dev + } + EXPORT_SYMBOL(pci_release_resource); + +-int pci_resize_resource(struct pci_dev *dev, int resno, int size) ++int pci_resize_resource(struct pci_dev *dev, int resno, int size, ++ int exclude_bars) + { +- struct resource *res = dev->resource + resno; + struct pci_host_bridge *host; + int old, ret; + u32 sizes; +@@ -440,10 +440,6 @@ int pci_resize_resource(struct pci_dev * + if (host->preserve_config) + return -ENOTSUPP; + +- /* Make sure the resource isn't assigned before resizing it. */ +- if (!(res->flags & IORESOURCE_UNSET)) +- return -EBUSY; +- + pci_read_config_word(dev, PCI_COMMAND, &cmd); + if (cmd & PCI_COMMAND_MEMORY) + return -EBUSY; +@@ -463,19 +459,14 @@ int pci_resize_resource(struct pci_dev * + if (ret) + return ret; + +- res->end = res->start + pci_rebar_size_to_bytes(size) - 1; ++ ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars); ++ if (ret) ++ goto error_resize; + +- /* Check if the new config works by trying to assign everything. */ +- if (dev->bus->self) { +- ret = pci_reassign_bridge_resources(dev->bus->self, res->flags); +- if (ret) +- goto error_resize; +- } + return 0; + + error_resize: + pci_rebar_set_size(dev, resno, old); +- res->end = res->start + pci_rebar_size_to_bytes(old) - 1; + return ret; + } + EXPORT_SYMBOL(pci_resize_resource); +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -1380,7 +1380,8 @@ static inline int pci_rebar_bytes_to_siz + } + + u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar); +-int __must_check pci_resize_resource(struct pci_dev *dev, int i, int size); ++int __must_check pci_resize_resource(struct pci_dev *dev, int i, int size, ++ int exclude_bars); + int pci_select_bars(struct pci_dev *dev, unsigned long flags); + bool pci_device_is_present(struct pci_dev *pdev); + void pci_ignore_hotplug(struct pci_dev *dev); +@@ -1450,7 +1451,6 @@ void pci_assign_unassigned_resources(voi + void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge); + void pci_assign_unassigned_bus_resources(struct pci_bus *bus); + void pci_assign_unassigned_root_bus_resources(struct pci_bus *bus); +-int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type); + void pdev_enable_device(struct pci_dev *); + int pci_enable_resources(struct pci_dev *, int mask); + void pci_assign_irq(struct pci_dev *dev); diff --git a/queue-6.1/pci-free-saved-list-without-holding-pci_bus_sem.patch b/queue-6.1/pci-free-saved-list-without-holding-pci_bus_sem.patch new file mode 100644 index 0000000000..f0d70f4531 --- /dev/null +++ b/queue-6.1/pci-free-saved-list-without-holding-pci_bus_sem.patch @@ -0,0 +1,38 @@ +From stable+bounces-274688-greg=kroah.com@vger.kernel.org Wed Jul 15 04:46:07 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 22:45:55 -0400 +Subject: PCI: Free saved list without holding pci_bus_sem +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Bjorn Helgaas" , "Alex Bennée" , "Sasha Levin" +Message-ID: <20260715024559.106836-2-sashal@kernel.org> + +From: Ilpo Järvinen + +[ 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 +Signed-off-by: Bjorn Helgaas +Tested-by: Alex Bennée # 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 +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/setup-bus.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/pci/setup-bus.c ++++ b/drivers/pci/setup-bus.c +@@ -2326,8 +2326,8 @@ cleanup: + pci_claim_resource(bridge, i); + pci_setup_bridge(bridge->subordinate); + } +- free_list(&saved); + up_read(&pci_bus_sem); ++ free_list(&saved); + + return ret; + } diff --git a/queue-6.1/pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch b/queue-6.1/pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch new file mode 100644 index 0000000000..ac757a1a7b --- /dev/null +++ b/queue-6.1/pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch @@ -0,0 +1,55 @@ +From stable+bounces-274502-greg=kroah.com@vger.kernel.org Tue Jul 14 20:22:01 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 14:21:13 -0400 +Subject: PCI: imx6: Fix IMX6SX_GPR12_PCIE_TEST_POWERDOWN handling +To: stable@vger.kernel.org +Cc: Richard Zhu , Manivannan Sadhasivam , Bjorn Helgaas , Frank Li , Sasha Levin +Message-ID: <20260714182113.3081792-1-sashal@kernel.org> + +From: Richard Zhu + +[ Upstream commit aad953fb4eed0df5486cd54ccad80ac197678e01 ] + +The IMX6SX_GPR12_PCIE_TEST_POWERDOWN bit does not control the PCIe +reference clock on i.MX6SX. Instead, it is part of i.MX6SX PCIe core +reset sequence. + +Move the IMX6SX_GPR12_PCIE_TEST_POWERDOWN assertion/deassertion into +the core reset functions to properly reflect its purpose. Remove the +.enable_ref_clk() callback for i.MX6SX since it was incorrectly +manipulating this bit. + +Fixes: e3c06cd063d6 ("PCI: imx6: Add initial imx6sx support") +Signed-off-by: Richard Zhu +Signed-off-by: Manivannan Sadhasivam +Signed-off-by: Bjorn Helgaas +Reviewed-by: Frank Li +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/20260319090844.444987-1-hongxing.zhu@nxp.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/dwc/pci-imx6.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +--- a/drivers/pci/controller/dwc/pci-imx6.c ++++ b/drivers/pci/controller/dwc/pci-imx6.c +@@ -539,9 +539,6 @@ static int imx6_pcie_enable_ref_clk(stru + dev_err(dev, "unable to enable pcie_axi clock\n"); + break; + } +- +- regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, +- IMX6SX_GPR12_PCIE_TEST_POWERDOWN, 0); + break; + case IMX6QP: + case IMX6Q: +@@ -740,6 +737,8 @@ static int imx6_pcie_deassert_core_reset + imx7d_pcie_wait_for_phy_pll_lock(imx6_pcie); + break; + case IMX6SX: ++ regmap_clear_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, ++ IMX6SX_GPR12_PCIE_TEST_POWERDOWN); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR5, + IMX6SX_GPR5_PCIE_BTNRST_RESET, 0); + break; diff --git a/queue-6.1/pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch b/queue-6.1/pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch new file mode 100644 index 0000000000..31793b5dc9 --- /dev/null +++ b/queue-6.1/pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch @@ -0,0 +1,127 @@ +From stable+bounces-274609-greg=kroah.com@vger.kernel.org Wed Jul 15 01:34:30 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 19:34:15 -0400 +Subject: PCI: mediatek: Convert bool to single quirks entry and bitmap +To: stable@vger.kernel.org +Cc: Christian Marangi , Manivannan Sadhasivam , AngeloGioacchino Del Regno , Sasha Levin +Message-ID: <20260714233418.3592225-2-sashal@kernel.org> + +From: Christian Marangi + +[ 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 +Signed-off-by: Manivannan Sadhasivam +Reviewed-by: AngeloGioacchino Del Regno +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 +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/pcie-mediatek.c | 33 ++++++++++++++++++++------------- + 1 file changed, 20 insertions(+), 13 deletions(-) + +--- a/drivers/pci/controller/pcie-mediatek.c ++++ b/drivers/pci/controller/pcie-mediatek.c +@@ -143,23 +143,31 @@ + struct mtk_pcie_port; + + /** ++ * enum mtk_pcie_quirks - MTK PCIe quirks ++ * @MTK_PCIE_FIX_CLASS_ID: host's class ID needed to be fixed ++ * @MTK_PCIE_FIX_DEVICE_ID: host's device ID needed to be fixed ++ * @MTK_PCIE_NO_MSI: Bridge has no MSI support, and relies on an external block ++ */ ++enum mtk_pcie_quirks { ++ MTK_PCIE_FIX_CLASS_ID = BIT(0), ++ MTK_PCIE_FIX_DEVICE_ID = BIT(1), ++ MTK_PCIE_NO_MSI = BIT(2), ++}; ++ ++/** + * struct mtk_pcie_soc - differentiate between host generations +- * @need_fix_class_id: whether this host's class ID needed to be fixed or not +- * @need_fix_device_id: whether this host's device ID needed to be fixed or not +- * @no_msi: Bridge has no MSI support, and relies on an external block + * @device_id: device ID which this host need to be fixed + * @ops: pointer to configuration access functions + * @startup: pointer to controller setting functions + * @setup_irq: pointer to initialize IRQ functions ++ * @quirks: PCIe device quirks. + */ + struct mtk_pcie_soc { +- bool need_fix_class_id; +- bool need_fix_device_id; +- bool no_msi; + unsigned int device_id; + struct pci_ops *ops; + int (*startup)(struct mtk_pcie_port *port); + int (*setup_irq)(struct mtk_pcie_port *port, struct device_node *node); ++ enum mtk_pcie_quirks quirks; + }; + + /** +@@ -720,7 +728,7 @@ static int mtk_pcie_startup_port_v2(stru + writel(val, port->base + PCIE_RST_CTRL); + + /* Set up vendor ID and class code */ +- if (soc->need_fix_class_id) { ++ if (soc->quirks & MTK_PCIE_FIX_CLASS_ID) { + val = PCI_VENDOR_ID_MEDIATEK; + writew(val, port->base + PCIE_CONF_VEND_ID); + +@@ -728,7 +736,7 @@ static int mtk_pcie_startup_port_v2(stru + writew(val, port->base + PCIE_CONF_CLASS_ID); + } + +- if (soc->need_fix_device_id) ++ if (soc->quirks & MTK_PCIE_FIX_DEVICE_ID) + writew(soc->device_id, port->base + PCIE_CONF_DEVICE_ID); + + /* 100ms timeout value should be enough for Gen1/2 training */ +@@ -1129,7 +1137,7 @@ static int mtk_pcie_probe(struct platfor + + host->ops = pcie->soc->ops; + host->sysdata = pcie; +- host->msi_domain = pcie->soc->no_msi; ++ host->msi_domain = !!(pcie->soc->quirks & MTK_PCIE_NO_MSI); + + err = pci_host_probe(host); + if (err) +@@ -1219,9 +1227,9 @@ static const struct dev_pm_ops mtk_pcie_ + }; + + static const struct mtk_pcie_soc mtk_pcie_soc_v1 = { +- .no_msi = true, + .ops = &mtk_pcie_ops, + .startup = mtk_pcie_startup_port, ++ .quirks = MTK_PCIE_NO_MSI, + }; + + static const struct mtk_pcie_soc mtk_pcie_soc_mt2712 = { +@@ -1231,19 +1239,18 @@ static const struct mtk_pcie_soc mtk_pci + }; + + static const struct mtk_pcie_soc mtk_pcie_soc_mt7622 = { +- .need_fix_class_id = true, + .ops = &mtk_pcie_ops_v2, + .startup = mtk_pcie_startup_port_v2, + .setup_irq = mtk_pcie_setup_irq, ++ .quirks = MTK_PCIE_FIX_CLASS_ID, + }; + + static const struct mtk_pcie_soc mtk_pcie_soc_mt7629 = { +- .need_fix_class_id = true, +- .need_fix_device_id = true, + .device_id = PCI_DEVICE_ID_MEDIATEK_7629, + .ops = &mtk_pcie_ops_v2, + .startup = mtk_pcie_startup_port_v2, + .setup_irq = mtk_pcie_setup_irq, ++ .quirks = MTK_PCIE_FIX_CLASS_ID | MTK_PCIE_FIX_DEVICE_ID, + }; + + static const struct of_device_id mtk_pcie_ids[] = { diff --git a/queue-6.1/pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch b/queue-6.1/pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch new file mode 100644 index 0000000000..e7b935f254 --- /dev/null +++ b/queue-6.1/pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch @@ -0,0 +1,183 @@ +From stable+bounces-274611-greg=kroah.com@vger.kernel.org Wed Jul 15 01:35:06 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 19:34:17 -0400 +Subject: PCI: mediatek: Fix IRQ domain leak when port fails to enable +To: stable@vger.kernel.org +Cc: Manivannan Sadhasivam , Manivannan Sadhasivam , Caleb James DeLisle , Sasha Levin +Message-ID: <20260714233418.3592225-4-sashal@kernel.org> + +From: Manivannan Sadhasivam + +[ 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 +Signed-off-by: Manivannan Sadhasivam +Cc: stable@vger.kernel.org # 5.10 +Cc: Caleb James DeLisle +Link: https://patch.msgid.link/20260521174617.17692-1-mani@kernel.org +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/pcie-mediatek.c | 67 ++++++++++++++++++++------------- + drivers/pci/pci.h | 3 + + 2 files changed, 45 insertions(+), 25 deletions(-) + +--- a/drivers/pci/controller/pcie-mediatek.c ++++ b/drivers/pci/controller/pcie-mediatek.c +@@ -540,25 +540,29 @@ static void mtk_pcie_enable_msi(struct m + writel(val, port->base + PCIE_INT_MASK); + } + +-static void mtk_pcie_irq_teardown(struct mtk_pcie *pcie) ++static void mtk_pcie_irq_teardown_port(struct mtk_pcie_port *port) + { +- struct mtk_pcie_port *port, *tmp; ++ irq_set_chained_handler_and_data(port->irq, NULL, NULL); + +- list_for_each_entry_safe(port, tmp, &pcie->ports, list) { +- irq_set_chained_handler_and_data(port->irq, NULL, NULL); ++ if (port->irq_domain) ++ irq_domain_remove(port->irq_domain); + +- if (port->irq_domain) +- irq_domain_remove(port->irq_domain); ++ if (IS_ENABLED(CONFIG_PCI_MSI)) { ++ if (port->msi_domain) ++ irq_domain_remove(port->msi_domain); ++ if (port->inner_domain) ++ irq_domain_remove(port->inner_domain); ++ } + +- if (IS_ENABLED(CONFIG_PCI_MSI)) { +- if (port->msi_domain) +- irq_domain_remove(port->msi_domain); +- if (port->inner_domain) +- irq_domain_remove(port->inner_domain); +- } ++ irq_dispose_mapping(port->irq); ++} + +- irq_dispose_mapping(port->irq); +- } ++static void mtk_pcie_irq_teardown(struct mtk_pcie *pcie) ++{ ++ struct mtk_pcie_port *port, *tmp; ++ ++ list_for_each_entry_safe(port, tmp, &pcie->ports, list) ++ mtk_pcie_irq_teardown_port(port); + } + + static int mtk_pcie_intx_map(struct irq_domain *domain, unsigned int irq, +@@ -841,7 +845,7 @@ static int mtk_pcie_startup_port(struct + return 0; + } + +-static void mtk_pcie_enable_port(struct mtk_pcie_port *port) ++static int mtk_pcie_enable_port(struct mtk_pcie_port *port) + { + struct mtk_pcie *pcie = port->pcie; + struct device *dev = pcie->dev; +@@ -850,7 +854,7 @@ static void mtk_pcie_enable_port(struct + err = clk_prepare_enable(port->sys_ck); + if (err) { + dev_err(dev, "failed to enable sys_ck%d clock\n", port->slot); +- goto err_sys_clk; ++ return err; + } + + err = clk_prepare_enable(port->ahb_ck); +@@ -898,11 +902,15 @@ static void mtk_pcie_enable_port(struct + goto err_phy_on; + } + +- if (!pcie->soc->startup(port)) +- return; ++ err = pcie->soc->startup(port); ++ if (err) { ++ dev_info(dev, "Port%d link down\n", port->slot); ++ goto err_soc_startup; ++ } + +- dev_info(dev, "Port%d link down\n", port->slot); ++ return 0; + ++err_soc_startup: + phy_power_off(port->phy); + err_phy_on: + phy_exit(port->phy); +@@ -918,8 +926,8 @@ err_aux_clk: + clk_disable_unprepare(port->ahb_ck); + err_ahb_clk: + clk_disable_unprepare(port->sys_ck); +-err_sys_clk: +- mtk_pcie_port_free(port); ++ ++ return err; + } + + static int mtk_pcie_parse_port(struct mtk_pcie *pcie, +@@ -1095,8 +1103,13 @@ static int mtk_pcie_setup(struct mtk_pci + return err; + + /* enable each port, and then check link status */ +- list_for_each_entry_safe(port, tmp, &pcie->ports, list) +- mtk_pcie_enable_port(port); ++ list_for_each_entry_safe(port, tmp, &pcie->ports, list) { ++ err = mtk_pcie_enable_port(port); ++ if (err) { ++ mtk_pcie_irq_teardown_port(port); ++ mtk_pcie_port_free(port); ++ } ++ } + + /* power down PCIe subsys if slots are all empty (link down) */ + if (list_empty(&pcie->ports)) +@@ -1200,14 +1213,18 @@ static int mtk_pcie_resume_noirq(struct + { + struct mtk_pcie *pcie = dev_get_drvdata(dev); + struct mtk_pcie_port *port, *tmp; ++ int err; + + if (list_empty(&pcie->ports)) + return 0; + + clk_prepare_enable(pcie->free_ck); + +- list_for_each_entry_safe(port, tmp, &pcie->ports, list) +- mtk_pcie_enable_port(port); ++ list_for_each_entry_safe(port, tmp, &pcie->ports, list) { ++ err = mtk_pcie_enable_port(port); ++ if (err) ++ mtk_pcie_port_free(port); ++ } + + /* In case of EP was removed while system suspend. */ + if (list_empty(&pcie->ports)) +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -14,6 +14,9 @@ + #define PCI_EXP_AER_FLAGS (PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | \ + PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE) + ++/* Power stable to PERST# inactive from PCIe card Electromechanical Spec */ ++#define PCIE_T_PVPERL_MS 100 ++ + /* + * PCIe r6.0, sec 5.3.3.2.1 + * Recommends 1ms to 10ms timeout to check L2 ready. diff --git a/queue-6.1/pci-mediatek-use-generic-macro-for-tpvperl-delay.patch b/queue-6.1/pci-mediatek-use-generic-macro-for-tpvperl-delay.patch new file mode 100644 index 0000000000..1df62eff7e --- /dev/null +++ b/queue-6.1/pci-mediatek-use-generic-macro-for-tpvperl-delay.patch @@ -0,0 +1,42 @@ +From stable+bounces-274610-greg=kroah.com@vger.kernel.org Wed Jul 15 01:35:01 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 19:34:16 -0400 +Subject: PCI: mediatek: Use generic MACRO for TPVPERL delay +To: stable@vger.kernel.org +Cc: Christian Marangi , Manivannan Sadhasivam , AngeloGioacchino Del Regno , Sasha Levin +Message-ID: <20260714233418.3592225-3-sashal@kernel.org> + +From: Christian Marangi + +[ 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 +Signed-off-by: Manivannan Sadhasivam +Reviewed-by: AngeloGioacchino Del Regno +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 +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/pcie-mediatek.c | 7 +------ + 1 file changed, 1 insertion(+), 6 deletions(-) + +--- a/drivers/pci/controller/pcie-mediatek.c ++++ b/drivers/pci/controller/pcie-mediatek.c +@@ -714,12 +714,7 @@ static int mtk_pcie_startup_port_v2(stru + */ + writel(PCIE_LINKDOWN_RST_EN, port->base + PCIE_RST_CTRL); + +- /* +- * Described in PCIe CEM specification sections 2.2 (PERST# Signal) and +- * 2.2.1 (Initial Power-Up (G3 to S0)). The deassertion of PERST# should +- * be delayed 100ms (TPVPERL) for the power and clock to become stable. +- */ +- msleep(100); ++ msleep(PCIE_T_PVPERL_MS); + + /* De-assert PHY, PE, PIPE, MAC and configuration reset */ + val = readl(port->base + PCIE_RST_CTRL); diff --git a/queue-6.1/pci-move-resizable-bar-code-to-rebar.c.patch b/queue-6.1/pci-move-resizable-bar-code-to-rebar.c.patch new file mode 100644 index 0000000000..e27582ebd5 --- /dev/null +++ b/queue-6.1/pci-move-resizable-bar-code-to-rebar.c.patch @@ -0,0 +1,510 @@ +From stable+bounces-274692-greg=kroah.com@vger.kernel.org Wed Jul 15 04:47:16 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 22:45:58 -0400 +Subject: PCI: Move Resizable BAR code to rebar.c +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Bjorn Helgaas" , "Christian König" , "Sasha Levin" +Message-ID: <20260715024559.106836-5-sashal@kernel.org> + +From: Ilpo Järvinen + +[ 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 +Signed-off-by: Bjorn Helgaas +Reviewed-by: Christian König +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 +Signed-off-by: Greg Kroah-Hartman +--- + Documentation/driver-api/pci/pci.rst | 3 + drivers/pci/Makefile | 2 + drivers/pci/pci.c | 136 ---------------------- + drivers/pci/pci.h | 1 + drivers/pci/rebar.c | 212 +++++++++++++++++++++++++++++++++++ + drivers/pci/setup-res.c | 64 ---------- + 6 files changed, 217 insertions(+), 201 deletions(-) + create mode 100644 drivers/pci/rebar.c + +--- a/Documentation/driver-api/pci/pci.rst ++++ b/Documentation/driver-api/pci/pci.rst +@@ -31,6 +31,9 @@ PCI Support Library + .. kernel-doc:: drivers/pci/slot.c + :export: + ++.. kernel-doc:: drivers/pci/rebar.c ++ :export: ++ + .. kernel-doc:: drivers/pci/rom.c + :export: + +--- a/drivers/pci/Makefile ++++ b/drivers/pci/Makefile +@@ -4,7 +4,7 @@ + + obj-$(CONFIG_PCI) += access.o bus.o probe.o host-bridge.o \ + remove.o pci.o pci-driver.o search.o \ +- pci-sysfs.o rom.o setup-res.o irq.o vpd.o \ ++ pci-sysfs.o rebar.o rom.o setup-res.o irq.o vpd.o \ + setup-bus.o vc.o mmap.o setup-irq.o + + obj-$(CONFIG_PCI) += msi/ +--- a/drivers/pci/pci.c ++++ b/drivers/pci/pci.c +@@ -1840,33 +1840,6 @@ static void pci_restore_config_space(str + } + } + +-static void pci_restore_rebar_state(struct pci_dev *pdev) +-{ +- unsigned int pos, nbars, i; +- u32 ctrl; +- +- pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR); +- if (!pos) +- return; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- nbars = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >> +- PCI_REBAR_CTRL_NBAR_SHIFT; +- +- for (i = 0; i < nbars; i++, pos += 8) { +- struct resource *res; +- int bar_idx, size; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; +- res = pdev->resource + bar_idx; +- size = pci_rebar_bytes_to_size(resource_size(res)); +- ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; +- ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT; +- pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); +- } +-} +- + /** + * pci_restore_state - Restore the saved state of a PCI device + * @dev: PCI device that we're dealing with +@@ -3784,115 +3757,6 @@ void pci_acs_init(struct pci_dev *dev) + } + + /** +- * pci_rebar_find_pos - find position of resize ctrl reg for BAR +- * @pdev: PCI device +- * @bar: BAR to find +- * +- * Helper to find the position of the ctrl register for a BAR. +- * Returns -ENOTSUPP if resizable BARs are not supported at all. +- * Returns -ENOENT if no ctrl register for the BAR could be found. +- */ +-static int pci_rebar_find_pos(struct pci_dev *pdev, int bar) +-{ +- unsigned int pos, nbars, i; +- u32 ctrl; +- +- pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR); +- if (!pos) +- return -ENOTSUPP; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- nbars = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >> +- PCI_REBAR_CTRL_NBAR_SHIFT; +- +- for (i = 0; i < nbars; i++, pos += 8) { +- int bar_idx; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; +- if (bar_idx == bar) +- return pos; +- } +- +- return -ENOENT; +-} +- +-/** +- * pci_rebar_get_possible_sizes - get possible sizes for BAR +- * @pdev: PCI device +- * @bar: BAR to query +- * +- * Get the possible sizes of a resizable BAR as bitmask defined in the spec +- * (bit 0=1MB, bit 19=512GB). Returns 0 if BAR isn't resizable. +- */ +-u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar) +-{ +- int pos; +- u32 cap; +- +- pos = pci_rebar_find_pos(pdev, bar); +- if (pos < 0) +- return 0; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap); +- cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap); +- +- /* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */ +- if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f && +- bar == 0 && cap == 0x700) +- return 0x3f00; +- +- return cap; +-} +-EXPORT_SYMBOL(pci_rebar_get_possible_sizes); +- +-/** +- * pci_rebar_get_current_size - get the current size of a BAR +- * @pdev: PCI device +- * @bar: BAR to set size to +- * +- * Read the size of a BAR from the resizable BAR config. +- * Returns size if found or negative error code. +- */ +-int pci_rebar_get_current_size(struct pci_dev *pdev, int bar) +-{ +- int pos; +- u32 ctrl; +- +- pos = pci_rebar_find_pos(pdev, bar); +- if (pos < 0) +- return pos; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- return (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> PCI_REBAR_CTRL_BAR_SHIFT; +-} +- +-/** +- * pci_rebar_set_size - set a new size for a BAR +- * @pdev: PCI device +- * @bar: BAR to set size to +- * @size: new size as defined in the spec (0=1MB, 19=512GB) +- * +- * Set the new size of a BAR as defined in the spec. +- * Returns zero if resizing was successful, error code otherwise. +- */ +-int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size) +-{ +- int pos; +- u32 ctrl; +- +- pos = pci_rebar_find_pos(pdev, bar); +- if (pos < 0) +- return pos; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; +- ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT; +- pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); +- return 0; +-} +- +-/** + * pci_enable_atomic_ops_to_root - enable AtomicOp requests to root port + * @dev: the PCI device + * @cap_mask: mask of desired AtomicOp sizes, including one or more of: +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -615,6 +615,7 @@ static inline int acpi_get_rc_resources( + } + #endif + ++void pci_restore_rebar_state(struct pci_dev *pdev); + int pci_rebar_get_current_size(struct pci_dev *pdev, int bar); + int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size); + static inline u64 pci_rebar_size_to_bytes(int size) +--- /dev/null ++++ b/drivers/pci/rebar.c +@@ -0,0 +1,212 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * PCI Resizable BAR Extended Capability handling. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pci.h" ++ ++/** ++ * pci_rebar_find_pos - find position of resize ctrl reg for BAR ++ * @pdev: PCI device ++ * @bar: BAR to find ++ * ++ * Helper to find the position of the ctrl register for a BAR. ++ * Returns -ENOTSUPP if resizable BARs are not supported at all. ++ * Returns -ENOENT if no ctrl register for the BAR could be found. ++ */ ++static int pci_rebar_find_pos(struct pci_dev *pdev, int bar) ++{ ++ unsigned int pos, nbars, i; ++ u32 ctrl; ++ ++ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR); ++ if (!pos) ++ return -ENOTSUPP; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ nbars = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >> ++ PCI_REBAR_CTRL_NBAR_SHIFT; ++ ++ for (i = 0; i < nbars; i++, pos += 8) { ++ int bar_idx; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; ++ if (bar_idx == bar) ++ return pos; ++ } ++ ++ return -ENOENT; ++} ++ ++/** ++ * pci_rebar_get_possible_sizes - get possible sizes for BAR ++ * @pdev: PCI device ++ * @bar: BAR to query ++ * ++ * Get the possible sizes of a resizable BAR as bitmask defined in the spec ++ * (bit 0=1MB, bit 19=512GB). Returns 0 if BAR isn't resizable. ++ */ ++u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar) ++{ ++ int pos; ++ u32 cap; ++ ++ pos = pci_rebar_find_pos(pdev, bar); ++ if (pos < 0) ++ return 0; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap); ++ cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap); ++ ++ /* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */ ++ if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f && ++ bar == 0 && cap == 0x700) ++ return 0x3f00; ++ ++ return cap; ++} ++EXPORT_SYMBOL(pci_rebar_get_possible_sizes); ++ ++/** ++ * pci_rebar_get_current_size - get the current size of a BAR ++ * @pdev: PCI device ++ * @bar: BAR to set size to ++ * ++ * Read the size of a BAR from the resizable BAR config. ++ * Returns size if found or negative error code. ++ */ ++int pci_rebar_get_current_size(struct pci_dev *pdev, int bar) ++{ ++ int pos; ++ u32 ctrl; ++ ++ pos = pci_rebar_find_pos(pdev, bar); ++ if (pos < 0) ++ return pos; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ return (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> PCI_REBAR_CTRL_BAR_SHIFT; ++} ++ ++/** ++ * pci_rebar_set_size - set a new size for a BAR ++ * @pdev: PCI device ++ * @bar: BAR to set size to ++ * @size: new size as defined in the spec (0=1MB, 19=512GB) ++ * ++ * Set the new size of a BAR as defined in the spec. ++ * Returns zero if resizing was successful, error code otherwise. ++ */ ++int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size) ++{ ++ int pos; ++ u32 ctrl; ++ ++ pos = pci_rebar_find_pos(pdev, bar); ++ if (pos < 0) ++ return pos; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; ++ ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT; ++ pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); ++ return 0; ++} ++ ++void pci_restore_rebar_state(struct pci_dev *pdev) ++{ ++ unsigned int pos, nbars, i; ++ u32 ctrl; ++ ++ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR); ++ if (!pos) ++ return; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl); ++ ++ for (i = 0; i < nbars; i++, pos += 8) { ++ struct resource *res; ++ int bar_idx, size; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; ++ res = pci_resource_n(pdev, bar_idx); ++ size = pci_rebar_bytes_to_size(resource_size(res)); ++ ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; ++ ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT; ++ pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); ++ } ++} ++ ++/** ++ * pci_resize_resource - reconfigure a Resizable BAR and resources ++ * @dev: the PCI device ++ * @resno: index of the BAR to be resized ++ * @size: new size as defined in the spec (0=1MB, 31=128TB) ++ * @exclude_bars: a mask of BARs that should not be released ++ * ++ * Reconfigure @resno to @size and re-run resource assignment algorithm ++ * with the new size. ++ * ++ * Prior to resize, release @dev resources that share a bridge window with ++ * @resno. This unpins the bridge window resource to allow changing it. ++ * ++ * The caller may prevent releasing a particular BAR by providing ++ * @exclude_bars mask, but this may result in the resize operation failing ++ * due to insufficient space. ++ * ++ * Return: 0 on success, or negative on error. In case of an error, the ++ * resources are restored to their original places. ++ */ ++int pci_resize_resource(struct pci_dev *dev, int resno, int size, ++ int exclude_bars) ++{ ++ struct pci_host_bridge *host; ++ int old, ret; ++ u32 sizes; ++ u16 cmd; ++ ++ /* Check if we must preserve the firmware's resource assignment */ ++ host = pci_find_host_bridge(dev->bus); ++ if (host->preserve_config) ++ return -ENOTSUPP; ++ ++ pci_read_config_word(dev, PCI_COMMAND, &cmd); ++ if (cmd & PCI_COMMAND_MEMORY) ++ return -EBUSY; ++ ++ sizes = pci_rebar_get_possible_sizes(dev, resno); ++ if (!sizes) ++ return -ENOTSUPP; ++ ++ if (!(sizes & BIT(size))) ++ return -EINVAL; ++ ++ old = pci_rebar_get_current_size(dev, resno); ++ if (old < 0) ++ return old; ++ ++ ret = pci_rebar_set_size(dev, resno, size); ++ if (ret) ++ return ret; ++ ++ ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars); ++ if (ret) ++ goto error_resize; ++ ++ return 0; ++ ++error_resize: ++ pci_rebar_set_size(dev, resno, old); ++ return ret; ++} ++EXPORT_SYMBOL(pci_resize_resource); +--- a/drivers/pci/setup-res.c ++++ b/drivers/pci/setup-res.c +@@ -427,70 +427,6 @@ void pci_release_resource(struct pci_dev + } + EXPORT_SYMBOL(pci_release_resource); + +-/** +- * pci_resize_resource - reconfigure a Resizable BAR and resources +- * @dev: the PCI device +- * @resno: index of the BAR to be resized +- * @size: new size as defined in the spec (0=1MB, 31=128TB) +- * @exclude_bars: a mask of BARs that should not be released +- * +- * Reconfigure @resno to @size and re-run resource assignment algorithm +- * with the new size. +- * +- * Prior to resize, release @dev resources that share a bridge window with +- * @resno. This unpins the bridge window resource to allow changing it. +- * +- * The caller may prevent releasing a particular BAR by providing +- * @exclude_bars mask, but this may result in the resize operation failing +- * due to insufficient space. +- * +- * Return: 0 on success, or negative on error. In case of an error, the +- * resources are restored to their original places. +- */ +-int pci_resize_resource(struct pci_dev *dev, int resno, int size, +- int exclude_bars) +-{ +- struct pci_host_bridge *host; +- int old, ret; +- u32 sizes; +- u16 cmd; +- +- /* Check if we must preserve the firmware's resource assignment */ +- host = pci_find_host_bridge(dev->bus); +- if (host->preserve_config) +- return -ENOTSUPP; +- +- pci_read_config_word(dev, PCI_COMMAND, &cmd); +- if (cmd & PCI_COMMAND_MEMORY) +- return -EBUSY; +- +- sizes = pci_rebar_get_possible_sizes(dev, resno); +- if (!sizes) +- return -ENOTSUPP; +- +- if (!(sizes & BIT(size))) +- return -EINVAL; +- +- old = pci_rebar_get_current_size(dev, resno); +- if (old < 0) +- return old; +- +- ret = pci_rebar_set_size(dev, resno, size); +- if (ret) +- return ret; +- +- ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars); +- if (ret) +- goto error_resize; +- +- return 0; +- +-error_resize: +- pci_rebar_set_size(dev, resno, old); +- return ret; +-} +-EXPORT_SYMBOL(pci_resize_resource); +- + int pci_enable_resources(struct pci_dev *dev, int mask) + { + u16 cmd, old_cmd; diff --git a/queue-6.1/pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch b/queue-6.1/pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch new file mode 100644 index 0000000000..78c81aa185 --- /dev/null +++ b/queue-6.1/pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch @@ -0,0 +1,71 @@ +From stable+bounces-274687-greg=kroah.com@vger.kernel.org Wed Jul 15 04:46:58 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 22:45:54 -0400 +Subject: PCI: Prevent resource tree corruption when BAR resize fails +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Simon Richter" , "Alex Bennée" , "Bjorn Helgaas" , "Sasha Levin" +Message-ID: <20260715024559.106836-1-sashal@kernel.org> + +From: Ilpo Järvinen + +[ 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 +Link: https://lore.kernel.org/linux-pci/67840a16-99b4-4d8c-9b5c-4721ab0970a2@hogyros.de/ +Reported-by: Alex Bennée +Link: https://lore.kernel.org/linux-pci/874irqop6b.fsf@draig.linaro.org/ +Signed-off-by: Ilpo Järvinen +Signed-off-by: Bjorn Helgaas +Tested-by: Alex Bennée # 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 +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/setup-bus.c | 5 +++++ + 1 file changed, 5 insertions(+) + +--- a/drivers/pci/setup-bus.c ++++ b/drivers/pci/setup-bus.c +@@ -2314,6 +2314,11 @@ cleanup: + bridge = dev_res->dev; + i = res - bridge->resource; + ++ if (res->parent) { ++ release_child_resources(res); ++ pci_release_resource(bridge, i); ++ } ++ + res->start = dev_res->start; + res->end = dev_res->end; + res->flags = dev_res->flags; diff --git a/queue-6.1/pci-skip-resizable-bar-restore-on-read-error.patch b/queue-6.1/pci-skip-resizable-bar-restore-on-read-error.patch new file mode 100644 index 0000000000..4e287abf26 --- /dev/null +++ b/queue-6.1/pci-skip-resizable-bar-restore-on-read-error.patch @@ -0,0 +1,61 @@ +From stable+bounces-274691-greg=kroah.com@vger.kernel.org Wed Jul 15 04:47:13 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 22:45:59 -0400 +Subject: PCI: Skip Resizable BAR restore on read error +To: stable@vger.kernel.org +Cc: Marco Nenciarini , Bjorn Helgaas , Sasha Levin +Message-ID: <20260715024559.106836-6-sashal@kernel.org> + +From: Marco Nenciarini + +[ 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 +Signed-off-by: Bjorn Helgaas +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/666cac19b5daa0ab0e0ab64454e76b4d24465dbd.1776429882.git.mnencia@kcore.it +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/rebar.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +--- a/drivers/pci/rebar.c ++++ b/drivers/pci/rebar.c +@@ -131,6 +131,9 @@ void pci_restore_rebar_state(struct pci_ + return; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ if (PCI_POSSIBLE_ERROR(ctrl)) ++ return; ++ + nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl); + + for (i = 0; i < nbars; i++, pos += 8) { +@@ -138,6 +141,9 @@ void pci_restore_rebar_state(struct pci_ + int bar_idx, size; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ if (PCI_POSSIBLE_ERROR(ctrl)) ++ return; ++ + bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; + res = pci_resource_n(pdev, bar_idx); + size = pci_rebar_bytes_to_size(resource_size(res)); diff --git a/queue-6.1/perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch b/queue-6.1/perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch new file mode 100644 index 0000000000..e2b449bd62 --- /dev/null +++ b/queue-6.1/perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch @@ -0,0 +1,57 @@ +From stable+bounces-276818-greg=kroah.com@vger.kernel.org Thu Jul 16 21:03:47 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 15:03:40 -0400 +Subject: perf/x86/intel/uncore: Defer ADL global PMON enable to enable_box() +To: stable@vger.kernel.org +Cc: Zide Chen , "Peter Zijlstra (Intel)" , Dapeng Mi , Sasha Levin +Message-ID: <20260716190340.1045396-1-sashal@kernel.org> + +From: Zide Chen + +[ 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 +Signed-off-by: Peter Zijlstra (Intel) +Reviewed-by: Dapeng Mi +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 +Signed-off-by: Greg Kroah-Hartman +--- + arch/x86/events/intel/uncore_snb.c | 7 ------- + 1 file changed, 7 deletions(-) + +--- a/arch/x86/events/intel/uncore_snb.c ++++ b/arch/x86/events/intel/uncore_snb.c +@@ -537,12 +537,6 @@ void tgl_uncore_cpu_init(void) + skl_uncore_msr_ops.init_box = rkl_uncore_msr_init_box; + } + +-static void adl_uncore_msr_init_box(struct intel_uncore_box *box) +-{ +- if (box->pmu->pmu_idx == 0) +- wrmsrl(ADL_UNC_PERF_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN); +-} +- + static void adl_uncore_msr_enable_box(struct intel_uncore_box *box) + { + wrmsrl(ADL_UNC_PERF_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN); +@@ -561,7 +555,6 @@ static void adl_uncore_msr_exit_box(stru + } + + static struct intel_uncore_ops adl_uncore_msr_ops = { +- .init_box = adl_uncore_msr_init_box, + .enable_box = adl_uncore_msr_enable_box, + .disable_box = adl_uncore_msr_disable_box, + .exit_box = adl_uncore_msr_exit_box, diff --git a/queue-6.1/proc-move-fdinfo-ptrace_mode_read-check-into-the-inode-.permission-operation.patch b/queue-6.1/proc-move-fdinfo-ptrace_mode_read-check-into-the-inode-.permission-operation.patch new file mode 100644 index 0000000000..265ce4c207 --- /dev/null +++ b/queue-6.1/proc-move-fdinfo-ptrace_mode_read-check-into-the-inode-.permission-operation.patch @@ -0,0 +1,163 @@ +From stable+bounces-277024-greg=kroah.com@vger.kernel.org Fri Jul 17 13:43:32 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 07:18:20 -0400 +Subject: proc: Move fdinfo PTRACE_MODE_READ check into the inode .permission operation +To: stable@vger.kernel.org +Cc: "Tyler Hicks (Microsoft)" , "Kevin Parsons (Microsoft)" , "Christian Brauner" , "Christian König" , "Jann Horn" , "Kalesh Singh" , "Hardik Garg" , "Allen Pais" , "Sasha Levin" +Message-ID: <20260717111823.1573608-2-sashal@kernel.org> + +From: "Tyler Hicks (Microsoft)" + +[ Upstream commit 0a960ba49869ebe8ff859d000351504dd6b93b68 ] + +The following commits loosened the permissions of /proc//fdinfo/ +directory, as well as the files within it, from 0500 to 0555 while also +introducing a PTRACE_MODE_READ check between the current task and +'s task: + + - commit 7bc3fa0172a4 ("procfs: allow reading fdinfo with PTRACE_MODE_READ") + - commit 1927e498aee1 ("procfs: prevent unprivileged processes accessing fdinfo dir") + +Before those changes, inode based system calls like inotify_add_watch(2) +would fail when the current task didn't have sufficient read permissions: + + [...] + lstat("/proc/1/task/1/fdinfo", {st_mode=S_IFDIR|0500, st_size=0, ...}) = 0 + inotify_add_watch(64, "/proc/1/task/1/fdinfo", + IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_CREATE|IN_DELETE| + IN_ONLYDIR|IN_DONT_FOLLOW|IN_EXCL_UNLINK) = -1 EACCES (Permission denied) + [...] + +This matches the documented behavior in the inotify_add_watch(2) man +page: + + ERRORS + EACCES Read access to the given file is not permitted. + +After those changes, inotify_add_watch(2) started succeeding despite the +current task not having PTRACE_MODE_READ privileges on the target task: + + [...] + lstat("/proc/1/task/1/fdinfo", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0 + inotify_add_watch(64, "/proc/1/task/1/fdinfo", + IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_CREATE|IN_DELETE| + IN_ONLYDIR|IN_DONT_FOLLOW|IN_EXCL_UNLINK) = 1757 + openat(AT_FDCWD, "/proc/1/task/1/fdinfo", + O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = -1 EACCES (Permission denied) + [...] + +This change in behavior broke .NET prior to v7. See the github link +below for the v7 commit that inadvertently/quietly (?) fixed .NET after +the kernel changes mentioned above. + +Return to the old behavior by moving the PTRACE_MODE_READ check out of +the file .open operation and into the inode .permission operation: + + [...] + lstat("/proc/1/task/1/fdinfo", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0 + inotify_add_watch(64, "/proc/1/task/1/fdinfo", + IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_CREATE|IN_DELETE| + IN_ONLYDIR|IN_DONT_FOLLOW|IN_EXCL_UNLINK) = -1 EACCES (Permission denied) + [...] + +Reported-by: Kevin Parsons (Microsoft) +Link: https://github.com/dotnet/runtime/commit/89e5469ac591b82d38510fe7de98346cce74ad4f +Link: https://stackoverflow.com/questions/75379065/start-self-contained-net6-build-exe-as-service-on-raspbian-system-unauthorizeda +Fixes: 7bc3fa0172a4 ("procfs: allow reading fdinfo with PTRACE_MODE_READ") +Cc: stable@vger.kernel.org +Cc: Christian Brauner +Cc: Christian König +Cc: Jann Horn +Cc: Kalesh Singh +Cc: Hardik Garg +Cc: Allen Pais +Signed-off-by: Tyler Hicks (Microsoft) +Link: https://lore.kernel.org/r/20240501005646.745089-1-code@tyhicks.com +Signed-off-by: Christian Brauner +Stable-dep-of: 6255da28d4bb ("proc: protect ptrace_may_access() with exec_update_lock (FD links)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/fd.c | 42 ++++++++++++++++++++---------------------- + 1 file changed, 20 insertions(+), 22 deletions(-) + +--- a/fs/proc/fd.c ++++ b/fs/proc/fd.c +@@ -72,7 +72,18 @@ out: + return 0; + } + +-static int proc_fdinfo_access_allowed(struct inode *inode) ++static int seq_fdinfo_open(struct inode *inode, struct file *file) ++{ ++ return single_open(file, seq_show, inode); ++} ++ ++/** ++ * Shared /proc/pid/fdinfo and /proc/pid/fdinfo/fd permission helper to ensure ++ * that the current task has PTRACE_MODE_READ in addition to the normal ++ * POSIX-like checks. ++ */ ++static int proc_fdinfo_permission(struct mnt_idmap *idmap, struct inode *inode, ++ int mask) + { + bool allowed = false; + struct task_struct *task = get_proc_task(inode); +@@ -86,18 +97,13 @@ static int proc_fdinfo_access_allowed(st + if (!allowed) + return -EACCES; + +- return 0; ++ return generic_permission(idmap, inode, mask); + } + +-static int seq_fdinfo_open(struct inode *inode, struct file *file) +-{ +- int ret = proc_fdinfo_access_allowed(inode); +- +- if (ret) +- return ret; +- +- return single_open(file, seq_show, inode); +-} ++static const struct inode_operations proc_fdinfo_file_inode_operations = { ++ .permission = proc_fdinfo_permission, ++ .setattr = proc_setattr, ++}; + + static const struct file_operations proc_fdinfo_file_operations = { + .open = seq_fdinfo_open, +@@ -339,6 +345,8 @@ static struct dentry *proc_fdinfo_instan + ei = PROC_I(inode); + ei->fd = data->fd; + ++ inode->i_op = &proc_fdinfo_file_inode_operations; ++ + inode->i_fop = &proc_fdinfo_file_operations; + tid_fd_update_inode(task, inode, 0); + +@@ -358,23 +366,13 @@ static int proc_readfdinfo(struct file * + proc_fdinfo_instantiate); + } + +-static int proc_open_fdinfo(struct inode *inode, struct file *file) +-{ +- int ret = proc_fdinfo_access_allowed(inode); +- +- if (ret) +- return ret; +- +- return 0; +-} +- + const struct inode_operations proc_fdinfo_inode_operations = { + .lookup = proc_lookupfdinfo, ++ .permission = proc_fdinfo_permission, + .setattr = proc_setattr, + }; + + const struct file_operations proc_fdinfo_operations = { +- .open = proc_open_fdinfo, + .read = generic_read_dir, + .iterate_shared = proc_readfdinfo, + .llseek = generic_file_llseek, diff --git a/queue-6.1/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch b/queue-6.1/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch new file mode 100644 index 0000000000..c168e0c107 --- /dev/null +++ b/queue-6.1/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch @@ -0,0 +1,274 @@ +From stable+bounces-277026-greg=kroah.com@vger.kernel.org Fri Jul 17 13:42:40 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 07:18:22 -0400 +Subject: proc: protect ptrace_may_access() with exec_update_lock (FD links) +To: stable@vger.kernel.org +Cc: Jann Horn , "Christian Brauner (Amutable)" , Sasha Levin +Message-ID: <20260717111823.1573608-4-sashal@kernel.org> + +From: Jann Horn + +[ 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//fd/ symlinks") +Cc: stable@vger.kernel.org +Signed-off-by: Jann Horn +Link: https://patch.msgid.link/20260518-procfs-lockfix-part1-v1-2-5c3d20e0ac33@google.com +Signed-off-by: Christian Brauner (Amutable) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/base.c | 119 ++++++++++++++++++++--------------------------------- + fs/proc/fd.c | 25 ++++------- + fs/proc/internal.h | 2 + 3 files changed, 58 insertions(+), 88 deletions(-) + +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -217,33 +217,24 @@ static int get_task_root(struct task_str + return result; + } + +-static int proc_cwd_link(struct dentry *dentry, struct path *path) ++static int proc_cwd_link(struct dentry *dentry, struct path *path, ++ struct task_struct *task) + { +- struct task_struct *task = get_proc_task(d_inode(dentry)); + int result = -ENOENT; + +- if (task) { +- task_lock(task); +- if (task->fs) { +- get_fs_pwd(task->fs, path); +- result = 0; +- } +- task_unlock(task); +- put_task_struct(task); ++ task_lock(task); ++ if (task->fs) { ++ get_fs_pwd(task->fs, path); ++ result = 0; + } ++ task_unlock(task); + return result; + } + +-static int proc_root_link(struct dentry *dentry, struct path *path) ++static int proc_root_link(struct dentry *dentry, struct path *path, ++ struct task_struct *task) + { +- struct task_struct *task = get_proc_task(d_inode(dentry)); +- int result = -ENOENT; +- +- if (task) { +- result = get_task_root(task, path); +- put_task_struct(task); +- } +- return result; ++ return get_task_root(task, path); + } + + /* +@@ -703,23 +694,6 @@ static int proc_pid_syscall(struct seq_f + /* Here the fs part begins */ + /************************************************************************/ + +-/* permission checks */ +-static bool proc_fd_access_allowed(struct inode *inode) +-{ +- struct task_struct *task; +- bool allowed = false; +- /* Allow access to a task's file descriptors if it is us or we +- * may use ptrace attach to the process and find out that +- * information. +- */ +- task = get_proc_task(inode); +- if (task) { +- allowed = ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS); +- put_task_struct(task); +- } +- return allowed; +-} +- + int proc_nochmod_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, + struct iattr *attr) + { +@@ -1778,16 +1752,12 @@ static const struct file_operations proc + .release = single_release, + }; + +-static int proc_exe_link(struct dentry *dentry, struct path *exe_path) ++static int proc_exe_link(struct dentry *dentry, struct path *exe_path, ++ struct task_struct *task) + { +- struct task_struct *task; + struct file *exe_file; + +- task = get_proc_task(d_inode(dentry)); +- if (!task) +- return -ENOENT; + exe_file = get_task_exe_file(task); +- put_task_struct(task); + if (exe_file) { + *exe_path = exe_file->f_path; + path_get(&exe_file->f_path); +@@ -1797,26 +1767,42 @@ static int proc_exe_link(struct dentry * + return -ENOENT; + } + ++static int call_proc_get_link(struct dentry *dentry, struct inode *inode, struct path *path_out) ++{ ++ struct task_struct *task; ++ int ret; ++ ++ task = get_proc_task(inode); ++ if (!task) ++ return -ENOENT; ++ ret = down_read_killable(&task->signal->exec_update_lock); ++ if (ret) ++ goto out_put_task; ++ if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) { ++ ret = -EACCES; ++ goto out; ++ } ++ ret = PROC_I(inode)->op.proc_get_link(dentry, path_out, task); ++ ++out: ++ up_read(&task->signal->exec_update_lock); ++out_put_task: ++ put_task_struct(task); ++ return ret; ++} ++ + static const char *proc_pid_get_link(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) + { + struct path path; +- int error = -EACCES; ++ int error; + + if (!dentry) + return ERR_PTR(-ECHILD); +- +- /* Are we allowed to snoop on the tasks file descriptors? */ +- if (!proc_fd_access_allowed(inode)) +- goto out; +- +- error = PROC_I(inode)->op.proc_get_link(dentry, &path); +- if (error) +- goto out; +- +- error = nd_jump_link(&path); +-out: ++ error = call_proc_get_link(dentry, inode, &path); ++ if (!error) ++ error = nd_jump_link(&path); + return ERR_PTR(error); + } + +@@ -1850,17 +1836,11 @@ static int proc_pid_readlink(struct dent + struct inode *inode = d_inode(dentry); + struct path path; + +- /* Are we allowed to snoop on the tasks file descriptors? */ +- if (!proc_fd_access_allowed(inode)) +- goto out; +- +- error = PROC_I(inode)->op.proc_get_link(dentry, &path); +- if (error) +- goto out; +- +- error = do_proc_readlink(&path, buffer, buflen); +- path_put(&path); +-out: ++ error = call_proc_get_link(dentry, inode, &path); ++ if (!error) { ++ error = do_proc_readlink(&path, buffer, buflen); ++ path_put(&path); ++ } + return error; + } + +@@ -2248,21 +2228,16 @@ static const struct dentry_operations ti + .d_delete = pid_delete_dentry, + }; + +-static int map_files_get_link(struct dentry *dentry, struct path *path) ++static int map_files_get_link(struct dentry *dentry, struct path *path, ++ struct task_struct *task) + { + unsigned long vm_start, vm_end; + struct vm_area_struct *vma; +- struct task_struct *task; + struct mm_struct *mm; + int rc; + + rc = -ENOENT; +- task = get_proc_task(d_inode(dentry)); +- if (!task) +- goto out; +- + mm = get_task_mm(task); +- put_task_struct(task); + if (!mm) + goto out; + +--- a/fs/proc/fd.c ++++ b/fs/proc/fd.c +@@ -170,24 +170,19 @@ static const struct dentry_operations ti + .d_delete = pid_delete_dentry, + }; + +-static int proc_fd_link(struct dentry *dentry, struct path *path) ++static int proc_fd_link(struct dentry *dentry, struct path *path, ++ struct task_struct *task) + { +- struct task_struct *task; + int ret = -ENOENT; ++ unsigned int fd = proc_fd(d_inode(dentry)); ++ struct file *fd_file; + +- task = get_proc_task(d_inode(dentry)); +- if (task) { +- unsigned int fd = proc_fd(d_inode(dentry)); +- struct file *fd_file; +- +- fd_file = fget_task(task, fd); +- if (fd_file) { +- *path = fd_file->f_path; +- path_get(&fd_file->f_path); +- ret = 0; +- fput(fd_file); +- } +- put_task_struct(task); ++ fd_file = fget_task(task, fd); ++ if (fd_file) { ++ *path = fd_file->f_path; ++ path_get(&fd_file->f_path); ++ ret = 0; ++ fput(fd_file); + } + + return ret; +--- a/fs/proc/internal.h ++++ b/fs/proc/internal.h +@@ -107,7 +107,7 @@ extern struct kmem_cache *proc_dir_entry + void pde_free(struct proc_dir_entry *pde); + + union proc_op { +- int (*proc_get_link)(struct dentry *, struct path *); ++ int (*proc_get_link)(struct dentry *, struct path *, struct task_struct *); + int (*proc_show)(struct seq_file *m, + struct pid_namespace *ns, struct pid *pid, + struct task_struct *task); diff --git a/queue-6.1/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch b/queue-6.1/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch new file mode 100644 index 0000000000..8a8e58fa43 --- /dev/null +++ b/queue-6.1/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch @@ -0,0 +1,207 @@ +From stable+bounces-277097-greg=kroah.com@vger.kernel.org Fri Jul 17 17:00:45 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 11:00:07 -0400 +Subject: proc: protect ptrace_may_access() with exec_update_lock (part 1) +To: stable@vger.kernel.org +Cc: Jann Horn , "Christian Brauner (Amutable)" , Sasha Levin +Message-ID: <20260717150007.1930653-3-sashal@kernel.org> + +From: Jann Horn + +[ 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 +Link: https://patch.msgid.link/20260518-procfs-lockfix-part1-v1-1-5c3d20e0ac33@google.com +Signed-off-by: Christian Brauner (Amutable) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/array.c | 6 ++++++ + fs/proc/base.c | 41 ++++++++++++++++++++++------------------- + fs/proc/namespaces.c | 12 ++++++++++++ + 3 files changed, 40 insertions(+), 19 deletions(-) + +--- a/fs/proc/array.c ++++ b/fs/proc/array.c +@@ -473,6 +473,11 @@ static int do_task_stat(struct seq_file + unsigned long flags; + int exit_code = task->exit_code; + struct signal_struct *sig = task->signal; ++ int ret; ++ ++ ret = down_read_killable(&task->signal->exec_update_lock); ++ if (ret) ++ return ret; + + state = *get_task_state(task); + vsize = eip = esp = 0; +@@ -648,6 +653,7 @@ static int do_task_stat(struct seq_file + seq_puts(m, " 0"); + + seq_putc(m, '\n'); ++ up_read(&task->signal->exec_update_lock); + if (mm) + mmput(mm); + return 0; +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -413,18 +413,24 @@ static int proc_pid_wchan(struct seq_fil + { + unsigned long wchan; + char symname[KSYM_NAME_LEN]; ++ int err; + ++ err = down_read_killable(&task->signal->exec_update_lock); ++ if (err) ++ return err; + if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) + goto print0; + + wchan = get_wchan(task); + if (wchan && !lookup_symbol_name(wchan, symname)) { + seq_puts(m, symname); ++ up_read(&task->signal->exec_update_lock); + return 0; + } + + print0: + seq_putc(m, '0'); ++ up_read(&task->signal->exec_update_lock); + return 0; + } + #endif /* CONFIG_KALLSYMS */ +@@ -2333,17 +2339,15 @@ static struct dentry *proc_map_files_loo + if (!task) + goto out; + +- result = ERR_PTR(-EACCES); +- if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) +- goto out_put_task; +- + result = ERR_PTR(-ENOENT); + if (dname_to_vma_addr(dentry, &vm_start, &vm_end)) + goto out_put_task; + +- mm = get_task_mm(task); +- if (!mm) ++ mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); ++ if (IS_ERR(mm)) { ++ result = ERR_CAST(mm); + goto out_put_task; ++ } + + result = ERR_PTR(-EINTR); + if (mmap_read_lock_killable(mm)) +@@ -2393,23 +2397,22 @@ proc_map_files_readdir(struct file *file + if (!task) + goto out; + +- ret = -EACCES; +- if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) +- goto out_put_task; +- + ret = 0; + if (!dir_emit_dots(file, ctx)) + goto out_put_task; + +- mm = get_task_mm(task); +- if (!mm) ++ mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); ++ if (IS_ERR(mm)) { ++ ret = PTR_ERR(mm); ++ /* if the task has no mm, the directory should just be empty */ ++ if (ret == -ESRCH) ++ ret = 0; + goto out_put_task; ++ } + + ret = mmap_read_lock_killable(mm); +- if (ret) { +- mmput(mm); +- goto out_put_task; +- } ++ if (ret) ++ goto out_put_mm; + + nr_files = 0; + +@@ -2435,8 +2438,7 @@ proc_map_files_readdir(struct file *file + if (!p) { + ret = -ENOMEM; + mmap_read_unlock(mm); +- mmput(mm); +- goto out_put_task; ++ goto out_put_mm; + } + + p->start = vma->vm_start; +@@ -2444,7 +2446,6 @@ proc_map_files_readdir(struct file *file + p->mode = vma->vm_file->f_mode; + } + mmap_read_unlock(mm); +- mmput(mm); + + for (i = 0; i < nr_files; i++) { + char buf[4 * sizeof(long) + 2]; /* max: %lx-%lx\0 */ +@@ -2461,6 +2462,8 @@ proc_map_files_readdir(struct file *file + ctx->pos++; + } + ++out_put_mm: ++ mmput(mm); + out_put_task: + put_task_struct(task); + out: +--- a/fs/proc/namespaces.c ++++ b/fs/proc/namespaces.c +@@ -55,6 +55,10 @@ static const char *proc_ns_get_link(stru + if (!task) + return ERR_PTR(-EACCES); + ++ error = down_read_killable(&task->signal->exec_update_lock); ++ if (error) ++ goto out_put_task; ++ + if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) + goto out; + +@@ -64,6 +68,8 @@ static const char *proc_ns_get_link(stru + + error = nd_jump_link(&ns_path); + out: ++ up_read(&task->signal->exec_update_lock); ++out_put_task: + put_task_struct(task); + return ERR_PTR(error); + } +@@ -80,11 +86,17 @@ static int proc_ns_readlink(struct dentr + if (!task) + return res; + ++ res = down_read_killable(&task->signal->exec_update_lock); ++ if (res) ++ goto out_put_task; ++ + if (ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) { + res = ns_get_name(name, sizeof(name), task, ns_ops); + if (res >= 0) + res = readlink_copy(buffer, buflen, name); + } ++ up_read(&task->signal->exec_update_lock); ++out_put_task: + put_task_struct(task); + return res; + } diff --git a/queue-6.1/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch b/queue-6.1/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch new file mode 100644 index 0000000000..46a2c60d35 --- /dev/null +++ b/queue-6.1/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch @@ -0,0 +1,219 @@ +From stable+bounces-277023-greg=kroah.com@vger.kernel.org Fri Jul 17 13:43:33 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 07:18:21 -0400 +Subject: proc: rename proc_setattr to proc_nochmod_setattr +To: stable@vger.kernel.org +Cc: Christoph Hellwig , Jan Kara , Christian Brauner , Sasha Levin +Message-ID: <20260717111823.1573608-3-sashal@kernel.org> + +From: Christoph Hellwig + +[ 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// 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 +Link: https://patch.msgid.link/20260325063711.3298685-5-hch@lst.de +Reviewed-by: Jan Kara +Signed-off-by: Christian Brauner +Stable-dep-of: 6255da28d4bb ("proc: protect ptrace_may_access() with exec_update_lock (FD links)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/base.c | 22 +++++++++++----------- + fs/proc/fd.c | 10 +++++----- + fs/proc/internal.h | 4 ++-- + fs/proc/namespaces.c | 4 ++-- + fs/proc/proc_net.c | 2 +- + 5 files changed, 21 insertions(+), 21 deletions(-) + +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -720,7 +720,7 @@ static bool proc_fd_access_allowed(struc + return allowed; + } + +-int proc_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, ++int proc_nochmod_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, + struct iattr *attr) + { + int error; +@@ -794,7 +794,7 @@ static int proc_pid_permission(struct us + + + static const struct inode_operations proc_def_inode_operations = { +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static int proc_single_show(struct seq_file *m, void *v) +@@ -1867,7 +1867,7 @@ out: + const struct inode_operations proc_pid_link_inode_operations = { + .readlink = proc_pid_readlink, + .get_link = proc_pid_get_link, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + +@@ -2317,7 +2317,7 @@ proc_map_files_get_link(struct dentry *d + static const struct inode_operations proc_map_files_link_inode_operations = { + .readlink = proc_pid_readlink, + .get_link = proc_map_files_get_link, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static struct dentry * +@@ -2396,7 +2396,7 @@ out: + static const struct inode_operations proc_map_files_inode_operations = { + .lookup = proc_map_files_lookup, + .permission = proc_fd_permission, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static int +@@ -2892,7 +2892,7 @@ static struct dentry *proc_##LSM##_attr_ + static const struct inode_operations proc_##LSM##_attr_dir_inode_ops = { \ + .lookup = proc_##LSM##_attr_dir_lookup, \ + .getattr = pid_getattr, \ +- .setattr = proc_setattr, \ ++ .setattr = proc_nochmod_setattr, \ + } + + #ifdef CONFIG_SECURITY_SMACK +@@ -2951,7 +2951,7 @@ static struct dentry *proc_attr_dir_look + static const struct inode_operations proc_attr_dir_inode_operations = { + .lookup = proc_attr_dir_lookup, + .getattr = pid_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + #endif +@@ -3441,7 +3441,7 @@ static struct dentry *proc_tgid_base_loo + static const struct inode_operations proc_tgid_base_inode_operations = { + .lookup = proc_tgid_base_lookup, + .getattr = pid_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + .permission = proc_pid_permission, + }; + +@@ -3641,7 +3641,7 @@ static int proc_tid_comm_permission(stru + } + + static const struct inode_operations proc_tid_comm_inode_operations = { +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + .permission = proc_tid_comm_permission, + }; + +@@ -3772,7 +3772,7 @@ static const struct file_operations proc + static const struct inode_operations proc_tid_base_inode_operations = { + .lookup = proc_tid_base_lookup, + .getattr = pid_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static struct dentry *proc_task_instantiate(struct dentry *dentry, +@@ -3971,7 +3971,7 @@ static int proc_task_getattr(struct user + static const struct inode_operations proc_task_inode_operations = { + .lookup = proc_task_lookup, + .getattr = proc_task_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + .permission = proc_pid_permission, + }; + +--- a/fs/proc/fd.c ++++ b/fs/proc/fd.c +@@ -82,7 +82,7 @@ static int seq_fdinfo_open(struct inode + * that the current task has PTRACE_MODE_READ in addition to the normal + * POSIX-like checks. + */ +-static int proc_fdinfo_permission(struct mnt_idmap *idmap, struct inode *inode, ++static int proc_fdinfo_permission(struct user_namespace *mnt_userns, struct inode *inode, + int mask) + { + bool allowed = false; +@@ -97,12 +97,12 @@ static int proc_fdinfo_permission(struct + if (!allowed) + return -EACCES; + +- return generic_permission(idmap, inode, mask); ++ return generic_permission(mnt_userns, inode, mask); + } + + static const struct inode_operations proc_fdinfo_file_inode_operations = { + .permission = proc_fdinfo_permission, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static const struct file_operations proc_fdinfo_file_operations = { +@@ -328,7 +328,7 @@ int proc_fd_permission(struct user_names + const struct inode_operations proc_fd_inode_operations = { + .lookup = proc_lookupfd, + .permission = proc_fd_permission, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static struct dentry *proc_fdinfo_instantiate(struct dentry *dentry, +@@ -369,7 +369,7 @@ static int proc_readfdinfo(struct file * + const struct inode_operations proc_fdinfo_inode_operations = { + .lookup = proc_lookupfdinfo, + .permission = proc_fdinfo_permission, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + const struct file_operations proc_fdinfo_operations = { +--- a/fs/proc/internal.h ++++ b/fs/proc/internal.h +@@ -183,8 +183,8 @@ extern int proc_pid_statm(struct seq_fil + extern const struct dentry_operations pid_dentry_operations; + extern int pid_getattr(struct user_namespace *, const struct path *, + struct kstat *, u32, unsigned int); +-extern int proc_setattr(struct user_namespace *, struct dentry *, +- struct iattr *); ++int proc_nochmod_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, ++ struct iattr *attr); + extern void proc_pid_evict_inode(struct proc_inode *); + extern struct inode *proc_pid_make_inode(struct super_block *, struct task_struct *, umode_t); + extern void pid_update_inode(struct task_struct *, struct inode *); +--- a/fs/proc/namespaces.c ++++ b/fs/proc/namespaces.c +@@ -92,7 +92,7 @@ static int proc_ns_readlink(struct dentr + static const struct inode_operations proc_ns_link_inode_operations = { + .readlink = proc_ns_readlink, + .get_link = proc_ns_get_link, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static struct dentry *proc_ns_instantiate(struct dentry *dentry, +@@ -179,5 +179,5 @@ out_no_task: + const struct inode_operations proc_ns_dir_inode_operations = { + .lookup = proc_ns_dir_lookup, + .getattr = pid_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; +--- a/fs/proc/proc_net.c ++++ b/fs/proc/proc_net.c +@@ -321,7 +321,7 @@ static int proc_tgid_net_getattr(struct + const struct inode_operations proc_net_inode_operations = { + .lookup = proc_tgid_net_lookup, + .getattr = proc_tgid_net_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static int proc_tgid_net_readdir(struct file *file, struct dir_context *ctx) diff --git a/queue-6.1/proc-use-generic-setattr-for-proc-pid-net.patch b/queue-6.1/proc-use-generic-setattr-for-proc-pid-net.patch new file mode 100644 index 0000000000..da1c43964c --- /dev/null +++ b/queue-6.1/proc-use-generic-setattr-for-proc-pid-net.patch @@ -0,0 +1,46 @@ +From stable+bounces-277025-greg=kroah.com@vger.kernel.org Fri Jul 17 13:43:01 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 07:18:19 -0400 +Subject: proc: use generic setattr() for /proc/$PID/net +To: stable@vger.kernel.org +Cc: "Thomas Weißschuh" , "Zhangjin Wu" , "Willy Tarreau" , "Christian Brauner" , "Sasha Levin" +Message-ID: <20260717111823.1573608-1-sashal@kernel.org> + +From: Thomas Weißschuh + +[ Upstream commit 18e66ae67673f2c8df6f02428798b1355691f2a9 ] + +All other files in /proc/$PID/ use proc_setattr(). + +Not using it allows the usage of chmod() on /proc/$PID/net, even on +other processes owned by the same user. +The same would probably also be true for other attributes to be changed. + +As this technically represents an ABI change it is not marked for +stable so any unlikely regressions are caught during a full release cycle. + +Fixes: e9720acd728a ("[NET]: Make /proc/net a symlink on /proc/self/net (v3)") +Signed-off-by: Thomas Weißschuh +Link: https://lore.kernel.org/lkml/d0d111ef-edae-4760-83fb-36db84278da1@t-8ch.de/ +Fixes: b4844fa0bdb4 ("selftests/nolibc: implement a few tests for various syscalls") +Tested-by: Zhangjin Wu +Signed-off-by: Willy Tarreau +Message-Id: <20230624-proc-net-setattr-v1-2-73176812adee@weissschuh.net> +Signed-off-by: Christian Brauner +Stable-dep-of: 6255da28d4bb ("proc: protect ptrace_may_access() with exec_update_lock (FD links)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/proc_net.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/fs/proc/proc_net.c ++++ b/fs/proc/proc_net.c +@@ -321,6 +321,7 @@ static int proc_tgid_net_getattr(struct + const struct inode_operations proc_net_inode_operations = { + .lookup = proc_tgid_net_lookup, + .getattr = proc_tgid_net_getattr, ++ .setattr = proc_setattr, + }; + + static int proc_tgid_net_readdir(struct file *file, struct dir_context *ctx) diff --git a/queue-6.1/regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch b/queue-6.1/regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch new file mode 100644 index 0000000000..38b8daa1b9 --- /dev/null +++ b/queue-6.1/regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch @@ -0,0 +1,45 @@ +From stable+bounces-278319-greg=kroah.com@vger.kernel.org Tue Jul 21 03:28:42 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:28:14 -0400 +Subject: regulator: scmi: fix of_node refcount leak in scmi_regulator_probe() +To: stable@vger.kernel.org +Cc: Wentao Liang , Mark Brown , Sasha Levin +Message-ID: <20260721012814.3459151-2-sashal@kernel.org> + +From: Wentao Liang + +[ Upstream commit fa11039d6cdff84584a3ef8cc1f5e1b56e045da2 ] + +scmi_regulator_probe() calls of_find_node_by_name() which takes a +reference on the returned device node. On the error path where +process_scmi_regulator_of_node() fails, the function returns without +calling of_node_put() on the child node, leaking the reference. + +Add of_node_put(np) on the error path to properly release the +reference. + +Cc: stable@vger.kernel.org +Fixes: 0fbeae70ee7c ("regulator: add SCMI driver") +Signed-off-by: Wentao Liang +Link: https://patch.msgid.link/20260527104850.872415-1-vulab@iscas.ac.cn +Signed-off-by: Mark Brown +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/regulator/scmi-regulator.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +--- a/drivers/regulator/scmi-regulator.c ++++ b/drivers/regulator/scmi-regulator.c +@@ -349,8 +349,10 @@ static int scmi_regulator_probe(struct s + for_each_child_of_node_scoped(np, child) { + ret = process_scmi_regulator_of_node(sdev, ph, child, rinfo); + /* abort on any mem issue */ +- if (ret == -ENOMEM) ++ if (ret == -ENOMEM) { ++ of_node_put(np); + return ret; ++ } + } + of_node_put(np); + /* diff --git a/queue-6.1/regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch b/queue-6.1/regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch new file mode 100644 index 0000000000..957b04c9f6 --- /dev/null +++ b/queue-6.1/regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch @@ -0,0 +1,52 @@ +From stable+bounces-278318-greg=kroah.com@vger.kernel.org Tue Jul 21 03:28:38 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:28:13 -0400 +Subject: regulator: scmi: Simplify with scoped for each OF child loop +To: stable@vger.kernel.org +Cc: Krzysztof Kozlowski , Mark Brown , Sasha Levin +Message-ID: <20260721012814.3459151-1-sashal@kernel.org> + +From: Krzysztof Kozlowski + +[ Upstream commit 99cf5db9cdd39136fd5dbd10bda833aa0f870452 ] + +Use scoped for_each_available_child_of_node_scoped() when iterating over +device nodes to make code a bit simpler. + +Signed-off-by: Krzysztof Kozlowski +Link: https://patch.msgid.link/20240814-cleanup-h-of-node-put-regulator-v1-7-87151088b883@linaro.org +Signed-off-by: Mark Brown +Stable-dep-of: fa11039d6cdf ("regulator: scmi: fix of_node refcount leak in scmi_regulator_probe()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/regulator/scmi-regulator.c | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +--- a/drivers/regulator/scmi-regulator.c ++++ b/drivers/regulator/scmi-regulator.c +@@ -298,7 +298,7 @@ static int process_scmi_regulator_of_nod + static int scmi_regulator_probe(struct scmi_device *sdev) + { + int d, ret, num_doms; +- struct device_node *np, *child; ++ struct device_node *np; + const struct scmi_handle *handle = sdev->handle; + struct scmi_regulator_info *rinfo; + struct scmi_protocol_handle *ph; +@@ -346,13 +346,11 @@ static int scmi_regulator_probe(struct s + */ + of_node_get(handle->dev->of_node); + np = of_find_node_by_name(handle->dev->of_node, "regulators"); +- for_each_child_of_node(np, child) { ++ for_each_child_of_node_scoped(np, child) { + ret = process_scmi_regulator_of_node(sdev, ph, child, rinfo); + /* abort on any mem issue */ +- if (ret == -ENOMEM) { +- of_node_put(child); ++ if (ret == -ENOMEM) + return ret; +- } + } + of_node_put(np); + /* diff --git a/queue-6.1/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch b/queue-6.1/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch new file mode 100644 index 0000000000..bdaf207082 --- /dev/null +++ b/queue-6.1/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch @@ -0,0 +1,56 @@ +From stable+bounces-277096-greg=kroah.com@vger.kernel.org Fri Jul 17 17:00:41 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 11:00:06 -0400 +Subject: seqlock: Change do_task_stat() to use scoped_seqlock_read() +To: stable@vger.kernel.org +Cc: Oleg Nesterov , "Peter Zijlstra (Intel)" , Sasha Levin +Message-ID: <20260717150007.1930653-2-sashal@kernel.org> + +From: Oleg Nesterov + +[ Upstream commit b76f72bea2c601afec81829ea427fc0d20f83216 ] + +To simplify the code and make it more readable. + +[peterz: change to new interface] +Signed-off-by: Oleg Nesterov +Signed-off-by: Peter Zijlstra (Intel) +Stable-dep-of: 6650527444da ("proc: protect ptrace_may_access() with exec_update_lock (part 1)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/array.c | 9 ++------- + 1 file changed, 2 insertions(+), 7 deletions(-) + +--- a/fs/proc/array.c ++++ b/fs/proc/array.c +@@ -473,7 +473,6 @@ static int do_task_stat(struct seq_file + unsigned long flags; + int exit_code = task->exit_code; + struct signal_struct *sig = task->signal; +- unsigned int seq = 1; + + state = *get_task_state(task); + vsize = eip = esp = 0; +@@ -530,10 +529,7 @@ static int do_task_stat(struct seq_file + if (permitted && (!whole || num_threads < 2)) + wchan = !task_is_running(task); + +- do { +- seq++; /* 2 on the 1st/lockless path, otherwise odd */ +- flags = read_seqbegin_or_lock_irqsave(&sig->stats_lock, &seq); +- ++ scoped_seqlock_read (&sig->stats_lock, ss_lock_irqsave) { + cmin_flt = sig->cmin_flt; + cmaj_flt = sig->cmaj_flt; + cutime = sig->cutime; +@@ -555,8 +551,7 @@ static int do_task_stat(struct seq_file + } + rcu_read_unlock(); + } +- } while (need_seqretry(&sig->stats_lock, seq)); +- done_seqretry_irqrestore(&sig->stats_lock, seq, flags); ++ } + + if (whole) { + thread_group_cputime_adjusted(task, &utime, &stime); diff --git a/queue-6.1/seqlock-introduce-scoped_seqlock_read.patch b/queue-6.1/seqlock-introduce-scoped_seqlock_read.patch new file mode 100644 index 0000000000..63fb44b6f8 --- /dev/null +++ b/queue-6.1/seqlock-introduce-scoped_seqlock_read.patch @@ -0,0 +1,164 @@ +From stable+bounces-277095-greg=kroah.com@vger.kernel.org Fri Jul 17 17:02:14 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 11:00:05 -0400 +Subject: seqlock: Introduce scoped_seqlock_read() +To: stable@vger.kernel.org +Cc: Peter Zijlstra , Oleg Nesterov , Sasha Levin +Message-ID: <20260717150007.1930653-1-sashal@kernel.org> + +From: Peter Zijlstra + +[ 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 +Signed-off-by: Peter Zijlstra (Intel) +Stable-dep-of: 6650527444da ("proc: protect ptrace_may_access() with exec_update_lock (part 1)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/seqlock.h | 111 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 111 insertions(+) + +--- a/include/linux/seqlock.h ++++ b/include/linux/seqlock.h +@@ -1266,4 +1266,115 @@ done_seqretry_irqrestore(seqlock_t *lock + if (seq & 1) + read_sequnlock_excl_irqrestore(lock, flags); + } ++ ++enum ss_state { ++ ss_done = 0, ++ ss_lock, ++ ss_lock_irqsave, ++ ss_lockless, ++}; ++ ++struct ss_tmp { ++ enum ss_state state; ++ unsigned long data; ++ spinlock_t *lock; ++ spinlock_t *lock_irqsave; ++}; ++ ++static inline void __scoped_seqlock_cleanup(struct ss_tmp *sst) ++{ ++ if (sst->lock) ++ spin_unlock(sst->lock); ++ if (sst->lock_irqsave) ++ spin_unlock_irqrestore(sst->lock_irqsave, sst->data); ++} ++ ++extern void __scoped_seqlock_invalid_target(void); ++ ++#if defined(CONFIG_CC_IS_GCC) && CONFIG_GCC_VERSION < 90000 ++/* ++ * For some reason some GCC-8 architectures (nios2, alpha) have trouble ++ * determining that the ss_done state is impossible in __scoped_seqlock_next() ++ * below. ++ */ ++static inline void __scoped_seqlock_bug(void) { } ++#else ++/* ++ * Canary for compiler optimization -- if the compiler doesn't realize this is ++ * an impossible state, it very likely generates sub-optimal code here. ++ */ ++extern void __scoped_seqlock_bug(void); ++#endif ++ ++static inline void ++__scoped_seqlock_next(struct ss_tmp *sst, seqlock_t *lock, enum ss_state target) ++{ ++ switch (sst->state) { ++ case ss_done: ++ __scoped_seqlock_bug(); ++ return; ++ ++ case ss_lock: ++ case ss_lock_irqsave: ++ sst->state = ss_done; ++ return; ++ ++ case ss_lockless: ++ if (!read_seqretry(lock, sst->data)) { ++ sst->state = ss_done; ++ return; ++ } ++ break; ++ } ++ ++ switch (target) { ++ case ss_done: ++ __scoped_seqlock_invalid_target(); ++ return; ++ ++ case ss_lock: ++ sst->lock = &lock->lock; ++ spin_lock(sst->lock); ++ sst->state = ss_lock; ++ return; ++ ++ case ss_lock_irqsave: ++ sst->lock_irqsave = &lock->lock; ++ spin_lock_irqsave(sst->lock_irqsave, sst->data); ++ sst->state = ss_lock_irqsave; ++ return; ++ ++ case ss_lockless: ++ sst->data = read_seqbegin(lock); ++ return; ++ } ++} ++ ++#define __scoped_seqlock_read(_seqlock, _target, _s) \ ++ for (struct ss_tmp _s __cleanup(__scoped_seqlock_cleanup) = \ ++ { .state = ss_lockless, .data = read_seqbegin(_seqlock) }; \ ++ _s.state != ss_done; \ ++ __scoped_seqlock_next(&_s, _seqlock, _target)) ++ ++/** ++ * scoped_seqlock_read (lock, ss_state) - execute the read side critical ++ * section without manual sequence ++ * counter handling or calls to other ++ * helpers ++ * @lock: pointer to seqlock_t protecting the data ++ * @ss_state: one of {ss_lock, ss_lock_irqsave, ss_lockless} indicating ++ * the type of critical read section ++ * ++ * Example: ++ * ++ * scoped_seqlock_read (&lock, ss_lock) { ++ * // read-side critical section ++ * } ++ * ++ * Starts with a lockess pass first. If it fails, restarts the critical ++ * section with the lock held. ++ */ ++#define scoped_seqlock_read(_seqlock, _target) \ ++ __scoped_seqlock_read(_seqlock, _target, __UNIQUE_ID(seqlock)) ++ + #endif /* __LINUX_SEQLOCK_H */ diff --git a/queue-6.1/serial-8250_mid-disable-dma-for-selected-platforms.patch b/queue-6.1/serial-8250_mid-disable-dma-for-selected-platforms.patch new file mode 100644 index 0000000000..bd2fe6b7a1 --- /dev/null +++ b/queue-6.1/serial-8250_mid-disable-dma-for-selected-platforms.patch @@ -0,0 +1,64 @@ +From stable+bounces-277228-greg=kroah.com@vger.kernel.org Sat Jul 18 02:51:26 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 20:51:18 -0400 +Subject: serial: 8250_mid: Disable DMA for selected platforms +To: stable@vger.kernel.org +Cc: Andy Shevchenko , micas-opensource , stable , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260718005118.2484504-2-sashal@kernel.org> + +From: Andy Shevchenko + +[ Upstream commit b1b4efea05a56c0995e4702a86d6624b4fdff32f ] + +In accordance with Errata (specification updates) +HSUART May Stop Functioning when DMA is Active. + +- Denverton document #572409, rev 3.4, DNV60 +- Ice Lake Xeon D document #714070, ICXD65 +- Snowridge document #731931, SNR44 + +For a quick fix just disable the respective callbacks during the device probe. +Depending on the future development we might remove them completely. + +Reported-by: micas-opensource +Closes: https://lore.kernel.org/linux-serial/20250625031409.2404219-1-opensource@ruijie.com.cn/ +Fixes: 6ede6dcd87aa ("serial: 8250_mid: add support for DMA engine handling from UART MMIO") +Cc: stable +Signed-off-by: Andy Shevchenko +Link: https://patch.msgid.link/20260626094937.561776-1-andriy.shevchenko@linux.intel.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/tty/serial/8250/8250_mid.c | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +--- a/drivers/tty/serial/8250/8250_mid.c ++++ b/drivers/tty/serial/8250/8250_mid.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + + #include + +@@ -368,8 +369,16 @@ static const struct mid8250_board dnv_bo + .freq = 133333333, + .base_baud = 115200, + .bar = 1, +- .setup = dnv_setup, +- .exit = dnv_exit, ++ /* ++ * Errata: ++ * HSUART May Stop Functioning when DMA is Active. ++ * ++ * - Denverton document #572409, rev 3.4, DNV60 ++ * - Ice Lake Xeon D document #714070, ICXD65 ++ * - Snowridge document #731931, SNR44 ++ */ ++ .setup = PTR_IF(false, dnv_setup), ++ .exit = PTR_IF(false, dnv_exit), + }; + + static const struct pci_device_id pci_ids[] = { diff --git a/queue-6.1/serial-8250_mid-remove-8250_pci-usage.patch b/queue-6.1/serial-8250_mid-remove-8250_pci-usage.patch new file mode 100644 index 0000000000..dba9842f32 --- /dev/null +++ b/queue-6.1/serial-8250_mid-remove-8250_pci-usage.patch @@ -0,0 +1,120 @@ +From stable+bounces-277227-greg=kroah.com@vger.kernel.org Sat Jul 18 02:51:40 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 20:51:17 -0400 +Subject: serial: 8250_mid: Remove 8250_pci usage +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Greg Kroah-Hartman" , "Sasha Levin" +Message-ID: <20260718005118.2484504-1-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit a136abd7e7abe0f1247f8ffde6cc7c8ab09f985b ] + +8250_mid uses FL_*BASE* from linux/8250_pci.h and nothing else. The +code can be simplified by directly defining BARs within the driver +instead. + +Signed-off-by: Ilpo Järvinen +Link: https://lore.kernel.org/r/20230915094336.13278-1-ilpo.jarvinen@linux.intel.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: b1b4efea05a5 ("serial: 8250_mid: Disable DMA for selected platforms") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/tty/serial/8250/8250_mid.c | 18 +++++++----------- + 1 file changed, 7 insertions(+), 11 deletions(-) + +--- a/drivers/tty/serial/8250/8250_mid.c ++++ b/drivers/tty/serial/8250/8250_mid.c +@@ -12,7 +12,6 @@ + #include + + #include +-#include + + #include "8250.h" + +@@ -32,9 +31,9 @@ + struct mid8250; + + struct mid8250_board { +- unsigned int flags; + unsigned long freq; + unsigned int base_baud; ++ unsigned int bar; + int (*setup)(struct mid8250 *, struct uart_port *p); + void (*exit)(struct mid8250 *); + }; +@@ -169,7 +168,6 @@ static int dnv_setup(struct mid8250 *mid + { + struct hsu_dma_chip *chip = &mid->dma_chip; + struct pci_dev *pdev = to_pci_dev(p->dev); +- unsigned int bar = FL_GET_BASE(mid->board->flags); + int ret; + + pci_set_master(pdev); +@@ -183,7 +181,7 @@ static int dnv_setup(struct mid8250 *mid + chip->dev = &pdev->dev; + chip->irq = pci_irq_vector(pdev, 0); + chip->regs = p->membase; +- chip->length = pci_resource_len(pdev, bar); ++ chip->length = pci_resource_len(pdev, mid->board->bar); + chip->offset = DNV_DMA_CHAN_OFFSET; + + /* Falling back to PIO mode if DMA probing fails */ +@@ -291,7 +289,6 @@ static int mid8250_probe(struct pci_dev + { + struct uart_8250_port uart; + struct mid8250 *mid; +- unsigned int bar; + int ret; + + ret = pcim_enable_device(pdev); +@@ -303,7 +300,6 @@ static int mid8250_probe(struct pci_dev + return -ENOMEM; + + mid->board = (struct mid8250_board *)id->driver_data; +- bar = FL_GET_BASE(mid->board->flags); + + memset(&uart, 0, sizeof(struct uart_8250_port)); + +@@ -316,8 +312,8 @@ static int mid8250_probe(struct pci_dev + uart.port.flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE; + uart.port.set_termios = mid8250_set_termios; + +- uart.port.mapbase = pci_resource_start(pdev, bar); +- uart.port.membase = pcim_iomap(pdev, bar, 0); ++ uart.port.mapbase = pci_resource_start(pdev, mid->board->bar); ++ uart.port.membase = pcim_iomap(pdev, mid->board->bar, 0); + if (!uart.port.membase) + return -ENOMEM; + +@@ -353,25 +349,25 @@ static void mid8250_remove(struct pci_de + } + + static const struct mid8250_board pnw_board = { +- .flags = FL_BASE0, + .freq = 50000000, + .base_baud = 115200, ++ .bar = 0, + .setup = pnw_setup, + .exit = pnw_exit, + }; + + static const struct mid8250_board tng_board = { +- .flags = FL_BASE0, + .freq = 38400000, + .base_baud = 1843200, ++ .bar = 0, + .setup = tng_setup, + .exit = tng_exit, + }; + + static const struct mid8250_board dnv_board = { +- .flags = FL_BASE1, + .freq = 133333333, + .base_baud = 115200, ++ .bar = 1, + .setup = dnv_setup, + .exit = dnv_exit, + }; diff --git a/queue-6.1/series b/queue-6.1/series index adf514dfeb..2d6c5f1be7 100644 --- a/queue-6.1/series +++ b/queue-6.1/series @@ -936,3 +936,108 @@ mtd-rawnand-fsl_ifc-return-errors-for-failed-page-reads.patch mtd-rawnand-lpc32xx_mlc-fail-dma-transfers-on-timeout.patch mtd-rawnand-lpc32xx_slc-fail-dma-transfer-on-completion-timeout.patch perf-x86-amd-brs-fix-kernel-address-leakage.patch +acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch +iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch +iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch +iio-imu-inv_icm42600-make-timestamp-module-chip-independent.patch +iio-move-inv_icm42600-timestamp-module-in-common.patch +iio-make-invensense-timestamp-module-generic.patch +iio-imu-inv_mpu6050-use-the-common-inv_sensors-timestamp-module.patch +iio-invensense-remove-redundant-initialization-of-variable-period.patch +iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch +iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch +iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch +alsa-aoa-check-snd_ctl_new1-return-value.patch +bitops-make-bytes_to_bits-treewide-available.patch +iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch +vfio-pci-use-bitfield-for-struct-vfio_pci_core_device-flags.patch +vfio-pci-use-a-private-flag-to-prevent-power-state-change-with-vfs.patch +vfio-pci-latch-disable_idle_d3-per-device.patch +pci-altera-fix-resource-leaks-on-probe-failure.patch +vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch +pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch +staging-rtl8723bs-fix-indentation-issues.patch +staging-rtl8723bs-fix-space-issues.patch +staging-rtl8723bs-fix-heap-buffer-overflow-in-rtw_cfg80211_set_wpa_ie.patch +pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch +pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch +pci-mediatek-use-generic-macro-for-tpvperl-delay.patch +pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch +staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch +staging-rtl8723bs-fix-spaces-around-binary-operators.patch +staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch +crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch +pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch +pci-free-saved-list-without-holding-pci_bus_sem.patch +pci-fix-restoring-bars-on-bar-resize-rollback-path.patch +pci-add-kerneldoc-for-pci_resize_resource.patch +pci-move-resizable-bar-code-to-rebar.c.patch +pci-skip-resizable-bar-restore-on-read-error.patch +mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch +mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch +coresight-etb10-restore-atomic_t-for-shared-reading-state.patch +gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch +mm-shrinkers-fix-debugfs-file-permissions.patch +mm-shrinker-fix-null-pointer-dereference-in-debugfs.patch +netfilter-ebtables-use-vmalloc_array-to-improve-code.patch +netfilter-ebtables-zero-chainstack-array.patch +bluetooth-make-handle-of-hci_conn-be-unique.patch +bluetooth-remove-bt_hs.patch +bluetooth-l2cap-fix-slab-use-after-free-in-l2cap_connect.patch +bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch +bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch +bluetooth-l2cap-fix-not-tracking-outstanding-tx-ident.patch +bluetooth-l2cap-fix-deadlock-in-l2cap_conn_del.patch +bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch +bluetooth-hci_core-enable-buffer-flow-control-for-sco-esco.patch +cifs-handle-when-server-stops-supporting-multichannel.patch +cifs-after-disabling-multichannel-mark-tcon-for-reconnect.patch +cifs-fix-reacquisition-of-volume-cookie-on-still-live-connection.patch +cifs-add-tracing-for-the-cifs_tcon-struct-refcounting.patch +smb-client-resolve-swn-tcon-from-live-registrations.patch +ksmbd-use-opener-credentials-for-fsctl-mutations.patch +smb-move-some-duplicate-definitions-to-common-cifsglob.h.patch +smb-move-smb_version_values-to-common-smbglob.h.patch +ksmbd-fix-use-after-free-in-__ksmbd_close_fd-via-durable-scavenger.patch +ksmbd-fix-mechtoken-leak-when-spnego-decode-fails-after-token-alloc.patch +ksmbd-destroy-async_ida-in-ksmbd_conn_free.patch +ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch +ksmbd-track-the-connection-owning-a-byte-range-lock.patch +writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch +writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch +perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch +proc-use-generic-setattr-for-proc-pid-net.patch +proc-move-fdinfo-ptrace_mode_read-check-into-the-inode-.permission-operation.patch +proc-rename-proc_setattr-to-proc_nochmod_setattr.patch +proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch +hid-add-haptics-page-defines.patch +hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch +seqlock-introduce-scoped_seqlock_read.patch +seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch +proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch +treewide-switch-rename-to-timer_delete.patch +hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch +serial-8250_mid-remove-8250_pci-usage.patch +serial-8250_mid-disable-dma-for-selected-platforms.patch +hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch +hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch +bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch +bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch +usb-iowarrior-remove-inherent-race-with-minor-number.patch +usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch +usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch +usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch +crypto-atmel-sha204a-mark-of-related-data-as-maybe-unused.patch +crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch +crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch +nvmet-remove-superfluous-initialization.patch +usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch +crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch +btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch +crypto-qat-validate-rsa-crt-component-lengths.patch +audit-add-audit_log_nf_skb-helper-function.patch +audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch +regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch +regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch +btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch +btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch diff --git a/queue-6.1/smb-client-resolve-swn-tcon-from-live-registrations.patch b/queue-6.1/smb-client-resolve-swn-tcon-from-live-registrations.patch new file mode 100644 index 0000000000..fd01f23c5e --- /dev/null +++ b/queue-6.1/smb-client-resolve-swn-tcon-from-live-registrations.patch @@ -0,0 +1,604 @@ +From stable+bounces-275233-greg=kroah.com@vger.kernel.org Thu Jul 16 12:24:32 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 06:18:24 -0400 +Subject: smb: client: resolve SWN tcon from live registrations +To: stable@vger.kernel.org +Cc: Michael Bommarito , Steve French , Sasha Levin +Message-ID: <20260716101824.1461870-5-sashal@kernel.org> + +From: Michael Bommarito + +[ 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 +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Steve French +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/cifs_swn.c | 314 ++++++++++++++++++++++++++++++++++++++--------- + fs/smb/client/trace.h | 2 + 2 files changed, 262 insertions(+), 54 deletions(-) + +--- a/fs/smb/client/cifs_swn.c ++++ b/fs/smb/client/cifs_swn.c +@@ -28,10 +28,54 @@ struct cifs_swn_reg { + bool net_name_notify; + bool share_name_notify; + bool ip_notify; ++}; + +- struct cifs_tcon *tcon; ++struct cifs_swn_reg_info { ++ int id; ++ unsigned int ref_count; ++ const char *net_name; ++ const char *share_name; ++ bool net_name_notify; ++ bool share_name_notify; ++ bool ip_notify; + }; + ++static void cifs_swn_snapshot_reg(struct cifs_swn_reg *swnreg, ++ struct cifs_swn_reg_info *info) ++{ ++ info->id = swnreg->id; ++ info->ref_count = kref_read(&swnreg->ref_count); ++ info->net_name = swnreg->net_name; ++ info->share_name = swnreg->share_name; ++ info->net_name_notify = swnreg->net_name_notify; ++ info->share_name_notify = swnreg->share_name_notify; ++ info->ip_notify = swnreg->ip_notify; ++} ++ ++static int cifs_swn_dup_reg(struct cifs_swn_reg *swnreg, ++ struct cifs_swn_reg_info *info) ++{ ++ cifs_swn_snapshot_reg(swnreg, info); ++ ++ info->net_name = kstrdup(swnreg->net_name, GFP_KERNEL); ++ if (!info->net_name) ++ return -ENOMEM; ++ ++ info->share_name = kstrdup(swnreg->share_name, GFP_KERNEL); ++ if (!info->share_name) { ++ kfree(info->net_name); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static void cifs_swn_free_reg_info(struct cifs_swn_reg_info *info) ++{ ++ kfree(info->net_name); ++ kfree(info->share_name); ++} ++ + static int cifs_swn_auth_info_krb(struct cifs_tcon *tcon, struct sk_buff *skb) + { + int ret; +@@ -73,7 +117,8 @@ static int cifs_swn_auth_info_ntlm(struc + * The authentication information to connect to the witness service is bundled + * into the message. + */ +-static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg) ++static int cifs_swn_send_register_message(struct cifs_swn_reg_info *swnreg, ++ struct cifs_tcon *tcon) + { + struct sk_buff *skb; + struct genlmsghdr *hdr; +@@ -111,10 +156,10 @@ static int cifs_swn_send_register_messag + * told to switch to it (client move message). In these cases we unregister from the + * server address and register to the new address when we receive the notification. + */ +- if (swnreg->tcon->ses->server->use_swn_dstaddr) +- addr = &swnreg->tcon->ses->server->swn_dstaddr; ++ if (tcon->ses->server->use_swn_dstaddr) ++ addr = &tcon->ses->server->swn_dstaddr; + else +- addr = &swnreg->tcon->ses->server->dstaddr; ++ addr = &tcon->ses->server->dstaddr; + + ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage), addr); + if (ret < 0) +@@ -138,10 +183,10 @@ static int cifs_swn_send_register_messag + goto nlmsg_fail; + } + +- authtype = cifs_select_sectype(swnreg->tcon->ses->server, swnreg->tcon->ses->sectype); ++ authtype = cifs_select_sectype(tcon->ses->server, tcon->ses->sectype); + switch (authtype) { + case Kerberos: +- ret = cifs_swn_auth_info_krb(swnreg->tcon, skb); ++ ret = cifs_swn_auth_info_krb(tcon, skb); + if (ret < 0) { + cifs_dbg(VFS, "%s: Failed to get kerberos auth info: %d\n", __func__, ret); + goto nlmsg_fail; +@@ -149,7 +194,7 @@ static int cifs_swn_send_register_messag + break; + case NTLMv2: + case RawNTLMSSP: +- ret = cifs_swn_auth_info_ntlm(swnreg->tcon, skb); ++ ret = cifs_swn_auth_info_ntlm(tcon, skb); + if (ret < 0) { + cifs_dbg(VFS, "%s: Failed to get NTLM auth info: %d\n", __func__, ret); + goto nlmsg_fail; +@@ -179,7 +224,8 @@ fail: + /* + * Sends an uregister message to the userspace daemon based on the registration + */ +-static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg) ++static int cifs_swn_send_unregister_message(struct cifs_swn_reg_info *swnreg, ++ struct cifs_tcon *tcon) + { + struct sk_buff *skb; + struct genlmsghdr *hdr; +@@ -208,7 +254,7 @@ static int cifs_swn_send_unregister_mess + goto nlmsg_fail; + + ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage), +- &swnreg->tcon->ses->server->dstaddr); ++ &tcon->ses->server->dstaddr); + if (ret < 0) + goto nlmsg_fail; + +@@ -245,6 +291,88 @@ nlmsg_fail: + } + + /* ++ * Allocation-free mirror of extract_hostname() + extract_sharename() from ++ * fs/smb/client/unc.c. Those helpers kmalloc(GFP_KERNEL); this runs under ++ * cifs_tcp_ses_lock and tcon->tc_lock, both spinlocks, so we mirror their ++ * parsing in place against the caller's stable net_name/share_name strings. ++ * Keep in sync with unc.c. ++ */ ++static bool cifs_swn_tcon_matches(struct cifs_tcon *tcon, ++ const char *net_name, ++ const char *share_name) ++{ ++ const char *unc = tcon->tree_name; ++ const char *host, *share, *delim; ++ size_t host_len, share_len; ++ ++ if (!tcon->use_witness) ++ return false; ++ ++ /* extract_hostname: require strlen(unc) >= 3 */ ++ if (strnlen(unc, 3) < 3) ++ return false; ++ /* extract_hostname: skip all leading '\' characters */ ++ for (host = unc; *host == '\\'; host++) ++ ; ++ if (!*host) ++ return false; ++ delim = strchr(host, '\\'); ++ if (!delim) ++ return false; ++ host_len = delim - host; ++ if (strlen(net_name) != host_len || ++ strncasecmp(host, net_name, host_len)) ++ return false; ++ ++ /* extract_sharename: start at unc + 2, then first '\' onward */ ++ share = unc + 2; ++ delim = strchr(share, '\\'); ++ if (!delim) ++ return false; ++ share = delim + 1; ++ share_len = strlen(share); ++ ++ return strlen(share_name) == share_len && ++ !strncasecmp(share, share_name, share_len); ++} ++ ++/* ++ * One SWN registration id represents one net/share name pair. Multiple ++ * mounted tcons can therefore share the id. Pick a live representative at ++ * use time instead of caching the first tcon pointer in the registration. ++ */ ++static struct cifs_tcon *cifs_swn_get_tcon(struct cifs_swn_reg_info *swnreg) ++{ ++ struct TCP_Server_Info *server; ++ struct cifs_ses *ses; ++ struct cifs_tcon *tcon; ++ ++ spin_lock(&cifs_tcp_ses_lock); ++ list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) { ++ list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { ++ list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { ++ spin_lock(&tcon->tc_lock); ++ if (tcon->status == TID_EXITING || ++ !cifs_swn_tcon_matches(tcon, swnreg->net_name, ++ swnreg->share_name)) { ++ spin_unlock(&tcon->tc_lock); ++ continue; ++ } ++ ++tcon->tc_count; ++ trace_smb3_tcon_ref(tcon->debug_id, ++ tcon->tc_count, ++ netfs_trace_tcon_ref_get_swn_notify); ++ spin_unlock(&tcon->tc_lock); ++ spin_unlock(&cifs_tcp_ses_lock); ++ return tcon; ++ } ++ } ++ } ++ spin_unlock(&cifs_tcp_ses_lock); ++ return NULL; ++} ++ ++/* + * Try to find a matching registration for the tcon's server name and share name. + * Calls to this function must be protected by cifs_swnreg_idr_mutex. + * TODO Try to avoid memory allocations +@@ -350,8 +478,6 @@ static struct cifs_swn_reg *cifs_get_swn + reg->net_name_notify = true; + reg->share_name_notify = true; + reg->ip_notify = (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT); +- +- reg->tcon = tcon; + unlock: + mutex_unlock(&cifs_swnreg_idr_mutex); + +@@ -371,11 +497,6 @@ fail_unlock: + static void cifs_swn_reg_release(struct kref *ref) + { + struct cifs_swn_reg *swnreg = container_of(ref, struct cifs_swn_reg, ref_count); +- int ret; +- +- ret = cifs_swn_send_unregister_message(swnreg); +- if (ret < 0) +- cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n", __func__, ret); + + idr_remove(&cifs_swnreg_idr, swnreg->id); + kfree(swnreg->net_name); +@@ -383,23 +504,33 @@ static void cifs_swn_reg_release(struct + kfree(swnreg); + } + +-static void cifs_put_swn_reg(struct cifs_swn_reg *swnreg) ++static void cifs_put_swn_reg_locked(struct cifs_swn_reg *swnreg, ++ struct cifs_tcon *tcon) + { +- mutex_lock(&cifs_swnreg_idr_mutex); ++ if (kref_read(&swnreg->ref_count) == 1) { ++ struct cifs_swn_reg_info swnreg_info; ++ int ret; ++ ++ cifs_swn_snapshot_reg(swnreg, &swnreg_info); ++ ret = cifs_swn_send_unregister_message(&swnreg_info, tcon); ++ if (ret < 0) ++ cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n", ++ __func__, ret); ++ } ++ + kref_put(&swnreg->ref_count, cifs_swn_reg_release); +- mutex_unlock(&cifs_swnreg_idr_mutex); + } + +-static int cifs_swn_resource_state_changed(struct cifs_swn_reg *swnreg, const char *name, int state) ++static int cifs_swn_resource_state_changed(struct cifs_tcon *tcon, const char *name, int state) + { + switch (state) { + case CIFS_SWN_RESOURCE_STATE_UNAVAILABLE: + cifs_dbg(FYI, "%s: resource name '%s' become unavailable\n", __func__, name); +- cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true); ++ cifs_signal_cifsd_for_reconnect(tcon->ses->server, true); + break; + case CIFS_SWN_RESOURCE_STATE_AVAILABLE: + cifs_dbg(FYI, "%s: resource name '%s' become available\n", __func__, name); +- cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true); ++ cifs_signal_cifsd_for_reconnect(tcon->ses->server, true); + break; + case CIFS_SWN_RESOURCE_STATE_UNKNOWN: + cifs_dbg(FYI, "%s: resource name '%s' changed to unknown state\n", __func__, name); +@@ -505,7 +636,7 @@ unlock: + return ret; + } + +-static int cifs_swn_client_move(struct cifs_swn_reg *swnreg, struct sockaddr_storage *addr) ++static int cifs_swn_client_move(struct cifs_tcon *tcon, struct sockaddr_storage *addr) + { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr; + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr; +@@ -515,14 +646,17 @@ static int cifs_swn_client_move(struct c + else if (addr->ss_family == AF_INET6) + cifs_dbg(FYI, "%s: move to %pI6\n", __func__, &ipv6->sin6_addr); + +- return cifs_swn_reconnect(swnreg->tcon, addr); ++ return cifs_swn_reconnect(tcon, addr); + } + + int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info) + { + struct cifs_swn_reg *swnreg; ++ struct cifs_swn_reg_info swnreg_info; ++ struct cifs_tcon *tcon; + char name[256]; + int type; ++ int ret = 0; + + if (info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]) { + int swnreg_id; +@@ -530,21 +664,34 @@ int cifs_swn_notify(struct sk_buff *skb, + swnreg_id = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]); + mutex_lock(&cifs_swnreg_idr_mutex); + swnreg = idr_find(&cifs_swnreg_idr, swnreg_id); +- mutex_unlock(&cifs_swnreg_idr_mutex); + if (swnreg == NULL) { ++ mutex_unlock(&cifs_swnreg_idr_mutex); + cifs_dbg(FYI, "%s: registration id %d not found\n", __func__, swnreg_id); + return -EINVAL; + } ++ ret = cifs_swn_dup_reg(swnreg, &swnreg_info); ++ mutex_unlock(&cifs_swnreg_idr_mutex); ++ if (ret) ++ return ret; + } else { + cifs_dbg(FYI, "%s: missing registration id attribute\n", __func__); + return -EINVAL; + } + ++ tcon = cifs_swn_get_tcon(&swnreg_info); ++ if (!tcon) { ++ cifs_dbg(FYI, "%s: registration id %d has no live tcon\n", ++ __func__, swnreg_info.id); ++ ret = -ENODEV; ++ goto free_info; ++ } ++ + if (info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]) { + type = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]); + } else { + cifs_dbg(FYI, "%s: missing notification type attribute\n", __func__); +- return -EINVAL; ++ ret = -EINVAL; ++ goto out; + } + + switch (type) { +@@ -556,15 +703,18 @@ int cifs_swn_notify(struct sk_buff *skb, + sizeof(name)); + } else { + cifs_dbg(FYI, "%s: missing resource name attribute\n", __func__); +- return -EINVAL; ++ ret = -EINVAL; ++ goto out; + } + if (info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]) { + state = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]); + } else { + cifs_dbg(FYI, "%s: missing resource state attribute\n", __func__); +- return -EINVAL; ++ ret = -EINVAL; ++ goto out; + } +- return cifs_swn_resource_state_changed(swnreg, name, state); ++ ret = cifs_swn_resource_state_changed(tcon, name, state); ++ break; + } + case CIFS_SWN_NOTIFICATION_CLIENT_MOVE: { + struct sockaddr_storage addr; +@@ -573,28 +723,36 @@ int cifs_swn_notify(struct sk_buff *skb, + nla_memcpy(&addr, info->attrs[CIFS_GENL_ATTR_SWN_IP], sizeof(addr)); + } else { + cifs_dbg(FYI, "%s: missing IP address attribute\n", __func__); +- return -EINVAL; ++ ret = -EINVAL; ++ goto out; + } +- return cifs_swn_client_move(swnreg, &addr); ++ ret = cifs_swn_client_move(tcon, &addr); ++ break; + } + default: + cifs_dbg(FYI, "%s: unknown notification type %d\n", __func__, type); + break; + } + +- return 0; ++out: ++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify); ++free_info: ++ cifs_swn_free_reg_info(&swnreg_info); ++ return ret; + } + + int cifs_swn_register(struct cifs_tcon *tcon) + { + struct cifs_swn_reg *swnreg; ++ struct cifs_swn_reg_info swnreg_info; + int ret; + + swnreg = cifs_get_swn_reg(tcon); + if (IS_ERR(swnreg)) + return PTR_ERR(swnreg); + +- ret = cifs_swn_send_register_message(swnreg); ++ cifs_swn_snapshot_reg(swnreg, &swnreg_info); ++ ret = cifs_swn_send_register_message(&swnreg_info, tcon); + if (ret < 0) { + cifs_dbg(VFS, "%s: Failed to send swn register message: %d\n", __func__, ret); + /* Do not put the swnreg or return error, the echo task will retry */ +@@ -615,35 +773,68 @@ int cifs_swn_unregister(struct cifs_tcon + return PTR_ERR(swnreg); + } + ++ cifs_put_swn_reg_locked(swnreg, tcon); + mutex_unlock(&cifs_swnreg_idr_mutex); + +- cifs_put_swn_reg(swnreg); +- + return 0; + } + +-void cifs_swn_dump(struct seq_file *m) ++/* ++ * Snapshot one registration under cifs_swnreg_idr_mutex and return. Callers ++ * intentionally do the per-registration network/genlmsg work without the ++ * mutex held, both to keep the critical section short and to avoid nesting ++ * cifs_swnreg_idr_mutex inside the higher tc_lock when a live tcon is then ++ * pinned for the send. ++ */ ++static int cifs_swn_get_next_reg_info(int *id, struct cifs_swn_reg_info *info) + { + struct cifs_swn_reg *swnreg; ++ int ret = 0; ++ ++ mutex_lock(&cifs_swnreg_idr_mutex); ++ swnreg = idr_get_next(&cifs_swnreg_idr, id); ++ if (swnreg) { ++ ret = cifs_swn_dup_reg(swnreg, info); ++ if (!ret) { ++ *id = swnreg->id + 1; ++ ret = 1; ++ } ++ } ++ mutex_unlock(&cifs_swnreg_idr_mutex); ++ ++ return ret; ++} ++ ++void cifs_swn_dump(struct seq_file *m) ++{ ++ struct cifs_swn_reg_info swnreg_info; ++ struct cifs_tcon *tcon; + struct sockaddr_in *sa; + struct sockaddr_in6 *sa6; +- int id; ++ int id = 0; ++ int ret; + + seq_puts(m, "Witness registrations:"); + +- mutex_lock(&cifs_swnreg_idr_mutex); +- idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) { ++ while ((ret = cifs_swn_get_next_reg_info(&id, &swnreg_info)) > 0) { + seq_printf(m, "\nId: %u Refs: %u Network name: '%s'%s Share name: '%s'%s Ip address: ", +- id, kref_read(&swnreg->ref_count), +- swnreg->net_name, swnreg->net_name_notify ? "(y)" : "(n)", +- swnreg->share_name, swnreg->share_name_notify ? "(y)" : "(n)"); +- switch (swnreg->tcon->ses->server->dstaddr.ss_family) { ++ swnreg_info.id, swnreg_info.ref_count, ++ swnreg_info.net_name, swnreg_info.net_name_notify ? "(y)" : "(n)", ++ swnreg_info.share_name, swnreg_info.share_name_notify ? "(y)" : "(n)"); ++ ++ tcon = cifs_swn_get_tcon(&swnreg_info); ++ if (!tcon) { ++ seq_puts(m, "(no live tcon)"); ++ goto next; ++ } ++ ++ switch (tcon->ses->server->dstaddr.ss_family) { + case AF_INET: +- sa = (struct sockaddr_in *) &swnreg->tcon->ses->server->dstaddr; ++ sa = (struct sockaddr_in *)&tcon->ses->server->dstaddr; + seq_printf(m, "%pI4", &sa->sin_addr.s_addr); + break; + case AF_INET6: +- sa6 = (struct sockaddr_in6 *) &swnreg->tcon->ses->server->dstaddr; ++ sa6 = (struct sockaddr_in6 *)&tcon->ses->server->dstaddr; + seq_printf(m, "%pI6", &sa6->sin6_addr.s6_addr); + if (sa6->sin6_scope_id) + seq_printf(m, "%%%u", sa6->sin6_scope_id); +@@ -651,23 +842,38 @@ void cifs_swn_dump(struct seq_file *m) + default: + seq_puts(m, "(unknown)"); + } +- seq_printf(m, "%s", swnreg->ip_notify ? "(y)" : "(n)"); ++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify); ++next: ++ seq_printf(m, "%s", swnreg_info.ip_notify ? "(y)" : "(n)"); ++ cifs_swn_free_reg_info(&swnreg_info); + } +- mutex_unlock(&cifs_swnreg_idr_mutex); ++ if (ret < 0) ++ seq_printf(m, "\nFailed to snapshot witness registration: %d", ret); + seq_puts(m, "\n"); + } + + void cifs_swn_check(void) + { +- struct cifs_swn_reg *swnreg; +- int id; ++ struct cifs_swn_reg_info swnreg_info; ++ struct cifs_tcon *tcon; ++ int id = 0; + int ret; + +- mutex_lock(&cifs_swnreg_idr_mutex); +- idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) { +- ret = cifs_swn_send_register_message(swnreg); ++ while ((ret = cifs_swn_get_next_reg_info(&id, &swnreg_info)) > 0) { ++ tcon = cifs_swn_get_tcon(&swnreg_info); ++ if (!tcon) { ++ cifs_dbg(FYI, "%s: registration id %d has no live tcon\n", ++ __func__, swnreg_info.id); ++ goto free_info; ++ } ++ ++ ret = cifs_swn_send_register_message(&swnreg_info, tcon); + if (ret < 0) + cifs_dbg(FYI, "%s: Failed to send register message: %d\n", __func__, ret); ++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify); ++free_info: ++ cifs_swn_free_reg_info(&swnreg_info); + } +- mutex_unlock(&cifs_swnreg_idr_mutex); ++ if (ret < 0) ++ cifs_dbg(FYI, "%s: Failed to snapshot registration: %d\n", __func__, ret); + } +--- a/fs/smb/client/trace.h ++++ b/fs/smb/client/trace.h +@@ -32,6 +32,7 @@ + EM(netfs_trace_tcon_ref_get_find, "GET Find ") \ + EM(netfs_trace_tcon_ref_get_find_sess_tcon, "GET FndSes") \ + EM(netfs_trace_tcon_ref_get_reconnect_server, "GET Reconn") \ ++ EM(netfs_trace_tcon_ref_get_swn_notify, "GET SwnNot") \ + EM(netfs_trace_tcon_ref_new, "NEW ") \ + EM(netfs_trace_tcon_ref_new_ipc, "NEW Ipc ") \ + EM(netfs_trace_tcon_ref_new_reconnect_server, "NEW Reconn") \ +@@ -41,6 +42,7 @@ + EM(netfs_trace_tcon_ref_put_mnt_ctx, "PUT MntCtx") \ + EM(netfs_trace_tcon_ref_put_dfs_refer, "PUT DfsRfr") \ + EM(netfs_trace_tcon_ref_put_reconnect_server, "PUT Reconn") \ ++ EM(netfs_trace_tcon_ref_put_swn_notify, "PUT SwnNot") \ + EM(netfs_trace_tcon_ref_put_tlink, "PUT Tlink ") \ + EM(netfs_trace_tcon_ref_see_cancelled_close, "SEE Cn-Cls") \ + EM(netfs_trace_tcon_ref_see_fscache_collision, "SEE FV-CO!") \ diff --git a/queue-6.1/smb-move-smb_version_values-to-common-smbglob.h.patch b/queue-6.1/smb-move-smb_version_values-to-common-smbglob.h.patch new file mode 100644 index 0000000000..ffc84ce574 --- /dev/null +++ b/queue-6.1/smb-move-smb_version_values-to-common-smbglob.h.patch @@ -0,0 +1,319 @@ +From stable+bounces-276756-greg=kroah.com@vger.kernel.org Thu Jul 16 18:31:18 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 11:36:59 -0400 +Subject: smb: move smb_version_values to common/smbglob.h +To: stable@vger.kernel.org +Cc: ZhangGuoDong , ChenXiaoSong , Namjae Jeon , Steve French , Sasha Levin +Message-ID: <20260716153704.583869-2-sashal@kernel.org> + +From: ZhangGuoDong + +[ Upstream commit 34cf191bb6a349dc88ec2c4f6355fe006ac669e0 ] + +Merge the struct members of the server and the client: + + - req_capabilities: from client + - header_preamble_size: from client + - cap_unicode: from client + - capabilities: from server, rename to req_capabilities + - max_read_size: from server + - max_write_size: from server + - max_trans_size: from server + - max_credits: from server + - create_durable_size: from server + - create_durable_v2_size: from server + - create_mxac_size: from server + - create_disk_id_size: from server + - create_posix_size: from server + +Then move duplicate definitions to common header file. + +Co-developed-by: ChenXiaoSong +Signed-off-by: ChenXiaoSong +Signed-off-by: ZhangGuoDong +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/cifsglob.h | 21 --------------------- + fs/smb/common/cifsglob.h | 31 +++++++++++++++++++++++++++++++ + fs/smb/server/smb2misc.c | 2 +- + fs/smb/server/smb2ops.c | 28 ++++++++++++++-------------- + fs/smb/server/smb2pdu.c | 10 +++++----- + fs/smb/server/smb_common.h | 29 ----------------------------- + 6 files changed, 51 insertions(+), 70 deletions(-) + +--- a/fs/smb/client/cifsglob.h ++++ b/fs/smb/client/cifsglob.h +@@ -543,27 +543,6 @@ struct smb_version_operations { + void (*is_network_name_deleted)(char *buf, struct TCP_Server_Info *srv); + }; + +-struct smb_version_values { +- char *version_string; +- __u16 protocol_id; +- __u32 req_capabilities; +- __u32 large_lock_type; +- __u32 exclusive_lock_type; +- __u32 shared_lock_type; +- __u32 unlock_lock_type; +- size_t header_preamble_size; +- size_t header_size; +- size_t max_header_size; +- size_t read_rsp_size; +- __le16 lock_cmd; +- unsigned int cap_unix; +- unsigned int cap_nt_find; +- unsigned int cap_large_files; +- __u16 signing_enabled; +- __u16 signing_required; +- size_t create_lease_size; +-}; +- + #define HEADER_SIZE(server) (server->vals->header_size) + #define MAX_HEADER_SIZE(server) (server->vals->max_header_size) + #define HEADER_PREAMBLE_SIZE(server) (server->vals->header_preamble_size) +--- a/fs/smb/common/cifsglob.h ++++ b/fs/smb/common/cifsglob.h +@@ -9,6 +9,37 @@ + #ifndef _COMMON_CIFS_GLOB_H + #define _COMMON_CIFS_GLOB_H + ++struct smb_version_values { ++ char *version_string; ++ __u16 protocol_id; ++ __le16 lock_cmd; ++ __u32 req_capabilities; ++ __u32 max_read_size; ++ __u32 max_write_size; ++ __u32 max_trans_size; ++ __u32 max_credits; ++ __u32 large_lock_type; ++ __u32 exclusive_lock_type; ++ __u32 shared_lock_type; ++ __u32 unlock_lock_type; ++ size_t header_preamble_size; ++ size_t header_size; ++ size_t max_header_size; ++ size_t read_rsp_size; ++ unsigned int cap_unix; ++ unsigned int cap_nt_find; ++ unsigned int cap_large_files; ++ unsigned int cap_unicode; ++ __u16 signing_enabled; ++ __u16 signing_required; ++ size_t create_lease_size; ++ size_t create_durable_size; ++ size_t create_durable_v2_size; ++ size_t create_mxac_size; ++ size_t create_disk_id_size; ++ size_t create_posix_size; ++}; ++ + static inline void inc_rfc1001_len(void *buf, int count) + { + be32_add_cpu((__be32 *)buf, count); +--- a/fs/smb/server/smb2misc.c ++++ b/fs/smb/server/smb2misc.c +@@ -460,7 +460,7 @@ int ksmbd_smb2_check_message(struct ksmb + } + + validate_credit: +- if ((work->conn->vals->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) && ++ if ((work->conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) && + smb2_validate_credit_charge(work->conn, hdr)) + return 1; + +--- a/fs/smb/server/smb2ops.c ++++ b/fs/smb/server/smb2ops.c +@@ -15,7 +15,7 @@ + static struct smb_version_values smb21_server_values = { + .version_string = SMB21_VERSION_STRING, + .protocol_id = SMB21_PROT_ID, +- .capabilities = SMB2_GLOBAL_CAP_LARGE_MTU, ++ .req_capabilities = SMB2_GLOBAL_CAP_LARGE_MTU, + .max_read_size = SMB21_DEFAULT_IOSIZE, + .max_write_size = SMB21_DEFAULT_IOSIZE, + .max_trans_size = SMB21_DEFAULT_IOSIZE, +@@ -41,7 +41,7 @@ static struct smb_version_values smb21_s + static struct smb_version_values smb30_server_values = { + .version_string = SMB30_VERSION_STRING, + .protocol_id = SMB30_PROT_ID, +- .capabilities = SMB2_GLOBAL_CAP_LARGE_MTU, ++ .req_capabilities = SMB2_GLOBAL_CAP_LARGE_MTU, + .max_read_size = SMB3_DEFAULT_IOSIZE, + .max_write_size = SMB3_DEFAULT_IOSIZE, + .max_trans_size = SMB3_DEFAULT_TRANS_SIZE, +@@ -68,7 +68,7 @@ static struct smb_version_values smb30_s + static struct smb_version_values smb302_server_values = { + .version_string = SMB302_VERSION_STRING, + .protocol_id = SMB302_PROT_ID, +- .capabilities = SMB2_GLOBAL_CAP_LARGE_MTU, ++ .req_capabilities = SMB2_GLOBAL_CAP_LARGE_MTU, + .max_read_size = SMB3_DEFAULT_IOSIZE, + .max_write_size = SMB3_DEFAULT_IOSIZE, + .max_trans_size = SMB3_DEFAULT_TRANS_SIZE, +@@ -95,7 +95,7 @@ static struct smb_version_values smb302_ + static struct smb_version_values smb311_server_values = { + .version_string = SMB311_VERSION_STRING, + .protocol_id = SMB311_PROT_ID, +- .capabilities = SMB2_GLOBAL_CAP_LARGE_MTU, ++ .req_capabilities = SMB2_GLOBAL_CAP_LARGE_MTU, + .max_read_size = SMB3_DEFAULT_IOSIZE, + .max_write_size = SMB3_DEFAULT_IOSIZE, + .max_trans_size = SMB3_DEFAULT_TRANS_SIZE, +@@ -204,7 +204,7 @@ void init_smb2_1_server(struct ksmbd_con + conn->signing_algorithm = SIGNING_ALG_HMAC_SHA256_LE; + + if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES) +- conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING; ++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_LEASING; + } + + /** +@@ -221,20 +221,20 @@ void init_smb3_0_server(struct ksmbd_con + conn->signing_algorithm = SIGNING_ALG_AES_CMAC_LE; + + if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES) +- conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING | ++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_LEASING | + SMB2_GLOBAL_CAP_DIRECTORY_LEASING; + + if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION && + conn->cli_cap & SMB2_GLOBAL_CAP_ENCRYPTION) +- conn->vals->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION; ++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION; + + if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION || + (!(server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION_OFF) && + conn->cli_cap & SMB2_GLOBAL_CAP_ENCRYPTION)) +- conn->vals->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION; ++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION; + + if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) +- conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL; ++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL; + } + + /** +@@ -251,16 +251,16 @@ void init_smb3_02_server(struct ksmbd_co + conn->signing_algorithm = SIGNING_ALG_AES_CMAC_LE; + + if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES) +- conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING | ++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_LEASING | + SMB2_GLOBAL_CAP_DIRECTORY_LEASING; + + if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION || + (!(server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION_OFF) && + conn->cli_cap & SMB2_GLOBAL_CAP_ENCRYPTION)) +- conn->vals->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION; ++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION; + + if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) +- conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL; ++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL; + } + + /** +@@ -277,11 +277,11 @@ int init_smb3_11_server(struct ksmbd_con + conn->signing_algorithm = SIGNING_ALG_AES_CMAC_LE; + + if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_LEASES) +- conn->vals->capabilities |= SMB2_GLOBAL_CAP_LEASING | ++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_LEASING | + SMB2_GLOBAL_CAP_DIRECTORY_LEASING; + + if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) +- conn->vals->capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL; ++ conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL; + + INIT_LIST_HEAD(&conn->preauth_sess_table); + return 0; +--- a/fs/smb/server/smb2pdu.c ++++ b/fs/smb/server/smb2pdu.c +@@ -283,7 +283,7 @@ int init_smb2_neg_rsp(struct ksmbd_work + /* Not setting conn guid rsp->ServerGUID, as it + * not used by client for identifying connection + */ +- rsp->Capabilities = cpu_to_le32(conn->vals->capabilities); ++ rsp->Capabilities = cpu_to_le32(conn->vals->req_capabilities); + /* Default Max Message Size till SMB2.0, 64K*/ + rsp->MaxTransactSize = cpu_to_le32(conn->vals->max_trans_size); + rsp->MaxReadSize = cpu_to_le32(conn->vals->max_read_size); +@@ -975,7 +975,7 @@ bool smb3_encryption_negotiated(struct k + * SMB 3.0 and 3.0.2 dialects use the SMB2_GLOBAL_CAP_ENCRYPTION flag. + * SMB 3.1.1 uses the cipher_type field. + */ +- return (conn->vals->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) || ++ return (conn->vals->req_capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) || + conn->cipher_type; + } + +@@ -1228,7 +1228,7 @@ int smb2_handle_negotiate(struct ksmbd_w + rc = -EINVAL; + goto err_out; + } +- rsp->Capabilities = cpu_to_le32(conn->vals->capabilities); ++ rsp->Capabilities = cpu_to_le32(conn->vals->req_capabilities); + + /* For stats */ + conn->connection_type = conn->dialect; +@@ -3254,7 +3254,7 @@ int smb2_open(struct ksmbd_work *work) + share_ret = ksmbd_smb_check_shared_mode(fp->filp, fp); + if (!test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_OPLOCKS) || + (req_op_level == SMB2_OPLOCK_LEVEL_LEASE && +- !(conn->vals->capabilities & SMB2_GLOBAL_CAP_LEASING))) { ++ !(conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LEASING))) { + if (share_ret < 0 && !S_ISDIR(file_inode(fp->filp)->i_mode)) { + rc = share_ret; + goto err_out1; +@@ -7632,7 +7632,7 @@ static int fsctl_validate_negotiate_info + goto err_out; + } + +- neg_rsp->Capabilities = cpu_to_le32(conn->vals->capabilities); ++ neg_rsp->Capabilities = cpu_to_le32(conn->vals->req_capabilities); + memset(neg_rsp->Guid, 0, SMB2_CLIENT_GUID_SIZE); + neg_rsp->SecurityMode = cpu_to_le16(conn->srv_sec_mode); + neg_rsp->Dialect = cpu_to_le16(conn->dialect); +--- a/fs/smb/server/smb_common.h ++++ b/fs/smb/server/smb_common.h +@@ -336,35 +336,6 @@ struct file_id_full_dir_info { + char FileName[]; + } __packed; /* level 0x105 FF rsp data */ + +-struct smb_version_values { +- char *version_string; +- __u16 protocol_id; +- __le16 lock_cmd; +- __u32 capabilities; +- __u32 max_read_size; +- __u32 max_write_size; +- __u32 max_trans_size; +- __u32 max_credits; +- __u32 large_lock_type; +- __u32 exclusive_lock_type; +- __u32 shared_lock_type; +- __u32 unlock_lock_type; +- size_t header_size; +- size_t max_header_size; +- size_t read_rsp_size; +- unsigned int cap_unix; +- unsigned int cap_nt_find; +- unsigned int cap_large_files; +- __u16 signing_enabled; +- __u16 signing_required; +- size_t create_lease_size; +- size_t create_durable_size; +- size_t create_durable_v2_size; +- size_t create_mxac_size; +- size_t create_disk_id_size; +- size_t create_posix_size; +-}; +- + struct filesystem_posix_info { + /* For undefined recommended transfer size return -1 in that field */ + __le32 OptimalTransferSize; /* bsize on some os, iosize on other os */ diff --git a/queue-6.1/smb-move-some-duplicate-definitions-to-common-cifsglob.h.patch b/queue-6.1/smb-move-some-duplicate-definitions-to-common-cifsglob.h.patch new file mode 100644 index 0000000000..fa1bb12c4e --- /dev/null +++ b/queue-6.1/smb-move-some-duplicate-definitions-to-common-cifsglob.h.patch @@ -0,0 +1,162 @@ +From stable+bounces-276755-greg=kroah.com@vger.kernel.org Thu Jul 16 18:30:19 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 11:36:58 -0400 +Subject: smb: move some duplicate definitions to common/cifsglob.h +To: stable@vger.kernel.org +Cc: ZhangGuoDong , ChenXiaoSong , Steve French , Sasha Levin +Message-ID: <20260716153704.583869-1-sashal@kernel.org> + +From: ZhangGuoDong + +[ Upstream commit d877470b59910b5c50383d634dda3782386bba51 ] + +In order to maintain the code more easily, move duplicate definitions to +new common header file. + +Co-developed-by: ChenXiaoSong +Signed-off-by: ChenXiaoSong +Signed-off-by: ZhangGuoDong +Signed-off-by: Steve French +Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/cifsglob.h | 19 +------------------ + fs/smb/common/cifsglob.h | 30 ++++++++++++++++++++++++++++++ + fs/smb/server/smb_common.h | 14 +------------- + 3 files changed, 32 insertions(+), 31 deletions(-) + create mode 100644 fs/smb/common/cifsglob.h + +--- a/fs/smb/client/cifsglob.h ++++ b/fs/smb/client/cifsglob.h +@@ -26,6 +26,7 @@ + #include + #include + #include ++#include "../common/cifsglob.h" + #include "../common/smb2pdu.h" + #include "smb2pdu.h" + +@@ -606,12 +607,6 @@ get_rfc1002_length(void *buf) + return be32_to_cpu(*((__be32 *)buf)) & 0xffffff; + } + +-static inline void +-inc_rfc1001_len(void *buf, int count) +-{ +- be32_add_cpu((__be32 *)buf, count); +-} +- + struct TCP_Server_Info { + struct list_head tcp_ses_list; + struct list_head smb_ses_list; +@@ -913,8 +908,6 @@ compare_mid(__u16 mid, const struct smb_ + #define CIFS_MAX_RFC1002_WSIZE ((1<<17) - 1 - sizeof(WRITE_REQ) + 4) + #define CIFS_MAX_RFC1002_RSIZE ((1<<17) - 1 - sizeof(READ_RSP) + 4) + +-#define CIFS_DEFAULT_IOSIZE (1024 * 1024) +- + /* + * Windows only supports a max of 60kb reads and 65535 byte writes. Default to + * those values when posix extensions aren't in force. In actuality here, we +@@ -2022,30 +2015,20 @@ extern __u32 cifs_lock_secret; + extern mempool_t *cifs_mid_poolp; + + /* Operations for different SMB versions */ +-#define SMB1_VERSION_STRING "1.0" +-#define SMB20_VERSION_STRING "2.0" + #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY + extern struct smb_version_operations smb1_operations; + extern struct smb_version_values smb1_values; + extern struct smb_version_operations smb20_operations; + extern struct smb_version_values smb20_values; + #endif /* CIFS_ALLOW_INSECURE_LEGACY */ +-#define SMB21_VERSION_STRING "2.1" + extern struct smb_version_operations smb21_operations; + extern struct smb_version_values smb21_values; +-#define SMBDEFAULT_VERSION_STRING "default" + extern struct smb_version_values smbdefault_values; +-#define SMB3ANY_VERSION_STRING "3" + extern struct smb_version_values smb3any_values; +-#define SMB30_VERSION_STRING "3.0" + extern struct smb_version_operations smb30_operations; + extern struct smb_version_values smb30_values; +-#define SMB302_VERSION_STRING "3.02" +-#define ALT_SMB302_VERSION_STRING "3.0.2" + /*extern struct smb_version_operations smb302_operations;*/ /* not needed yet */ + extern struct smb_version_values smb302_values; +-#define SMB311_VERSION_STRING "3.1.1" +-#define ALT_SMB311_VERSION_STRING "3.11" + extern struct smb_version_operations smb311_operations; + extern struct smb_version_values smb311_values; + +--- /dev/null ++++ b/fs/smb/common/cifsglob.h +@@ -0,0 +1,30 @@ ++/* SPDX-License-Identifier: LGPL-2.1 */ ++/* ++ * ++ * Copyright (C) International Business Machines Corp., 2002,2008 ++ * Author(s): Steve French (sfrench@us.ibm.com) ++ * Jeremy Allison (jra@samba.org) ++ * ++ */ ++#ifndef _COMMON_CIFS_GLOB_H ++#define _COMMON_CIFS_GLOB_H ++ ++static inline void inc_rfc1001_len(void *buf, int count) ++{ ++ be32_add_cpu((__be32 *)buf, count); ++} ++ ++#define SMB1_VERSION_STRING "1.0" ++#define SMB20_VERSION_STRING "2.0" ++#define SMB21_VERSION_STRING "2.1" ++#define SMBDEFAULT_VERSION_STRING "default" ++#define SMB3ANY_VERSION_STRING "3" ++#define SMB30_VERSION_STRING "3.0" ++#define SMB302_VERSION_STRING "3.02" ++#define ALT_SMB302_VERSION_STRING "3.0.2" ++#define SMB311_VERSION_STRING "3.1.1" ++#define ALT_SMB311_VERSION_STRING "3.11" ++ ++#define CIFS_DEFAULT_IOSIZE (1024 * 1024) ++ ++#endif /* _COMMON_CIFS_GLOB_H */ +--- a/fs/smb/server/smb_common.h ++++ b/fs/smb/server/smb_common.h +@@ -10,6 +10,7 @@ + + #include "glob.h" + #include "nterr.h" ++#include "../common/cifsglob.h" + #include "../common/smb2pdu.h" + #include "smb2pdu.h" + +@@ -26,16 +27,8 @@ + #define SMB311_PROT 6 + #define BAD_PROT 0xFFFF + +-#define SMB1_VERSION_STRING "1.0" +-#define SMB20_VERSION_STRING "2.0" +-#define SMB21_VERSION_STRING "2.1" +-#define SMB30_VERSION_STRING "3.0" +-#define SMB302_VERSION_STRING "3.02" +-#define SMB311_VERSION_STRING "3.1.1" +- + #define SMB_ECHO_INTERVAL (60 * HZ) + +-#define CIFS_DEFAULT_IOSIZE (64 * 1024) + #define MAX_CIFS_SMALL_BUFFER_SIZE 448 /* big enough for most */ + + #define MAX_STREAM_PROT_LEN 0x00FFFFFF +@@ -462,9 +455,4 @@ static inline unsigned int get_rfc1002_l + { + return be32_to_cpu(*((__be32 *)buf)) & 0xffffff; + } +- +-static inline void inc_rfc1001_len(void *buf, int count) +-{ +- be32_add_cpu((__be32 *)buf, count); +-} + #endif /* __SMB_COMMON_H__ */ diff --git a/queue-6.1/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch b/queue-6.1/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch new file mode 100644 index 0000000000..45f4ef50fc --- /dev/null +++ b/queue-6.1/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch @@ -0,0 +1,56 @@ +From stable+bounces-274649-greg=kroah.com@vger.kernel.org Wed Jul 15 02:47:19 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:47:02 -0400 +Subject: staging: rtl8723bs: core: move constants to right side in comparison +To: stable@vger.kernel.org +Cc: William Hansen-Baird , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260715004704.3934423-1-sashal@kernel.org> + +From: William Hansen-Baird + +[ Upstream commit cf0f2680c30d4b438a5e84b73dc70be405936b54 ] + +Move constants to right side in if-statement conditions. + +Signed-off-by: William Hansen-Baird +Link: https://patch.msgid.link/20251224100329.762141-3-william.hansen.baird@gmail.com +Signed-off-by: Greg Kroah-Hartman +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 +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 4 ++-- + drivers/staging/rtl8723bs/core/rtw_security.c | 2 +- + 2 files changed, 3 insertions(+), 3 deletions(-) + +--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c ++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +@@ -1012,7 +1012,7 @@ static int rtw_get_cipher_info(struct wl + pbuf = rtw_get_wpa_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12); + + if (pbuf && (wpa_ielen > 0)) { +- if (_SUCCESS == rtw_parse_wpa_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x)) { ++ if (rtw_parse_wpa_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) { + pnetwork->bcn_info.pairwise_cipher = pairwise_cipher; + pnetwork->bcn_info.group_cipher = group_cipher; + pnetwork->bcn_info.is_8021x = is8021x; +@@ -1022,7 +1022,7 @@ static int rtw_get_cipher_info(struct wl + pbuf = rtw_get_wpa2_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12); + + if (pbuf && (wpa_ielen > 0)) { +- if (_SUCCESS == rtw_parse_wpa2_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x)) { ++ if (rtw_parse_wpa2_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) { + pnetwork->bcn_info.pairwise_cipher = pairwise_cipher; + pnetwork->bcn_info.group_cipher = group_cipher; + pnetwork->bcn_info.is_8021x = is8021x; +--- a/drivers/staging/rtl8723bs/core/rtw_security.c ++++ b/drivers/staging/rtl8723bs/core/rtw_security.c +@@ -1538,7 +1538,7 @@ void rtw_sec_restore_wep_key(struct adap + struct security_priv *securitypriv = &(adapter->securitypriv); + signed int keyid; + +- if ((_WEP40_ == securitypriv->dot11PrivacyAlgrthm) || (_WEP104_ == securitypriv->dot11PrivacyAlgrthm)) { ++ if ((securitypriv->dot11PrivacyAlgrthm == _WEP40_) || (securitypriv->dot11PrivacyAlgrthm == _WEP104_)) { + for (keyid = 0; keyid < 4; keyid++) { + if (securitypriv->key_mask & BIT(keyid)) { + if (keyid == securitypriv->dot11PrivacyKeyIndex) diff --git a/queue-6.1/staging-rtl8723bs-fix-heap-buffer-overflow-in-rtw_cfg80211_set_wpa_ie.patch b/queue-6.1/staging-rtl8723bs-fix-heap-buffer-overflow-in-rtw_cfg80211_set_wpa_ie.patch new file mode 100644 index 0000000000..1d3ee745be --- /dev/null +++ b/queue-6.1/staging-rtl8723bs-fix-heap-buffer-overflow-in-rtw_cfg80211_set_wpa_ie.patch @@ -0,0 +1,66 @@ +From stable+bounces-274554-greg=kroah.com@vger.kernel.org Tue Jul 14 22:04:41 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:41 -0400 +Subject: staging: rtl8723bs: fix heap buffer overflow in rtw_cfg80211_set_wpa_ie() +To: stable@vger.kernel.org +Cc: Alexandru Hossu , stable , Luka Gejak , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260714200241.3154071-3-sashal@kernel.org> + +From: Alexandru Hossu + +[ Upstream commit 5a752a616e756844388a1a45404db9fc29fec655 ] + +supplicant_ie is a 256-byte array in struct security_priv. The WPA and +WPA2 IE copy paths use: + + memcpy(padapter->securitypriv.supplicant_ie, &pwpa[0], wpa_ielen + 2); + +where wpa_ielen is the raw IE length field (u8, 0-255). When a local user +supplies a connect request via nl80211 with a crafted WPA IE of length 255, +wpa_ielen + 2 equals 257, overflowing the 256-byte buffer by one byte into +the adjacent last_mic_err_time field. + +rtw_parse_wpa_ie() does not prevent this: its length consistency check +compares *(wpa_ie+1) against (u8)(wpa_ie_len-2), which is (u8)(255) == 255 +when wpa_ie_len = 257, so the check passes silently. + +Add explicit bounds checks for both the WPA and WPA2 paths before the +memcpy, rejecting any IE whose total size (wpa_ielen + 2) exceeds the +supplicant_ie buffer. + +Fixes: 554c0a3abf21 ("staging: Add rtl8723bs sdio wifi driver") +Cc: stable +Reviewed-by: Luka Gejak +Signed-off-by: Alexandru Hossu +Link: https://patch.msgid.link/20260522004531.1038924-4-hossu.alexandru@gmail.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +--- a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c ++++ b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c +@@ -1482,6 +1482,10 @@ static int rtw_cfg80211_set_wpa_ie(struc + + pwpa = rtw_get_wpa_ie(buf, &wpa_ielen, ielen); + if (pwpa && wpa_ielen > 0) { ++ if (wpa_ielen + 2 > sizeof(padapter->securitypriv.supplicant_ie)) { ++ ret = -EINVAL; ++ goto exit; ++ } + if (rtw_parse_wpa_ie(pwpa, wpa_ielen + 2, &group_cipher, &pairwise_cipher, NULL) == _SUCCESS) { + padapter->securitypriv.dot11AuthAlgrthm = dot11AuthAlgrthm_8021X; + padapter->securitypriv.ndisauthtype = Ndis802_11AuthModeWPAPSK; +@@ -1491,6 +1495,10 @@ static int rtw_cfg80211_set_wpa_ie(struc + + pwpa2 = rtw_get_wpa2_ie(buf, &wpa2_ielen, ielen); + if (pwpa2 && wpa2_ielen > 0) { ++ if (wpa2_ielen + 2 > sizeof(padapter->securitypriv.supplicant_ie)) { ++ ret = -EINVAL; ++ goto exit; ++ } + if (rtw_parse_wpa2_ie(pwpa2, wpa2_ielen + 2, &group_cipher, &pairwise_cipher, NULL) == _SUCCESS) { + padapter->securitypriv.dot11AuthAlgrthm = dot11AuthAlgrthm_8021X; + padapter->securitypriv.ndisauthtype = Ndis802_11AuthModeWPA2PSK; diff --git a/queue-6.1/staging-rtl8723bs-fix-indentation-issues.patch b/queue-6.1/staging-rtl8723bs-fix-indentation-issues.patch new file mode 100644 index 0000000000..fe294c9f1b --- /dev/null +++ b/queue-6.1/staging-rtl8723bs-fix-indentation-issues.patch @@ -0,0 +1,56 @@ +From stable+bounces-274547-greg=kroah.com@vger.kernel.org Tue Jul 14 22:04:13 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:39 -0400 +Subject: staging: rtl8723bs: Fix indentation issues +To: stable@vger.kernel.org +Cc: Franziska Naepelt , Franziska Naepelt , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260714200241.3154071-1-sashal@kernel.org> + +From: Franziska Naepelt + +[ Upstream commit b59cba2309b16fa364df03e3693b8d45c3fadbd6 ] + +Fix the following checkpatch indentation issues: +- WARNING: suspect code indent for conditional statements (32, 48) +- WARNING: suspect code indent for conditional statements (24, 24) +- ERROR: code indent should use tabs where possible + +Signed-off-by: Franziska Naepelt +Link: https://lore.kernel.org/r/20230619180351.18925-1-franziska.naepelt@gmail.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: 5a752a616e75 ("staging: rtl8723bs: fix heap buffer overflow in rtw_cfg80211_set_wpa_ie()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +--- a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c ++++ b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c +@@ -582,7 +582,7 @@ static int rtw_cfg80211_ap_set_encryptio + + psecuritypriv->dot118021XGrpPrivacy = _WEP40_; + if (param->u.crypt.key_len == 13) +- psecuritypriv->dot118021XGrpPrivacy = _WEP104_; ++ psecuritypriv->dot118021XGrpPrivacy = _WEP104_; + + } else if (strcmp(param->u.crypt.alg, "TKIP") == 0) { + psecuritypriv->dot118021XGrpPrivacy = _TKIP_; +@@ -1306,7 +1306,7 @@ static int cfg80211_rtw_scan(struct wiph + } else if (request->n_channels <= 4) { + for (j = request->n_channels - 1; j >= 0; j--) + for (i = 0; i < survey_times; i++) +- memcpy(&ch[j*survey_times+i], &ch[j], sizeof(struct rtw_ieee80211_channel)); ++ memcpy(&ch[j*survey_times+i], &ch[j], sizeof(struct rtw_ieee80211_channel)); + _status = rtw_sitesurvey_cmd(padapter, ssid, RTW_SSID_SCAN_AMOUNT, ch, survey_times * request->n_channels); + } else { + _status = rtw_sitesurvey_cmd(padapter, ssid, RTW_SSID_SCAN_AMOUNT, NULL, 0); +@@ -2811,7 +2811,7 @@ int rtw_wdev_alloc(struct adapter *padap + wdev->netdev = pnetdev; + + wdev->iftype = NL80211_IFTYPE_STATION; /* will be init in rtw_hal_init() */ +- /* Must sync with _rtw_init_mlme_priv() */ ++ /* Must sync with _rtw_init_mlme_priv() */ + /* pmlmepriv->fw_state = WIFI_STATION_STATE */ + padapter->rtw_wdev = wdev; + pnetdev->ieee80211_ptr = wdev; diff --git a/queue-6.1/staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch b/queue-6.1/staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch new file mode 100644 index 0000000000..631509c895 --- /dev/null +++ b/queue-6.1/staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch @@ -0,0 +1,94 @@ +From stable+bounces-274651-greg=kroah.com@vger.kernel.org Wed Jul 15 02:47:21 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:47:04 -0400 +Subject: staging: rtl8723bs: fix OOB reads in rtw_get_sec_ie(), rtw_get_wapi_ie(), and rtw_get_wps_attr() +To: stable@vger.kernel.org +Cc: Alexandru Hossu , stable , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260715004704.3934423-3-sashal@kernel.org> + +From: Alexandru Hossu + +[ 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 +Signed-off-by: Alexandru Hossu +Link: https://patch.msgid.link/20260522004531.1038924-8-hossu.alexandru@gmail.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c ++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +@@ -584,9 +584,14 @@ int rtw_get_wapi_ie(u8 *in_ie, uint in_l + cnt = (_TIMESTAMP_ + _BEACON_ITERVAL_ + _CAPABILITY_); + + while (cnt < in_len) { ++ if (cnt + 2 > in_len) ++ break; ++ if (cnt + 2 + in_ie[cnt + 1] > in_len) ++ break; + authmode = in_ie[cnt]; + + if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY && ++ in_ie[cnt + 1] >= 8 && + (!memcmp(&in_ie[cnt + 6], wapi_oui1, 4) || + !memcmp(&in_ie[cnt + 6], wapi_oui2, 4))) { + if (wapi_ie) +@@ -619,9 +624,14 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l + cnt = (_TIMESTAMP_ + _BEACON_ITERVAL_ + _CAPABILITY_); + + while (cnt < in_len) { ++ if (cnt + 2 > in_len) ++ break; ++ if (cnt + 2 + in_ie[cnt + 1] > in_len) ++ break; + authmode = in_ie[cnt]; + + if ((authmode == WLAN_EID_VENDOR_SPECIFIC) && ++ in_ie[cnt + 1] >= 4 && + (!memcmp(&in_ie[cnt + 2], &wpa_oui[0], 4))) { + if (wpa_ie) + memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); +@@ -706,6 +716,9 @@ u8 *rtw_get_wps_attr(u8 *wps_ie, uint wp + if (len_attr) + *len_attr = 0; + ++ if (wps_ielen < 6) ++ return attr_ptr; ++ + if ((wps_ie[0] != WLAN_EID_VENDOR_SPECIFIC) || + (memcmp(wps_ie + 2, wps_oui, 4))) { + return attr_ptr; +@@ -716,6 +729,8 @@ u8 *rtw_get_wps_attr(u8 *wps_ie, uint wp + + while (attr_ptr - wps_ie < wps_ielen) { + /* 4 = 2(Attribute ID) + 2(Length) */ ++ if (attr_ptr + 4 > wps_ie + wps_ielen) ++ break; + u16 attr_id = get_unaligned_be16(attr_ptr); + u16 attr_data_len = get_unaligned_be16(attr_ptr + 2); + u16 attr_len = attr_data_len + 4; diff --git a/queue-6.1/staging-rtl8723bs-fix-space-issues.patch b/queue-6.1/staging-rtl8723bs-fix-space-issues.patch new file mode 100644 index 0000000000..664c760285 --- /dev/null +++ b/queue-6.1/staging-rtl8723bs-fix-space-issues.patch @@ -0,0 +1,255 @@ +From stable+bounces-274552-greg=kroah.com@vger.kernel.org Tue Jul 14 22:04:36 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:40 -0400 +Subject: staging: rtl8723bs: Fix space issues +To: stable@vger.kernel.org +Cc: Franziska Naepelt , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260714200241.3154071-2-sashal@kernel.org> + +From: Franziska Naepelt + +[ Upstream commit c4b811b9361b0d7ea167a79587f5ec0a13fd8863 ] + +Fix the following checkpatch space issues: +- CHECK: spaces preferred around that '*' (ctx:VxV) +- CHECK: spaces preferred around that '+' (ctx:VxV) +- CHECK: spaces preferred around that '-' (ctx:VxV) +- CHECK: spaces preferred around that '|' (ctx:VxV) +- CHECK: No space is necessary after a cast +- WARNING: please, no spaces at the start of a line + +Signed-off-by: Franziska Naepelt +Link: https://lore.kernel.org/r/20230701102538.5359-1-franziska.naepelt@googlemail.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: 5a752a616e75 ("staging: rtl8723bs: fix heap buffer overflow in rtw_cfg80211_set_wpa_ie()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c | 76 +++++++++++----------- + 1 file changed, 38 insertions(+), 38 deletions(-) + +--- a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c ++++ b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c +@@ -95,14 +95,14 @@ static struct ieee80211_channel rtw_2ghz + static void rtw_2g_channels_init(struct ieee80211_channel *channels) + { + memcpy((void *)channels, (void *)rtw_2ghz_channels, +- sizeof(struct ieee80211_channel)*RTW_2G_CHANNELS_NUM ++ sizeof(struct ieee80211_channel) * RTW_2G_CHANNELS_NUM + ); + } + + static void rtw_2g_rates_init(struct ieee80211_rate *rates) + { + memcpy(rates, rtw_g_rates, +- sizeof(struct ieee80211_rate)*RTW_G_RATES_NUM ++ sizeof(struct ieee80211_rate) * RTW_G_RATES_NUM + ); + } + +@@ -126,8 +126,8 @@ static struct ieee80211_supported_band * + if (!spt_band) + goto exit; + +- spt_band->channels = (struct ieee80211_channel *)(((u8 *)spt_band)+sizeof(struct ieee80211_supported_band)); +- spt_band->bitrates = (struct ieee80211_rate *)(((u8 *)spt_band->channels)+sizeof(struct ieee80211_channel)*n_channels); ++ spt_band->channels = (struct ieee80211_channel *)(((u8 *)spt_band) + sizeof(struct ieee80211_supported_band)); ++ spt_band->bitrates = (struct ieee80211_rate *)(((u8 *)spt_band->channels) + sizeof(struct ieee80211_channel) * n_channels); + spt_band->band = band; + spt_band->n_channels = n_channels; + spt_band->n_bitrates = n_bitrates; +@@ -247,10 +247,10 @@ struct cfg80211_bss *rtw_cfg80211_inform + u32 wpsielen = 0; + u8 *wpsie = NULL; + +- wpsie = rtw_get_wps_ie(pnetwork->network.ies+_FIXED_IE_LENGTH_, pnetwork->network.ie_length-_FIXED_IE_LENGTH_, NULL, &wpsielen); ++ wpsie = rtw_get_wps_ie(pnetwork->network.ies + _FIXED_IE_LENGTH_, pnetwork->network.ie_length - _FIXED_IE_LENGTH_, NULL, &wpsielen); + + if (wpsie && wpsielen > 0) +- psr = rtw_get_wps_attr_content(wpsie, wpsielen, WPS_ATTR_SELECTED_REGISTRAR, (u8 *)(&sr), NULL); ++ psr = rtw_get_wps_attr_content(wpsie, wpsielen, WPS_ATTR_SELECTED_REGISTRAR, (u8 *)(&sr), NULL); + + if (sr != 0) { + /* it means under processing WPS */ +@@ -277,9 +277,9 @@ struct cfg80211_bss *rtw_cfg80211_inform + /* We've set wiphy's signal_type as CFG80211_SIGNAL_TYPE_MBM: signal strength in mBm (100*dBm) */ + if (check_fwstate(pmlmepriv, _FW_LINKED) == true && + is_same_network(&pmlmepriv->cur_network.network, &pnetwork->network, 0)) { +- notify_signal = 100*translate_percentage_to_dbm(padapter->recvpriv.signal_strength);/* dbm */ ++ notify_signal = 100 * translate_percentage_to_dbm(padapter->recvpriv.signal_strength);/* dbm */ + } else { +- notify_signal = 100*translate_percentage_to_dbm(pnetwork->network.phy_info.signal_strength);/* dbm */ ++ notify_signal = 100 * translate_percentage_to_dbm(pnetwork->network.phy_info.signal_strength);/* dbm */ + } + + buf = kzalloc(MAX_BSSINFO_LEN, GFP_ATOMIC); +@@ -453,20 +453,20 @@ check_bss: + roam_info.links[0].channel = notify_channel; + roam_info.links[0].bssid = cur_network->network.mac_address; + roam_info.req_ie = +- pmlmepriv->assoc_req+sizeof(struct ieee80211_hdr_3addr)+2; ++ pmlmepriv->assoc_req + sizeof(struct ieee80211_hdr_3addr) + 2; + roam_info.req_ie_len = +- pmlmepriv->assoc_req_len-sizeof(struct ieee80211_hdr_3addr)-2; ++ pmlmepriv->assoc_req_len - sizeof(struct ieee80211_hdr_3addr) - 2; + roam_info.resp_ie = +- pmlmepriv->assoc_rsp+sizeof(struct ieee80211_hdr_3addr)+6; ++ pmlmepriv->assoc_rsp + sizeof(struct ieee80211_hdr_3addr) + 6; + roam_info.resp_ie_len = +- pmlmepriv->assoc_rsp_len-sizeof(struct ieee80211_hdr_3addr)-6; ++ pmlmepriv->assoc_rsp_len - sizeof(struct ieee80211_hdr_3addr) - 6; + cfg80211_roamed(padapter->pnetdev, &roam_info, GFP_ATOMIC); + } else { + cfg80211_connect_result(padapter->pnetdev, cur_network->network.mac_address +- , pmlmepriv->assoc_req+sizeof(struct ieee80211_hdr_3addr)+2 +- , pmlmepriv->assoc_req_len-sizeof(struct ieee80211_hdr_3addr)-2 +- , pmlmepriv->assoc_rsp+sizeof(struct ieee80211_hdr_3addr)+6 +- , pmlmepriv->assoc_rsp_len-sizeof(struct ieee80211_hdr_3addr)-6 ++ , pmlmepriv->assoc_req + sizeof(struct ieee80211_hdr_3addr) + 2 ++ , pmlmepriv->assoc_req_len - sizeof(struct ieee80211_hdr_3addr) - 2 ++ , pmlmepriv->assoc_rsp + sizeof(struct ieee80211_hdr_3addr) + 6 ++ , pmlmepriv->assoc_rsp_len - sizeof(struct ieee80211_hdr_3addr) - 6 + , WLAN_STATUS_SUCCESS, GFP_ATOMIC); + } + } +@@ -721,7 +721,7 @@ static int rtw_cfg80211_set_encryption(s + param->u.crypt.err = 0; + param->u.crypt.alg[IEEE_CRYPT_ALG_NAME_LEN - 1] = '\0'; + +- if (param_len < (u32) ((u8 *) param->u.crypt.key - (u8 *) param) + param->u.crypt.key_len) { ++ if (param_len < (u32)((u8 *)param->u.crypt.key - (u8 *)param) + param->u.crypt.key_len) { + ret = -EINVAL; + goto exit; + } +@@ -1036,10 +1036,10 @@ static int cfg80211_rtw_get_station(stru + } + + /* for Ad-Hoc/AP mode */ +- if ((check_fwstate(pmlmepriv, WIFI_ADHOC_STATE) +- || check_fwstate(pmlmepriv, WIFI_ADHOC_MASTER_STATE) +- || check_fwstate(pmlmepriv, WIFI_AP_STATE)) +- && check_fwstate(pmlmepriv, _FW_LINKED)) { ++ if ((check_fwstate(pmlmepriv, WIFI_ADHOC_STATE) || ++ check_fwstate(pmlmepriv, WIFI_ADHOC_MASTER_STATE) || ++ check_fwstate(pmlmepriv, WIFI_AP_STATE)) && ++ check_fwstate(pmlmepriv, _FW_LINKED)) { + /* TODO: should acquire station info... */ + } + +@@ -1238,7 +1238,7 @@ static int cfg80211_rtw_scan(struct wiph + spin_unlock_bh(&pwdev_priv->scan_req_lock); + + if (check_fwstate(pmlmepriv, WIFI_AP_STATE) == true) { +- if (check_fwstate(pmlmepriv, WIFI_UNDER_WPS|_FW_UNDER_SURVEY|_FW_UNDER_LINKING) == true) { ++ if (check_fwstate(pmlmepriv, WIFI_UNDER_WPS | _FW_UNDER_SURVEY | _FW_UNDER_LINKING) == true) { + need_indicate_scan_done = true; + goto check_need_indicate_scan_done; + } +@@ -1292,7 +1292,7 @@ static int cfg80211_rtw_scan(struct wiph + } + + /* parsing channels, n_channels */ +- memset(ch, 0, sizeof(struct rtw_ieee80211_channel)*RTW_CHANNEL_SCAN_AMOUNT); ++ memset(ch, 0, sizeof(struct rtw_ieee80211_channel) * RTW_CHANNEL_SCAN_AMOUNT); + for (i = 0; i < request->n_channels && i < RTW_CHANNEL_SCAN_AMOUNT; i++) { + ch[i].hw_value = request->channels[i]->hw_value; + ch[i].flags = request->channels[i]->flags; +@@ -1306,7 +1306,7 @@ static int cfg80211_rtw_scan(struct wiph + } else if (request->n_channels <= 4) { + for (j = request->n_channels - 1; j >= 0; j--) + for (i = 0; i < survey_times; i++) +- memcpy(&ch[j*survey_times+i], &ch[j], sizeof(struct rtw_ieee80211_channel)); ++ memcpy(&ch[j * survey_times + i], &ch[j], sizeof(struct rtw_ieee80211_channel)); + _status = rtw_sitesurvey_cmd(padapter, ssid, RTW_SSID_SCAN_AMOUNT, ch, survey_times * request->n_channels); + } else { + _status = rtw_sitesurvey_cmd(padapter, ssid, RTW_SSID_SCAN_AMOUNT, NULL, 0); +@@ -1462,7 +1462,7 @@ static int rtw_cfg80211_set_wpa_ie(struc + goto exit; + } + +- if (ielen > MAX_WPA_IE_LEN+MAX_WPS_IE_LEN+MAX_P2P_IE_LEN) { ++ if (ielen > MAX_WPA_IE_LEN + MAX_WPS_IE_LEN + MAX_P2P_IE_LEN) { + ret = -EINVAL; + goto exit; + } +@@ -1482,19 +1482,19 @@ static int rtw_cfg80211_set_wpa_ie(struc + + pwpa = rtw_get_wpa_ie(buf, &wpa_ielen, ielen); + if (pwpa && wpa_ielen > 0) { +- if (rtw_parse_wpa_ie(pwpa, wpa_ielen+2, &group_cipher, &pairwise_cipher, NULL) == _SUCCESS) { ++ if (rtw_parse_wpa_ie(pwpa, wpa_ielen + 2, &group_cipher, &pairwise_cipher, NULL) == _SUCCESS) { + padapter->securitypriv.dot11AuthAlgrthm = dot11AuthAlgrthm_8021X; + padapter->securitypriv.ndisauthtype = Ndis802_11AuthModeWPAPSK; +- memcpy(padapter->securitypriv.supplicant_ie, &pwpa[0], wpa_ielen+2); ++ memcpy(padapter->securitypriv.supplicant_ie, &pwpa[0], wpa_ielen + 2); + } + } + + pwpa2 = rtw_get_wpa2_ie(buf, &wpa2_ielen, ielen); + if (pwpa2 && wpa2_ielen > 0) { +- if (rtw_parse_wpa2_ie(pwpa2, wpa2_ielen+2, &group_cipher, &pairwise_cipher, NULL) == _SUCCESS) { ++ if (rtw_parse_wpa2_ie(pwpa2, wpa2_ielen + 2, &group_cipher, &pairwise_cipher, NULL) == _SUCCESS) { + padapter->securitypriv.dot11AuthAlgrthm = dot11AuthAlgrthm_8021X; + padapter->securitypriv.ndisauthtype = Ndis802_11AuthModeWPA2PSK; +- memcpy(padapter->securitypriv.supplicant_ie, &pwpa2[0], wpa2_ielen+2); ++ memcpy(padapter->securitypriv.supplicant_ie, &pwpa2[0], wpa2_ielen + 2); + } + } + +@@ -1893,7 +1893,7 @@ static int cfg80211_rtw_set_pmksa(struct + + memcpy(psecuritypriv->PMKIDList[index].PMKID, (u8 *)pmksa->pmkid, WLAN_PMKID_LEN); + psecuritypriv->PMKIDList[index].bUsed = true; +- psecuritypriv->PMKIDIndex = index+1; ++ psecuritypriv->PMKIDIndex = index + 1; + blInserted = true; + break; + } +@@ -2095,8 +2095,8 @@ static netdev_tx_t rtw_cfg80211_monitor_ + _rtw_xmit_entry(skb, padapter->pnetdev); + return NETDEV_TX_OK; + +- } else if ((frame_control & (IEEE80211_FCTL_FTYPE|IEEE80211_FCTL_STYPE)) == +- (IEEE80211_FTYPE_MGMT|IEEE80211_STYPE_ACTION)) { ++ } else if ((frame_control & (IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) == ++ (IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ACTION)) { + /* only for action frames */ + struct xmit_frame *pmgntframe; + struct pkt_attrib *pattrib; +@@ -2210,7 +2210,7 @@ static int rtw_cfg80211_add_monitor_if(s + goto out; + + *ndev = pwdev_priv->pmon_ndev = mon_ndev; +- memcpy(pwdev_priv->ifname_mon, name, IFNAMSIZ+1); ++ memcpy(pwdev_priv->ifname_mon, name, IFNAMSIZ + 1); + + out: + if (ret && mon_wdev) { +@@ -2304,14 +2304,14 @@ static int rtw_add_beacon(struct adapter + if (head_len < 24) + return -EINVAL; + +- pbuf = rtw_zmalloc(head_len+tail_len); ++ pbuf = rtw_zmalloc(head_len + tail_len); + if (!pbuf) + return -ENOMEM; + +- memcpy(pbuf, (void *)head+24, head_len-24);/* 24 =beacon header len. */ +- memcpy(pbuf+head_len-24, (void *)tail, tail_len); ++ memcpy(pbuf, (void *)head + 24, head_len - 24);/* 24 =beacon header len. */ ++ memcpy(pbuf + head_len - 24, (void *)tail, tail_len); + +- len = head_len+tail_len-24; ++ len = head_len + tail_len - 24; + + /* check wps ie if inclued */ + rtw_get_wps_ie(pbuf + _FIXED_IE_LENGTH_, len - _FIXED_IE_LENGTH_, NULL, &wps_ielen); +@@ -2601,7 +2601,7 @@ static int cfg80211_rtw_mgmt_tx(struct w + pwdev_priv = adapter_wdev_data(padapter); + + /* cookie generation */ +- *cookie = (unsigned long) buf; ++ *cookie = (unsigned long)buf; + + /* indicate ack before issue frame to avoid racing with rsp frame */ + rtw_cfg80211_mgmt_tx_status(padapter, *cookie, buf, len, ack, GFP_KERNEL); diff --git a/queue-6.1/staging-rtl8723bs-fix-spaces-around-binary-operators.patch b/queue-6.1/staging-rtl8723bs-fix-spaces-around-binary-operators.patch new file mode 100644 index 0000000000..c51b2f36b1 --- /dev/null +++ b/queue-6.1/staging-rtl8723bs-fix-spaces-around-binary-operators.patch @@ -0,0 +1,206 @@ +From stable+bounces-274650-greg=kroah.com@vger.kernel.org Wed Jul 15 02:47:19 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:47:03 -0400 +Subject: staging: rtl8723bs: fix spaces around binary operators +To: stable@vger.kernel.org +Cc: Nikolay Kulikov , Ethan Tidmore , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260715004704.3934423-2-sashal@kernel.org> + +From: Nikolay Kulikov + +[ Upstream commit 0c9d1b56f9af0762a5be5118e1bb962f23784dc4 ] + +Add missing spaces and fix line length to comply with kernel coding +style. + +Signed-off-by: Nikolay Kulikov +Reviewed-by: Ethan Tidmore +Link: https://patch.msgid.link/20260221172751.52329-1-nikolayof23@gmail.com +Signed-off-by: Greg Kroah-Hartman +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 +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 78 +++++++++++++------------ + 1 file changed, 42 insertions(+), 36 deletions(-) + +--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c ++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +@@ -455,10 +455,10 @@ int rtw_parse_wpa_ie(u8 *wpa_ie, int wpa + return _FAIL; + } + +- if ((*wpa_ie != WLAN_EID_VENDOR_SPECIFIC) || (*(wpa_ie+1) != (u8)(wpa_ie_len - 2)) || +- (memcmp(wpa_ie+2, RTW_WPA_OUI_TYPE, WPA_SELECTOR_LEN))) { ++ if ((*wpa_ie != WLAN_EID_VENDOR_SPECIFIC) || ++ (*(wpa_ie + 1) != (u8)(wpa_ie_len - 2)) || ++ (memcmp(wpa_ie + 2, RTW_WPA_OUI_TYPE, WPA_SELECTOR_LEN))) + return _FAIL; +- } + + pos = wpa_ie; + +@@ -518,7 +518,7 @@ int rtw_parse_wpa2_ie(u8 *rsn_ie, int rs + return _FAIL; + } + +- if ((*rsn_ie != WLAN_EID_RSN) || (*(rsn_ie+1) != (u8)(rsn_ie_len - 2))) ++ if ((*rsn_ie != WLAN_EID_RSN) || (*(rsn_ie + 1) != (u8)(rsn_ie_len - 2))) + return _FAIL; + + pos = rsn_ie; +@@ -586,18 +586,18 @@ int rtw_get_wapi_ie(u8 *in_ie, uint in_l + while (cnt < in_len) { + authmode = in_ie[cnt]; + +- /* if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY) */ +- if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY && (!memcmp(&in_ie[cnt+6], wapi_oui1, 4) || +- !memcmp(&in_ie[cnt+6], wapi_oui2, 4))) { ++ if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY && ++ (!memcmp(&in_ie[cnt + 6], wapi_oui1, 4) || ++ !memcmp(&in_ie[cnt + 6], wapi_oui2, 4))) { + if (wapi_ie) +- memcpy(wapi_ie, &in_ie[cnt], in_ie[cnt+1]+2); ++ memcpy(wapi_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); + + if (wapi_len) +- *wapi_len = in_ie[cnt+1]+2; ++ *wapi_len = in_ie[cnt + 1] + 2; + +- cnt += in_ie[cnt+1]+2; /* get next */ ++ cnt += in_ie[cnt + 1] + 2; /* get next */ + } else { +- cnt += in_ie[cnt+1]+2; /* get next */ ++ cnt += in_ie[cnt + 1] + 2; /* get next */ + } + } + +@@ -621,9 +621,10 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l + while (cnt < in_len) { + authmode = in_ie[cnt]; + +- if ((authmode == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt+2], &wpa_oui[0], 4))) { ++ if ((authmode == WLAN_EID_VENDOR_SPECIFIC) && ++ (!memcmp(&in_ie[cnt + 2], &wpa_oui[0], 4))) { + if (wpa_ie) +- memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt+1]+2); ++ memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); + + *wpa_len = in_ie[cnt + 1] + 2; + cnt += in_ie[cnt + 1] + 2; /* get next */ +@@ -632,10 +633,10 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l + if (rsn_ie) + memcpy(rsn_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); + +- *rsn_len = in_ie[cnt+1]+2; +- cnt += in_ie[cnt+1]+2; /* get next */ ++ *rsn_len = in_ie[cnt + 1] + 2; ++ cnt += in_ie[cnt + 1] + 2; /* get next */ + } else { +- cnt += in_ie[cnt+1]+2; /* get next */ ++ cnt += in_ie[cnt + 1] + 2; /* get next */ + } + } + } +@@ -667,20 +668,20 @@ u8 *rtw_get_wps_ie(u8 *in_ie, uint in_le + while (cnt < in_len) { + eid = in_ie[cnt]; + +- if ((eid == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt+2], wps_oui, 4))) { ++ if ((eid == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt + 2], wps_oui, 4))) { + wpsie_ptr = &in_ie[cnt]; + + if (wps_ie) +- memcpy(wps_ie, &in_ie[cnt], in_ie[cnt+1]+2); ++ memcpy(wps_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); + + if (wps_ielen) +- *wps_ielen = in_ie[cnt+1]+2; ++ *wps_ielen = in_ie[cnt + 1] + 2; + +- cnt += in_ie[cnt+1]+2; ++ cnt += in_ie[cnt + 1] + 2; + + break; + } +- cnt += in_ie[cnt+1]+2; /* goto next */ ++ cnt += in_ie[cnt + 1] + 2; /* goto next */ + } + + return wpsie_ptr; +@@ -758,12 +759,12 @@ u8 *rtw_get_wps_attr_content(u8 *wps_ie, + + if (attr_ptr && attr_len) { + if (buf_content) +- memcpy(buf_content, attr_ptr+4, attr_len-4); ++ memcpy(buf_content, attr_ptr + 4, attr_len - 4); + + if (len_content) +- *len_content = attr_len-4; ++ *len_content = attr_len - 4; + +- return attr_ptr+4; ++ return attr_ptr + 4; + } + + return NULL; +@@ -1009,20 +1010,25 @@ static int rtw_get_cipher_info(struct wl + int group_cipher = 0, pairwise_cipher = 0, is8021x = 0; + int ret = _FAIL; + +- pbuf = rtw_get_wpa_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12); ++ pbuf = rtw_get_wpa_ie(&pnetwork->network.ies[12], ++ &wpa_ielen, ++ pnetwork->network.ie_length - 12); + + if (pbuf && (wpa_ielen > 0)) { +- if (rtw_parse_wpa_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) { ++ if (rtw_parse_wpa_ie(pbuf, wpa_ielen + 2, &group_cipher, ++ &pairwise_cipher, &is8021x) == _SUCCESS) { + pnetwork->bcn_info.pairwise_cipher = pairwise_cipher; + pnetwork->bcn_info.group_cipher = group_cipher; + pnetwork->bcn_info.is_8021x = is8021x; + ret = _SUCCESS; + } + } else { +- pbuf = rtw_get_wpa2_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12); ++ pbuf = rtw_get_wpa2_ie(&pnetwork->network.ies[12], &wpa_ielen, ++ pnetwork->network.ie_length - 12); + + if (pbuf && (wpa_ielen > 0)) { +- if (rtw_parse_wpa2_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) { ++ if (rtw_parse_wpa2_ie(pbuf, wpa_ielen + 2, &group_cipher, ++ &pairwise_cipher, &is8021x) == _SUCCESS) { + pnetwork->bcn_info.pairwise_cipher = pairwise_cipher; + pnetwork->bcn_info.group_cipher = group_cipher; + pnetwork->bcn_info.is_8021x = is8021x; +@@ -1091,21 +1097,21 @@ u16 rtw_mcs_rate(u8 bw_40MHz, u8 short_G + u16 max_rate = 0; + + if (MCS_rate[0] & BIT(7)) +- max_rate = (bw_40MHz) ? ((short_GI)?1500:1350):((short_GI)?722:650); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 1500 : 1350) : ((short_GI) ? 722 : 650); + else if (MCS_rate[0] & BIT(6)) +- max_rate = (bw_40MHz) ? ((short_GI)?1350:1215):((short_GI)?650:585); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 1350 : 1215) : ((short_GI) ? 650 : 585); + else if (MCS_rate[0] & BIT(5)) +- max_rate = (bw_40MHz) ? ((short_GI)?1200:1080):((short_GI)?578:520); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 1200 : 1080) : ((short_GI) ? 578 : 520); + else if (MCS_rate[0] & BIT(4)) +- max_rate = (bw_40MHz) ? ((short_GI)?900:810):((short_GI)?433:390); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 900 : 810) : ((short_GI) ? 433 : 390); + else if (MCS_rate[0] & BIT(3)) +- max_rate = (bw_40MHz) ? ((short_GI)?600:540):((short_GI)?289:260); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 600 : 540) : ((short_GI) ? 289 : 260); + else if (MCS_rate[0] & BIT(2)) +- max_rate = (bw_40MHz) ? ((short_GI)?450:405):((short_GI)?217:195); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 450 : 405) : ((short_GI) ? 217 : 195); + else if (MCS_rate[0] & BIT(1)) +- max_rate = (bw_40MHz) ? ((short_GI)?300:270):((short_GI)?144:130); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 300 : 270) : ((short_GI) ? 144 : 130); + else if (MCS_rate[0] & BIT(0)) +- max_rate = (bw_40MHz) ? ((short_GI)?150:135):((short_GI)?72:65); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 150 : 135) : ((short_GI) ? 72 : 65); + + return max_rate; + } diff --git a/queue-6.1/treewide-switch-rename-to-timer_delete.patch b/queue-6.1/treewide-switch-rename-to-timer_delete.patch new file mode 100644 index 0000000000..8a13fa5812 --- /dev/null +++ b/queue-6.1/treewide-switch-rename-to-timer_delete.patch @@ -0,0 +1,37 @@ +From stable+bounces-277136-greg=kroah.com@vger.kernel.org Fri Jul 17 18:38:32 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 12:38:23 -0400 +Subject: treewide: Switch/rename to timer_delete[_sync]() +To: stable@vger.kernel.org +Cc: Thomas Gleixner , Ingo Molnar , Sasha Levin +Message-ID: <20260717163824.2035351-1-sashal@kernel.org> + +From: Thomas Gleixner + +[ 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 +Signed-off-by: Ingo Molnar +Stable-dep-of: 75fe87e19d8a ("HID: appleir: fix UAF on pending key_up_timer in remove()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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[] = { diff --git a/queue-6.1/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch b/queue-6.1/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch new file mode 100644 index 0000000000..1521fe123d --- /dev/null +++ b/queue-6.1/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch @@ -0,0 +1,175 @@ +From sashal@kernel.org Sun Jul 19 20:01:35 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 14:01:31 -0400 +Subject: usb: atm: ueagle-atm: wait for pre-firmware load in .disconnect() +To: stable@vger.kernel.org +Cc: Mauricio Faria de Oliveira , stable , syzbot+ce1e5a1b4e086b43e56d@syzkaller.appspotmail.com, syzbot+306212936b13e520679d@syzkaller.appspotmail.com, syzbot+457452d30bcdda75ead2@syzkaller.appspotmail.com, Andrey Tsygunka , Stanislaw Gruszka , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260719180131.1055331-1-sashal@kernel.org> + +From: Mauricio Faria de Oliveira + +[ 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 + +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 +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 +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 +Acked-by: Stanislaw Gruszka +Link: https://patch.msgid.link/20260526-ueagle-atm_req-fw-sync-v3-1-93c01961daaf@igalia.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/atm/ueagle-atm.c | 36 +++++++++++++++++++++++++++++++----- + 1 file changed, 31 insertions(+), 5 deletions(-) + +--- a/drivers/usb/atm/ueagle-atm.c ++++ b/drivers/usb/atm/ueagle-atm.c +@@ -599,7 +599,9 @@ static int uea_send_modem_cmd(struct usb + static void uea_upload_pre_firmware(const struct firmware *fw_entry, + void *context) + { +- struct usb_device *usb = context; ++ struct usb_interface *intf = context; ++ struct usb_device *usb = interface_to_usbdev(intf); ++ struct completion *fw_done = usb_get_intfdata(intf); + const u8 *pfw; + u8 value; + u32 crc = 0; +@@ -670,15 +672,17 @@ err_fw_corrupted: + err: + release_firmware(fw_entry); + uea_leaves(usb); ++ complete(fw_done); + } + + /* + * uea_load_firmware - Load usb firmware for pre-firmware devices. + */ +-static int uea_load_firmware(struct usb_device *usb, unsigned int ver) ++static int uea_load_firmware(struct usb_interface *intf, unsigned int ver) + { + int ret; + char *fw_name = EAGLE_FIRMWARE; ++ struct usb_device *usb = interface_to_usbdev(intf); + + uea_enters(usb); + uea_info(usb, "pre-firmware device, uploading firmware\n"); +@@ -702,7 +706,7 @@ static int uea_load_firmware(struct usb_ + } + + ret = request_firmware_nowait(THIS_MODULE, 1, fw_name, &usb->dev, +- GFP_KERNEL, usb, ++ GFP_KERNEL, intf, + uea_upload_pre_firmware); + if (ret) + uea_err(usb, "firmware %s is not available\n", fw_name); +@@ -2598,8 +2602,23 @@ static int uea_probe(struct usb_interfac + + usb_reset_device(usb); + +- if (UEA_IS_PREFIRM(id)) +- return uea_load_firmware(usb, UEA_CHIP_VERSION(id)); ++ if (UEA_IS_PREFIRM(id)) { ++ struct completion *fw_done; ++ ++ /* Wait for the firmware load to be done, in .disconnect() */ ++ fw_done = kzalloc(sizeof(*fw_done), GFP_KERNEL); ++ if (!fw_done) ++ return -ENOMEM; ++ ++ init_completion(fw_done); ++ usb_set_intfdata(intf, fw_done); ++ ++ ret = uea_load_firmware(intf, UEA_CHIP_VERSION(id)); ++ if (ret) ++ kfree(fw_done); ++ ++ return ret; ++ } + + ret = usbatm_usb_probe(intf, id, &uea_usbatm_driver); + if (ret == 0) { +@@ -2630,6 +2649,13 @@ static void uea_disconnect(struct usb_in + usbatm_usb_disconnect(intf); + mutex_unlock(&uea_mutex); + uea_info(usb, "ADSL device removed\n"); ++ } else if (usb->config->desc.bNumInterfaces == 1) { ++ struct completion *fw_done = usb_get_intfdata(intf); ++ ++ uea_dbg(usb, "pre-firmware device, waiting firmware upload\n"); ++ wait_for_completion(fw_done); ++ uea_dbg(usb, "pre-firmware device, finished waiting\n"); ++ kfree(fw_done); + } + + uea_leaves(usb); diff --git a/queue-6.1/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch b/queue-6.1/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch new file mode 100644 index 0000000000..ddaee67cb3 --- /dev/null +++ b/queue-6.1/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch @@ -0,0 +1,94 @@ +From sashal@kernel.org Mon Jul 20 15:51:01 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 09:50:57 -0400 +Subject: usb: gadget: f_fs: initialize reset_work at allocation time +To: stable@vger.kernel.org +Cc: "Tyler Baker" , stable , "Loic Poulain" , "Dmitry Baryshkov" , "Srinivas Kandagatla" , "Peter Chen" , "Michał Nazarewicz" , "Greg Kroah-Hartman" , "Sasha Levin" +Message-ID: <20260720135057.1305612-1-sashal@kernel.org> + +From: Tyler Baker + +[ 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 +Signed-off-by: Tyler Baker +Cc: Loic Poulain +Cc: Dmitry Baryshkov +Cc: Srinivas Kandagatla +Tested-by: Loic Poulain +Reviewed-by: Peter Chen +Acked-by: Michał Nazarewicz +Link: https://patch.msgid.link/20260609193635.2284430-1-tyler.baker@oss.qualcomm.com +Signed-off-by: Greg Kroah-Hartman +[ dropped the ffs_func_disable hunk since 6.6 predates the disable-body split and routes disable through ffs_func_set_alt, so removing the single lazy INIT_WORK there covers both paths ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/gadget/function/f_fs.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -255,6 +255,7 @@ static int ffs_acquire_dev(const char *d + static void ffs_release_dev(struct ffs_dev *ffs_dev); + static int ffs_ready(struct ffs_data *ffs); + static void ffs_closed(struct ffs_data *ffs); ++static void ffs_reset_work(struct work_struct *work); + + /* Misc helper functions ****************************************************/ + +@@ -1786,6 +1787,7 @@ static struct ffs_data *ffs_data_new(con + init_waitqueue_head(&ffs->ev.waitq); + init_waitqueue_head(&ffs->wait); + init_completion(&ffs->ep0req_completion); ++ INIT_WORK(&ffs->reset_work, ffs_reset_work); + + /* XXX REVISIT need to update it in some places, or do we? */ + ffs->ev.can_stall = 1; +@@ -3342,7 +3344,6 @@ static int ffs_func_set_alt(struct usb_f + + if (ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; +- INIT_WORK(&ffs->reset_work, ffs_reset_work); + schedule_work(&ffs->reset_work); + return -ENODEV; + } diff --git a/queue-6.1/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch b/queue-6.1/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch new file mode 100644 index 0000000000..bf42052700 --- /dev/null +++ b/queue-6.1/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch @@ -0,0 +1,55 @@ +From stable+bounces-277908-greg=kroah.com@vger.kernel.org Mon Jul 20 17:49:11 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 11:00:54 -0400 +Subject: usb: gadget: f_fs: Tie read_buffer lifetime to ffs_epfile +To: stable@vger.kernel.org +Cc: Neill Kapron , stable , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260720150054.1919483-1-sashal@kernel.org> + +From: Neill Kapron + +[ 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 +Assisted-by: Antigravity:gemini-3.1-pro +Signed-off-by: Neill Kapron +Link: https://patch.msgid.link/20260619040609.4010746-3-nkapron@google.com +Signed-off-by: Greg Kroah-Hartman +[ 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 +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/gadget/function/f_fs.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -1292,7 +1292,6 @@ ffs_epfile_release(struct inode *inode, + + ENTER(); + +- __ffs_epfile_read_buffer_free(epfile); + ffs_data_closed(epfile->ffs); + + return 0; +@@ -1957,6 +1956,7 @@ static void ffs_epfiles_destroy(struct f + + for (; count; --count, ++epfile) { + BUG_ON(mutex_is_locked(&epfile->mutex)); ++ __ffs_epfile_read_buffer_free(epfile); + if (epfile->dentry) { + d_delete(epfile->dentry); + dput(epfile->dentry); diff --git a/queue-6.1/usb-iowarrior-remove-inherent-race-with-minor-number.patch b/queue-6.1/usb-iowarrior-remove-inherent-race-with-minor-number.patch new file mode 100644 index 0000000000..529d125c72 --- /dev/null +++ b/queue-6.1/usb-iowarrior-remove-inherent-race-with-minor-number.patch @@ -0,0 +1,109 @@ +From stable+bounces-277487-greg=kroah.com@vger.kernel.org Sun Jul 19 17:29:31 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 11:28:58 -0400 +Subject: usb: iowarrior: remove inherent race with minor number +To: stable@vger.kernel.org +Cc: Oliver Neukum , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260719152859.190429-1-sashal@kernel.org> + +From: Oliver Neukum + +[ 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 +Link: https://patch.msgid.link/20260312094619.1590556-1-oneukum@suse.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: c602254ba4c1 ("USB: iowarrior: fix use-after-free on disconnect race") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/misc/iowarrior.c | 17 +++-------------- + 1 file changed, 3 insertions(+), 14 deletions(-) + +--- a/drivers/usb/misc/iowarrior.c ++++ b/drivers/usb/misc/iowarrior.c +@@ -74,7 +74,6 @@ struct iowarrior { + struct mutex mutex; /* locks this structure */ + struct usb_device *udev; /* save off the usb device pointer */ + struct usb_interface *interface; /* the interface for this device */ +- unsigned char minor; /* the starting minor number for this device */ + struct usb_endpoint_descriptor *int_out_endpoint; /* endpoint for reading (needed for IOW56 only) */ + struct usb_endpoint_descriptor *int_in_endpoint; /* endpoint for reading */ + struct urb *int_in_urb; /* the urb for reading data */ +@@ -246,7 +245,6 @@ static void iowarrior_write_callback(str + */ + static inline void iowarrior_delete(struct iowarrior *dev) + { +- dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor); + kfree(dev->int_in_buffer); + usb_free_urb(dev->int_in_urb); + kfree(dev->read_queue); +@@ -297,9 +295,6 @@ static ssize_t iowarrior_read(struct fil + goto exit; + } + +- dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n", +- dev->minor, count); +- + /* read count must be packet size (+ time stamp) */ + if ((count != dev->report_size) + && (count != (dev->report_size + 1))) { +@@ -379,8 +374,6 @@ static ssize_t iowarrior_write(struct fi + retval = -ENODEV; + goto exit; + } +- dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n", +- dev->minor, count); + /* if count is 0 we're already done */ + if (count == 0) { + retval = 0; +@@ -523,9 +516,6 @@ static long iowarrior_ioctl(struct file + goto error_out; + } + +- dev_dbg(&dev->interface->dev, "minor %d, cmd 0x%.4x, arg %ld\n", +- dev->minor, cmd, arg); +- + retval = 0; + io_res = 0; + switch (cmd) { +@@ -672,8 +662,6 @@ static int iowarrior_release(struct inod + if (!dev) + return -ENODEV; + +- dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor); +- + /* lock our device */ + mutex_lock(&dev->mutex); + +@@ -776,6 +764,7 @@ static int iowarrior_probe(struct usb_in + struct usb_host_interface *iface_desc; + int retval = -ENOMEM; + int res; ++ int minor; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(struct iowarrior), GFP_KERNEL); +@@ -891,12 +880,12 @@ static int iowarrior_probe(struct usb_in + goto error; + } + +- dev->minor = interface->minor; ++ minor = interface->minor; + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, "IOWarrior product=0x%x, serial=%s interface=%d " + "now attached to iowarrior%d\n", dev->product_id, dev->chip_serial, +- iface_desc->desc.bInterfaceNumber, dev->minor - IOWARRIOR_MINOR_BASE); ++ iface_desc->desc.bInterfaceNumber, minor - IOWARRIOR_MINOR_BASE); + return retval; + + error: diff --git a/queue-6.1/usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch b/queue-6.1/usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch new file mode 100644 index 0000000000..7bad252573 --- /dev/null +++ b/queue-6.1/usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch @@ -0,0 +1,39 @@ +From sashal@kernel.org Sun Jul 19 20:01:37 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 14:01:34 -0400 +Subject: usb: typec: tcpm: Fix VDM type for Enter Mode commands +To: stable@vger.kernel.org +Cc: Andy Yan , stable , Heikki Krogerus , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260719180134.1055370-1-sashal@kernel.org> + +From: Andy Yan + +[ Upstream commit 9cff680e47632b7723cb19f9c5e63669063c3417 ] + +VDO() second parameter is VDM type (bit 15): 1 for SVDM, 0 for UVDM. +Using 'vdo ? 2 : 1' corrupts SVID low bit when vdo is non-NULL +(2 << 15 = BIT(16)). Enter Mode is always SVDM, hardcode to 1. + +Fixes: 8face9aa57c8 ("usb: typec: Add parameter for the VDO to typec_altmode_enter()") +Cc: stable +Signed-off-by: Andy Yan +Reviewed-by: Heikki Krogerus +Link: https://patch.msgid.link/20260604105059.18750-1-andyshrk@163.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/typec/tcpm/tcpm.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -2231,7 +2231,7 @@ static int tcpm_altmode_enter(struct typ + if (svdm_version < 0) + return svdm_version; + +- header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE); ++ header = VDO(altmode->svid, 1, svdm_version, CMD_ENTER_MODE); + header |= VDO_OPOS(altmode->mode); + + tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0); diff --git a/queue-6.1/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch b/queue-6.1/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch new file mode 100644 index 0000000000..eaa4ada40d --- /dev/null +++ b/queue-6.1/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch @@ -0,0 +1,92 @@ +From stable+bounces-274437-greg=kroah.com@vger.kernel.org Tue Jul 14 18:39:07 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 12:38:16 -0400 +Subject: vfio/mlx5: Fix racy bitfields and tighten struct layout +To: stable@vger.kernel.org +Cc: Alex Williamson , Yishai Hadas , Kevin Tian , Alex Williamson , Sasha Levin +Message-ID: <20260714163816.2850342-1-sashal@kernel.org> + +From: Alex Williamson + +[ 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 +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-8 +Signed-off-by: Alex Williamson +Reviewed-by: Kevin Tian +Link: https://lore.kernel.org/r/20260615191241.688297-5-alex.williamson@nvidia.com +Signed-off-by: Alex Williamson +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/vfio/pci/mlx5/cmd.h | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +--- a/drivers/vfio/pci/mlx5/cmd.h ++++ b/drivers/vfio/pci/mlx5/cmd.h +@@ -82,23 +82,26 @@ struct mlx5_vhca_qp { + struct mlx5_vhca_page_tracker { + u32 id; + u32 pdn; +- u8 is_err:1; ++ /* Flags modified at runtime - dedicated storage unit */ ++ u8 is_err; ++ int status; + struct mlx5_uars_page *uar; + struct mlx5_vhca_cq cq; + struct mlx5_vhca_qp *host_qp; + struct mlx5_vhca_qp *fw_qp; + struct mlx5_nb nb; +- int status; + }; + + struct mlx5vf_pci_core_device { + struct vfio_pci_core_device core_device; + int vf_id; + u16 vhca_id; ++ /* Flags only modified on setup/release - bitfield ok */ + u8 migrate_cap:1; +- u8 deferred_reset:1; +- u8 mdev_detach:1; +- u8 log_active:1; ++ /* Flags modified at runtime - dedicated storage unit */ ++ u8 mdev_detach; ++ u8 log_active; ++ u8 deferred_reset; + struct completion tracker_comp; + /* protect migration state */ + struct mutex state_mutex; diff --git a/queue-6.1/vfio-pci-latch-disable_idle_d3-per-device.patch b/queue-6.1/vfio-pci-latch-disable_idle_d3-per-device.patch new file mode 100644 index 0000000000..de6514ce98 --- /dev/null +++ b/queue-6.1/vfio-pci-latch-disable_idle_d3-per-device.patch @@ -0,0 +1,154 @@ +From stable+bounces-274113-greg=kroah.com@vger.kernel.org Tue Jul 14 05:33:39 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 23:33:07 -0400 +Subject: vfio/pci: Latch disable_idle_d3 per device +To: stable@vger.kernel.org +Cc: Alex Williamson , Kevin Tian , Alex Williamson , Sasha Levin +Message-ID: <20260714033307.2397254-3-sashal@kernel.org> + +From: Alex Williamson + +[ Upstream commit 4575e9aac5336d1365138c0284773bf8da4b1fa3 ] + +When disable_idle_d3 was introduced in vfio-pci, it directly manipulated +the device power state with pci_set_power_state(). There were no +refcounts to maintain or balanced operations, we could unconditionally +bring the device to D0 and conditionally move it to D3hot. Therefore +the module parameter was made writable. + +Later, in commit c61302aa48f7 ("vfio/pci: Move module parameters to +vfio_pci.c"), as part of the vfio-pci-core split, the writable aspect +of the module parameter was nullified. The parameter value could still +be changed through sysfs, but the vfio-pci driver latched the values +into vfio-pci-core globals at module init. Loading the vfio-pci module, +or unloading and reloading, with non-default or different values could +change the globals relative to existing devices bound to vfio-pci +variant drivers. + +Runtime PM was introduced in commit 7ab5e10eda02 ("vfio/pci: Move the +unused device into low power state with runtime PM"), which marks the +point where power states became refcounted. PM get and put operations +need to be balanced, but the same module operations noted above can +change the global variables relative to those devices already bound to +vfio-pci variant drivers. This introduces a window where PM operations +can now become unbalanced. + +To resolve this with a narrow footprint for stable backports, the +disable_idle_d3 flag is latched into the vfio_pci_core_device at the +time of initialization, such that the device always operates with a +consistent value. + +NB. vfio_pci_dev_set_try_reset() now unconditionally raises the +runtime PM usage count around bus reset to account for disable_idle_d3 +becoming a per-device rather than global flag. When this flag is set, +the additional get/put pair is harmless and allows continued use of the +shared vfio_pci_dev_set_pm_runtime_get() helper. + +Fixes: 7ab5e10eda02 ("vfio/pci: Move the unused device into low power state with runtime PM") +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-8 +Signed-off-by: Alex Williamson +Reviewed-by: Kevin Tian +Link: https://lore.kernel.org/r/20260615191241.688297-2-alex.williamson@nvidia.com +Signed-off-by: Alex Williamson +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/vfio/pci/vfio_pci_core.c | 19 ++++++++++--------- + include/linux/vfio_pci_core.h | 1 + + 2 files changed, 11 insertions(+), 9 deletions(-) + +--- a/drivers/vfio/pci/vfio_pci_core.c ++++ b/drivers/vfio/pci/vfio_pci_core.c +@@ -472,7 +472,7 @@ int vfio_pci_core_enable(struct vfio_pci + u16 cmd; + u8 msix_pos; + +- if (!disable_idle_d3) { ++ if (!vdev->disable_idle_d3) { + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret < 0) + return ret; +@@ -547,7 +547,7 @@ out_free_state: + out_disable_device: + pci_disable_device(pdev); + out_power: +- if (!disable_idle_d3) ++ if (!vdev->disable_idle_d3) + pm_runtime_put(&pdev->dev); + return ret; + } +@@ -674,7 +674,7 @@ out: + vfio_pci_dev_set_try_reset(vdev->vdev.dev_set); + + /* Put the pm-runtime usage counter acquired during enable */ +- if (!disable_idle_d3) ++ if (!vdev->disable_idle_d3) + pm_runtime_put(&pdev->dev); + } + EXPORT_SYMBOL_GPL(vfio_pci_core_disable); +@@ -2091,6 +2091,8 @@ int vfio_pci_core_init_dev(struct vfio_d + INIT_LIST_HEAD(&vdev->sriov_pfs_item); + init_rwsem(&vdev->memory_lock); + ++ vdev->disable_idle_d3 = disable_idle_d3; ++ + return 0; + } + EXPORT_SYMBOL_GPL(vfio_pci_core_init_dev); +@@ -2183,7 +2185,7 @@ int vfio_pci_core_register_device(struct + + dev->driver->pm = &vfio_pci_core_pm_ops; + pm_runtime_allow(dev); +- if (!disable_idle_d3) ++ if (!vdev->disable_idle_d3) + pm_runtime_put(dev); + + ret = vfio_register_group_dev(&vdev->vdev); +@@ -2192,7 +2194,7 @@ int vfio_pci_core_register_device(struct + return 0; + + out_power: +- if (!disable_idle_d3) ++ if (!vdev->disable_idle_d3) + pm_runtime_get_noresume(dev); + + pm_runtime_forbid(dev); +@@ -2212,7 +2214,7 @@ void vfio_pci_core_unregister_device(str + vfio_pci_vf_uninit(vdev); + vfio_pci_vga_uninit(vdev); + +- if (!disable_idle_d3) ++ if (!vdev->disable_idle_d3) + pm_runtime_get_noresume(&vdev->pdev->dev); + + pm_runtime_forbid(&vdev->pdev->dev); +@@ -2527,7 +2529,7 @@ static void vfio_pci_dev_set_try_reset(s + * state. Increment the usage count for all the devices in the dev_set + * before reset and decrement the same after reset. + */ +- if (!disable_idle_d3 && vfio_pci_dev_set_pm_runtime_get(dev_set)) ++ if (vfio_pci_dev_set_pm_runtime_get(dev_set)) + return; + + if (!pci_reset_bus(pdev)) +@@ -2537,8 +2539,7 @@ static void vfio_pci_dev_set_try_reset(s + if (reset_done) + cur->needs_reset = false; + +- if (!disable_idle_d3) +- pm_runtime_put(&cur->pdev->dev); ++ pm_runtime_put(&cur->pdev->dev); + } + } + +--- a/include/linux/vfio_pci_core.h ++++ b/include/linux/vfio_pci_core.h +@@ -80,6 +80,7 @@ struct vfio_pci_core_device { + bool needs_pm_restore:1; + bool pm_intx_masked:1; + bool pm_runtime_engaged:1; ++ bool disable_idle_d3:1; + bool sriov_active; + struct pci_saved_state *pci_saved_state; + struct pci_saved_state *pm_save; diff --git a/queue-6.1/vfio-pci-use-a-private-flag-to-prevent-power-state-change-with-vfs.patch b/queue-6.1/vfio-pci-use-a-private-flag-to-prevent-power-state-change-with-vfs.patch new file mode 100644 index 0000000000..6e943f59db --- /dev/null +++ b/queue-6.1/vfio-pci-use-a-private-flag-to-prevent-power-state-change-with-vfs.patch @@ -0,0 +1,220 @@ +From stable+bounces-274109-greg=kroah.com@vger.kernel.org Tue Jul 14 05:33:23 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 23:33:01 -0400 +Subject: vfio/pci: Use a private flag to prevent power state change with VFs +To: stable@vger.kernel.org +Cc: Raghavendra Rao Ananta , Jason Gunthorpe , Alex Williamson , Sasha Levin +Message-ID: <20260714033301.2397090-2-sashal@kernel.org> + +From: Raghavendra Rao Ananta + +[ Upstream commit 40ef3edf151e184d021917a5c4c771cc0870844a ] + +The current implementation uses pci_num_vf() while holding the +memory_lock to prevent changing the power state of a PF when +VFs are enabled. This creates a lockdep circular dependency +warning because memory_lock is held during device probing. + +[ 286.997167] ====================================================== +[ 287.003363] WARNING: possible circular locking dependency detected +[ 287.009562] 7.0.0-dbg-DEV #3 Tainted: G S +[ 287.015074] ------------------------------------------------------ +[ 287.021270] vfio_pci_sriov_/18636 is trying to acquire lock: +[ 287.026942] ff45bea2294d4968 (&vdev->memory_lock){+.+.}-{4:4}, at: +vfio_pci_core_runtime_resume+0x1f/0xa0 +[ 287.036530] +[ 287.036530] but task is already holding lock: +[ 287.042383] ff45bea3a96b8230 (&new_dev_set->lock){+.+.}-{4:4}, at: +vfio_group_fops_unl_ioctl+0x44d/0x7b0 +[ 287.051879] +[ 287.051879] which lock already depends on the new lock. +[ 287.051879] +[ 287.060070] +[ 287.060070] the existing dependency chain (in reverse order) is: +[ 287.067568] +[ 287.067568] -> #2 (&new_dev_set->lock){+.+.}-{4:4}: +[ 287.073941] __mutex_lock+0x92/0xb80 +[ 287.078058] vfio_assign_device_set+0x66/0x1b0 +[ 287.083042] vfio_pci_core_register_device+0xd1/0x2a0 +[ 287.088638] vfio_pci_probe+0xd2/0x100 +[ 287.092933] local_pci_probe_callback+0x4d/0xa0 +[ 287.098001] process_scheduled_works+0x2ca/0x680 +[ 287.103158] worker_thread+0x1e8/0x2f0 +[ 287.107452] kthread+0x10c/0x140 +[ 287.111230] ret_from_fork+0x18e/0x360 +[ 287.115519] ret_from_fork_asm+0x1a/0x30 +[ 287.119983] +[ 287.119983] -> #1 ((work_completion)(&arg.work)){+.+.}-{0:0}: +[ 287.127219] __flush_work+0x345/0x490 +[ 287.131429] pci_device_probe+0x2e3/0x490 +[ 287.135979] really_probe+0x1f9/0x4e0 +[ 287.140180] __driver_probe_device+0x77/0x100 +[ 287.145079] driver_probe_device+0x1e/0x110 +[ 287.149803] __device_attach_driver+0xe3/0x170 +[ 287.154789] bus_for_each_drv+0x125/0x150 +[ 287.159346] __device_attach+0xca/0x1a0 +[ 287.163720] device_initial_probe+0x34/0x50 +[ 287.168445] pci_bus_add_device+0x6e/0x90 +[ 287.172995] pci_iov_add_virtfn+0x3c9/0x3e0 +[ 287.177719] sriov_add_vfs+0x2c/0x60 +[ 287.181838] sriov_enable+0x306/0x4a0 +[ 287.186038] vfio_pci_core_sriov_configure+0x184/0x220 +[ 287.191715] sriov_numvfs_store+0xd9/0x1c0 +[ 287.196351] kernfs_fop_write_iter+0x13f/0x1d0 +[ 287.201338] vfs_write+0x2be/0x3b0 +[ 287.205286] ksys_write+0x73/0x100 +[ 287.209233] do_syscall_64+0x14d/0x750 +[ 287.213529] entry_SYSCALL_64_after_hwframe+0x77/0x7f +[ 287.219120] +[ 287.219120] -> #0 (&vdev->memory_lock){+.+.}-{4:4}: +[ 287.225491] __lock_acquire+0x14c6/0x2800 +[ 287.230048] lock_acquire+0xd3/0x2f0 +[ 287.234168] down_write+0x3a/0xc0 +[ 287.238019] vfio_pci_core_runtime_resume+0x1f/0xa0 +[ 287.243436] __rpm_callback+0x8c/0x310 +[ 287.247730] rpm_resume+0x529/0x6f0 +[ 287.251765] __pm_runtime_resume+0x68/0x90 +[ 287.256402] vfio_pci_core_enable+0x44/0x310 +[ 287.261216] vfio_pci_open_device+0x1c/0x80 +[ 287.265947] vfio_df_open+0x10f/0x150 +[ 287.270148] vfio_group_fops_unl_ioctl+0x4a4/0x7b0 +[ 287.275476] __se_sys_ioctl+0x71/0xc0 +[ 287.279679] do_syscall_64+0x14d/0x750 +[ 287.283975] entry_SYSCALL_64_after_hwframe+0x77/0x7f +[ 287.289559] +[ 287.289559] other info that might help us debug this: +[ 287.289559] +[ 287.297582] Chain exists of: +[ 287.297582] &vdev->memory_lock --> (work_completion)(&arg.work) +--> &new_dev_set->lock +[ 287.297582] +[ 287.310023] Possible unsafe locking scenario: +[ 287.310023] +[ 287.315961] CPU0 CPU1 +[ 287.320510] ---- ---- +[ 287.325059] lock(&new_dev_set->lock); +[ 287.328917] +lock((work_completion)(&arg.work)); +[ 287.336153] lock(&new_dev_set->lock); +[ 287.342523] lock(&vdev->memory_lock); +[ 287.346382] +[ 287.346382] *** DEADLOCK *** +[ 287.346382] +[ 287.352315] 2 locks held by vfio_pci_sriov_/18636: +[ 287.357125] #0: ff45bea208ed3e18 (&group->group_lock){+.+.}-{4:4}, +at: vfio_group_fops_unl_ioctl+0x3e3/0x7b0 +[ 287.367048] #1: ff45bea3a96b8230 (&new_dev_set->lock){+.+.}-{4:4}, +at: vfio_group_fops_unl_ioctl+0x44d/0x7b0 +[ 287.376976] +[ 287.376976] stack backtrace: +[ 287.381353] CPU: 191 UID: 0 PID: 18636 Comm: vfio_pci_sriov_ +Tainted: G S 7.0.0-dbg-DEV #3 PREEMPTLAZY +[ 287.381355] Tainted: [S]=CPU_OUT_OF_SPEC +[ 287.381356] Call Trace: +[ 287.381357] +[ 287.381358] dump_stack_lvl+0x54/0x70 +[ 287.381361] print_circular_bug+0x2e1/0x300 +[ 287.381363] check_noncircular+0xf9/0x120 +[ 287.381364] ? __lock_acquire+0x5b4/0x2800 +[ 287.381366] __lock_acquire+0x14c6/0x2800 +[ 287.381368] ? pci_mmcfg_read+0x4f/0x220 +[ 287.381370] ? pci_mmcfg_write+0x57/0x220 +[ 287.381371] ? lock_acquire+0xd3/0x2f0 +[ 287.381373] ? pci_mmcfg_write+0x57/0x220 +[ 287.381374] ? lock_release+0xef/0x360 +[ 287.381376] ? vfio_pci_core_runtime_resume+0x1f/0xa0 +[ 287.381377] lock_acquire+0xd3/0x2f0 +[ 287.381378] ? vfio_pci_core_runtime_resume+0x1f/0xa0 +[ 287.381379] ? lock_is_held_type+0x76/0x100 +[ 287.381382] down_write+0x3a/0xc0 +[ 287.381382] ? vfio_pci_core_runtime_resume+0x1f/0xa0 +[ 287.381383] vfio_pci_core_runtime_resume+0x1f/0xa0 +[ 287.381384] ? __pfx_pci_pm_runtime_resume+0x10/0x10 +[ 287.381385] __rpm_callback+0x8c/0x310 +[ 287.381386] ? ktime_get_mono_fast_ns+0x3d/0xb0 +[ 287.381389] ? __pfx_pci_pm_runtime_resume+0x10/0x10 +[ 287.381390] rpm_resume+0x529/0x6f0 +[ 287.381392] ? lock_is_held_type+0x76/0x100 +[ 287.381394] __pm_runtime_resume+0x68/0x90 +[ 287.381396] vfio_pci_core_enable+0x44/0x310 +[ 287.381398] vfio_pci_open_device+0x1c/0x80 +[ 287.381399] vfio_df_open+0x10f/0x150 +[ 287.381401] vfio_group_fops_unl_ioctl+0x4a4/0x7b0 +[ 287.381402] __se_sys_ioctl+0x71/0xc0 +[ 287.381404] do_syscall_64+0x14d/0x750 +[ 287.381405] ? entry_SYSCALL_64_after_hwframe+0x77/0x7f +[ 287.381406] ? trace_irq_disable+0x25/0xd0 +[ 287.381409] entry_SYSCALL_64_after_hwframe+0x77/0x7f + +Introduce a private flag 'sriov_active' in the vfio_pci_core_device +struct. This allows the driver to track the SR-IOV power state requirement +without relying on pci_num_vf() while holding the memory_lock. The lock is +now only held to set the flag and ensure the device is in D0, after which +pci_enable_sriov() can be called without the lock. + +Fixes: f4162eb1e2fc ("vfio/pci: Change the PF power state to D0 before enabling VFs") +Cc: stable@vger.kernel.org +Suggested-by: Jason Gunthorpe +Suggested-by: Alex Williamson +Signed-off-by: Raghavendra Rao Ananta +Link: https://lore.kernel.org/r/20260514173449.3282188-1-rananta@google.com +[promote bitfield to plain bool to avoid storage-unit races] +Signed-off-by: Alex Williamson +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/vfio/pci/vfio_pci_core.c | 17 ++++++++++++++--- + include/linux/vfio_pci_core.h | 1 + + 2 files changed, 15 insertions(+), 3 deletions(-) + +--- a/drivers/vfio/pci/vfio_pci_core.c ++++ b/drivers/vfio/pci/vfio_pci_core.c +@@ -237,8 +237,11 @@ int vfio_pci_set_power_state(struct vfio + int ret; + + /* Prevent changing power state for PFs with VFs enabled */ +- if (pci_num_vf(pdev) && state > PCI_D0) +- return -EBUSY; ++ if (state > PCI_D0) { ++ lockdep_assert_held_write(&vdev->memory_lock); ++ if (vdev->sriov_active) ++ return -EBUSY; ++ } + + if (vdev->needs_pm_restore) { + if (pdev->current_state < PCI_D3hot && state >= PCI_D3hot) { +@@ -2271,8 +2274,9 @@ int vfio_pci_core_sriov_configure(struct + + down_write(&vdev->memory_lock); + vfio_pci_set_power_state(vdev, PCI_D0); +- ret = pci_enable_sriov(pdev, nr_virtfn); ++ vdev->sriov_active = true; + up_write(&vdev->memory_lock); ++ ret = pci_enable_sriov(pdev, nr_virtfn); + if (ret) { + pm_runtime_put(&pdev->dev); + goto out_del; +@@ -2286,6 +2290,13 @@ int vfio_pci_core_sriov_configure(struct + } + + out_del: ++ /* ++ * Avoid taking the memory_lock intentionally. A race with a power ++ * state transition would at most result in an -EBUSY, leaving the ++ * device in PCI_D0. ++ */ ++ vdev->sriov_active = false; ++ + mutex_lock(&vfio_pci_sriov_pfs_mutex); + list_del_init(&vdev->sriov_pfs_item); + out_unlock: +--- a/include/linux/vfio_pci_core.h ++++ b/include/linux/vfio_pci_core.h +@@ -80,6 +80,7 @@ struct vfio_pci_core_device { + bool needs_pm_restore:1; + bool pm_intx_masked:1; + bool pm_runtime_engaged:1; ++ bool sriov_active; + struct pci_saved_state *pci_saved_state; + struct pci_saved_state *pm_save; + int ioeventfds_nr; diff --git a/queue-6.1/vfio-pci-use-bitfield-for-struct-vfio_pci_core_device-flags.patch b/queue-6.1/vfio-pci-use-bitfield-for-struct-vfio_pci_core_device-flags.patch new file mode 100644 index 0000000000..faba6bc056 --- /dev/null +++ b/queue-6.1/vfio-pci-use-bitfield-for-struct-vfio_pci_core_device-flags.patch @@ -0,0 +1,70 @@ +From stable+bounces-274108-greg=kroah.com@vger.kernel.org Tue Jul 14 05:33:22 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 23:33:00 -0400 +Subject: vfio/pci: Use bitfield for struct vfio_pci_core_device flags +To: stable@vger.kernel.org +Cc: Reinette Chatre , Jason Gunthorpe , Kevin Tian , Thomas Gleixner , Alex Williamson , Sasha Levin +Message-ID: <20260714033301.2397090-1-sashal@kernel.org> + +From: Reinette Chatre + +[ Upstream commit 9cd0f6d5cbb6fda09aa83beb8146c287a552017e ] + +struct vfio_pci_core_device contains eleven boolean flags. +Boolean flags clearly indicate their usage but space usage +starts to be a concern when there are many. + +An upcoming change adds another boolean flag to +struct vfio_pci_core_device, thereby increasing the concern +that the boolean flags are consuming unnecessary space. + +Transition the boolean flags to use bitfields. On a system that +uses one byte per boolean this reduces the space consumed +by existing flags from 11 bytes to 2 bytes with room for +a few more flags without increasing the structure's size. + +Suggested-by: Jason Gunthorpe +Signed-off-by: Reinette Chatre +Reviewed-by: Kevin Tian +Acked-by: Thomas Gleixner +Reviewed-by: Jason Gunthorpe +Link: https://lore.kernel.org/r/cf34bf0499c889554a8105eeb18cc0ab673005be.1683740667.git.reinette.chatre@intel.com +Signed-off-by: Alex Williamson +Stable-dep-of: 40ef3edf151e ("vfio/pci: Use a private flag to prevent power state change with VFs") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/vfio_pci_core.h | 22 +++++++++++----------- + 1 file changed, 11 insertions(+), 11 deletions(-) + +--- a/include/linux/vfio_pci_core.h ++++ b/include/linux/vfio_pci_core.h +@@ -69,17 +69,17 @@ struct vfio_pci_core_device { + u16 msix_size; + u32 msix_offset; + u32 rbar[7]; +- bool pci_2_3; +- bool virq_disabled; +- bool reset_works; +- bool extended_caps; +- bool bardirty; +- bool has_vga; +- bool needs_reset; +- bool nointx; +- bool needs_pm_restore; +- bool pm_intx_masked; +- bool pm_runtime_engaged; ++ bool pci_2_3:1; ++ bool virq_disabled:1; ++ bool reset_works:1; ++ bool extended_caps:1; ++ bool bardirty:1; ++ bool has_vga:1; ++ bool needs_reset:1; ++ bool nointx:1; ++ bool needs_pm_restore:1; ++ bool pm_intx_masked:1; ++ bool pm_runtime_engaged:1; + struct pci_saved_state *pci_saved_state; + struct pci_saved_state *pm_save; + int ioeventfds_nr; diff --git a/queue-6.1/writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch b/queue-6.1/writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch new file mode 100644 index 0000000000..e95936a89a --- /dev/null +++ b/queue-6.1/writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch @@ -0,0 +1,294 @@ +From stable+bounces-276803-greg=kroah.com@vger.kernel.org Thu Jul 16 20:39:14 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 14:21:57 -0400 +Subject: writeback: Avoid contention on wb->list_lock when switching inodes +To: stable@vger.kernel.org +Cc: Jan Kara , Tejun Heo , Sasha Levin +Message-ID: <20260716182158.920140-1-sashal@kernel.org> + +From: Jan Kara + +[ Upstream commit e1b849cfa6b61f1c866a908c9e8dd9b5aaab820b ] + +There can be multiple inode switch works that are trying to switch +inodes to / from the same wb. This can happen in particular if some +cgroup exits which owns many (thousands) inodes and we need to switch +them all. In this case several inode_switch_wbs_work_fn() instances will +be just spinning on the same wb->list_lock while only one of them makes +forward progress. This wastes CPU cycles and quickly leads to softlockup +reports and unusable system. + +Instead of running several inode_switch_wbs_work_fn() instances in +parallel switching to the same wb and contending on wb->list_lock, run +just one work item per wb and manage a queue of isw items switching to +this wb. + +Acked-by: Tejun Heo +Signed-off-by: Jan Kara +Stable-dep-of: cba38ec4cbd3 ("writeback: fix race between cgroup_writeback_umount() and inode_switch_wbs()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/fs-writeback.c | 99 ++++++++++++++++++++++++--------------- + include/linux/backing-dev-defs.h | 4 + + include/linux/writeback.h | 2 + mm/backing-dev.c | 5 + + 4 files changed, 74 insertions(+), 36 deletions(-) + +--- a/fs/fs-writeback.c ++++ b/fs/fs-writeback.c +@@ -343,7 +343,8 @@ static struct bdi_writeback *inode_to_wb + } + + struct inode_switch_wbs_context { +- struct rcu_work work; ++ /* List of queued switching contexts for the wb */ ++ struct llist_node list; + + /* + * Multiple inodes can be switched at once. The switching procedure +@@ -353,7 +354,6 @@ struct inode_switch_wbs_context { + * array embedded into struct inode_switch_wbs_context. Otherwise + * an inode could be left in a non-consistent state. + */ +- struct bdi_writeback *new_wb; + struct inode *inodes[]; + }; + +@@ -462,13 +462,11 @@ skip_switch: + return switched; + } + +-static void inode_switch_wbs_work_fn(struct work_struct *work) ++static void process_inode_switch_wbs(struct bdi_writeback *new_wb, ++ struct inode_switch_wbs_context *isw) + { +- struct inode_switch_wbs_context *isw = +- container_of(to_rcu_work(work), struct inode_switch_wbs_context, work); + struct backing_dev_info *bdi = inode_to_bdi(isw->inodes[0]); + struct bdi_writeback *old_wb = isw->inodes[0]->i_wb; +- struct bdi_writeback *new_wb = isw->new_wb; + unsigned long nr_switched = 0; + struct inode **inodep; + +@@ -528,6 +526,38 @@ relock: + atomic_dec(&isw_nr_in_flight); + } + ++void inode_switch_wbs_work_fn(struct work_struct *work) ++{ ++ struct bdi_writeback *new_wb = container_of(work, struct bdi_writeback, ++ switch_work); ++ struct inode_switch_wbs_context *isw, *next_isw; ++ struct llist_node *list; ++ ++ /* ++ * Grab out reference to wb so that it cannot get freed under us ++ * after we process all the isw items. ++ */ ++ wb_get(new_wb); ++ while (1) { ++ list = llist_del_all(&new_wb->switch_wbs_ctxs); ++ /* Nothing to do? */ ++ if (!list) ++ break; ++ /* ++ * In addition to synchronizing among switchers, I_WB_SWITCH ++ * tells the RCU protected stat update paths to grab the i_page ++ * lock so that stat transfer can synchronize against them. ++ * Let's continue after I_WB_SWITCH is guaranteed to be ++ * visible. ++ */ ++ synchronize_rcu(); ++ ++ llist_for_each_entry_safe(isw, next_isw, list, list) ++ process_inode_switch_wbs(new_wb, isw); ++ } ++ wb_put(new_wb); ++} ++ + static bool inode_prepare_wbs_switch(struct inode *inode, + struct bdi_writeback *new_wb) + { +@@ -557,6 +587,13 @@ static bool inode_prepare_wbs_switch(str + return true; + } + ++static void wb_queue_isw(struct bdi_writeback *wb, ++ struct inode_switch_wbs_context *isw) ++{ ++ if (llist_add(&isw->list, &wb->switch_wbs_ctxs)) ++ queue_work(isw_wq, &wb->switch_work); ++} ++ + /** + * inode_switch_wbs - change the wb association of an inode + * @inode: target inode +@@ -570,6 +607,7 @@ static void inode_switch_wbs(struct inod + struct backing_dev_info *bdi = inode_to_bdi(inode); + struct cgroup_subsys_state *memcg_css; + struct inode_switch_wbs_context *isw; ++ struct bdi_writeback *new_wb = NULL; + + /* noop if seems to be already in progress */ + if (inode->i_state & I_WB_SWITCH) +@@ -594,40 +632,34 @@ static void inode_switch_wbs(struct inod + if (!memcg_css) + goto out_free; + +- isw->new_wb = wb_get_create(bdi, memcg_css, GFP_ATOMIC); ++ new_wb = wb_get_create(bdi, memcg_css, GFP_ATOMIC); + css_put(memcg_css); +- if (!isw->new_wb) ++ if (!new_wb) + goto out_free; + +- if (!inode_prepare_wbs_switch(inode, isw->new_wb)) ++ if (!inode_prepare_wbs_switch(inode, new_wb)) + goto out_free; + + isw->inodes[0] = inode; + +- /* +- * In addition to synchronizing among switchers, I_WB_SWITCH tells +- * the RCU protected stat update paths to grab the i_page +- * lock so that stat transfer can synchronize against them. +- * Let's continue after I_WB_SWITCH is guaranteed to be visible. +- */ +- INIT_RCU_WORK(&isw->work, inode_switch_wbs_work_fn); +- queue_rcu_work(isw_wq, &isw->work); ++ wb_queue_isw(new_wb, isw); + return; + + out_free: + atomic_dec(&isw_nr_in_flight); +- if (isw->new_wb) +- wb_put(isw->new_wb); ++ if (new_wb) ++ wb_put(new_wb); + kfree(isw); + } + +-static bool isw_prepare_wbs_switch(struct inode_switch_wbs_context *isw, ++static bool isw_prepare_wbs_switch(struct bdi_writeback *new_wb, ++ struct inode_switch_wbs_context *isw, + struct list_head *list, int *nr) + { + struct inode *inode; + + list_for_each_entry(inode, list, i_io_list) { +- if (!inode_prepare_wbs_switch(inode, isw->new_wb)) ++ if (!inode_prepare_wbs_switch(inode, new_wb)) + continue; + + isw->inodes[*nr] = inode; +@@ -651,6 +683,7 @@ bool cleanup_offline_cgwb(struct bdi_wri + { + struct cgroup_subsys_state *memcg_css; + struct inode_switch_wbs_context *isw; ++ struct bdi_writeback *new_wb; + int nr; + bool restart = false; + +@@ -663,12 +696,12 @@ bool cleanup_offline_cgwb(struct bdi_wri + + for (memcg_css = wb->memcg_css->parent; memcg_css; + memcg_css = memcg_css->parent) { +- isw->new_wb = wb_get_create(wb->bdi, memcg_css, GFP_KERNEL); +- if (isw->new_wb) ++ new_wb = wb_get_create(wb->bdi, memcg_css, GFP_KERNEL); ++ if (new_wb) + break; + } +- if (unlikely(!isw->new_wb)) +- isw->new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */ ++ if (unlikely(!new_wb)) ++ new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */ + + nr = 0; + spin_lock(&wb->list_lock); +@@ -680,27 +713,21 @@ bool cleanup_offline_cgwb(struct bdi_wri + * bandwidth restrictions, as writeback of inode metadata is not + * accounted for. + */ +- restart = isw_prepare_wbs_switch(isw, &wb->b_attached, &nr); ++ restart = isw_prepare_wbs_switch(new_wb, isw, &wb->b_attached, &nr); + if (!restart) +- restart = isw_prepare_wbs_switch(isw, &wb->b_dirty_time, &nr); ++ restart = isw_prepare_wbs_switch(new_wb, isw, &wb->b_dirty_time, ++ &nr); + spin_unlock(&wb->list_lock); + + /* no attached inodes? bail out */ + if (nr == 0) { + atomic_dec(&isw_nr_in_flight); +- wb_put(isw->new_wb); ++ wb_put(new_wb); + kfree(isw); + return restart; + } + +- /* +- * In addition to synchronizing among switchers, I_WB_SWITCH tells +- * the RCU protected stat update paths to grab the i_page +- * lock so that stat transfer can synchronize against them. +- * Let's continue after I_WB_SWITCH is guaranteed to be visible. +- */ +- INIT_RCU_WORK(&isw->work, inode_switch_wbs_work_fn); +- queue_rcu_work(isw_wq, &isw->work); ++ wb_queue_isw(new_wb, isw); + + return restart; + } +--- a/include/linux/backing-dev-defs.h ++++ b/include/linux/backing-dev-defs.h +@@ -154,6 +154,10 @@ struct bdi_writeback { + struct list_head blkcg_node; /* anchored at blkcg->cgwb_list */ + struct list_head b_attached; /* attached inodes, protected by list_lock */ + struct list_head offline_node; /* anchored at offline_cgwbs */ ++ struct work_struct switch_work; /* work used to perform inode switching ++ * to this wb */ ++ struct llist_head switch_wbs_ctxs; /* queued contexts for ++ * writeback switching */ + + union { + struct work_struct release_work; +--- a/include/linux/writeback.h ++++ b/include/linux/writeback.h +@@ -288,6 +288,8 @@ static inline void wbc_init_bio(struct w + bio_associate_blkg_from_css(bio, wbc->wb->blkcg_css); + } + ++void inode_switch_wbs_work_fn(struct work_struct *work); ++ + #else /* CONFIG_CGROUP_WRITEBACK */ + + static inline void inode_attach_wb(struct inode *inode, struct page *page) +--- a/mm/backing-dev.c ++++ b/mm/backing-dev.c +@@ -415,6 +415,7 @@ static void cgwb_release_workfn(struct w + wb_exit(wb); + bdi_put(bdi); + WARN_ON_ONCE(!list_empty(&wb->b_attached)); ++ WARN_ON_ONCE(work_pending(&wb->switch_work)); + call_rcu(&wb->rcu, cgwb_free_rcu); + } + +@@ -491,6 +492,8 @@ static int cgwb_create(struct backing_de + wb->memcg_css = memcg_css; + wb->blkcg_css = blkcg_css; + INIT_LIST_HEAD(&wb->b_attached); ++ INIT_WORK(&wb->switch_work, inode_switch_wbs_work_fn); ++ init_llist_head(&wb->switch_wbs_ctxs); + INIT_WORK(&wb->release_work, cgwb_release_workfn); + set_bit(WB_registered, &wb->state); + bdi_get(bdi); +@@ -624,6 +627,8 @@ static int cgwb_bdi_init(struct backing_ + if (!ret) { + bdi->wb.memcg_css = &root_mem_cgroup->css; + bdi->wb.blkcg_css = blkcg_root_css; ++ INIT_WORK(&bdi->wb.switch_work, inode_switch_wbs_work_fn); ++ init_llist_head(&bdi->wb.switch_wbs_ctxs); + } + return ret; + } diff --git a/queue-6.1/writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch b/queue-6.1/writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch new file mode 100644 index 0000000000..ce53b11cf3 --- /dev/null +++ b/queue-6.1/writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch @@ -0,0 +1,181 @@ +From stable+bounces-276804-greg=kroah.com@vger.kernel.org Thu Jul 16 20:28:53 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 14:21:58 -0400 +Subject: writeback: fix race between cgroup_writeback_umount() and inode_switch_wbs() +To: stable@vger.kernel.org +Cc: Baokun Li , Jan Kara , Tejun Heo , "Christian Brauner (Amutable)" , Sasha Levin +Message-ID: <20260716182158.920140-2-sashal@kernel.org> + +From: Baokun Li + +[ Upstream commit cba38ec4cbd3a7b8b942a8d52531a05be8a9ff0d ] + +When a container exits, the following BUG_ON() is occasionally triggered: + +================================================================== + VFS: Busy inodes after unmount of sdb (ext4) + ------------[ cut here ]------------ + kernel BUG at fs/super.c:695! + CPU: 3 PID: 6 Comm: containerd-shim Tainted: G OE K 6.6 #1 + pstate: 63400009 (nZCv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--) + pc : generic_shutdown_super+0xf0/0x100 + lr : generic_shutdown_super+0xf0/0x100 + Call trace: + generic_shutdown_super+0xf0/0x100 + kill_block_super+0x20/0x48 + ext4_kill_sb+0x28/0x60 + deactivate_locked_super+0x54/0x130 + deactivate_super+0x84/0xa0 + cleanup_mnt+0xa4/0x140 + __cleanup_mnt+0x18/0x28 + task_work_run+0x78/0xe0 + do_notify_resume+0x204/0x240 +================================================================== + +The root cause is a race between cgroup_writeback_umount() and +inode_switch_wbs()/cleanup_offline_cgwb(). There is a window between +inode_prepare_wbs_switch() returning true and the subsequent +wb_queue_isw() call. Following is the process that triggers the issue: + + CPU A (umount) | CPU B (writeback) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + inode_switch_wbs/cleanup_offline_cgwb + atomic_inc(&isw_nr_in_flight) + inode_prepare_wbs_switch + -> passes SB_ACTIVE check + __iget(inode) + generic_shutdown_super + sb->s_flags &= ~SB_ACTIVE + cgroup_writeback_umount(sb) + smp_mb() + atomic_read(&isw_nr_in_flight) + rcu_barrier() + -> no pending RCU callbacks + flush_workqueue(isw_wq) + -> nothing queued, returns + evict_inodes(sb) + -> Inode skipped as isw still holds a ref. + sop->put_super(sb) + /* destroys percpu counters */ + -> VFS: Busy inodes after unmount! + wb_queue_isw() + queue_work(isw_wq, ...) + /* later in work function */ + inode_switch_wbs_work_fn + process_inode_switch_wbs + iput() -> evict + percpu_counter_dec() // UAF! + +Fix this by extending the RCU read-side critical section in +inode_switch_wbs() and cleanup_offline_cgwb() to cover from +inode_prepare_wbs_switch() through wb_queue_isw(). Since there is +no sleep in this window, rcu_read_lock() can be used. Then add a +synchronize_rcu() in cgroup_writeback_umount() before the existing +rcu_barrier(), so that all in-flight switchers that have passed the +SB_ACTIVE check have completed queue_work() before flush_workqueue() +is called. + +The existing rcu_barrier() is intentionally retained so this fix can +be backported unchanged to stable kernels (5.10.y, 6.6.y, ...) that +still queue switches via queue_rcu_work(). It is a no-op on current +mainline (since commit e1b849cfa6b6 ("writeback: Avoid contention on +wb->list_lock when switching inodes")) and is removed in a follow-up +patch. + +Fixes: a1a0e23e4903 ("writeback: flush inode cgroup wb switches instead of pinning super_block") +Cc: stable@vger.kernel.org +Suggested-by: Jan Kara +Link: https://lore.kernel.org/all/mxnjq2l6guusfchvauxr3v7c4bwjasybxlleqbbh4efloeqspz@iqylk76ohufz +Reviewed-by: Jan Kara +Signed-off-by: Baokun Li +Link: https://patch.msgid.link/20260521095016.2791354-2-libaokun@linux.alibaba.com +Acked-by: Tejun Heo +Signed-off-by: Christian Brauner (Amutable) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/fs-writeback.c | 31 +++++++++++++++++++++++++++++-- + 1 file changed, 29 insertions(+), 2 deletions(-) + +--- a/fs/fs-writeback.c ++++ b/fs/fs-writeback.c +@@ -623,12 +623,19 @@ static void inode_switch_wbs(struct inod + + atomic_inc(&isw_nr_in_flight); + +- /* find and pin the new wb */ ++ /* ++ * Paired with synchronize_rcu() in cgroup_writeback_umount(): ++ * holding rcu_read_lock across inode_prepare_wbs_switch() ++ * (covering the SB_ACTIVE check and the inode grab) and ++ * wb_queue_isw() ensures synchronize_rcu() cannot return until ++ * the work is queued, so the subsequent flush_workqueue() will ++ * wait for the switch. ++ */ + rcu_read_lock(); ++ /* find and pin the new wb */ + memcg_css = css_from_id(new_wb_id, &memory_cgrp_subsys); + if (memcg_css && !css_tryget(memcg_css)) + memcg_css = NULL; +- rcu_read_unlock(); + if (!memcg_css) + goto out_free; + +@@ -643,9 +650,11 @@ static void inode_switch_wbs(struct inod + isw->inodes[0] = inode; + + wb_queue_isw(new_wb, isw); ++ rcu_read_unlock(); + return; + + out_free: ++ rcu_read_unlock(); + atomic_dec(&isw_nr_in_flight); + if (new_wb) + wb_put(new_wb); +@@ -704,6 +713,14 @@ bool cleanup_offline_cgwb(struct bdi_wri + new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */ + + nr = 0; ++ /* ++ * Paired with synchronize_rcu() in cgroup_writeback_umount(). ++ * Holding rcu_read_lock across the SB_ACTIVE check, the inode grab ++ * and wb_queue_isw() ensures synchronize_rcu() cannot return until ++ * the work is queued, so the subsequent flush_workqueue() will wait ++ * for the switch. ++ */ ++ rcu_read_lock(); + spin_lock(&wb->list_lock); + /* + * In addition to the inodes that have completed writeback, also switch +@@ -721,6 +738,7 @@ bool cleanup_offline_cgwb(struct bdi_wri + + /* no attached inodes? bail out */ + if (nr == 0) { ++ rcu_read_unlock(); + atomic_dec(&isw_nr_in_flight); + wb_put(new_wb); + kfree(isw); +@@ -728,6 +746,7 @@ bool cleanup_offline_cgwb(struct bdi_wri + } + + wb_queue_isw(new_wb, isw); ++ rcu_read_unlock(); + + return restart; + } +@@ -1159,6 +1178,14 @@ void cgroup_writeback_umount(void) + + if (atomic_read(&isw_nr_in_flight)) { + /* ++ * Paired with rcu_read_lock() in inode_switch_wbs() and ++ * cleanup_offline_cgwb(). synchronize_rcu() waits for any ++ * in-flight switcher that already passed the SB_ACTIVE check ++ * to finish queueing its work, so flush_workqueue() below ++ * will then drain it. ++ */ ++ synchronize_rcu(); ++ /* + * Use rcu_barrier() to wait for all pending callbacks to + * ensure that all in-flight wb switches are in the workqueue. + */