--- /dev/null
+From d05cb470663a2a1879277e544f69e660208f08f2 Mon Sep 17 00:00:00 2001
+From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
+Date: Fri, 29 Dec 2023 11:51:34 -0500
+Subject: ftrace: Fix modification of direct_function hash while in use
+
+From: Steven Rostedt (Google) <rostedt@goodmis.org>
+
+commit d05cb470663a2a1879277e544f69e660208f08f2 upstream.
+
+Masami Hiramatsu reported a memory leak in register_ftrace_direct() where
+if the number of new entries are added is large enough to cause two
+allocations in the loop:
+
+ for (i = 0; i < size; i++) {
+ hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
+ new = ftrace_add_rec_direct(entry->ip, addr, &free_hash);
+ if (!new)
+ goto out_remove;
+ entry->direct = addr;
+ }
+ }
+
+Where ftrace_add_rec_direct() has:
+
+ if (ftrace_hash_empty(direct_functions) ||
+ direct_functions->count > 2 * (1 << direct_functions->size_bits)) {
+ struct ftrace_hash *new_hash;
+ int size = ftrace_hash_empty(direct_functions) ? 0 :
+ direct_functions->count + 1;
+
+ if (size < 32)
+ size = 32;
+
+ new_hash = dup_hash(direct_functions, size);
+ if (!new_hash)
+ return NULL;
+
+ *free_hash = direct_functions;
+ direct_functions = new_hash;
+ }
+
+The "*free_hash = direct_functions;" can happen twice, losing the previous
+allocation of direct_functions.
+
+But this also exposed a more serious bug.
+
+The modification of direct_functions above is not safe. As
+direct_functions can be referenced at any time to find what direct caller
+it should call, the time between:
+
+ new_hash = dup_hash(direct_functions, size);
+ and
+ direct_functions = new_hash;
+
+can have a race with another CPU (or even this one if it gets interrupted),
+and the entries being moved to the new hash are not referenced.
+
+That's because the "dup_hash()" is really misnamed and is really a
+"move_hash()". It moves the entries from the old hash to the new one.
+
+Now even if that was changed, this code is not proper as direct_functions
+should not be updated until the end. That is the best way to handle
+function reference changes, and is the way other parts of ftrace handles
+this.
+
+The following is done:
+
+ 1. Change add_hash_entry() to return the entry it created and inserted
+ into the hash, and not just return success or not.
+
+ 2. Replace ftrace_add_rec_direct() with add_hash_entry(), and remove
+ the former.
+
+ 3. Allocate a "new_hash" at the start that is made for holding both the
+ new hash entries as well as the existing entries in direct_functions.
+
+ 4. Copy (not move) the direct_function entries over to the new_hash.
+
+ 5. Copy the entries of the added hash to the new_hash.
+
+ 6. If everything succeeds, then use rcu_pointer_assign() to update the
+ direct_functions with the new_hash.
+
+This simplifies the code and fixes both the memory leak as well as the
+race condition mentioned above.
+
+Link: https://lore.kernel.org/all/170368070504.42064.8960569647118388081.stgit@devnote2/
+Link: https://lore.kernel.org/linux-trace-kernel/20231229115134.08dd5174@gandalf.local.home
+
+Cc: stable@vger.kernel.org
+Cc: Mark Rutland <mark.rutland@arm.com>
+Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+Cc: Jiri Olsa <jolsa@kernel.org>
+Cc: Alexei Starovoitov <ast@kernel.org>
+Cc: Daniel Borkmann <daniel@iogearbox.net>
+Acked-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Fixes: 763e34e74bb7d ("ftrace: Add register_ftrace_direct()")
+Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/trace/ftrace.c | 100 +++++++++++++++++++++++---------------------------
+ 1 file changed, 47 insertions(+), 53 deletions(-)
+
+--- a/kernel/trace/ftrace.c
++++ b/kernel/trace/ftrace.c
+@@ -1183,18 +1183,19 @@ static void __add_hash_entry(struct ftra
+ hash->count++;
+ }
+
+-static int add_hash_entry(struct ftrace_hash *hash, unsigned long ip)
++static struct ftrace_func_entry *
++add_hash_entry(struct ftrace_hash *hash, unsigned long ip)
+ {
+ struct ftrace_func_entry *entry;
+
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+- return -ENOMEM;
++ return NULL;
+
+ entry->ip = ip;
+ __add_hash_entry(hash, entry);
+
+- return 0;
++ return entry;
+ }
+
+ static void
+@@ -1349,7 +1350,6 @@ alloc_and_copy_ftrace_hash(int size_bits
+ struct ftrace_func_entry *entry;
+ struct ftrace_hash *new_hash;
+ int size;
+- int ret;
+ int i;
+
+ new_hash = alloc_ftrace_hash(size_bits);
+@@ -1366,8 +1366,7 @@ alloc_and_copy_ftrace_hash(int size_bits
+ size = 1 << hash->size_bits;
+ for (i = 0; i < size; i++) {
+ hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
+- ret = add_hash_entry(new_hash, entry->ip);
+- if (ret < 0)
++ if (add_hash_entry(new_hash, entry->ip) == NULL)
+ goto free_hash;
+ }
+ }
+@@ -2536,7 +2535,7 @@ ftrace_find_unique_ops(struct dyn_ftrace
+
+ #ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
+ /* Protected by rcu_tasks for reading, and direct_mutex for writing */
+-static struct ftrace_hash *direct_functions = EMPTY_HASH;
++static struct ftrace_hash __rcu *direct_functions = EMPTY_HASH;
+ static DEFINE_MUTEX(direct_mutex);
+ int ftrace_direct_func_count;
+
+@@ -2555,39 +2554,6 @@ unsigned long ftrace_find_rec_direct(uns
+ return entry->direct;
+ }
+
+-static struct ftrace_func_entry*
+-ftrace_add_rec_direct(unsigned long ip, unsigned long addr,
+- struct ftrace_hash **free_hash)
+-{
+- struct ftrace_func_entry *entry;
+-
+- if (ftrace_hash_empty(direct_functions) ||
+- direct_functions->count > 2 * (1 << direct_functions->size_bits)) {
+- struct ftrace_hash *new_hash;
+- int size = ftrace_hash_empty(direct_functions) ? 0 :
+- direct_functions->count + 1;
+-
+- if (size < 32)
+- size = 32;
+-
+- new_hash = dup_hash(direct_functions, size);
+- if (!new_hash)
+- return NULL;
+-
+- *free_hash = direct_functions;
+- direct_functions = new_hash;
+- }
+-
+- entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+- if (!entry)
+- return NULL;
+-
+- entry->ip = ip;
+- entry->direct = addr;
+- __add_hash_entry(direct_functions, entry);
+- return entry;
+-}
+-
+ static void call_direct_funcs(unsigned long ip, unsigned long pip,
+ struct ftrace_ops *ops, struct ftrace_regs *fregs)
+ {
+@@ -4223,8 +4189,8 @@ enter_record(struct ftrace_hash *hash, s
+ /* Do nothing if it exists */
+ if (entry)
+ return 0;
+-
+- ret = add_hash_entry(hash, rec->ip);
++ if (add_hash_entry(hash, rec->ip) == NULL)
++ ret = -ENOMEM;
+ }
+ return ret;
+ }
+@@ -5266,7 +5232,8 @@ __ftrace_match_addr(struct ftrace_hash *
+ return 0;
+ }
+
+- return add_hash_entry(hash, ip);
++ entry = add_hash_entry(hash, ip);
++ return entry ? 0 : -ENOMEM;
+ }
+
+ static int
+@@ -5410,7 +5377,7 @@ static void remove_direct_functions_hash
+ */
+ int register_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
+ {
+- struct ftrace_hash *hash, *free_hash = NULL;
++ struct ftrace_hash *hash, *new_hash = NULL, *free_hash = NULL;
+ struct ftrace_func_entry *entry, *new;
+ int err = -EBUSY, size, i;
+
+@@ -5436,17 +5403,44 @@ int register_ftrace_direct(struct ftrace
+ }
+ }
+
+- /* ... and insert them to direct_functions hash. */
+ err = -ENOMEM;
++
++ /* Make a copy hash to place the new and the old entries in */
++ size = hash->count + direct_functions->count;
++ if (size > 32)
++ size = 32;
++ new_hash = alloc_ftrace_hash(fls(size));
++ if (!new_hash)
++ goto out_unlock;
++
++ /* Now copy over the existing direct entries */
++ size = 1 << direct_functions->size_bits;
++ for (i = 0; i < size; i++) {
++ hlist_for_each_entry(entry, &direct_functions->buckets[i], hlist) {
++ new = add_hash_entry(new_hash, entry->ip);
++ if (!new)
++ goto out_unlock;
++ new->direct = entry->direct;
++ }
++ }
++
++ /* ... and add the new entries */
++ size = 1 << hash->size_bits;
+ for (i = 0; i < size; i++) {
+ hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
+- new = ftrace_add_rec_direct(entry->ip, addr, &free_hash);
++ new = add_hash_entry(new_hash, entry->ip);
+ if (!new)
+- goto out_remove;
++ goto out_unlock;
++ /* Update both the copy and the hash entry */
++ new->direct = addr;
+ entry->direct = addr;
+ }
+ }
+
++ free_hash = direct_functions;
++ rcu_assign_pointer(direct_functions, new_hash);
++ new_hash = NULL;
++
+ ops->func = call_direct_funcs;
+ ops->flags = MULTI_FLAGS;
+ ops->trampoline = FTRACE_REGS_ADDR;
+@@ -5454,17 +5448,17 @@ int register_ftrace_direct(struct ftrace
+
+ err = register_ftrace_function_nolock(ops);
+
+- out_remove:
+- if (err)
+- remove_direct_functions_hash(hash, addr);
+-
+ out_unlock:
+ mutex_unlock(&direct_mutex);
+
+- if (free_hash) {
++ if (free_hash && free_hash != EMPTY_HASH) {
+ synchronize_rcu_tasks();
+ free_ftrace_hash(free_hash);
+ }
++
++ if (new_hash)
++ free_ftrace_hash(new_hash);
++
+ return err;
+ }
+ EXPORT_SYMBOL_GPL(register_ftrace_direct);
+@@ -6309,7 +6303,7 @@ ftrace_graph_set_hash(struct ftrace_hash
+
+ if (entry)
+ continue;
+- if (add_hash_entry(hash, rec->ip) < 0)
++ if (add_hash_entry(hash, rec->ip) == NULL)
+ goto out;
+ } else {
+ if (entry) {
--- /dev/null
+From 623b1f896fa8a669a277ee5a258307a16c7377a3 Mon Sep 17 00:00:00 2001
+From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
+Date: Tue, 26 Dec 2023 12:59:02 -0500
+Subject: ring-buffer: Fix wake ups when buffer_percent is set to 100
+
+From: Steven Rostedt (Google) <rostedt@goodmis.org>
+
+commit 623b1f896fa8a669a277ee5a258307a16c7377a3 upstream.
+
+The tracefs file "buffer_percent" is to allow user space to set a
+water-mark on how much of the tracing ring buffer needs to be filled in
+order to wake up a blocked reader.
+
+ 0 - is to wait until any data is in the buffer
+ 1 - is to wait for 1% of the sub buffers to be filled
+ 50 - would be half of the sub buffers are filled with data
+ 100 - is not to wake the waiter until the ring buffer is completely full
+
+Unfortunately the test for being full was:
+
+ dirty = ring_buffer_nr_dirty_pages(buffer, cpu);
+ return (dirty * 100) > (full * nr_pages);
+
+Where "full" is the value for "buffer_percent".
+
+There is two issues with the above when full == 100.
+
+1. dirty * 100 > 100 * nr_pages will never be true
+ That is, the above is basically saying that if the user sets
+ buffer_percent to 100, more pages need to be dirty than exist in the
+ ring buffer!
+
+2. The page that the writer is on is never considered dirty, as dirty
+ pages are only those that are full. When the writer goes to a new
+ sub-buffer, it clears the contents of that sub-buffer.
+
+That is, even if the check was ">=" it would still not be equal as the
+most pages that can be considered "dirty" is nr_pages - 1.
+
+To fix this, add one to dirty and use ">=" in the compare.
+
+Link: https://lore.kernel.org/linux-trace-kernel/20231226125902.4a057f1d@gandalf.local.home
+
+Cc: stable@vger.kernel.org
+Cc: Mark Rutland <mark.rutland@arm.com>
+Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+Acked-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Fixes: 03329f9939781 ("tracing: Add tracefs file buffer_percentage")
+Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/trace/ring_buffer.c | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+--- a/kernel/trace/ring_buffer.c
++++ b/kernel/trace/ring_buffer.c
+@@ -881,9 +881,14 @@ static __always_inline bool full_hit(str
+ if (!nr_pages || !full)
+ return true;
+
+- dirty = ring_buffer_nr_dirty_pages(buffer, cpu);
++ /*
++ * Add one as dirty will never equal nr_pages, as the sub-buffer
++ * that the writer is on is not counted as dirty.
++ * This is needed if "buffer_percent" is set to 100.
++ */
++ dirty = ring_buffer_nr_dirty_pages(buffer, cpu) + 1;
+
+- return (dirty * 100) > (full * nr_pages);
++ return (dirty * 100) >= (full * nr_pages);
+ }
+
+ /*
mm-memory-failure-cast-index-to-loff_t-before-shifting-it.patch
mm-memory-failure-check-the-mapcount-of-the-precise-page.patch
revert-nvme-fc-fix-race-between-error-recovery-and-creating-association.patch
+ring-buffer-fix-wake-ups-when-buffer_percent-is-set-to-100.patch
+ftrace-fix-modification-of-direct_function-hash-while-in-use.patch
+tracing-fix-blocked-reader-of-snapshot-buffer.patch
+wifi-cfg80211-fix-cqm-for-non-range-use.patch
+wifi-nl80211-fix-deadlock-in-nl80211_set_cqm_rssi-6.6.x.patch
--- /dev/null
+From 39a7dc23a1ed0fe81141792a09449d124c5953bd Mon Sep 17 00:00:00 2001
+From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
+Date: Thu, 28 Dec 2023 09:51:49 -0500
+Subject: tracing: Fix blocked reader of snapshot buffer
+
+From: Steven Rostedt (Google) <rostedt@goodmis.org>
+
+commit 39a7dc23a1ed0fe81141792a09449d124c5953bd upstream.
+
+If an application blocks on the snapshot or snapshot_raw files, expecting
+to be woken up when a snapshot occurs, it will not happen. Or it may
+happen with an unexpected result.
+
+That result is that the application will be reading the main buffer
+instead of the snapshot buffer. That is because when the snapshot occurs,
+the main and snapshot buffers are swapped. But the reader has a descriptor
+still pointing to the buffer that it originally connected to.
+
+This is fine for the main buffer readers, as they may be blocked waiting
+for a watermark to be hit, and when a snapshot occurs, the data that the
+main readers want is now on the snapshot buffer.
+
+But for waiters of the snapshot buffer, they are waiting for an event to
+occur that will trigger the snapshot and they can then consume it quickly
+to save the snapshot before the next snapshot occurs. But to do this, they
+need to read the new snapshot buffer, not the old one that is now
+receiving new data.
+
+Also, it does not make sense to have a watermark "buffer_percent" on the
+snapshot buffer, as the snapshot buffer is static and does not receive new
+data except all at once.
+
+Link: https://lore.kernel.org/linux-trace-kernel/20231228095149.77f5b45d@gandalf.local.home
+
+Cc: stable@vger.kernel.org
+Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+Cc: Mark Rutland <mark.rutland@arm.com>
+Acked-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Fixes: debdd57f5145f ("tracing: Make a snapshot feature available from userspace")
+Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/trace/ring_buffer.c | 3 ++-
+ kernel/trace/trace.c | 20 +++++++++++++++++---
+ 2 files changed, 19 insertions(+), 4 deletions(-)
+
+--- a/kernel/trace/ring_buffer.c
++++ b/kernel/trace/ring_buffer.c
+@@ -949,7 +949,8 @@ void ring_buffer_wake_waiters(struct tra
+ /* make sure the waiters see the new index */
+ smp_wmb();
+
+- rb_wake_up_waiters(&rbwork->work);
++ /* This can be called in any context */
++ irq_work_queue(&rbwork->work);
+ }
+
+ /**
+--- a/kernel/trace/trace.c
++++ b/kernel/trace/trace.c
+@@ -1893,6 +1893,9 @@ update_max_tr(struct trace_array *tr, st
+ __update_max_tr(tr, tsk, cpu);
+
+ arch_spin_unlock(&tr->max_lock);
++
++ /* Any waiters on the old snapshot buffer need to wake up */
++ ring_buffer_wake_waiters(tr->array_buffer.buffer, RING_BUFFER_ALL_CPUS);
+ }
+
+ /**
+@@ -1944,12 +1947,23 @@ update_max_tr_single(struct trace_array
+
+ static int wait_on_pipe(struct trace_iterator *iter, int full)
+ {
++ int ret;
++
+ /* Iterators are static, they should be filled or empty */
+ if (trace_buffer_iter(iter, iter->cpu_file))
+ return 0;
+
+- return ring_buffer_wait(iter->array_buffer->buffer, iter->cpu_file,
+- full);
++ ret = ring_buffer_wait(iter->array_buffer->buffer, iter->cpu_file, full);
++
++#ifdef CONFIG_TRACER_MAX_TRACE
++ /*
++ * Make sure this is still the snapshot buffer, as if a snapshot were
++ * to happen, this would now be the main buffer.
++ */
++ if (iter->snapshot)
++ iter->array_buffer = &iter->tr->max_buffer;
++#endif
++ return ret;
+ }
+
+ #ifdef CONFIG_FTRACE_STARTUP_TEST
+@@ -8514,7 +8528,7 @@ tracing_buffers_splice_read(struct file
+
+ wait_index = READ_ONCE(iter->wait_index);
+
+- ret = wait_on_pipe(iter, iter->tr->buffer_percent);
++ ret = wait_on_pipe(iter, iter->snapshot ? 0 : iter->tr->buffer_percent);
+ if (ret)
+ goto out;
+
--- /dev/null
+From leo@leolam.fr Sat Dec 16 06:48:17 2023
+From: "Léo Lam" <leo@leolam.fr>
+Date: Sat, 16 Dec 2023 05:47:15 +0000
+Subject: wifi: cfg80211: fix CQM for non-range use
+To: stable@vger.kernel.org
+Cc: "Johannes Berg" <johannes.berg@intel.com>, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>, "Léo Lam" <leo@leolam.fr>
+Message-ID: <20231216054715.7729-2-leo@leolam.fr>
+
+From: Johannes Berg <johannes.berg@intel.com>
+
+commit 7e7efdda6adb385fbdfd6f819d76bc68c923c394 upstream.
+
+[note: this is commit 4a7e92551618f3737b305f62451353ee05662f57 reapplied;
+that commit had been reverted in 6.6.6 because it caused regressions, see
+https://lore.kernel.org/stable/2023121450-habitual-transpose-68a1@gregkh/
+for details]
+
+My prior race fix here broke CQM when ranges aren't used, as
+the reporting worker now requires the cqm_config to be set in
+the wdev, but isn't set when there's no range configured.
+
+Rather than continuing to special-case the range version, set
+the cqm_config always and configure accordingly, also tracking
+if range was used or not to be able to clear the configuration
+appropriately with the same API, which was actually not right
+if both were implemented by a driver for some reason, as is
+the case with mac80211 (though there the implementations are
+equivalent so it doesn't matter.)
+
+Also, the original multiple-RSSI commit lost checking for the
+callback, so might have potentially crashed if a driver had
+neither implementation, and userspace tried to use it despite
+not being advertised as supported.
+
+Cc: stable@vger.kernel.org
+Fixes: 4a4b8169501b ("cfg80211: Accept multiple RSSI thresholds for CQM")
+Fixes: 37c20b2effe9 ("wifi: cfg80211: fix cqm_config access race")
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: "Léo Lam" <leo@leolam.fr>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/wireless/core.h | 1
+ net/wireless/nl80211.c | 50 ++++++++++++++++++++++++++++++-------------------
+ 2 files changed, 32 insertions(+), 19 deletions(-)
+
+--- a/net/wireless/core.h
++++ b/net/wireless/core.h
+@@ -299,6 +299,7 @@ struct cfg80211_cqm_config {
+ u32 rssi_hyst;
+ s32 last_rssi_event_value;
+ enum nl80211_cqm_rssi_threshold_event last_rssi_event_type;
++ bool use_range_api;
+ int n_rssi_thresholds;
+ s32 rssi_thresholds[] __counted_by(n_rssi_thresholds);
+ };
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -12824,10 +12824,6 @@ static int cfg80211_cqm_rssi_update(stru
+ int i, n, low_index;
+ int err;
+
+- /* RSSI reporting disabled? */
+- if (!cqm_config)
+- return rdev_set_cqm_rssi_range_config(rdev, dev, 0, 0);
+-
+ /*
+ * Obtain current RSSI value if possible, if not and no RSSI threshold
+ * event has been received yet, we should receive an event after a
+@@ -12902,18 +12898,6 @@ static int nl80211_set_cqm_rssi(struct g
+ wdev->iftype != NL80211_IFTYPE_P2P_CLIENT)
+ return -EOPNOTSUPP;
+
+- if (n_thresholds <= 1 && rdev->ops->set_cqm_rssi_config) {
+- if (n_thresholds == 0 || thresholds[0] == 0) /* Disabling */
+- return rdev_set_cqm_rssi_config(rdev, dev, 0, 0);
+-
+- return rdev_set_cqm_rssi_config(rdev, dev,
+- thresholds[0], hysteresis);
+- }
+-
+- if (!wiphy_ext_feature_isset(&rdev->wiphy,
+- NL80211_EXT_FEATURE_CQM_RSSI_LIST))
+- return -EOPNOTSUPP;
+-
+ if (n_thresholds == 1 && thresholds[0] == 0) /* Disabling */
+ n_thresholds = 0;
+
+@@ -12921,6 +12905,20 @@ static int nl80211_set_cqm_rssi(struct g
+ old = rcu_dereference_protected(wdev->cqm_config,
+ lockdep_is_held(&wdev->mtx));
+
++ /* if already disabled just succeed */
++ if (!n_thresholds && !old)
++ return 0;
++
++ if (n_thresholds > 1) {
++ if (!wiphy_ext_feature_isset(&rdev->wiphy,
++ NL80211_EXT_FEATURE_CQM_RSSI_LIST) ||
++ !rdev->ops->set_cqm_rssi_range_config)
++ return -EOPNOTSUPP;
++ } else {
++ if (!rdev->ops->set_cqm_rssi_config)
++ return -EOPNOTSUPP;
++ }
++
+ if (n_thresholds) {
+ cqm_config = kzalloc(struct_size(cqm_config, rssi_thresholds,
+ n_thresholds),
+@@ -12935,13 +12933,26 @@ static int nl80211_set_cqm_rssi(struct g
+ memcpy(cqm_config->rssi_thresholds, thresholds,
+ flex_array_size(cqm_config, rssi_thresholds,
+ n_thresholds));
++ cqm_config->use_range_api = n_thresholds > 1 ||
++ !rdev->ops->set_cqm_rssi_config;
+
+ rcu_assign_pointer(wdev->cqm_config, cqm_config);
++
++ if (cqm_config->use_range_api)
++ err = cfg80211_cqm_rssi_update(rdev, dev, cqm_config);
++ else
++ err = rdev_set_cqm_rssi_config(rdev, dev,
++ thresholds[0],
++ hysteresis);
+ } else {
+ RCU_INIT_POINTER(wdev->cqm_config, NULL);
++ /* if enabled as range also disable via range */
++ if (old->use_range_api)
++ err = rdev_set_cqm_rssi_range_config(rdev, dev, 0, 0);
++ else
++ err = rdev_set_cqm_rssi_config(rdev, dev, 0, 0);
+ }
+
+- err = cfg80211_cqm_rssi_update(rdev, dev, cqm_config);
+ if (err) {
+ rcu_assign_pointer(wdev->cqm_config, old);
+ kfree_rcu(cqm_config, rcu_head);
+@@ -19131,10 +19142,11 @@ void cfg80211_cqm_rssi_notify_work(struc
+ wdev_lock(wdev);
+ cqm_config = rcu_dereference_protected(wdev->cqm_config,
+ lockdep_is_held(&wdev->mtx));
+- if (!wdev->cqm_config)
++ if (!cqm_config)
+ goto unlock;
+
+- cfg80211_cqm_rssi_update(rdev, wdev->netdev, cqm_config);
++ if (cqm_config->use_range_api)
++ cfg80211_cqm_rssi_update(rdev, wdev->netdev, cqm_config);
+
+ rssi_level = cqm_config->last_rssi_event_value;
+ rssi_event = cqm_config->last_rssi_event_type;
--- /dev/null
+From stable+bounces-6868-greg=kroah.com@vger.kernel.org Sat Dec 16 06:57:02 2023
+From: "Léo Lam" <leo@leolam.fr>
+Date: Sat, 16 Dec 2023 05:47:17 +0000
+Subject: wifi: nl80211: fix deadlock in nl80211_set_cqm_rssi (6.6.x)
+To: stable@vger.kernel.org
+Cc: "Léo Lam" <leo@leolam.fr>, "Philip Müller" <philm@manjaro.org>, "Johannes Berg" <johannes.berg@intel.com>
+Message-ID: <20231216054715.7729-4-leo@leolam.fr>
+
+From: "Léo Lam" <leo@leolam.fr>
+
+Commit 008afb9f3d57 ("wifi: cfg80211: fix CQM for non-range use"
+backported to 6.6.x) causes nl80211_set_cqm_rssi not to release the
+wdev lock in some of the error paths.
+
+Of course, the ensuing deadlock causes userland network managers to
+break pretty badly, and on typical systems this also causes lockups on
+on suspend, poweroff and reboot. See [1], [2], [3] for example reports.
+
+The upstream commit 7e7efdda6adb ("wifi: cfg80211: fix CQM for non-range
+use"), committed in November 2023, is completely fine because there was
+another commit in August 2023 that removed the wdev lock:
+see commit 076fc8775daf ("wifi: cfg80211: remove wdev mutex").
+
+The reason things broke in 6.6.5 is that commit 4338058f6009 was applied
+without also applying 076fc8775daf.
+
+Commit 076fc8775daf ("wifi: cfg80211: remove wdev mutex") is a rather
+large commit; adjusting the error handling (which is what this commit does)
+yields a much simpler patch and was tested to work properly.
+
+Fix the deadlock by releasing the lock before returning.
+
+[1] https://bugzilla.kernel.org/show_bug.cgi?id=218247
+[2] https://bbs.archlinux.org/viewtopic.php?id=290976
+[3] https://lore.kernel.org/all/87sf4belmm.fsf@turtle.gmx.de/
+
+Link: https://lore.kernel.org/stable/e374bb16-5b13-44cc-b11a-2f4eefb1ecf5@manjaro.org/
+Fixes: 008afb9f3d57 ("wifi: cfg80211: fix CQM for non-range use")
+Tested-by: "Léo Lam" <leo@leolam.fr>
+Tested-by: Philip Müller <philm@manjaro.org>
+Cc: stable@vger.kernel.org
+Cc: Johannes Berg <johannes.berg@intel.com>
+Signed-off-by: "Léo Lam" <leo@leolam.fr>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/wireless/nl80211.c | 18 ++++++++++++------
+ 1 file changed, 12 insertions(+), 6 deletions(-)
+
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -12906,17 +12906,23 @@ static int nl80211_set_cqm_rssi(struct g
+ lockdep_is_held(&wdev->mtx));
+
+ /* if already disabled just succeed */
+- if (!n_thresholds && !old)
+- return 0;
++ if (!n_thresholds && !old) {
++ err = 0;
++ goto unlock;
++ }
+
+ if (n_thresholds > 1) {
+ if (!wiphy_ext_feature_isset(&rdev->wiphy,
+ NL80211_EXT_FEATURE_CQM_RSSI_LIST) ||
+- !rdev->ops->set_cqm_rssi_range_config)
+- return -EOPNOTSUPP;
++ !rdev->ops->set_cqm_rssi_range_config) {
++ err = -EOPNOTSUPP;
++ goto unlock;
++ }
+ } else {
+- if (!rdev->ops->set_cqm_rssi_config)
+- return -EOPNOTSUPP;
++ if (!rdev->ops->set_cqm_rssi_config) {
++ err = -EOPNOTSUPP;
++ goto unlock;
++ }
+ }
+
+ if (n_thresholds) {