Linux wireless drivers development
 help / color / mirror / Atom feed
* Re: [PATCH ath-next v2 0/3] wifi: ath12k: Improve dp stats
From: Baochen Qiang @ 2026-06-29  7:25 UTC (permalink / raw)
  To: Sreeramya Soratkal, ath12k; +Cc: linux-wireless
In-Reply-To: <20260626085253.3927269-1-sreeramya.soratkal@oss.qualcomm.com>



On 6/26/2026 4:52 PM, Sreeramya Soratkal wrote:
> This short series improves the device_dp_stats debugfs interface in
> ath12k to produce more useful and accurate diagnostic output in MLO
> configurations.
> 
> Patch 1 fixes a latent correctness issue where the REO Rx and WBM
> release error tables were unconditionally printed for
> ATH12K_MAX_DEVICES columns showing zero-filled entries for device
> slots that have no hardware. Replacing the compile-time constant
> with the runtime device count keeps the output compact and correct.
> 
> Patch 2 prepends a ktime-based millisecond timestamp to the top of
> the dump. In MLO setups the per-device file is to be read
> independently for each ath12k instance, so without a common timestamp
> it is difficult to verify whether two snapshots are contemporaneous,
> making cross-device counter comparisons unreliable.
> 
> Patch 3 extends the existing per-radio section to report the center
> frequency on which each radio is currently operating.  This allows
> the reader to correlate throughput and error counters with the channel
> without having to cross-reference iw/nl80211 output separately.
> 
> ---
> Changes in v2:
> - Rebased the patch series on the latest tip of ath-next
> ---
> 
> Sreeramya Soratkal (3):
>   wifi: ath12k: Use runtime device count in dp stats display
>   wifi: ath12k: Add timestamp to dp stats display
>   wifi: ath12k: Show per-radio center freq in dp stats
> 
>  drivers/net/wireless/ath/ath12k/debugfs.c | 14 ++++++++++++--
>  1 file changed, 12 insertions(+), 2 deletions(-)
> 
> 
> base-commit: 972c4dd19cb92e03d75b66c426cfade07582a1ba

Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>


^ permalink raw reply

* Re: [PATCH ath-next v2 1/2] wifi: ath12k: fix dp_link_peer dangling references on AP vdev rollback
From: Baochen Qiang @ 2026-06-29  7:02 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: linux-wireless, ath12k
In-Reply-To: <20260629-ath12k-mlo-peer-delete-race-v2-1-362b25590d19@oss.qualcomm.com>



On 6/29/2026 3:01 PM, Baochen Qiang wrote:
> ath12k_mac_vdev_create() for an AP vdev creates the bss self-peer via
> ath12k_peer_create(), which finishes by calling
> ath12k_dp_link_peer_assign() to publish the dp_link_peer in the
> dp_hw->dp_peers[peerid_index] RCU table, in the dp_peer's
> link_peers[] array, and in the per-addr rhashtable.
> 
> If a step after ath12k_peer_create() fails the function jumps to
> err_peer_del, which open-codes a WMI peer_delete and waits for the
> unmap / delete_resp events. The wait_for_peer_delete_done() path
> relies on ath12k_dp_link_peer_unmap_event() freeing the dp_link_peer
> when the unmap arrives, but err_peer_del never calls
> ath12k_dp_link_peer_unassign() first. The published references in
> the dp_hw RCU table, dp_peer->link_peers[] and the rhashtable are
> left pointing at the dp_link_peer that unmap_event then frees,
> producing dangling pointers and use-after-free on subsequent
> lookups.
> 
> Replace the open-coded sequence with a call to ath12k_peer_delete(),
> which already does ath12k_dp_link_peer_unassign() before sending the
> WMI command. This drops the published references before the
> dp_link_peer is freed, in the same order as the normal teardown path
> in ath12k_mac_remove_link_interface().
> 
> Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3
> 
> Fixes: 5525f12fa671 ("wifi: ath12k: Attach and detach ath12k_dp_link_peer to ath12k_dp_peer")
> Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>

forgot to pick:

Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>

^ permalink raw reply

* [PATCH ath-next v2 2/2] wifi: ath12k: fix MLO peer delete race
From: Baochen Qiang @ 2026-06-29  7:01 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: linux-wireless, ath12k, Baochen Qiang
In-Reply-To: <20260629-ath12k-mlo-peer-delete-race-v2-0-362b25590d19@oss.qualcomm.com>

ath12k_peer_mlo_link_peers_delete() sends WMI peer_delete for every
link before waiting for any peer_unmap / peer_delete_resp event. The
shared per-radio completion ar->peer_delete_done could not
disambiguate which peer a response was for: every call to
ath12k_peer_delete_send() did
reinit_completion(&ar->peer_delete_done), so when an event for the
first link arrived between two sends it raised the count to 1 and
the second send promptly cleared it; the wait for the second link
then timed out with

    Timeout in receiving peer delete response

Replace the shared completion with a per-radio waiter list, with
each pending ath12k_peer_delete() caller queueing an
ath12k_peer_delete_wait carrying its (vdev_id, addr) and a private
struct completion. ath12k_peer_delete_resp_event() matches the
response against the list under ar->data_lock and signals the
matching waiter.

Also correct the endian conversion in ath12k_peer_delete_resp_event()
logging, and add the missing \n in some logging.

Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3

Fixes: 8e6f8bc28603 ("wifi: ath12k: Add MLO station state change handling")
Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/core.c |   2 +-
 drivers/net/wireless/ath/ath12k/core.h |   5 +-
 drivers/net/wireless/ath/ath12k/mac.c  |   2 +-
 drivers/net/wireless/ath/ath12k/peer.c | 130 ++++++++++++++++++++++++++-------
 drivers/net/wireless/ath/ath12k/peer.h |  14 +++-
 drivers/net/wireless/ath/ath12k/wmi.c  |  16 ++--
 6 files changed, 131 insertions(+), 38 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
index 742d4fd1b598..f71650039292 100644
--- a/drivers/net/wireless/ath/ath12k/core.c
+++ b/drivers/net/wireless/ath/ath12k/core.c
@@ -1524,7 +1524,7 @@ static void ath12k_core_pre_reconfigure_recovery(struct ath12k_base *ab)
 			complete_all(&ar->scan.completed);
 			complete(&ar->scan.on_channel);
 			complete(&ar->peer_assoc_done);
-			complete(&ar->peer_delete_done);
+			ath12k_peer_delete_wait_flush(ar);
 			complete(&ar->install_key_done);
 			complete(&ar->vdev_setup_done);
 			complete(&ar->vdev_delete_done);
diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
index fc5127b5c1a3..1436ff4316e7 100644
--- a/drivers/net/wireless/ath/ath12k/core.h
+++ b/drivers/net/wireless/ath/ath12k/core.h
@@ -665,7 +665,8 @@ struct ath12k {
 
 	/* protects the radio specific data like debug stats, ppdu_stats_info stats,
 	 * vdev_stop_status info, scan data, ath12k_sta info, ath12k_link_vif info,
-	 * channel context data, survey info, test mode data, regd_channel_update_queue.
+	 * channel context data, survey info, test mode data, regd_channel_update_queue,
+	 * peer_delete_waits.
 	 */
 	spinlock_t data_lock;
 
@@ -687,7 +688,7 @@ struct ath12k {
 	u8 radio_idx;
 
 	struct completion peer_assoc_done;
-	struct completion peer_delete_done;
+	struct list_head peer_delete_waits;
 
 	int install_key_status;
 	struct completion install_key_done;
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index 2e5a075191ae..4c86a8eb5841 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -15040,11 +15040,11 @@ static void ath12k_mac_setup(struct ath12k *ar)
 	spin_lock_init(&ar->dp.ppdu_list_lock);
 	INIT_LIST_HEAD(&ar->arvifs);
 	INIT_LIST_HEAD(&ar->dp.ppdu_stats_info);
+	INIT_LIST_HEAD(&ar->peer_delete_waits);
 
 	init_completion(&ar->vdev_setup_done);
 	init_completion(&ar->vdev_delete_done);
 	init_completion(&ar->peer_assoc_done);
-	init_completion(&ar->peer_delete_done);
 	init_completion(&ar->install_key_done);
 	init_completion(&ar->bss_survey_done);
 	init_completion(&ar->scan.started);
diff --git a/drivers/net/wireless/ath/ath12k/peer.c b/drivers/net/wireless/ath/ath12k/peer.c
index c222bdaa333c..5084e8c42a3f 100644
--- a/drivers/net/wireless/ath/ath12k/peer.c
+++ b/drivers/net/wireless/ath/ath12k/peer.c
@@ -9,6 +9,55 @@
 #include "debug.h"
 #include "debugfs.h"
 
+static void ath12k_peer_delete_wait_register(struct ath12k *ar,
+					     struct ath12k_peer_delete_wait *wait,
+					     u32 vdev_id, const u8 *addr)
+{
+	wait->vdev_id = vdev_id;
+	ether_addr_copy(wait->addr, addr);
+	init_completion(&wait->done);
+
+	spin_lock_bh(&ar->data_lock);
+	list_add(&wait->list, &ar->peer_delete_waits);
+	spin_unlock_bh(&ar->data_lock);
+}
+
+static void ath12k_peer_delete_wait_unregister(struct ath12k *ar,
+					       struct ath12k_peer_delete_wait *wait)
+{
+	spin_lock_bh(&ar->data_lock);
+	list_del(&wait->list);
+	spin_unlock_bh(&ar->data_lock);
+}
+
+void ath12k_peer_delete_resp_signal(struct ath12k *ar, u32 vdev_id, const u8 *addr)
+{
+	struct ath12k_peer_delete_wait *wait;
+
+	guard(spinlock_bh)(&ar->data_lock);
+
+	list_for_each_entry(wait, &ar->peer_delete_waits, list) {
+		if (wait->vdev_id == vdev_id &&
+		    ether_addr_equal(wait->addr, addr)) {
+			complete(&wait->done);
+			return;
+		}
+	}
+
+	ath12k_warn(ar->ab, "failed to find link peer with vdev id %u addr %pM\n",
+		    vdev_id, addr);
+}
+
+void ath12k_peer_delete_wait_flush(struct ath12k *ar)
+{
+	struct ath12k_peer_delete_wait *wait;
+
+	spin_lock_bh(&ar->data_lock);
+	list_for_each_entry(wait, &ar->peer_delete_waits, list)
+		complete(&wait->done);
+	spin_unlock_bh(&ar->data_lock);
+}
+
 static int ath12k_wait_for_dp_link_peer_common(struct ath12k_base *ab, int vdev_id,
 					       const u8 *addr, bool expect_mapped)
 {
@@ -62,20 +111,19 @@ static int ath12k_wait_for_peer_deleted(struct ath12k *ar, int vdev_id, const u8
 	return ath12k_wait_for_dp_link_peer_common(ar->ab, vdev_id, addr, false);
 }
 
-int ath12k_wait_for_peer_delete_done(struct ath12k *ar, u32 vdev_id,
-				     const u8 *addr)
+int ath12k_wait_for_peer_delete_done(struct ath12k *ar,
+				     struct ath12k_peer_delete_wait *wait)
 {
-	int ret;
 	unsigned long time_left;
+	int ret;
 
-	ret = ath12k_wait_for_peer_deleted(ar, vdev_id, addr);
+	ret = ath12k_wait_for_peer_deleted(ar, wait->vdev_id, wait->addr);
 	if (ret) {
-		ath12k_warn(ar->ab, "failed wait for peer deleted");
+		ath12k_warn(ar->ab, "failed wait for peer deleted\n");
 		return ret;
 	}
 
-	time_left = wait_for_completion_timeout(&ar->peer_delete_done,
-						3 * HZ);
+	time_left = wait_for_completion_timeout(&wait->done, 3 * HZ);
 	if (time_left == 0) {
 		ath12k_warn(ar->ab, "Timeout in receiving peer delete response\n");
 		return -ETIMEDOUT;
@@ -91,8 +139,6 @@ static int ath12k_peer_delete_send(struct ath12k *ar, u32 vdev_id, const u8 *add
 
 	lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy);
 
-	reinit_completion(&ar->peer_delete_done);
-
 	ret = ath12k_wmi_send_peer_delete_cmd(ar, addr, vdev_id);
 	if (ret) {
 		ath12k_warn(ab,
@@ -106,6 +152,7 @@ static int ath12k_peer_delete_send(struct ath12k *ar, u32 vdev_id, const u8 *add
 
 int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr)
 {
+	struct ath12k_peer_delete_wait wait;
 	int ret;
 
 	lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy);
@@ -114,17 +161,25 @@ int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr)
 				     &(ath12k_ar_to_ah(ar)->dp_hw), vdev_id,
 				     addr, ar->hw_link_id);
 
+	/*
+	 * Register the stack waiter before sending so the resp_event for
+	 * this peer cannot arrive while no waiter is queued.
+	 */
+	ath12k_peer_delete_wait_register(ar, &wait, vdev_id, addr);
+
 	ret = ath12k_peer_delete_send(ar, vdev_id, addr);
 	if (ret)
-		return ret;
+		goto out;
 
-	ret = ath12k_wait_for_peer_delete_done(ar, vdev_id, addr);
+	ret = ath12k_wait_for_peer_delete_done(ar, &wait);
 	if (ret)
-		return ret;
+		goto out;
 
 	ar->num_peers--;
 
-	return 0;
+out:
+	ath12k_peer_delete_wait_unregister(ar, &wait);
+	return ret;
 }
 
 static int ath12k_wait_for_peer_created(struct ath12k *ar, int vdev_id, const u8 *addr)
@@ -184,22 +239,26 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif,
 	peer = ath12k_dp_link_peer_find_by_vdev_and_addr(dp, arg->vdev_id,
 							 arg->peer_addr);
 	if (!peer) {
+		struct ath12k_peer_delete_wait wait;
+
 		spin_unlock_bh(&dp->dp_lock);
 		ath12k_warn(ar->ab, "failed to find peer %pM on vdev %i after creation\n",
 			    arg->peer_addr, arg->vdev_id);
 
-		reinit_completion(&ar->peer_delete_done);
+		ath12k_peer_delete_wait_register(ar, &wait, arg->vdev_id,
+						 arg->peer_addr);
 
 		ret = ath12k_wmi_send_peer_delete_cmd(ar, arg->peer_addr,
 						      arg->vdev_id);
 		if (ret) {
 			ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM\n",
 				    arg->vdev_id, arg->peer_addr);
+			ath12k_peer_delete_wait_unregister(ar, &wait);
 			return ret;
 		}
 
-		ret = ath12k_wait_for_peer_delete_done(ar, arg->vdev_id,
-						       arg->peer_addr);
+		ret = ath12k_wait_for_peer_delete_done(ar, &wait);
+		ath12k_peer_delete_wait_unregister(ar, &wait);
 		if (ret)
 			return ret;
 
@@ -283,13 +342,14 @@ u16 ath12k_peer_ml_alloc(struct ath12k_hw *ah)
 
 int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta)
 {
+	DECLARE_BITMAP(registered, IEEE80211_MLD_MAX_NUM_LINKS);
 	struct ieee80211_sta *sta = ath12k_ahsta_to_sta(ahsta);
 	struct ath12k_hw *ah = ahvif->ah;
 	struct ath12k_link_vif *arvif;
 	struct ath12k_link_sta *arsta;
+	int ret, err_ret = 0;
 	unsigned long links;
 	struct ath12k *ar;
-	int ret, err_ret = 0;
 	u8 link_id;
 
 	lockdep_assert_wiphy(ah->hw->wiphy);
@@ -297,8 +357,19 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st
 	if (!sta->mlo)
 		return -EINVAL;
 
-	/* FW expects delete of all link peers at once before waiting for reception
-	 * of peer unmap or delete responses
+	struct ath12k_peer_delete_wait *waits __free(kfree) =
+				kzalloc_objs(*waits, IEEE80211_MLD_MAX_NUM_LINKS);
+	if (!waits)
+		return -ENOMEM;
+
+	bitmap_zero(registered, IEEE80211_MLD_MAX_NUM_LINKS);
+
+	/*
+	 * Firmware expects delete of all link peers at once before waiting
+	 * for reception of peer unmap or delete responses. Phase 1 registers
+	 * a per-link stack waiter and sends WMI peer delete for every
+	 * link; the resp_event handler matches each response to its
+	 * (vdev_id, addr) waiter on ar->peer_delete_waits.
 	 */
 	links = ahsta->links_map;
 	for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
@@ -318,29 +389,36 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st
 					     arvif->vdev_id, arsta->addr,
 					     ar->hw_link_id);
 
+		ath12k_peer_delete_wait_register(ar, &waits[link_id],
+						 arvif->vdev_id, arsta->addr);
+
 		ret = ath12k_peer_delete_send(ar, arvif->vdev_id, arsta->addr);
 		if (ret) {
 			ath12k_warn(ar->ab,
 				    "failed to delete peer vdev_id %d addr %pM ret %d\n",
 				    arvif->vdev_id, arsta->addr, ret);
 			err_ret = ret;
+			ath12k_peer_delete_wait_unregister(ar, &waits[link_id]);
 			continue;
 		}
+
+		set_bit(link_id, registered);
 	}
 
-	/* Ensure all link peers are deleted and unmapped */
+	/*
+	 * Phase 2: wait for unmap + delete_resp on each registered link
+	 * and tear down the waiter.
+	 */
 	links = ahsta->links_map;
 	for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
-		arvif = wiphy_dereference(ah->hw->wiphy, ahvif->link[link_id]);
-		arsta = wiphy_dereference(ah->hw->wiphy, ahsta->link[link_id]);
-		if (!arvif || !arsta)
+		if (!test_bit(link_id, registered))
 			continue;
 
+		arvif = wiphy_dereference(ah->hw->wiphy, ahvif->link[link_id]);
 		ar = arvif->ar;
-		if (!ar)
-			continue;
 
-		ret = ath12k_wait_for_peer_delete_done(ar, arvif->vdev_id, arsta->addr);
+		ret = ath12k_wait_for_peer_delete_done(ar, &waits[link_id]);
+		ath12k_peer_delete_wait_unregister(ar, &waits[link_id]);
 		if (ret) {
 			err_ret = ret;
 			continue;
diff --git a/drivers/net/wireless/ath/ath12k/peer.h b/drivers/net/wireless/ath/ath12k/peer.h
index 49d89796bc46..9343944c2b8e 100644
--- a/drivers/net/wireless/ath/ath12k/peer.h
+++ b/drivers/net/wireless/ath/ath12k/peer.h
@@ -9,13 +9,23 @@
 
 #include "dp_peer.h"
 
+struct ath12k_peer_delete_wait {
+	struct list_head list;
+	u32 vdev_id;
+	u8 addr[ETH_ALEN];
+	struct completion done;
+};
+
+void ath12k_peer_delete_resp_signal(struct ath12k *ar, u32 vdev_id, const u8 *addr);
+void ath12k_peer_delete_wait_flush(struct ath12k *ar);
+
 void ath12k_peer_cleanup(struct ath12k *ar, u32 vdev_id);
 int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr);
 int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif,
 		       struct ieee80211_sta *sta,
 		       struct ath12k_wmi_peer_create_arg *arg);
-int ath12k_wait_for_peer_delete_done(struct ath12k *ar, u32 vdev_id,
-				     const u8 *addr);
+int ath12k_wait_for_peer_delete_done(struct ath12k *ar,
+				     struct ath12k_peer_delete_wait *wait);
 int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta);
 struct ath12k_ml_peer *ath12k_peer_ml_find(struct ath12k_hw *ah,
 					   const u8 *addr);
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index 84a31b953db8..6066ca8d9fc4 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -7072,25 +7072,29 @@ static void ath12k_peer_delete_resp_event(struct ath12k_base *ab, struct sk_buff
 {
 	struct wmi_peer_delete_resp_event peer_del_resp;
 	struct ath12k *ar;
+	u32 vdev_id;
 
 	if (ath12k_pull_peer_del_resp_ev(ab, skb, &peer_del_resp) != 0) {
-		ath12k_warn(ab, "failed to extract peer delete resp");
+		ath12k_warn(ab, "failed to extract peer delete resp\n");
 		return;
 	}
 
+	vdev_id = le32_to_cpu(peer_del_resp.vdev_id);
+
 	rcu_read_lock();
-	ar = ath12k_mac_get_ar_by_vdev_id(ab, le32_to_cpu(peer_del_resp.vdev_id));
+	ar = ath12k_mac_get_ar_by_vdev_id(ab, vdev_id);
 	if (!ar) {
-		ath12k_warn(ab, "invalid vdev id in peer delete resp ev %d",
-			    peer_del_resp.vdev_id);
+		ath12k_warn(ab, "invalid vdev id in peer delete resp ev %d\n",
+			    vdev_id);
 		rcu_read_unlock();
 		return;
 	}
 
-	complete(&ar->peer_delete_done);
+	ath12k_peer_delete_resp_signal(ar, vdev_id,
+				       peer_del_resp.peer_macaddr.addr);
 	rcu_read_unlock();
 	ath12k_dbg(ab, ATH12K_DBG_WMI, "peer delete resp for vdev id %d addr %pM\n",
-		   peer_del_resp.vdev_id, peer_del_resp.peer_macaddr.addr);
+		   vdev_id, peer_del_resp.peer_macaddr.addr);
 }
 
 static void ath12k_vdev_delete_resp_event(struct ath12k_base *ab,

-- 
2.25.1


^ permalink raw reply related

* [PATCH ath-next v2 1/2] wifi: ath12k: fix dp_link_peer dangling references on AP vdev rollback
From: Baochen Qiang @ 2026-06-29  7:01 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: linux-wireless, ath12k, Baochen Qiang
In-Reply-To: <20260629-ath12k-mlo-peer-delete-race-v2-0-362b25590d19@oss.qualcomm.com>

ath12k_mac_vdev_create() for an AP vdev creates the bss self-peer via
ath12k_peer_create(), which finishes by calling
ath12k_dp_link_peer_assign() to publish the dp_link_peer in the
dp_hw->dp_peers[peerid_index] RCU table, in the dp_peer's
link_peers[] array, and in the per-addr rhashtable.

If a step after ath12k_peer_create() fails the function jumps to
err_peer_del, which open-codes a WMI peer_delete and waits for the
unmap / delete_resp events. The wait_for_peer_delete_done() path
relies on ath12k_dp_link_peer_unmap_event() freeing the dp_link_peer
when the unmap arrives, but err_peer_del never calls
ath12k_dp_link_peer_unassign() first. The published references in
the dp_hw RCU table, dp_peer->link_peers[] and the rhashtable are
left pointing at the dp_link_peer that unmap_event then frees,
producing dangling pointers and use-after-free on subsequent
lookups.

Replace the open-coded sequence with a call to ath12k_peer_delete(),
which already does ath12k_dp_link_peer_unassign() before sending the
WMI command. This drops the published references before the
dp_link_peer is freed, in the same order as the normal teardown path
in ath12k_mac_remove_link_interface().

Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3

Fixes: 5525f12fa671 ("wifi: ath12k: Attach and detach ath12k_dp_link_peer to ath12k_dp_peer")
Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/mac.c | 18 ++----------------
 1 file changed, 2 insertions(+), 16 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index af354bef5c0d..2e5a075191ae 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -10564,22 +10564,8 @@ int ath12k_mac_vdev_create(struct ath12k *ar, struct ath12k_link_vif *arvif)
 
 err_peer_del:
 	if (ahvif->vdev_type == WMI_VDEV_TYPE_AP) {
-		reinit_completion(&ar->peer_delete_done);
-
-		ret = ath12k_wmi_send_peer_delete_cmd(ar, arvif->bssid,
-						      arvif->vdev_id);
-		if (ret) {
-			ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM\n",
-				    arvif->vdev_id, arvif->bssid);
-			goto err_dp_peer_del;
-		}
-
-		ret = ath12k_wait_for_peer_delete_done(ar, arvif->vdev_id,
-						       arvif->bssid);
-		if (ret)
-			goto err_dp_peer_del;
-
-		ar->num_peers--;
+		/* ignore return value: propagate the original error */
+		ath12k_peer_delete(ar, arvif->vdev_id, arvif->bssid);
 	}
 
 err_dp_peer_del:

-- 
2.25.1


^ permalink raw reply related

* [PATCH ath-next v2 0/2] wifi: ath12k: fix peer delete race in MLO scenario
From: Baochen Qiang @ 2026-06-29  7:01 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: linux-wireless, ath12k, Baochen Qiang

Patch 1 fixes a pre-existing UAF in ath12k_mac_vdev_create()'s
err_peer_del rollback path.

Patch 2 fixes "Timeout in receiving peer delete response" on MLO
disconnect, caused by a per-radio shared completion that gets
clobbered between back-to-back WMI peer_delete sends.

Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
---
Changes in v2:
- rebased on ToT
- patch 2/2: make ath12k_peer_delete_wait_register()
             and ath12k_peer_delete_wait_unregister() static to peer.c
- Link to v1: https://lore.kernel.org/r/20260617-ath12k-mlo-peer-delete-race-v1-0-ab3c4f455dfb@oss.qualcomm.com

---
Baochen Qiang (2):
      wifi: ath12k: fix dp_link_peer dangling references on AP vdev rollback
      wifi: ath12k: fix MLO peer delete race

 drivers/net/wireless/ath/ath12k/core.c |   2 +-
 drivers/net/wireless/ath/ath12k/core.h |   5 +-
 drivers/net/wireless/ath/ath12k/mac.c  |  20 +----
 drivers/net/wireless/ath/ath12k/peer.c | 130 ++++++++++++++++++++++++++-------
 drivers/net/wireless/ath/ath12k/peer.h |  14 +++-
 drivers/net/wireless/ath/ath12k/wmi.c  |  16 ++--
 6 files changed, 133 insertions(+), 54 deletions(-)
---
base-commit: 1547a99cd8d8c1ab3e04dbd92b72b3b5f7cb85a9
change-id: 20260602-ath12k-mlo-peer-delete-race-74fdaf880017

Best regards,
-- 
Baochen Qiang <baochen.qiang@oss.qualcomm.com>


^ permalink raw reply

* Re: [PATCH ath-next 2/2] wifi: ath12k: fix MLO peer delete race
From: Baochen Qiang @ 2026-06-29  6:54 UTC (permalink / raw)
  To: Rameshkumar Sundaram, Jeff Johnson; +Cc: linux-wireless, ath12k
In-Reply-To: <fa3d454e-e125-4aed-99a0-48bba9debe31@oss.qualcomm.com>



On 6/29/2026 1:34 PM, Rameshkumar Sundaram wrote:
> On 6/17/2026 2:58 PM, Baochen Qiang wrote:
>> ath12k_peer_mlo_link_peers_delete() sends WMI peer_delete for every
>> link before waiting for any peer_unmap / peer_delete_resp event. The
>> shared per-radio completion ar->peer_delete_done could not
>> disambiguate which peer a response was for: every call to
>> ath12k_peer_delete_send() did
>> reinit_completion(&ar->peer_delete_done), so when an event for the
>> first link arrived between two sends it raised the count to 1 and
>> the second send promptly cleared it; the wait for the second link
>> then timed out with
>>
>>      Timeout in receiving peer delete response
>>
>> Replace the shared completion with a per-radio waiter list, with
>> each pending ath12k_peer_delete() caller queueing an
>> ath12k_peer_delete_wait carrying its (vdev_id, addr) and a private
>> struct completion. ath12k_peer_delete_resp_event() matches the
>> response against the list under ar->data_lock and signals the
>> matching waiter.
>>
>> Also correct the endian conversion in ath12k_peer_delete_resp_event()
>> logging, and add the missing \n in some logging.
>>
>> Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3
>>
>> Fixes: 8e6f8bc28603 ("wifi: ath12k: Add MLO station state change handling")
>> Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
>> ---
>>   drivers/net/wireless/ath/ath12k/core.c |   2 +-
>>   drivers/net/wireless/ath/ath12k/core.h |   5 +-
>>   drivers/net/wireless/ath/ath12k/mac.c  |   2 +-
>>   drivers/net/wireless/ath/ath12k/peer.c | 130 ++++++++++++++++++++++++++-------
>>   drivers/net/wireless/ath/ath12k/peer.h |  19 ++++-
>>   drivers/net/wireless/ath/ath12k/wmi.c  |  16 ++--
>>   6 files changed, 136 insertions(+), 38 deletions(-)
>>
>> diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/
>> core.c
>> index 742d4fd1b598..f71650039292 100644
>> --- a/drivers/net/wireless/ath/ath12k/core.c
>> +++ b/drivers/net/wireless/ath/ath12k/core.c
> 
> { ... }
> 
>> diff --git a/drivers/net/wireless/ath/ath12k/peer.h b/drivers/net/wireless/ath/ath12k/
>> peer.h
>> index 49d89796bc46..3dc720a3dc12 100644
>> --- a/drivers/net/wireless/ath/ath12k/peer.h
>> +++ b/drivers/net/wireless/ath/ath12k/peer.h
>> @@ -9,13 +9,28 @@
>>     #include "dp_peer.h"
>>   +struct ath12k_peer_delete_wait {
>> +    struct list_head list;
>> +    u32 vdev_id;
>> +    u8 addr[ETH_ALEN];
>> +    struct completion done;
>> +};
>> +
>> +void ath12k_peer_delete_wait_register(struct ath12k *ar,
>> +                      struct ath12k_peer_delete_wait *wait,
>> +                      u32 vdev_id, const u8 *addr);
>> +void ath12k_peer_delete_wait_unregister(struct ath12k *ar,
>> +                    struct ath12k_peer_delete_wait *wait);
> 
> 
> The struct and functions appear to be used only within peer.c and are not referenced
> elsewhere. Could the function be made static in peer.c ?

Yeah, I can do that.

> 
>> +void ath12k_peer_delete_resp_signal(struct ath12k *ar, u32 vdev_id, const u8 *addr);
>> +void ath12k_peer_delete_wait_flush(struct ath12k *ar);
> 
> 
> -- 
> Ramesh


^ permalink raw reply

* Re: [PATCH ath-next] wifi: ath12k: fix rx_mpdu_start layout for QCC2072
From: Baochen Qiang @ 2026-06-29  6:37 UTC (permalink / raw)
  To: Wei Zhang, jeff.johnson; +Cc: ath12k, linux-wireless, linux-kernel
In-Reply-To: <20260629061529.1993932-1-wei.zhang@oss.qualcomm.com>



On 6/29/2026 2:15 PM, Wei Zhang wrote:
> QCC2072's rx_mpdu_start TLV has a different field layout from QCN9274.
> Reusing struct rx_mpdu_start_qcn9274 in hal_rx_desc_qcc2072 causes the
> RX datapath to read the wrong offsets for info2, info4, pn[] and
> phy_ppdu_id, producing corrupted sequence number, PN, ppdu_id and
> mpdu-info flags (encrypted, fragment, addr2/addr4 valid).
> 
> Add a dedicated struct rx_mpdu_start_qcc2072 that matches the actual
> hardware descriptor layout, and use it in hal_rx_desc_qcc2072.
> 
> Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00188-QCACOLSWPL_V1_TO_SILICONZ-1
> 
> Fixes: 28badc78142e ("wifi: ath12k: add HAL descriptor and ops for QCC2072")
> Signed-off-by: Wei Zhang <wei.zhang@oss.qualcomm.com>

Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>

^ permalink raw reply

* Re: [PATCH ath-next v2 0/3] wifi: ath12k: Improve dp stats
From: Aishwarya R @ 2026-06-29  6:36 UTC (permalink / raw)
  To: Sreeramya Soratkal, ath12k; +Cc: linux-wireless
In-Reply-To: <20260626085253.3927269-1-sreeramya.soratkal@oss.qualcomm.com>



On 6/26/2026 2:22 PM, Sreeramya Soratkal wrote:
> This short series improves the device_dp_stats debugfs interface in
> ath12k to produce more useful and accurate diagnostic output in MLO
> configurations.
> 
> Patch 1 fixes a latent correctness issue where the REO Rx and WBM
> release error tables were unconditionally printed for
> ATH12K_MAX_DEVICES columns showing zero-filled entries for device
> slots that have no hardware. Replacing the compile-time constant
> with the runtime device count keeps the output compact and correct.
> 
> Patch 2 prepends a ktime-based millisecond timestamp to the top of
> the dump. In MLO setups the per-device file is to be read
> independently for each ath12k instance, so without a common timestamp
> it is difficult to verify whether two snapshots are contemporaneous,
> making cross-device counter comparisons unreliable.
> 
> Patch 3 extends the existing per-radio section to report the center
> frequency on which each radio is currently operating.  This allows
> the reader to correlate throughput and error counters with the channel
> without having to cross-reference iw/nl80211 output separately.
> 
> ---
> Changes in v2:
> - Rebased the patch series on the latest tip of ath-next
> ---
> 
> Sreeramya Soratkal (3):
>    wifi: ath12k: Use runtime device count in dp stats display
>    wifi: ath12k: Add timestamp to dp stats display
>    wifi: ath12k: Show per-radio center freq in dp stats
> 
>   drivers/net/wireless/ath/ath12k/debugfs.c | 14 ++++++++++++--
>   1 file changed, 12 insertions(+), 2 deletions(-)
> 
> 
> base-commit: 972c4dd19cb92e03d75b66c426cfade07582a1ba

Reviewed-by: Aishwarya R <aishwarya.r@oss.qualcomm.com>


^ permalink raw reply

* Re: [PATCH wireless-next] wifi: cfg80211: fix regulatory.db async firmware request blocking __usermodehelper_disable()
From: Kavita Kavita @ 2026-06-29  6:33 UTC (permalink / raw)
  To: Carlos Llamas; +Cc: johannes, linux-wireless
In-Reply-To: <aj6QvBdf3hGzDQig@google.com>



On 6/26/2026 8:16 PM, Carlos Llamas wrote:
> On Thu, Jun 25, 2026 at 02:59:04PM +0530, Kavita Kavita wrote:
>> cfg80211 schedules an asynchronous request_firmware() work item for
>> regulatory.db via request_firmware_work_func(). When the direct
>> firmware load fails, _request_firmware() falls back to the sysfs
>> fallback path via firmware_fallback_sysfs(), which blocks indefinitely
>> in wait_for_completion_killable_timeout() waiting for userspace to
>> supply the firmware through the sysfs interface.
>>
>> While this work item is pending, any caller of
>> __usermodehelper_disable() will deadlock attempting to acquire the
>> usermodehelper rwsem for writing, since the sysfs firmware fallback
>> path holds the rwsem for reading and is blocked waiting for userspace
>> response that can never arrive while usermode helpers are being
>> disabled.
>>
>> Observed call traces where system suspend blocked due to regulatory.db
>> async firmware request:
>>
>>   kworker/6:3 (pid 194) holding usermodehelper rwsem read lock, blocked
>>    waiting for userspace firmware response:
>>      wait_for_completion_killable_timeout+0x48
>>      firmware_fallback_sysfs+0x270
>>      _request_firmware+0x790
>>      request_firmware_work_func+0x44
>>      process_one_work[jt]+0x59c
>>      worker_thread+0x260
>>      kthread+0x150
>>      ret_from_fork+0x10
>>
>>   Caller blocked in __usermodehelper_disable() during system suspend:
>>      rwsem_down_write_slowpath+0x768
>>      down_write+0x98
>>      __usermodehelper_disable+0x3c
>>      freeze_processes+0x18
>>      pm_suspend+0x320
>>      state_store+0x104
>>      kernfs_fop_write_iter[jt]+0x168
>>      vfs_write+0x270
>>      ksys_write+0x78
>>
>> Any service or kernel subsystem invoking __usermodehelper_disable() is
>> susceptible to this hang as long as the regulatory.db sysfs fallback
>> request remains outstanding.
>>
>> Fix this by replacing the unconditional uevent-based load with a
>> two-step approach. First, attempt a synchronous load via
>> request_firmware_direct() at init time. This is fast and
>> non-blocking, if the file is present in standard paths it is loaded
>> immediately with no delay. If not found, the load is deferred to
>> wiphy_regulatory_register() and triggered via
>> firmware_request_nowait_nowarn() only when the first non-self-managed
>> wiphy registers.
>>
>> For self-managed drivers (REGULATORY_WIPHY_SELF_MANAGED), the hang is
>> avoided because wiphy_regulatory_register() handles them separately
>> and the deferred load path is never reached, so the file load is not
>> attempted at all. For this case, regulatory information is obtained
>> from driver/firmware during wiphy registration. For non-self-managed
>> drivers, the file is required and is expected to be present. The
>> deferred load via firmware_request_nowait_nowarn() is triggered only
>> when the first such wiphy registers. This ensures the database is
>> loaded only when it is actually needed, avoiding the sysfs fallback
>> path on systems where the file is absent at init time.
>>
>> Also refactor regdb_fw_cb() into two functions: regdb_load() which
>> validates and stores the firmware image, and regdb_fw_cb_restore()
>> which is the async callback that calls restore_regulatory_settings()
>> to replay all pending regulatory requests (country hints from core,
>> user, driver and country IE) that arrived while the database was not
>> yet available.
>>
>> NOTE:
>> This issue was observed on Android platforms where regulatory.db is
>> absent.
>> Steps to reproduce on Android platforms:
>>   1. Power off the device completely.
>>   2. Connect the charger; the device enters off-mode charging.
>>   3. While in off-mode charging, short press the power key.
>>
>> Signed-off-by: Kavita Kavita <kavita.kavita@oss.qualcomm.com>
> 
> Are you missing a `Fixes:` tag for this?
> 


I have included Fixes Tag in v2 version. Thanks.



^ permalink raw reply

* [PATCH wireless-next v2] wifi: cfg80211: fix regulatory.db async firmware request blocking __usermodehelper_disable()
From: Kavita Kavita @ 2026-06-29  6:31 UTC (permalink / raw)
  To: johannes; +Cc: linux-wireless, kavita.kavita

cfg80211 schedules an asynchronous request_firmware() work item for
regulatory.db via request_firmware_work_func(). When the direct
firmware load fails, _request_firmware() falls back to the sysfs
fallback path via firmware_fallback_sysfs(), which blocks indefinitely
in wait_for_completion_killable_timeout() waiting for userspace to
supply the firmware through the sysfs interface.

While this work item is pending, any caller of
__usermodehelper_disable() will deadlock attempting to acquire the
usermodehelper rwsem for writing, since the sysfs firmware fallback
path holds the rwsem for reading and is blocked waiting for userspace
response that can never arrive while usermode helpers are being
disabled.

Observed call traces where system suspend blocked due to regulatory.db
async firmware request:

  kworker/6:3 (pid 194) holding usermodehelper rwsem read lock, blocked
   waiting for userspace firmware response:
     wait_for_completion_killable_timeout+0x48
     firmware_fallback_sysfs+0x270
     _request_firmware+0x790
     request_firmware_work_func+0x44
     process_one_work[jt]+0x59c
     worker_thread+0x260
     kthread+0x150
     ret_from_fork+0x10

  Caller blocked in __usermodehelper_disable() during system suspend:
     rwsem_down_write_slowpath+0x768
     down_write+0x98
     __usermodehelper_disable+0x3c
     freeze_processes+0x18
     pm_suspend+0x320
     state_store+0x104
     kernfs_fop_write_iter[jt]+0x168
     vfs_write+0x270
     ksys_write+0x78

Any service or kernel subsystem invoking __usermodehelper_disable() is
susceptible to this hang as long as the regulatory.db sysfs fallback
request remains outstanding.

Fix this by replacing the unconditional uevent-based load with a
two-step approach. First, attempt a synchronous load via
request_firmware_direct() at init time. This is fast and
non-blocking, if the file is present in standard paths it is loaded
immediately with no delay. If not found, the load is deferred to
wiphy_regulatory_register() and triggered via
firmware_request_nowait_nowarn() only when the first non-self-managed
wiphy registers.

For self-managed drivers (REGULATORY_WIPHY_SELF_MANAGED), the hang is
avoided because wiphy_regulatory_register() handles them separately
and the deferred load path is never reached, so the file load is not
attempted at all. For this case, regulatory information is obtained
from driver/firmware during wiphy registration. For non-self-managed
drivers, the file is required and is expected to be present. The
deferred load via firmware_request_nowait_nowarn() is triggered only
when the first such wiphy registers. This ensures the database is
loaded only when it is actually needed, avoiding the sysfs fallback
path on systems where the file is absent at init time.

Also refactor regdb_fw_cb() into two functions: regdb_load() which
validates and stores the firmware image, and regdb_fw_cb_restore()
which is the async callback that calls restore_regulatory_settings()
to replay all pending regulatory requests (country hints from core,
user, driver and country IE) that arrived while the database was not
yet available.

NOTE:
This issue was observed on Android platforms where regulatory.db is
absent.
Steps to reproduce on Android platforms:
  1. Power off the device completely.
  2. Connect the charger; the device enters off-mode charging.
  3. While in off-mode charging, short press the power key.

Fixes: 007f6c5e6eb4 ("cfg80211: support loading regulatory database as firmware file")
Signed-off-by: Kavita Kavita <kavita.kavita@oss.qualcomm.com>
---
Changes in v2:
 - Added Fixes Tag in commit.
---
 net/wireless/reg.c | 132 ++++++++++++++++++++++++++-------------------
 1 file changed, 78 insertions(+), 54 deletions(-)

diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index 1e8214d6b6d8..d7b864d32ba1 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -988,78 +988,98 @@ static int query_regdb(const char *alpha2)
 	return -ENODATA;
 }
 
-static void regdb_fw_cb(const struct firmware *fw, void *context)
+MODULE_FIRMWARE("regulatory.db");
+
+/* Validate and store firmware image as regdb. Used by all load paths. */
+static int regdb_load(const struct firmware *fw)
 {
-	int set_error = 0;
-	bool restore = true;
 	void *db;
 
-	if (!fw) {
-		pr_info("failed to load regulatory.db\n");
-		set_error = -ENODATA;
-	} else if (!valid_regdb(fw->data, fw->size)) {
+	ASSERT_RTNL();
+
+	if (!valid_regdb(fw->data, fw->size)) {
 		pr_info("loaded regulatory.db is malformed or signature is missing/invalid\n");
-		set_error = -EINVAL;
+		return -EINVAL;
 	}
 
+	db = kmemdup(fw->data, fw->size, GFP_KERNEL);
+	if (!db)
+		return -ENOMEM;
+
+	regdb = db;
+	return 0;
+}
+
+static void regdb_fw_cb_restore(const struct firmware *fw, void *context)
+{
+	int err;
+
 	rtnl_lock();
-	if (regdb && !IS_ERR(regdb)) {
-		/* negative case - a bug
-		 * positive case - can happen due to race in case of multiple cb's in
-		 * queue, due to usage of asynchronous callback
-		 *
-		 * Either case, just restore and free new db.
-		 */
-	} else if (set_error) {
-		regdb = ERR_PTR(set_error);
-	} else if (fw) {
-		db = kmemdup(fw->data, fw->size, GFP_KERNEL);
-		if (db) {
-			regdb = db;
-			restore = context && query_regdb(context);
-		} else {
-			restore = true;
-		}
-	}
 
-	if (restore)
+	/* Skip if a concurrent wiphy registration already loaded the db. */
+	if (regdb && !IS_ERR(regdb))
+		goto out_unlock;
+
+	/*
+	 * Replay all pending regulatory hints that arrived while the
+	 * database was not yet available, regardless of load outcome.
+	 */
+	if (!fw) {
+		pr_info("failed to load regulatory.db\n");
+		regdb = ERR_PTR(-ENODATA);
 		restore_regulatory_settings(true, false);
+		goto out_unlock;
+	}
 
-	rtnl_unlock();
+	err = regdb_load(fw);
+	if (err)
+		regdb = ERR_PTR(err);
 
-	kfree(context);
+	restore_regulatory_settings(true, false);
 
+out_unlock:
+	rtnl_unlock();
 	release_firmware(fw);
 }
 
-MODULE_FIRMWARE("regulatory.db");
-
 static int query_regdb_file(const char *alpha2)
 {
+	const struct firmware *fw;
 	int err;
 
 	ASSERT_RTNL();
 
-	if (regdb)
+	if (regdb && !IS_ERR(regdb))
 		return query_regdb(alpha2);
 
-	alpha2 = kmemdup(alpha2, 2, GFP_KERNEL);
-	if (!alpha2)
-		return -ENOMEM;
+	/*
+	 * Load failed or async udev load in progress. If -EINPROGRESS,
+	 * hints are preserved and replayed once the udev load completes.
+	 */
+	if (IS_ERR(regdb) && PTR_ERR(regdb) != -EINPROGRESS)
+		return PTR_ERR(regdb);
 
-	err = request_firmware_nowait(THIS_MODULE, true, "regulatory.db",
-				      &reg_fdev->dev, GFP_KERNEL,
-				      (void *)alpha2, regdb_fw_cb);
+	/*
+	 * Preserve the hint if the file is not found on direct paths;
+	 * an async udev load will be triggered on wiphy registration
+	 * and will replay all pending hints on completion.
+	 */
+	err = request_firmware_direct(&fw, "regulatory.db", &reg_fdev->dev);
 	if (err)
-		kfree(alpha2);
+		return 0;
+	err = regdb_load(fw);
+	release_firmware(fw);
+	if (err) {
+		regdb = ERR_PTR(err);
+		return err;
+	}
 
-	return err;
+	return query_regdb(alpha2);
 }
 
 int reg_reload_regdb(void)
 {
 	const struct firmware *fw;
-	void *db;
 	int err;
 	const struct ieee80211_regdomain *current_regdomain;
 	struct regulatory_request *request;
@@ -1068,21 +1088,14 @@ int reg_reload_regdb(void)
 	if (err)
 		return err;
 
-	if (!valid_regdb(fw->data, fw->size)) {
-		err = -ENODATA;
-		goto out;
-	}
-
-	db = kmemdup(fw->data, fw->size, GFP_KERNEL);
-	if (!db) {
-		err = -ENOMEM;
-		goto out;
-	}
-
 	rtnl_lock();
 	if (!IS_ERR_OR_NULL(regdb))
 		kfree(regdb);
-	regdb = db;
+	err = regdb_load(fw);
+	if (err) {
+		regdb = ERR_PTR(err);
+		goto out_unlock;
+	}
 
 	/* reset regulatory domain */
 	current_regdomain = get_cfg80211_regdom();
@@ -1103,7 +1116,6 @@ int reg_reload_regdb(void)
 
 out_unlock:
 	rtnl_unlock();
- out:
 	release_firmware(fw);
 	return err;
 }
@@ -4085,6 +4097,8 @@ void wiphy_regulatory_register(struct wiphy *wiphy)
 {
 	struct regulatory_request *lr = get_last_request();
 
+	ASSERT_RTNL();
+
 	/* self-managed devices ignore beacon hints and country IE */
 	if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) {
 		wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS |
@@ -4097,6 +4111,16 @@ void wiphy_regulatory_register(struct wiphy *wiphy)
 		 */
 		if (lr->initiator == NL80211_REGDOM_SET_BY_USER)
 			reg_call_notifier(wiphy, lr);
+	} else if (!regdb) {
+		/*
+		 * regulatory.db not yet loaded; trigger an async udev
+		 * request to load it when the first wiphy registers.
+		 */
+		if (!firmware_request_nowait_nowarn(THIS_MODULE,
+						    "regulatory.db",
+						    &reg_fdev->dev, GFP_KERNEL,
+						    NULL, regdb_fw_cb_restore))
+			regdb = ERR_PTR(-EINPROGRESS);
 	}
 
 	if (!reg_dev_ignore_cell_hint(wiphy))

base-commit: 972c4dd19cb92e03d75b66c426cfade07582a1ba
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH ath-next 0/3] wifi: ath12k: Fix memory allocation and improve reserved memory handling
From: Rameshkumar Sundaram @ 2026-06-29  6:30 UTC (permalink / raw)
  To: Aaradhana Sahu, ath12k; +Cc: linux-wireless
In-Reply-To: <20260611033314.2330-1-aaradhana.sahu@oss.qualcomm.com>

On 6/11/2026 9:03 AM, Aaradhana Sahu wrote:
> The current implementation relies on index-based device tree reserved
> memory lookup. Different platform variants define reserved memory
> regions in different orders and combinations, making the index-based
> lookup fragile across platforms.
> 
> Currently, the driver allocates the HOST_DDR_REGION_TYPE
> starting from the base of the Q6 region, which overlaps the Q6 read-only
> firmware section. This causes firmware assertions during QMI memory
> allocation, as the firmware expects writable regions to be placed after the
> read-only section.
> 
> Address these issues by:
> - switching to name-based reserved memory lookup to remove dependency on
>    device tree node ordering,
> - refactoring QMI memory assignment logic to reduce code duplication and
>    improve maintainability,
> - placing HOST_DDR and BDF regions after the Q6 read-only firmware section
>    using the bdf_addr_offset parameter.
> 
> This series ensure correct reserved memory handling across different
> platform variants, prevent overlapping memory allocations, and maintain
> backward compatibility by reusing existing reserved memory region names
> defined in device tree bindings.
> 
> Overview:
>    - Patch [1/3] switches to name-based reserved memory lookup for platform
>      compatibility.
>    - Patch [2/3] refactors QMI memory assignment to eliminate code
>      duplication.
>    - Patch [3/3] fixes memory allocation to place HOST_DDR and BDF regions
>      after Q6 read-only section.
> 
> Aaradhana Sahu (3):
>    wifi: ath12k: switch to name-based reserved memory lookup
>    wifi: ath12k: refactor QMI memory assignment
>    wifi: ath12k: allocate HOST_DDR and BDF regions after Q6 RO region
> 
>   drivers/net/wireless/ath/ath12k/ahb.c  |  18 +--
>   drivers/net/wireless/ath/ath12k/core.c |  25 ----
>   drivers/net/wireless/ath/ath12k/core.h |   2 -
>   drivers/net/wireless/ath/ath12k/qmi.c  | 168 ++++++++++---------------
>   4 files changed, 78 insertions(+), 135 deletions(-)
> 
> 
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>

^ permalink raw reply

* [PATCH ath-next] wifi: ath12k: fix rx_mpdu_start layout for QCC2072
From: Wei Zhang @ 2026-06-29  6:15 UTC (permalink / raw)
  To: jeff.johnson; +Cc: ath12k, linux-wireless, linux-kernel, wei.zhang

QCC2072's rx_mpdu_start TLV has a different field layout from QCN9274.
Reusing struct rx_mpdu_start_qcn9274 in hal_rx_desc_qcc2072 causes the
RX datapath to read the wrong offsets for info2, info4, pn[] and
phy_ppdu_id, producing corrupted sequence number, PN, ppdu_id and
mpdu-info flags (encrypted, fragment, addr2/addr4 valid).

Add a dedicated struct rx_mpdu_start_qcc2072 that matches the actual
hardware descriptor layout, and use it in hal_rx_desc_qcc2072.

Tested-on: QCC2072 hw1.0 PCI WLAN.COL.1.0.c2-00188-QCACOLSWPL_V1_TO_SILICONZ-1

Fixes: 28badc78142e ("wifi: ath12k: add HAL descriptor and ops for QCC2072")
Signed-off-by: Wei Zhang <wei.zhang@oss.qualcomm.com>
---
 .../wireless/ath/ath12k/wifi7/hal_rx_desc.h   | 34 ++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h b/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h
index 0d19a9cbb68c..6d69851e529d 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_rx_desc.h
@@ -140,6 +140,38 @@ struct rx_mpdu_start_qcn9274 {
 	__le32 res1;
 } __packed;
 
+struct rx_mpdu_start_qcc2072 {
+	__le32 info0;
+	__le32 info2;
+	__le32 reo_queue_desc_lo;
+	__le32 info1;
+	__le32 pn[4];
+	__le32 info4;
+	__le32 peer_meta_data;
+	__le16 ast_index;
+	__le16 sw_peer_id;
+	__le16 info3;
+	__le16 phy_ppdu_id;
+	__le32 info5;
+	__le32 info6;
+	__le16 frame_ctrl;
+	__le16 duration;
+	u8 addr1[ETH_ALEN];
+	u8 addr2[ETH_ALEN];
+	u8 addr3[ETH_ALEN];
+	__le16 seq_ctrl;
+	u8 addr4[ETH_ALEN];
+	__le16 qos_ctrl;
+	__le32 ht_ctrl;
+	__le32 info7;
+	__le32 res0;
+	__le32 res1;
+	__le32 res2;
+	__le32 info8;
+	__le32 res3;
+	__le32 res4;
+} __packed;
+
 #define QCN9274_MPDU_START_SELECT_MPDU_START_TAG			BIT(0)
 #define QCN9274_MPDU_START_SELECT_INFO0_REO_QUEUE_DESC_LO		BIT(1)
 #define QCN9274_MPDU_START_SELECT_INFO1_PN_31_0				BIT(2)
@@ -1492,7 +1524,7 @@ struct hal_rx_desc_qcc2072 {
 	struct rx_msdu_end_qcn9274 msdu_end;
 	u8 rx_padding0[RX_BE_PADDING0_BYTES];
 	__le32 mpdu_start_tag;
-	struct rx_mpdu_start_qcn9274 mpdu_start;
+	struct rx_mpdu_start_qcc2072 mpdu_start;
 	struct rx_pkt_hdr_tlv_qcc2072 pkt_hdr_tlv;
 	u8 msdu_payload[];
 };

base-commit: 1547a99cd8d8c1ab3e04dbd92b72b3b5f7cb85a9
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH ath-next v3] wifi: ath12k: add QMI capability negotiation for dynamic memory mode
From: Rameshkumar Sundaram @ 2026-06-29  5:42 UTC (permalink / raw)
  To: Aaradhana Sahu, ath12k; +Cc: linux-wireless
In-Reply-To: <20260626084327.2022076-1-aaradhana.sahu@oss.qualcomm.com>

On 6/26/2026 2:13 PM, Aaradhana Sahu wrote:
> On AHB platforms, firmware operates in two modes: fixed-memory mode where
> firmware uses hardcoded addresses for memory regions such as BDF and does
> not request HOST_DDR memory from the host, and dynamic-memory mode where
> firmware expects the host to provide memory addresses including HOST_DDR
> after the Q6 read-only region and relies on host allocation for all memory
> types.
> 
> Introduce QMI capability negotiation to support both modes. Add a new QMI
> PHY capability flag dynamic_ddr_support which is advertised by firmware to
> indicate it supports dynamic memory mode. When the host detects this
> capability, set the dynamic_mem_support flag in the host capability message
> to signal the host is ready to provide dynamic memory allocation. This
> triggers firmware to send the HOST_DDR memory request and use the
> host-provided address.
> 
> For backward compatibility, if firmware doesn't advertise
> dynamic_ddr_support, the firmware continues to operate in fixed-memory mode
> where firmware uses predefined addresses.
> 
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
> Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1
> 
> Signed-off-by: Aaradhana Sahu <aaradhana.sahu@oss.qualcomm.com>
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>

^ permalink raw reply

* Re: [PATCH ath-next] wifi: ath12k: change MAC buffer ring size to 4096
From: Rameshkumar Sundaram @ 2026-06-29  5:40 UTC (permalink / raw)
  To: Yingying Tang, ath12k; +Cc: linux-wireless, yintang
In-Reply-To: <20260610031358.2043716-1-yingying.tang@oss.qualcomm.com>

On 6/10/2026 8:43 AM, Yingying Tang wrote:
> For WCN7850, MAC buffer ring size is updated to 2048 in
> 955df16f2a4c3 ("wifi: ath12k: change MAC buffer ring size to 2048")
> to increase peak throughput.
> 
> But during the RX process, a phenomenon can still be observed where
> the throughput drops by about 30% from its peak value and then recovers,
> and this behavior repeats during RX.
> 
> After increasing MAC buffer ring size to 4096, the data rate drop has
> gone.
> 
> Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3
> 
> Signed-off-by: Yingying Tang <yingying.tang@oss.qualcomm.com>
> ---
>   drivers/net/wireless/ath/ath12k/dp.h | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h
> index f8cfc7bb29dd..6596089d4168 100644
> --- a/drivers/net/wireless/ath/ath12k/dp.h
> +++ b/drivers/net/wireless/ath/ath12k/dp.h
> @@ -205,7 +205,7 @@ struct ath12k_pdev_dp {
>   #define DP_REO_CMD_RING_SIZE		256
>   #define DP_REO_STATUS_RING_SIZE		2048
>   #define DP_RXDMA_BUF_RING_SIZE		4096
> -#define DP_RX_MAC_BUF_RING_SIZE		2048
> +#define DP_RX_MAC_BUF_RING_SIZE		4096
>   #define DP_RXDMA_REFILL_RING_SIZE	2048
>   #define DP_RXDMA_ERR_DST_RING_SIZE	1024
>   #define DP_RXDMA_MON_STATUS_RING_SIZE	1024

Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>

^ permalink raw reply

* Re: [PATCH ath-next 1/2] wifi: ath12k: fix dp_link_peer dangling references on AP vdev rollback
From: Rameshkumar Sundaram @ 2026-06-29  5:35 UTC (permalink / raw)
  To: Baochen Qiang, Jeff Johnson; +Cc: linux-wireless, ath12k
In-Reply-To: <20260617-ath12k-mlo-peer-delete-race-v1-1-ab3c4f455dfb@oss.qualcomm.com>

On 6/17/2026 2:58 PM, Baochen Qiang wrote:
> ath12k_mac_vdev_create() for an AP vdev creates the bss self-peer via
> ath12k_peer_create(), which finishes by calling
> ath12k_dp_link_peer_assign() to publish the dp_link_peer in the
> dp_hw->dp_peers[peerid_index] RCU table, in the dp_peer's
> link_peers[] array, and in the per-addr rhashtable.
> 
> If a step after ath12k_peer_create() fails the function jumps to
> err_peer_del, which open-codes a WMI peer_delete and waits for the
> unmap / delete_resp events. The wait_for_peer_delete_done() path
> relies on ath12k_dp_link_peer_unmap_event() freeing the dp_link_peer
> when the unmap arrives, but err_peer_del never calls
> ath12k_dp_link_peer_unassign() first. The published references in
> the dp_hw RCU table, dp_peer->link_peers[] and the rhashtable are
> left pointing at the dp_link_peer that unmap_event then frees,
> producing dangling pointers and use-after-free on subsequent
> lookups.
> 
> Replace the open-coded sequence with a call to ath12k_peer_delete(),
> which already does ath12k_dp_link_peer_unassign() before sending the
> WMI command. This drops the published references before the
> dp_link_peer is freed, in the same order as the normal teardown path
> in ath12k_mac_remove_link_interface().
> 
> Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3
> 
> Fixes: 5525f12fa671 ("wifi: ath12k: Attach and detach ath12k_dp_link_peer to ath12k_dp_peer")
> Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>



^ permalink raw reply

* Re: [PATCH ath-next 2/2] wifi: ath12k: fix MLO peer delete race
From: Rameshkumar Sundaram @ 2026-06-29  5:34 UTC (permalink / raw)
  To: Baochen Qiang, Jeff Johnson; +Cc: linux-wireless, ath12k
In-Reply-To: <20260617-ath12k-mlo-peer-delete-race-v1-2-ab3c4f455dfb@oss.qualcomm.com>

On 6/17/2026 2:58 PM, Baochen Qiang wrote:
> ath12k_peer_mlo_link_peers_delete() sends WMI peer_delete for every
> link before waiting for any peer_unmap / peer_delete_resp event. The
> shared per-radio completion ar->peer_delete_done could not
> disambiguate which peer a response was for: every call to
> ath12k_peer_delete_send() did
> reinit_completion(&ar->peer_delete_done), so when an event for the
> first link arrived between two sends it raised the count to 1 and
> the second send promptly cleared it; the wait for the second link
> then timed out with
> 
>      Timeout in receiving peer delete response
> 
> Replace the shared completion with a per-radio waiter list, with
> each pending ath12k_peer_delete() caller queueing an
> ath12k_peer_delete_wait carrying its (vdev_id, addr) and a private
> struct completion. ath12k_peer_delete_resp_event() matches the
> response against the list under ar->data_lock and signals the
> matching waiter.
> 
> Also correct the endian conversion in ath12k_peer_delete_resp_event()
> logging, and add the missing \n in some logging.
> 
> Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3
> 
> Fixes: 8e6f8bc28603 ("wifi: ath12k: Add MLO station state change handling")
> Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
> ---
>   drivers/net/wireless/ath/ath12k/core.c |   2 +-
>   drivers/net/wireless/ath/ath12k/core.h |   5 +-
>   drivers/net/wireless/ath/ath12k/mac.c  |   2 +-
>   drivers/net/wireless/ath/ath12k/peer.c | 130 ++++++++++++++++++++++++++-------
>   drivers/net/wireless/ath/ath12k/peer.h |  19 ++++-
>   drivers/net/wireless/ath/ath12k/wmi.c  |  16 ++--
>   6 files changed, 136 insertions(+), 38 deletions(-)
> 
> diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
> index 742d4fd1b598..f71650039292 100644
> --- a/drivers/net/wireless/ath/ath12k/core.c
> +++ b/drivers/net/wireless/ath/ath12k/core.c

{ ... }

> diff --git a/drivers/net/wireless/ath/ath12k/peer.h b/drivers/net/wireless/ath/ath12k/peer.h
> index 49d89796bc46..3dc720a3dc12 100644
> --- a/drivers/net/wireless/ath/ath12k/peer.h
> +++ b/drivers/net/wireless/ath/ath12k/peer.h
> @@ -9,13 +9,28 @@
>   
>   #include "dp_peer.h"
>   
> +struct ath12k_peer_delete_wait {
> +	struct list_head list;
> +	u32 vdev_id;
> +	u8 addr[ETH_ALEN];
> +	struct completion done;
> +};
> +
> +void ath12k_peer_delete_wait_register(struct ath12k *ar,
> +				      struct ath12k_peer_delete_wait *wait,
> +				      u32 vdev_id, const u8 *addr);
> +void ath12k_peer_delete_wait_unregister(struct ath12k *ar,
> +					struct ath12k_peer_delete_wait *wait);


The struct and functions appear to be used only within peer.c and are 
not referenced elsewhere. Could the function be made static in peer.c ?

> +void ath12k_peer_delete_resp_signal(struct ath12k *ar, u32 vdev_id, const u8 *addr);
> +void ath12k_peer_delete_wait_flush(struct ath12k *ar);


--
Ramesh

^ permalink raw reply

* Re: [PATCH v4] wifi: ath6kl: fix OOB access from firmware ADDBA window size
From: Vasanthakumar Thiagarajan @ 2026-06-29  4:57 UTC (permalink / raw)
  To: Tristan Madani, linux-wireless, linux-kernel
  Cc: Jeff Johnson, Johannes Berg, Tristan Madani
In-Reply-To: <178243229402.3653332.17576562809691611363@talencesecurity.com>



On 6/26/2026 5:34 AM, Tristan Madani wrote:
> aggr_recv_addba_req_evt() logs a debug message when the firmware-supplied
> win_sz is outside [AGGR_WIN_SZ_MIN, AGGR_WIN_SZ_MAX] but does not
> return. The out-of-range win_sz is then used in TID_WINDOW_SZ() to
> compute a kzalloc size and stored in rxtid->hold_q_sz, leading to
> zero-size or overflowed allocations and subsequent out-of-bounds access.
> 
> Clean up any previously active aggregation session for the TID first,
> then return early when win_sz is out of the valid range, instead of
> proceeding with a broken allocation size.
> 
> Fixes: bdcd81707973 ("Add ath6kl cleaned up driver")
> Cc: stable@vger.kernel.org
> Suggested-by: Vasanthakumar Thiagarajan <vasanthakumar.thiagarajan@oss.qualcomm.com>

You can remove my suggested-by as I only provided review comments on your patch.

> Signed-off-by: Tristan Madani <tristan@talencesecurity.com>

With the above comment addressed,

Reviewed-by: Vasanthakumar Thiagarajan <vasanthakumar.thiagarajan@oss.qualcomm.com>

^ permalink raw reply

* Re: rtw89: RTL8852BE P2P-device iftype and STA+P2P interface combination
From: Doug Brewer @ 2026-06-29  4:49 UTC (permalink / raw)
  To: Ping-Ke Shih; +Cc: linux-wireless@vger.kernel.org
In-Reply-To: <54e7877f76944bd29e57d6eafe5e3811@realtek.com>

On Mon, Jun 29, 2026 at 10:10 AM Ping-Ke Shih <pkshih@realtek.com> wrote:
>
> Doug Brewer wrote:
> > Hi,
> >
> > I'm experimenting with Wi-Fi Display (Miracast sink) concurrent with an
> > STA connection on an RTL8852BE (PCIe) using the mainline rtw89 driver
> > (kernel 6.18.37).
> >
> > iw phy reports:
> > Supported interface modes:
> >    * managed, AP, P2P-client, P2P-GO
> >    (no P2P-device)
> > interface combinations are not supported
> >
> > In practice this blocks the standard P2P flow: there is no P2P-device
> > iftype for a dedicated discovery context, and no advertised interface
> > combination for managed + P2P-client coexistence.
> >
> > My questions:
> > 1. is P2P-device iftype support planned for rtw89 on RTL885x? Is there a
> >     known technical blocker, or is it simply not yet implemented?
>
> We are planning to add P2P-device iftype. It needs to consider the cases of
> channel context, conditions of power save, and etc. It will take some time.
>
> I think it would be okay that you use STA interface to do P2P negotiation,
> and then create P2P-client or P2P-GO iftype then.
>
> > 2. would advertising a managed + P2P-client interface combination
> >     (single channel) be feasible on the current rtw89?
>
> This is a SCC which is supported.
>
> > 3. is MCC (#channels > 1) on the roadmap, or considered out of scope?
>
> Current support MCC as well. However, we are cooking new firmware to support
> hw_scan with two operation channels -- which doesn't matter if you don't need
> to do scan when MCC is operating.

Thanks for the suggestion. I tried using the STA interface for P2P
negotiation, and wanted to share what I found.

With the STA connected (2.4GHz ch11) and an active p2p_connect, a
wpa_supplicant -dd trace shows GO negotiation getting fairly far:

Peer's GO-NEG Request is received
send the GO-NEG Response on ch11, peer ACKs it (TX ack=1)
State goes GO_NEG -> CONNECT
then time out waiting for the GO-NEG Confirm, status=-1

I select ch11 as the P2P operating channel (same as STA, SCC), while
the peer's operating preference is 5GHz ch149. It looks like after we TX
the Response, the radio doesn't stay on ch11 to listen for the Confirm,
so the frame is missed -- presumably because the single radio is serving
the STA connection.

Aalso tested with the STA on 5GHz (ch149); the result is the same
GO-NEG Confirm timeout.

Is this the channel-context issue that P2P-device iftype will address?
And with the current driver, is there any way to keep the P2P listen
context on the operating channel during GO negotiation while STA is up?

(FWIW, passing an explicit freq= to p2p_connect is rejected with FAIL,
whether or not it matches the STA channel.)

Thanks,
Doug

^ permalink raw reply

* Re: [PATCH ath-next v2] wifi: ath12k: correct monitor destination ring size
From: Rameshkumar Sundaram @ 2026-06-29  4:18 UTC (permalink / raw)
  To: Aaradhana Sahu, ath12k; +Cc: linux-wireless
In-Reply-To: <20260616062342.4079796-1-aaradhana.sahu@oss.qualcomm.com>

On 6/16/2026 11:53 AM, Aaradhana Sahu wrote:
> The default memory profile configures rxdma_monitor_dst_ring_size as 8092,
> which is a typo. The intended value is 8192, consistent with all other ring
> sizes in the table being powers of two.
> 
> Correct the monitor destination ring size to 8192.
> 
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
> 
> Fixes: defae535dd63 ("wifi: ath12k: Add a table of parameters entries impacting memory consumption")
> Signed-off-by: Aaradhana Sahu <aaradhana.sahu@oss.qualcomm.com>
> ---
> v2:
>    -Added fixes tag.
> ---
>   drivers/net/wireless/ath/ath12k/core.c | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
> index 742d4fd1b598..b488bd8fea9a 100644
> --- a/drivers/net/wireless/ath/ath12k/core.c
> +++ b/drivers/net/wireless/ath/ath12k/core.c
> @@ -49,7 +49,7 @@ ath12k_mem_profile_based_param ath12k_mem_profile_based_param[] = {
>   		.dp_params = {
>   			.tx_comp_ring_size = 32768,
>   			.rxdma_monitor_buf_ring_size = 4096,
> -			.rxdma_monitor_dst_ring_size = 8092,
> +			.rxdma_monitor_dst_ring_size = 8192,
>   			.num_pool_tx_desc = 32768,
>   			.rx_desc_count = 12288,
>   		},
> 
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>

^ permalink raw reply

* Re: [PATCH ath-next v2 0/3] wifi: ath12k: Improve dp stats
From: Rameshkumar Sundaram @ 2026-06-29  4:16 UTC (permalink / raw)
  To: Sreeramya Soratkal, ath12k; +Cc: linux-wireless
In-Reply-To: <20260626085253.3927269-1-sreeramya.soratkal@oss.qualcomm.com>

On 6/26/2026 2:22 PM, Sreeramya Soratkal wrote:
> This short series improves the device_dp_stats debugfs interface in
> ath12k to produce more useful and accurate diagnostic output in MLO
> configurations.
> 
> Patch 1 fixes a latent correctness issue where the REO Rx and WBM
> release error tables were unconditionally printed for
> ATH12K_MAX_DEVICES columns showing zero-filled entries for device
> slots that have no hardware. Replacing the compile-time constant
> with the runtime device count keeps the output compact and correct.
> 
> Patch 2 prepends a ktime-based millisecond timestamp to the top of
> the dump. In MLO setups the per-device file is to be read
> independently for each ath12k instance, so without a common timestamp
> it is difficult to verify whether two snapshots are contemporaneous,
> making cross-device counter comparisons unreliable.
> 
> Patch 3 extends the existing per-radio section to report the center
> frequency on which each radio is currently operating.  This allows
> the reader to correlate throughput and error counters with the channel
> without having to cross-reference iw/nl80211 output separately.
> 
> ---
> Changes in v2:
> - Rebased the patch series on the latest tip of ath-next
> ---
> 
> Sreeramya Soratkal (3):
>    wifi: ath12k: Use runtime device count in dp stats display
>    wifi: ath12k: Add timestamp to dp stats display
>    wifi: ath12k: Show per-radio center freq in dp stats
> 
>   drivers/net/wireless/ath/ath12k/debugfs.c | 14 ++++++++++++--
>   1 file changed, 12 insertions(+), 2 deletions(-)
> 
> 
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>

^ permalink raw reply

* Re: [PATCH ath-next 0/2] wifi: ath12k: correct monitor-mode RX metadata handling
From: Rameshkumar Sundaram @ 2026-06-29  4:14 UTC (permalink / raw)
  To: Sushant Butta, ath12k; +Cc: linux-wireless
In-Reply-To: <20260609064856.547032-1-sushant.butta@oss.qualcomm.com>

On 6/9/2026 12:18 PM, Sushant Butta wrote:
> This series fixes monitor-mode meta data handling in ath12k and trims dead code:
> 
> Monitor mode delivers raw 802.11 frames should not set RX_FLAG_8023 flag,
> and populate peer/link metadata in ieee80211_rx_status for monitor MSDUs.
> 
> Sushant Butta (2):
>    wifi: ath12k: Skip setting RX_FLAG_8023 for Ethernet-II (DIX) frames
>      in monitor mode
>    wifi: ath12k: Skip peer link info update in rx_status for monitor
>      MSDUs
> 
>   drivers/net/wireless/ath/ath12k/dp_mon.c      | 70 +------------------
>   drivers/net/wireless/ath/ath12k/dp_mon.h      |  4 +-
>   .../net/wireless/ath/ath12k/wifi7/dp_mon.c    |  7 +-
>   3 files changed, 4 insertions(+), 77 deletions(-)
> 
> 

Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>

^ permalink raw reply

* Re: [patch 09/24] timekeeping: Add CLOCK_AUX support for ktime_get_snapshot_id()
From: Thomas Weißschuh @ 2026-06-29  3:55 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: LKML, David Woodhouse, Miroslav Lichvar, John Stultz,
	Stephen Boyd, Anna-Maria Behnsen, Frederic Weisbecker,
	Arthur Kiyanovski, Rodolfo Giometti, Vincent Donnefort,
	Marc Zyngier, Oliver Upton, kvmarm, Oliver Upton, Richard Cochran,
	netdev, Takashi Iwai, Miri Korenblit, Johannes Berg, Jacob Keller,
	Tony Nguyen, Saeed Mahameed, Peter Hilber, Michael S. Tsirkin,
	virtualization, linux-wireless, linux-sound
In-Reply-To: <87fr29ib4v.ffs@fw13>

On Fri, Jun 26, 2026 at 05:17:52PM +0200, Thomas Gleixner wrote:
> On Fri, Jun 26 2026 at 13:03, Thomas Weißschuh wrote:
> > On Fri, Jun 26, 2026 at 12:49:41PM +0200, Thomas Gleixner wrote:
> >> On Fri, Jun 26 2026 at 10:48, Thomas Weißschuh wrote:
> >> > On Tue, May 26, 2026 at 07:14:13PM +0200, Thomas Gleixner wrote:
> >> > (...)
> >> >
> >> >>  static inline void tk_update_aux_offs(struct timekeeper *tk, ktime_t offs)
> >> >> @@ -1218,6 +1223,12 @@ bool ktime_get_snapshot_id(struct system
> >> >>  		tkd = &tk_core;
> >> >>  		offs = &tk_core.timekeeper.offs_boot;
> >> >>  		break;
> >> >> +	case CLOCK_AUX ... CLOCK_AUX_LAST:
> >> >> +		tkd = aux_get_tk_data(clock_id);
> >> >> +		if (!tkd)
> >> >> +			return false;
> >> >> +		offs = &tkd->timekeeper.offs_aux;
> >> >> +		break;
> >> >
> >> > 'tkd' is also used to compute 'monoraw'. However 'tkr_raw' and 'tkr_mono'
> >> > are the same for auxilary clocks, so this will compute a wrong 'monoraw'.
> >> 
> >> AUX clocks are independent in the first place and the MONORAW part is
> >> the "MONORAW" related to the AUX clock itself. 
> >> 
> >> > Instead 'monoraw' should be computed based on 'tk_core'.
> >> > Which then also requires the sequence locking of 'tk_core'.
> >> 
> >> No. From a PTP and steering point of view you want the "raw" value which
> >> is related to the AUX clock itself and not the global one.
> >
> > Ack.
> >
> > However the kdocs call it 'CLOCK_MONOTONIC_RAW'. Can we clean this up?
> 
> Yes. Something like the below?

Looks good, thanks.
The corresponding structure definitions are a also affected, though.

> Thanks,
> 
>         tglx
> ---
> --- a/kernel/time/timekeeping.c
> +++ b/kernel/time/timekeeping.c
> @@ -1202,10 +1202,21 @@ static inline u64 tk_clock_read_snapshot
>  
>  /**
>   * ktime_get_snapshot_id -  Simultaneously snapshot a given clock ID with
> - *			    CLOCK_MONOTONIC_RAW and the underlying
> + *			    the corresponding monotonic raw the underlying
>   *			    clocksource counter value.
>   * @clock_id:		The clock ID to snapshot
>   * @systime_snapshot:	Pointer to struct receiving the system time snapshot
> + *
> + * For the system time keeping clocks (REALTIME, MONOTONIC and BOOTTIME) the
> + * monotonic raw clock is CLOCK_MONOTONIC_RAW. For AUX clocks this is the
> + * monotonic raw clock related to the AUX clock. These AUX clock related
> + * monotonic raw clocks have a strict linear offset to the system time
> + * CLOCK_MONOTONIC_RAW:
> + *
> + *	MONOTONIC_RAW(AUX$N) = CLOCK_MONOTONIC_RAW(system) + offset(AUX$N)
> + *
> + * The offset is established when a AUX clock is initialized, but it is
> + * currently not accessible.
>   */
>  void ktime_get_snapshot_id(clockid_t clock_id, struct system_time_snapshot *systime_snapshot)
>  {
> @@ -1512,6 +1523,9 @@ EXPORT_SYMBOL_GPL(ktime_real_to_base_clo
>   * @xtstamp:		Receives simultaneously captured system and device time
>   *
>   * Reads a timestamp from a device and correlates it to system time
> + *
> + * See documentation for ktime_get_snapshot_id() for information about the raw
> + * monotonic time stamp which is used here.
>   */
>  int get_device_system_crosststamp(int (*get_time_fn)
>  				  (ktime_t *device_time,
> 

^ permalink raw reply

* [PATCH wireless-next] wifi: rt2x00: Use flexible array for hw_mode_spec channels
From: Rosen Penev @ 2026-06-29  3:31 UTC (permalink / raw)
  To: linux-wireless
  Cc: Stanislaw Gruszka, Kees Cook, Gustavo A. R. Silva, open list,
	open list:KERNEL HARDENING (not covered by other areas):Keyword:b__counted_by(_le|_be|_ptr)?b

struct hw_mode_spec stores channel information that is allocated by the
individual rt2x00 drivers during hardware probing.  The channel info
array has the same lifetime as the hardware mode specification, but it
is currently allocated separately and then freed through a separate
pointer.

Make struct rt2x00_dev hold a pointer to the hardware mode
specification, store the channel info as a flexible array member, and
allocate both pieces together with kzalloc_flex().

Assisted-by: Codex:GPT-5.5
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
 .../net/wireless/ralink/rt2x00/rt2400pci.c    | 24 +++---
 .../net/wireless/ralink/rt2x00/rt2500pci.c    | 55 ++++++++------
 .../net/wireless/ralink/rt2x00/rt2500usb.c    | 55 ++++++++------
 .../net/wireless/ralink/rt2x00/rt2800lib.c    | 74 ++++++++++---------
 drivers/net/wireless/ralink/rt2x00/rt2x00.h   |  4 +-
 .../net/wireless/ralink/rt2x00/rt2x00config.c | 12 +--
 .../net/wireless/ralink/rt2x00/rt2x00dev.c    |  4 +-
 drivers/net/wireless/ralink/rt2x00/rt61pci.c  | 42 ++++++-----
 drivers/net/wireless/ralink/rt2x00/rt73usb.c  | 50 +++++++------
 9 files changed, 177 insertions(+), 143 deletions(-)

diff --git a/drivers/net/wireless/ralink/rt2x00/rt2400pci.c b/drivers/net/wireless/ralink/rt2x00/rt2400pci.c
index cac191304bf5..3fb74dcbee63 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2400pci.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2400pci.c
@@ -1559,7 +1559,7 @@ static const struct rf_channel rf_vals_b[] = {
 
 static int rt2400pci_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 {
-	struct hw_mode_spec *spec = &rt2x00dev->spec;
+	struct hw_mode_spec *spec;
 	struct channel_info *info;
 	u8 *tx_power;
 	unsigned int i;
@@ -1580,27 +1580,25 @@ static int rt2400pci_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 	/*
 	 * Initialize hw_mode information.
 	 */
-	spec->supported_bands = SUPPORT_BAND_2GHZ;
-	spec->supported_rates = SUPPORT_RATE_CCK;
+	spec = kzalloc_flex(*spec, channels_info, ARRAY_SIZE(rf_vals_b));
+	if (!spec)
+		return -ENOMEM;
 
 	spec->num_channels = ARRAY_SIZE(rf_vals_b);
 	spec->channels = rf_vals_b;
 
-	/*
-	 * Create channel information array
-	 */
-	info = kzalloc_objs(*info, spec->num_channels);
-	if (!info)
-		return -ENOMEM;
-
-	spec->channels_info = info;
+	spec->supported_bands = SUPPORT_BAND_2GHZ;
+	spec->supported_rates = SUPPORT_RATE_CCK;
 
 	tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_START);
 	for (i = 0; i < 14; i++) {
-		info[i].max_power = TXPOWER_FROM_DEV(MAX_TXPOWER);
-		info[i].default_power1 = TXPOWER_FROM_DEV(tx_power[i]);
+		info = &spec->channels_info[i];
+		info->max_power = TXPOWER_FROM_DEV(MAX_TXPOWER);
+		info->default_power1 = TXPOWER_FROM_DEV(tx_power[i]);
 	}
 
+	rt2x00dev->spec = spec;
+
 	return 0;
 }
 
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2500pci.c b/drivers/net/wireless/ralink/rt2x00/rt2500pci.c
index fc35b60e422c..4c85f401c091 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2500pci.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2500pci.c
@@ -1854,7 +1854,10 @@ static const struct rf_channel rf_vals_5222[] = {
 
 static int rt2500pci_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 {
-	struct hw_mode_spec *spec = &rt2x00dev->spec;
+	const struct rf_channel *channels;
+	unsigned int num_channels = 0;
+	unsigned int supported_bands;
+	struct hw_mode_spec *spec;
 	struct channel_info *info;
 	u8 *tx_power;
 	unsigned int i;
@@ -1880,52 +1883,58 @@ static int rt2500pci_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 	/*
 	 * Initialize hw_mode information.
 	 */
-	spec->supported_bands = SUPPORT_BAND_2GHZ;
-	spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM;
+	supported_bands = SUPPORT_BAND_2GHZ;
 
 	if (rt2x00_rf(rt2x00dev, RF2522)) {
-		spec->num_channels = ARRAY_SIZE(rf_vals_bg_2522);
-		spec->channels = rf_vals_bg_2522;
+		num_channels = ARRAY_SIZE(rf_vals_bg_2522);
+		channels = rf_vals_bg_2522;
 	} else if (rt2x00_rf(rt2x00dev, RF2523)) {
-		spec->num_channels = ARRAY_SIZE(rf_vals_bg_2523);
-		spec->channels = rf_vals_bg_2523;
+		num_channels = ARRAY_SIZE(rf_vals_bg_2523);
+		channels = rf_vals_bg_2523;
 	} else if (rt2x00_rf(rt2x00dev, RF2524)) {
-		spec->num_channels = ARRAY_SIZE(rf_vals_bg_2524);
-		spec->channels = rf_vals_bg_2524;
+		num_channels = ARRAY_SIZE(rf_vals_bg_2524);
+		channels = rf_vals_bg_2524;
 	} else if (rt2x00_rf(rt2x00dev, RF2525)) {
-		spec->num_channels = ARRAY_SIZE(rf_vals_bg_2525);
-		spec->channels = rf_vals_bg_2525;
+		num_channels = ARRAY_SIZE(rf_vals_bg_2525);
+		channels = rf_vals_bg_2525;
 	} else if (rt2x00_rf(rt2x00dev, RF2525E)) {
-		spec->num_channels = ARRAY_SIZE(rf_vals_bg_2525e);
-		spec->channels = rf_vals_bg_2525e;
+		num_channels = ARRAY_SIZE(rf_vals_bg_2525e);
+		channels = rf_vals_bg_2525e;
 	} else if (rt2x00_rf(rt2x00dev, RF5222)) {
-		spec->supported_bands |= SUPPORT_BAND_5GHZ;
-		spec->num_channels = ARRAY_SIZE(rf_vals_5222);
-		spec->channels = rf_vals_5222;
+		supported_bands |= SUPPORT_BAND_5GHZ;
+		num_channels = ARRAY_SIZE(rf_vals_5222);
+		channels = rf_vals_5222;
 	}
 
 	/*
 	 * Create channel information array
 	 */
-	info = kzalloc_objs(*info, spec->num_channels);
-	if (!info)
+	spec = kzalloc_flex(*spec, channels_info, num_channels);
+	if (!spec)
 		return -ENOMEM;
 
-	spec->channels_info = info;
+	spec->num_channels = num_channels;
+	spec->channels = channels;
+	spec->supported_bands = supported_bands;
+	spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM;
 
 	tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_START);
 	for (i = 0; i < 14; i++) {
-		info[i].max_power = MAX_TXPOWER;
-		info[i].default_power1 = TXPOWER_FROM_DEV(tx_power[i]);
+		info = &spec->channels_info[i];
+		info->max_power = MAX_TXPOWER;
+		info->default_power1 = TXPOWER_FROM_DEV(tx_power[i]);
 	}
 
 	if (spec->num_channels > 14) {
 		for (i = 14; i < spec->num_channels; i++) {
-			info[i].max_power = MAX_TXPOWER;
-			info[i].default_power1 = DEFAULT_TXPOWER;
+			info = &spec->channels_info[i];
+			info->max_power = MAX_TXPOWER;
+			info->default_power1 = DEFAULT_TXPOWER;
 		}
 	}
 
+	rt2x00dev->spec = spec;
+
 	return 0;
 }
 
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2500usb.c b/drivers/net/wireless/ralink/rt2x00/rt2500usb.c
index 58728df6666c..cbc1c1399cc6 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2500usb.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2500usb.c
@@ -1661,7 +1661,10 @@ static const struct rf_channel rf_vals_5222[] = {
 
 static int rt2500usb_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 {
-	struct hw_mode_spec *spec = &rt2x00dev->spec;
+	const struct rf_channel *channels;
+	unsigned int num_channels = 0;
+	unsigned int supported_bands;
+	struct hw_mode_spec *spec;
 	struct channel_info *info;
 	u8 *tx_power;
 	unsigned int i;
@@ -1693,52 +1696,58 @@ static int rt2500usb_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 	/*
 	 * Initialize hw_mode information.
 	 */
-	spec->supported_bands = SUPPORT_BAND_2GHZ;
-	spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM;
+	supported_bands = SUPPORT_BAND_2GHZ;
 
 	if (rt2x00_rf(rt2x00dev, RF2522)) {
-		spec->num_channels = ARRAY_SIZE(rf_vals_bg_2522);
-		spec->channels = rf_vals_bg_2522;
+		num_channels = ARRAY_SIZE(rf_vals_bg_2522);
+		channels = rf_vals_bg_2522;
 	} else if (rt2x00_rf(rt2x00dev, RF2523)) {
-		spec->num_channels = ARRAY_SIZE(rf_vals_bg_2523);
-		spec->channels = rf_vals_bg_2523;
+		num_channels = ARRAY_SIZE(rf_vals_bg_2523);
+		channels = rf_vals_bg_2523;
 	} else if (rt2x00_rf(rt2x00dev, RF2524)) {
-		spec->num_channels = ARRAY_SIZE(rf_vals_bg_2524);
-		spec->channels = rf_vals_bg_2524;
+		num_channels = ARRAY_SIZE(rf_vals_bg_2524);
+		channels = rf_vals_bg_2524;
 	} else if (rt2x00_rf(rt2x00dev, RF2525)) {
-		spec->num_channels = ARRAY_SIZE(rf_vals_bg_2525);
-		spec->channels = rf_vals_bg_2525;
+		num_channels = ARRAY_SIZE(rf_vals_bg_2525);
+		channels = rf_vals_bg_2525;
 	} else if (rt2x00_rf(rt2x00dev, RF2525E)) {
-		spec->num_channels = ARRAY_SIZE(rf_vals_bg_2525e);
-		spec->channels = rf_vals_bg_2525e;
+		num_channels = ARRAY_SIZE(rf_vals_bg_2525e);
+		channels = rf_vals_bg_2525e;
 	} else if (rt2x00_rf(rt2x00dev, RF5222)) {
-		spec->supported_bands |= SUPPORT_BAND_5GHZ;
-		spec->num_channels = ARRAY_SIZE(rf_vals_5222);
-		spec->channels = rf_vals_5222;
+		supported_bands |= SUPPORT_BAND_5GHZ;
+		num_channels = ARRAY_SIZE(rf_vals_5222);
+		channels = rf_vals_5222;
 	}
 
 	/*
 	 * Create channel information array
 	 */
-	info = kzalloc_objs(*info, spec->num_channels);
-	if (!info)
+	spec = kzalloc_flex(*spec, channels_info, num_channels);
+	if (!spec)
 		return -ENOMEM;
 
-	spec->channels_info = info;
+	spec->num_channels = num_channels;
+	spec->channels = channels;
+	spec->supported_bands = supported_bands;
+	spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM;
 
 	tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_START);
 	for (i = 0; i < 14; i++) {
-		info[i].max_power = MAX_TXPOWER;
-		info[i].default_power1 = TXPOWER_FROM_DEV(tx_power[i]);
+		info = &spec->channels_info[i];
+		info->max_power = MAX_TXPOWER;
+		info->default_power1 = TXPOWER_FROM_DEV(tx_power[i]);
 	}
 
 	if (spec->num_channels > 14) {
 		for (i = 14; i < spec->num_channels; i++) {
-			info[i].max_power = MAX_TXPOWER;
-			info[i].default_power1 = DEFAULT_TXPOWER;
+			info = &spec->channels_info[i];
+			info->max_power = MAX_TXPOWER;
+			info->default_power1 = DEFAULT_TXPOWER;
 		}
 	}
 
+	rt2x00dev->spec = spec;
+
 	return 0;
 }
 
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
index 97290899674e..7b7f6dcc9247 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2800lib.c
@@ -11778,7 +11778,9 @@ static const struct rf_channel rf_vals_7620[] = {
 
 static int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 {
-	struct hw_mode_spec *spec = &rt2x00dev->spec;
+	const struct rf_channel *channels;
+	unsigned int num_channels = 0;
+	struct hw_mode_spec *spec;
 	struct channel_info *info;
 	s8 *default_power1;
 	s8 *default_power2;
@@ -11840,19 +11842,17 @@ static int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 	/*
 	 * Initialize hw_mode information.
 	 */
-	spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM;
-
 	switch (rt2x00dev->chip.rf) {
 	case RF2720:
 	case RF2820:
-		spec->num_channels = 14;
-		spec->channels = rf_vals;
+		num_channels = 14;
+		channels = rf_vals;
 		break;
 
 	case RF2750:
 	case RF2850:
-		spec->num_channels = ARRAY_SIZE(rf_vals);
-		spec->channels = rf_vals;
+		num_channels = ARRAY_SIZE(rf_vals);
+		channels = rf_vals;
 		break;
 
 	case RF2020:
@@ -11870,44 +11870,52 @@ static int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 	case RF5372:
 	case RF5390:
 	case RF5392:
-		spec->num_channels = 14;
+		num_channels = 14;
 		if (rt2800_clk_is_20mhz(rt2x00dev))
-			spec->channels = rf_vals_3x_xtal20;
+			channels = rf_vals_3x_xtal20;
 		else
-			spec->channels = rf_vals_3x;
+			channels = rf_vals_3x;
 		break;
 
 	case RF7620:
-		spec->num_channels = ARRAY_SIZE(rf_vals_7620);
-		spec->channels = rf_vals_7620;
+		num_channels = ARRAY_SIZE(rf_vals_7620);
+		channels = rf_vals_7620;
 		break;
 
 	case RF3052:
 	case RF3053:
-		spec->num_channels = ARRAY_SIZE(rf_vals_3x);
-		spec->channels = rf_vals_3x;
+		num_channels = ARRAY_SIZE(rf_vals_3x);
+		channels = rf_vals_3x;
 		break;
 
 	case RF3853:
-		spec->num_channels = ARRAY_SIZE(rf_vals_3853);
-		spec->channels = rf_vals_3853;
+		num_channels = ARRAY_SIZE(rf_vals_3853);
+		channels = rf_vals_3853;
 		break;
 
 	case RF5592:
 		reg = rt2800_register_read(rt2x00dev, MAC_DEBUG_INDEX);
 		if (rt2x00_get_field32(reg, MAC_DEBUG_INDEX_XTAL)) {
-			spec->num_channels = ARRAY_SIZE(rf_vals_5592_xtal40);
-			spec->channels = rf_vals_5592_xtal40;
+			num_channels = ARRAY_SIZE(rf_vals_5592_xtal40);
+			channels = rf_vals_5592_xtal40;
 		} else {
-			spec->num_channels = ARRAY_SIZE(rf_vals_5592_xtal20);
-			spec->channels = rf_vals_5592_xtal20;
+			num_channels = ARRAY_SIZE(rf_vals_5592_xtal20);
+			channels = rf_vals_5592_xtal20;
 		}
 		break;
 	}
 
-	if (WARN_ON_ONCE(!spec->channels))
+	if (WARN_ON_ONCE(!channels))
 		return -ENODEV;
 
+	spec = kzalloc_flex(*spec, channels_info, num_channels);
+	if (!spec)
+		return -ENOMEM;
+
+	spec->num_channels = num_channels;
+	spec->channels = channels;
+	spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM;
+
 	spec->supported_bands = SUPPORT_BAND_2GHZ;
 	if (spec->num_channels > 14)
 		spec->supported_bands |= SUPPORT_BAND_5GHZ;
@@ -11959,19 +11967,13 @@ static int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 	/*
 	 * Create channel information and survey arrays
 	 */
-	info = kzalloc_objs(*info, spec->num_channels);
-	if (!info)
-		return -ENOMEM;
-
 	rt2x00dev->chan_survey =
 		kzalloc_objs(struct rt2x00_chan_survey, spec->num_channels);
 	if (!rt2x00dev->chan_survey) {
-		kfree(info);
+		kfree(spec);
 		return -ENOMEM;
 	}
 
-	spec->channels_info = info;
-
 	default_power1 = rt2800_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_BG1);
 	default_power2 = rt2800_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_BG2);
 
@@ -11982,10 +11984,11 @@ static int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 		default_power3 = NULL;
 
 	for (i = 0; i < 14; i++) {
-		info[i].default_power1 = default_power1[i];
-		info[i].default_power2 = default_power2[i];
+		info = &spec->channels_info[i];
+		info->default_power1 = default_power1[i];
+		info->default_power2 = default_power2[i];
 		if (default_power3)
-			info[i].default_power3 = default_power3[i];
+			info->default_power3 = default_power3[i];
 	}
 
 	if (spec->num_channels > 14) {
@@ -12002,10 +12005,11 @@ static int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 			default_power3 = NULL;
 
 		for (i = 14; i < spec->num_channels; i++) {
-			info[i].default_power1 = default_power1[i - 14];
-			info[i].default_power2 = default_power2[i - 14];
+			info = &spec->channels_info[i];
+			info->default_power1 = default_power1[i - 14];
+			info->default_power2 = default_power2[i - 14];
 			if (default_power3)
-				info[i].default_power3 = default_power3[i - 14];
+				info->default_power3 = default_power3[i - 14];
 		}
 	}
 
@@ -12033,6 +12037,8 @@ static int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 		break;
 	}
 
+	rt2x00dev->spec = spec;
+
 	return 0;
 }
 
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00.h b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
index 7d313e86d3f2..babc04210a65 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00.h
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
@@ -404,9 +404,9 @@ struct hw_mode_spec {
 
 	unsigned int num_channels;
 	const struct rf_channel *channels;
-	const struct channel_info *channels_info;
 
 	struct ieee80211_sta_ht_cap ht;
+	struct channel_info channels_info[] __counted_by(num_channels);
 };
 
 /*
@@ -802,7 +802,7 @@ struct rt2x00_dev {
 	/*
 	 * hw capability specifications.
 	 */
-	struct hw_mode_spec spec;
+	struct hw_mode_spec *spec;
 
 	/*
 	 * This is the default TX/RX antenna setup as indicated
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00config.c b/drivers/net/wireless/ralink/rt2x00/rt2x00config.c
index f895f560a185..b97cb67c4361 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00config.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00config.c
@@ -165,7 +165,7 @@ void rt2x00lib_config_antenna(struct rt2x00_dev *rt2x00dev,
 static u16 rt2x00ht_center_channel(struct rt2x00_dev *rt2x00dev,
 				   struct ieee80211_conf *conf)
 {
-	struct hw_mode_spec *spec = &rt2x00dev->spec;
+	struct hw_mode_spec *spec = rt2x00dev->spec;
 	int center_channel;
 	u16 i;
 
@@ -194,6 +194,7 @@ void rt2x00lib_config(struct rt2x00_dev *rt2x00dev,
 		      struct ieee80211_conf *conf,
 		      unsigned int ieee80211_flags)
 {
+	struct hw_mode_spec *spec = rt2x00dev->spec;
 	struct rt2x00lib_conf libconf;
 	u16 hw_value;
 	u16 autowake_timeout;
@@ -218,13 +219,8 @@ void rt2x00lib_config(struct rt2x00_dev *rt2x00dev,
 			hw_value = conf->chandef.chan->hw_value;
 		}
 
-		memcpy(&libconf.rf,
-		       &rt2x00dev->spec.channels[hw_value],
-		       sizeof(libconf.rf));
-
-		memcpy(&libconf.channel,
-		       &rt2x00dev->spec.channels_info[hw_value],
-		       sizeof(libconf.channel));
+		memcpy(&libconf.rf, &spec->channels[hw_value], sizeof(libconf.rf));
+		memcpy(&libconf.channel, &spec->channels_info[hw_value], sizeof(libconf.channel));
 
 		/* Used for VCO periodic calibration */
 		rt2x00dev->rf_channel = libconf.rf.channel;
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
index edc1f8eac747..3ffbc8aabbae 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00dev.c
@@ -1097,7 +1097,7 @@ static void rt2x00lib_remove_hw(struct rt2x00_dev *rt2x00dev)
 		rt2x00dev->hw->wiphy->bands[NL80211_BAND_5GHZ] = NULL;
 	}
 
-	kfree(rt2x00dev->spec.channels_info);
+	kfree(rt2x00dev->spec);
 	kfree(rt2x00dev->chan_survey);
 }
 
@@ -1116,7 +1116,7 @@ static const struct ieee80211_tpt_blink rt2x00_tpt_blink[] = {
 
 static int rt2x00lib_probe_hw(struct rt2x00_dev *rt2x00dev)
 {
-	struct hw_mode_spec *spec = &rt2x00dev->spec;
+	struct hw_mode_spec *spec = rt2x00dev->spec;
 	int status;
 
 	if (test_bit(DEVICE_STATE_REGISTERED_HW, &rt2x00dev->flags))
diff --git a/drivers/net/wireless/ralink/rt2x00/rt61pci.c b/drivers/net/wireless/ralink/rt2x00/rt61pci.c
index 79e1fd0a1fbd..479825f8bba9 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt61pci.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt61pci.c
@@ -2654,7 +2654,10 @@ static const struct rf_channel rf_vals_seq[] = {
 
 static int rt61pci_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 {
-	struct hw_mode_spec *spec = &rt2x00dev->spec;
+	const struct rf_channel *channels;
+	unsigned int num_channels = 0;
+	unsigned int supported_bands;
+	struct hw_mode_spec *spec;
 	struct channel_info *info;
 	u8 *tx_power;
 	unsigned int i;
@@ -2693,46 +2696,51 @@ static int rt61pci_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 	/*
 	 * Initialize hw_mode information.
 	 */
-	spec->supported_bands = SUPPORT_BAND_2GHZ;
-	spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM;
+	supported_bands = SUPPORT_BAND_2GHZ;
 
 	if (!rt2x00_has_cap_rf_sequence(rt2x00dev)) {
-		spec->num_channels = 14;
-		spec->channels = rf_vals_noseq;
+		num_channels = 14;
+		channels = rf_vals_noseq;
 	} else {
-		spec->num_channels = 14;
-		spec->channels = rf_vals_seq;
+		num_channels = 14;
+		channels = rf_vals_seq;
 	}
 
 	if (rt2x00_rf(rt2x00dev, RF5225) || rt2x00_rf(rt2x00dev, RF5325)) {
-		spec->supported_bands |= SUPPORT_BAND_5GHZ;
-		spec->num_channels = ARRAY_SIZE(rf_vals_seq);
+		supported_bands |= SUPPORT_BAND_5GHZ;
+		num_channels = ARRAY_SIZE(rf_vals_seq);
 	}
 
 	/*
 	 * Create channel information array
 	 */
-	info = kzalloc_objs(*info, spec->num_channels);
-	if (!info)
+	spec = kzalloc_flex(*spec, channels_info, num_channels);
+	if (!spec)
 		return -ENOMEM;
 
-	spec->channels_info = info;
+	spec->num_channels = num_channels;
+	spec->channels = channels;
+	spec->supported_bands = supported_bands;
+	spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM;
 
 	tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_G_START);
 	for (i = 0; i < 14; i++) {
-		info[i].max_power = MAX_TXPOWER;
-		info[i].default_power1 = TXPOWER_FROM_DEV(tx_power[i]);
+		info = &spec->channels_info[i];
+		info->max_power = MAX_TXPOWER;
+		info->default_power1 = TXPOWER_FROM_DEV(tx_power[i]);
 	}
 
 	if (spec->num_channels > 14) {
 		tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_A_START);
 		for (i = 14; i < spec->num_channels; i++) {
-			info[i].max_power = MAX_TXPOWER;
-			info[i].default_power1 =
-					TXPOWER_FROM_DEV(tx_power[i - 14]);
+			info = &spec->channels_info[i];
+			info->max_power = MAX_TXPOWER;
+			info->default_power1 = TXPOWER_FROM_DEV(tx_power[i - 14]);
 		}
 	}
 
+	rt2x00dev->spec = spec;
+
 	return 0;
 }
 
diff --git a/drivers/net/wireless/ralink/rt2x00/rt73usb.c b/drivers/net/wireless/ralink/rt2x00/rt73usb.c
index d6b7174d087a..17338378329b 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt73usb.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt73usb.c
@@ -2088,7 +2088,10 @@ static const struct rf_channel rf_vals_5225_2527[] = {
 
 static int rt73usb_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 {
-	struct hw_mode_spec *spec = &rt2x00dev->spec;
+	const struct rf_channel *channels;
+	unsigned int num_channels = 0;
+	unsigned int supported_bands;
+	struct hw_mode_spec *spec;
 	struct channel_info *info;
 	u8 *tx_power;
 	unsigned int i;
@@ -2114,49 +2117,54 @@ static int rt73usb_probe_hw_mode(struct rt2x00_dev *rt2x00dev)
 	/*
 	 * Initialize hw_mode information.
 	 */
-	spec->supported_bands = SUPPORT_BAND_2GHZ;
-	spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM;
+	supported_bands = SUPPORT_BAND_2GHZ;
 
 	if (rt2x00_rf(rt2x00dev, RF2528)) {
-		spec->num_channels = ARRAY_SIZE(rf_vals_bg_2528);
-		spec->channels = rf_vals_bg_2528;
+		num_channels = ARRAY_SIZE(rf_vals_bg_2528);
+		channels = rf_vals_bg_2528;
 	} else if (rt2x00_rf(rt2x00dev, RF5226)) {
-		spec->supported_bands |= SUPPORT_BAND_5GHZ;
-		spec->num_channels = ARRAY_SIZE(rf_vals_5226);
-		spec->channels = rf_vals_5226;
+		supported_bands |= SUPPORT_BAND_5GHZ;
+		num_channels = ARRAY_SIZE(rf_vals_5226);
+		channels = rf_vals_5226;
 	} else if (rt2x00_rf(rt2x00dev, RF2527)) {
-		spec->num_channels = 14;
-		spec->channels = rf_vals_5225_2527;
+		num_channels = 14;
+		channels = rf_vals_5225_2527;
 	} else if (rt2x00_rf(rt2x00dev, RF5225)) {
-		spec->supported_bands |= SUPPORT_BAND_5GHZ;
-		spec->num_channels = ARRAY_SIZE(rf_vals_5225_2527);
-		spec->channels = rf_vals_5225_2527;
+		supported_bands |= SUPPORT_BAND_5GHZ;
+		num_channels = ARRAY_SIZE(rf_vals_5225_2527);
+		channels = rf_vals_5225_2527;
 	}
 
 	/*
 	 * Create channel information array
 	 */
-	info = kzalloc_objs(*info, spec->num_channels);
-	if (!info)
+	spec = kzalloc_flex(*spec, channels_info, num_channels);
+	if (!spec)
 		return -ENOMEM;
 
-	spec->channels_info = info;
+	spec->num_channels = num_channels;
+	spec->channels = channels;
+	spec->supported_bands = supported_bands;
+	spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM;
 
 	tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_G_START);
 	for (i = 0; i < 14; i++) {
-		info[i].max_power = MAX_TXPOWER;
-		info[i].default_power1 = TXPOWER_FROM_DEV(tx_power[i]);
+		info = &spec->channels_info[i];
+		info->max_power = MAX_TXPOWER;
+		info->default_power1 = TXPOWER_FROM_DEV(tx_power[i]);
 	}
 
 	if (spec->num_channels > 14) {
 		tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_A_START);
 		for (i = 14; i < spec->num_channels; i++) {
-			info[i].max_power = MAX_TXPOWER;
-			info[i].default_power1 =
-					TXPOWER_FROM_DEV(tx_power[i - 14]);
+			info = &spec->channels_info[i];
+			info->max_power = MAX_TXPOWER;
+			info->default_power1 = TXPOWER_FROM_DEV(tx_power[i - 14]);
 		}
 	}
 
+	rt2x00dev->spec = spec;
+
 	return 0;
 }
 
-- 
2.54.0


^ permalink raw reply related

* [PATCH wireless-next] bcma: host_soc: convert to devres and drop remove callback
From: Rosen Penev @ 2026-06-29  3:18 UTC (permalink / raw)
  To: linux-wireless; +Cc: Rafał Miłecki, open list

Replace of_iomap with devm_platform_ioremap_resource to let
the device resource framework handle unmapping. Use
devm_add_action_or_reset to schedule bcma_bus_unregister on
device removal, allowing the explicit remove callback to be
dropped entirely.

Assisted-by: opencode:big-pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
 drivers/bcma/host_soc.c | 42 ++++++++++++-----------------------------
 1 file changed, 12 insertions(+), 30 deletions(-)

diff --git a/drivers/bcma/host_soc.c b/drivers/bcma/host_soc.c
index 20b1816c570b..411cfbf53f7b 100644
--- a/drivers/bcma/host_soc.c
+++ b/drivers/bcma/host_soc.c
@@ -188,22 +188,20 @@ int __init bcma_host_soc_register(struct bcma_soc *soc)
 
 int __init bcma_host_soc_init(struct bcma_soc *soc)
 {
-	struct bcma_bus *bus = &soc->bus;
-	int err;
+	return bcma_bus_early_register(&soc->bus);
+}
 
-	/* Scan bus and initialize it */
-	err = bcma_bus_early_register(bus);
-	if (err)
-		iounmap(bus->mmio);
+#ifdef CONFIG_OF
+static void bcma_host_soc_unregister(void *data)
+{
+	struct bcma_bus *bus = data;
 
-	return err;
+	bcma_bus_unregister(bus);
 }
 
-#ifdef CONFIG_OF
 static int bcma_host_soc_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
-	struct device_node *np = dev->of_node;
 	struct bcma_bus *bus;
 	int err;
 
@@ -215,9 +213,9 @@ static int bcma_host_soc_probe(struct platform_device *pdev)
 	bus->dev = dev;
 
 	/* Map MMIO */
-	bus->mmio = of_iomap(np, 0);
-	if (!bus->mmio)
-		return -ENOMEM;
+	bus->mmio = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(bus->mmio))
+		return PTR_ERR(bus->mmio);
 
 	/* Host specific */
 	bus->hosttype = BCMA_HOSTTYPE_SOC;
@@ -229,24 +227,9 @@ static int bcma_host_soc_probe(struct platform_device *pdev)
 	/* Register */
 	err = bcma_bus_register(bus);
 	if (err)
-		goto err_unmap_mmio;
-
-	platform_set_drvdata(pdev, bus);
-
-	return err;
-
-err_unmap_mmio:
-	iounmap(bus->mmio);
-	return err;
-}
-
-static void bcma_host_soc_remove(struct platform_device *pdev)
-{
-	struct bcma_bus *bus = platform_get_drvdata(pdev);
+		return err;
 
-	bcma_bus_unregister(bus);
-	iounmap(bus->mmio);
-	platform_set_drvdata(pdev, NULL);
+	return devm_add_action_or_reset(dev, bcma_host_soc_unregister, bus);
 }
 
 static const struct of_device_id bcma_host_soc_of_match[] = {
@@ -261,7 +244,6 @@ static struct platform_driver bcma_host_soc_driver = {
 		.of_match_table = bcma_host_soc_of_match,
 	},
 	.probe		= bcma_host_soc_probe,
-	.remove		= bcma_host_soc_remove,
 };
 
 int __init bcma_host_soc_register_driver(void)
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH ath-next v3] wifi: ath12k: add QMI capability negotiation for dynamic memory mode
From: Baochen Qiang @ 2026-06-29  2:24 UTC (permalink / raw)
  To: Aaradhana Sahu, ath12k; +Cc: linux-wireless
In-Reply-To: <20260626084327.2022076-1-aaradhana.sahu@oss.qualcomm.com>



On 6/26/2026 4:43 PM, Aaradhana Sahu wrote:
> On AHB platforms, firmware operates in two modes: fixed-memory mode where
> firmware uses hardcoded addresses for memory regions such as BDF and does
> not request HOST_DDR memory from the host, and dynamic-memory mode where
> firmware expects the host to provide memory addresses including HOST_DDR
> after the Q6 read-only region and relies on host allocation for all memory
> types.
> 
> Introduce QMI capability negotiation to support both modes. Add a new QMI
> PHY capability flag dynamic_ddr_support which is advertised by firmware to
> indicate it supports dynamic memory mode. When the host detects this
> capability, set the dynamic_mem_support flag in the host capability message
> to signal the host is ready to provide dynamic memory allocation. This
> triggers firmware to send the HOST_DDR memory request and use the
> host-provided address.
> 
> For backward compatibility, if firmware doesn't advertise
> dynamic_ddr_support, the firmware continues to operate in fixed-memory mode
> where firmware uses predefined addresses.
> 
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
> Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1
> 
> Signed-off-by: Aaradhana Sahu <aaradhana.sahu@oss.qualcomm.com>

Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox